这是Linux内核函数(用-mno-red-zone
编译)的编译器输出:
load_balance:
.LFB2408:
.loc 2 6487 0
.cfi_startproc
.LVL1355:
pushq %rbp #
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp #,
.cfi_def_cfa_register 6
pushq %r15 #
pushq %r14 #
pushq %r13 #
pushq %r12 #
.cfi_offset 15, -24
.cfi_offset 14, -32
.cfi_offset 13, -40
.cfi_offset 12, -48
movq %rdx, %r12 # sd, sd
pushq %rbx #
.LBB2877:
.loc 2 6493 0
movq $load_balance_mask, -136(%rbp) #, %sfp
.LBE2877:
.loc 2 6487 0
subq $184, %rsp #,
.cfi_offset 3, -56
.loc 2 6489 0
....
注意后面的"subq $184, %rsp" 编译器已经溢出了到堆栈(顺便说一句,泄漏是疯狂的,因为它泄漏了一个常量价值!)
Linus 2天前向gcc报告了这个bug。但我不明白这是什么虫子。为什么subq
是错误的?
编辑:Bug报告在这里:很抱歉之前没有包含这个https://gcc.gnu.org/bugzilla/show_bug.cgi?id=61904
我不明白为什么
subq
是错误的?
问题在于它相对于movq $load_balance_mask, -136(%rbp)
指令的顺序。subq
通过修改堆栈指针在堆栈上分配空间,movq
写入该分配区域内的位置。但在这种情况下,movq
出现在subq
之前,即它正在写入(截至目前)未分配的堆栈空间。现在,如果在movq
和subq
之间发生中断,并且中断处理程序试图触摸堆栈的同一区域,该怎么办?结果可能会发生各种奇怪的事情,其中大多数可能是不好的。
在存在红色区域的情况下,首先使用movq
是可以的。引用自维基百科:
红色区域是内存中堆栈指针之外的一个固定大小的区域,没有被"分配"。这个内存区域不能被中断/异常/信号处理程序修改。这允许将空间用于临时数据,而无需修改堆栈指针的额外开销。x86-64 ABI要求一个128字节的红色区域。
然而,正如Linus在关于这个bug的电子邮件线程中所写的:"但是我们用-mno-red-zone构建内核。我们不遵循x86-64 ABI wrt redzoning"。
在禁用红色区域的情况下,代码生成器不应该在subq
之前输出movq
我看不出有什么问题。常量并没有溢出,它只是初始化了一个局部变量。红色区域在堆栈指针下128字节,因此-136(%rbp)
在限制范围内,因为rbp
在五次压入之前具有rsp
的值,减少了40。编译器可以随时调整rsp
。也可能是alloca
调用。
您可以提供一个链接,或者至少提供一个bug报告的摘要。我在gcc bugzilla里找不到任何相关的东西。原始的C源代码也会很有用。