使用静态库链接不等同于链接其对象文件。

4

问题:

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

两个固件映像都可以成功链接并成功加载到微控制器上。

后者(链接对象)执行正常,符合预期,而前者(链接静态库)则不然。

编译期间唯一的警告是生产商提供的HAL中的 unused-but-set-variable ,由于各种宏定义在已编译的实现中不必要;以及各种弱函数中的unused-parameter ,也在生厂商提供的HAL中。

描述:

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

由于我希望使用相同的驱动程序和HAL开发多个应用程序(两者都是完整且经过测试,所以不会经常更改),因此我希望将HAL和驱动程序编译并分发为静态库,然后可以将其与应用程序源代码链接。

问题在于,在链接应用程序和静态库时,固件映像在微处理器上无法正确执行。当直接从静态库中提取对象文件并将其与应用程序链接时,固件映像会按预期执行。

具体而言:

使用以下方法链接静态库时创建的二进制文件无法正常工作:

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

当使用以下方式链接从静态库中提取的对象时,创建二进制文件:

@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-sections的输出以及app.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中断发生是一个误导,如被接受的答案所述

--Mike

4个回答

7

摘要:

问题在于静态库中并没有包含所有的对象,导致固件镜像不完整。解决方法是使用链接器标志--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" stackoverflow 帖子找到了答案。
阅读此帖子后,了解到具有相同内存地址的函数的 gdb 函数名称报告通常不准确,因此我检查了故障固件映像的生成的 .map 文件,并确认 WWDG_IRQHandler 位于大多数 IRQHandlers(包括系统定义和使用的某些定时器中断)的相同内存地址处。
此外,所有stm32f4xx_it.o对象中定义的中断(该对象定义了系统使用的中断的IRQHandler,并包含在静态库中)都指向Default_Handler的内存地址,并且相应的IRQHandler符号被列为由startup_stm32f407xx.o提供。
然后我检查了实际链接到固件映像中的对象文件(perl -n -e '/libtest\.a\((.*?)\)/ && print "$1\n"' app.map | sort -u),并发现只有一部分对象被链接。
startup_stm32f407xx.s的进一步检查显示它定义了许多弱符号,例如:
.weak TIM2_IRQHandler

在链接静态库的过程中,链接器会搜索未定义符号的库,并包含第一个找到的对象来定义这些符号。然后,它从未定义列表中删除该符号,以及由所包含的对象定义的任何其他未定义符号。
我猜发生的事情是,链接器在startup_stm32f407xx.o中找到了另外未定义的符号,并将其包含在内。它认为所有IRQHandler符号都由其中的弱定义来定义。由于stm32f4xx_it.o没有定义任何未定义符号,因此从未包含该对象。这种情况发生了多次,使用不同的对象文件;有时包括强符号,有时包括弱符号,这取决于首先搜索哪个对象。有趣(但并不奇怪)的是,如果删除弱定义,则包含包含强定义的对象,并且该文件中的所有强定义(正确地)覆盖已包含的弱定义。
解决了问题之后,我不确定接下来该做什么。这是链接器的一个错误吗?

有趣。我从未掉进那个坑,但使用LTO时遇到了类似的弱符号问题(这就是为什么我现在不使用它的原因)。然而,这加强了我的态度,不使用这些库。至少我知道所有涉及的代码。然而,对于中断处理程序,我做了很多相同的事情:在启动时使用弱别名到一个无限循环,并将实际处理程序作为强符号。这个问题是否特定于使用存档(其中包括库)而不是普通对象文件?实际上,我质疑存档的优点。 - too honest for this site
关于gdb给出的错误名称:当执行停止时,调试器只有当前指令的地址(PC寄存器)。因此,它必须查找与该地址匹配的符号(或在函数情况下最接近该地址的符号)。如果有多个符号匹配,则可能显示所有这些符号,但对于大多数情况来说这是毫无意义的,因此gdb只会停在第一个匹配上。哪个符号取决于查找算法(线性、哈希表等)。这是调试中相当常见的问题。 - too honest for this site

1
如果你能解释“二进制文件不起作用”到底是什么意思,那么你将获得更好的答案。
你是否获得了一个无法加载到芯片中的二进制文件?
如果是这样,请仔细查看命令行链接器输出。
你是否生成了可以加载到芯片中但却没有看到预期行为的内容?
如果是这样,请使用硬件调试器。通过代码步进直到出现故障,或者让其运行,然后停止并查看你停在哪里。
很可能你只是通过重新排列内存中所有内容发现了一些一直存在于代码中的错误。 数组溢出、错误指针引用和未初始化变量是典型的罪魁祸首。 打开-Wextra-Wall选项可以帮助检测出这些问题。
另外一个想法:确保您的LDSCRIPT具有实际零件号的正确闪存和RAM大小(即不适用于系列中的其他零件)。

谢谢你的建议,Brian;我已经更新了问题并提供了更多信息。我还仔细检查了链接脚本的目标,但不幸的是这不是问题所在。 - Mike Hamer

1
我目前也与该MCU一起工作。但是,出于很好的原因,我避免使用ST“标准”库。
看起来似乎在启动期间已启用看门狗,并且很快就会过期(中断是早期警告)。这可能是由于运行时行为的变化所致。这可能会因链接器创建的跳转表和/或 tink-time 优化(LTO)以及编译器和其他优化而有所不同。
给定的大小似乎超出了相同编译/链接选项的正常变化范围。但是,在-Os vs. -O3和LTO /无LTO方面,它们非常可能存在(对于后者,结果代码大小可能会更大或更小,具体取决于-O)。此外,我注意到某些gcc / ld版本存在LTO问题,所有代码都必须使用相同的选项进行编译和链接!还要检查所使用的ABI是否匹配(C和gcc-libs使用)。
一个很好的开始是通过WWDG-> CR上的观察点从复位粗略步进启动。还要检查EWI位;这实际上将允许中断。

谢谢你的回复,Olaf。它帮助我找到了解决方案。如果可以问一下,你在STM32开发中使用哪个HAL? - Mike Hamer
@MikeHamer:完全没有。我自己编写驱动程序并使用自己的框架。这并不比配置和使用“HAL”更困难。甚至更适合项目需求,因为它不仅仅是一个“硬件抽象层”,而是一个正常的驱动程序系统。这样做更快、更好。 - too honest for this site

0
在我的情况下,arm-none-eabi-ld没有链接从作为单独的静态库编译的启动文件的向量表。我通过在链接脚本中添加ENTRY(Reset_Handler)来解决了这个问题,这样链接器就被强制从这个静态库中获取一些符号(Reset_Handler函数),并且链接了向量表。对我来说,这是一个优雅的解决方案。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接