问题:
最近在调试代码的时候遇到一个问题:调用静态库中的函数修改全局变量出现异常(data abort)。
分析:
把代码反汇编,发现是在操作全局变量时使用的相对地址,手动计算发现地址偏移不正确,导致访问的地址是错误的地址,然后出现异常。
解决:
别人指导说修改gcc的PIC参数。查看makefile,发现在编译静态库的时候有-fPIC这个参数。去掉该参数后编译,查看反汇编代码,在操作全局变量时使用的是绝对地址。
在GCC的在线文档中有描述:
-fpic
Generate position-independent code (PIC) suitable for use in a shared library, if supported for the target machine. Such code accesses all constant addresses through a global offset table (GOT). The dynamic loader resolves the GOT entries when the program starts (the dynamic loader is not part of GCC; it is part of the operating system). If the GOT size for the linked executable exceeds a machine-specific maximum size, you get an error message from the linker indicating that -fpic does not work; in that case, recompile with -fPIC instead. (These maximums are 8k on the SPARC, 28k on AArch64 and 32k on the m68k and RS/6000. The x86 has no such limit.)
Position-independent code requires special support, and therefore works only on certain machines. For the x86, GCC supports PIC for System V but not for the Sun 386i. Code generated for the IBM RS/6000 is always position-independent.
When this flag is set, the macros pic and PIC are defined to 1.
-fpic
如果目标计算机支持,则生成适合在共享库中使用的位置无关代码(PIC)。这样的代码通过全局偏移表(GOT)访问所有常量地址。动态加载程序在程序启动时解析GOT条目(动态加载程序不是GCC的一部分;它是操作系统的一部分)。如果链接的可执行文件的GOT大小超过了计算机特定的最大大小,则会从链接器中收到一条错误消息,指示-fpic无法正常工作;在这种情况下,请改用-fPIC重新编译。 (在SPARC上,最大值为8k;在AArch64上,最大值为28k;在m68k和RS / 6000上,最大值为32k。x86没有此限制。)
与位置无关的代码需要特殊的支持,因此仅在某些机器上有效。对于x86,GCC支持PIC for System V,但不支持Sun 386i。为IBM RS / 6000生成的代码始终与位置无关。
设置此标志后,宏__pic__和__PIC__被定义为1。
-fPIC
If supported for the target machine, emit position-independent code, suitable for dynamic linking and avoiding any limit on the size of the global offset table. This option makes a difference on AArch64, m68k, PowerPC and SPARC.
Position-independent code requires special support, and therefore works only on certain machines.
When this flag is set, the macros pic and PIC are defined to 2.
-fPIC
如果目标机器支持,则发出与位置无关的代码,该代码适用于动态链接并避免对全局偏移表的大小进行任何限制。此选项对AArch64,m68k,PowerPC和SPARC有所不同。
与位置无关的代码需要特殊的支持,因此仅在某些机器上有效。
设置此标志后,宏__pic__和__PIC__被定义为2。
fPIC的全称是 Position Independent Code
, 用于生成位置无关代码。
即使不加fPIC也可以生成.so文件,但是对于源文件有要求,例如
因为不加fPIC编译的so必须要在加载到用户程序的地址空间时重定向所有表目,所以在它里面不能引用其它地方的代码
如下:
#include <stdio.h>
int func1(int a)
{
printf("haha a=%d\n", 2);
a++;
return a;
}
使用 gcc -shared -o libb3.so c.c 编译将报错
/usr/bin/ld: /tmp/ccCViivC.o: relocation R_X86_64_32 against `.rodata' can not be used when making a shared object; recompile with -fPIC
/tmp/ccCViivC.o: could not read symbols: Bad value
将上述代码中的printf
函数注释,则可以编译通过。
对于不加 -fPIC生成的动态库,“ 生成动态库时假定它被加载在地址 0 处。加载时它会被加载到一个地址(base),这时要进行一次重定位(relocation),把代码、数据段中所有的地址加上这个 base 的值。这样代码运行时就能使用正确的地址了。”
加上fPIC选项生成的动态库,显然是位置无关的。
“这样的代码本身就能被放到线性地址空间的任意位置,无需修改就能正确执行。通常的方法是获取指令指针的值,加上一个偏移得到全局变量/函数的地址。”
加fPIC选项的 源文件对于,它引用的函数头文件编写有很宽松的尺度。
比如只需要包含个声明的函数的头文件,即使没有相应的C文件来实现,编译成so库照样可以通过。
加-fPIC:
加了fPIC实现真正意义上的多个进程共享so文件
。
多个进程引用同一个 PIC 动态库时,可以共用内存。这一个库在不同进程中的虚拟地址不同,但操作系统显然会把它们映射到同一块物理内存上。
不加-fPIC:
不加fPIC,则加载so文件时,需要对代码段引用的数据对象需要重定位
,重定位会修改代码段的内容,这就造成每个使用这个.so文件代码段的进程在内核里都会生成这个.so文件代码段的copy.每个copy都不一样,取决于这个.so文件代码段和数据段内存映射的位置。
可见,这种方式更消耗内存。
但是不加fPIC编译的 so文件的优点是加载速度比较快。
参考链接:
GCC 在线文档
gcc编译参数-fPIC的一些问题
-fpic 与-fPIC的区别
linux编译动态库 fPIC作用