arm64 assembly crash course
Note: May or may not be specific to iOS.
- General purpose registers 0 through 30 with two addressing modes:
- Zero register
xzr. Write to = discard, read from =
- Stack pointer
sp- unlike other instruction sets, never modified implicitly (e.g. no
- Instruction pointer
pc, not modifiable directly.
- A lot of float, vector and system registers, look up as needed.
- First register in assembly is usually destination, rest are source (except for
movto copy one register to another, e.g.
mov x0, x1->
x0 = x1.
- Small constants usually OR'ed with zero register, e.g.
orr x0, xzr, 5.
- Big constants usually loaded with
movz x0, 0x1234, lsl 32 movk x0, 0x5678, lsl 16 movk x0, 0x9abc
x0 = 0x123456789abc.
movnfor negative values, e.g.
movn x0, 1->
x0 = -1.
lsrinstructions = logic-shift-left and logic-shift-right, e.g.
lsl x0, x0, 8->
x0 <<= 8.
lsrnot only used as instructions, but also as operands to other instructions (see
aslfor arithmetic shift also exists, but less frequently used.
- Lots of arithmetic, logic and bitwise instructions, look up as needed.
strwith multiple variations and addressing modes:
ldr x0, [x1]->
x0 = *x1
str x0, [x1]->
*x1 = x0
ldr x0, [x1, 0x10]->
x0 = *(x1 + 0x10)
stpto load/store two registers at once behind each other, e.g.:
stp x0, x1, [x2]->
*x2 = x0; *(x2 + 8) = x1;
- Multiple variations for load/store size:
- Register names
- Register names
- Multiple variations for sign-extending registers smaller than 64-bit:
ldrsw x0, [x1]-> load 32-bit int, sign extend to 64-bit
ldrsh x0, [x1]-> load 16-bit int, sign extend to 64-bit
ldrsb x0, [x1]-> load 8-bit int, sign extend to 64-bit
- (No equivalent
- Three register addressing modes:
ldr x0, [x1, 0x10]
ldr x0, [x1, 0x10]!(notice the
x1 += 0x10; x0 = *x1;
ldr x0, [x1], 0x10->
x0 = *x1; x1 += 0x10;
- Memory addresses usually computed by PC-relative instructions:
adr x0, 0x12345(only works for small offset from PC)
- Bigger ranges use
adrp x0, 0xffffff8012345000 ; "address of page", last 12 bits are always zero add x0, x0, 0x678
- Even bigger ranges usually stored as pointers in data segment, offset by linker and loaded with
x7first 8 arguments, rest on the stack (low address to high) with natural alignment (as if they were members of a struct)
x29frame pointer (basically also just callee-saved)
- Functions that save anything in
x28usually start like this:and end like this:
stp x24, x23, [sp, -0x40]! stp x22, x21, [sp, 0x10] stp x20, x19, [sp, 0x20] stp x29, x30, [sp, 0x30] add x29, sp, 0x30The stack for local variables is usually managed separately though, with
ldp x29, x30, [sp, 0x30] ldp x20, x19, [sp, 0x20] ldp x22, x21, [sp, 0x10] ldp x24, x23, [sp], 0x40 ret
add sp, sp, 0x...and
sub sp, sp, 0x....
- Variadic arguments are passed on the stack (low address to high), each promoted to 8 bytes. Structs that don't fit into 8 bytes have a pointer passed instead.
Fixed arguments that don't fit into
x7come before variadic arguments on the stack, naturally aligned.
- System register
nzcvholds condition flags (Negative, Zero, Carry, oVerflow).
Set by one instruction and acted upon by a subsequent one, the latter using condition codes.
(Could be accessed as normal system register, but usually isn't.)
- Some instructions use condition codes as suffixes (
instr.cond), others as source operands (
instr ..., cond). List of condition codes:
ne= equal/not equal
ge= less than/less or equal/greater than/greater or equal (signed)
hs= lower/lower or same/higher/higher or same (unsigned)
- A few more weird flags, seldom used.
- Unlike many other instruction sets, arm64 sets carry on no-borrow rather than borrow.
cc= carry set/carry clear are aliases of
cmp= most common/basic compare instruction, sets condition flags. Examples:
cmp x0, x1 cmp x0, 3
- Other instructions that set condition flags:
cmn= compare negative
tst= bitwise test
adcs= add/add with carry
sbcs= subtract/subtract with carry
ngcs= negate/negate with carry
- Some more bitwise and float instructions.
- Some instructions that act on condition flags:
cset= conditional set, e.g.:->
cmp x0, 3 cset x0, lo
x0 = (x0 < 3)
csel= conditional select, e.g.:->
cmp x0, 3 csel x0, x1, x2, lo
x0 = (x0 < 3) ? x1 : x2
(Translates nicely to ternary conditional.)
ccmp= conditional compare, e.g.:->
cmp x0, 3 ccmp x0, 7, 2, hs b.hi 0xffffff8012345678
hicondition will be true if
x0 < 3 || x0 > 7(third
ccmpoperand is raw
Often generated by compiler for logical and/or of two conditions.
- Many, many more.
b= simple branch, jump to PC-relative address.
Can be unconditional:or conditional:
b 0xffffff8012345678Used primarily within function for flow control.
cmp x0, 3 b.lo 0xffffff8012345678 ; jump to 0xffffff8012345678 if x < 3
cbnz= compare-branch-zero and compare-branch-non-zero.
Just shorter ways to writeor
cmp xN, 0 b.eq 0x...(Translate nicely to C
cmp xN, 0 b.ne 0x...
tbnz= test single bit and branch if zero/non-zero.
tbz x0, 3, ...translates to
if((x0 & (1 << 3)) == 0) goto ....
bl= branch-and-link (e.g.
Store return address to
x30and jump to PC-relative address. Used for static function calls.
blr= branch-and-link to register (e.g.
Store return address to
x30and jump to address in
x8. Used for calls with function pointers or C++ virtual methods.
br= branch to register (e.g.
Jump to address in
x8. Used for tail calls.
ret= return to address in register, default:
Can in theory use registers other than
ret x8), but compiler doesn't usually generate that.