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

674 ЧАСТЬ IV Мощные средства и методы отладки неуправляемого кода
}
} ;
/*////////////////////////////////////////////////////////////////////// Глобальные объекты с областью видимости файл
//////////////////////////////////////////////////////////////////////*/
//Автоматический класс. static CAutoMatic g_cAuto ;
//Массив модулей.
static CModuleItemArray g_cModArray ;
/*////////////////////////////////////////////////////////////////////// Прототипы функций
//////////////////////////////////////////////////////////////////////*/
/*//////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////*/
extern "C" void SWSDLL_DLLINTERFACE __declspec(naked) _penter ( void )
{
DWORD_PTR dwCallerFunc ;
// Пролог функции. |
|
|
__asm |
|
|
{ |
|
|
PUSH |
EBP |
// Создание стандартного кадра стека. |
MOV |
EBP , ESP |
|
PUSH |
EAX |
// Сохранение EAX, так как он нужен |
|
|
// мне до сохранения всех регистров. |
MOV |
EAX , ESP |
// Загрузка текущего указателя стека |
|
|
// в регистр EAX. |
SUB |
ESP , __LOCAL_SIZE |
// Резервирование пространства |
|
|
// для локальных переменных. |
PUSHAD |
// Сохранение значений всех |
|
|
|
// регистров общего назначения. |
// Теперь я могу вычислить адрес возврата. |
||
ADD |
EAX , 04h + 04h |
// Нужно учесть команды PUSH EBP |
|
|
// и PUSH EAX. |
MOV |
EAX , [EAX] |
// Получение адреса возврата. |
SUB |
EAX , 5 |
// Чтобы определить начало функции, |
|
|
// надо вычесть 56байтовый переход, |
|
|
// использованный для вызова _penter. |
MOV |
[dwCallerFunc] , EAX |
// Сохранение нового адреса возврата. |

ГЛАВА 19 Утилита Smooth Working Set |
675 |
|
|
}
//Если событие начала/завершения находится
//в сигнальном состоянии, ничего не делаем.
if ( WAIT_TIMEOUT == WaitForSingleObject ( g_hStartStopEvent , 0 ))
{
// Выполняем всю работу. g_cModArray.IncrementFunctionEntry ( dwCallerFunc ) ;
} |
|
// Эпилог функции. |
|
__asm |
|
{ |
|
POPAD |
// Восстановление всех регистров |
|
// общего назначения. |
ADD ESP , __LOCAL_SIZE |
// Удаление пространства, выделенного |
|
// для локальных переменных. |
POP EAX |
// Восстановление регистра EAX. |
MOV ESP , EBP |
// Восстановление кадра стека. |
POP EBP |
|
RET |
// Возврат в вызвавшую функцию. |
} |
|
}
Формат файла .SWS и перечисление символов
Как показывает листинг 19 1, в _penter ничего удивительного. Все становится интереснее, когда дело касается организации адресов функций. Так как мне нуж но связать адрес с именем функции, то в некоторых местах программы я прибе гаю к услугам своего старого друга — сервера символов DBGHELP.DLL. Однако про смотр символов при помощи сервера символов — не самая быстрая операция, а доступ к данным нужен при каждом вызове функции, поэтому я должен был най ти компактный и быстрый способ его выполнения.
Размышляя об этом, я захотел упорядочить данные при помощи отсортиро ванного массива всех адресов функций с соответствующими им счетчиками вы зовов. В этом случае, получив адрес возврата в _penter, я мог бы просто выпол нить для него быстрый двоичный поиск. Такое решение казалось относительно простым, потому что оно требовало только перечисления символов модулей и сортировки массива функций. Все данные для этого у меня имелись.
Я решил, что SWS подобно WST должна хранить счетчики вызовов для каждо го запуска каждого модуля в отдельном файле данных. Я предпочел этот подход, потому что он позволяет удалить информацию о конкретном запуске приложе ния из объединенного набора данных, если она вам не нужна. WST использует для наименования файлов формат <имя модуля>.<номер вызова>, но я хотел, чтобы SWS поддерживала схему <имя модуля>.<номер вызова>.SWS, чтобы я мог в конеч

676 ЧАСТЬ IV Мощные средства и методы отладки неуправляемого кода
ном счете написать графическую программу, облегчающую объединение данных обо всех запусках.
Выбрав способ обработки данных в период выполнения, я приступил к рас смотрению создания файла порядка. Как я уже говорил, мне нужен был способ объединения информации об отдельных запусках. Однако при размышлении над фактическим созданием файла порядка я понял, что у меня нет некоторых дан ных. Файл порядка должен содержать имена функций, а также их размеры, в то время как я планировал хранить только адреса функций. Хотя я опять мог бы использовать символьную машину при генерировании файла порядка, единствен ный способ получения размера символа — перечисление всех символов модуля. Так как я уже выполнял полное перечисление символов на первоначальных эта пах генерирования данных, я решил, что мне следует просто добавить в файл размеры функций. Я не нуждаюсь в хранении имен функций, потому что их все гда можно узнать, загрузив PDB файл для двоичного файла.
Если вы все же нашли книгу Расса Блейка «Optimizing Windows NT» и прочи тали главу «Tuning the Working Set of Your Application» (настройка рабочего набо ра программы), вас, вероятно, интересует, почему я ничего не говорю о наборах битов и интервалах времени. Группа, работавшая над производительностью Win dows NT, использовала при создании WST схему, в которой каждой функции со ответствует один бит из набора. Каждые столько то секунд WST регистрирует при помощи этого набора битов функции, выполненные за прошедший интервал вре мени. Меня часто удивляет, почему они реализовали WST именно так. На первый взгляд, набор битов позволяет сэкономить память, но при этом нужно помнить, что должен быть реализован некоторый способ отображения битов и адресов функций. Не думаю, что такая схема экономит намного больше памяти, чем мой метод. Мне кажется, что программисты, работавшие над производительностью Windows NT, использовали набор битов потому, что оптимизировали при помо щи WST целую ОС. Я же, напротив, работаю с отдельными двоичными файлами, так что это вопрос масштаба.
Разрабатывая структуры данных, я был озабочен одним моментом: при вызо ве функции я просто хотел увеличивать счетчик. В многопоточных программах я должен защищать это значение, чтобы в каждый конкретный момент времени им мог манипулировать только один поток. Я хотел сделать SWS как можно более быстрой, поэтому увеличение счетчика вызовов функций лучше всего выполнять при помощи API функции InterlockedIncrement. Так как она использует аппарат ный механизм блокировки (префикс команды LOCK), то гарантирует согласован ность данных в многопоточных приложениях. Однако в Microsoft Win32 наиболь шим числом, которое можно передать в InterlockedIncrement, является 32 разряд ное значение, в связи с чем возникает проблема с превышением 4 294 967 295 вы зовов функций. Четыре миллиарда — много, но и этого может не хватить для не которых циклов сообщений при долгом выполнении приложения.
Для решения этой проблемы программа WST в период выполнения только записывала вызовы функций, а общее их число подсчитывалось постфактум, ког да проще обрабатывать возможное переполнение. При настройке ОС вероятность выполнения некоторых функций более 4 миллиардов раз довольно высока. Од


678 |
ЧАСТЬ IV Мощные средства и методы отладки неуправляемого кода |
|||
|
|
|
||
|
|
|
||
|
// Число записей в этом файле. |
|
||
|
DWORD |
dwEntryCount ; |
|
|
|
// Поле |
флагов. |
|
|
|
DWORD |
dwFlags ; |
|
|
|
// Имя модуля для этого файла. |
|
||
|
TCHAR |
szModuleName[ MAX_PATH ] ; |
|
|
|
DWORD |
dwPadding ; |
|
|
|
} SWSFILEHEADER , * LPSWSFILEHEADER ; |
|
||
|
/*////////////////////////////////////////////////////////////////////// |
|
||
|
Тип записи SWS6файла. |
|
||
|
//////////////////////////////////////////////////////////////////////*/ |
|
||
|
typedef struct tag_SWSENTRY |
|
||
|
{ |
|
|
|
|
// Адрес функции. |
|
||
|
DWORD64 |
dwFnAddr ; |
|
|
|
// Размер функции. |
|
||
|
DWORD |
dwSize ; |
|
|
|
// Счетчик вызовов. |
|
||
|
DWORD |
dwExecCount ; |
|
|
|
} SWSENTRY , * LPSWSENTRY ; |
|
||
|
#endif |
// _FILEFORMAT_H |
|
|
|
|
|
|
|
В связи с форматом SWS файла я хочу обратить ваше внимание на то, что я храню в нем адрес загрузки двоичного файла. Сначала я хранил в нем только адреса функций, но потом вспомнил о возможности перемещения двоичного файла в памяти. В этой ситуации SWSDLL.DLL могла бы быть вызвана с каким либо адре сом, и у меня не было бы никакой записи для этого адреса в SWS файлах, загру женных для модуля. Хотя всем нам следует модифицировать базовые адреса на ших DLL, иногда мы про это забываем, и я хотел, чтобы SWS правильно обраба тывала такие случаи.
Некоторые проблемы у меня вызвало генерирование символов для первона чального модуля SWS. Из за особенностей компоновки программ и генерирова ния символов многие символы модуля не имеют в себе вызовов _penter. Скажем, при компоновке программы со статической стандартной библиотекой C в вашем модуле будет содержаться множество стандартных функций C. Чтобы ускорить просмотр адресов утилитой SWS, я реализовал несколько способов сокращения числа символов.
Функция обратного вызова для перечисления символов и некоторые мои по пытки ограничения их числа приведены в листинге 19 3. Прежде всего я реали зовал проверку того, имеет ли символ соответствующую информацию о номере строки. Так как я полагал, что функции, содержащие в себе вызовы _penter, будут скомпилированы правильно, с соблюдением всех вышеописанных этапов, я смог безопасно избавиться от многих посторонних символов. Следующий шаг на пути к устранению символов заключался в проверке, являются ли частью символов специфические строки. Например, все символы, начинающиеся с _imp__, представ ляют собой функции, импортируемые из других DLL. Еще две проверки я оставил


680 |
ЧАСТЬ IV Мощные средства и методы отладки неуправляемого кода |
|||
|
|
|
||
|
|
|
||
|
|
|
||
|
for ( int i = 0 ; i < IGNORE_CONTAINING_COUNT ; i++ ) |
|
||
|
{ |
|
|
|
|
if ( NULL != strstr ( szSymbolName |
, |
|
|
|
g_szIgnoreContaining[ i ] |
) ) |
|
|
|
{ |
|
|
|
|
// Выход. |
|
|
|
|
return ( TRUE ) ; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
if ( NULL != pCTX6>pfnVerboseOutput ) |
|
|
|
|
{ |
|
|
|
#ifdef _WIN64 |
|
|
|
|
|
pCTX6>pfnVerboseOutput(_T(" |
Adding Symbol : 0x%016I64X %S\n"), |
||
#else |
|
|
|
|
|
pCTX6>pfnVerboseOutput(_T(" |
Adding Symbol : 0x%08X %S\n" ) , |
||
#endif |
|
|
|
|
|
(DWORD_PTR)ulSymbolAddress |
, |
||
|
szSymbolName |
|
); |
|
|
} |
|
|
|
|
if ( FALSE == pCTX6>pSWSFile6>AddData ( ulSymbolAddress |
, |
||
|
|
ulSymbolSize |
|
, |
|
|
0 |
|
) ) |
|
{ |
|
|
|
ASSERT ( !"Adding to SWS file failed!" ) ; return ( FALSE ) ;
}
pCTX6>iAddedCount++ ; return ( TRUE ) ;
}
Период выполнения и оптимизация
Одна проблема с символами в период выполнения была связана с тем, что сим вольная машина не возвращает статические функции. Становясь подозрительным, если я не находил в модуле адрес, я, как обычно, включал в программу вызовы 6–7 диагностических информационных окон. Сначала я несколько смутился тем, что видел диагностические сообщения, так как в одной из моих тестовых программ никакая функция не была объявлена статической. Взглянув на стек в отладчике, я увидел символ с именем наподобие $E127. В функции имелся вызов _penter, и все казалось правильным. Наконец я понял, что это функция, сгенерированная ком пилятором, такая как конструктор копий. Хотя мне по настоящему нравится вы полнять проверку ошибок в коде, я заметил, что в некоторых программах хвата ло этих сгенерированных компилятором функций, поэтому я мог только сооб щить о проблеме в отладочных компоновках при помощи TRACE.
Последний интересный аспект SWS — оптимизация модуля. Функция TuneModule довольно объемна, поэтому в листинге 19 4 я привел только ее алгоритм. Как вы можете увидеть, на каждой странице кода я размещаю как можно больше функ

