近日在静态编译sysbench时遇到了一个问题,运行编译好的静态可执行文件时报错:
1 | root@test:~/sysbench-1.0.20/src# ./sysbench |
sysbench内置了LuaJIT解释器用于执行SQL性能测试,动态编译时运行正常,按提示得知错误为符号db_destroy未找到,首先使用readelf打印可执行文件sysbench的符号表看看有没有名叫db_destory的函数:
1 | root@test:~/sysbench-1.0.20/src# readelf -s sysbench | grep db_destroy |
结果显示该函数存在,从错误提示可见该错误是由Lua抛出,而非C代码,遂定位Lua代码:
1 | root@test:~/sysbench-1.0.20/src# find . -type f -name "sysbench.sql.lua" |
该Lua文件以C String的形式被编译进最终可执行文件内,在当前目录内的Makefile.am内可见以下代码:
1 | BUILT_SOURCES = sysbench.lua.h sysbench.rand.lua.h sysbench.sql.lua.h \ |
定位到错误行:
1 | -- sql_driver metatable |
可知对象driver_mt的垃圾回收方法被指向了一个ffi方法ffi.C.db_destroy,尝试定位它:
1 | ffi = require("ffi") |
可见db_destroy为一个C函数,尝试定位:
1 | root@test:~/sysbench-1.0.20/src# find . -name "*.c" | xargs grep db_destroy |
1 | int db_destroy(db_driver_t *drv) |
至此可以确定db_destroy函数存在并被连接到了sysbench可执行文件内,为什么LuaJIT不能透过ffi.C在静态编译时调用它呢?通过ffi.C为关键词在LuaJIT的文档里找到了这段文字:
ffi.C
This is the default C library namespace — note the uppercase ‘C’. It binds to the default set of symbols or libraries on the target system. These are more or less the same as a C compiler would offer by default, without specifying extra link libraries.On POSIX systems, this binds to symbols in the default or global namespace. This includes all exported symbols from the executable and any libraries loaded into the global namespace. This includes at least libc, libm, libdl (on Linux), libgcc (if compiled with GCC), as well as any exported symbols from the Lua/C API provided by LuaJIT itself.
可以得知在POSIX系统上,可以通过ffi.C访问当前可执行文件的符号表,以及透过dlopen()加载的动态链接库中的符号表。由此基于以下结论:
- Linux下可执行文件的符号表可以被strip掉并不影响执行,C函数相互调用所需的地址信息已被硬编码在C代码内。
- sysbench.sql.lua作为Lua脚本,在C的编译时以字符串形态进入可执行文件内,在运行时方被LuaJIT解释执行。
- 被dlopen()引入的动态链接库中的函数需要以dlsym()函数导出函数地址方可被调用。
所以,当LuaJIT解释执行j脚本以ffi.C呼叫db_destroy()时,LuaJIT才知道它需要去寻找一个名db_destroy的符号,此时只有dlsym可以做到这一点,dlsym的实现如下:
1 | const char *strtab = ... /* locate .dynstr */ |
如代码所示,dlsym通过读取动态链接库的.dynstr与.dynsym两个ELF Sections 来搜索符号并取得符号对应地址,但sysbench可执行文件经包含了db_destroy()函数且它并不是一个动态库[黑人问号脸]。
于是我再次link了一个非静态的sysbench看看和静态的有什么区别,首先还是看一下符号表:
1 | root@test:~/sysbench-1.0.20/src# readelf -s sysbench_shared | grep db_destroy |
可以看到出来了两个地址相同的符号,通过万能的谷歌得知ELF可执行文件可以同时拥有两个符号表,分别名为.dynsym和.symtab,.dynsym需要通过gcc参数-rdynamic或连接器参数-export-dynamic获取,且不会被strip掉,Luajit的ffi设计依赖于.dynsym来进行运行时符号查找。
-export-dynamic
Allow symbols from output-file to be resolved with
dlsym
(see Dlopened modules).
而静态编译参数-static会让gcc参数-rdynamic无效化,缺少了.dynsym自然ffi.C无法正常工作,从而导致sysbench报错,我尝试直接以-export-dynamic参数连接,但因为libc.a没有用位置无关代码参数(-fPIE)编译而失败。
解决思路: