CMakelist与gcc编译参数-fPIC的一些问题
Reference:
在编译动态库时,出现问题:
relocation R_X86_64_PC32 against symbol `_ZTVN5boost6system14error_categoryE’ can not be used when making a shared object; recompile with -fPIC
笔者验证以下两种方式有效:
- 在 CMakeLists.txt 文件中添加 add_compile_options(-fPIC);
- set(CMAKE_C_FLAGS“$ {CMAKE_C_FLAGS} -fPIC”)
set(CMAKE_CXX_FLAGS“$ {CMAKE_CXX_FLAGS} -fPIC”)
1. 重定位
编译器和汇编器生成的是起始于位置0处的代码区段和数据区段的二进制文件。通过把每一个符号的定义和内存位置关联起来,并且修改所有对这些符号的引用使之指向正确的内存位置,链接器会重定位(relocation)这些区段。不加 -fPIC
时,加载动态库会被加载到一个地址(base),这时要进行一次重定位,把代码、数据段所有的地址加上这个 base 的值,这样代码运行时就能使用正确的地址了。
加上 fPIC 选项生成的动态库,显然是位置无关的,这样的代码本身就能被放到线性地址空间的任意位置,无需修改就能正确执行。通常的方法是获取指令指针的值,加上一个偏移得到全局变量 / 函数的地址。
2. 原理
-fPIC 作用于编译阶段,告诉编译器产生与位置无关代码(Position-Independent Code),则产生的代码中,没有绝对地址,全部使用相对地址,故而代码可以被加载器加载到内存的任意位置,都可以正确的执行。这正是共享库所要求的,共享库被加载时,在内存的位置不是固定的。
gcc -shared -fPIC -o 1.so 1.c
这里有一个 -fPIC
参数,其中 PIC 就是上面描述的 position independent code。PIC 使 .so 文件的代码段变为真正意义上的共享。
如果不加 -fPIC
参数,则加载 .so 文件的代码段时,代码段引用的数据对象需要重定位,重定位会修改代码段的内容,这就造成每个使用这个 .so 文件代码段的进程在内核里都会生成这个 .so 文件代码段的 copy。每个 copy 都不一样,取决于这个 .so 文件代码段和数据段内存映射的地址。也就是说,
不加 -fPIC
参数编译出来的 so 文件,是要再加载时根据加载到的位置再次重定位的,因为它里面的代码并不是位置无关代码。如果被多个应用程序共同使用,那么它们必须每个程序维护一份 .so 的代码副本了,这是因为 .so 被每个程序加载的地址都不同,显然这些重定位后的代码也不同,当然不能共享。
我们总是用 -fPIC
来生成 .so,也从来不用 -fPIC
来生成 .a。-fPIC
与动态链接可以说基本没有关系,libc.so 一样可以不用 -fPIC
编译,只是这样的 so 必须要在加载到用户程序的地址空间时重定向所有表目。
因此,不用fPIC编译so并不总是不好,如果你满足以下4个需求/条件:
1.该库可能需要经常更新
2.该库需要非常高的效率(尤其是有很多全局量的使用时)
3.该库并不很大.
4.该库基本不需要被多个应用程序共享
如果用没有加这个参数的编译后的共享库,也可以使用的话,可能是两个原因:
1:gcc默认开启-fPIC选项
2:loader使你的代码位置无关
从GCC来看,shared应该是包含fPIC选项的,但似乎不是所以系统都支持,所以最好显式加上fPIC选项。参见如下:
`-shared'
Produce a shared object which can then be linked with other
objects to form an executable. Not all systems support this
option. For predictable results, you must also specify the same
set of options that were used to generate code (`-fpic', `-fPIC',
or model suboptions) when you specify this option.(1)
3. 在内存引用上,加不加 fPIC 的异同
加了 -fPIC
实现真正意义上的多个进程共享 .so 文件。
多个进程引用同一个 -fPIC
动态库时,可以共用内存。这一个库在不同进程中的虚拟地址不同,但操作系统显然会把它们映射到同一块物理内存上。
对于不加 -fPIC
,则加载 .so 文件时,需要对代码段引用的数据对象需要重定位,重定位会修改代码段的内容,这就造成每个使用这个 .so 文件代码段的进程在内核里都会生成这个 .so 文件代码段的 copy。每个 copy 都不一样,取决于这个 .so 文件代码段和数据段内存映射的位置。
可见,这种方式更消耗内存。
但是不加 -fPIC
编译的 .so 文件的优点是加载速度比较快。