二.计算机系统之程序的机器级表示

  1. 工具程序员

    • C语言的编译和反编译工具,能够帮助咱们更快的理解汇编代码与C语言的关系.
    • 1.1 在centos上是使用gccbuidunit来编译和反编译C代码
    • 1.2 OBJDUMP:object dump,反汇编器
  2. 编译过程编程

    • 计算机执行机器代码,用字节序列编码表明底层的操做处理数据、管理存储器、读写存储设备上的数据、网络通讯,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. 消除没必要要的计算,用快速操做代替慢速操做
  1. 程序编码
  • 先用一个小程序来看一下编译器如何运行的
  • 3.1 代码
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;
}
- 查看编译的部分,能够看出编译器的好坏能够决定一个程序的执行效率。在逻辑优化,环境优化后。编译器优化是最后的优化方式。
  • 3.2 主要的预编译逻辑
将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
  • 3.5 链接器(反编译,将sum分配内存)
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
  1. 数据格式(每次操做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
  1. 访问信息
  • 寄存器信息
寄存器号 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(帧指针)
    1. %eax,%ecx,%edx%ebx,%esi,%edi的保存和恢复方式不一样
    1. %ebp:每次函数调用开辟栈帧的帧底
    1. %esp:永远做为栈指针,调用栈的信息,通常即是当前指令的栈地址
    1. 字节操做指令能够独立读或者写前4个寄存器的2个地位字节(0~7,8~15)。
  • 操做数指示符(基本上都是地址引用,只有在局部变量CPU计算才会读取实际值)小程序

    1. 当即数:任何能犯贱一个32位的字里面的数值均可以用当即数,$-577或$0x1F
    1. 寄存器:双字节-->%eax,字-->%ax,单字节-->%al
    1. 存储器:根据计算出来的地址访问某个存储器的位置,M[addr]

-- 举例centos

    1. $55=55,$0x108=0x108
    1. %eax:寄存器%eax的值;0x103:地址0x103的值
    1. 通用地址寻址表达:Imm(Ea,Ei,s):地址=Ei*s+Ea+Imm。其中Imm:从0开始,Ea/Ei:寄存器值,s:一、二、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,由于函数的临时变量都计算好了的,恰好那么长的栈长度。全部一个成熟的程序都会将动态变量放在堆中,这是如今内存便宜了的状况下使用.若是内存空间小,仍是得一个地址一个地址的计算栈空间。
  1. 算术和逻辑操做
操做方式 指令 效果 描述
一元操做 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 算术右移
  1. 控制
  • 访问条件码(将一个字节<D>设置为0或者1)
指令 同义名 效果 设置条件
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 相等/零
  1. 过程
  • 过程调用
指令 描述
call Label 过程调用
call *Operand 过程调用
leave 为返回准备栈
ret 从过程调用中返回
  1. 数组分配和访问
  • 数据类型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
  1. 异质的数组结构
  • 结构(struct):每一个变量都占用内存
  • 联合(union):整个联合占用最大类型的空间
  1. 理解指针
  • 每一个指针都有一个值
  • 运算符*用于指针的间接引用
  • 数字与指针紧密联系
  • 将指针从一个类型强制转换成另外一种类型,至改变它的类型,而不改变它的值
  • 指针能够指向一个函数
  1. 浮点程序的机器级表示
相关文章
相关标签/搜索