程序的机器级表示
本节内容来源于深入理解计算机系统(csapp)一书第三章(精通细节是理解更深和更基本概念的先决条件)
序言
Inter从8位->32位->64位
期间晶体管大致沿着十八个月翻一翻的速度增成,这就是摩尔定理;这个增长速度是计算机革命的驱动力
机器编码中最重要的是指令集体系结构(Instruction Set Architecture, ISA)和虚拟地址
指令集定义了数据类型及格式,指令格式,寻址方式和可访问地址空间的大小等等,相当于定义了cpu的状态
指令

常见通用寄存器比如%rax,%rbx,这两都是64位的;
一个程序就是一个有限的顺序指令,执行程序就是依次取指执行的过程
虚拟地址则将不同进程的虚拟地址和不同内存的物理地址映射起来


指令
csapp里面用的是AT&T格式的指令,S->D,前面是源,后面是目的
指令又分为操作和操作数

操作数
操作数又分为源操作数和目的操作数,源操作数就是不随指令变化的操作数,目的操作数就是随着指令变化的操作数,前者可以看作变量,后者就是处理过后的量,成了因变量,所以叫目的操作数
操作数按类型分又可以分为:立即数$,寄存器%edx,存储器(%edx)
寻址方式
M[]表示在内存中,R[]表示在寄存器中,Imm立即数
比例变址寻址:M[Imm+R[rb]+R[ri]·S]

其他寻址方式类似比例变址
变址寻址:M[Imm+R[rb]+R[ri]]
基址偏移量寻址:M[Imm+R[rb]]
间接寻址:M[R[rb]]

间接寻址就是指针的用法
绝对寻址:M[Imm]
寄存器寻址:R[rb]
操作
操作规定了执行什么操作,并会规定执行的数据类型
数据类型
四字q | 双字q | 字w | 字节b |
---|---|---|---|
quattuor | double | word | byte |
64位 | 32位 | 16位 | 一个字节是八位,8bit |
mov指令
mov用来传值,例如
1 | movq $0x0, %edx |
这条指令会把立即数0,传到寄存器%edx中,传送数据类型为4字,64位数据
以传送类型分,因此mov有四种,为 movb
,movw
,movl
,movq
由于x86-64惯例,movl会把目的操作数的高位置0
mov的扩展指令
MOVABSQ
,传送绝对四字
MOVZ
零扩展,会给目的操作数的剩余空位补0
MOVS
符号扩展,会给目的操作数的剩余空位补符号位
数据传送实例
1 | long ex(){ |
指令
1 | //xp in %rdi y in %rsi |

movq (%rdi),%rax
先根据寄存器%rdi内的地址,去内存中找到xp的值,直接把值赋给寄存器%rax
movq %rsi,(%rdi)
此时%rsi的值是y,把%rsi直接赋给%rdi所指向的值,此时内存中的xp值被修改成了y
ret
返回寄存器%rax的值,他的值此时是xp,所以返回xp
压栈出栈指令
栈是一种数据结构,特点是先进后出,进栈出栈都是在栈顶上操作

对应指令 pop
出栈,push
压栈
指向栈顶的指针%rsp(%esp)extend stack point,前面是64位,后面是32位
指向栈底的指针%rbp(%ebp)extend base point
所以,压栈和出栈都相当于两条指令
1 | pushq S |
假设栈顶的地址是 0x10

R[%rsp]-8->R[%rsp]
给寄存器-8,记录新地址 0x8
S->M[R[%rsp]]
把S的值赋到%rsp记录的新地址(0x8)下

M[R[%rsp]]->D
把%rsp寄存器的值当作地址,找到对应地址下的值,此时即栈顶元素。赋给D
R[%rsp]+8->R[%rsp]
%rsp自加8
可以看出,出栈其实没有删除掉出栈的元素,只是把出栈的那个值传到了别处,然后栈指针加8;不过,上一个元素虽然没被删除,但是可以被新增的元素覆盖掉
算数逻辑指令

一元指令就是一个操作数,比如自加1,二元就是两个操作数,其目的操作数又是源,参与运算,又是目的,存储值
加载有效地址
1 | leaq S, D //&s->D |
它的第一个操作数看上去是一个存储器引用,但该指令并不是从指定的位置读取数据,而是将有效地址写入到目的操作数,类似于 C 语言的取地址操作符“&”
移位运算
1 | SAl k,D //D<<k->D |
SAL 和 SHL 都是左移指令,效果是一样的,移动几位,右边补上几位0;右移指令不同,算术右移 SAR 是补上符号位,即右边的第一位;逻辑右移 SHR 是补上 0
异或运算
1 | XORq S,D //s^D->D |
讨论下面两条指令的区别
1 | xorq %rdx, %rdx |
这两条指令都相当于给寄存器置为0,前者是计算为0,后者是传了个立即数0;但是,编为机器代码后,前者只有3字节,后者有7字节
为什么会这样,于是在gcc下验证
编写text.c汇编代码(为了方便对比,见下)
编译不链接
1 | gcc -c text.c>text.s |
执行完后 ls
查看会生成 text.o
文件
反汇编并保存到 1.txt
中
1 | objdump -d text.o>1.txt |
查看 code 1.txt
text.s
1 | xorw %dx, %dx |
反汇编
1 | text.o: file format elf64-x86-64 |
可以看到,确实如书上所说,前3字节是操作,后4字节是操作数