Cha*_*Kim 0 assembly arm abi eabi arm64
这是来自 linux 源代码 arch/arm64/kernel/head.S 显示内核启动。代码首先调用preserve_boot_args和下一个调用el2_setup使用bl(分支和链接)。我也展示了程序preserve_boot_args。
SYM_CODE_START(primary_entry)
bl preserve_boot_args
bl el2_setup // Drop to EL1, w0=cpu_boot_mode
adrp x23, __PHYS_OFFSET
and x23, x23, MIN_KIMG_ALIGN - 1 // KASLR offset, defaults to 0
bl set_cpu_boot_mode_flag
bl __create_page_tables
/*
* The following calls CPU setup code, see arch/arm64/mm/proc.S for
* details.
* On return, the CPU will be ready for the MMU to be turned on and
* the TCR will have been set.
*/
bl __cpu_setup // initialise processor
b __primary_switch
SYM_CODE_END(primary_entry)
SYM_CODE_START_LOCAL(preserve_boot_args)
mov x21, x0 // x21=FDT
adr_l x0, boot_args // record the contents of
stp x21, x1, [x0] // x0 .. x3 at kernel entry
stp x2, x3, [x0, #16]
dmb sy // needed before dc ivac with
// MMU off
mov x1, #0x20 // 4 x 8 bytes
b __inval_dcache_area // tail call
SYM_CODE_END(preserve_boot_args)
Run Code Online (Sandbox Code Playgroud)
据我了解,bl用于调用过程(在过程之后,返回到 lr - 链接寄存器中保存的地址,x30)并且b只是去不返回的标记地址。但是在preserve_boot_args上面的程序中,就在最后,有一条b __inval_dcache_area指令直接去__inval_dcache_area而不返回。那么它如何返回到原始代码(在哪里bl el2_setup)?程序如何结束?SYM_CODE_END 的定义是这样的:
#define SYM_END(name, sym_type) \
.type name sym_type ASM_NL \
.size name, .-name
#endif
Run Code Online (Sandbox Code Playgroud)
我无法理解这段代码是如何让它返回到lr. 我们不应该做类似的事情mv pc, lr吗?
这看起来像是调用优化——有时也称为尾调用优化,这有助于减少递归的堆栈深度——但在一般情况下也很有用。
这种优化的工作方式是,调用者 A 调用一个函数 B,后者调用另一个函数 C。如果 B 在调用 C 后要直接返回 A,那么 B 可以改为跳转到 C!由于没有更聪明,C 返回到它的调用者,它看起来是 A。 通过这样做,B 不需要堆栈帧并且不必保存链接寄存器——它只是将它的返回地址传递给 C。
这种优化跳过了 C 到 B 的正常返回,使 C 直接返回到 A。 这种转换仅在某些情况下启用(即正确):
从硬件的角度来看,当使用优化时(在 B 中编码,然后调用 B),就好像 B 和 C 合并了:如果你愿意,函数 A 调用“BC”。动态地,有一个bl(A->BC) 和一个ret(BC->A) — 很好地平衡,这有利于硬件分支预测器的调用堆栈处理。
我们无法在大多数高级语言中表达尾调用优化,因为大多数语言只有“调用子程序”而没有“跳转到子程序”功能。因此,充其量,我们可以编写如上所述在返回时不起作用的代码,并让语言/编译器执行优化,如果它知道优化。
在 A 调用 B 调用 C,B & C 是函数,但 A 可能是也可能不是函数(它可能只是一些汇编代码——虽然它是 B 的调用者,但 A 本身不需要被调用或调用一个函数。虽然调用链可能很深,但调用链最顶端的第一个代码不是函数(例如它是_start或有时是main)并且没有返回的地方(所以不ret用于退出;它不会'没有调用者提供的返回地址参数。(如果代码有返回位置,即要使用的返回地址,那么根据定义,它不是调用链的顶部(名义上是一个函数)。
这个初始代码可以在模式中扮演 A 而不是 B 或 C 的角色。当 A 不是函数时,A 对 B 的调用将排除尾调用,因为 B 没有返回到 A 的调用者。这就是为什么模式必须是A调用B调用C,B&C必须是函数,我们考虑将优化应用到B。如果A是函数,它必须有一个调用者,这样才能起到中间的作用模式中的函数(C 也可以,例如,如果 C 调用 D)。