<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Linux 修改 ELF 解決 glibc 兼容性問題

          共 11653字,需瀏覽 24分鐘

           ·

          2021-02-03 08:00

          轉自:Soul Of Free Loop

          https://zohead.com/archives/mod-elf-glibc/

          Linux glibc 問題

          相信有不少 Linux 用戶都碰到過運行第三方(非系統(tǒng)自帶軟件源)發(fā)布的程序時的 glibc 兼容性問題,這一般是由于當前 Linux 系統(tǒng)上的 GNU C 庫(glibc)版本比較老導致的,例如我在 CentOS 6 64 位系統(tǒng)上運行某第三方閉源軟件時會報:

          [root@centos6-dev?~]#?ldd?tester
          ./tester:?/lib64/libc.so.6:?version?`GLIBC_2.17'?not?found?(required?by?./tester)
          ./tester:?/lib64/libc.so.6:?version?`GLIBC_2.14'
          ?not?found?(required?by?./tester)????????
          linux-vdso.so.1?=>??(0x00007ffe795fe000)????????
          libpthread.so.0?=>?/lib64/libpthread.so.0?(0x00007fc7d4c73000)????????
          libOpenCL.so.1?=>?/usr/lib64/libOpenCL.so.1?(0x00007fc7d4a55000)????????
          libdl.so.2?=>?/lib64/libdl.so.2?(0x00007fc7d4851000)???????
          libm.so.6?=>?/lib64/libm.so.6?(0x00007fc7d45cd000)????????
          libgcc_s.so.1?=>?/lib64/libgcc_s.so.1?(0x00007fc7d43b7000)????????
          libc.so.6?=>?/lib64/libc.so.6?(0x00007fc7d4023000)????????
          /lib64/ld-linux-x86-64.so.2?(0x00007fc7d4e90000)

          CentOS 6 自帶的 glibc 還是很老的 2.12 版本,而下載的第三方程序依賴 glibc 2.17 版本,這種情況要么自己重新編譯程序,要么只能升級系統(tǒng)的 glibc 版本。由于我使用的程序是第三方編寫并且是閉源軟件無法自己編譯,升級 glibc 固然可能能解決問題,但是 glibc 做為最核心的基礎庫,在生產(chǎn)環(huán)境上直接升級畢竟動作還是太大,因此希望還是能有更好的解決途徑。

          問題分析

          首先我們可以檢查一下程序使用了新版本 glibc 的哪些符號,使用 objdump 命令可以查看 ELF 文件的動態(tài)符號信息:

          [root@centos6-dev?~]#?objdump?-T?tester?|?grep?GLIBC_2.1.*
          0000000000000000??????DF?*UND*??0000000000000000??GLIBC_2.14??memcpy
          0000000000000000??????DF?*UND*??0000000000000000??GLIBC_2.17??clock_gettime

          從上面的輸出可以看到程序使用了 glibc 2.14 版本的 memcpy 函數(shù)和 glibc 2.17 版本的 clock_gettime 函數(shù),而這兩個常用的函數(shù)按說應該是 glibc 很早就已經(jīng)支持了的,我們可以確認一下當前系統(tǒng) glibc 提供的符號版本:

          [root@centos6-dev?~]#?objdump?-T?/lib64/libc.so.6?|?grep?memcpy
          0000000000091300??w???DF?.text??0000000000000009??GLIBC_2.2.5?wmemcpy
          0000000000101070?g????DF?.text??000000000000001b??GLIBC_2.4???__wmemcpy_chk
          00000000000896b0?g????DF?.text??0000000000000465??GLIBC_2.2.5?memcpy
          00000000000896a0?g????DF?.text??0000000000000009??GLIBC_2.3.4?__memcpy_chk
          [root@centos6-dev?~]#?objdump?-T?/lib64/libc.so.6?|?grep?clock_gettime
          000000000038f800?g????DO?.bss???0000000000000008??GLIBC_PRIVATE?__vdso_clock_gettime

          這里可以看出 CentOS 6 的 glibc 庫提供的 memcpy 實現(xiàn)是 2.2.5 版本的,另外 libc 沒有直接實現(xiàn) clock_gettime 函數(shù),因為老版本 glibc 里 clock_gettime 是由 librt 庫提供 clock_gettime 支持的,而且同樣也是 2.2.5 版本:

          [root@centos6-dev?~]#?objdump?-T?/lib64/librt.so.1?|?grep?clock_gettime
          0000000000000000??????DO?*UND*??0000000000000000??GLIBC_PRIVATE?__vdso_clock_gettime
          0000000000003e70?g????DF?.text??000000000000008b??GLIBC_2.2.5?clock_gettime

          看過這里就基本明白了,第三方程序的開發(fā)者是在自帶新版本 glibc 的 Linux 系統(tǒng)上編譯的,memcpy 和 clock_gettime 的實現(xiàn)默認使用了該系統(tǒng)上 glibc 所提供的最新版本,這樣在低版本 glibc 系統(tǒng)中就無法正常運行。

          解決方法

          雖然我們無法重新編譯第三方程序,但如果可以修改 ELF 文件強制讓 LD 庫加載程序時使用老版本的 memcpy 和 clock_gettime 實現(xiàn),應該就可以避免升級 glibc。

          分析 ELF

          首先用 readelf 命令查看 ELF 的符號表,由于該命令輸出非常多,這里只貼出我們關心的信息:

          [root@centos6-dev?~]#?readelf?-sV?tester
          Symbol?table?'.dynsym'?contains?4583?entries:???
          Num:????Value??????????Size?Type????Bind???Vis??????Ndx?Name?????
          0:?0000000000000000?????0?NOTYPE??LOCAL??DEFAULT??UND????
          ......????
          11:?0000000000000000?????0?FUNC????GLOBAL?DEFAULT??UND?memcpy@GLIBC_2.14?(5)????
          ......????
          67:?0000000000000000?????0?FUNC????GLOBAL?DEFAULT??UND?clock_gettime@GLIBC_2.17?(16)????
          ......??
          4582:?0000000000794260????70?FUNC????WEAK???DEFAULT???12?_ZNSt15basic_streambufIwS
          Version?symbols?section?'.gnu.version'?contains?4583?entries:?
          Addr:?000000000045b508??Offset:?0x05b508??Link:?4?(.dynsym)??
          000:???0?(*local*)???????0?(*local*)???????2?(GLIBC_2.2.5)???3?(GLIBC_2.2.5)??
          004:???3?(GLIBC_2.2.5)???3?(GLIBC_2.2.5)???3?(GLIBC_2.2.5)???3?(GLIBC_2.2.5)??
          008:???4?(GLIBC_2.3.2)???3?(GLIBC_2.2.5)???0?(*local*)???????5?(GLIBC_2.14)??
          ......??
          040:???2?(GLIBC_2.2.5)???3?(GLIBC_2.2.5)???3?(GLIBC_2.2.5)??10?(GLIBC_2.17)??
          ......??
          11e0:???1?(*global*)??????1?(*global*)??????1?(*global*)??????1?(*global*)??11e4:???1?(*global*)??????1?(*global*)??????1?(*global*)

          Version?needs?section?'.gnu.version_r'?contains?6?entries:?
          Addr:?0x000000000045d8d8??Offset:?0x05d8d8??Link:?5?(.dynstr)??
          000000:?Version:?1??File:?ld-linux-x86-64.so.2??Cnt:?1??
          0x0010:???Name:?GLIBC_2.3??Flags:?none??Version:?17??
          0x0020:?Version:?1??File:?libgcc_s.so.1??Cnt:?3??
          0x0030:???Name:?GCC_3.0??Flags:?none??Version:?13??
          0x0040:???Name:?GCC_3.3??Flags:?none??Version:?11??
          0x0050:???Name:?GCC_4.2.0??Flags:?none??Version:?10??
          0x0060:?Version:?1??File:?libm.so.6??Cnt:?1??
          0x0070:???Name:?GLIBC_2.2.5??Flags:?none??Version:?8??
          0x0080:?Version:?1??File:?libpthread.so.0??Cnt:?2??
          0x0090:???Name:?GLIBC_2.3.2??Flags:?none??Version:?15??
          0x00a0:???Name:?GLIBC_2.2.5??Flags:?none??Version:?7??
          0x00b0:?Version:?1??File:?libc.so.6??Cnt:?10??
          0x00c0:???Name:?GLIBC_2.8??Flags:?none??Version:?19??
          0x00d0:???Name:?GLIBC_2.9??Flags:?none??Version:?18??
          0x00e0:???Name:?GLIBC_2.17??Flags:?none??Version:?16??
          0x00f0:???Name:?GLIBC_2.4??Flags:?none??Version:?14??
          0x0100:???Name:?GLIBC_2.3.4??Flags:?none??Version:?12??
          0x0110:???Name:?GLIBC_2.3??Flags:?none??Version:?9??
          0x0120:???Name:?GLIBC_2.7??Flags:?none??Version:?6??
          0x0130:???Name:?GLIBC_2.14??Flags:?none??Version:?5??
          0x0140:???Name:?GLIBC_2.3.2??Flags:?none??Version:?4??
          0x0150:???Name:?GLIBC_2.2.5??Flags:?none??Version:?3?
          0x0160:???Version:?1??File:?libdl.so.2??Cnt:?1??
          0x0170:???Name:?GLIBC_2.2.5??Flags:?none??Version:?2

          我們可以在 ELF 的 .dynsym 動態(tài)符號表中看到程序用于動態(tài)鏈接的所有導入導出符號,memcpy 和 clock_gettime 后面括號里的數(shù)字就是十進制的版本號(分別為 5 和 16),而我們需要格外關注的是下面的 .gnu.version 和 .gnu.version_r 符號版本信息段。

          .gnu.version 表包含所有動態(tài)符號的版本信息,.dynsym 動態(tài)符號表中的每個符號都可以在 .gnu.version 中看到對應的條目(.dynsym 中一共 4583 個符號剛好與 .gnu.version 的結束位置 0x11e7 相等)。

          從上面的輸出可以看到 .gnu.version 表從 0x05b508 偏移量開始,我們可以看看對應偏移量的十六進制數(shù)據(jù):

          Offset(h)?00?01?02?03?04?05?06?07?08?09?0A?0B?0C?0D?0E?0F
          0005B500??????????????????????????00?00?00?00?02?00?03?00??????????........
          0005B510??03?00?03?00?03?00?03?00?04?00?03?00?00?00?05?00??................
          0005B520??03?00?03?00?06?00?00?00?03?00?07?00?08?00?08?00??................
          0005B530??03?00?09?00?03?00?03?00?0A?00?07?00?03?00?00?00??................
          0005B540??03?00?03?00?0B?00?07?00?03?00?03?00?00?00?07?00??................
          0005B550??00?00?03?00?03?00?03?00?03?00?0C?00?09?00?00?00??................
          0005B560??07?00?03?00?03?00?07?00?03?00?07?00?0C?00?00?00??................
          0005B570??0D?00?03?00?07?00?07?00?0E?00?0F?00?03?00?0D?00??................
          0005B580??03?00?03?00?03?00?03?00?02?00?03?00?03?00?10?00??................
          0005B590??03?00?00?00?03?00?07?00?08?00?07?00?07?00?03?00??................
          0005B5A0??03?00?0D?00?03?00?00?00?03?00?03?00?03?00?00?00??................

          .gnu.version 中的每個條目占用兩個字節(jié),其值為符號的版本,由此可以看到其中第 0x0b 個符號(也就是 .dynsym 表中的 memcpy@GLIBC_2.14 符號)的偏移量即為 0x05b51e(0x05b508 + 0x0b x 2),該偏移量的值 0x0005 也剛好和 .dynsym 表中的值對應,當然 clock_gettime 符號對應的偏移量 0x05b58e 的值 0x0010 同樣也是如此。

          下面關鍵的 .gnu.version_r 表示二進制程序實際依賴的庫文件版本,從輸出中也能看到 .gnu.version_r 表是按照不同的庫文件進行分段顯示的,每個條目占用 0x10 也就是 16 個字節(jié),該表是從 0x05d8d8 偏移量開始,我們看看 GLIBC_2.17 也就是 0x05d9b8 處的十六進制數(shù)據(jù):

          Offset(h)?00?01?02?03?04?05?06?07?08?09?0A?0B?0C?0D?0E?0F
          0005D9B0??B0?70?03?00?10?00?00?00?97?91?96?06?00?00?10?00??°p......—‘–.....
          0005D9C0??BA?70?03?00?10?00?00?00?14?69?69?0D?00?00?0E?00??op.......ii.....
          0005D9D0??C5?70?03?00?10?00?00?00?74?19?69?09?00?00?0C?00???p......t.i.....
          0005D9E0??CF?70?03?00?10?00?00?00?13?69?69?0D?00?00?09?00???p.......ii.....
          0005D9F0??6A?70?03?00?10?00?00?00?17?69?69?0D?00?00?06?00??jp.......ii.....
          0005DA00??DB?70?03?00?10?00?00?00?94?91?96?06?00?00?05?00???p......”‘–.....
          0005DA10??E5?70?03?00?10?00?00?00?72?19?69?09?00?00?04?00???p......r.i.....
          0005DA20??9A?70?03?00?10?00?00?00?75?1A?69?09?00?00?03?00???p......u.i.....
          0005DA30??8E?70?03?00?00?00?00?00?01?00?01?00?D8?03?00?00???p..........?...

          .gnu.version_r 表中每個條目是 16 個字節(jié)的 Elfxx_Vernaux 結構體,其聲明如下(Elfxx_Half 占用 2 個字節(jié),Elfxx_Word 占用 4 個字節(jié)):

          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 個字節(jié)的庫名稱(也就是上面的 GLIBC_2.17 字符串)的 hash 值,vna_other 為對應的 .gnu.version 表中符號的版本值,vna_name 指向庫名稱字符串的偏移量(也可以在 ELF 頭中找到),vna_next 為下一個條目的位置(一般固定為 0x00000010)。

          由上面的輸出我們可以看到 GLIBC_2.17 對應的 0x05d9b8 處的開始的 4 個字節(jié) vna_hash hash 值為 0x06969197,而 vna_other 的值 0x0010(輸出里的 Version: 16)也與 .gnu.version 中 clock_gettime 符號的值一致。同樣 GLIBC_2.14 也與 memcpy 符號的值相符。

          修改 ELF 符號表

          由于 Linux 系統(tǒng)中的 LD 庫(也就是 /lib64/ld-linux-x86-64.so.2 庫)加載 ELF 時檢查 .gnu.version_r 表中的符號,我們可以使用任何一款十六進制編輯器來修改 .gnu.version_r 表中的符號值來強制使用老版本的函數(shù)實現(xiàn)。

          首先我們發(fā)現(xiàn) .gnu.version_r 的 libc.so.6 段下面有 10 個條目,最后一個則是我們需要的 GLIBC_2.2.5 版本的符號(從上面的十六進制輸出中我們可以看到該符號的偏移量為 0x05da28,vna_hash 值為 0x09691A75,vna_other 版本值為 0x0003,vna_name 字符串名稱指向 0003708E 地址),因為這樣我們才可以在不修改 ELF 文件大小的前提下直接將 libc.so.6 段下的其它高版本條目指向老版本條目的值。

          例如 GLIBC_2.17 對應的 0x05d9b8 偏移量,我們可以直接將 vna_hash 值改為 GLIBC_2.2.5 的 0x09691A75 值,將 vna_other 改為 0003708E 值,為了保持和 .gnu.version 表中的版本值一致,這里我們就不修改 vna_other 值了。

          對于 GLIBC_2.14 偏移量我們也修改成同樣的值,修改保存之后的 ELF 文件再使用 readelf 命令檢查就能看到變化了(只列出了修改的 .gnu.version-r 表):

          [root@centos6-dev?~]#?readelf?-sV?tester
          ......
          Version?needs?section?'.gnu.version_r'?contains?6?entries:?
          Addr:?0x000000000045d8d8??Offset:?0x05e8d8??Link:?2?(.dynstr)??
          000000:?Version:?1??File:?ld-linux-x86-64.so.2??Cnt:?1??
          0x0010:???Name:?GLIBC_2.3??Flags:?none??Version:?17??
          0x0020:?Version:?1??File:?libgcc_s.so.1??Cnt:?3??
          0x0030:???Name:?GCC_3.0??Flags:?none??Version:?13??
          0x0040:???Name:?GCC_3.3??Flags:?none??Version:?11?
          0x0050:???Name:?GCC_4.2.0??Flags:?none??Version:?10??
          0x0060:?Version:?1??File:?libm.so.6??Cnt:?1??
          0x0070:???Name:?GLIBC_2.2.5??Flags:?none??Version:?8??
          0x0080:?Version:?1??File:?libpthread.so.0??Cnt:?2??
          0x0090:???Name:?GLIBC_2.3.2??Flags:?none??Version:?15??
          0x00a0:???Name:?GLIBC_2.2.5??Flags:?none??Version:?7??
          0x00b0:?Version:?1??File:?libc.so.6??Cnt:?10??
          0x00c0:???Name:?GLIBC_2.8??Flags:?none??Version:?19??
          0x00d0:???Name:?GLIBC_2.9??Flags:?none??Version:?18??
          0x00e0:???Name:?GLIBC_2.2.5??Flags:?none??Version:?16??
          0x00f0:???Name:?GLIBC_2.4??Flags:?none??Version:?14??
          0x0100:???Name:?GLIBC_2.3.4??Flags:?none??Version:?12??
          0x0110:???Name:?GLIBC_2.3??Flags:?none??Version:?9??
          0x0120:???Name:?GLIBC_2.7??Flags:?none??Version:?6??
          0x0130:???Name:?GLIBC_2.2.5??Flags:?none??Version:?5??
          0x0140:???Name:?GLIBC_2.3.2??Flags:?none??Version:?4??
          0x0150:???Name:?GLIBC_2.2.5??Flags:?none??Version:?3??
          0x0160:?Version:?1??File:?libdl.so.2??Cnt:?1??
          0x0170:???Name:?GLIBC_2.2.5??Flags:?none??Version:?2

          patchelf 修改 ELF 文件

          一般的程序如果只使用了高版本 memcpy 的話,一般這樣修改之后程序就可以運行了。但不巧我使用的第三方程序還使用了高版本 glibc 中的 clock_gettime,只是這樣修改的話由于 CentOS 6 的 libc 2.12 庫并沒有提供 clock_gettime,運行時還是會報錯。

          這個時候我們就需要請出大殺器 PatchELF 了,這個小工具由 NixOS 團隊開發(fā),可以直接增加、刪除、替換 ELF 文件依賴的庫文件,使用起來也非常簡單。

          檢出 PatchELF 的源代碼,按照 GitHub 倉庫上介紹的步驟編譯安裝就可以使用了(一般發(fā)行版自帶的 patchelf 工具版本較老不支持一些新的功能)。

          雖然 CentOS 6 的 libc 庫沒有提供 clock_gettime 實現(xiàn),但好在 glibc 自帶的 librt 庫里還是提供了的,因此我們可以使用 patchelf 工具修改原版的程序文件,讓程序優(yōu)先加載 librt 庫,這樣程序就能正確加載 clock_gettime 符號了:

          [root@centos6-dev?~]#?patchelf?--add-needed?librt.so.1?tester

          然后按照上面介紹的方法用十六進制編輯器修改新生成的 ELF 文件的 .gnu.version_r 表(因為 patchelf 運行之后新 ELF 文件的符號表就和之前的不一樣了),將 GLIBC_2.17 和 GLIBC_2.14 統(tǒng)一改為 GLIBC_2.2.5 符號,保存 ELF 文件之后就可以看到效果了:

          [root@centos6-dev?~]#?ldd?tester????????
          linux-vdso.so.1?=>??(0x00007fffc17ee000)????????
          librt.so.1?=>?/lib64/librt.so.1?(0x00007f7f84dca000)????????
          libpthread.so.0?=>?/lib64/libpthread.so.0?(0x00007f7f84bad000)????????
          libOpenCL.so.1?=>?/usr/lib64/libOpenCL.so.1?(0x00007f7f8498f000)????????
          libdl.so.2?=>?/lib64/libdl.so.2?(0x00007f7f8478b000)????????
          libm.so.6?=>?/lib64/libm.so.6?(0x00007f7f84507000)????????
          libgcc_s.so.1?=>?/lib64/libgcc_s.so.1?(0x00007f7f842f1000)????????
          libc.so.6?=>?/lib64/libc.so.6?(0x00007f7f83f5d000)????????
          /lib64/ld-linux-x86-64.so.2?(0x00007f7f84fd2000)

          從 ldd 命令的輸出中可以看到修改后的程序會加載 librt 庫,而且也沒有 glibc 版本的報錯了,經(jīng)過測試程序運行起來也沒有問題了。



          推薦閱讀:
          專輯|Linux文章匯總
          專輯|程序人生
          專輯|C語言
          我的知識小密圈

          關注公眾號,后臺回復「1024」獲取學習資料網(wǎng)盤鏈接。

          歡迎點贊,關注,轉發(fā),在看,您的每一次鼓勵,我都將銘記于心~



          瀏覽 41
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  免费无码毛片 | 亚洲人色| 成人99视频 | 国产成人免费在线观看 | 黑人大屌轮奸视频播放免费成人 |