如何解决编译 LD_PRELOAD 包装器的冲突类型
我尝试使用 LD_PRELOAD 来挂钩 sprintf
函数,所以我会将缓冲区的结果打印到文件:
#define _GNU_SOURCE
#include <stdio.h>
#include<dlfcn.h>
int sprintf (char * src,const char * format,char* argp)
{
int (*original_func)(char*,const char *,char*);
original_func = dlsym(RTLD_NEXT,"sprintf");
int ret = (*original_func)(src,format,argp);
FILE* output = fopen("log.txt","a");
fprintf(output,"%s \n",src);
fclose(output);
return ret;
}
当我编译这段代码时 gcc -Wall -fPIC -shared -o my_lib.so test_ld.c -ldl
test_ld.c:5:5: error: conflicting types for ‘sprintf’
int sprintf (char * src,char* argp)
^
In file included from test_ld.c:2:0:
/usr/include/stdio.h:364:12: note: prevIoUs declaration of ‘sprintf’ was here
extern int sprintf (char *__restrict __s,
我该如何解决?
解决方法
Alex 的第一个解决方案很好地解决了一个问题:sprintf
的声明冲突(尽管没有理由不使用与 stdio.h
中相同的签名,请参阅 dbush 的回答)。然而,即便如此,房间里仍然有一头大大象:sprintf
是一头variadic function。
这意味着,每当被包装的程序调用 sprintf
时,除了第三个参数一个 char *
之外,您的输出可能不正确(甚至可能取决于编译器的 -O
级别)
从可变参数函数调用可变参数函数(本质上就是你在这里所做的)是一个 known problem。任何解决方案都是不可移植的。使用 gcc
,您可以使用 __buitlin_apply
并利用 gcc
自己处理参数列表的私有方式:
/* sprintf.c,compile with gcc -Wall -fPIC -shared -o sprintf.so sprintf.c -ldl
and use with LD_PRELOAD=./sprintf.so <program> */
#define _GNU_SOURCE
#define sprintf xsprintf
#include <stdio.h>
#include<dlfcn.h>
#undef sprintf
# define ENOUGH 100 /* how many bytes of our call stack
to pass to the original function */
int sprintf (char *src) /* only needs the first argument */
{
void *original_func = dlsym(RTLD_NEXT,"sprintf");
void *arg = __builtin_apply_args();
void *ret = __builtin_apply((void *)original_func,arg,ENOUGH);
FILE* output = fopen("log.txt","a");
fprintf(output,"%s \n",src);
fclose(output);
__builtin_return(ret);
}
几点说明:
- 在设计良好的库中,可变参数函数将有一个非可变参数函数,它使用一个
va_list
参数而不是可变数量的参数。在这种情况下(sprintf
-vsprintf
就是这种情况),您可以使用带有va_*
宏的 Alex 的(便携式)第二个解决方案。如果不是,__builtin_apply()
的解决方案是唯一可能的解决方案,尽管是针对gcc
的。 - 另见:call printf using va_list
- 可能取决于编译器版本,当使用
main.c
标志编译-O2
时,main()
实际上会调用__sprintf_chk()
而不是sprintf()
(不管 { {1}}) 并且包装器不起作用。要演示包装器,请使用-fno-builtin
编译main.c
。当然,更改主程序以使包装器工作是摇尾巴。这显示了构建包装器的脆弱性:程序通常不会调用您期望的库函数。预先-O0
可以节省大量工作......
您遇到的主要问题是您的 sprintf
原型与官方原型不匹配。您的函数具有以下签名:
int sprintf (char * src,const char * format,char* argp);
虽然官方有:
int sprintf(char *str,const char *format,...);
您需要更改您的函数以具有此签名。完成此操作后,您将需要使用 va_list
来获取可变参数。然后,您将使用它来调用 vsprintf
,它接受这种类型的参数,而不是使用 dlsym
来加载 sprintf
。
#include <stdio.h>
#include <stdarg.h>
int sprintf (char * src,...)
{
va_list args;
va_start(args,format);
int ret = vsprintf(src,format,args);
va_end(args);
FILE* output = fopen("log.txt","a");
fprintf(output,src);
fclose(output);
return ret;
}
,
您可以在 stdio 中重命名符号,但是还有另一个问题,像 gcc 这样的编译器使用内置实现,除非您传递像 -fno-builtin
这样的标志,否则编译器会在可执行文件中生成内联代码,它不会为 sprintf 等函数链接任何库。
sprintf.c:
#define _GNU_SOURCE
#define sprintf xsprintf
#include <stdio.h>
#include<dlfcn.h>
#undef sprintf
int sprintf (char * src,char* argp)
{
int (*original_func)(char*,const char *,char*);
original_func = dlsym(RTLD_NEXT,"sprintf");
int ret = (*original_func)(src,argp);
FILE* output = fopen("log.txt",src);
fclose(output);
return ret;
}
main.c:
#include <stdio.h>
int main(int argc,char *argv[]) {
char buffer[80];
sprintf(buffer,"hello world");
puts(buffer);
return 0;
}
生成文件:
all: libsprintf.so main
main: main.c
gcc -Wall -O2 -fno-builtin -o main main.c
libsprintf.so: sprintf.c
gcc -Wall -O2 -shared -fPIC -fno-builtin -o libsprintf.so sprintf.c -ldl
.PHONY: clean
clean:
-rm -f main libsprintf.so
用法:
make
LD_PRELOAD=./libsprintf.so ./main
编辑
带有可变参数实现的 sprintf.c(它不调用 sprintf):
#define _GNU_SOURCE
#define sprintf xsprintf
#include <stdio.h>
#include<dlfcn.h>
#undef sprintf
#include <stdarg.h>
int sprintf (char * src,const char * fmt,fmt);
int ret = vsprintf(src,fmt,src);
fclose(output);
return ret;
}
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。