为什么C程序为局部变量保留空间未使用?
我对Page37的本地变量储备空间感到好奇。 他说,我们需要2个记忆单词,因此将堆栈指针向下移动2个单词。 执行此指令: SUBL $ 8,%ESP 所以,在这里,我想我明白了。
但是,我编写C代码以验证此储备空间。
#include <stdio.h>
int test(int a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8, int a9, int a10, int a11, int a12) {
printf("a1=%#x, a2=%#x, a3=%#x, a4=%#x, a5=%#x, a6=%#x, a7=%#x, a8=%#x, a9=%#x, a10=%#x, a11=%#x, a12=%#x", a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12);
return 0;
}
int main(void){
test(0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12);
printf("Wick is me!");
return 0;
}
然后,我将GCC转换为可执行文件, gcc -og -g
,并使用GDB调试器。
我将 disass
用于主函数,并在下面复制了一些ASM代码。
0x000055555555519d <+0>: endbr64
0x00005555555551a1 <+4>: sub $0x8,%rsp # reserve space?
0x00005555555551a5 <+8>: pushq $0x12
0x00005555555551a7 <+10>: pushq $0x11
0x00005555555551a9 <+12>: pushq $0x10
0x00005555555551ab <+14>: pushq $0x9
0x00005555555551ad <+16>: pushq $0x8
0x00005555555551af <+18>: pushq $0x7
0x00000000000011b1 <+20>: mov $0x6,%r9d
0x00000000000011b7 <+26>: mov $0x5,%r8d
0x00000000000011bd <+32>: mov $0x4,%ecx
0x00000000000011c2 <+37>: mov $0x3,%edx
0x00000000000011c7 <+42>: mov $0x2,%esi
0x00000000000011cc <+47>: mov $0x1,%edi
0x00000000000011d1 <+52>: callq 0x1149 <test>
0x00000000000011d6 <+57>: add $0x30,%rsp
0x00000000000011da <+61>: lea 0xe89(%rip),%rsi # 0x206a
0x00000000000011e1 <+68>: mov $0x1,%edi
0x00000000000011e6 <+73>: mov $0x0,%eax
0x00000000000011eb <+78>: callq 0x1050 <__printf_chk@plt>
0x00000000000011f0 <+83>: mov $0x0,%eax
0x00000000000011f5 <+88>: add $0x8,%rsp
0x00005555555551f9 <+92>: retq
我怀疑这是预备空间指导。然后,我逐行执行汇编代码,并在堆栈中检查内容。
为什么此指令仅Sub 8字节,而0x7FFFFFFFE390似乎是Main Function的返回地址。这是否应该不是储备空间?
是附近内容附近的RSP地址。 ir $ rsp,x/40xb rsp地址
0x7fffffffe390: 0x00 0x52 0x55 0x55 0x55 0x55 0x00 0x00 => after sub
0x7fffffffe398: 0xb3 0x20 0xdf 0xf7 0xff 0x7f 0x00 0x00 => before sub
然后,我执行所有 pushq
指令,并使用 x/64xb 0x7fffffffeffe360
。
0x7fffffffe360: 0x07 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x7fffffffe368: 0x08 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x7fffffffe370: 0x09 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x7fffffffe378: 0x10 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x7fffffffe380: 0x11 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x7fffffffe388: 0x12 0x00 0x00 0x00 0x00 0x00 0x00 0x00
above is local variables
==========================
0x7fffffffe390: 0x00 0x52 0x55 0x55 0x55 0x55 0x00 0x00
0x7fffffffe398: 0xb3 0x20 0xdf 0xf7 0xff 0x7f 0x00 0x00
我认为 0x7fffffe390〜0x7fffffffe398
是本地变量的保留空间,但没有更改!我的测试方式错了吗?
执行环境:
- GDB版本:9.2
- GCC版本:9.4.0
- OS:X86_64 GNU/Linux
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
X86-64 SYSV ABI要求在通话时堆栈16。
由于呼叫指令将8字节的回程地址推到堆栈,因此堆栈在功能开始时总是被8端对齐,如果要进行嵌套呼叫,则呼叫者将需要按下奇数八字形与堆栈再次合并16。
由于您的功能需要12个整数参数,其中6个以8个字节作为堆栈,因此需要在堆栈参数之前将额外的8字节推到堆栈中,以便在调用之前对堆栈进行16对准。
如果您的函数进行了11个参数(或其他6个(寄存器参数) +奇数参数的参数),则不应需要额外的堆栈推动。
海湾合作委员会和clang仍然很奇怪
sub rsp,16
(GCC)和push rax; sub rsp,8;
(clang)( https:///gcc.godbolt。 org/z/jgj5wpq8c )。我不明白为什么。The x86-64 SysV ABI requires that stacks be 16-aligned at the time of a call.
Since a call instructions pushes an 8-byte return-address to the stack, the stack is always misaligned by 8 at the start of a function and if a nested call is to be made then the caller will need to have pushed an odd number of eight-bytes to the stack to make it 16-aligned again.
Since your function takes 12 integer arguments, 6 of which go to the stack as eight-bytes each, an extra 8-byte needs to be pushed to the stack before the stack arguments so the stack is 16-aligned before the call.
If your function took 11 arguments (or any other 6 (register arguments) +odd stack number of arguments), then no extra stack push should be needed.
Gcc and clang are still weirdly generating
sub rsp, 16
(gcc) andpush rax; sub rsp, 8;
(clang) for that case (https://gcc.godbolt.org/z/jGj5WPq8c). I don't understand why.回想一下,在X86_64中,呼叫指令执行以下操作:
按下RIP的当前值,这是函数返回时将执行的下一个指令。 (将RSP向下移动
内存 - 回想一下,在X86_64中,堆栈成长,因此RBP&gt; RSP)。
按下RBP的当前值,RBP用于帮助还原呼叫者的堆栈框架。 (再次向下移动RSP)
将当前底部指针RBP移至当前堆栈指针RSP。 (实际上,从当前RSP开始,这将创建一个零尺寸的堆栈)
因此在您显示的内存转储中创建一个零尺寸的堆栈:
0x7fffffffe390
的值是要执行下一个函数的地址,而从主要的。该指令位于0x0000005555555555200
(请记住,请记住,英特尔处理器很小,因此您必须向后读取该值)。此内存地址与您为代码显示的其他内存值一致。此外,Main(RBP)的堆栈框架的底部位于
0x7FFFFFFFF7DF20B3
上,它看起来与您显示的其他堆栈地址一致。一旦执行了“ MAIN”的呼叫,您就可以输入该函数的可实现,这是您拥有的拆卸的前三行:
第二行
sub $ 0x8,%RSP
减去0x8从堆栈指针中,从RBP-&gt; rsp形成了一个新堆栈。这个空间是为局部变量保留的空间(以及在函数执行时可能需要的任何其他空间。接下来,我们将有一系列的pushq和mov's - 而这些都在做同样的事情。您需要回想一下
对函数的论点进行了权利评估,因此首先评估了测试的最后一个参数
拳头六个参数以64位代码的寄存器传递,因此
A1 - &GT; A6通过您看到的寄存器传递。
将六个争论以外的任何东西都推到堆栈上,因此a7 - &gt;将A12推到堆上。
您所有的论点都是文字,因此没有局部变量,并且值直接在PushQ或Mov中使用。
下一个汇编是
在此中看到的实际调用测试。下一个说明是清理堆栈。回想一下,我们在堆栈上推出6个8字节值,从而导致堆栈向下增长48个字。添加0x30(48个小数)可以通过向上移动RSP有效地从堆栈中删除THOS 6值。
接下来的两行将设置要传递给printf的参数,下一行
mov $ 0x0,%eax
正在清除eax,这是函数的返回值通常为。汇编的最后位(内存地址有更改,我怀疑这是来自代码的第二次运行):
执行对printf的实际调用,然后清除返回值(printf返回一个int值,其中打印了字符数) ,最后
添加$ 0x8,%RSP
撤消了在拆卸的第2行上执行的减法,从而有效地破坏了Main的堆栈框架。最后一行retq
是从main返回。您是正确的,因为
sub $ 0x8,%rsp
正在保留8个字节的本地变量(或中间值)。但是,MAIN不使用任何局部变量,因此什么都不会改变。作为测试,您可以向主要添加一些本地变量:
在这种情况下,您应该看到对第2行中的RSP减去值的修改。我们希望需要额外的24字节堆栈空间,但是可以是由于某些原因,不同
3*a'和
2*b'需要存储在堆栈或寄存器中。a'和
b',然后设置` C'至35。使用-O0或-OG以及使用-M32(32位处理器的强制代码)可能会删除其中一些问题。
Recall that in x86_64, the call instruction does the following:
push the current value of RIP, which is the next instruction that will be executed when the function returns. (which moves RSP down in
memory - recall that in x86_64 the stack grows down, thus RBP > RSP).
push the current value of RBP, which is used to help restore the caller's stack frame. (which moves RSP down again)
move the current bottom pointer, RBP, to the current stack pointer, RSP. (effectively this creates a zero sized stack starting at where RSP is currently at)
Thus in the memory dump that you show:
The value at
0x7fffffffe390
is the address of the next function to be executed afer the return from main. This instruction is located at0x0000555555555200
(remember that intel processor are little endian, so you have to read the value backwards). This memory address is consistent with the other memory values you've shown for the code.Additionally, the bottom of the stack frame for main (RBP) is located at
0x7ffff7df20b3
, which looks consistent with the other stack addresses you've shown.As soon as the call to `main' is executed, you enter the preable of the function, which is the first three lines of the disassembly you have:
The second line
sub $0x8, %rsp
subtracts 0x8 from the stack pointer, thus forming a new stack from RBP->RSP. This space is the space reserved for local variables (and any other space that might be needed as the function executes.Next we have a series of pushq's and mov's - and these all are doing the same thing. You need to recall that
arguments to a function are evaluated right to left, thus the last argument to test is evaluated first
the fist six arguments are passed in registers in 64-bit code, thus
a1 -> a6 are passed in the register that you see.
anything beyond six arguments are pushed on the stack, thus a7 -> a12 are pushed on the stack.
All of you arguments are literals, so there is no local variables and the values are used directly in the pushq's or mov's.
The next bit of assembly is
In this we see the actual call to test. The next instruction is to clean up the stack. Recall that we push 6 8-byte values on the stack, causing the stack to grow downwards by 48-bytes. Adding 0x30 (48 decimal) effectively removes thos 6 values from the stack by moving RSP upward.
The next two lines are setting up the parameters that are going to be passed to printf, the next line
mov $0x0, %eax
is clearing the EAX, which is where the return value from a function typically goes.The last bit of assembly (memory address have change, I suspect that this is from a second run of the code):
performs that actual call to printf, then clears the return value (printf returns an int value with the number of characters printed), and finally the
add $0x8, %rsp
undoes the subtraction performed on line 2 of the disassembly, effectively destroying the stack frame for main. The last lineretq
is the return from main.You are correct in that
sub $0x8,%rsp
is reserving 8 bytes for local variables (or intermediate values). However, main does not use any local variables, so nothing is going to change.As a test, you could add a few local variables to main:
In this case you should see some modification to the value being subtracted from RSP in line 2. We would expect an additional 24 byte of stack space being needed, however it can be different for a few reasons
3*a' and
2*b' need to be stored somewhere -- either on the stack or in registers.a' and
b' and just set `c' to 35.Using -O0 or -Og as well as using -m32 (forcing code for a 32-bit processor) might remove some of these issues.
更新:
i误读
-og
为-O0
。通过优化,还有一些其他并发症(例如,GCC确切选择通过参数,它是否保留了当地人的空间 ,还是将这些当地人保留在寄存器中等)。要了解发生的事情,您应该首先了解图片而无需优化。
x86_64
上的堆栈上有几种“预订空间”的方法:push ...
sub ...,%rsp
输入...
也有几种“取消保存”的方法:
pop ...
,add ...,%rsp
,离开< /代码>。
在您的情况下,这是
pushq
指令,同时将值放入堆栈插槽中,并保留该值的空间。您没有显示
retq
之前发生的事情,但我怀疑您的“不保留”看起来像添加$ 68,%RSP
。ps您的序列
0x01,0x02 ...,0x09,0x10,...
。请注意,这些是不是连续数字:0x09
之后的下一个数字是0x0a
。Update:
I misread
-Og
as-O0
. With optimization on, there are several additional complications (such as how exactly GCC choses to pass arguments, whether it reserves space for locals at all or keeps these locals in a register, etc. etc.).To understand what's going on, you should first understand the picture without optimizations.
There are several ways to "reserve space" on stack on
x86_64
:push ...
sub ...,%rsp
enter ...
There are also several ways to "unreserve" it:
pop ...
,add ...,%rsp
,leave
.In your case, it's the
pushq
instruction which simultaneously puts a value into the stack slot and reserves space for that value.You didn't show what happens just before
retq
, but I suspect that your "unreserve" looks something likeadd $68,%rsp
.P.S. You have a sequence of
0x01, 0x02 ..., 0x09, 0x10, ...
. Note that these are not consecutive numbers: the next number after0x09
is0x0a
.