!! Введение !!!! Шаг 1. Начало работы Для установки лучше всего воспользоваться автоматическим инсталлятором. Он установит pykd в нужно место, а также установит и зарегистрирует все необходимые компоненты. Если установка завершилась без ошибок, пора познакомится с pykd. Для этого стартуем windbg и начинаем отладочную сессию ( открываем процесс, дамп или устанавливаем соединение с отладчиком ядра ). Теперь можно загрузить pykd. Для этого выполняем команду: .load pykd.pyd Если во время загрузки случится какая либо ошибка - windbg выдаст сообщение. Отсутствие каких либо сообщений свидетельствует об удачной загрузки расширения. Теперь можно начинать работу. Выполним команду !pycmd. После ее выполнения отладчик перейдет в режим ввода пользовательских данных. Весь пользовательский ввод будет обрабатываться интерпретатором python. {{ 0:000> !pycmd Python 2.6.6 (r266:84297, Aug 24 2010, 18:13:38) [MSC v.1500 64 bit (AMD64)] on win32 Type "help", "copyright", "credits" or "license" for more information. (InteractiveConsole) >>> print "Hello world!" Hello world! >>> }} Тут самое время ознакомится с синтаксисом python, если кто еще не знаком. Уверяю, это не должно занять много времени: python очень прост в освоении. Давайте вспомним базовые основы синтаксиса python: {{ >>> def printHello(): ... i = 0 ... while i < 4: ... print "Hello #%d" % i ... i += 1 ... >>> printHello() Hello #0 Hello #1 Hello #2 Hello #3 >>> }} Обратите внимание: вложенность блоков задается количеством лидирующих пробелов. Это, так сказать, "фирменная" особенность python. Пока этих знаний нам будет вполне достаточно. Двигаемся дальше. !!!! Шаг 2. Доступ к регистрам. Любой отладчик должен предоставлять три базовые возможности: чтение регистров процессора, чтение памяти и управление режимом отладки. Начнем с регистров. С pykd это делается довольно просто: {{ >>> print hex(reg("eip")) 0x778ecb60 >>> print hex(reg("esp")) 0x1ef0e0 >>> print hex(reg("esp")+4) 0x1ef0e4 }} В данном случае, мы используем функцию PYKD *reg*. Она осуществляет чтение регистров процессора по имени. Пытливый читатель может спросить: как мы используем функции из PYKD без импортирования самого модуля? На самом деле, модуль конечно надо импортировать. Просто PYKD это сделал автоматически при конструировании консоли Python. Давайте напишем небольшой пример и посмотрим, куда указывает текущий счетчик инструкций: {{ >>> print findSymbol(reg("eip")) ntdll!LdrpDoDebuggerBreak+30 }} Функция *findSymbol* пытается для данного адреса найти т.н. отладочный символ. В данном случае мы видим, что счетчика инструкций равен смещению 0x30 относительно функции LdrpDoDebuggerBreak, т.е мы попросту находимся внутри функции LdrpDoDebuggerBreak, находящейся в модуле ntdll. Мы это смогли выяснить, поскольку у нас есть отладочная информация для модуля ntdll.dll ( соответствующий pdb файл ). Если у вас по какой то причине символы не показываются, необходимо проверить настройки путей к символам в windbg. !!!! Шаг 4. Доступ к памяти Для доступа к памяти PYKD предлагает большой набор функций. Их можно разделить на 3 группы: * Чтение значения из памяти: *ptrByte* *ptrWord* *ptrDWord* *ptrQWord* И другие, с полным набором можно ознакомится в [справке по API|PYKD 0.2. API Reference] Все функции принимают в качестве параметра адрес и возвращают значение, хранящееся по данному адресу. * Чтение массивов *loadBytes* *loadWords* *loadDWords* *loadQWords* Все функции принимают в качестве параметров указатель на начало массива и его длину в элементах. Возвращают объект *list* c элементами массива * Чтение строк *loadCStr* *loadWStr* Функции читают из памяти 0-терминированные строки возвращают python строки. Давайте модифицируем предыдущий пример и выведем аргументы функции. Будем считать, что функция имеет соглашение о вызове stdcall и ее параметры адресуются регистром ebp {{ >>> def printFunc(): ... print findSymbol( reg("eip") ) ... params = [ ptrDWord( reg("ebp") + 4*(i+1) ) for i in range(0,3) ] ... print "var1: %x var2: %x var3: %x" % ( params[0], params[1], params[2] ) ... >>> print printFunc() ntdll32!LdrpDoDebuggerBreak+2c var1: 774b1383 var2: fffdd000 var3: fffde000 None >>> }} Обратите внимание на конструкцию: {{ params = [ ptrDWord( reg("ebp") + 4*(i+1) ) for i in range(0,3) ] }} Это т.н генератор списка - специальная конструкция python, которую можно использовать для инициализации списков. Это конструкция эквивалентна следующей {{ [ ptrDWord( reg("ebp") + 4) ), ptrDWord( reg("ebp") + 8) ), ptrDWord( reg("ebp") + 0xC) ) ] }} !!!! Шаг 5. Доступ к памяти с учетом типа. При отладке программ мы чаще всего сталкиваемся с типизированными переменными. PYKD имеет богатые возможности для доступа к переменным с учетом типа. По сути, эта главная "фишка" всего проекта: доступ к полям структур и классов осуществляется очень похожим на исходный код способом. Например, допустим у нас есть следующий код на Си: {{ struct STRUCT_A { int filed1; char field2; }; STRUCT_A a = { 100, 2} }} Теперь во время отладки мы хотим проверить состояние переменной 'a' c помощью PYKD: {{ a = typedVar( "module!STRUCT_A", getOffset("module!a") ) if a.field1!=100 or a.field2!=2: print "ERROR! a is not poperly initialized!" }}