Что делать дальше с утилитой CrashFinder?
Теперь, немного разобравшись с тем, как работает CrashFinder, поговорим о том, как расширить его функциональные возможности. Хотя CrashFinder — полноценное приложение, далее предложено несколько дополнений, которые сделают его мощнее и намного более легким для использования. Если вы хотите получать больше информации о двоичных образах, добавьте в CrashFinder следующие свойства:
автоматическое добавление зависимых DLL. В настоящее время добавление каждого двоичного образа в CrashFinder-проект приходится выполнять вручную. Было бы намного лучше, если бы при создании нового проекта CrashFinder загружал в новый проект нужный ЕХЕ-файл (по подсказке пользователя), и затем автоматически добавлял к нему все зависимые DLL-файлы. Конечно, это свойство не обеспечило бы поиска динамически загружаемых DLL (т. к. они загружаются обращением к функции LoadLibrary), но все же привело бы к экономии какого-то времени за счет автоматического добавления индивидуальных двоичных файлов; отображение дополнительной информации в информационной панели. Класс CBinaryimage позволяет выводить гораздо больше информации (кроме той символьной информации, которая выводится в настоящее время в правой панели программы). С помощью метода GetAdditionaiinfo можно добавить, например, файлы заголовков, а также списки импортированных и экспортируемых функций; вставка списков DLL для автоматического добавления их к проекту. Окно Output отладчика выводит списки всех DLL, которые загружает приложение. Можно расширить CrashFinder так, чтобы позволить пользователю вставлять текст из окна Output в документальную панель программы CrashFinder и выполнять сканирование этого текста в поисках имен DLL.
Использование утилиты CrashFinder
Как видите, чтение МАР-файла не слишком затруднительно. Оно скорее утомительно, но это, конечно, не должно касаться других членов команды (инженеров по качеству, персонала технической поддержки и даже менеджеров). Чтобы облегчить и их жизнь, я решил сделать утилиту CrashFinder пригодной к употреблению всеми членами команды — от разработчиков до инженеров службы поддержки (включая, конечно, и специалистов по тестированию). Я старался, чтобы все аварийные отчеты включили по возможности максимум информации об ошибках. Если соблюдается процедура создания соответствующих отладочных символов, описанная в главе 2, то применение CrashFinder не вызовет затруднений.
При использовании программы CrashFinder в рабочих группах необходимо особенно внимательно относиться к обеспечению доступности двоичных образов1 и связанных с ними PDB-файлов, потому что CrashFinder не хранит никакой другой информации о приложении, кроме путей к двоичным образам. CrashFinder сохраняет только имена двоичных файлов, так что один и тот же проект, работающий с утилитой CrashFinder, можно применять повсюду в цикле производства. Если бы CrashFinder хранил более детальную информацию о приложении, такую, например, как таблицы символов, то, вероятно, пришлось бы создавать CrashFinder-проект для каждого построения продукта. Если следовать этой рекомендации и разрешать свободный доступ к двоичным и PDB-файлам при аварийном завершении приложения, то специалистам по тестированию или поддержке останется лишь запускать CrashFinder, чтобы добавить к отчету об ошибках соответствующую информацию. Как всем известно, чем больше информации о проблеме доступно разработчику, тем легче ему решить эту проблему.
Здесь и далее под двоичными образами (binary images) автор понимает, по-видимому, любые двоичные файлы конкретного приложения — EXE, DLL, OCX и т. д. — Пер.
Вероятно, для конкретного приложения придется создать несколько CrashFinder-проектов. Если системные DLL являются частью CrashFinder-проекта, то для каждой операционной системы, которую вы поддерживаете, нужно будет создавать отдельные проекты.
Придется также создать CrashFinder- проект для каждой версии приложения, посылаемой специалистам по тестированию, не входящим в команду разработчиков, и отдельно для каждой такой версии хранить двоичные и PDB-файлы.
На рис. 8.2 изображен пользовательский интерфейс утилиты CrashFinder, в которую загружен один из проектов. Левая часть дочернего окна содержит управляемое дерево, на котором показаны выполняемые файлы и связанные с ними DLL-библиотеки. Маркеры * указывают, что символы каждого из двоичных образов (файлов) были загружены успешно. Если бы CrashFinder не смог загрузить символы, то слева от имени файла был бы указан маркер х. В правой части отображается символическая информация о выделенном в левой панели двоичном образе.
Двоичный образ добавляется к CrashFinder-'Проекту командой Add Image меню Edit. Добавляя двоичные образы, имейте в виду, что CrashFinder воспринимает только один ЕХЕ-файл в каждом проекте. Если приложение включает несколько ЕХЕ-файлов, необходимо создать отдельный CrashFinder-проект для каждого из них. Поскольку CrashFinder является МDI-приложением, то можно открыть отдельные проекты для каждого ЕХЕ-файла (с целью определения соответствующих аварийных позиций). Когда вы добавляете DLL-файлы, CrashFinder проверяет отсутствие конфликтов в адресах загрузки с любыми другими DLL, уже находящимися в проекте. Если CrashFinder обнаруживает конфликт, то позволит изменить адрес загрузки конфликтующей DLL только для текущего экземпляра CrashFinder-проекта. Такой режим удобен, когда вы, имея CrashFinder-проект для отладочной конфигурации, забываете перебазировать свои DLL: Как указано в главе 2, нужно всегда устанавливать базовые адреса для всех DLL приложения.
MDI — Multiple-Document Interface (многодокументный интерфейс). — Пер.
Если приложение изменится через какое-то время, то можно удалить ненужные двоичные образы, выбрав команду Remove Image меню Edit. Адрес загрузки двоичного образа можно изменять с помощью команды Image Properties меню Edit.
Целесообразно также добавлять в дерево системные DLL, которые используются проектом. Это дает возможность локализовать проблему, когда программа аварийно завершается в одном из них. Как уже говорилось в главе 5, наличие установленных отладочных символов в Windows 2000 иногда очень помогает при пошаговом выполнении кода дизассемблера системного модуля. Теперь есть даже более серьезная причина для установки отладочных символов в Windows 2000 — утилита CrashFinder может их использовать для того, чтобы предоставить разработчику возможность отыскивать аварии даже в системных модулях.
RaiSon d'etre утилиты CrashFinder состоит в том, чтобы преобразовать аварийный адрес в имя функции, имя исходного файла и номер строки. Выполнение команды Find Crash меню Edit открывает диалоговое окно Find Crash, показанное на рис. 8.3. Для каждого аварийного адреса, который требуется отыскать, нужно лишь ввести шестнадцатеричный адрес в редактируемое поле Hexadecimal Address и нажать кнопку Find.
Raison d'etre — разумное основание (франц.). — Пер.
Рис. 8.3. Поиск позиции аварийного останова с помощью утилиты CrashFinder
В нижней части диалогового окна Find Crash отображена информация, относящаяся к последнему найденному адресу. Большинство полей здесь самоочевидно и не требует объяснения. Поле Fn Displacement показывает смещение адреса ошибки от начала функции в байтах, а поле Source Displacement — смещение адреса ошибки от начала ближайшей исходной строки. Помните, что отдельная исходная строка может порождать несколько инструкций языка ассемблера, особенно, если вызовы функций являются частью списка параметров. Имейте в виду, что при использовании программы CrashFinder нельзя отыскивать адреса, которые не являются адресами правильных (исполняемых) инструкций. Если в программе на C++ очищается указатель this, то это может вызвать аварийный останов в адресе 0x00000001. К счастью, такие типы ошибок не столь распространены как обычные ошибки нарушения доступа к памяти, которые можно легко найти с помощью утилиты CrashFinder.
Основные моменты реализации
Сама программа CrashFinder является непосредственным MFC-приложением (т. е. напрямую использует библиотеку классов Microsoft Foundation Class), поэтому большая ее часть должна быть хорошо узнаваемой. Чтобы облегчить дальнейшее расширение программы CrashFinder (это можно сделать, следуя рекомендациям, приведенным в разделе "Что делать дальше с утилитой CrashFinder?" в конце этой главы), укажу на три ключевых момента и поясню основные особенности их реализации. Во-первых, речь пойдет о символьной машине, которую использует CrashFinder, во-вторых, мы рассмотрим, где выполняется основная работа в программе CrashFinder, и, наконец, опишем архитектуру данных этой программы.
CrashFinder использует символьную машину DBGHELP.DLL, описанную в главе 4. Единственной интересной деталью является то, что необходимо заставить эту машину загружать весь исходный файл и информацию о номерах строк, пересылая флажок SYMOPT_LOAD_LINES в функцию symsetoptions. Символьная машина DBGHELP.DLL по умолчанию не загружает исходный файл и информацию о номерах строк, так что об этом должен позаботиться программист.
Еще одна особенность реализации программы CrashFinder — вся работа, по существу, выполняется в документальном классе ccrashFinderDoc. Он содержит класс csyinboiEngine, осуществляет поиск всех символов и управляет их представлением (видом). Его ключевая функция— ccrashFinderDoc:: LoadAndShowimage — показана в листинге 8-2. Эта функция подтверждает правильность двоичного образа, проверяет его на наличие элементов проекта, имеющих конфликтующие адреса загрузки, загружает символы и вставляет образ в конец дерева. Она вызывается и при добавлении двоичного образа к проекту, и при открытии проекта. Перекладывая управление этими рутинными операциями на функцию CcrashFinderDoc: :LoadAndShowimage, разработчик может быть уверен, что основная логика программы CrashFinder всегда находится в одном месте, и что проект хранит только имена двоичных образов, а не копирует всю таблицу символов.
Листинг 8-2. Функция CcrashFinderDoc: :LoadAndShowimage
BOOL CcrashFinderDoc :: LoadAndShowimage ( CBinaryImage * plmage,
BOOL bModifiesDoc)
{
// Проверить предположения за пределами функции.
ASSERT ( this);
ASSERT ( NULL != m_pcTreeControl);
// Строка, которая может использоваться для любых сообщений
CString sMsg ;
// Состояние для графики дерева
int iState = STATEJSIOTVALID;
// Переменная для булевского возвращаемого значения
BOOL bRet ;
// Убедиться, что параметр — в порядке.
ASSERT ( NULL != plmage);
if ( NULL == plmage)
{
// Ничего не может случиться с плохим указателем,
return ( FALSE);
}
// Проверить, правилен ли этот образ. Если это так, убедиться,
// что его еще нет в списке и что он не имеет
// конфликтующего адреса загрузки. Если это не так, все равно
// добавить его, т. к. нехорошо просто выбрасывать данные
// пользователя.
// Если образ плох, я просто показываю его с неправильным растром
// и не загружаю в символьную машину,
if ( TRUE == p!mage->IsValidImage ())
{
// Здесь сканируются элементы массива данных, чтобы отыскать
// три проблемных условия:
// 1. Двоичный образ уже есть в списке. Если это так, то возможен
// только преждевременный выход.
// 2. Двоичный образ будет загружен в адрес, который уже
// есть в списке. Если это так, открываем диалоговое окно
// Properties для двоичного образа, чтобы перед его
// добавлением в список можно было изменить адрес загрузки.
// 3. Проект уже включает исполняемый (ЕХЕ-)образ, и plmage тоже
// является исполняемым.
// Для начала я всегда оптимистично предполагаю, что данные в
// plmage правильны.
BOOL bValid = TRUE;
int iCount = m_cDataArray.GetSize ();
for ( int i = 0; i < iCount; i++)
{
CBinaryImage * pTemp = (CBinaryImage *)m_cDataArray[ i ];
ASSERT ( NULL != pTemp);
if ( NULL = pTemp)
{
// Мало ли что может случиться с плохим указателем!
return ( FALSE);
}
// Согласованы ли два этих Cstring-значения?
if ( pImage->GetFullName О = pTemp->GetFullName ())
{
// Сообщить пользователю!!
sMsg.FormatMessage ( IDS_DUPLICATEFILE,
pTemp->GetFullName () );
AfxMessageBox ( sMsg);
return ( FALSE);
}
// Если текущее изображение из структуры данных неправильно,
// то проверим дублирование имен, как это было только что |
// показано, но адреса загрузки и характеристики ЕХЕ-образа
// проверить трудно. Если рТетр — неправильный,
//то следует пропустить эти проверки..
// Это может привести к проблемам, но т. к. рТетр отмечен
//в списке как неправильный, то переустановка
// свойств становится проблемой пользователя.
if ( TRUE == pTemp.->IsValidIinage ( FALSE) )
{
// Проверить, что в проект не добавлены два ЕХЕ-файла.
if ( 0 == ( IMAGE_FILE_DLL &
pTemp->GetCharacteristics ()))
{
if ( 0 = { IMAGE_FILE_DLL &
pImage->GetCharacteristics ()))
{
// Сообщить пользователю!!
SMsg.FormatMessage ( IDS_EXEALREADYINPROJECT,
p!mage->GetFullName (), pTemp->GetFullName () );
AfxMessageBox ( sMsg);
// Попытка загрузить два образа, помеченных как
// "ЕХЕ", будет автоматически отбрасывать данные
// для plmage. return ( FALSE);
}
}
// Проверить конфликты адресов загрузки,
if ( pImage->GetLoadAddress () == pTemp->GetLoadAddress() )
{
sMsg.FormatMessage ( IDS_DUPLICATELOADADDR ,
pImage->GetFullName () ,
pTemp->GetFullName () );
if ( IDYES == AfxMessageBox ( sMsg, MB_YESNO))
{
// Пользователь хочет изменить свойства вручную
pImage~>SetProperties ();
// Проверить, что адрес загрузки на самом деле
// изменился и что нет конфликта
//с другим двоичным образом.
int iIndex;
if ( TRUE =
IsConflictingLoadAddress (
pImage->GetLoadAddress(),
iIndex ) )
{
sMsg.FormatMessage
( IDS_DUPLICATELOADADDRFINAL,
p!mage->GetFullName () ,
((CBinaryImage*)m_cDataArray[iIndex])->GetFullName());
AfxMessageBox ( sMsg);
// Данные в pImage неправильные, поэтому
// двигаемся дальше и выходим из цикла.
bValid = FALSE;
break;
}
}
else
{
// Данные в plmage неправильные, поэтому
// двигаемся дальше и выходим из цикла.
bValid = FALSE;
break;
}
}
}
}
if ( TRUE = bValid)
{
// Этот образ хорош (по крайней мере, по отношению
//к загруженным символам).
iState = STATE_VALIDATED;
}
else
{
iState = STATE_NOTVALID;
}
}
else
{
// Этот образ неправильный.
iState = STATE_NOTVALID;
}
if ( STATE_VALIDATED = iState)
{
// Попытка загрузить этот образ в символьную машину.
bRet =
m_cSymEng.SymLoadModule(NULL ,
(PSTR)(LPCSTR)pImage->GetFullName(),
NULL
pImage->GetLoadAddress () ,
0 );
// Наблюдение закончено. SymLoadModule возвращает адрес загрузки
// образа, неравный TRUE.
ASSERT ( FALSE != bRet);
if ( FALSE == bRet)
{
TRACE ( "m_cSymEng.SymLoadModule failed!!\n");
iState = STATE_NOTVALID;
}
else
{
iState ь STATE_VALIDATED;
}
}
// Установить значение "Extra Data" для plmage в состояние загрузки
// отладочных символов i
f ( STATEJVALIDATED == iState)
{
pImage->SetExtraData ( TRUE);
}
else
{
pImage->SetExtraData ( FALSE);
}
// Поместить этот элемент в массив.
m_cDataArray.Add ( plmage);
// Добавлен ли элемент модификации документа?
if ( TRUE == bModifiesDoc)
{
SetModifiedFlag ();
}
CCrashFinderApp * pApp = (CCrashFinderApp*)AfxGetApp ();
ASSERT ( NULL != pApp);
// Поместить строку в дерево.
HTREEITEM hltem =
m_pcTreeControl->Insert!tem ( pApp->ShowFullPaths ()
? pImage->GetFullName ()
: pImage->GetName () ,
iState ,
iState );
ASSERT ( NULL != hltem);
// Поместить указатель на образ в данные элемента. Этот указатель
// облегчает обновление символьной информации модуля
// всякий раз, когда его вид претерпевает изменения.
bRet = m_pcTreeControl->SetItemData ( hltem, (DWORD)plmage);
ASSERT ( bRet);
// Форсировать выбор элемента.
bRet = m_pcTreeControl->SelectItem ( hltem);
// Bee OK, Jumpmaster!
return ( bRet);
}
И, наконец, опишем архитектуру данных программы CrashFinder. Ее главная структура данных — это просто массив классов cbinaryimage. Каждый класс cbinaryimage представляет отдельный двоичный образ, добавляемый к проекту, и обслуживает информацию о таких деталях этого образа, как адрес загрузки, двоичные свойства и имя. Документ1 добавляет объект cbinaryimage (двоичный образ) к массиву главных данных и помещает значение соответствующего указателя в слот данных узлового элемента дерева. При выборке элемента в представлении дерева двоичных файлов (в левой панели окна программы CrashFinder) выбранный узел пересылается назад в документ, так чтобы документ смог получить объект cbinaryimage и просмотреть его символьную информацию (предъявив ее пользователю в правой панели окна программы CrashFinder).
Точнее — объект класса CcrashFinderDoc. — Пер.
Поиск функции, исходного файла и номера строки
Алгоритм для извлечения функции, исходного файла и номера строки из МАР-файла прост, но выполняется с применением шестнадцатеричной арифметики. Пусть, например, аварийный останов произошел по адресу 0x03901099 в модуле MAPDLL.DLL, показанном в листинге 8-1.
Прежде всего, нужно заглянуть в МАР-файлы своего проекта и найти файл, который содержит аварийный адрес. Сначала посмотрите на предпочтительный адрес загрузки и последний адрес в секции общих функций. Адрес ошибки должен находиться между этими значениями, если это не так, значит перед нами "неправильный" МАР-файл.
Чтобы отыскать функцию (или ближайшую общую функцию, если ошибка произошла в статической С-функции), найдите в колонке Rva+Base первый адрес, который превышает аварийный. Тогда предыдущий вход в МАР-файле и есть та функция, которая вызвала аварийный останов. Например, в листинге 8-1 первым адресом функции, превосходящим аварийный (0x3901099), является адрес Ox39010F6, следовательно, ошибку вызвала функция ?MapDLLHappyFunc@@YAPADPAD@z. Имя функции, которое начинается со знака вопроса, является декорированным именем (decorated name) языка C++. Чтобы транслировать это имя, передайте его как параметр команд- | ной строке программы UNDNAME.EXE из Platform SDK. Например, ?MapDLLHappyFunc@@YApADPAD@z переводится этой программой в обычное имя MapDLLHappyFunc, о чем, вероятно, можно было догадаться, просто взглянув на декорированное имя. Другие декорированные имена C++ расшифровы вать труднее, особенно когда используются перегруженные функции. Номер строки вычисляется по следующей формуле:
(crash address) — (preferred load address) — 0x1000
Напомним, что адреса указываются как смещение от начала первой секции кода, что и учитывает эта формула. Вероятно, нетрудно догадаться, почему вычитается предположительный адрес загрузки, но зачем вычитать еще и 0x1000? Адрес ошибки представляет собой смещение от начала секции кода, но эта секция не является первой частью двоичного файла. Его первой частью является РЕ заголовок, длина которого равна 0x1000 байт.
РЕ-файл — от англ. Portable Executable, переносимый (на другую платформу) исполняемый файл. — Пер.
Не знаю точно, почему компоновщик генерирует МАР-файлы, которые требуют этого добавочного вычисления. Разработчики утилиты LINK.EXE ввели колонку Rva+Base недавно, и не вполне понятно, почему они просто не установили в этой колонке и номер строки тоже.
Вычислив смещение, просматривайте МАР-файл, пока не найдете ближайший номер, который не превосходит расчетное значение. Имейте в виду, что на фазе генерации компилятор может перемещать коды, так что исходные строки не располагаются в порядке возрастания. В рассматриваемом примере использована следующая формула:
0x03901099 - 0x03900000 - 0x1000 = 0x99
В листинге 8-1 самый близкий, но не превышающий расчетное значение адреса 0001:00000099 номер строки равен 38 (с адресом 0001:00000096), т. е. речь идет о строке 38 в файле MAPDLL.CPP.
Рассматривая МАР-файл для модуля, написанного на языке Visual Basic, следует знать, что номера строк, о которых сообщает МАР-файл (и утилита CrashFinder), не соответствуют номерам строк, которые отображает редактор Visual Basic. Компилируемые двоичные файлы принимают во внимание полный заголовок в верхней части исходного файла, который Visual Basic скрывает от просмотра. Чтобы найти строку, о которой сообщает компилятор, нужно открыть VB-файл в текстовом редакторе, таком как редактор Visual C++, и перейти к строкам, перечисленным в соответствующем списке МАР-файла.
Эта глава описывает процесс уточнения
Эта глава описывает процесс уточнения исходной позиции аварийного останова в ситуации, когда единственной информацией, которой вы обладаете, является аварийный адрес. Здесь имеются две возможности. Первая: для выяснения имени исходного файла и номера строки конкретной аварии можно обратиться к соответствующему МАР-файлу. МАР-файлы — это единственное текстовое представление ваших символов, и необходимо регулярно создавать такие файлы для каждого финального построения приложения. Вторая: для преобразования аварийного адреса в имя функции, имя исходного файла и номер аварийной строки нужно использовать утилиту CrashFinder. Эта утилита берет на себя всю работу по такому преобразованию и позволяет сообщать другим членам команды максимально возможное количество информации при авариях их приложений. Хотя CrashFinder легче использовать, чем МАР-файлы, у разработчика должно войти в привычку создание МАР-файлов, потому что форматы файлов символов постоянно изменяются, и когда это происходит, только МАР-файлы помогут, локализовать причину аварийного останова.
Содержимое МАР-файла
Пример МАР-файла показан в листинге 8-1. Верхняя часть МАР-файла содержит имя модуля, метку даты/времени (timestamp), указывающую, когда LINK.EXE скомпоновал модуль и предпочтительный адрес загрузки. После заголовка расположена информация, которая показывает, какие секции присоединил компоновщик из различных OBJ- и LIB-файлов.
Далее следует информация об общих (public) функциях. Обратите внимание на эту часть. Если в программе есть С-функции, объявленные как статические (static), то они не будут показаны в МАР-файле. К счастью, номера строк все еще будут отражать статические функции.
Важными данными для общих функций являются их имена и информация в колонке Rva+Base, в которой указываются их стартовые адреса. За секцией общих функций следует строчная информация. Строки отображаются следующим образом:
10 0001:00000030
Первое число — номер строки, а второе представляет собой смещение от начала секции кода, в которой находится эта строка. Это выглядит довольно путано, но позже мы рассмотрим способ преобразования адреса в имя исходного файла и номер строки в нем.
Если модуль содержит экспортируемые функции, то они перечислены в заключительной секции МАР-файла. Можно получить эту же информацию, запустив утилиту DUMPBIN с параметром <EXPORTS<ИМЯ_МОДУЛЯ>.
Листинг 8-1.Пример MAP-файла
MapDLL
Timestamp is 37f41936 (Thu Sep 30 22:15:18 1999)
Preferred load address is 03900000
Start Length Name Class
0001:00000000 00001421H .text CODE
0002:00000000 0000012cH .rdata DATA
0002:00000130 00000193H .edata DATA
0003:00000000 00000104H .CRT$XCA DATA
0003:00000104 00000104H .CRT$XCZ DATA
0003:00000208 00000104H .CRT$XIA DATA
0003:0000030с 00000104Н .CRT$XIZ DATA
0003:00000410 00000176H .data DATA
0003:00000588 00000030H .bss DATA
0004:00000000 00000014H .idata$2 DATA
0004:00000014 00000014H .idata$3 DATA
0004:00000028 00000050H .idata$4 DATA
0004:00000078 00000050H .idata$5 DATA
0004:000000c8 00000179H .idata$6 DATA
Address Publics by Value Rva+Base Lib:0bject
0001:00000030 _DllMain@12 03901030 f MapDLL.obj
0001:0000004с ?MapDLLFunction@@YAHXZ 0390104с f MapDLL.obj
0001:00000076 ?MapDLLHappyFunc@@YAPADPAD@Z 03901076 f MapDLL.obj
0001:000000f6 _printf 039010f6 f MSVCRTD:MSVCRTD.dll
0001:000000fc _chkesp 039010fc f MSVCRTD:MSVCRTD.dll
0001:00000110 _CRT_INIT@12 03901110 f MSVCRTD:crtdll.obj
0001:00000220 _DllMainCRTStartup@12 03901220 f MSVCRTD:crtdll.obj
0001:00000314 _free_dbg 03901314 f MSVCRTD:MSVCRTD.dll
0001:0000031a _initterm 0390131a f MSVCRTD:MSVCRTD.dll
0001:00000320 _onexit 03901320 f
MSVCRTD:atonexit.obj
0001:00000360 __atexit 03901360 f
MSVCRTD:atonexit.obj
0001:00000378 _malloc_dbg 03901378 f MSVCRTD:MSVCRTD.dll
0001:0000037e __dllonexit 0390137e f MSVCRTD:MSVCRTD.dll
0002:0000001c ??_C@_08JKC@crtdll?4c?$AA@ 0390301с MSVCRTDrcrtdll.obj
0003:00000000 __xc_a 03904000
MSVCRTD:cinitexe.obj
0003:00000104 __xc_z 03904104
MSVCRTD:cinitexe.obj
0003:00000208 __xi_a 03904208
MSVCRTD:cinitexe.obj
0003:0000030с __xi_z 0390430с
MSVCRTD:cinitexe.obj
0003:0000058с _adjust_fdiv 0390458с <common>
0003:00000598 __onexitend 03904598 <common>
0003:000005a8 __onexitbegin 039045a8 <common>
0003:000005ac _pRawDllMain 039045ac <common>
0004: 00000000 _IMPORT_DESCRIPTOR_MSVCRTD 03905000 MSVCRTD:MSVCRTD.dll
0004:00000014 _NULL_IMPORT_DESCRIPTOR 03905014 MSVCRTD:MSVCRTD.dll
0004:00000078 __imp__malloc_dbg 03905078 MSVCRTD:MSVCRTD.dll
0004:0000007с _imp chkesp 0390507с MSVCRTD:MSVCRTD.dll
0004:00000080 imp free_dbg 03905080 MSVCRTD:MSVCRTD.dll
0004:00000084 _imp initterm 03905084 MSVCRTD:MSVCRTD.dll
0004:00000088 _imp_jjrintf 03905088 MSVCRTD:MSVCRTD.dll
0004:0000008с _imp adjust_fdiv 0390508с MSVCRTD:MSVCRTD.dll
0004:00000090 _imp___dllonexit 03905090 MSVCRTD:MSVCRTD.dll
0004:00000094 _imp onexit 93905094 MSVCRTD:MSVCRTD.dll
0004:00000098 \177MSVCRTD_NULL_THUNK_DATA 03905098 MSVCRTD:MSVCRTD.dll
entry point at 0001:00000220
Line numbers for .\Debug\MapDLL.obj(D:\MapFile\MapDLL\MapDLL.cpp)
segment '. text
10 0001:00000030 12 0001:0000003b 19 0001:00000041 20 0001:00000046' 24 0001:0000004c 25 0001:00000050 26 0001:00000067 27 0001:0000006c 35 0001:00000076 36 0001:0000007a 37 0001:0000007f 38 0001:00000096 39 0001:0000009c 40 0001:0000009f 30 0001:000000a9 31 0001:OOOOOOad 32 0001:000000c4
Line numbers for g:\vc\LIB\MSVCRTD.lib(atonexit.c) segment .text
84 0001:00000320 89 0001:00000324 98 0001:0000035b 103 0001:00000360
104 0001:00000363 105 0001:00000376
Line numbers for g:\vc\LIB\MSVCRTD.lib(crtdll.c) segment .text
135 0001:00000110 140 0001:00000114 141 0001:0000011a 142
0001:00000123
143 0001:00000130 147 0001:00000132 156 0001:00000139 164
0001:00000147
170 0001:0000014d 175 0001:00000175 177 0001:0000017с 179
0001:00000187
184 0001:00000193 189 0001:OOOOOlaS 192 0001:000001b4 219 0001:OOOOOlbc
220 0001:000001c5 222 0001:000001cd 227 0001:OOOOOlel 228 0001:000001e9
236 0001:000001ee 238 0001:00000202 242 0001:0000020c 243 0001:00,000211
251 0001:00000220 252 0001:00000224 258 0001:0000022b 259 0001:0000023a
261 0001:00000241 263 0001:0000024d 264 0001:00000256 266 0001:0000026b
267 0001:00000271 269 0001:00000285 270 0001:0000028b 273 0001:0000028f
276 0001:000002a3 284 0001:000002af 287 0001:000002be 289 0001:000002ca
290 0001:000002df 292 0001:000002e6 293 0001:000002f5 296 0001:ОООООЗОа
297 0001:0000030d '
Exports
ordinal name
1 ?MapDLLFunction@@YAHXZ (int_cdecl MapDLLFunction(void))
2 ?MapDLLHappyFunc@@YAPADPAD@Z (char * _cdecl MapDLLHappyFunc(char *))
Создание и чтение МАР-файла
Многие не понимают, зачем создавать МАР-файлы в финальных построениях. Очень просто: потому что МАР-файлы являются единственным текстовым представлением глобальных символов программы, информации об ее исходном файле и о номерах строк в этом файле. Работать с утилитой CrashFinder намного проще, чем расшифровывать МАР-файлы, но зато для чтения последних не требуется (для получения той же самой информации) программа поддержки и наличие всех необходимых двоичных файлов программы (DLL, OCX и т. д.). Поверьте, если когда-нибудь в будущем вам потребуется вычислять, где произошла авария в старых версиях вашей программы, то нужную информацию удастся найти только в соответствующих МАР-файлах.
Можно создавать МАР-файлы для модулей, компилируемых как в Microsoft Visual C++, так и в Microsoft Visual Basic. В Visual C++ для этого нужно на вкладке Link диалогового окна Project Settings в редактируемой области Project Options допечатать ключи компоновщика /MAPINFO: EXPORTS и /MAPINFO:LINES. В списке Category следует выбрать элемент Debug и включить флажок Generate mapfile.
Если вы работаете с реальным проектом, то двоичные файлы, вероятно, направляются в собственный выходной каталог. По умолчанию компоновщик записывает МАР-файл в тот же каталог, что и промежуточные файлы, поэтому следует указать, что МАР-файл направляется в каталог вывода двоичных файлов. В редактируемом поле Mapfile name можно ввести $ (OUTDIR) \<проект>.МАР, где <проект> — имя конкретного проекта, $ (OOTDIR; — это макрос программы NMAKE.EXE, который система построения будет замещать реальным выходным каталогом. На рис. 8.1 показаны окончательные установки МАР-файла для проекта MapDLL, который включен в сопровождающий компакт-диск.
Создание МАР-файла для VB-модуля .включает установку тех же флагов, но иным, довольно интересным способом. Visual Basic использует тот же самый компоновщик (LINK.EXE), что и Visual C++, и некоторые ключи его командной строки можно устанавливать через переменную окружения LINK.
Если задать для нее значение " /MAP :<проект>.МАР/мдргаго: EXPORTS /MAPINFO:LINES", то Visual Basic будет генерировать МАР-файл на шаге компоновки процесса компиляции. Определив значение указанной переменной в окне Command Prompt, необходимо из этого же окна запустить и Visual Basic (чтобы переменная LINK находилась в зоне видимости программы VB6.EXE).
Возможно, в повседневной работе МАР-файлы и не нужны, но они могут понадобиться в будущем. Утилита CrashFinder и отладчик полагаются на таблицы символов и символьную машину для их чтения. Не забывайте регулярно сохранять РDВ файлы, что же касается таблиц символов, то они изменяются часто, и разработчик не имеет никакого контроля над их форматами. Например, те, кто обновлял версию 5 Microsoft Visual Studio (до версии 6), наверняка заметили, что инструменты типа CrashFinder переставали работать с программами, компилированными в новой версии (Visual Studio 6). Дело в том, что компания Microsoft изменила формат таблиц символов (и делает это регулярно). В этой ситуации МАР-файлы — единственное ваше спасение.
PDB — Program Data Base (база данных программы). — Пер.
Будь вы даже лет через пять разработчиком, пользующимся Windows 2005 и Visual Studio 11 Service Pack 6, можно поручиться, что найдутся заказчики, которые будут выполнять программы, созданные вами в далеком 1999 году. Когда они вызовут вас по тревоге и сообщат адрес ошибки, то неизвестно, сколько времени уйдет на поиски компакт-дисков с Visual Studio 6, необходимой для чтения сохраненных когда-то PDB-файлов. А при наличии МАР-файлов проблему можно будет найти за пять минут.