看内核代码,发现在内核在加载module时将 全局的结构体变量注册到链表中有以下几种方式。总结以下
第一种 方式,在so模块中专门写一个函数用来将本模块中的全局变量注册到链表中。例子
#include <stdio.h>
#include "main.h"
static void print_func()
{
printf("%s\n","test_so1");
}
static struct so_funcs my_func = {
.name="test_so1",
.do_func=print_func,
};
void init_test_so1()
{
test_funcs[0]=&my_func;
}
方式, 是dlopen打开,以后调用dlsym 找到 init_test_so1函数地址,并执行这个函数。
第二种方式,通过constructor attribute,在加载so时自动执行init函数,代码如下
#include <stdio.h>
#include "main.h"
static void print_func()
{
printf("%s\n","test_so2");
}
static struct so_funcs my_func = {
.name="test_so2",
.do_func=print_func,
};
void __attribute__((constructor)) init_test_so2()
{
test_funcs[1]=&my_func;
}
由于有attribute((constructor)) ,dlopen在打开so库时,会自动执行init_test_so2函数将全局变量注册到链表中。
第三种方式,与第一中方式类似,第一种方式通过dlsym找到函数符号地址,执行函数,第三种找到符号变量,然后注册到链表中,在查找符号变量时 有两种方式,第一种,通过符号变量名查找变量地址,第二种通过将变量放在特殊的section中,通过section查找地址。例子如下
#include <stdio.h>
#include "main.h"
static void print_func()
{
printf("%s\n","test_so3");
}
static struct so_funcs my_func = {
.name="test_so3",
.do_func=print_func,
};
static struct so_funcs __attribute__((section(".my_sections"))) *module_func = &my_func;//放在特殊的section中 my_sections.在主程序中会利用这个查找
struct so_funcs __attribute__((section(".my_sections"))) *module_func1 = &my_func;
main函数如下
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <link.h>
#include <dlfcn.h>
#include "main.h"
#define test1_path "./libtest_so1.so"
#define test2_path "./libtest_so2.so"
#define test3_path "./libtest_so3.so"
struct so_funcs *test_funcs[3];
void analysis_test3(void *test3)
{
struct link_map *map;
dlinfo(test3, RTLD_DI_LINKMAP, &map);
printf("%p: %s: %p\n", (void *)map->l_addr, map->l_name, map->l_ld);
test_funcs[2] = *(struct so_funcs **)((unsigned long)(map->l_ld)+(unsigned long)(0x1060-0xe18)); //注意 这个地方的0x1060 和0xe18是通过objdump -h libtest_so3.so查到的。可以做到自动化查询,这只是一个简单的例子没有做。
printf("----------%p\n",test_funcs[2]);
}
int main()
{
void *test1_handle=NULL;
void *test2_handle=NULL;
void *test3_handle=NULL;
void (*register_func)();
int i=0;
for (i=0;i<3;i++)
test_funcs[i]=NULL;
test1_handle = dlopen(test1_path,RTLD_LAZY|RTLD_GLOBAL);
printf("address = %p\n",test1_handle);
printf("error %s\n",dlerror());
test2_handle = dlopen(test2_path,RTLD_LAZY|RTLD_GLOBAL);
printf("address = %p\n",test2_handle);
printf("error %s\n",dlerror());
test3_handle = dlopen(test3_path,RTLD_LAZY|RTLD_GLOBAL);
printf("address = %p\n",test3_handle);
printf("error %s\n",dlerror());
register_func=(void(*)())dlsym(test1_handle,"init_test_so1");
printf("address is %p\n",register_func);
printf("error %s\n",dlerror());
register_func();
analysis_test3(test3_handle);
for(i=0;i<3;i++){
if(test_funcs[i]!=NULL){
printf("funcs %s is run\n",test_funcs[i]->name);
test_funcs[i]->do_func();
}else{
printf("funcs %d is NULL\n",i);
}
}
struct so_funcs *test33=*(struct so_funcs **)dlsym(test3_handle,"module_func1");
printf("last address %p\n",test33);
return 0;
}
main.c文件非常,唯一需要注意的就是如何查找 指定section的首地址的问题,本例子简单的使用了 dynamic section 偏移和my_sections偏移,以及指导dynamic内存中的地址后计算的。现在是通过手工计算出来的,可以做成自动化。
Makefile 如下
all:libtest_so1.so libtest_so2.so libtest_so3.so
gcc -rdynamic -o main main.c -ldl
libtest_so1.so:test_so1.c
gcc -c -fPIC test_so1.c
gcc test_so1.o -shared -o libtest_so1.so
libtest_so2.so:test_so2.c
gcc -c -fPIC test_so2.c
gcc test_so2.o -shared -o libtest_so2.so
libtest_so3.so:test_so3.c
gcc -c -fPIC test_so3.c
gcc test_so3.o -shared -o libtest_so3.so
clean:
rm -rf *.o *.so main
注意在编译main 程序时必须加上rdynamic 否则在调用dlopen时会报错找不到 main.c中定义的全局变量