我正在试验Linux上纯静态链接的PIE可执行文件的概念,但遇到了GNU binutils链接器在使用-pie
时坚持在输出二进制文件中添加PT_INTERP头的问题,即使在给出-static
时也是如此。有没有办法抑制这种行为?也就是说,有没有办法告诉GNU ld具体不要将某些标题写入输出文件?也许使用链接器脚本?
(请不要回答声称它不起作用;我很清楚程序仍然需要重定位处理 - 加载地址相对重定位只是因为我使用-Bsymbolic
- 我有特殊的启动代码到位标准的Scrt1.o
来处理这个问题。但是如果没有动态链接器已经开始并且正在进行工作,除非将二进制文件中的PT_INTERP
标头剔除,否则我无法调用它。)
也许我是天真的,但是...不足以搜索默认的链接描述文件,编辑它,并删除.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
并使用该脚本链接您的程序。
我想我可能已经找到了解决方案:只需使用-shared
而不是-pie
来制作饼干二进制文件。您需要一些额外的链接器选项来修补该行为,但它似乎避免了对自定义链接描述文件的需要。或者换句话说,-shared
链接器脚本已经基本上正确用于链接静态饼图二进制文件。
如果我使用它,我将使用我正在使用的确切命令行更新答案。
更新:它的工作原理!这是命令行:
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规范文件中......
扩展我之前的注释,因为这不适合那个微不足道的盒子(这只是一个想法或讨论,请不要觉得有义务接受或奖励赏金),也许最简单,最干净的做法是juts添加一个构建后的步骤来从生成的二进制文件中删除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:
如果指定链接器无法将链接器输入文件识别为目标文件或存档文件,则会尝试将该文件作为链接描述文件读取。如果无法将文件解析为链接描述文件,则链接器将报告错误。
与隐式定义的链接描述文件相比,这似乎意味着用户定义的链接描述文件中的值将替换默认脚本中的值。
我不是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的build命令:
$ 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
-Wl,--no-dynamic-linker worked
对我来说。