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