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

         

Быстрое прерывание на произвольной функции


При установке точек прерывания существует много интересных возможностей, связанных со способностью отладчика Visual C++ оценивать выражения. Если известно имя функции, на которой нужно выполнить прерывание, то вместо утомительного поиска по всему исходному коду достаточно ввести это имя в редактируемое поле Break at. Если в одном из загруженных модулей существует соответствующий символ, то отладчик поместит точку прерывания на первую инструкцию этой функции. Если программа остановлена в отладчике, а имя введенной функции неправильно, то отладчик выведет панель с сообщением. При вводе имени С++-функции нужно вводить также и квалификатор соответствующего класса. Например, чтобы организовать прерывание на методе Опок класса coiaiog из библиотеки MFC, следует ввести coialog: :0nOk.

Отладчик достаточно развит, чтобы учитывать перегруженные (overloaded) члены классов, и он подсказывает имена конкретных версий перегруженных функций в специальном окне. Например, если для установки точки прерывания в MFC-приложении ввести в поле Break at имя cstring: :cstring как имя функции, на которой следует установить точку прерывания, то отладчик не будет знать, какая версия конструктора класса cstring интересует программиста и подскажет их в списке Symbols диалогового окна Resolve Ambiguity, показанного на рис. 5.2. В этом окне перечисляются (в расширенном синтаксисе точек прерывания) восемь конструкторов cstring, из которых и нужно выбрать подходящую версию.

Рис. 5.2. Диалоговое окно Resolve Ambiguity

Самый легкий способ устанавливать точки прерывания на сложных функциях, таких как operator-функции классов, состоит в том, чтобы напечатать достаточно информации для показа в диалоговом окне Resolve Ambiguity. Например, MFC-класс cstring имеет перегруженные операторы назначения, и чтобы увидеть их список в окне Resolve Ambiguity, нужно напечатать в поле Break at cstring: :operator= (operator-функция класса cstring). Чтобы выполнить прямую установку точки прерывания, в поле Break at можно также указать параметры функции (если они известны). Например, вместо CString: :operator= можно напечатать CString: :operator=(const char *) И вполне обойтись без диалогового окна Resolve Ambiguity.



Команда Set Next Statement




Одним из самых известных скрытых свойств отладчика является команда Set Next Statement. Она доступна как в окне исходного кода, так и в окне Disassembly — по контекстному меню (открываемому щелчком правой кнопки мыши), но только тогда, когда выполняется отладка. Команда Set Next Statement позволяет изменять указатель инструкции (команды), т. е. устанавливать его на различные места программы. То же самое можно сделать и прямой установкой регистра EIP. Изменение точки выполнения программы — фантастическая отладочная техника, особенно если во время блочного тестирования требуется протестировать собственные обработчики ошибок.

Имейте в виду, если не проявить чрезвычайную осторожность, то изменение указателя инструкции может легко привести к аварийному завершению программы. Если программа выполняется в режиме отладки, то использование Set Next Statement не приводит к большим неприятностям. Однако в оптимизированном режиме финальной сборки безопаснее всего выполнять команду Set Next Statement только в окне Disassembly. Компилятор будет перемещать код так, что исходные строки могут и не выполняться линейно. Кроме того, нужно знать, создает ли ваш код временные переменные в стеке во время использования Set Next Statement. В главе 6 последняя ситуация рассмотрена более подробно.

Если гипотеза состоит в том, что обнаруженная ошибка могла быть в некоторой ветви кода, надо установить точку прерывания в отладчике перед подозрительной функцией или функциями. Затем проверить данные и параметры, входящие в функции и выполнить пошаговый проход через эти функции (step over). Если проблема не дублируется, выполните команду Set Next Statement, чтобы вернуть точку выполнения назад, к точке прерывания, и измените данные, входящие в функции. Эта тактика позволит протестировать несколько гипотез в одной отладочной сессии, экономя таким образом время. Нетрудно видеть, что невозможно применять эту технику во всех случаях, потому что, как только вы выполняете некоторый код в своей программе, его повторное выполнение может нарушить ее состояние. Set Next Statement лучше всего работает на коде, который не слишком сильно изменяет свое состояние.


Как упоминалось ранее, команда Set Next Statement удобна во время блочного тестирования. Например, она полезна, когда требуется тестировать обработчики ошибок. Скажем, в if-операторе необходимо проверить, что случится, если условие не выполнится. Все, что нужно сделать в этом случае, — это позволить условию выполниться и использовать команду Set Next Statement, чтобы переместить точку выполнения вниз, до ветви с отказом. В дополнение к Set Next Statement, одноразовую (one-shot) точку прерывания позволяет устанавливать пункт меню Run To Cursor, доступный также в меню Debug. При тестировании я также изредка использую Run To Cursor. Превосходный пример применения Set Next Statement для тестировании или отладке — заполнение структур данных (особенно списков и массивов). Пусть в некотором коде заполняется структура данных и затем эта структура добавляется к связному списку. Чтобы добавить к этому списку несколько дополнительных элементов, можно выполнить Set Next Statement (так что можно будет видеть, как выполняется обработка). Такое использование Set Next Statement особенно удобно, когда при отладке нужно установить режим трудно дублируемых данных.





Модификаторы позиционных точек прерывания


Мы рассмотрели вопросы правильного размещения экспортируемых функций, и теперь читатель должен уверенно устанавливать позиционную (location) точку прерывания в любом месте приложения. Роль позиционных точек прерывания в отладочном процессе довольно значительна, но, как было указано в начале данной главы, к ним можно также добавить некоторый "интеллект" с целью повышения эффективности работы отладчика. Носителями этих "интеллектуальных" возможностей являются специальные модификаторы позиционных точек прерывания: счетчики пропусков, условные выражения и изменения переменных.

Счетчики пропусков

Самый простой модификатор позиционной точки прерывания (Tll) — это счетчик пропусков (skip count). Он указывает отладчику, что нужно вставить точку прерывания, но пропускать ее выполнение (т. е. не приостанавливать приложение в этой точке) указанное количество раз. С помощью этого модификатора легко решается проблема прерывания на нужной итерации цикла.

Добавлять счетчик пропусков к простой точке прерывания довольно легко. Сначала установите обычную позиционную точку прерывания и раскройте диалоговое окно Breakpoints. Выделите позиционную точку прерывания в списке Breakpoints, и щелкните кнопку Condition. Затем в нижнем редактируемом поле диалогового окна Breakpoint Condition введите нужное число пропусков точки прерывания.

Когда отладчик, наконец, остановит выполнение приложения в такой точке, он сообщит, сколько раз выполнялся помеченный им оператор. Если есть цикл, в котором имеет место аварийный останов, но неизвестно, на какой итерации это происходит, то нужно добавить позиционную точку прерывания со счетчиком пропусков к строке какого-нибудь оператора внутри цикла. Значение счетчика пропусков должно быть больше, чем общее число итераций цикла. Когда программа завершится аварийно, откройте диалоговое окно Breakpoints и посмотрите на точку прерывания, обозначенную в списке Breakpoints1. После строки описателя точки прерывания располагается остаток счетчика пропусков (в круглых скобках).
Знание различных псевдорегистров, которые обеспечивают доступ к регистровым и специальным значениям, а также творческий подход — вот два секрета применения условных выражений. Например, хотя отладчик Visual C++ не имеет явного метода для установки позиционной точки прерывания, которая срабатывает только в определенном потоке под Windows 2000, но если установить выражение @TIВ=линейный адрес TIB, то прерывание произойдет только на указанном потоке. Первый шаг заключается в том, чтобы ввести псевдорегистр @ТIB в окно Watch и найти линейный адрес информационного блока потока, на котором требуется выполнить прерывание. Чтобы активизировать поток, который надо проверить, можно использовать диалоговое окно Threads отладчика. Если, например, поток содержит в псевдорегистре @Т1В адрес, равный Ox7FFDEOOO, то выражение выглядит так: @TIB == Ox7FFDEOOO. Для Windows 98 нужно просмотреть регистр FS, который является уникальным для каждого потока, и задать выражение @FS == значение конкретного потока (thread specific value).

Для того чтобы выполнить прерывание, основанное на конкретном коде последней ошибки (last error code), можно использовать псевдорегистр @ERR. Например, чтобы выполнить прерывание после API-вызова, который, согласно документации Platform SDK, может сгенерировать последнюю ошибку ERROR_FILE_NOT_FOUND, выражение должно выглядеть так: @ERR==2. Числовое значение ошибки с идентификатором ERROR_FILE_NOT_FOUND можно найти в файле WINERROR.H. Все псевдорегистры перечислены в табл. 5.1.

Наконец, из-за того что нельзя вызывать функции в таких выражениях, прерывание на строке с конкретным (строчным) значением затруднительно.

 TIB — Thread Information Block, информационный блок потока. — Пер.

В этом случае просто установите выражение, которое проверяет каждый символ, например, так:

(szBuff[0] = 'Р')-&& (szBuff [l] = 'a'j&& (SzBuff [2] == 'm').

Другой способ использования условных выражений в прерываниях: можно комбинировать их со счетчиком пропусков.


Такой прием позволяет выполнить прерывание, когда n-ое значение выражения станет равно true (где п — исходное значение счетчика пропусков).

Таблица 5.1. Выражения и псевдорегистры окна Watch

Псевдорегистр

Описание

©ERR

Значение последней ошибки; то же значение возвращается API-функцией GetLastError

©TIB

Информационный блок текущего потока; необходим, потому что отладчик не обрабатывает формат FS:0

©CLK

Недокументированный регистр часов; используется только в окне Watch

@ЕАХ, @ЕВХ, ©ЁСХ, @EDX, ©ESI, ©EDI, ©EIP, ©ESP, ©EBP, ©EFL

Регистры Intel CPU

@CS, @DS, @ES, @SS, @FS, ©GS

Сегментные регистры Intel CPU

©STO, ©ST1, @ST2, ©ST3, ©ST4, ©ST5, ©ST6, ©ST7

Регистры чисел с плавающей точкой Intel CPU

Изменения переменных

Последний модификатор позиционных ТП связан с изменением значения переменной. Позиционная ТП с этим типом модификатора останавливает отладчик, когда изменяется значение указанной в ней переменной. Следует помнить, что переменная проверяется только тогда, когда позиционная точка прерывания срабатывает (т. е. когда отладчик останавливает выполнение приложения). Этот модификатор применяется, например, если известно, что в функции более высокого уровня происходит перезапись памяти, и необходимо свести к минимуму ущерб, который может при этом принести вызов функции более низкого уровня. В такой ситуации установите позиционные точки прерывания после каждого вызова функции и выполните в них проверку изменения значения переменной. Преимуществом данной методики является возможность просмотра отладчиком целого буфера, если это необходимо.

Добавление этого модификатора происходит точно так же, как добавление других модификаторов позиционных точек прерывания — с использованием диалогового окна Breakpoint Condition для установки необходимых условных параметров. Единственное недоразумение может возникнуть из-за того, что редактируемое поле (Enter the expression to be evaluated), в которое вводится наблюдаемая переменная, является одновременно и полем для ввода условного выражения.


В среднем редактируемом поле диалогового окна Breakpoint Condition (с именем Enter the number of elements to watch in an array or structure) пользователь сообщает отладчику, сколько элементов массива или области памяти надо наблюдать. Если значение, на котором требуется выполнить прерывание, является разыменованием указателя, например, *pMyData, то в это поле нужно ввести число наблюдаемых байт.

 Чтобы открыть эту панель, нужно установить на подходящем исходном операторе простую позиционную точку прерывания и, не изменяя позиции курсора ввода, ввести команду Edit|Breakpoints, Затем в открывшемся диалоговом окне Breakpoints ввести в поле Break at вкладки Location номер строки (после символа "точка"), в которой установлена данная точка прерывания (например, .47), и нажать кнопку Condition (под полем Break at). — Пер.

 Если же ввести в поле Enter the expression to be evaluated сам указатель pMyData (без звездочки), то прерывание произойдет, когда изменится сам указатель, указывающий на другую область памяти. — Пер.



Окно Watch


Окно Watch занимает высокое место в списке важных свойств отладчика Visual C++. Что делает окно Watch чрезвычайно популярным — так это его разносторонность. Можно по-разному работать с этим окном, собирая информацию о приложении. Самая впечатляющая возможность заключается в том, что оно позволяет легко изменять" значение переменной, редактируя его в правой стороне окна. Вспомните обсуждение синтаксиса для выражений точек прерывания. В окне Watch используется такая же оценка выражений, поэтому и синтаксис расширенных точек прерывания, и правила составления выражений, и псевдорегистры могут использоваться и в окне Watch.

Форматирование данных и оценка выражений

Чтобы приобрести опыт манипулирования окном Watch, необходимо запомнить форматирующие символы, приведенные в табл. 5.3 и 5.4, которые получены из документации Visual C++ на MSDN. Форматный символ записывают через запятую после переменной. Наиболее полезный спецификатор формата для СОМ-программирования — hr. Если разместить в окне Watch выражение @ЕАХ,hr, то при переходе через вызов СОМ-метода можно увидеть результат вызова в понятной форме (@ЕАХ — это регистр Intel CPU, в котором хранятся возвращаемые значения). Применение форматных спецификаторов позволяет легко управлять видом данных и экономить огромное количество времени на их интерпретацию.

Таблица 5.3. Символы форматирования для переменных окна Watch

Символ

Описание формата

Пример

Вид на экране

d, i

Десятичное целое со знаком

(int)OxFOOOF065,d

-268373915

u

Десятичное целое без знака

0x0065, u

101

о

Восьмеричное целое без знака

OxF065,o

0170145

х, X

Шестнадцатеричное целое

61541, X

OxOOOOF065

1 , h

Префикс long или short для d, i, u, о, х, X

0x00406042, hx

OxOc22

£

Плавающая точка со знаком

3./2.,f

1.500000

е

Научная нотация со знаком

3./2,е

1.500000e+000

g

Плавающая точка или научная нотация со знаком, в зависимости от того, какой формат короче

3./2,g

1.5

с

Одиночный символ

0x0065, с

'e1

s

Строка

szHiWorld, s

"Hello world"

su

Строка Unicode

szWHiWorld, su

"Hello world"

st

Строка Unicode или ANSI, в зависимости от установки в AUTOEXP.DAT



hr

HRESULT или Win32 код ошибки

0x00000000, hr

S_OK

we

.Флажок класса Windows

0x00000040, we

WC_

DEFAULTCHAR (Заметим, что хотя этот формат и докуме

нтирован он не работает в Visual C++ 6)

wm

Номера Windows-сообщений

0x0010, wm

WM_CLOSE


Таблица 5.4. Символы форматирования для дампов памяти окна Watch

Символ

Описание формата

Пример

Вид на экране



64 ASCII символы

Ox0012ffac,ma

0x001 2ffac .4.0.".OW&.

.1W&.0.:W.1 .."1.JO&.12.

."1..0y.1

m

16 байт (шестнад-цатеричных), за которыми следует 16 ASCII символов

Ox0012ffac,m

0x001 2ffac

 ЬЗ 34 cb 00 84 30 94 80 ff                  22 8а 30 57 26 00               00 .4...0...".OW&..

rob

16 байт (шестнад-цатеричных), за которыми следует 16 ASCII символов

Ox0012ffac,mb

0x001 2ffac ЬЗ 34 cb 00 84        30 94 80 ff 22 8a 30 57          26 00 00 A..O...".OW&..

mw

8 слов

Ox0012ffac,mw

0x001 2ffac 34b3 OOcb 3084         8094 22ff 308a 2657 0000

md

4 двойных слова

Ox0012ffac,md

0x001 2ffac ООсЬ34ЬЗ 80943084 308a22ff 00002657

mq

4 четверных слова

Ox0012ffac,mq

0x001 2ffac 8094308400cb34b3 00002657308a22ff

mu

2-байтовые символы (Unicode)

Ox0012ffac,mu

0x001 2ffac

34ЬЗ OOcb 3084 8094 22ff 308a 2657 0000 7 99797

#

(Не документирован). Расширяет указатель (на область памяти) на указанное число значений

pCharArray, 10

Расширенный массив из 10 символов (использующий расширители + /-)

Недокументированный спецификатор формата позволяет расширить область памяти, указателя на указанное число значений. Если имеется указатель на массив из 10 длинных целых чисел (long int), окно Watch покажет только первое значение. Чтобы видеть весь массив, поместите за переменной количество значений, .которое хотите наблюдать. Например, указатель с расширителем (pLong, 10) покажет расширяемый массив из 10 элементов.


Если имеется большой массив, то можно сместить указатель к его середине, а в расширении указать то количество значений, которое требуется отобразить. Например, переменная с форматом (pBigArray+100) ,20 показывает в окне Watch 20 элементов со смещением 99. При этом имеется ошибка: значения индекса всегда начинаются с 0, независимо от позиции первого отображенного элемента от начала массива. В примере с pBigArray первый индекс, показанный как.0, является 100-м элементом массива, второй — 101-м элементом и т. д.

Кроме широких возможностей форматирования данных по желанию разработчика, окно Watch позволяет выполнять приведение типов переменных и показывать их в любой нужной ему форме. Например, чтобы получить смещения указателя, можно использовать выражения BY, WO и DW. Разрешены также адресная операция (&) и операция указателя (*). Обе они позволяют получать значения адресов памяти и наблюдать результаты операций приведения типов. В окне Watch можно явно указывать контекст переменной, если применять спецификаторы контекста, описанные выше в разделе "Синтаксис расширенных точек прерывания и позиционные точки прерывания" этой главы. Наконец, все форматы и спецификаторы, используемые в окне Watch, работают также и в окне QuickWatch.

Нетрудно заметить, что окно Watch предоставляет гораздо больше возможностей, чем простой просмотр статических переменных. Фактически, это средство оценки выражений, в котором можно проверять любые условные операторы. Во время тестирования элементов своих программ я очень широко использую окно Watch для проверки операторов if и других условных операторов. Для этого необходимо поместить в окно Watch индивидуальные переменные условного оператора, а вслед за ними — сам условный оператор. Это позволяет видеть как значения каждой переменной, так и результаты условной оценки. Для того чтобы изменить оценку условного оператора, можно сначала изменять значения индивидуальных переменных и прослеживать вывод в окне Watch. Из-за того что окно Watch так хорошо управляет выражениями, программисту не нужно для несложных вычислений открывать программу Калькулятор.


Простые вычисления можно выполнять прямо в окне Watch.

Таймирование кода в окне Watch

Еще один изящный прием — применение окна Watch для наблюдения за временем выполнения участков программы. В качестве элементарного таймера может служить недокументированный псевдорегистр @CLK. Во многих случаях нужно только грубое представление временного интервала между двумя точками программы, и @CLK помогает уточнить время выполнения между двумя точками прерывания. Имейте в виду, что это время включает накладные расходы отладчика. Весь фокус заключается в том, чтобы ввести (в окно Watch) два элемента наблюдения @CLK: первый — просто "@CLK" и второй — "@CLK=0". Второй элемент сбросит таймер (установит на 0) после того, как выполнение возобновится. Поскольку время измеряется в микросекундах, а я предпочитаю — в миллисекундах, то устанавливаю первый @CLK в формате @CLK/1000,d. Форматный символ ,d введен для того, чтобы показать таймер в виде десятичного числа, если в окне Watch предварительно установлен шестнадцатеричный формат отображения (Hexadecimal Display). Хотя таймер @CLK и недостаточно совершенен, но для приближенных подсчетов он вполне пригоден.

Вызов функций в окне Watch

И последнее: окно Watch наделено способностью выполнять функции внутри отладчика. Можно задать вопрос: "А зачем это нужно?" А для того, чтобы полностью настроить отладочную среду на требования пользователя. Например, вместо десятиминутного просмотра 10 различных структур данных (чтобы убедиться в их однородности), можно написать специальную функцию, которая проверяет данные, и затем вызывать ее прямо из окна Watch, когда это необходимо (например, когда отладчик останавливает приложение).

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


При вызове функции в окне Watch ей можно передавать параметры, а это позволяет создавать функции-шаблоны, работающие на различных типах данных. Окно Watch можно представлять себе как ограниченное окно Immediate из IDE Microsoft Visual Basic.

Если отладочная функция не имеет параметров, не забывайте использовать при ее вызове круглые скобки с пустым списком параметров, чтобы указать отладчику Visual C++, что вызывается функция, а просматривается значение переменной. Например, если речь идет об отладочной функции void MyMemCheck (), надо вызвать ее в окне Watch в формате MyMemCheck (). Если же отладочная функция получает параметры, просто передайте их ей, как будто вызываете обычную функцию1. Если отладочная функция возвращает значение, то оно будет выведено в правой части окна Watch.

При вызове отладочных функций в окне Watch вы встретитесь с рядом ограничений. Эти ограничения не вызывают никаких трудностей, если придерживаться нескольких правил. Первое правило: пока функция находится в окне Watch, она может выполняться только в однопоточном контексте. Если имеется многопоточная программа, необходимо ввести отладочную функцию в окно Watch, проверить результаты и затем немедленно удалить ее из окна. Если отладочная функция выполнится в другом потоке (отличающемся от первого), то второй поток немедленно завершится. Второе правило: отладочная функция должна выполняться меньше чем за 20 секунд; если за это время она выдаст исключение, то отладчик завершит всю программу. Последнее правило: такая функция должна только читать память и проверять данные. Если возникает проблема, просто вызовите функцию OutputDebugstring или printf. Причем неизвестно, что может случиться, если программист начнет изменять память или вызывать API-функции Windows.

 То есть укажите в круглых скобках вслед за именем функции список соответствующих аргументов. — Пер.

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


Это случается при следующих условиях:

 если при выполнении программы срабатывают точки прерывания; П при одношаговом проходе строки или инструкции;  если по завершении редактирования текста отладочной функции (в левой части окна Watch) нажата клавиша <Enter>;  когда происходит исключение в выполняющейся программе, и управление передается опять в отладчик. Еще раз подчеркну: у вас должно войти в привычку введение специальной отладочной функции, выполнение с ее помощью оценки и немедленное ее удаление из окна Watch. Это позволяет избежать любых сюрпризов и контролировать выполнение отладочной функции. Кроме того, не нужно создавать отладочные функции для каждой мелочи в приложении. Их надо писать только для наиболее критических структур данных, особенно если это структура, которую необходимо видеть целиком, или для того, чтобы проверять достоверность данных, которые должны быть скоординированы. Например, если предполагается, что структура А имеет поле, которое соответствует полю в структуре В, и оба поля должны быть обновлены, чтобы поддержать согласованность данных, то отладочная функция, которую можно вызвать из окна Watch, является хорошим средством координации полей. Наконец, не пытайтесь программировать дамп-функций для индивидуальных структур. Окно Watch само расширяет такие структуры, так что не нужно тратить впустую время, повторно изобретая это колесо.

Автоматическое расширение собственных типов

Хотя документация Visual C++ только упоминает эту тему, программист может создавать собственные типы, автоматически расширяемые в окне Watch, так же, как и в окнах QuickWatch и DataTips. Вы, вероятно, видели некоторые общие типы, такие как cobject и RECT, расширенные в окне Watch. Так вот, можно легко организовать дело таким образом, чтобы выгоду из расширяемости окна Watch извлекали ваши собственные типы. Весь фокус заключается в текстовом файле AUTOEXP.DAT из подкаталога Microsoft Visual Studio\Common\MSDev98\Bin. Просто добавьте в конец файла вход для своих собственных типов.



В качестве примера рассмотрим добавление входа с авторасширением  для структуры PROCESS_INFORMATION, которая посылается в API-функцию createProcess. Первый шаг состоит в проверке того, что отладчик Visual C++ распознает в качестве типа. В примере программы переменная PROCESS_INFORMATION помещена в окно Watch, на ней выполнен щелчок правой кнопкой мыши и выбран пункт Properties контекстного меню. В диалоговом окне Program Variable Properties в качестве метки (имени) типа указан идентификатор _PROCESS_INFORMATION, который, если посмотреть на определение структуры, приведенное ниже, соответствует метке структуры.

typedef struct _PROCESS_INFORMATION {

HANDLE hProcess;

HANDLE hThread;

DWORD dwProcessId;

DWORD dwThreadld;

 } PROCESS_INFORMATION

Документация в AUTOEXP.DAT говорит, что формат для авторасширяемого входа таков:

тип = [текст]<член[,формат]>....

В табл. 5.5 показаны значения каждого поля этого формата. Обратите внимание, что в авторасширении можно показать несколько членов.

Таблица 5.5. Входы авторасширений в AUTOEXP.DAT

Поле

Описание

Тип

Имя типа. Для шаблонных типов, за этим полем может следовать поле со звездочкой "<*>", чтобы охватить все производные типы

Текст

Любой литеральный текст. Эта поле, в общем случае, есть имя члена или его короткая версия

Член

Фактический член данных, который будет показан в окне Watch. Этим полем может быть выражение (так, если нужно добавить некоторые смещения к различным указателям, то в вычисление можно включить смещения). Работают также операции приведения типов

Формат

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

Если с помощью структуры PROCESS_INFORMATION требуется просмотреть значения hProcess и hThread, правило авторасширения должно выглядеть так:

_PROCESS_INFORMATION=hProcess=<hProcess,Х> hThread=<hThread,X>

Спецификатор формата , х указывает, что значения отображаются в шестнадцатеричной форме.

В файле AUTOEXP.DAT можно увидеть один специальный форматирующий код— <,t>. Этот код просит отладчик разместить в качестве имени типа имя максимального (по уровню) производного типа. Например, если имеется базовый класс А с производным классом В, и только А имеет правило авторасширения, то авторасширением для переменной типа В будет имя класса В, за которым следует правило авторасширения для класса А. Формат <, t> очень полезен для прямой поддержки классов.



Отладка компилированного кода Visual Basic


Отладка компилированного кода Visual Basic включает проверку и отладку приложения в форме р-кода (перед его компиляцией). Отладка компилированного кода Visual Basic не столь проста, как отладка С-кода, потому что отладочная информация, которую генерирует Visual Basic, не содержит достаточных сведений о типах. Следовательно, отладчик не может расшифровывать различные объекты. Прежде чем обращаться к отладчику Visual C++, нужно использовать инструмент независимого поставщика, такой как программа SmartCheck фирмы Compuware NuMega, потому что это намного облегчает отладку в Visual Basic. Программа SmartCheck знает, как конвертировать сложные сообщения об ошибках Visual Basic в такие условия, которые точно выводят на проблему, так что не нужно использовать отладчик Visual C++. SmartCheck может также воспроизводить полный поток приложения Visual Вазютак, что пользователь может видеть, как выполняется его программа. В случае с компилированным кодом можно упростить отладку, дополнительно использовав условно компилированные вызовы функции Outputoebugstring. Во многих случаях оказалось легче отлаживать компилированный Visual Basic код на уровне языка ассемблера. Допускаю, что это звучит дико, но это действительно так. Материал главы 6 поможет читателю совершенствоваться в навыках работы на языке ассемблера.

Чтобы приготовить файлы Visual Basic к отладке, нужно сначала (во время компиляции) сгенерировать PDB-файлы. Можно установить этот режим на вкладке Compile диалогового окна Project Properties. На ней также устанавливается режим компилятора No Optimizations. Только не забудьте возвратиться в режим оптимизации, когда начнете построение коммерческой версии.

Для того чтобы можно было видеть локальные переменные стандартного типа в окне Variables Visual C++, перейдите на вкладку Locals. Visual Basic использует много временных переменных, поэтому на вкладке показано много переменных типа unnamed_vari. Если прокрутить экран вниз к основанию окна, то можно увидеть локальные переменные.


Начав работать с Visual Basic, я был озадачен, когда получил случайное исключение "Floating-point inexact result" (Неточный результат операции с плавающей точкой). Программа не выполняла никаких действий с плавающей точкой, так что я понятия не имел, откуда могло взяться это сообщение. Проделав некоторую поисковую работу, я обнаружил, что в Visual Basic реализована собственная версия структурированной обработки исключений (SEH). К сожалению, она использует в качестве одного из исключений значение EXCEPTION_FLT_INEXACT_RESULT, и когда исключение не  обработано, появляется ложное сообщение об исключении.

Одна уловка, которую используют некоторое мои коллеги, особенно когда они тестируют компилированный элемент управления ActiveX, состоит в выполнении полной среды Visual Basic под отладчиком Visual C++. Этот прием позволяет отлаживать элемент управления ActiveX и отводит программе на р-коде роль тестового приспособления. Способность переключаться между тестовой программой на р-коде и компилированным компонентом позволяет легче видеть обе стороны проблемы.



Расширенные точки прерывания


Установка точки прерывания на исходной строке в отладчике Visual C++ (для проектной конфигурации Win32 Debug или Win32 Unicode Debug) довольно проста: загрузите исходный файл, поместите курсор на строку, где требуется остановить выполнение, переместите указатель мыши на кнопку Insert/Remove Breakpoint и щелкните левой кнопкой мыши.

Эта кнопка находится в панелях команд Buid и BuildMiniBar Visual C++. — Пер

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

На заре компьютеризации точек прерывания просто не существовало. Единственная "стратегия" для обнаружения ошибок состояла в том, чтобы выполнять программу до тех пор, пока она не завершится аварийно, и затем, страница за страницей, пробираться через распечатки шестнадцатеричных дампов памяти, отыскивая проблему. Единственными отладчиками в те мрачные для отладки времена были операторы трассировки и вера в Бога. В эпоху отладочного Ренессанса, наступившую после появления языков более высокого уровня, разработчики смогли устанавливать точки прерывания, но должны были выполнять отладку только на уровне языка ассемблера. Языки более высокого уровня еще не имели никаких средств для просмотра локальных переменных или наблюдения за программой в исходной форме. Эра современной отладки началась тогда, когда языки программирования развились в более сложные инструменты, а разработчики стали устанавливать точки прерывания на строках исходного кода и просматривать переменные на дисплее, который интерпретировал значения переменных в точном соответствии с их типом. Такие простые позиционные точки прерывания (location breakpoint) до сих пор остаются чрезвычайно мощным средством отладки: они позволяют решить 99,46 процентов отладочных проблем.

Тем не менее, работа с такого рода точками прерывания очень быстро может стать утомительной.
Что будет, если установить точку прерывания на строке внутри цикла for, который выполняется 10 000 раз, а ошибка обнаружится на последней итерации? Во-первых, устанут руки (придется 10 000 раз выполнить команду Go), а во-вторых, нахождение ошибочной итерации займет много времени. Было бы хорошо иметь некоторый способ сообщить отладчику, что точка прерывания не должна срабатывать 9 999 раз перед остановкой.

 В связи с этим точки прерывания подобного типа будем называть позиционными точками прерывания (location breakpoint). Это самый простой из нескольких типов прерываний, с которыми работает Microsoft Visual С. — Пер.

 То есть расположенные в определенной позиции исходного кода. — Пер.

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

 пропустить точку прерывания (перед ее выполнением) определенное количество раз;  выполнить прерывание, когда отмеченное этой точкой выражение примет значение true;  выполнить прерывание, когда изменится переменная или адрес памяти. Расширенные возможности точек прерывания, в конце концов, серьезно усовершенствовали отладчики, позволяя разработчикам за считанные минуты делать то, на что при использовании простых позиционных точек прерывания тратились часы.



в этой главе описан первый



У отладчика Visual C++ много мощных свойств, и в этой главе описан первый их набор — расширенные точки прерывания и удаленная отладка. Самый важный вывод заключается в следующем: отладчик может выполнять значительный объем работ, если вы знаете, как эффективно его применять. Чтобы минимизировать время, проводимое в отладчике, нужно стремиться максимально использовать его возможности.
Расширенные точки прерывания помогают избежать утомительных сеансов отладки, позволяя определить точные условия, при которых срабатывает точка прерывания. Контекстная часть расширенного синтаксиса точки прерывания служит для того, чтобы сообщить отладчику область действия точек прерывания и их точное расположение. Стандартная, позиционная точка прерывания имеет три модификатора, которые позволяют управлять моментом ее срабатывания: счетчики пропусков, условные выражения и изменения переменных. Другие типы точек прерывания — это точки прерывания, связанные с глобальными выражениями и изменениями глобальных данных. Из этих двух типов наиболее полезны точки прерывания, связанные с изменениями глобальных данных. Они позволяют установить место в памяти, связанное с аппаратно-поддерживаемой точкой прерывания. Такая точка прерывания срабатывает тогда, когда в это место записываются данные.
Удаленная отладка — это двухмашинное отладочное решение, которое является серьезным дополнением к комплекту инструментов отладки. Это один из немногих способов, с помощью которых можно отлаживать трудные ситуации, например, при работе с кодом активации окна, обработке проблем рисования, освобождении памяти для приложения, отладке в Windows 98 и попытке избежать изменения состояний в машине.
Другое полезное дополнение к вашему отладочному арсеналу — большое количество советов и особых приемов, которые помогают расширить применение отладчика Visual C++. Окно Watch с его замечательной гибкостью позволяет делать удивительные вещи для ускорения отладки. В дополнение к возможности изменять значения переменных, окно Watch предлагает все виды режимов форматирования, обеспечивающие просмотр данных в любой форме. Окно Watch позволяет также вызывать программные функции из отладчика. Благодаря этому свойству можно создавать и использовать специальные отладочные функции, автоматизирующие наиболее утомительные задачи отладки. Наконец, можно определять правила авторасширения для собственных типов, чтобы быстро просматривать важные члены своих структур и классов.

Синтаксис расширенных точек прерывания и позиционные точки прерывания


Перед обсуждением возможностей расширенных точек прерывания потратим немного времени на рассмотрение их синтаксиса. Это важно, потому что в диалоговом окне Breakpoints показан список описателей вставленных в код точек прерывания в соответствующем формате. К счастью, синтаксис таких описателей довольно прост. В листинге 5-1 (раздел "Точки прерывания глобальных выражений и условные точки прерывания" данной главы) приведена программа AdvancedBP, которая демонстрирует каждый тип расширенных точек прерывания. Проект этой программы находится на сопровождающем компакт-диске, его можно открыть в Visual Studio и обращаться к нему при работе с этим разделом.

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

В полном формате контекста перечисляются (через запятую в указанном порядке) имена функции, исходного файла и двоичного модуля:

{[функция], [исходный файл], [двоичный модуль]}

 Загрузочный или исполняемый модуль приложения.. Все элементы контекста не обязательны. При пропусках элементов запятые сохраняются. — Пер.

Чтобы установить точку прерывания, нужно определить достаточное количество контекстной информации. В простой позиционной точке прерывания отладчику надо только имя исходного файла. Читателю, вероятно, знаком простой формат позиционной точки прерывания в диалоговом окне Breakpoints. Если установить такую точку прерывания на строке, например, 20 файла TEST.CPP, то в диалоговом окне Breakpoints будет описана так:

{,TEST.CPP,}.20

 См. список Breakpoints в нижней части окна Breakpoints, открываемого командой Edit|Breakpoints.
— Пер.
Возможность указать контекст для позиционной точки прерывания позволяет решать особенно неприятный тип проблем отладки. Рассмотрим следующий случай: исходный файл с диагностической функцией CheckMyMem используется двумя библиотеками динамической компоновки — A.DLL и B.DLL и включен в них с помощью статической компоновки. Если соблюдаются принципы профилактического программирования, то имеют место многократные вызовы этой функции из обеих DLL. Однако только при вызове из В.DLL часто происходят случайные аварийные остановы. Если установить стандартную позиционную точку прерывания в исходном коде функции checkMyMem (ее синтаксис в этом случае выглядит как {,CHECKMYMEM.CPP, } .27), то она будет срабатывать в обеих DLL, даже если вы хотите, чтобы вызовы делались только из В.DLL. Если нужно, чтобы точка прерывания срабатывала только в B.DLL, то придется использовать контекст точки прерывания в следующей форме: {, CHECKMYMEM.CPP, в.DLL}.27. Синтаксис контекста можно ввести с клавиатуры непосредственно в редактируемое поле Break At на вкладке Location диалогового окна Breakpoints (открываемого командой Edit|Breakpoints или нажатием клавиш <Alt>+ +<F9>) отладчика Visual C++; однако легче использовать диалоговое окно Advanced Breakpoint, показанное на рис. 5.1. Для его открытия нажмите кнопку со стрелкой справа от редактируемого поля Break at (в панели Breakpoints), чтобы раскрыть меню. В этом меню выберите пункт Advanced и затем в поля группы Context окна Advanced Breakpoint введите информацию для контекстной части синтаксиса точки прерывания.
Реальная мощь расширенных точек прерывания сосредоточена во второй части их синтаксиса, где указывается расположение (позиция) точки прерывания в исходном коде, выражение, переменная или Windows-сообщение, связанные с точкой прерывания. Синтаксис позиционных точек прерывания довольно прост -г- после фигурных скобок контекста указывается десятичный номер строки, на которой располагается точка прерывания в исходном коде (номер отделяется от контекста десятичной точкой).Однако можно выполнять прерывание с помощью других типов позиций. Например, если необходимо выполнить прерывание по известному абсолютному адресу (а не по номеру строки), то нужно просто ввести этот адрес в поле Break at диалогового окна Breakpoints. Например, при написании данного раздела использовалась упомянутая выше программа AdvancedBP. Точка входа данного приложения (mainCRTStartup) расположена по адресу 0x401210 — это и есть абсолютный адрес, который задавался для точки прерывания. Напомним, что при вводе шестнадцатеричного адреса нужно указывать префикс Ох.

Рис. 5.1. Спецификация контекста точки прерывания в диалоговом окне Advanced Breakpoint

Советы и специальные приемы


Рассмотрим некоторые специальные приемы, которые позволяют собирать как можно больше информации об отлаживаемом приложении.



Точки прерывания глобальных выражений и условные точки прерывания


До этого момента мы говорили о единственном типе точек прерывания — о позиционных точках прерывания (location breakpoints) и их модификациях. При рассмотрении синтаксиса точек прерывания упоминались три других типа таких точек (в дополнение к позиционным): точки прерывания выражений (expression breakpoints), точки прерывания переменных (variable breakpoints) и точки прерывания Windows-сообщений (Windows message breakpoints). Точки прерывания выражений и переменных подобны соответствующим модификаторам позиционных точек прерывания, за исключением того, что по области своего действия (scope) они глобальны. Причем для них действуют те же правила, что и для модифицированных позиционных точек. На latel CPU оба этих типа точек прерывания будут пытаться использовать аппаратную точку прерывания через один из специальных отладочных регистров CPU.

Отладочные регистры контролируют адрес и 1, 2 или 4 байта памяти по этому адресу. Если ТП, связанная с выражениями или изменениями данных, конструируется таким образом, чтобы отладчик мог сохранять их в одном из отладочных регистров, то программа сможет выполняться с высокой скоростью до тех пор, пока не произойдут изменения в соответствующем 

Точки прерывания на системных или экспортируемых функциях


Размещение точки прерывания на первой инструкции функции — очень мощная техника. Однако попытка установить точку прерывания на функции, которую программа импортирует из DLL, ни к чему не приведет. Со стороны отладчика здесь все в порядке, нужно только дать ему некоторую контекстную информацию о том, где он может найти функцию. Кроме того, важна еще одна небольшая деталь: имя функции зависит от того, загружены ли отладочные символы DLL. Имейте в виду, что точку прерывания на системных DLL-функциях можно устанавливать только в Microsoft Windows 2000. Недостаток защиты для "копирования-при-записи" (обсуждавшийся в главе 4) и есть та причина, по которой нельзя устанавливать точки прерывания на системных функциях Windows 98, которые загружены выше 2 Гбайтной границы памяти. Чтобы заставить эту технику работать в Windows 2000, нужно использовать формат COFF (см. главу 4) и включить в отладчике загрузку экспорта. Для этого, работая в IDE Visual C++, убедитесь, что на вкладке Debug диалогового окна Options (открываемого командой Tools|Options) установлен флажок Load COFF & Exports.

Чтобы показать, как устанавливаются точки прерывания на системной DLL, установим такую точку на функцию LoadLibrary из KERNEL32.DLL. Поскольку уже известно, как нужно устанавливать контекст для позиционной точки прерывания, то ясно, что первая часть контекста записывается как {,,KERNEL32.DLL} и идентифицирует модуль функции. Отладчик Visual C++ придерживается иерархического подхода к символической информации, при котором более полные наборы символов имеют приоритет над менее полными. Так, файлы программных баз данных (PDB), которые включают всю возможную информацию — номера исходных строк, имена функций, переменных и типов1, всегда имеют приоритет над COFF/DBG-файлами, которые содержат только символические имена общих (public) функций2. COFF/DBG-файлы имеют приоритет над экспортируемыми именами, которые являются разновидностью псевдосимволов. Для того чтобы подтвердить, что отладчик загружает символы для DLL, нужно контролировать вкладку Debug окна Output.
Если в окне Output выводится сообщение "Loaded symbols for 'DLL name" (Загружены символы для 'имя DLL'), значит имеются полные символы для этой DLL. Наоборот, если выводится "Loaded 'DLL name, no matching symbolic information found" (Загружена 'имя DLL', соответствующая символьная информация не найдена) или "Loaded exports for 'DLL name " (Загружены эксперты для 'имя DLL"), значит символы не были загружены.

Здесь обсуждается тема отладочных символов, поэтому вспомним, что отладочные символы Windows 2000 нужно всегда устанавливать. Хотя они и не помогут реализовать полную обратную разработку операционной системы, потому что содержат символические имена только для общих (public) компонентов. Но если символы загружены, то вы, по крайней мере, будете видеть, в каких функциях вы находитесь при просмотре стека или окна Disassembly. Причем запомните, нужно обновлять символы операционной системы каждый раз, когда устанавливается очередной пакет обслуживания (Service Pack) операционной системы. Отладочные символы для Windows 2000 находятся на компакт-диске Customer Support Diagnostics (Диагностика поддержки клиента). Visual Studio для Windows NT 4 включает программу Windows NT Symbols Setup, которая устанавливает соответствующие символы.

 Это и есть полный набор символов. — Пер.

 И поэтому содержат неполный набор символов. — Пер.

 Окно Output открывает в нижней части окна Microsoft Visual C++ команда VievjOutput или <Alt>+<2>. - Пер.

Если отладочные символы не загружены, то в качестве строки для установки точки прерывания нужно использовать имя, экспортированное из DLL. Можно проверить это имя, запустив утилиту DUMPBIN с ключом /EXPORTS:

DOMPBIN /EXPORTS DLL-Name

Если вы выполните DUMPBIN для KERNEL32.DLL, то увидите не функцию LoadLibrary, а две функции с похожими именами — LoadLibraryA и LoadLibraryw. Суффиксы указывают на набор применяемых данной функцией символов: суффикс А происходит от ANSI (American National Standards Institute), a w — от wide (или Unicode).


Windows 2000 использует кодировку Unicode для интернационализации. Если программа компилировалась с параметром UNICODE, то нужно выбрать версию LoadLibraryw, а если нет, то LoadLibraryA. Однако функция LoadLibraryA это просто оболочка, которая распределяет память для конвертирования ANSI-строки в Unicode и вызывает LoadLibraryw, поэтому технически можно использовать также и LoadLibraryw. Если наверняка известно, что программа собирается вызывать только одну из этих функций, то можно просто установить точку прерывания на этой функции. Если такой уверенности нет, установите точки прерывания сразу на двух функциях. Если символы не загружены, то синтаксис точки прерывания для прерывания на функции LoadLibrary выглядит либо так: (,,KERNEL32.DLL}LoadLibraryA, либо так: {,,KERNEL32.DLL)LoadLibraryw.

Если приложение ориентировано только на Windows 2000, то нужно везде использовать Unicode. Это обеспечит значительное повышение производительности. Мэтт Пьетрек в декабрьской (за 1997 год) колонке "Under the Hood" в Microsoft Systems Journal сообщил, что ANSI-оболочки обеспечивают значительный рост производительности. Кроме того, поддержка Unicode облегчает интернационализацию программы.

Если символы загружены, то необходимо выполнить некоторые вычисления, чтобы согласовать декорированные имена символов. Для этого нужно только знать соглашение о вызове экспортируемой функции и прототип функции. Подробности соглашений о вызовах приведены в главе 6. Ниже показан прототип функции LoadLibrary из WINBASE.H с некоторыми макросами, расширенными для ясности:

_declspec (dllimport) 

HMODULE 

_stdcall

  LoadLibraryA(

LPCSTR IpLibFileName 

);

Макрос WINBASEAPI расширяется в стандартное соглашение о вызовах _stdcall, которое, между прочим, является соглашением о вызовах для всех системных API-функций. Стандартные вызовы функций декорируются префиксом с символом подчеркивания и суффиксом с символом "@", за которым следует число байт, помещенных в стек.


К счастью, вычислить это число довольно просто: оно равно сумме байт, отводимых в стеке для всех параметров. Для семейства CPU Intel Pentium нужно просто сосчитать число параметров и умножить его на 4. Для функции LoadLibrary, которая имеет один параметр, окончательное декорированное имя выгладит так: _LoadLibraryA@4. Вот еще два примера, которые показывают, как должны выглядеть окончательные декорированные имена функций:

 _createProcessA040 — для функции CreateProcess, которая имеет 10 параметров;   _TlsAiioc@o — для функции TisAiioc, которая не имеет параметров. Даже если функция не имеет параметров, нужно придерживаться формата "@#" (как мы только что видели, декорированное имя функции TISAIIOC имеет вид maiito:_TlsAiioc@o). Когда символы не загружаются, ANSI- и Unicode-условия все еще применимы. Если символы загружены, синтаксис прерывания на LoadLibrary выглядит или так: {, ,KERNEL32.DLL}_LoadLibraryA@4, или так: {,, KERNEL32. DLL}_LoadLibraryW@4.



Точки прерывания Windows-сообщений


Точки прерывания Windows-сообщений (Windows message breakpoints) останавливают отладочный прогон приложения, когда оконная процедура этого приложения получает определенное Windows-сообщение. Точки прерывания этого типа устанавливаются на вкладке Messages диалогового окна Breakpoints (рис. 5.5). Для программ, разработанных средствами SDK1 языка С, установка этой точки прерывания достаточно проста, потому что когда выполняется подчиненный отладчик, раскрывающийся список поля Break at WndProc уже содержит имя нужной оконной процедуры. Для установки таких ТП нужно:

1. Выбрать из списка Break at WndProc оконную процедуру.

2. Выбрать из списка Set one breakpoint for each message to watch идентификатор того сообщения, при получении которого должно произойти прерывание.

3. Нажать кнопку ОК.

Если в списке Set one breakpoint for each message to watch нет подходящего идентификатора, его следует ввести в это поле вручную.

В настоящее время в Windows-программировании широко используются библиотеки С++-классов, такие как MFC, позволяющие напрямую работать с точками прерывания Windows-сообщений. Если установить ТП этого типа на главной оконной процедуре MFC: {, ,MFC42D.DLL}AfxWndProc, то она будет работать. Однако такая установка позволяет останавливать приложение только на сообщениях главного окна приложения. Если нужно выполнять прерывания на частных Windows-сообщениях, обрабатываемых в оконных процедурах дочерних оконных классов (производных от класса cwnd), то придется перенести эту ТП из AfxWndProc на оконную процедуру дочернего окна.

1 SDK (Software Development Kit) — инструментальная система разработки программ. — Пер.

Если нужно выполнить прерывание на общем сообщении, то лучше использовать условную позиционную точку прерывания, устанавливая ее в MFC-методе cwnd: :WindowProc. Для этого сначала нужно найти значение this-ука-зателя интересующего вас класса, а затем отыскать в файле WINUSER.H значение Windows-сообщения, на котором требуется остановить программу. С помощью этих двух значений нужно установить позиционную точку прерывания в cwnd: :WindowProc.
При написании данного раздела автор отлаживал программы пакета Visual C++ 6.0 Service Pack 3, и чтобы выполнять прерывание на сообщении WM_PAINT только от своего класса, он установил расширенную позиционную точку прерывания со следующим описателем:

{,WINCORE.CPP,}.1584 when (this=Ox0012FE74)&&(message==OxF)

Рис. 5.5. Вкладка Messages для установки точек прерывания от Windows-сообщений

При использовании точки прерывания с выражением "this. ..&&.. .message" нужно проявлять осторожность, потому что указатель this, вероятно, изменяется в зависимости от способа выделения памяти вашему классу и от изменений, вносимых в отлаживаемый код. Если же необходимо быстро установить точку прерывания, то всегда можно добавить метод обработки (с помощью мастера классов Class Wizard) и установить на нем простую (не расширенную) позиционную точку прерывания.

В этой главе много внимания уделено расширенным точкам прерывания. В листинге программы AdvancedBP показаны все типы точек прерывания, кроме прерываний от Windows-сообщений. Синтаксис точек прерывания каждого типа показан в виде строчных комментариев в исходном коде программы (что позволяет видеть соответствующие описатели без открытия диалогового окна Breakpoints). Настоятельно советую всем разработчикам попрактиковаться в установке расширенных точек прерывания в собственных программах. Поразительно, как много полезного можно узнать при этом и об отладчике, и о собственном проекте!

Общий вопрос отладки

Почему точки прерывания исчезают или перескакивают с места на место?

Содержать в порядке точки прерывания исходного кода помогает редактор интегрированной среды разработки (IDE) Visual C++, потому что именно он удерживает точки прерывания на соответствующей строке кода. Если приходится редактировать исходный код вне IDE, и точка прерывания оказывается установленной не на активной строке исходного кода, то отладчик отреагирует на это следующим сообщением:

One or more breakpoints are not positioned on valid lines.


These breakpoints have been moved to the next valid line.

(Одна или несколько точек прерывания не были позиционированы на правильной строке. Эти точки прерывания перемещены к следующей правильной строке).

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

Если отладчик вообще не может установить точку прерывания, вы получите сообщение:

One or more breakpoints cannot be set and have been disabled. Execution will stop at the beginning of the program.

(Одна или несколько точек прерывания не могут быть установлены и были отключены. Выполнение остановится в начале программы.)

Нажав кнопку ОК, чтобы закрыть панель сообщения, нужно открыть диалоговое окно Breakpoints и отыскать точки прерывания со сброшенными флажками. Они-то и доставили неприятности отладчику.

Наиболее общей причиной отказа в установке точек прерывания является попытка установить точку прерывания в явно загруженную DLL — т. е. в DLL, загруженную явным обращением к API-функции LoadLibrary. Все DLL-файлы модели компонентных объектов (СОМ) загружаются явно, так что эта проблема возникает с раздражающей частотой. Отладчик должен установить все точки прерывания еще при запуске, поэтому в начале отладки нужно будет вручную добавить к списку дополнительных DLL явно загруженные DLL (чтобы заставить отладчик загрузить символы этих DLL). В диалоговом окне Project Settings на вкладке Debug выберите пункт Additional DLLs в списке Category. В списке Modules добавьте все DLL, которые проект может когда-либо загружать.

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

Эту панель можно открыть командой Project|Settings из Microsoft Visual C++.— Пер.



Удаленная отладка


Теперь, когда читатель хорошо осведомлен относительно нюансов расширенных точек прерывания, обратимся к одной из скрытых возможностей отладчика Visual C++ — удаленной отладке. Удаленная отладка означает, что ваша программа и крошечная отладочная заглушка выполняются на одной (удаленной) машине, а отладчик — на другой (локальной) машине, причем связь поддерживается через протоколы TCP/IP1. Хотя с помощью отладчика Visual C++ можно многое сделать и на одиночной машине, иногда разумнее выбрать двухмашинный (удаленный) вариант отладки. Удаленную отладку полезно применять, столкнувшись с любой из следующих ситуаций (которые могут сложиться под влиянием отладчика с полным графическим интерфейсом пользователя — GUI-отладчика):

при отладке критического кода активизации окна;  при отладке графических программ;  если нужно освободить как можно больше памяти для приложения;  при выполнении отладки в Windows 98;  если программист не хочет радикально изменять конфигурацию машины, устанавливая полную интегрированную среду разработки Visual C++.

 Самая плохая ситуация — первая (отладка критического кода активизации окна). На отдельной машине пошаговая отладка через Windows-сообщение WM_SETFOCUS и аналогичные процессы невозможна, потому что в пошаговом режиме программа теряет фокус отладчика. GUI-отладчики это, конечно, великолепно, но принцип неопределенности Гейзенберга справедлив и применительно к отладчикам! Перемещение отладчика на отдельную машину позволяет как отладчику, так и вашей программе иметь свой собственный фокус, аккуратно обходя эту проблему.

Вторая ситуация, в которой удаленная отладка может дать значительную экономию времени — когда нужно отлаживать код графической программы. Хотя в простых случаях в системе с единственным монитором можно разделить отладчик и соответствующее приложение по разным углам экрана, бывают ситуации, когда отладчик нужно отправить на другую машину. Если приложению постоянно необходима большая часть экрана, или речь идет об отладке игрового приложения, использующего технологию Microsoft DirectX2, то без удаленной отладки не обойтись.


 TCP/IP — Transmission Control Protocol/Internet Protocol (Протокол Управления Передачей/Протокол Интернета). — Пер.

  Технология DirectX предоставляет разработчикам программного обеспечения специальные средства для доступа к устройствам. Это нечто среднее между стандартными средствами Windows и прямым доступом к устройствам. — Пер.

Третья ситуация — более тонкая и заслуживает отдельного внимания. Полный GUI-отладчик занимает значительное количество ресурсов. Если вам нужны вся мощь и комфорт отладчика Visual C++, значит приложение должно отказаться от некоторой части памяти, чтобы отладчик мог выполняться. В разработках реальных приложений отладочные построения могут достигать огромных размеров. Удаленная отладка позволит освободить часть памяти для выполнения приложения. Удаленная отладка особенно полезна, когда речь идет о тестировании на машинах с минимальными ресурсами.

Четвертая ситуация, в которой уместна удаленная отладка: при отладке программ, выполняющихся под управлением операционной системы Windows 98. Удаленная отладка позволяет отлаживать программы одной операционной системы, находясь в другой операционной системе. Кроме того, оказалось что отладчик Visual C++ лучше работает в Windows 2000, поэтому следует предпочесть Windows 2000 на локальной машине, где работает отладчик, a Windows 98 — на удаленной, где находится отлаживаемое приложение.

Наконец, заключительная ситуация: отладка выполняется на машине, на которой у приложения, по вашему мнению, могут возникнуть проблемы, связанные с версией системных DLL. Установка полной IDE для Visual C++ на рассматриваемой системе может привести к обновлению различных системных DLL, таким образом уничтожая шанс дублирования и исправления ошибки. Решить проблему можно копированием всего нескольких файлов, необходимых для работы удаленной части отладчика, на удаленную машину.

Подробное объяснение методики удаленной отладки приводится в документации Visual Studio. Желающие могут обратиться к теме библиотеки MSDN "Debugging Remote Applications" (Отладка удаленных приложений).



Остальная часть данного раздела посвящена специальным приемам и методикам, которые сделают удаленную отладку приятным и полезным экспериментом. Документация сообщает, какие файлы необходимо скопировать на удаленную машину, но она не сообщает, где их следует разместить. Эти каталоги перечислены в табл. 5.2.

Таблица 5.2. Файлы, необходимые для удаленной отладки, и их размещение в Visual C++

Файлы

Размещение в установленном Visual C++

MSVCMON.EXE, TLNOT.DLL, DM.DLL, MSDIS110.DLL

<VS Common>\MSDEV98\BIN

MSVCRT.DLL, MSVCP60.DLL

%SYSTEMROOT%\System32

PSAPI.DLL (только Windows 2000)

%SYSTEMROOT%\System32

Для успешной удаленной отладки нужно, во-первых, установить две системы так, чтобы локальная машина, на которой работает отладчик, могла отыскивать те же двоичные файлы, которые выполняет удаленная машина. Как минимум, нужно иметь общий (shared) доступ ко всем дисководам удаленной машины, потому что локальная машина будет нуждаться в доступе к согласованным двоичным файлам. Размещение отлаживаемой программы зависит от ее сложности: она может размещаться на локальной машине, а выполняться на удаленной; она может находиться на обеих машинах или только на удаленной машине. Если это стандартная ЕХЕ-программа, то удаленная машина может, вероятно, выполнять ее вне локальной машины. Однако в современном мире новейших СОМ-технологий иногда непросто принять решение о том, где следует выполнять программу. Лично я размещал программу на идентичных дисках и каталогах на обеих машинах. Во-вторых, прежде чем начать удаленную отладку, следует гарантировать, что имеется полностью и правильно установленная программа, готовая к выполнению на удаленной машине (куда бы вы ни решили поместить ее).

MSVCMON.EXE — это отладочная заглушка, которая выполняется на удаленной машине. Она имеет кнопку Settings, которая должна обеспечивать возможность задания сетевого имени локальной машины, но эта функция не действует, т. к. MSVCMON.EXE почему-то игнорирует установки.


Запустив MSVCMON.EXE, убедитесь, что ваш отладочный проект установлен правильно на локальной машине. Введите полный путь и имя двоичного (исполняемого) файла удаленной машины в редактируемое поле Remote Executable Path and File Name на вкладке Debug диалогового окна Project Settings. Когда вы заставляете локальный отладчик выполнять отладку удаленно, он фактически не стартует процесс отладки. Его стартует MSVCMON.EXE, поэтому необходимо сообщить этой программе, где следует искать двоичный файл.

После того как проект установлен и нажата кнопка Go, отладка, в значительной степени, выполняется так же, как обычно. Единственное отличие состоит в том, что локальный отладчик будет иногда подсказывать расположение согласованных DLL (вот почему необходимо разделение всех дисков удаленного компьютера — всегда должна быть возможность найти согласованные двоичные файлы, чтобы при встрече с какой-либо проблемой можно было взглянуть на ее источник). Хотя специальный флажок позволяет отключить согласование DLL, делать это не следует. Проблема состоит в том, что раз вы выключили согласование DLL, не существует никакого способа включить его обратно иначе, как удалив ОРТ-файл проекта. Кроме того, в ОРТ-файле проекта сохраняются пути к удаленно согласованным двоичным файлам, так что если позже вы захотите присоединиться к другой машине для удаленной отладки того же проекта, то единственным способом переустановки положения двоичного файла будет удаление и ОРТ-файла проекта. Имейте в виду, что удаление этого файла приведет к потере всех точек прерывания и компоновок экрана.

Можно комбинировать приложение дистанционного управления, например, pcAnywhere фирмы Symantec, с удаленной отладкой, получая возможность отлаживать свое приложение на машине пользователя. Мне приходилось делать это в корпоративных сетях интранет, но не в Интернете. Конечно, это может привести к некоторым проблемам безопасности, но все же неплохо иметь такое средство в своем арсенале. Вполне возможна ситуация, при которой машины пользователя и разработчика находятся на разных континентах, а проблему надо устранить в один день!

И, наконец, два последних не очень существенных замечания относительно удаленной отладки. Во-первых, закончив удаленную отладку, не забудьте установить в IDE Visual C++ режим локальной отладки. Установка удаленной отладки глобальна для всех проектов, хотя, по моему мнению, это должна быть раздельная установка по проектам. Во-вторых, если речь идет о сетевом приложении или о системе, которая чувствительна к сетевому трафику, то удаленная отладка использует TCP/IP, поэтому отладчик может помешать вам. В любой из этих ситуаций следует предпочесть отладчик WinDBG, который поддерживает удаленную отладку, используя последовательные порты и нуль-модемный кабель.



Условии или в данных. Если же


на вкладке Data диалогового окна Breakpoints) фактические адреса выражений и данных. В программе AdvancedBP, показанной в листинге 5-1, требовалось установить точку прерывания глобального выражения, которая должна срабатывать, если "первый символ в имени глобальной переменной g_szGiobai изменится на "G". Для этого пришлось найти адрес переменной g_szciobai (он оказался равным 0x00404594) и установить (в поле Enter the expression to be evaluated на вкладке Data диалогового окна Breakpoints) точку прерывания выражения в формате:

Mchar*) Ox00404594='G'

Однако при пошаговом просмотре каждой инструкции будет показано неправильное выражение:

WO (0x00404594) =='G'

Мне так и не удалось найти подходящей точки прерывания глобального выражения, потому что очень трудно подыскать такое выражение, которое примет отладчик. Намного удобнее в этом случае воспользоваться точкой прерывания глобальной переменной.

/*00З*/ ttinclude <windows.h>

/*004*/ void LocBPFunc ( void)

/*005*/ { // {,AdvancedBP.cpp,}.6

/*006V printf ( "Hello from LocBPFuncW) ;

/*007*/ }

/*008*/ void SkipLocBPFunc ( void)

/*009*/ { // {,AdvancedBP.cpp,}.12 skip 99 times(s)

/*010*/ for ( int i = 0; i < 100; i++)

/*011*/ {

/*012*/ printf ( "SkipLocBPFunc iteration = %d\n", i);

/*013*/ }

/*014*/ }

/*015*/ void ExprTrueLocBP ( void)

/*016*/ { // {,AdvancedBP.cpp, } .20 when 'j=8'

/*017*/ int j = 0;

/*018*/ for ( int i = 0; i < 10; i++)

/*019*/ {

/*020*/ j = i * 2;

/*021*/ }

/*022*/ }

/*023*/ void DataChangeLocBP ( void)

/*024*/.{ // {,AdvancedBP.cpp,}.26 когда изменяется szBuff[5](length:1)

/*025*/ char szBuff[ 10 ];

/*026*/ strcpy ( szBuff, "String!");

/*027*/ }

/*028*/ char g_szGlobal[ 10 ];

/*029*/ int g_ilnt = 0;

/*030*/ void main ( void)

/*031*/ { // 0x401210 -> ТП-адрес точки входа в jmainCRTStartup

/*032*/ LocBPFunc ();

/*0ЗЗ*/ SkipLocBPFunc ();

/*034*/ ExprTrueLocBP ();

/*035*/ DataChangeLocBP ();

/*036*/

/*037*/ //{,,KERNEL32.DLL}_LoadLibraryA@4 <- с отладочными символами


/*038*/ //{,,KERNEL32.DLL}LoadLibrary <- без отладочных символов

/*039*/ LoadLibrary ( "KERNEL32.DLL");

/*040*/

/*041*/ // (char)Ox00404594=='G' <- ТП глобального выражения.

/*042*/ strcpy ( g_szGlobal, "Global!");

/*043*/

/*044*/ // (long)Ох4045АО <- ТП глобальной переменной.

/*045*/ g_ilnt = 0x42;

/*046*/

/*047*/ printf ( "Done!\n");

На рис. 5. 4 приведено диалоговое окно команды Edit|Breakpoints, в нижней части которого показан список Breakpoints, содержащий описания всех установленных в программе AdvancedBP точек прерывания. Флажки в левой части каждого описателя позволяют управлять активностью соответствующих ТП: включение флажка активизирует ТП, а выключение деактивизирует ее (что вынуждает отладчик не выполнять останов программы на этой ТП во время отладочного прохода).

Подобно точке прерывания глобального выражения, точка прерывания глобальной переменной работает наилучшим образом, если при ее установке задать шестнадцатеричный адрес, выполнить явное приведение типа адреса к длинному указателю и указать число элементов наблюдения равным 1. Когда содержимое памяти по этому адресу изменяется, отладчик останавливается. Например, в программе AdvancedBP можно модифицировать выражение *(char*)Ox00404594=='G' так, чтобы прерывание происходило тогда, когда содержимое ячейки памяти по этому адресу изменяется (а не просто содержит код символа "G"). Для этого нужно выполнить приведение адреса к типу long, Т.е. установить ТП по адресу "* (long*) (0x00404594) " (СМ.комментарий в строке 044). Чтобы задействовать отладочные регистры, в общем случае требуется одна или две попытки установить такие ТП с правильными выражениями. Еще раз отметим, что при приведении адреса к типу long не следует устанавливать (в поле Enter the number of elements to watch in an array or structure на вкладке Data диалогового окна Breakpoints) число элементов наблюдения больше 1. Значение  в этом поле указывает, что контролируется запись в область памяти размером в двойное слово (DWORD), а из-за того что аппаратные регистры отладки не могут обрабатывать ссылки, превосходящие по размеру двойное слово, устанавливается пошаговый метод проверки памяти.



Рис. 5.4. Список описателей точек прерывания программы AdvancedBP с флажками активизации/деактивизации ТП

Хотя имеется четыре отладочных регистра, в этой программе только две глобальных точки прерывания, работающих одновременно. Если установить группу глобальных точек прерывания, то можно гарантировать, что отладчик будет выполнять программу в пошаговом режиме.

Точки прерывания глобальных переменных — превосходное средство для поиска наиболее сложных видов ошибок, связанных с неконтролируемой записью в память (wild writes). Как указывалось в главе 2, это происходит, когда при записи в память используется неинициализированный  указатель, что вызывает непредсказуемую модификацию данных в памяти.

 Чтобы открыть эту панель, нужно установить на подходящем исходном операторе простую позиционную точку прерывания и, не изменяя позиции курсора ввода, ввести команду Edit|Breakpoints, Затем в открывшемся диалоговом окне Breakpoints ввести в поле Break at вкладки Location номер строки (после символа "точка"), в которой установлена данная точка прерывания (например, .47), и нажать кнопку Condition (под полем Break at). — Пер.

 Если же ввести в поле Enter the expression to be evaluated сам указатель pMyData (без звездочки), то прерывание произойдет, когда изменится сам указатель, указывающий на другую область памяти. — Пер.

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



Установка точек прерывания


Может показаться, что о точках прерывания уже все рассказано, но добавим следующее. Каждый знает, что можно размещать точки прерывания на строке исходного кода и в окне Disassembly, но знаете ли вы, что допускается также установка их в окне Call Stack (т. е. в стеке вызовов)? Когда нужно выйти из глубоко вложенной цепочки вызовов, то установка точки прерывания в окне Call Stack — очень полезная техника. Для установки точки прерывания в окне Call Stack выполните правый щелчок по функции, на которой требуется приостановить приложение, и в раскрывшемся контекстном меню выберите пункт Insert/Remove Breakpoint (останов выполнится сразу же, как только произойдет возврат к этой функции).

Вместо удаления точек прерывания после тяжелого сеанса отладки, лучше просто выключить их. Выключать точки прерывания можно либо щелкнув правой кнопкой мыши на каждой из них в окне исходного кода и выбрав команду Disable Breakpoint, либо перейдя в диалоговое окно Breakpoints и выключая флажки рядом с точками прерывания в списке Breakpoints в нижней части этой панели. Оставляйте точки прерывания в коде, чтобы обеспечить быстрый возврат приложения к состоянию, в котором оно находилось, когда возник сбой. Конечно, надо обновлять любые точки прерывания, которые могли измениться, перед тем как начать новый сеанс отладки. После | проверки решения можно удалить все точки прерывания.

Для гарантированного получения всего набора точек прерывания нажмите кнопку Step Into, чтобы загрузить и запустить подчиненный отладчик прежде, чем будете устанавливать любые точки прерывания, отличающиеся от простых позиционных точек. Отладчик может проверять точки прерывания и показывать диалоговое окно Resolve Ambiguity только тогда, когда активен подчиненный отладчик.