作者:耿介之
原创作品转载请注明出处 :http://blog.csdn.net/jiezhi2013/article/details/44132493
《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000
1.C程序代码
首先我们写一段小程序:
main.c
int g(int x)
{
return x + 5;
}
int f(int x)
{
return g(x) + 1;
}
int main(void)
{
return f(2) + 3;
}
2.反汇编
gcc -S -o main.s main.c -m32
得到如下文件
main.s
.file "main.c"
.text
.globl g
.type g, @function
g:
.LFB0:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
movl 8(%ebp), %eax
addl $5, %eax
popl %ebp
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
.LFE0:
.size g, .-g
.globl f
.type f, @function
f:
.LFB1:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
subl $4, %esp
movl 8(%ebp), %eax
movl %eax, (%esp)
call g
addl $1, %eax
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
.LFE1:
.size f, .-f
.globl main
.type main, @function
main:
.LFB2:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
subl $4, %esp
movl $2, (%esp)
call f
addl $3, %eax
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
.LFE2:
.size main, .-main
.ident "GCC: (Ubuntu 4.8.2-19ubuntu1) 4.8.2"
.section .note.GNU-stack,"",@progbits
g:
pushl %ebp
movl %esp, %ebp
movl 8(%ebp), %eax
addl $5, %eax
popl %ebp
ret
f:
pushl %ebp
movl %esp, %ebp
subl $4, %esp
movl 8(%ebp), %eax
movl %eax, (%esp)
call g
addl $1, %eax
leave
ret
main:
pushl %ebp
movl %esp, %ebp
subl $4, %esp
movl $2, (%esp)
call f
addl $3, %eax
leave
ret
3.汇编分析
分析当然从main函数开始:
- pushl %ebp:将当前ebp(堆栈基指针)压栈,同时esp(堆栈顶指针)向下移动1个单位(32位下即4个字节);
- movl %esp, %ebp:将esp的值赋给ebp;
- subl $4, %esp:将esp下移1个单位(堆栈是从上而下的,所以减去4实则为栈顶加1个单位);
- movl $2, (%esp):将值2压栈;
- call f:call可以分解为两个动作,pushl %eip(*)和movl $24, %eip(*),这里的$24其实就是把下一行的地址复制给eip(指令寄存器,存放当前指令的下一条指令的地址),ebp下移一个单位,下面从f函数执行:
- 其中 pushl %ebp,movl%esp, %ebp,subl $4, %esp三条指令和前面的1,2,3是一样的,略过;
- movl 8(%ebp), %eax:ebp+8即为ebp往上两个单位的位置,我们知道其实指向第4步压入的值2并将其复制为eax(累加器);
- movl %eax, (%esp):将eax赋值给esp;
- call g:同5;
- g函数中的pushl %ebp,movl%esp, %ebp, movl8(%ebp), %eax同上;
- addl $5, %eax:把5加给eax,通过上面我们知道eax现在的值是2,所以现在eax值为2+5 = 7;
- popl %ebp:上一个压入ebp是在g函数的开始,所以这里相当于把ebp指向进入g函数之前的位置了,esp也相应的移到上个单位;
- ret:即return,相当于popl %eip,而上一个eip是在进入g函数的时候压入的(即call g),至此g函数执行完,继续回到f函数执行,(所计算的值7还保存在eax里)。
- addl $1, %eax:再把eax加1,此时eax的值为8;
- leave:相当于movl $esp, %eip和popl %ebp两条指令。此时esp指向main函数的call f的下一条指令,ebp指向main函数的ebp了。
- ret:同上
- addl $3, %eax:eap的值为11了。
- leave:同上;
- ret:同上。
至此,我们把这一小段反汇编代码看完了。
4.总结
其实我们可以看到,这段C程序在计算机内的执行,其实都是建立在对堆栈操作的基础上的。而堆栈的操作基本也是对栈底、栈顶的操作,所以理解了上面诸如,push、pop、move等操作后,我们就可以对一些简单的程序进行汇编分析了。