Библиотечное взаимодействие

Я пытался перехватить вызовы malloc и free, следуя нашему учебнику (книга CSAPP). Я следовал их точному коду и почти тому же коду, который я нашел в Интернете, и я продолжаю получать ошибку сегментации. Я слышал, как наш профессор говорил что-то о printf, который использует malloc и освобождает память, поэтому я думаю, что это происходит потому, что я перехватываю malloc, и поскольку я использую функцию printf внутри перехватывающей функции, она будет вызывать себя рекурсивно. Однако я не могу найти решение этой проблемы? Наш профессор продемонстрировал, что перехват работает (он не показал нам код) и печатает нашу информацию каждый раз, когда происходит malloc, поэтому я знаю, что это возможно. Может кто подскажет рабочий метод??

Вот код, который я использовал и ничего не получил: mymalloc.c

#ifdef RUNTIME
// Run-time interposition of malloc and free based on // dynamic linker's (ld-linux.so) LD_PRELOAD mechanism #define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h> #include <dlfcn.h>
void *malloc(size_t size) {
static void *(*mallocp)(size_t size) = NULL; char *error;
void *ptr;
// get address of libc malloc
if (!mallocp) {
mallocp = dlsym(RTLD_NEXT, "malloc"); if ((error = dlerror()) != NULL) {
            fputs(error, stderr);
            exit(EXIT_FAILURE);
         }
}
ptr = mallocp(size);
printf("malloc(%d) = %p\n", (int)size, ptr); return ptr;
}
#endif

тест.с

#include <stdio.h>
#include <stdlib.h>
int main(){
   printf("main\n");
   int* a = malloc(sizeof(int)*5);
   a[0] = 1;
   printf("end\n");
}

Результат, который я получаю:

$ gcc -o test test.c
$ gcc -DRUNTIME -shared -fPIC mymalloc.c -o mymalloc.so
$ LD_PRELOAD=./mymalloc.so ./test
Segmentation Fault

Это код, который я попробовал и получил ошибку сегментации (от https://gist.github.com/iamben/4124829):

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>

void* malloc(size_t size)
{
        static void* (*rmalloc)(size_t) = NULL;
        void* p = NULL;

        // resolve next malloc
        if(!rmalloc) rmalloc = dlsym(RTLD_NEXT, "malloc");

        // do actual malloc
        p = rmalloc(size);

        // show statistic
        fprintf(stderr, "[MEM | malloc] Allocated: %lu bytes\n", size);

        return p;
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define STR_LEN 128

int main(int argc, const char *argv[])
{
        char *c;
        char *str1 = "Hello ";
        char *str2 = "World";

        //allocate an empty string
        c = malloc(STR_LEN * sizeof(char));
        c[0] = 0x0;

        //and concatenate str{1,2}
        strcat(c, str1);
        strcat(c, str2);

        printf("New str: %s\n", c);


        return 0;
}

Makefile из репозитория git не работал, поэтому я вручную скомпилировал файлы и получил:

$ gcc -shared -fPIC libint.c -o libint.so
$ gcc -o str str.c
$ LD_PRELOAD=./libint.so ./str
Segmentation fault

Я делаю это часами и все равно получаю тот же неверный результат, несмотря на то, что я скопировал код из учебника. Буду очень признателен за любую помощь!!


person juimdpp    schedule 13.12.2020    source источник
comment
Попробуйте fprintf(stderr, ...), чтобы избежать рекурсии. stderr не буферизуется.   -  person chqrlie    schedule 13.12.2020
comment
@chqrlie Я уже пробовал это и все равно получаю ту же ошибку ... Может ли быть проблема с оператором компиляции gcc ??   -  person juimdpp    schedule 13.12.2020
comment
Вы также можете попробовать sprintf(buf, ...) в локальный массив и write(2, buf, strlen(buf))   -  person chqrlie    schedule 13.12.2020
comment
Это сработает, если вы просто напишете постоянную строку с write() из вашего malloc?   -  person chqrlie    schedule 13.12.2020
comment
Если вы просто хотите отслеживать malloc/free, вы можете использовать переменные среды. см. man malloc   -  person chqrlie    schedule 13.12.2020
comment
@chqrlie: проблема не в буферизации, и sprintf с write тоже не сработает. Проблема в том, что printf выделяет некоторое пространство для форматирования, как и sprintf. Это форматирование выполняется для подготовки строки к записи в поток, поэтому оно происходит до того, как произойдет какая-либо буферизация.   -  person Eric Postpischil    schedule 13.12.2020
comment
@chqrlie Я пытался написать, что сработало! Я получаю чрезмерные отпечатки buf, пока не получаю Segmentation Fault, что подтверждает, что проблема была в рекурсии! Спасибо.   -  person juimdpp    schedule 13.12.2020


Ответы (1)


Один из способов справиться с этим — отключить printf, когда ваш return вызывается рекурсивно:

static char ACallIsInProgress = 0;
if (!ACallIsInProgress)
{
    ACallIsInProgress = 1;
    printf("malloc(%d) = %p\n", (int)size, ptr);
    ACallIsInProgress = 0;
}
return ptr;

При этом, если printf вызывает malloc, ваша подпрограмма просто вызовет фактический malloc (через mallocp) и вернется, не вызывая другого printf. Вы пропустите вывод информации о вызове malloc, который делает printf, но это, как правило, терпимо, когда вставка используется для изучения общей программы, а не библиотеки C.

Если вам нужно поддерживать многопоточность, может потребоваться дополнительная работа.

Реализация printf может выделять буфер только один раз, когда он используется в первый раз. В этом случае вы можете инициализировать флаг, отключающий printf, как описано выше, вызвать printf один раз в подпрограмме main (может быть, убедитесь, что она включает в себя хорошую задачу форматирования, которая заставляет printf выделять буфер, а не простую строку), а затем установите флаг, чтобы включить вызов printf и оставить его установленным для остальной части программы.

Другой вариант заключается в том, чтобы ваша подпрограмма malloc вообще не использовала printf, а кэшировала данные в буфере для записи позже какой-либо другой подпрограммой или записывала необработанные данные в файл с использованием write, а затем эти данные интерпретировались и форматировались отдельной программой. . Или необработанные данные могут быть записаны по конвейеру в программу, которая форматирует и печатает их и не использует ваш вставленный malloc.

person Eric Postpischil    schedule 13.12.2020
comment
Спасибо за ваш ответ! Однако мне интересно, как работает статический символ. Если char равен 0, он войдет в условие if и вернет указатель. Тогда как будет выполняться следующий оператор (где 0 присваивается символу)? Какова его цель?? - person juimdpp; 13.12.2020
comment
Во время отладки я обнаружил, что проблема на самом деле не в функции printf, а в функции dlsym (я пробовал функцию write() (запись строки) до и после dlsym и бесконечный цикл печати строки ). Как это возможно?? Есть ли у вас какие-либо идеи? - person juimdpp; 13.12.2020
comment
Обидно, что printf выделяет память с malloc для выполнения такого простого форматирования! - person chqrlie; 13.12.2020
comment
@juimdpp: К вашему сведению, я отредактировал ответ, чтобы изменить код — сначала я не заметил, что return находится в той же строке, что и printf. Я переместил его за пределы оператора if. - person Eric Postpischil; 13.12.2020
comment
@juimdpp: static char ACallIsInProgress служит для записи того, выполняется ли в данный момент подпрограмма malloc и вызывает ли printf. Объявление его с помощью static создает один объект char, который используется для всех вызовов подпрограммы, а не по умолчанию для каждого вызова, имеющего свою собственную отдельную копию. После того, как мы установим его в 1 и вызовем printf, если printf затем вызовет malloc, подпрограмма обнаружит, что ACallIsInProgress не равно нулю, и пропустит вызов printf и вернется. Тогда первый вызов printf вернется, а malloc сбросит ACallIsInProgress на 0 и вернется. - person Eric Postpischil; 13.12.2020
comment
@juimdpp: Если dlsym вызывает malloc, вам может потребоваться перенести инициализацию mallocp из вашей процедуры malloc. - person Eric Postpischil; 13.12.2020
comment
Также сделайте эту переменную локальной для потока. (Ключевое слово __thread для некоторых компиляторов.) - person Zsigmond Lőrinczy; 13.12.2020