当前位置:网站首页>动态库基本原理和使用方法,-fPIC 选项的来龙去脉

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

2022-07-07 22:10:00 wowocpp

动态库基本原理和使用方法,-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
原网站

版权声明
本文为[wowocpp]所创,转载请带上原文链接,感谢
https://blog.csdn.net/wowocpp/article/details/125668121