# armv7a keywords: arm, armv7, abi - ISA type: `RISC` - Endianness: `little`, `big` ## Registers ### General purpose registers ```markdown bytes [3:0] alt desc --------------------------------------------- r0-r12 general purpose registers r11 fp r13 sp stack pointer r14 lr link register r15 pc program counter ``` ### Special registers ```markdown bytes [3:0] desc --------------------------------------------- cpsr current program status register ``` ### CPSR register ```markdown cpsr bits desc ----------------------------- [31] N negative flag [30] Z zero flag [29] C carry flag [28] V overflow flag [27] Q cummulative saturation (sticky) [9] E load/store endianness [8] A disable asynchronous aborts [7] I disable IRQ [6] F disable FIQ [5] T indicate Thumb state [4:0] M process mode (USR, FIQ, IRQ, SVC, ABT, UND, SYS) ``` ## Instructions cheatsheet ### Accessing system registers Reading from system registers: ```armasm mrs r0, cpsr // move cpsr into r0 ``` Writing to system registers: ```armasm msr cpsr, r0 // move r0 into cpsr ``` ### Control Flow ```armasm b // relative forward/back branch bl // relative forward/back branch & link return addr in r14 (LR) // branch & exchange (can change between ARM & Thumb instruction set) // bit Rm[0] == 0 -> ARM // bit Rm[0] == 1 -> Thumb bx // absolute branch to address in register Rm blx // absolute branch to address in register Rm & // link return addr in r14 (LR) ``` ### Load/Store Different addressing modes. ```armasm ldr r1, [r0] // r1 = [r0] ldr r1, [r0, #4] // r1 = [r0+4] ldr r1, [r0, #4]! // pre-inc : r0+=4; r1 = [r0] ldr r1, [r0], #4 // post-inc: [r0] = r1; r0+=4 ldr r0, [r1, r2, lsl #3] // r0 = [r1 + (r2<<3)] ``` Load/store multiple registers full-descending. ```armasm stmfd r0!, {r1-r2, r5} // r0-=4; [r0]=r5 // r0-=4; [r0]=r2 // r0-=4; [r0]=r1 ldmfd r0!, {r1-r2, r5} // r1=[r0]; r0+=4 // r2=[r0]; r0+=4 // r5=[r0]; r0+=4 ``` > `!` is optional but has the effect to update the base pointer register `r0` here. Push/Pop ```armasm push {r0-r2} // effectively stmfd sp!, {r0-r2} pop {r0-r2} // effectively ldmfd sp!, {r0-r2} ``` ## Procedure Call Standard ARM ([`aapcs32`][aapcs32]) ### Passing arguments to functions - integer/pointer arguments ```markdown reg arg ----------- r0 1 .. .. r3 4 ``` - a double word (64bit) is passed in two consecutive registers (eg `r1+r2`) - additional arguments are passed on the stack. Arguments are pushed `right-to-left (RTL)`, meaning next arguments are closer to current `sp`. ```markdown void take(..., int a5, int a6); | | | ... | Hi | +-->| a6 | | +---------->| a5 | <-SP | +-----+ v | ... | Lo ``` ### Return values from functions - integer/pointer return values ```markdown reg size ----------------- r0 32 bit r0+r1 64 bit ``` ### Callee saved registers - `r4` - `r11` - `sp` ### Stack - full descending - full: `sp` points to the last used location (valid item) - descending: stack grows downwards - `sp` must be 4byte aligned (word boundary) at all time - `sp` must be 8byte aligned on public interface interfaces ### Frame chain - not strictly required by each platform - linked list of stack-frames - each frame links to the frame of its caller by a `frame record` - a frame record is described as a `(FP,LR)` pair (2x32bit) - `r11 (FP)` must point to the frame record of the current stack-frame ```markdown +------+ Hi | 0 | frame0 | +->| 0 | | | | ... | | | +------+ | | | LR | frame1 | +--| FP |<-+ | | ... | | | +------+ | | | LR | | current | r11 ->| FP |--+ frame v | ... | Lo ``` - end of the frame chain is indicated by following frame record `(0,-)` - location of the frame record in the stack frame is not specified - `r11` is not updated before the new frame record is fully constructed ### Function prologue & epilogue - prologue ```armasm push {fp, lr} mov fp, sp // FP points to frame record ``` - epilogue ```armasm pop {fp, pc} // pop LR directly into PC ``` ## ASM skeleton Small assembler skeleton, ready to use with following properties: - use raw Linux syscalls (`man 2 syscall` for ABI) - no `C runtime (crt)` - gnu assembler [`gas`][gas_doc] ```armasm // file: greet.S #include // syscall NRs .arch armv7-a .section .text, "ax" .balign 4 // Emit `arm` instructions, same as `.arm` directive. .code 32 .global _start _start: // Branch with link and exchange instruction set. blx _do_greet mov r0, #0 // exit code mov r7, #__NR_exit // exit(2) syscall swi 0x0 // Emit `thumb` instructions, same as `.thumb` directive. .code 16 .thumb_func _do_greet: mov r0, #2 // fd ldr r1, =greeting // buf ldr r2, =greeting_len // &len ldr r2, [r2] // len mov r7, #__NR_write // write(2) syscall swi 0x0 // Branch and exchange instruction set. bx lr .balign 8 // align data on 8byte boundary .section .rodata, "a" greeting: .asciz "Hi ASM-World!\n" greeting_len: .int .-greeting ``` > man gcc: `file.S` assembler code that must be preprocessed. To cross-compile and run: ```bash > arm-linux-gnueabi-gcc -o greet greet.S -nostartfiles -nostdlib \ -Wl,--dynamic-linker=/usr/arm-linux-gnueabi/lib/ld-linux.so.3 \ && qemu-arm ./greet Hi ASM-World! ``` > Cross-compiling on `Ubuntu 20.04 (x86_64)`, paths might differ on other > distributions. Explicitly specifying the dynamic linker should not be > required when compiling natively on arm. ## References - [Procedure Call Standard ARM][aapcs32] - [ARMv7-A Programmer's Guide][armv7a_prog_guide] - [ARMv7-A Architecture Reference Manual][armv7a_arm] - [GNU Assembler][gas_doc] - [GNU Assembler Directives][gas_directives] - [GNU Assembler `ARM` dependent features][gas_arm] [aapcs32]: https://github.com/ARM-software/abi-aa/blob/master/aapcs32/aapcs32.rst [armv7a_prog_guide]: https://developer.arm.com/documentation/den0013/latest [armv7a_arm]: https://developer.arm.com/documentation/ddi0406/latest [gas_doc]: https://sourceware.org/binutils/docs/as [gas_directives]: https://sourceware.org/binutils/docs/as/Pseudo-Ops.html#Pseudo-Ops [gas_arm]: https://sourceware.org/binutils/docs/as/ARM_002dDependent.html