目录
前言
64位和32位汇编的差异
示例说明
前言
公司项目需要实现通过汇编来获取调用栈的功能,自己写了一个,直接崩溃,回头学习一下,以此为记;
64位和32位汇编的差异
这方面我现在涉及的不多,就不展开了,大家可以自己去查一下资料,或者直接参考一下下面的链接:
https://blog.csdn.net/qq_29343201/article/details/51278798
示例说明
重头戏来了,先上C源码:
#include <stdio.h>
#include <stdlib.h>
//gcc test.c -o test -g
int SumFunc(int a, int b)
{
int sum = 0;
sum = a + b;
return sum;
}
int main(int argc, char* argv[])
{
int a = 3;
int b = 4;
int sum = 0;
sum = SumFunc(a, b);
printf("sum = %d\n",sum);
return 0;
}
编译后,通过objdump看一下汇编代码:
注意AT&T和Intel的汇编的差异,后面不再说了;
开始GDB之:
前面几步没啥好说的,主要是关注RBP、RSP、RIP这3个,然后直接到关键点:
输入si[注意一定是si,不能是ni],执行单条汇编命令跳入函数,也就是执行call 0x40052d,注意RSP、RIP以及stack的变化:
发生变化的寄存器:
1、RIP
变化是正常的,RIP每一步都会变换;但是注意SumFunc里的push rbp还没有执行——这是下一个要执行的指令——那么变化了的RIP到底执行了什么?
2、RSP
原来:
现在:
——这里有点奇怪!
首先看一下RSP当前指向的是啥:
可以看到,RSP入栈了main函数里call SumFunc后的下一条指令——这就很清楚了,这是保护现场,用于在SumFunc完成调用后继续执行main流程的下一步;
所以,RSP减少了 0x7fffffffe170 - 0x7fffffffe168 = 8[这里注意不是10-8=2,而是(F+1)-8 = 8],就是RIP寄存器的大小(8字节),等于是把一条汇编指令入栈;
同时可以得到这个结论:
在汇编代码里,call 一个函数的时候,其实是调用了 push RIP;
下面继续:
从上面的截图也可以看到,进入SumFunc后,前两条汇编是:
一般进入每个函数的开始都是这两条;下面来分析一下这两条指令做了什么;
1. push rbp
很明显,就是把rbp入栈;rbp里保存的是什么呢?
rbp指向的是:
从开始到现在,rbp的值还没有变化过,用下面的图可以简单表示:
——上面这个截图有个错误,就是edi应该是占4个字节,rdi才是8字节,edi是指rdi的低32位;
在进入SumFunc时,执行第一句 push rbp前,当前的栈,以及各个寄存器的情况:
现在我们执行push rbp:
可以发现,入栈后,rbp的值没有变化,还是190,而rsp的值又减小了8——符合预期,现在的栈的情况:
在GDB调试界面上输入ni,执行mov rbp, rsp,就是把rsp的值赋给rbp,rsp的值不发生变化,而rbp从原来的190变为160:
然后继续执行,下面两句汇编是从edi和esi中获取入参,放入rbp相应的位置上,供后面相加使用:
——这里有一点需要注意:在64位汇编中,当参数少于7个时, 参数从左到右放入寄存器: rdi, rsi, rdx, rcx, r8, r9。在本例中就是:
简单来说就是把原来存储入参的寄存器的值转移到eax和edx中,然后进行add的操作;在执行完add操作后,会把sum写入到rbp-0x4中;
再执行pop rbp的操作,在上述过程中,rsp和rbp一直没有发生变化,都是160,并且rbp在rsp的栈顶:
rbp和rsp均发生了变化:
1.rsp:
pop是做出栈的动作,所以rsp应该是增加8,变为168
2.rbp:
pop rbp,对于rbp而言,应该是rbp = pop(rsp),等于是把栈顶的值赋给rbp
这里注意:0x7fffffffe160是栈顶的地址,其对应的值是0x7fffffffe190,所以pop后,rbp的值是0x7fffffffe190;
再执行ret,回到main函数内,注意rsp和rip的变化
可以看到rsp做了pop的动作,并且把pop的值赋给了rip,所以通过这里可以得到结论:
ret ====> pop rip
接下来就是调用printf函数打印的过程了,中间涉及一点,就是调用SumFunc时,返回的返回值是保存在eax里的——64位汇编里,函数返回值结果一般保存在rax或者eax(rax的低32位)中;
另外需要注意,在3和4时,rbp已经发生了变化,但对整个分析没有影响,因为结果是放在eax中的,使用eax进行函数结果的返回;
而edi中存储的则是printf函数的第一个参数:
至此,整个过程可以说告一段落;