跳到主要内容

6.2 项目编译与链接常见问题​

项目编译常见问题与解决方法​

Q1: 点击 Build Project 没有任何反应或报错?​

  1. 检查是否已安装并启用 CMake Tools 扩展。
  2. 检查 Output 视图、控制台视图是否有报错信息。

Q2: 修改了 CMakeLists.txt 或 Presets 后,构建似乎没有生效?​

  1. 构建流程通常会自动检测变化并触发 Configure。
  2. 如果遇到异常,可尝试手动执行 Clean Project 后再 Build。
  3. 或者在命令面板执行 CMake: Configure 强制重新配置。

Q3: 源文件是如何被包含到工程中的?​

CMake 项目不同于 Eclipse 项目,需要手动指定编译所需要的全部源文件,当前在 cmake/CMakeLists.txt 文件中通过 COMMON_SOURCES 字段全部列出。

Q4: 如何支持 C++?​

C++ 默认处于关闭状态。需要在 configPresets.json 中将 ENABLE_CPP 设置为 true:

cmake/configPresets.json
{
"cacheVariables": {
"ENABLE_CPP": true
}
}

启用后,CMake 将启用 C++ 相关工具链配置。

Q5: 如何排除某个文件夹/文件,使其中的源文件不参与编译?​

由于当前需要手动指定所有源码文件,不再需要排除功能。

Q6: 修改配置后编译结果不对,如何重新 Configure?​

当你修改了 CMakeLists.txt、CMakePresets.json、configPresets.json、flags.cmake 等配置文件后,如果编译结果与预期不符,建议按以下步骤操作:

方法 1:重新 Configure(推荐首先尝试)

在 VS Code 中:

  1. 按 Ctrl+Shift+P 打开命令面板。
  2. 执行 "CMake: Configure",或者在 CMake Tool Window 中点击 Configure 按钮。

这会重新解析所有 CMake 配置文件并生成构建缓存。

方法 2:删除 CMake Cache(解决顽固缓存问题)

如果以上方法无效,可以完全删除 cmake 输出目录后重新进行 build 操作。

Q7: 如何自定义编译选项(如添加宏定义或警告开关)?​

在 CMakePresets.json 对应 Preset 的 cacheVariables 中修改 USER_C_FLAGS / USER_CXX_FLAGS:

cmake/CMakePresets.json
{
"name": "Debug",
"cacheVariables": {
"USER_C_FLAGS": "-O0 -g -DMY_DEBUG_MACRO=1 -Wall",
"USER_CXX_FLAGS": "-O0 -g -DMY_DEBUG_MACRO=1 -Wall",
"USER_ASM_FLAGS": "-gdwarf-3"
}
}

或者在扩展底部的功能视图找到 Build Configuration 面板,通过 Edit 按钮进行可视化编辑。推荐通过 USER_*_FLAGS 添加自定义选项,而不是直接修改 flags.cmake。这样可以保持系统默认 flags 和用户自定义 flags 的分离。

Q8: 链接失败,显示 .text 和 .data 重叠​

现象:

链接阶段报错,提示 .text 与 .data 发生地址重叠(如 section .text [...] overlaps with .data [...]),或提示位置计数器超出 Flash / 内存区域范围。

原因:

新版 LLVM/Clang 工具链中,链接脚本的位置计数器赋值(. = <表达式>)遵循绝对物理地址语义,而非 GNU GCC 中的段内相对偏移语义:

  1. .text 段首:在 GCC 中常用 . = 0x0000; 表示段内相对偏移 0;但在 LLVM 中属于绝对物理地址赋值。虽然部分芯片 Flash 起始地址恰好为 0x00000000,但链接器默认已经从 ORIGIN(flash) 开始分配,显式赋值纯属冗余,且在 Flash 起始地址非 0 的芯片上会直接引发越界冲突。
  2. 段内偏移与空间保留:在 RAM 区域(如 .data 段)中若写成 . = 0x4000; 或 0x0200 / 0x0400 等裸常数偏移,在 LLVM 语义下会被直接解析为物理地址 0x00004000(落入 Flash 空间),导致位置计数器逆向跳回 Flash 区域,进而引发 .text 与 .data 致命重叠。

解决方案:

参考官方工程模板(如 KF32A100GQP_gcc.ld 与 KF32A100GQP_clang.ld)进行如下修改:

  1. 移除 .text 段首的绝对地址赋值:直接省略 . = 0x0000;,由链接器自动从 Flash 起始地址开始放置向量表。
  2. 段内偏移改用基地址绝对运算:如需对 RAM 或特定段内做偏移/长度限制,必须显式加上对应区域的起始地址(如 ORIGIN(ram) + 0x4000),避免使用裸数值。
<chip_name>.ld (.text 段首)
SECTIONS
{
/*#############################################################*/
.text :
{
- . = 0x0000;
+ /* 默认从 Flash 起始地址 ORIGIN(flash) 开始放置,此行直接省略 */
KEEP (*vector.o(.text*)) /* chip interrupt vector ,writed in file named vector.s or vector.c */
KEEP (*vector.obj(.text*)) /* compatible */
KEEP (*vector.c.obj(.text*)) /* compatible */
KEEP (*vector.asm.obj(.text*)) /*compatible*/
<chip_name>.ld (RAM 段内偏移)
__lpdata_start__ = ALIGN(4);
*(.lpdata*) /* Reset but Keep Space */
- /* . = 0x4000 ; */ /* GCC 相对偏移写法,在 LLVM 中会被解释为绝对物理地址 0x4000 */
+ /* . = ORIGIN(ram) + 0x4000 ; */ /* LLVM 需显式加上 ORIGIN(ram) 基址,确保留在 RAM 区域内 */
__lpdata_end__ = ALIGN(4);

Q9: HEX 文件尾部填充 0,实际 HEX 大小与计算显示大小不符​

现象:

编译生成的 HEX 固件文件中包含大量连续无意义的 0 填充(Padding),导致生成的 HEX 文件体积偏大,烧录时间显著增加。

原因:

在旧版 GCC 链接脚本中,通常将 .data(有初值全局变量)、.bss(未初始化全局变量)、堆与栈全部合并在同一个 .data 段中:

.data : AT(__text_end__) { ... } > ram

由于给整个段声明了 Flash 加载地址(LMA)AT(...),链接器在生成烧录镜像时,会为该段内的所有内容(包括本应只在运行时驻留 RAM 的 .bss、堆和栈)分配 Flash 存储映像,并在尾部生成大量 0x00 填充,造成固件体积虚胖。

解决方案:

参考官方新建的工程模板ld文件,将链接脚本重构为三个独立输出段:

  1. .data 段:仅保留带初始值的全局变量,末尾采用 } > ram AT > flash 声明(比旧的 AT(__text_end__) 更加稳健清晰)。
  2. .bss (NOLOAD) 段:拆分为独立段并添加 (NOLOAD) 属性,显式告知链接器该段仅在 RAM 中分配运行时地址(VMA),不在 Flash 镜像中分配存储空间(LMA)。
  3. .heap_stack (NOLOAD) 段:堆与栈空间同样拆分为带有 (NOLOAD) 属性的独立段,彻底杜绝尾部全零填充。
<chip_name>.ld
- .data : AT(__text_end__)
+ .data :
{
__data_start__ = .;
KEEP (*(.ramvector*))
__lpdata_start__ = ALIGN(4);
*(.lpdata*)
__lpdata_end__ = ALIGN(4);
...
*(.data*)
. = ALIGN(4);
__data_end__ = .;
- /*--------------------------------------------------*/
- . = ALIGN(4);
- __bss_start__ = .; /* global variable with no initialization */
- *(COMMON*)
- *(.bss*)
- . = ALIGN(4);
- __bss_end__ = .;
- /*--------------------------------------------------*/
- __allot_end__ = .; /* heap and stack space */
- ... (堆与栈配置及栈顶指针) ...
- __initial_sp = ORIGIN(ram) + LENGTH(ram);
- } > ram
+ } > ram AT > flash
+
+ /* bss,纯 RAM,不再占用 Flash 镜像空间 */
+ .bss (NOLOAD):
+ {
+ . = ALIGN(4);
+ __bss_start__ = .;
+ *(COMMON*)
+ *(.bss*)
+ . = ALIGN(4);
+ __bss_end__ = .;
+ } > ram
+
+ /* 堆 + 栈,纯 RAM,不再占用 Flash 镜像空间 */
+ .heap_stack (NOLOAD):
+ {
+ . = ALIGN(4);
+ __allot_end__ = .; /* heap and stack space */
+ __Heap_Start__ = __allot_end__ ;
+ ...
+ __initial_sp = ORIGIN(ram) + LENGTH(ram);
+ PROVIDE(__Heap_length__ = 0x400);
+ } > ram

Q10: 栈指针异常导致的运行时错误​

现象:

程序运行时,栈指针(SP)被错误地指向了 Flash 地址空间,而非预期的 RAM 区域。这导致函数调用前后硬件自动执行的压栈/出栈操作(保存和恢复寄存器现场)实际在 Flash 上执行写入,从而导致程序运行时错误,引发程序跑飞、复位等异常行为。

原因及解决方案:

LENGTH(ram) 仅表示 RAM 区域的大小(长度),并非其实际物理结束地址。正确的栈顶(__initial_sp)应为 RAM 的起始地址加上其长度,即 ORIGIN(ram) + LENGTH(ram)。

请在链接脚本中修改栈顶指针定义:

<chip_name>.ld
- __initial_sp = LENGTH(ram);
+ __initial_sp = ORIGIN(ram) + LENGTH(ram);

修改后,栈指针在复位启动时能够正确指向 RAM 的高地址末尾(栈向下增长),确保函数调用与中断嵌套压栈时数据写入正常的 SRAM 区域,消除内存写入异常。

Q11: 链接时 memmove 行为 crash​

现象:

链接过程中发生 crash,且堆栈中显示是 memmove 系统调用导致的。

原因及解决方案:

__LMA_STACK_LIMITS__ = DEFINED(Stack_Mem)? (__initial_sp - ( __Logic_Stack_Start__ - __Logic_Stack_End__ )): (. );
. = __LMA_STACK_LIMITS__ ;

__initial_sp = LENGTH(ram);

原来的 GNU 脚本导致 __initial_sp 值出错,__LMA_STACK_LIMITS__ 值出错,最终导致位置计数器出错。以下给位置计数器的赋值也会得到一样的结果:

.data:
{
...
s = 0x0;
. = s;
}

发生这种情况时需要检查位置计数器的赋值。