0%

LuaJIT ffi与dlsym的姻缘(后续)

为了验证前文中的猜测,我写了一个假的dlsym()并连接进sysbench,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdio.h>

char *dlerror(void)
{
return "fake error";
}
void *dlopen (const char *__file, int __mode)
{
return NULL;
}

int dlclose (void *__handle)
{
return 0;
}

void *dlsym (void *__restrict __handle, const char *__restrict __name)
{
printf("Fakedl: Looking for %s\n",__name);
return NULL;
}

执行后结果如下:

1
2
3
4
5
6
7
root@test:~/sysbench-1.0.20/src# ./sysbench
sysbench 1.0.20 (using bundled LuaJIT 2.1.0-beta2)

Reading the script from the standard input:

Fakedl: Looking for db_destroy
PANIC: unprotected error in call to Lua API ([string "sysbench.sql.lua"]:207: fake error)

可以看到dlsym()打印的日志与dlerror()返回的错误信息,由此可以确认前文的猜测成立,现在得想办法来解决问题,前文已经说到解决思路1和3不太现实,只有第2种思路或许可以取巧,Let’s do it。首先需要确认dlsym()返回的指针与.symtab内的地址是否吻合(实在没有精力去看ELF资料,土办法上马),写一份测试代码test.c来确认:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <stdio.h>
#include <dlfcn.h>

int test1(int number)
{
return number*2;
}
int test2(int number)
{
return number*3;
}
int main( int argc, char *argv[])
{
int (*func)(int);
printf("test1@%016llx: %d\n", test1, test1(2));
printf("test1@%016llx: %d\n", test2, test2(2));
func = dlsym(NULL, "test1");
if (func)
printf("test1@%016x: %d\n", func, func(2));
func = dlsym(NULL, "test2");
if (func)
printf("test2@%016x: %d\n", func, func(2));
}

动态编译后执行后结果如下:

1
2
3
4
5
6
7
8
9
10
root@test:~/sysbench-1.0.20/src# readelf -s test | grep test
9: 00000000000008ca 14 FUNC GLOBAL DEFAULT 14 test1
18: 00000000000008d8 18 FUNC GLOBAL DEFAULT 14 test2

root@test:~/sysbench-1.0.20/src# ./test
test1@0000000081f3c8ca: 4
test2@0000000081f3c8d8: 6
root@test:~/sysbench-1.0.20/src# ./test
test1@00000000210838ca: 4
test2@00000000210838d8: 6

看起来运行时的函数地址相比符号表内的地址上叠加了一个全局内存地址,接下来看看静态编译后的结果:

1
2
3
4
5
6
7
8
9
root@test:~/sysbench-1.0.20/src# readelf -s test | grep test
1021: 0000000000400b5d 14 FUNC GLOBAL DEFAULT 6 test1
1480: 0000000000400b6b 18 FUNC GLOBAL DEFAULT 6 test2
root@test:~/sysbench-1.0.20/src# ./test
test1@0000000000400b5d: 4
test1@0000000000400b6b: 6
root@test:~/sysbench-1.0.20/src# ./test
test1@0000000000400b5d: 4
test1@0000000000400b6b: 6

静态编译后的函数地址与符号表内的地址完全一致,那么接下来就考虑如何实现一个hack版的dlsym()了,当然,需要高效率的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
#include <string.h>
#define add(name, addr) else if (!strcmp(__name, name)) \
return (void *)addr

void *dlsym (void *__restrict __handle, const char *__restrict __name)
{
if (!1)
return (void *)0x0000000000000000;
add("db_free_results_int",0x0000000000406f50);
add("db_init",0x0000000000406fa0);
add("db_bulk_do_insert",0x0000000000407a10);
...
}

hack版dlsym()中的add宏可由下面的shell脚本自动生成,sysbench中的LuaJIT只会调取db_和sb_开头的一些列数据库函数,所以只导出相关函数即可:

1
2
3
4
5
root@test:~/sysbench-1.0.20/src# readelf -s sysbench | grep FUNC | grep ' db_' | awk '{pf="0x";addr=(pf""$2);ff="add(\"";fs="\",";fl=");";name=(ff""$8""fs""addr""fl);print name}'
add("db_free_results_int",0x0000000000406f50);
add("db_init",0x0000000000406fa0);
add("db_bulk_do_insert",0x0000000000407a10);
...

最后连接将hack版dlsym()的.o文件放在-ldl前即可,sysbench终于可以跑起来了!

该方法绝非最优解,但却是我力所能及、最容易实现的方法,如果各位读者有更优方案请给我留言。