【问题标题】:Conflicting types compiling a LD_PRELOAD wrapper编译 LD_PRELOAD 包装器的冲突类型
【发布时间】:2021-07-01 05:09:20
【问题描述】:

我尝试使用 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 的参数类型与之前声明的 on(来自 stdib)不同。 sprintf 的类型是int sprintf(char *restrict s, const char *restrict format, ...)。我不确定restrict 关键字是否必要,但sprintf 将可变参数作为第三个参数,而不是指针。

标签: c linux printf ld-preload conflicting-libraries


【解决方案1】:

Alex 的第一个解决方案很好地解决了一个问题:sprintf 的冲突声明(尽管没有理由使用与 stdio.h 中相同的签名,请参阅 dbush 的回答)。然而,即便如此,房间里还剩下一头大象:sprintfvariadic function

这意味着,每当包装程序调用 sprintf 时使用除一个 char * 第三个参数之外的任何其他参数时,您的输出可能不正确(甚至可能取决于编译器的 -O 级别)

从可变参数函数调用可变参数函数(本质上就是您在这里所做的)是known problem。任何解决方案都将是不可移植的。使用gcc,您可以使用__buitlin_apply 并利用gccs 自己的私有方式处理参数列表:

/* 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-specific 是一个。
  • 另见:call printf using va_list
  • 可能取决于编译器版本,当使用-O2 标志编译main.c 时,main() 实际上将调用__sprintf_chk() 而不是sprintf()(不管-fno-builtin)和包装器 '不工作。为了演示包装器,编译 main.c-O0。当然,更改主程序以使包装器工作是在摇尾巴。这显示了构建包装器的脆弱性:程序通常不会调用您期望的库函数。提前ltrace &lt;program&gt; 可以省很多工作....

【讨论】:

  • 1) 你为什么定义 #define ENOUGH 100 ? 2) 你用 LD_PRELOAD 写了 int sprintf (char *src) 你不是必须写函数的完整签名吗? 3)你测试过你的代码吗?如果我不包含 FILE 我无法编译该代码,如果我包含 stdio.h 我再次遇到冲突错误。
  • __builtin_apply() 返回一个指向数据的指针,该指针描述如何使用与传递给当前函数的相同参数执行调用。 __builtin_apply(func, arg, N) 调用 func,其参数与周围函数 sprintf 相同。为此,它使用最大 N 字节的堆栈空间 - 100 字节似乎是合理的。由于我们不需要在我们的函数中引用formatargp,我们可以将它们排除在外——链接器不会进行类型检查。
  • 你测试你的代码了吗?如果我不包含 FILE 我无法编译该代码,如果我包含 stdio.h 我再次遇到冲突错误。
  • 是的,我做了 - 程序的初始 #defines#includes 与 Alex 的解决方案相同 - 我添加了它们
  • 我从 Alex 那里拿 main.c 和你的代码为 test_ld.c 并用 gcc gcc -Wall -O2 -shared -fPIC -o libsprintf.so test_ld.c -ldl 编译它并用 LD_PRELOAD=./libsprintf.so ./main 运行它,但这不起作用,txt 文件没有创建
【解决方案2】:

您遇到的主要问题是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;
}

【讨论】:

    【解决方案3】:

    你可以在stdio中重命名符号,但是还有一个问题,像gcc这样的编译器使用内置实现,除非你传递一个像-fno-builtin这样的标志,编译器会在可执行文件中生成内联代码,它不会' t 为 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;
    }
    

    生成文件:

    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 , ...)
    {
        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;
    }
    

    【讨论】:

    • 1) 为什么main.c 必须与-fno-builtin 一起编译 2) #define sprintf xsprintfsprintf 替换为stdio.h
    • @MicrosoctCprog 1) 请参阅更新答案中的链接,其中提到了有关非内置的 gcc 文档。 2) 是的,一旦将 something 定义为 somethingelse 源中与 something 匹配的所有内容(包括包含的标头)都会被预处理器替换为 其他东西
    • 对不起,我没有提到这一点,但我无法用-fno-builtin 编译 main.c,我假设我无法控制钩子二进制文件,只能运行它
    • @MicrosoctCprog 即使你没有提到我认为是这样,问题是,如果你的可执行文件是使用内置构建的,不会链接任何库,sprintf 不会被定义为动态符号,你不能使用 LD_PRELOAD 来挂钩它。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-08-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多