assemblyarmgnuremapposition-independent-code

What determines position-independence following a memory remap operation?


I've started reading Miro Samek's "Building Bare-Metal ARM Systems with GNU" and have found myself stuck on a certain point. What's caused my confusion is found in one of the notes found on page 10 of the PDF:

NOTE: The function low_level_init() can be coded in C/C++ with the following restrictions. The function must execute in the ARM state and it must not rely on the initialization of .data section or clearing of the .bss section. Also, if the memory remapping is performed at all, it must occur inside the low_level_init() function because the code is no longer position-independent after this function returns

How exactly is the code "no longer position-independent"? It seems as though the referenced code (viewable on pages 7 - 9 in the PDF) is still position-independent after returning from low_level_init / after the _cstartup label. The only thing that seems different about the instructions after the _cstartup label is that they reference labels that are defined in the linker script (Section 3 of the guide).

So how exactly does the remap affect whether or not the instructions following it are position-independent?


Solution

  • Position independent is a load-time concept — not a runtime concept.  Position independent is a quality of code that allows it to be loaded at any address in memory and will still work, but position independent is not a quality of a running program.

    Once we have a call stack and/or (relocated) data referring to code, we no longer have position independence of either code or data and cannot move them.  Effectively, position independence vanishes when the program begins execution (despite position independent code).

    Both return addresses — dynamically generated by calling (e.g. BL) — as well as data pointers to code (code pointer vectors (as in vtables), and initialized global function pointers) destroy position independence for program that is running.

    The author's cautionary node is a way of describing that position independence vanishes.  Adding to the confusion is that through very careful operation they allow for actually moving the code even though its execution has already begun, so here we have position independence actually lasting some small amount past the start of execution.

    But a program cannot function normally (e.g. make calls and use function pointers) without giving up position independence, and so they choose to draw the line in the sand at the end of low_level_init.

    For example, the reset code goes to a great length to use a non-standard invocation to call low_level_init — providing it with an lr value without using either BL or mov lr,pc (which would capture the pre-remapped (ROM) address of cstartup.)  The lr value provided is the address of (relocated) cstartup to which low_level_init will "return"!

        (10) LDR r0,=_reset /* pass the reset address as the 1st argument */
        (11) LDR r1,=_cstartup /* pass the return address as the 2nd argument */
        (12) MOV lr,r1 /* set the return address after the remap */
        (13) LDR sp,=__stack_end__ /* set the temporary stack pointer */
        (14) B low_level_init /* relative branch enables remap */
    _cstartup:
    

    That B combined with the MOV lr,r1 is the invocation.