我试图使用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 , const char * format , 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,
我该怎么解决?
您遇到的主要问题是您的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 , const char * format , ...)
{
va_list args;
va_start(args, format);
int ret = vsprintf(src, format, args);
va_end(args);
FILE* output = fopen("log.txt","a");
fprintf(output,"%s n",src);
fclose(output);
return ret;
}
Alex的第一个解决方案很好地解决了一个问题:sprintf
的冲突声明(尽管没有理由不使用与stdio.h
相同的签名,请参阅dbush的答案(。然而,即便如此,房间里仍然有一头大象:sprintf
是一个变异函数。
这意味着,每当包装的程序使用除char *
第三个参数之外的任何其他参数调用sprintf
时,您的输出可能不正确(甚至可能取决于编译器的-O
级别(
从变差函数调用变差函数(本质上就是您在这里所做的(是一个已知的问题。任何解决方案都是不可移植的。使用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
就是这样的情况(,您可以将Alex的(可移植的(第二个解决方案与va_*
宏一起使用。如果不是,则使用__builtin_apply()
的解决方案是唯一可能的,尽管gcc
是特定的 - 另请参阅:使用va_list调用printf
- 可能取决于编译器版本,当使用
-O2
标志编译main.c
时,main()
实际上会调用__sprintf_chk()
而不是sprintf()
(与-fno-builtin
无关(,并且包装器将不起作用。要演示包装器,请使用-O0
编译main.c
。当然,更改主程序以使包装器工作是一件令人头疼的事。这表明了构建包装器的脆弱性:程序通常不会调用您期望的库函数。提前一个ltrace <program>
可以节省很多工作
您可以在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 , 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;
}
main.c:
#include <stdio.h>
int main(int argc, char *argv[]) {
char buffer[80];
sprintf(buffer, "hello world");
puts(buffer);
return 0;
}
Makefile:
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
编辑
带有variadic实现的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 , ...)
{
va_list args;
va_start(args, fmt);
int ret = vsprintf(src, fmt, args);
va_end(args);
FILE* output = fopen("log.txt","a");
fprintf(output,"%s n", src);
fclose(output);
return ret;
}