我有一个共享库,比如somelib.so
,它使用libc中的ioctl
(根据objdump
)。
我的目标是编写一个包裹somelib.so
的新库,并提供自定义ioctl
。我想避免预加载库,以确保只有somelib.so
中的来电才能使用自定义ioctl
。
这是我目前的片段:
typedef int (*entryfunctionFromSomelib_t) (int par, int opt);
typedef int (*ioctl_t) (int fd, int request, void *data);
ioctl_t real_ioctl = NULL;
int ioctl(int fd, int request, void *data )
{
fprintf( stderr, "trying to wrap ioctl\n" );
void *handle = dlopen( "libc.so.6", RTLD_NOW );
if (!handle)
fprintf( stderr, "Error loading libc.so.6: %s\n", strerror(errno) );
real_ioctl = (ioctl_t) dlsym( handle, "ioctl" );
return real_ioctl( fd, request, data);
}
int entryfunctionFromSomelib( int par, int opt ) {
void *handle = dlopen( "/.../somelib.so", RTLD_NOW );
if (!handle)
fprintf( stderr, "Error loading somelib.so: %s\n", strerror(errno) );
real_entryfunctionFromSomelib = entryfunctionFromSomelib_t dlsym( handle, "entryfunctionFromSomelib" );
return real_entryfunctionFromSomelib( par, opt );
}
但是,在ioctl
形式somelib.so
的调用未被重定向到我的自定义ioctl
实现的意义上,它不起作用。如何强制包裹的somelib.so
这样做?
======================
@Nominal Animal post之后添加的其他信息:
此处通过readelf -s | grep functionname
获取mylib.so(编辑后的somelib.so)的一些信息:
246: 0000000000000000 121 FUNC GLOBAL DEFAULT UND dlsym@GLIBC_2.2.5 (11)
42427: 0000000000000000 121 FUNC GLOBAL DEFAULT UND dlsym@@GLIBC_2.2.5
184: 0000000000000000 37 FUNC GLOBAL DEFAULT UND ioctl@GLIBC_2.2.5 (6)
42364: 0000000000000000 37 FUNC GLOBAL DEFAULT UND ioctl@@GLIBC_2.2.5
修补后' mylib.so它还将新功能显示为:
184: 0000000000000000 37 FUNC GLOBAL DEFAULT UND iqct1@GLIBC_2.2.5 (6)
我已经版本化了#39;并从我的wrap_mylib库中导出符号,readelf
现在显示:
25: 0000000000000d15 344 FUNC GLOBAL DEFAULT 12 iqct1@GLIBC_2.2.5
63: 0000000000000d15 344 FUNC GLOBAL DEFAULT 12 iqct1@GLIBC_2.2.5
但是,当我尝试dlopen
wrap_mylib时,我收到以下错误:
symbol iqct1, version GLIBC_2.2.5 not defined in file libc.so.6 with link time reference
这可能是因为mylib.so尝试从libc.so.6 dlsym
iqct1
?
答案 0 :(得分:2)
如果binutils' objcopy
可以修改动态符号,mylib.so
是ELF动态库,我们可以使用
mv mylib.so old.mylib.so
objcopy --redefine-sym ioctl=mylib_ioctl old.mylib.so mylib.so
将库中的符号名称从ioctl
重命名为mylib_ioctl
,以便我们实施
int mylib_ioctl(int fd, int request, void *data);
在链接到最终二进制文件的另一个库或对象中。
不幸的是,this feature is not implemented(截至2017年初至少)。
如果替换符号名称与原始名称的长度完全相同,我们可以使用丑陋的黑客解决此问题。符号名称是ELF文件中的字符串(前面和后面都是一个空字节),因此我们可以使用例如GNU sed:
LANG=C LC_ALL=C sed -e 's|\x00ioctl\x00|\x00iqct1\x00|g' old.mylib.so > mylib.so
这会将名称从ioctl()
替换为iqct1()
。它显然不是最优的,但它似乎是最简单的选择。
如果您发现需要向您实施的iqct1()
功能添加版本信息,可以使用GCC添加类似于
__asm__(".symver iqct1,iqct1@GLIBC_2.2.5");
其中版本遵循@
字符。
这是一个实际的例子,展示了我在实践中如何测试它。
首先,让我们创建 mylib.c ,代表mylib.c的源代码(OP没有 - 否则只需更改源代码并重新编译库就可以解决问题):
#include <unistd.h>
#include <errno.h>
int myfunc(const char *message)
{
int retval = 0;
if (message) {
const char *end = message;
int saved_errno;
ssize_t n;
while (*end)
end++;
saved_errno = errno;
while (message < end) {
n = write(STDERR_FILENO, message, (size_t)(end - message));
if (n > 0)
message += n;
else {
if (n == -1)
retval = errno;
else
retval = EIO;
break;
}
}
errno = saved_errno;
}
return retval;
}
导出的唯一功能是myfunc(message)
,如 mylib.h 中所声明:
#ifndef MYLIB_H
#define MYLIB_H
int myfunc(const char *message);
#endif /* MYLIB_H */
让我们将mylib.c编译成动态共享库mylib.so
:
gcc -Wall -O2 -fPIC -shared mylib.c -Wl,-soname,libmylib.so -o mylib.so
而不是来自C库的write()
(它的POSIX函数就像ioctl()
,而不是标准的C),我们希望使用我们自己设计的mywrt()
在我们自己的计划中。上面的命令将原始库保存为mylib.so
(在内部命名为libmylib.so
),因此我们可以使用
sed -e 's|\x00write\x00|\x00mywrt\x00|g' mylib.so > libmylib.so
更改符号名称,将修改后的库保存为libmylib.so
。
接下来,我们需要一个测试可执行文件,它提供ssize_t mywrt(int fd, const void *buf, size_t count);
函数(原型与它替换的write(2)
函数相同。 test.c :
#include <stdlib.h>
#include <stdio.h>
#include "mylib.h"
ssize_t mywrt(int fd, const void *buffer, size_t bytes)
{
printf("write(%d, %p, %zu);\n", fd, buffer, bytes);
return bytes;
}
__asm__(".symver mywrt,mywrt@GLIBC_2.2.5");
int main(void)
{
myfunc("Hello, world!\n");
return EXIT_SUCCESS;
}
.symver
行为GLIBC_2.2.5
指定版本mywrt
。
版本取决于使用的C库。在这种情况下,我运行了objdump -T $(locate libc.so) 2>/dev/null | grep -e ' write$'
,它给了我
00000000000f66d0 w DF .text 000000000000005a GLIBC_2.2.5 write
倒数第二个字段是所需的版本。
因为需要导出mywrt
符号才能使用动态库,所以我创建了 test.syms :
{
mywrt;
};
为了编译测试可执行文件,我使用了
gcc -Wall -O2 test.c -Wl,-dynamic-list,test.syms -L. -lmylib -o test
由于libmylib.so
位于当前工作目录中,我们需要将当前目录添加到动态库搜索路径中:
export LD_LIBRARY_PATH=$PWD:$LD_LIBRARY_PATH
然后,我们可以运行我们的测试二进制文件:
./test
它将输出类似
的内容write(2, 0xADDRESS, 14);
因为那是mywrt()
函数的作用。如果我们要检查未修改的输出,我们可以运行mv -f mylib.so libmylib.so
并重新运行./test
,然后输出
Hello, world!
这显示了这种方法,虽然取决于共享库文件的非常粗略的二进制修改(使用sed
- 但仅仅因为objcopy
尚未支持--redefine-sym
动态符号),在实践中应该工作得很好。
这也是开源如何优于专有库的一个完美示例:尝试解决这个小问题所花费的努力量至少比它要高出一个数量级。已经将库源中的ioctl
调用重命名为例如mylib_ioctl()
,然后重新编译它。
在OP的案例中,似乎有必要在最终二进制文件中插入dlsym()
(来自<dlfcn.h>
,在POSIX.1-2001中标准化)。
假设使用
修改原始动态库sed -e 's|\x00ioctl\x00|\x00iqct1\x00|g;
s|\x00dlsym\x00|\x00d15ym\x00|g;' mylib.so > libmylib.so
我们将两个自定义函数实现为
int iqct1(int fd, unsigned long request, void *data)
{
/* For OP to implement! */
}
__asm__(".symver iqct1,iqct1@GLIBC_2.2.5");
void *d15ym(void *handle, const char *symbol)
{
if (!strcmp(symbol, "ioctl"))
return iqct1;
else
if (!strcmp(symbol, "dlsym"))
return d15ym;
else
return dlsym(handle, symbol);
}
__asm__(".symver d15ym,d15ym@GLIBC_2.2.5");
请检查版本是否与您使用的C库相对应。上面的相应.syms文件只包含
{ i1ct1; d15ym; };
否则实施应如本答案前面所示的实际例子中所述。
因为ioctl()
的实际原型是int ioctl(int, unsigned long, ...);
,所以没有保证这适用于ioctl()
的所有常规用途。在Linux中,第二个参数是unsigned long
类型,第三个参数是指针或long或unsigned long - 在所有Linux体系结构中,指针和long / unsigned long具有相同的大小 - 所以它应该工作,除非实现ioctl()
的驱动程序也被关闭,在这种情况下你只是被软管,并且仅限于希望这个工作,或切换到具有适当的Linux支持和开源驱动程序的其他硬件。
以上特殊情况下两个原始符号,并将它们硬连接到替换功能。 (我称之为替换而不是插入符号,因为我们确实用这些符号替换mylib.so
调用的符号,而不是插入对{{1}的调用}和ioctl()
。)
这是一种相当野蛮的方法,但由于dlsym()
缺乏动态符号重新定义支持,除了使用sed
之外,它对于完成的内容和实际发生的内容非常强大和清晰