我想使用ARM assembly
将branch
转到特定地址(而不是标签),而无需修改LR
寄存器。因此,我选择B
而不是BL
或BX
。我希望在GCC inline asm
中完成此操作。
这是文档,这是我尝试的:
#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,=%0nt"
"LDR r4,[r5]nt"
"ADD r4,#1nt"
"B r4"
: /*output*/
: /*input*/
"r" (addr)
: /*clobbered*/
"r4" ,"r5"
);
解释:将变量的地址加载到R5,然后将该地址的值加载到R4。然后将1添加到LSB(ARM规范需要EMM?)。最后分支到该地址。
由于您在C中编程,您可以完全使用普通的C方法,而无需任何组件:只需施放可变,该变量是要寻址要跳到的指针,到函数指针并立即调用:
:((void (*)(void)) addr)();
只是对这个括号丛林的解释:使用此代码,您将addr
施加到指针(由Star (*)
表示)到一个不采用参数的函数(第二个void
表示没有参数),并且也没有返回任何内容(第一个void
)。最后,最后两个括号是该功能的实际调用。Google用于" C功能指针"以获取有关该方法的更多信息。
但是,如果这对您不起作用,并且您仍然想采用汇编方法,那么您正在寻找的指令实际上是BX
(不确定您最初为什么排除了这一点。但是我可以猜测命名"分支和交换"误导您相信寄存器参数已与程序计数器交换(然后更改),这是不是的情况,但在开始时也使我感到困惑)。
为此,简单地回顾说明:
-
B
将以标签作为参数。实际上,跳跃将被编码为与当前位置的偏移,该位置告诉处理器将许多指令转发或向后跳跃(通常是编译器,汇编器或链接器,将为您计算该偏移)。在执行过程中,控制流将简单地转移到该位置,而无需更改任何寄存器(这意味着链接寄存器LR
也将保持不变) -
BX R0
将从寄存器中获取绝对(so 不是偏移量)地址,在这种情况下为R0
,并在该地址继续执行。这也可以完成,没有更改任何其他寄存器。 -
BL
和BLX R0
是前两个指令的相应对应物。他们将做同样的事情控制流,但除此之外,除此之外,将当前程序计数器保存在链接寄存器LR
中。如果应稍后返回。
本质上,您需要做的是:
asm("BX %0" : : "r"(addr));
指示编译器确保变量addr
在寄存器中(r
),您承诺只读取而不要更改。最重要的是,返回后,您不会更改(clobbered)其他任何寄存器。
请参阅此处https://gcc.gnu.org/onlinedocs/gcc/constraints.html有关内联装配约束的更多信息。
帮助您了解为什么还有其他解决方案在这里流动,这里有一些有关手臂建筑的事情:
- 程序计数器
PC
用于许多指令,可作为常规寄存器R15
访问。这只是该精确寄存器号码的别名。 - 这意味着几乎所有算术和注册更改指令都可以将其作为论点。但是,对于其中许多人来说,这是高度弃用的。
- 如果您正在查看编译为ARM代码的程序的拆卸,则任何功能都将以三件事之一结束:
-
BX LR
可以做您想做的事情:获取链接寄存器的内容(LR
是R14
的别名),然后跳到该位置,有效地返回呼叫者 -
POP {R4-R11, PC}
还原呼叫者保存的寄存器并跳回呼叫者。几乎可以肯定的是,在功能开始时,这将有PUSH {R4-R11, LR}
的对应物:您正在将链接寄存器的内容(返回地址)推入堆栈中,但将其存储回程序计数器中,有效地返回了呼叫者,最终 -
B
分支到另一个功能,如果此功能以尾声结束并将其延长到该功能以返回原始呼叫者。
-
希望有帮助,马丁
您不能分支到寄存器,只能分支到标签上。如果您想跳到寄存器中的地址,则需要将其移至PC寄存器(R15)。
#define JMP(addr)
__asm__("mov pc,%0"
: /*output*/
: /*input*/
"r" (addr)
);