林光伟 原创作品转载请注明出处
《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000 ”
实验内容:
将下面的C程序反汇编,并分析其运行及堆栈变化过程
int g(int x) { return x + 3; } int f(int x) { return g(x); } int main(void) { return f(8) + 1; }
实验环境:
gcc version 4.4.7 (Ubuntu/Linaro 4.4.7-8ubuntu1) , ubuntu 14.04 64bit
实验步骤
1、输入 ctrl + t 打开命令终端, 输入gedit main.c,打开编辑器,输入上述程序, 并保存退出。

在命令行中输入
gcc -S -o main.s main.c -m32
由于实验平台是64位机,gcc选项"-m32"表示将程序编译成32位的指令格式,如果在32位机,"-m32"可以不用加上去。
于是就可以看到在当前的目录下便生成了一个main.s的汇编程序。

main.s 的内容如下
.file "main.c" .text .globl g .type g, @function g: pushl %ebp movl %esp, %ebp movl 8(%ebp), %eax addl $3, %eax popl %ebp ret .size g, .-g .globl f .type f, @function f: pushl %ebp movl %esp, %ebp subl $4, %esp movl 8(%ebp), %eax movl %eax, (%esp) call g leave ret .size f, .-f .globl main .type main, @function main: pushl %ebp movl %esp, %ebp subl $4, %esp movl $8, (%esp) call f addl $1, %eax leave ret .size main, .-main .ident "GCC: (Ubuntu/Linaro 4.4.7-8ubuntu1) 4.4.7" .section .note.GNU-stack,"",@progbits
为了方便阅读,去掉以"."开头的内容,就变成
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
可以看到main.s 的3个函数与main.c里的3个函数是一一对应的。程序从main处开始执行
int main(void)对应着:
pushl %ebp
movl %esp, %ebp
ESP就是一直指向栈顶的指针,而EBP只是存取某时刻的栈顶指针,以方便对栈的操作,如获取函数参数、局部变量等。执行pushl,esp会自动减4,而执行popl,esp则会自动加4,上面两条指命执行之前的情况是是
|
esp-> ebp-> |
|
(栈顶)
(表1)
pushl %ebp,ebp的值压入栈顶,esp -= 4,栈顶下移。 movl %esp, %ebp,
|
esp-> ebp-> |
old ebp |
(栈顶)
(表2)
return f(8) + 1;先将立即8当成参数传入函数f
subl $4, %esp;栈顶下移
movl $8, (%esp);将立即数8压入栈顶
| ebp-> | old ebp |
| esp-> | 8 |
(栈顶)
(表3)
然手调用f函数
call f
call 函数可以分解成
A. 将EIP寄存器的当前值压入到栈中供返回时使用
B. 将被调用过程的偏移(相对于当前段)加载到EIP寄存器中
C. 开始执行被调用过程
| ebp-> | old epb |
| 8 | |
| esp-> | old epi |
(栈顶)
(表4)
然后程序就进入到f函数
f: pushl %ebp ;位置1 movl %esp, %ebp ;位置2 subl $4, %esp ;位置3 movl 8(%ebp), %eax ;位置4 movl %eax, (%esp) ;位置5 call g leave ;位置A ret
执行到f函数位置3的地方后,
| ebp-> | old epb |
| 8 | |
| old epi | |
|
ebp-> |
old2 epb |
| esp-> |
(栈顶)
(表5)
位置4, movl 8(%ebp), %eax ,变址寻址,将ebp-8地址的数存入eax ,这个地址上存数值为8。
位置5将eax的值(8)送入栈顶。f函数就是将收到的参数8传给g,再将g返回的参数返回,实际上是什么也没有做。
然后进入了g函数:
g: pushl %ebp movl %esp, %ebp movl 8(%ebp), %eax ;位置6 addl $3, %eax ;位置7 popl %ebp ;位置8 ret ;位置9
位置6, 将f函数传入的参数8移到eax
位置7,将立即数3与eax进行运算,实际eax就得到3+8的值
位置8,pupl %epb,从表5上分析,将esp的值加4,esp所指向old2 epb,将old2 epb传给epb,于是epb的指向又回到了old epb。
位置9,ret 相当于pop %eip,于是esp值又加4, esp指向 old eip,将old eip传给eip,也就是说,程序反回到了f leave这条指令上。
leave ret
相当于
movl %ebp, %esp
pob %ebp
pop %eip
cpu就回到了main的 addl $1, %eax,eax 保存着上次运算的结果,然后与立即数加1,完成运算。
浙公网安备 33010602011771号