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

         

Чтение и запись памяти


Чтение из памяти подчиненного отладчика довольно простая операция. Выполняет ее функция ReadProcessMemory. Когда основной отладчик запускает подчиненный, то он имеет полный доступ к подчиненному, потому что дескриптор процесса, возвращенный событием отладки CREATE_PROCESS_DEBUG_EVENT, содержит спецификаторы доступа PROCESS_VM_READ и PROCESS_VM_WRITE. Если ваш отладчик прикрепляется к процессу с помощью функции DebugActiveProcess, то, чтобы получить дескриптор подчиненного отладчика, нужно вызвать функцию openProcess и указать при этом доступ как для чтения, так и для записи.

Прежде чем рассказывать о записи в память подчиненного отладчика, нужно кратко объяснить важную концепцию "копирование-при-записи" (сору-on-write) Когда Windows загружает выполняемый файл, она разделяет между различными использующими его процессами так много отображаемых страниц памяти этого двоичного файла, насколько это возможно. Если один из этих процессов выполняется под отладчиком, и одна из этих страниц имеет записанную в нее точку прерывания, то очевидно, что эта точка прерывания не может быть представлена во всех процессах, разделяющих эту страницу. Как только какой-нибудь процесс, работающий вне отладчика, выполнит этот код, он завершится аварийно с исключением EXCEPTION_BREAKPOINT. Чтобы не допустить такой ситуации, операционная система, видя, что страница изменена для конкретного процесса, делает копию этой страницы, которая является частной (private) для процесса, записавшего в нее точку прерывания. Таким образом, как только процесс пишет в страницу памяти, операционная система ее копирует.

Запись в память подчиненного отладчика почти столь же проста, как и чтение из нее. Однако, поскольку страницы памяти, в которые будет произведена запись, могут быть помечены атрибутом "только-для-чтения", то, чтобы получить текущие характеристики защиты страницы, нужно сначала вызвать функцию virtualQueryEx. Имея эти характеристики, можно использовать API-функцию virtualProtectEx, чтобы установить для страницы параметр защиты PAGE_EXECUTE_READWRITE, что позволит вам писать в нее, а Windows сможет подготовиться к выполнению операций "копирование-при-записи".
По завершении записи в память нужно установить защиту страницы в исходное состояние. Если этого не сделать, подчиненный отладчик может случайно записать страницу (и преуспеть в этом, тогда как должен был потерпеть неудачу). Если бы, например, первоначальным параметром защиты страницы было "только-чтение", то случайная запись подчиненного отладчика привела бы к нарушению доступа. Без восстановления защиты страницы (от записи) случайная запись не сгенерирует соответствующего исключения, и вы будете иметь случай, когда выполнение под отладчиком отличается от выполнения вне его.

Интересная деталь отладочного API Win32: когда появляется событие OUTPUT_DEBUG_STRING_EVENT, то ответственным за получение строки для вывода является основной отладчик. Информация, переданная отладчику, включает расположение и длину строки. Получив это сообщение, отладчик читает память вне подчиненного отладчика. В главе 3 упоминалось, что при выполнении под отладчиком операторы трассировки могут легко изменить поведение приложения. Поскольку все потоки в приложении останавливаются, когда цикл отладки обрабатывает событие, вызов функции OutputDebugString в подчиненном отладчике означает, что все потоки стоят. Листинг 4-3 показывает, как WDBG обрабатывает событие OUTPUT_DEBUG_STRING_EVENT. Заметьте, что функция DBG_ReadProcessMemory является функцией-оболочкой вокруг ReadProcessMemory из LOCALASSIST.DLL.

Листинг 4-3.OutputDebugStringEvent изPROCESSDEBUGEVENTS.CPP

static

DWORD OutputDebugStringEvent ( CDebugBaseUser * pUserClass ,

LPDEBUGGEEINFO pData ,

 DWORD dwProcessId, 

DWORD dwThreadld , 

OUTPUT_DEBUG_STRING_INFO & stODSI )

 {

TCHAR szBuff[ 512 ];

HANDLE hProc = pData->GetProcessHandle (); DWORD dwRead;

 // Читать память.

BOOL bRet = DBG_ReadProcessMemory( hProc ,

stODSI.lpDebugStringData , 

szBuff, min ( sizeof ( szBuff) ,

stODSI.nDebugStringLength), 

&dwRead ) ;

ASSERT ( TRUE == bRet);

if ( TRUE == bRet)

{

// Строку всегда завершает NULL . 

szBuff [ dwRead + 1 ] = _T ( '\0');

 // Преобразовать символы CR/LF .

pUserClass->ConvertCRLF ( szBuff, sizeof ( szBuff)); 

// Послать ковертированную строку в класс пользователя.

 pUserClass->OutputDebugStringEvent ( dwProcessId,

dwThreadld , szBuff ); 

}

 return ( DBG_CONTINUE);

}



Содержание раздела