你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

Linux glibc 问题

[复制链接]
gaosmile 发布时间:2020-12-5 22:53
Linux glibc 问题
9 M2 y: _% h& [6 x1 @/ U* n
相信有不少 Linux 用户都碰到过运行第三方(非系统自带软件源)发布的程序时的 glibc 兼容性问题,这一般是由于当前 Linux 系统上的 GNU C 库(glibc)版本比较老导致的,例如我在 CentOS 6 64 位系统上运行某第三方闭源软件时会报:

  1. , R( h8 U1 K' M% N! G& b3 m6 G7 V
  2. [root@centos6-dev ~]# ldd tester- x+ L5 C0 S  q9 r7 h
  3. ./tester: /lib64/libc.so.6: version `GLIBC_2.17' not found (required by ./tester)$ O/ C& x) y& P7 e0 A
  4. ./tester: /lib64/libc.so.6: version `GLIBC_2.14' not found (required by ./tester)
    3 R7 y. n# K$ x$ w& u9 a; s
  5.         linux-vdso.so.1 =>  (0x00007ffe795fe000)5 v1 |! R) q8 X0 x( k; f5 ]# g
  6.         libpthread.so.0 => /lib64/libpthread.so.0 (0x00007fc7d4c73000)
    % O% E, P6 w+ M5 {
  7.         libOpenCL.so.1 => /usr/lib64/libOpenCL.so.1 (0x00007fc7d4a55000)
    & Z" V2 f: q! d+ s' @$ ]
  8.         libdl.so.2 => /lib64/libdl.so.2 (0x00007fc7d4851000)" F! I2 f8 p; |6 ?  [# V
  9.         libm.so.6 => /lib64/libm.so.6 (0x00007fc7d45cd000)
    8 b0 r1 Z, n; {
  10.         libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00007fc7d43b7000)
    * s# `+ p) y9 w2 r  ?  c0 [$ t& T
  11.         libc.so.6 => /lib64/libc.so.6 (0x00007fc7d4023000)0 l0 k. w" X$ u, R- y, J
  12.         /lib64/ld-linux-x86-64.so.2 (0x00007fc7d4e90000)
复制代码

# ]4 E, l" o# B  g# }7 N  [
, [* s( c, C' T4 Z% V! n
CentOS 6 自带的 glibc 还是很老的 2.12 版本,而下载的第三方程序依赖 glibc 2.17 版本,这种情况要么自己重新编译程序,要么只能升级系统的 glibc 版本。由于我使用的程序是第三方编写并且是闭源软件无法自己编译,升级 glibc 固然可能能解决问题,但是 glibc 做为最核心的基础库,在生产环境上直接升级毕竟动作还是太大,因此希望还是能有更好的解决途径。
+ M0 l( l$ x. p5 n0 V# w7 y- d) `
问题分析

8 S9 S+ c# X3 P  l- b) o2 d
首先我们可以检查一下程序使用了新版本 glibc 的哪些符号,使用 objdump 命令可以查看 ELF 文件的动态符号信息:

  1. 2 T7 W5 f. f( d! a# D
  2. [root@centos6-dev ~]# objdump -T tester | grep GLIBC_2.1.*2 [0 S% D+ U5 Z$ M. ?9 S; n
  3. 0000000000000000      DF *UND*  0000000000000000  GLIBC_2.14  memcpy
    * }! ]8 n2 }) }! N
  4. 0000000000000000      DF *UND*  0000000000000000  GLIBC_2.17  clock_gettime
复制代码
% u2 }3 a5 h8 N; C2 ^
从上面的输出可以看到程序使用了 glibc 2.14 版本的 memcpy 函数和 glibc 2.17 版本的 clock_gettime 函数,而这两个常用的函数按说应该是 glibc 很早就已经支持了的,我们可以确认一下当前系统 glibc 提供的符号版本:
  1. 0 |: k6 A! ^9 d) o. H
  2. [root@centos6-dev ~]# objdump -T /lib64/libc.so.6 | grep memcpy
    2 _+ Z# E9 t7 R
  3. 0000000000091300  w   DF .text  0000000000000009  GLIBC_2.2.5 wmemcpy7 v; w7 P( y7 F& N) G, O
  4. 0000000000101070 g    DF .text  000000000000001b  GLIBC_2.4   __wmemcpy_chk
    8 ]- E0 h. c9 k  Q* L2 `( _% o; S5 o
  5. 00000000000896b0 g    DF .text  0000000000000465  GLIBC_2.2.5 memcpy' S( ^0 l. u3 ^5 a' e' o' r
  6. 00000000000896a0 g    DF .text  0000000000000009  GLIBC_2.3.4 __memcpy_chk% @* Z+ w5 O* c  h
  7. [root@centos6-dev ~]# objdump -T /lib64/libc.so.6 | grep clock_gettime
    ' d: a0 r# l" |6 h8 S) ]. D* `4 f  _  }
  8. 000000000038f800 g    DO .bss   0000000000000008  GLIBC_PRIVATE __vdso_clock_gettime
复制代码
1 U0 g& c9 R2 T, ^! s
这里可以看出 CentOS 6 的 glibc 库提供的 memcpy 实现是 2.2.5 版本的,另外 libc 没有直接实现 clock_gettime 函数,因为老版本 glibc 里 clock_gettime 是由 librt 库提供 clock_gettime 支持的,而且同样也是 2.2.5 版本:

  1. 8 X2 z9 \' T' P7 d/ Q
  2. [root@centos6-dev ~]# objdump -T /lib64/librt.so.1 | grep clock_gettime+ K. @' h- i. Y4 _9 S. W
  3. 0000000000000000      DO *UND*  0000000000000000  GLIBC_PRIVATE __vdso_clock_gettime
    3 u! Y3 G8 D1 g2 K! z( A/ I
  4. 0000000000003e70 g    DF .text  000000000000008b  GLIBC_2.2.5 clock_gettime
复制代码
: X. `/ p, m0 z4 D
看过这里就基本明白了,第三方程序的开发者是在自带新版本 glibc 的 Linux 系统上编译的,memcpy 和 clock_gettime 的实现默认使用了该系统上 glibc 所提供的最新版本,这样在低版本 glibc 系统中就无法正常运行。

6 ?+ b5 e! v8 p# ~; }7 O" c
解决方法

! v! r% ~3 J5 B3 W
虽然我们无法重新编译第三方程序,但如果可以修改 ELF 文件强制让 LD 库加载程序时使用老版本的 memcpy 和 clock_gettime 实现,应该就可以避免升级 glibc。

+ r) o7 W5 u8 G4 d0 n0 |: y+ u/ w
分析 ELF

) J0 x8 T  L' M
首先用 readelf 命令查看 ELF 的符号表,由于该命令输出非常多,这里只贴出我们关心的信息:
  1. 9 L2 A. j! p2 K/ |5 I+ K
  2. [root@centos6-dev ~]# readelf -sV tester
    3 E5 t$ M8 L/ V9 }2 k
  3. Symbol table '.dynsym' contains 4583 entries:
    ; y- p; C* U. p: d
  4.    Num:    Value          Size Type    Bind   Vis      Ndx Name1 @. b1 b; ]" w  X
  5.      0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND$ @( ]5 B) k  x
  6.     ......
    9 Z, ?: z4 C7 t' i9 V
  7.     11: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND memcpy@GLIBC_2.14 (5)
    + }; @0 g& U! H" E4 A, H
  8.     ......
    " K" R! m# _( O0 a0 s
  9.     67: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND clock_gettime@GLIBC_2.17 (16)+ O9 j. H: W* c8 ^" f
  10.     ......
    " u2 @$ K+ W- G
  11.   4582: 0000000000794260    70 FUNC    WEAK   DEFAULT   12 _ZNSt15basic_streambufIwS
    9 w# X5 _& }! a" O0 `) p

  12. $ O. g, Y; Z3 O  h9 {
  13. Version symbols section '.gnu.version' contains 4583 entries:/ X  f- X; m, K7 z. b
  14. Addr: 000000000045b508  Offset: 0x05b508  Link: 4 (.dynsym)
    / _1 F/ B/ d, Z
  15.   000:   0 (*local*)       0 (*local*)       2 (GLIBC_2.2.5)   3 (GLIBC_2.2.5)2 }4 Z8 t5 n  L7 P# S
  16.   004:   3 (GLIBC_2.2.5)   3 (GLIBC_2.2.5)   3 (GLIBC_2.2.5)   3 (GLIBC_2.2.5)
    3 |3 G' p8 R# }
  17.   008:   4 (GLIBC_2.3.2)   3 (GLIBC_2.2.5)   0 (*local*)       5 (GLIBC_2.14)
    7 V2 i( e3 r& k
  18.   ......
    5 i  b2 x& \; ]
  19.   040:   2 (GLIBC_2.2.5)   3 (GLIBC_2.2.5)   3 (GLIBC_2.2.5)  10 (GLIBC_2.17)5 D8 D& b. a4 g
  20.   ......
    ; r2 l6 }4 H7 F* }4 E  r! y, R6 B
  21.   11e0:   1 (*global*)      1 (*global*)      1 (*global*)      1 (*global*)( k3 o: p. d) N$ U2 V
  22.   11e4:   1 (*global*)      1 (*global*)      1 (*global*)
    6 @* Z$ v6 t4 e* |

  23. + L6 b  Q- X5 p4 ~' _9 i
  24. Version needs section '.gnu.version_r' contains 6 entries:  i/ E8 b7 K& q- u. I
  25. Addr: 0x000000000045d8d8  Offset: 0x05d8d8  Link: 5 (.dynstr)4 C" @7 \# b! a) R, j9 f2 k9 R
  26.   000000: Version: 1  File: ld-linux-x86-64.so.2  Cnt: 1
    * h+ a8 @/ f" P
  27.   0x0010:   Name: GLIBC_2.3  Flags: none  Version: 17- k1 K( i" V! X/ `: o) S
  28.   0x0020: Version: 1  File: libgcc_s.so.1  Cnt: 33 Q7 I  c5 S' j8 x8 }* {5 t
  29.   0x0030:   Name: GCC_3.0  Flags: none  Version: 13' F0 w0 h1 [: R' T5 C
  30.   0x0040:   Name: GCC_3.3  Flags: none  Version: 110 {6 y  R& N* }0 W9 `" f: p) W2 _
  31.   0x0050:   Name: GCC_4.2.0  Flags: none  Version: 10, f- y2 [. C4 \3 h" h% j! J' Z
  32.   0x0060: Version: 1  File: libm.so.6  Cnt: 1  z7 e3 c, \! Z
  33.   0x0070:   Name: GLIBC_2.2.5  Flags: none  Version: 8; J4 z/ S5 B) H/ M
  34.   0x0080: Version: 1  File: libpthread.so.0  Cnt: 29 K2 m7 |/ n, W4 t, l$ o4 F
  35.   0x0090:   Name: GLIBC_2.3.2  Flags: none  Version: 156 w5 T3 \; g8 k1 Z7 w
  36.   0x00a0:   Name: GLIBC_2.2.5  Flags: none  Version: 7; z) {+ n6 {. H% u- I" g4 }
  37.   0x00b0: Version: 1  File: libc.so.6  Cnt: 104 \0 @* i9 @+ m: o$ I+ t
  38.   0x00c0:   Name: GLIBC_2.8  Flags: none  Version: 196 @" Y. b0 }$ Y' c( M
  39.   0x00d0:   Name: GLIBC_2.9  Flags: none  Version: 18) n$ N/ |# _" ]5 w
  40.   0x00e0:   Name: GLIBC_2.17  Flags: none  Version: 16! E% o/ n% x# S  Z" T$ H/ f
  41.   0x00f0:   Name: GLIBC_2.4  Flags: none  Version: 14
    # Q( a* y, r# W2 y1 r
  42.   0x0100:   Name: GLIBC_2.3.4  Flags: none  Version: 12* \8 m- N. D! a1 _( j) K0 h
  43.   0x0110:   Name: GLIBC_2.3  Flags: none  Version: 9
    8 T4 f! G" w: ~7 [! N9 J1 ?
  44.   0x0120:   Name: GLIBC_2.7  Flags: none  Version: 6
    ' I7 o  M; v9 I/ V( e5 R
  45.   0x0130:   Name: GLIBC_2.14  Flags: none  Version: 5# \! l) ^; e* [( d) y- [
  46.   0x0140:   Name: GLIBC_2.3.2  Flags: none  Version: 4
    ( v( o3 O; ?+ p7 ]. ^) m
  47.   0x0150:   Name: GLIBC_2.2.5  Flags: none  Version: 3# s) I8 W) H& U. `* J) j9 I- R
  48.   0x0160: Version: 1  File: libdl.so.2  Cnt: 1
    0 {) W. ^- r0 I
  49.   0x0170:   Name: GLIBC_2.2.5  Flags: none  Version: 2
复制代码

% ?, Z- y& t  c& [. m/ R

4 W0 e0 ^! b* r! w% x
我们可以在 ELF 的 .dynsym 动态符号表中看到程序用于动态链接的所有导入导出符号,memcpy 和 clock_gettime 后面括号里的数字就是十进制的版本号(分别为 5 和 16),而我们需要格外关注的是下面的 .gnu.version 和 .gnu.version_r 符号版本信息段。
; O$ d4 U% T  `6 [, E7 Q5 Q6 p( e
.gnu.version 表包含所有动态符号的版本信息,.dynsym 动态符号表中的每个符号都可以在 .gnu.version 中看到对应的条目(.dynsym 中一共 4583 个符号刚好与 .gnu.version 的结束位置 0x11e7 相等)。
# q  s! j  Z  Z2 s- a2 A
从上面的输出可以看到 .gnu.version 表从 0x05b508 偏移量开始,我们可以看看对应偏移量的十六进制数据:

  1. / H  C5 m& ]+ c- J' C
  2. Offset(h) 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F: x* p8 |: ~/ }7 @. y. `1 [
  3. 0005B500                          00 00 00 00 02 00 03 00          ........
    2 f( p4 p4 L: N
  4. 0005B510  03 00 03 00 03 00 03 00 04 00 03 00 00 00 05 00  ................3 i6 l6 B' y+ u* J2 f' y" n( L
  5. 0005B520  03 00 03 00 06 00 00 00 03 00 07 00 08 00 08 00  ................
    4 O. U) V8 ?" r0 r
  6. 0005B530  03 00 09 00 03 00 03 00 0A 00 07 00 03 00 00 00  ................
    9 v6 G7 Z. E+ N8 h  h- D
  7. 0005B540  03 00 03 00 0B 00 07 00 03 00 03 00 00 00 07 00  ................
    ' b* l- `- ?& U
  8. 0005B550  00 00 03 00 03 00 03 00 03 00 0C 00 09 00 00 00  ................
    - {+ i/ U- G  |+ ]& K  p5 A
  9. 0005B560  07 00 03 00 03 00 07 00 03 00 07 00 0C 00 00 00  ................% r4 g. p! |' G8 h$ N
  10. 0005B570  0D 00 03 00 07 00 07 00 0E 00 0F 00 03 00 0D 00  ................
    ( G1 P% w6 `/ d. M$ x4 D
  11. 0005B580  03 00 03 00 03 00 03 00 02 00 03 00 03 00 10 00  ................
    ! u0 J$ H" S  e4 y' c: H4 _" z! f
  12. 0005B590  03 00 00 00 03 00 07 00 08 00 07 00 07 00 03 00  ................3 j0 e4 Q- ~' ]& g' q, Z; p
  13. 0005B5A0  03 00 0D 00 03 00 00 00 03 00 03 00 03 00 00 00  ................
复制代码
/ z, R, s8 Q$ _- N& t6 e0 Y
.gnu.version 中的每个条目占用两个字节,其值为符号的版本,由此可以看到其中第 0x0b 个符号(也就是 .dynsym 表中的 memcpy@GLIBC_2.14 符号)的偏移量即为 0x05b51e(0x05b508 + 0x0b x 2),该偏移量的值 0x0005 也刚好和 .dynsym 表中的值对应,当然 clock_gettime 符号对应的偏移量 0x05b58e 的值 0x0010 同样也是如此。
8 U1 q+ d8 B; d/ R* {
下面关键的 .gnu.version_r 表示二进制程序实际依赖的库文件版本,从输出中也能看到 .gnu.version_r 表是按照不同的库文件进行分段显示的,每个条目占用 0x10 也就是 16 个字节,该表是从 0x05d8d8 偏移量开始,我们看看 GLIBC_2.17 也就是 0x05d9b8 处的十六进制数据:
  1. # W: x/ ^' S" v# A; d5 C
  2. Offset(h) 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
    5 p& n2 H8 _9 V
  3. 0005D9B0  B0 70 03 00 10 00 00 00 97 91 96 06 00 00 10 00  °p......—‘–.....8 x  V2 s: E  C
  4. 0005D9C0  BA 70 03 00 10 00 00 00 14 69 69 0D 00 00 0E 00  ºp.......ii....., N# [6 C4 o. x; Y
  5. 0005D9D0  C5 70 03 00 10 00 00 00 74 19 69 09 00 00 0C 00  Åp......t.i.....
    1 h4 f( j5 @) S5 U' Q
  6. 0005D9E0  CF 70 03 00 10 00 00 00 13 69 69 0D 00 00 09 00  Ïp.......ii.....- w$ f9 L. N2 L
  7. 0005D9F0  6A 70 03 00 10 00 00 00 17 69 69 0D 00 00 06 00  jp.......ii.....! v9 b. O, c& t8 q! `
  8. 0005DA00  DB 70 03 00 10 00 00 00 94 91 96 06 00 00 05 00  Ûp......”‘–.....
    9 c6 e! Q& K1 q. ~1 g6 k
  9. 0005DA10  E5 70 03 00 10 00 00 00 72 19 69 09 00 00 04 00  åp......r.i.....
    : o! V' Q% S* O" m3 G
  10. 0005DA20  9A 70 03 00 10 00 00 00 75 1A 69 09 00 00 03 00  šp......u.i.....2 r% {' {. b" w0 R( R2 }
  11. 0005DA30  8E 70 03 00 00 00 00 00 01 00 01 00 D8 03 00 00  Žp..........Ø...
复制代码
" T5 Q* g% s' _4 `$ @: |
.gnu.version_r 表中每个条目是 16 个字节的 Elfxx_Vernaux 结构体,其声明如下(Elfxx_Half 占用 2 个字节,Elfxx_Word 占用 4 个字节):

  1. * V( y8 L* a3 B5 Y8 |7 C' k$ @
  2. typedef struct {1 T+ P3 G, N9 G7 z
  3.     Elfxx_Word    vna_hash;
    $ x- w8 w1 h, d7 [, f% q" A7 e
  4.     Elfxx_Half    vna_flags;
    * T6 s! S+ U/ Q6 N4 j
  5.     Elfxx_Half    vna_other;
    & M) \) v3 n  t  p" n; D- _4 V
  6.     Elfxx_Word    vna_name;
    - H1 c  H+ F2 F. q- S
  7.     Elfxx_Word    vna_next;
    ) }% y, d; o7 X% _0 t) M
  8. } Elfxx_Vernaux;
复制代码

* D9 n- a2 ?" o: K2 r; [2 r: i
vna_hash 为 4 个字节的库名称(也就是上面的 GLIBC_2.17 字符串)的 hash 值,vna_other 为对应的 .gnu.version 表中符号的版本值,vna_name 指向库名称字符串的偏移量(也可以在 ELF 头中找到),vna_next 为下一个条目的位置(一般固定为 0x00000010)。
: C8 F" w- U+ C2 I  l6 l
由上面的输出我们可以看到 GLIBC_2.17 对应的 0x05d9b8 处的开始的 4 个字节 vna_hash hash 值为 0x06969197,而 vna_other 的值 0x0010(输出里的 Version: 16)也与 .gnu.version 中 clock_gettime 符号的值一致。同样 GLIBC_2.14 也与 memcpy 符号的值相符。
# n% E  ^3 e/ ^/ e# G6 Z! N
修改 ELF 符号表
0 J1 x0 O; h, Z9 T: G0 e
由于 Linux 系统中的 LD 库(也就是 /lib64/ld-linux-x86-64.so.2 库)加载 ELF 时检查 .gnu.version_r 表中的符号,我们可以使用任何一款十六进制编辑器来修改 .gnu.version_r 表中的符号值来强制使用老版本的函数实现。
+ i3 p% C* e( c& W) 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 段下的其它高版本条目指向老版本条目的值。

& ?# k4 d+ |, R8 J5 G
例如 GLIBC_2.17 对应的 0x05d9b8 偏移量,我们可以直接将 vna_hash 值改为 GLIBC_2.2.5 的 0x09691A75 值,将 vna_other 改为 0003708E 值,为了保持和 .gnu.version 表中的版本值一致,这里我们就不修改 vna_other 值了。
5 |5 w9 J/ t8 h6 v+ M+ ]
对于 GLIBC_2.14 偏移量我们也修改成同样的值,修改保存之后的 ELF 文件再使用 readelf 命令检查就能看到变化了(只列出了修改的 .gnu.version-r 表):

  1. $ u! j* }& W$ {/ n* k9 t" Q
  2. [root@centos6-dev ~]# readelf -sV tester7 B! X2 n9 Z/ Q
  3. ......2 J- c/ o4 Q% u
  4. Version needs section '.gnu.version_r' contains 6 entries:
    ( \% y" k& v3 w
  5. Addr: 0x000000000045d8d8  Offset: 0x05e8d8  Link: 2 (.dynstr)
    6 }# @5 ?- [+ F4 H, H3 q
  6.   000000: Version: 1  File: ld-linux-x86-64.so.2  Cnt: 11 I2 D3 h' J$ F1 I/ H- g
  7.   0x0010:   Name: GLIBC_2.3  Flags: none  Version: 17
    & i4 q5 s; p3 t! R# p3 D5 ]3 [
  8.   0x0020: Version: 1  File: libgcc_s.so.1  Cnt: 33 d8 u7 Q$ p9 Y" w+ ^2 e
  9.   0x0030:   Name: GCC_3.0  Flags: none  Version: 13
    * H8 z& X9 z; q5 Q5 z4 ^
  10.   0x0040:   Name: GCC_3.3  Flags: none  Version: 11
    6 W0 f5 \& p( A8 ~' K6 c  B9 e
  11.   0x0050:   Name: GCC_4.2.0  Flags: none  Version: 10* C! i2 w0 U( k, B
  12.   0x0060: Version: 1  File: libm.so.6  Cnt: 1
    ' D- P! y1 E8 {( m5 v: ]( d
  13.   0x0070:   Name: GLIBC_2.2.5  Flags: none  Version: 81 Y6 d. L# e, w( ^+ y8 i2 I
  14.   0x0080: Version: 1  File: libpthread.so.0  Cnt: 29 l0 B2 d& w! P( y4 R  X; V
  15.   0x0090:   Name: GLIBC_2.3.2  Flags: none  Version: 15- M5 z* ^/ f6 b, t! O' [
  16.   0x00a0:   Name: GLIBC_2.2.5  Flags: none  Version: 7& R0 s0 ~! _: ?4 j* E
  17.   0x00b0: Version: 1  File: libc.so.6  Cnt: 10
    " \( _2 O0 Q: [$ Y) d" r
  18.   0x00c0:   Name: GLIBC_2.8  Flags: none  Version: 19
    ( l4 Y: T5 v/ \& z( J
  19.   0x00d0:   Name: GLIBC_2.9  Flags: none  Version: 18) q  M- y2 H0 ]7 d) a$ a" |$ P$ |
  20.   0x00e0:   Name: GLIBC_2.2.5  Flags: none  Version: 162 X8 d, n3 X9 h: ?6 A9 k. ?/ y
  21.   0x00f0:   Name: GLIBC_2.4  Flags: none  Version: 14) y: e' h. {( \8 Z" G0 [
  22.   0x0100:   Name: GLIBC_2.3.4  Flags: none  Version: 12# a5 H" v+ H. {+ J
  23.   0x0110:   Name: GLIBC_2.3  Flags: none  Version: 9* B3 ^+ T0 }2 R7 B$ Q7 D; Q& Q
  24.   0x0120:   Name: GLIBC_2.7  Flags: none  Version: 6* B2 E4 j$ }. w' v( N' e
  25.   0x0130:   Name: GLIBC_2.2.5  Flags: none  Version: 53 T7 Q" c' v. z% K% f
  26.   0x0140:   Name: GLIBC_2.3.2  Flags: none  Version: 45 l  E/ U9 @1 Z4 ~% s) a" u3 ^& |& u
  27.   0x0150:   Name: GLIBC_2.2.5  Flags: none  Version: 3
    * ?" c% i/ ~% k4 i/ G1 j; A
  28.   0x0160: Version: 1  File: libdl.so.2  Cnt: 1
    / {* A6 g' K* Q* [
  29.   0x0170:   Name: GLIBC_2.2.5  Flags: none  Version: 2
复制代码
$ T7 j/ u" c9 K. D1 S
patchelf 修改 ELF 文件
: A* F$ t% N& \7 |" [! h: W* N
一般的程序如果只使用了高版本 memcpy 的话,一般这样修改之后程序就可以运行了。但不巧我使用的第三方程序还使用了高版本 glibc 中的 clock_gettime,只是这样修改的话由于 CentOS 6 的 libc 2.12 库并没有提供 clock_gettime,运行时还是会报错。

% {& C6 ~/ X7 e0 b2 F+ [& I
这个时候我们就需要请出大杀器 PatchELF 了,这个小工具由 NixOS 团队开发,可以直接增加、删除、替换 ELF 文件依赖的库文件,使用起来也非常简单。

+ R8 Y4 S; ~* ?- s/ n5 l
检出 PatchELF 的源代码,按照 GitHub 仓库上介绍的步骤编译安装就可以使用了(一般发行版自带的 patchelf 工具版本较老不支持一些新的功能)。
: J, k7 R% E$ r9 g+ |7 K
虽然 CentOS 6 的 libc 库没有提供 clock_gettime 实现,但好在 glibc 自带的 librt 库里还是提供了的,因此我们可以使用 patchelf 工具修改原版的程序文件,让程序优先加载 librt 库,这样程序就能正确加载 clock_gettime 符号了:

  1. # `1 w. ]% H0 N0 @; U
  2. [root@centos6-dev ~]# patchelf --add-needed librt.so.1 tester
复制代码

1 P/ m/ \+ g5 N
$ G" w7 w2 H- u' ~
然后按照上面介绍的方法用十六进制编辑器修改新生成的 ELF 文件的 .gnu.version_r 表(因为 patchelf 运行之后新 ELF 文件的符号表就和之前的不一样了),将 GLIBC_2.17 和 GLIBC_2.14 统一改为 GLIBC_2.2.5 符号,保存 ELF 文件之后就可以看到效果了:

  1. / {9 {# p" W3 @3 A
  2. [root@centos6-dev ~]# ldd tester
    8 _# ~/ A* h) p8 T
  3.         linux-vdso.so.1 =>  (0x00007fffc17ee000)9 ]4 M# v5 {  d4 H+ V# o. L3 X3 A
  4.         librt.so.1 => /lib64/librt.so.1 (0x00007f7f84dca000)# X. o2 q6 Y" b# N* H9 _' c
  5.         libpthread.so.0 => /lib64/libpthread.so.0 (0x00007f7f84bad000)
    7 d0 S! }( c# I) I' D* l8 Q: Z$ a
  6.         libOpenCL.so.1 => /usr/lib64/libOpenCL.so.1 (0x00007f7f8498f000)' e3 y: `' E9 J% m8 l1 Y
  7.         libdl.so.2 => /lib64/libdl.so.2 (0x00007f7f8478b000)
    & y6 m5 X8 {! Z5 K  |% ]) S
  8.         libm.so.6 => /lib64/libm.so.6 (0x00007f7f84507000)
    & k1 z+ M1 [1 K; j; |
  9.         libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00007f7f842f1000)
    2 u% F7 |5 L+ Q# @& m
  10.         libc.so.6 => /lib64/libc.so.6 (0x00007f7f83f5d000)/ q) ~2 Y1 @, q9 \0 O2 [
  11.         /lib64/ld-linux-x86-64.so.2 (0x00007f7f84fd2000)
复制代码

' A* }$ x$ w5 g) I$ m& T# H3 h0 V
从 ldd 命令的输出中可以看到修改后的程序会加载 librt 库,而且也没有 glibc 版本的报错了,经过测试程序运行起来也没有问题了。

4 J8 Q, _2 q  ~6 y6 k3 K. K, {
赞 收藏 评论0 发布时间:2020-12-5 22:53

举报

0个回答
关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版