我开发了一个用户态应用,他的编译和运行环境不同。也就是我会在主机A上编译它,再把编译产物放到主机B上运行。为了保证他在主机B上能够运行,需要考虑动态链接库的问题。
具体来讲,主机A是我的开发机,主机B是QEMU模拟机,其上运行的Linux和rootfs是我使用buildroot工具构建的。
一段代码写出来之后,他用到哪些动态链接库,为什么会在编译期决定?
自己编写的代码会调用一些外部库函数,编译期发现这些函数只有声明没有定义,就会在编译产物中留下对这些函数的引用(等待被重定向或者被指定动态链接库)。
链接器会找到实现这些函数的动态链接库,动态链接库中往往有多个版本的对这个函数的不同实现,链接器会选择最新的版本,把这个版本号记录在编译产物中对应的引用位置。
编译产物实际运行时,会在运行环境中的动态链接库中寻找对应版本的函数实现。如果运行环境中动态链接库版本过老,找不到对应版本的函数实现,就会报错。
动态链接库文件 *.so 往往包含对一个函数的多个版本实现,达到了向前兼容(forward compatible)的能力——用之前(forward)版本编译的代码可以运行在更新(backward)的环境中。
1. 编码阶段:通过 #include 和函数调用,声明了对外部功能的需求意图。
2. 构建配置阶段:通过编译器参数(如 -lfoo),指定了满足该意图的动态库文件名(如 libfoo.so)。
3. 链接阶段(编译期):
- 链接器找到对应的 .so 文件。
- 发现其中有多个版本的函数实现。
- 链接器选择最新/默认的版本(例如 func@@V2)。
- 链接器将库的文件名(SONAME)和函数的具体版本号同时“烙印”在编译产物中。
4. 运行阶段:
- 加载器根据“烙印”的库文件名加载运行环境中的 .so。
- 加载器根据“烙印”的版本号寻找对应的函数实现。
- 如果环境里的库太老(没有该版本号),报错。
也就是构建时,我告诉编译器(或者依赖编译器的默认配置)需要哪些动态链接库,链接期这些库的名字和提供的函数实现的版本会被烙印在编译产物中,运行时,加载器会在运行环境中找同样名字的动态链接库,并在其中找同样版本的函数实现。
具体为什么用 buildroot 下面的 gcc 来链接,产物所依赖的动态链接库就是测试环境 rootfs 中所具备的了?
因为 buildroot 在构建 Linux image 和 rootfs 的同时在宿主环境(即主机A)上镜像了一套 rootfs,他提供的 gcc 也默认向这个镜像的 rootfs 中寻找动态链接库。所以这个 gcc 编译链接出的可执行文件所依赖的动态链接库一定是主机B rootfs 中所具备的。

1256

被折叠的 条评论
为什么被折叠?



