难得的周日,看了些动态链接的文章。 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函数的反汇编代码: