IDF组件需要链接回main组件的回调的链接问题

RigoLigo
Posts: 1
Joined: Sun Sep 28, 2025 3:47 am

IDF组件需要链接回main组件的回调的链接问题

Postby RigoLigo » Sun Sep 28, 2025 4:11 am

像TinyUSB这类库,它们调用用户回调的方式是:直接在库代码里声明回调函数,自己不实现;让用户在自己的代码里实现,然后等到链接的时候再链接起来。

这个方式在传统的MCU项目里能用:它们通常只有一个最终的elf target。但是在ESP-IDF中,第三方的和第一方的库、用户app都被编译成一个个.a静态库,这就引出了一个问题:

libmain.a需要调用TinyUSB的任务函数,于是libmain.a依赖libespressif__tinyusb.a;
libespressif__tinyusb.a需要调用libmain.a提供的USB事件回调函数,于是libespressif__tinyusb.a依赖libmain.a。
这就造成了.a静态库之间的循环依赖问题。由于GNU链接器ld的特性(见 https://stackoverflow.com/questions/451 ... ors-in-gcc),它按顺序打开.a归档的时候完全不会处理不需要的object file,那么理论上在这种.a库循环依赖的场景中:要么main链接不到TinyUSB的所有API,要么TinyUSB链接不到main提供的回调。

实际的情况是,如果你把TinyUSB回调放在一个单独的.c文件(这在实现一些复杂的功能的时候几乎是必要的,因为代码量很大,总不能都像例程一样塞在main.c里),那么链接的时候TinyUSB就会无法链接到main提供的回调,即使这个文件已经被编译成object file、打包到libmain.a中。而我翻看所有直接使用TinyUSB的例程无一例外都是直接把回调放在main.c里,直接规避了这个问题。

如果在build.ninja里给elf target的编译指令加上“-Wl,--verbose”,观察输出信息可以看到,提供回调的那个object file根本就没有被链接器处理过。一个workaround是给链接器提供一个“-Wl,--start-group”参数让它不要按顺序链接,但ESP-IDF似乎根本没有提供一个能为最终elf目标添加构建选项的API(至少在我入门的这几天没有翻到,这也是为什么加verbose是直接改的build.ninja,因为别无他法了)。

如果我们用户要移植一些其他的库,很多嵌入式库都是用这种方式调用用户回调的,做成组件的话同样会遇到这个麻烦。希望能有一个更通用的解决方案兼容这些库。

Who is online

Users browsing this forum: No registered users and 3 guests