1

We have a bunch of C code that needs to be compiled as position independent code on an embedded system (Cortex-M7). This all works fine with ARMCC (--ropi --rwpi).

Now we need to do the same under GCC (12.2 release 1).

The system we are loading the code with (ThreadX Modules) handles using r9 as an offset register for the Global Offset Table and offers an example which employs the following compiler options:

-fpie -fno-plt -mno-pic-data-is-text-relative -msingle-pic-base

...and no particular linker otions.

The problem we are having is that any attempt to take the address of a global variable does not work under GCC (and does work under ARMCC). For instance:

char buffer[100];
char* pBuffer = buffer;

int appModMain()
{
    printf("buffer address: %p, buffer address via pBuffer: %p \n", buffer, pBuffer);
}

...results in a printed output of the form:

buffer address:  60C8C25C, buffer address via pBuffer: 1000025C

In other words pBuffer is not being offset correctly. This is clear when you look at the disassembled code:

00000188 <appModMain>:
     188:   b580        push    {r7, lr}
     18a:   af00        add r7, sp, #0
     18c:   4b1a        ldr r3, [pc, #104]  ; (1f8 <appModMain+0x70>)
     18e:   f859 3003   ldr.w   r3, [r9, r3]
     192:   681b        ldr r3, [r3, #0]
     194:   461a        mov r2, r3
     196:   4b19        ldr r3, [pc, #100]  ; (1fc <appModMain+0x74>)
     198:   f859 3003   ldr.w   r3, [r9, r3]
     19c:   4619        mov r1, r3
     19e:   4b18        ldr r3, [pc, #96]   ; (200 <appModMain+0x78>)
     1a0:   f859 3003   ldr.w   r3, [r9, r3]
     1a4:   4618        mov r0, r3
     1a6:   f000 f8bb   bl  320 <printf>
     1aa:   f640 30b8   movw    r0, #3000   ; 0xbb8

When loading-up the variable to put into r2, which is going to be passed to printf() as pBuffer, r3 needs to be adjusted, yet it is not.

We've tried various permutations of -mpic-data-is-text-relative/-mnopic-data-is-text-relative and -msingle-pic-base/-mnosingle-pic-base but none of them make a difference: we just cannot make a pointer reference to a global variable work. Unfortunately we can't change the source code, there's quite a large amount of it and some if it is not ours.

Does anyone have any advice as to how we might make GCC comply?

EDIT:

In the disassembled output, the contents of 1f8 and 1fc for the example above are:

     1f8:   000001bc
     1fc:   000001b0

...in the .got section of the disassembled output, these offset are towards the end, here:

Disassembly of section .got:

00006b88 <__ctors_end__>:
...
    6d34:   1002c5a8
    6d38:   1000025c    ; 6b88 + 1b0
    6d3c:   00001e15
    6d40:   1002c5ac
    6d44:   100001d0    ; 6b88 + 1bc

...and 100001d0 in the disassembled output turns out to be:

Disassembly of section .data:

...

100001d0 <pBuffer>:
100001d0:   1000025c

...while 1000025c in the disassembled output turns out to be:

Disassembly of section .bss:

...

1000025c <buffer>:
    ...

So pBuffer is initialised by the compiler to the un-offsetted address of buffer, and the compiler knows that pBuffer it is a pointer to something in the GOT, yet it did not do anything to add the offset when it generated the code.

Rob
  • 865
  • 1
  • 8
  • 21
  • what does the GOT look like for this build relative to the variables of interest, it looks like it is doing the indirection, so please complete the quesiton to show what is missing. preferrably make a complete minimal example. – old_timer Feb 24 '23 at 16:50
  • Sorry, now added. – Rob Feb 24 '23 at 17:43
  • 1
    It is up to you to patch the GOT at boot time. That is the whole point. The GOT is in RAM, not Flash. It is data that gets patch at **LOAD** time. You can set the linker file so that the data does not need to be patched. [ThreadX LD](https://github.com/azure-rtos/threadx/blob/master/ports_module/cortex_m7/gnu/example_build/sample_threadx_module.ld), RAM/ROM need to be set correctly and then GOT doesn't need to update. If the system assigns address dynamically (eg. security), then you need to patch the GOT. – artless noise Feb 27 '23 at 14:58
  • Thanks @artlessnoise: if that is the case, how would we know which items in the GOT need to be offset and which don't? For instance, `pBuffer` does but if we had a global integer that happened to be assigned the value 0x1000025c for some reason, that would obviously not need to be offset. I guess the compiler and/or linker gives us that information somehow? Maybe the `.reloc` section (which, interestingly, is not present in our disassembled output at all)? – Rob Feb 27 '23 at 15:19
  • 1
    The global integer with ' 0x1000025c' is not put in the GOT. The GOT has a range and the global integer space lives outside that range. However, most likely you just need to set a linker file correctly so the GOT is not needed. Are you sure it is needed or only a symptom of another problem with addresses in a linker file. Do you have a 'map' file and what are the linker commands? If ThreadX is relocating code/data to different locations as an address randomization, then it maybe needed. – artless noise Feb 27 '23 at 23:27
  • Well, the code+data may be loaded anywhere into RAM by ThreadX Modules, so we can't use a linker file with fixed memory locations. ThreadX Modules expects the compiled code to use R9 as the PIC offset so that the ThreadXModules loader only has to set R9 and everything should work. Except that it _doesn't_ work in the specific case of global pointers to things that are in the GOT; the compiler doesn't help with these and the ThreadX Modules-provided loader also does nothing. But from what you're saying, if we globally added R9 to all entries in the GOT at initialisation it should work...? – Rob Feb 28 '23 at 00:06

0 Answers0