使用GCC内联ARM汇编跳转到一个地址

6
我想在不修改LR寄存器的情况下,使用ARM汇编将分支(branch)到特定地址(不是标签),因此我选择使用B而不是BL或BX。我希望在GCC内联汇编中完成这项任务。 这里是文档链接,以下是我尝试过的内容:
#define JMP(addr) \
    __asm__("b %0" \
            : /*output*/ \
            : /*input*/ \
            "r" (addr) \
           );

这是一个C宏,可以用地址调用。运行时出现以下错误:

error: undefined reference to 'r3'

该错误是由使用"r"引起的。我稍微研究了一下,发现这可能是gcc 4.9.*版本中的一个bug。

顺便说一下,我正在使用Android/Linux Gcc 4.9交叉编译器,在OSX上运行。 此外,我不知道是否应该在Rm上加载一些东西。

干杯!

编辑: 我将宏更改为此内容,但仍然得到undefined reference to r3 and r4

#define JMP(addr) \
    __asm__("LDR r5,=%0\n\t" \
            "LDR r4,[r5]\n\t"\
            "ADD r4,#1\n\t" \
            "B r4" \
            : /*output*/ \
            : /*input*/ \
            "r" (addr) \
            : /*clobbered*/ \
            "r4" ,"r5" \
           );

说明: 将变量的地址加载到r5,然后将该地址的值加载到r4。然后将LSB加1(根据ARM规范需要?)。最后跳转到该地址。

LDR 伪指令可以将寄存器加载为以下内容之一:32 位常量值或地址。因此,在您的情况下,编译器认为 rX 是一个符号。 - auselen
2个回答

6

由于你正在使用C进行编程,所以你可以完全不使用汇编语言,只需将指向要跳转的地址的变量强制转换为函数指针并立即调用它:

((void (*)(void)) addr)();

这段代码的解释如下:你将addr强制转换为指向不带参数的函数的指针(第二个void表示没有参数,第一个void表示该函数不返回任何值),最后两个括号是对该函数的实际调用。如果您想了解更多关于此方法的信息,请谷歌搜索“C函数指针”。

但如果这对你不起作用,你仍然想采用汇编方法,那么你要找的指令实际上是BX(不确定为什么你最初排除了它,但我猜测名称“分支和交换”让你认为寄存器参数与程序计数器交换(从而改变),但事实并非如此,但在开始时也让我感到困惑)。

简单回顾一下指令:

  • B会将标签作为参数。实际上,跳转将编码为相对于当前位置的偏移量,这告诉处理器向前或向后跳转那么多条指令(通常编译器、汇编器或链接器会为您计算该偏移量)。在执行期间,控制流将简单地转移到该位置,而不更改任何寄存器(这也意味着链接寄存器LR将保持不变)。
  • BX R0将从寄存器中获取绝对地址(因此不是偏移量),在这种情况下为R0,并在该地址处继续执行。这也是在不更改任何其他寄存器的情况下完成的。
  • BLBLX R0是前两个指令的相应对应项。它们将在控制流方面执行相同的操作,但除此之外还会将当前程序计数器保存在链接寄存器LR中。如果调用的函数后来要返回,则需要这样做。

因此,实质上,您需要做的就是:

asm("BX %0" : : "r"(addr));

指示编译器确保变量addr在寄存器(r)中,您承诺仅读取不更改。此外,在返回时,您不会更改(破坏)任何其他寄存器。
有关内联汇编约束的更多信息,请参见此处https://gcc.gnu.org/onlinedocs/gcc/Constraints.html
为了帮助您了解为什么还有其他解决方案浮动,这里是一些关于ARM体系结构的内容:
  • 程序计数器PC对于许多指令来说都可以作为常规寄存器R15进行访问。它只是该精确寄存器编号的别名。
  • 这意味着几乎所有算术和寄存器修改指令都可以将其作为参数。但是,对于其中的许多指令,高度不推荐使用。
  • 如果您正在查看编译为ARM代码的程序的反汇编,任何函数都将以以下三种方式之一结束:
    • BX LR完全符合您的要求:获取链接寄存器的内容(LRR14的别名),并跳转到该位置,有效地返回给调用者
    • POP {R4-R11, PC}恢复调用者保存的寄存器并跳回到调用者。这几乎肯定会在函数开头有PUSH {R4-R11, LR}的对应项:您正在将链接寄存器(返回地址)推入堆栈,但是最终将其存储回程序计数器,有效地返回给调用者
    • B分支到不同的函数,如果该函数以尾递归结束,并将其返回给原始调用者。
希望能帮到您, 马丁

4

你不能跳转到一个寄存器,只能跳转到一个标签。如果你想要跳转到一个寄存器中的地址,你需要将其移动到PC寄存器(r15)中。

#define JMP(addr) \
    __asm__("mov pc,%0" \
            : /*output*/ \
            : /*input*/ \
            "r" (addr) \
           );

我几分钟前写了类似的东西,但还没有测试过!希望它能正常工作! 顺便提一下,由于这是gcc asm的PC版本,所以在你的示例中应该使用%0进行交换! - Paschalis
4
我认为它们不应该被交换。对于 ARM,GCC 使用标准的 ARM 操作数排序。你可能在想 x86 目标,因为 GCC 在那里使用了非标准的顺序。 - Ross Ridge

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接