编译WITH_PIC(-DWITH_PIC, - with-pic)实际上做了什么?

Jef*_*eff 12 gcc compilation cmake libtool

从源代码编译二进制文件时,生成PIC对象之间的实际差异是什么?在某个时刻,有人会说,"我应该在编译MySQL时生成/使用PIC对象." 或不?

我已经阅读了Gentoo的位置无关代码简介,位置无关代码内部,HOWTO修复-fPIC错误,Libtool创建目标文件位置无关代码.

从PHP的./configure --help:

--with-pic:尝试仅使用PIC /非PIC对象[默认=同时使用].

来自MySQL的cmake -LAH .:

-DWITH_PIC:生成PIC对象

这个信息是一个好的开始,但给我留下了很多问题.

根据我的理解,它-fPIC在编译器中打开,编译器又在生成的二进制文件/库中生成PIC对象.我为什么要那样做?或相反亦然.也许风险更大或者可能使二进制文件更不稳定?编译某些体系结构时可能应该避免(在我的情况下是amd64/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
Run Code Online (Sandbox Code Playgroud)

ste*_*hke 18

有两个概念不应该混淆:

  1. 可重定位的二进制文件
  2. 位置独立代码

它们都处理类似的问题,但处于不同的层面.

问题

大多数处理器架构都有两种寻址:绝对和相对.寻址通常用于两种类型的访问:访问数据(读取,写入等)和执行代码的不同部分(跳转,调用等).两者都可以绝对完成(调用位于固定地址的代码,读取固定地址的数据)或相对(跳转到五条指令,相对于指针读取).

相对寻址通常需要成本,速度和内存.速度,因为处理器必须先计算指针的绝对地址和相对值才能访问实际内存位置或实际指令.内存,因为必须存储一个额外的指针(通常在寄存器中,这是非常快但内存非常稀缺).

绝对寻址并不总是可行的,因为当天真地实现时,必须在编译时知道所有地址.在许多情况下,这是不可能的.从外部库调用代码时,可能不知道操作系统将在哪个内存位置加载库.在堆上寻址数据时,不会事先知道操作系统将为此操作保留哪个堆块.

然后有很多技术细节.例如,处理器架构只允许相对跳跃达到一定限度; 所有更广泛的跳跃必须是绝对的.或者在具有非常宽的地址范围(例如64位或甚至128位)的架构上,相对寻址将导致更紧凑的代码(因为相对地址可以使用16位或8位,但绝对地址必须始终为64位或128位).

可重定位的二进制文件

当程序使用绝对地址时,它们会对地址空间的布局做出非常强烈的假设.操作系统可能无法满足所有这些假设.为了解决这个问题,大多数操作系统都可以使用技巧:二进制文件丰富了额外的元数据.然后,操作系统使用此元数据在运行时更改二进制文件,因此修改后的假设适合当前情况.通常元数据描述二进制中指令的位置,它使用绝对定位.当操作系统然后加载二进制文件时,它会在必要时更改存储在这些指令中的绝对地址.

这些元数据的一个示例是ELF文件格式的"重定位表".

有些操作系统使用技巧,因此在运行之前不需要总是处理每个文件:它们预处理文件并更改数据,因此它们的假设很可能适合运行时的情况(因此不需要修改).此过程在Mac OS X上称为"预绑定",在Linux上称为"prelink".

可重定位二进制文​​件在链接器级别生成.

位置无关代码(PIC)

编译器可以生成仅使用相对寻址的代码.这可能意味着对数据和代码的相对寻址,或仅针对这些类别中的一个.gcc上的选项"-fPIC"表示强制执行代码的相对寻址(即仅相对跳转和调用).然后代码可以在任何内存地址上运行而无需任何修改.在某些处理器体系结构中,这样的代码并不总是可能的,例如,当相对跳跃在其范围内受限时(例如,允许最多128个指令宽的相对跳跃).

位置无关代码在编译器级别处理.仅包含PIC代码的可执行文件不需要重定位信息.

何时需要PIC代码

在某些特殊情况下,绝对需要PIC代码,因为在加载过程中重新定位是不可行的.一些例子:

  1. 某些嵌入式系统可以直接从文件系统运行二进制文件,而无需先将它们加载到内存中.当文件系统已经在存储器中时,例如在ROM或FLASH存储器中,通常就是这种情况.然后执行的启动速度更快,并且不需要(通常是稀缺的)RAM的额外部分.此功能称为" 就地执行 ".
  2. 您正在使用一些特殊的插件系统.极端情况是所谓的"shell代码",即使用安全漏洞注入的代码.然后,您通常不会知道代码在运行时的位置,并且相关的可执行文件不会为您的代码提供重定位服务.
  3. 操作系统不支持可重定位的二进制文件(通常由于资源稀缺,例如在嵌入式平台上)
  4. 操作系统可以在运行的程序之间缓存公共内存页面.在重定位期间更改二进制文件时,此缓存将不再起作用(因为每个二进制文件都有自己的重定位代码版本).

什么时候应该避免PIC

  1. 在某些情况下,编译器可能无法使所有位置独立(例如,因为编译器不够"聪明"或因为处理器架构太受限制)
  2. 由于许多指针操作,位置无关代码可能太慢或太大.
  3. 优化器可能在许多指针操作中出现问题,因此它不会应用必要的优化,并且可执行文件将像molasse一样运行.

建议/结论

由于某些特殊约束,可能需要PIC代码.在所有其他情况下,坚持使用默认值.如果您不了解此类约束,则不需要"-fPIC".