今天有人问怎么在没有root的不可更新老旧Linux环境里使用高版本GCC,主要困难在于GCC对glibc版本有一定要求。假设使用现成的GCC二进制包,解压到本地后执行gcc,报错:/lib64/libc.so.6: version `GLIBC_2.14' not found,即系统glibc的libc.so.6中缺乏更高版本的符号(参考info '(ld) VERSION')。使用新符号通常表示API有更新,不过很多时候旧版本的库也能用,可能有两个原因:一是编译机器的库版本可能较新,soname版本号较高,编译出来的可执行文件也会有较高的版本要求;二是源文件因保守指定了较高的版本号,实际上旧版本也能用。
比如最近ncurses主版本号更新到6,/usr/lib/libncursesw.so.5变成了/usr/lib/libncursesw.so.6,导致很多可执行文件没法用了:
1 | % ldd /usr/lib/j8/bin/jconsole |
不想重新编译的话,可以软链接做一个libncursesw.so.5,放在ld.so默认会查找的路径下,或者运行时指定环境变量LD_LIBRARY_PATH。另外还有一种修改可执行文件的方法:用十六进制编辑工具把.dynstr
section中的字串libncursesw.so.5改成libncursesw.so.6。再次执行时,ld.so将会用新的名字查找依赖的库,就顺利找到了/usr/lib/libncursesw.so.6,但做这样的修改得保证新的路径名长度小于等于原长,否则会覆盖.dynstr中后面的字串。另一种办法是构造一个新的.dynstr放到ELF的其他地方,并变更section
header中的.dynstr地址信息,非常麻烦。
回到正题,xptree下载了gcc-c++-4.8.3-9.el7.x86_64.rpm和gcc-4.8.3-9.el7.x86_64.rpm,解压后执行,报错:/lib64/libc.so.6: version `GLIBC_2.14' not found。解压glibc-2.17-78.el7.x86_64.rpm,报错:
1 | /usr/bin/python: relocation error: MY_LOCAL/lib64/libc.so.6: symbol |
看来对ld-linux-x86-64.so.2(dynamic
linker,下面简称为ld.so)的版本也有要求。就像修改依赖库的路径那样,修改program
header中的PT_INTERP:
1 | bbe -b '/\x2flib64\x2fld-linux-x86-64.so.2/:27' -e 'L 1' -e 'r 0 /tmp/l/ld.so\0' bin/gcc -o bin/gcc.new |
效果是把gcc中第一次出现的/lib64/ld-linux-x86-64.so.2(PT_INTERP项)替换成/tmp/l/ld.so\0,可以观察到:
1 | % ldd bin/gcc.new |
有几个被gcc
g++调用的可执行文件如cc1、cc1plus等可能也得做同样修改。之后gcc即可执行,但编译生成的可执行文件还使用原先的dynamic
linkter,也许会在某些情况下无法执行。一种办法是在用gcc
g++链接时指定-Wl,--dynamic-linker,/tmp/l/ld.so,这样产生的可执行文件就会使用/tmp/l/ld.so作为dynamic
linker。另一种方法是修改gcc中的built-in specs:
1 | % gcc -dumpspecs |
用bvi、bviplus等十六进制编辑器在可执行文件gcc中找这段字串,把/lib64/ld-linux-x86-64.so.2}}}改成/tmp/l/ld.so}}},原串多出来的地方用空格填充。这样得到的gcc产生的可执行文件无需指定-Wl,--dynamic-linker,默认就会使用新的dynamic
linker了:
1 | % bin/gcc.new -xc - -o a <<< 'int main(){}' |
跳出XY problem
如果要装的东西多些,可以考虑Gentoo Prefix、NixOS等,还有unprivileged LXC containers等。轻量省事的方法是userspace chroot PRoot,不需要root,但弊端是运行的进程处于ptrace下,而一个进程不能被ptrace两次,因此gdb、strace等都没法用。