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

       

Таблицы символов, символьные машины и проход стека


Реальное мастерство в написании отладчиков опирается на символьные машины (т. е. на программный код, который манипулирует таблицами символов). Отладка на традиционном уровне языка ассемблера интересна в течение первой пары минут работы, но быстро надоедает. Таблицы символов (symbol tables), называемые также символами отладки (debugging symbols) — это то, что превращает шестнадцатеричные числа в строки, имена функций и имена переменных исходных файлов. Таблицы символов содержат, кроме того, информацию о типах, которую использует ваша программа. Эта информация позволяет отладчику отображать на экране "сырые" данные как структуры и переменные, которые вы определили в своей программе. Иметь дело с современными таблицами символов трудно, потому что наиболее часто используемый их формат — PDB (Program DataBase — База данных программы), не документирован, и владельцы прав не планируют его документировать. К счастью, можно получить по крайней мере частичный доступ к таблицам символов.

Форматы символов отладки

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

SYM — самый старый формат, который применялся во времена MS-DOS и 16-разрядных Windows. В настоящее время SYM-формат употребляется только для отладочных символов в Windows 98. SYM-формат применяется лишь потому, что большая часть основного ядра операционной системы все еще остается 16-разрядным кодом. Единственный отладчик, активно использующий символы этого формата, — это WDEB386.

COFF (Common Object File Format — общий формат объектных файлов) :дин из первых форматов таблиц символов, который был представлен в Windows NT 3.1 (первой версии Windows NT). Команда разработчиков Windows NT имела опыт в разработке операционной системы и хотела загружать Windows NT с помощью некоторых существующих инструментов. Формат COFF является частью большой спецификации, которой пользовались различные поставщики UNIX-систем, пытавшиеся создать общие форматы двоичных файлов.
Хотя в WINNT.H находится полная спецификация COFF-символов, инструменты Microsoft генерируют только некоторые еe части — public-функции и глобальные переменные. В Microsoft привыкли поддерживать исходную и строчную информацию, но постепенно отошли от формата COFF в пользу более современных форматов символьных таблиц.
Формат С7 или CodeView появился еще во времена MS-DOS как часть системы программирования Microsoft C/C++ версии 7. Возможно, вы слышали название "CodeView". Это имя старого отладчика Microsoft. Формат С7 был модифицирован, чтобы поддерживать операционные системы Win32, и тетерь можно генерировать этот формат, запуская компилятор CL.EXE из командной строки с ключом /Z7 или выбирая пункт С7 Compatible1 из раскрывающегося списка Debug Info на вкладке C/C++ диалогового окна Project Settings.
Здесь и далее речь идет об интерфейсе с IDE Microsoft Visul C++. — Пер
Чтобы включить параметр компоновщика /PDB:NONE, на вкладке  Link диалогового окна Project Settings выключите в категории Customize  флажок Use Program Database. Отладчики WinDBG и Visual C++ поддерживают как полную отладку на уровне исходного кода, так и построчную отладку в формате С7. Компоновщик добавляет символьную информацию к двоичному файлу уже после того, как он выполнит компоновку, потому отладочные символы формата С7 содержатся внутри выполняемого модуля. Добавление отладочных символов более чем вдвое увеличивает объем двоичного файла. Для того чтобы узнать, содержит ли двоичный файл отладочную информацию формата С7, нужно открыть его в шестнадцатеричном редакторе и, переместившись к его концу, просмотреть содержимое последних строк. Если будет обнаружена строка с последовательностью символов "NB11", значит файл содержит отладочную информацию формата С7.
Спецификацию С7 можно найти на MSDN в разделе "VC5.0 Symbolic Debug Information". Спецификация перечисляет только необработанную байтовую структуру и определения типов. Тем, кто захочет увидеть фактические определения типов на С, рекомендуем просмотреть исходный код программы Dr.


Watson на компакт-дисках MSDN. В каталоге \ Include этой программы имеется несколько файлов заголовков старого формата С7. Хотя эти файлы значительно устарели, они могут дать некоторое представление о том, на что похожи эти структуры.
При желании, конечно, можно использовать формат С7, но лучше этого не делать. Отказаться от использования формата С7 нужно по двум причинам. Во-первых, он автоматически выключает инкрементную компоновку, из-за чего катастрофически увеличивается время компоновки. Во-вторых, значительно возрастает размер двоичных файлов. Можно убрать символьную информацию с помощью программы REBASE.EXE, но существуют такие форматы (например, PDB), которые удаляют ее автоматически.
PDB — наиболее общий из используемых сегодня символьных форматов, поддерживает как Visual C++, так и Visual Basic. В отличие от формата С7, PDB-символы сохраняются в отдельном файле или файлах, в зависимости от того, как приложение скомпоновано. По умолчанию, загрузочный файл Visual C++ 6 компонуется с ключом /PDBTYPE:SEPT, который помещает информацию о типах в файл VC60.PDB, а сами символы — в файл <имя-двоичного-файла>.РОВ. Отделение информации о типах от символов отладки ускоряет компоновку и требует меньше места на диске. Однако в документации указано, что если вы строите двоичный файл, который могли бы j отлаживать другие, то, чтобы вся информация о типах и символы отладки были сведены в единый PDB-файл, нужно указать ключ /PDBTYPE:CON. К счастью, Visual Basic автоматически использует этот ключ.
Чтобы посмотреть, содержит ли двоичный PDB-файл символьную информацию, откройте его в шестнадцатеричном редакторе и перейдите к концу файла. Вы увидите маркер отладочной информации. Если маркер начинает>:я с символов "NB10" и заканчивается полным путем к PDB-файлу, построенному во время компоновки, то двоичный файл включает PDB-символы. Отладочный формат PDB внутренне напоминает формат С7. Однако компания Microsoft оптимизировала этот формат для инкрементной компоновки.


К сожалению, интерфейсы низкого уровня с PDB-файлами являются собственностью фирмы Microsoft и не опубликованы.
DBG — файлы этого формата уникальны в том смысле, что, в отличие от файлов других символьных форматов, их создает не компоновщик. В них хранятся отладочные символы форматов COFF или С7. В DBG-файлах применяются структуры, определенные файловым форматом РЕ (Portable Executable), который использует все выполняемые файлы в операционных системах Win32. DBG-файлы создаются с помощью утилиты REBASE.EXE, которая извлекает из программного модуля отладочную информацию форматов COFF или С7 и помещает ее в DBG-файл. Нет никакой необходимости выполнять REBASE.EXE для модуля, который был построен с использованием PDB-файлов, потому что символы уже отделены от модуля. Желающим изучить методику создания DBG-файлов следует прочитать MSDN-доку-ментацию по REBASE.EXE. Microsoft распространяет отладочные символы операционной системы в DBG-файлах, а в Windows 2000 также включены и PDB-файлы. Не надейтесь, что отладочные символы операционной системы включают все, что нужно, скажем, для ее обратного проектирования. Учтите, что DBG-файлы содержат только общую и глобальную информацию. Однако их использование может значительно облегчить вашу работу с информацией окна Disassembly.
Если вы заинтересовались символьными машинами и начинаете исследовать возможности их программирования, то рано или поздно встретите символы еще одного типа — ОМАР. Символы этого типа появляются только в некоторых приложениях Microsoft. Их можно иногда встретить при получении дампа символьной информации с помощью утилиты DUMPBIN.EXE, запускаемой с параметром /SYMBOLS. (DUMPBIN.EXE распространяется вместе с системой программирования Visual C++.) Символьный формат ОМАР совершенно не документирован. Как уже говорилось, Microsoft использует специальный внутренний инструмент, который реорганизует компилированный двоичный файл так, чтобы поместить наиболее часто вызываемый код в начало файла. ОМАР-символы имеют какое-то отношение к тем отладочным символам, которые принимают во внимание этот послекомпоновочный шаг.


Подобную оптимизацию выполняет и программа Working Set Tuner (WST), поставляемая вместе с Platform SDK. WST работает на функциональном уровне, не проникая внутрь функций, тогда как инструмент Microsoft опускается до так называемого уровня базовых блоков. В следующем кодовом фрагменте базовый блок обозначен стрелками:
if ( TRUE = blsError)
{ <- Начало базового блока.
// Обработка ошибок. 
} <- Конец базового блока.
Инструмент Microsoft перемещает обработчик ошибки в конец двоичного файла, так что только наиболее общий код размещается в его начале. ОМАР-символы оказываются некоторой разновидностью адресных записей (fixup1) для главных символов, потому что инструмент Microsoft манипулирует двоичным файлом уже после того, как тот был построен.
Доступ к символьной информации
Для доступа к символьной информации можно использовать символьную машину DBGHELP.DLL фирмы Microsoft. DBGHELP.DLL может читать символьные форматы PDB, COFF и С7. В прошлом символьная машина была в IMAGEHLP.DLL, но Microsoft умудрился вывести ее из ядра системы и поместил в DLL, которую было легче обновлять. Для работы с программами, которые использовали символьную машину, встроенную в IMAGEHLP.DLL, в него все еще включены списки экспортируемых функций символьной машины (symbol engine exports). Новая IMAGEHLP.DLL пересылает соответствующие функции в DBGHELP.DLL. Во время написания этой книги MSDN-документация символьной машины еще оставалась частью библиотеки IMAGEHLP.DLL.
Символьная машина DBGHELP.DLL преобразует адрес в имя ближайшей общей функции или глобальной переменной. Она может также работать и наоборот, отыскивая адрес по имени конкретной функции. Наконец, она может отыскивать имя исходного файла и номер строки для конкретного адреса. Символьная машина DBGHELP.DLL не поддерживает ни поиск параметров или локальных переменных, ни оценку их типов. Как будет показано позже, используя только эти ограниченные функциональные возможности, можно построить некоторые превосходные утилиты, оказывающие неоценимую помощь при поиске многочисленных проблем в приложениях.


В WDBG был использован простой класс-оболочка (язык C++), который показан (файл SYMBOLENGINE.H) в листинге 4-7. Первоначально этот класс был написан как часть библиотеки BUGSLAYERUTIL.DLL. Это значительно урезанный вариант API символьной машины DBGHELP.DLL, но он обеспечивает и некоторые дополнительные возможности для решения проблем, с которыми приходится сталкиваться в старых версиях символьной машины IMAGEHLP.DLL. Исходный код SYMBOLENGINE.H представлен на тот случай, если придется использовать этот класс со старыми символьными машинами IMAGEHLP.DLL.
 Fixup — адресная запись (запись, генерируемая ассемблером для редактора связей в отношении каждого адреса, который не может быть определен). — Пер.
 Листнг4-7.Файл SYMBOLENGINE.H
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - -
"Debugging Applications" (Microsoft Press)
Copyright (c) 1997-2000 John Robbins — All rights reserved.
- - - - - - - -- - - - - - - - - - - - - - - - - - - - - - 
Этот класс - сокращенный вариант символьной машины DBGHELP.DLL. Эн охватывает только те функции, которые имеют уникальное значение HANDLE-дескриптора. Остальные функции символьной машины DBGHELP.DLL являются глобальными, поэтому они не включены в этот класс.
 - - - - - -- - - - - - - - - - - - - - - - - - - - - - - - - - 
Макроопределения компиляции:
DO_NOT_WORK_AROUND_SRCLINE_BUG — 
При определении этой константы класс не будет работать с ошибкой SymGetLineFromAddr, когда поиски PDB fMile терпят неудачу уже после первого поиска.
USE_BUGSLAYERUTIL — 
При определении этой константы данный класс будет применять другой метод инициализации символьной машины — BSUSymlnitialize из BUGSLAYERUTIL.DLL. Кроме того, флажок захвата процесса начнет работать для всех 32-разрядных Windows-систем ОС. При использовании этого определения кроме SYMBOLENGINE.Н нужно также включать и файл BUGSLAYERUTIL.H.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
#ifndef _SYMBOLENGINE_H


#define _SYMBOLENGINE_H
// Можно включить либо IMAGEHLP.DLL, либо DBGHELP.DLL.
# include "imagehlp.h"
#include <tchar.h>
// Включайте эти директивы в случае, если пользователь забывает
// про компоновку соответствующих библиотек
#pragma comment (lib, "dbghelp. lib")
#pragma comment (lib, "version, lib")
// Грандиозная идея создания классов-оболочек на структурах, которые
// имеют размерные поля, пришла от коллеги-журналиста из MSJ Поля
// (Paul DiLascia). Спасибо, Поль!;
// Я не включаю в класс константу IMAGEHLP_SYMBOL, потому что это
// структура переменного размера.
// Класс-оболочка IMAGEHLP_MODULE
struct CImageHlp_Module : public IMAGEHLP_MODULE
{
CImageHlp_Module ()
{
memset ( this, NULL, sizeof ( IMAGEHLP_MODULE));
SizeOfStruct = sizeof ( IMAGEHLP_MODULE); 
}
};
// Класс-оболочка IMAGEHLP_LINE 
struct CImageHlp_Line : public IMAGEHLP_LINE 
{
CImageHlp_Line () 
{
memset ( this, NULL, sizeof ( IMAGEHLP_LINE)); 
SizeOfStruct = sizeof ( IMAGEHLP_LINE); 
}
};
// Класс символьной машины class CSymbolEngine
{
/*- - - - - - - - - - - - - - - - - - - - - - - - - -
Public-конструктор и деструктор 
- - - -- - - - - - - - - - - - - - - - - - - - - - - - - - */
public :
// Чтобы использовать этот класс, вызовите метод Symlnitialize для
// инициализации символьной машины и затем применяйте другие методы
// вместо соответствующих функций из DBGHELP.DLL
CSymbolEngine ( void)
{
}
virtual -CSymbolEngine ( void)
{
}
/ *- - - - - - - - - - - - - - - - - - - - - - - - - - - -
Вспомогательные информационные public-функции
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
public :
// Возвращает версию используемого файла DBGHELP.DLL.
// Чтобы преобразовать возвращаемые значения в читаемый формат,
// применяется функция:
// wsprintf ( szVer ,
// _Т ( "%d.%02d.%d.%d"),
// HIWORD ( dwMS)
// LOWORD ( dwMS)
// HIWORD ( dwLS)
// LOWORD ( dwLS) );


// Параметр szVer будет содержать строку вида: 5.00.1878.1
BOOL GetlmageHlpVersion ( DWORD & dwMS, DWORD & dwLS)
{
return( GetlnMemoryFileVersion ( _T ( "DBGHELP.DLL"),
dwMS , 
dwLS ) ) ; 
}
BOOL GetDbgHelpVersion ( DWORD & dwMS, DWORD & dwLS)
 {
return( GetlnMemoryFileVersion ( __T ( "DBGHELP.DLL"),
dwMS , 
dwLS ) ) ; 
}
// Возвращает версию DLL-файлов, читающих PDB.
 BOOL GetPDBReaderVersion ( DWORD & dwMS, DWORD & dwLS)
 {
// Первым проверяется файл MSDBI.DLL.
if ( TRUE == GetlnMemoryFileVersion ( _T ( "MSDBI.DLL"),
dwMS ,
 dwLS ) )
 {
return ( TRUE); 
}
else if.( TRUE == GetlnMemoryFileVersion ( _T ( "MSPDB60.DLL"),
dwMS
dwLS ) ) 
{
return ( TRUE); 
}
// Теперь пришла очередь проверить MSPDB50.DLL. 
return ( GetlnMemoryFileVersion ( _T ( "MSPDB50.DLL"),
dwMS
dwLS ) ) ;
 }
// Рабочая функция, используемая двумя предшествующими функциями.
 BOOL GetlnMemoryFileVersion ( LPCTSTR szFile,
DWORD & dwMS , 
DWORD & dwLS ) 
{
HMODULE hlnstlH = GetModuleHandle ( szFile);
// Получить полное имя файла загруженной версии
TCHAR sz!mageHlp[ MAX_PATH ];
GetModuleFileName ( hlnst-IH, szImageHlp, MAX_PATH);
dwMS = 0;
dwLS = 0;
// Получить размер информации о версии.
DWORD dwVerlnfoHandle;
DWORD dwVerSize;
dwVerSize = GetFileVersionlnfoSize ( szImageHlp ,
SdwVerlnfoHandle ); 
if ( 0 == dwVerSize) 
{
return ( FALSE); 
}
// Получили размер информации о версии, теперь получим
 // саму информацию.
LPVOID IpData = (LPVOID)new TCHAR [ dwVerSize ]; 
if ( FALSE == GetFileVersionlnfo ( szImageHlp ,
dwVerlnfoHandle , dwVerSize , IpData )) 
{
delete [] IpData; return ( FALSE);
 }
VS_FIXEDFILEINFO * IpVerlnfo; 
UINT uiLen;
BOOL bRet = VerQueryValue ( IpData ,
_T ( "\\")
  (LPVOID*)SlpVerlnfo, &uiLen ) ;


 if ( TRUE == bRet)
 {
dwMS = lpVerInfo->dwFileVersionMS;
 dwLS = lpVer!nfo->dwFileVersionLS;
 }
delete [] IpData; return ( bRet);
}
 /*- - - - - - - - - - - - - - - - - - - - - - - - - - - -
Public-методы инициализации и чистки
- - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
public :
BOOL Symlnitialize ( IN HANDLE hProcess ,
 IN LPSTR UserSearchPath, 
IN BOOL flnvadeProcess )
 {
m_hProcess = hProcess;
return ( ::Symlnitialize ( hProcess ,
UserSearchPath, fInvadeProcess ));
 }
#ifdef USE_BUGSLAYERUTIL
BOOL BSUSymlnitialize ( DWORD dwPID ,
HANDLE hProcess ,
 PSTR UserSearchPath,
 BOOL flnvadeProcess ) 
{
m_hProcess = hProcess;
return ( ::BSUSymlnitialize ( dwPID ,
hProcess , UserSearchPath, flnvadeProcess ));
 }
#endif // USE_BUGSLAYERUTIL 
BOOL SymCleanup ( void) 
{
return ( ::SymCleanup ( m_hProcess)) ;
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - 
Public-методы манипуляций с модулями
- - - - - - - - - - - - - - - - - - - - - - - - - - * /
public :
BOOL SymEnumerateModules ( IN PSYM_ENUMMODULES_CALLBACK
EnumModulesCallback, 
IN PVOID UserContext) 
{
return ( ::SymEnumerateModules ( m_hProcess ,
EnumModulesCallback , 
UserContext )) ; 
}
BOOL SymLoadModule { IN HANDLE hFile , 
IN PSTR ImageName , 
IN PSTR ModuleName , 
IN DWORD BaseOfDll ,
 IN DWORD SizeOfDll ) 
{
return ( ::SymLoadModule ( m_hProcess ,
hFile
ImageName ,
 ModuleName , 
BaseOfDll SizeOfDll )); 

BOOL EnumerateLoadedModules ( IN PENUMLOADED_MODULES_CALLBACK
EnumLoadedModulesCallback,
 IN PVOID UserContext )
{
return ( ::EnumerateLoadedModules ( m_hProcess ,
EnumLoadedModulesCallback, 
UserContext )); 
}
BOOL SymUnloadModule ( IN DWORD BaseOfDll) 
{
return ( ::SymUnloadModule ( m_hProcess, BaseOfDll)); 

BOOL SymGetModulelnfo ( IN DWORD dwAddr


OUT PIMAGEHLP__MODULE Modulelnfo ) 
{
return ( ::SymGetModulelnfo ( m_hProcess ,
dwAddr ,
 Modulelnfo )); 
}
DWORD SymGetModuleBase ( IN DWORD dwAddr) 
{
return ( ::SymGetModuleBase ( m_hProcess, dwAddr));
 }
 /*- - - - - - - - - - - - - - - - - - - - - - - - - - 
Public-методы манипуляций с символами
- - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
public :
BOOL SymEnumerateSymbols ( IN DWORD BaseOfDll,
IN PSYM_ENUMSYMBOLS_CALLBACK
EnumSymbolsCallback,
IN PVOID UserContext) 
{
return ( ::SymEnumerateSymbols ( m_hProcess ,
BaseOfDll
EnumSymbolsCallback, 
UserContext )); 
}
BOOL SymGetSymFromAddr ( IN DWORD dwAddr ,
OUT PDWORD pdwDisplacement, 
OUT PIMAGEHLP_SYMBOL Symbol ) 
{
return ( ::SymGetSymFromAddr ( m_hProcess ,
dwAddr , 
pdwDisplacement , 
Symbol )); 
}
BOOL SymGetSymFromName ( IN LPSTR Name ,
OUT PIMAGEHLP_SYMBOL Symbol )
{
return ( ::SymGetSymFromName ( m_hProcess,
Name ,
 Symbol }}; 
}
BOOL SymGetSymNext ( IN OUT PIMAGEHLP_SYMBOL Symbol)
 {
return ( ::SymGetSymNext ( m_hProcess, Symbol)); 
}
BOOL SymGetSymPrev ( IN OUT PIMAGEHLP_SYMBOL Symbol)
 {
return ( ::SymGetSymPrev ( m_hProcess, Symbol));
}
 /*- - - - - - - - - - - - - - - - - - - - - - - - - - - - 
Public-метод манипуляций с исходной строкой
- - - -- - - - - - - - - - - - - - - - - - - - - - - - - */
public :
BOOL SymGetLineFromAddr ( IN DWORD dwAddr ,
OUT PDWORD pdwDisplacement, OUT PIMAGEHLP_LINE Line ) 
{
# ifde f DO_NOT_WORK_AROUND_SRCLINE_BUG
// Просто передайте значения, возвращенные main-функцией
 return ( ::SymGetLineFromAddr ( m_hProcess ,
dwAddr 
, pdwDisplacement, 
Line ) ) ;
#else
// Проблема в том, что символьная машина находит только те адреса
 // исходных строк (после первого поиска), которые попадают точно
 //на нулевые смещения. Чтобы найти строку и возвратить


 // подходящее смещение, я возвращаюсь назад на 100 байт.
 DWORD dwTempDis = 0;
while ( FALSE == ::SymGetLineFromAddr ( m_hProcess ,
dwAddr — dwTempDis ,
 pdwDisplacement , 
Line ) ) 
}
dwTempDis += 1;
if ( 100 == dwTempDis)
{
return ( FALSE); 
}
}
if (0 != dwTempDis)
 {
*pdwDisplacement = dwTempDis; 
}
return { TRUE);
#endif // DO_NOT_WORK_AROUND_SRCLINE_BUG 
}
BOOL SymGetLineFromName ( IN LPSTR ModuleName ,
IN LPSTR FileName ,
 IN DWORD dwLineNumber ,
 OUT PLONG plDisplacement , 
IN OUT PIMAGEHLP_LINE Line ) 
{
return ( ::SymGetLineFromName ( m_hProcess ,
ModuleName , 
FileName , 
dwLineNumber ,
plDisplacement , 
Line ) ) ; 
}
BOOL SymGetLineNext ( IN OUT PIMAGEHLP_LINE Line) 
{
return ( ::SymGetLineNext ( m_hProcess, Line)); 
}
BOOL SymGetLinePrev ( IN OUT PIMAGEHLP_LINE Line) 
{
return ( ::SymGetLinePrev ( m_hProcess, Line));
 }
BOOL SymMatchFileName ( IN LPSTR FileName ,
IN LPSTR Match , 
OUT LPSTR * FileNameStop ,
 OUT LPSTR * MatchStop ) 
{
return ( ::SymMatchFileName ( FileName ,
Match , 
FileNameStop , 
MatchStop ));
 } 
/*- - - - - - - - - - - - - - - - - - - -- - - - - - - - - - - - -
Разные public-члены
- - - - - - - -- - - - - - - - - - - - - - - - - - - - - - - -*/
public :
LPVOID SymFunctionTableAccess ( DWORD AddrBase)
 {
return ( ::SymFunctionTableAccess ( m_hProcess, AddrBase)); 
}
BOOL SymGetSearchPath ( OUT LPSTR SearchPath ,
IN DWORD SearchPathLength ) 
{
return ( ::SymGetSearchPath ( m_hProcess ,
SearchPath , 
SearchPathLength )); 
}
BOOL SymSetSearchPath ( IN LPSTR SearchPath) 
{
return ( ::SymSetSearchPath ( m_hProcess, SearchPath)); 

BOOL SymRegisterCallback ( IN PSYMBOL_REGISTERED_CALLBACK
CallbackFunction,
IN PVOID UserContext ) 
{
return ( ::SymRegisterCallback ( m_hProcess ,


CallbackFunction , 
UserContext ));
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
Защищенные члены данных
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
protected :
// Уникальное значение, которое будет использоваться для этого 
// экземпляра символьной машины. Это значение не должно быть
 // значением актуального процесса, а просто — уникальным значением. 
HANDLE m_hProcess ;
};
ttendif // _SYMBOLENGINE_H
Перед появлением Windows 2000 получить рабочий вариант символьной машины, поддерживаемой фирмой Microsoft, было не так легко. Главная причина трудностей состояла в том, что символьная машина была включена в состав системного файла IMAGEHLP.DLL, который использовали многие программы. Заменить ее новым вариантом внутри загруженного системного файла было невозможно, а получить более новую версию — сложно. Теперь, когда DBGHELP.DLL больше не является системной библиотекой, ее гораздо легче обновлять. Для этого нужно просто следить, чтобы на вашей машине всегда была установлена последняя версия Platform SDK. Ее всегда можно загрузить с сайта www.microsoft.com или получить как часть подписки MSDN. Все исходные коды в этой книге ориентированы на DBGHELP.DLL, поэтому нужно следить за тем, чтобы DBGHELP.DLL была установлена и путь к ее каталогу указан в переменной окружения PATH.
Установка DBGHELP.DLL — только часть дела, ведь для того чтобы загружать символьные файлы, нужно гарантировать их доступность для символьной машины. В случае DBG-файлов символьная машина DBGHELP.DLL будет искать их в следующих местах:

  •  текущий рабочий каталог приложения, использующего DBGHELP.DLL (а не подчиненный отладчик!);
  •  переменная среды _NT_SYMBOL_PATH;
  •  переменная среды _NT_ALT_SYMBOL_PATH;
  •  переменная среды SYSTEMROOT.
Каталоги, на которые указывают переменные среды, должны быть организованы определенным образом. Например, если приложение состоит из ЕХЕ-файла и пары DLL, расположенных в каталоге C:\MyFiles, то под этим каталогом нужно создать следующую структуру подкаталогов:


  •  C:\MyFiles
  •  C:\MyFiles\Symbols
  •  C:\MyFiles\Symbols\Exe
  •  C:\MyFiles\Symbols\Dll
Два последних подкаталога предназначены для размещения соответствующих DBG-файлов приложения.
Единственное различие при работе с PDB-файлами состоит в том, что символьная машина DBGHELP.DLL будет отыскивать PDB-файлы в первичном каталоге приложения и пробовать загружать PDB из этого каталога. Если символьная машина DBGHELP.DLL не сможет загрузить PDB-файлы из этого каталога, то она будет пытаться искать и загружать их так же, как DBG-файлы (т. е. из тех же подкаталогов, которые должны были быть созданы для хранения файлов отладочных символов).
 То есть для каждого типа рабочих файлов приложения создается отдельный подкаталог для DBG-файлов. — Пер.
 Где хранятся как двоичные файлы приложения, так и соответствующие PDB-файлы, которые были созданы компоновщиком на этапе отладочного построения. — Пер.
Прохождение стека
К счастью для всех нас, нет необходимости писать собственный код для прохода стека. В DBGHELP.DLL определена специальная API-функция stackwalk, которая берет на себя все заботы по работе со стеком. WDBG использует ее точно так же, как это делает отладчик Visual C++. Единственная неприятность — нет подробной документации по структуре STACKFRAME. В листинге 4-8 показаны только те поля этой структуры, которые должны быть заполнены. Функция stackwalk так хорошо заботится обо всех деталях, что вы можете и не знать, что при оптимизированном коде проход по стеку может быть довольно трудной задачей. Причина этих трудностей заключается в том, что для некоторых функций компилятор может выполнять оптимизацию вдали от области стека, т. е. от того места, где выталкиваются его элементы. Компиляторы Visual C++ и Visual Basic довольно агрессивны, когда они выполняют оптимизацию, и если они могут использовать стековый регистр как рабочий, то они будут это делать. Чтобы облегчать работу со стеком в таких ситуациях, компилятор генерирует то, что называется данными FPO (Frame Pointer Omission).


FPO-данные — это таблица, которую функция stackwalk использует для вычислений, связанных с обработкой тех функций, которые пропускают нормальную область стека. Мы рассматриваем FPO-данные еще и потому, что ссылки на них иногда встречаются в MSDN и в различных отладчиках. Можно подробнее познакомиться со структурой FPO-данных в файле WINNT.H.
 Листинг 4-8. InitializeStackFrameWithGontext из i386CPUHELP.C
BOOL CPUHELP_DLLINTERFACE _stdcall
InitializeStackFrameWithContext ( STACKFRAME * pStack,
CONTEXT * pCtx)
{
ASSERT ( FALSE == IsBadReadPtr ( pCtx, sizeof ( CONTEXT))); 
ASSERT ( FALSE == IsBadWritePtr ( pStack, sizeof ( STACKFRAME)) 
} ;
if ( ( TRUE == IsBadReadPtr ( pCtx, sizeof ( CONTEXT))) ||
( TRUE == IsBadWritePtr ( pStack, sizeof ( STACKFRAME)))) 
{
return ( FALSE);
 }
pStack->AddrPC.Offset = pCtx->Eip; 
pStack->AddrPC.Mode = AddrModeFlat ;
 pStack->AddrStack.Offset = pCtx->Esp;
 pStack->AddrStack.Mode = AddrModeFlat ;
 pStack->AddrFrame.Offset = pCtx->Ebp;
 pStack->AddrFrame.Mode = AddrModeFlat ;
 return ( TRUE); 
}

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