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

         

Что делать дальше с утилитой 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 (многодокументный интерфейс). — Пер.

Рис. 8.2. Интерфейс пользователя утилиты CrashFinder

Если приложение изменится через какое-то время, то можно удалить ненужные двоичные образы, выбрав команду 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 (база данных программы). — Пер.

Рис. 8.1. Установки МАР-файла в диалоговом окне Project Settings

Будь вы даже лет через пять разработчиком, пользующимся Windows 2005 и Visual Studio 11 Service Pack 6, можно поручиться, что найдутся заказчики, которые будут выполнять программы, созданные вами в далеком 1999 году. Когда они вызовут вас по тревоге и сообщат адрес ошибки, то неизвестно, сколько времени уйдет на поиски компакт-дисков с Visual Studio 6, необходимой для чтения сохраненных когда-то PDB-файлов. А при наличии МАР-файлов проблему можно будет найти за пять минут.