实验四 第三章 程序的机器级表示
本章学习内容是汇编语言,一定要能读懂
3.1-3.7中练习重点:3.1,3.3,3.5,3.6,3.9,3.14,3.15,3.16,3.22,3.23,3.27,3.29,3.30,3.33,3.34
X86 寻址方式经历三代:
1 DOS时代的平坦模式,不区分用户空间和内核空间,很不安全
2 8086的分段模式
3 IA32的带保护模式的平坦模式
对于机器级编程来说,其中两种抽象尤为重要
1 机器级程序的格式和行为,定义为指令集体系结构(ISA),它定义了处理器状态,指令的格式,以及每条指令对状态的影响
2 机器级程序使用的存储器地址是虚拟地址,提供的存储器模型看上去是一个非常大的字节数组
数据格式:由于是从16位体系结构扩展成32位,intel用术语字(word)表示16位数据类型,因此32位为双字(double words),64位数为4字(quad words)
对于ISA,要有以下基本观念:IA32的ISA和x86-64的ISA,以及其他大多数ISA,在抽象时都将指令按顺序执行抽象。但是处理器的硬件可以并发地执行许多指令,并且采用了一些safeguards来确保并行执行之后的结果和一条一条顺序执行的结果一样
一个IA32 CPU包含一组8个存储32位值的寄存器,用以存整数数据和指针:eax,ecx,edx,ebx,esi,edi,esp,ebp.大多数情况下前六个都用作通用寄存器,eax,ecx,edx的存储和恢复惯例不同于ebx,edi,esi(前三者为被调用者保存,后三者为调用者保存,详见3.7.3);最后两个用于存储指针,由于在过处理中非常重要,分别指向栈帧的顶部和底部,必须保持
关于ISA的指令:
一是,ISA中每条指令占用字节数不等,常用的指令所需的字节数少,不太常用的指令的字节数多,这样的话,相对于每条指令占用等长字节数的ISA,这种占用字节数不等的ISA为程序产生的总空间要更少。
二是,ISA中的指令在设计时,达到了一个效果,对于一个二进制指令串,从某个字节开始,译码的结果是唯一的,这是达到了编码理论中“唯一可译性”的要求。
(我觉得大概正是反汇编器正是利用了唯一可译性,从而将一串指令字节序列分割开来的)
IA机器代码和原始的C代码差别很大
1 程序计数器(PC)指令将要执行的下一条指令在存储器中的地址
2 整数寄存器文件包含8个命名的位置,分别存储32位的值
3 一些浮点寄存器存放浮点数据
gcc -S xxx.c -o xxx.s 获得汇编代码,也可以用objdump -d xxx 反汇编
注意: 64位机器上想要得到32代码:gcc -m32 -S xxx.c
MAC OS中没有objdump, 有个基本等价的命令otool
Ubuntu中 gcc -S code.c (不带-O1) 产生的代码更接近教材中代码(删除"."开头的语句)
二进制文件可以用od 命令查看,也可以用gdb的x命令查看。 有些输出内容过多,我们可以使用 more或less命令结合管道查看,也可以使用输出重定向来查看
od code.o | more
od code.o > code.txt
当一个源文件生成了'.o'的目标二进制文件后,无法直接查看。
但是还是有个查看目标代码文件内容的方法,就是对'.o'目标文件使用反汇编器。它的输出还是二进制文件,但是,反汇编器将这些二进制按照指令进行了分段。让我们知道哪一段是一个指令(格式上与汇编器产生的汇编文件一样,分行的)
表中不同数据的汇编代码后缀
C声明 | Intel数据类型 | 汇编代码后缀 | 字节 |
char | 字节 | b | 1 |
short | 字 | w | 2 |
int | 双字 | l | 4 |
long int | 双字 | l | 4 |
long long int | ----- | - | 4 |
char * | 双字 | l | 4 |
float | 单精度 | s | 4 |
double | 双精度 | l | 8 |
long double | 扩展精度 | t | 10/12 |
数据传送指令有三个变种:movb(传送字节)movw(传送字)movl(传送双字)
寄存器:
esi edi可以用来操纵数组
esp ebp用来操纵栈帧
对于寄存器,特别是通用寄存器中的eax,ebx,ecx,edx,要理解32位的eax,16位的ax,8位的ah,al都是独立的,通过下面例子说明:
假定当前是32位x86机器,eax寄存器的值为0x8226,执行完addw $0x8266, %ax指令后eax的值是多少?
解析:0x8226+0x826=0x1044c, ax是16位寄存器,出现溢出,最高位的1会丢掉,剩下0x44c,不要以为eax是32位的不会发生溢出
操作数的三种类型:立即数、寄存器、存储器
立即数:即常数值
寄存器:表示某个寄存器内容
存储器:根据计算出来的地址(通常称有效地址)访问某个存储器位置
因此寻址方式也有多种,如:立即数寻址、寄存器寻址、绝对寻址、间接寻址、变址寻址、伸缩化 的变址寻址……
有效地址的计算方式 Imm(Eb,Ei,s) = Imm + R[Eb] + R[Ei]*s
区分MOV,MOVS,MOVZ三个命令
MOV相当于C语言的赋值”=“
MOVS将作了符号扩展的字节传送到字
MOVZ将作了零扩展的字节传送到字
(不能从内存地址直接MOV到另一个内存地址,要用寄存器中转一下)
mov族(mov指令还有很多兄弟指令如movb、movw、movsb、movzb)、pop、push
movsb、movzb分别为符号扩展、零扩展,它们只拷贝一个字节,源操作数均为单字节,并设置目的操作数中其余的位,效果如下:
初始假设:%dh=8D %eax=98765432
1 movb %dh,%al ;%eax=9876548D
2 movsbl %dh,%eax ;%eax=FFFFFF8D(目的操作数高24位设为源字节最高位,在这里为很显然为1,所以前24位为全F)
3 movzbl %dh,%eax ;%eax=0000008D(目的操作数高24位被设为0)
pushl指令等价于:
subl $4,%esp
movl %ebp,(%esp) //注意这里的括号引起的差别
popl指令等价于:
movl (%esp),%eax
addl $4,%esp
栈顶元素的地址是所有栈中元素地址中最低的
1.指针其实是地址,间接引用指针就是将该指针放在一个寄存器中 ,然后在间接存储器引用中引用这个寄存器
2.局部变量通常保存在寄存器中,而不是存储器(个人猜测应该是局部变量属于动态分配,局部变量因此被动态置入寄存器,而非存储器)
例如调用exchange:
int a = 4;
int b = exchange(&a,3);
printf("a=%d,b=%d\n",a,b);
打印出a=3,b=4
(&(取址)创建一个指针,在本例中,该指针指向保存局部变量a的位置.然后函数exchange用3覆盖存储在a中的值,但是返回4作为函数的值)
算术和逻辑操作
移位操作 :sall==shll(填0) sarl(算术右移,填符号位) shrl(逻辑右移,填0)
特殊算术操作:imull(有符号64位乘法) mull(无符号64位乘法) cltd(转换为四字) idivl(有符号除法) divl(无符号除法)
使用gcc –S –o main.s main.c -m32
命令编译成汇编代码