要查看目标代码(.o文件),最经常使用的使用反汇编器。在Linux中是命令objdump -d file.o能够调用程序OBJDUMP充当这个角色。可是它产生的相似汇编代码格式的文本和由gcc生产的汇编代码的字节序列有细微差异,好比前者省略了表示大小的后缀。函数
数据格式oop
Intel一般用”字“来表示16位的数据类型,所以,32位数为”双字"。注意:虽然汇编代码中,都是使用后缀“l"表示4字节的整数和8字节的双精度浮点数,可是不存在歧义,由于浮点数使用的是彻底不一样的指令的寄存器。测试
在IA32的CPU中有8个32位的寄存器。大多数指令不以固定的寄存器做为源/目标寄存器。不过,%ebp(Extended Base Pointer)和%esp(Extended Stack Pointer,栈顶指针)保存着指向程序栈(在IA32中,程序栈被放在存储器的某一个区域中并向下增加)的重要位置的指针,按照惯例,%eax(Extended Accumulator Register)一般用来存储函数返回值和累加器。此外,咱们还能够单独地读取某些寄存器的低位字节。好比字操做指令能够只读写寄存器的低16位,其他的字节不会改变。编码
简单介绍MOV类指令spa
movl | 传送双字 |
movwl | 将作了符号扩展的字传送到双字 |
movwl | 将作了零扩展的字传送到双字 |
pushl S | R[%esp] <--- R[%esp] + 4翻译 M[R[%esp] <--- S指针 |
popl D | D <--- M[R[%esp]];blog R[%esp] <--- R[%esp] + 4get |
算术和逻辑操做编译器
leal(load effective address)最后的”l"比较迷惑人,看起来像是处理双字大小的,然而实际上leal指令没有大小变种,不像add类,mov类指令。leal其实是movl的变种,指令形式是从存储器读数据到寄存器,如,可是并无真正引用存储器,它只是计算了一下地址,如"leal 7(%edx,%edx,4), %eax",若%edx存储的是x,则就将寄存器%eax设置为x+4x+7=5x+7。形式化能够写做:leal S, D意为D<---&S,加载有效地址。
SAL k, D | 左移 |
SHL k, D | 左移(等价于SAL) |
SAR k, D | 算术右移 |
SHR k, D | 逻辑右移 |
控制
条件码
除了整数寄存器,CPU还维护了一组单个位的条件码寄存器,他们描述了最近的算术或逻辑运算操做的属性。咱们能够经过检测这些寄存器的值来执行条件分支指令。
CF:进位标志。最近的操做使最高位产生了进位。能够用来检查无符号操做数的溢出。
ZF:零标志。最近的操做得出的结果为0。
SF:符号标志。最近的操做获得的结果为负数。
OF:溢出标志。最近的操做致使一个补码的溢出——正溢出或负溢出。
假设执行了一个加法运算t=a+b(t,a,b都是整型),能够根据以下表达式来设置条件码:
CF | (unsigned) t < (unsigned) a | 无符号溢出 |
ZF | (t == 0) | 零 |
SF | (t < 0) | 负数 |
OF | (a < 0 == b < 0) && (t < 0 != a < 0) | 有符号溢出 |
注意:leal不改变任何条件码。对于XOR,进位标志和溢出标志会设置为0。对于移位操做,进位操做会被设置为最后一个被移出的位,溢出标志设置为0。这里强调一下CMP和TEST指令。CMP和SUB指令的行为是同样的(CMP S2, S1: S1 - S2),而CMP只改变条件码,不更新目标寄存器。当两个操做数相等时,ZF会被设置为0,其余标志则能够用来判断两个数的大小。TEST和AND(按位与&)指令的行为是同样的,而TEST只改变条件码。当两个操做数是同样的,如指令testl &eax, %eax一般被用来检查%eax是正数、负数仍是零。然而条件码一般不会直接读取。咱们经过SET类指令针对条件码的不一样组合而设置值。SET类指令的操做数只能是单字节寄存器或一个字节的存储器位置。
JMP类指令当执行于PC(programming counter)寻址时,程序计数器的值是跳转指令后面的那条指令的地址,而不是跳转指令自己的地址。好比
jb指令的跳转目标是0x8048340,其对应的目标编码为72 0xe7(72是jb指令的表示,0xe7是-25的补码表示)。所以0x8048359-25=0x8048340。
条件分支
C语言中的if-else语句的通用形式模板:
if(test-expr)
then-statement
else
else-statement
对于这种通用形式,汇编实现一般会使用下面这种形式
t = test-expr
if(!t)
goto false;
then-statement
goto done;
false:
else-statementdone:
举例说明,如下是一段C语言对应的反汇编代码
x at %ebp+8, y at %ebp+12 | |
1 movl 8(%ebp), %eax | 将地址为%ebp+8的值转移到%eax(x)中 |
2 movl 12(%ebp), %edx | 将地址为%ebp+12的值转移到%edx(y)中 |
3 cmpl $-3, %eax | 将x与-3进行比较 |
4 jge .L2 | 若x大于或等于-3,则跳转到L2 |
5 cmpl %edx, %eax | 将x与y进行比较 |
6 jle .L3 | 若x小于或等于y,则跳转到L3 |
7 imull %edx, %eax | %eax = %eax * % edx (x * y) |
8 jmp .L4 | 无条件跳转到L4 |
9 .L3: | |
10 leal (%edx, %eax), %eax | %eax = %eax + %edx (x + y) |
11 jmp .L4 | 无条件跳转到L4 |
12 .L2: | |
13 cmpl $2, %eax | 将x与2进行比较 |
14 jg .L5 | 若x大于2,则跳转到L5 |
15 xorl %edx, %eax | %eax = %eax ^ %edx (x ^ y) |
16 jmp .L4 | 无条件跳转到L4 |
17 .L5 | |
18 subl %edx, %eax | %eax = %eax - %edx (x - y) |
19 .L4 |
经过分析汇编代码,咱们能够很容易完成C代码的填空。C代码的第一个if对应于汇编语言的第3行,并注意将条件取反,所以为x<-3,此外,易得汇编语言的L2对应于C代码的八、9行。所以C语言的第8行应填写x<=2,第9行填写x^y,第2行填写x-y。同理,C代码的第4行应该填写y<x,第5行填写x*y,第7行填写x+y。注意:这里的初始化表达式(C代码的第2行)向下移了(移到了汇编代码15行),这样一来,只有当肯定它就是返回值的时候,才会计算它。
循环
C语言提供的多种循环结构,即do-while、while和for循环。由于汇编没有相应的指令存在,可是可使用条件测试和跳转组合结合起来实现循环的效果。大多数的编译器根据一个循环的do-while形式来产生循环代码。其余的循环会首先被转换成do-while形式,而后再翻译成机器代码。do-while循环的通用形式以下:
do
body-statement
while(test-expr);
能够看到,body-statement至少会被执行一次。上述的do-while的通用形式能够翻译为以下的条件和goto语句
loop:
body-statement
t = test-expr;
if(t)
goto loop;
while语句的通用形式以下
while(test-expr)
body-statement
将while循环翻译成机器代码有不少方法,采用gcc的策略,使用条件分支,将其转换为do-while循环,以下
t = test-expr;
if(!t)
goto done;
loop:
body-statement
t = test-expr;
if(t)
goto loop;
done:
for循环的通用形式以下
for (init-expr; test-expr; update-expr)
body-statement
把它翻译为goto代码为
init-expr;
t = test-expr;
if(!t)
goto done;
loop:
body-statement
update-expr;
t = test-expr;
if(t)
goto loop;
done: