image.png
我们目前用的电脑,CPU 一般是 x86-64 架构,是 64 位机。
main.c
#include
int main(int argc, char *argv[]) {
printf("Hello, 1519!\n");
return 0;
}
gcc -S -O2 main.c -o main.s
.section __TEXT,__text,regular,pure_instructions
.build_version macos, 10, 15 sdk_version 10, 15, 4
.globl _main ## -- Begin function main
.p2align 4, 0x90
_main: ## @main
.cfi_startproc
## %bb.0:
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset %rbp, -16
movq %rsp, %rbp
.cfi_def_cfa_register %rbp
leaq L_str(%rip), %rdi
callq _puts
xorl %eax, %eax
popq %rbp
retq
.cfi_endproc
## -- End function
.section __TEXT,__cstring,cstring_literals
L_str: ## @str
.asciz "Hello, 1519!"
.subsections_via_symbols
as main.s -o main.o
gcc main.o -o main
./main
Hello, 1519!
汇编语言的组成元素这段代码里有指令、伪指令、标签和注释四种元素,每个元素单独占一行。
指令(instruction)是直接由 CPU 进行处理的命令:
开头的一个单词是助记符(mnemonic),后面跟着的是操作数(operand),有多个操作数时以逗号分隔。
movq %rsp, %rbp
伪指令以“.”开头,末尾没有冒号“:”
.subsections_via_symbols
标签以冒号“:”结尾,用于对伪指令生成的数据或指令做标记。例如 L_.str: 标签是对一个字符串做了标记。其他代码可以访问标签,例如跳转到这个标签所标记的指令。
L_str: ## @str
.asciz "Hello, 1519!"
第四种元素,注释,以“#”号开头,这跟 C 语言中以 // 表示注释语句是一样的。
详细了解指令这个元素
image.png
而在指令中使用操作数,可以使用四种格式,它们分别是:立即数、寄存器、直接内存访问和间接内存访问。
立即数以 $
开头
GNU 的汇编器规定寄存器一定要以 %
开头。
movl $15, %eax
直接内存访问
当我们在代码中看到操作数是一个数字时,它其实指的是内存地址。
间接内存访问:带有括号,比如(%rbp),它是指 %rbp 寄存器的值所指向的地址。
间接内存访问的完整形式是:
偏移量(基址,索引值,字节数)
这样的格式。
其地址是:
基址 + 索引值 * 字节数 + 偏移量
mov 指令
mov 寄存器|内存|立即数, 寄存器|内存
lea 指令,lea 是“load effective address”的意思,装载有效地址。
lea 源,目的
add 指令是做加法运算,它可以采取下面的格式:
add 立即数, 寄存器
add 寄存器, 寄存器
add 内存, 寄存器
add 立即数, 内存
add 寄存器, 内存
其他算术运算的指令:
image.png
栈:
image.png
image.png
image.png
6 个的参数
image.png
超过 6 个的参数
image.png
跳转类:
image.png
过程调用:
image.png
比较操作:
image.png
image.png
x86-64 架构的寄存器
在汇编代码中,我们经常要使用各种以 % 开头的寄存器的符号。初学者阅读这些代码时,通常会有一些疑问:有几个寄存器可以用?我可以随便用它们吗?使用不当会不会造成错误?等等。所以,有必要让你熟悉一下这些寄存器,了解它们的使用方法。x86-64 架构的 CPU 里有很多寄存器,我们在代码里最常用的是 16 个 64 位的通用寄存器,分别是:
%rax,%rbx,%rcx,%rdx,%rsi,%rdi,%rbp,%rsp,
%r8,%r9,%r10,%r11,%r12,%r13,%r14,%r15。
%rax 除了其他用途外,通常在函数返回的时候,把返回值放在这里。
%rsp 作为栈指针寄存器,指向栈顶。
%rdi,%rsi,%rdx,%rcx,%r8,%r9 给函数传整型参数,依次对应第 1 参数到第 6 参数。超过 6 个参数怎么办?放在栈桢里。
如果程序要使用 %rbx,%rbp,%r12,%r13,%r14,%r15 这几个寄存器,是由被调用者(Callee)负责保护的,也就是写到栈里,在返回的时候要恢复这些寄存器中原来的内容。其他寄存器的内容,则是由调用者(Caller)负责保护,如果不想这些寄存器中的内容被破坏,那么要自己保护起来。
image.png
寄存器
寄存器
8 个 80 位的 x87 寄存器,用于做浮点计算;
8 个 64 位的 MMX 寄存器,用于 MMX 指令(即多媒体指令),这 8 个跟 x87 寄存器在物理上是相同的寄存器。在传递浮点数参数的时候,要用 mmx 寄存器。
16 个 128 位的 SSE 寄存器,用于 SSE 指令。我们将在应用篇里使用 SSE 指令,讲解 SIMD 的概念。
指令寄存器,rip,保存指令地址。CPU 总是根据这个寄存器来读取指令。
flags(64 位:rflags, 32 位:eflags)寄存器:每个位用来标识一个状态。比如,它们会用于比较和跳转的指令,比如 if 语句翻译成的汇编代码,就会用它们来保存 if 条件的计算结果。
参考:https://time.geekbang.org/column/intro/219