From e9fd76d59dccab86de0c25684429ac9904e5ff7a Mon Sep 17 00:00:00 2001 From: Jiu-xiao <2592509183@qq.com> Date: Wed, 14 Oct 2026 16:40:52 +0100 Subject: [PATCH] Place the GOT in Flash after .fini_array in STM32 linker scripts 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. --- README.md | 24 ++++ src/libxr/generator_stm32_cmake.py | 31 +++-- src/libxr/stm32_linker_script.py | 174 +++++++++++++++++++++++++ tests/test_stm32_linker_script.py | 202 +++++++++++++++++++++++++++++ 4 files changed, 419 insertions(+), 12 deletions(-) create mode 100644 src/libxr/stm32_linker_script.py create mode 100644 tests/test_stm32_linker_script.py diff --git a/README.md b/README.md index 236748b..43be759 100644 --- a/README.md +++ b/README.md @@ -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. @@ -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 diff --git a/src/libxr/generator_stm32_cmake.py b/src/libxr/generator_stm32_cmake.py index 75b83a8..b4226fa 100644 --- a/src/libxr/generator_stm32_cmake.py +++ b/src/libxr/generator_stm32_cmake.py @@ -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 区域。 @@ -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. @@ -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): @@ -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): diff --git a/src/libxr/stm32_linker_script.py b/src/libxr/stm32_linker_script.py new file mode 100644 index 0000000..49e71b9 --- /dev/null +++ b/src/libxr/stm32_linker_script.py @@ -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[ \t]*)(?P\.[\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() + 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")) + 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)] diff --git a/tests/test_stm32_linker_script.py b/tests/test_stm32_linker_script.py new file mode 100644 index 0000000..94bdb6c --- /dev/null +++ b/tests/test_stm32_linker_script.py @@ -0,0 +1,202 @@ +"""STM32 链接脚本中 GOT 的位置(libxr.stm32_linker_script):没有 .got 时插在 .fini_array 之后, +已有的 .got 不紧跟 .fini_array 时给出警告,重复运行不再改动。 +The place of the GOT in an STM32 linker script (libxr.stm32_linker_script): without .got one is +inserted after .fini_array, an existing .got that does not directly follow .fini_array gets a +warning, and running again changes nothing. +""" + +import tempfile +import textwrap +from pathlib import Path + +from fixtures import TestCase, logging_marker + +from libxr import generator_stm32_cmake as stm32_cmake +from libxr import stm32_linker_script as linker + +# CubeMX 6.18.1 为 STM32F407 写出的链接脚本中 .fini_array 前后的部分(省略号处有删节)。 +# The part around .fini_array of the linker script CubeMX 6.18.1 writes for an STM32F407 (cut +# where the ellipses are). +CUBEMX_SCRIPT = textwrap.dedent("""\ + /* Memories definition */ + MEMORY + { + CCMRAM (xrw) : ORIGIN = 0x10000000, LENGTH = 64K + RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 128K + FLASH (rx) : ORIGIN = 0x8000000, LENGTH = 1024K + } + + /* Sections */ + SECTIONS + { + /* ... */ + .init_array (READONLY) : /* The "READONLY" keyword is only supported in GCC11 and later, remove it if using GCC10 or earlier. */ + { + . = ALIGN(4); + PROVIDE_HIDDEN (__init_array_start = .); + KEEP (*(SORT(.init_array.*))) + KEEP (*(.init_array*)) + PROVIDE_HIDDEN (__init_array_end = .); + . = ALIGN(4); + } >FLASH + + .fini_array (READONLY) : /* The "READONLY" keyword is only supported in GCC11 and later, remove it if using GCC10 or earlier. */ + { + . = ALIGN(4); + PROVIDE_HIDDEN (__fini_array_start = .); + KEEP (*(SORT(.fini_array.*))) + KEEP (*(.fini_array*)) + PROVIDE_HIDDEN (__fini_array_end = .); + . = ALIGN(4); + } >FLASH + + _siccmram = LOADADDR(.ccmram); + + /* CCM-RAM section */ + .ccmram : + { + . = ALIGN(4); + _sccmram = .; /* create a global symbol at ccmram start */ + *(.ccmram) + *(.ccmram*) + . = ALIGN(4); + _eccmram = .; /* create a global symbol at ccmram end */ + } >CCMRAM AT> FLASH + + /* used by the startup to initialize data */ + _sidata = LOADADDR(.data); + + /* Initialized data sections goes into RAM, load LMA copy after code */ + .data : + { + . = ALIGN(4); + _sdata = .; /* create a global symbol at data start */ + *(.data) /* .data sections */ + *(.data*) /* .data* sections */ + . = ALIGN(4); + } >RAM AT> FLASH + + /* Initialized TLS data section */ + .tdata : ALIGN(4) + { + *(.tdata .tdata.* .gnu.linkonce.td.*) + . = ALIGN(4); + _edata = .; /* define a global symbol at data end */ + } >RAM AT> FLASH + /* ... */ + } + """) + +# 插入后的 .got 输出段。 +# The .got output section after the insertion. +GOT_BLOCK = """\ + + /* GOT entries are link-time constants in this static image. ld.lld puts the init/fini + arrays and the GOT in one RELRO range, so .got must directly follow .fini_array. */ + .got : + { + . = ALIGN(4); + *(.got .got.*) + . = ALIGN(4); + } >FLASH +""" +FINI_END = " PROVIDE_HIDDEN (__fini_array_end = .);\n . = ALIGN(4);\n } >FLASH\n" +PLACED_SCRIPT = CUBEMX_SCRIPT.replace(FINI_END, FINI_END + GOT_BLOCK) +# .got 在 CCM 段之后的脚本:ST Arm Clang 的 NEWLIB 配置链接失败的写法。 +# A script with .got after the CCM section: the layout an ST Arm Clang NEWLIB link fails on. +GOT_AFTER_CCM = CUBEMX_SCRIPT.replace( + " /* used by the startup", GOT_BLOCK.lstrip("\n") + "\n /* used by the startup" +) + + +class PlacedGot(TestCase): + """单个链接脚本中 .got 的插入、保留和警告。 + The insertion, keeping and warnings of .got in a single linker script. + """ + + def setUp(self): + super().setUp() + temporary = tempfile.TemporaryDirectory() + self.addCleanup(temporary.cleanup) + self.path = Path(temporary.name) / "STM32F407XX_FLASH.ld" + + def place(self, text): + """写入链接脚本并放置 GOT,返回 (是否写回, 结果)。 + Write the linker script, place the GOT and return (whether it was written, the result). + """ + self.path.write_bytes(text.encode("utf-8")) + changed = linker.place_got(self.path) + return changed, self.path.read_bytes().decode("utf-8") + + def test_a_cubemx_script_gets_got_directly_after_fini_array(self): + self.assertEqual(self.place(CUBEMX_SCRIPT), (True, PLACED_SCRIPT)) + + def test_running_again_changes_nothing(self): + self.place(CUBEMX_SCRIPT) + with self.assertLogs(level="INFO") as logs: + logging_marker() + self.assertEqual(self.place(PLACED_SCRIPT), (False, PLACED_SCRIPT)) + self.assertEqual(logs.output, ["WARNING:root:marker"]) + + def test_line_endings_stay_as_the_file_has_them(self): + crlf = CUBEMX_SCRIPT.replace("\n", "\r\n") + self.assertEqual(self.place(crlf), (True, PLACED_SCRIPT.replace("\n", "\r\n"))) + + def test_the_got_takes_the_memory_region_of_fini_array(self): + rom = CUBEMX_SCRIPT.replace("FLASH", "ROM") + self.assertEqual(self.place(rom), (True, PLACED_SCRIPT.replace("FLASH", "ROM"))) + + def test_got_in_comments_or_input_rules_does_not_count(self): + # 更早的 BSP 在 .data 中加载 GOT;插入的 .got 在前,先取走这些输入段。 + # Earlier BSPs loaded the GOT with .data; the inserted .got comes first and takes those + # input sections. + with_rule = CUBEMX_SCRIPT.replace( + " *(.data*) /* .data* sections */\n", + " *(.data*) /* .data* sections */\n" + " *(.got .got.*) /* .got : loaded with .data */\n", + ) + changed, text = self.place(with_rule) + self.assertTrue(changed) + self.assertEqual(text, with_rule.replace(FINI_END, FINI_END + GOT_BLOCK)) + + def test_a_got_elsewhere_is_left_alone_with_a_warning(self): + with self.assertLogs(level="WARNING") as logs: + self.assertEqual(self.place(GOT_AFTER_CCM), (False, GOT_AFTER_CCM)) + self.assertEqual(len(logs.output), 1) + self.assertIn("does not directly follow .fini_array", logs.output[0]) + + def test_a_script_without_fini_array_is_left_alone_with_a_warning(self): + script = "SECTIONS\n{\n .text :\n {\n *(.text*)\n } >FLASH\n}\n" + with self.assertLogs(level="WARNING") as logs: + self.assertEqual(self.place(script), (False, script)) + self.assertIn("has no .fini_array output section", logs.output[0]) + + +class ProjectScripts(TestCase): + """工程根目录中的每份链接脚本,以及 libxr stm32 cmake 中的这一步。 + Every linker script in the root of a project, and this step of libxr stm32 cmake. + """ + + def setUp(self): + super().setUp() + temporary = tempfile.TemporaryDirectory() + self.addCleanup(temporary.cleanup) + self.root = Path(temporary.name) + + def test_every_linker_script_in_the_root_is_placed(self): + (self.root / "STM32F407XX_FLASH.ld").write_text(CUBEMX_SCRIPT, encoding="utf-8") + (self.root / "STM32F407XX_RAM.ld").write_text(PLACED_SCRIPT, encoding="utf-8") + (self.root / "notes.txt").write_text(CUBEMX_SCRIPT, encoding="utf-8") + written = linker.place_got_in_project(self.root) + self.assertEqual(written, [self.root / "STM32F407XX_FLASH.ld"]) + for name in ("STM32F407XX_FLASH.ld", "STM32F407XX_RAM.ld"): + self.assertEqual((self.root / name).read_text(encoding="utf-8"), PLACED_SCRIPT) + self.assertEqual((self.root / "notes.txt").read_text(encoding="utf-8"), CUBEMX_SCRIPT) + + def test_integrating_cmake_places_the_got(self): + (self.root / "CMakeLists.txt").write_text("project(demo)\n", encoding="utf-8") + (self.root / "STM32F407XX_FLASH.ld").write_text(CUBEMX_SCRIPT, encoding="utf-8") + stm32_cmake.integrate(str(self.root)) + self.assertEqual( + (self.root / "STM32F407XX_FLASH.ld").read_text(encoding="utf-8"), PLACED_SCRIPT + )