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

  1. 工具

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

    • 计算机执行机器代码,用字节序列编码代表底层的操作处理数据、管理存储器、读写存储设备上的数据、网络通信,C语言编译器需要下面几个步骤,就能将我们的代码翻译成可执行的机器代码
过程编写代码预编译编译汇编器连接器
产生文件xx.cxx.ixx.sxx.oxxx.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字节b1movbxxx.o
shortw2movwxxx.o
int双字l4movlxxx.o
long int双字l4movlxxx.o
long long int--b4movlxxx.o
char *双字l4movlxxx.o
float单精度s4movsxxx.o
duoble双精度l(另外一组指令和寄存器)8movlxxx.o
long duoble字节t10/12movtxxx.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]

-- 举例

    1. $55=55,$0x108=0x108
    1. %eax:寄存器%eax的值;0x103:地址0x103的值
    1. 通用地址寻址表达: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,DD<-S将S复制给D
movb传送字节
movw传送字
movl传送双字
MOVS S,DD<--符号扩展(S)传送符号扩展字节(将字节的高位用符号<1>填充,然后传入进字/双字中)
movsbw将做了符号扩展的字节传送到
movsbl将做了符号扩展的字节传送到双字
movswl将做了符号扩展的传送到双字
MOVZ S,DD<--零扩展(S)传送符号扩展字节(将字节的高位用符号<0>填充,然后传入进字/双字中)
movzbw将做了零扩展的字节传送到
movzbl将做了零扩展的字节传送到双字
movzwl将做了零扩展的传送到双字
pushl S直接耍、寄存器,压入栈中将双字压入栈
popl D将栈中的值放入%ebp中将双字出栈
  • tips: 为什么每次方法开辟栈帧都会是push和pop,因为函数的临时变量都计算好了的,刚好那么长的栈长度。所有一个成熟的程序都会将动态变量放在堆中,这是现在内存便宜了的情况下使用.如果内存空间小,还是得一个地址一个地址的计算栈空间。
  1. 算术和逻辑操作
操作方式指令效果描述
一元操作leal S,DD<-&S加载有效地址
一元操作INC DD<--D+1加1
一元操作DEC DD<--D-1减1
一元操作NEG DD<-- -D取负
一元操作NOT DD<-- ~D取反
二元操作ADD S,DD<--D+S
二元操作SUB S,DD<--D-S
二元操作IMUL S,DD<--D*S
二元操作XOR S,DD<--D^S异或
二元操作OR S,DD<--DS
二元操作AND S,DD<--D&S
位操作SAL S,DD<--D<<S左移
位操作SHL S,DD<--D<<S左移
位操作SAR S,DD<--D<<AS算术左移
位操作SHR S,DD<--D>>LS算术右移
  1. 控制
  • 访问条件码(将一个字节<D>设置为0或者1)
指令同义名效果设置条件
sete DsetzD<--ZF相等/零
setne DsetnzD<-- ~ZF不等/非零
sets DD<--SF负数
setns DD<-- ~ZF非负数
setg DsetnleD<-- ~(SF^OF)&~ZF大于(有符号>)
setge DsetnlD<-- ~(SF^OF)大于等于(有符号>=)
setl DsetngeD<-- SF^OF小于(有符号<)
setle DsetngD<-- ~(SF^OF)或ZF小于等于(有符号<=)
seta DsetnbeD<-- ~CF&~ZF超过(无符号>)
setae DsetnbD<-- ~CF超过或相等(无符号>=)
setb DsetnaeD<-- CF低于(无符号<)
setbe DsetnaD<-- CF或ZF相等/零
  • 跳转指令机器编码(当满足条件会跳转到一条带标号的目的地<Label>)
指令同义名跳转条件描述
jmp Label1直接跳转
jmp *Operand1简介跳转
je LabeljzZF相等/零
je Labeljnz~ZF不相等/非零
js LabelSF负数
jns Label~SF非负数
jg Labeljnle~(SF^OF)&~ZF大于(有符号>)
jge Labeljnl~(SF^OF)大于或等于(有符号>=)
jl LabeljngeSF^OF小于(有符号<)
jle Labeljng(SF^OF)或ZF小于等于(有符号<=)
ja Labeljnbe~CF&~ZF超过(无符号>)
jae Labeljnb~CF超过或相等(无符号>=)
jb LabeljnaeCF低于(无符号<)
jbe LabeljnaCF或ZF相等/零
  • 条件传送指令(当传送条件满足时,将S值复制到R中)
指令同义名传送条件描述
cmove S,RcmovzZF相等/零
cmovne S,Rcmovnz~ZF不相等/非零
cmovs S,RSF负数
cmovns S,R~SF非负数
cmovg S,Rcmovnle~(SF^OF)&~ZF大于(有符号>)
cmovge S,Rcmovnl~(SF^OF)大于或等于(有符号>=)
cmovl S,RcmovngeSF^OF小于(有符号<)
cmovle S,Rcmovng(SF^OF)或ZF小于等于(有符号<=)
cmova S,Rcmovnbe~CF&~ZF超过(无符号>)
cmovae S,Rcmovnb~CF超过或相等(无符号>=)
cmovb S,RcmovnaeCF低于(无符号<)
cmovbe S,RcmovnaCF或ZF相等/零
  1. 过程
  • 过程调用
指令描述
call Label过程调用
call *Operand过程调用
leave为返回准备栈
ret从过程调用中返回
  1. 数组分配和访问
  • 数据类型T整数常数N : T:A[N]
  • 下面的声明
声明数组元素大小总的大小起始地址
char A[12]112XaXa+i
char *B[12]432XbXb+i
double C[6]848XcXc+i
double *D[5]520XdXd+i
  1. 异质的数组结构
  • 结构(struct):每个变量都占用内存
  • 联合(union):整个联合占用最大类型的空间
  1. 理解指针
  • 每个指针都有一个值
  • 运算符*用于指针的间接引用
  • 数字与指针紧密联系
  • 将指针从一个类型强制转换成另一种类型,至改变它的类型,而不改变它的值
  • 指针可以指向一个函数
  1. 浮点程序的机器级表示

转载于:https://my.oschina.net/u/2246410/blog/1801310

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值