背景

手上有个单体项目(没错就是 LotKeyboard)需要支持多个 MCU,这就意味着硬件相关的操作应当封装起来,抽象为一系列的 HAL 的 API。

在将所有硬件相关的 API 都抽象出来之后,为了提高工程的编译速度,各个 MCU 的 HAL 被封装到了一个单独的静态库中。

在搞完这些拆分工作之后,发现了硬件偶尔会自动重启。根据重启日志,发现是看门狗动作执行的重启。为了方便调试卡死的问题,看门狗内写了一些打印寄存器的代码。但奇怪的是,并没有观察到这些寄存器信息。接入调试器也发现,并没有在对应的中断处理函数中断下来。

原因

通过调试器多次查找,发现中断之后直接进入了startup文件中的默认中断处理函数(while(1)),而不是进入自定义的中断处理函数。再通过直接打印中断向量表和查看对应.map文件,确定了中断向量表里面指向的就是默认中断函数。

直接删除掉默认中断函数并重新编译,就能够正常进入自定义的中断处理函数了。那么问题来了,是什么原因造成的标记为weak的默认中断函数没有被正常覆盖呢?

最后还是在万能的 StackOverflow 找到了答案。链接器在处理静态库的链接时,不会尝试区分是否为强符号还是弱符号,只会使用其找到的第一个符号。这个项目的Startup文件中定义了所有中断的弱符号,而startup文件是MCU相关的文件,所以被我塞到了HAL库里。链接器随机选用了默认中断或是自定义中断处理,这也就是为什么有的中断能正常断下,而有的中断却无法正常断下。

根据 这篇回答 和 GCC 文档的说法,弱符号主要是给库提供默认实现的,外部调用库的人可以通过强符号覆盖这些默认实现。链接器在处理静态库的时候,只会使用当前没有的符号。这就意味着,即便我在一个库里面定义强符号,而在外部调用库的地方定义弱符号,弱符号也不会被强符号所覆盖。

解决方案

前面提到,为了加速编译,需要把 HAL 库单独作为一个编译单元拆到外面去;而上面又提到,静态库在链接时,强弱符号定义无法覆盖;我们是一个 MCU 程序,更没办法编译为动态库动态加载。那么,有没有什么方法能够既让其拆出去,又不是一个静态/动态库呢?

答案是 CMake 的 Object Library(对象库)。使用以下语句声明 Object Library:

add_library(<name> OBJECT <sources>...)

CMake 的这种库类型只编译不链接,最后通过特定的语句,将其放入需要使用的编译单元中一起链接:

add_executable(... $<TARGET_OBJECTS:objlib_name> ...)

当然,这也不是没有坑的。对象库没办法嵌套,所以你只能在最外面使用上面的方法一起链接。这里解释了为什么,后续可以期待下 CMake 的改进。

分类: 未分类

0 条评论

发表回复

Avatar placeholder

您的邮箱地址不会被公开。 必填项已用 * 标注