程序的机器级表示

本节内容来源于深入理解计算机系统(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
2
3
4
5
long ex(){
long x=*xp;
*xp=y;
return x;
}

指令

1
2
3
4
5
//xp in %rdi  y in %rsi
ex:
movq (%rdi),%rax
movq %rsi,(%rdi)
ret

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
2
3
4
5
6
pushq S
R[%rsp]-8->R[%rsp]
S->M[R[%rsp]]
popq D
M[R[%rsp]]->D
R[%rsp]+8->R[%rsp]

假设栈顶的地址是 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
2
xorq %rdx, %rdx
movq $0x0, %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
2
3
4
5
6
7
8
9
10
11
12
13
14
xorw %dx, %dx
xorl %edx, %edx
xorq %rdx, %rdx
xorq %rdi, %rdi
xorq %rsi, %rsi
movq $0x0, %rdx
movq $0xa, %rdx
movq $0xaa, %rdx
movq $0xaaaa, %rdx
movq $0xaaaaaa, %rdx
movq $0xaaaaaaaa, %rdx
movq %rdi, %rdx
movq (%rdi), %rdx
movq %rdi, (%rdx)

反汇编

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
text.o:     file format elf64-x86-64
Disassembly of section .text:
0000000000000000 <.text>:
0: 66 31 d2 xor %dx,%dx
3: 31 d2 xor %edx,%edx
5: 48 31 d2 xor %rdx,%rdx
8: 48 31 ff xor %rdi,%rdi
b: 48 31 f6 xor %rsi,%rsi
e: 48 c7 c2 00 00 00 00 mov $0x0,%rdx
15: 48 c7 c2 0a 00 00 00 mov $0xa,%rdx
1c: 48 c7 c2 aa 00 00 00 mov $0xaa,%rdx
23: 48 c7 c2 aa aa 00 00 mov $0xaaaa,%rdx
2a: 48 c7 c2 aa aa aa 00 mov $0xaaaaaa,%rdx
31: 48 ba aa aa aa aa 00 movabs $0xaaaaaaaa,%rdx
38: 00 00 00
3b: 48 89 fa mov %rdi,%rdx
3e: 48 8b 17 mov (%rdi),%rdx
41: 48 89 3a mov %rdi,(%rdx)

可以看到,确实如书上所说,前3字节是操作,后4字节是操作数