sbrk - Valgrind не сообщает об утечке памяти - PullRequest
3 голосов
/ 29 мая 2020

Я написал эту маленькую версию malloc (нет free):

#include <cstdio>
#include <cstddef>
#include <unistd.h>

#define word_size sizeof(intptr_t)
#define align(n) ((n + word_size - 1) & ~(word_size - 1))

void* my_malloc(size_t size) {
    void* p = sbrk(0);
    printf("before allocation: %p\n", p);
    if (sbrk(align(size)) == (void*) -1) {
        // failed to allocate memory
        return NULL;
    }
    printf("after allocation: %p\n", sbrk(0));
    return p;
}

int main() {
    int* foo = (int*) my_malloc(1);
    *foo = 100;
    printf("after allocation outside: %p\n", sbrk(0));
}

Вот результат кода:

before allocation: 0x1796000
after allocation: 0x1796008
after allocation outside: 0x1796008

Как видите, память, выделенная my_malloc, не была освобождена. Тем не менее, когда я пропускаю его через valgrind с этим:

valgrind --leak-check=yes ./main

, я получаю следующее:

==1592== Memcheck, a memory error detector
==1592== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==1592== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==1592== Command: ./main
==1592== HEAP SUMMARY:
==1592==     in use at exit: 0 bytes in 0 blocks
==1592==   total heap usage: 2 allocs, 2 frees, 73,728 bytes allocated
==1592== 
==1592== All heap blocks were freed -- no leaks are possible
==1592== 
==1592== For counts of detected and suppressed errors, rerun with: -v
==1592== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

Как видите, valgrind не нашел ни одного утечки памяти! Я неправильно использую valgrind или это ошибка?

Ответы [ 2 ]

1 голос
/ 30 мая 2020

Первый ответ не очень точен.

Короткий ответ для OP: Valgrind не обнаружит утечку в пользовательских пулах памяти, если вы не сообщите ему больше о своем распределителе.

Long ответ.

Когда вы запускаете valgrind, запускаются два исполняемых файла. Во-первых, сам valgrind. Это небольшое приложение, которое считывает несколько аргументов командной строки, устанавливает несколько переменных среды, затем просматривает гостевое приложение, чтобы определить его разрядность, а затем execve соответствующий инструмент, например memcheck-x86-freebsd.

Инструмент не связывает с ничем . Ни lib c, ни даже код запуска lib c. Точка входа находится в ассемблере, который создает новый стек и запускает valgrind_main. Поскольку инструмент не связан ни с чем, он должен реализовать для себя все функции времени выполнения C, которые ему нужны или которые он хочет использовать. Сюда входят некоторые функции, которые компиляторы могут неявно генерировать. Например, код, который выполняет присваивание структуры, может привести к тому, что компилятор сгенерирует вызов memcpy(). Это то, о чем идет речь в цитируемом тексте. Эти функции также предоставляются инструментом, а не lib c. Это не связано с механизмом перенаправления.

Итак, перенаправление. valgrind будет иметь LD_PRELOAD (или версию c для платформы, например LD_32_PRELOAD). Это будет указывать на «основной» компонент (например, vgpreload_core-x86-freebsd.so и компонент инструмента, например vgpreload_memcheck-x86-freebsd.so. Инструмент выполняет работу загрузчика ссылок и помещает mmap этих файлов в память. Во время этого процесса он будет прочтите информацию Elf и обратите внимание на любые "специальные" функции. Эти функции используют сложную систему изменения имен, и инструмент распознает, что, например, _vgr10010ZU_libcZdsoZa_malloc является заменой malloc в libc. Адреса из этих функций перенаправления сохраняются и будут использоваться при запуске гостя. Этот механизм также означает, что разные инструменты могут перенаправлять разные функции. Например, memcheck необходимо перенаправить семейства функций malloc и new, а DRD и Helgrind необходимо перенаправить pthread_* и sema_*.

Для другого конца перенаправления инструмент также увидит целевые функции при загрузке библиотеки c. по умолчанию он не обрабатывает статически связанные приложения, но вы можете указать ему с помощью аргумента --soname-synonyms=somalloc=NONE искать в гостевом исполняемом файле, где n он загружен.

Перенаправление не полностью заменяет целевую функцию, оно просто действует как прокладка, позволяющая инструменту отслеживать то, что запросила функция.

Теперь вернемся к sbrk. Valgrind знает об этом, но только как системный вызов. Он проверит, что аргумент size не получен из недопустимого хранилища. memcheck не будет отслеживать память, как это происходит с malloc. Если вы используете massif с --pages-as-heap=yes, тогда он будет профилировать использование sbrk.

Если вы хотите, чтобы memcheck проверял ваши пользовательские функции распределения, вам нужно сделать одно из двух.

0 голосов
/ 29 мая 2020

Valgrind проверяет утечки памяти, связывая свои собственные функции вместо malloc, calloc, realloc и free. Это можно увидеть, когда valgrind сообщает об утечке памяти:

==24877== 8 bytes in 1 blocks are definitely lost in loss record 1 of 1
==24877==    at 0x4C29EA3: malloc (vg_replace_malloc.c:309)
==24877==    by 0x4EC3AA9: strdup (in /usr/lib64/libc-2.17.so)
==24877==    by 0x40058E: main (x1.c:9)

Здесь вы можете видеть, что вызывается версия strdup lib c, но вместо malloc вызывается замена valgrind для *1009* lib c malloc.

Вы выделяете память на более низком уровне, используя sbrk. Valgrind не перехватывает эту функцию, поэтому не обнаруживает утечки. Фактически, согласно документации valgrind , он перечисляет sbrk как зависимость. Из раздела 1.1.4:

Чтобы узнать, какие символы glib c используются Valgrind, восстановите флаги ссылок -nostdlib -Wl,-no-undefined. Это приводит к сбою связывания, но расскажет, от чего вы зависите. Я в основном, но не полностью, избавился от зависимостей glib c; что остается, ИМО, довольно безвредно. AFAIK текущие зависимости: memset, memcmp, stat, system, sbrk, setjmp и longjmp.

...