今天有人问怎么在没有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等都没法用。