共享模块的全局变量

共享模块的全局变量

Content #

当一个模块引用了一个定义在共享对象的全局变量。一个共享对象定义了一个全局变量global,而模块module.c中是这么引用的:

extern int global;
int foo()
{
   global = 1;
}

当编译器编译module.c时,它无法根据这个上下文判断global是定义在同一个模块的的其他目标文件还是定义在另外一个共享对象之中,即无法判断是否为跨模块间的调用。

假设module.c是程序可执行文件的一部分,那么由于程序主模块的代码并不是地址无关代码,编译器会产生这样的代码:

movl   $0x1,XXXXXXXX

XXXXXXXX就是global的地址。由于可执行文件在运行时并不进行代码重定位,所以变量的地址必须在链接过程中确定下来。为了能够使得链接过程正常进行,链接器会在创建可执行文件时,在它的“.bss”段创建一个global变量的副本。那么问题就很明显了,现在global变量定义在原先的共享对象中,而在可执行文件的“.bss”段还有一个副本。如果同一个变量同时存在于多个位置中,这在程序实际运行过程中肯定是不可行的。

于是解决的办法只有一个,那就是所有的使用这个变量的指令都指向位于可执行文件中的那个副本。

ELF共享库在编译时,默认都把定义在模块内部的全局变量当作定义在其他模块的全局变量,也就是说当作类型四,通过GOT来实现变量的访问。当共享模块被装载时,如果某个全局变量在可执行文件中拥有副本,那么动态链接器就会把 GOT中的相应地址指向该副本,这样该变量在运行时实际上最终就只有一个实例。如果变量在共享模块中被初始化,那么动态链接器还需要将该初始化值复制到程序主模块中的变量副本;如果该全局变量在程序主模块中没有副本,那么GOT中的相应地址就指向模块内部的该变量副本。

假设module.c是一个共享对象的一部分,那么GCC编译器在-fPIC的情况下,就会把对global的调用按照跨模块模式产生代码。原因也很简单:编译器无法确定对 global的引用是跨模块的还是模块内部的。即使是模块内部的,即模块内部的全局变量的引用,按照上面的结论,还是会产生跨模块代码,因为global可能被可执行文件引用,从而使得共享模块中对global的引用要执行可执行文件中的 global副本。

From #

程序员的自我修养