问题分析:
本篇文档是在《更改引用高版本glibc的程序到引用低版本的glibc》之后的补充文档,如果以后遇到相同问题,首先看我之前原创的《更改文件引用的高版本glibc到低版本glibc》这篇,然后再来看本篇。
本篇文档将详细记录一个在低版本glibc机器上运行由a.cpp文件编译之后的a文件,由最初的缺少GLIBC_2.14错误提示到最终成功运行的一系列步骤。
1:我们在45.154机器上查看GLIBC版本,如下:
这是a.cpp文件:
使用g++ a.cpp命令编译之后生成了a.out文件,我们在45.154机器上,也就是在本机运行a.out文件,结果如下:
可以发现a.out文件可以成功运行。
2:现在我们将a.out文件传输到45.152机器上,这个机器上的GLIBC版本如下:
可以发现该机器上的GLIBC版本比较低,我们通过scp命令将a.out文件从45.154上传输到45.152上,具体命令如下:
执行之后,输入命令,即可成功传输到45.152机器上。
3:我们在45.152机器上执行./a.out命令,运行结果如下:
可以发现,此时由于152机器上没有所需的GLIBC_2.14,因此程序执行报错了,那么我们首先需要确定这个a.out程序引用了GLIBC_2.14的哪个函数,再查看GLIBC_2.12中有没有这个函数,如果有,那么只需要将该引用修改为引用低版本的GLIBC即可。
首先我们可以检查一下程序使用了新版本 glibc 的哪些符号,使用 objdump
命令可以查看 ELF 文件的动态符号信息:
从上面的输出可以看到程序使用了 glibc 2.14 版本的 memcpy
函数,而这个常用的函数按说应该是 glibc 很早就已经支持了的,我们可以确认一下当前系统 glibc 提供的符号版本:
这里可以看出glibc 库提供的 memcpy
实现是 2.2.5 版本的,看过这里就基本明白了,第三方程序的开发者是在自带新版本 glibc 的 Linux 系统上编译的,memcpy
的实现默认使用了该系统上 glibc 所提供的最新版本,这样在低版本 glibc 系统中就无法正常运行。
解决办法:
虽然我们无法重新编译第三方程序,但如果可以修改 ELF 文件强制让 LD 库加载程序时使用老版本的 memcpy
实现,应该就可以避免升级 glibc。
分析 ELF
首先用 readelf 命令查看 ELF 的符号表,由于该命令输出非常多,这里只贴出我们关心的信息:
我们可以在 ELF 的 .dynsym
动态符号表中看到程序用于动态链接的所有导入导出符号,memcpy
后面括号里的数字就是十进制的版本号(为 3
),而我们需要格外关注的是下面的 .gnu.version
和 .gnu.version_r
符号版本信息段。
.gnu.version
表包含所有动态符号的版本信息,.dynsym
动态符号表中的每个符号都可以在 .gnu.version
中看到对应的条目。
下面关键的 .gnu.version_r
表示二进制程序实际依赖的库文件版本,从输出中也能看到 .gnu.version_r
表是按照不同的库文件进行分段显示的,每个条目占用 0x0010 也就是 16 个字节,该表是从 0x000450
偏移量开始,我们看看 GLIBC_2.14
也就是 0x000460
处的十六进制数据:
.gnu.version_r 表中每个条目是 16 个字节的 Elfxx_Vernaux 结构体,其声明如下(Elfxx_Half 占用 2 个字节,Elfxx_Word 占用 4 个字节):
typedef struct { Elfxx_Word vna_hash; Elfxx_Half vna_flags; Elfxx_Half vna_other; Elfxx_Word vna_name; Elfxx_Word vna_next; } Elfxx_Vernaux; |
vna_hash 为 4 个字节的库名称(也就是上面的 GLIBC_2.14 字符串)的 hash 值,vna_other 为对应的 .gnu.version 表中符号的版本值,vna_name 指向库名称字符串的偏移量(也可以在 ELF 头中找到),vna_next 为下一个条目的位置(一般固定为 0x00000010)。
由上面的输出我们可以看到 GLIBC_2.14
对应的 0x000460
处的开始的 4 个字节 vna_hash
hash 值为 94919606
,而 vna_other
的值 0300
(输出里的 Version: 3
)也与 .gnu.version
符号的值一致。
修改 ELF 符号表
由于 Linux 系统中的 LD 库(也就是 /lib64/ld-linux-x86-64.so.2
库)加载 ELF 时检查 .gnu.version_r
表中的符号,我们可以使用任何一款十六进制编辑器来修改 .gnu.version_r
表中的符号值来强制使用老版本的函数实现。
我们现在需要修改0x000460
这行数字中的vna_hash; vna_other; vna_name;这三项和下面的0x000470
保持一致,
0x000470
是对
GLIBC_2.12
的引用。
这三项所在的字节分别是
1~4
字节,
7~8
字节,
9~12
字节。
修改之后内容如下:
首先在命令模式,输入%!xxd –r转换为二进制,再使用:wq保存即可。否则在执行./a.out的时候会报错。
此时,修改保存之后的 ELF 文件再使用 readelf
命令检查就能看到变化了(只列出了修改的 .gnu.version-r
表):
可以发现,原来上面那行引用的GLIBC_2.14变成了现在的GLIBC_2.2.5,此时再使用./a.out命令执行得到正确结果: