Languages: English | 简体中文 | 繁體中文 | 日本語 | 한국어 | Français | Deutsch | Español | Italiano | Русский | العربية
For readers unfamiliar with ARM64, focusing on instructions generated by the dyncode compiler. Each instruction includes annotations and before/after comparisons.
General-purpose registers:
x0 ~ x30 64-bit general-purpose registers
w0 ~ w30 corresponding low 32-bit aliases
x29 (fp) frame pointer
x30 (lr) link register (holds return address)
sp stack pointer (not x31!)
xzr / wzr zero register (reads always return 0, writes are discarded)
Special registers:
pc program counter (cannot be written directly)
Apple ABI reserved:
x16, x17 platform reserved (intra-procedure-call scratch)
x18 platform reserved (TLS base, Apple exclusive)
Calling convention (AAPCS64):
Arguments: x0~x7 (integer), d0~d7 (floating-point)
Return value: x0 (integer), d0 (floating-point)
Callee-saved: x19~x28, x29(fp), x30(lr), sp
Caller-saved: x0~x18, d0~d31
Red zone: 128 bytes below sp (leaf functions can use directly)
b .Lloop ; branch to .Lloop (PC-relative, ±128MB range)bl _fib ; branch to _fib, saving return address in lr (x30)
; encoded as imm26 offset → produces ARM64_RELOC_BRANCH26!Why dyncode must avoid bl: bl uses a 26-bit immediate offset that the linker fills in. For external symbols, this produces an ARM64_RELOC_BRANCH26 relocation. DynCode has no linker, so this must be replaced with blr.
blr x8 ; branch to address in x8, save return address in lr
br x8 ; branch to address in x8, no link save (tail call)ret ; equivalent to br lr, jumps to address saved in x30adr x0, .Ldata ; x0 = PC + offset_to_.Ldataadrp x0, _sym@PAGE ; x0 = (PC & ~0xFFF) + page_offset
add x0, x0, _sym@PAGEOFF ; x0 += within-page offset
; produces ARM64_RELOC_PAGE21 + ARM64_RELOC_PAGEOFF12 relocationsEquivalent to x86_64's lea rax, [rip + _sym], but split into two instructions.
mov x0, #0x4142 ; x0 = 0x4142; Construct 64-bit value 0x0001_0002_0003_0004:
mov x0, #0x0004 ; x0 = 0x0000_0000_0000_0004
movk x0, #0x0003, lsl #16 ; x0 = 0x0000_0000_0003_0004
movk x0, #0x0002, lsl #32 ; x0 = 0x0000_0002_0003_0004
movk x0, #0x0001, lsl #48 ; x0 = 0x0001_0002_0003_0004Why this is the core of Data2TextPass: constant data (like string "hello\n") cannot go in .data (would produce relocations), so Data2TextPass converts them into mov/movk sequences stored on the stack.
ldr x0, [sp, #16] ; x0 = *(sp + 16), 64-bit load
str x0, [sp, #16] ; *(sp + 16) = x0, 64-bit store
ldrb w0, [x1] ; w0 = *(uint8_t*)x1, zero-extended to 32-bit
strb w0, [x1, #3] ; *(uint8_t*)(x1 + 3) = low 8 bits of w0stp x29, x30, [sp, #-16]! ; sp -= 16 first, then store x29 and x30
; pre-index notation ("!" means writeback)
ldp x29, x30, [sp], #16 ; load x29 and x30, then sp += 16
; post-index notation; Prologue
stp x29, x30, [sp, #-16]! ; save frame pointer and return address
mov x29, sp ; establish frame pointer
; ... function body ...
; Epilogue
ldp x29, x30, [sp], #16 ; restore frame pointer and return address
ret ; returnadd x0, x1, x2 ; x0 = x1 + x2
sub x0, x1, #42 ; x0 = x1 - 42
and x0, x1, #0xFF ; x0 = x1 & 0xFF
orr x0, x1, x2 ; x0 = x1 | x2
eor x0, x1, x2 ; x0 = x1 ^ x2
lsr x0, x1, #1 ; x0 = x1 >> 1 (logical right shift)
lsl x0, x1, #3 ; x0 = x1 << 3cmp x0, #0 ; compare x0 with 0 (set condition codes)
b.eq .Lzero ; branch if equal
b.ne .Lnonzero ; branch if not equal
b.lt .Lnegative ; branch if less than (signed)
b.gt .Lpositive ; branch if greater than (signed)
; Conditional select
csel x0, x1, x2, eq ; x0 = (eq) ? x1 : x2_main:
add w0, w0, w1 ; w0 = 3 + 4 = 7
ret ; return 7; ZeroRelocPass forces always_inline → compiler unrolls recursion into loop
_main:
mov w8, #0 ; fib[0] = 0
mov w9, #1 ; fib[1] = 1
.Lloop:
cmp w0, #1
b.le .Ldone
add w10, w8, w9 ; fib[i] = fib[i-1] + fib[i-2]
mov w8, w9
mov w9, w10
sub w0, w0, #1
b .Lloop
.Ldone:
mov w0, w9
ret; After Data2TextPass + backend:
_main:
mov w8, #0x4241 ; w8 = 'A' | ('B' << 8) = 0x4241 (little-endian)
strh w8, [sp, #-4]! ; store to stack (2 bytes + padding)
mov w9, #0 ; store '\0'
strb w9, [sp, #2]
ldrb w0, [sp] ; load 'A' = 65
ldrb w1, [sp, #1] ; load 'B' = 66
add w0, w0, w1 ; 65 + 66 = 131
add sp, sp, #4
retDarwin arm64 BSD syscall ABI:
x16= syscall numberx0..x7= argumentssvc #0x80triggers trap- Return value in
x0, error sets Carry flag
_main:
sub sp, sp, #16
; Construct "hi\n" on stack (Data2TextPass style)
mov w8, #0x6968 ; 'hi' little-endian
strh w8, [sp, #12]
mov w9, #0x0a ; '\n'
strb w9, [sp, #14]
; write(1, &msg, 3) — direct svc #0x80
mov x16, #4 ; SYS_write
mov x0, #1 ; fd = 1
add x1, sp, #12 ; buf = &msg
mov x2, #3 ; n = 3
svc #0x80 ; ← no bl/blr, no import, no relocation
; exit(0)
mov x16, #1 ; SYS_exit
mov x0, #0 ; status
svc #0x80
add sp, sp, #16
retThe entire instruction stream is 100% within __TEXT,__text. The .o relocation table is empty except for intra-section branches — this is "true dyncode".
| Concept | x86_64 equivalent | ARM64 | DynCode notes |
|---|---|---|---|
| Function call | call rel32 |
bl imm26 |
Intra-section: extractor patches BRANCH26; cross-section: use blr xN |
| Address load | lea rax, [rip+sym] |
adrp+add |
Intra-section: extractor patches PAGE21/PAGEOFF12; cross-section: rejected |
| 64-bit immediate | mov rax, imm64 |
mov+movk ×4 |
No relocation, core of Data2TextPass |
| Prologue | push rbp; mov rbp,rsp |
stp x29,x30,[sp,#-16]! |
Single instruction saves register pair |
| Return | ret |
ret |
Equivalent to br lr |
| Syscall | syscall |
svc #0x80 |
Darwin BSD: x16 = nr, x0~x7 = args |