HeadlinesBriefing favicon HeadlinesBriefing.com

GCC 嵌套函数蹦床栈优化技术

Hacker News •
×

GCC 嵌套函数蹦床栈优化。无需可执行栈即可在 GCC 上间接调用嵌套函数。Martin Uecker,2026-08-29。我们上次讨论了如何在 GCC 和 Clang 上使用嵌套函数作为回调而不需要可执行栈。但如果需要支持旧版 GCC 怎么办?当然,可以直接接受可执行栈,但也可以通过一种技巧来避免。GCC:嵌套函数和蹦床。首先讨论 GCC 如何支持获取嵌套函数的地址。下面展示的是不使用新宏的玩具示例。在 x86_64 上,生成的汇编将蹦床放在栈上,并通过内联的 baz 函数立即调用它。蹦床是一段简短的代码序列,它将静态帧寄存器加载到栈上的一个结构中,该结构包含来自父函数的捕获变量,然后跳转到局部函数。如果将常量 -17591、-17847 和 -1864106167 转换回汇编指令,会得到以下 x86_64 代码:movq $bar.0, %r11,movq $frame, %r10,jump *%r11。静态链和代码地址都是蹦床代码中 mov 指令使用的立即数常量。我们可以不使用蹦床地址来调用函数,而是从蹦床中提取代码地址和静态链,并将它们与 __builtin_call_with_static_chain 内置函数一起使用,以直接调用局部函数。因此,我们可以将从蹦床读取这两个指针值作为旧版 GCC 的后备机制。缺点是仍然会创建蹦床,编译器仍然无法将间接调用去虚拟化,栈仍然会被标记为可执行。那么获得了什么?由于我们从不实际调用蹦床,我们可以使用以下命令再次使栈不可执行,这至少解决了该功能的安全顾虑。patchelf --clear-execstack program。这个想法在我的实验性库 noplate 中得到了实现,其中从代码地址和静态链构造了一个宽指针。蹦床作为函数描述符。还有一个我认为值得探索的想法:也可以将蹦床本身用作函数描述符。我们不在创建蹦床的地方提取静态链和代码指针,而是像往常一样传递蹦床的地址。但在可能调用蹦床的每个地方,我们首先检查指针是否指向蹦床,然后提取代码地址和静态链,使用 __builtin_call_with_static_chain 直接调用嵌套函数。在某种意义上,我们可以说我们不是在调用蹦床,而是在调用点使用一个超级简单的解释器来解释蹦床的代码,该解释器只能解释这个特定的代码序列,并且简单到……