反汇编程序分析计算机是如何工作的
--刘君红+ 原创作品转载请注明出处 + 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000
一、经过编译得到源代码的汇编码
源代码如下:
int g(int x)
{
return x + 3;
}
int f(int x)
{
return g(x);
}
int main(void)
{
return f(8) + 1;
}
保存在main.c文件中;
经过gcc -S -o main.s main.c -m32命令行编译之后得到汇编码(去掉链接时代码)如下:
g:
pushl %ebp
movl %esp, %ebp
movl 8(%ebp), %eax
addl $3, %eax
popl %ebp
ret
f:
pushl %ebp
movl %esp, %ebp
subl $4, %esp
movl 8(%ebp), %eax
movl %eax, (%esp)
call g
leave
ret
main:
pushl %ebp
movl %esp, %ebp
subl $4, %esp
movl $8, (%esp)
call f
addl $1, %eax
leave
ret
二、分析汇编代码
1、程序从main开始执行
如图所示堆栈:(从上到下分别为0号、1号、2号、3号、4号、5号内存单元,每个内存单元4个字节,内存地址从上到下逐渐减小)
假设一开始ebp指向0号内存单元,esp也指向0号内存单元,下图中每个单元代表内存4个字节
第一条指令pushl %ebp 是将ebp中的内容压栈,执行的操作是将esp向下移动4个字节后,将ebp寄存器内容压栈,此时ebp寄存器指向1号内存单元;
0: (ebp) 0 |
1: |
2: |
3: |
4: |
5: |
第二条指令movl %esp, %ebp是将esp寄存器存的内容赋给ebp寄存器,即esp和ebp寄存器都指向1号内存单元;
第三条指令 subl $4, %esp 是将esp寄存器内容减4,即esp指向2号内存单元;
第四条指令movl $8, (%esp) 是将立即数8存入esp寄存器所指向的内存单元,此时栈的内容如下:(ebp指向1号内存单元,esp指向2号内存单元)
0: (ebp) 0 |
1: 8 |
2: (eip)main中第6行代码位置 |
3: |
4: |
5: |
第五条指令call f,执行的操作有两个:pushl %eip ;movl (f) %eip;即先将eip内容压栈,此时ebp指向1号内存单元,esp指向3号内存单元;然后将f符号的地址赋给eip寄存器,即程序开始执行f符号处的代码;
2、程序从符号f处开始执行
F处第一条指令为pushl %ebp,执行的操作为esp向下移动一个内存单元,将ebp寄存器内容压栈,此时esp指向4号内存单元,ebp指向1号内存单元,内存栈的情况为:
0: (ebp) 0 |
1: 8 |
2: (eip)main中第6行代码位置 |
3: (ebp)1 |
4: |
5: |
第二条指令为movl %esp, %ebp,将esp寄存器内容赋给ebp,此时ebp指向4号内存单元,esp也指向4号内存单元;
第三条指令为subl $4, %esp,将esp向下移动一个内存单元,即esp指向5号内存单元,ebp指向4号内存单元;
第四条指令为 movl 8(%ebp), %eax,将ebp内容加8所指向内存单元的内容赋给eax寄存器,即将1号内存单元内容立即数8赋给寄存器eax;
第五条指令 movl %eax, (%esp),将eax寄存器内容赋给esp所指向的内存单元,即
0: (ebp) 0 |
1: 8 |
2: (eip)main中第6行代码位置 |
3: (ebp)1 |
4: 8 |
5: |
第六条指令 call g,执行的操作为 pushl %eip,movl (g) %eip;首先将eip内容压栈,此时esp指向6号内存单元,ebp指向4号内存单元,内存栈如下:
0: (ebp) 0 |
1: 8 |
2: (eip)main中第6行代码位置 |
3: (ebp)1 |
4: 8 |
5: (eip)f函数中的第7行代码位置 |
6: |
然后将符号g所在的内存地址赋给eip寄存器。
3、程序转向g函数处执行
G函数第一条指令为 pushl %ebp, 将ebp内容压栈,此时esp指向7号内存单元,ebp指向4号内存单元,内存栈情况为:
0: (ebp) 0 |
1: 8 |
2: (eip)main中第6行代码位置 |
3: (ebp)1 |
4: 8 |
5: (eip)f函数中的第7行代码位置 |
6: (ebp)4 |
7: |
第二条指令为 movl %esp, %ebp, 将esp内容赋给ebp,此时ebp指向7号内存单元,esp也指向7号内存单元;
第三条指令 movl 8(%ebp), %eax, 将ebp内容加8所指向的内存单元的数据赋给eax,即将4号内存单元中的立即数8赋给eax;
第四条指令 addl $3, %eax, 将立即数3加到eax寄存器,eax寄存器内容变为11;
第五条指令 popl %ebp,将栈顶指针内容popl出赋给ebp,此时ebp指向4号内存单元,esp指向6号内存单元;
0: (ebp) 0 |
1: 8 |
2: (eip)main中第6行代码位置 |
3: (ebp)1 |
4: 8 |
5: (eip)f函数中的第7行代码位置 |
6: |
第六条指令 ret,执行的操作为popl %eip,即将栈顶指针所指向的内容赋给eip寄存器,此时eip指向f函数中的第7行代码位置,esp指向5号内存单元,ebp指向4号内存单元;
0: (ebp) 0 |
1: 8 |
2: (eip)main中第6行代码位置 |
3: (ebp)1 |
4: 8 |
5: |
:4、程序返回f函数第7行代码位置执行
此时执行f函数中第七行代码leave,执行的操作为 movl %ebp, %esp;popl %ebp。即将ebp寄存器内容赋给esp,此时esp指向4号内存单元,ebp也指向4号内存单元,然后将栈顶指针所指向的内存单元的内容popl出赋给ebp,此时ebp指向1号内存单元,esp指向3号内存单元;
0: (ebp) 0 |
1: 8 |
2: (eip)main中第6行代码位置 |
3: |
接着执行第八行指令 ret,执行的操作为:popl %eip, 即将栈顶指针的内容popl出赋给eip,此时eip指向main函数中第6行代码位置,esp指向2号内存单元,ebp指向1号内存单元;
0: (ebp) 0 |
1: 8 |
2: |
5、程序返回main函数中第6行代码位置执行
执行main函数中第六行指令addl $1, %eax, 将立即数加到eax上,此时eax寄存器的值是12;
执行第七行指令 leave,执行的操作为movl %ebp, %esp;popl%ebp。即此时esp指向0号内存单元,ebp也指向0号内存单元,回到初始栈的情况;
执行第八行指令 ret, 执行操作为 popl %eip;
三、总结
1、
-
存储程序计算机工作模型,计算机系统最最基础性的逻辑结构;
-
函数调用堆栈,高级语言得以运行的基础,只有机器语言和汇编语言的时候堆栈机制对于计算机来说并不那么重要,但有了高级语言及函数,堆栈成为了计算机的基础功能;
-
enter
-
pushl %ebp
-
movl %esp,%ebp
-
-
leave
-
movl %ebp,%esp
-
popl %ebp
-
-
函数参数传递机制和局部变量存储
-
-
中断,多道程序操作系统的基点,没有中断机制程序只能从头一直运行结束才有可能开始运行其他程序。
2、从以上可以看出堆栈的两个作用
- 保存程序上下文,即程序从一个函数到另一个函数跳转之前执行到的位置及寄存器的值等;
- 传递函数参数