Затем мы создадим фиктивную программу на языке C – memory.c и добавим memwatch.h в строку 3, чтобы можно было включить MEMWATCH.
Кроме того, два флага compile-time -DMEMWATCH и -DMW_STDIO необходимо добавить к оператору компиляции для каждого исходного файла в программе.
# cat memory.c
1 #include
2 #include
3 #include "memwatch.h"
4
5 int main(void)
6 {
7 char *ptr1;
8 char *ptr2;
9
10 ptr1 = malloc(512);
11 ptr2 = malloc(512);
12
13 ptr2 = ptr1;
14 free(ptr2);
15 free(ptr1);
16 }
Код, показанный выше, выделяет два 512-байтовых блока памяти (строки 10 и 11), а затем указатель на первый блок устанавливается на второй блок (строка 13).
В результате адрес второго блока теряется, и происходит утечка памяти.
Теперь скомпилируйте файл memwatch.c, который является частью пакета MEMWATCH, с образцом исходного кода (memory1.c).
Ниже приведен пример файла makefile для сборки memory1.c. memory1 – это исполняемый файл, созданный этим make-файлом:
# gcc -DMEMWATCH -DMW_STDIO memory1.c memwatch.c -o memory1
Затем мы выполняем программу memory1, которая фиксирует две аномалии управления памятью.
./memory1
MEMWATCH detected 2 anomalies
MEMWATCH создает журнал с именем memwatch.log. как вы можете видеть ниже, он создается при запуске программы memory1.
MEMWATCH сообщит вам, в какой строке возникла проблема.
В случае отсутствия уже освобожденного указателя он определяет это условие.
То же самое и с неизведанной памятью.
В разделе в конце журнала отображается статистика, включая объем утечки памяти, объем использованной памяти и общий объем выделенной памяти.
На приведенном выше рисунке вы можете видеть, что ошибки управления памятью возникают в строке 15, которая показывает, что имеется двойная фри память.
Следующая ошибка – это утечка памяти размером 512 байт, и эта память выделена в строке 11.
2. Valgrind
Valgrind – это специальный инструмент Intel для архитектуры x86, который имитирует процессор класса x86 для прямого отслеживания всех обращений к памяти и анализа потока данных.
Одно из его преимуществ состоит в том, что вам не нужно перекомпилировать программы и библиотеки, которые вы хотите проверить, хотя это работает лучше, если бы они были скомпилированы с параметром -g, чтобы они включали таблицы символов отладки.
Он работает, выполняя программу в эмулируемой среде и перехватывая выполнение в различных точках.
Это приводит к большому недостатку Valgrind, заключающемуся в том, что программа работает с небольшой скоростью, что делает ее менее полезной при тестировании чего-либо с ограничениями в реальном времени.
Valgrind доступен в большинстве дистрибутивов Linux, поэтому вы можете сразу установить инструмент.
# rpm -Uvh /tmp/valgrind-3.15.0-11.el7.x86_64.rpm
Preparing... ################################# [100%]
Updating / installing...
1:valgrind-1:3.15.0-11.el7 ################################# [100%]
Затем вы можете выполнить valgrind с процессом, у которого вы хотите проверить утечку памяти.
Например, я хочу проверить утечку памяти у процесса amsHelper, который выполняется с параметром -f.
Нажмите Ctrl + C, чтобы остановить мониторинг
# valgrind amsHelper -f
==30159== Memcheck, a memory error detector
==30159== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==30159== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==30159== Command: amsHelper -f
==30159==
NET-SNMP version 5.7.3 AgentX subagent connected
^C==30159==
==30159== HEAP SUMMARY:
==30159== in use at exit: 853,777 bytes in 11,106 blocks
==30159== total heap usage: 21,779 allocs, 10,673 frees, 28,226,706 bytes allocated
==30159==
==30159== LEAK SUMMARY:
==30159== definitely lost: 73 bytes in 2 blocks
==30159== indirectly lost: 0 bytes in 0 blocks
==30159== possibly lost: 32,341 bytes in 120 blocks
==30159== still reachable: 821,363 bytes in 10,984 blocks
==30159== suppressed: 0 bytes in 0 blocks
==30159== Rerun with --leak-check=full to see details of leaked memory
==30159==
==30159== For lists of detected and suppressed errors, rerun with: -s
==30159== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
Чтобы сохранить вывод в файл журнала и собрать более подробную информацию об утечке, используйте –leak-check-full вместе с –log-file = /path/og/log/file.
Нажмите Ctrl + C, чтобы остановить мониторинг
# valgrind --leak-check=full --log-file=/tmp/mem-leak-amsHelper.log amsHelper -f
NET-SNMP version 5.7.3 AgentX subagent connected
Теперь вы можете проверить содержимое /tmp/mem-leak-amsHelper.log.
Когда он обнаруживает проблему, вывод Valgrind имеет следующий формат:
==771== 72 bytes in 1 blocks are definitely lost in loss record 2,251 of 3,462
==771== at 0x4C2B975: calloc (vg_replace_malloc.c:711)
==771== by 0x6087603: ??? (in /usr/lib64/libnss3.so)
==771== by 0x60876A8: ??? (in /usr/lib64/libnss3.so)
==771== by 0x6087268: ??? (in /usr/lib64/libnss3.so)
==771== by 0x607C11A: ??? (in /usr/lib64/libnss3.so)
==771== by 0x60808C5: ??? (in /usr/lib64/libnss3.so)
==771== by 0x602A269: ??? (in /usr/lib64/libnss3.so)
==771== by 0x602AA80: NSS_InitContext (in /usr/lib64/libnss3.so)
==771== by 0x550F3BA: rpmInitCrypto (in /usr/lib64/librpmio.so.3.2.2)
==771== by 0x52CBF8D: rpmReadConfigFiles (in /usr/lib64/librpm.so.3.2.2)
==771== by 0x473C74: ??? (in /usr/sbin/amsHelper)
==771== by 0x441E24: ??? (in /usr/sbin/amsHelper)
3. Memleax
Одним из недостатков Valgrind является то, что вы не можете проверить утечку памяти существующего процесса, и тут нам на помощь приходит memleax.
Он перехватывает вызов целевого процесса о выделении и освобождении памяти и сообщает в реальном времени о блоках памяти, которые живут достаточно долго.
Порог истечения срока действия по умолчанию составляет 10 секунд, однако вы всегда должны устанавливать его с помощью опции -e в соответствии с вашими сценариями.
Вы можете скачать memleax из официального репозитория github
rpm -Uvh /tmp/memleax-1.1.1-1.el7.centos.x86_64.rpm
error: Failed dependencies:
libdwarf.so.0()(64bit) is needed by memleax-1.1.1-1.el7.centos.x86_64
libunwind-x86_64.so.8()(64bit) is needed by memleax-1.1.1-1.el7.centos.x86_64
libunwind.so.8()(64bit) is needed by memleax-1.1.1-1.el7.centos.x86_64
Поэтому я вручную скопировал эти rpms моего официального репозитория, так как это частная сеть, я не мог использовать yum или dnf.
# rpm -Uvh /tmp/libdwarf-20130207-4.el7.x86_64.rpm
Preparing... ################################# [100%]
Updating / installing...
1:libdwarf-20130207-4.el7 ################################# [100%]
# rpm -Uvh /tmp/libunwind-1.2-2.el7.x86_64.rpm
Preparing... ################################# [100%]
Updating / installing...
1:libunwind-2:1.2-2.el7 ################################# [100%]
Теперь, когда я установил обе зависимости, я продолжу и установлю memleax rpm:
# rpm -Uvh /tmp/memleax-1.1.1-1.el7.centos.x86_64.rpm
Preparing... ################################# [100%]
Updating / installing...
1:memleax-1.1.1-1.el7.centos ################################# [100%]
Затем вам понадобится PID процесса, который вы хотите отслеживать.
Вы можете получить PID вашего процесса из вывода ps -ef
root 2102 1 0 12:29 ? 00:00:01 /sbin/amsHelper -f
root 45256 1 0 13:13 ? 00:00:00 amsHelper
root 49372 44811 0 13:23 pts/0 00:00:00 grep amsH
Теперь мы проверим утечку памяти 45256 PID.
# memleax 45256
Warning: no debug-line found for /usr/sbin/amsHelper
== Begin monitoring process 45256...
CallStack[1]: memory expires with 688 bytes, backtrace:
0x00007f0bc87010d0 libc-2.17.so calloc()+0
0x00000000004079d3 amsHelper
0x0000000000409249 amsHelper
0x0000000000407077 amsHelper
0x00000000004a7a60 amsHelper
0x00000000004a8c4c amsHelper
0x00000000004afd90 amsHelper
0x00000000004ac97a amsHelper table_helper_handler()+2842
0x00000000004afd90 amsHelper
0x00000000004bae09 amsHelper
0x00000000004bb707 amsHelper
0x00000000004bb880 amsHelper
0x00000000004bbca2 amsHelper
0x00000000004e7eb1 amsHelper
0x00000000004e8a3e amsHelper
0x00000000004e98a9 amsHelper
0x00000000004e98fb amsHelper
0x00000000004051b4 amsHelper
0x00007f0bc869d555 libc-2.17.so __libc_start_main()+245
0x00000000004053e2 amsHelper
CallStack[1]: memory expires with 688 bytes, 2 times again
CallStack[1]: memory expires with 688 bytes, 3 times again
CallStack[1]: memory expires with 688 bytes, 4 times again
CallStack[1]: memory expires with 688 bytes, 5 times again
CallStack[2]: memory expires with 15 bytes, backtrace:
0x00007f0bc87006b0 libc-2.17.so malloc()+0
0x00007f0bc8707afa libc-2.17.so __GI___strdup()+26
0x00007f0bc8731141 libc-2.17.so tzset_internal()+161
0x00007f0bc8731b03 libc-2.17.so __tz_convert()+99
Вы можете получить результат, аналогичный приведенному выше, в случае утечки памяти в процессе приложения.
Нажмите Ctrl + C, чтобы остановить мониторинг
4. Сбор дампа ядра
Иногда это помогает разработчику: мы можем поделиться дампом ядра процесса, в котором происходит утечка памяти.
В Red Hat / CentOS вы можете собирать дамп ядра с помощью abrt и abrt-addon-ccpp
Прежде чем начать, убедитесь, что система настроена на создание ядер приложений, сняв ограничения ядра:
# ulimit -c unlimited
Затем установите эти rpm пакеты :
# yum install abrt abrt-addon-ccpp abrt-tui
Убедитесь, что установлены ccpp hooks:
# abrt-install-ccpp-hook install
# abrt-install-ccpp-hook is-installed; echo $?;
Убедитесь, что эта служба запущена и включен ccpp hook для захвата дампов ядра:
# systemctl enable abrtd.service --now
# systemctl enable abrt-ccpp.service --now
Включим хуки
# abrt-auto-reporting enabled
Чтобы получить список сбоев в командной строке, введите следующую команду:
# abrt-cli list
Но поскольку сбоев нет, вывод будет пустым.
Затем узнайте PID, для которого вы хотите собрать дамп ядра, например, я буду собирать для PID 45256
root 2102 1 0 12:29 ? 00:00:01 /sbin/amsHelper -f
root 45256 1 0 13:13 ? 00:00:00 amsHelper
root 49372 44811 0 13:23 pts/0 00:00:00 grep amsH
Затем мы должны отправить SIGABRT, то есть -6 сигнал kill на этот PID, чтобы сгенерировать дамп ядра.
# kill -6 45256
Затем вы можете проверить список доступных дампов, где сможете увидеть новую запись для этого PID.
Этот дамп будет содержать всю информацию, необходимую для анализа утечки этого процесса.
5. Как определить утечку памяти с помощью стандартных инструментов Linux
Мы обсудили сторонние инструменты, которые можно использовать для обнаружения утечки памяти с дополнительной информацией в коде, которая может помочь разработчику проанализировать и исправить ошибку.
Но если наше требование – просто следить за процессом, который резервирует память без причины, тогда нам придется полагаться на системные инструменты, такие как sar, vmstat, pmap, meminfo и т.l
Итак, давайте узнаем об использовании этих инструментов для определения возможного сценария утечки памяти.
Прежде чем начать, вы должны быть знакомы с приведенными ниже областями.
- Как проверить фактическую память, потребляемую отдельным процессом
- Сколько резервирования памяти является нормальным для вашего процесса приложения
pmap предоставит вам более подробный вывод памяти, потребляемой отдельными сегментами адреса и библиотеками процесса, как вы можете видеть ниже.
# pmap -X $(pgrep amsHelper -f)
15046: /sbin/amsHelper -f
Address Perm Offset Device Inode Size Rss Pss Referenced Anonymous Swap Locked Mapping
00400000 r-xp 00000000 fd:02 7558 1636 1152 1152 1152 0 0 0 amsHelper
00799000 r--p 00199000 fd:02 7558 4 4 4 4 4 0 0 amsHelper
0079a000 rw-p 0019a000 fd:02 7558 52 48 48 48 20 0 0 amsHelper
007a7000 rw-p 00000000 00:00 0 356 48 48 48 48 0 0
01962000 rw-p 00000000 00:00 0 9716 9716 9716 9716 9716 0 0 [heap]
7fd75048b000 r-xp 00000000 fd:02 3406 524 320 44 320 0 0 0 libfreeblpriv3.so
7fd75050e000 ---p 00083000 fd:02 3406 2048 0 0 0 0 0 0 libfreeblpriv3.so
7fd75070e000 r--p 00083000 fd:02 3406 8 8 8 8 8 0 0 libfreeblpriv3.so
7fd750710000 rw-p 00085000 fd:02 3406 4 4 4 4 4 0 0 libfreeblpriv3.so
7fd750711000 rw-p 00000000 00:00 0 16 16 16 16 16 0 0
<output trimmed>
7fd75ba5f000 rw-p 00022000 fd:02 4011 4 4 4 4 4 0 0 ld-2.17.so
7fd75ba60000 rw-p 00000000 00:00 0 4 4 4 4 4 0 0
7ffdeb75d000 rw-p 00000000 00:00 0 132 32 32 32 32 0 0 [stack]
7ffdeb79a000 r-xp 00000000 00:00 0 8 4 0 4 0 0 0 [vdso]
ffffffffff600000 r-xp 00000000 00:00 0 4 0 0 0 0 0 0 [vsyscall]
====== ===== ===== ========== ========= ==== ======
196632 15896 13896 15896 10384 0 0 KB
В качестве альтернативы вы можете получить ту же информацию с более подробной информацией, используя smaps соответствующего процесса.
Вот небольшой скрипт, чтобы объединить память и получить общую сумму, но вы также можете удалить pipe и разбить команду, чтобы получить более подробную информацию.
# cat /proc/$(pgrep amsHelper)/smaps | grep -i pss | awk '{Total+=$2} END {print Total/1024" MB"}'
14.4092 MB
Таким образом, вы можете поставить задание cron или создать демона для своевременного мониторинга потребления памяти вашим приложением, используя эти инструменты, чтобы выяснить, не потребляют ли они слишком много памяти с течением времени.
Заключение
В этом руководстве я поделился различными командами, инструментами и методами для обнаружения и отслеживания утечки памяти в различных типах приложений, таких как программы C или C ++, приложения Linux и т. д.
Выбор инструментов будет зависеть от ваших требований. Есть много других инструментов, таких как YAMD, Electric fence, gdb core dump и т. д., которые могут помочь вам зафиксировать утечку памяти.
Наконец, я надеюсь, что шаги из статьи по проверке и отслеживанию утечки памяти в Linux были полезны.