连接器重复符号处理(一)动态库重复符号处理规则

文章目录

      • 链接静态库时有重复符号
      • 动态库链接时处理重复符号的规则
        • 符号的优先级
      • 例子
        • 可执行程序的二进制文件符号与动态库ABI函数冲突
        • 不同动态库ABI符号冲突
        • 动态库的内部符号与动态的导出符号重复。

在日常开发工作中,经常会遇到符号重复定义的问题,这篇文章主要描述的是链接器在处理重复符号时的规则。

在解析引用的过程中,最常发生的问题就是会出现重复符号,该问题发生在链接的最后阶段,具体为在所有可用符号列表中包含两个甚至更多同名符号,连接器就会报错,一般如下错误:

./libsecond.a(secondfile.o):在函数‘duplicateFunction(int)’中:
/root/test/cf/secondfile.cpp:6: duplicateFunction(int) 的多重定义
./libfirst.a(firstfile.o):/root/test/cf/firstfile.cpp:6:第一次在此定义

判断相同的符号,在C和C++有些不同:

  • 在C中,只要函数,结构体,数据类型的名称一样,那么就认为这些符号是相同的。
  • 在C++中,因为函数重载的存在,只有函数的名称和参数列表中每个参数的类型都相同时函数才是重复的。

链接静态库时有重复符号

下面两个文件 firstfile.cppsecodfile.cpp分别编译成静态库,再链接到可执行程序中。

void firstFunction();
void duplicateFunction(int a);
#include 
void firsFunction() {
    std::cout<<"first function"<<std::endl;
}

void duplicateFunction(int a) {
    std::cout<<"duplicate function in first file "<<a<<std::endl;
}

gcc -Wall -g -c firstfile.cpp
ar -rcs libfirst.a firstfile.o

void secondFunction();
void duplicateFunction(int b);
#include 
void secondFunction() {
    std::cout<<"second function"<<std::endl;
}

void duplicateFunction(int b) {
    std::cout<<"duplicate function in secode file "<<b<<std::endl;
}

gcc -Wall -g -c secondfile.cpp
ar -rcs libsecond.a secondfile.o

#include 
#include "firstfile.h"
#include "secondfile.h"
int main() {
    duplicateFunction(0);
    secondFunction();
    return 0;
}

g++ main.cpp -L. -lfirst -lsecond -o app

报错:

./libsecond.a(secondfile.o):在函数‘duplicateFunction(int)’中:
/root/test/cf/secondfile.cpp:6: duplicateFunction(int) 的多重定义
./libfirst.a(firstfile.o):/root/test/cf/firstfile.cpp:6:第一次在此定义

程序app链接libfirst.alibsecond.a链接静态库不允许有重复符号

动态库链接时处理重复符号的规则

链接器在处理动态库的符号链接时,比静态库复杂些,对重复符号并非是零容忍,所以即使多个动态库中有相同的符号,链接器也有一套规则选定一个符号,而非直接报错
对通过dlopen动态加载的方式并没重复符合的问题。
对静态链接的方式链接动态库处理重复符号为以下规则:

  • 符号的优先级优先级是根据在进程内存映射中不同部分的符号赋予了不同的重要等级
  • 链接时指定的动态库链接顺序:如果同等优先级的代码中存在两个或多个重复符号,相比于后出现在动态库链接列表中的动态库,更早传递给链接器的动态库中的符号将有更高的优先级。

符号的优先级

  • 第1 优先级符号,客户程序二进制文件符号(链接动态库了的程序)。
  • 第2优先级符号,动态库可见符号(动态库导出了的符号)。
  • 第3优先级符号,不参与链接的符号(动态库中未导出的符号)。

处理重复符号是通常不考虑静态符号(static方法或变量),无论符号属于客户二进制文件还是属于静态链接的动态库。

例子

可执行程序的二进制文件符号与动态库ABI函数冲突

改例子中包含 一个静态库一个动态库客户程序静态库与动态库中有一个同名的函数

int staticLibFirstFunction(int x);
int staticLibSecondFunction(int x);
int sharedStaticDuplicateFunction(int x);
#include 
int staticLibFirstFunction(int x){
    std::cout<<__FUNCTION__<<std::endl;
	return x+1;
}

int staticLibSecondFunction(int x){
    std::cout<<__FUNCTION__<<std::endl;
    return x+2;
}

int sharedStaticDuplicateFunction(int x){
    std::cout<<"staticlib:"<<__FUNCTION__<<std::endl;
    return 0;
}

gcc -Wall -g -c staticlib.cpp
ar -rcs libstaticlib.a staticlib.o

int shlibFunction(void);
int sharedStaticDuplicateFunction(int x);
#include 
int shlibFunction(void) {
    std::cout<<__FUNCTION__<<std::endl;
    return 0;
}

int sharedStaticDuplicateFunction(int x) {
    std::cout<<"sharedLib:"<<__FUNCTION__<<std::endl;
    return 0;
}

gcc -Wall -g -c shlib.c
gcc -fIPC -shared shlib.o -Wl,-soname,libshlib.so.1 -o libshlib.so.1.0.0
ldconfig -n .
ln -s libshlib.so.1 libshlib.so

ldconfig -n是让ldconfig创建所需动态库版本的软连接(这里是libshlib.so.1,因为soname是这个)。

#include "staticlib.h"
#include "shlib.h"
int main() {
    staticLibFirstFunction(0);
    staticLibSecondFunction(1);
    shlibFunction();
    sharedStaticDuplicateFunction(2);
    return 0;
}

gcc -Wall -g -I…/ -c main.c
gcc main.o -L…/ -lstaticlib -L…/sharedlib -lshlib -Wl,-R./ -o app

-Wl,-R 是给链接器传递设在rpath值,用于运行时定义依赖的动态库的路径,可以通过readelf -d的查看DT_RPATH/RPATH值。

运行结果

staticLibFirstFunction
staticLibSecondFunction
shlibFunction
staticlib:sharedStaticDuplicateFunction

对于在静态库和动态库中都定义了的符号sharedStaticDuplicateFunction,链接器找的是静态库中的符号。因为静态库中的符号就相当于是可执行程序的符号,是第一优先级符号

不同动态库ABI符号冲突

不同动态库的ABI符号冲突,两个动态库中有同名的导出符号,根据前面描述的优先级规则,两个动态库的符号都属于第二优先级,链接器将根据静态链接时的库的顺序选择一个。

int shlibFunction(void);
int firstshlibFunction(void);
#include 
int shlibFunction(void) {
    std::cout<<"firstshlib:"<<__FUNCTION__<<std::endl;
    return 0;
}

int firstshlibFunction(void) {
    std::cout<<__FUNCTION__<<std::endl;
    return 0;
}


gcc -Wall -g -c -fPIC firstshlib.cp
gcc -shared firstshlib.o -Wl,-soname,libfirstshlib.so.1 -o libfirstshlib.so.1.0.0
ldconfig -n .
ln -s libfirstshlib.so.1 libfirstshlib.so

动态库libfirstshlib.so,导出的符号为shlibFunctionfirstshlibFunction

int shlibFunction(void);
int secondshlibFunction(void);
int secondshlibAnotherFunction(void);
#include 
int shlibFunction(void) {
    std::cout<<"secondshlib:"<<__FUNCTION__<<std::endl;
    return 0;
}

int secondshlibFunction(void) {
    std::cout<<__FUNCTION__<<std::endl;
    return 0;
}

int secondshlibAnotherFunction(void) {
    shlibFunction();
    return 0;
}

gcc -Wall -g -fPIC -c secondshlib.cpp
gcc -shared secondshlib.o -Wl,-soname,libsecondshlib.so.1 -o libsecondshlib.so.1.0.0
ldconfig -n .
ln -s libsecondshlib.so.1 libsecondshlib.so

动态库libsecondshlib.so,导出的符号为shlibFunction, secondshlibFunction,secondshlibAnotherFunction。其中shlibFunction为重复符合。

#include 
#include "firstshlib.h"
#include "secondshlib.h"
int main() {
    shlibFunction();
    firstshlibFunction();
    secondshlibFunction();
    secondshlibAnotherFunction();
    return 0;
}

g++ -Wall -g -I./firstshlib -I./secondshlib -c main.cpp
g++ main.o -L./ -lfirstshlib -lsecondshlib -Wl,-R./ -o app

最后生成程序app时,首先链接的是libfirstshlib.so,所以输出结果如下:

firstshlib:shlibFunction
firstshlibFunction
secondshlibFunction
firstshlib:shlibFunction

动态中重复的符号shlibFunction,最终选择的是libfirstshlib.so的符号。
对函数动态库libsecondshlib.so中的函数secondshlibAnotherFunction(),在实现的内部调用的是重复符号shlibFunction(),它最终也是链接的libfirstshlib.so中的符号。

如果将链接顺序改为:g++ main.o -L./ -lsecondshlib -lfirstshlib -Wl,-R./ -o app,输出如下:

secondshlib:shlibFunction
firstshlibFunction
secondshlibFunction
secondshlib:shlibFunction

通过这个例子就可以说明,同优先级的符号,由链接时的顺序决定。

动态库的内部符号与动态的导出符号重复。

void shlibFunction();
void firstshlibFunction();
#include 
void shlibFunction() {
    std::cout<<"firstshlib:"<<__FUNCTION__<<std::endl;
}

void firstshlibFunction() {
    std::cout<<__FUNCTION__<<std::endl;
}

gcc -Wall -g -fPIC -c firstshlib/firstshlib.cpp
gcc -shared firstshlib.o -Wl,-soname,libfirstshlib.so.1 -o libfirstshlib.so.1.0.0
ldconfig -n .
ln -s libfirstshlib.so.1 libfirstshlib.so

动态库libfisrtshlib.so的导出符号为shlibFunction, firstshlibFunction

void secondshlibFunction();
#include 
static void shlibFunction() {
    std::cout<<"secondshlib,static function:"<<__FUNCTION__<<std::endl;
}

void secondshlibFunction() {
    shlibFunction();
}

gcc -Wall -g -fPIC -c secondshlib/secondshlib.cpp
gcc -shared secondshlib.o -Wl,-soname,libsecondshlib.so.1 -o libsecondshlib.so.1.0.0
ldconfig -n .
ln -s libsecondshlib.so.1 libsecondshlib.so

动态库libsecondshlib.so的导出符号为secondshlibFunction,它还有一个内部符号shlibFunction,被static修饰。它与libfirstshlib.so中的导出符号shlibFunction重名。

#include "firstshlib.h"
#include "secondshlib.h"

int main() {
    shlibFunction();
    firstshlibFunction();
    secondshlibFunction();
}

g++ -Wall -g -l./fisrtshlib -l./secondshlib -c main.cpp
g++ main.o -L./ -lfirstshlib -lsecondshlib -Wl,-R./ -o app

运行app,输出入下:

firstshlib:shlibFunction
firstshlibFunction
secondshlib,static function:shlibFunction

可执行程序app中的shlibFunction方法是链接的firstshlib中的方法,而secondshlibFunction中调用的shlibFunction链接的是libsecondshlib.so中的自己的内部方法,其实在生成libsecondshlib.so动态库时就确定了。在生成执行文件时,内部符号并不参与链接。

上面就是连接器链接动态库时,处理重复符号的规则,这套规则虽然能避免链接出错,但也可能会导致链接了错误的同名符号而导致逻辑的问题。
一般在开发阶段,应该尽可能的采用独立的命名规则来规避符合重复的问题,特别是在C++中通过命名空间的机制,可以很大程度的避免重复符号。但是对规模庞大,依赖第三方库的应用来说,重复符号的问题难以控制。那么就需要我们能知道这套规则,尽可能的使链接器能链接到正确的符号。

在下一篇文章中,将介绍这套规则可能会导致的问题及解决方法。

你可能感兴趣的:(C++编译系统,c++,开发语言)