【问题标题】:Writing ARM machine instructions and executing them from C (On the Raspberry pi)编写 ARM 机器指令并从 C 中执行它们(在 Raspberry pi 上)
【发布时间】:2013-03-27 00:07:08
【问题描述】:

我正在尝试用 C 和 ARM 编写一些自我修改的代码。我之前向similar question 询问过关于 MIPS 的问题,现在我正在尝试将项目移植到 ARM。
我的系统 := 树莓派上的 Raspbian、ARMv6、GCC

有几件事我不确定:

  • ARM 是否需要 D 缓存回写/I 缓存无效(缓存刷新)?如果是这样,我们该怎么做?

我也尝试了一个例子

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>

int inc(int x){ //increments x
    uint16_t *ret = malloc(2 * sizeof(uint16_t));

    *(ret + 0) = 0x3001; //add r0 1 := r0 += 1
    *(ret + 1) = 0x4770; //bx lr    := jump back to inc()

    int(*f)(int) = (int (*)(int)) ret;
    return (*f)(x);
}

int main(){
    printf("%d",inc(6)); //expect '7' to be printed
exit(0);}

但我不断收到分段错误。我正在使用 aapcs 调用约定,据我所知,这是所有 ARM 的默认设置

如果有人指出我正确的方向,我将不胜感激

额外问题(意思是,它实际上不必回答,但知道会很酷)-我“来自 MIPS 背景”,ARM 程序员在没有 0 寄存器的情况下怎么办? (例如,硬编码为值 0 的寄存器)

【问题讨论】:

  • 这不是 arm 或 mips 问题,而是 linux 和保护问题。使用 mmap 分配内存空间并启用它以执行等,然后对其进行修改,然后您可以分支到它而不会出现保护错误。有很多SO问题已经回答了“Linux上的自我修改代码”问题,对于mips,arm,x86,ppc等问题的答案相同。
  • 至于你的额外问题:在我知道的所有处理器中(大约 10 个),ARM 拥有最扩展的方式将常量输入到处理器的数据路径中,所以为什么只使用 0。所以我的反问是:为什么 MIPS 会浪费一个寄存器的指令编码空间来将 0 放入数据路径?当然是一个反问。
  • 您至少需要int tmp = 0; /* Drain WB */ __asm volatile(" mcr p15, 0, %0, c7, c10, 4\n" : "=r" (tmp)); 确保在跳转之前将写入提交到 RAM。
  • @C.E.Sally 对于奖金奖金,MOV Rd,#1 将适用于任何 8 位值。好的部分是,如果您需要设置额外的位,您可以在将原始 8 位旋转到寄存器中的任意位置时将它们“或”。使用MVN,您可以设置 8 个零位。
  • 此外,您还需要分配可执行内存。通过 malloc 分配的内存可能不能保证可执行,因为它应该是数据而不是代码。尝试使用 mmap 分配内存并设置可执行标志。这是我为 x86 写的答案(但也应该在 ARM 下工作)stackoverflow.com/a/14826557/268025 编辑:殴打 - 刚刚看到 dwelch 的评论。

标签: c assembly arm jit raspberry-pi


【解决方案1】:

有几个问题。

  1. 您不会刷新 D-Cache 和 I-Cache,因此大多数时候 I-Cache 会从 L2 获取陈旧数据。在 linux 下有一个 libc/sys-call 可以为你做这件事。使用 __clear_cache(begin, end) 或 _builtin_clear_cache(begin, end)。
  2. 您输出 Thumb-Code,但您不关心如何调用您的代码。解决这个问题的最简单方法是使用一些 asm 代码来执行实际的 blx 调用,并将地址与 1 进行或运算,因为该位设置处理器运行的模式。因为你的 malloc 地址将始终与字边界,让你在 arm 模式下调用 thumb-code。

【讨论】:

  • 我从文档中得到的印象是拇指指令是半字对齐的,所以如果我的内存块是字对齐的,我会很好吗?我也不明白你设置拇指模式是什么意思。再次从文档中,我得到的印象是,对于 ARMv6-M,我将始终处于拇指模式
  • 至于缓存一致性,我都试过了,但不幸的是仍然有段错误,所以我认为我在其他地方还有另一个问题
【解决方案2】:

blogs.arm.com 上阅读Caches and Self-Modifying Code。文章还包括一个示例,它可以满足您的描述。

从文章中回答你的问题

... ARM 架构通常被认为是修改后的哈佛架构。 ...

纯哈佛架构的典型缺点是指令存储器不能从与数据存储器相同的地址空间直接访问,尽管此限制不适用于 ARM。 在ARM上,你可以将指令写入内存,但是由于D-cache和I-cache不一致,新写入的指令可能会被I-cache的现有内容屏蔽,导致处理器执行旧的(或可能无效的)说明。

请参阅 __clear_cache 了解如何使缓存无效。

如果您打算将指令推送到内存中,我希望您也了解 ARM/Thumb 指令集。

【讨论】:

  • 我知道有 ARM 和 thumb 但现在我很难理解我的设置是否可以在 arm 中运行。文档似乎在说 ARMv6-M,只有拇指可用
【解决方案3】:

好的,所以这适用于我的树莓派。

#include <stdio.h>
#include <sys/mman.h>
#include <stdint.h>
#include <stdlib.h>

int inc(int x){ //increments x

    uint32_t *ret = mmap(NULL,
            2 * sizeof(uint32_t),  // Space for 16 instructions. (More than enough.)
            PROT_READ | PROT_WRITE | PROT_EXEC,
            MAP_PRIVATE | MAP_ANONYMOUS,
            -1,0);
    if (ret == MAP_FAILED) {
        printf("Could not mmap a memory buffer with the proper permissions.\n");
        return -1;
    }

    *(ret + 0) = 0xE2800001; //add r0 r0 #1 := r0 += 1
    *(ret + 1) = 0xE12FFF1E; //bx lr        := jump back to inc()

    __clear_cache((char*) ret, (char*) (ret+2));

    int(*f)(int) = (int (*)(int)) ret;
    return (*f)(x);
}

int main(){
    printf("%d\n",inc(6)); //expect '7' to be printed
exit(0);}

【讨论】:

  • 非常感谢您提供此示例代码。这很有帮助。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-12-02
  • 2012-09-14
  • 2015-03-21
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多