20

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

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

4

5 回答 5

13

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

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

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

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

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

于 2012-05-11T03:46:22.237 回答
12

我想我可能已经找到了一个解决方案:简单地使用-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 和环境数组的辅助向量以找到 DYNAMIC 部分,然后循环重定位表并应用所有相对类型的重定位(唯一应该存在的重定位)。

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

于 2012-05-27T23:25:12.183 回答
4

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

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

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


显然(如果这是您已经看到的,请纠正我)您可以使用命令覆盖链接描述文件中任何 ELF 标头的行为ld使用指定特定的标头类型不应该是包含在任何段中。我不确定语法,但我认为它看起来像这样:PHDRS: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 文档中,您可以使用以下命令覆盖链接器脚本--library-path

--library-path=searchdir

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

此外,来自有关隐式链接器脚本的部分

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

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

于 2012-05-11T03:31:05.763 回答
3

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

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

我希望这能帮到您。

更新:

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

主.c:

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

主要的.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
于 2012-05-17T21:31:14.813 回答
2

-Wl,--no-dynamic-linker worked为了我。

于 2019-03-07T18:17:04.130 回答