Конфликтующие типы при компиляции оболочки 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,

Как я могу это исправить?


person MicrosoctCprog    schedule 05.04.2021    source источник
comment
Ошибка в том, что типы ваших аргументов для sprintf отличаются от ранее объявленных on (из stdib). Тип sprintf — int sprintf(char *restrict s, const char *restrict format, ...). Я не уверен, нужны ли ключевые слова restrict, но sprintf принимает varargs в качестве третьего аргумента, а не указатель.   -  person Paul Hankin    schedule 05.04.2021


Ответы (3)


Основная проблема, с которой вы столкнулись, заключается в том, что ваш прототип для 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;
}
person dbush    schedule 07.04.2021

Первое решение Алекса хорошо решает одну проблему: конфликтующие объявления sprintf (хотя нет причин не использовать ту же подпись, что и в stdio.h, см. ответ dbush) . Однако даже в этом случае остается один большой слон: sprintf — это вариационная функция.

Это означает, что всякий раз, когда обернутая программа вызывает sprintf с чем-либо, кроме одного char * третьего аргумента, ваш вывод может быть неверным (и даже может зависеть от уровня -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 является таким случаем) вы можете использовать второе решение Алекса (портативное) с макросами va_*. Если нет, то решение с __builtin_apply() является единственно возможным, хотя и специфичным для gcc.
  • См. также: вызов printf с использованием va_list
  • Возможно, в зависимости от версии компилятора, при компиляции main.c с флагом -O2 main() фактически вызовет __sprintf_chk() вместо sprintf() (независимо от -fno-builtin), и оболочка не будет работать. Чтобы продемонстрировать оболочку, скомпилируйте main.c с -O0. Менять основную программу, чтобы заставить обертку работать, — это, конечно, хвост, виляющий собакой. Это показывает хрупкость построения оболочек: программы часто вызывают не те библиотечные функции, которые вы ожидаете. ltrace <program> заранее может сэкономить много работы....
person Hans Lub    schedule 05.04.2021
comment
1) Почему вы определили # определить ДОСТАТОЧНО 100? 2) вы написали int sprintf (char *src) с LD_PRELOAD, разве вы не должны писать полную подпись функции? 3) Вы тестировали свой код? если я не включу ‹stdio.h› для FILE, я не смогу скомпилировать этот код, а если я включу stdio.h, я снова получу ошибку конфликта. - person MicrosoctCprog; 05.04.2021
comment
__builtin_apply() возвращает указатель на данные, описывающие, как выполнить вызов с теми же аргументами, которые были переданы текущей функции. __builtin_apply(func, arg, N) вызывает func с теми же аргументами, что и окружающая функция sprintf. Для этого он использует максимум N байт пространства стека - 100 байт кажется разумным. Поскольку нам не нужно ссылаться на format и argp внутри нашей функции, мы можем просто их не указывать — компоновщики не проверяют тип. - person Hans Lub; 05.04.2021
comment
Вы тестировали свой код? если я не включу ‹stdio.h› для FILE, я не смогу скомпилировать этот код, а если я включу stdio.h, я снова получу ошибку конфликта. - person MicrosoctCprog; 05.04.2021
comment
Да, я сделал - начальные #defines и #includes программы идентичны решению Алекса - я их добавил - person Hans Lub; 05.04.2021
comment
Я взял main.c у Алекса и ваш код как test_ld.c, скомпилировал его с помощью gcc gcc -Wall -O2 -shared -fPIC -o libsprintf.so test_ld.c -ldl и запустил с LD_PRELOAD=./libsprintf.so ./main, но это не сработало, txt файл не создан - person MicrosoctCprog; 05.04.2021
comment
Я взял main.c у Алекса и ваш код как test_ld.c, скомпилировал его с помощью gcc gcc -Wall -O2 -shared -fPIC -o libsprintf.so test_ld.c -ldl и запустил с LD_PRELOAD=./libsprintf.so ./main, но это не сработало, txt файл не создан - person MicrosoctCprog; 05.04.2021
comment
Да, даже если он скомпилирован с -fno-builtin, main не вызовет ваш новый и блестящий sprintf. Я решил это, заменив #include <stdio.h> на extern int sprintf(char*, char *, ...); extern void puts(char *);. Это вопрос реализации (насколько я знаю, sprintf может быть макросом внутри stdio.h) и не очень важен для общей проблемы. - person Hans Lub; 05.04.2021
comment
Давайте продолжим это обсуждение в чате. - person MicrosoctCprog; 05.04.2021
comment
Пожалуйста, отредактируйте свой пост с полным решением, которое я могу скомпилировать и запустить. - person MicrosoctCprog; 05.04.2021

Вы можете переименовать символ в 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;
}

основной.с:

#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

изменить

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;
}
person Alex    schedule 05.04.2021
comment
1) Почему main.c нужно компилировать с -fno-builtin 2) #define sprintf xsprintf заменить sprintf на stdio.h? - person MicrosoctCprog; 05.04.2021
comment
@MicrosoctCprog 1) см. ссылку в обновленном ответе, там упоминается документация gcc о отсутствии встроенных функций. 2) да, как только вы определяете something как somethingelse, все, что соответствует something в источниках (включая включенные заголовки), заменяется препроцессором на что-то другое - person Alex; 05.04.2021
comment
Извините, что я не упомянул об этом, но я не могу скомпилировать main.c с -fno-builtin моим предположением, что я не могу управлять бинарным хуком, только запускаю его - person MicrosoctCprog; 05.04.2021
comment
@MicrosoctCprog, даже если вы не упомянули, я так и предполагал, проблема в том, что если ваш исполняемый файл был построен с использованием встроенного, он не будет связывать какую-либо библиотеку, sprintf не будет определен как динамический символ, и вы не можете использовать LD_PRELOAD чтобы зацепить его. - person Alex; 05.04.2021