【问题标题】:Is there an option to GNU ld to omit -dynamic-linker (PT_INTERP) completely?GNU ld 是否有完全省略 -dynamic-linker (PT_INTERP) 的选项?
【发布时间】:2012-05-15 00:06:10
【问题描述】:

我正在 Linux 上尝试纯静态链接的 PIE 可执行文件的概念,但遇到了一个问题,即 GNU binutils 链接器在使用 -pie 时坚持在输出二进制文件中添加 PT_INTERP 标头,即使在还给了-static。有没有办法抑制这种行为?也就是说,有没有办法专门告诉 GNU ld 不要将某些标头写入输出文件?也许使用链接器脚本?

(请不要回答声称它不起作用;我很清楚该程序仍然需要重定位处理 - 加载地址相对重定位只是因为我使用了-Bsymbolic - 我有特殊的启动代码代替标准Scrt1.o 来处理这个问题。但是如果没有动态链接器已经启动并完成工作,我无法调用它,除非将PT_INTERP 标头从二进制文件中十六进制编辑出来。)

【问题讨论】:

  • 让我看看我是否直截了当。您正在指定自己的入口点,这反过来又处理一些自定义重定位,并且您不希望内核在标准解释器中加载?如果您要链接需要通过.init 运行初始化的库怎么办?根据我的经验,如果你想对你的可执行文件做一些事情,但是没有办法通过 LDFLAGS 的一些排列来生成它,那么这不是一个好主意。
  • 如果我想把它放在应用程序的构建系统中,我会 100% 同意你的看法。对于一个应用程序来使用像这样的链接器选项是一个可怕的黑客,它无论如何都不会工作,因为它需要将所有.a 库构建为 PIC。但是,我正在研究的是一个新的工具链选项,旨在用于面向安全的发行版,其中为 setuid 二进制文件运行动态链接器是不可接受的风险。如果在 ld 级别不需要更改,那么部署会容易得多,只需在 gcc 规范文件和 crt 级别。
  • 看起来你必须为 ld 编写一个补丁,然后与他们争论为什么应该将它应用于主干。而且,这听起来很有趣。
  • 如果可以使用链接器脚本,那将不如仅使用命令行选项理想,但比内部修补要好得多。非常感谢精通链接器脚本的人的回答。
  • 一开始需要省略吗?即,构建后的 Make(或任何您喜欢的)步骤是否足以剥离 PT_INTERP 标头?

标签: c static-linking dynamic-linking binutils pie-chart


【解决方案1】:

也许我太天真了,但是...搜索默认链接器脚本、对其进行编辑并删除.interp 部分中链接的行还不够吗?

例如,在我的机器中,脚本位于/usr/lib/ldscripts 中,而有问题的行是SECTIONS 部分中的interp : { *(.interp) }

您可以转储运行以下命令使用的默认脚本:

$ ld --verbose ${YOUR_LD_FLAGS} | \
    gawk 'BEGIN { s = 0 } { if ($0 ~ /^=/) s = !s; else if (s == 1) print; }'

您可以稍微修改gawk 脚​​本以删除interp 行(或仅使用grep -v 并使用该脚本链接您的程序。

【讨论】:

  • 到目前为止,这可能是最好的方法。不幸的是,它需要为每个系统制作新版本的链接器脚本,并且不能仅仅依靠现有的工作链接器脚本。但是,如果我可以让概念证明运行良好,也许我可以在上游 binutils 中获得它。
  • @R.. - 好吧,我使用strace 的技巧不起作用,因为脚本实际上被编译成ld,而不是从磁盘读取。但是您可以使用ld --verbose 和输出中的一些魔法来获得它(请参阅更新的答案。
  • 从这段代码中,您可以制作 ld 包装器,就像有些人为 gcc 所做的那样。
【解决方案2】:

我想我可能已经找到了解决方案:只需使用 -shared 而不是 -pie 来制作 pie 二进制文件。您需要一些额外的链接器选项来修补行为,但它似乎避免了对自定义链接器脚本的需要。或者换句话说,-shared 链接器脚本对于链接静态 pie 二进制文件已经基本正确。

如果我得到它,我将使用我正在使用的确切命令行更新答案。

更新:有效!这是命令行:

gcc -shared -static-libgcc -Wl,-static -Wl,-Bsymbolic \
    -nostartfiles -fPIE Zcrt1.s Zcrt2.c /usr/lib/crti.o hello.c /usr/lib/crtn.o

其中 Zcrt1.s 是 Scrt1.s 的修改版本,它在正常工作之前调用 Zcrt2.c 中的函数,并且 Zcrt2.c 中的代码处理刚刚经过 argv 和环境数组的 aux 向量以找到DYNAMIC 部分,然后遍历重定位表并应用所有相对类型的重定位(唯一应该存在的重定位)。

现在所有这些都可以(通过一些工作)打包到一个脚本或 gcc 规范文件中......

【讨论】:

  • 巧妙的把戏。你基本上是在建立你需要的东西,而不是减去。 +1 祝你好运。
【解决方案3】:

扩展我之前的笔记,因为这不适合那个小盒子(这只是一个想法或讨论,请不要觉得有义务接受或奖励赏金),也许是最简单和最干净的方法是否要添加一个构建后步骤以从生成的二进制文件中去除 PT_INTERP 标头?

PT_INTERP 替换为PT_NULL 比手动编辑标题和可能不得不改变所有内容更容易。我不知道您是否可以找到一种通过现有工具(某种可编写脚本的十六进制查找和替换)简单地修补文件的方法,或者您是否必须编写一个小程序来做到这一点。我知道 libbfd(GNU 二进制文件描述符库)在后一种情况下可能是你的朋友,因为它会让整个业务变得更容易。

我想我只是不明白为什么通过ld 选项执行此操作很重要。如果有的话,我可以理解为什么它会更好;但正如一些(诚然很轻)谷歌搜索表明没有这样的功能,单独和事后进行可能不会那么麻烦。 (也许将标志添加到ld 比编写脚本将PT_INTERP 替换为PT_NULL 更容易,但说服开发人员将其拉到上游是另一回事。)


显然(如果这是您已经看到的内容,请纠正我)您可以覆盖 ld 与链接描述文件 with the PHDRS command 中的任何 ELF 标头有关的行为,并使用 :none 来指定特定的标头类型不应包含在任何段中。我不确定语法,但我认为它看起来像这样:

PHDRS
{
  headers PT_PHDR PHDRS ;
  interp PT_INTERP ;
  text PT_LOAD FILEHDR PHDRS ;
  data PT_LOAD ;
  dynamic PT_DYNAMIC ;
}

SECTIONS
{
  . = SIZEOF_HEADERS;
  .interp : { } :none
  ...
}

ld docs,您可以使用--library-path 覆盖链接描述文件:

--library-path=searchdir

将路径 searchdir 添加到 ld 将搜索的路径列表中 存档库和 ld 控制脚本。您可以使用此选项 次数。目录按以下顺序搜索 它们是在命令行中指定的。上指定的目录 在默认目录之前搜索命令行。全部-L 选项适用于所有 -l 选项,无论 出现选项。搜索的默认路径集(没有被 用 `-L' 指定)取决于 ld 使用的仿真模式,并且 在某些情况下,它也是如何配置的。请参阅环境部分 变量。路径也可以在链接脚本中指定 SEARCH_DIR 命令。以这种方式指定的目录在 链接描述文件出现在命令行中的点。

另外,来自the section on Implicit Linker Scripts

如果您指定了链接器无法识别的链接器输入文件 作为目标文件或存档文件,它将尝试将文件读取为 一个链接器脚本。如果无法将文件解析为链接描述文件,则 链接器会报错。

这似乎暗示了用户定义的链接器脚本中的值,与隐式定义的链接器脚本相比,替换默认脚本中的值。

【讨论】:

  • 这很容易做实验,实际上我就是这么做的。但是当目标是构建一个工具链时,它根本没有帮助,您可以在 CFLAGSLDFLAGS 中删除一些额外的选项并让任何程序构建为静态派。
  • 除非 ld 的上游补丁提供对省略 PT_INTERP 的支持(假设尚不存在),我认为没有任何替代方案可以创建通用工具链。 .
  • 我找到了一些关于覆盖任何 ELF 标头的默认行为的信息并更新了我的答案 - 这适用于您吗?
  • 这看起来很有希望。有没有办法将它链接到 ld 已经在使用的现有链接器脚本上,而无需复制/复制其中已有的所有内容?
  • 有用户定义和隐式定义的链接器脚本。我认为其中一个会满足您的需要,因为我不确定 ld 文档中“覆盖”的实际含义是什么。查看更新后的帖子。
【解决方案4】:

我不是 GNU ld 方面的专家,但我在documentation 中找到了以下信息:

特殊的 secname `/DISCARD/' 可用于丢弃输入部分。 分配给名为“/DISCARD/”的输出节的任何节 不包含在最终的链接输出中。

希望对你有帮助。

更新:

(这是解决方案的第一个版本,它不起作用,因为 INTERP 部分与标题 PT_INTERP 一起被删除。)

main.c:

int main(int argc, char **argv)                                                                                                                               
{                                                                                                                                                             
    return 0;                                                                                                                                                 
}

main.x:

SECTIONS {                                                                                                                                                    
    /DISCARD/ : { *(.interp) }                                                                                                                                
}

构建命令:

$ gcc -nostdlib -pie -static -Wl,-T,main.x main.c
$ readelf -S a.out | grep .interp

不带选项的构建命令 -Wl,-T,main.x:

$ gcc -nostdlib -pie -static main.c 
/usr/bin/ld: warning: cannot find entry symbol _start; defaulting to 0000000000000218
$ readelf -S a.out | grep .interp
  [ 1] .interp           PROGBITS        00000134 000134 000013 00   A  0   0  1

更新 2:

此解决方案的想法是将原始部分“INTERP”(链接描述文件中的.interp)重命名为.interp1。换句话说,该节的全部内容都放在 .interp1 节中。因此,我们可以安全地删除 INTERP 部分(现在为空),而不必担心丢失默认链接描述文件设置,因此标题 INTERP_PT 也将被删除。

SECTIONS {
    .interp1 : { *(.interp); } : NONE
    /DISCARD/ : { *(.interp) }
}

为了显示文件中存在 INTERP 部分的内容(如 .interp1),但删除了 INTERP_PT 标头,我使用了 readelf + grep 的组合。

$ gcc -nostdlib -pie -Wl,-T,main.x main.c
$ readelf -l a.out | grep interp
   00     .note.gnu.build-id .text .interp1 .dynstr .hash .gnu.hash .dynamic .got.plt 
$ readelf -S a.out | grep interp
  [ 3] .interp1          PROGBITS        0000002e 00102e 000013 00   A  0   0  1

【讨论】:

  • 我尝试将它与.interp 一起使用,但无法让它工作,可能是因为它是由默认链接描述文件而不是源文件创建的。你知道应该怎么写吗?
  • 嗯,你需要查看的是-l 而不是-S(程序头文件)。没错,这会抑制 INTERP 标头,但它似乎也导致生成单个伪造的 LOAD 标头而不是正确的标头(整个程序以读写方式加载)所以它似乎只是跳过底层系统 ld 脚本...
  • 现在问题对我来说变得更清楚了。您能否尝试其他解决方案。我还发现了一个link,据说 -static 和 -pie 选项不兼容。
  • 任何带有-T 的东西都会丢弃整个默认链接器脚本,从而生成损坏的(或至少非常次优的)二进制文件。它是INTERP(如PT_INTERP,程序头),而不是interp,你想用grep 来检查输出。可执行文件中的部分纯粹是注释/调试信息;程序加载器不会使用它们。
  • 您尝试过第二种解决方案,还是因为不喜欢第一种而没有尝试过?它不会丢弃整个默认链接器脚本。或者如果确实如此,请您描述一下如何?自然,您需要使用其他选项而不是“-nostdlib”进行构建。选择示例中的选项以便文件正在构建而不是运行。而且,程序加载器怎么可能不使用这些部分?我的意思是,该部分包含代码和数据。很奇怪……
【解决方案5】:

-Wl,--no-dynamic-linker worked 给我。

【讨论】:

  • 是的,我编写了添加它的补丁。 :-) 但无论如何 +1 并欢迎来到该网站。
  • 在 binutils 2.25 中不起作用,unrecognized option '--no-dynamic-linker'。此选项仅在 2.26 或更高版本中可用。
猜你喜欢
  • 2011-05-27
  • 2017-07-24
  • 1970-01-01
  • 2014-06-07
  • 2020-06-30
  • 2022-07-19
  • 2011-04-30
  • 1970-01-01
  • 2010-10-10
相关资源
最近更新 更多