第三章 程序的机器级表示
3.1 历史观点
3.2 程序编码
1.命令行
(1)编译
linux> gcc -Og -o p p1.c p2.c #编译p1.c和p2.c文件
(2)ATT格式汇编
linux> gcc -Og -S mytry.c #产生C语言汇编文件mytry.s,可直接查看
linux> gcc -Og -c mytry.c #产生二进制目标代码文件mytry.o,无法直接查看
linux> objdump -d mytry.o #输出mytry.o文件的机器代码和反汇编代码,在命令行中输出
linux> gcc -Og -o prog main.c mytry.c #生成可执行文件prog
linux> objdump -d prog #输出prog文件的机器代码和反汇编代码,在命令行中输出
(3)Intel格式汇编
linux> gcc -Og -S -masm=intel mytry.c #产生C语言汇编文件mytry.s,可直接查看
2. gcc命令的过程
C预处理器(扩展源代码,插入#include的文件并扩展#define定义的宏)
编译器(产生源文件的汇编代码.s)
汇编器(将汇编代码转换成二进制目标代码文件.o)
链接器(将目标代码文件与实现函数库的代码合并,并产生可执行文件.p)
3.2.1 机器级代码
1.两种抽象
(1)指令集架构/指令集体系结构(Instruction Set Architecture, ISA)
(2)虚拟地址
3.2.2 代码示例
1.汇编vs反汇编
(1)指令长度:1-15字节
(2)反汇编器省略许多指令结尾的q,而给call和ret指令添加q后缀
2.反汇编.o vs 反汇编prog
(1)地址不同
(2)反汇编prog填上了callq等指令所需的地址
(3)反汇编prog在末尾插入nop,使函数代码变为16字节,便于储存下一个代码块
3.2.3 关于格式的注释
1."."开头的行:伪指令,指导汇编器和链接器工作
2.ATT格式汇编vsIntel格式汇编
(1)intel省略表示大小的后缀
(2)intel省略寄存器前%
(3)intel用不同的方式描述内存中的位置,如“QWORD PTR [rbx]”替代“(%rbx)”
(4)在带有多个操作数的指令情况下,列出的操作数顺序相反
3.3 数据格式
1.C语言数据类型在x86-64中的大小
注:
(1)b = byte = 8 bits, w = word = 16 bits, l = long word = 32 bits, q = quad word = 64 bits
(2)浮点数:s = short float(?我猜的) = 32 bits, l = long float(?) = 64 bits
(3)l既表示4字节整数又表示8字节浮点数,但并不会产生歧义,因为浮点数使用一组完全不同的寄存器
3.4 访问信息
1.16个整数寄存器(非常重要)
注:
(1)生成小于8字节结果的指令,剩下的字节会如何?
生成1或2字节的指令保持剩余字节不变,生成4字节的指令把高位4字节置0
(2)(i guess)栈指针 %rsp = register stack pointer
3.4.1 操作数指示符
1.操作数(operand)
(1)立即数(immediate):表示常数值,ATT汇编代码中表示为“$”+标准C表示法表示的整数,自动选择最紧凑的方法编码(?)
(2)寄存器(register):表示某个寄存器的内容,16个寄存器中的低位1、2、4、8字节中的一个作为操作数
(3)内存引用:根据有效地址访问内存位置
注:
(1)ra表示任意寄存器a,R[ra]表示它的值(这是将寄存器集合视为数组R,寄存器标识符作为索引)
(2)Mb[Addr]表示对存储在内存中地址Addr开始的b个字节值的饮用,可省去下标b
(3)Imm(rb, ri, s)是最常用的内存引用的寻址模式,包含:立即数偏移Imm(缺省为0)、基址寄存器rb(缺省为0)、变址寄存器ri(缺省为0)、比例因子s(s=1,2,4,8,缺省为1),有效地址为Imm+R[rb]+R[ri]*s
写在前面:书中把许多不同的指令划分为指令类,每一类执行相同的操作,只不过操作数的大小不同
3.4.2 数据传送指令
1.MOV类——简单的数据传送指令:把数据从源位置复制到目的位置
格式:MOV source源操作数, destination目的操作数
注:
(1)寄存器部分的大小必须与指令做后一个字符(b、w、l、q)指定的大小相匹配
(2)S、D均可以是内存地址或寄存器,但不能同时为内存地址;从内存传送数据到内存需要两条指令:内存->寄存器,寄存器->内存
(3)movq vs movabsq: movq只能表示以表示为32位补码数字的立即数作为源操作数,然后扩展符号得到64位,而movabsq能够以任意64位立即数作为源操作数,但只能以寄存器作为目的
2.MOVZ和MOVS类:将较小的源值复制到较大的目的时使用
格式:
(1)零扩展(MOV zero)——高位补0: MOVZ+源大小+目的大小 source, register
(2)符号扩展(MOV sign)——高位扩展符号位:MOVS+源大小+目的大小 source, register
注:
(1)S可以是内存地址或寄存器,R只能是寄存器
(2)不存在movzlq指令,但可以用以32位寄存器为目的的movl指令实现,高位4字节置0
(3)cltq指令无操作数,效果与movslq %eax,%rax完全一致,但是编码更紧凑(我理解为:更省地方)
(4)一个有趣的小练习
答案: