Function 非叶函数和叶函数尾声中LR和PC指令的使用

Function 非叶函数和叶函数尾声中LR和PC指令的使用,function,assembly,arm,gdb,thumb,Function,Assembly,Arm,Gdb,Thumb,我试图通过azeria-labs.com上的指南学习汇编 我有一个关于在非叶函数和叶函数的尾声中使用LR寄存器和PC寄存器的问题 在下面的代码片段中,它们显示了这些函数在结尾部分的不同之处 如果我用C写一个程序,看看GDB,它总是用“pop{r11,pc}作为非叶函数,用“pop{r11}”;叶函数的“bx lr”。谁能告诉我这是为什么 当我在叶函数中时。例如,如果我使用“bx lr”或“pop pc”返回到父函数,是否会产生差异 /* An epilogue of a leaf functio

我试图通过azeria-labs.com上的指南学习汇编

我有一个关于在非叶函数和叶函数的尾声中使用LR寄存器和PC寄存器的问题

在下面的代码片段中,它们显示了这些函数在结尾部分的不同之处

如果我用C写一个程序,看看GDB,它总是用“pop{r11,pc}作为非叶函数,用“pop{r11}”;叶函数的“bx lr”。谁能告诉我这是为什么

当我在叶函数中时。例如,如果我使用“bx lr”或“pop pc”返回到父函数,是否会产生差异

/* An epilogue of a leaf function */ 
pop    {r11}        
bx     lr           

/* An epilogue of a non-leaf function */
pop    {r11, pc}

在叶函数中,没有其他函数调用会修改链接寄存器
lr

对于非叶函数,必须保留
lr
,方法是将其推到堆栈中(函数前面未显示的地方)

非叶函数的结束语可以重写:

pop    {r11, lr}
bx     lr
然而,这又是一条指令,因此效率稍低

我正在努力学习组装

我有一个关于在非叶函数和叶函数的尾声中使用LR寄存器和PC寄存器的问题

这是汇编程序之美和痛苦的一部分。任何东西的使用都没有规则。由您决定需要什么。请参阅:因为它可能会有所帮助

…对于非叶函数,它总是使用
pop{r11,pc}
,对于叶函数,它总是使用
pop{r11};bx lr
。有人能告诉我这是为什么吗

“C”编译器是不同的。它有称为ABI的规则。最新版本称为“AAPCSfor”或“ATPCS for”。这些规则的存在使得不同的编译器可以相互调用函数。注意1 Ie,工具可以运行。您可以在汇编程序中使用此“规则”,也可以忽略它。例如,如果您的目标是与编译器代码,您需要遵循ABI规则

一些规则说明了需要在堆栈上推送什么以及如何使用寄存器。叶子不同的“原因”是它更高效。写入寄存器
lr
比内存快得多(推送到堆栈)。当它是一个非叶函数时,那里的函数调用将破坏现有的
lr
,之后您将不会返回正确的位置,因此lr被推到堆栈中以使事情正常工作

当我在叶函数中时。例如,如果我使用“bx lr”或“pop pc”返回到父函数,是否会产生差异

bx lr比pop pc快,因为一个使用内存,另一个不使用。功能上它们是一样的。然而,使用汇编程序的一个常见原因是速度更快。从功能上来说,你会得到相同的执行路径,只是需要更长的时间;多少取决于内存系统对于带有TCM的Cortex-M,uld几乎可以忽略不计,对于Cortex-a CPU,uld非常高


ARM使用登记器传递参数,因为这比在堆栈上推参数更快。请考虑此代码,

int foo(int a, int b, int c) {return a+b+c;}
int bar(int a) { return foo(a, 1, 2);}
这是一个可能的ARM代码注释2

这不是任何ARM编译器都会做的事情。惯例是使用R0、R1和R2传递参数。因为这样更快,实际生成的代码更少。但任何一种方法都可以实现同样的效果。也许

  foo:
   add r0,r0,r1  ; a = a + b
   add r0,r0,r2  ; a = a + c
   bx  lr

  bar:
   push lr     ; a = a from caller of bar.
   mov r1, #1  ; b = 1
   mov r2, #2  ; c = 2
   bl foo
   pop pc
lr
与参数有些相似。您可以将参数推到堆栈上,或将其保留在寄存器中。您可以将
lr
放在堆栈上,然后稍后将其弹出,或将其保留在堆栈中。不应低估的是,当代码使用寄存器作为对象时,它可以变得快多少移动东西通常是汇编代码不是最佳的标志。移动越多,
mov
push
pop
你的代码越慢

因此,一般来说,ABI中有很多想法使其尽可能快。较旧的APC比较新的AAPC稍慢,但它们都能工作

注1:如果打开优化,您将注意到
静态
和非静态函数之间的差异。这是因为编译器可能会忽略ABI以加快速度。静态函数可以被其他编译器调用,并且不需要互操作


注2:事实上,CPU设计人员对ABI考虑了很多,并考虑了寄存器的数量。寄存器太多,操作码会很大。寄存器太少,会使用大量内存而不是寄存器。

pop
只有在您之前推送某个东西时才起作用。在叶函数中,您通常不会破坏
lr
因此它通常不会被推送,因此不能被弹出,这就是为什么使用
bx-lr
的原因。非叶函数需要保存
lr
,因为调用另一个函数会破坏它,所以它被推送并且可以被弹出。也就是说,没有任何东西禁止将
lr
推送到叶函数中的堆栈,这样您就可以使用相同的prologue/epilogue.leaf或not如果函数没有修改一个必须按照调用约定保留的寄存器,那么它就不需要在堆栈上推送该寄存器。lr就是其中之一。请注意,编译器也没有推送r4、r5、r6…lr更敏感,因为只要lr用作ret,无论调用约定是什么urn地址(如果需要使用bl/blx)如果函数修改了它,您需要保留它。为了符合当前的标准,您的代码/编译器或至少显示的代码没有。lr可以是用于保持叶函数堆栈对齐的额外寄存器,而不是因为它必须这样做。听起来您看到的是始终创建帧指针的未优化代码,其他e它不必弹出任何内容(除非它保存/恢复了一些保留通话的注册表)
  foo:
   add r0,r0,r1  ; a = a + b
   add r0,r0,r2  ; a = a + c
   bx  lr

  bar:
   push lr     ; a = a from caller of bar.
   mov r1, #1  ; b = 1
   mov r2, #2  ; c = 2
   bl foo
   pop pc