Memcheck как устранить в си
Перейти к содержимому

Memcheck как устранить в си

HOW-TO: Программа на Си. Часть 6

До сих пор я представлял вам некий код и инструкции, как его скомпилировать и выполнить. Вероятно, до этого момента вам нужны были только редактор (emacs, vi, …) и компилятор (gcc). Тем не менее, существует ещё множество других утилит, облегчающих разработку кода (ведь разработка это не только набор исходного когда, а также компиляция, тестирование, и прочее). Есть даже IDE (интегрированные среды разработки), комбинирующие некоторые из этих утилит в красивый графический интерфейс (например CDT для Eclipse, kdevelop, Code::blocks, anjuta, и другие), но, по моему мнению, начинающий программист должен иметь представление о том, как они работают, изнутри, прежде чем он начнёт использовать горячие клавиши. Несмотря на существование большого количества утилит, покрывающих множество категорий, в этой статье мы сфокусируемся на поиске и устранении ошибок в коде/приложении.

strace and ltrace

strace — один из моих лучших друзей. ltrace — также отличный инструмент, но я нечасто им пользуюсь. Вы можете установить их, набрав:

Что же они делают? Strace перехватывает системные вызовы процесса. Системный вызов — это процедура, которая переносит управление в режим ядра для функций, выполняющихся в пространстве пользователя. Например, инкремент переменной транслируется в простую команду ассемблера, но когда вам нужно обратиться к ресурсам системы, это всегда приводит в режим ядра. Прочитав 'man 2 syscalls', вы получите список системных вызовов, поддерживаемых ядром. Итак, почему же заглядывать сюда — хорошая идея? Если знать какие системные вызовы делает ваше приложение, можно пройтись по его логической цепочке, и это хорошо, потому что не является вторжением в программу, и вы можете сделать это с любым исполняемым файлом в системе. В качестве примера, я рассмотрю вывод wget, устанавливаемой по команде:

который показан на Схеме 1, то увидим во время выполнения несколько интересных частей:

Здесь мы видим, что всё начинается с вызова 'execve()' (посмотрите man execve; и для любого системного вызова — первое слово в строке напечатанной strace), который загружает исполняемый файл. Чуть позже приложение проверяет, существует ли файл конфигурации '/etc/wgetrc/'. Он существует и он читается. Далее мы видим, что процесс пытается открыть '.wgetrc' в моей домашней директории, но этот файл не существует, поэтому и не открывается. Следующий пример (Схема 2) показывает, что '/etc/resolv.conf' в данный момент открыт, а также открыт сокет для DNS сервера, для того чтобы определить адрес по моему запросу:

Разве это не прекрасно? Мы изучили внутреннее устройство приложения, не взглянув ни на одну строчку кода; тут же мы узнали где оно хранит свои файлы конфигурации, один из которых не существует, и как оно перевело запись DNS в ip-адрес. ltrace работает подобным образом, но, вместо трассировки системных вызовов, показывает, какие функции вызывались и какие из них находятся в динамически связанных библиотеках (Схема 3):

ldd говорит нам, что wget использует среди прочих libssl (безопасные соединения), libpthread (для создания потоков), libz (сжатие), и libc. Libc по существу является основой вашей системы. Она реализует основные функции С, такие как printf(), malloc(), и free(), часто связывая их с системными вызовами (например, printf() с write()). Теперь ltrace расскажет нам, где наше приложение использует функции, предоставляемые библиотеками. Итак, если мы рассмотрим вывод:

Valgrind

Valgrind можно установить, набрав:

Это набор утилит, которые выполняют продвинутую проверку приложений. Для дополнительной информации о доступных утилитах зайдите на сайт http://www.valgrind.org. В этой статье я рассмотрю только самую используемую утилиту под названием 'memcheck'. Эта утилита переопределяет вызовы libc, которые занимаются обработкой памяти. Получается система учёта использования ресурсов — вся ли память (выделенная динамически) возвращена обратно в систему, и вся ли выделенная память по-прежнему доступна?

Листинг 1:

Посмотрите на листинг 1. Это пример плохого кода. Происходит вызов функции leak() (строки 3-7) 10 раз, которая выделяет 10 байтов и не освобождает их. Затем выделяется некоторое количество памяти в функции main, и выполнение переходит в бесконечный цикл. Во-первых, я хочу, чтобы перед запуском кода вы заменили цикл for на while(1), и malloc(10) на malloc(1000). Запустив приложение, вы увидите что произойдёт с вашей системой. Ваша физическая память заполнится, затем будет заполнен своп, и, в конечном счёте, oom_killer (служба завершения процессов, пожирающих всю память) закроет раздобревший процесс. Такие вещи являются разрушительными для системы и для её производительности. Вы только что наблюдали эффект утечки памяти. Проблемная особенность динамического запроса памяти — память всегда нужно возвращать обратно! Это пример утечки памяти «в ускоренном воспроизведении». Некоторые приложения, которые теряют несколько байт в час, могут идеально работать годами — прежде чем всё упадет к чёртовой бабушке. Вот почему valgrind очень полезен. Вот вывод Листинга 1 на моей системе после компиляции:

Вывод на Схеме 4:

Когда я прерываю цикл while(1) нажав ctrl+c, он мне сообщает сколько вызовов malloc() я сделал, сколько памяти я получил, и сколько вернул обратно. В итоге делается вывод, что я потерял 100 байт памяти в 10 блоках. Это значит, что я запрашивал память, которая теперь мне недоступна, потому что у меня нет на неё указателя (в выводе: «definitely lost”), а также, что я получил 15 байт в одном блоке, который, на момент завершения, всё ещё могу освободить, потому что у меня есть на него указатель. Вот почему я написал цикл while(1). Если бы я этого не сделал, valgrind сообщил бы, что я потерял 115 байт в 11 блоках (проверьте это!), потому что valgrind ведёт учёт того, что в действительности произошло; он не смотрит в будущее для того, чтобы узнать, что может произойти в системе. Ещё одна вещь, о которой стоит упомянуть: я говорил, что cкомпилировал код с ключом »-g«, который добавляет отладочную информацию в исполняемый файл. Вот откуда valgrind знает, в каком файле и на какой строке произошла ошибка. Если скомпилировать следующим образом:

то вывод будет выглядеть так:

Он по-прежнему говорит нам, что происходит утечка памяти, но уже не сообщает, в каком файле и в какой строке что-то идёт не так. Итак, хорошая новость — valgrind сообщает нам, есть утечки памяти или нет. Плохая новость — нам нужен исполняемый файл с отладочной информацией, если мы хотим локализовать утечку. Мы можем перекомпилировать исполняемый файл для поиска и устранения неисправностей — для этого нам нужен исходный код!

Выводы

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

Memcheck как устранить в си

Профиль
Группа: Участник
Сообщений: 4
Регистрация: 21.4.2006

Репутация: нет
Всего: нет

Подскажите как использовать модуль Memcheck для фиксирования утечек памяти.

Это сообщение отредактировал(а) psergio — 13.12.2006, 15:27

Профиль
Группа: Модератор
Сообщений: 9820
Регистрация: 18.5.2006
Где: Днепропетровск

Репутация: 4
Всего: 260

бросаешь его первым в список используемых модулей. вроде, все. там в архиве readme лежит — что, не нашел?

Добавлено @ 16:53

Цитата(psergio @ 13.12.2006, 14:26 )
фиксирования утечек памяти

Профиль
Группа: Админ
Сообщений: 11743
Регистрация: 12.10.2005
Где: Зеленоград

Репутация: 109
Всего: 459

Обсуждение действий администрации форума производятся только в этом форуме

гениальность идеи состоит в том, что ее невозможно придумать

Профиль
Группа: Участник Клуба
Сообщений: 1458
Регистрация: 5.3.2005
Где: Riga, Latvia

Репутация: 23
Всего: 51

Цитата(psergio @ 13.12.2006, 15:26 )
Подскажите как использовать модуль Memcheck для фиксирования утечек памяти.

В исходном файле проекта(Project->View Source):
* в секции Uses первым модулем прописываешь Memcheck.
* после begin, но перед Application.Initialize; вписываешь MemChk;

В настройках проекта(Project->Options):
* на закладке Linker ставишь галочку Include TD32 Debug Info
* на закладке Compiler в группе Code Generation ставишь галочку Stack Frames.

После этого делаешь Build Project. И запускаешь программу. После завершения работы программы в папке с exe появится файл со списком утечек. А также, Delphi начнёт генерировать Exceptions на тех строках, где "скорее всего" и произошла сама утечка.

п.с. включив галочку Include TD32 Debug Info размер exe файла увеличится раз в 5(!), так что при создании финального билда Memcheck лучше отключить.

Профиль
Группа: Участник
Сообщений: 170
Регистрация: 17.3.2007
Где: Сириус, созвездие Большого Пса

Репутация: нет
Всего: нет

Профиль
Группа: Админ
Сообщений: 11743
Регистрация: 12.10.2005
Где: Зеленоград

Репутация: 109
Всего: 459

Цитата
Q8: Is there a problem with ShareMem ?

A: Yes. As far as I know, if your project uses ShareMem and you launch MemCheck, the program will fail with an invalid pointer operation error on termination. I don't know where this problem comes from. The error seems to occur when the FreeMem of the memory manager of ShareMem is called on program termination. However I don't understand what happens since the ShareMem unit does not have a finalization section. Any hint will be very welcome ! I think this prevents the use of MemCheck when writing a DLL.

Here is what David Fielder answers for this question:
I am using Memcheck alongside ShareMem just fine. I just make sure ShareMem is first, and Memcheck is second in the uses clause. ShareMem calls statically loads DelphiMM.dll, and frees the dll when the program terminates. So the error you get will be caused by DelphiMM.dll attempting to tidy up before being freed.

Обсуждение действий администрации форума производятся только в этом форуме

гениальность идеи состоит в том, что ее невозможно придумать

Профиль
Группа: Участник
Сообщений: 170
Регистрация: 17.3.2007
Где: Сириус, созвездие Большого Пса

Репутация: нет
Всего: нет

Сделал всё так как писа Bose.
При этом использую FastShareMem, но он описывается и используется в dll.
При завершении программа выдаёт ошибку "Runtime error 204 at 00405729", "Runtime error 216 at 004047CE" и зацикливается на ошибке "Runtime error 216 at 00407642".

TOP 10 Leaks: begin
Leak #0 User allocated memory (GetMem)
Size: 20
1 Occurence
call stack — 0 : Routine @[email protected]@NewAnsiString Find error: 004057DD
call stack — 1 : Routine @[email protected]@DoCreate Find error: 0046D01F
call stack — 2 : Routine @[email protected]@AfterConstruction Find error: 0046CCDB
call stack — 3 : Routine @[email protected]@Create Find error: 0046CCB1
call stack — 4 : Routine @[email protected]@CreateForm Find error: 0047587C
call stack — 5 : (no debug info) Find error: 0053ACA2
call stack — 6 : (no debug info) Find error: 7C816D4B
call stack — 7 : (no debug info) Find error: FFFFFFFC

Leak #1 User allocated memory (GetMem)
Size: 18
1 Occurence
call stack — 0 : Routine @[email protected]@NewAnsiString Find error: 004057DD
call stack — 1 : Routine @[email protected]@DoCreate Find error: 0046D01F
call stack — 2 : Routine @[email protected]@AfterConstruction Find error: 0046CCDB
call stack — 3 : Routine @[email protected]@Create Find error: 0046CCB1
call stack — 4 : Routine @[email protected]@CreateForm Find error: 0047587C
call stack — 5 : (no debug info) Find error: 0053ACA2
call stack — 6 : (no debug info) Find error: 7C816D4B
call stack — 7 : (no debug info) Find error: FFFFFFFC

Leak #2 User allocated memory (GetMem)
Size: 16
1 Occurence
call stack — 0 : (no debug info) Find error: 0000083C

TOP 10 Leaks: end

Total leak: 54 bytes

*** MEMCHK: Blocks STILL allocated ***

Leak #0 User allocated memory (GetMem)
Size: 20
1 Occurence
call stack — 0 : Routine @[email protected]@NewAnsiString Find error: 004057DD
call stack — 1 : Routine @[email protected]@DoCreate Find error: 0046D01F
call stack — 2 : Routine @[email protected]@AfterConstruction Find error: 0046CCDB
call stack — 3 : Routine @[email protected]@Create Find error: 0046CCB1
call stack — 4 : Routine @[email protected]@CreateForm Find error: 0047587C
call stack — 5 : (no debug info) Find error: 0053ACA2
call stack — 6 : (no debug info) Find error: 7C816D4B
call stack — 7 : (no debug info) Find error: FFFFFFFC

Leak #1 User allocated memory (GetMem)
Size: 18
1 Occurence
call stack — 0 : Routine @[email protected]@NewAnsiString Find error: 004057DD
call stack — 1 : Routine @[email protected]@DoCreate Find error: 0046D01F
call stack — 2 : Routine @[email protected]@AfterConstruction Find error: 0046CCDB
call stack — 3 : Routine @[email protected]@Create Find error: 0046CCB1
call stack — 4 : Routine @[email protected]@CreateForm Find error: 0047587C
call stack — 5 : (no debug info) Find error: 0053ACA2
call stack — 6 : (no debug info) Find error: 7C816D4B
call stack — 7 : (no debug info) Find error: FFFFFFFC

Leak #2 User allocated memory (GetMem)
Size: 16
1 Occurence
call stack — 0 : (no debug info) Find error: 0000083C

*** MEMCHK: End of allocated blocks ***

*** MEMCHK: Chronological leak information ***

* User allocated memory (GetMem) (Leak #2) Size: 16
* User allocated memory (GetMem) (Leak #1) Size: 18
* User allocated memory (GetMem) (Leak #0) Size: 20

*** MEMCHK: End of chronological leak information ***

*** MEMCHK: Blocks written to after destruction ***

Bad blocks count: 0

Профиль
Группа: Участник Клуба
Сообщений: 1458
Регистрация: 5.3.2005
Где: Riga, Latvia

Репутация: 23
Всего: 51

Цитата(ilya198293 @ 24.6.2007, 13:00 )
При завершении программа выдаёт ошибку "Runtime error 204 at 00405729"

В конце работы MemCheck специально генерирует Exceptionы, в тех местах где выделяется память которая потом не освобождается .

Кстати memcheck несовместим с FastMM. По крайней мере, мне не удалось заставить их нормально работать в паре.

Профиль
Группа: Админ
Сообщений: 11743
Регистрация: 12.10.2005
Где: Зеленоград

Репутация: 109
Всего: 459

Цитата(Bose @ 25.6.2007, 13:37 )
Кстати memcheck несовместим с FastMM. По крайней мере, мне не удалось заставить их нормально работать в паре.

Обсуждение действий администрации форума производятся только в этом форуме

гениальность идеи состоит в том, что ее невозможно придумать

Профиль
Группа: Участник Клуба
Сообщений: 1458
Регистрация: 5.3.2005
Где: Riga, Latvia

Репутация: 23
Всего: 51

Цитата(Bose @ 25.6.2007, 13:37 )
Кстати memcheck несовместим с FastMM. По крайней мере, мне не удалось заставить их нормально работать в паре.
Цитата(Alexeis @ 25.6.2007, 13:43 )
Получается что в Delphi 2007 уже нельзя его юзать.

Я недолго изучал эту проблему.

В принципе, FastMM умеет отслеживать утечки памяти, и лог пишёт намного более подробный(настолько подробный, что я с ним так и не разобрался толком). Единственное, что более приятно при использовании memcheck — это генерация exception-ов прямо в тех местах кода, где была выделена неосвобождённая память.

Профиль
Группа: Модератор
Сообщений: 11363
Регистрация: 13.10.2004
Где: Питер

Репутация: 192
Всего: 484

Профиль
Группа: Участник
Сообщений: 170
Регистрация: 17.3.2007
Где: Сириус, созвездие Большого Пса

Репутация: нет
Всего: нет

Цитата
В исходном файле проекта(Project->View Source):
* в секции Uses первым модулем прописываешь Memcheck.
* после begin, но перед Application.Initialize; вписываешь MemChk;

В настройках проекта(Project->Options):
* на закладке Linker ставишь галочку Include TD32 Debug Info
* на закладке Compiler в группе Code Generation ставишь галочку Stack Frames.

После этого делаешь Build Project. И запускаешь программу. После завершения работы программы в папке с exe появится файл со списком утечек. А также, Delphi начнёт генерировать Exceptions на тех строках, где "скорее всего" и произошла сама утечка.

TOP 10 Leaks: begin
Leak #0 User allocated memory (GetMem)
Size: 16
1 Occurence
call stack — 0 : (no debug info) Find error: 0000FFFF

TOP 10 Leaks: end

Total leak: 16 bytes

*** MEMCHK: Blocks STILL allocated ***

Leak #0 User allocated memory (GetMem)
Size: 16
1 Occurence
call stack — 0 : (no debug info) Find error: 0000FFFF

*** MEMCHK: End of allocated blocks ***

*** MEMCHK: Chronological leak information ***

* User allocated memory (GetMem) (Leak #0) Size: 16

*** MEMCHK: End of chronological leak information ***

*** MEMCHK: Blocks written to after destruction ***

Bad blocks count: 0

*** MEMCHK: End of blocks written to after destruction ***
==========================================================
и начали сыпаться ошибки с текстом Runtime error 217 at 0040DDB6.
Что это вообще значит и может есть где небольшой мануальчик по работе с MemCheck?

Запрещается!

1. Публиковать ссылки на вскрытые компоненты

2. Обсуждать взлом компонентов и делиться вскрытыми компонентами

  • Литературу по Дельфи обсуждаем здесь
  • Действия модераторов можно обсудить здесь
  • С просьбами о написании курсовой, реферата и т.п. обращаться сюда
  • Вопросы по реализации алгоритмов рассматриваются здесь
  • 90% ответов на свои вопросы можно найти в DRKB (Delphi Russian Knowledge Base) — крупнейшем в рунете сборнике материалов по Дельфи

Если Вам понравилась атмосфера форума, заходите к нам чаще! С уважением, Snowy, MetalFan, bems, Poseidon, Rrader.

0 Пользователей читают эту тему (0 Гостей и 0 Скрытых Пользователей)
0 Пользователей:
« Предыдущая тема | Delphi: Общие вопросы | Следующая тема »

[ Время генерации скрипта: 0.1545 ] [ Использовано запросов: 21 ] [ GZIP включён ]

Ловим утечки памяти в С/С++

Сегодня я хочу немного приоткрыть свет над тем, как бороться с утечкой памяти в Си или С++.

На Хабре уже существует две статьи, а именно: Боремся с утечками памяти (C++ CRT) и Утечки памяти в С++: Visual Leak Detector. Однако я считаю, что они недостаточно раскрыты, или данные способы могут не дать нужного вам результата, поэтому я хотел бы по возможности разобрать всем доступные способы, дабы облегчить вам жизнь.

Windows — разработка
Начнем с Windows, а именно разработка под Visual Studio, так как большинство начинающих программистов пишут именно под этой IDE.

Для понимания, что происходит, прикладываю реальный пример:

А также есть Student.h и Student.c в котором объявлены структуры и функции.

Есть задача: продемонстрировать отсутствие утечек памяти. Первое, что приходит в голову — это CRT. Тут все достаточно просто.

В начало файла, где находится main, необходимо добавить этот кусок кода:

А перед return 0 нужно прописать это: _CrtDumpMemoryLeaks(); .

В итоге, в режиме Debug, студия будет выводить это:

Супер! Теперь вы знаете, что у вас утечка памяти. Теперь нужно устранить это, поэтому необходимо просто узнать, где мы забываем очистить память. И вот тут возникает проблема: а где, собственно, выделялась эта память?

После того, как я повторил все шаги, я выяснил, что память теряется где-то здесь:

Но как так — то? Я же все освобождаю? Или нет?

И тут мне сильно не хватало Valgrind, с его трассировкой вызовов.

В итоге, после 15 минут прогугливания, я нашел аналог Valgrind — Visual Leak Detector. Это сторонняя библиотека, обертка над CRT, которая обещала показывать трассировку! Это то, что мне необходимо.

Чтобы её установить, необходимо перейти в репозиторий и в assets найти vld-2.5.1-setup.exe

Правда, последнее обновление было со времен Visual Studio 2015, но оно работает и с Visual Studio 2019. Установка стандартная, просто следуйте инструкциям.

Чтобы подключить VLD, необходимо прописать #include <vld.h> .

Преимущество этой утилиты заключается в том, что можно не запускать в режиме debug (F5), ибо все выводится в консоль. В самом начале будет выводиться это:

И вот, что будет выдавать при утечке памяти:

Вот, я вижу трассировку! Так, а где строки кода? А где названия функций?

Ладно, обещание сдержали, однако это не тот результат, который я хотел.

Остается один вариант, который я нашел в гугле: моментальный снимок памяти. Он делается просто: в режиме debug, когда доходите до return 0, необходимо в средстве диагностики перейти во вкладку «Использование памяти» и нажать на «Сделать снимок». Возможно, у вас будет отключена эта функция, как на первом скриншоте. Тогда необходимо включить, и перезапустить дебаг.

После того, как вы сделали снимок, у вас появится под кучей размер. Я думаю, это сколько всего было выделено памяти в ходе работы программы. Нажимаем на этот размер. У нас появится окошко, в котором будут содержаться объекты, которые хранятся в этой куче. Чтобы посмотреть подробную информацию, необходимо выбрать объект и нажать на кнопку «Экземпляры представления объекта Foo».

Да! Это победа! Полная трассировка с местоположением вызовов! Это то, что было необходимо изначально.

Linux — разработка
Теперь, посмотрим, что творится в Linux.

В Linux существует утилита valgrind. Чтобы установить valgrind, необходимо в консоли прописать sudo apt install valgrind (Для Debian-семейства).

Я написал небольшую программу, которая заполняет динамический массив, но при этом, не очищается память:

Скомпилировав программу с помощью CLang, мы получаем .out файл, который мы подкидываем valgrind’у.

С помощью команды valgrind ./a.out . Как работает valgrind, думаю, есть смысл описать в отдельной статье, а сейчас, как выполнится программа, valgrind выведет это:

Таким образом, valgrind пока показывает, сколько памяти было потеряно. Чтобы увидеть, где была выделена память, необходимо прописать —leak-check=full , и тогда, valgrind, помимо выше описанного, выведет это:

Конечно, тут не указана строка, однако уже указана функция, что не может не радовать.

Есть альтернативы valgrind’у, такие как strace или Dr.Memory, но я ими не пользовался, да и они применяется в основном там, где valgrind бессилен.

Выводы

Я рад, что мне довелось столкнуться с проблемой поиска утечки памяти в Visual Studio, так как я узнал много новых инструментов, когда и как ими пользоваться и начал разбирать, как работают эти инструменты.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *