Linux 下高版本glibc庫應用程序在低版本環(huán)境下運行方法(linux各版本對glibc版本兼容)
整個Linux底層基于gcc 編譯器編譯C和C 程序構造的,因此 gcc 通用c類庫是最基本,最重要的類庫,稱為glibc庫.不僅一般的應用程序,各種操作系統(tǒng)的命令行工具也是基glibc的構建的.
在部署C/C 應用時,經(jīng)常碰到這樣問題,就是開發(fā)環(huán)境 的glibc版本較高,但是實際部署的運行環(huán)境 有的glibc,版本高,有的glibc版 本低,強行運行會顯示如下提示,
./ResolverService: /lib/aarch64-linux-gnu/libc.so.6: version GLIBC_2.28' not found (required by /usr/lib/libQT5Network.so.5)
這就是因為運行環(huán)境 最高支持GLIBC 2.26,而程序是在2.31的環(huán)境 編譯的,這是一個非常常見的問題.如何解決?
高成本解決方案
升級系統(tǒng)glibc庫
最容易想到是升級系統(tǒng)glibc庫,這危險性相當于自己給自己做心臟手術,基本做到一半,所有系統(tǒng)工具都無法工作,導致系統(tǒng)崩掉,只能重裝系統(tǒng),所以嚴重不推薦.
使用虛擬機環(huán)境
在服務端部署經(jīng)常采用的辦法,就是在操作系統(tǒng)之上再運行一層虛擬機,比如 Docker,KVM,在虛擬機運行是統(tǒng)一glibc庫版 本,這個方法缺點就需要系統(tǒng)資源較多,安裝成本比較高.在嵌入式Linux下并不適用.
低成本解決辦法 — 修改應用程序
解決的思路就是讓應用程序 不去裝載默認glibc的入口程序 ,一般固定是在/lib目錄下,通常是名字是 ld-linux打頭動態(tài)庫文件.
轉而讓應用程序 去尋找用戶自己安裝的glibc目錄下的裝載程序.
這個方法只需在用gcc編譯應用程序,鏈接選項就可以一次性解決所有動態(tài)庫版本不符問題,而且哪怕你沒有源碼只有運行程序,也可以同樣用patchelf這個工具 給應用程序 打補丁即可.代價非常低.
比如我一個Qt應用程序 ,使用了幾十個Qt 的so動態(tài)庫,很多都 引用高版本的glibc函數(shù),只需要在編譯最終應用程序加一句就行.不需要重新編譯Qt類庫,是不是很方便?
步驟1:準備高版本的glibc
可以在安裝高版本的環(huán)境直接把文件拷出來即可,比如我ARM64板上glibc 2.31需要如下庫,其中 ld-linux-aarch64.so.1 就是入口可執(zhí)行,假設我把這一些文件放在 /home/hxy/glibc-2.31 目錄下.大體有如下文件
ld-2.31.so libc.so.6 libpthread.so.0ld-linux-aarch64.so.1 libdl-2.31.so libresolv-2.31.solibc-2.31.so libdl.a libresolv.alibc.a libdl.so libresolv.solibc_nonshared.a libdl.so.2 libresolv.so.2libcrypt.a libgcc_s.so.1 librt-2.31.solibcrypto.so.1.1 libm-2.31.so librt.alibcryptsetup.so.12 libm.a librt.solibcryptsetup.so.12.5.0 libm.so librt.so.1libcrypt.so libm.so.6 libstdc .so.6libcrypt.so.1 libpthread-2.31.so libstdc .so.6.0.28libcrypt.so.1.1.0 libpthread.alibc.so libpthread.so
找不到對應glibc動態(tài)庫,可以這個開源項目下 https://github.com/matrix1001/glibc-all-in-one 取得自己的想要的版本編譯一下.或者直接搜索相應的 deb安裝包
步驟2: 編譯時加入對另一版本glibc 的引用
需要增加兩個gcc 的鏈接參數(shù):
- 顯式鏈接指定動態(tài)庫加載器 -Wl,–dynamic-linker
顯式鏈接是相對用 -L -l c 隱式鏈接而言, 隱式鏈接就是編譯后的應用程序ELF可執(zhí)行文件只保存鏈接庫名字,不帶路徑,加徑順序按缺省規(guī)則來進行. 一般是 /lib –> ld.so.conf–>LD_LIBRARY_PATH 這樣順序進行.而使用–dynamic-linker表示這是帶路徑動態(tài)庫加載器直接裝入即可.
在我的例子是,這樣就跳開了/lib下同名動態(tài)glibc入口的引用,直接引用我的目錄名字
-Wl,--dynamic-linker=/home/hxy/glibc-2.31/ld-linux-aarch64.so.1
2.指定動態(tài)庫的裝載目錄 -Wl,–rpath
相當其它未帶路徑so,優(yōu)先從這個目錄裝入,這一句也很重要因為ld-linux-xxx.so動態(tài)庫需要裝入同目錄下其它glibc的動態(tài),因此必須設置這個目錄,否則又去裝入/lib的動態(tài)庫導致運行失敗
-Wl,--rpath=/home/hxy/glibc-2.31/
以上兩句是要同時放在一個鏈接語句當中,順便提一下,如果你是Linux 下Qt 項目,只需在pro文件增加一句QMAKE_LFLAGS的定義即可在最終的編譯語句達到這樣效果
QMAKE_LFLAGS = -Wl,--dynamic-linker=/home/hxy/glibc-2.31/ld-linux-aarch64.so.1 -Wl,--rpath=/home/hxy/glibc-2.31/
編譯好在目標主機上也建立一個/home/hxy/glibc-2.31 并放入動態(tài)庫.即可運行
補充步驟2: 只有可執(zhí)行文件 ,用patchelf 打補丁
當沒有源碼編譯或者編譯很麻煩的情況,可以直接對可執(zhí)行elf文件打補丁加上上述兩個參數(shù),效果一樣的 ,首先安裝patchelf
sudo apt-get install patchelf
elf 的interpreter字段是指明動態(tài)加載器路徑,修改這個字段的作用等同于 -Wl,–dynamic-linker
rpath字段就是對應-Wl,–rpath的內(nèi)容,假設應用程序名叫ResolverService,補丁指令如下
patchelf –set-interpreter /home/firefly/glibc-2.31/ld-linux-aarch64.so.1
–set-rpath /home/firefly/glibc-2.31 ResolverService?
以上兩種方法bluedrum即在RK3399的高版本環(huán)境運行成功,同時又能切換到RK3308低版 本下環(huán)境 運行.