linux 显式链接so库

linux加载程序变成进程的过程

  • fork进程, 在内核创建进程相关内核项, 加载进程可执行文件
  • 查找依赖的so库, 加载映射虚拟地址
  • 初始化程序变量

动态库依赖越多, 进程启动就越慢, 并且发布程序的时候, 这些链接但没有使用的so同样要一起跟着发布, 否则进程启动时候找不到对应的so导致启动失败.

一些查看依赖的命令

  • 查看依赖关系: readelf -d xx.so

  • 查看链接到的so库: ldd xx.so

  • 查看不需要链接的so库: ldd -u xx.so

  • 编译时自动忽略无用的so库: gcc –as-needed

链接so库有两种途径: 显式和隐式

显式加载: 程序主动调用dlopen打开so库, 使用dlopen打开的so并不是在进程启动时候加载映射的, 而是当进程运行到调用dlopen代码地方才加载该so; 也就是说, 如果每个进程显示链接a.so, 但是如果发布该程序时候忘记附带发布该a.so, 程序仍然能够正常启动, 甚至如果运行逻辑没有触发运行到调用dlopen函数代码地方

显式加载的函数接口

The four functions dlopen(), dlsym(), dlclose(), dlerror() implement the interface to the dynamic linking loader.

  • void *dlopen(const char *filename, int flag);

The function dlopen() loads the dynamic library file named by the null-terminated string filename and returns an opaque “handle” for the dynamic library.

  • void *dlsym(void *handle, const char *symbol);

The function dlsym() takes a “handle” of a dynamic library returned by dlopen() and the null-terminated symbol name, returning the address where that symbol is loaded into memory.

  • int dlclose(void *handle);

The function dlclose() decrements the reference count on the dynamic library handle handle.
If the reference count drops to zero and no other loaded libraries use symbols in it, then the dynamic library is unloaded.

  • char *dlerror(void);

The function dlerror() returns a human readable string describing the most recent error that occurred from dlopen(), dlsym() or dlclose() since the last call to dlerror().

c调用示例:

//add.h
int add(int a, int b);
//add.c
#include <stdio.h>
#include "add.h"
int add(int a, int b)
{
   return a+b;
}
//编译:gcc -shared -o add.so add.c

//测试
//test.c
#include <stdio.h>
#include <dlfcn.h>

typedef int(*AddFunc)(int,int);

int main()
{
    AddFunc add;
    void* dp = NULL;

    dp = dlopen("./add.so",RTLD_LAZY );
    if(dp==NULL)
    {
        printf("dlopen failed\n");
        return 0;
    }
    add = (AddFunc)dlsym(dp,"add");
    printf("dlopen success\nresult=%d\n",add(3,4));
    dlclose(dp);

    return 0;
}
//编译: g++ -o main main.cpp -ldl

备注: 如果将add改为cpp后缀, 然后编译生成so文件, test.cpp在调用dlsym函数时会出错, 原因不详! 这应该是与name mangling有关. 解决办法: 在add.h中函数声明时, 增加extern “C”防止其符号名被mangle (用 extern “C”声明的函数将使用函数名作符号名,就像C函数一样)

c++加载类调用示例

//BTClass.h
#ifndef BTCLASS_H
#define BTCLASS_H

class BTClass
{
public:
    BTClass(){}
    virtual ~BTClass(){}

    virtual void Hello() = 0;
};

// the types of the class factories
typedef BTClass* create_t();
typedef void destroy_t(BTClass*);

#endif // BTCLASS_H

//TClass.h
#ifndef TCLASS_H
#define TCLASS_H

#include "BTClass.h"
#include <iostream>

class TClass : public BTClass
{
public:
    TClass(){}
    virtual ~TClass(){}
    virtual void Hello();
};

// the class factories
extern "C" BTClass* create()
{
    return new TClass;
}

extern "C" void destroy(BTClass* p)
{
    delete p;
}

#endif // TCLASS_H

//TClass.cpp
#include "TClass.h"

void TClass::Hello()
{
    std::cout << "this is a test for class" << std::endl;
}

//Test:main.cpp
#include <iostream>
#include <dlfcn.h>
#include "BTClass.h"

int main()
{
    void* hd = dlopen("./libtclass.so",RTLD_LAZY);
    if(!hd)
        std::cerr << "Can not load lib: " << dlerror() << std::endl;

    dlerror();

    create_t* createTClass = (create_t*)dlsym(hd,"create");
    const char* dlsym_error = dlerror();
    if(dlsym_error)
    {
        std::cerr << "Can not load symbol create: " << dlsym_error << std::endl;
        return 0;
    }

    destroy_t* destroyTClass = (destroy_t*)dlsym(hd,"destroy");
    dlsym_error = dlerror();
    if(dlsym_error)
    {
        std::cerr << "Can not load symbol destroy: " << dlsym_error << std::endl;
        return 0;
    }

    BTClass* piBTClass = createTClass();
    piBTClass->Hello();

    destroyTClass(piBTClass);

    dlclose(hd);

    std::cout << "this is the end!" << std::endl;
    return 0;
}

extern “C”的两种声明形式

//内联形式: 外部链接(extern linkage)和C语言链接(language linkage)
extern "C" int foo;
extern "C" void bar();
//等价于花括号形式:这里只是语言链接形式
extern "C" {
    extern int foo;
    extern void bar();
}

对于函数来说,extern和non-extern的函数声明没有区别,但对于变量就有不同了

extern "C" int foo;//是个声明
extern "C" {//不仅是声明,也可以是定义
    int foo;
}

编译使用-fPIC的作用(Position Independent Code)

  • 不加-fPIC生成的so, 必须要在加载到用户程序的地址空间时, 重定向所有表目
  • 加上-fPIC生成的so, 与位置无关; 这样的代码本身就能被放到线性地址空间的任意位置,无需修改就能正确执行。通常的方法是获取指令指针的值,加上一个偏移得到全局变量/函数的地址
  • 加上-fPIC生成的so, 可以实现多个进程共享

参考链接

猜你喜欢

转载自blog.csdn.net/robothj/article/details/80619942