You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

GCC未对函数调用时的栈空间分配应用栈冲突保护是否为设计使然?

GCC未对函数调用时的栈空间分配应用栈冲突保护是否为设计使然?

嘿,这个问题观察得太细致了!咱们先把背景和问题拆解清楚,再聊聊为什么会出现你看到的这种情况。

首先看你给出的C++代码:

struct Base {
    int x[10000];
};

void foo(Base base) {}

int main() {
    Base base;
    foo(base);
}

这段代码里foo是传值接收Base对象,所以main需要在自己的栈上创建一个Base的副本,再传给foo——这也就是汇编里那串memcpy和后续调用的原因。

接下来看你提到的两个关键汇编片段:

第一个是main开头的栈冲突保护逻辑:

leaq    -36864(%rsp), %r11
.LPSRL0:
subq    $4096, %rsp
orq     $0, (%rsp)
cmpq    %r11, %rsp
jne     .LPSRL0

这部分是GCC启用-fstack-clash-protection后的标准操作:逐页分配栈空间,并且每分配一页就访问一次(orq $0, (%rsp)),这样如果分配过程中跳过了操作系统的栈Guard Page,会立刻触发页错误,避免攻击者利用栈冲突漏洞。这部分是为main函数自身的局部变量(比如你的Base base)分配栈空间时的保护。

然后是你关注的、为调用foo准备参数的片段:

subq    $40000, %rsp
movq    %rsp, %rax
movq    %rax, %rcx
leaq    -40016(%rbp), %rax
movl    $40000, %edx
movq    %rax, %rsi
movq    %rcx, %rdi
call    memcpy
call    foo(Base)

这里直接一次性调整rsp分配了40000字节(正好是Base的大小:10000个int × 4字节),完全没走逐页分配+访问的栈冲突保护流程——这确实是GCC的设计使然,原因主要有这几点:

  • 栈冲突保护的覆盖范围限定
    GCC的-fstack-clash-protection特性从设计之初,就只针对函数入口处为自身局部变量分配栈空间的场景。它的核心目标是防范函数内部大型局部变量导致的栈冲突,而调用者为函数参数分配栈空间的操作,不在这个特性的覆盖范围内。

  • 参数分配的场景考量
    虽然理论上参数空间也可能很大(比如你这里的40000字节),但GCC的实现团队认为,这类场景相对少见,且现有栈保护机制(比如栈金丝雀-fstack-protector-strong)已经能覆盖部分风险。另外,为参数分配添加逐页保护会增加调用函数的开销,对于大多数参数较小的场景来说得不偿失。

  • 现有实现的逻辑边界
    从编译器的实现角度看,函数入口的栈帧分配是统一处理的,容易加入栈冲突保护;而参数分配是分散在各个函数调用点的,要逐个处理会增加编译器的复杂度,目前GCC还没有把这部分纳入栈冲突保护的范畴。

简单来说:你看到的差异不是编译器的bug,而是GCC当前对-fstack-clash-protection的设计选择——它只保护函数自身局部变量的栈分配,不管调用函数前的参数栈空间分配。

备注:内容来源于stack exchange,提问作者Ernie Sanderson

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.20 11:24:51