glibc 版本(version `GLIBC_2.14' not found)問題


簡述

很多時候,沒法使用高版本系統,或者升級 glibc 版本,導致很多兼容性問題。這類的答案網上有很多,給出的解決方案也不少,這里做個簡單的記錄,方便參考。

大致來說,有這么幾種方式:

  1. 在低版本環境下編譯,在高版本環境下使用。(比如在 centos 6 上使用 gcc 編譯的程序,可以跑在 ubuntu 18.04)
  2. 使用靜態鏈接 libclibstd++ )的方式。(依賴庫太多的時候,蠻麻煩的,也要考慮glibc對內核版本的要求)
  3. 符號替換,使用低版本符號替換編譯環境下的高版本符號,或使用鏈接器的wrap選項實現 libc 函數的包裝。
  4. 發布程序自帶 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}};
    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")) {
      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 的處理,修改為對更多版本的處理。

參考資料


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM