《程序员的自我修养》读书笔记一

P199: “默认情况下,如果可执行文件是动态连接的,那么 GCC 会使用 PIC 的方法来产生可执行文件的代码段部分,以便于不同的进程能够共享” 但我用 GCC 去编译 32 位可执行文件,加 -fPIC 和不加 -fPIC,编出的指令是有差别的:

-fPIC 对生成指令的影响

为了访问文件内定义的全局变量,前者生成的代码是走了 GOT 的(实际结果是,形式上是走了,但貌似链接器发现最后生成的是非 PIE (位置无关可执行文件,需要加 -pie 才能生成) ,所以优化了下,导致实际上也是走绝对调用,四条指令只有后两条有用,这个先不管),后者并没有走,而是用绝对地址访问。

通过实践我发现,默认情况下,可执行文件对外部变量和函数(存在于共享库中)的访问,借助了 GOT,应该是为了避免对代码段重定位;而对文件内部函数和变量的访问,走的是绝对地址的方式,应该是为了提升访问效率。虽然可执行文件通过绝对地址访问了内部函数和变量,致使其必须在装载到指定的地址处,失去了地址无关的特性,但也正因为其装载的地址是固定的,所以那些绝对地址并不需要重定位,进程并不需要在装载后对代码段进行修改,因此可执行文件的代码段是能够共享的。可执行文件的这种特点说明,共享和位置无关并非绑定的关系,能做到地址无关也能做到共享,但能共享并不一定能做到地址无关。

虽然作者的意思不一定是说 GCC 默认会加 -fPIC 来生成可执行文件,但如上所述,生成出的可执行文件也并不是位置无关的,所以说 “使用 PIC 的方法来产生可执行文件的代码段部分” 这种表述容易产生误解。PIC 机制主要确保两点,一是对不确定的函数和变量的访问走 GOT,二是对能确定的函数和变量的访问使用相对地址。可执行文件默认只做到了第一点,而对于能确定的变量,比如图中的 a,可执行文件能够确定自己访问的变量就是它(共享库则不同,运行时由于全局符号介入可能访问的是其他模块的同名变量),所以就用绝对地址访问。我觉得这里说成是 “默认情况下,如果可执行文件是动态连接的,那么 GCC 产生的可执行文件代码段部分会使用 GOT 来访问外部函数和变量,以便于不同的进程能够共享” 会更好一点。

其实 P197 的这段话: “由于程序主模块的代码并不是地址无关代码,也就是说代码不会使用这种类似于 PIC 的机制” 的前半句也强调了可执行文件并不是地址无代码,但这句话的后半句又容易产生新的误解,说它 “不会使用类似于 PIC 的机制” ,容易让读者误认为连 GOT 也不会使用。

我当时在这两个地方困惑了很久,如果有人有类似的困惑,希望这篇笔记能有所帮助。如果有不对的地方,希望大家不吝赐教。