05 计算机指令:使用纸带编程
一、编译成汇编
test.c
// test.c
int main()
{
int a = 1;
int b = 2;
a = a + b;
}
程序想要在 Linux 操作系统上运行,需要将整个程序翻译成一个汇编代码的程序,这个过程叫编译成汇编代码
对于汇编代码,可以再使用汇编器翻译成机器码。机器码由 0 和 1 组成的机器语言表示。一条条机器码就是一条条的计算机指令
使用 gcc 和 objdump 两个命令,打印出程序对应的汇编代码和机器码
gcc -g -c test.c
// -M:指定了指令集的类型为 intel
objdump -d -M intel -S test.o
命令执行结果
test.o: 文件格式 elf64-x86-64
Disassembly of section .text:
0000000000000000 <main>:
int main()
{
0: 55 push rbp
1: 48 89 e5 mov rbp,rsp
int a = 1;
4: c7 45 f8 01 00 00 00 mov DWORD PTR [rbp-0x8],0x1
int b = 2;
b: c7 45 fc 02 00 00 00 mov DWORD PTR [rbp-0x4],0x2
a = a + b;
12: 8b 45 fc mov eax,DWORD PTR [rbp-0x4]
15: 01 45 f8 add DWORD PTR [rbp-0x8],eax
18: b8 00 00 00 00 mov eax,0x0
}
1d: 5d pop rbp
1e: c3 ret
- 8 行:push rbp,将 rbp 基址指针寄存器 push 入栈
- 9 行:mov rbp,rsp,将 rsp 栈指针寄存器指向地址赋给 rbp 基址指针寄存器
- 11 行:mov DWORD PTR [rbp-0x8],0x1
DWORD 双字(四个字节)、PTR 即指针,pointer 的缩写
[] 里的值对应一个地址值,地址指向一个双字型数据
mov DWORD PTR [rbp-0x8],0x1 意思是将 16 进制的 1 赋值给基址指针寄存器偏移 8 位的位置 - 13 行:mov DWORD PTR [rbp-0x4],0x2,同 11 行,意思是将 16 进制的 2 赋值给基址指针寄存器偏移 4 位的位置
- 15 行:mov eax,DWORD PTR [rbp-0x4],将基址指针寄存器偏移 4 位的位置的指向赋给 eax 寄存器
- 16 行:add DWORD PTR [rbp-0x8],eax,将 eax 和基址指针寄存器偏移 8 位的位置指向的值相加,结果赋给基址指针寄存器偏移 8 位的位置,即 eax(2)+ rbp-8(1)= 3,结果 3 赋给基址指针寄存器偏移 8 位的位置
- 17 行:mov eax,0x0,将 eax 寄存器清零
- 19 行:pop rbp,将 rbp 基址指针寄存器 pop 弹出
- 20 行:ret,函数返回
补充:生成 Linux 汇编
Linux 和 Windows 生成的汇编参数的顺序是反的,Linux 是正序,Windows 是逆序
二、解析指令和机器码
- 第一类:算术类指令
加减乘除,在 CPU 层面,都会变成一条条算数指令 - 第二类:数据传输类指令
给变量赋值、在内存里读写数据 - 第三类:逻辑类指令
逻辑上的与或非 - 第四类:条件分支类指令
if-else 等 - 第五类:无条件跳转指令
在调用函数时,其实就是发起了一个无条件跳转指令
2.1 MIPS 指令集
操作码(Opcode):高 6 位,代表这条指令具体是什么样的指令
R 指令:一般用来做算数和逻辑操作,里面有读取和写入寄存器的地址,如果是逻辑位移,后面还有位移操作的偏移量,功能码是在前面的操作码不够的时候,扩展操作码来表示对应的具体指令
I 指令:通常用在数据传输、条件分支,以及运算时使用的是常数的时候,因为没有位移量和操作码,也没有了第三个寄存器(rd),而是把直接把这三部分合并成一个地址值或者一个常数
J 指令:是一个跳转指令,高 6 位之外的 26 位都是一个跳转后的地址
三、举个栗子
add $t0,$s1,$s2
这是一个加法算数指令,解释一下具体操作
add:表示这是一个加法操作,对应 MIPS 指令里的 opcode 是 0,5 位二进制:000000
rs:代表第一个寄存器,对应 s1 的地址是 17???,5 位二进制:10001
rt:代表第二个寄存器,对应 s2 的地址是 18???,二进制:10010
rd:代表目标的临时寄存器,对应 t0 的地址是 8???,二进制:01000
不是位移操作,位移量是 0,二进制:00000
功能码是 32???,二进制:100000
000000 10001 10010 01000 00000 100000 = 000000 0010 0011 0010 0100 0000 0010 0000 = 0 2 3 2 4 0 2 0 = 0x02324020
打在纸带上,就是这样
加法指令执行的过程,以及 CPU 怎么和 ALU 算数逻辑单元串联起来,待续…