glibc 版本(version `GLIBC_2.14' not found)问题
简述#
很多时候,没法使用高版本系统,或者升级 glibc
版本,导致很多兼容性问题。这类的答案网上有很多,给出的解决方案也不少,这里做个简单的记录,方便参考。
大致来说,有这么几种方式:
- 在低版本环境下编译,在高版本环境下使用。(比如在 centos 6 上使用 gcc 编译的程序,可以跑在 ubuntu 18.04)
- 使用静态链接
libc
(libstd++
)的方式。(依赖库太多的时候,蛮麻烦的,也要考虑glibc对内核版本的要求) - 符号替换,使用低版本符号替换编译环境下的高版本符号,或使用链接器的wrap选项实现 libc 函数的包装。
- 发布程序自带
libc.so
等,链接时指定-Wl,-rpath
或添加环境变量LD_LIBRARY_PATH
来使用自带的动态库。
解决这个问题的程序#
1、 glibc_hack 脚本#
这是一个可以修改二进制文件,将 GLIBC_版本符号 替换为 弱引用(weak)的脚本。 使用弱引用版本的程序,任会输出错误消息,但在运行时不会直接终止该程序。《在旧的glibc上运行新的应用程序》 这里阐述了这个脚本的处理过程,使用这个脚本并非很优的做法。
这个脚本的处理是比较简单的,处理的情况也有限(2.14),适用情况有限。
#Programatically do the steps outlined at # http://www.lightofdawn.org/wiki/wiki.cgi/NewAppsOnOldGlibc # - sets GLIBC 2_14 to weak to run targets on older libcs. set -e if [ ! -x $1 ]; then exit -1 fi SECTION_OFFSET=$(printf "%d" $(readelf -V $1 |grep '.gnu.version_r' -A1 | grep 'Offset' | awk '{print $4}')) GLIB_2_14_OFFSET=$(printf "%d" $(readelf -V $1 | grep 'Name: GLIBC_2.14' | awk '{print $1}' | tr -d : )) INDEX=$(( SECTION_OFFSET + GLIB_2_14_OFFSET + 4 + 1 )) echo "Going to patch $1: " echo ".gnu_version_r table (@ $(printf '%0X' $SECTION_OFFSET))" echo "----> GLIBC_2.14 (@ $(printf '%0X' $GLIB_2_14_OFFSET))" echo "Offset $(printf '%0X' $INDEX)" xxd -c 1 -p $1 | awk "{if (NR==$INDEX)\$0=\"02\"; print;}" | xxd -r -c 1 -p > $1.patched
脚本的地址在这里:https://github.com/mathew-hall/glibc_hack
2、修改高版本依赖到低版本的小程序#
下面代码是对网上一个程序的修改,用于将 ELF64
(如果要处理32位程序,将其中的一些结构体类型的64
改为32
即可)文件中的对 libc.so.6
依赖的符号,对其依赖的高 GLIBC_xxx
版本(2.12
及以上)替换为低版本(2.2.5
)。
/* (c) 2012 Andrei Nigmatulin */ /* Modifiers solym(ymwh@foxmail.com) */ /* 用于去除对 GLIBC 的高版本依赖,将高版本符号替换到低版本 */ #include <elf.h> #include <errno.h> #include <fcntl.h> #include <stdio.h> #include <string.h> #include <sys/mman.h> #include <sys/stat.h> #include <sys/types.h> #include <unistd.h> // ELF文件解析(二):ELF header详解 // https://www.cnblogs.com/jiqingwu/p/elf_explore_2.html // ELF文件格式 // https://www.cntofu.com/book/114/Theory/ELF.md // ELF文件格式解析(完) // https://www.52pojie.cn/thread-591986-1-1.html struct glibc_version_index { const char *version_str; // 标识版本的字符串 unsigned version_idx; // 记录这个版本的版本索引值 }; int process_elf(void *elf, size_t elf_sz) { // 获取 ELF文件头,在文件的开始,保存了路线图,描述了该文件的组织情况 Elf64_Ehdr *elf_hdr = elf; // 获取 节头表(Section header table) Elf64_Shdr *sh = (Elf64_Shdr *)((char *)elf + elf_hdr->e_shoff); // 获取 节头字符串表 Elf64_Shdr *sh_str = sh + elf_hdr->e_shstrndx; char *strtab = (char *)elf + sh_str->sh_offset; // 指向 .dynsym 表位置 Elf64_Shdr *sh_dynsym = 0; Elf64_Sym *dynsym = 0; // 指向符号信息表 // 指向 .dynstr 表位置 Elf64_Shdr *sh_dynstr = 0; char *dynstr = 0; // 指向 .gnu.version 表位置 Elf64_Shdr *sh_version = 0; unsigned short *versions = 0; // 版本索引值列表 // 指向 .gnu.version_r 表位置 Elf64_Shdr *sh_version_r = 0; Elf64_Verneed *verneed = 0; // 用于遍历版本依赖节表 unsigned i; // 遍历 节头 每一个 表 for (i = 1; i < elf_hdr->e_shnum; i++) { // 获取指针 Elf64_Shdr *this = sh + i; // 获取当前节名称 char *name = strtab + this->sh_name; // 判断是否是 .dynsym 表 // .dynsym表包含有关动态链接所需的所有符号的信息 // 该表中的某个位置隐藏了依赖于该新glibc的函数的名称 if (this->sh_type == SHT_DYNSYM && 0 == strcmp(name, ".dynsym")) { sh_dynsym = this; dynsym = (typeof(dynsym))((char *)elf + this->sh_offset); printf("找打 .dynsym section\n"); } // 判断是否是 .dynstr 表 // .dynstr 表包含实际版本的实际字符串(.gnu.version_r 仅是记录值) else if (this->sh_type == SHT_STRTAB && !strcmp(name, ".dynstr")) { sh_dynstr = this; dynstr = (typeof(dynstr))((char *)elf + this->sh_offset); printf("找到 .dynstr section\n"); } // 判断是否是 .gnu.version 表 // .gnu.version表它包含所有动态符号的版本信息 // .dynsym中列出的每个符号都会在此处有一个对应的条目 else if (this->sh_type == SHT_GNU_versym && !strcmp(name, ".gnu.version")) { sh_version = this; versions = (typeof(versions))((char *)elf + this->sh_offset); printf("找到 .gnu.version section\n"); } // 判断是否是 .gnu.version_r 表 // .gnu.version_r 表包含二进制文件所需的库版本(因此带有_r后缀) // 每个条目都显示版本名称(GLIBC_2.2.5,GLIBC_2.14等),并在其末尾显示“版本号” //“版本”号用词不准确,实际上是其他表可以用来引用它的索引。 else if (this->sh_type == SHT_GNU_verneed && !strcmp(name, ".gnu.version_r")) { sh_version_r = this; verneed = (typeof(verneed))((char *)elf + this->sh_offset); printf("找到 .gnu.version_r section\n"); } } if (!sh_dynsym || !sh_dynstr || !sh_version || !sh_version_r) { fprintf(stderr, "没有找到有效表节\n"); return -1; } /* 记录 GLIBC_2.2.5 版本索引 */ struct glibc_version_index glibc_2_2_5 = {"GLIBC_2.2.5", -1U}; // 记录 高版本 在版本索引的位置 struct glibc_version_index glibc_highver_arr[] = { /*{"GLIBC_2.3", -1U}, {"GLIBC_2.3.2", -1U}, {"GLIBC_2.3.3", -1U}, {"GLIBC_2.3.4", -1U}, {"GLIBC_2.4", -1U}, {"GLIBC_2.5", -1U}, {"GLIBC_2.6", -1U}, {"GLIBC_2.7", -1U}, {"GLIBC_2.8", -1U}, {"GLIBC_2.9", -1U}, {"GLIBC_2.10", -1U}, {"GLIBC_2.11", -1U},*/ {"GLIBC_2.12", -1U}, {"GLIBC_2.13", -1U}, {"GLIBC_2.14", -1U}, {"GLIBC_2.15", -1U}, {"GLIBC_2.16", -1U}, {"GLIBC_2.17", -1U}, {"GLIBC_2.18", -1U}, {"GLIBC_2.22", -1U}, {"GLIBC_2.23", -1U}, {"GLIBC_2.24", -1U}, {"GLIBC_2.25", -1U}, {"GLIBC_2.26", -1U}, {"GLIBC_2.27", -1U}, {"GLIBC_2.28", -1U}, {"GLIBC_2.29", -1U}, {"GLIBC_2.30", -1U}, {"GLIBC_2.31", -1U}, {"GLIBC_2.32", -1U}, {"GLIBC_2.32", -1U}, {"GLIBC_2.34", -1U}, {"GLIBC_2.35", -1U}}; unsigned glibc_highver_count = sizeof(glibc_highver_arr)/sizeof(glibc_highver_arr[0]); /** * typedef struct { * Elf64_Half vn_version; // 此成员标识该结构的版本(0表示无效版本) * Elf64_Half vn_cnt; // Elf64_Vernaux 数组中的元素数目 * Elf64_Word vn_file; // 以空字符结尾的字符串的字符串表偏移,用于提供版本依赖性的文件名。 * // 此名称与文件中找到的 .dynamic 依赖项之一匹配。 * Elf64_Word vn_aux; // 字节偏移,范围从此 Elf64_Verneed 项的开头到关联文件依赖项所需的版本定义的 Elf64_Vernaux * // 数组。必须存在至少一种版本依赖性。也可以存在其他版本依赖性,具体数目由 vn_cnt 值表示。 * Elf64_Word vn_next; // 从此 Elf64_Verneed 项的开头到下一个 Elf64_Verneed 项的字节偏移 * } Elf64_Verneed; */ Elf64_Verneed *next_verneed; int last = 0; // 遍历 .gnu.version_r 表 for (; !last; verneed = next_verneed) { // 获取依赖的文件名 char *filename = dynstr + verneed->vn_file; // 获取下一个 表项 next_verneed = (typeof(next_verneed))((char *)verneed + verneed->vn_next); // 判断当前是否是最后一个表项了 last = verneed->vn_next == 0; // 依赖文件不是 libc.so.6,就跳过 if (strcmp(filename, "libc.so.6") && strcmp(filename, "libm.so.6")) { continue; } // 获取 Elf64_Vernaux 数组的结尾 char *end_of_naux = (char *)next_verneed; if (last) { end_of_naux = (char *)elf + sh_version_r->sh_offset + sh_version_r->sh_size; } /** * typedef struct { * Elf64_Word vna_hash; // 版本依赖性名称的散列值 * Elf64_Half vna_flags; // 版本依赖性特定信息(VER_FLG_WEAK[0x2]弱版本标识符) * Elf64_Half vna_other; // 目前未使用 * Elf64_Word vna_name; // 以空字符结尾的字符串的字符串表偏移,用于提供版本依赖性的名称。 * Elf64_Word vna_next; // 从此 Elf64_Vernaux 项的开头到下一个 Elf64_Vernaux 项的字节偏移 * } Elf64_Vernaux; */ // 获取 Elf64_Vernaux 数组的元素数,首个元素 unsigned cnt = verneed->vn_cnt; Elf64_Vernaux *naux = (typeof(naux))((char *)verneed + verneed->vn_aux); Elf64_Vernaux *next_naux; // 遍历 Elf64_Vernaux 数组(记录每一个依赖版本的信息) for (; cnt--; naux = next_naux) { char *name = dynstr + naux->vna_name; // GLIBC_xxx 字符串 // 指向下一个元素 next_naux = (typeof(next_naux))((char *)naux + naux->vna_next); printf("检查 %p %s %u\n", naux, name, naux->vna_next); // 如果是 GLIBC_2.2.5 记录下索引值 if (strcmp(name, glibc_2_2_5.version_str) == 0) { glibc_2_2_5.version_idx = naux->vna_other; continue; } // 判断是否在高版本列表里面 unsigned hveridx = 0; for (; hveridx < glibc_highver_count; ++hveridx) { if (strcmp(name, glibc_highver_arr[hveridx].version_str) == 0) { break; } } // 如果属于高版本中的一个 if (hveridx != glibc_highver_count) { // 记录下索引值 glibc_highver_arr[hveridx].version_idx = naux->vna_other; // 将整个 Elf64_Vernaux 数组当前项后面元素向前移动 // 也就是将当前项从数组中移除掉 if (cnt > 0 /*剩余未处理元素必须大于0,才需要*/ ) { memmove(naux, next_naux, end_of_naux - (char *)next_naux); } // 下一个指向当前,也就是前移一个元素(因为这个元素已经被覆盖了,或者就是最后一个) next_naux = naux; } } // 处理完 libc.so.6 就跳出 break; } if (glibc_2_2_5.version_idx == -1U) { fprintf(stderr, "无法找到 GLIBC_2.2.5 索引值\n"); return -1; } for (i = 0; i < glibc_highver_count; ++i) { if (glibc_highver_arr[i].version_idx != -1U) { printf("%s 索引值: %d\n", glibc_highver_arr[i].version_str, glibc_highver_arr[i].version_idx); } } // 找到并修补所有依赖 GLIBC_2.xx 高版本号的符号 for (i = 1; i < sh_version->sh_size / sizeof(unsigned short); i++) { unsigned short v = versions[i]; // 判断版本是否为高版本 unsigned hveridx = 0; for (; hveridx < glibc_highver_count; ++hveridx) { if (v == glibc_highver_arr[hveridx].version_idx) { break; } } // 不是就跳过 if (hveridx == glibc_highver_count) { continue; } // 修改版本到 2.2.5 printf(" 修改 '%s': %s(%u) -> %s(%u)\n", dynstr + dynsym[i].st_name, glibc_highver_arr[hveridx].version_str,glibc_highver_arr[hveridx].version_idx, glibc_2_2_5.version_str,glibc_2_2_5.version_idx); // 修改高版本的到低版本 versions[i] = glibc_2_2_5.version_idx; // 这里也可以写成 =0 (local defualt) } return 0; } int main(int argc, char **argv) { if (argc < 2) { fprintf(stderr, "用法: %s <filename>\n", argv[0]); return 1; } // 打开输入文件 int fd = open(argv[1], O_RDWR); if (0 > fd) { perror("打开文件失败"); return 1; } // 获取文件状态信息,主要是获取文件大小 struct stat st; if (0 > fstat(fd, &st)) { perror("获取文件状态失败"); close(fd); return 1; } // 进行内存映射文件,便于后面处理的时候直接操作 void *mem = mmap(0, st.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (mem == MAP_FAILED) { perror("内存映射文件失败"); close(fd); return 1; } close(fd); // 处理文件 process_elf(mem, st.st_size); // 将修改同步到文件 if (0 > msync(mem, st.st_size, MS_SYNC)) { perror("msync() failed"); return 1; } // 解除内存映射 munmap(mem, st.st_size); return 0; }
修改前代码在在 https://github.com/anight/patch-memcpy 里面,这里主要将原本仅仅对 GLIBC_2.14
的处理,修改为对更多版本的处理。
参考资料#
- linux 修改 elf 文件的dynamic linker 和 rpath
- Linux修改ELF解决glibc兼容性问题
- Linux下符号版本原理及实现
- glibc和Symbol Versioning和如何链接出低版本glibc可运行的程序
- GLIBC_2.14 兼容问题 version `GLIBC_2.14′ not found
- Linux下安装glibc-2.14,解决“`GLIBC_2.14' not found”问题
- linux系统各发行版本出厂时glibc的版本列表
- https://github.com/anight/patch-memcpy
- [ 链接程序和库指南 > 第 7 章 目标文件格式 > 文件格式 > 版本控制节 > 版本依赖性节]
- Linux下符号版本原理及实现
- 从gcc静态链接开始的讨论,学了好几手
- gcc/g++静态链接和动态链接解决glibc版本不兼容的问题
- ELF文件格式
- 谈谈Linux应用程序 ABI兼容性
- Linux compatibility matrix
- Multiple glibc libraries on a single host
- 同时使用多种版本的libc && 编译libc
- Linux C++ 应用二进制兼容实践
- 更改引用高版本glibc的程序到引用低版本的glibc
标签:
C/C++
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
2017-05-12 无法启动此程序,因为计算机中丢失 api-ms-win-crt-stdio-l1-1-0.dll 解决