Arm Assembly 在子程序中推送 POP 链接寄存器和 pc 并在子程序中调用子程序的正确方法

See*_*und 1 assembly stack arm subroutine

用于 ARM 组装

我一直在我的子程序中执行以下操作:

SubRoutine:
  PUSH {r1,r2,lr}
  //code that changes r1 and r2
  POP {r1,r2,lr}
  bx lr
Run Code Online (Sandbox Code Playgroud)

这是从子程序返回并继续执行主函数中代码的正确方法吗?我看到周围的人正在做以下事情:

SubRoutine:
  PUSH {r1,r2,lr}
  //code that changes r1 and r2
  POP {r1,r2,pc}
  bx lr
Run Code Online (Sandbox Code Playgroud)

但我不知道为什么你在推 LR 时会 POP 电脑。哪个是正确的方法,为什么?

此外,如果您在子例程中调用子例程,请执行以下操作:

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
Run Code Online (Sandbox Code Playgroud)

或者你是这样做的:

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
Run Code Online (Sandbox Code Playgroud)

art*_*ise 5

您应该注意三种情况。

  1. 叶子: void foo(void) {};
  2. 尾部调用: int foo(void) { return bar(); };
  3. 中间的: int foo(void) { int i; i = bar() + 4; return i; };

有很多方法可以实现这些调用。下面是一些示例,并不是在 ARM 汇编程序中实现结语和序言的唯一方法。


叶函数

许多函数是叶子类型,不需要保存lr. 您只需使用bx lr来返回。例如,

SubRoutine:
  PUSH {r1,r2}
  //code that changes r1 and r2
  POP {r1,r2}
  bx lr
Run Code Online (Sandbox Code Playgroud)

此外,通常使用 r1 和 r2 传递参数,并且子程序可以自由使用/销毁它们。ARM 调用约定 如果您从汇编程序中调用“C”函数,就会出现这种情况。所以通常情况下,没有人会保存 r1 和 r2,但由于它是汇编程序,您可以随心所欲地做任何事情(即使这是一个坏主意)。所以实际上这个例子只有bx lr在你遵循标准的情况下。


尾呼叫

如果你的函数是一个叶子,除了对另一个函数的最终调用,你可以使用以下快捷方式,

Sub_w_tail:
// Save callee-saved regs (for whatever calling convention you need)
// Leave LR as is.
// ... do stuff
B  tail_call
Run Code Online (Sandbox Code Playgroud)

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'
Run Code Online (Sandbox Code Playgroud)

旧调用约定的一些详细信息在ARM 链接和帧寄存器问题中。您可以使用此约定。在 ARM 汇编程序中执行结语序言的方法有很多种。


最后一个相当复杂;或者至少编码很乏味。让编译器决定使用哪些寄存器以及将哪些放在堆栈上会好得多。但是,通常在编写汇编程序时您只需要知道如何编写第一个(LEAF函数)。仅在汇编程序中编写从高级语言调用的优化子例程的效率最高。了解它们如何工作以理解编译后的代码很有用。您还应该考虑内联汇编器,这样您就不必处理这些细微差别。