用于ARM程序集
我在子例程中一直在执行以下操作:
SubRoutine:
PUSH {r1,r2,lr}
//code that changes r1 and r2
POP {r1,r2,lr}
bx lr
这是从子程序返回并继续使用主函数中的代码的正确方法吗?我已经看到人们正在做以下事情:
SubRoutine:
PUSH {r1,r2,lr}
//code that changes r1 and r2
POP {r1,r2,pc}
bx lr
但是我不知道为什么当你推动LR时会弹出PC。这是正确的方法和原因?
此外,如果在子例程中调用子例程,是否执行以下操作:
SubRoutine:
PUSH {r1,r2,lr}
//code that changes r1 and r2
PUSH {lr}
bl AnotherRoutine (where bx lr will be used to return from it)
POP {lr}
POP {r1,r2,pc}
bx lr
或者你是这样做的:
SubRoutine:
PUSH {r1,r2,lr}
//code that changes r1 and r2
PUSH {lr}
bl AnotherRoutine(where bx lr will be used to return from it)
POP {pc}
POP {r1,r2,pc}
bx lr
答案 0 :(得分:3)
您应该注意三种情况。
void foo(void) {};
int foo(void) { return bar(); };
int foo(void) { int i; i = bar() + 4; return i; };
实现这些调用的方法有很多种。下面是一些示例,并不是在ARM汇编程序中实现结语和序言的唯一方法。
LEAF功能
许多函数是 leaf 类型,不需要保存lr
。您只需使用bx lr
即可返回。例如,
SubRoutine:
PUSH {r1,r2}
//code that changes r1 and r2
POP {r1,r2}
bx lr
此外,通常使用r1和r2传递参数, SubRoutine 可以自由使用/销毁它们。 ARM calling conventions 这将是如果你打电话给C'汇编程序的功能。所以通常情况下,没有人会保存r1和r2但是因为它是汇编程序,你可以随心所欲地做任何事情(即使这是一个坏主意)。因此,如果您遵循标准,实际上该示例仅为bx lr
。
留言
如果您的函数是 leaf ,除了最后调用另一个函数外,您可以使用以下捷径,
Sub_w_tail:
// Save callee-saved regs (for whatever calling convention you need)
// Leave LR as is.
// ... do stuff
B tail_call
调用者将LR
保存到Sub_w_tail
,您只需直接跳转到tail_call
即可返回原来的来电者。
中级功能
这是最复杂的。这是一个可能的序列,
SubRoutine:
PUSH {r1,r2,lr}
//code that changes r1 and r2
bl AnotherRoutine (where bx lr will be used to return from it)
// more code
POP {r1,r2,pc} // returns to caller of 'SubRoutine'
较早的调用约定的一些细节在ARM Link and frame registers问题中。您可以使用此约定。在ARM汇编程序中执行结尾和序言的方法有很多种。
最后一个很复杂;或至少编码繁琐。让编译器确定要使用哪些寄存器以及放置在堆栈上的内容要好得多。但是,通常您只需要知道在编写汇编程序时如何编写第一个( LEAF 函数)。仅对编译器中更高级语言调用的优化子例程进行编码是最有效的。知道它们如何工作以理解编译代码是很有用的。您还应该考虑内联汇编程序,这样您就不必处理这些细微差别。