Skip to content

Conversation

@iabdalkader
Copy link

@iabdalkader iabdalkader commented Nov 3, 2025

⚠️ Note this requires:

This patch changes the build to perform two-pass linking to separate rodata with relocations (copied to RAM) from rodata with no relocations (pure const data) that can be kept in flash by llext.

For dynamic linking mode:

  • First pass: Link with build-dynamic.ld to create temp ELF (unchanged).
  • Analyze the elf and extract, sort and write out the sections:
    • .rodata: sections WITH relocations → copied to RAM (LLEXT_MEM_RODATA)
    • .rodata_noreloc: sections WITHOUT relocations → kept in flash (LLEXT_MEM_RODATA_NO_RELOC)
  • Second pass: Link with generated rodata_split.ld + build-dynamic.ld.

For static linking mode:

  • Generates empty rodata_split.ld (no section separation needed)

Output from readelf on an example sketch that includes ~64KBs CA certs blob. Before the patch:

There are 18 section headers, starting at offset 0x149b4:

Section Headers:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] .text             PROGBITS        00000000 000034 001260 00  AX  0   0  4
  [ 2] .rel.text         REL             00000000 013fd0 000798 08   I 15   1  4
  [ 3] .static_thread_data_area PROGBITS        00001260 001294 000000 00   W  0   0  1
  [ 4] .rodata           PROGBITS        00001260 001294 0108bc 00   A  0   0  4
  [ 5] .rel.rodata       REL             00000000 014768 000120 08   I 15   4  4
  [ 6] .data             PROGBITS        00011b1c 011b50 000110 00  WA  0   0  4
  [ 7] .rel.data         REL             00000000 014888 000050 08   I 15   6  4
  [ 8] ._usbd_context.static.usbd_ PROGBITS        00000000 011c60 000074 00  WA  0   0  4
  [ 9] .rel._usbd_context.static.usbd_ REL             00000000 0148d8 000020 08   I 15   8  4
  [10] .bss              NOBITS          00000074 011cd4 0004ac 00  WA  0   0  4
  [11] .exported_sym     PROGBITS        00000520 011cd4 000008 00   A  0   0  4
  [12] .rel.exported_sym REL             00000000 0148f8 000010 08   I 15  11  4
  [13] .init_array       INIT_ARRAY      00000528 011cdc 000008 04  WA  0   0  4
  [14] .rel.init_array   REL             00000000 014908 000010 08   I 15  13  4
  [15] .symtab           SYMTAB          00000000 011ce4 001460 10     16 170  4
  [16] .strtab           STRTAB          00000000 013144 000e8c 00      0   0  1
  [17] .shstrtab         STRTAB          00000000 014918 00009b 00      0   0  1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
  L (link order), O (extra OS processing required), G (group), T (TLS),
  C (compressed), x (unknown), o (OS specific), E (exclude),
  D (mbind), y (purecode), p (processor specific)

After:

There are 19 section headers, starting at offset 0x149c0:

Section Headers:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] .rodata           PROGBITS        00000000 000034 000078 00   A  0   0  4
  [ 2] .rel.rodata       REL             00000000 01402c 0000c0 08   I 16   1  4
  [ 3] .rodata.noreloc   PROGBITS        00000078 0000ac 010808 00   A  0   0  4
  [ 4] .text             PROGBITS        00000000 0108b4 001268 00  AX  0   0  4
  [ 5] .rel.text         REL             00000000 0140ec 0007a0 08   I 16   4  4
  [ 6] .static_thread_data_area PROGBITS        00001268 011b1c 000000 00   W  0   0  1
  [ 7] .data             PROGBITS        00001268 011b1c 0000f8 00  WA  0   0  4
  [ 8] .rel.data         REL             00000000 01488c 000048 08   I 16   7  4
  [ 9] ._usbd_context.static.usbd_ PROGBITS        00000000 011c14 000074 00  WA  0   0  4
  [10] .rel._usbd_context.static.usbd_ REL             00000000 0148d4 000020 08   I 16   9  4
  [11] .bss              NOBITS          00000074 011c88 0004ac 00  WA  0   0  4
  [12] .exported_sym     PROGBITS        00000520 011c88 000008 00   A  0   0  4
  [13] .rel.exported_sym REL             00000000 0148f4 000010 08   I 16  12  4
  [14] .init_array       INIT_ARRAY      00000528 011c90 000008 04  WA  0   0  4
  [15] .rel.init_array   REL             00000000 014904 000010 08   I 16  14  4
  [16] .symtab           SYMTAB          00000000 011c98 001540 10     17 184  4
  [17] .strtab           STRTAB          00000000 0131d8 000e54 00      0   0  1
  [18] .shstrtab         STRTAB          00000000 014914 0000ab 00      0   0  1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
  L (link order), O (extra OS processing required), G (group), T (TLS),
  C (compressed), x (unknown), o (OS specific), E (exclude),
  D (mbind), y (purecode), p (processor specific)

Alternatives considered and tested:

This is by far the simplest and safest approach since it relies on the toolchain to do all the magic so we can trust it Not to break the elf. That said, I implemented and tested two more solutions:

1- Patch elf to set custom flags and/or section type: first attempt, using a custom Python/Go script, can set special section flags or section type, to mark rodata sections with no relocations, but this required more modifications to llext and using section attributes.
2- Reorder sections: using a custom Python/Go script, reorder elf sections such that all rodata with relocs come first (or last, doesn't matter). llext doesn't support non-contiguous regions, so if we just reorder them, plus my patch above, things work out. While this fixes the issue in one pass, it doesn't merge the sections, so final elf will have many rodata and rodata.noreloc sections. This can be fixed by merging the sections, but the relocations will have to be fixed. Doable, but tricky and risky.

This patch changes the build to perform two-pass linking to separate rodata
with relocations (copied to RAM) from rodata with no relocations (pure data)
that can be kept in flash by llext.

For dynamic linking mode:
- First pass: Link with build-dynamic.ld to create temp ELF (unchanged).
- Analyze the elf and extract, sort and write out the sections:
    - .rodata: sections WITH relocations → copied to RAM (LLEXT_MEM_RODATA)
    - .rodata_noreloc: sections WITHOUT relocations → kept in flash (LLEXT_MEM_RODATA_NO_RELOC)
- Second pass: Link with generated rodata_split.ld + build-dynamic.ld.

For static linking mode:
- Generates empty rodata_split.ld (no section separation needed)

Signed-off-by: iabdalkader <i.abdalkader@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant