使用 GCC 内联 ARM asm 分支到一个地址

Branch to an address using GCC inline ARM asm

我想 branch 使用 ARM assembly 到特定地址(不是标签),而不修改 LR 寄存器。所以我选择 B 而不是 BLBX。 我希望在 GCC inline asm.

中完成

Here 是文档,这是我试过的:

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

它是一个 C 宏,可以用 address 调用。当我 运行 它时,我收到以下错误:

error: undefined reference to 'r3'

错误是因为使用了"r"。我仔细研究了一下,发现它可能是 gcc 4.9.* 版本上的错误。

顺便说一句,我在 OSX 上使用 Android/Linux Gcc 4.9 cross compiler。 另外,我不知道我是否应该在 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 规范要求的 emm?)。最后分支到那个地址。

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

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

由于您是用 C 编程的,所以您可以只使用普通的 C 方法,根本不需要任何汇编:只需将包含指向您要跳转到的地址的指针的变量转换为函数指针并调用马上:

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

只是对这个括号丛林的解释: 使用此代码,您将 addr 转换为一个指针(由星号 (*) 表示)到一个不带参数的函数(第二个 void 表示没有参数)并且 return没什么(第一个void)。最后两个括号是该函数的实际调用。 Google for "C function pointer" 了解有关该方法的更多信息。

但是如果这对您不起作用并且您仍然想采用汇编方法,那么您正在寻找的指令实际上是 BX(不确定您最初为什么排除它。但我可以猜想 "Branch and Exchange" 这个名字误导你相信寄存器参数与程序计数器交换(并因此改变),这是 NOT 的情况,但它一开始我也很困惑。

为此,只需简单回顾一下说明:

  • B 会将标签作为参数。实际上,跳转将被编码为相对于当前位置的偏移量,它告诉处理器向前或向后跳转那么多指令(通常编译器、汇编器或 linker 会负责为您计算该偏移量)。在执行期间,控制流将简单地转移到那个位置 而不会 更改任何寄存器(这也意味着 link 寄存器 LR 将保持不变)
  • BX R0 将从寄存器中获取绝对地址(因此 不是 偏移量)地址,在本例中为 R0,并在该地址继续执行.这也是在 没有 更改任何其他寄存器的情况下完成的。
  • BLBLX R0 是前两条指令的对应部分。他们将明智地控制流做同样的事情,但最重要的是将当前程序计数器保存在 link 寄存器 LR 中。如果被调用的函数稍后应该 return,则需要这样做。

所以本质上,您需要做的是:

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

指示编译器确保变量 addr 位于寄存器 (r) 中,您承诺只读而不会更改。最重要的是,在 return 之后,您不会更改(破坏)任何其他寄存器。

看这里 https://gcc.gnu.org/onlinedocs/gcc/Constraints.html 有关内联汇编约束的更多信息。

为了帮助您理解为什么还有其他解决方案,这里有一些关于 ARM 架构的信息:

  • 程序计数器 PC 适用于许多可作为常规寄存器访问的指令 R15。它只是该确切寄存器编号的别名。
  • 这意味着几乎所有算术和寄存器更改指令都可以将其作为参数。但是,对于其中许多人来说,它已被高度弃用。
  • 如果您正在查看编译为 ARM 代码的程序的反汇编,任何函数都将以以下三种情况之一结束:
    • BX LR 这正是你想做的:获取 link 寄存器的内容(LRR14 的别名)并跳转到那个位置,有效地 returning 给呼叫者
    • POP {R4-R11, PC}恢复调用者保存的寄存器并跳回调用者。这几乎肯定会在函数的开头有 PUSH {R4-R11, LR} 的对应物:您正在将 link 寄存器的内容(return 地址)压入堆栈,但将其存储回程序计数器有效地 returning 到最后的调用者
    • B 分支到另一个函数,如果此函数以尾调用结束并将其留给该函数 return 给原始调用者。

希望对您有所帮助, 马丁