Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ $ libxr stm32 setup -t usart1
[INFO] System: FreeRTOS
[INFO] Generated User: wrote app_main.cpp, app_main.h, flash_map.hpp, libxr_config.yaml
[INFO] Generated LibXR.CMake at: cmake\LibXR.CMake
[INFO] Placed the GOT in FLASH after .fini_array in STM32F103xx_FLASH.ld
[INFO] LibXR.CMake included in CMakeLists.txt.
[INFO] [Pass] All tasks completed.
[INFO] Next: #include "app_main.h" and call app_main() in the default task StartDefaultTask (Core/Src/main.c), inside USER CODE sections, which CubeMX keeps when it regenerates the code.
Expand Down Expand Up @@ -637,6 +638,29 @@ $ libxr stm32 toolchain clang --newlib
[INFO] Done.
```

`libxr stm32 cmake` 还处理工程根目录中的链接脚本(`*.ld`)。CubeMX 写出的链接脚本没有 `.got`
输出段,而 CubeMX 生成的 `syscalls.c` 和 `sysmem.c` 会写 `errno`;ST Arm Clang 的 picolibc 中
`errno` 是线程局部变量,访问它要用一个 GOT 条目。没有 `.got` 输出段时,ld.lld 把这个条目放进 RAM,
`.bin` 从 Flash 一直延伸到 RAM(约 384 MiB),启动后条目中也不是链接时的值。因此
`libxr stm32 cmake` 为没有 `.got` 输出段的链接脚本在 `.fini_array` 之后加入一段,放在与
`.fini_array` 相同的存储区。ld.lld 要求 init/fini 数组和 GOT 连续,两者之间隔着其他输出段时,
STARM_NEWLIB 配置的链接报 `.got is not contiguous with other relro sections`;已有的 `.got` 不紧跟
`.fini_array` 时只给出警告,不改动。CubeMX 重新生成代码时保留已有的链接脚本,GCC 构建不受这一段
影响。

`libxr stm32 cmake` also handles the linker scripts (`*.ld`) in the root of the project. The
linker script CubeMX writes has no `.got` output section, while the `syscalls.c` and `sysmem.c`
CubeMX generates write `errno`; in the picolibc of ST Arm Clang `errno` is thread-local, and
reaching it takes a GOT entry. Without a `.got` output section ld.lld puts that entry in RAM, the
`.bin` spans Flash up to RAM (about 384 MiB), and after startup the entry does not hold the linked
value. `libxr stm32 cmake` therefore adds a `.got` output section after `.fini_array` to a linker
script without one, in the same memory region as `.fini_array`. ld.lld requires the init/fini
arrays and the GOT to be contiguous: with another output section between them, an STARM_NEWLIB
link fails with `.got is not contiguous with other relro sections`, and an existing `.got` that
does not directly follow `.fini_array` only gets a warning and stays as it is. CubeMX keeps an
existing linker script when it regenerates the code, and GCC builds are not affected by the
section.

---

## 💾 Flash 布局 / Flash Layout
Expand Down
31 changes: 19 additions & 12 deletions src/libxr/generator_stm32_cmake.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@
"""libxr stm32 cmake:把 LibXR 接入 CubeMX 生成的 CMake 工程。
libxr stm32 cmake: integrate LibXR into a CMake project generated by CubeMX.

整体生成 cmake/LibXR.CMake,把 ST Arm Clang 工具链文件恢复为 CubeMX 原样,并在 CMakeLists.txt
中 include LibXR.CMake。这些文件改变后,已有的构建目录在下次构建时由 CMake 重新配置。
整体生成 cmake/LibXR.CMake,把 ST Arm Clang 工具链文件恢复为 CubeMX 原样,在链接脚本中把 .got
放进 Flash,并在 CMakeLists.txt 中 include LibXR.CMake。这些文件改变后,已有的构建目录在下次构建时
由 CMake 重新配置。
It generates cmake/LibXR.CMake as a whole, restores the ST Arm Clang toolchain file to what
CubeMX wrote, and includes LibXR.CMake from CMakeLists.txt. CMake reconfigures existing build
directories on their next build after these files change.
CubeMX wrote, puts .got in Flash in the linker scripts, and includes LibXR.CMake from
CMakeLists.txt. CMake reconfigures existing build directories on their next build after these
files change.

LibXR.CMake 的结构固定:“Project settings”块在最前,其后依次是编译选项的 User Code 区域、LibXR、
Application、库的优化设置和固件镜像(链接后由 ELF 生成 .hex 和 .bin),最后是应用设置的 User Code 区域。
Expand Down Expand Up @@ -36,6 +38,8 @@
from xr_syntax.cpp import CppDocument
from xr_syntax.i18n import tr

from libxr.stm32_linker_script import place_got_in_project

# LibXR.CMake 第一行:说明文件由 libxr stm32 setup 生成,供用户修改的是设置块和两个 User Code 区域。
# The first line of LibXR.CMake: the file is generated by libxr stm32 setup, and the settings block
# and the two User Code regions are the parts for the user to edit.
Expand Down Expand Up @@ -1104,15 +1108,17 @@ def integrate(input_directory: str) -> None:
"""把 LibXR 接入 input_directory 中的 CubeMX CMake 工程。
Integrate LibXR into the CubeMX CMake project in input_directory.

依次生成或重写 cmake/LibXR.CMake,把 cmake/starm-clang.cmake 恢复为 CubeMX 原样,并在
CMakeLists.txt 没有 include LibXR.CMake 时(见 includes_libxr_cmake())追加一行。LIBXR_SYSTEM
由 Core/Inc 中的 FreeRTOSConfig.h 或 app_threadx.h 决定,都没有时为 None。输入目录或
CMakeLists.txt 不存在时以状态 1 退出。
依次生成或重写 cmake/LibXR.CMake,把 cmake/starm-clang.cmake 恢复为 CubeMX 原样,在根目录的
链接脚本中把 .got 放进 Flash、紧跟 .fini_array(见 stm32_linker_script),并在 CMakeLists.txt
没有 include LibXR.CMake 时(见 includes_libxr_cmake())追加一行。LIBXR_SYSTEM 由 Core/Inc
中的 FreeRTOSConfig.h 或 app_threadx.h 决定,都没有时为 None。输入目录或 CMakeLists.txt
不存在时以状态 1 退出。
In order, it generates or rewrites cmake/LibXR.CMake, restores cmake/starm-clang.cmake to
what CubeMX wrote, and appends a line that includes LibXR.CMake to CMakeLists.txt when it
does not include it yet (see includes_libxr_cmake()). LIBXR_SYSTEM follows FreeRTOSConfig.h
or app_threadx.h in Core/Inc, and is None without either. A missing input directory or
CMakeLists.txt exits with status 1.
what CubeMX wrote, puts .got in Flash directly after .fini_array in the linker scripts of the
root (see stm32_linker_script), and appends a line that includes LibXR.CMake to
CMakeLists.txt when it does not include it yet (see includes_libxr_cmake()). LIBXR_SYSTEM
follows FreeRTOSConfig.h or app_threadx.h in Core/Inc, and is None without either. A missing
input directory or CMakeLists.txt exits with status 1.
"""

if not os.path.isdir(input_directory):
Expand Down Expand Up @@ -1147,6 +1153,7 @@ def integrate(input_directory: str) -> None:
update_or_create_libxr_cmake(file_path, system, project_uses_xrobot(input_directory))

restore_starm_clang_toolchain(os.path.join(cmake_dir, "starm-clang.cmake"))
place_got_in_project(input_directory)

cmake_content = read_text_with_fallback(main_cmake_path)
if not includes_libxr_cmake(cmake_content):
Expand Down
174 changes: 174 additions & 0 deletions src/libxr/stm32_linker_script.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
"""STM32 链接脚本中 GOT 的位置:.got 输出段放在 Flash 中,紧跟 .fini_array。
The place of the GOT in an STM32 linker script: a .got output section in Flash, directly after
.fini_array.

CubeMX 写出的链接脚本没有 .got 输出段,而 CubeMX 生成的 syscalls.c 和 sysmem.c 写 errno;ST Arm
Clang 的 picolibc 中 errno 是线程局部变量,访问它要用一个 GOT 条目。ld.lld 把这个孤立的段放进 RAM,
加载地址等于运行地址:.bin 从 Flash 一直覆盖到 RAM(约 384 MiB),启动代码复制 .data 时写进这个字的
也不是链接时的值。静态镜像中的 GOT 条目是链接时就确定的常量,所以放在 Flash 中。ld.lld 把 init/fini
数组和 GOT 归入同一段 RELRO 范围,要求它们连续,所以 .got 紧跟 .fini_array;中间隔着别的输出段时,
STARM_NEWLIB 的链接报 ".got is not contiguous with other relro sections"。
The linker script CubeMX writes has no .got output section, while the syscalls.c and sysmem.c
CubeMX generates write errno; in the picolibc of ST Arm Clang errno is thread-local, and reaching
it takes a GOT entry. ld.lld puts that orphan section in RAM with its load address equal to its
run address: the .bin spans Flash up to RAM (about 384 MiB), and the startup copy of .data does
not write the linked value into that word. GOT entries of a static image are constants fixed at
link time, so they go to Flash. ld.lld puts the init/fini arrays and the GOT in one RELRO range
that must be contiguous, so .got directly follows .fini_array; with another output section in
between, an STARM_NEWLIB link fails with ".got is not contiguous with other relro sections".
"""

import logging
import re
from pathlib import Path

from xr_syntax.i18n import tr

# 插入的 .got 输出段,按 .fini_array 的缩进和内存区域写出。
# The inserted .got output section, written with the indentation and memory region of .fini_array.
GOT_SECTION = """\
{indent}/* GOT entries are link-time constants in this static image. ld.lld puts the init/fini
{indent} arrays and the GOT in one RELRO range, so .got must directly follow .fini_array. */
{indent}.got :
{indent}{{
{indent} . = ALIGN(4);
{indent} *(.got .got.*)
{indent} . = ALIGN(4);
{indent}}} {region}
"""

# 一行开头的输出段定义:段名,可选的括号(如 (READONLY)、(NOLOAD)),其他属性,然后是冒号。
# An output section definition at the start of a line: the name, an optional parenthesis such as
# (READONLY) or (NOLOAD), other attributes, then the colon.
_SECTION_HEADER = re.compile(
r"^(?P<indent>[ \t]*)(?P<name>\.[\w.$]+|/DISCARD/)[ \t]*(?:\([^)\n]*\))?[^:;=\n{}]*:(?![=:])",
re.MULTILINE,
)
_COMMENT = re.compile(r"/\*.*?\*/", re.DOTALL)


def _without_comments(text: str) -> str:
"""把 text 中的注释换成等长的空格(换行保留),使偏移量不变。
Replace the comments of text with as many spaces (keeping the line breaks), so that offsets
stay the same.
"""
return _COMMENT.sub(lambda m: re.sub(r"[^\n]", " ", m.group()), text)


def _sections(code: str) -> list[re.Match]:
"""code(已去掉注释)中 SECTIONS 块顶层的输出段定义,按出现顺序排列。
The output section definitions at the top level of the SECTIONS block of code (with the
comments removed), in their order.
"""
found = []
for match in _SECTION_HEADER.finditer(code):
start = match.start("name")
if code.count("{", 0, start) - code.count("}", 0, start) == 1:
found.append(match)
return found


def _closing_line_end(code: str, header: re.Match) -> tuple[int, str] | None:
"""header 所定义的输出段结束的那一行:(该行末尾的偏移量, 右花括号之后的内存区域)。
The line where the output section that header defines ends: (the offset of the end of that
line, the memory region after the closing brace).

找不到配对的花括号时为 None。
None when the braces do not pair up.
"""
opening = code.find("{", header.end())
if opening < 0:
return None
depth = 0
for index in range(opening, len(code)):
if code[index] == "{":
depth += 1
elif code[index] == "}":
depth -= 1
if depth == 0:
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()
Comment on lines +89 to +91

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

return None


def place_got(file_path: str | Path) -> bool:
"""确保链接脚本 file_path 中 .got 输出段紧跟 .fini_array;写回时返回 True。
Make sure the .got output section of the linker script file_path directly follows
.fini_array; True when the file was written back.

没有 .got 输出段时,在 .fini_array 之后插入一个,内存区域与 .fini_array 相同。已有的 .got
不紧跟 .fini_array 时只给出警告,用户写的段不动;没有 .fini_array 时也只警告。换行符和编码
保持文件原来的写法。
Without a .got output section, one is inserted after .fini_array, in the memory region of
.fini_array. An existing .got that does not directly follow .fini_array only gets a warning,
and the user's section stays; a script without .fini_array also only gets a warning. The line
endings and the encoding stay as the file has them.
"""
path = Path(file_path)
raw = path.read_bytes()
eol = "\r\n" if b"\r\n" in raw else "\n"
text = raw.decode("utf-8", errors="surrogateescape").replace("\r\n", "\n")
code = _without_comments(text)
sections = _sections(code)
names = [match.group("name") for match in sections]
if ".fini_array" not in names:
logging.warning(
tr(
f"{path} has no .fini_array output section; the GOT placement is left unchanged",
f"{path} 中没有 .fini_array 输出段,GOT 的位置保持不变",
)
)
return False
fini = names.index(".fini_array")
if ".got" in names:
if fini + 1 < len(names) and names[fini + 1] == ".got":
return False
logging.warning(
tr(
f"{path}: the .got output section does not directly follow .fini_array, so an "
'ST Arm Clang (ld.lld) link can fail with ".got is not contiguous with other '
'relro sections"; move it directly after .fini_array',
f"{path} 中的 .got 输出段没有紧跟 .fini_array,ST Arm Clang(ld.lld)链接可能报"
"“.got is not contiguous with other relro sections”;请把它移到 .fini_array 之后",
)
)
return False
closing = _closing_line_end(code, sections[fini])
if closing is None:
logging.warning(
tr(
f"{path}: the braces of .fini_array do not pair up; the GOT placement is left "
"unchanged",
f"{path} 中 .fini_array 的花括号不配对,GOT 的位置保持不变",
)
)
return False
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

logging.info(
tr(
f"Placed the GOT in {region.lstrip('>').strip()} after .fini_array in {path}",
f"已在 {path} 中把 GOT 放进 {region.lstrip('>').strip()},紧跟 .fini_array",
)
)
return True


def place_got_in_project(input_directory: str | Path) -> list[Path]:
"""对 input_directory 根目录中的每个链接脚本(*.ld)调用 place_got(),返回写回的文件。
Call place_got() on each linker script (*.ld) in the root of input_directory; return the
files that were written back.

CubeMX 的 CMake 工程把链接脚本放在根目录;一个工程可以有多份(如 Flash 和 RAM 各一份)。
A CubeMX CMake project keeps its linker scripts in the root; a project can have several
(one for Flash and one for RAM, for example).
"""
scripts = sorted(
entry
for entry in Path(input_directory).iterdir()
if entry.is_file() and entry.suffix.lower() == ".ld"
)
return [script for script in scripts if place_got(script)]
Loading
Loading