Текст книги "Linux программирование в примерах"
Автор книги: Арнольд Роббинс
Жанры:
Программирование
,сообщить о нарушении
Текущая страница: 51 (всего у книги 55 страниц)
dmalloc
Библиотека dmalloc
предоставляет большое число опций отладки. Ее автором является Грей Ватсон (Gray Watson), есть также и свой веб-сайт.[180]180
http://www.dmalloc.com
– Примеч. автора.
[Закрыть] Как и в случае с Electric Fence, она может быть уже установленной на вашей системе, или же вы можете ее извлечь и построить самостоятельно.
Библиотека dmalloc
проверяет наличие в переменной окружения DMALLOC_OPTIONS
управляющей информации. Например, она может выглядеть следующим образом:
$ echo $DMALLOC_OPTIONS
debug=0x4e40503,inter=100,log=dm-log
Компонент 'debug
' этой переменной содержит набор битовых флагов, которыми для большинства людей почти невозможно непосредственно управлять. Поэтому документация описывает двухэтапный процесс для облегчения их использования.
Первый шаг заключается в определении функции оболочки с названием dmalloc
, которая вызывает программу драйвера dmalloc
:
$ dmalloc() {
> eval 'command dmalloc -b $*' /* Команда 'command' обходит функции оболочки */
> }
После того, как это сделано, вы можете передать функции опции для установки файла журнала (-1), указать число итераций, после которых dmalloc
должна проверить свои внутренние структуры данных (-1), и указать уровень отладки или другой тэг ('low
').
$ dmalloc -1 dm-log -i 100 low
Как и Electric Fence, библиотека dmalloc
может быть скомпонована с приложением статически или связана динамически при помощи LD_PRELOAD
. Последнее демонстрирует следующий пример:
$ LD_PRELOAD=libdmalloc.so ch15-badmem1 -b /* Запустить с проверкой */
p =
ЗАМЕЧАНИЕ. Не используйте '
export LD_PRELOAD=libdmalloc.so
'! Если вы это сделаете, каждая программа, которую вы запустите, такая какls
, будет выполняться со включенной проверкойmalloc()
. Ваша система быстро станет непригодной. Если вы сделали это случайно, можете использовать 'unset LD_PRELOAD
', чтобы восстановить обычное поведение.
Результаты записываются в файл dm-log
следующим образом:
$ cat dm-log
1062078174: 1: Dmalloc version '4.8.1' from 'http://dmalloc.com/'
1062078174: 1: flags = 0x4e40503, logfile 'dm-log'
1062078174: 1: interval = 100, addr = 0, seen # = 0
1062078174: 1: starting time = 1062078174
1062078174: 1: free bucket count/bits: 63/6
1062078174: 1: basic-block 4096 bytes, alignment 8 bytes, heap grows up
1062078174: 1: heap: 0x804a000 to 0x804d000, size 12288 bytes (3 blocks)
1062078174: 1: heap checked 0
1062078174: 1: alloc calls: malloc 1, calloc 0, realloc 0, free 0
1062078174: 1: alloc calls: recalloc 0, memalign 0, valloc 0
1062078174: 1: total memory allocated: 30 bytes (1 pnts)
1062078174: 1: max in use at one time: 30 bytes (1 pnts)
1062078174: 1: max alloced with 1 call: 30 bytes
1062078174: 1: max alloc rounding loss: 34 bytes (53%)
1062078174: 1: max memory space wasted: 3998 bytes (98%)
1062078174: 1: final user memory space: basic 0, divided 1, 4062 bytes
1062078174: 1: final admin overhead: basic 1, divided 1, 8192 bytes (66%)
1062078174: 1: final external space: 0 bytes (0 blocks)
1062078174: 1: top 10 allocations:
1062078174: 1: total-size count in-use-size count source
1062078174: 1: 30 1 30 1 ra=0x8048412
1062078174: 1: 30 1 30 1 Total of 1
1062078174: 1: dumping not-freed pointers changed since 0:
1062078174: 1: not freed: '0x804c008|s1' (30 bytes) from 'ra=0x8048412'
1062078174: 1: total-size count source
1062078174: 1: 30 1 ra=0x8048412 /* Выделение здесь */
1062078174: 1: 30 1 Total of 1
1062078174: 1: unknown memory: 1 pointer, 30 bytes
1062078174: 1: ending time = 1062078174, elapsed since start = 0:00:00
Вывод содержит много статистических данных, которые нам пока не интересны. Интересна строка, в которой указывается не освобожденная память, с адресом возврата, указывающим на выделившую память функцию ('ra=0х8048412
'). Документация dmalloc
объясняет, как получить расположение в исходном коде этого адреса с использованием GDB.
$ gdb ch15-badmem1 /* Запустить GDB */
GNU gdb 5.3
...
(gdb) x 0x8048412 /* Проверить адрес */
0x8048412
(gdb) info line *(0x8048412) /* Получить сведения о строке */
Line 11 of "ch15-badmem1.с" starts at address 0x8048408
and ends at 0x8048418
Это трудно, но выполнимо, если нет другого выбора. Однако, если вы включите в свою программу заголовочный файл "dmalloc.h
" (после всех остальных операторов #include
), вы можете получить сведения из исходного кода непосредственно в отчете.
...
1062080258: 1: top 10 allocations:
1062080258: 1: total-size count in-use-size count source
1062080258: 1: 30 1 30 1 ch15-badmem2.c:13
1062080258: 1: 30 1 30 1 Total of 1
1062080258: 1: dumping not-freed pointers changed since 0:
1062080258: 1: not freed: '0x804c008|s1' (30 bytes) from 'ch15-badmem2.c:13'
1062080258: 1: total-size count source
1062080258: 1: 30 1 ch15-badmem2.с:13
1062080258: 1: 30 1 Total of 1
...
(Файл ch15-badmem2.c
является аналогичным ch15-badmem1.с
, за исключением того, что он включает "dmalloc.h"
, поэтому мы не стали беспокоиться с его отображением).
Отдельные возможности отладки включаются или выключаются посредством использования лексем (tokens) – специально распознаваемых идентификаторов – и опций -р
для добавления лексем (свойств) или -m
для их удаления. Имеются предопределенные комбинации, 'low
', 'med
' и 'high
'. Чем являются эти комбинации, вы можете увидеть с помощью 'dmalloc -Lv
'.
$ dmalloc low /* Установить low */
$ dmalloc -Lv /* Показать установки */
Debug Malloc Utility: http://dmalloc.com/
For a list of the command-line options enter: dmalloc –usage
Debug-Flags 0x4e40503 (82052355) (low) /* Текущие лексемы */
log-stats, log-non-free, log-bad-space, log-elapsed-time, check-fence,
free-blank, error-abort, alloc-blank, catch-null
Address not-set
Interval 100
Lock-On not-set
Logpath 'log2'
Start-File not-set
Полный список лексем вместе с кратким объяснением и соответствующим каждой лексеме числовым значением можно получить с помощью 'dmalloc -DV
':
$ dmalloc -DV
Debug Tokens:
none (nil) – no functionality (0)
log-stats (lst) – log general statistics (0x1)
log-non-free (lnf) – log non-freed pointers (0x2)
log-known (lkn) – log only known non-freed (0x4)
log-trans (ltr) – log memory transactions (0x8)
log-admin (lad) – log administrative info (0x20)
log-blocks (lbl) – log blocks when heap-map (0x40)
log-bad-space (lbs) – dump space from bad pnt (0x100)
log-nonfree-space (lns) – dump space from non-freed pointers (0x200)
log-elapsed-time (let) – log elapsed-time for allocated pointer (0x40000)
log-current-time (let) – log current-time for allocated pointer (0x80000)
check-fence (cfe) – check fence-post errors (0x400)
check-heap (che) – check heap adm structs (0x800)
check-lists (cli) – check free lists (0x1000)
check-blank (cbl) – check mem overwritten by alloc-blank, free-blank (0x2000)
check-funcs (cfu) – check functions (0x4000)
force-linear (fli) – force heap space to be linear (0x10000)
catch-signals (csi) – shutdown program on SIGHUP, SIGINT, SIGTERM (0x20000)
realloc-copy (rco) – copy all re-allocations (0x100000)
free-blank (fbl) – overwrite freed memory space with BLANK_CHAR (0x200000)
error-abort (eab) – abort immediately on error (0x400000)
alloc-blank (abl) – overwrite newly alloced memory with BLANK_CHAR (0x800000)
heap-check-map (hem) – log heap-map on heap-check (0x1000000)
print-messages (pme) – write messages to stderr (0x2000000)
catch-null (cnu) – abort if no memory available (0x4000000)
never-reuse (nre) – never re-use freed memory (0x8000000)
allow-free-null (afn) – allow the frees of NULL pointers (0x20000000)
error-dump (edu) – dump core on error and then continue (0x40000000)
К этому времени у вас должно быть ощущение того, как использовать dmalloc
, и его гибкости, dmalloc
является избыточным для нашей простой демонстрационной программы, но он неоценим для более крупномасштабного, реального приложения.
Инструменты, описанные в предыдущем разделе, все фокусируются на отладке динамической памяти, и это в самом деле является значительной проблемной областью для многих программ. Однако, проблемы динамической памяти не являются единственной разновидностью. Программа Valgrind под лицензией GPL охватывает большое разнообразие проблем, включая те, которые происходят от динамической памяти.
Руководство по Valgrind описывает программу также или лучше, чем можем мы, поэтому мы будем цитировать (и сокращать) его по мере продвижения вперед.
Valgrind является гибким инструментом для отладки и профилирования исполняемых файлов Linux-x86. Инструмент состоит из ядра, которое программно обеспечивает искусственный процессор x86, и ряда «оболочек», каждая из которых является отладочным или профилирующим инструментом. Архитектура модульная, так что можно легко создавать новые «оболочки», не нарушая существующую структуру.
Наиболее полезной «оболочкой» является
memcheck
.«Оболочка»
memcheck
обнаруживает в ваших программах проблемы с управлением памятью. Проверяются все чтения и записи памяти, а вызовыmalloc/new/free/delete
перехватываются. В результатеmemcheck
может обнаружить следующие проблемы• Использование неинициализированной памяти.
• Чтение/запись в память после ее освобождения.
• Чтение/запись за границей выделенного
malloc
блока.• Чтение/запись в ненадлежащие области стека.
• Утечки памяти, когда указатели на выделенные
malloc
теряются навсегда.• Несоответствующее использование
malloc/new/new[]
противfree/delete/delete[]
.• Некоторые неправильные употребления
pthreads
API POSIX.Проблемы, подобные этим, могут быть трудно обнаруживаемыми другими средствами, часто остающимися необнаруженными в течение длительного времени и вызывающими редкие, трудные для обнаружения отказы.
Другие «оболочки» более специализированы:
•
cachegrind
осуществляет обстоятельную имитацию кэшей I1, D1 и L2 процессора, поэтому может точно указать источники осечек кэшей в вашем коде.•
addrcheck
идентичнаmemcheck
за исключением одной детали – она не проверяет неинициализированные данные. Все остальные проверки – главным образом, точная проверка адресов – по-прежнему проводится. Обратной стороной этого является то, что вы не перехватываете ошибки неинициализированных данных, которые может найтиmemcheck
.Но положительная сторона значительна: программы работают почти в два раза быстрее, чем с
memcheck
, используя значительно меньше памяти. Утилита по-прежнему находит чтения/записи освобожденной памяти, памяти за пределами выделенных блоков и в других недействительных местах, ошибки, которые вы действительно хотите обнаружить до выпуска программы в свет!•
helgrind
является отладочной оболочкой, предназначенной для обнаружения состязания данных в многопоточных программах.
Наконец, руководство отмечает:
Valgrind тесно связан с особенностями процессора, операционной системы и, в меньшей степени, компилятора и основных библиотек С. Это затрудняет его переносимость, поэтому мы с самого начала сконцентрировались на том, что мы считаем широко использующейся платформой: Linux на x86. Valgrind использует стандартный механизм Unix '
./configure
', 'make
', 'make install
', и мы попытались обеспечить его работу на машинах с ядром 2.2 или 2.4 и glibc 2.1.X, 2.2.X или 2.3.1. Это должно охватить значительное большинство современных установок Linux. Обратите внимание, что glibc-2.3.2+ с пакетом NPTL (Native POSIX Thread Library – собственная библиотека потоков POSIX) не будет работать. Мы надеемся исправить это, но это будет нелегко.
Если вы используете GNU/Linux на другой платформе или используете коммерческую систему Unix, Valgrind не окажет вам большой помощи. Однако, поскольку системы GNU/Linux на x86 довольно обычны (и вполне доступны), вполне вероятно, что вы сможете приобрести ее с умеренным бюджетом, или по крайней мере, занять на время! Что еще, когда Valgrind нашел для вас проблему, она исправляется для любой платформы, для которой компилируется ваша программа. Таким образом, разумно использовать систему x86 GNU/Linux для разработки, а какую-нибудь другую коммерческую систему Unix для развертывания высококачественного продукта.[181]181
Все в большей степени для разработки высококачественных продуктов используется также GNU/Linux! – Примеч. автора.
[Закрыть]
Хотя из руководства Valgrind у вас могло сложиться впечатление, что существуют отдельные команды memcheck
, addrcheck
и т.д., это не так. Вместо этого программа оболочки драйвера с именем valgrind
запускает отладочное ядро с соответствующей «оболочкой», указанной в опции –skin=
. Оболочкой по умолчанию является memcheck
; таким образом, запуск просто valgrind
равносильно 'valgrind –skin=memcheck
' (Это обеспечивает совместимость с более ранними версиями Valgrind, которые осуществляли лишь проверку памяти, это имеет также больший смысл, поскольку оболочка memcheck
предоставляет большую часть сведений.)
Valgrind предусматривает ряд опций. За всеми подробностями мы отсылаем вас к его документации. Опции поделены на две группы; из тех, которые используются с ядром (т. е. работают для всех оболочек), наиболее полезными могут быть следующие:
–gdb-attach=no/yes
Запускается с подключенным к процессу GDB для интерактивной отладки. По умолчанию используется no
.
–help
Перечисляет опции.
–logfile=файл
Записывает сообщения в файл.pid
.
–num-callers=число
Выводит число вызывающих в трассировке стека. По умолчанию 4.
–skin=оболочка
Использует соответствующую оболочку. По умолчанию memcheck
.
–trace-children=no|yes
Запускает трассировку также в порожденных процессах. По умолчанию используется no
.
-V
, –verbose
Использует более полный вывод. Это включает перечисление загруженных библиотек, а также подсчеты всех различных видов ошибок.
Из опций для оболочки memcheck
мы полагаем, что эти являются наиболее полезными.
–leak-check=no|yes
Искать утечки памяти после завершения программы. По умолчанию используется no
.
–show-reachable=no|yes
Показать доступные блоки после завершения программы. Если используется –show-reachable=yes
, Valgrind ищет динамически выделенную память, на которую все еще есть указывающий на нее указатель. Такая память не является утечкой, но о ней все равно следует знать. По умолчанию используется no
.
Давайте посмотрим на Valgrind в действии. Помните ch15-badmem.c
? (См. раздел 15.5.2.2 «Electric Fence».) Опция -b
записывает в память, находящуюся вне выделенного malloc()
блока. Вот что сообщает Valgrind:
$ valgrind ch15-badmem1 -b
1 ==8716== Memcheck, a.k.a. Valgrind, a memory error detector for x86-linux.
2 ==8716== Copyright (C) 2002-2003, and GNU GPL'd, by Julian Seward.
3 ==8716== Using valgrind-20030725, a program supervision framework for x86-linux.
4 ==8716== Copyright (C) 2000-2003, and GNU GPL'd, by Julian Seward.
5 ==8716== Estimated CPU clock rate is 2400 MHz
6 ==8716== For more details, rerun with: -v
7 ==8716==
8 p =
9 ==8716== Invalid write of size 1
10 ==8716== at 0x8048466: main (ch15-badmem1.c:18)
11 ==8716== by 0x420158D3: __libc_start_main (in /lib/i686/libc-2.2.93.so)
12 ==8716== by 0x8048368: (within /home/arnold/progex/code/ch15/ch15-badmem1)
13 ==8716== Address 0x4104804E is 12 bytes after a block of size 30 alloc'd
14 ==8716== at 0x40025488: malloc (vg_replace_malloc.с:153)
15 ==8716== by 0x8048411: main (ch15-badmem1.c:11)
16 ==8716== by 0x420158D3: __libc_start_main (in /lib/i686/libc-2.2.93.so)
17 ==8716== by 0x8048368: (within /home/arnold/progex/code/ch15/ch15-badmem1)
18 ==8716==
19 ==8716== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
20 ==8716== malloc/free: in use at exit: 30 bytes in 1 blocks.
21 ==8716== malloc/free: 1 allocs, 0 frees, 30 bytes allocated.
22 ==8716== For a detailed leak analysis, rerun with: –leak-check=yes
23 ==8716== For counts of detected errors, rerun with: -v
(Были добавлены номера строк в выводе, чтобы облегчить обсуждение.) Строка 8 является выводом программы; остальные от Valgrind в стандартную ошибку. Сообщение об ошибке находится в строках 9–17. Она указывает, сколько байтов было записано неверно (строка 9), где это случилось (строка 10), и показывает трассировку стека. Строки 13–17 описывают, откуда была выделена память. Строки 19–23 подводят итоги.
Опция -f
программы ch15-badmem1
освобождает выделенную память, а затем записывает в нее через висячий указатель. Вот что сообщает Valgrind в этом случае:
$ valgrind ch15-badmem1 -f
==8719== Memcheck, a.k.a. Valgrind, a memory error detector for x86-linux.
...
p =
==8719== Invalid write of size 1
==8719== at 0x8048498: main (ch15-badmem1.с:21)
==8719== by 0x420158D3: __libc_start_main (in /lib/i686/libc-2.2.93.so)
==8719== by 0x8048368: (within /home/arnold/progex/code/ch15/ch15-badmem1)
==8719== Address 0x41048024 is 0 bytes inside a block of size 30 free'd
==8719== at 0x40025722: free (vg_replace_malloc.с:220)
==8719== by 0x8048491: main (ch15-badmem1.c:20)
==8719== by 0x420158D3: __libc_start_main (in /lib/i686/libc-2.2.93.so)
==8719== by 0x8048368: (within /home/arnold/progex/code/ch15/ch15-badmem1)
...
На этот раз в отчете указано, что запись была осуществлена в освобожденную память и что вызов free()
находится в строке 20 ch15-badmem1.c
.
При вызове без опций ch15-badmem1.c
выделяет и использует память, но не освобождает ее. О таком случае сообщает опция —leak-check=yes
:
$ valgrind –leak-check=yes ch15-badmem1
1 ==8720== Memcheck, a.k.a. Valgrind, a memory error detector for x86-linux.
...
8 p =
9 ==8720==
10 ==8720== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
11 ==8720== malloc/free: in use at exit: 30 bytes in 1 blocks.
12 ==8720== malloc/free: 1 allocs, 0 frees, 30 bytes allocated.
...
16 ==8720==
17 ==8720== 30 bytes in 1 blocks are definitely lost in loss record 1 of 1
18 ==8720== at 0x40025488: malloc (vg_replace_malloc.c:153)
19 ==8720== by 0x8048411: main (ch15-badmem1.c:11)
20 ==8720== by 0x420158D3: __libc_start_main (in /lib/i686/libc-2.2.93.so)
21 ==8720== by 0x8048368: (within /home/arnold/progex/code/ch15/ch15-badmem1)
22 ==8720==
23 ==8720== LEAK SUMMARY:
24 ==8720== definitely lost: 30 bytes in 1 blocks.
25 ==8720== possibly lost: 0 bytes in 0 blocks.
26 ==8720== still reachable: 0 bytes in 0 blocks.
27 ==8720== suppressed: 0 bytes in 0 blocks.
28 ==8720== Reachable blocks (those to which a pointer was found) are not shown.
29 ==8720== To see them, rerun with: –show-reachable=yes
Строки 17–29 предоставляют отчет об утечке; эта память была выделена в строке 11 ch15-badmem1.с
.
Помимо отчетов о неправильном использовании динамической памяти, Valgrind может диагностировать использование неинициализированной памяти. Рассмотрим следующую программу, ch15-badmem3.c
:
1 /* ch15-badmem3.c – плохое обращение с нединамической памятью */
2
3 #include
4 #include
5
6 int main(int argc, char **argv)
7 {
8 int a_var; /* Обе не инициализированы */
9 int b_var;
10
11 /* Valgrind не отметит это; см. текст. */
12 a_var = b_var;
13
14 /* Использование неинициализированной памяти; это отмечается. */
15 printf("a_var = %dn", a_var);
16
17 return 0;
18 }
При запуске Valgrind выдает этот (сокращенный) отчет:
==29650== Memcheck, a.k.a. Valgrind, a memory error detector for x86-linux.
...
==29650== Use of uninitialised value of size 4
==29650== at 0x42049D2A: _IO_vfprintf_internal (in /lib/i686/libc-2.2.93.so)
==29650== by 0x420523C1: _IO_printf (in /lib/1686/libc-2.2.93.so)
==29650== by 0x804834D: main (ch15-badmem3.с:15)
==29650== by 0x420158D3: __libc_start_main (in /lib/i686/libc-2.2.93.so)
==29650==
==29650== Conditional jump or move depends on uninitialised value(s)
==29650== at 0X42049D32: _IO_vfprintf_internal (in /lib/i686/libc-2.2.93.so)
==29650== by 0x420523C1: _IO_printf (in / lib/i686/libc-2.2.93.so)
==29650== by 0x804834D: main (ch15-badmem3.c:15)
==29650== by 0x420158D3: __libc_start_main (in /lib/i686/libc-2.2.93.so)
...
a_var = 1107341000
==29650==
==29650== ERROR SUMMARY: 25 errors from 7 contexts (suppressed: 0 from 0)
==29650== malloc/free: in use at exit: 0 bytes in 0 blocks.
==29650== malloc/free: 0 allocs, 0 frees, 0 bytes allocated.
==29650== For a detailed leak analysis, rerun with: –leak-check=yes
==29650== For counts of detected errors, rerun with: -v
В документации Valgrind объясняется, что копирование неинициализированных данных не выдает сообщений об ошибках. Оболочка memcheck отмечает состояние данных (неинициализированные) и отслеживает его при перемещениях данных. Таким образом, a_var
считается неинициализированной, поскольку это значение было получено от b_var
, которая была неинициализированной.
memcheck
сообщает о проблеме лишь тогда, когда неинициализированное значение используется. Здесь это происходит в библиотеке С (_IO_vfprintf_internal()
), которая должна преобразовать значение в строку, для этого, она проводит с этим значением вычисления.
К сожалению, хотя Valgrind может обнаружить использование неинициализированной памяти вплоть до уровня битов, он не может осуществлять проверки границ массивов для локальных и глобальных переменных. (Valgrind может осуществлять проверку границ для динамической памяти, поскольку он сам обрабатывает такую память, поэтому знает о начале и конце каждой области.)
В заключение, Valgrind является мощным инструментом отладки памяти. Он использовался в таких крупномасштабных, многопоточных производственных программах, как KDE 3, OpenOffice и веб-браузер Konqueror. Он конкурирует с несколькими коммерческими предложениями, а другая его версия была даже использована (совместно с эмулятором WINE[182]182
http://www.winehq.com
– Примеч. автора.
[Закрыть]) для отладки программ, написанных для Microsoft Windows с использованием Visual С++! Вы можете получить Valgrind с его веб-сайта[183]183
http://valgrind.kde.org
– Примеч. автора.
[Закрыть].