gcc参数寄存器溢出x86-64

时间:2011-08-26 08:10:58

标签: c gcc assembly x86-64 cpu-registers

我正在尝试使用x86-64程序集。编译了这个虚函数:

long myfunc(long a, long b, long c, long d,
            long e, long f, long g, long h)
{
    long xx = a * b * c * d * e * f * g * h;
    long yy = a + b + c + d + e + f + g + h;
    long zz = utilfunc(xx, yy, xx % yy);
    return zz + 20;
}

使用gcc -O0 -g我很惊讶在函数程序集的开头找到以下内容:

0000000000400520 <myfunc>:
  400520:       55                      push   rbp
  400521:       48 89 e5                mov    rbp,rsp
  400524:       48 83 ec 50             sub    rsp,0x50
  400528:       48 89 7d d8             mov    QWORD PTR [rbp-0x28],rdi
  40052c:       48 89 75 d0             mov    QWORD PTR [rbp-0x30],rsi
  400530:       48 89 55 c8             mov    QWORD PTR [rbp-0x38],rdx
  400534:       48 89 4d c0             mov    QWORD PTR [rbp-0x40],rcx
  400538:       4c 89 45 b8             mov    QWORD PTR [rbp-0x48],r8
  40053c:       4c 89 4d b0             mov    QWORD PTR [rbp-0x50],r9
  400540:       48 8b 45 d8             mov    rax,QWORD PTR [rbp-0x28]
  400544:       48 0f af 45 d0          imul   rax,QWORD PTR [rbp-0x30]
  400549:       48 0f af 45 c8          imul   rax,QWORD PTR [rbp-0x38]
  40054e:       48 0f af 45 c0          imul   rax,QWORD PTR [rbp-0x40]
  400553:       48 0f af 45 b8          imul   rax,QWORD PTR [rbp-0x48]
  400558:       48 0f af 45 b0          imul   rax,QWORD PTR [rbp-0x50]
  40055d:       48 0f af 45 10          imul   rax,QWORD PTR [rbp+0x10]
  400562:       48 0f af 45 18          imul   rax,QWORD PTR [rbp+0x18]

gcc非常奇怪地将所有参数寄存器溢出到堆栈中,然后将它们从内存中取出以进行进一步操作。

这只发生在-O0上(-O1没有问题),但为什么呢?这看起来像是对我的反优化 - 为什么gcc会这样做?

2 个答案:

答案 0 :(得分:7)

我绝不是GCC的内部专家,但我会试一试。不幸的是,关于GCC注册分配和溢出的大多数信息似乎都已过时(引用不再存在的local-alloc.c等文件)。

我正在查看gcc-4.5-20110825的源代码。

GNU C Compiler Internals中,提到初始功能代码由expand_function_start中的gcc/function.c生成。我们在处理参数时找到以下内容:

4462   /* Initialize rtx for parameters and local variables.
4463      In some cases this requires emitting insns.  */
4464   assign_parms (subr);

assign_parms中,处理每个参数存储位置的代码如下:

3207       if (assign_parm_setup_block_p (&data))
3208         assign_parm_setup_block (&all, parm, &data);
3209       else if (data.passed_pointer || use_register_for_decl (parm))
3210         assign_parm_setup_reg (&all, parm, &data);
3211       else
3212         assign_parm_setup_stack (&all, parm, &data);

assign_parm_setup_block_p处理聚合数据类型,在这种情况下不适用,因为数据不作为指针传递,GCC会检查use_register_for_decl

这里的相关部分是:

1972   if (optimize)
1973     return true;
1974 
1975   if (!DECL_REGISTER (decl))
1976     return false;

DECL_REGISTER测试变量是否使用register关键字声明。现在我们得到了答案:当未启用优化时,大多数参数都存在于堆栈中,然后由assign_parm_setup_stack处理。在源代码最终溢出值之前获取的路由对于指针参数来说稍微复杂一些,但如果你很好奇,可以在同一个文件中进行跟踪。

为什么GCC会在禁用优化的情况下溢出所有参数和局部变量?帮助调试。考虑这个简单的功能:

1 extern int bar(int);
2 int foo(int a) {
3         int b = bar(a | 1);
4         b += 42;
5         return b;
6 }

使用gcc -O1 -c编译,这会在我的计算机上生成以下内容:

 0: 48 83 ec 08             sub    $0x8,%rsp
 4: 83 cf 01                or     $0x1,%edi
 7: e8 00 00 00 00          callq  c <foo+0xc>
 c: 83 c0 2a                add    $0x2a,%eax
 f: 48 83 c4 08             add    $0x8,%rsp
13: c3                      retq   

哪个好,除非你在第5行打破并尝试打印a的值,你得到

(gdb) print a
$1 = <value optimized out>

由于参数被覆盖,因为它在调用bar后未被使用。

答案 1 :(得分:6)

有几个原因:

  1. 在一般情况下,函数的参数必须被视为局部变量,因为它可以存储到函数中或者在函数中使用其地址。因此,最简单的方法是为每个参数分配一个堆栈槽。
  2. 使用堆栈位置发送调试信息变得更加简单:参数的值始终位于某个特定位置,而不是在寄存器和内存之间移动。
  3. 当您正在查看-O0代码时,请考虑编译器的首要任务是尽可能减少编译时间并生成高质量的调试信息。