gcc参数寄存器溢出x86-64

我正在尝试使用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 : 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会这样做?

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

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

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

 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会在禁用优化的情况下溢出所有参数和局部变量? 帮助调试。 考虑这个简单的function:

 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  c: 83 c0 2a add $0x2a,%eax f: 48 83 c4 08 add $0x8,%rsp 13: c3 retq 

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

 (gdb) print a $1 =  

因为参数被覆盖,因为在调用bar之后它没有被使用。

有几个原因:

  1. 在一般情况下,函数的参数必须被视为局部变量,因为它可以存储到函数中或在函数中获取其地址。 因此,最简单的方法是为每个参数分配一个堆栈槽。
  2. 使用堆栈位置发出调试信息变得更加简单:参数的值始终位于某个特定位置,而不是在寄存器和内存之间移动。

当您正在查看-O0代码时,请考虑编译器的首要任务是尽可能减少编译时间并生成高质量的调试信息。