Подтвердить что ты не робот

Valgrind в отчетах MIPS не использует кучи

Я использую valgrind (v3.10.0) для поиска утечки памяти в сложном приложении (сильно модифицированная сборка net-snmp), которая строится как часть более крупного программного пакета. Я уверен, что есть утечка (объем памяти приложения растет линейно без ограничений), но valgrind всегда сообщает о завершении после следующего.

==1139== HEAP SUMMARY:
==1139==     in use at exit: 0 bytes in 0 blocks
==1139==   total heap usage: 0 allocs, 0 frees, 0 bytes allocated
==1139== 
==1139== All heap blocks were freed -- no leaks are possible

Общее использование кучи не может быть нулевым - в приложении malloc и free есть много, много вызовов. Valgrind все еще способен обнаруживать ошибки "Invalid Write".

Скомпилированное приложение вместе с другими пакетами программного обеспечения компилируется с помощью инструментальной цепочки uclibc-gcc для процессора MIPS (uclibc v0.9.29), который должен быть свернут на встроенное устройство, работающее с linux-оболочкой busybox (v1.17.2), Я запускаю valgrind прямо на устройстве. При запуске Valgrind я использую следующие параметры:

--tool=memcheck --leak-check=full --undef-value-errors=no --trace-children=yes

В принципе, Valgrind не обнаруживает никакого использования кучи, хотя я использовал кучу. Почему это может быть? Являются ли какие-либо из моих предположений (ниже) неправильными?


Что я пробовал

Простая тестовая программа

Я скомпилировал простую тестовую программу (используя ту же цель и инструментальную цепочку, что и приложение выше) из учебника быстрого обучения Valgaind , чтобы узнать, обнаружит ли Valgrind протечь. Конечный результат был таким же, как и выше: без использования кучи.

Связанные проблемы?

Документация Valgrind имеет следующие ответы на их часто задаваемые вопросы:

Если ваша программа статически связана, большинство инструментов Valgrind будут работать только хорошо, если они смогут заменить некоторые функции, такие как malloc, своими версиями. По умолчанию статически связанные функции malloc не заменяются. Ключевым показателем этого является то, что Memcheck говорит: "Все блоки кучи были освобождены - утечки не возможны".

Вышеупомянутое звучит точно так же, как и моя проблема, поэтому я проверил, что он динамически связан с библиотеками C, содержащими malloc и free. Я использовал исполняемый файл uclibc toolchain ldd executable (я не могу использовать родной linux ldd), а вывод включал следующие строки:

libc.so.0 => not found (0x00000000)
/lib/ld-uClibc.so.0 => /lib/ld-uClibc.so.0 (0x00000000)

(Причина, по которой они не найдены, заключается в том, что я запускаю это на хост-устройстве x86, целевое устройство mips не имеет исполняемого файла ldd.) Исходя из моего понимания, malloc и free быть в одной из этих библиотек, и они, похоже, динамически связаны. Я также выполнил readelf и nm в исполняемом файле, чтобы подтвердить, что ссылки на malloc и free равны undefined (что характерно для динамически связанного исполняемого файла).

Кроме того, я попробовал запустить Valgrind с опцией --soname-synonyms=somalloc=NONE, как это было предложено в FAQ.

Поддержка LD_PRELOAD?

Как отмечают комментаторы и ответчики, Valgrind зависит от использования LD_PRELOAD. Было высказано предположение, что моя инструментальная цепочка не поддерживает эту функцию. Чтобы подтвердить, что это так, я последовал за этим примером, чтобы создать простую тестовую библиотеку и загрузить ее (я заменил rand() на функцию, которая возвращает 42). Тест работал, поэтому кажется, что моя цель поддерживает LD_PRELOAD просто отлично.

Данные эльфа

Я также включу некоторую информацию из команды readelf, которая может быть полезна. Вместо гигантского дампа я урезал все, чтобы включить только то, что может быть релевантным.

Dynamic section
  Tag        Type                         Name/Value
 0x00000001 (NEEDED)                     Shared library: [libnetsnmpagent.so.30]
 0x00000001 (NEEDED)                     Shared library: [libnetsnmpmibs.so.30]
 0x00000001 (NEEDED)                     Shared library: [libnetsnmp.so.30]
 0x00000001 (NEEDED)                     Shared library: [libgcc_s.so.1]
 0x00000001 (NEEDED)                     Shared library: [libc.so.0]
 0x0000000f (RPATH)                      Library rpath: [//lib]

Symbol table '.dynsym'
   Num:    Value  Size Type    Bind   Vis      Ndx Name
    27: 00404a40     0 FUNC    GLOBAL DEFAULT  UND free
    97: 00404690     0 FUNC    GLOBAL DEFAULT  UND malloc
4b9b3361

Ответ 1

Сначала дайте реальный тест, чтобы убедиться, что что-то статически связано.

$ ldd -v /bin/true
    linux-vdso.so.1 =>  (0x00007fffdc502000)
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f0731e11000)
    /lib64/ld-linux-x86-64.so.2 (0x00007f07321ec000)

    Version information:
    /bin/true:
        libc.so.6 (GLIBC_2.3) => /lib/x86_64-linux-gnu/libc.so.6
        libc.so.6 (GLIBC_2.3.4) => /lib/x86_64-linux-gnu/libc.so.6
        libc.so.6 (GLIBC_2.14) => /lib/x86_64-linux-gnu/libc.so.6
        libc.so.6 (GLIBC_2.4) => /lib/x86_64-linux-gnu/libc.so.6
        libc.so.6 (GLIBC_2.2.5) => /lib/x86_64-linux-gnu/libc.so.6
    /lib/x86_64-linux-gnu/libc.so.6:
        ld-linux-x86-64.so.2 (GLIBC_2.3) => /lib64/ld-linux-x86-64.so.2
        ld-linux-x86-64.so.2 (GLIBC_PRIVATE) => /lib64/ld-linux-x86-64.so.2

Вторая строка на выходе показывает, что она динамически связана с libc, которая содержит malloc.

Что касается того, что может пойти не так, я могу предложить четыре вещи:

  • Возможно, он не связан с обычным libc, но в какой-то другой библиотеке C (например, uclibc) или что-то еще valgrind не ожидает. Вышеприведенный тест покажет вам, с чем он связан. Чтобы работать valgrind, он использует LD_PRELOAD для обертывания функций malloc() и free() (описание обертки общей функции здесь). Если ваш заменитель libc не поддерживает LD_PRELOAD или (так или иначе), библиотека C malloc() и free() вообще не используются (с этими именами), тогда valgrind не будет работать. Возможно, вы можете включить линию ссылок, используемую при создании своего приложения.

  • Он протекает, но он не выделяет память с помощью malloc(). Например, он может (маловероятно) выполнять собственные вызовы brk() или (более вероятно) выделения памяти с помощью mmap. Вы можете использовать это, чтобы узнать (это был свалка cat).

.

$ cat /proc/PIDNUMBERHERE/maps
00400000-0040b000 r-xp 00000000 08:01 805303                             /bin/cat
0060a000-0060b000 r--p 0000a000 08:01 805303                             /bin/cat
0060b000-0060c000 rw-p 0000b000 08:01 805303                             /bin/cat
02039000-0205a000 rw-p 00000000 00:00 0                                  [heap]
7fbc8f418000-7fbc8f6e4000 r--p 00000000 08:01 1179774                    /usr/lib/locale/locale-archive
7fbc8f6e4000-7fbc8f899000 r-xp 00000000 08:01 1573024                    /lib/x86_64-linux-gnu/libc-2.15.so
7fbc8f899000-7fbc8fa98000 ---p 001b5000 08:01 1573024                    /lib/x86_64-linux-gnu/libc-2.15.so
7fbc8fa98000-7fbc8fa9c000 r--p 001b4000 08:01 1573024                    /lib/x86_64-linux-gnu/libc-2.15.so
7fbc8fa9c000-7fbc8fa9e000 rw-p 001b8000 08:01 1573024                    /lib/x86_64-linux-gnu/libc-2.15.so
7fbc8fa9e000-7fbc8faa3000 rw-p 00000000 00:00 0
7fbc8faa3000-7fbc8fac5000 r-xp 00000000 08:01 1594541                    /lib/x86_64-linux-gnu/ld-2.15.so
7fbc8fca6000-7fbc8fca9000 rw-p 00000000 00:00 0
7fbc8fcc3000-7fbc8fcc5000 rw-p 00000000 00:00 0
7fbc8fcc5000-7fbc8fcc6000 r--p 00022000 08:01 1594541                    /lib/x86_64-linux-gnu/ld-2.15.so
7fbc8fcc6000-7fbc8fcc8000 rw-p 00023000 08:01 1594541                    /lib/x86_64-linux-gnu/ld-2.15.so
7fffe1674000-7fffe1695000 rw-p 00000000 00:00 0                          [stack]
7fffe178d000-7fffe178f000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]

Обратите внимание, действительно ли конечный адрес [heap] растет, или вы видите дополнительные записи mmap. Другим хорошим показателем того, работает ли valgrind, является отправка SIGSEGV или аналогичный процессу и просмотр, видите ли вы, что куча используется при выходе.

  1. Он не протекает в строгом смысле слова, но он протекает во всех смыслах и целях. Например, возможно, он имеет структуру данных (например, кеш), которая со временем растет. При выходе программа (правильно) освобождает все записи в кеше. Итак, при выходе ничего не используется в куче. В этом случае вы захотите узнать, что растет. Это сложнее. Я бы использовал технику, чтобы убить программу (см. Выше), захватить вывод и обработать его. Если вы видите 500 вещей через 24 часа, 1000 после 48 часов и 1500 после 72 часов, это должно указывать на то, что "утечка". Однако, как отмечает харис в комментариях, в то время как это приведет к тому, что память не будет отображаться как утечка, она не объясняет, что "общая загрузка кучи" равна нулю, так как здесь описываются суммы выделенных и освобожденных. >

  2. Возможно, valgrind просто не работает на вашей платформе. Что произойдет, если вы создадите очень простую программу, как показано ниже, и запустите valgrind на ней на своей платформе? Если это не работает, вам нужно выяснить, почему valgrind не работает правильно. Обратите внимание, что valgrind в MIPS довольно новый. Здесь - это поток электронной почты, где разработчик с MIPS и uclibc обнаруживает valgrind не сообщает о каких-либо выделениях. Его решение состоит в замене ntpl на linuxthreads.

.

#include <stdio.h>
#include <stdlib.h>
int
main (int argc, char **argv)
{
  void *p = malloc (100);       /* does not leak */
  void *q = malloc (100);       /* leaks */
  free (p);
  exit (0);
}

Ответ 2

(добавление другого ответа, поскольку сам вопрос существенно изменился после того, как ОП присудил первую награду)

Основываясь на моем понимании ваших изменений, у вас есть:

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

Для меня это означает, что valgrind имеет ошибку или несовместим с вашей toolchain. Я нашел ссылки, чтобы сказать, что он должен работать с вашей цепочкой инструментов, поэтому это означает, что в любом случае есть ошибка.

Поэтому я предлагаю вам сообщить об ошибке, используя описанный механизм здесь. Возможно, оставьте немного о своем сложном приложении, и просто укажите, что простая тестовая программа не работает. Если вы еще этого не сделали, вы можете попробовать список рассылки пользователей, как описано здесь.

Ответ 3

Чтобы убедиться, что исполняемый файл не связан статически, я запустил файл snmpd

Ваша проблема, скорее всего, не в том, что двоичный файл статически связан (вы теперь знаете, что это не так), но что malloc и free статически связаны с ним (возможно, вы используете альтернативную реализацию malloc, такую ​​как tcmalloc?).

Когда вы построили простой тестовый пример (на котором Valgrind работал правильно), вы, вероятно, не использовали ту же самую командную строку связи (и те же библиотеки), что и ваше реальное приложение.

В любом случае тривиально проверить:

readelf -Ws snmpd | grep ' malloc'

Если это показывает UND (т.е. undefined), Valgrind не должен перехватывать его. Но, скорее всего, это показывает FUNC GLOBAL DEFAULT ... malloc, а это означает, что ваш snmpd так же хорош, как статически связан с valgrind.

Предполагая, что моя догадка правильная, переставьте snmpd с флагом -Wl,-y,malloc. Это скажет вам, какая библиотека определяет ваш malloc. Удалите его из ссылки, найдите и исправьте утечку, а затем решите, стоит ли иметь эту библиотеку, какую проблему она вызвала.