I am trying to build a static position-independent executable with gcc
provided option -static-pie
. The target is bare-metal risc-v, so no OS, no dynamic loader. I have a linker script similar to following:
ENTRY(_start)
SECTIONS {
.text (READONLY) : ALIGN(64) {
startup.o(.text.startup)
*(EXCLUDE_FILE(startup.o) .text .text.*)
}
.rodata (READONLY) : ALIGN(64) {
*(.srodata .srodata.*)
*(.rodata .rodata.*)
*(.got .got.plt)
}
.data ALIGN(64): {
__global_pointer$ = . + 0x800;
*(.sdata .sdata.*)
*(.data .data.*)
}
.bss (NOLOAD): ALIGN(64) {
_bss_start = .;
*(.sbss .sbss.*)
*(.bss .bss.*)
_bss_end = .;
}
.stack (NOLOAD): ALIGN(64) {
_stack_start = .;
. = . + 0x400;
_stack_end = .;
}
}
and when compiling with -fpie
and linking with -static-pie
it seems to produce a correct PIE binary which seems to function correctly from any address it is loaded to.
Now, to the problem. Consider we have a special memory region at fixed address which I want the program to use (for example some shared memory with another processor) and I want to define it via the linker script. With position dependent code I would do something like this:
In the code:
__attribute__((section(".special_section")))
volatile uint8_t shared_mem[100];
In the linker script:
SECTIONS {
.....
.special_section 0x12340000 (NOLOAD): {
*(.special_section)
}
.....
}
and this will ensure that the array shared_mem
is located at the fixed address 0x12340000
.
However this does not work with static-pie
. The accesses to shared_mem
which are generated by the compiler are relative to the address the binary is loaded to (that's the idea of PIE, right?). The question is - is there a way to define a specific output section to have an absolute address?
UPDATE: Here is more info of what I am seeing. With the linker script as above and the extra section as follows:
.special_section 0x1234AB00 (NOLOAD): {
*(.special_section)
}
the following code (except the startup code, which I omit):
#include <stdint.h>
__attribute__((section(".special_section")))
volatile uint8_t shared_mem[100];
int main(void) {
shared_mem[0] = 0x55;
while (1);
return 0;
}
the generated (interleaved) assembly looks like this:
__attribute__((section(".special_section")))
volatile uint8_t shared_mem[100];
int main(void) {
shared_mem[0] = 0x55;
34: 05500793 li a5,85
38: 1234b717 auipc a4,0x1234b
3c: acf70423 sb a5,-1336(a4) # 1234ab00 <shared_mem>
while (1);
40: a001 j 40 <main+0xc>
As we can see the destination address in a4
is formed using PC-relative instruction auipc
, which adds the current PC = 0x38 value with (0x1234b << 12) = 0x1234_B000
, and then a4=0x55
is stored at that address at offset -1336 = -0x538
(that is 0x38 + 0x1234B000 - 0x538 = 0x1234B000 = 0x1234AB00
- as expected).
However, if the program is loaded to an address other than 0x0
, the PC in the above calculation will be different, so the destination address of the operation will be different too.