使用GDB探究函数调用过程中堆栈以及CPU寄存器的变化过程

一、目的

最近在看汇编,对于函数调用时RBP/RSP寄存器的变化比较好奇,故用一个简单的c程序,在gdb下对其做一个探究。

二、环境

OS:Ubuntu18.04.1 x86_64
Kernel:5.4.0-149-generic

三、过程

1、源文件main.c
在这里插入图片描述
2、编译+gdb运行
在这里插入图片描述
3、加断点运行
在这里插入图片描述
4、查看CPU寄存器
在这里插入图片描述
关注rbp/rsp这两个寄存器。这里有个疑问:rsp寄存器指向的内存地址,比rbp低10h个字节,为什么?需要查看main函数的汇编代码。

5、查看汇编代码
在这里插入图片描述
使用"disassemble main"命令查看main函数的汇编代码。发现此时rip寄存器指向"movl $0x1,-0x8(%rbp)"这条语句,之前的三条语句是在跳转到main函数时自动完成的。下面来分析下最开始这三条语句:

push   %rbp				# 将rbp中存放的地址压入堆栈
mov    %rsp,%rbp		# 将rsp中存放的地址复制到rbp寄存器中
sub    $0x10,%rsp		# rsp自减10h,这16个字节空间是用来存放临时变量的,这就是为什么一进main函数rsp就比rbp低了10h。

这三行语句完成了当前函数的堆栈初始化,每个函数都有自己的堆栈帧(stack frame),我们来画下当前main函数的stack frame。从CPU寄存器中得知rbp指向0x7fffffffddc0,rsp指向0x7fffffffddb0。采用4字节对齐。rbp和rsp都是64位寄存器,占8个字节,所以stack frame如下:
在这里插入图片描述
至于内存中的值,可以使用如下命令去读:
在这里插入图片描述

6、单步调试

输入两次"n"命令,将会执行以下两条指令:

movl   $0x1,-0x8(%rbp)		# 把rbp-8即ddb8的地方存入变量a的值
movl   $0x2,-0x4(%rbp)		# 把rbp-4即ddbc的地方存入变量b的值

执行完后的stack frame如下:
在这里插入图片描述


继续输入"n",执行callq指令:

callq  0x5555555545fa <func>    # 跳转到func,这一步会把原来的rip寄存器内容压栈,原来rip的值是0x0000555555554643

执行完后的stack frame如下:
在这里插入图片描述
查看dda8内存地址的内容:
在这里插入图片描述


查看func的汇编代码:
在这里插入图片描述
发现此时rip指向第三行指令,前两行指令在callq指令时已完成,分析下前两行指令:

push   %rbp		    # rbp入栈。即将原来rbp的值0x7fffffffddc0存放到dda0的内存处,同时rsp移动到dda0
mov    %rsp,%rbp	# 将当前rsp的值即0x7fffffffdda0更新到rbp中

执行完后的stack frame如下:
在这里插入图片描述
此时完成了func函数的stack frame的初始化。


继续执行以下指令:

movl   $0x1,-0x14(%rbp)
movl   $0x2,-0x10(%rbp)
movl   $0x3,-0xc(%rbp)
movl   $0x4,-0x8(%rbp)
movl   $0x5,-0x4(%rbp)

执行后的stack frame:
在这里插入图片描述
查看内存:
在这里插入图片描述


继续执行以下指令:

mov    $0x0,%eax    # 立即数0放入eax寄存器,作为返回值
pop    %rbp	        # 从栈顶取出内容即0x7fffffffddc0赋给rbp,rsp自减即dda8
retq			    # 从栈顶取出内容即0x555555554643赋给rip,rsp自减即ddb0

执行后的stack frame:
在这里插入图片描述
rbp和rsp又回到了调用func之前的状态,即还原了main函数的stack frame。以上就是函数调用过程中堆栈及CPU寄存器的变化。

四、总结

1、函数的起始通过下面这两行指令,完成原函数堆栈基址的保存,并为新函数创建了新的stack frame。

push   %rbp
mov    %rsp,%rbp

2、在子函数stack frame刚初始化完时,rsp和rbp寄存器指向同一个内存地址(main函数为了存放临时变量,rsp进行了自减,这种情况先抛开不谈)。这个内存地址中存放的内容是原函数或者说父函数的堆栈基址。在子函数返回时,通过"pop %rbp",rbp又重新指向了父函数的堆栈基址。

五、GDB命令

1、查看CPU寄存器

i register [寄存器名]

2、查看内存的值

x/<n/f/u>
	n: 要显示的内存单元的数量
	f: 格式,x-16进制,d-10进制
	u: 每个内存单元的字节长度,默认4。b-单字节,h-双字节,w-四字节,g-8字节

3、查看函数的汇编代码

disassemble [函数名]
  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值