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

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...