1.简单的程序
#include <stdio.h>
int fun(int a,int b)
{
int j =0;
j = a+b;
return j;
}
int main()
{
int x = 0x5;
int y = 0x6;
int z = 0x8;
z = fun(x,y);
printf("hello\n");
return 0;
}
如何查看这个简单的程序是如何执行下来的呢?
简单的c语言程序可以直接了当的看完,具体调试时我们使用命令
gcc test.c -g
此时生成的二进制机器码文件,此时的文件生成的代码是给linux看的,所以我们看起来很难理解,所以我们退而求其次,我们求汇编成的代码,以 .s 结尾
gcc test.c -S -o test.s
此时生成 a.out 文件,我们使用 gdb程序来运行 a.out
gdb遵循GUN GPL 原则开源
为了方便观看程序执行过程,所以我们需要对程序进行断点处理
命令 b 就是需要打断点的位置
成功打入断点,这里在main函数之前发生了很多事情,但是我们并不用关心这些。
之后使用命令 r 开跑(run),到main停止
注:
1.了解单步跟踪和单步跟踪进入的区别,单步跟踪即一步一步的运行程序,逐语句是一个语句一个语句执行,逐过程是一个过程的执行,当下一步为调用函数时,我们需要进入到函数内部,那就是单步跟踪进入,看函数是如何一步一步执行的,而不是跳过函数调用过程直接给出结果,我们该如何做呢?
在gdb中,需要使用命令 s 和 n。
s即 执行一行代码,如果该行代码中有函数调用,那么进入该函数;
n即 执行一行代码,一并执行包含的函数调用。
2.了解p命令和寄存器rbp,rsp
p命令即为打印,打印出rsp,rbp的地址以及我们下一个执行语句的地址
我们可以直接发现rsp地址高于main的地址,rsp就是指向栈里的地址,代码段在低地址。
当我们执行s 时,发现rsp,rbp位置并没有发生改变,我们使用x/16x打印十六进制的值,打印十六个,从rsp开始打印(每一段为四个字节即64位下的int)
我们发现地址是从rsp开始向上打印
当我们再次执行s时
所执行的为红色箭头所指那一步,push 了rbp
此时我们查看地址发现rsp地址低了八位,就是我们压入栈rbp的地址
我们再次进行 s 操作,此时我们执行的是上面的 movq 这段代码,也就是将rbp指针移动到rsp的位置
此时我们的栈顶指针等于了栈底指针,也就是空栈
再次s,执行上面的subq 语句
我们发现rsp动了,rbp没动,跟上面的地址一样,动了16个位置,就是分配给main的栈针
再次s,执行,将5放在rbp偏移12的位置
也就是这个位置
再次s,执行这一段,把6存放在rbp偏移8的位置上
再次s,执行下面这一段
我们这里的 5 6 8就是我们上面c 语言代码中定义的 x y z。
接下来我们应该执行到了上面代码中main的调用函数的位置
再次s,将 6 也就是y,放在了edx的寄存器中
我们再次s,执行下面第一行代码,也就是x=5的位置搬到寄存器中
再次s,执行下面第一行,做了内存的交接
再次s,此时才是 call fun
此时我们看一下rsp和rbp的位置,发现位置没有改变
此时我们s,进入到fun函数里面我们发现rsp变了位置,少了2也就是8个位置,这个8就是地址
这个地址其实是我们执行的fun函数下一行的语句的地址,不然我们怎么知道函数执行完返回到哪里,我们后面的printf该如何进行呢,就是把后面语句的地址一并压入到了栈中
再次s,地址不变,执行了endbr64,意思是上个栈结束了,到了新的fun里面
再次s之后,我们发现地址又少了8
我们看一下rsp,其实就是我们把上一个main里面的栈底指针地址拿了过来,方便我们返回函数时,回到main里面的位置,这是我们回去的地址
我们再次s,得到rsp和rbp地址一样了,也就是执行了
相当于我们main函数里面的将栈置空
再次s执行
再次s执行
再次s执行
就是内存的偏移,和前面一样
这里的-20 和 -24 的位置就是 5 6 的位置,也就是我们传进来的 xy。
再次 s ,执行addl 相加
s之后,我们发现eax中的值变成了两者的和,这里的 -4 位置就是 j 的位置
s后,这个汇编动作其实是多余的,提升效率可以删掉
我们s 之后再次查看地址,发现并没有改变,rsp rmp 都是0
再次s之后,fun调用结束
查看地址rsp rmp 我们发现rsp地址高了 8位,也就是我们之前存进栈的main地址,并赋值给了rbp,所以rbp又回到了调用之前的main 的栈底地址位置(这就是之前我们为什么要把main的栈底地址压入到fun的栈中)
再次s,rbp归位了所以不动,rsp栈顶继续压,移动了8位置,并且这里的movl执行就是返回值给main里面的z,接下来执行printf,程序结束。