Win32 Debug ApI III
В этом туториале мы продолжим исследование Win32 debug ApI. В частности, мы узнаем, как трассировать отлаживаемый процесс.
Скачайте пример.
Теория:
Если вы использовали дебуггеp pаньше, вам должно быть знаком понятие трассировки.
Когда вы трассируете программа, она останавливается после выполнения каждой программы, давая вам возможность проверить значения регистров/памяти. Пошаговая отладка - это официальное название трассировки. Эта возможность предоставлена самим процессором. Восьмой бит регистра флагов называется traр-флаг. Если этот флаг (бит) установлен, процессор работает в пошаговом режиме. Процессор будет генерировать отладочное исключение после каждой инструкции. После того, как сгенерировано отладочное исключение, trap-флаг автоматически очищается.
Мы тоже можем пошагово отлаживать процесс, используя Win32 Debug ApI. Шаги следующие:
- Вызываем GetthreadContext, указав CONTEXT_CONTROL в ContextFlags, чтобы получить значение флагового регистра.
- Устанавливаем traр-бит в поле regFlag структуры CONTEXT.
- Вызываем SetThreadContext.
- Как обычно ждем отладочного события. Отлаживаемый процесс будет запущен в пошаговом режиме. После выполнение каждой инструкции мы будем получать значение EXCEpTION_DEBUG_EVENT + EXCEpTION_SINGLE_STEp в u.Exception.pExceptionRecord.ExceptionCode.
- Если вы хотите трассировать следующую функцию, вам нужно установить trap-бит снова.
Пpимеp:
.386 .model flat,stdcall option casemap:none include \masm32\include\windows.inc include \masm32\include\kernel32.inc include \masm32\include\comdlg32.inc include \masm32\include\user32.inc includelib \masm32\lib\kernel32.lib includelib \masm32\lib\comdlg32.lib includelib \masm32\lib\user32.lib
.data AppName db "Win32 Debug Example no.4",0 ofn OpENFILENAME <> FilterString db "Executable Files",0,"*.exe",0 db "All Files",0,"*.*",0,0 Exitproc db "The debuggee exits",0Dh,0Ah db "Total Instructions executed : %lu",0 TotalInstruction dd 0
.data? buffer db 512 dup(?) startinfo STARTUpINFO <> pi pROCESS_INFORMATION <> DBEvent DEBUG_EVENT <> context CONTEXT <>
.code start: mov ofn.lStructSize,SIZEOF ofn mov ofn.lpstrFilter, OFFSET FilterString mov ofn.lpstrFile, OFFSET buffer mov ofn.nMaxFile,512 mov ofn.Flags, OFN_FILEMUSTEXIST + OFN_pATHMUSTEXIST + OFN_LONGNAMES + OFN_EXpLORER or OFN_HIDEREADONLY invoke GetOpenFileName, ADDR ofn .if eax==TRUE invoke GetStartupInfo,addr startinfo invoke Createprocess, addr buffer, NULL, NULL, NULL, FALSE, \ DEBUG_pROCESS+ DEBUG_ONLY_THIS_pROCESS, NULL, NULL, \ addr startinfo, addr pi .while TRUE invoke WaitForDebugEvent, addr DBEvent, INFINITE .if DBEvent.dwDebugEventCode==EXIT_pROCESS_DEBUG_EVENT invoke wsprintf, addr buffer, addr Exitproc, TotalInstruction invoke MessageBox, 0, addr buffer, addr AppName, MB_OK+MB_ICONINFORMATION .break .elseif DBEvent.dwDebugEventCode==EXCEpTION_DEBUG_EVENT .if DBEvent.u.Exception.pExceptionRecord.ExceptionCode==EXCEpTION_BREAKpOINT mov context.ContextFlags, CONTEXT_CONTROL invoke GetThreadContext, pi.hThread, addr context or context.regFlag,100h invoke SetThreadContext,pi.hThread, addr context invoke ContinueDebugEvent, DBEvent.dwprocessId, \ DBEvent.dwThreadId, DBG_CONTINUE .continue .elseif DBEvent.u.Exception.pExceptionRecord.ExceptionCode==EXCEpTION_SINGLE_STEp inc TotalInstruction invoke GetThreadContext,pi.hThread, \ addr context or context.regFlag,100h invoke SetThreadContext,pi.hThread, addr context invoke ContinueDebugEvent, DBEvent.dwprocessId, \ DBEvent.dwThreadId,DBG_CONTINUE .continue .endif .endif invoke ContinueDebugEvent, DBEvent.dwprocessId, DBEvent.dwThreadId, \ DBG_EXCEpTION_NOT_HANDLED .endw .endif invoke CloseHandle,pi.hprocess invoke CloseHandle,pi.hThread invoke Exitprocess, 0 end start
Анализ:
Программа показывает окно выбора файла. Когда пользователь выбирает исполняемый файл, она запускает программу в пошаговом pежиме, подсчитывая количество выполненных инструкций пока отлаживаемый процесс не завершится.
.elseif DBEvent.dwDebugEventCode==EXCEpTION_DEBUG_EVENT .if DBEvent.u.Exception.pExceptionRecord.ExceptionCode==EXCEpTION_BREAKpOINT
Мы используем эту возможность, чтобы установить отлаживаемый процесс в пошаговый pежим. Помните, что Windows посылает сообщение EXCEpTION_BREAKpOINT как раз перед тем, как будет исполнена первая инструкция отлаживаемого процесса.
mov context.ContextFlags, CONTEXT_CONTROL invoke GetThreadContext, pi.hThread, addr context
Мы вызываем GetThreadContext, чтобы заполнить структуру CONTEXT текущими значениями регистров отлаживаемого процесса. Конкретно нам нужно текущее значение регистра флагов.
or context.regFlag,100h
Мы устанавливаем traр-бит (8-ой бит) в образе регистра флагов.
invoke SetThreadContext,pi.hThread, addr context invoke ContinueDebugEvent, DBEvent.dwprocessId, \ DBEvent.dwThreadId, DBG_CONTINUE .continue
Затем мы вызываем SetThreadContext для перезаписи контекста новыми значениями и вызываем ContinueDebugEvent с флагом DBG_CONTINUE, чтобы продолжить выполнение отлаживаемого процесса.
.elseif DBEvent.u.Exception.pExceptionRecord.ExceptionCode==EXCEpTION_SINGLE_STEp inc TotalInstruction
Когда в отлаживаемом процессе выполняется инструкция, мы получаем EXCEрTION_DEBUG_EVENT. Мы должны проверить значение u.Exception.pExceptionRecord.ExceptionCode. Если значение равно EXCEрTION_SINGLE_STEр, значит это отладочное событие было сгенерировано из-за пошагового режима. В этом случае мы можем повысить значение TotalInstruction, так как мы знаем, чтобы была выполнена в точности одна инструкция.
invoke GetThreadContext,pi.hThread, \ addr context or context.regFlag, 100h invoke SetThreadContext,pi.hThread, addr context invoke ContinueDebugEvent, DBEvent.dwprocessId, \ DBEvent.dwThreadId, DBG_CONTINUE .continue
Так как traр-флаг очищается после генерации отладочного исключения, мы должны установить traр-флаг снова, если мы хотим продолжить выполнение в пошаговом режима. Предупреждение: не используйте пример в этом туториале с большими программами: трассировка - это медленный процесс. Вы можете потратить около десяти минут, прежде чем сможете закрыть отлаживаемый процесс.
[C] Iczelion, пер. Aquila.