GCC的符号可见性——解决多个库同名符号冲突问题

码界星芒
• 阅读 3040

最近项目遇到一些问题,场景如下

主程序依赖了两个库libA的funcA函数和libB的funcB函数。示意的代码(main.cpp)如下:

include  int funcA(int, int); int funcB(int, int); int main() { printf("%d,", funcA(2, 1)); printf("%d\n", funcB(2, 1)); return 0;

}
libA示意实现(libA.cpp)如下:

int subfunc(int a, int b) { return a + b;
} int funcA(int a, int b) { return subfunc(a, b);
}
libB示意实现(libB.cpp)如下:

int subfunc(int a, int b) { return a - b;
} int funcB(int a, int b) { return subfunc(a, b);
}
可见funcA调用了libA中的内部函数subfunc,funcB调用了libB中的内部函数subfunc,这两个subfunc实现不同,但不幸的是名字不小心起得一样了

这时我们尝试编译并运行:

g++ -fPIC libA.cpp -shared -o libA.so
g++ -fPIC libB.cpp -shared -o libB.so
g++ main.cpp libA.so libB.so -o main
export LD_LIBRARY_PATH=.
./main 
我们期望的结果是3,1(funcA和funcB各自调用不同的subfunc实现),

实际得到的结果是3,3(funcA和funcB都调用了libA中的subfunc实现)

原因

我们通过readelf来查看符号:

$ readelf -a libA.so | grep subfunc
000000200a60 000200000007 R_X86_64_JUMP_SLO 0000000000000708 _Z7subfuncii + 0 
2: 0000000000000708 20 FUNC GLOBAL DEFAULT 10 _Z7subfuncii
45: 0000000000000708 20 FUNC GLOBAL DEFAULT 10 _Z7subfuncii
$ readelf -a libB.so | grep subfunc
000000200a60 000200000007 R_X86_64_JUMP_SLO 0000000000000708 _Z7subfuncii + 0 
2: 0000000000000708 22 FUNC GLOBAL DEFAULT 10 _Z7subfuncii
45: 0000000000000708 22 FUNC GLOBAL DEFAULT 10 _Z7subfuncii
可见libA和libB里面都有subfunc符号,名字完全一样,而且都是GLOBAL的

GLOBAL的符号即全局的符号,同名的全局符号会被认为是同一个符号,由于main先加载了libA,得到了libA中的subfunc符号,再加载libB时,就把libB中的subfunc忽略了。

解决方案

这其实是符号的可见性(Symbol Visibility)问题,既然有GLOBAL符号,那自然会有LOCAL符号,LOCAL的符号只在当前lib可见,全局不可见。

如何将符号变成LOCAL的呢,最直接的就是加上visibility为hidden的标志,修改后的libA.cpp:

__attribute__ ((visibility ("hidden"))) int subfunc(int a, int b) { return a + b;
} int funcA(int a, int b) { return subfunc(a, b);
}
再重新编译执行,可以得到结果为3,1,成功!这里再查看一下libA的符号:

$ readelf -a libA.so | grep subfunc 
40: 00000000000006a8 20 FUNC LOCAL DEFAULT 10 _Z7subfuncii 
可见subfunc符号已经变成了LOCAL

默认LOCAL

上面的方法可以解决问题,但是,实际情况往往是,libA里面有很多的内部函数,而暴露给外部的只有少数,能不能指定少数符号为GLOBAL,其它的都是LOCAL呢?答案是肯定的,修改libA.cpp如下:

int subfunc(int a, int b) { return a + b;
}

__attribute__ ((visibility ("default"))) int funcA(int a, int b) { return subfunc(a, b);
}
这时,libA的编译参数需要加上-fvisibility=hidden:

g++ -fPIC libA.cpp -shared -fvisibility=hidden -o libA.so
同样可以解决问题。

跨平台兼容性

windows平台对于符号的行为是不一样的,windows默认动态库里符号是LOCAL的,通过__declspec(dllexport)来声明GLOBAL符号,所以可以用下面的方式来兼容:

if defined _WIN32 || defined __CYGWIN__ #ifdef BUILDING_DLL #ifdef __GNUC__ #define DLL_PUBLIC __attribute__ ((dllexport)) #else #define DLL_PUBLIC __declspec(dllexport) // Note: actually gcc seems to also supports this syntax. #endif #else #ifdef __GNUC__ #define DLL_PUBLIC __attribute__ ((dllimport)) #else #define DLL_PUBLIC __declspec(dllimport) // Note: actually gcc seems to also supports this syntax. #endif #endif #define DLL_LOCAL #else #if __GNUC__ >= 4 #define DLL_PUBLIC __attribute__ ((visibility ("default"))) #define DLL_LOCAL  __attribute__ ((visibility ("hidden"))) #else #define DLL_PUBLIC #define DLL_LOCAL #endif #endif

隐藏外部依赖的符号

我遇到的实际情况比上面更复杂一些,subfunc并不是在libA中实现的,而是在另一个外部库libsubfunc.a中实现的。libA通过包含头文件来获取到这个函数:

include "subfunc.h" int funcA(int a, int b) { return subfunc(a, b);

}
上面的-fvisibility仅对实现生效,不能对声明生效。但libsubfunc.a是第三方库,我们不能去改它的代码,也不能改它的头文件,对于这种情况,gcc提供了下面方式来支持:

pragma GCC visibility push(hidden) #include "subfunc.h" #pragma GCC visibility pop int funcA(int a, int b) { return subfunc(a, b);

}
这种方式更方便灵活。

如果是dlopen载入so可以通过参数控制。

点赞
收藏
评论区
推荐文章
Stella981 Stella981
3年前
Cppcheck简单测评
测评代码如下:include <cstdioinclude <stringint main(void){ // 多了或者少了格式化参数 ::printf("%d%d%d\n", int(1), int(2)); ::printf("%d%d%d\n", int(1), int(2
Wesley13 Wesley13
3年前
C语言
主函数程序的入口main函数不可没有,也不可出现多行main函数,main有且仅有一个。intmain()return0main前面的int指的是函数调用后返回一个整型值。int是整形。返回0,0是整数voidmain是过时的函数。库函数C语言本身提供给我们使用的函数。数据类型:char字符型
Wesley13 Wesley13
3年前
Java 8 中的 Lambda 表达式 vs. Kotlin 中的 Lambda
直接上一段Kotlin的函数式编程的代码:packagecom.easykotlin.lec02funsum1(x:Int,y:Int):Int{returnxy}funsum2(x:Int,y:Int)xy//
Stella981 Stella981
3年前
C++ lambda函数
lambda函数lambda函数是什么?还是直接看代码比较容易理解:intmain(){  inta1;  intb2;  //定义一个lambda函数  autosum\\(intx,inty)int{    returnxy;  
Stella981 Stella981
3年前
Lua ip to int 和 int to ip
function_M.ipToInt(str)localnum0ifstrandtype(str)"string"thenlocalo1,o2,o3,o4str:match("(%d)%.(%d)%.(%d)%.(%d)")num2^24o1
Stella981 Stella981
3年前
Linux int $0x80
exp1:sys\_exit().section.data.section.text.globl\_start\_start:       movl   $1,%eax    \_sys\_call       movl   $0,%ebx    \_return0       int    
Stella981 Stella981
3年前
JVM 字节码指令表
字节码助记符指令含义0x00nop什么都不做0x01aconst\_null将null推送至栈顶0x02iconst\_m1将int型1推送至栈顶0x03iconst\_0将int型0推送至栈顶0x04iconst\_1将int型1推送至栈顶0x05ic
Stella981 Stella981
3年前
C++11 thread mutex 我怎么感觉我被坑了。
二话不说,直接上代码了。include <threadinclude <iostreaminclude <mutexusing namespace std;volatile int shared_value  0;volatile int running_count  1
Wesley13 Wesley13
3年前
9999二进制 及 x=x&(x
题目:以下代码结果是多少?\include<iostreamusingnamespacestd;int func(int x){int count\0;while(x){count;x\x&(x\1);
Stella981 Stella981
3年前
C++中int的构造函数
引言看到有本书中是这样写的:std::map<intd;da;如果map中没有\\操作符会自动插入,插入时会调用默认构造函数初始化,int会初始化吗?大牛写的书应该不会有错。问题曾经见过有这样的写法:inta(10);貌似是构造函数的语法。那么这些呢:
Python进阶者 Python进阶者
2年前
盘点一个Pandas处理的基础题目
大家好,我是皮皮。一、前言前几天在Python星耀交流群【dcpeng】问了一个Pandas基础的问题,提问截图如下:原始代码如下:importpandasaspdfromdataclassesimportdataclass@dataclassclassC:a:int0b:int0@propertydefad
码界星芒
码界星芒
Lv1
方舟安可极,离思故难任!
文章
4
粉丝
0
获赞
0