参考:深入浅出计算机组成原理,九曲阑干"深入理解计算机系统" www.bilibili.com/video/BV1Ci…
前言
从硬件的角度来看,CPU 就是一个超大规模集成电路,通过电路实现了加法、乘法乃至各种各样的处理逻辑。
从软件工程师的角度,CPU 就是一个执行各种计算指令(Instruction Code)的逻辑机器。
不同的 CPU 有着不同的计算机指令集,本系列文章将依据 Intel 的 x86-64 指令集来进行介绍。
接下来,我们以一个例子来简单介绍一下 Intel 的 x86-64 指令集。
背景知识
- 在 Intel x86-64 的处理器中包含了 16 个通用目的寄存器,这些寄存器用来存放整数数据和指针
%rax %rbx %rcx %rdx
%rsi %rdi %rbp %rsp
%r8 %r9 %r10 %r11
%r12 %r13 %r14 %r15
通用寄存器:可用于存储任何指令的地址或数据的寄存器。
- 调用者保存寄存器和被调用者保存寄存器。
func_A:
...
movq $123, %rbx
call func_B
addq %rbx, %rax
...
ret
Caller
func_B:
...
addq $456 %rbx
...
ret
Callee
寄存器 rbx 的内容在调用函数 B 的前后应该保持一致。解决这个问题,有两个策略:
- 调用者保存
func_A:
...
movq $123, %rbx
// save register rbx
call func_B
// restore register rbx
addq %rbx, %rax
...
ret
- 被调用者保存
func_B:
...
// save register rbx
addq $456 %rbx
// restore register rbx
...
ret
Callee
对于具体使用哪一种策略,不同的寄存器被定义成不同的策略。
Callee saved:
%rbx, %rbp, %r12, %r13, %r14, %r15
Caller saved:
%r10, %r11
%rax
%rdi, %rsi, %rdx, %rcx, %r8, %r9
例子
首先看一个 c 代码的示例:
// main.c
#include <stdio.h>
void mulstore(long, long, long*);
int main()
{
long d;
multstore(2, 3, &d);
printf("2 * 3 --> %ld\n", d);
return 0;
}
long mult2(long a, long b)
{
long s = a * b;
return s;
}
// mstore.c
long mult2(long, long);
void mulstore(long x, long y, long* dest)
{
long t = mult2(x, y);
*dest = t;
}
我们可以通过以下命令来编译程序:
// 其中编译选项 -Og 是用来告诉编译器生成符合原始 C 代码整体结构的机器代码
linux > gcc -Og -o prog main.c mstore.c
首先以源文件 mstore.c 为例,看一下 C 代码与汇编代码之间的关系
// 产生汇编文件 mstore.s
linux > gcc -Og -S mstore.c
其中以"."开头的行都是知道汇编器和链接器工作的伪指令,也就是说我们完全可以忽略这些以"."开头的行。
mulstore:
pushq %rbx
movq %rdx, %rbx
call mult2
movq %rax, (%rbx)
popq %rbx
ret
- 第一条命令
pushq %rbx // 将寄存器rbx的值压入程序栈进行保存
与该命令相对应的是 popq 命令
pushq %rbx // 保存寄存器 rbx 的内容
...
popq %rbx // 恢复寄存器 rbx 的内容
- 第二条命令
movq %rdx, %rbx // 将寄存器 rdx 的内容复制到寄存器 rbx
根据寄存器的用法,函数 multstore 的三个参数分别保存在
x -> %rdi
y -> %rsi
dest -> %rdx
当这条指令执行完成后 rdx 寄存器和 rbx 寄存器内的值相同,都是 dest 变量的内容。 其中 movq 中的 q 表示数据的大小。
| C declaration | Intel data type | Assembly-code suffix | Size(bytes) |
|---|---|---|---|
| char | Byte | b | 1 |
| short | Word | w | 2 |
| int | Double word | l | 4 |
| long | Quad word | q | 8 |
| char* | Quad word | q | 8 |
| float | Single precision | s | 4 |
| double | Double precision | 1 | 8 |
大多数 GCC 生成的汇编指令都有一个字符后缀来表示操作数的大小
例如,数据传输指令就有四种:
movb, movw, movl, movq
- 第三条指令
call mult2 // 函数调用,函数返回值会保存到寄存器 rax 中
x * y -> %rax
movq %rax, (%rbx) // 将寄存器 rax 的值送到内存中,内存的地址就是存放在 rbx 中
ret // 函数返回
额外介绍:
// 生成机器代码文件 mstore.o
linux > gcc -Og -c mstore.c
反汇编器 objdump,将机器码反汇编成汇编代码
linux > objdump -d mstore.o
常见指令
常见的指令可以分成五大类:
- 算术类指令。加减乘除。
- 数据传输类指令。给变量赋值、在内存里读写数据,用的都是数据传输类指令。
- 逻辑类指令。逻辑上的与或非。
- 条件分支类指令。日常的 "if/else",其实都是条件分支类指令。
- 无条件跳转指令。函数调用。