
Роббинс Д. - Отладка приложений для Microsoft .NET и Microsoft Windows - 2004
.pdf
564 |
ЧАСТЬ IV Мощные средства и методы отладки неуправляемого кода |
|||||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
DD_beginthreadex ( void * |
|
security |
|
, |
|
|
|
|
unsigned |
|
stack_size |
, |
|
|
||
|
unsigned ( __stdcall *start_address )( void * ) , |
|||||||
|
void * |
|
arglist |
|
, |
|
|
|
|
unsigned |
|
initflag |
|
, |
|
|
|
|
unsigned * |
thrdaddr |
|
) ; |
|
|
||
uintptr_t |
|
|
|
|
|
|
|
|
|
DD_beginthread ( void( __cdecl *start_address )( void * ) , |
|
||||||
|
unsigned |
stack_size |
|
|
|
, |
|
|
|
void * |
arglist |
|
|
|
|
) ; |
|
VOID DD_endthreadex ( unsigned retval ) ; |
|
|
|
|
|
|||
VOID DD_endthread ( void ) ; |
|
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////// |
||||||||
// Функции ожидания и специальные функции |
|
|
|
|
|
|||
DWORD DD_WaitForSingleObject ( HANDLE |
hHandle |
|
, |
|
|
|
||
|
|
DWORD |
dwMilliseconds |
) ; |
|
|
||
DWORD DD_WaitForSingleObjectEx ( HANDLE hHandle |
|
, |
|
|
||||
|
|
DWORD |
dwMilliseconds , |
|
|
|||
|
|
BOOL |
bAlertable |
) ; |
|
|
||
DWORD DD_WaitForMultipleObjects( DWORD |
|
|
nCount |
|
|
, |
||
|
|
CONST HANDLE * lpHandles |
|
, |
||||
|
|
BOOL |
|
|
bWaitAll |
|
, |
|
|
|
DWORD |
|
|
dwMilliseconds |
) ; |
||
DWORD DD_WaitForMultipleObjectsEx( DWORD |
|
nCount |
|
, |
||||
|
|
CONST HANDLE * lpHandles |
|
, |
||||
|
|
BOOL |
|
bWaitAll |
|
, |
||
|
|
DWORD |
|
dwMilliseconds , |
||||
|
|
BOOL |
|
bAlertable |
|
) ; |
||
DWORD DD_MsgWaitForMultipleObjects ( DWORD |
nCount |
|
, |
|
||||
|
|
LPHANDLE |
pHandles |
|
, |
|
||
|
|
BOOL |
fWaitAll |
|
, |
|
||
|
|
DWORD |
dwMilliseconds, |
|
||||
|
|
DWORD |
dwWakeMask |
) ; |
||||
DWORD DD_MsgWaitForMultipleObjectsEx ( DWORD |
|
nCount |
|
|
, |
|||
|
|
|
LPHANDLE |
pHandles |
|
, |
||
|
|
|
DWORD |
|
dwMilliseconds |
, |
||
|
|
|
DWORD |
|
dwWakeMask |
|
, |
|
|
|
|
DWORD |
|
dwFlags |
|
) ; |
|
DWORD DD_SignalObjectAndWait ( HANDLE hObjectToSignal , |
|
|
||||||
|
|
HANDLE |
hObjectToWaitOn |
, |
|
|
||
|
|
DWORD |
dwMilliseconds |
, |
|
|
||
|
|
BOOL |
bAlertable |
|
) ; |
|
|
|
BOOL DD_CloseHandle ( HANDLE hObject ) ; |
|
|
|
|
|
|||
//////////////////////////////////////////////////////////////////////// |
||||||||
// Функции работы с критическими секциями |
|
|
|
|
|
|||
VOID DD_InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection); |
||||||||
BOOL DD_InitializeCriticalSectionAndSpinCount ( |
|
|
|
|
||||
|
|
LPCRITICAL_SECTION lpCriticalSection, |
||||||
|
|
|
|
|
|
|
|
|

566 |
ЧАСТЬ IV Мощные средства и методы отладки неуправляемого кода |
||||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
BOOL |
|
bManualReset |
, |
|
|
|
|
BOOL |
|
bInitialState |
, |
|
|
|
|
LPCSTR |
lpName |
) ; |
|
|
|
HANDLE DD_CreateEventW |
( LPSECURITY_ATTRIBUTES |
lpEventAttributes |
, |
|
||
|
|
|
BOOL |
|
bManualReset |
, |
|
|
|
|
BOOL |
|
bInitialState |
, |
|
|
|
|
LPCWSTR |
lpName |
) ; |
|
|
|
HANDLE DD_OpenEventA ( |
DWORD |
dwDesiredAccess |
, |
|
|
|
|
|
|
BOOL |
bInheritHandle |
, |
|
|
|
|
|
LPCSTR lpName |
) ; |
|
|
|
|
HANDLE DD_OpenEventW ( |
DWORD |
dwDesiredAccess |
, |
|
|
|
|
|
|
BOOL |
bInheritHandle |
, |
|
|
|
|
|
LPCWSTR lpName |
) ; |
|
|
|
|
BOOL DD_PulseEvent ( HANDLE hEvent ) ; |
|
|
|
|||
|
BOOL DD_ResetEvent ( HANDLE hEvent ) ; |
|
|
|
|||
|
BOOL DD_SetEvent ( HANDLE hEvent ) ; |
|
|
|
|||
|
#endif |
// _DD_FUNCS_H |
|
|
|
|
|
|
|
|
|
|
|
|
|
В первой версии DeadlockDetection я допустил глупейшую ошибку «наследо! вания при редактировании». Я создал функции!ловушки для LoadLibraryA и LoadLib raryW и понял, что мне также нужны ловушки для LoadLibraryExA и LoadLibraryExW. Как типичный программист, я вырезал функции LoadLibraryA/W, вставил их в нуж! ное место кода и отредактировал с учетом особенностей LoadLibraryExA/W. Если вы посмотрите на макрос HOOKFN_EPILOG, то увидите, что он принимает некоторое значение, а именно число параметров функциии. Наверное, вы уже догадались, что случилось: я забыл изменить это значение с 1 на 3, поэтому вызовы LoadLibrary ExA/W удаляли из стека два лишних элемента и приводили к краху программ!
Изучив код всех функций!ловушек, я понял, что он по сути везде одинаков. Для инкапсуляции общих действий я создал макросы HOOKFN_STARTUP и HOOKFN_SHUTDOWN. Как видно по имени, макрос HOOKFN_STARTUP размещается в начале функции!ловушки и заботится о прологе, а также выполняет необходимое протоколирование до вызова действительной функции. Он принимает следующие параметры: перечис! ление функции, флаг DDOPT_*, показывающий, к какой группе относится данная функция, и булев флаг, выполняющий предварительное протоколирование, если имеет значение TRUE. Предварительное протоколирование предназначено для функций, которые потенциально могут вызывать блокировку, таких как WaitFor SingleObject. Макрос HOOKFN_SHUTDOWN принимает число параметров функции и тот же флаг DDOPT_*, что передается в HOOKFN_STARTUP. Конечно, чтобы не допустить ту же ошибку, которую я сделал в случае ловушек LoadLibraryExA/W, я проверил число параметров HOOKFN_SHUTDOWN.
Я хочу упомянуть еще несколько деталей. Во!первых, DeadlockDetection все! гда активна в вашем приложении, даже если вы приостанавливаете протоколиро! вание. Вместо того чтобы устанавливать и удалять ловушки, я оставляю функции перехваченными и изучаю некоторые внутренние флаги, чтобы определить, как ловушка должна себя вести. Поддержание всех функций в перехваченном состо! янии упрощает переключение между разными протоколируемыми функциями в
ГЛАВА 15 Блокировка в многопоточных приложениях |
567 |
|
|
период выполнения, но несколько снижает эффективность вашей программы. Я чувствовал, что реализация перехвата и его отмены «на лету» привела бы к по! явлению дополнительных ошибок.
Во!вторых, DeadlockDetection перехватывает функции при загрузке DLL в вашу программу с помощью LoadLibrary. Однако DeadlockDetection может получить контроль только после выполнения в DLL функции DllMain, поэтому, если какие! то объекты синхронизации создаются или используются во время DllMain, Deadlock! Detection может их упустить.
В!третьих, DeadlockDetection также перехватывает функции GetProcAddress и ExitProcess. Перехват GetProcAddress полезен, если ваша программа или элемент управления сторонней фирмы, который может привести к блокировке, вызывает GetProcAddress для нахождения метода синхронизации в период выполнения.
Я перехватываю ExitProcess потому, что при завершении приложения мне нужно отменить перехват функций и завершить DeadlockDetection, чтобы она не вызва! ла крах или зависание вашей программы. Так как при завершении программы контролировать порядок выгрузки DLL невозможно, вы с легкостью можете по! пасть в ситуацию, когда DLL утилиты DeadlockDetection, такая как DeadDetExt, выгружается до самой DeadlockDetection. К счастью, очень немногие программи! сты обрабатывают несколько потоков после вызова ExitProcess.
Перехват ExitProcess реализован в файле DEADLOCKDETECTION.CPP. Из!за ог! ромной важности правильного завершения DeadlockDetection я принудительно перехватываю все вызовы ExitProcess даже в игнорируемых модулях. Это позво! ляет исключить неожиданные ошибки, при которых функции синхронизации остаются перехваченными после завершения DeadlockDetection.
Наконец, вместе с DeadlockDetection вы можете найти на CD несколько тесто! вых программ. Все они включены в главное решение DeadlockDetectionTests и компонуются вместе с DEADLOCKDETECTION.DLL. Они помогут вам понять работу DeadlockDetection.
Что после DeadlockDetection?
DeadlockDetection — достаточно полная утилита, и я успешно применял ее для отслеживания многих многопоточных блокировок. Однако, как всегда, я предла! гаю вам обдумать возможности расширения DeadlockDetection, чтобы сделать ее еще полезнее. Вот некоторые мои идеи по этому поводу.
Создайте отдельное приложение для работы с файлом DEADLOCKDETECTI! ON.INI. Будет еще лучше, если оно позволит указывать DLL DeadDetExt и будет проверять, что эта DLL экспортирует корректные функции.
Вы можете оптимизировать функции!ловушки, если они не выполняют про! токолирования. В этом случае нужно копировать не все значения регистров.
На данный момент DeadlockDetection просто пропускает перехват некоторых DLL, которые ей указаны. Было бы великолепно, если б вы разработали меха! низм пропуска DLL с учетом выполняемой программы.

ГЛАВА 15 Блокировка в многопоточных приложениях |
569 |
|
|
Резюме
Разрабатывать многопоточные приложения трудно, и в этой области встречают! ся одни из самых сложных ошибок. В данной главе я обсудил методы, которые помогут вам избегать блокировок с самого начала проекта. Как я подчеркнул в начале главы, при программировании многопоточного приложения особую важ! ность приобретает заблаговременное планирование, поэтому, приступая к рабо! те над такой программой, вы должны предоставить своей группе достаточно вре! мени и ресурсов для тщательного и правильного ее проектирования. Однако, когда вы столкнетесь с неизбежными многопоточными блокировками, не нужно пани! ковать — в этой главе я представил утилиту DeadlockDetection, которая скажет вам, какие потоки заблокированы на каком объекте синхронизации.
Наконец (и важность этого момента нельзя переоценить), при программиро! вании многопоточных приложений вы должны разрабатывать, выполнять и тес! тировать их на многопроцессорных компьютерах, иначе многопоточность луч! ше вообще не использовать — так вы избежите некоторых чрезвычайно серьез! ных ошибок.

Г Л А В А
16
Автоматизированное
тестирование
В главе 3 я рассказал про блочные тесты и объяснил, почему они так важны для разработки высококачественного кода. Если вы работаете преимущественно над внутренней логикой приложения, блочные тесты могут быть довольно просты. На CD в каталоге BugslayerUtil\Tests вы можете найти все блочные тесты, использо! ванные мной при разработке BUGSLAYERUTIL.DLL. Почти все они — консольные приложения, прекрасно справляющиеся со своими обязанностями.
К сожалению, тестирование пользовательского интерфейса (UI) гораздо сложнее независимо от того, является ли приложение «толстым клиентом» Microsoft .NET или работает на базе браузера. В этой главе я расскажу про мою утилиту Tester, которая поможет вам автоматизировать тестирование кода UI. По сравнению с версией Tester, включенной в первое издание книги, новая утилита Tester уже при! ближается к возможностям полного коммерческого средства регрессивного тес! тирования. Tester на самом деле применяют очень многие группы разработчиков, чем я весьма польщен. Она не только проще в работе многих коммерческих сис! тем, но и гораздо дешевле.
Проклятие блочного тестирования: UI
Абсолютно уверен, что если разработчики программ для Microsoft Windows и получают туннельный синдром, то это вызвано не написанием исходного кода, а многократными нажатиями одних и тех же комбинаций клавиш при тестирова! нии приложений. После пятитысячного нажатия Alt+F, O запястья сковываются сильнее, чем арматура, залитая бетоном. При отсутствии средства, автоматизиру! ющего доступ к различным функциям ваших приложений, обычно необходимо следовать некоторому сценарию, чтобы гарантировать, что блочное тестирова!

572 ЧАСТЬ IV Мощные средства и методы отладки неуправляемого кода
ства регрессивного тестирования, то знаете, насколько различные функции они могут поддерживать: от простого управления окном до проверки самых сложных и причудливых свойств окна. Я хотел сосредоточиться на потребностях разработ! чиков во время блочного тестирования и сделать Tester простым в использова! нии. Вот какими были основные требования к Tester.
1.Возможность управления им при помощи любого языка, поддерживающего COM.
2.При получении строки нажатых клавиш в формате, используемом классом System.Windows.Forms.SendKeys, Tester должен уметь проигрывать ее активному окну.
3.Tester должен поддерживать возможность нахождения любых окон верхнего уровня или дочерних окон по их заголовку или классу.
4.При получении любого HWND Tester должен быть способен получить все свой! ства окна.
5.Tester должен уведомлять пользовательский сценарий о создании/уничтоже! нии конкретного окна, чтобы сценарий мог обработать потенциальные усло! вия ошибки или выполнить дополнительную обработку окна. Tester не должен ограничивать возможность расширения кода, позволяя разработчикам удов! летворить любые свои потребности.
6.Tester должен уметь записывать нажатия клавиш в строки, совместимые с его модулем воспроизведения.
7.Сохраняемые сценарии Tester должны быть полными, т. е. готовыми к запуску.
8.Пользователь должен иметь возможность редактирования автоматически сге! нерированного сценария перед его сохранением.
9.Tester должен гарантировать правильность потока воспроизведения информа! ции, присваивая фокус конкретным окнам, в том числе любым дочерним эле! ментам управления.
Tester поддерживает практически полный набор функций, однако он, навер! ное, не решит всех задач, поставленных перед вашим отделом контроля качества, состоящим из 20 человек. Я просто хотел создать средство, которое позволило бы нам, программистам, автоматизировать блочное тестирование. Думаю, Tester от! вечает этим требованиям. Он очень помог мне при разработке WDBG, отладчика с графическим пользовательским интерфейсом (GUI), описанного в главе 4. Са! мый приятный аспект работы с Tester при создании WDBG заключался в том, что он избавил меня от многих тысяч нажатий клавиш. Как видите, я уже добрался до 16 главы и все еще могу двигать пальцами!
Использование Tester
Работать с Tester относительно просто. Сначала я расскажу про объект Tester и его использование в сценариях, а затем перейду к обсуждению записи сценариев при помощи программы TESTREC.EXE. Разобравшись с объектом, который Tester пре! доставляет вашим сценариям, вы сможете создавать их более эффективно.