常见汇编指令

0x0. 引言

好记性不如烂笔头,本文旨在记录一些日常工作中经常会遇到的汇编指令,并会不断更新。目前只包含ARMAT&T两种格式,以ARM64为主。

0x1. ARM64

mov

传送指令:将立即数传送到寄存器/内存单元,或互传内存单元和寄存器两者中的数据。

例如:mov x0, #0x1 / mov $0x1, %rdi 将立即数0x1移动到寄存器x0/rdi中

mvn

该指令与mov唯一不同的是:需要对操作数进行按位取反。

str

store register. 将寄存器中的数据存到内存单元中。

例如:str x0, [sp, #0x28] 将寄存器x0中的数据存到地址sp+0x28处

stp

store pair of registers.

例如:stp x29, x30, [sp, #0x70] 先将x29存到地址sp+0x70处,再将x30存到sp+0x78处 (Tip1)。

Tip1: 64位下,寄存器大小为8bytes

ldr

load register. 从内存中读取数据,存到寄存器中。

例如:ldr x1, [sp, #0x30] 将内存地址sp+0x30处的数据加载到x1中。

ldp

load pair of registers.

例如:ldp x29, x30, [sp, #0x70] 将内存地址sp+0x70处的数据加载到x29中,再将sp+0x78(Tip1)处的数据加载到x30中。

b

branch. 无条件跳转。

例如:b 0x1b6b79cf8 跳转到0x1b6b79cf8处继续执行。

bl

branch with link. 将下一条指令地址copy到lr中,然后跳转。由于保存了下一条指令地址(相对于pc),所以可实现子程序的返回,而b只能单纯的实现跳转,不能实现子程序返回。

例如:

   0x102b7d8a4 <+160>: bl   0x102bda450 ; symbol stub for: objc_msgSend
-> 0x102b7d8a8 <+164>: adrp x0, 107
1
2

当程序执行完bl指令时,lr中的内容应该是是bl下一条指令的地址,即0x102b7d8a8。通过控制台查看lr中的内容如下:

fp = 0x000000016d2853c0
lr = 0x0000000102b7d8a8  `-[AppDelegate application:didFinishLaunchingWithOptions:] + 164 at AppDelegate.m:41:5
sp = 0x000000016d285350
1
2
3

cbz

compare and branch on zero. 比较操作数是否为0,若为0则跳转。

例如:cbz w24, 0x1b6b798f4 若w24 (x24寄存器低32位) 中的数据为0,则跳转到0x1b6b798f4处执行 (Note1)。

Note1: 跳转地址需要在cbz/cbnz指令之后的4~130字节内。

cbnz

compare and branch on non-zero. 操作数不为0,则跳转。

例如:cbnz w27, 0x1b73c9e2c 若w27中的数据不为0,则跳转到0x1b73c9e2c处执行 (Note1)。

lsl

logical shift left. 左移位。

例如:str x0, [x1, x2, lsl #2] 将x2中的值左移两位,再加上x1中的值,得到的结果为一个内存地址,最后将x0中的数据存到该内存中。

lsr

logical shift right. 右移位。

cmp

compare. 比较两个数是否相等,首先将两个数相减,若差为0,则ZF (Tip2) 为1,即两者相等。

cmp w8, #0x3 通过判断w8中的值与0x3的差值是否为0,即ZF (Tip2) 是否为1,来比较两者是否相等。

Tip2: ZF是零标志位寄存器。它记录相关指令执行后,其结果是否为0。若结果为0,则ZF=1;若结果为1,则ZF=0。

adrp

form pc-relative address to 4KB page. 先将操作数左移12位,再将pc的低12位清零 (即3个十六进制位清零,得到一个页对齐地址),最后两者相加,赋值给寄存器。

例如:

   0x1b703f074 <+44>:  adrp   x20, 270332
-> 0x1b703f078 <+48>:  ldr    x0, [x20, #0x7a0]
1
2

执行adrp时,先将操作数270332 (十六进制为0x41ffc) 左移12位得到0x41ffc000,再将pc (0x1b703f074) 低12清零得到 0x1b703f000,最后两者相加得到0x1f903b000赋值给x20。通过控制台查看x20中的内容:

 x19 = 0x000000016ef25fb0
 x20 = 0x00000001f903b000  
 x21 = 0x0000000281454380
1
2
3

adr

load a program-relative or register-relative address into a register. 是一条伪指令,遇到该指令时汇编器会生成一条addsub指令,来计算pc和操作数的和或者差,最后将得到的结果,放在寄存器中。

例如:

; 生成加法指令,即x0 = 0x1022c3b08 + 0x2fc48 = 0x1022f3750
   0x1022c3b08 <+108>: adr x0, #0x2fc48  ; _MergedGlobals + 16
-> 0x1022c3b0c <+112>: nop 

; 生成减法指令,即x2 = 0x1022c3b10 - 0x390 = 0x1022c3780
   0x1022c3b10 <+116>: adr x2, #-0x390 ; initializeAvailabilityCheck
-> 0x1022c3b14 <+120>: nop
1
2
3
4
5
6
7

brk

breakpoint instruction. 用于生成断点指令异常。

tbz

test branch zero. 测试位为0,则跳转。

tbz w24, #0x6, 0x19307005c ; 即w24第6位,若为0,则跳转到0x19307005c执行
1

w24 二级制数为:0b00000111000000000000100000000110

tbnz

test branch no zero. 测试位不为0,则跳转。

tbnz w24, #0x6, 0x19307005c ; 即w24第6位,若不为0,则跳转到0x19307005c执行
1

w24 二级制数为:0b00000111000000000000100000000110

0x2. AT&T

test

将指令后的两个数进行按位与操作,其结果影响ZF。通常用来判断某个数是否为0/nil。

例如:

testq %rax, %rax ; 若%rax中的值为0,则ZF=1;反之,ZF=0,q表示四字长
1

sete

取ZF中的值,放入目标寄存器中。

例如:

testq %rax, %rax  ; 两个寄存器中的值按位与,结果影响ZF
sete  %r12        ; 若%rax和%rbx相等,即ZF=1,则将%r12设为1,反之设为0
1
2

setne

取ZF中的值,然后取反,再放入目标寄存器中。

例如:setne %rax 若ZF=1,则%rax设为0;反之设为1

je / jz

je 和 jz这两条指令虽然名字不同,但做的事情都是一样的。即ZF=1,则跳转。

例如:

libobjc.A.dylib`objc_retain:
    0x7fff50bad350 <+0>:  testq  %rdi, %rdi      ; 判断%rdi中的值(第一个参数)是否为nil
    0x7fff50bad353 <+3>:  je     0x7fff50bad371  ; <+33> 若%rdi为nil,则跳转到0x7fff50bad371执行
    0x7fff50bad355 <+5>:  js     0x7fff50bad373  ; <+35>
->  0x7fff50bad357 <+7>:  movq   (%rdi), %rax
1
2
3
4
5

jne / jnz

jne 和 jnz这两条指令做的事情也都是一样的。即ZF=0,则跳转。

js

jump if sign. 若结果为负数,即SF=1,则进行跳转。

SF: 符号标志位

它记录相关指令执行后,其结果是否为负。若结果为负,则SF=1;若为非负,则SF=0。

nop

no operation. 不执行任何操作。是一条单字节指令。 Opcode 90

ud2

undefined instruction. 该指令用于生成一个无效操作码。当CPU试图执行无效或未定义的操作码时,将发生无效的操作码异常UD2指令除了引发无效的操作码异常外,与NOP指令相同。

这里的异常是指CPU在发生“错误”时生成的。大多数情况下,有些异常并不是真正的错误,而是中断的一种类型。比如: Page Fault。异常分类如下:

  1. Faults (错误):这些错误可以被纠正,程序可以像什么都没有发生一样继续运行。
  2. Traps (陷阱):陷阱指令执行后会立即被报告。
  3. Aborts (终止):一些严重的不可恢复的错误。

ret

该指令用于子程序的返回。将栈顶数据(返回地址)弹出,赋值给 ip 寄存器,此时 sp 会增加一个内存单元大小,来释放栈空间。

栈底 [ ... | ... , parameters, return address | previous frame pointer, %rbp ] 栈顶

pop %rbp 👇
栈底 [ ... | ... , parameters, return address ] 栈顶

ret 👇
栈底 [ ... | ... , parameters ] 栈顶

caller release parameters 👇
栈底 [ ... | ... ] 栈顶

最后更新: 4/9/2020, 3:11:50 PM