|
不知道大家平时编程过程中使用动态链接库的情况多不多,如果一个程序引用了无数个动态链接库,那就有可能引入符号冲突的问题,问题如下: ; [2 d+ D _/ _* ?- X5 k
想象中 + _* J0 z. l, f; s* S% O7 S
. b6 D) Z! z1 \) c
实际上
# w0 j! z. h( P; X$ c. A& p3 @; j* Q7 s# ?/ p6 c
7 ~. m, c# _; y) {& V
下面,我们尝试解决它。
' x+ `0 Q/ I$ a; I$ Q
最开始介绍下g++基本命令参数:
. V( M* W( G* N# ?) F- U# W7 O7 G- g++# t8 z+ Y* {( R& X$ H
- -c <file> 编译源文件,但是不进行链接, b9 O4 b* Y8 w
- -o <file> 指定输出文件的名字! ^3 q/ n% Z( k
- -s strip,移除符号信息- l/ _0 Q4 ^, T l, p- h
- -L <dir> 指令搜索链接库的路径
( O; k7 a4 X, N' [ - -l <lib> 指定要链接的链接库
! C! T" {7 E9 J/ e# Q7 A) d/ \4 a - -shared 产生动态目标文件
复制代码 d8 b9 h: D1 a6 O* H4 o/ S( v
先来看一段代码: - 0 a6 [$ D+ ^+ C" [ i, s* _& o
- #include <stdio.h>
( M! C, E' d! E/ c5 s, F# N! P, t5 I
' I* O( S! q1 F8 z/ ?0 }- void DoThing() { printf("work \n"); }
复制代码
/ Y z1 c3 }4 D
再定义一个简单的main.cc程序:
8 I& P1 e* `# b5 i- #include <stdio.h>
$ S0 \0 N, `" m
5 A" I) U1 l8 |- void DoThing();
7 a- y* W1 \# X! U& d9 R
) [) I2 T; f0 b( P+ V; D( p- int main() {
: ?( H% { i9 Z! z - printf("start \n");
& N$ i' ]6 _- ^2 W/ w. a( a4 { - DoThing();
- ?: H* c1 ]3 x+ f6 m - printf("finished \n");
3 F8 a9 S' Z1 a - return 0;
+ u" A5 L# V. ?1 e" }# Q( n - }
复制代码
# e' Z$ [8 ^' Q2 n6 i+ K
编译这两个文件,并分别打包成静态库: - 9 K$ `+ S k y( T( v
- g++ -c work.cc -o work.o) M- {1 b2 L+ y$ O" N
- ar rc libwork.a work.o
8 d& K) p; p$ T/ U/ ]' k - g++ -c main.cc -o main.o
. q4 v/ s" `; u1 q2 A - ar rc libmain.a main.o
复制代码
6 L0 s8 Z2 n. T! X: |; k
现在将这两个静态库链接成一个可执行文件,注意链接器如果发现当前库中使用了没有被定义的符号,它只会向后查找,因此最低级别没有其它依赖的库应该放在最右边,如果出现了符号冲突问题,链接器会使用最左边的符号。 : H9 {- n& R2 E; [- O/ N+ y
如果这样进行链接: - 2 C f( e7 \% y& l, {
- g++ -c work.cc -o work.o# O( e- L. x& ~7 m
- ar rc libwork.a work.o
) ?7 `4 H$ M! I# o, p - g++ -c main.cc -o main.o6 A8 A D. |. [+ H4 L4 s
- ar rc libmain.a main.o
复制代码
' `7 @! Z7 Z& J! |- b; a3 C
G. M" N5 j0 x" S6 h, F
链接失败,因为main库里的DoThing符号没有被定义,链接器向后查找,没有找到对应的符号定义,这里更改下链接库的顺序: - 7 l( z h( L2 H
- g++ -s -L. -o main.exe -lmain -lwork
+ L: ~2 D- Z7 R# d& ~ - $ ./main.exe
/ V$ G" m Z/ K3 m$ ? - start
. E; `' ?9 ^( C3 { - work
, S) z8 S: L8 m* n - finished
复制代码
% |7 C% T3 Y* n7 y9 h
链接成功。
( G U) z! q, e6 i
现在写一个简单的容易产生符号冲突的文件conflict.cc: - 1 t) \, [8 L8 Y4 C
- #include <stdio.h>3 f, p( O8 R. }8 Q6 A& d+ w
, B( r l; Q* l, y, o- void DoThing() { printf("conflict \n"); }
复制代码+ Y: }! T: @3 d! L7 j
编译并打包成静态库:$ @+ {$ G P9 n* O
$ O8 }; {6 v9 w+ a B4 k- g++ -c conflict.cc -o conflict.o5 B+ {8 q( Z" Y# n4 o9 H, T6 D
- ar rc libconflict.a conflict.o
复制代码 4 P8 J$ |+ g9 H' ^6 ]
+ I* s5 u: O+ s
如果按这样的顺序链接成一个可执行程序: - 8 v- }0 O& p( {
- $ g++ -s -L. -o main.exe -lmain -lwork -lconflict
) A, T) D7 @) s& c/ a8 W - $ ./main.exe
/ u V3 W/ n9 {* V - start% x4 [8 m1 N+ q' q( Y$ J% R
- work, d# n- ]# z5 P. |0 ~; i
- finished
复制代码 " T7 w1 p* A" Q6 J: k
2 K3 g% r* E# }1 n
如果稍微更改一下链接的顺序: - # x4 {9 b* h. \ t
- $ g++ -s -L. -o main.exe -lmain -lconflict -lwork
. H+ z% y6 J; _0 i( l9 ?4 E - $ ./main.exe
$ s- V* S" E* Z( f! R - start$ h8 q7 T y2 |8 u& ?
- conflict; x1 A' R% }, D; l: J
- finished
复制代码 . v8 J, ~3 n+ f% c0 m& B: A
. ?9 ]8 b3 X8 L) `* v4 ^% D0 W
这里发现顺序的不同导致了程序输出内容不同,究其原因就是那潜在的符号冲突。 , K. K' \6 {+ Q
现在再试试动态库,先介绍如何使用动态库: - # D3 \( D+ A" y4 k) J% f
- $ rm libconflict.a# a8 x4 h7 x6 I. y9 \) _
- $ g++ -shared conflict.o -o libconflict.so% d9 b2 t6 @: B: q7 _# w( F
- $ g++ -s -L. -o main.exe -lmain -lconflict, O! M' a! q2 {' a! R6 m
- $ LD_LIBRARY_PATH=. ./main.exe+ X# G0 o. ^& ^' i: _$ {" G
- start
1 ?: w* _! J( N* F1 ?' m- ]0 D* H V. C4 O - conflict) W: c) K$ n$ J5 `% m
- finished
复制代码
! }4 p( ?# ?+ P1 C+ Z E
: K3 W! W/ d1 g7 }' b
现在再引用一个中间层在动态链接库中调用conflict的文件layer.cc
7 m$ d% i, L1 {3 v, k( _1 N - . z/ e, R8 g5 N [9 U# D0 i
- #include <stdio.h>
% e& E; T( T, O0 n% \3 _) E - void DoThing();, i/ h3 z0 Y Y6 }, _1 A, P# @
- void DoLayer() {
5 f. ~1 _# t/ i, a! Y" N1 l: B/ k - printf("layer \n");4 P. Z! l) T+ Q3 e( m; N0 ~
- DoThing();
! s; {5 B2 |3 ?8 V9 J: } - }
复制代码3 P+ u' D) {7 Q3 f2 o) Y
并把layer和conflict打包成一个动态链接库: - & s- t. r9 T3 [
- $ g++ -c layer.cc -o layer.o5 W8 r3 a' C6 D% H2 G0 n0 q
- $ g++ -shared layer.o conflict.o -o libconflict.so
d# \. r$ N p2 K& X
复制代码 / u7 T/ q5 ~, b, u9 X' E
O" x( g. f8 w: y. ?. N
然后更新main.c程序,main里面调用layer,layer里调用conflict:
+ n% H) b* g' c, k( v; T+ r/ ?- #include <stdio.h>1 E# ? j% h5 }, v$ d
- void DoLayer();
2 a9 G' v0 Y5 k& ]5 S - int main() {
9 O' Q3 O( V6 q4 |& B6 e - printf("start \n");; H; y. P+ e2 B* I7 F. f
- DoLayer();
r5 p* U. |) r$ g, ] - printf("finished \n");
! b- k2 A ^9 |2 q - return 0;
6 B9 b0 z% x* }+ s& V% C9 K - }
2 U: K3 n( C' p: u% u' y0 k; r
复制代码* a. \, l9 `9 A7 o! u3 u
编译链接执行: - 1 }) R0 _3 w$ e! R1 R
- $ g++ -c main.cc -o main.o
# i t" o* i8 r( i - $ ar rc libmain.a main.o
& Y2 D1 y. G5 r9 c1 P2 C$ O3 ^ - $ g++ -s -L. -o main.exe -lmain -lconflict9 n4 P) O# H3 a8 ?9 P* U; U4 M9 s
- $ LD_LIBRARY_PATH=. ./main.exe
# o" y( X! i0 |# h8 X - start: b+ o8 n6 d: x" D( @6 S
- layer8 X1 \# U7 I7 a7 A/ O
- conflict
9 }. _# q' \# V+ }4 l4 c$ V - finished
复制代码+ g$ t1 a7 Y( L4 k" C- m
正常输出,没啥问题,现在再把之前的work.cc也塞到main.cc中,观察下冲突:
8 k0 x2 @. `0 C# Q+ |% `: _5 P/ u
% }& B' S7 e( S8 v; n- #include <stdio.h>
1 {' H @9 q/ t/ ?0 } - void DoThing();0 p7 L8 w3 G) R; ^# E
- void DoLayer();5 b7 N5 }0 D0 B7 F
- int main() {
: q6 @0 K- ~' X7 O1 ^ - printf("start \n");4 e$ n% P' i: Y) F" z y
- DoThing();
' B) z9 m M/ _) ]0 `1 B - DoLayer();0 A: s- r" j( g) A3 c6 o9 q/ u
- printf("finished \n");. J* ]# ]$ n$ s( r& }5 b5 H7 ` V
- return 0;2 p1 |* N) b2 _9 k, r& H( `
- }
复制代码0 {$ e/ F. H! A; R& m. w% m
把work.o和main.o打包成一个库,之后和conflict链接成一个可执行程序,运行:
. Z9 T; r# y4 q% |" d- $ g++ -c main.cc -o main.o) [) c# S. r9 N3 p
- $ ar rc libmain.a main.o work.o
/ _4 q) W% T% }2 t ~ - $ g++ -s -L. -o main.exe -lmain -lconflict
' [, ~7 L4 k' j T! d" G% A - $ LD_LIBRARY_PATH=. ./main.exe
* J' T; ^. L g$ q - start* W8 j0 A# \. D* a4 R, l6 l
- work' b. u% u' [# N' j' W
- layer- x2 g3 D- d8 Z& [( h, W# ?
- work; H7 T. Q, ?# [" g, [
- finished
复制代码
$ Y: F: o/ m4 ^+ M& Y# Q3 L
这里输出了两个work,正常情况下第二个work应该输出conflict,怎么解决呢? + E9 K5 X* r7 N, W% ?
可以考虑使用-fvisibility=hidden来隐藏内部的符号,链接库内部使用的符号把它隐藏掉,不让它被导出,外部也不会改变它的调用路径。
5 y# q6 Y1 D2 V1 [% r: m . g1 [7 G& |4 T) z5 q: h8 P
先使用nm看一下libconflict.so里面的符号:
0 D1 K( A+ H3 `. X& M$ O/ \- $ nm -CD libconflict.so
8 f# A r+ c0 I, D - w _ITM_deregisterTMCloneTable9 ~4 V9 p/ ?* m
- w _ITM_registerTMCloneTable! F5 m. X& W; G8 @3 a! {! j8 u
- 000000000000065a T DoLayer()+ u3 f @/ T& c/ V& z* V: X0 o
- 0000000000000672 T DoThing()* K" L9 O4 f2 @. s
- 0000000000201030 B __bss_start
0 ~3 J' R7 {$ C% @. i9 R" I1 M - w __cxa_finalize
- |, i4 m& p, {7 T7 z - w __gmon_start__
! o, c. B9 V$ U- a3 E - 0000000000201030 D _edata
/ W2 d5 n |, T; l, P; ~: n - 0000000000201038 B _end+ B4 k; e" {6 J' \! [
- 0000000000000688 T _fini
. q, S! x0 W# v$ Y# g: A, G - 0000000000000528 T _init
6 a; u1 X* a- U( |1 j* c6 `- O - U puts
复制代码1 \( a6 g5 i, }+ f. I' f7 O
如果把符号隐藏掉: - ! H# s; _% B+ u; W& r J
- $ g++ -fvisibility=hidden -c layer.cc -o layer.o
' I: y1 R- W9 q; w - $ g++ -fvisibility=hidden -c conflict.cc -o conflict.o
( r! \; l G) } - $ g++ -shared layer.o conflict.o -o libconflict.so
% [& r. y. e9 ] - 再使用nm看一下libconflict.so里面的符号:* S. f1 h$ w& f+ N L
- $ nm -CD libconflict.so- j2 e6 C" R0 ?
- w _ITM_deregisterTMCloneTable
6 _/ i7 b- ]! B. b/ E0 Q - w _ITM_registerTMCloneTable4 c- m5 }9 K, M+ L6 P2 n
- 0000000000201028 B __bss_start
5 J, p. R2 _. {' k. q - w __cxa_finalize8 E) k# R" j j/ R! p8 T+ k
- w __gmon_start__1 {4 ~4 C( G* x/ z
- 0000000000201028 D _edata; O, j' b; \- d+ {2 B' _
- 0000000000201030 B _end6 v! B! D* V+ t- f* G
- 0000000000000618 T _fini
- T |8 a& j& }# u" _" V - 00000000000004c0 T _init0 s+ J1 w' |5 B3 b6 ]# G6 a
- U puts
复制代码
" X0 S! c; p& h% l5 B, d
这样的话main函数肯定不能调用DoLayer啦,因为DoLayer符号没有暴露出来: - , W' |4 ]* B9 V, m3 L
- $ g++ -s -L. -o main.exe -lmain -lconflict9 M9 B$ x# z7 G4 u
- ./libmain.a(main.o): In function `main':
9 j1 _" Z p9 @) V* ^ - main.cc:(.text+0x16): undefined reference to `DoLayer()'
9 m3 \& b: R0 B$ Y3 b$ [ - collect2: error: ld returned 1 exit statu
复制代码
0 Z6 ]! C5 D* y7 b4 j2 Y
那怎么暴露出来特定符号呢,直接看代码,改动了layer.cc:- F" i: z' N. }, Q
- ( \' L, e" o) s' _* p
- #include <stdio.h>! a8 s n$ d: H' d: p
- void DoThing();) P/ k- e" \3 I0 t
- __attribute__ ((visibility ("default"))) void DoLayer() {3 |. y: ^0 V/ i
- printf("layer \n");- U$ U& V% E% u( l& G0 q# h3 m
- DoThing();( {, {9 g8 i# I
- }
复制代码! K# n- B% ^# @
再编译链接运行看看结果:
~+ i+ @2 k1 {9 f$ {2 V! s- $ g++ -fvisibility=hidden -c layer.cxx -o layer.o
0 v5 Z- t! P4 S1 p7 E# \ - $ g++ -shared layer.o conflict.o -o libconflict.so& y& n; L3 J. T. o1 v
- $ g++ -s -L. -o main.exe -lmain -lconflict
6 b( n: V% i" i9 i5 W1 Z - $ LD_LIBRARY_PATH=. ./main.exe
5 e. X1 z% Y! s+ C - start3 J, k1 j0 n$ H1 h
- work: k. Q, K6 L! T6 `9 A M v
- layer6 @/ [& x, M+ g" I9 ]; Z
- conflict
4 p9 c& c9 L u e - finished
复制代码
0 ~ f' i9 W' i$ l( Z+ g: A
发现已经是我们期待的结果啦,符号冲突的问题因此被解决。; N: a5 ^$ Q, g7 g2 s0 T
- f# s, ^: u r9 W; @' u2 H
是不是感觉很麻烦,难道每个要暴露的符号都要加上__attribute__这种修饰吗,这里其实可以写一个export文件,告诉编译器要导出的所有符号有哪些。 - 7 V: C& |! A$ S; W5 `" \/ e- i
- export.txt! `" y4 D, r% i7 W2 u
- 7 Q, _! ^# f) G
- {, h8 z3 `7 j5 d" ?
- global: *DoLayer*;0 J; K u @1 v2 H% `' Q$ l9 z$ ]
- local: *;
! T% H6 }7 t) b' [ - };8 q) @. h9 N& {6 G6 K
- g++ -Wl,--version-script=export.txt -s -shared layer.o conflict.o -o libconflict.so
复制代码
& ?) X' l: [ _. ~: j ^
1 ]( t6 h9 m$ r$ J( I
但这种方式只有在gcc中才可以被使用,我在clang中尝试使用但是失败啦,所以为了兼容性不建议使用这种方式,还是消停的使用__attribute__来解决符号冲突问题吧。
7 T" S- ?2 S+ z) p" ` Tips通过隐藏符号可以减小可执行程序的大小,还可以解决符号冲突问题,但有个缺点,因为隐藏了符号,线上程序运行时如果出现crash,通过堆栈信息我们看不到具体函数调用路径,给定位问题带来了困难。所以,是否需要使用这种办法,还应根据实际情况具体抉择。
: v+ ?3 s7 p7 Y0 f3 @& z+ m2 N |