понедельник, 10 октября 2011 г.

Обработка исключений и точек останова

В pykd v. 0.0.20 появился новый полезный функционал: обработка исключений и точек останова. Возможность обработки этих событий реализована новыми методами класса debugEvent: onBreakpoint и onException. Для использования этого функционала, как и в случае с событиями загрузки/выгрузки модулей, необходимо реализовать свои вышеописанные обработчики. Сведения о произошедшем событии передаются в эти методы словарями, ключи и типы значений которых указаны в документации. Следует отметить, что для события срабатывания точки останова количество сформированных пар, может быть меньше, чем описано: если точка останова не обладает каким-либо свойством, данные не заносятся в словарь. А состав словаря, передаваемого в обработчик исключения, более стабилен: варьируется только список параметров исключения. Но даже, если у исключения нет параметров, то в словаре все равно окажется список, который будет пуст.

У pykd уже был класс bp, который позволяет ставить/снимать точку останова и назначать собственный обработчик. Этот класс так остался, так как имеет более простой интерфейс, но он был расширен новым конструктором, в который передается только целевой адрес. При установке такой точки, вместо вызова обработчика просто возвращается статус DEBUG_STATUS_BREAK.

Новый функционал debugEvent::onBreakpoint реализован не как замена, а как альтернатива существующему механизму. То есть в одном и том же скрипте можно использовать как механизм bp, так и переопределение метода debugEvent::onBreakpoint. Это было сделано для того, что бы можно было управлять исключениями и точками останова из одного класса - debugEvent.

Мы достаточно мало освещаем тот факт, что pykd не только расширение к WinDbg, но и полноценный модуль для языка python, который позволяет получить доступ к API Debug Engine. Поэтому пример использования обработки исключений будет нацелен именно на такой подход: samples\watchDog.py. В начале скрипт, если переданы аргументы командной строки, стартует указанный отлаживаемый процесс. Затем он ставит обработчик исключения и ожидает возникновения исключения. Но это так же полноценный скрипт для pykd, функционирующего как расширение WinDbg. Например, при вызове из WinDbg можно не указывать параметров, тогда скрипт будет следить за исключениями на отлаживаемом объекте.

Важной особенностью логики скрипта является пропуск исключений, для которых FirstChance == True. Это необходимо для того, что бы пропускать исключения, которые возможно будут обработаны самим отлаживаемым объектом, например если исключение произошло в блоке SEH'а.

Теперь запускаем скрипт командной строкой "python watchDog.py test.exe". Тем самым pykd от лица процесса python.exe создаст отлаживаемый процесс test.exe, который запустится на исполнение с предварительно установленным обработчиком исключений. В test.exe специально был включен следующий код:

__try { *(char *)0 = 1; } __except(EXCEPTION_EXECUTE_HANDLER) {}
*(char *)3 = 1;


Результат исполнения скрипта следующий:

*** shit happens
Exception code : EXCEPTION_ACCESS_VIOLATION
Exception flags : 0
Exception record : 0x0
Exception address : 0x40122C(00401010) test!main+0x21c | (00401250) test!Define_the_symbol__ATL_MIXED::Thank_you::Thank_you

Parameters :
0x1
0x3

eax=00000000 ebx=0018fa64 ecx=00000000 edx=00000000 esi=00401222 edi=00000000eip=774c15ee esp=0018fa50 ebp=0018ff40 iopl=0 nv up ei pl zr na pe nccs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246ntdll!ZwRaiseException+0x12:774c15ee 83c404 add esp,4

ChildEBP RetAddr Args to Child
0018ff40 004017f3 00000001 00302330 00302370 ntdll!ZwRaiseException+0x120018ff88
76ae339a 7efde000 0018ffd4 774d9ed2 test!__tmainCRTStartup+0xfb0018ff94
774d9ed2 7efde000 62b6a849 00000000 kernel32!BaseThreadInitThunk+0x120018ffd4
774d9ea5 0040184a 7efde000 ffffffff ntdll!RtlInitializeExceptionChain+0x630018ffec
00000000 0040184a 7efde000 00000000 ntdll!RtlInitializeExceptionChain+0x36

Как видно по второму параметру исключения (адрес памяти - 0x3) скрипт поймал именно необрабатываемое исключение: использования неверного указателя *(char *)3 = 1. Если включить дополнительную трассировку (например, вывод в консоль) для FirstChance == True, то можно увидеть, что onException срабатывает 3-а раза, первые 2-а из которых игнорируются, так как могут быть обработаны отлаживаемым объектом.

воскресенье, 9 октября 2011 г.

Release 0.0.20

Всем доброго дня! Спешу представить очередной релиз pykd - версия 0.0.20.

Для тех, кто предпочитает словам дела, страница загрузки и удачи в использовании! Я же хотел кратко пробежаться по составу релиза:

Управление отладчиком:


Добавлены следующие функции: breakin, attachProcess, attachKernel, debuggerPath
Наличие функции breakin позволяет написать с помощью pykd свой отладчик. А функции attachProcess и attachKernel в этом помогут ( напомню, что ранее уже были реализованы функции loadDump и createProcess ).

Функция debuggerPath возвращает полный путь к исполняемому модулю программы, использующей pykd. Если вы, к примеру, захотите модифицировать файл symsrv.ini - она может быть полезна.

В ближайшее время мы опубликуем тутор по разработке отладчика на PySide. Следите за блогом!

класс cpuReg


Дополнен такой казалось бы малозначительной деталью, как конструктор с параметром "индекс регистра". Это позволяет реализовать перечисление всех регистров CPU:
def getRegisterSet(self):
    regSet=[]
    try:
        i = 0
        while True:
            reg = pykd.cpuReg(i)
            regSet.append(reg)
            i += 1

    except pykd.BaseException:
        pass

    return regSet

класс typedVar


Добавлен конструктор, принимающий имя переменной нужного типа. При этом сам тип указывать не надо, он будет взят из отладочной информации. Т.е достаточно написать:
var = typedVar( myModule.myVar )

Это особенно удобно при работе с переменными, тип которых заранее не известен. Например, с шаблонами С++.

класс disasm


Добавлен метод assembly, позволяющий изменять код по укзанному ( по умолчанию: по текущему ) смещению:
nt = loadModule( "nt" )
dasm = disasm( nt.NtCreateFile )
dasm.assembly( "int 3" )

getProcessorType


Возвращает тип процессора. Функция вернет строку, описывающую тип физического процессора. Сравните с getProcessorMode, которая возвращает текущий режим. Вот так, к примеру, можно было бы реализовать команду !sw из стандартного расширения wow64ext:
if getProcessorType() == "X64":
  setProcessorMode( {"X64": "X86", "X86": "X64"}[ getProcessorMode() ] )

багфикс


9219: улучшена поддержка многопоточности. Функции, которые могут надолго заблокировать исполнение python программы ( такие как go() и dbgCommand() ) сохраняют состояние потока, что позволяет планировщику python выполнять код в других потоках

9518: Исправлен баг с выводом текста через команду dprint(ln). До этого текст, содержащий символ '%' выводился неправильно ( обычно, обрезался ).

9555: Метод name() класса dbgModuleClass возвращал строку, содержащую нулевой символ. В итоге при использовании данного метода вывод мог повреждаться ( обрезаться )