打印堆栈帧

Printing Stack Frames

所以我目前正在学习堆栈帧,我想尝试(手动)打印函数的堆栈帧。

我脑子里的栈帧如下图(我可能错了):

|                                |  0xffff0fdc
+--------------------------------+
|              ...               |  0xffff0fd8
+--------------------------------+
|           parameter 2          |  0xffff0fd4
+--------------------------------+
|           parameter 1          |  0xffff0fd0
+--------------------------------+
|         return address         |  0xffff0fcc
+--------------------------------+
|        local variable 2        |  0xffff0fc8
+--------------------------------+
|        local variable 1        |  0xffff0fc4
+--------------------------------+

所以我首先写了这个函数来实现上面的结果并打印出来:

void func(int a,int b)
{
    uint64_t loc = 0;
    uint64_t *sp = &loc;

    printf("%" PRIu64 "\n",*(sp));
    printf("%" PRIu64 "\n",*(sp+4));
    printf("%" PRIu64 "\n",*(sp+8));
    printf("%" PRIu64 "\n",*(sp+12));
}

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

我得到:

0

12884901890

51266344552759297

18034967110614932

绝对不是预期的那样

我也试过 "scanning" 通过堆栈找到参数之一:

while (*sp != a) sp++

没有太大的成功。我的方法有什么问题吗?


我还有一个问题: 给定一个递归函数,简单地取阶乘(int n),我们如何找到基指针在堆栈中的地址?


如果您需要汇编代码: 注意这里只包含函数"func"生成的汇编代码。 我在汇编代码与源代码相关的地方添加了注释。

                    pushq   %rbp
                    .cfi_def_cfa_offset 16
                    .cfi_offset 6, -16
                    movq    %rsp, %rbp
                    .cfi_def_cfa_register 6
                    subq    , %rsp
                    movl    %edi, -20(%rbp)
                    movl    %esi, -24(%rbp)

                   ***// uint64_t loc = 0;***

                    movq    [=14=], -16(%rbp)

                   ***// uint64_t *sp = &loc;***

                    leaq    -16(%rbp), %rax
                    movq    %rax, -8(%rbp)

                  ***// printf("%" PRIu64 "\n",*sp);***

                    movq    -8(%rbp), %rax
                    movq    (%rax), %rax
                    movq    %rax, %rsi
                    movl    $.LC0, %edi

                    movl    [=14=], %eax

                    call    printf

                    ***printf("%" PRIu64 "\n",*(sp+8));***

                    movq    -8(%rbp), %rax
                    addq    , %rax
                    movq    (%rax), %rax
                    movq    %rax, %rsi
                    movl    $.LC0, %edi

                    movl    [=14=], %eax

                    call    printf

                    ***// printf("%" PRIu64 "\n",*(sp+16));***

                    movq    -8(%rbp), %rax
                    subq    $-128, %rax
                    movq    (%rax), %rax
                    movq    %rax, %rsi
                    movl    $.LC0, %edi

                    movl    [=14=], %eax

                    call    printf

                   ***// printf("%" PRIu64 "\n",*(sp+32));***

                    movq    -8(%rbp), %rax
                    addq    6, %rax

                    movq    (%rax), %rax
                    movq    %rax, %rsi
                    movl    $.LC0, %edi

                    movl    [=14=], %eax

                    call    printf

                    leave
                    .cfi_def_cfa 7, 8
                    ret

任何能帮助我更好地理解堆栈的建议都将不胜感激!

PS : 我不允许使用任何外部函数

x86-64 不会传递堆栈上的前几个参数(类型允许),因此您没有机会打印它们。此外,局部变量的实际堆栈布局取决于编译器和设置。

由于您提供了汇编代码,我们可以检查如下所示的布局:

        return address
rbp     saved rbp
rbp-8   local variable "sp"
rbp-16  local variable "loc"
rbp-20  local copy of argument "a"
rbp-24  local copy of argument "b"

还要注意 ab 是 4 个字节,其余的是 8 个字节。此外,C 指针算术按项目大小缩放,所以 *(sp+4) 变为 4 * 8 = 32字节不是您可能想要的 4

如果栈布局不变,可以用这段代码来说明:

#include <stdio.h>
#include <stdint.h>
int main();
void func(int a,int b)
{
    uint64_t loc = 0;
    char *sp = (char*)&loc;

    printf("main = %p\n", main);
    printf("return address = %p\n", *(void**)(sp + 24));
    printf("saved rbp = %p\n", *(void**)(sp + 16));
    printf("sp = %p\n", *(void**)(sp + 8));
    printf("loc = %lld\n", *(uint64_t*)(sp));
    printf("a = %d\n", *(int*)(sp - 4));
    printf("b = %d\n", *(int*)(sp - 8));
}

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

示例输出:

main = 0x4005e6
return address = 0x4005f9
saved rbp = 0x7ffe057bf240
sp = 0x7ffe057bf220
loc = 0
a = 2
b = 3