当C代码被编译成机器代码时,没有明显的理由在堆栈上保留20个字节

use*_*291 1 c x86 assembly nasm

我正在按照Nick Blundell的操作系统开发部分5.1.3进行操作.我正在研究如何将以下C代码编译成机器代码:

void caller_fun(){
        callee_fun(0xdede);
}

int callee_fun(int arg){
        return arg;
}
Run Code Online (Sandbox Code Playgroud)

我最后拆解的机器代码ndisasm是这样的:

00000000  55                push ebp
00000001  89E5              mov ebp,esp
00000003  83EC08            sub esp,byte +0x8
00000006  83EC0C            sub esp,byte +0xc
00000009  68DEDE0000        push dword 0xdede
0000000E  E806000000        call dword 0x19
00000013  83C410            add esp,byte +0x10
00000016  90                nop
00000017  C9                leave
00000018  C3                ret
00000019  55                push ebp
0000001A  89E5              mov ebp,esp
0000001C  8B4508            mov eax,[ebp+0x8]
0000001F  5D                pop ebp
00000020  C3                ret
Run Code Online (Sandbox Code Playgroud)

研究堆栈指针和基指针是如何工作的,我制作了下面的图表,显示了当0x1C处理器运行偏移量的操作码时的堆栈情况:

                 Stack situation when processor is
             running `mov eax,[ebp+0x8]` at offset 0x1C

    +---------------------------------+
    |           4 bytes for           |
    |    `push ebp` at offset 0x00    |
    +---------------------------------+
    |        20 (8+12) bytes for      |
    |        `sub esp,byte +0x8`      |
    |    and `sub esp,byte +0xc`      |
    |    at offsets 0x03 and 0x06     |
    +---------------------------------+
    | 4 bytes for `push dword 0xdede` |
    |         at offset 0x09          |
    +---------------------------------+
    | 4 bytes for instruction pointer |
    |      by `call dword 0x19`       |
    |        at offset 0x0E           |
    +---------------------------------+
    |     4 bytes for `push ebp`      |
    |        at offset 0x19           |
    +---------------------------------+ --> ebp & esp are both here by 
                                               `mov ebp,esp`
                                              at offset 0x1A

现在,我有一些我无法通过研究和研究得出的问题:

  1. 我的堆栈情况图是否正确?

  2. 为什么20个字节压入堆栈通过sub esp,byte +0x8sub esp,byte +0xc在偏移0x030x06

  3. 即使需要20个字节的堆栈存储器,为什么它不是由单个指令分配的sub esp,byte +0x14,即0x14=0x8+0xc


我正在用这个make文件编译C代码:

all: call_fun.o call_fun.bin call_fun.dis

call_fun.o: call_fun.c
    gcc -ffreestanding -c call_fun.c -o call_fun.o

call_fun.bin: call_fun.o
    ld -o call_fun.bin -Ttext 0x0 --oformat binary call_fun.o

call_fun.dis: call_fun.bin
    ndisasm -b 32 call_fun.bin > call_fun.dis
Run Code Online (Sandbox Code Playgroud)

Ser*_*eyA 5

如果没有优化,堆栈将用于保存和恢复基本指针.在x86_64调用约定(https://en.wikipedia.org/wiki/X86_calling_conventions)中,在调用函数时,堆栈必须以16字节边界对齐,因此很可能在您的情况下会发生这种情况.至少,当我在我的系统上编译代码时,这就是我的情况.这是ASM:

callee_fun(int): # @callee_fun(int)
  pushq %rbp
  movq %rsp, %rbp
  movl %edi, -4(%rbp)
  movl -4(%rbp), %eax
  popq %rbp
  retq
caller_fun(): # @caller_fun()
  pushq %rbp
  movq %rsp, %rbp
  subq $16, %rsp
  movl $57054, %edi # imm = 0xDEDE
  callq callee_fun(int)
  movl %eax, -4(%rbp) # 4-byte Spill
  addq $16, %rsp
  popq %rbp
  retq
Run Code Online (Sandbox Code Playgroud)

值得注意的是,当启用优化时,根本没有堆栈使用或修改:

callee_fun(int): # @callee_fun(int)
  movl %edi, %eax
  retq
caller_fun(): # @caller_fun()
  retq
Run Code Online (Sandbox Code Playgroud)

最后,但并非最不重要的是,在使用ASM列表时,请不要反汇编目标文件或可执行文件.而是指导您的编译器生成程序集列表.这将为您提供更多背景信息.

如果你正在使用gcc,那么这样做的一个很好的命令就是

gcc -fverbose-asm -S -O
Run Code Online (Sandbox Code Playgroud)