动态库基本原理和使用方法,-fPIC 选项的来龙去脉

本文详细介绍了动态库的三个演进阶段:静态共享库、装载时重定位和地址无关代码(PIC)。 PIC使得代码能在多个进程中共享,减少内存消耗。-fPIC选项用于生成地址无关的代码,允许动态链接器在装载时完成重定位。通过GOT(全局偏移表)处理模块间的函数调用和数据访问。了解-fPIC的使用对于理解和优化程序的动态链接至关重要。

动态库基本原理和使用方法,-fPIC 选项的来龙去脉

使用 gcc 生成 so 文件时,一般要加一个 - fPIC 选项,这个选项是啥意思?本文介绍了 PIC 的来龙去脉。
1 链接视图看动态共享库和静态库
如果我们在主程序 main.c 中使用到一个函数 foobar (),对 main.c 进行编译,编译器还不知道 foobar () 函数的地址,在编译阶段生成的 main.o 中,foobar 一个未解析的引用。

链接器将 main.o 链接成可执行文件时,必须确定 main.o 中所引用的 foorbar () 函数的性质。如果 foobar () 是一个定义在其他静态目标模块(静态库或者目标文件)中的函数,那么链接器会按照静态链接的规则将 main.o 中的 foobar () 函数地址引用重定位。

gcc -o main.out mian.c ./libfoo.a

如果 foobar () 是定义在某个动态共享库中的函数,那么链接器就会把 foobar 标记为一个动态链接的符号,foobar 此时是一个对动态符号的引用。不对它进行重定位。

gcc -o main.out main.c ./libfoo.so

2 执行视图看动态库
动态库发展过程有三个阶段。

2.1 演进阶段 1:静态共享库
静态共享库不是静态库,是指共享库在编译时已经确定了自己在进程虚拟地址空间的位置。操作系统在某个特定的地址划分出一些地址块,为那些已知的模块预留足够的空间。在链接视图中,静态共享库在进程的虚拟地址空间的地址已经被决定了,如果后续要升级静态共享库,还是需要重新编译,链接可执行 ELF。

2.2 演进阶段 2:装载时重定位
装载重定位是指在链接时,对所有绝对地址的引用不作重定位,推迟到装载时再完成。程序是按照整体进行装载,程序中指令和数据相对位置不会改变,程序的基地址可能每次装载时不一样。一旦模块装载地址确定,系统就可以对程序中所有的绝对地址进行重定位。

Linux GCC 选项

gcc -shared -o libbook.so book.c

使用装载时重定位,动态链接模块被装载映射到虚拟地址空间后,指令会被修改,多个进程无法共享同一份指令代码。指令被重定位以后对每个应用程序来说是不同的。演进阶段 2 的装载时重定位,无法解决指令部分需要在多个进程之间共享的需求。也就失去了动态链接节省内存的优势。

2.3 演进阶段 3:地址无关代码(Position Independent Code, PIC)
要在多个进程之间共享指令部分,需要将指令进行分类。按照是否跨模块分两类:模块内引用和模块外部引用; 按照引用方式分两类:指令引用和数据访问。这样一共有四种组合:模块内部的函数调用、跳转,模块内部的数据访问(比如模块中定义的全局变量、静态变量),模块间的函数调用、跳转,模块间的数据访问(比如其他模块中定义的全局变量)。

类型一 模块内部函数调用、跳转

模块内部的跳转、函数调用都可以使用相对地址调用,不需要重定位。

类型二 模块内部数据访问

模块内部指令与模块内部数据之间的相对位置是固定的,只需要相对于当前指令加上固定的偏移量就可以访问模块内部数据。

类型三 模块间的数据访问

模块间的数据访问目的地址要等到装载时才能确定,ELF 文件的做法是在数据段里面建一个指向这些模块间全局变量的指针数组,也被称为全局偏移表(Global Offset Table, GOT),当代码需要引用模块间全局变量时,可以通过 GOT 中的表项间接的引用。由于 GOT 是存放在数据段中,所以动态库在装载时可以被修改,每个进程都有独立的副本,相互之间不受影响。在编译时可以确定 GOT 相对于当前指令的偏移,编译器决定 GOT 内的每一项(4 个字节为一项,一个指针)对应于哪一个全局变量名称,也就 GOT 给出了需要重定位的全局变量有哪些,以及该全局变量相对于 GOT 的位置。动态链接器在装载模块时会查找每个变量所在地址,然后填充 GOT 中的各个项,确保 GOT 中每个指针所指向的地址是正确的。

类型四 模块间的函数调用、跳转

与类型三类似,也是通过 GOT,在 GOT 中存放的是目标函数的地址,当模块需要调用目标函数时,可以通过 GOT 进行间接跳转。

使用 GCC 产生地址无关代码

gcc -fPIC -shared -o libbook.so book.c

注意:这里的 - fPIC 中的 PIC 是大写,也有小写的 - fpic(产生的代码相对较小,而且较快),在有些平台使用小写的 - fpic 选项有一些限制,而大写的 - fPIC 没有这个问题。绝大多数情况使用 - fPIC。

可以通过以下方法查看一个 so 是不是 PIC,如果是 PIC,以下命令会有输出,如果不是 PIC,则不会有任何输出。

readelf -d libbook.so | grep TEXTREL
<think> 我们正在讨论的是在静态库编译时添加 `-fPIC` 选项方法。根据引用,`-fPIC` 选项用于生成位置无关代码,这在将静态库链接到动态库时是必需的。以下将详细说明添加方法。 ### 1. **理解 `-fPIC` 的作用** - 当静态库需要被链接到动态库(共享库)时,静态库必须编译为位置无关代码(Position Independent Code,PIC)[^3]。 - 如果不添加 `-fPIC`,在生成动态库时可能遇到类似以下错误: ``` relocation R_X86_64_32 against `.rodata' can not be used when making a shared object; recompile with -fPIC ``` ### 2. **添加 `-fPIC` 的方法** - **编译器命令行直接添加**: 在编译静态库的每个源文件时,显式添加 `-fPIC` 选项。例如: ```bash gcc -c -fPIC source1.c -o source1.o gcc -c -fPIC source2.c -o source2.o ar rcs libstatic.a source1.o source2.o ``` 此方法适用于小型项目或手动编译场景。 - **构建系统配置(如 Makefile)**: 在 Makefile 中全局添加 `CFLAGS += -fPIC`。示例: ```makefile CFLAGS = -fPIC -Wall -O2 libstatic.a: source1.o source2.o ar rcs $@ $^ %.o: %.c gcc $(CFLAGS) -c $< -o $@ ``` 这样所有 `.o` 文件在编译时都会自动包含 `-fPIC` 选项- **CMake 配置**: 在 `CMakeLists.txt` 中,通过 `set(CMAKE_POSITION_INDEPENDENT_CODE ON)` 启用全局 PIC 支持: ```cmake cmake_minimum_required(VERSION 3.10) project(static_lib) set(CMAKE_POSITION_INDEPENDENT_CODE ON) # 为所有目标添加 -fPIC add_library(static_lib STATIC source1.cpp source2.cpp) ``` - **特定构建工具调整**: 如引用 [4] 提到的 Boost 库案例,可通过修改构建配置文件(如 `gcc.jam`)强制添加 `-fPIC`: ```python # 在 tools/build/v2/tools/gcc.jam 中修改 if ! $(target) in cygwin windows { OPTIONS on $(targets) += -fPIC ; # 非 Windows 平台添加 -fPIC } ``` ### 3. **注意事项** - **平台兼容性**: 在 Windows 平台(如 Cygwin、MSVC)上,`-fPIC` 是默认行为或无效,显式指定可能引发警告[^2][^4]。需根据平台条件添加: ```makefile ifeq ($(OS),Windows_NT) CFLAGS += # Windows 下无需 -fPIC else CFLAGS += -fPIC endif ``` - **性能影响**: PIC 代码会略微增加运行时开销(约 1-5%),因为需通过 GOT/PLT 间接寻址[^3]。若静态库仅用于可执行文件,无需添加 `-fPIC`。 ### 4. **验证是否生效** - 检查生成的 `.o` 文件是否包含 PIC 标志: ```bash readelf -r source1.o | grep R_X86_64_REX_GOTPCRELX ``` 若输出包含 `R_X86_64_PC32` 等重定位类型,表明 PIC 已启用。 ### 总结 添加 `-fPIC` 的方法包括: 1. 命令行编译时直接添加 `-fPIC` 选项。 2. 在 Makefile 中设置 `CFLAGS += -fPIC`。 3. 在 CMake 中配置 `set(CMAKE_POSITION_INDEPENDENT_CODE ON)`。 4. 修改构建工具配置文件(如 Boost 的 `gcc.jam`)。 需注意平台差异性能影响,并在链接到动态库的场景下使用[^3][^4]。 §§相关问题§§ 1. 如何检查一个静态库是否已编译为位置无关代码(PIC)? 2. 在 CMake 中如何为特定目标(而非全局)添加 `-fPIC` 选项? 3. Windows 平台下编译静态库时是否需要处理位置无关代码? 4. 使用 `-fPIC` 编译静态库对性能有何具体影响? </think>在静态库编译时添加 `-fPIC`(Position Independent Code)选项是确保静态库能被正确链接到动态库的关键步骤。以下是具体操作方法及原理: ### 一、添加 `-fPIC` 的三种方法 1. **编译源文件时直接添加** 在编译每个源文件为对象文件(`.o`)时显式添加选项: ```bash gcc -c -fPIC source1.c -o source1.o # 编译单个文件 gcc -c -fPIC source2.c -o source2.o ar rcs libmylib.a source1.o source2.o # 打包为静态库 ``` 2. **通过构建系统全局配置** - **Makefile 示例**: ```makefile CFLAGS += -fPIC # 全局生效 libmylib.a: source1.o source2.o ar rcs $@ $^ %.o: %.c gcc $(CFLAGS) -c $< -o $@ ``` - **CMake 配置**: 在 `CMakeLists.txt` 中添加: ```cmake set(CMAKE_POSITION_INDEPENDENT_CODE ON) # 为所有目标启用 -fPIC add_library(mylib STATIC source1.c source2.c) ``` 3. **修改构建工具配置(如 Boost)** 参考引用 [4],可编辑构建配置文件(如 `gcc.jam`)强制添加: ```python if ! $(target) in cygwin windows { OPTIONS on $(targets) += -fPIC ; # 非 Windows 平台自动添加 } ``` ### 二、技术原理 添加 `-fPIC` 后编译器会生成位置无关代码[^3]: 1. **寻址方式** 使用相对地址而非绝对地址,例如访问全局变量时通过全局偏移表(GOT)间接寻址。 2. **关键数据结构** - **全局偏移表(GOT)**:存储全局变量函数的实际地址。 - **过程链接表(PLT)**:处理函数调用,首次调用时解析真实地址并存入 GOT。 3. **重定位支持** 生成重定位表,允许动态链接器在加载时调整内存地址。 ### 三、注意事项 1. **平台差异** - Linux/macOS 必须添加 `-fPIC` 才能将静态库链接到动态库[^3] - Windows 默认支持位置无关代码,无需显式添加(甚至可能报警告)[^2][^4] 2. **性能影响** 通过 GOT/PLT 的间接寻址会引入约 1-5% 的性能开销,但对动态库场景是必需的[^3]。 3. **错误处理** 若未添加 `-fPIC` 时尝试链接到动态库,会触发典型错误: ``` relocation R_X86_64_32 against `.rodata' can not be used... recompile with -fPIC ``` ### 四、验证方法 检查生成的静态库是否包含 PIC 代码: ```bash # 查看对象文件的重定位类型 readelf -r libmylib.a | grep R_X86_64_PC32 # 若有 R_X86_64_PC32 输出表示 PIC 生效 ``` > **关键总结**:在 Linux/macOS 平台构建需链接到动态库的静态库时,必须通过编译选项、构建系统配置或工具链修改添加 `-fPIC`[^3][^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值