编译期链接时共享库搜索路径优先级实验

news/2024/9/24 3:19:03/文章来源:https://www.cnblogs.com/paw5zx/p/18330701

目录
  • 前言
  • 实验环境
  • 目录说明
  • 准备工作
  • 单独测试
    • 不配置路径
    • 默认路径
    • LIBRARY_PATH
    • -L
  • 优先级测试
    • 默认路径和LIBRARY_PATH
    • -L和默认路径
  • DEBUG模式
    • 编译器配置详细信息
    • 链接器详细信息
    • DEBUG总结
    • 验证
  • 默认路径>LIBRARY_PATH原因
  • 附录
    • 库文件源码
    • 主程序源码
    • makefile

前言

《共享库链接和加载时的路径搜索优先级》中提到,使用g++时,共享库在编译期链接时的库路径搜索优先级为:-L指定的路径>LIBRARY_PATH记录的路径>默认路径

本实验分三步验证上述结论
①单独测试每种方法指定的路径的可行性
②对比测试三种方法间的优先级
③使用DEBUG模式,查看链接器输出的详细信息,二次验证上述结论

值得注意的是,我看网上都说LIBRARY_PATH指定的路径优先级要大于默认路径的优先级,但是就我的测试结果来看,结论是相反的(可能是我使用了g++而不是直接使用底层的ld?)。

实验环境

操作系统:Ubuntu 20.04
编译器:g++-11.4.0
make:GNU Make 4.2.1

目录说明

项目的目录结构如下:

.
├── lib
├── obj
├── libhello.cpp
├── libhello_alt.cpp
├── main.cpp
└── makefile

其中:

  • lib为存放共享库的文件夹
  • obj为存放可重定位目标文件的文件夹
  • libhello.cpplibhello_alt.cpp为共享库源码(用于模拟不同版本的共享库),他们中都只有一个hello函数,两个hello函数的函数签名完全相同。其中libhello.cpp将被编译为libhello.so.1.1.0(soname为libhello.so.1),libhello_alt.cpp将被编译为libhello.so.2.1.0(soname为libhello.so.2
  • mian.cpp为主函数,其中调用了hello函数
  • makefile为自动化构建脚本

在附录中,我将提供本次实验涉及到的代码。

准备工作

在终端中进入项目路径,并输入make,会在./lib下生成libhello.so.1.1.0libhello.so.2.1.0,在./obj下生成main.o
生成后项目的目录结构如下:

.
├── lib
│   ├── libhello.so.1.1.0	
│   └── libhello.so.2.1.0
├── obj
│   └── main.o
├── libhello.cpp
├── libhello_alt.cpp
├── main.cpp
└── makefile

单独测试

不配置路径

不做任何路径的配置并且不在默认路径下放置libhello.so文件,查看是否可以将main.ohello的共享库文件链接成功。

直接使用makefile中预设好的命令即可完成上述操作:

make main_none

输出:

可以看到由于我们没有配置任何额外的搜索路径,并且没有在默认搜索路径下放置libhello.so文件,链接器就找不到相应的共享库文件,就会链接失败。

单次实验结束后,使用make clean命令清除本次实验生成的文件,然后再次使用make命令重新生成共享库文件和可重定位目标文件。(每次做完一个小实验,都要重复此步骤,后不赘述)

默认路径

libhello.so.1.1.0拷贝至默认搜索路径/usr/lib,并在/usr/lib下创建一个软链接(libhello.so)指向它,然后进行链接操作,查看是否可以将main.ohello的共享库文件链接成功。

直接使用makefile中预设好的命令即可完成上述操作:

make main_default

输出:

没有报错。

然后使用readelf -d查看可执行文件的动态段信息,可见链接成功,共享库的soname已经被写入到可执行文件的动态段信息中了。

LIBRARY_PATH

创建路径/opt/hellolib,将libhello.so.1.1.0拷贝至/opt/hellolib,并在/opt/hellolib下创建一个软链接(libhello.so)指向它。然后将/opt/hellolib添加至LIBRARY_PATH并进行main.ohello的共享库文件的链接操作,查看是否可以链接成功。

直接使用makefile中预设好的命令即可完成上述操作:

make main_library_path

输出:

没有报错。

然后使用readelf -d查看可执行文件的动态段信息,可见链接成功,共享库的soname已经被写入到可执行文件的动态段信息中了。

-L

创建路径/opt/hellolib,将libhello.so.1.1.0拷贝至/opt/hellolib,并在/opt/hellolib下创建一个软链接(libhello.so)指向它,然后添加链接选项-L/opt/hellolib并进行链接操作,查看是否可以将main.ohello的共享库文件链接成功。

直接使用makefile中预设好的命令即可完成上述操作:

make main_l

输出:

没有报错。

然后使用readelf -d查看可执行文件的动态段信息,可见链接成功,共享库的soname已经被写入到可执行文件的动态段信息中了。

优先级测试

默认路径和LIBRARY_PATH

  • ①将libhello.so.2.1.0拷贝至默认搜索路径/usr/lib,并在/usr/lib下创建一个软链接(libhello.so)指向它。
  • ②创建路径/opt/hellolib,将libhello.so.1.1.0拷贝至/opt/hellolib,并在/opt/hellolib下创建一个软链接(libhello.so)指向它。
  • ③将/opt/hellolib添加至LIBRARY_PATH并进行main.ohello的共享库文件的链接操作。

直接使用makefile中预设好的命令即可完成上述操作:

make cmp_default_libpath

输出:

然后使用readelf -d查看可执行文件的动态段信息,可见链接成功,并且链接的是默认路径下的共享库文件libhello.so.2.1.0(其soname为libhello.so.2)。因此可以得出结论:默认路径搜索优先级要高于LIBRARY_PATH指定的路径的搜索优先级。

对于上述结论,将会在后文的DEBUG模式中给出更详细的验证。

-L和默认路径

  • ①创建路径/opt/hellolib,将libhello.so.2.1.0拷贝至/opt/hellolib,并在/opt/hellolib下创建一个软链接(libhello.so)指向它。
  • ②将libhello.so.1.1.0拷贝至默认搜索路径/usr/lib,并在/usr/lib下创建一个软链接(libhello.so)指向它。
  • ③添加链接选项-L/opt/hellolib并进行main.ohello的共享库文件的链接操作。

直接使用makefile中预设好的命令即可完成上述操作:

make cmp_l_default

输出:

然后使用readelf -d查看可执行文件的动态段信息,可见链接成功,并且链接的是-L指定路径下的共享库文件libhello.so.2.1.0(其soname为libhello.so.2)。因此可以得出结论:-L指定路径搜索优先级要高于默认搜索路径的搜索优先级。

对于上述结论,将会在后文的DEBUG模式中给出更详细的验证。

DEBUG模式

在makefile中我添加了一个用于对比三种路径优先级的目标cmp_all,其中

  • -L指定路径为/opt/hellolib_L
  • 默认路径为/usr/lib
  • LIBRARY_PATH指定路径为/opt/hellolib.so文件(libhello.so.1.1.0的软链接)仅放置于此路径下。

此外我还预设了一个DEBUG模式,开启DEBUG模式可以查看编译过程的详细信息,开启的方法就是在命令后面添加DEBUG_MODE=1,例如:

make cmp_all DEBUG_MODE=1

下面我们就使用DEBUG模式运行cmp_all查看其输出(输出信息很多,我截取关键部分讲解):

编译器配置详细信息

我们先看一下gcc在编译过程中输出的编译器配置详细信息:

图片中的文字内容如下:

LIBRARY_PATH=/usr/lib/gcc/x86_64-linux-gnu/11/:/usr/lib/gcc/x86_64-linux-gnu/11/../../../x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/11/../../../../lib/:/lib/x86_64-linux-gnu/:/lib/../lib/:/usr/lib/x86_64-linux-gnu/:/usr/lib/../lib/:/opt/hellolib/:/usr/lib/gcc/x86_64-linux-gnu/11/../../../:/lib/:/usr/lib/

我们可以发现编译器列出了系统环境变量LIBRARY_PATH的内容,包含:

  • ①我们向环境变量添加的/opt/hellolib/,其所处位置应该是由编译器规定的
  • ②系统默认的库路径(/usr/lib/lib),位于最后
  • ③根据编译器配置自动添加的路径,如/usr/lib/gcc/x86_64-linux-gnu/11/

然后再往下看,COLLECT_GCC_OPTIONS列出了传递给g++的一些选项:

图片中的文字内容如下(省略了一部分不需要关注的):

COLLECT_GCC_OPTIONS=...-L/opt/hellolib_L-L/usr/lib/gcc/x86_64-linux-gnu/11 -L/usr/lib/gcc/x86_64-linux-gnu/11/../../../x86_64-linux-gnu -L/usr/lib/gcc/x86_64-linux-gnu/11/../../../../lib -L/lib/x86_64-linux-gnu -L/lib/../lib -L/usr/lib/x86_64-linux-gnu -L/usr/lib/../lib -L/opt/hellolib -L/usr/lib/gcc/x86_64-linux-gnu/11/../../.. ...

可以发现:

  • ①我们通过-L显式添加的路径/opt/hellolib_L被排在了最前面
  • LIBRARY_PATH中的路径(除了/usr/lib//lib/,原因暂时未知),都被加上-L并传给了COLLECT_GCC_OPTIONS,并排在/opt/hellolib_L之后。

链接器详细信息

然后我们再看链接器输出的详细信息:

图片中的文字内容如下:

SEARCH_DIR("=/usr/local/lib/x86_64-linux-gnu"); 
SEARCH_DIR("=/lib/x86_64-linux-gnu"); 
SEARCH_DIR("=/usr/lib/x86_64-linux-gnu"); 
SEARCH_DIR("=/usr/lib/x86_64-linux-gnu64"); 
SEARCH_DIR("=/usr/local/lib64"); 
SEARCH_DIR("=/lib64"); 
SEARCH_DIR("=/usr/lib64"); 
SEARCH_DIR("=/usr/local/lib"); 
SEARCH_DIR("=/lib"); 
SEARCH_DIR("=/usr/lib"); 
SEARCH_DIR("=/usr/x86_64-linux-gnu/lib64"); 
SEARCH_DIR("=/usr/x86_64-linux-gnu/lib");

SEARCH_DIR指令是用来指定链接器在搜索动态和静态库文件时应当考虑的目录,这些路径通常包括系统的标准库目录,如/usr/lib/lib等。但是注意,通过-L指定的路径会在运行时临时添加到SEARCH_DIR列表的前面,即-L指定的路径搜索优先级更高。

DEBUG总结

至此,我们可以简单总结一下上述信息:

  • 我们设置的LIBRARY_PATH的值会传给编译器
  • 编译器根据自己的配置以及我们手动赋予的LIBRARY_PATH变量的值,生成一个新的LIBRARY_PATH(我们手动赋予的LIBRARY_PATH变量的值处于一个特定的位置),并将这个新的LIBRARY_PATH的值(除了/usr/lib/lib)加上-L传递给编译器
  • 我们显式使用-L指定的路径也被传递给编译器,并位于所有-L选项的最前面

而且对于编译器配置的路径,如/usr/lib/gcc/x86_64-linux-gnu/11/../../../../lib/,其本质就是/usr/lib/(这也是默认路径优先级大于LIBRARY_PATH指定路径优先级的原因)。

因此对于-L指定路径LIBRARY_PATH指定路径默认路径,最终都被转化为-L的形式传递给编译器,且他们排列优先级为:

-L指定路径>默认路径>LIBRARY_PATH指定路径

因此他们的搜索优先级也是符合上述排列。

验证

最后我们可以通过链接器在链接特定库(比如我们的libhello)时的搜索过程验证上述结论:

可见链接器先是搜索我们使用-L指定的路径/opt/hellolib_L,然后搜索编译器配置的路径/usr/lib/gcc/x86_64-linux-gnu/11/../../../../lib/(其本质就是默认路径/usr/lib/),最后搜索LIBRARY_PATH指定的路径/opt/hellolib。证明了编译过程中链接时库搜索路径的优先级为

-L指定路径>默认路径>LIBRARY_PATH指定路径

默认路径>LIBRARY_PATH原因

如上文所述,g++根据自己的配置将例如:

/usr/lib/gcc/x86_64-linux-gnu/11/../../../../lib/

的路径添加到了LIBRARY_PATH中,而且位于用户设置的LIBRARY_PATH之前。这个路径的本质就是/usr/lib/。这就导致最终出现默认路径搜索优先级大于LIBRARY_PATH指定路径的搜索优先级的现象。

至于手动使用ld去链接.o.so文件,后面有机会再做测试。

附录

库文件源码

//file: libhello.cpp
#include <iostream>
void hello()
{std::cout << "Hello from the 1.1.0 library!" << std::endl;
}
//file: libhello_alt.cpp
#include <iostream>
void hello()
{std::cout << "Hello from the 2.1.0 library!" << std::endl;
}

主程序源码

//file: main.cpp
extern void hello();
int main()
{hello();return 0;
}

makefile

//file: makefile
CXX = g++
CXXFLAGS = -fPIC
LDFLAGS = -shared
DEBUG_MODE ?= 0ifeq ($(DEBUG_MODE),1)DEBUG_OPTS = -v -Wl,--verbose
endifall: lib/libhello.so.1.1.0 lib/libhello.so.2.1.0 obj/main.olib/libhello.so.1.1.0: libhello.cpp$(CXX) $(CXXFLAGS) -o $@ $^ $(LDFLAGS) -Wl,-soname,libhello.so.1lib/libhello.so.2.1.0: libhello_alt.cpp$(CXX) $(CXXFLAGS) -o $@ $^ $(LDFLAGS) -Wl,-soname,libhello.so.2obj/main.o: main.cpp$(CXX) -c -o $@ $^# 在任何路径下都无法搜索到libhello.so
main_none: obj/main.o$(CXX) $(DEBUG_OPTS) -o $@ $^ -lhello# 测试默认路径/usr/lib 
main_default: obj/main.ocp ./lib/libhello.so.1.1.0 /usr/libln -sf /usr/lib/libhello.so.1.1.0 /usr/lib/libhello.so$(CXX) $(DEBUG_OPTS) -o $@ $^ -lhello# 测试仅使用LIBRARY_PATH
main_library_path: obj/main.omkdir -p /opt/hellolibcp ./lib/libhello.so.1.1.0 /opt/hellolibln -sf /opt/hellolib/libhello.so.1.1.0 /opt/hellolib/libhello.soLIBRARY_PATH=/opt/hellolib $(CXX) $(DEBUG_OPTS) -o $@ $^ -lhello# 测试仅使用-L
main_l: obj/main.omkdir -p /opt/hellolibcp ./lib/libhello.so.1.1.0 /opt/hellolibln -sf /opt/hellolib/libhello.so.1.1.0 /opt/hellolib/libhello.so$(CXX) $(DEBUG_OPTS) -o $@ $^ -L/opt/hellolib -lhello# 比较默认路径和LIBRARY_PATH的搜索优先级
cmp_default_libpath: obj/main.ocp ./lib/libhello.so.2.1.0 /usr/libln -sf /usr/lib/libhello.so.2.1.0 /usr/lib/libhello.somkdir -p /opt/hellolibcp ./lib/libhello.so.1.1.0 /opt/hellolibln -sf /opt/hellolib/libhello.so.1.1.0 /opt/hellolib/libhello.soLIBRARY_PATH=/opt/hellolib $(CXX) $(DEBUG_OPTS) -o $@ $^ -lhello# 比较-L和默认路径的优先级
cmp_l_default: obj/main.omkdir -p /opt/hellolibcp ./lib/libhello.so.2.1.0 /opt/hellolibln -sf /opt/hellolib/libhello.so.2.1.0 /opt/hellolib/libhello.socp ./lib/libhello.so.1.1.0 /usr/libln -sf /usr/lib/libhello.so.1.1.0 /usr/lib/libhello.so$(CXX) $(DEBUG_OPTS) -o $@ $^ -L/opt/hellolib -lhello# 总体比较测试,集合了显示-L,LIBRARY_PATH和默认路径
cmp_all: main.cppmkdir -p /opt/hellolibmkdir -p /opt/hellolib_Lcp ./lib/libhello.so.1.1.0 /opt/hellolibln -sf /opt/hellolib/libhello.so.1.1.0 /opt/hellolib/libhello.soLIBRARY_PATH=/opt/hellolib $(CXX) $(DEBUG_OPTS) -o $@ $^ -L/opt/hellolib_L -lhelloclean:rm -f ./lib/* ./obj/* main_* cmp_*rm -f /usr/lib/libhello.so*rm -rf /opt/hellolib*ldconfig.PHONY: clean main_none main_default main_library_path  main_l cmp_default_libpath cmp_l_default cmp_all

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/773709.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

共享库编译期链接优先级实验

做实验验证共享库在编译期链接时库路径搜索的优先级目录前言实验环境目录说明准备工作单独测试不配置路径默认路径LIBRARY_PATH-L优先级测试默认路径和LIBRARY_PATH-L和默认路径DEBUG模式编译器配置详细信息链接器详细信息DEBUG总结验证默认路径>LIBRARY_PATH原因附录库文件…

ComfyUI插件:ComfyUI Impact 节点(三)

前言: 学习ComfyUI是一场持久战,而 ComfyUI Impact 是一个庞大的模块节点库,内置许多非常实用且强大的功能节点 ,例如检测器、细节强化器、预览桥、通配符、Hook、图片发送器、图片接收器等等。通过这些节点的组合运用,我们可以实现的工作有很多,例如自动人脸检测和优化修…

CSP11

CSP11 T1暴力 #include <bits/stdc++.h> #define speed() ios::sync_with_stdio(false),cin.tie(0),cout.tie(0); #define ll long long #define ull unsigned long long #define lid (rt<<1) #define rid (rt<<1|1) // #define endl \n //#define int long …

Ansible管理密码库文件

ansible可能需要访问密码或API密钥等敏感数据,以便能配置受管主机。通常,此信息可能以纯文本形式存储在清单变量或其他Ansible文件中。但若如此,任何有权访问Ansible文件的用户或存储,这些Ansible文件的版本控制系统都能够访问此敏感数据。这存在安全风险。使用Ansible随附…

Ansible忽略任务失败

在默认情况下,任务失败时会中止剧本任务,不过可以通过忽略失败的任务来覆盖此类行为。在可能出错且不影响全局的段中使用ignore_errors关键词来达到目的。 环境: 受控主机清单文件: [dev] 192.168.10.129 [all:vars] ansible_ssh_user=root ansible_ssh_pass=123 编写yum文…

React 的 KeepAlive 实战指南:深度解析组件缓存机制

Vue 的 Keep-Alive 组件是用于缓存组件的高阶组件,可以有效地提高应用性能。它能够使组件在切换时仍能保留原有的状态信息,并且有专门的生命周期方便去做额外的处理。该组件在很多场景非常有用,比如:tabs 缓存页面分步表单路由缓存 在 Vue 中,通过 KeepAlive 包裹内…

LangChain补充七:Hub和LangSmith入门

一:Hub简介 https://blog.csdn.net/DEVELOPERAA/article/details/139983286 (一)简介 1.早期 最开始的LangChainHub,类似于github一样,可以理解为LangChain 工具包 或者说 组件中心,里面提供了高质量的组件方便开发者使用。确确实实是一个分享和探索Prompt、链 和Agent的…

借助大语言模型快速升级你的 Java 应用程序

大家都知道我爱小 Q。在我“转码”的征程中,它就像上帝之手,在我本该枯燥漫长的学习进程中拉满快进条。 不仅是我,最近 Amazon Q Developer 还帮助 Amazon 一个由 5 人组成的团队在短短两天内将 1,000 多个生产应用程序从 Java 8 升级到 Java 17(每个应用程序的平均时间不到…

8080端口被占用

微服务项目,启动时发生8080端口占用,但是其他启动了并没有使用到8080端口。 cmd命令窗口查看: netstat -aon | findstr "8080"右键添加需要了解的列表查看8080端口占用的pid比如占用端口的服务是VMware NAT Servic。 在服务管理器(services.msc)中停止VMware NA…

[米联客-安路飞龙DR1-FPSOC] FPGA基础篇连载-20 读写I2C接口的RTC时钟芯片

软件版本:Anlogic -TD5.9.1-DR1_ES1.1 操作系统:WIN10 64bit 硬件平台:适用安路(Anlogic)FPGA 实验平台:米联客-MLK-L1-CZ06-DR1M90G开发板 板卡获取平台:https://milianke.tmall.com/ 登录"米联客"FPGA社区 http://www.uisrc.com 视频课程、答疑解惑! 1概述 …

【YashanDB知识库】如何远程连接、使用YashanDB?

问题现象 在各个项目实施中,我们经常遇到客户、开发人员需要连接和使用YashanDB但不知如何操作的问题,本文旨在介绍远程连接、使用YashanDB的几种方式。 问题的风险及影响 无风险 问题影响的版本 历史版本~23.2 问题发生原因 无 解决方法及规避方式 不需要规避 问题分析和处理…

GIS场景零代码拖拽式编辑,支持TMS/WMS/WMTS等多种GIS协议

在三维GIS领域,编辑场景和处理影像数据通常是一个复杂且费时的过程,但现在有了山海鲸可视化,这一切都变得简单有趣。这款免费可视化工具为您提供了零代码拖拽式编辑的体验,让您无需编程知识就能轻松创建和优化GIS场景。通过直观的界面,您只需动动鼠标就能完成从场景编辑到…