Skip to content

Place the GOT in Flash after .fini_array in STM32 linker scripts - #35

Open
Jiu-xiao wants to merge 1 commit into
devfrom
fix/stm32-linker-got
Open

Jiu-xiao wants to merge 1 commit into
devfrom
fix/stm32-linker-got

Conversation

@Jiu-xiao

Copy link
Copy Markdown
Member

Summary

libxr stm32 cmake (and so libxr stm32 setup) now places the GOT of STM32 projects in Flash: every linker script in the project root that has no .got output section gets one directly after .fini_array, in the memory region of .fini_array.

Why

The linker script STM32CubeMX 6.18.1 writes has no .got output section, while the syscalls.c and sysmem.c it generates write errno. In the picolibc of ST Arm Clang (STARM_PICOLIBC, the profile CubeMX selects) errno is thread-local and takes one GOT entry. ld.lld puts the orphan .got in RAM with its load address equal to its run address:

  • the .bin spans Flash up to RAM (402,653,276 bytes for an STM32F407 project);
  • the startup copy of .data fills the entry from past the end of the load image, so it does not hold the linked TLS offset.

The section has to follow .fini_array directly. ld.lld keeps the init/fini arrays and the GOT in one RELRO range; with another output section in between (a CCM section, for example), an STARM_NEWLIB link fails with .got is not contiguous with other relro sections.

Behavior

  • No .got output section: one is inserted after .fini_array, with a comment explaining the placement.
  • An existing .got that does not directly follow .fini_array: a warning; the user's section stays.
  • No .fini_array, or unbalanced braces: a warning; nothing is changed.
  • Line endings and encoding stay as the file has them; a second run changes nothing.
  • .got that appears only in comments or as an input rule inside .data does not count as an output section.

New log line of libxr stm32 setup:

[INFO] Placed the GOT in FLASH after .fini_array in STM32F103xx_FLASH.ld

Changes

  • src/libxr/stm32_linker_script.py: place_got() and place_got_in_project().
  • src/libxr/generator_stm32_cmake.py: integrate() calls place_got_in_project() after restoring starm-clang.cmake.
  • tests/test_stm32_linker_script.py: 9 tests.
  • README.md: a paragraph in "CMake and Toolchains" and the new line in the libxr stm32 setup example (real output on a fresh CubeMX STM32F103RC project).

Verification

  • python -m unittest discover -s tests: 637 tests pass (the 5 clang-format tests skip without clang-format 21.1.8); ruff format --check and ruff check (0.16.9) are clean.
  • An STM32F407 project with the linker script CubeMX 6.18.1 writes, after libxr stm32 cmake, built in Release with GCC 14.2 and ST Arm Clang 21.1.1 (STARM_HYBRID, STARM_NEWLIB, STARM_PICOLIBC): all four link. With STARM_PICOLIBC the .bin goes from 402,653,276 to 118,104 bytes, and .got sits in Flash holding the linked value 0x8.
  • The linker scripts of the existing STM32 BSPs that already place .got after .fini_array stay byte-identical; a script with .got after a CCM section gets the warning.

The linker script CubeMX writes has no .got output section, while the
syscalls.c and sysmem.c it generates write errno. In the picolibc of ST Arm
Clang errno is thread-local and takes a GOT entry; ld.lld puts that orphan
section in RAM with its load address equal to its run address, so the .bin
spans Flash up to RAM (about 384 MiB) and the startup copy of .data does not
write the linked value into the entry.

libxr stm32 cmake, and therefore libxr stm32 setup, now adds a .got output
section directly after .fini_array, in the memory region of .fini_array, to
every linker script in the project root that has none. It has to follow
.fini_array directly: ld.lld keeps the init/fini arrays and the GOT in one
RELRO range, and with another output section in between an STARM_NEWLIB link
fails with ".got is not contiguous with other relro sections". An existing
.got elsewhere only gets a warning and stays as the user wrote it. Line
endings and encoding stay as the file has them, and a second run changes
nothing.
@greptile-apps

greptile-apps Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

RetriggerConfidence Score: 4/5

[High impact] Modifies embedded device linker scripts during build setup.

Fix the next-line region clause handling before merging; it can break existing linker scripts.

Fix All in Claude CodeFindings

  1. P1 Flash placement moves ▶
  2. P2 Untouched line endings change ▶
Fix with agent prompt
### Issue 1
src/libxr/stm32_linker_script.py:89-91
If `.fini_array` has its `>FLASH` clause on the line after `}`, this code inserts `.got` between them. The original `>FLASH` then belongs to `.got`, leaving `.fini_array` without its explicit region. Without `(READONLY)`, `.fini_array` can move to RAM and break the ST Arm Clang link because the arrays and `.got` no longer sit together. Find the end of the full section, including its region clause, before inserting.

### Issue 2
src/libxr/stm32_linker_script.py:150
If a script contains both LF and CRLF line endings, this write converts every line to CRLF. That changes untouched lines despite the promise to preserve line endings, adding noise to diffs and making merges harder. Preserve the original text around the insertion and apply the chosen ending only to the new block.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Summary

Adds .got after .fini_array in project-root linker scripts and calls this step from libxr stm32 cmake.

  • STM32 CMake setup puts missing GOT sections in Flash after .fini_array.

Reviews (1) · Last reviewed commit: "Place the GOT in Flash after .fini_array..." · Reviewed by Greptile

Comment on lines +89 to +91
line_end = code.find("\n", index)
line_end = len(code) if line_end < 0 else line_end
return line_end, code[index + 1 : line_end].strip()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Flash placement moves

If .fini_array has its >FLASH clause on the line after }, this code inserts .got between them. The original >FLASH then belongs to .got, leaving .fini_array without its explicit region. Without (READONLY), .fini_array can move to RAM and break the ST Arm Clang link because the arrays and .got no longer sit together. Find the end of the full section, including its region clause, before inserting.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/libxr/stm32_linker_script.py
Line: 89-91

Comment:
**Flash placement moves**

If `.fini_array` has its `>FLASH` clause on the line after `}`, this code inserts `.got` between them. The original `>FLASH` then belongs to `.got`, leaving `.fini_array` without its explicit region. Without `(READONLY)`, `.fini_array` can move to RAM and break the ST Arm Clang link because the arrays and `.got` no longer sit together. Find the end of the full section, including its region clause, before inserting.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Claude Code Fix in Codex

line_end, region = closing
block = GOT_SECTION.format(indent=sections[fini].group("indent"), region=region)
placed = text[:line_end] + "\n\n" + block.rstrip("\n") + text[line_end:]
path.write_bytes(placed.replace("\n", eol).encode("utf-8", errors="surrogateescape"))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Untouched line endings change

If a script contains both LF and CRLF line endings, this write converts every line to CRLF. That changes untouched lines despite the promise to preserve line endings, adding noise to diffs and making merges harder. Preserve the original text around the insertion and apply the chosen ending only to the new block.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/libxr/stm32_linker_script.py
Line: 150

Comment:
**Untouched line endings change**

If a script contains both LF and CRLF line endings, this write converts every line to CRLF. That changes untouched lines despite the promise to preserve line endings, adding noise to diffs and making merges harder. Preserve the original text around the insertion and apply the chosen ending only to the new block.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Claude Code Fix in Codex

This branch has not been deployed

No deployments
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