c-编译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 , 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;
}

相关内容

  • 没有找到相关文章

最新更新