2

我目前正在玩,检查堆栈帧,试图了解它是如何工作的。在阅读了一些总是解释一般结构的文章之后:

local vars <---SP 低地址

BP <---BP

ret addr args 高地址

我有一个示例程序,它调用一个带有三个参数的函数,并有两个缓冲区作为局部变量:

#include <stdio.h>

void function(int a, int b, int c);

int main()
{
        function(1, 2, 3);
        return 0;
}

void function(int a, int b, int c)
{
        char buffer1[5];
        char buffer2[10];
}

我看了看程序的汇编代码,很惊讶在调用函数时没有找到我所期望的。我期待的东西是:

# The arguments are pushed onto the stack:
push 3
push 2
push 1
call function       # Pushes ret address onto stack and changes IP to function
...
# In function:
# Push old base pointer onto stack and set current base pointer to point to it
push rbp
mov rbp, rsp

# Reserve space for stack frame etc....

因此,在执行函数时,框架的结构将类似于:

buffers   <--- SP           low address
old BP    <--- BP
ret Addr
1
2
3                           high address

但相反,会发生以下情况:

函数调用:

    mov     edx, 3
    mov     esi, 2
    mov     edi, 1
    call    function

既然我们可以直接压栈,为什么还要在这里使用寄存器???在我们调用的实际函数中:

    .cfi_startproc
    push    rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    mov     rbp, rsp
    .cfi_def_cfa_register 6
    sub     rsp, 48
    mov     DWORD PTR [rbp-36], edi
    mov     DWORD PTR [rbp-40], esi
    mov     DWORD PTR [rbp-44], edx
    mov     rax, QWORD PTR fs:40
    mov     QWORD PTR [rbp-8], rax
    xor     eax, eax
    mov     rax, QWORD PTR [rbp-8]
    xor     rax, QWORD PTR fs:40
    je      .L3
    call    __stack_chk_fail

据我所见,为堆栈帧保留了 48 个字节,对吗?然后,使用函数调用中的寄存器,将函数的参数复制到堆栈的末尾。所以它看起来像这样:

3            <--- SP
2
1
??
??
old BP       <--- BP
return Address
??

我假设缓冲区介于 args 和 old 之间BP。但是我真的不确定确切的位置...因为它们总共只有 15 个字节,而保留了 48 个字节...那里不会有一堆未使用的空间吗?有人可以帮我概述这里发生的事情吗?这是依赖于处理器的东西吗?我正在使用英特尔 i7。

干杯,砖

4

2 回答 2

1

有几个问题。首先,这 3 个参数通过寄存器传递,因为这是 ELF ABI 规范的一部分。我不确定这些天最新的 (x86-64) SysV ABI 文档保存在哪里(x86-64.org 似乎已不复存在)。Agner Fog 维护了很多优秀的文档,包括一个关于调用约定的文档。

调用 使堆栈分配变得复杂__stack_check_fail,这是作为检测堆栈粉碎/缓冲区溢出的对策而添加的。ABI 的一部分还指定在函数调用之前堆栈必须是 16 字节对齐的。如果您使用 重新编译-fno-stack-protector,您将更好地了解正在发生的事情。

此外,由于该函数不执行任何操作,因此它不是一个特别好的示例。它存储参数(不必要的),需要 12 个字节。buffer1并且buffer2可能是 8 字节对齐的,实际上分别需要 8 和 16 个字节,可能还有另外 4 个字节来对齐它们。我可能错了——我手头没有规范。所以这是 36 或 40 字节。然后调用对齐需要 16 字节对齐 48 个字节。

我认为关闭堆栈保护并检查此叶函数的堆栈帧,并查阅 x86-64 ABI 规范以了解局部变量的对齐要求等会更有指导意义。

于 2013-04-03T13:03:53.463 回答
0

它相当依赖于编译器。您可以尝试使用“extern”关键字关闭优化或标记功能(强制使用默认调用约定)。

使用寄存器是因为这比通过堆栈发送参数要快得多。

于 2013-04-03T10:41:53.500 回答