今天有人問怎麼在沒有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等都沒法用。