在从源代码编译二进制文件时,生成PIC对象与不生成二进制文件之间的实际差别是什么?在这条路的哪一点,有人会说,“我在编译MySQL时应该生成/使用PIC对象。”还是不想?
我读过Gentoo对位置独立代码的介绍,位置独立代码内部,如何修复-fPIC错误,Libtool创建对象文件和位置独立码。
来自PHP的./configure --help
来自MySQL的cmake -LAH .
-DWITH_PIC:生成PIC对象
这是一个很好的开始,但给我留下了很多问题。
据我所知,它打开了编译器中的-fPIC,然后在生成的二进制文件/库中生成PIC对象。我为什么要这么做?反之亦然。也许它的风险更大,或者可能会使二进制值不那么稳定?也许在某些体系结构(在我的例子中是amd64 64/x86_64)上编译时,应该避免这种情况?
默认的MySQL构建设置PIC=OFF。正式的MySQL发行版构建设置了PIC=ON。而PHP“试图同时使用这两种方法。”在我的测试中,设置-DWITH_PIC=ON会导致稍微大一点的二进制文件:
PIC=OFF PIC=ON
mysql 776,160 778,528
mysqld 7,339,704 7,476,024发布于 2013-08-12 11:18:22
有两个概念是不应混淆的:
他们都处理类似的问题,但在一个不同的层次。
问题所在
大多数处理器体系结构有两种寻址方式:绝对寻址和相对寻址。寻址通常用于两种访问:访问数据(读、写等)。并执行代码的不同部分(跳转、调用等)。两者都可以绝对完成(调用位于固定地址上的代码,在固定地址读取数据)或相对(跳到五个指令返回,相对于指针读取)。
相对寻址通常要花费速度和内存。速度,因为处理器必须从指针和相对值中计算绝对地址,然后才能访问实际内存位置或实际指令。内存,因为必须存储额外的指针(通常在寄存器中,这是非常快的,但也非常稀缺的内存)。
绝对寻址并不总是可行的,因为在天真地实现时,必须在编译时知道所有的地址。在许多情况下,这是不可能的。当从外部库调用代码时,您可能不知道操作系统将在哪个内存位置上加载库。当对堆上的数据进行寻址时,您将无法预先知道操作系统将为此操作保留哪些堆块。
还有很多技术细节。例如,处理器体系结构将只允许相对跳转到一定的极限;然后,所有更宽的跳转都必须是绝对的。或者在地址范围非常宽的体系结构(例如64位甚至128位)上,相对寻址将导致更紧凑的代码(因为相对地址可以使用16位或8位,但绝对地址必须始终是64位或128位)。
可重定位的二进制文件
当程序使用绝对地址时,它们会对地址空间的布局做出非常强的假设。操作系统可能无法满足所有这些假设。为了解决这个问题,大多数操作系统都可以使用一个技巧:二进制文件中添加了额外的元数据。然后,操作系统使用此元数据在运行时更改二进制文件,因此修改后的假设符合当前的情况。通常,元数据描述指令在二进制文件中的位置,使用绝对定位。当操作系统加载二进制文件时,它会在必要时更改这些指令中存储的绝对地址。
这些元数据的一个例子是ELF文件格式中的“重新定位表”。
有些操作系统使用技巧,因此它们不需要总是在运行之前处理每个文件:它们预处理文件并更改数据,因此它们的假设很可能适合运行时的情况(因此不需要修改)。这个过程在Mac上称为“预绑定”,在Linux上称为"prelink“。
可重定位的二进制文件是在链接器级别产生的。
位置独立代码(PIC)
编译器可以生成仅使用相对寻址的代码。这可能意味着数据和代码的相对寻址,或者仅针对其中一个类别。例如,gcc上的选项"-fPIC“意味着代码的相对寻址(即只有相对跳转和呼叫)。然后,代码可以在任何内存地址上运行,无需任何修改。在某些处理器体系结构上,这样的代码并不总是可能的,例如,当相对跳转的范围受到限制时(例如,允许最大128条指令宽相对跳转)。
位置无关代码在编译器级别上处理。只包含PIC代码的可执行文件不需要重新定位信息。
什么时候需要PIC代码?
在某些特殊情况下,绝对需要PIC代码,因为在加载期间重新定位是不可行的。下面是一些例子:
何时应避免事先知情同意
咨询/结论
由于一些特殊的约束,可能需要PIC代码。在所有其他情况下,坚持默认设置。如果您不知道这些约束,则不需要"-fPIC“。
发布于 2013-08-04 01:53:32
有两个真正的原因,你想要这样编译。
第一,如果您想创建一个共享库。通常,在Linux上共享库必须是PIC。
第二,您可能希望编译主可执行文件“饼”,这基本上是用于可执行文件的PIC。PIE是一种安全特性,允许将地址空间随机化应用于主可执行文件。
发布于 2013-08-05 20:56:53
可以在启用和禁用PIC代码的情况下构建共享库和可执行文件。也就是说,如果你在没有事先知情同意的情况下构建它们,其他应用程序仍然可以使用它们。然而,并非所有地方都支持非PIC库,但是在Linux上有一些限制。
=== --这是一个你不需要的简短解释;-) ===
PIC所做的是,它使代码位置独立。每个共享库都在内存中的某个位置加载--出于安全原因,这个位置通常是随机的--因此代码中的“绝对”内存引用不可能是“绝对的”,实际上它们相对于库的内存段开始地址。在加载库之后,必须对其进行调整。
这可以通过遍历所有它们(它们的地址将存储在文件头中)并进行更正来完成。但是这是缓慢的,如果基址不同,“校正”图像不能在进程之间共享。
因此,通常使用不同的方法。每个对内存的引用都是通过一个特殊寄存器(通常是ebx)来完成的。当一个函数被调用时,它在开始时跳转到一个特殊的代码块,该代码块将ebx值调整到库的内存段地址。然后,该函数使用ebx +知道偏移量访问其数据。
因此,对于每个程序,只有这个代码块必须进行调整,而不是每个函数和内存引用。
注意,如果已知函数是从同一个共享库的其他函数调用的,编译器/链接器可以省略PIC寄存器(ebx)调整,因为已知它已经具有正确的值。在一些体系结构中(最显著的是x86_64),程序可以访问相对于IP (当前指令指针)的数据,IP已经被绝对调整,因此它消除了像ebx这样的特殊寄存器的需要及其调整。
===这里是可以跳过的部分的末尾,而无需读取===
那么,你为什么要在没有事先知情同意的情况下构建一些东西呢?
首先,它使您的编程速度降低了几个百分点,因为在每个函数的开始时,会运行额外的代码来调整寄存器,并且优化器无法使用一个宝贵的寄存器(仅限x86)。函数通常不知道它是从同一个库调用的还是从另一个库调用的,因此即使内部调用也会受到惩罚。因此,如果你想优化速度-尝试编译没有事先知情同意。
然后,代码的大小要大一点,正如您注意到的,因为每个函数将包含更多的设置PIC寄存器的指令。
如果我们使用链接时间优化(--lto开关)和受保护的函数可见性,编译器知道哪些函数根本不被外部调用,因此它们不需要PIC代码,这在某种程度上是可以避免的。但我还没试过。
你为什么要用PIC?因为它更安全(这是地址空间随机化所必需的);因为并非所有系统都支持非PIC库;因为对于非PIC库,启动加载时间可能更慢(整个代码段必须调整为绝对地址,而不是表存根);如果加载到不同的空间(即可能导致使用更多内存),加载的库段不能共享。然后,并不是所有编译器/链接器标志都与非PIC库兼容(据我所知,线程本地支持),因此有时您根本无法构建非PIC代码。
因此,非PIC代码的风险更大(安全性更低),而且您不能总是得到它,但是如果您需要它(例如速度)--为什么不呢?
https://stackoverflow.com/questions/18026333
复制相似问题