最近在深入学习操作系统, 少不了和汇编代码打交道, 尽管原来对汇编指令有所了解, 但实际看到一条指令还是模凌两可. 本文对常用汇编指令进行一个总结, 同时区分几个易混的指令

汇编格式说明

一般来说, 在Linux下使用GCC生成的汇编代码会使用AT&T/UNIX汇编语法, 而使用IDA, pwndbg, gef反编译出来的代码会使用Intel汇编语法

下面大致讲一下两者语法的不同, 后文将使用AT&T汇编格式

  1. 操作数顺序不同

    Op-code src dst //AT&T语法
    Op-code dst src //Intel语法
  2. 寄存器, 立即数的使用

    movl  $0xff,%ebx //AT&T语法
    mov ebx,0ffh //Intel语法
  3. 操作数大小

    movb  (%rbp), %al //AT&T语法
    mov al, byte ptr rbp //Intel语法

    movb, movw, movl, movq 分别代表操作1, 2, 4, 8字节, 若仅是mov, 则根据应用是32位还是64位决定是movl还是movq

  4. 内存寻址

    movl -8(%ebp, %edx, 4), %eax //AT&T语法
    mov eax, [%ebp + %edx * 4 - 8h] //Intel语法

常见指令

mov指令

mov作为最常用的指令, 用法非常灵活

语法

mov <reg>, <reg> // 寄存器的值到寄存器的值
mov <reg>, <mem> // 寄存器的值到寄存器指针指向的内存(通过加括号的方式)
mov <mem>, <reg> // 内存到寄存器
mov <imm>, <reg> // 立即数到寄存器(立即数前要加$)
mov <imm>, <mem> // 立即数到内存

内存寻址方式可采用disp(base, index, scale)的方式, 对表达式整体计算的值作为内存地址 如mov -8(%ebp, %edx, 4), %eax 表示加载 *(ebp + (edx * 4) - 8) 到 eax寄存器

push / pop指令

push可以理解为在mov到esp内存的同时使esp减小(高地址到低地址扩展)
pop 可以理解为在mov到esp内存的同时的同时使esp增加

语法

push <reg>
push <mem>
push <imm>

在64位里push操作8字节的数, 32位只能操作4子节的数

lea 指令

lea 可以将内存操作数放入寄存器中
lea 虽然用法单一, 但十分灵活, 常用来代替乘法和加法指令

语法

lea <mem>, <reg>

这里的mem并不是真的取内存中的值, 而是操作数本身 如lea -8(%ebp, %edx, 4), %eax 表示加载 ebp + (edx * 4) - 8 的值到 eax寄存器

add / sub 指令

这两个指令比较简单, 就是做加法和减法, 将操作结果存在目的操作数中

语法

add <reg>, <reg>
add <mem>, <reg>
add <reg>, <mem>
add <imm>, <reg>
add <imm>, <mem>

inc / dec 指令

基本和add / sub一致, 但只能加减1

语法

inc <reg>
inc <mem>

imul 指令

乘法指令, 可以有两个操作数或三个操作数, 第三个操作数必须为寄存器

语法

imul <reg32>, <reg32>
imul <mem>, <reg32>
imul <imm>, <reg32>, <reg32>
imul <imm>, <mem>, <reg32>

idiv 指令

除法指令, 需要把被除数放入eax寄存器中, 得到商在eax中, 余数在edx中

语法

idiv <reg>
idiv <mem>

and / or / xor 指令

做与, 或, 异或运算

语法

and <reg>, <reg>
and <mem>, <reg>
and <reg>, <mem>
and <imm>, <reg>
and <imm>, <mem>

常使用xor %edx, %edx 将寄存器置0

not / neg 指令

not 按位取反, neg 按补码取负

语法

not <reg>
not <mem>

shl / shr / sal / sar指令

逻辑左移/逻辑右移/算数左移/算数右移操作

语法

shl <imm>, <reg>
shl <imm>, <mem>
shl %cl, <reg>
shl %cl, <mem>

jmp / jcondition /cmp指令

跳转到某一标签, 常用语循环语句和判断语句

语法

jmp <label>

cmp <reg>, <reg>
cmp <mem>, <reg>
cmp <reg>, <mem>
cmp <imm>, <reg>

je <label> // 等于时跳转
jne <label> // 不等时跳转
jz <label> // cmp将两数相减, 为0时跳转
jg <label> // 大于时跳转
jge <label> // 大于等于跳转
jl <label> // 小于时跳转
jle <label> // 小于等于跳转

call / ret 指令

函数调用及函数返回

语法

call <label>

ret

常见问题

mov 和 lea 指令的区别

在简单操作上两者可以等价

mov %eax, %ebx

lea (%eax), %ebx

若mov使用了内存寻址, 则mov会取到对应地址的值, 而lea仅操作地址

mov -8(%ebp, %edx, 4), %eax

lea -8(%ebp, %edx, 4), %eax

在这种情况下, mov便无法替代lea

i++为什么不是原子操作

尽管add/inc可以直接对内存进行操作, 但实际情况下会先从内存加载到寄存器中, 再对寄存器进行加操作

转载申请

知识共享许可协议

本文知识共享署名 4.0 国际许可协议进行许可,转载时请注明原文链接