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

编程过程中使用动态链接库引入符号冲突的问题

[复制链接]
gaosmile 发布时间:2021-1-26 09:43
不知道大家平时编程过程中使用动态链接库的情况多不多,如果一个程序引用了无数个动态链接库,那就有可能引入符号冲突的问题,问题如下:
; [2 d+ D  _/ _* ?- X5 k
想象中
+ _* J0 z. l, f; s* S% O7 S
微信图片_20210126094247.jpg
. b6 D) Z! z1 \) c
实际上
# w0 j! z. h( P; X$ c. A& p3 @; j* Q7 s# ?/ p6 c
微信图片_20210126094250.jpg

7 ~. m, c# _; y) {& V
下面,我们尝试解决它。

' x+ `0 Q/ I$ a; I$ Q
最开始介绍下g++基本命令参数:

  1. . V( M* W( G* N# ?) F- U# W7 O7 G
  2. g++# t8 z+ Y* {( R& X$ H
  3. -c <file> 编译源文件,但是不进行链接, b9 O4 b* Y8 w
  4. -o <file> 指定输出文件的名字! ^3 q/ n% Z( k
  5. -s        strip,移除符号信息- l/ _0 Q4 ^, T  l, p- h
  6. -L <dir>  指令搜索链接库的路径
    ( O; k7 a4 X, N' [
  7. -l <lib>  指定要链接的链接库
    ! C! T" {7 E9 J/ e# Q7 A) d/ \4 a
  8. -shared   产生动态目标文件
复制代码
  d8 b9 h: D1 a6 O* H4 o/ S( v
先来看一段代码:
  1. 0 a6 [$ D+ ^+ C" [  i, s* _& o
  2. #include <stdio.h>
    ( M! C, E' d! E/ c5 s, F# N! P, t5 I

  3. ' I* O( S! q1 F8 z/ ?0 }
  4. void DoThing() { printf("work \n"); }
复制代码

/ Y  z1 c3 }4 D
再定义一个简单的main.cc程序:

  1. 8 I& P1 e* `# b5 i
  2. #include <stdio.h>
    $ S0 \0 N, `" m

  3. 5 A" I) U1 l8 |
  4. void DoThing();
    7 a- y* W1 \# X! U& d9 R

  5. ) [) I2 T; f0 b( P+ V; D( p
  6. int main() {
    : ?( H% {  i9 Z! z
  7.     printf("start \n");
    & N$ i' ]6 _- ^2 W/ w. a( a4 {
  8.     DoThing();
    - ?: H* c1 ]3 x+ f6 m
  9.     printf("finished \n");
    3 F8 a9 S' Z1 a
  10.     return 0;
    + u" A5 L# V. ?1 e" }# Q( n
  11. }
复制代码

# e' Z$ [8 ^' Q2 n6 i+ K
编译这两个文件,并分别打包成静态库:
  1. 9 K$ `+ S  k  y( T( v
  2. g++ -c work.cc -o work.o) M- {1 b2 L+ y$ O" N
  3. ar rc libwork.a work.o
    8 d& K) p; p$ T/ U/ ]' k
  4. g++ -c main.cc -o main.o
    . q4 v/ s" `; u1 q2 A
  5. ar rc libmain.a main.o
复制代码

6 L0 s8 Z2 n. T! X: |; k
现在将这两个静态库链接成一个可执行文件,注意链接器如果发现当前库中使用了没有被定义的符号,它只会向后查找,因此最低级别没有其它依赖的库应该放在最右边,如果出现了符号冲突问题,链接器会使用最左边的符号。
: H9 {- n& R2 E; [- O/ N+ y
如果这样进行链接:
  1. 2 C  f( e7 \% y& l, {
  2. g++ -c work.cc -o work.o# O( e- L. x& ~7 m
  3. ar rc libwork.a work.o
    ) ?7 `4 H$ M! I# o, p
  4. g++ -c main.cc -o main.o6 A8 A  D. |. [+ H4 L4 s
  5. ar rc libmain.a main.o
复制代码

' `7 @! Z7 Z& J! |- b; a3 C

  G. M" N5 j0 x" S6 h, F
链接失败,因为main库里的DoThing符号没有被定义,链接器向后查找,没有找到对应的符号定义,这里更改下链接库的顺序:
  1. 7 l( z  h( L2 H
  2. g++ -s -L. -o main.exe -lmain -lwork
    + L: ~2 D- Z7 R# d& ~
  3. $ ./main.exe
    / V$ G" m  Z/ K3 m$ ?
  4. start
    . E; `' ?9 ^( C3 {
  5. work
    , S) z8 S: L8 m* n
  6. finished
复制代码

% |7 C% T3 Y* n7 y9 h
链接成功。

( G  U) z! q, e6 i
现在写一个简单的容易产生符号冲突的文件conflict.cc:
  1. 1 t) \, [8 L8 Y4 C
  2. #include <stdio.h>3 f, p( O8 R. }8 Q6 A& d+ w

  3. , B( r  l; Q* l, y, o
  4. void DoThing() { printf("conflict \n"); }
复制代码
+ Y: }! T: @3 d! L7 j
编译并打包成静态库:$ @+ {$ G  P9 n* O

  1. $ O8 }; {6 v9 w+ a  B4 k
  2. g++ -c conflict.cc -o conflict.o5 B+ {8 q( Z" Y# n4 o9 H, T6 D
  3. ar rc libconflict.a conflict.o
复制代码
4 P8 J$ |+ g9 H' ^6 ]
+ I* s5 u: O+ s
如果按这样的顺序链接成一个可执行程序:
  1. 8 v- }0 O& p( {
  2. $ g++ -s -L. -o main.exe -lmain -lwork -lconflict
    ) A, T) D7 @) s& c/ a8 W
  3. $ ./main.exe
    / u  V3 W/ n9 {* V
  4. start% x4 [8 m1 N+ q' q( Y$ J% R
  5. work, d# n- ]# z5 P. |0 ~; i
  6. finished
复制代码
" T7 w1 p* A" Q6 J: k
2 K3 g% r* E# }1 n
如果稍微更改一下链接的顺序:
  1. # x4 {9 b* h. \  t
  2. $ g++ -s -L. -o main.exe -lmain -lconflict -lwork
    . H+ z% y6 J; _0 i( l9 ?4 E
  3. $ ./main.exe
    $ s- V* S" E* Z( f! R
  4. start$ h8 q7 T  y2 |8 u& ?
  5. conflict; x1 A' R% }, D; l: J
  6. finished
复制代码
. v8 J, ~3 n+ f% c0 m& B: A
. ?9 ]8 b3 X8 L) `* v4 ^% D0 W
这里发现顺序的不同导致了程序输出内容不同,究其原因就是那潜在的符号冲突。
, K. K' \6 {+ Q
现在再试试动态库,先介绍如何使用动态库:
  1. # D3 \( D+ A" y4 k) J% f
  2. $ rm libconflict.a# a8 x4 h7 x6 I. y9 \) _
  3. $ g++ -shared conflict.o -o libconflict.so% d9 b2 t6 @: B: q7 _# w( F
  4. $ g++ -s -L. -o main.exe -lmain -lconflict, O! M' a! q2 {' a! R6 m
  5. $ LD_LIBRARY_PATH=. ./main.exe+ X# G0 o. ^& ^' i: _$ {" G
  6. start
    1 ?: w* _! J( N* F1 ?' m- ]0 D* H  V. C4 O
  7. conflict) W: c) K$ n$ J5 `% m
  8. 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
  1. . z/ e, R8 g5 N  [9 U# D0 i
  2. #include <stdio.h>
    % e& E; T( T, O0 n% \3 _) E
  3. void DoThing();, i/ h3 z0 Y  Y6 }, _1 A, P# @
  4. void DoLayer() {
    5 f. ~1 _# t/ i, a! Y" N1 l: B/ k
  5.     printf("layer \n");4 P. Z! l) T+ Q3 e( m; N0 ~
  6.     DoThing();
    ! s; {5 B2 |3 ?8 V9 J: }
  7. }
复制代码
3 P+ u' D) {7 Q3 f2 o) Y
并把layer和conflict打包成一个动态链接库:
  1. & s- t. r9 T3 [
  2. $ g++ -c layer.cc -o layer.o5 W8 r3 a' C6 D% H2 G0 n0 q
  3. $ 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:

  1. + n% H) b* g' c, k( v; T+ r/ ?
  2. #include <stdio.h>1 E# ?  j% h5 }, v$ d
  3. void DoLayer();
    2 a9 G' v0 Y5 k& ]5 S
  4. int main() {
    9 O' Q3 O( V6 q4 |& B6 e
  5.     printf("start \n");; H; y. P+ e2 B* I7 F. f
  6.     DoLayer();
      r5 p* U. |) r$ g, ]
  7.     printf("finished \n");
    ! b- k2 A  ^9 |2 q
  8.     return 0;
    6 B9 b0 z% x* }+ s& V% C9 K
  9. }
    2 U: K3 n( C' p: u% u' y0 k; r
复制代码
* a. \, l9 `9 A7 o! u3 u
编译链接执行:
  1. 1 }) R0 _3 w$ e! R1 R
  2. $ g++ -c main.cc -o main.o
    # i  t" o* i8 r( i
  3. $ ar rc libmain.a main.o
    & Y2 D1 y. G5 r9 c1 P2 C$ O3 ^
  4. $ g++ -s -L. -o main.exe -lmain -lconflict9 n4 P) O# H3 a8 ?9 P* U; U4 M9 s
  5. $ LD_LIBRARY_PATH=. ./main.exe
    # o" y( X! i0 |# h8 X
  6. start: b+ o8 n6 d: x" D( @6 S
  7. layer8 X1 \# U7 I7 a7 A/ O
  8. conflict
    9 }. _# q' \# V+ }4 l4 c$ V
  9. finished
复制代码
+ g$ t1 a7 Y( L4 k" C- m
正常输出,没啥问题,现在再把之前的work.cc也塞到main.cc中,观察下冲突:
8 k0 x2 @. `0 C# Q+ |% `: _5 P/ u

  1. % }& B' S7 e( S8 v; n
  2. #include <stdio.h>
    1 {' H  @9 q/ t/ ?0 }
  3. void DoThing();0 p7 L8 w3 G) R; ^# E
  4. void DoLayer();5 b7 N5 }0 D0 B7 F
  5. int main() {
    : q6 @0 K- ~' X7 O1 ^
  6.     printf("start \n");4 e$ n% P' i: Y) F" z  y
  7.     DoThing();
    ' B) z9 m  M/ _) ]0 `1 B
  8.     DoLayer();0 A: s- r" j( g) A3 c6 o9 q/ u
  9.     printf("finished \n");. J* ]# ]$ n$ s( r& }5 b5 H7 `  V
  10.     return 0;2 p1 |* N) b2 _9 k, r& H( `
  11. }
复制代码
0 {$ e/ F. H! A; R& m. w% m
把work.o和main.o打包成一个库,之后和conflict链接成一个可执行程序,运行:

  1. . Z9 T; r# y4 q% |" d
  2. $ g++ -c main.cc -o main.o) [) c# S. r9 N3 p
  3. $ ar rc libmain.a main.o work.o
    / _4 q) W% T% }2 t  ~
  4. $ g++ -s -L. -o main.exe -lmain -lconflict
    ' [, ~7 L4 k' j  T! d" G% A
  5. $ LD_LIBRARY_PATH=. ./main.exe
    * J' T; ^. L  g$ q
  6. start* W8 j0 A# \. D* a4 R, l6 l
  7. work' b. u% u' [# N' j' W
  8. layer- x2 g3 D- d8 Z& [( h, W# ?
  9. work; H7 T. Q, ?# [" g, [
  10. 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里面的符号:

  1. 0 D1 K( A+ H3 `. X& M$ O/ \
  2. $ nm -CD libconflict.so
    8 f# A  r+ c0 I, D
  3.                  w _ITM_deregisterTMCloneTable9 ~4 V9 p/ ?* m
  4.                  w _ITM_registerTMCloneTable! F5 m. X& W; G8 @3 a! {! j8 u
  5. 000000000000065a T DoLayer()+ u3 f  @/ T& c/ V& z* V: X0 o
  6. 0000000000000672 T DoThing()* K" L9 O4 f2 @. s
  7. 0000000000201030 B __bss_start
    0 ~3 J' R7 {$ C% @. i9 R" I1 M
  8.                  w __cxa_finalize
    - |, i4 m& p, {7 T7 z
  9.                  w __gmon_start__
    ! o, c. B9 V$ U- a3 E
  10. 0000000000201030 D _edata
    / W2 d5 n  |, T; l, P; ~: n
  11. 0000000000201038 B _end+ B4 k; e" {6 J' \! [
  12. 0000000000000688 T _fini
    . q, S! x0 W# v$ Y# g: A, G
  13. 0000000000000528 T _init
    6 a; u1 X* a- U( |1 j* c6 `- O
  14.                  U puts
复制代码
1 \( a6 g5 i, }+ f. I' f7 O
如果把符号隐藏掉:
  1. ! H# s; _% B+ u; W& r  J
  2. $ g++ -fvisibility=hidden -c layer.cc -o layer.o
    ' I: y1 R- W9 q; w
  3. $ g++ -fvisibility=hidden -c conflict.cc -o conflict.o
    ( r! \; l  G) }
  4. $ g++ -shared layer.o conflict.o -o libconflict.so
    % [& r. y. e9 ]
  5. 再使用nm看一下libconflict.so里面的符号:* S. f1 h$ w& f+ N  L
  6. $ nm -CD libconflict.so- j2 e6 C" R0 ?
  7.                  w _ITM_deregisterTMCloneTable
    6 _/ i7 b- ]! B. b/ E0 Q
  8.                  w _ITM_registerTMCloneTable4 c- m5 }9 K, M+ L6 P2 n
  9. 0000000000201028 B __bss_start
    5 J, p. R2 _. {' k. q
  10.                  w __cxa_finalize8 E) k# R" j  j/ R! p8 T+ k
  11.                  w __gmon_start__1 {4 ~4 C( G* x/ z
  12. 0000000000201028 D _edata; O, j' b; \- d+ {2 B' _
  13. 0000000000201030 B _end6 v! B! D* V+ t- f* G
  14. 0000000000000618 T _fini
    - T  |8 a& j& }# u" _" V
  15. 00000000000004c0 T _init0 s+ J1 w' |5 B3 b6 ]# G6 a
  16.                  U puts
复制代码

" X0 S! c; p& h% l5 B, d
这样的话main函数肯定不能调用DoLayer啦,因为DoLayer符号没有暴露出来:
  1. , W' |4 ]* B9 V, m3 L
  2. $ g++ -s -L. -o main.exe -lmain -lconflict9 M9 B$ x# z7 G4 u
  3. ./libmain.a(main.o): In function `main':
    9 j1 _" Z  p9 @) V* ^
  4. main.cc:(.text+0x16): undefined reference to `DoLayer()'
    9 m3 \& b: R0 B$ Y3 b$ [
  5. collect2: error: ld returned 1 exit statu
复制代码

0 Z6 ]! C5 D* y7 b4 j2 Y
那怎么暴露出来特定符号呢,直接看代码,改动了layer.cc:- F" i: z' N. }, Q
  1. ( \' L, e" o) s' _* p
  2. #include <stdio.h>! a8 s  n$ d: H' d: p
  3. void DoThing();) P/ k- e" \3 I0 t
  4. __attribute__ ((visibility ("default"))) void DoLayer() {3 |. y: ^0 V/ i
  5.     printf("layer \n");- U$ U& V% E% u( l& G0 q# h3 m
  6.     DoThing();( {, {9 g8 i# I
  7. }
复制代码
! K# n- B% ^# @
再编译链接运行看看结果:

  1.   ~+ i+ @2 k1 {9 f$ {2 V! s
  2. $ g++ -fvisibility=hidden -c layer.cxx -o layer.o
    0 v5 Z- t! P4 S1 p7 E# \
  3. $ g++ -shared layer.o conflict.o -o libconflict.so& y& n; L3 J. T. o1 v
  4. $ g++ -s -L. -o main.exe -lmain -lconflict
    6 b( n: V% i" i9 i5 W1 Z
  5. $ LD_LIBRARY_PATH=. ./main.exe
    5 e. X1 z% Y! s+ C
  6. start3 J, k1 j0 n$ H1 h
  7. work: k. Q, K6 L! T6 `9 A  M  v
  8. layer6 @/ [& x, M+ g" I9 ]; Z
  9. conflict
    4 p9 c& c9 L  u  e
  10. 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文件,告诉编译器要导出的所有符号有哪些。
  1. 7 V: C& |! A$ S; W5 `" \/ e- i
  2. export.txt! `" y4 D, r% i7 W2 u
  3. 7 Q, _! ^# f) G
  4. {, h8 z3 `7 j5 d" ?
  5. global: *DoLayer*;0 J; K  u  @1 v2 H% `' Q$ l9 z$ ]
  6. local: *;
    ! T% H6 }7 t) b' [
  7. };8 q) @. h9 N& {6 G6 K
  8. 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
赞 收藏 评论0 发布时间:2021-1-26 09:43

举报

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