汇编
汇编语言也是一门计算机编程语言。汇编指令是汇编语言的一部分,汇编指令和机器指令一一对应,每一条汇编指令都对应着一条机器指令。机器指令是二进制格式的,汇编指令使用符号来表示机器指令。
不同的 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 = rspadd/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 0x4005b8push/pop 指令
专用于函数调用栈的入栈出栈指令,这两个指令都会自动修改 rsp 寄存器。
push 源操作数
pop 目的操作数push 入栈时 rsp 寄存器的值先减去 8 把栈位置留出来(移动栈顶指针,因为是由高位到低位,所以是减 8),然后把操作数复制到 rsp 所指位置。
push 指令相当于:
sub $8,%rsp
mov 源操作数,(%rsp)pop 指令相当于:
mov( %rsp), 目的操作数
add $8,%rspleave 指令
leave 指令没有操作数,它一般放在函数的尾部 ret 指令之前,用于调整 rsp 和 rbp,这条指令相当于:
mov %rbp,%rsp
pop %rbp