简体   繁体   中英

Conflicting types compiling a LD_PRELOAD wrapper

I tried to use LD_PRELOAD to hook sprintf function, so I will print to file the result of buffer:

#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;


}

When I compile this code gcc -Wall -fPIC -shared -o my_lib.so test_ld.c -ldl

I got error

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,

How can I fix that?

Alex's first solution nicely addresses one problem: the conflicting declarations of sprintf (although there is no reason to not use the same signature as in stdio.h , see dbush's answer). However, even then there remains one large elephant in the room: sprintf is a variadic function .

That means that, whenever the wrapped program calls sprintf with anything other than one char * third argument, your output may not be correct (and may even depend on your compiler's -O level)

Calling variadic functions from variadic functions (what is essentially what you're doing here) is a known problem . Any solution will be non-portable. With gcc , you can use __buitlin_apply and tap into gcc s own private way of handling argument lists:

/* 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);                                                                                                                         
                                                                                                                                                
}               

A few remarks:

  • In well-designed libraries variadic functions will have a non-variadic counterpart that uses one va_list argument instead of a variable number of arguments. In that case (and sprintf - vsprintf is such a case) you can use Alex's (portable) second solution with the va_* macros. If not, the solution with __builtin_apply() is the only possible, albeit gcc -specific, one.
  • See also: call printf using va_list
  • Possibly depending on the compiler version, when compiling main.c with the -O2 flag, main() will actually call __sprintf_chk() instead of sprintf() (regardless of -fno-builtin ) and the wrapper won't work . To demonstrate the wrapper, compile main.c with -O0 . Changing the main program to get the wrapper to work is the tail wagging the dog, of course. This shows the fragility of building wrappers: programs often don't call the library functions you expect . A ltrace <program> beforehand can save a lot of work....

The main problem you're having is that your prototype for sprintf doesn't match the official one. Your function has this signature:

int sprintf (char * src , const char *  format , char* argp);

While the official one has:

int sprintf(char *str, const char *format, ...);

You'll need to change your function to have this signature. Once you do that, you'll need to use a va_list to get the variadic argument. Then you would use that to call vsprintf which takes an argument of this type instead of using dlsym to load 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;
}

You can rename the symbol in stdio, but then there's another problem, compilers like gcc use built-in implementations, unless you pass a flag like -fno-builtin , the compiler will generate the code inline in the executable, it won't link any library for functions like 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

usage:

make
LD_PRELOAD=./libsprintf.so ./main

edit

sprintf.c with variadic implementation (it doesn't call 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;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM