我正在使用
gcc
生成的目标文件进行一些实验。
虽然我至少表面上知道位置无关代码有什么用,但我试图具体地发现它。
我在文件中生成了这个简单的代码
timestwo.c
:
int timestwo(int x)
{
return 2 * x;
}
我编译的:
gcc -c timestwo.c
gcc -c -fPIC timestwo.c -o timestwo_pic.o
则生成的目标文件
timestwo.o
和 timestwo_pic.o
按位相同。还有 readelf -a
我看不出有什么区别。
您能否提供一个 MWE,让我可以看到
pic
和非 pic
代码之间的区别?
现在,您的发行版的 GCC 编译器很可能默认生成 PIC 代码, 因此,为了确保它发出非 PIC 代码,您应该指定
-fno-PIC
。
无论如何,您不会看到示例代码(例如您的代码)有任何差异, 仅引用局部符号和/或常量。 PIC 仅在代码引用外部时起作用 符号 - 其运行时地址将是非位置无关的绝对值 程序,但对于位置无关的程序,将在运行时通过查找程序的全局偏移表(GOT)来计算, 它已由动态链接器使用其实际决定的符号地址进行了修补。
这是一个使用 -fPIC v. -fno-PIC 进行不同编译的基本文件:
$ cat file.c
extern int foo;
int bar()
{
return foo;
}
$ gcc -c -fno-PIC file.c -o file_no_PIC.o -save-temps
$ gcc -c -fPIC file.c -o file_PIC.o -save-temps
我指定
-save-temps
只是为了生成相应的程序集列表以进行比较。
对象二进制文件不同:
$ diff file_no_PIC.o file_PIC.o
Binary files file_no_PIC.o and file_PIC.o differ
以下是装配清单之间的差异:
$ diff file_no_PIC.s file_PIC.s
15c15,16
< movl foo(%rip), %eax
---
> movq foo@GOTPCREL(%rip), %rax
> movl (%rax), %eax
在非 PIC 代码中,只需将双字(32 位)从
foo
(相对于 PC)地址加载到 bar()
寄存器即可从 foo
返回 eax
。
在 PIC 代码中,它是通过首先将 foo
的全局偏移表值(PC 相对)地址处的 64 位加载到 rax
寄存器中来返回的 - 使得 rax
保存 foo
的运行时地址 - 然后将该地址处的 32 位(int
)加载到 eax
。