wsl,64位机,编译共享库

难得的周日,看了些动态链接的文章。 IOT物联网小镇
自己也想编译一个动态库,中间遇到了一些问题。写篇文章记录下。

b.c 代码

#include <stdio.h>

int b = 30;

void func_b(void)
{
    printf("in func_b. b = %d \n", b);
}

a.c代码如下. a.c依赖b.c

#include <stdio.h>

// 内部定义【静态】全局变量
static int a1 = 10;

// 内部定义【非静态】全局变量
int a2 = 20;

// 声明外部变量
extern int b;

// 声明外部函数
extern void func_b(void);

// 内部定义的【静态】函数
static void func_a2(void)
{
    printf("in func_a2 \n");
}

// 内部定义的【非静态】函数
void func_a3(void)
{
    printf("in func_a3 \n");
}

// 被 main 调用
void func_a1(void)
{
    printf("in func_a1 \n");

    // 操作内部变量
    a1 = 11;
    a2 = 21;

    // 操作外部变量
    b  = 31;

    // 调用内部函数
    func_a2();
    func_a3();

    // 调用外部函数
    func_b();
}

main.c代码如下 main依赖a.c

#include <stdio.h>
#include <unistd.h>
#include <dlfcn.h>

// 声明外部变量
extern int a2;
extern void func_a1();

typedef void (*pfunc)(void);

int main(void)
{
    printf("in main \n");

    // 打印此进程的全局符号表
    void *handle = dlopen(0, RTLD_NOW);
    if (NULL == handle)
    {
        printf("dlopen failed! \n");
        return -1;
    }

    printf("\n------------ main ---------------\n");
    // 打印 main 中变量符号的地址
    pfunc addr_main = dlsym(handle, "main");
    if (NULL != addr_main)
        printf("addr_main = 0x%x \n", (unsigned int)addr_main);
    else
        printf("get address of main failed! \n");

    printf("\n------------ liba.so ---------------\n");
    // 打印 liba.so 中变量符号的地址
    unsigned int *addr_a1 = dlsym(handle, "a1");
    if (NULL != addr_a1)
        printf("addr_a1 = 0x%x \n", addr_a1);
    else
        printf("get address of a1 failed! \n");

    unsigned int *addr_a2 = dlsym(handle, "a2");
    if (NULL != addr_a2)
        printf("addr_a2 = 0x%x \n", addr_a2);
    else
        printf("get address of a2 failed! \n");

    // 打印 liba.so 中函数符号的地址
    pfunc addr_func_a1 = dlsym(handle, "func_a1");
    if (NULL != addr_func_a1)
        printf("addr_func_a1 = 0x%x \n", (unsigned int)addr_func_a1);
    else
        printf("get address of func_a1 failed! \n");

    pfunc addr_func_a2 = dlsym(handle, "func_a2");
    if (NULL != addr_func_a2)
        printf("addr_func_a2 = 0x%x \n", (unsigned int)addr_func_a2);
    else
        printf("get address of func_a2 failed! \n");

    pfunc addr_func_a3 = dlsym(handle, "func_a3");
    if (NULL != addr_func_a3)
        printf("addr_func_a3 = 0x%x \n", (unsigned int)addr_func_a3);
    else
        printf("get address of func_a3 failed! \n");


    printf("\n------------ libb.so ---------------\n");
    // 打印 libb.so 中变量符号的地址
    unsigned int *addr_b = dlsym(handle, "b");
    if (NULL != addr_b)
        printf("addr_b = 0x%x \n", addr_b);
    else
        printf("get address of b failed! \n");

    // 打印 libb.so 中函数符号的地址
    pfunc addr_func_b = dlsym(handle, "func_b");
    if (NULL != addr_func_b)
        printf("addr_func_b = 0x%x \n", (unsigned int)addr_func_b);
    else
        printf("get address of func_b failed! \n");

    dlclose(handle);

    // 操作外部变量
    a2 = 100;

    // 调用外部函数
    func_a1();

    // 为了让进程不退出,方便查看虚拟空间中的地址信息
    while(1) sleep(5);
    return 0;
}
gcc b.c -fPIC -shared -o libb.so -m64
gcc a.c -fPIC -shared -o liba.so -l b -L. -m64
gcc main.c  -L. -l a -l b -o main -m64 -ldl    -- ldl一定要加上


选项	说明
-L	    指明共享库所在的目录
-l	    指明共享库的名称,该名称是处在头lib 和后缀.so 中的名称。例如上面共享库libb.so,则参数为 -l b 。
查看应用程序依赖的共享库: ldd libb.so
查看目标文件中定义的符号: nm libb.so

遇到问题一:
gcc编译报错如下
在这里插入图片描述
解决方案: 需要在编译时加上-ldl。

遇到问题二:

编译a.c后, ldd liba.so, 发现libb.so => not found.
在这里插入图片描述
解决方案: 设置LD_LIBRARY_PATH.
参考此方案 export LD_LIBRARY_PATH 的使用

export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH

一切都弄好好,重新ldd liba.so,ldd main. 一切都正常。
在这里插入图片描述

执行main
在这里插入图片描述

通过 ps -ef | grep main 获取进程id.
在这里插入图片描述
通过指令:$ cat /proc/[进程的 pid]/maps 读取程序的虚拟内存区域。
在这里插入图片描述

readelf -s liba.so 查看符号表.
动态链接库中保护两个符号表:.dynsym(动态符号表: 表示模块中符号的导出、导入关系) 和 .symtab(符号表: 表示模块中的所有符号);
.symtab 中包含了 .dynsym;

红色矩形框前面的Ndx列是数字,表示该符号位于当前文件的哪一个段中(即:段索引);

绿色矩形框前面的Ndx列是UND,表示这个符号没有找到,是一个外部符号(需要重定位);
在这里插入图片描述
在这里插入图片描述

通过readelf -S liba.so指令来看一下这个ELF文件中都有哪些section:
在这里插入图片描述
在这里插入图片描述
可以看到:一共有29个section,其中的22、23就是两个GOT表。

通过指令 readelf -l liba.so ,来查看一下segment信息:装载器并不是把这些sections分开来处理,而是根据不同的读写属性,把多个section看做一个segment。
在这里插入图片描述

在这里插入图片描述

那么:liba.so通过什么方式来告诉动态链接器:需要对.got和.got.plt这两个表中的表项进行地址重定位呢?

在静态链接的时候,目标文件是通过两个重定位表.rel.text和.rel.data这两个段信息来告诉链接器的。

对于动态链接来说,也是通过两个重定位表来传递需要重定位的符号信息的,只不过名字有些不同:.rela.dyn和.rela.plt。

通过指令 readelf -r liba.so来查看重定位信息:

在这里插入图片描述
从上图可以看出:
liba.so 引用了外部符号 b,类型是 R_386_GLOB_DAT,这个符号的重定位描述信息在 .rela.dyn 段中;

liba.so 引用了外部符号 func_b, 类型是 R_386_JUMP_SLOT,这个符号的重定位描述信息在 .rela.plt 段中;

如下图所以,该load段的虚拟内存地址为0x0000000000003df0. 因此我们可以计算出.got, .got.plt的虚拟内存地址。
在这里插入图片描述
在这里插入图片描述

objdump -d liba.so 反汇编liba.so代码。 这里贴出func_a1函数的反汇编代码:
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值