Repository navigation
Conversation
Add a new option CONFIG_PIC_OPTIONS to enable pie compilation / linking. Change compiler and linker flags when generating pie image. Signed-off-by: Omar Naffaa <onaffaa@qti.qualcomm.com>
Update isr.S and pmp.S to use lla instead of la when accessing local, file scoped labels. Using local addressing results in unnecessary entries being placed in the GOT. Signed-off-by: Omar Naffaa <onaffaa@qti.qualcomm.com>
At link-time with "-pie" enabled the following sections are added into relocation sections within the ELF: __tdata_start, __tdata_end, __tdata_size, __tbss_start, __tbss_end, and __tbss_size. At boot time these variables are adjusted by the offset between the build time and runtime address, resulting in an incorrect size. Compute size explicitly as "end - start". Signed-off-by: Omar Naffaa <onaffaa@qti.qualcomm.com>
At link-time with "-pie" enabled the following sections are added into relocation sections within the ELF: __rom_region_start, __rom_region_end, and __rom_region_size. At boot time these variables are adjusted by the offset between the build time and runtime address, resulting in an incorrect size. Compute size explicitly. Signed-off-by: Omar Naffaa <onaffaa@qti.qualcomm.com>
Software ISR tables (isr_tables.c) are generated by reading symbol values out of the pre-linked zephyr_pre0.elf, then baked directly into the final image. For PIE builds those baked values are load-address- relative, so they must be corrected once the image is actually placed in memory at runtime. gen_isr_tables.py/gen_isr_tables_parser_carrays.py now read the .rela.dyn section and resolve the vector table, sw_isr_table, and shared-interrupt-table symbols against zephyr_pre0.elf when CONFIG_PIC_OPTIONS is set, emitting each table's pre0 load address plus a per-entry relocation list. z_adjust_isr_tables() in kernel/init.c uses this at startup to patch the tables to their final, relocated addresses. CONFIG_GEN_SW_ISR_TABLE_SWITCH and CONFIG_ISR_TABLES_LOCAL_DECLARATION are not supported for relocatable images; arch/Kconfig now makes them depend on !PIC_OPTIONS so the two schemes can't be selected together. Signed-off-by: Omar Naffaa <onaffaa@qti.qualcomm.com>
Add the load-address delta computation and .rela.dyn walk needed to self-relocate a statically-linked position independent executable at reset, before any GOT/data access or ISR dispatch can occur. Only R_RISCV_RELATIVE entries are handled, since a static PIE has no dynamic linker and therefore no symbol-resolution relocations. Adjust the RISC-V linker script to place .got/.rela.plt/.rela.dyn for the PIC_OPTIONS build instead of the .plt/.iplt layout used by non-PIE images, and discard the .plt/.dynamic input sections that GNU ld synthesizes for -pie but that a no-dynamic-linker static PIE never uses. Signed-off-by: Omar Naffaa <onaffaa@qti.qualcomm.com>
Create version of hello world with CONFIG_PIC_OPTIONS enabled to validate dynamic ELF functionality. Signed-off-by: Omar Naffaa <onaffaa@qti.qualcomm.com>
| SECTION_PROLOGUE(.rela.dyn,, ALIGN(0x1000)) | ||
| { | ||
| __rel_dyn_start = .; | ||
| KEEP(*(.rela*)) | ||
| } | ||
| __rel_dyn_end = .; |
| reloc_pc: | ||
| auipc t0, 0 /* Get the runtime address of this instruction */ | ||
| la t1, reloc_pc /* Get the linker time address of this instruction */ | ||
| sub t2, t0, t1 |
There was a problem hiding this comment.
Modification of the following from zephyr/kernel/config/soc/sifive/common/vector.S made with the intention that 4k alignment is not strictly required. If the value is offset by some value less than 4k the lui t1, 0xFFFFF instruction masks the offset entirely (e.g. if offsetting by 5k).
The code below relies on the address of __start being close to the runtime address where the calculation is done, whereas defining reloc_pc allows the calculation to be done anywhere in this routine.
/* Calculate the delta between linker time address and run time address */
/* Get the runtime address */
auipc t0, 0
lui t1, 0xFFFFF /* load 0xFFFFF000 into t1 */
and t0, t0, t1
/* Get the linker time address */
la t1, __start
/* get the delta between t1 and t0 */
sub t2, t0, t1
| andi t5, t2, 0x3 | ||
| beqz t5, delta_aligned | ||
| delta_misaligned: | ||
| j delta_misaligned | ||
| delta_aligned: |
There was a problem hiding this comment.
This goes in hand with relaxing the requirement that the offset must be a multiple of 4K. This leads to an infinite loop to diagnose the fault instead of silently masking the offset if it doesn't meet alignment criteria.
It may be useful to make a variant of this ( andi t5, t2, 0xFFF ) instead of using lui t1, 0xFFFFF above even if 4k alignment is required if we want to fault on a non-4k alignment instead of silently rounding it down.
| Using ``llvm-objcopy`` from a CPULLVM toolchain install | ||
| (``<cpullvm-toolchain>/bin/llvm-objcopy``), shift the LMA of every section in |
There was a problem hiding this comment.
This should be cleaned up to reference LLVM rather than CPULLVM.
| Testing Self-Relocation with a Shifted LMA | ||
| =========================================== |
There was a problem hiding this comment.
Feedback appreciated on this idea for testing in upstream. It requires manually shifting the LMA so that the ELF is interpreted as relocated (in downstream we use T32 to load the ELF at an offset).

Problem Description
Currently the behavior of Zephyr is to compile its ELF as position dependent, which results in the generated ELF needing to be loaded at the physical address the firmware was compiled against.
By adding support to compile position independent flags using the
-fPICand-pieflags several benefits are introduced:Proposed Change (Summary)
The high-level scope of changes to enable this functionality are:
CONFIG_PIC_OPTIONS, that determines which compiler / linker flags are to be included.Proposed Change (Detailed)
Adding PIE Compilation / Linking Support
A new feature flag
CONFIG_PIC_OPTIONSwould be introduced to support building PIE if enabled. If disabled (default), the current functionality that prevents using position independent compilation / linking remains.no_position_independentis used, which adds the compiler properties-fno-picand-fno-pieand--no-pieas a linker propertyWith
CONFIG_PIC_OPTIONS=ynew a propertyposition_independent_elfthat has the following effects:-fPICas a property for compilation-Wl, -pie -Wl, --no-dynamic-linkeras a property for linkingNote: The
--no-dynamic-linkerlinker option prevents generation of the PT_INTERP program header and other metadata required for dynamic loading. As a result, the relocatable PIE image remains a static, self-contained ELF binary that performs any required relocations internally during startup.Modify Linker Scripts to Handle PIE-specific Sections
At link-time several sections are generated for PIE images:
.dynsym: Dynamic symbol table containing symbols that may need to be referenced or resolved at load/runtime..dynstr: String table containing the names associated with entries in.dynsymand other dynamic-linking metadata..hash: Hash table used by the dynamic linker to efficiently locate symbols in.dynsym..plt: Procedure Linkage Table containing stubs used to call dynamically linked functions..got: Global Offset Table containing addresses used by position-independent code to access global data and external symbols.Rather than resolve symbols dynamically at run-time early boot-up code will be added that resolves all symbols present in relocation sections. To identify this the location of the Global Offset Table (GOT) and relocation sections are determined by the symbols:
__got_start,__got_endfor the GOT section range__rela_dyn_start,__rela_dyn_endfor the range that defines relocation sectionsPerforming Self Relocation
Relocation Process (Example)
With a PIE binary self relocation is done for global data that would otherwise reference a stale address if the ELF is executed from a different physical address than what was compiled against. PC-relative addressing is used to get the address of the GOT entry for some global symbol, which is then used to get the linking address of the global symbol. The image below shows a typical entry within the
rela.dynsection.The example below uses a GOT entry, but this is not the only
r_offsettype inrela.dyn; one other example would be const string pointers.auipc) is marked in redProcedure
Adjusting Entries in GOT
To determine the offset where the ELF is loaded from relative to its physical address logic will be added within the
__startroutine withinvector.S. The logic below introduces a label -reloc_pc- whose runtime and link-time address are used to determine the offset used to adjust the entries in the GOT.Once this offset is computed iterate through each entry stored in
__rel_dyn_startto__rel_dyn_end, where each entry is 12 bytes composed of 3 4-byte values:r_offset,r_info, andr_addend, that have anr_infovalue of typeR_RISCV_RELATIVE.r_offset- the link-time address of the memory location that needs to be patchedr_info- packs the symbol index and relocation typer_addend- the constant value used to compute the relocated address; adjusted by the load offset to get the actual runtime value that gets storedSome
rela.dynentries aren't guaranteed to be 4-byte aligned (e.g. packed elements within a struct), which requires additional handling when writing the patched values (e.g.r_offset+t2) to perform half-word or single-byte stores to avoid a fault due to misalignment.Interrupt Relocation
An additional update would be needed for
gen_isr_defines.py, which parses and patches the.intListsection to resolve addresses in_sw_isr_table. This is done in 2 steps:funcmember of_isr_listparammember of_isr_listCare must be taken when updating function parameters (point 2), as
paramvalues may represent a literal rather than an address. The example below illustrates this nuance:isr_handler, which has a parameter 0x4 that is converted to an integer usingPOINTER_TO_INTplic_irq_handler, whose parameter is a pointer to adevicestruct that needs to be relocatedThe symbol table (
.symtab) is used to determine what interrupt relocations should occur at runtime and populates a list of interrupts to relocate. This table is consumed by a newly addedz_adjust_isr_tablesfunction that is invoked inz_cstart.build/zephyr/isr_tables.cat compile-time{11, 0}indicates that the parameter of ISR 11 was identified as needing to be relocated{-1, -1}denotes the end of the list of interrupts to relocateThe second index (e.g.
0in{11, 0}) is used whenCONFIG_SHARED_INTERRUPTSare enabled to determine which client index requires it'sargto be relocated. IfCONFIG_SHARED_INTERRUPTSthe value is a "don't care".Dependencies
GCC does not support the necessary
-pielinking flag, which requires that this feature is compiled with LLVM.Changes made were verified downstream using:
Additionally,
CONFIG_QEMU_DEVICE_LOADERmust be enabled if testing on QEMU as the dynamic sections are placed prior torom_start.Concerns and Unresolved Questions
Interactions with userspace, as kernel objects are added to a lookup table at compile time. Follow up work would be to address this similar to how interrupts are relocated, but for now
CONFIG_PIC_OPTIONSwould depend on userspace being disabled.CONFIG_GEN_SW_ISR_TABLE_SWITCHis not supported with this feature, as interrupt relocation requires patching the pointers to each ISR at runtime. This feature generates C-code with literal addresses that can't be patched, unlikeCONFIG_GEN_SW_ISR_TABLE_ARRAYAlternatives Considered