打印堆栈帧
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"
还要注意 a
和 b
是 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
所以我目前正在学习堆栈帧,我想尝试(手动)打印函数的堆栈帧。
我脑子里的栈帧如下图(我可能错了):
| | 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"
还要注意 a
和 b
是 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