以下是在读《深入理解计算机系统》前面的章节“程序的机器级表示”时,自己动手在linux上使用了gdb对一个简单的C程序进行反汇编,通过不懈的努力终于查清楚弄明白了绝大多数的语句。且均以注释的形式列在汇编语句后面。
所有这些注释大概花了整整一天时间,不过还好,感觉对于C程序的机器级实现终于算是有了一个比较透彻的理解,对于以前编译出现的有些bug的原因有了一种原来如此的感慨。感觉这段代码及注释对自己或者大家都可能有用,所以稍作整理放于此:
说明:
反汇编指令使用的是:gdb disas [function-name]。另外也可以使用:objdump -d/-D obj-name对整个程序进行反汇编(通过这个反汇编,你可以发现main函数并非一个程序的入口而是__start函数!)。
程序很简单,就两个函数:sum和main,源码如下:
#include
int sum(int x, int y)
{
int accum = 0;
int t;
t = x + y;
accum += t;
return accum;
}
int main( int argc, char **argv)
{
int x = 1, y = 2;
int result = sum( x, y );
printf("\n\n result = %d \n\n", result);
return 0;
}
编译源程序,不使用优化选项并反汇编分析:
gcc -o test test.c得到test
gdb test
(gdb) disas sum
(gdb) disas main
查看反汇编后的代码
Dump of assembler code for function sum:
0x08048354 : push %ebp //esp <- esp-4
0x08048355 : mov %esp,%ebp
0x08048357 : sub $0x10,%esp
0x0804835a : movl $0x0,0xfffffff8(%ebp) //accum = 0
0x08048361 : mov 0xc(%ebp),%eax //gotcha! ^_^
0x08048364 : add 0x8(%ebp),%eax // x + y
0x08048367 : mov %eax,0xfffffffc(%ebp) // t = x + y
0x0804836a : mov 0xfffffffc(%ebp),%eax
0x0804836d : add %eax,0xfffffff8(%ebp) // accum = accum + t
0x08048370 : mov 0xfffffff8(%ebp),%eax // 返回值是放在eax中的
0x08048373 : leave //恢复父函数的堆栈框指针:esp<-ebp, pop ebp,在地址上的表现就是堆栈顶底均回到之前的地方!
0x08048374 : ret //函数返回,回到上级调用:pop eip?
main函数反汇编结果:
0x08048375 : lea 0x4(%esp),%ecx
0x08048379 : and $0xfffffff0,%esp //使栈地址16字节对齐!这里也说明了栈向下(低地址)生长的优点了:地址对齐操作总是是esp指向当前位置的下方!(lihux自己悟出的^_^)
0x0804837c : pushl 0xfffffffc(%ecx)
0x0804837f : push %ebp //保存main之前函数的栈基址
//pushl push都使用了的原因:当要压栈的对象已经确定(也就是说已经知道是字节、字或者双字),那么使用push就不会产生歧义,也就是说汇编器可以自己判断自己要操作的是什么长度的操作对象;但是当汇编器不能自己判断操作对象长度时,就需要使用pushl之类的指令来指明操作对象长度(类似的还有mov,movl;lea,leal等);
上面的%ebp指的是esp寄存器吧,这是一个32位的寄存器,汇编器是知道这个寄存器存放的是dworld,不需要显式的指明也没有歧义;而 0xfffffffc(%ecx)是使用基址寻址方式指向的一个内存空间,内存是连续的,汇编器不能仅仅根据一个内存地址就判断出那里存放的数据的长度,所以直接这样写就会产生歧义,所以要使用pushl之类的指令来指明要操作的数据的长度;
0x08048380 : mov %esp,%ebp //在main之前函数的栈下方建main的栈,基址给ebp
0x08048382 : push %ecx
0x08048383 : sub $0x24,%esp //栈顶指针下移24,以留作main函数用?是的!^_^。关于栈对齐问题:可以看到,在执行时完毕之后栈顶指针esp已经16字节对齐,而在执行到当前指令前,又进行了三次压栈操作,每次四字节,共12字节,所以这里用0x24,这样,又重新使esp16字节对齐了(同时还必须保证分配的空间不小于main函数需要存放局部变量的空间^_^)。
0x08048386 : movl $0x1,0xfffffff0(%ebp) //主函数实参存放于内存的最高处,x = 1
0x0804838d : movl $0x2,0xfffffff4(%ebp) //y = 2
0x08048394 : mov 0xfffffff4(%ebp),%eax
0x08048397 : mov %eax,0x4(%esp) //x的形参存入Mem本地空间,供子函数调用
0x0804839b : mov 0xfffffff0(%ebp),%eax
0x0804839e : mov %eax,(%esp) //由此可以看出:调用函数sum之前,需先将形参放到栈顶!
0x080483a1 : call 0x8048354 //call调用,先将下一条命令的地址压入堆栈,所以esp<- esp-4
0x080483a6 : mov %eax,0xfffffff8(%ebp) //result = sum(x,y),返回值在eax中
0x080483a9 : mov 0xfffffff8(%ebp),%eax
0x080483ac : mov %eax,0x4(%esp)
0x080483b0 : movl $0x80484a0,(%esp) //字符串地址
0x080483b7 : call 0x8048298 <>
0x080483bc : mov $0x0,%eax
0x080483c1 : add $0x24,%esp //?何为
0x080483c4 : pop %ecx
0x080483c5 : pop %ebp
0x080483c6 : lea 0xfffffffc(%ecx),%esp
0x080483c9 : ret
0x080483ca : nop
0x080483cb : nop
0x080483cc : nop
0x080483cd : nop
0x080483ce : nop
0x080483cf : nop
编译源程序,使用 -O1 优化选项并反汇编分析:
使用 -O1优化后的程序反汇编后的结果:可见优化主要在减少了存取内存的次数,节省了内存空间
Dump of assembler code for function sum:
0x08048354 : push %ebp
0x08048355 : mov %esp,%ebp
0x08048357 : mov 0xc(%ebp),%eax
0x0804835a : add 0x8(%ebp),%eax
0x0804835d : pop %ebp
0x0804835e : ret
End of assembler dump.
Dump of assembler code for function main:
0x0804835f : lea 0x4(%esp),%ecx
0x08048363 : and $0xfffffff0,%esp
0x08048366 : pushl 0xfffffffc(%ecx)
0x08048369 : push %ebp
0x0804836a : mov %esp,%ebp
0x0804836c : push %ecx
0x0804836d : sub $0x14,%esp //可见优化后的程序自身预留空间也减少了。
0x08048370 : movl $0x2,0x4(%esp)
0x08048378 : movl $0x1,(%esp)
0x0804837f : call 0x8048354
0x08048384 : mov %eax,0x4(%esp)
0x08048388 : movl $0x8048480,(%esp)
0x0804838f : call 0x8048298 <>
0x08048394 : mov $0x0,%eax
0x08048399 : add $0x14,%esp
0x0804839c : pop %ecx
0x0804839d : pop %ebp
0x0804839e : lea 0xfffffffc(%ecx),%esp
0x080483a1 : ret
0x080483a2 : nop
0x080483a3 : nop
0x080483a4 : nop
编译源程序,使用优化选项 -O2并反汇编分析:
使用 -O2优化后的程序反汇编后的结果:可见较-O1级优化又有大幅的缩短(代码长度)
Dump of assembler code for function sum:
0x08048410 <+0>: push %ebp
0x08048411 <+1>: mov %esp,%ebp
0x08048413 <+3>: mov 0xc(%ebp),%eax
0x08048416 <+6>: add 0x8(%ebp),%eax
0x08048419 <+9>: pop %ebp
0x0804841a <+10>: ret
End of assembler dump.
Dump of assembler code for function main:
0x08048420 <+0>: push %ebp
0x08048421 <+1>: mov %esp,%ebp
0x08048423 <+3>: and $0xfffffff0,%esp
0x08048426 <+6>: sub $0x10,%esp
0x08048429 <+9>: movl $0x3,0x8(%esp) //我晕:这优化的也太厉害了吧:直接计算出了1+2=3!
0x08048431 <+17>: movl $0x8048510,0x4(%esp)
0x08048439 <+25>: movl $0x1,(%esp)
0x08048440 <+32>: call 0x804832c <>
0x08048445 <+37>: xor %eax,%eax //因为IA32中xor 比 mov 速度快!
0x08048447 <+39>: leave
0x08048448 <+40>: ret
转自:http://lib.csdn.net/article/c/24860