为什么C程序为局部变量保留空间未使用?

发布于 2025-02-07 10:06:00 字数 3662 浏览 1 评论 0 原文

我正在从头开始阅读编程。 pdf地址:

我对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

I'm reading Programming from the Ground Up.
pdf address: http://mirror.ossplanet.net/nongnu/pgubook/ProgrammingGroundUp-0-8.pdf

I'm curious about Page37's reserve space for local variables.
He said, we need to 2 words of memory, so move stack pointer down 2 words.
execute this instruction: subl $8, %esp
so, here, I think I'm understand.

But, I write c code to verify this reserve space.

#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;
}

then, I use gcc convert to Executable file, gcc -Og -g, and use gdb debugger.

I use disass to main function, and copied some of the asm code in below.

   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

I'm dubious that this is reserve space instruction. then, I execute assembly code line by line and check content in the stack.

Why is this instruction only sub 8 byte, and 0x7fffffffe390 seems main function's return address. Should this not be reserve space?

below is rsp address nearby content. i r $rsp, x/40xb rsp address

0x7fffffffe390: 0x00    0x52    0x55    0x55    0x55    0x55    0x00    0x00   => after sub
0x7fffffffe398: 0xb3    0x20    0xdf    0xf7    0xff    0x7f    0x00    0x00   => before sub

then, I execute all pushq instruction, and use x/64xb 0x7fffffffe360.

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

I think 0x7fffffffe390~0x7fffffffe398 is reserve space for local variables, but it no change! Is my test way wrong?

Execution environment:

  • GDB version: 9.2
  • GCC version: 9.4.0
  • os: x86_64 GNU/Linux

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

清旖 2025-02-14 10:06:01

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) and push rax; sub rsp, 8; (clang) for that case (https://gcc.godbolt.org/z/jGj5WPq8c). I don't understand why.

琴流音 2025-02-14 10:06:01

回想一下,在X86_64中,呼叫指令执行以下操作:

  1. 按下RIP的当前值,这是函数返回时将执行的下一个指令。 (将RSP向下移动
    内存 - 回想一下,在X86_64中,堆栈成长,因此RBP&gt; RSP)。

  2. 按下RBP的当前值,RBP用于帮助还原呼叫者的堆栈框架。 (再次向下移动RSP)

  3. 将当前底部指针RBP移至当前堆栈指针RSP。 (实际上,从当前RSP开始,这将创建一个零尺寸的堆栈)

因此在您显示的内存转储中创建一个零尺寸的堆栈:

0x7fffffffe390: 0x00    0x52    0x55    0x55    0x55    0x55    0x00    0x00
0x7fffffffe398: 0xb3    0x20    0xdf    0xf7    0xff    0x7f    0x00    0x00

0x7fffffffe390 的值是要执行下一个函数的地址,而从主要的。该指令位于 0x0000005555555555200 (请记住,请记住,英特尔处理器很小,因此您必须向后读取该值)。此内存地址与您为代码显示的其他内存值一致。

此外,Main(RBP)的堆栈框架的底部位于 0x7FFFFFFFF7DF20B3 上,它看起来与您显示的其他堆栈地址一致。

一旦执行了“ MAIN”的呼叫,您就可以输入该函数的可实现,这是您拥有的拆卸的前三行:

0x000055555555519d <+0>: endbr64 
0x00005555555551a1 <+4>: sub    $0x8,%rsp  # reserve space?
0x00005555555551a5 <+8>: pushq  $0x12

第二行 sub $ 0x8,%RSP 减去0x8从堆栈指针中,从RBP-&gt; rsp形成了一个新堆栈。这个空间是为局部变量保留的空间(以及在函数执行时可能需要的任何其他空间。

接下来,我们将有一系列的pushq和mov's - 而这些都在做同样的事情。您需要回想一下

  1. 对函数的论点进行了权利评估,因此首先评估了测试的最后一个参数

  2. 拳头六个参数以64位代码的寄存器传递,因此
    A1 - &GT; A6通过您看到的寄存器传递。

  3. 将六个争论以外的任何东西都推到堆栈上,因此a7 - &gt;将A12推到堆上。

您所有的论点都是文字,因此没有局部变量,并且值直接在PushQ或Mov中使用。

下一个汇编是

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

在此中看到的实际调用测试。下一个说明是清理堆栈。回想一下,我们在堆栈上推出6个8字节值,从而导致堆栈向下增长48个字。添加0x30(48个小数)可以通过向上移动RSP有效地从堆栈中删除THOS 6值。

接下来的两行将设置要传递给printf的参数,下一行 mov $ 0x0,%eax 正在清除eax,这是函数的返回值通常为。

汇编的最后位(内存地址有更改,我怀疑这是来自代码的第二次运行):

0x00000000000011eb <+78>:    callq  0x1050 <__printf_chk@plt>
0x00000000000011f0 <+83>:    mov    $0x0,%eax
0x00000000000011f5 <+88>:    add    $0x8,%rsp
0x00005555555551f9 <+92>:    retq

执行对printf的实际调用,然后清除返回值(printf返回一个int值,其中打印了字符数) ,最后添加$ 0x8,%RSP 撤消了在拆卸的第2行上执行的减法,从而有效地破坏了Main的堆栈框架。最后一行 retq 是从main返回。

您是正确的,因为 sub $ 0x8,%rsp 正在保留8个字节的本地变量(或中间值)。但是,MAIN不使用任何局部变量,因此什么都不会改变。

作为测试,您可以向主要添加一些本地变量:

int a = 5, b = 10, c;
c = 3*a + 2*b;

printf("Wick is me %d\n", c);   // <--- note modification in this line

在这种情况下,您应该看到对第2行中的RSP减去值的修改。我们希望需要额外的24字节堆栈空间,但是可以是由于某些原因,不同

  1. 的结果 3*a'和 2*b'需要存储在堆栈或寄存器中。
  2. A和B的价值是文字,可以存储在寄存器中。
  3. 编译可能能够推断出3 a + 2 b是一个常数,并在编译时执行数学,优化 a'和 b',然后设置` C'至35。

使用-O0或-OG以及使用-M32(32位处理器的强制代码)可能会删除其中一些问题。

Recall that in x86_64, the call instruction does the following:

  1. 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).

  2. push the current value of RBP, which is used to help restore the caller's stack frame. (which moves RSP down again)

  3. 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:

0x7fffffffe390: 0x00    0x52    0x55    0x55    0x55    0x55    0x00    0x00
0x7fffffffe398: 0xb3    0x20    0xdf    0xf7    0xff    0x7f    0x00    0x00

The value at 0x7fffffffe390 is the address of the next function to be executed afer the return from main. This instruction is located at 0x0000555555555200 (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:

0x000055555555519d <+0>: endbr64 
0x00005555555551a1 <+4>: sub    $0x8,%rsp  # reserve space?
0x00005555555551a5 <+8>: pushq  $0x12

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

  1. arguments to a function are evaluated right to left, thus the last argument to test is evaluated first

  2. the fist six arguments are passed in registers in 64-bit code, thus
    a1 -> a6 are passed in the register that you see.

  3. 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

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

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):

0x00000000000011eb <+78>:    callq  0x1050 <__printf_chk@plt>
0x00000000000011f0 <+83>:    mov    $0x0,%eax
0x00000000000011f5 <+88>:    add    $0x8,%rsp
0x00005555555551f9 <+92>:    retq

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 line retq 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:

int a = 5, b = 10, c;
c = 3*a + 2*b;

printf("Wick is me %d\n", c);   // <--- note modification in this line

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

  1. The results of the calculations 3*a' and 2*b' need to be stored somewhere -- either on the stack or in registers.
  2. The value of a and b are literals and may be stored in registers.
  3. The compiles might be able to deduce that 3a + 2b is a constant and perform the math at compile time, optimize away both 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.

渡你暖光 2025-02-14 10:06:01

更新:

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.


Where is reserve space?

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 like add $68,%rsp.

P.S. You have a sequence of 0x01, 0x02 ..., 0x09, 0x10, .... Note that these are not consecutive numbers: the next number after 0x09 is 0x0a.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文