Отладка приложений

         

Использование MemDumperValidator


Расширение MemDumperValidator из DCRT-библиотеки значительно облегчает отладку памяти. По умолчанию DCRT-библиотека выдает сообщения об утечках памяти и проверках корректности тех блоков памяти, в которых оба типа записей (с начала — underwrites или с конца блока — overwrites) не подвергались разрушению. Оба отчета могут быть очень полезны, но если отчет об утечках памяти выглядит так, как показано ниже, то довольно трудно точно определить, в памяти какого типа произошла утечка:

Detected memory leaks

Dumping objects ->

с:\vc\INCLUDE\crtdbg.h(552) : (596} normal block at Ox008CD5BO,

24 bytes long.

Data: < k w k > 90 6B 8C 00 BO DD 8C 00 00 00 80 77 90 6В 8С 00

Object dump complete.

Как говорилось ранее, средств проверки корректности памяти, заданных по умолчанию, может оказаться недостаточно. Для отлавливания некоторых специальных видов записей в память необходима более глубокая проверка корректности (иначе вы вообще не сможете перехватывать такие записи). Дополнительные проверки корректности и информация отчетов об утечках памяти, обеспечиваемые расширением MemDumperValidator, особенно полезны во время отладки. Чем больше информации получено во время отладки, тем меньше времени она занимает.

Расширение MemDumperValidator использует в своей работе идентификаторы блоков памяти DCRT-библиотеки, что позволяет ему ассоциировать тип блока со специфическим набором функций работы с памятью, которым что-то известно о содержимом соответствующего блока. Каждому блоку памяти, распределенному через библиотеку DCRT, назначается специальный идентификатор, как показано в табл. 15.3. Типы блоков являются параметрами следующих функций распределения памяти библиотеки DCRT: _nh_maiioc_ dbg (new), _malloc_dbg (malloc), _calloc_dbg (calloc) И _realloc_dbg (realloc).

Таблица 15.3. Идентификаторы блоков памяти

Идентификатор блока Описание

_NORMAL_BLOCK

Вызов обычной функции распределения (new, malloc или calloc) создает нормальные блоки. Определение

#define _CRTDBG_MAP_ALLOC

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

_CRT_BLOCK

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

CLIENT BLOCK

Если нужно, чтобы приложение выполняло специальную трассировку блоков распределенной памяти, то можно вызывать особые отладочные функции распределения, передавая им в качестве параметра специальное значение CLIENT BLOCK VALUE (см. ниже вызов heap alloc dbg после директивы #define). Можно прослеживать подтипы клиентских блоков, помещая 1 6-разрядное значение в 16 верхних разрядов значения блока, как показано ниже:

#define CLIENT_BLOCK_VALUE(x) \

 (_CLIENT_BLOCK | (x«16) )

 heap alloc dbg ( 10, 

 CLIENT BLOCK VALUE ( OxA) , 

_ FILE _ ,

 _ LINE _ ) ;

Для дампов блоков памяти этого типа (т. е. памяти, зарегистрированной в форме клиентских блоков) приложение может обеспечить функцию-обработчик (через функцию CrtSetDumpClient). Функция-обработчик будет вызываться всякий раз, когда функции DCRT-библиотеки потребуется выполнить дамп клиент-


(прод.)

ского блока. Кроме того, имеется специальная функция (__CrtDoForAlldientObjects), которая позволяет получить список всех клиентских блоков, распределенных к моменту ее вызова. MFC использует данный идентификатор для всех классов производных от класса cobject. Расширение MemDumperValidator тоже использует описанный механизм

FREE BLOCK

Вызов подпрограммы освобождения памяти обычно удаляет память из списков отладочной кучи. Однако если при обращении к функции CrtSetDbgFlag вы устанавливаете флажок CRTDBG DELAY FREE MEM DF, то память не освобождается, а выравнивается влево и заполняется символами OxDD

IGNORE BLOCK

Если вы временно отключаете трассировку DCRT-библиотеки, то любые распределения, выполненные после этого, будут помечаться как блоки Ignore

Расширение MemDumperValidator работает следующим образом: после установки подходящего класса (в языке C++) или типов данных (в языке С), DCR-библиотека будет вызывать MemDumperValidator всякий раз, когда нужно выполнить дамп блока памяти. Расширение просматривает значения данных в блоке и если находит подходящую дамп-функцию, то вызывает ее для выполнения дампа памяти. Аналогичным образом работает и та часть расширения, которая проверяет корректность данных в блоках памяти. Процесс проверки корректности отличается от процесса выдачи дампа только в том, что DCRT-библиотека вызывает не функции дампа, а функции проверки корректности данных соответствующего блока памяти.

Описать MemDumperValidator не так уж сложно, но вот заставить его работать — немного сложнее. В листинге 15.1 показан заголовочный файл MEMDUMPERVALIDATOR.H, который выполняет основную часть работ по инициализации. Включая в программу файла BUGSLAYERUTIL.H, вы автоматически включаете и MEMDUMPERVALIDATOR.H.

Листинг 15-1, JWEMDUMPERVAL1DATOR.H 

/*- - - - - - - - - - - - - - - - - - - - - - - - - - - -

"Debugging Applications" (Microsoft Press)

Copyright (с) 1997-2000 John Robbins — All rights reserved. 



- - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

 #ifndef _MEMDUMPERVALIDATOR_H

#define _MEMDUMPERVALIDATOR_H

// He включайте этот файл напрямую; вместо него включайте BUGSLAYER.H

#ifndef _BUGSLAYERUTIL_H

#error "Include BUGSLAYERUTIL.H instead of this file directly!"

#endif // _BUGSLAYERUTIL_H

// Включить заголовочный файл CRTDBG.H.

#include "MSJDBG.h"

#ifdef _cplusplus

extern "C" {

#endif // _ _cplusplus

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

#ifdef _DEBUG

/////////////////////////////////////////////////////////////

// Директивы typedef для функций вьдачи дампов и проверки корректности

////////////////////////////////////////////////////////////////

// Функция выдачи дампов памяти. Единственный параметр этой функции -

// указатель на блок памяти. Эта функция выводит данные блока памяти

// одним из нескольких доступных ей способов, но, для того чтобы быть

// состоятельной, она использует механизм формирования отчетов,

// которым пользуется остальная часть DCRT-библиотеки.

typedef void (*PFNMEMDUMPER)(const void *);

// Функция проверки корректности (validating function).

//Ее первый параметр — блок памяти,

// корректность которого нужно проверить, а второй — контекстная

// информация, пересылаемая в функцию ValidateAllBlocks function.

typedef void (*PFNMEMVALIDATOR)(const void *, const void *);

////////////////////////////////////////////////////////////////

// Полезный макрос

////////////////////////////////////////////////////////////////

// Макрос, используемый для установки значения подтипа Client-блока.

// Использование этого макроса — единственное санкционированное средство

// установки значения поля dwValue в структуре DVINFO (см. ниже).

tdefine CLIENT_BLOCK_VALUE(x) (_CLIENT_BLOCK|(x«16))

// Макрос для выбора подтипа

Idefine CLIENT_BLOCK_SUBTYPE(х) ((х » 16) & 0xFFFF)

/////////////////////////////////////////////////////////////



// Заголовок, используемый для инициализации функций дампа и проверки

// корректности Client-блока специфического подтипа

////////////////////////////////////////////////////////////

typedef struct tag_DVINFO

{

// Значение подтипа Client-блоков. Это значение должно быть 

// установлено с помощью определенного выше макроса. 

// CLIENT_BLOCK_VALUE. Чтобы выяснить, как расширение назначает 

// это число, см. функцию AddClientDV.

unsigned long dwValue ; 

// Указатель на функцию дампа 

PFNMEMDUMPER pfnDump

// Указатель на функцию проверки корректности 

PFNMEMVALIDATOR pfnValidate; 

} DVINFO, * LPDVINFO;

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

ФУНКЦИЯ : AddClientDV 

ОБСУЖДЕНИЕ :

Добавляет в список функции дампа и проверки корректности

 Client-блока. Если поле dwValue в структуре DVINFO равно О, 

то назначается следующее значение из списка. Возвращаемое значение

 должно всегда пересылаться в функцию _malloc_dbg в качестве

 значения Client-блока.

Если значение подтипа устанавливается с помощью макроса

 CLIENT_BLOCK__VALUE, то его можно использовать в качестве значения,

 передаваемого в функцию _malloc_dbg.

Заметим, что соответствующей функции удаления не существует.

 Почему возникает риск введения ошибок в отладочный код? Проблема

 производительности отходит на задний план, когда речь заходит

 о поиске ошибок. 

ПАРАМЕТРЫ :

IpDVInfo — Указатель на структуру DVINFO

 ВОЗВРАЩАЕТ :

1 — функции дампа и проверки корректности клиентского блока были

успешно добавлены. 

0 — функции дампа и проверки корректности клиентского блока не могут

быть добавлены.

- - - - - - - - - - - - - - - - - - - -  - - - - - - - - */

int BUGSUTIL_DLLINTERFACE _stdcall AddClientDV (LPDVINFO IpDVInfo);

/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - -

ФУНКЦИЯ : ValidateAllBlocks

 ОБСУЖДЕНИЕ :

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


Кроме

 того, просматривает все Client-блоки и вызывает специальные функции

  проверки корректности для различных подтипов этих блоков.

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

ПАРАМЕТРЫ :

pContext — Контекстная информация, которая будет передаваться

в каждую функцию проверки корректности. 

ВОЗВРАЩАЕТ :

Ничего. 

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/

void BUGSOTIL_DLLINTERFACE _stdcall

ValidateAllBlocks ( void * pContext);

 #ifdef _cplusplus

//////////////////////////////////////////////////////////////

// Макросы вспомогательных классов C++

////////////////////////////////////////////////////////////// 

// Объявите этот макрос в своем классе как обычный MFC-макрос,

 #define DECLAREJYEMDEBUG(classname)

 public :

static DVINFO m_stDVInfo;

static void ClassDumper ( const void * pData);

static void ClassValidator ( const void * pData,

const void * pContext);

static void * operator new ( size_t nSize)

{

if ( 0 == m_stDVInfo.dwValue)

{

m_stDVTnfо.pfnDump = classname::ClassDumper;

 m_stDVInfo.pfnValidate = classname::ClassValidator; 

AddClientDV ( &m_stDVInfo); 

}

return ( _malloc_dbg ( nSize 

(int)m_stDVlnfо.dwValue, 

_FILE_ ,

 _LINE_ ) ) ;

}

static void * operator new ( size_t nSize ,

char * IpszFileName,

 int nLine )

{

if ( 0 = m_stDVInfo.dwValue)

{

m_stDVInfo.pfnDump = classname::ClassDumper; 

m_stDVInfo.pfnValidate = classname::ClassValidator; 

AddClientDV ( &m_stDVInfo);

 }

return ( _malloc_dbg ( nSize 

(int)m_stDVInfо.dwValue,

 IpszFileName ,

 nLine )) ; 

}

static void operator delete ( void * pData)

 {

_free_dbg ( pData, (int)m_stDVInfo.dwValue);

}

// Объявите этот макрос в начале своего СРР-файла.

 #define IMPLEMENT_MEMDEBUG(classname)

DVINFO classname::m_stDVInfо = { 0, 0, 0 }

// Макрос для отладочных распределений памяти.


Если определено

// символическое имя DEBUG_NEW, то этот макрос использовать нельзя.

#ifdef DEBUG_NEW

tdefine MEMDEBUG_NEW DEBUG_NEW

#else

#define MEMDEBUG_NEW new ( _FILE_, _LINE_)

#endif

#endif // идентификатор _cplusplus определен

//////////////////////////////////////////////////////////////////

// Вспомогательные С-макросы

///////////////////////////////////////////////////////////////// 

// Используйте этот макрос для распределения памяти в С-стиле.

 // Единственной проблемой при этом является необходимость работы со

 // структурой DVINFO.

Idefine INITIALIZE_MEMDEBUG(IpDVInfo, pfnD, pfnV) 

{

ASSERT ( FALSE == IsBadWritePtr ( IpDVInfo,

sizeof ( DVINFO))); 

((LPDVINFO)IpDVInfo)->dwValue = 0; 

((LPDVINFO)IpDVInfo)->pfnDump = pfnD; 

((LPDVINFO)IpDVInfo)->pfnValidate = pfnV;

 AddClientDV ( IpDVInfo); 

}

// Макросы, которые преобразуют функции распределения памяти С-формата

 //в более удобную для применения форму. Он избавляет вас от 

// запоминания и кодирования различных значений блока DVINFO,

 // используемых в функциях работы с памятью.

#define MEMDEBUG_MALLOC(IpDVInfo, nSize) \

_malloc_dbg ( nSize , \

((LPDVINFO)IpDVInfo)->dwValue, \

_FILE_ , \

_LINE_ )

#define MEMDEBUG_REALLOC(IpDVInfo, pBlock, nSize) \ 

__realloc_dbg ( pBlock , \ 

nSize , \ 

((LPDVINFO)IpDVInfo)->dwValue , \ 

_FILE_ , \

 _LINE_ )

#define MEMDEBUG_EXPAND(IpDVInfo, pBlock, nSize) \

  _expand_dbg( pBlock , \ 

nSize , \ 

' ((LPDVINFO)IpDVInfo)->dwValue , \

 _FILE_ , \ 

_LINE_ )

#define MEMDEBUG_FREE(lpDVInfo, pBlock) \ 

_free_dbg ( pBlock , \ 

((LPDVINFO)IpDVInfo)->dwValue)

#define MEMDEBUG_MSIZE(IpDVInfo, pBlock} \

_msize_dbg ( pBlock, ((LPDVINFO)IpDVInfo)->dwValue)

// Макрос для вызова функции ValidateAllBlocks

#define VALIDATEALLBLOCKS(x) ValidateAllBlocks ( x)

#else // _DEBUG не определен.

#ifdef _cplusplus

#define DECLARE_MEMDEBUG(classname) 

#define IMPLEMENT_MEMDEBUG(classname)

#define MEMDEBUG_NEW new

#endif // _cplusplus

#define INITIALIZE_MEMDEBUG(IpDVInfo, pfnD, pfnV)

#define MEMDEBUG_MALLOC(IpDVInfo, nSize) \

malloc ( nSize)

 #define MEMDEBUG_REALLOC(IpDVInfo, pBlock, nSize) \

realloc ( pBlock, nSize)

 #define MEMDEBUG_EXPAND(IpDVInfo, pBlock, nSize) \

_expand ( pBlock, nSize)

 #define MEMDEBUG_FREE(IpDVInfo, pBlock) \

free ( pBlock) ttdefine MEMDEBUG_MSIZE(IpDVInfo, pBlock) \

_msize ( pBlock)

 #define VALIDATEALLBLOCKS(x)

 #endif // _DEBUG ttifdef _cplusplus

 }

#endif // _cplusplus

 #endif // _MEMDUMPERVALIDATOR_H



Содержание раздела