I am currently trying to understand the inner code of the glibc's syscall
function. Below is the code (taken from here).
/* In the EABI syscall interface, we don't need a special syscall to
implement syscall(). It won't work reliably with 64-bit arguments
(but that is true on many modern platforms). */
ENTRY (syscall)
mov ip, sp
push {r4, r5, r6, r7}
cfi_adjust_cfa_offset (16)
cfi_rel_offset (r4, 0)
cfi_rel_offset (r5, 4)
cfi_rel_offset (r6, 8)
cfi_rel_offset (r7, 12)
mov r7, r0
mov r0, r1
mov r1, r2
mov r2, r3
ldmfd ip, {r3, r4, r5, r6}
swi 0x0
pop {r4, r5, r6, r7}
cfi_adjust_cfa_offset (-16)
cfi_restore (r4)
cfi_restore (r5)
cfi_restore (r6)
cfi_restore (r7)
cmn r0, #4096
it cc
RETINSTR(cc, lr)
b PLTJMP(syscall_error)
PSEUDO_END (syscall)
I have some understanding of the code for passing the system call number and parameter, which are core functions of the syscall
function. But I do not understand what the cfi_adjust_cfa_offset
instruction, cfi_rel_offset
instruction, and cfi_restore
instruction do. I know that these instructions have little to do with the functionality of the syscall
function. But, I still want to know what these instructions do.
Thank you.
Those are not instructions but assembler directives (normally they should start with a .
but here they are macros, possibly to handle differences in assemblers). They tell the assembler to encode special metadata which helps debuggers and exception handlers to properly unwind the stack. Normally they are emitted by the compiler so you don't see them much, but they are especially important in low-level code or hand-written assembly like here.
Let's go through the code. First, some background.
CFA stands for "Canonical Frame Address" and by default is equal to the value of sp
at the call site (see here). On ARM calls do not push the return address on the stack, so sp
at function entry is the same.
mov ip, sp
This copies the sp
value (which is the CFA, so points to any additional arguments which did not fit in r0-r3 registers) to the ip
( aka r12
) register.
push {r4,r5,r6, r7}
This saves registers which will be modified shortly but are presumed to be not modified by the call (non-volatile registers). The push changes the sp
value by 4*4 =16 bytes, and it is no longer equal to the CFA.
cfi_adjust_cfa_offset(16)
This emits an opcode which tells the debuggers that CFA is at offset 16 from the presumed CFA (sp
).
cfi_rel_offset(r4, 0)
This tells debuggers that the original value of r4
can be found at offset 0 from the new adjusted CFA. Following directives describe the other three saved registers.
mov r7, r0
mov r0, r1
mov r1, r2
mov r2, r3
ldmfd ip, {r3, r4, r5, r6}
swi 0x0
This sets up syscall arguments in a way expected by the EABI calling convention (syscall number in r7
, first arguments in r0-r6
), then invokes the syscall instruction
pop {r4, r5, r6, r7}
cfi_adjust_cfa_offset (-16)
cfi_restore (r4)
cfi_restore (r5)
cfi_restore (r6)
cfi_restore (r7)
Here we restore the previously saved registers, adjust the CFA back by -16 (because sp
got changed by the pop
instruction, and specify that the registers have been restored to their original values).