工具
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
相等/零
跳转指令机器编码(当满足条件会跳转到一条带标号的目的地 )
指令
同义名
跳转条件
描述
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):整个联合占用最大类型的空间
理解指针
每个指针都有一个值
运算符*用于指针的间接引用
数字与指针紧密联系
将指针从一个类型强制转换成另一种类型,至改变它的类型,而不改变它的值
指针可以指向一个函数
浮点程序的机器级表示