-
工具
- C语言的编译和反编译工具,可以帮助我们更快的理解汇编代码与C语言的关系.
- 1.1 在centos上是使用gcc和buidunit来编译和反编译C代码
- 1.2 OBJDUMP:object dump,反汇编器
-
编译过程
- 计算机执行
机器代码
,用字节序列编码代表底层的操作:处理数据、管理存储器、读写存储设备上的数据、网络通信,C语言编译器需要下面几个步骤,就能将我们的代码翻译成可执行的机器代码
过程 | 编写代码 | 预编译 | 编译 | 汇编器 | 连接器 |
---|
产生文件 | xx.c | xx.i | xx.s | xx.o | xxx.o |
- 2.1 编写代码:使用C的提供的语法编写逻辑文件,产生 .c 后缀的逻辑代码文件
- 2.2 预编译:gcc将预处理代码(
#include
) 解释成具体的逻辑,这一步是将标准库和外部引用都加载到本文件中,,产生 .i 后缀的逻辑代码文件 - 2.3 编译:将 .i 的文件解释成汇编代码(
人类可以读的机器码
),产生 .s 后缀的逻辑代码文件 - 2.4 汇编器:将 .s 的文件翻译成机器码,这一阶段会将代码中的符号(变量,函数),全部整合,逻辑代码的
重定向优化
,多个变量选定为一个,但是整个文件并没有逻辑地址,产生 .o后缀的目标文件 - 2.5 连接器:这个阶段是对整个程序的文件(多个文件)中的变量、函数、静态库、动态库分配运行时的逻辑地址*,产生 .o 后缀的可执行文件
好的编译器:
a. 优化编译器产生的代码至少与一个熟练的汇编程序员手工编写的代码一样有效
b. 用高级语言编写的程序可以在很多的机器上编译和执行,而汇编代码则是与特定机器密切相关的
优化编译器:
a. 重新排列执行顺序
b. 消除不必要的计算,用快速操作代替慢速操作
程序编码
simple.c文件
int simple(int *xp,int y);
int sum =0;
int main()
{
printf("hello world !\n");
int x= 5;
int y=10;
int c = simple(&x,y);
printf("result %d, address %p",c,&c);
return 0;
}
int simple(int *xp,int y)
{
int t=*xp+y;
sum +=t;
return sum;
}
- 查看编译的部分,可以看出编译器的好坏可以决定一个程序的执行效率。在逻辑优化,环境优化后。编译器优化是最后的优化方式。
将stdio.h文件加载进文件
- 3.3 编译后的.s文件 (只查看simple函数,sum类型为global)
simple:
.LFB12:
.cfi_startproc
movl %esi, %eax
addl (%rdi), %eax
addl sum(%rip), %eax
movl %eax, sum(%rip)
ret
.cfi_endproc
.LFE12:
.size simple, .-simple
.globl sum
.bss
.align 4
.type sum, @object
.size sum, 4
sum:
.zero 4
- 3.4 汇编器(通过反汇编查看,sum的值为 0x0,汇编器不知道应该分配哪个类此)
000000000000003f <simple>:
3f: 89 f0 mov %esi,%eax
41: 03 07 add (%rdi),%eax
43: 03 05 00 00 00 00 add 0x0(%rip),%eax # 49 <simple+0xa>
49: 89 05 00 00 00 00 mov %eax,0x0(%rip) # 4f <simple+0x10>
4f: c3 retq
00000000004005bf <simple>:
4005bf: 89 f0 mov %esi,%eax
4005c1: 03 07 add (%rdi),%eax
4005c3: 03 05 77 0a 20 00 add 0x200a77(%rip),%eax # 601040 <__TMC_END__>
4005c9: 89 05 71 0a 20 00 mov %eax,0x200a71(%rip) # 601040 <__TMC_END__>
4005cf: c3 retq
数据格式(每次操作C类型使用的汇编命令)
C声明 | Intel数据类型 | 汇编代码后缀 | 大小字节 | 移动 | 增加 |
---|
char | 字节 | b | 1 | movb | xxx.o |
short | 字 | w | 2 | movw | xxx.o |
int | 双字 | l | 4 | movl | xxx.o |
long int | 双字 | l | 4 | movl | xxx.o |
long long int | -- | b | 4 | movl | xxx.o |
char * | 双字 | l | 4 | movl | xxx.o |
float | 单精度 | s | 4 | movs | xxx.o |
duoble | 双精度 | l(另外一组指令和寄存器) | 8 | movl | xxx.o |
long duoble | 字节 | t | 10/12 | movt | xxx.o |
访问信息
寄存器号 | 0~7位 | 8~15位 | 16位 | 32位 |
---|
1 | %al | %ah | %ax | %eax |
2 | %cl | %ch | %cx | %ecx |
3 | %dl | %dh | %dx | %edx |
| | | | |
4 | %bl | %bh | %bx | %ebx |
5 | | | %si | %esi |
6 | | | %di | %edi |
| | | | |
7 | | | %sp | %esp(栈指针) |
8 | | | %bp | %ebp(帧指针) |
-- 举例
-
- $55=55,$0x108=0x108
-
- %eax:寄存器%eax的值;0x103:地址0x103的值
-
- 通用地址寻址表达:Imm(Ea,Ei,s):地址=Ei*s+Ea+Imm。其中Imm:从0开始,Ea/Ei:寄存器值,s:1、2、4
- 3.1. (%eax):1*0+%eax+0 =%eaz
- 3.2. 260(%eax,%edx,4): 地址为4.%edx+%eax+260
指令 | 效果 | 描述 |
---|
MOV S,D | D<-S | 将S复制给D |
movb | 传送字节 | |
movw | 传送字 | |
movl | 传送双字 | |
| | |
MOVS S,D | D<--符号扩展(S) | 传送符号扩展字节(将字节的高位用符号<1>填充,然后传入进字/双字中) |
movsbw | 将做了符号扩展的字节传送到字 | |
movsbl | 将做了符号扩展的字节传送到双字 | |
movswl | 将做了符号扩展的字传送到双字 | |
| | |
MOVZ S,D | D<--零扩展(S) | 传送符号扩展字节(将字节的高位用符号<0>填充,然后传入进字/双字中) |
movzbw | 将做了零扩展的字节传送到字 | |
movzbl | 将做了零扩展的字节传送到双字 | |
movzwl | 将做了零扩展的字传送到双字 | |
| | |
pushl S | 将直接耍、寄存器,压入栈中 | 将双字压入栈 |
popl D | 将栈中的值放入%ebp中 | 将双字出栈 |
- tips: 为什么每次方法开辟栈帧都会是push和pop,因为函数的临时变量都计算好了的,刚好那么长的栈长度。所有一个成熟的程序都会将动态变量放在堆中,这是现在内存便宜了的情况下使用.如果内存空间小,还是得一个地址一个地址的计算栈空间。
算术和逻辑操作
操作方式 | 指令 | 效果 | 描述 |
---|
一元操作 | leal S,D | D<-&S | 加载有效地址 |
一元操作 | INC D | D<--D+1 | 加1 |
一元操作 | DEC D | D<--D-1 | 减1 |
一元操作 | NEG D | D<-- -D | 取负 |
一元操作 | NOT D | D<-- ~D | 取反 |
| | | |
二元操作 | ADD S,D | D<--D+S | 加 |
二元操作 | SUB S,D | D<--D-S | 减 |
二元操作 | IMUL S,D | D<--D*S | 乘 |
二元操作 | XOR S,D | D<--D^S | 异或 |
二元操作 | OR S,D | D<--D | S |
二元操作 | AND S,D | D<--D&S | 与 |
| | | |
位操作 | SAL S,D | D<--D<<S | 左移 |
位操作 | SHL S,D | D<--D<<S | 左移 |
位操作 | SAR S,D | D<--D<<AS | 算术左移 |
位操作 | SHR S,D | D<--D>>LS | 算术右移 |
控制
指令 | 同义名 | 效果 | 设置条件 |
---|
sete D | setz | D<--ZF | 相等/零 |
setne D | setnz | D<-- ~ZF | 不等/非零 |
| | | |
sets D | | D<--SF | 负数 |
setns D | | D<-- ~ZF | 非负数 |
| | | |
setg D | setnle | D<-- ~(SF^OF)&~ZF | 大于(有符号>) |
setge D | setnl | D<-- ~(SF^OF) | 大于等于(有符号>=) |
setl D | setnge | D<-- SF^OF | 小于(有符号<) |
setle D | setng | D<-- ~(SF^OF)或ZF | 小于等于(有符号<= ) |
| | | |
seta D | setnbe | D<-- ~CF&~ZF | 超过(无符号>) |
setae D | setnb | D<-- ~CF | 超过或相等(无符号>=) |
setb D | setnae | D<-- CF | 低于(无符号<) |
setbe D | setna | D<-- CF或ZF | 相等/零 |
- 跳转指令机器编码(当满足条件会跳转到一条带标号的目的地<Label>)
指令 | 同义名 | 跳转条件 | 描述 |
---|
jmp Label | | 1 | 直接跳转 |
jmp *Operand | | 1 | 简介跳转 |
| | | |
je Label | jz | ZF | 相等/零 |
je Label | jnz | ~ZF | 不相等/非零 |
| | | |
js Label | | SF | 负数 |
jns Label | | ~SF | 非负数 |
| | | |
jg Label | jnle | ~(SF^OF)&~ZF | 大于(有符号>) |
jge Label | jnl | ~(SF^OF) | 大于或等于(有符号>=) |
jl Label | jnge | SF^OF | 小于(有符号<) |
jle Label | jng | (SF^OF)或ZF | 小于等于(有符号<= ) |
| | | |
ja Label | jnbe | ~CF&~ZF | 超过(无符号>) |
jae Label | jnb | ~CF | 超过或相等(无符号>=) |
jb Label | jnae | CF | 低于(无符号<) |
jbe Label | jna | CF或ZF | 相等/零 |
- 条件传送指令(当传送条件满足时,将S值复制到R中)
指令 | 同义名 | 传送条件 | 描述 |
---|
cmove S,R | cmovz | ZF | 相等/零 |
cmovne S,R | cmovnz | ~ZF | 不相等/非零 |
| | | |
cmovs S,R | | SF | 负数 |
cmovns S,R | | ~SF | 非负数 |
| | | |
cmovg S,R | cmovnle | ~(SF^OF)&~ZF | 大于(有符号>) |
cmovge S,R | cmovnl | ~(SF^OF) | 大于或等于(有符号>=) |
cmovl S,R | cmovnge | SF^OF | 小于(有符号<) |
cmovle S,R | cmovng | (SF^OF)或ZF | 小于等于(有符号<= ) |
| | | |
cmova S,R | cmovnbe | ~CF&~ZF | 超过(无符号>) |
cmovae S,R | cmovnb | ~CF | 超过或相等(无符号>=) |
cmovb S,R | cmovnae | CF | 低于(无符号<) |
cmovbe S,R | cmovna | CF或ZF | 相等/零 |
过程
指令 | 描述 |
---|
call Label | 过程调用 |
call *Operand | 过程调用 |
leave | 为返回准备栈 |
ret | 从过程调用中返回 |
数组分配和访问
- 数据类型T 和 整数常数N : T:A[N]
- 下面的声明
声明 | 数组 | 元素大小 | 总的大小 | 起始地址 |
---|
char A[12] | 1 | 12 | Xa | Xa+i |
char *B[12] | 4 | 32 | Xb | Xb+i |
double C[6] | 8 | 48 | Xc | Xc+i |
double *D[5] | 5 | 20 | Xd | Xd+i |
异质的数组结构
- 结构(struct):每个变量都占用内存
- 联合(union):整个联合占用最大类型的空间
理解指针
- 每个指针都有一个值
- 运算符*用于指针的间接引用
- 数字与指针紧密联系
- 将指针从一个类型强制转换成另一种类型,至改变它的类型,而不改变它的值
- 指针可以指向一个函数
浮点程序的机器级表示