Go 1.22.6在ARM64上函数前导代码异常的技术问询
我无法理解Go运行时中这个函数的前导代码逻辑,以下是一个出现在Hello World程序里的Go运行时函数(环境:Go 1.22.6,macOS,ARM64):
0000000100064da0 <_runtime.osinit_hack_trampoline.abi0>: 100064da0: f81f0ffe str x30, [sp, #-0x10]! 100064da4: f81f83fd stur x29, [sp, #-0x8] 100064da8: d10023fd sub x29, sp, #0x8 100064dac: aa1f03e0 mov x0, xzr 100064db0: 9400a01f bl 0x10008ce2c <_xpc_date_create_from_current+0x10008ce2c> 100064db4: 9400a021 bl 0x10008ce38 <_xpc_date_create_from_current+0x10008ce38> 100064db8: a97ffbfd ldp x29, x30, [sp, #-0x8] 100064dbc: 910043ff add sp, sp, #0x10 100064dc0: d65f03c0 ret
这里帧指针(x29)指向栈指针(sp)之后8字节的位置。
对比Clang生成的C语言函数前导代码:
sub sp, sp, #32 stp x29, x30, [sp, #16] ; 16-byte Folded Spill add x29, sp, #16
我的疑问是:当调用0x100064db0处的C函数时,该函数会立即覆盖之前在0x100064da4处保存的帧指针,为什么这没有引发问题?我是不是忽略了什么?
补充说明:我现在认为这其实是Go的输出错误,帧指针链已经被破坏,LLDB这类回溯工具无法正常追踪栈了,我已经在GitHub提交了相关问题。
问题解析与结论
这确实是Go运行时生成的汇编代码存在的问题,核心原因是这段前导代码没有遵循ARM64的栈帧规范,导致帧指针链断裂:
栈布局的错误:
Go这段代码里,str x30, [sp, #-0x10]!先将栈指针sp向下调整0x10字节,把返回地址x30存在新sp的位置。接着stur x29, [sp, #-0x8]是把帧指针x29存在sp向上偏移-8字节的位置——也就是刚调整后的sp的上方8字节,这个区域属于调用者的栈帧,并不属于当前函数的栈空间。随后sub x29, sp, #0x8让x29指向这个不属于当前栈帧的地址,直接违反了ARM64 ABI中帧指针必须指向当前栈帧内有效位置的规范。调用C函数后的覆盖问题:
当调用C函数时,C函数会按照ARM64 ABI规范生成栈帧:先调整sp分配栈空间,然后在自己的栈帧内保存x29和x30。而Go代码里保存的x29的位置,正好处于C函数调整sp后会使用的栈区域,所以会被C函数的栈操作直接覆盖。无崩溃的假象:
之所以没立刻触发崩溃,是因为当前函数在调用完C函数后,直接从[sp, #-0x8]加载x29和x30,但此时这个位置的x29已经被C函数修改。不过一旦涉及到依赖帧指针链的操作(比如调试工具的栈回溯、异常展开),就会完全失效,这也是你用LLDB无法正常追踪栈的原因。
你的判断完全正确,这属于Go生成的汇编代码不符合ARM64 ABI规范的问题,破坏了帧指针链的完整性,导致调试工具无法正常工作。
内容的提问来源于stack exchange,提问作者Lawrence D'Anna

