gcc - 编译 WITH_PIC (-DWITH_PIC, --with-pic) 实际上有什么作用?

标签 gcc compilation cmake libtool

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

我已阅读 Gentoo's Introduction to Position Independent Code , Position Independent Code internals , HOWTO fix -fPIC errors , Libtool's Creating object files , 和 Position Independent Code .

来自 PHP ./configure --help :

--with-pic: Try to use only PIC/non-PIC objects [default=use both].



来自 MySQL 的 cmake -LAH . :

-DWITH_PIC: Generate PIC objects



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

据我了解,它开启 -fPIC在编译器中,它反过来在生成的二进制文件/库中生成 PIC 对象。我为什么要这样做?或相反亦然。也许它风险更大,或者可能会使二进制文件不太稳定?也许在某些架构上编译时应该避免它(在我的情况下是 amd64/x86_64)?

默认的 MySQL build设置 PIC=OFF。官方 MySQL 版本build设置 PIC=ON。而 PHP“试图同时使用两者”。在我的测试设置中 -DWITH_PIC=ON导致稍大的二进制文件:
          PIC=OFF     PIC=ON
mysql     776,160    778,528
mysqld  7,339,704  7,476,024

最佳答案

有两个概念不应混淆:

  • 可重定位二进制文​​件
  • 位置无关代码

  • 他们都处理类似的问题,但在不同的层面上。

    问题

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

    相对寻址通常会同时消耗速度和内存。速度,因为处理器必须根据指针和相对值计算绝对地址,然后才能访问实际内存位置或实际指令。内存,因为必须存储一个额外的指针(通常在一个寄存器中,它非常快但内存也非常稀缺)。

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

    然后还有很多技术细节。例如。处理器架构只允许相对跳转到一定的限制;所有更宽的跳跃都必须是绝对的。或者在地址范围很宽(例如64位甚至128位)的体系结构上,相对寻址会导致代码更紧凑(因为相对地址可以使用16位或8位,但绝对地址必须始终为64位或128 位)。

    可重定位的二进制文件

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

    这些元数据的一个例子是 ELF 文件格式的“重定位表”。

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

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

    位置无关代码 (PIC)

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

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

    什么时候需要PIC代码

    在某些特殊情况下,绝对需要 PIC 代码,因为加载时重定位是不可行的。一些例子:
  • 一些嵌入式系统可以直接从文件系统运行二进制文件,而无需先将它们加载到内存中。当文件系统已经在内存中时,通常就是这种情况,例如在 ROM 或闪存中。然后,可执行文件的启动速度要快得多,并且不需要(通常稀缺的)RAM 的额外部分。此功能称为“execute in place”。
  • 您正在使用一些特殊的插件系统。一种极端情况是所谓的“shell 代码”,即使用安全漏洞注入(inject)的代码。然后您通常不知道您的代码在运行时将位于何处,并且有问题的可执行文件不会为您的代码提供重定位服务。
  • 操作系统不支持可重定位的二进制文件(通常是由于资源稀缺,例如在嵌入式平台上)
  • 操作系统可以缓存正在运行的程序之间的公共(public)内存页。在重定位期间更改二进制文件时,此缓存将不再起作用(因为每个二进制文件都有自己的重定位代码版本)。

  • 什么时候应该避免 PIC
  • 在某些情况下,编译器可能无法使所有内容独立于位置(例如,因为编译器不够“聪明”或因为处理器架构过于受限)
  • 由于许多指针操作,位置无关代码可能太慢或太大。
  • 优化器可能会遇到许多指针操作的问题,因此它不会应用必要的优化,并且可执行文件会像 molasse 一样运行。

  • 建议/结论

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

    关于gcc - 编译 WITH_PIC (-DWITH_PIC, --with-pic) 实际上有什么作用?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18026333/

    相关文章:

    c++ - 通过 CMake 添加外部库(例如 Eigen)

    c++ - Cmake:为多配置 cmake 项目指定配置特定设置

    c++ - SIGFPE 访问 unordered_map 时

    c - 使用 pty 使用 GCC 编译时出错

    c# - Unity iOS编译: Cross Compilation job Mono.WebBrowser.dll failed错误

    c++ - 如何使用 cmake 正确链接库(.so y .a)

    c - 存储对变量数组所做的更改

    c++ - 使用 GCC 编译 .CPP

    perl - 如何让 OS X 10.6 将 JSON::XS 编译为 32 位而不是 64 位?

    ruby - Netbeans 偶尔使用 Ruby MRI 无法编译 – 解决方案?