Search code examples
assemblyarminstructionsinstruction-seteabi

what is cfi_adjust_cfa_offset and cfi_rel_offset?


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.


Solution

  • 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).