汇编
汇编语言也是一门计算机编程语言。汇编指令是汇编语言的一部分,汇编指令和机器指令一一对应,每一条汇编指令都对应着一条机器指令。机器指令是二进制格式的,汇编指令使用符号来表示机器指令。
不同的 CPU 所支持的机器指令不一样,所以其汇编指令也不同,即使是相同的 CPU,不同的汇编工具和平台所使用的汇编指令格式也有些差别。
汇编指令:
0x0000000000400770: add %rdx,%rax
编译成机器指令:
(gdb) x/3xb 0x40054d
0x40054d: 0x48 0x01 0xd0 # 机器指令
(gdb)
汇编指令格式
每一条汇编指令通常都由两部分组成:
- 操作码:作码指示 CPU 执行什么操作,比如是执行加法,减法还是读写内存。每条指令都必须要有操作码。
- 操作数:操作数表示指令的操作对象。比如加法操作需要两个加数,这两个加数就是这条指令的操作数。操作数的个数一般是 0 个,1 个或 2 个。
汇编指令示例
add %rdx,%rax
:将rdx
寄存器中的值加到rax
寄存器中。
add
,表示执行加法操作,它有两个操作数,rdx
和rax
。- 如果一条指令有两个操作数,那么第一个操作数叫做源操作数,第二个操作数叫做目的操作数,目的操作数表示这条指令执行完后结果应该保存的地方。
- 第二个操作数
rax
寄存器既是源操作数也是目的操作数,因为rax
既是加法操作的两个加数之一,又得存放加法操作的结果。 - 指令执行完后
rax
寄存器的值发生了改变,指令执行前的值被覆盖而丢失了,如果rax
寄存器之前的值还有用,那么就得先用指令把它保存到其它寄存器或内存之中。
callq 0x400526
:调用函数,只有一个操作数,操作数是0x400526
,它是被调用函数的地址。retq
:没有操作数,表示从被调用函数返回到调用函数继续执行。
常用指令
mov
复制源操作数到目的操作数。例:
mov %rsp,%rbp // 直接寻址,把 rsp 的值拷贝给 rbp,相当于 rbp = rsp
add/sub 指令
加减运算指令。例:
sub $0x350,%rsp // 源操作数是立即操作数,目的操作数直接寻址。rsp = rsp - 0x350
add %rdx,%rax // 直接寻址。rax = rax + rdx
$
符号做前缀,这种操作数叫做立即操作数,表示它是一个常量。
call/ret 指令
call 目标地址
指令执行函数调用,CPU 执行call
指令时首先会把 rip 寄存器中的值入栈,然后设置 rip 值为目标地址,又因为 rip 寄存器决定了下一条需要执行的指令,所以当 CPU 执行完当前call
指令后就会跳转到目标地址去执行(先把当前 rip 寄存器的值保存起来,因为要调用函数,所以把函数的目的地址放到 rip 寄存器中,这样 CPU 就可以跳转去执行目标地址的函数)。ret
指令从被调用的函数返回调用函数,它的实现原理是把call
指令入栈的返回地址弹出给 rip 寄存器。
# 调用函数片段
0x0000000000400559: callq 0x400526 <sum>
0x000000000040055e: mov %eax,-0x4(%rbp)
--------------------------------------------------
# 被调用函数片段
0x0000000000400526: push %rbp
......
0x000000000040053f: retq
函数调用过程
调用函数使用 callq 0x400526
指令调用 0x400526
处的函数,0x400526
是被调用函数的第一条指令所在的地址。0x40055e
会先从 rip 寄存器中取出入栈,然后把 rip 寄存器的值更新为 0x400526
。被调用函数在 0x40053f
处执行 retq
指令返回调用函数继续执行 0x40055e
地址处的指令。

图中返回地址就是 0x40055e
。
retq
指令从堆栈中弹出返回地址(即 0x40055e
),将弹出的地址加载到 rip 寄存器中。
函数执行完以后,局部变量会被销毁,rsp 寄存器也会恢复到函数调用前的状态。retq
只需要调整偏移量 rsp + 8
(64 位操作系统应该是 rsp + 16
)就能拿到返回地址。
jmp/je/jle/jg/jge 等等 j 开头的指令
这些都属于跳转指令,操作码后面直接跟要跳转到的地址或存有地址的寄存器,这些指令与高级编程语言中的 goto
和 if
等语句对应。用法示例:
jmp 0x4005f2
jle 0x4005ee
jl 0x4005b8
push/pop 指令
专用于函数调用栈的入栈出栈指令,这两个指令都会自动修改 rsp 寄存器。
push 源操作数
pop 目的操作数
push
入栈时 rsp 寄存器的值先减去 8 把栈位置留出来(移动栈顶指针,因为是由高位到低位,所以是减 8),然后把操作数复制到 rsp 所指位置。
push
指令相当于:
sub $8,%rsp
mov 源操作数,(%rsp)
pop
指令相当于:
mov( %rsp), 目的操作数
add $8,%rsp
leave 指令
leave
指令没有操作数,它一般放在函数的尾部 ret
指令之前,用于调整 rsp 和 rbp,这条指令相当于:
mov %rbp,%rsp
pop %rbp