方式一:使用gcc
gcc编译有四步走,预编译,编译,汇编,连接
使用-S编译选项
- gcc -S test.c
会在当前目录下生成test.s的文件,该文件即是相应的汇编程序
方式二:使用gdb
首先编译时要是用-g编译选项
- gcc -g ./test.c -o ./test
接着运行gdb
- gdb ./test--(可执行文件)
在gdb中使用 disassemble + frame(帧),即可查看相应代码段的汇编代码
frame通常为一个函数名。
方式三:使用objdump
命令为
- objdump -d test.o--(目标文件)
或者
- objdump -d test--(可执行文件)
一个反汇编代码解释:
(本例使用的GCC的汇编格式,这种格式叫做GAS(GNU ASsembler ,GNU汇编器) )
c语言代码如下:
------test.c-------
- #include <stdio.h>
- #include <unistd.h>
- int static_var = 5;
- int
- fun_ret_int(int a, int b, register int c)
- {
- int d=1;
- return a+b+c+d;
- }
- void fun()
- {
- int i1, i2, i3, i4,i5, i6,i7,i8,i9,i10;
- i1=1;i2=3;i3=5; i4=7;i5=9; i6=11;i7=13;i8=15;i9=17;i10=19;
- int i;
- for(i=11; i< 20;++i);
- i2 = fun_ret_int(1, 2, i1);
- }
- int main(int argc ,char *argv[])
- {
- int i =1;
- int b =2;
- argc = 3;
- char **a=argv;
- fun();
- return 0;
- }
现在编译:
gcc test.c -o test
反汇编 :
objdump -d test
我截取部分代码如下:
- 08048394 <fun_ret_int>:
- 8048394: 55 push %ebp
- 8048395: 89 e5 mov %esp,%ebp
- 8048397: 83 ec 10 sub $0x10,%esp
- 804839a: 8b 4d 10 mov 0x10(%ebp),%ecx
- 804839d: c7 45 fc 01 00 00 00 movl $0x1,-0x4(%ebp)
- 80483a4: 8b 45 0c mov 0xc(%ebp),%eax
- 80483a7: 8b 55 08 mov 0x8(%ebp),%edx
- 80483aa: 8d 04 02 lea (%edx,%eax,1),%eax
- 80483ad: 01 c8 add %ecx,%eax
- 80483af: 03 45 fc add -0x4(%ebp),%eax
- 80483b2: c9 leave
- 80483b3: c3 ret
- 080483b4 <fun>:
- 80483b4: 55 push %ebp
- 80483b5: 89 e5 mov %esp,%ebp
- 80483b7: 83 ec 3c sub $0x3c,%esp
- 80483ba: c7 45 fc 01 00 00 00 movl $0x1,-0x4(%ebp)
- 80483c1: c7 45 f8 03 00 00 00 movl $0x3,-0x8(%ebp)
- 80483c8: c7 45 f4 05 00 00 00 movl $0x5,-0xc(%ebp)
- 80483cf: c7 45 f0 07 00 00 00 movl $0x7,-0x10(%ebp)
- 80483d6: c7 45 ec 09 00 00 00 movl $0x9,-0x14(%ebp)
- 80483dd: c7 45 e8 0b 00 00 00 movl $0xb,-0x18(%ebp)
- 80483e4: c7 45 e4 0d 00 00 00 movl $0xd,-0x1c(%ebp)
- 80483eb: c7 45 e0 0f 00 00 00 movl $0xf,-0x20(%ebp)
- 80483f2: c7 45 dc 11 00 00 00 movl $0x11,-0x24(%ebp)
- 80483f9: c7 45 d8 13 00 00 00 movl $0x13,-0x28(%ebp)
- 8048400: c7 45 d4 01 00 00 00 movl $0x1,-0x2c(%ebp)
- 8048407: c7 45 d0 0b 00 00 00 movl $0xb,-0x30(%ebp)
- 804840e: eb 04 jmp 8048414 <fun+0x60>
- 8048410: 83 45 d0 01 addl $0x1,-0x30(%ebp)
- 8048414: 83 7d d0 13 cmpl $0x13,-0x30(%ebp)
- 8048418: 7e f6 jle 8048410 <fun+0x5c>
- 804841a: 8b 45 fc mov -0x4(%ebp),%eax
- 804841d: 89 44 24 08 mov %eax,0x8(%esp)
- 8048421: c7 44 24 04 02 00 00 movl $0x2,0x4(%esp)
- 8048428: 00
- 8048429: c7 04 24 01 00 00 00 movl $0x1,(%esp)
- 8048430: e8 5f ff ff ff call 8048394 <fun_ret_int>
- 8048435: 89 45 f8 mov %eax,-0x8(%ebp)
- 8048438: c9 leave
- 8048439: c3 ret
- 0804843a <main>:
- 804843a: 55 push %ebp
- 804843b: 89 e5 mov %esp,%ebp
- 804843d: 83 ec 10 sub $0x10,%esp
- 8048440: c7 45 fc 01 00 00 00 movl $0x1,-0x4(%ebp)
- 8048447: c7 45 f8 02 00 00 00 movl $0x2,-0x8(%ebp)
- 804844e: c7 45 08 03 00 00 00 movl $0x3,0x8(%ebp)
- 8048455: 8b 45 0c mov 0xc(%ebp),%eax
- 8048458: 89 45 f4 mov %eax,-0xc(%ebp)
- 804845b: e8 54 ff ff ff call 80483b4 <fun>
- 8048460: b8 00 00 00 00 mov $0x0,%eax
- 8048465: c9 leave
- 8048466: c3 ret
- 8048467: 90 nop
- 8048468: 90 nop
- 8048469: 90 nop
- 804846a: 90 nop
- 804846b: 90 nop
- 804846c: 90 nop
- 804846d: 90 nop
- 804846e: 90 nop
- 804846f: 90 nop
现在尝试对其中的一些语句进行分析:
在每个函数的开始部分都是:
- push %ebp
- mov %esp,%ebp
- sub $***,%esp
%ebp---是帧寄存器,在函数中就是函数的基址寄存器,指向一个函数的栈底(帧底)。
%esp---是栈寄存器,相当于是整个程序的基址寄存器,始终指向栈顶。
push---入栈操作。
mov ---移动
sub ---减法
第一句话 push %ebp 的意思是%ebp入栈,此时的%ebp保存的是上一个函数的帧起始地址,也即调用该函数的地址。
把%ebp压栈,保存起来,以便返回。
第二句 mov %esp,%ebp 的意思是 把%esp赋值给%ebp,%esp保存的是当前程序的栈顶,也即该函数所占用内存的起始地址。
把%esp赋值给%ebp,也就把%ebp设置成了当前函数的帧起始地址。
第三句话sub $***,%esp,并不会在每个程序中都会出现。可以尝试一下,如果一个函数没有任何局部变量,那么反汇编这句话也就
不会出来。这句话的意思是,把%esp减去一个数。我们知道栈空间是由高到底发展的,所以%esp++,相当于%esp=%esp-1。因为调用了 新函数,而且该函数有局部变量,那么栈空间就变大了,所以要扩展栈空间,也即是修改%esp,让其指向更低的地址。而让%esp减去多少呢?这要看函数占 用多少空间,于其中的局部变量有关,以及他将调用的函数参数有关。其并不计算其参数所占的空间,其参数所占的空间要算在调用它的函数中。
下面来看看参数的压栈顺序;
函数fun中语句:
- i2 = fun_ret_int(1, 2, i1);
所对应的汇编如下:
- 804841a: 8b 45 fc mov -0x4(%ebp),%eax
- 804841d: 89 44 24 08 mov %eax,0x8(%esp)
- 8048421: c7 44 24 04 02 00 00 movl $0x2,0x4(%esp)
- 8048428: 00
- 8048429: c7 04 24 01 00 00 00 movl $0x1,(%esp)
- 8048430: e8 5f ff ff ff call 8048394 <fun_ret_int>
从这个汇编中可以看出函数参数的入栈顺序是自左往右的。
mov -0x4(%ebp),%eax 是把i1 放到%eax 寄存器中
mov %eax,0x8(%esp) 是把%eax压栈,所以这句话是把i1放在距离%esp为8byte的地方,即8~12byte存放的是一个i1(int)
movl $0x2,0x4(%esp) 距%esp 4~8byte存放的是2(int)
movl $0x1,(%esp) 距%esp 0~4byte存放的是1(int)
因为栈是由高到低的,且%esp始终指向栈顶。所以看出入栈顺序是i1,2,1。正好与c文件中fun函数的参数顺序相反。
自右向左压栈有什么好处呢?第一,由于栈是FILO,所以反方向入栈,那么第一个参数也就距离%esp越近。每次取参数时也就很方便,不用把所有参数占用的空间都计算出来,然后在取。第二,当传递的参数过多时,每次都从栈顶计算,取适当位置的参数,其他便可忽略。