gcc 编译选项 -fPIC

2018年1月8日17:57:59 发表评论 15

gcc 编译选项 -fPIC

我们先来看一个测试用例:

接下来进行编译链接:

我们发现编译时没有问题,链接时出现了错误!
错误信息说在创建共享对象时,不能使用符号 'func' 重定位 R_X86_64_PC32 ; 请用 -fPIC 重新编译。

我们再试试编译 test.c 时加上 -fPIC 选项:

结果链接成功!

可以继续验证,如编译 func.c 时也加 -fPIC 选项,也可以链接成功,但编译 test.c 时必须加 -fPIC 选项。

为什么编译 test.c 时必须加 -fPIC 选项,编译 func.c 时可加可不加呢?

我们可以看到,func.c 它是 func() 函数的声明和定义,没有调用其他外部函数;而 test.c 只声明了 func() 函数,然后就直接调用这个函数,这个函数在哪里定义的它并不知道,所以在链接时就需要去从其他的 .o 文件里找这个函数定义的位置,也就是 'func' 符号对应的位置。

那么问题来了,编译时加上 -fPIC 和不加有什么不同呢?

解答:-fPIC 作用于编译阶段,告诉编译器产生与位置无关代码(Position-Independent Code)
产生的代码中,没有绝对地址,全部使用相对地址,故而代码可以被加载器加载到内存的任意位置,都可以正确的执行。这正是共享库所要求的,共享库被加载时,在内存的位置不是固定的。

加了fPIC实现真正意义上的多个进程共享so文件。多个进程引用同一个PIC动态库时,可以共用内存。这一个库在不同进程中的虚拟地址不同,但操作系统显然会把它们映射到同一块物理内存上。

不加fPIC,则加载 .so 文件时,需要对代码段引用的数据对象需要重定位,重定位会修改代码段的内容,这就造成每个使用这个 .so 文件代码段的进程在内核里都会生成这个 .so 文件代码段的 copy,每个 copy 都不一样,取决于这个 .so 文件代码段和数据段内存映射的位置。

但是不加 fPIC 编译的 .so 文件的优点是加载速度比较快。
如果你满足以下 1 个条件和 4 个需求,那就不加 fPIC 选项:

  1. 不加 fPIC 选项还可以编译成 .so(比如 func.c 不加 -fPIC 也可以编译成独立的 .so)
  2. 该库可能需要经常更新
  3. 该库需要非常高的效率(尤其是有很多全局量的使用时)
  4. 该库并不很大.
  5. 该库基本不需要被多个应用程序共享

加 fPIC 选项的源文件只需要包含个声明的函数的头文件,即使没有相应的 C 文件来实现,编译成so库照样可以通过。

我们可以测试,甚至可以把 test.c 单独编译成 .so ,但是如果不加 -fPIC 选项是不可以的。

而 func.c 不加 -fPIC 也可以编译成独立的 .so

fPIC的目的是什么?

解答:共享对象可能会被不同的进程加载到不同的位置上,如果共享对象中的指令使用了绝对地址、外部模块地址,那么在共享对象被加载时就必须根据相关模块的加载位置对这个地址做调整,也就是修改这些地址,让它在对应进程中能正确访问,而被修改到的段就不能实现多进程共享一份物理内存,它们在每个进程中都必须有一份物理内存的拷贝。fPIC指令就是为了让使用到同一个共享对象的多个进程能尽可能多的共享物理内存,它背后把那些涉及到绝对地址、外部模块地址访问的地方都抽离出来,保证代码段的内容可以多进程相同,实现共享。
抽离出这部分特殊的指令、地址之后,放到了一个叫做GOT(Global Offset Table)的地方,它放在数据段中,每一个进程都有独立的一份,里面的内容可能是变量的地址、函数的地址,不同进程它的内容很可能是不同的,这部分就是被隔离开的“地址相关”内容。模块被加载的时候,会把GOT表的内容填充好(在没有延迟绑定的情况下)。

装载时重定位就是不使用fPIC参数,代码段需要一个重定位表,在装载时修正所有特殊地址,以后运行时不需要再有GOT位置计算和间接访问。
如果在装载时就去计算GOT的内容,那么会影响加载速度,于是就有了延迟绑定(Lazy Binding),直到用时才去填充GOT。它使用到了PLT(Procedure Linkage Table):每一项都是一小段代码,对应于本运行模块要引用的函数。函数调用时,先到这里,然后再到GOT。在函数第一次被调用时,进入PLT跳到加载器,加载器计算函数的真正地址,然后将地址写入GOT对应项,以后的调用就直接从PLT跳到GOT记录的函数位置。这样也减少了运行时多次调用多次计算GOT位置。

如何查询一个 .so 是 PIC 共享对象呢?

TEXTREL表示代码段重定位表地址,PIC的共享对象不会包含任何代码段重定位表,所以这条命令不会有任何输出结果。

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: