与静态库链接不等同于与其对象链接

Linking with static library not equivalent to linking with its objects

问题:

使用静态库link生成的固件映像与link使用直接从静态库中提取的对象生成的固件映像不同。

两个固件映像 link 没有错误并成功加载到微控制器上。

后一个二进制文件(linked with objects)按预期成功执行,而前者(linked to static library)没有。

编译期间唯一的警告是 unused-but-set-variable 在制造商提供的 HAL 中,由于各种宏定义对于编译实现来说不是必需的; unused-parameter 在各种弱函数中,也在制造商提供的 HAL 中。

描述:

我正在为 STM32F407 开发嵌入式应用程序。到目前为止,我一直在使用一个代码库,包括微处理器的 HAL 和设置代码、特定外围设备的驱动程序以及使用前两者的应用程序。

由于我希望使用相同的驱动程序和 HAL 开发多个应用程序(两者都是完整的和经过测试的,所以不会经常更改),我希望将 HAL 和驱动程序编译和分发为静态库,这可以然后 link 使用应用程序源进行编辑。

问题是当 link 访问应用程序和静态库时,固件映像无法在微处理器上正确执行。当 link 直接从静态库中提取应用程序和目标文件时,固件映像按预期执行。

具体来说:

创建的二进制文件在 link 使用静态库时不起作用:

$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(APPOBJECTS) Library/libtest.a

创建的二进制文件在 link使用从静态库中提取的对象时有效:

@cd Library && $(AR) x libtest.a && cd ..
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(APPOBJECTS) Library/*.o

在这两种情况下:

CFLAGS = $(INCLUDES) $(DEFS) -ggdb3 -O0 -std=c99 -Wall -specs=nano.specs -nodefaultlibs
CFLAGS+= -fdata-sections -ffunction-sections -mcpu=cortex-m4 -march=armv7e-m -mthumb
CFLAGS+= -mfloat-abi=hard -mfpu=fpv4-sp-d16 -MD -MP -MF $@.d

LDFLAGS = -T$(LDSCRIPT) -Wl,-static -Wl,-Map=$(@:.elf=.map),--cref -Wl,--gc-sections

我已经比较了 -Wl,--print-gc-sectionsapp.map 文件的输出,但是这两个构建之间的差异已经足够大了,没有任何一件事会跳出来是错误的。我也尝试过不使用 -Wl,--gc-sections,但无济于事。

两个固件镜像arm-none-eabi-size的输出为:

 text      data     bss     dec     hex filename
43464        76    8568   52108    cb8c workingapp.elf

 text      data     bss     dec     hex filename
17716        44    8568   26328    66d8 brokenapp.elf

在没有 -Wl,--gc-sections

的情况下编译时可以看到类似的大小差异

使用arm-none-eabi-gdb调试微控制器的执行,故障固件映像在WWDG中断发生时进入死循环。该中断在固件中未启用,因此中断处理程序默认为 Default_Handler(无限循环)。当 运行 工作固件映像时不会发生此中断。

发生的 WWDG 中断实际上是一个转移注意力的问题,如已接受的答案中所述

--迈克

如果您能解释 "the binary doesn't work" 的真正含义,您会得到更好的答案。

您是否获得了您的编程工具根本无法加载到芯片中的二进制文件?

如果是这样,请仔细查看命令行上的链接器输出。

您是否正在生产可以加载到芯片中但未看到预期行为的东西?

如果是这样,请使用硬件调试器。单步执行代码直到出现问题,或者让它 运行,然后停止它并查看最终结果。

很有可能,您只是通过重新排列所有内容在内存中的位置来发现代码中一直存在的错误。数组溢出、错误的指针取消引用和未初始化的变量是典型的罪魁祸首。打开 -Wextra-Wall 可以帮助发现这些东西。

另一个想法:确保您的 LDSCRIPT 具有适合实际零件号的正确闪存和 RAM 大小(即不适用于系列中的不同零件)。

我目前也在使用那个 MCU。但是,我有充分的理由避免使用 ST "standard" 库。

看起来好像看门狗在启动期间已启用并且很快就会过期(中断是一个早期警告。这可能是由于 运行 时间行为的变化。这很可能会有所不同,具体取决于由于链接器创建的蹦床 and/or 连接时间优化 (LTO) 和编译器内联以及其他优化而导致的链接。

对于具有相同 compile/link 选项的正常变化,给出的尺寸似乎超出了范围。但是对于 -Os 与 -O3 和 LTO/no LTO 来说它们是非常可能的(而对于后者,生成的代码大小可能会更大或更小,具体取决于 -O)。此外,我注意到某些 gcc/ld 版本存在 LTO 问题,所有代码都必须使用相同的选项进行编译和链接(!)。还要检查使用的 ABI,它是否匹配(使用的 C- 和 gcc-libs。

一个好的开始是通过 WWDG->CR 的观察点从复位开始粗步启动。还要检查 EWI 位;这实际上会允许中断。

总结:

问题是并非静态库中的所有对象都包含在固件映像中。这是通过用 --whole-archive--no-whole-archive 链接器标志包围静态库来解决的:

 $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(APPOBJECTS) -Wl,--whole-archive Library/libtest.a -Wl,--no-whole-archive

问题的出现是因为如果链接器包含具有弱符号定义的库对象,它会认为这些符号已定义,并且不再搜索它们的(强)定义。因此,可能会或可能不会包含具有强定义的对象,具体取决于搜索顺序及其定义的其他符号。

解决路径:

使用arm-none-eabi-gdb调试,似乎禁用 WWDG中断正在发生并调用Default_Handler。结果证明这是一条红鲱鱼......这种情况经常发生,以至于我通过 "STM32 WWDG interrupt firing when not configured" Whosebug post.

找到了答案

阅读此 post 并了解到 gdb 函数名称报告对于共享相同内存地址的函数通常不准确,我检查了生成的 .map 文件以查找错误的固件映像并确认WWDG_IRQHandler 与大多数 IRQHandlers 包括 系统定义和使用的中断的 IRQHandlers 位于相同的内存地址(例如,一些定时器中断)。

此外,所有中断定义在stm32f4xx_it.o对象(它定义了系统使用的中断的IRQHandlers,并且包含在静态库中)指向到 Default_Handler 的内存地址,并且相应的 IRQHandler 符号被列为由 startup_stm32f407xx.o.

提供

然后我检查了哪些对象文件实际链接到固件映像 (perl -n -e '/libtest\.a\((.*?)\)/ && print "\n"' app.map | sort -u) 并发现只有一部分对象被链接。

进一步检查startup_stm32f407xx.s发现它定义了许多弱符号,例如:

.weak TIM2_IRQHandler

在链接静态库的过程中,链接器会在库中搜索未定义的符号,并包含它找到的第一个定义这些符号的对象。然后它从未定义列表中删除该符号,以及由包含的对象定义的任何其他未定义符号。

我的猜测是链接器在 startup_stm32f407xx.o 中发现了一个未定义的符号并包含了该对象。它认为所有 IRQHandler 符号都由其中的弱定义定义。对象 stm32f4xx_it.o 从未被包含,因为它没有定义任何未定义的符号。这发生了很多次,有很多不同的目标文件;有时包含强符号,有时包含弱符号,具体取决于首先搜索的对象。有趣(但不足为奇)的是,如果弱定义被删除,则包含强定义的对象将被包括在内,并且来自该文件的 all 强定义(正确地)覆盖已经包含的弱定义.

解决了这个问题后,我不知道下一步该怎么做。这是链接器错误吗?