-fPIC 作用于编译阶段,告诉编译器产生与位置无关代码(Position-Independent Code
),
则产生的代码中,没有绝对地址,全部使用相对地址,故而代码可以被加载器加载到内存的任意
位置,都可以正确的执行。这正是共享库所要求的,共享库被加载时,在内存的位置不是固定的。
gcc -shared -fPIC -o 1.so 1.c
这里有一个-fPIC
参数:
PIC
就是position independent code
PIC
使.so
文件的代码段变为真正意义上的共享。
如果不加-fPIC
,则加载.so
文件的代码段时,代码段引用的数据对象需要重定位, 重定位会修改代码段的内容,这就造成每个使用这个.so
文件代码段的进程在内核里都会生成这个.s
o文件代码段的copy
,每个copy
都不一样,取决于 这个.so
文件代码段和数据段内存映射的位置.
不加-fPIC
编译出来的so
文件,是要再加载时根据加载到的位置再次重定位的.(因为它里面的代码并不是位置无关代码)
如果被多个应用程序共同使用,那么它们必须每个程序维护一份so
的代码副本了.(因为so
被每个程序加载的位置都不同,显然这些重定位后的代码也不同,当然不能共享)
我们总是用-fPIC
来生成so
,也从来不用fPIC
来生成a
fPIC
与动态链接可以说基本没有关系,libc.so
一样可以不用fPIC
编译,只是这样的so
必须要在加载到用户程序的地址空间时重定向所有表目.
因此,不用fPIC
编译so并不总是不好.
如果你满足以下4个需求/条件:
- 该库可能需要经常更新
- 该库需要非常高的效率(尤其是有很多全局量的使用时)
- 该库并不很大.
- 该库基本不需要被多个应用程序共享
如果用没有加这个参数的编译后的共享库,也可以使用的话,可能是两个原因:
gcc
默认开启-fPIC
选项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)
-fPIC
的使用,会生成PIC
代码,.so
要求为 PIC
,以达到动态链接的目的,否则,无法实现动态链接。
non-PIC
与 PIC
代码的区别主要在于 access global data
, jump label
的不同。
-
比如一条
access global data
的指令,
non-PIC
的形式是:ld r3, var1
PIC
的形式则是:ld r3, var1-offset@GOT
,意思是从GOT
表的index
为var1-offset
的地方处,指示的地址处装载一个值,即var1-offset@GOT
处的4
个byte
。 其实就是var1
的地址。这个地址只有在运行的时候才知道,是由dynamic-loader(ld-linux.so)
填进去的。 -
再比如
jump label
指令
non-PIC
的形式是:jump printf
,意思是调用printf
。
PIC
的形式则是:jump printf-offset@GOT
,意思是跳到GO
T 表的index
为printf-offset
的地方处指示的地址去执行,这个地址处的代码摆放在.plt section
,每个外部函数对应一段这样的代码,其功能是呼叫dynamic-loader(ld-linux.so)
来查找函数的地址(本例中是printf
),然后将其地址写到GOT
表的index
为printf-offset
的地方,同时执行这个函数。这样,第2次呼叫printf
的时候,就会直接跳到printf
的地址,而不必再查找了。
GOT
是data section
, 是一个 table
, 除专用的几个 entry
,每个entry
的内容可以再执行的时候修改;
PLT
是 text section
, 是一段一段的 code
,执行中不需要修改。
每个target
实现PIC
的机制不同,但大同小异。比如MIPS
没有 .plt
, 而是叫 .stub
,功能和 .plt
一样。
可见,动态链接执行很复杂,比静态链接执行时间长;但是,极大的节省了 size
,PIC 和动态链接技术是计算机发展史上非常重要的一个里程碑。
gcc manul
上面有说
-fpic 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 and 32k on the m68k and RS/6000. The 386 has no such limit.)
-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 the m68k, PowerPC and SPARC. Position-independent code requires special support, and therefore works only on certain machines.
关键在于GOT
全局偏移量表里面的跳转项大小。
intel
处理器应该是统一4
字节,没有问题。
powerpc
上由于汇编码或者机器码的特殊要求,所以跳转项分为短、长两种。
-fpic
为了节约内存,在GOT
里面预留了“短”长度。
而-fPIC
则采用了更大的跳转项。
创建共享库和链接可执行文件类似:首先把源代码编译成目标文件, 然后把目标文件链接起来。目标文件需要创建成位置无关码(position-independent code
) (PIC
),概念上就是在可执行程序装载它们的时候, 它们可以放在可执行程序的内存里的任何地方, (用于可执行文件的目标文件通常不是用这个方式编译的。) 链接动态库的命令包含特殊标志,与链接可执行文件的命令是有区别的。
gcc
中的-fpic
选项,使用于在目标机支持时,编译共享库时使用。编译出的代码将通过全局偏移表(Global Offset Table
)中的常数地址访存,动态装载器将在程序开始执行时解析GOT
表项(注意,动态装载器操作系统的一部分,连接器是GCC
的一部分).
gcc
中的-fPIC
选项则是针对某些特殊机型做了特殊处理,比如适合动态链接并能避免超出GOT
大小限制之类的错误。而Open64
仅仅支持不会导致GOT
表溢出的PIC
编译。
GOT
大小
8k
----SPARC
32k
---- m68k and RS/6000.
386 无限制.
【总结】:
-fPIC
总是能够工作,但可能产生的代码较大。-fpic
通常能产生更快更小的代码,但有平台限制。