汇编

汇编语言也是一门计算机编程语言。汇编指令是汇编语言的一部分,汇编指令和机器指令一一对应,每一条汇编指令都对应着一条机器指令。机器指令是二进制格式的,汇编指令使用符号来表示机器指令。

不同的 CPU 所支持的机器指令不一样,所以其汇编指令也不同,即使是相同的 CPU,不同的汇编工具和平台所使用的汇编指令格式也有些差别。

汇编指令:

0x0000000000400770:    add %rdx,%rax

编译成机器指令:

(gdb) x/3xb 0x40054d
0x40054d: 0x48    0x01    0xd0 # 机器指令
(gdb)

汇编指令格式

每一条汇编指令通常都由两部分组成:

  1. 操作码:作码指示 CPU 执行什么操作,比如是执行加法,减法还是读写内存。每条指令都必须要有操作码。
  2. 操作数:操作数表示指令的操作对象。比如加法操作需要两个加数,这两个加数就是这条指令的操作数。操作数的个数一般是 0 个,1 个或 2 个。

汇编指令示例

  1. add %rdx,%rax:将 rdx 寄存器中的值加到 rax 寄存器中。
  • add,表示执行加法操作,它有两个操作数,rdxrax
  • 如果一条指令有两个操作数,那么第一个操作数叫做源操作数,第二个操作数叫做目的操作数,目的操作数表示这条指令执行完后结果应该保存的地方。
  • 第二个操作数 rax 寄存器既是源操作数也是目的操作数,因为 rax 既是加法操作的两个加数之一,又得存放加法操作的结果。
  • 指令执行完后 rax 寄存器的值发生了改变,指令执行前的值被覆盖而丢失了,如果 rax 寄存器之前的值还有用,那么就得先用指令把它保存到其它寄存器或内存之中。
  1. callq 0x400526:调用函数,只有一个操作数,操作数是 0x400526,它是被调用函数的地址。
  2. 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 地址处的指令。

stack-ret

图中返回地址就是 0x40055e

ℹ️

retq 指令从堆栈中弹出返回地址(即 0x40055e),将弹出的地址加载到 rip 寄存器中。

函数执行完以后,局部变量会被销毁,rsp 寄存器也会恢复到函数调用前的状态。retq 只需要调整偏移量 rsp + 8 (64 位操作系统应该是 rsp + 16)就能拿到返回地址。

jmp/je/jle/jg/jge 等等 j 开头的指令

这些都属于跳转指令,操作码后面直接跟要跳转到的地址或存有地址的寄存器,这些指令与高级编程语言中的 gotoif 等语句对应。用法示例:

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
最后更新于