|
Linux glibc 问题 9 M2 y: _% h& [6 x1 @/ U* n
相信有不少 Linux 用户都碰到过运行第三方(非系统自带软件源)发布的程序时的 glibc 兼容性问题,这一般是由于当前 Linux 系统上的 GNU C 库(glibc)版本比较老导致的,例如我在 CentOS 6 64 位系统上运行某第三方闭源软件时会报:
, R( h8 U1 K' M% N! G& b3 m6 G7 V- [root@centos6-dev ~]# ldd tester- x+ L5 C0 S q9 r7 h
- ./tester: /lib64/libc.so.6: version `GLIBC_2.17' not found (required by ./tester)$ O/ C& x) y& P7 e0 A
- ./tester: /lib64/libc.so.6: version `GLIBC_2.14' not found (required by ./tester)
3 R7 y. n# K$ x$ w& u9 a; s - linux-vdso.so.1 => (0x00007ffe795fe000)5 v1 |! R) q8 X0 x( k; f5 ]# g
- libpthread.so.0 => /lib64/libpthread.so.0 (0x00007fc7d4c73000)
% O% E, P6 w+ M5 { - libOpenCL.so.1 => /usr/lib64/libOpenCL.so.1 (0x00007fc7d4a55000)
& Z" V2 f: q! d+ s' @$ ] - libdl.so.2 => /lib64/libdl.so.2 (0x00007fc7d4851000)" F! I2 f8 p; |6 ? [# V
- libm.so.6 => /lib64/libm.so.6 (0x00007fc7d45cd000)
8 b0 r1 Z, n; { - libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00007fc7d43b7000)
* s# `+ p) y9 w2 r ? c0 [$ t& T - libc.so.6 => /lib64/libc.so.6 (0x00007fc7d4023000)0 l0 k. w" X$ u, R- y, J
- /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 文件的动态符号信息:
2 T7 W5 f. f( d! a# D- [root@centos6-dev ~]# objdump -T tester | grep GLIBC_2.1.*2 [0 S% D+ U5 Z$ M. ?9 S; n
- 0000000000000000 DF *UND* 0000000000000000 GLIBC_2.14 memcpy
* }! ]8 n2 }) }! N - 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 提供的符号版本: - 0 |: k6 A! ^9 d) o. H
- [root@centos6-dev ~]# objdump -T /lib64/libc.so.6 | grep memcpy
2 _+ Z# E9 t7 R - 0000000000091300 w DF .text 0000000000000009 GLIBC_2.2.5 wmemcpy7 v; w7 P( y7 F& N) G, O
- 0000000000101070 g DF .text 000000000000001b GLIBC_2.4 __wmemcpy_chk
8 ]- E0 h. c9 k Q* L2 `( _% o; S5 o - 00000000000896b0 g DF .text 0000000000000465 GLIBC_2.2.5 memcpy' S( ^0 l. u3 ^5 a' e' o' r
- 00000000000896a0 g DF .text 0000000000000009 GLIBC_2.3.4 __memcpy_chk% @* Z+ w5 O* c h
- [root@centos6-dev ~]# objdump -T /lib64/libc.so.6 | grep clock_gettime
' d: a0 r# l" |6 h8 S) ]. D* `4 f _ } - 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 版本:
8 X2 z9 \' T' P7 d/ Q- [root@centos6-dev ~]# objdump -T /lib64/librt.so.1 | grep clock_gettime+ K. @' h- i. Y4 _9 S. W
- 0000000000000000 DO *UND* 0000000000000000 GLIBC_PRIVATE __vdso_clock_gettime
3 u! Y3 G8 D1 g2 K! z( A/ I - 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 的符号表,由于该命令输出非常多,这里只贴出我们关心的信息: - 9 L2 A. j! p2 K/ |5 I+ K
- [root@centos6-dev ~]# readelf -sV tester
3 E5 t$ M8 L/ V9 }2 k - Symbol table '.dynsym' contains 4583 entries:
; y- p; C* U. p: d - Num: Value Size Type Bind Vis Ndx Name1 @. b1 b; ]" w X
- 0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND$ @( ]5 B) k x
- ......
9 Z, ?: z4 C7 t' i9 V - 11: 0000000000000000 0 FUNC GLOBAL DEFAULT UND memcpy@GLIBC_2.14 (5)
+ }; @0 g& U! H" E4 A, H - ......
" K" R! m# _( O0 a0 s - 67: 0000000000000000 0 FUNC GLOBAL DEFAULT UND clock_gettime@GLIBC_2.17 (16)+ O9 j. H: W* c8 ^" f
- ......
" u2 @$ K+ W- G - 4582: 0000000000794260 70 FUNC WEAK DEFAULT 12 _ZNSt15basic_streambufIwS
9 w# X5 _& }! a" O0 `) p
$ O. g, Y; Z3 O h9 {- Version symbols section '.gnu.version' contains 4583 entries:/ X f- X; m, K7 z. b
- Addr: 000000000045b508 Offset: 0x05b508 Link: 4 (.dynsym)
/ _1 F/ B/ d, Z - 000: 0 (*local*) 0 (*local*) 2 (GLIBC_2.2.5) 3 (GLIBC_2.2.5)2 }4 Z8 t5 n L7 P# S
- 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# } - 008: 4 (GLIBC_2.3.2) 3 (GLIBC_2.2.5) 0 (*local*) 5 (GLIBC_2.14)
7 V2 i( e3 r& k - ......
5 i b2 x& \; ] - 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
- ......
; r2 l6 }4 H7 F* }4 E r! y, R6 B - 11e0: 1 (*global*) 1 (*global*) 1 (*global*) 1 (*global*)( k3 o: p. d) N$ U2 V
- 11e4: 1 (*global*) 1 (*global*) 1 (*global*)
6 @* Z$ v6 t4 e* |
+ L6 b Q- X5 p4 ~' _9 i- Version needs section '.gnu.version_r' contains 6 entries: i/ E8 b7 K& q- u. I
- Addr: 0x000000000045d8d8 Offset: 0x05d8d8 Link: 5 (.dynstr)4 C" @7 \# b! a) R, j9 f2 k9 R
- 000000: Version: 1 File: ld-linux-x86-64.so.2 Cnt: 1
* h+ a8 @/ f" P - 0x0010: Name: GLIBC_2.3 Flags: none Version: 17- k1 K( i" V! X/ `: o) S
- 0x0020: Version: 1 File: libgcc_s.so.1 Cnt: 33 Q7 I c5 S' j8 x8 }* {5 t
- 0x0030: Name: GCC_3.0 Flags: none Version: 13' F0 w0 h1 [: R' T5 C
- 0x0040: Name: GCC_3.3 Flags: none Version: 110 {6 y R& N* }0 W9 `" f: p) W2 _
- 0x0050: Name: GCC_4.2.0 Flags: none Version: 10, f- y2 [. C4 \3 h" h% j! J' Z
- 0x0060: Version: 1 File: libm.so.6 Cnt: 1 z7 e3 c, \! Z
- 0x0070: Name: GLIBC_2.2.5 Flags: none Version: 8; J4 z/ S5 B) H/ M
- 0x0080: Version: 1 File: libpthread.so.0 Cnt: 29 K2 m7 |/ n, W4 t, l$ o4 F
- 0x0090: Name: GLIBC_2.3.2 Flags: none Version: 156 w5 T3 \; g8 k1 Z7 w
- 0x00a0: Name: GLIBC_2.2.5 Flags: none Version: 7; z) {+ n6 {. H% u- I" g4 }
- 0x00b0: Version: 1 File: libc.so.6 Cnt: 104 \0 @* i9 @+ m: o$ I+ t
- 0x00c0: Name: GLIBC_2.8 Flags: none Version: 196 @" Y. b0 }$ Y' c( M
- 0x00d0: Name: GLIBC_2.9 Flags: none Version: 18) n$ N/ |# _" ]5 w
- 0x00e0: Name: GLIBC_2.17 Flags: none Version: 16! E% o/ n% x# S Z" T$ H/ f
- 0x00f0: Name: GLIBC_2.4 Flags: none Version: 14
# Q( a* y, r# W2 y1 r - 0x0100: Name: GLIBC_2.3.4 Flags: none Version: 12* \8 m- N. D! a1 _( j) K0 h
- 0x0110: Name: GLIBC_2.3 Flags: none Version: 9
8 T4 f! G" w: ~7 [! N9 J1 ? - 0x0120: Name: GLIBC_2.7 Flags: none Version: 6
' I7 o M; v9 I/ V( e5 R - 0x0130: Name: GLIBC_2.14 Flags: none Version: 5# \! l) ^; e* [( d) y- [
- 0x0140: Name: GLIBC_2.3.2 Flags: none Version: 4
( v( o3 O; ?+ p7 ]. ^) m - 0x0150: Name: GLIBC_2.2.5 Flags: none Version: 3# s) I8 W) H& U. `* J) j9 I- R
- 0x0160: Version: 1 File: libdl.so.2 Cnt: 1
0 {) W. ^- r0 I - 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 偏移量开始,我们可以看看对应偏移量的十六进制数据:
/ H C5 m& ]+ c- J' C- Offset(h) 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F: x* p8 |: ~/ }7 @. y. `1 [
- 0005B500 00 00 00 00 02 00 03 00 ........
2 f( p4 p4 L: N - 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
- 0005B520 03 00 03 00 06 00 00 00 03 00 07 00 08 00 08 00 ................
4 O. U) V8 ?" r0 r - 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 - 0005B540 03 00 03 00 0B 00 07 00 03 00 03 00 00 00 07 00 ................
' b* l- `- ?& U - 0005B550 00 00 03 00 03 00 03 00 03 00 0C 00 09 00 00 00 ................
- {+ i/ U- G |+ ]& K p5 A - 0005B560 07 00 03 00 03 00 07 00 03 00 07 00 0C 00 00 00 ................% r4 g. p! |' G8 h$ N
- 0005B570 0D 00 03 00 07 00 07 00 0E 00 0F 00 03 00 0D 00 ................
( G1 P% w6 `/ d. M$ x4 D - 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 - 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
- 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 处的十六进制数据: - # W: x/ ^' S" v# A; d5 C
- Offset(h) 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
5 p& n2 H8 _9 V - 0005D9B0 B0 70 03 00 10 00 00 00 97 91 96 06 00 00 10 00 °p......—‘–.....8 x V2 s: E C
- 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
- 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 - 0005D9E0 CF 70 03 00 10 00 00 00 13 69 69 0D 00 00 09 00 Ïp.......ii.....- w$ f9 L. N2 L
- 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! `
- 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 - 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 - 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 }
- 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 个字节):
* V( y8 L* a3 B5 Y8 |7 C' k$ @- typedef struct {1 T+ P3 G, N9 G7 z
- Elfxx_Word vna_hash;
$ x- w8 w1 h, d7 [, f% q" A7 e - Elfxx_Half vna_flags;
* T6 s! S+ U/ Q6 N4 j - Elfxx_Half vna_other;
& M) \) v3 n t p" n; D- _4 V - Elfxx_Word vna_name;
- H1 c H+ F2 F. q- S - Elfxx_Word vna_next;
) }% y, d; o7 X% _0 t) M - } 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 表):
$ u! j* }& W$ {/ n* k9 t" Q- [root@centos6-dev ~]# readelf -sV tester7 B! X2 n9 Z/ Q
- ......2 J- c/ o4 Q% u
- Version needs section '.gnu.version_r' contains 6 entries:
( \% y" k& v3 w - Addr: 0x000000000045d8d8 Offset: 0x05e8d8 Link: 2 (.dynstr)
6 }# @5 ?- [+ F4 H, H3 q - 000000: Version: 1 File: ld-linux-x86-64.so.2 Cnt: 11 I2 D3 h' J$ F1 I/ H- g
- 0x0010: Name: GLIBC_2.3 Flags: none Version: 17
& i4 q5 s; p3 t! R# p3 D5 ]3 [ - 0x0020: Version: 1 File: libgcc_s.so.1 Cnt: 33 d8 u7 Q$ p9 Y" w+ ^2 e
- 0x0030: Name: GCC_3.0 Flags: none Version: 13
* H8 z& X9 z; q5 Q5 z4 ^ - 0x0040: Name: GCC_3.3 Flags: none Version: 11
6 W0 f5 \& p( A8 ~' K6 c B9 e - 0x0050: Name: GCC_4.2.0 Flags: none Version: 10* C! i2 w0 U( k, B
- 0x0060: Version: 1 File: libm.so.6 Cnt: 1
' D- P! y1 E8 {( m5 v: ]( d - 0x0070: Name: GLIBC_2.2.5 Flags: none Version: 81 Y6 d. L# e, w( ^+ y8 i2 I
- 0x0080: Version: 1 File: libpthread.so.0 Cnt: 29 l0 B2 d& w! P( y4 R X; V
- 0x0090: Name: GLIBC_2.3.2 Flags: none Version: 15- M5 z* ^/ f6 b, t! O' [
- 0x00a0: Name: GLIBC_2.2.5 Flags: none Version: 7& R0 s0 ~! _: ?4 j* E
- 0x00b0: Version: 1 File: libc.so.6 Cnt: 10
" \( _2 O0 Q: [$ Y) d" r - 0x00c0: Name: GLIBC_2.8 Flags: none Version: 19
( l4 Y: T5 v/ \& z( J - 0x00d0: Name: GLIBC_2.9 Flags: none Version: 18) q M- y2 H0 ]7 d) a$ a" |$ P$ |
- 0x00e0: Name: GLIBC_2.2.5 Flags: none Version: 162 X8 d, n3 X9 h: ?6 A9 k. ?/ y
- 0x00f0: Name: GLIBC_2.4 Flags: none Version: 14) y: e' h. {( \8 Z" G0 [
- 0x0100: Name: GLIBC_2.3.4 Flags: none Version: 12# a5 H" v+ H. {+ J
- 0x0110: Name: GLIBC_2.3 Flags: none Version: 9* B3 ^+ T0 }2 R7 B$ Q7 D; Q& Q
- 0x0120: Name: GLIBC_2.7 Flags: none Version: 6* B2 E4 j$ }. w' v( N' e
- 0x0130: Name: GLIBC_2.2.5 Flags: none Version: 53 T7 Q" c' v. z% K% f
- 0x0140: Name: GLIBC_2.3.2 Flags: none Version: 45 l E/ U9 @1 Z4 ~% s) a" u3 ^& |& u
- 0x0150: Name: GLIBC_2.2.5 Flags: none Version: 3
* ?" c% i/ ~% k4 i/ G1 j; A - 0x0160: Version: 1 File: libdl.so.2 Cnt: 1
/ {* A6 g' K* Q* [ - 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 w. ]% H0 N0 @; U- [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 文件之后就可以看到效果了:
/ {9 {# p" W3 @3 A- [root@centos6-dev ~]# ldd tester
8 _# ~/ A* h) p8 T - linux-vdso.so.1 => (0x00007fffc17ee000)9 ]4 M# v5 { d4 H+ V# o. L3 X3 A
- librt.so.1 => /lib64/librt.so.1 (0x00007f7f84dca000)# X. o2 q6 Y" b# N* H9 _' c
- libpthread.so.0 => /lib64/libpthread.so.0 (0x00007f7f84bad000)
7 d0 S! }( c# I) I' D* l8 Q: Z$ a - libOpenCL.so.1 => /usr/lib64/libOpenCL.so.1 (0x00007f7f8498f000)' e3 y: `' E9 J% m8 l1 Y
- libdl.so.2 => /lib64/libdl.so.2 (0x00007f7f8478b000)
& y6 m5 X8 {! Z5 K |% ]) S - libm.so.6 => /lib64/libm.so.6 (0x00007f7f84507000)
& k1 z+ M1 [1 K; j; | - libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00007f7f842f1000)
2 u% F7 |5 L+ Q# @& m - libc.so.6 => /lib64/libc.so.6 (0x00007f7f83f5d000)/ q) ~2 Y1 @, q9 \0 O2 [
- /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, { |