Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Роббинс Д. - Отладка приложений для Microsoft .NET и Microsoft Windows - 2004

.pdf
Скачиваний:
350
Добавлен:
13.08.2013
Размер:
3.3 Mб
Скачать

ГЛАВА 12 Нахождение файла и строки ошибки по ее адресу

453

 

 

Наконец, возможно, вас заинтересует содержание файла .P2M. Как я уже гово рил в главе 2, компактный код — хороший код. Однако узнать влияние различ ных ключей компилятора на размер отдельных функций можно только по обще му размеру двоичного файла. Кроме того, мы никак не можем узнать, как на кон кретную функцию влияет встраивание функций. Работая над PDB2MAP, я понял, что могу также сообщать размеры символов, потому что эту возможность поддер живает сервер символов DBGHELP.DLL. Поэтому, как показано в листинге 12 2, представляющем собой сокращенный файл .P2M, после заголовочной информа ции между адресом и именем функции выводится также ее размер. Вы увидите размеры почти всех функций, однако DBGHELP.DLL не гарантирует, что эта ин формация будет возвращена, поэтому в файлах .P2M возможно появление разме ров, равных 0.

Листинг 12-2. Сокращенный файл .P2M

PDB2MAP Generated Map File

Image: AssertTest

Timestamp is 3E0E7E2A > Sat Dec 28 23:46:34 2002

Preferred load address is 00400000

Address

Size

Function

 

 

 

 

0x00401050

36

??2@YAPAXI@Z

 

 

 

 

0x00401080

260

?MyThread@@YGKPAX@Z

 

 

 

 

0x00401190

38

?SleepThread@@YGKPAX@Z

 

 

 

0x004011C0

535

?TestThree@@YAXPAD@Z

 

 

 

 

0x004013E0

258

?TestTwo@@YAXXZ

 

 

 

 

0x004014F0

421

?TestOne@@YAXPAG@Z

 

 

 

 

0x004016A0

453

_wWinMain@16

 

 

 

 

0x00401A5E

6

_InitCommonControls@0

 

 

 

 

0x00401A64

6

_SuperAssertionW

 

 

 

 

. . .

 

 

 

 

 

 

 

 

Line

numbers for

 

 

 

 

 

 

d:\dev\booktwo\disk\bugslayerutil\tests\asserttest\asserttest.cpp

16

:

0x00401080

18

: 0x0040109F

19

: 0x004010CB

20

: 0x004 010DE

21

:

0x004010E5

22

: 0x0040113C

23

: 0x0040113E

26

: 0x004 01190

27

:

0x00401194

28

: 0x004011A8

29

: 0x004011AA

32

: 0x004 011C0

33

:

0x004011D7

39

: 0x004011DE

40

: 0x00401201

41

: 0x004 01207

43

:

0x00401223

44

: 0x0040127A

45

: 0x0040129D

46

: 0x004 012B2

47

:

0x0040131A

48

: 0x0040131F

49

: 0x00401334

50

: 0x004 0139C

53

:

0x004013E0

55

: 0x004013F7

57

: 0x0040140F

59

: 0x004 01427

60

:

0x0040143A

61

: 0x0040143C

62

: 0x0040143E

63

: 0x004 01498

64

:

0x004014A5

67

: 0x004014F0

68

: 0x00401515

70

: 0x004 01527

74

:

0x0040153E

76

: 0x00401548

78

: 0x004015CF

80

: 0x004 01632

81

:

0x00401638

82

: 0x0040163D

90

: 0x004016A0

91

: 0x004 016C4

92

:

0x0040171B

93

: 0x00401772

94

: 0x004017D2

96

: 0x004 01829

97

:

0x00401838

98

: 0x00401845

99

: 0x00401852

100

: 0x004 01854

см. след. стр.

454

 

ЧАСТЬ IV

Мощные средства и методы отладки неуправляемого кода

 

 

 

 

 

 

 

 

 

Line

numbers for f:\vs70builds\2292\vc\crtbld\crt\src\atonexit.c

 

 

76

:

0x00402810

81

:

0x00402814

90 : 0x0040284B

95 : 0x004 02850

 

 

96

:

0x00402853

97

:

0x00402866

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

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

Как вы только что увидели, читать MAP и P2M файлы не так уж и сложно. Одна ко это довольно утомительное и определенно немасштабируемое решение для других членов вашей группы, таких как сотрудники отдела контроля качества, техподдержки и даже руководители. Для улучшения масштабируемости CrashFinder я решил включить в отчеты об ошибках максимально подробную информацию, чтобы сделать его полезным для всех членов группы разработки: от программис тов до тестировщиков и инженеров по сопровождению программы. Если вы сле дуете моим советам и правильно создаете символы отладки (см. главу 2), все чле ны вашей группы смогут работать с CrashFinder без всяких проблем.

При использовании CrashFinder в группе вам нужно быть особенно вниматель ным к поддержанию доступа к двоичным образам и связанным с ними PDB фай лам, поскольку CrashFinder не хранит информации о вашем приложении, кроме путей к двоичным файлам. Это позволяет вам использовать один проект CrashFinder на всем протяжении цикла разработки. Если бы CrashFinder хранил более подроб ные сведения о вашем приложении, такие как таблицы символов, то вам, вероят но, нужно было бы создавать отдельный проект CrashFinder для каждой компо новки. Если вы примете мой совет и обеспечите легкий доступ к двоичным фай лам и PDB файлам, то при возникновении ошибки тестировщики и члены груп пы сопровождения должны будут только запустить CrashFinder и добавить важ ную информацию в отчет об ошибке. Все мы знаем, что чем больше мы знаем о конкретной проблеме, тем проще будет ее решение.

Для некоторых приложений вам, наверное, захочется создать несколько про ектов CrashFinder. Скажем, вы могли бы иметь один проект, указывающий на ме сто ежедневной компоновки, а также проекты для каждой версии программы, соответствующей контрольной точке. Если вы решите включить в проект Crash Finder системные DLL, вам нужно будет создать отдельные проекты для каждой поддерживаемой вами ОС. Вам также понадобятся отдельные проекты CrashFinder для всех версий программы, отсылаемых тестировщикам за пределы группы раз работки, поэтому для каждой такой версии нужно будет отдельно хранить двоич ные образы и PDB файлы.

Работа над CrashFinder оказалась весьма интересной. Говорят, он значительно облегчает труд, и я очень горжусь этим. Более того, целым рядом усовершенство ваний пользовательского интерфейса и функциональности CrashFinder обязан не мне, а другим талантливым программистам. К версии CrashFinder, которую вы найдете на CD, приложили руку Скотт Блюм (Scott Bloom), Чинг Минг Квок (Ching Ming Kwok), Джефф Шанхольтц (Jeff Shanholtz), Рич Петерс (Rich Peters), Пабло Преседо (Pablo Presedo), Джулиан Онионс (Julian Onions) и Кен Глэдстоун (Ken Gladstone). Всем им я очень признателен.

ГЛАВА 12 Нахождение файла и строки ошибки по ее адресу

455

 

 

На рис. 12 4 показан пользовательский интерфейс CrashFinder с одним из моих личных проектов. В верхней части дочернего окна — древовидный список, ото бражающий исполняемый файл и его DLL. Зеленые галочки говорят о том, что символы для каждого двоичного образа были загружены правильно. Если бы Crash Finder не смог загрузить символы, он указал бы на это при помощи красной бук вы X и развернул проблемный элемент списка, поясняя причину проблемы.

Рис. 12 4. Пользовательский интерфейс CrashFinder

Красный X появляется по трем причинам. Во первых, это может означать, что CrashFinder не нашел PDB файла, соответствующего двоичному файлу. Лучше всего хранить двоичные и PDB файлы в одном месте — тогда проблем быть не должно. Вторая причина в том, что при открытии сохраненного проекта CrashFinder больше не может найти двоичный файл. Наконец, проблема может быть обусловлена конфликтом адреса загрузки файла с какой либо из DLL проекта. ОС не допуска ет возможность конфликта DLL, поэтому CrashFinder также этого не позволяет. При конфликте загрузки вы можете изменить адрес конфликтующей DLL только для текущего проекта CrashFinder. Как я уже говорил, модификация базовых адресов DLL очень важна для успешного поиска ошибок.

Вся магия преобразования мистического адреса в информацию об исходном файле, функции и номере строки отображается в нижней части дочернего окна. Однако сначала я должен рассказать, как загрузить двоичные файлы в проект CrashFinder. Нажав на панели инструментов кнопку New, вы увидите стандартное диалоговое окно работы с файлами, предлагающее добавить в проект двоичный образ. Если у вас есть EXE файл, его нужно добавить в проект в первую очередь. В диалоговом окне Add Binary Image (добавить двоичный образ) вы можете выб рать несколько двоичных файлов, включив тем самым в CrashFinder сразу весь свой проект.

456 ЧАСТЬ IV Мощные средства и методы отладки неуправляемого кода

После выбора пункта Open (открыть) происходит много операций. В новую версию CrashFinder внесено одно отличное усовершенствование: теперь он авто матически ищет все неявно загружаемые DLL и добавляет их в проект. Если вы явно загрузили DLL как объекты COM, добавить их можно, выбрав в меню Edit (изме нить) пункт Add Image (добавить образ). CrashFinder также добавит в проект все дополнительные неявно скомпонованные модули.

Добавляя в проект двоичные образы, помните: проект CrashFinder может вклю чать только один EXE файл. Если приложение состоит из нескольких EXE фай лов, создайте для каждого отдельный проект CrashFinder. CrashFinder — приложе ние с многодокументным интерфейсом (multiple document interface, MDI), поэтому вы легко сможете открыть все проекты для всех EXE файлов. Когда вы добавляете в проект DLL, CrashFinder проверяет, не конфликтует ли ее адрес загрузки с про чими DLL проекта. Обнаружив конфликт, CrashFinder позволит изменить адрес заг рузки конфликтующей DLL только для текущего проекта. Это очень удобно, когда у вас есть проект CrashFinder для отладочной компоновки и вы случайно забыли модифицировать базовый адрес своих DLL.

По мере развития приложения вы можете удалять двоичные образы из проек та командой Remove Image (удалить образ) из меню Edit. Кроме того, вы всегда можете изменить адрес загрузки двоичного образа, выбрав пункт Image Properties (свойства образа) из меню Edit. Так как CrashFinder автоматически добавляет в проект системные DLL, нужные вашим двоичным файлам, вы сможете облегчить отладку, если используете сервер символов (см. главу 2). Теперь у вас есть еще более веская причина установки сервера символов: благодаря его поддержке Crash Finder’ом вы сможете изучать ошибки даже в системных модулях. Просмотрев информацию, изображенную для VERSION.DLL, вы увидите, что она загружает символы из моего сервера символов.

Назначение CrashFinder заключается в преобразовании адреса ошибки в ин формацию об исходном файле, имени функции и номере строки. Интересующие вас адреса нужно вводить в поле Hexadecimal Address(es) (шестнадцатеричные адреса) — в нижней половине дочернего окна. Когда вы нажмете на кнопку Find (найти), исходный файл, имя функции и номер строки появятся в поле вывода в низу окна. При желании вы можете ввести несколько адресов, разделив их про белами или запятыми. Так, вы можете ввести полный список адресов стека вызо вов из журнала Dr. Watson, получив сразу все нужные сведения.

По умолчанию CrashFinder не показывает смещение адреса от начала функции или от строки исходного кода. Чтобы увидеть эти сведения, укажите это в диало говом окне Options (свойства). Смещение от начала функции показывает, на сколько байт адрес отстоит от начала функции. Смещение от строки говорит, на сколько байт отстоит адрес от начала ближайшей строки исходного кода. Помните, что одной строке исходного кода могут соответствовать много ассемблерных команд, особенно если вы используете вызовы функций как параметры. Работая с Crash Finder, вы не сможете просмотреть адрес, не являющийся корректным адресом команды. Скажем, при неправильном обращении с указателем this вы можете столкнуться с ошибкой по такому адресу, как 0x00000001. К счастью, эти типы ошибок не так часты, как обычные ошибки нарушения доступа к памяти, кото рые CrashFinder находит с легкостью.

ГЛАВА 12 Нахождение файла и строки ошибки по ее адресу

457

 

 

Некоторые сведения о реализации

CrashFinder — простое приложение, написанное при помощи библиотеки MFC, поэтому большая его часть должна быть вам понятной. Тем не менее, чтобы вам было легче реализовать функции, которые я предлагаю в разделе «Что после CrashFinder?», мне хотелось бы пояснить три главных аспекта этой программы: работу сервера символов, функцию, в которой выполняется основной объем ра боты, и архитектуру данных CrashFinder.

CrashFinder использует сервер символов DBGHELP.DLL (см. главу 4). Я только хочу обратить ваше внимание на то, что загрузка всей информации об исходных файлах и номерах строк выполняется явно, путем передачи флага SYMOPT_LOAD_LINES

вфункцию SymSetOptions. По умолчанию сервер символов DBGHELP.DLL не загру жает информацию об исходных файлах и номерах строк.

Почти все операции CrashFinder обеспечивает класс документа, CCrashFinderDoc. Он содержит класс CSymbolEngine, выполняет весь просмотр символов и контро лирует представление данных. Ключевая функция CrashFinder, CCrashFinderDoc::Load AndShowImage, показана в листинге 12 3. Именно в ней осуществляется проверка корректности двоичного образа, его сравнение с существующими элементами проекта для предотвращения конфликтов адресов загрузки, загрузка символов и включение образа в конец дерева. Эта функция вызывается и при добавлении образа

впроект, и при открытии проекта. Выполнение всех этих задач в функции CCrash FinderDoc::LoadAndShowImage позволило мне сосредоточить базовую логику CrashFinder

водном месте и хранить в проекте только имена двоичных образов вместо ко пий таблицы символов.

Листинг 12-3.

Функция CCrashFinderDoc::LoadAndShowImage

 

 

 

 

BOOL CCrashFinderDoc :: LoadAndShowImage ( CBinaryImage * pImage

,

 

 

BOOL

bModifiesDoc

,

 

 

BOOL

bIgnoreDups

)

{

 

 

 

 

// Проверка данных, внешних по отношению к этой функции.

 

ASSERT ( this ) ;

 

 

 

ASSERT ( NULL != m_pcTreeControl ) ;

 

 

// Строка для вывода информационных сообщений.

 

 

CString

sMsg

;

 

 

// Состояние отображения дерева.

 

 

 

int

iState = STATE_NOTVALID ;

 

 

// Возвращаемое значение типа BOOL

 

 

 

BOOL

bRet

;

 

 

// Проверка правильности параметра. ASSERT ( NULL != pImage ) ;

if ( NULL == pImage )

{

// Недопустимое значение указателя. return ( FALSE ) ;

}

см. след. стр.

458ЧАСТЬ IV Мощные средства и методы отладки неуправляемого кода

//Проверка корректности образа. Если он корректен, я проверяю,

//не включен ли он уже в список и не конфликтует ли он с другими

//адресами загрузки. Если образ некорректен, я все равно добавляю

//его в список, потому что игнорировать данные пользователя нельзя.

//Если образ плохой, я просто отображаю его с соответствующим значком

//и не загружаю его в символьную машину.

if ( TRUE == pImage >IsValidImage ( ) )

{

//В этом блоке выполняется просмотр элементов массива данных,

//при этом осуществляется поиск следующих проблем.

//1. Двоичный образ уже находится в списке. Если это так,

//я могу только прервать операцию.

//2. Двоичный файл хочет загрузиться по адресу, который уже

//имеется в списке. В этом случае я вывожу для двоичного

//образа окно Properties, чтобы его адрес загрузки можно

//было изменить.

//3. Проект уже включает образ EXE, и pImage также

//указывает на исполняемый файл.

//Я всегда исхожу из предположения, что данные, на которые

//указывает pImage, корректны. Я — оптимист!

BOOL bValid = TRUE ;

INT_PTR iCount = m_cDataArray.GetSize ( ) ; for ( INT_PTR 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 ( ) )

{

if ( FALSE == bIgnoreDups )

{

// Сообщить об этом пользователю!! sMsg.FormatMessage ( IDS_DUPLICATEFILE ,

pTemp >GetFullName ( ) ) ; AfxMessageBox ( sMsg ) ;

}

return ( FALSE ) ;

}

//Если текущий образ из структуры данных некорректен,

//у меня неприятности. Выше я смог проверить совпадение

ГЛАВА 12 Нахождение файла и строки ошибки по ее адресу

459

 

 

//имен образов, однако конфликт адресов загрузки и попытку

//добавления двух EXE файлов проверить труднее. Если pTemp

//некорректен, я должен пропустить эти проверки. Это может

//привести к проблемам, но pTemp будет отмечен в списке как

//некорректный, поэтому пользователь сможет сам переназначить

//соответствующие свойства.

if ( TRUE == pTemp >IsValidImage ( FALSE ) )

{

//Проверка, позволяющая исключить

//добавление в проект двух EXE файлов. if ( 0 == ( IMAGE_FILE_DLL &

pTemp >GetCharacteristics ( ) ) )

{

if ( 0 == ( IMAGE_FILE_DLL & pImage >GetCharacteristics ( ) ) )

{

// Сообщить пользователю!!

 

sMsg.FormatMessage (

IDS_EXEALREADYINPROJECT ,

 

 

pImage >GetFullName ( ) ,

 

 

pTemp >GetFullName ( )

) ;

AfxMessageBox ( sMsg

) ;

 

//Попытка загрузки двух EXE образов приводит

//к автоматическому отбрасыванию данных

//для соответствующего pImage.

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 (

см. след. стр.

460

ЧАСТЬ IV Мощные средства и методы отладки неуправляемого кода

 

 

 

 

 

 

 

pImage >GetLoadAddress(),

 

iIndex

))

 

{

 

sMsg.FormatMessage

( IDS_DUPLICATELOADADDRFINAL , pImage >GetFullName ( ) ,

((CBinaryImage*)m_cDataArray[iIndex]) >GetFullName()); AfxMessageBox ( sMsg ) ;

//Данные pImage некорректны, поэтому

//выполняется выход из цикла.

bValid = FALSE ; break ;

}

}

else

{

//Данные pImage некорректны, поэтому

//выполняется выход из цикла.

bValid = FALSE ;

pImage >SetBinaryError ( eAddressConflict ) ; break ;

}

}

}

}

if ( TRUE == bValid )

{

// Этот образ в порядке (по крайней мере до загрузки символов). iState = STATE_VALIDATED ;

}

else

{

iState = STATE_NOTVALID ;

}

}

else

{

// Этот образ некорректен. iState = STATE_NOTVALID ;

}

 

if ( STATE_VALIDATED == iState )

 

{

 

bRet = (BOOL)

 

m_cSymEng.SymLoadModule64 ( NULL

,

(PWSTR)pImage >

 

GetFullNameString ( ) ,

NULL

,

pImage >GetLoadAddress ( )

,

ГЛАВА 12 Нахождение файла и строки ошибки по ее адресу

461

 

 

 

 

 

 

 

 

 

0

);

 

//Внимание! SymLoadModule возвращает

//адрес загрузки образа, а не TRUE. ASSERT ( FALSE != bRet ) ;

if ( FALSE == bRet )

{

TRACE ( "m_cSymEng.SymLoadModule failed!!\n" ) ; iState = STATE_NOTVALID ;

}

else

{

CImageHlp_Module cModInfo ; BOOL bRet =

m_cSymEng.SymGetModuleInfo64(pImage >GetLoadAddress(),

&cModInfo

);

ASSERT ( TRUE == bRet ) ; if ( TRUE == bRet )

{

// Проверка того, что тип символа не равен SymNone. if ( SymNone != cModInfo.SymType )

{

iState = STATE_VALIDATED ;

// Задание информации о символах образа. pImage >SetSymbolInformation ( cModInfo ) ;

}

else

{

iState = STATE_NOTVALID ;

//Выгрузка модуля. Символьная машина загружает

//модуль даже без символов, поэтому я должен

//выгрузить его. Я хочу, чтобы загружались

//только достойные этого модули.

m_cSymEng.SymUnloadModule64( pImage >GetLoadAddress());

pImage >SetBinaryError ( eNoSymbolsAtAll ) ;

}

}

else

{

iState = STATE_NOTVALID ;

}

}

}

//Для данного pImage устанавливается признак

//состояния загрузки символов.

if ( STATE_VALIDATED == iState )

{

pImage >SetExtraData ( TRUE ) ;

}

см. след. стр.

462 ЧАСТЬ IV Мощные средства и методы отладки неуправляемого кода

else

{

pImage >SetExtraData ( FALSE ) ;

}

//Элемент помещается в массив. m_cDataArray.Add ( pImage ) ;

//Изменился ли документ в результате добавления элемента? if ( TRUE == bModifiesDoc )

{

SetModifiedFlag ( ) ;

}

//Образ помещается в дерево.

bRet = m_cTreeDisplay.InsertImageInTree ( pImage , iState ) ;

ASSERT ( bRet ) ;

// Все OK!! return ( bRet ) ;

}

Наконец, два слова об архитектуре данных CrashFinder. Основная структура дан ных CrashFinder — простой массив объектов класса CBinaryImage. Объект CBinaryImage соответствует одному двоичному образу и содержит базовую информацию о нем, такую как адрес загрузки, свойства и имя. При добавлении в проект двоичного образа документ помещает объект CBinaryImage в основной массив данных и со храняет соответствующий указатель в слоте данных узла дерева. При выборе элемента дерева оно возвращает его указатель документу, который получает CBinary Image и просматривает его символьную информацию.

Что после CrashFinder?

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

Реализуйте поддержку двоичных файлов ОС и сделайте так, чтобы CrashFinder автоматически переключался между ними. Это повысит эффективность поис ка ошибок в коде ОС. В настоящий момент CrashFinder работает только с сис темными DLL, установленными на компьютере пользователя.

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

Соседние файлы в предмете Программирование на C++