[0.3.x] branch : docs

git-svn-id: https://pykd.svn.codeplex.com/svn@87944 9b283d60-5439-405e-af05-b73fd8c4d996
This commit is contained in:
SND\kernelnet_cp 2014-04-16 16:03:26 +00:00 committed by Mikhail I. Izmestev
parent d90c4a2776
commit 6ece37ff95
2 changed files with 1023 additions and 0 deletions

898
docs/ru/documentation.txt Normal file
View File

@ -0,0 +1,898 @@
{anchor:table}
! Оглавление
* [1. Введение|#intro]
** [1.1 Общие сведения|#commoninfo]
** [1.2 Быстрый старт|#gettingStarted]
** [1.3 Сборка из исходников|#Building]
** [1.4 Ручная установка|#Installing]
** [1.5 Изменения в API|#APIchange]
* [2. Команды для windbg|#windbg]
** [2.1 Загрузка плагина|#loadplugin]
** [2.2 Запуск скрипта|#runscript]
** [ 2.3 режим консоли|#console]
* [3. Управление отладкой|#debugging]
** [3.1 Остановка и возобновление процесса отладки|#break]
** [3.2 Пошаговое выполнение|#step]
** [3.3 Управление отладкой из python приложений|#consoledebug]
** [3.4 Печать отладочной информации|#dbgprint]
** [3.5 Выполнение команд отладчика|#dbgcommand]
** [3.6 Создание креш-дампа|#createcrash]
* [4. Работа с памятью и регистрами|#memory]
** [4.1 Доступ к регистрам общего назначения|#reg]
** [4.2 Доступ к модельно специфичным регистрам|#msr]
** [4.3 Нормализация виртуальных адресов|#addr64]
** [4.4 Прямой доступ к памяти|#memaccess]
** [4.5 Ошибки доступа к памяти|#memerror]
** [4.6 Чтение строк из памяти|#memstr]
* [5. Модули|#modules]
** [5.1 Класс module|#moduleclass]
** [5.2 События загрузки и выгрузки модулей|#moduleload]
* [6. Получение символьной информации|#syminfo]
** [6.1 Символьные ( pdb ) файлы|#pdbfile]
** [6.2 Информация о типах|#types]
* [7. Типизированные переменные|#typedVar]
** [7.1 Класс typedVar|#typedVar]
** [7.2 Создание экземпляра класса typedVar|#typedVarClass]
** [7.3 Методы класса typedVar|#typedVarMethod]
** [7.4 Классы и структуры|#typedVarUDT]
** [7.5 Массивы и указатели|#typedVarArray]
** [7.6 Энумераторы|#typedVarEnum]
** [7.7 Приведениe к другим типам|#typedVarCast]
* [8. Процессы и потоки|#ProcessThreads]
** [8.1 Потоки в пользовательском режиме|#UserModeThreads]
** [8.2 Потоки в режиме ядра|#KernelModeThreads]
** [8.3 Процессы в режиме ядра|#KernelModeProcess]
* [9. Локальные переменные|#Locals]
** [9.1 Текущие локальные переменные|#CurrentLocals]
* [10. Точки останова|#breakpoints]
** [10.1 Задание точек останова|#setBreakpoints]
** [10.2 Условные точки останова|#condBreakpoints]
* [11. Отладочные события|#eventHandler]
* [12. Класс disasm|#disasm]
* [API Reference|PYKD 0.2. API Reference]
{anchor:intro}
! 1. Введение
{anchor:commoninfo}
!!! 1.1 Общие сведения
Проект pykd стартовал в 2010 году. Основным мотивом для разрботки было неудобство встроенных средств для написания отладочных скриптов для windbg. Язык python был выбран в качестве альтернативного скриптового двжика по многим причинам: легкость изучения самого языка, наличие большой стандартной библиотеки, наличие мощного и удобного фреймворка для создания модулей расширения. Pykd представляет собой модуль для интерпретатора CPython. Сам pykd написан на C++ и использует Boost.Python для экспорта функция и классов в Python. Pykd предоставляет доступ к управлению отладкой на платформе Windows через библиотеку Debug Engine и получению символьной информации через библиотеку MS DIA. Отметим, что pykd не дает прямого доступа к COM интерфейсам Debug Engine и MS DIA. Вместо этого он реализует собственный интерфейс, делающий процесс разработки более быстрым и удобным ( мы на это надеемся ).
Отметим, что pykd может работать в двух режимах: как плагин для windbg, в этом случае он предоставляет команды для запуска скриптов в контексте отладочной сесии; как отдельный модуль для интерпретатора python. Последний режим может быть полезен для создания автоматических средств разбора креш-дампов например.
[Содержание|#table]
{anchor:gettingStarted}
!!! 1.2 Быстрый старт
Для быстрого старта лучше всего скачать автоматический инсталлятор. Он сам установит все необходимые компоненты ( в т.ч. и Python если он еще не установлен ). Чтобы убедится, что установка прошла успешно, запускаем winbg и начинаем отладку приложения и или анализ дампа. Загружаем *pykd*:
{{
.load pykd.pyd
}}
Если не возникло никаких сообщений об ошибках, значит все нормально. Но на всякий случай убедимся, что все действительно работает:
{{
>!pycmd
Python 2.6.5 (r265:79096, Mar 19 2010, 18:02:59) [MSC v.1500 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>>print "Hello world!"
Hello world!
>>>quit()
>
}}
Пробуем запустить скрипты из примеров:
{{
!py help
!py samples
}}
Если все получилось, можно переходить к написанию собственных скриптов.
[Содержание|#table]
{anchor:Building}
!!! 1.3 Сборка из исходников
!!!! Забираем исходники из [url:репозитория|http://pykd.codeplex.com/SourceControl/list/changesets].
!!!! Устанавливаем python
Скачать нужную версию можно [url:здесь|http://www.python.org].
!!!! Устанавливаем и настраиваем boost.
Если кто не знает, где взять: [url:http://www.boost.org]. Там же есть инструкция по установке и сборке.
!!!! Устанавливаем переменные окружения.
Для сборки требуются следующие переменные окружения:
{"$(DIA_SDK_ROOT)"} - путь к библиотеке MS DIA. Он должен выглядеть примерно так: C:\Program Files (x86)\Microsoft Visual Studio 9.0\DIA SDK. Библиотека MS DIA устанавливается вместе с Visual Studio.
{"$(DBG_SDK_ROOT)"} - путь к Debug Engine SDK Он должен выглядеть примерно так: C:\Program Files (x86)\Debugging Tools for Windows (x86)\sdk. Debug Engine SDK устанавливается вместе с Debugging Tools for Windows ( в настоящее время, входит в Platform SDK ).
{"$(BOOST_ROOT)"}- путь к каталогу, куда установлен boost
{"$(PYTHON_ROOT)"} - путь к каталогу, куда установлен python. Предполагается, что в системе установлены обе версии x86 и x64 и структура каталогов с python такая:
C:\Python26\x86\...
C:\Python26\x64\...
Переменная {"$(PYTHON_ROOT)"} в этом случае должна быть равна C:\Python26. Если в пути установки python а отсутсвует указани платформы, то надо будет подправить проектный файл.
!!!! Сборка библиотек boost.python
Для сборки потребуются статические библиотеки boost.python. В проектном файле прописаны следеующие пути к библиотекам boost.python:
{"$(BOOST_ROOT)\stage"} - для х86 сборки
{"$(BOOST_ROOT)\stage64"} - для х64 сборки
Собрать их можно с помощью команд:
{"bjam --stagedir=stage --with-python stage"}
{"bjam address-model=64 --stagedir=stage64 --with-python stage"}
Если у вас не установлен еще bjam, то скачать его можно [url:здесь|http://www.boost.org/users/download/boost_jam_3_1_18.html]
[Содержание|#table]
{anchor:Installing}
!!! 1.4 Ручная установка
Для ручной установки потребуется сам модуль pykd.pyd {"C+ редистрибутив runtime С++"} от visual studio ( vcredist ), при чем именно тот, с которым был собрано моудль. Если вы собиралм его самостоятельно, с этим проблем быть не должно. Если скачали с сайта - также проблем быть не должно, так как zip архив содержит нужный vcredist ( если конечно мы ничего не напутали при релизе :) ).
!!!! Куда скопировать pykd.pyd?
Это зависит от сценария использования. Если pykd будет использоваться как плагин к windbg, то имеет смысл скопировать его в каталог winext ( он находится в каталоге, куда установлен windbg ). В этом случае, его можно переименовать в pykd.dll, чтобы при загрузке можно было опустить расширение файла:
{{
kd>.load pykd
}}
Если pykd будет использовать для написания своих python программ, то его нужно разместить там, где его сможет найти интерпретатор python. Есть три варианта:
* Подкаталог Lib в катлоге, куда установлен python
* Любой свой каталог. Путь к нему необходимо указать в переменной окружения $(PATHONPATH} или задать через реестр
* Любой свой, путей нигде не прописывать, запускать python всегда из каталога с pykd.pyd
!!!! Установка vcredist
Конечно vcredist нужно установить. Иначе зачем его было скачивать?
!!!! Регистрация MS DIA
Библиотека MS DIA будет установлена во время инсталляции vcredist. Но для работы ее нужно еще зарегистрировать. Для этого необходимо найти каталог, куда был проинсталлирован модуль msdia90.dll и из этого каталога выполнить команду:
{{
regsvr32 msdia90.dll
}}
Если вы собирали модуль самостоятельно и использовали Visual Studio, то никаких действий с vcredist ом производить не надо - он уже есть на вашей машине и MS DIA также на месте.
[Содержание|#table]
{anchor:APIchange}
!!! 1.5 Изменения в API
!!!! loadModule
Функция loadModule убрана. Вместо нее необходимо использовать конструктор класса *module*
{{
# mod = loadModule('mymodule")
mod = module("mymodule")
}}
[Содержание|#table]
{anchor:windbg}
! 2. Команды для windbg
{anchor:loadplugin}
!!! 2.1 Загрузка плагина
Для загрузки плагина в windbg необходимо выполнить команду:
{{
kd>.load pykd_path/pykd.pyd
}}
Если pykd.pyd находится в каталоге winext ( подкаталог в Debugging Tools for Windows ), то путь к pykd можно не указывать:
{{
kd>.load pykd.pyd
}}
Если pykd.pyd переименовать в pykd.dll, то расширение можно не указывать:
{{
kd>.load pykd
}}
Просмотреть загруженные расширения windbg можно с помощью команды .chain
{{
kd>.chain
}}
Выгрузить плагин:
{{
kd>.unload pykd_path/pykd.pyd
kd>.unload pykd.pyd
kd>.unload pykd
}}
Чтобы не загружать pykd каждый раз, можно после загрузки плагина выполнить команду "Save Workspace". После этого pykd будет загружаться автоматически для данного воркспейса.
[Содержание|#table]
{anchor:runscript}
!!! 2.2 Запуск скрипта
Запуск скрипта осуществляется с помощью команды *!py*
{{
kd>!py script_path/script_name.py param1 param2 ...
}}
Расширение .py можно опустить. Чтобы не указывать полный путь к скрипту, нужно прописать его в переменную окружения PYTHONPATH или ( это более предпочтительный путь ), добавить ключ pykd в раздел реестра
{"HKEY_LOCAL_MACHINE\SOFTWARE\Python\PythonCore\2.6\PythonPath"}
В этом случае пути к скриптам задаются в Default значении созданного ключа.
К переменным в скрипте можно получить доступ через список sys.argv:
{{
import sys
print "script path: " + sys.argv[0]
print "param1: " + sys.argv[1]
print "param2: " + sys.argv[2]
}}
[Содержание|#table]
{anchor:console}
!!! 2.3 режим консоли
Запуск консоли python осуществляется командой !pycmd:
{{
1: kd> !pycmd
Python 2.6.5 (r265:79096, Mar 19 2010, 18:02:59) [MSC v.1500 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>>
}}
Перед запуском будет автоматически выполнен импорт pykd, так что сразу можно вызывать функции pykd. Напомним, что выход из консольного режима осуществляется через функцию quit(). При этом состояние python интерператора сохраняется:
{{
>>> a = 10
>>> quit(0)
1: kd> !pycmd
Python 2.6.5 (r265:79096, Mar 19 2010, 18:02:59) [MSC v.1500 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> print a
10
>>>
}}
[Содержание|#table]
{anchor:debugging}
! 3. Управление отладкой
{anchor:break}
!!! 3.1 Остановка и возобновление процесса отладки
В windbg для этого служат команды Break ( Ctrl+Break ) и Go ( F5 ).
Их аналоги в pykd:
*go()*
*breakin()*
*go* возобновляет процесс отладки и вернет управление только когда отладчик будет снова остановлен - сработает точка останова или отладка будет остановлена вручную через Ctrl+Break.
Это нужно учитывать при написании скриптов. Функция может вернуть исключение *DbgException*. Обычно это происходит если отлаживаемый процесс завершается.
{{
try:
while True:
go()
print "break"
except:
print "process terminted"
}}
Данный скрипт будет обрабатывать любые остановки отладчика и автоматически возобновлять исполнение.
Использовать функцию *breakin* при обычной работе врядли понадобится. Дело в том, что скрипты как правило выполняются только во время остановки отладчика. А в этот момент вызов функции *breakin* не имеет смысла. Для того, чтобы можно было останавливать процесс отладки из скрипта, придется создать отдельный поток в котором и вызывать эту функцию.
*Внимание!* Не пытайтесь использовать функции *breakin*, *go*, *trace* внутри обработчиков отладочных событий ( например, в условных точках останова ).
[Содержание|#table]
{anchor:step}
!!! 3.2 Пошаговое выполнение
Для пошаговой отладки ( трассировки ) служат две функции:
*step()*
*trace()*
Из действие аналогично командам отладки "trace into" и "trace over". Обе функции могут вернуть исключение *DbgException*, если отлаживаемый процесс уже завершился.
[Содержание|#table]
{anchor:debugstatus}
{anchor:consoledebug}
!!! 3.3 Управление отладкой из python приложений.
Если вы хотите исполнять свои скрипты вне windbg, то первым шагом, который вам необходимо сделать, будет создание отладочной сессии. Более подробно управление сессиями будет рассмотрено в соответствующем разделе. Если ваше приложение не планрует использовать несколько сессий отладки, то заботится об этом не надо - первая сессия будет создана автоматически при следующих вызовах:
*loadDump( dumpName )* - загружает креш-дамп
*id startProcess( imageName)* - запускает в режиме отладки новый процесс
*id attachProcess( processId)* - присоединяет отладчик к существующему процессу
*attachKernel( parameterStr)* - присоединяет отладчик к ядру отлаживаемой системы
Для отсоединения отладчика от отлаживаемого процесса служит вызов *detachProcess(id)*
Для остановки отладки и удаления отлаживаемого процесса служит
функция *killProcess(id)*
Узнать, режим отладчика можно с помощью вызовов:
* bool isDumpAnalyzing()
* bool isKernelDebugging()
Первая функция позволяет определить, находится ли отладчик в состоянии "живой" отладки или анализируется дамп памяти. Вторая функция позволяет различать отладку режима ядра или пользовательского режима. Если скрипт использует специфичные системные символы ( к примеру, символы ядра Windows ), то будет полезно вставить такую проверку в начале скрипта: это позволит сообщить пользователю, что он пытается запустить скрипт в неподходящей ситуации.
[Содержание|#table]
{anchor:dbgprint}
!!! 3.4 Печать отладочной информации
Для вывода информации на экран можно воспользоваться стандартным оператором *print*. Но рекомендуется использовать специальные функции:
*dprint( message, dml = False )*
*dprintln( message, dml = False )*
Вторая функция отличается от первой тем, что автоматически добавляет символ перевода строки. Опциональный параметр *dml* включает вывод с DML разметкой ( работает только в windbg ). Разметка DML похожа на очень-очень упрощенный HTML. Форматирование текста осуществляется с помощью специальных тегов:
* <b></b> - выделенный шрифт
* <i></i> - курсив
* <u></u> - шрифт с подчеркиванием
* <link cmd = "cmdStr">command</link> - выполнение команды ( похоже на тег <a> в HTML )
{{
dprinln("<b><u>This command reload all symbols</b></u>", True)
dprinln("<link cmd=\".reload /f\">reload</link>", True)
}}
[Содержание|#table]
{anchor:dbgcommand}
!!! 3.5 Выполнение команд отладчика
Для выполнения команды отладчика служит функция:
*commandOutput dbgCommand( commandStr )*
{{
s = dbgCommand("!analyze -v")
dprint(s)
}}
Для вычисления выражения ( аналог команды отладчика "?" ) служит функция
*expressionStr expr( expressionStr )*
{{
expr("@rax + 10")
}}
При выполнении python программы вам могут понадобиться команды из стандартных расширений windbg ( например, !analyze ). Их придется загрузить вручную. Для этого служит функция:
*long loadExt( extensionPath )*
Эта функция возвращает дескриптор расширения, который нужен для вызова функция расширения:
* str callExt( extHandle, command, params )
И, если необходимо, для выгрузки расширения:
* removeExt( extHandle )
*Внимание!* Работа с раcширениями windbg отличается от версии *pykd* 0.1! В версии 0.2 упразднен класс *ext* и можно не заботится о времени жизни загруженного расширения.
[Содержание|#table]
{anchor:createcrash}
!!! 3.6 Создание креш-дампа
Сохранить состояние системы в виде креш-дампа можно с помощью ф. *writeDump*. Функция доступна для режима ядра и пользовательского режима. Второй параметр задает тип дампа ( False - полный дамп, True - минидамп )
{{
writeDump( r"C:\dump\fulldump.dmp", False )
writeDump( r"C:\dump\minidump.dmp", True )
}}
[Содержание|#table]
{anchor:memory}
! 4. Работа с памятью и регистрами
{anchor:reg}
!!! 4.1 Доступ к регистрам общего назначения.
Доступ к регистрам общего назначения ( GPR ) осуществляется с помощью ф. *reg*:
* cpuReg reg( regName )
* cpuReg reg( regIndex )
Первый варинат принимает символьное имя регистра, второй - целочисленных индекс. Вторую форму можно использовать для перечисления регистров:
{{
import pykd
try:
i = 0
while True:
r = pykd.reg(i)
pykd.dprintln( "%s %x ( %d )" % ( r.name(), r, r )
i += 1
except pykd.BaseException:
pass
}}
Оба варианта функции *reg* возвращают экземпляр класса *cpuReg*. Если информация о регистре не может быть получена, будет возбужено исключение *BaseException*
Класс *cpuReg* имеет два метода:
* name()
* index()
Класс *cpuReg* может использовать в целочисленных вычислениях без дополнительных приведений типа:
{{
r = reg("eax")
print r / 10 * 234
}}
*Внимание:*
Текущая реализация *pykd* поддерживает работу только с целочисленными регистрами. Работа с FPU, MMX, SSE регистрами не поддерживается. Поддержка планируется в следующих версиях.
[Содержание|#table]
{anchor:msr}
!!! 4.2 Доступ к модельно-специфичным регистрам ( MSR )
Доступ к модельно-специфичный регистрам осуществляется через ф. *rdmsr( msrNumber )*:
{{
>>> print findSymbol( rdmsr( 0x176 ) )
nt!KiFastCallEntry
}}
[Содержание|#table]
{anchor:addr64}
!!! 4.3 Нормализация виртуальных адресов
Все функции *pykd* возвращают виртуальные адреса в т.н нормализованном виде. Он представляет собой 64 битное целое число. Для 32 битных платформ адрес раширяется с учетом знака до 64 битного. Эта операция на С выглядит так:
{{
ULONG64 addr64 = (ULONG64)(LONG)addr;
}}
Таким образом адреса будет преобразовываться так:
0x00100000 -> 0x00000000 00100000
0x80100000 -> 0xFFFFFFFF 80100000
Это нужно учитывать, если адреса, которые возвращают функции *pykd*, участвуют в арифметических операциях. Для исключения возможных ошибок сравнения рекомендуется использовать ф. *addr64()*:
{{
import pykd
nt = pykd.module("nt")
if nt > addr64( 0x80000000 ):
print "nt module is in highest address space"
}}
[Содержание|#table]
{anchor:memaccess}
!!! 4.4 Прямой доступ к памяти
Для доступа к памяти отлаживаемой системы *pykd* представляет большой набор функций.
Для чтения целых беззанковых чисел служат следующие функции:
* ptrByte( va )
* ptrWord( va )
* ptrDWord( va )
* ptrQWord( va )
Для получения результата в виде целых чисео со знаком служат аналогичные функции:
* ptrSignByte( va )
* ptrSignWord( va )
* ptrSignDWord( va )
* ptrSignQWord( va )
Для удобства разработки кроссплатформенных скриптов служат функции:
* ptrMWord(va)
* ptrSignMWord(va)
* ptrPtr(va)
Они возвращают результат в зависимости от битности платформы - 32 или 64 бита
Часто требуется прочесть блок памяти. Для этого служат функции:
* loadBytes( va, count )
* loadWords( va, count )
* loadDWords( va, count )
* loadQWords( va, count )
* loadSignBytes( va, count )
* loadSignWords( va, count )
* loadSignDWords( va, count )
* loadSignQWords( va, count )
* loadPtrs( va, count )
Все функции возвращают объект *list*.
[Содержание|#table]
{anchor:memerror}
!!! 4.5 Ошибки доступа к памяти
Все функции работы с памятью при невозможности прочесть данные по указанному адресу возвращают исключение *MemoryException*.
{{
try:
a = ptrByte( 0 )
except MemoryException:
print "memory exception ocurred"
}}
Проверить валидность виртуально адреса можно с помощью ф. *isValid(va)*
[Содержание|#table]
{anchor:memstr}
!!! 4.6 Чтение строк из памяти
Часто приходится читать из памяти строковые данные. Конечно, для этого можно было бы использовать ф. *loadBytes*, но это не всегда удобно. Поэтому в *pykd* добавлен набор функций, возвращающих данные в виде строки.
В первую очередь это:
* loadChars( va, count )
* loadWChars( va, count )
Они работают совершенно также как loadBytes и loadWords. Отличие только в возвращаемом значении ( *string* вместо *list* ). Это позволяет использовать их, например, совместно с модулем *struct*:
{{
from struct import unpack
shortField1, shortField2, longField = unpack('hhl', loadChars( addr, 8 ) )
}}
Для чтения 0-терминированных строк из памяти служат функции:
* loadСStr( va )
* loadWStr( va )
Обе возвращают строки ( loadWStr - UNICODE ). Отметим небезопасность использования данных функций - ведь наличие терминирующего нуля никто не гарантирует! *Внимание!* Максимальная длина строки ограничена 64K. При попытке прочесть строку длинее, будет возвращено исключение MemoryException.
В ядре Windows для представления строк используются структуры {"UNICODE_STRING"} и {"ANSI_STRING"}. Для работы с ними существуют соответствующие функции:
* loadAnsiString
* loadUnicodeString
[Содержание|#table]
{anchor:modules}
! 5. Модули
{anchor:moduleclass}
!!! 5.1 Класс module
Модуль - это исполняемый файл, отображенный на память. Обычная программа состоит из главного модуля ( как правило, с расширением .exe ) и набора библиотек. Работа с модулями осуществляется с помощью класса *module*.
!!!! 5.1.1 Создание экземпляра класса *module*:
Класс *module* имеет две формы конструктора:
* module( moduleName )
* module( va )
Первая форма создает модуль по его имени, второя - по виртуальному адресу, принадлежащему модулю. Если модуль не найден, конструктор возбудит исключение BaseException.
Пример:
{{
from pykd import *
try
ntdll = module( "ntdll" )
print ntdll.name(), hex(ntdll.begin()), hex(ntdll.size())
except BaseException:
print "module not found"
}}
!!!! 5.1.2 Получение информации о модуле
Для этого служат слежующие методы класса *module*:
* name() - возвращает имя модуля
* image() - возвращает имя исполняемого файла
* pdb() - возвращает имя и полный путь к файлу с символьной информацией
* begin() - возвращает виртуальный адрес, по которому загржен модуль
* end() - возвращает виртуальный адрес конца модуля
* checksum() - возвращает контрольную сумму
* timestamp() - возвращает временную метку
* getVersion() - возвращает кортеж, предсталяющий версию модуля. Например: ( 1, 0, 6452, 0 )
* queryVersion( valueName ) - возвращает значение из ресурсов моудля
!!!! 5.1.3 Загрузка и доступ к символам.
Для загрузки символьной информации служит метод *reload()*
Для поиска виртуального адреса, соответствующего нужному символу служит метод *offset( symName )*. Если указаный символ не найден, будет возбужедено исключение *BaseException*. Вместо явного вызова ф. *offset* можно получить адрес, соответствующий символу, обратившись к нему как свойству класса *module*
{{
>>> nt = module("nt")
>>> print hex( nt.offset("PsLoadedModuleList") )
0xfffff801acb5ae80L
>>> print hex( nt.__getattr__("PsLoadedModuleList") )
0xfffff801acb5ae80L
>>> print hex( nt.PsLoadedModuleList )
0xfffff801acb5ae80L
}}
Иногда может понадобится RVA символа, получить его можно с помощью ф. *rva( symbolName )*.
!!!! 5.1.4 Приведения к другим типам
Экземпляр класса *module* имеет операторы приведения к строке ( __str__ ) и целому числу:
{{
>>> nt = module("nt")
>>> print nt
Module: nt
Start: fffff801ac882000 End: fffff801acfc8000 Size: 746000
Image: ntkrnlmp.exe
Pdb: c:\sym\ntkrnlmp.pdb\569F266AE67D457D969D92298F8F98082\ntkrnlmp.pdb
Timestamp: 4f7118bb
Check Sum: 6b3b15
>>> print hex(nt)
fffff801ac882000
}}
Кроме того, экземпляр класса *module* может участвовать в арифметических операциях:
{{
>>> print hex( nt + 10 )
0xfffff801ac88200aL
}}
!!!! 5.1.5 Получение информации о типе
Кроме символов, описывающих переменные и функции ( сущности, которые имеют RVA ), могут быть символы, описывающие типы. Для них, естественно, RVA не задан.
Если для модуля есть информация о типах, то ее можно получить через функцию *type( typeName )*. Эта функция возвращает экземпляр класса *typeInfo*, работа с которым будет рассмотрена позже.
{{
>>> nt = module("nt")
>>> print nt.type("_MDL")
struct/class: _MDL Size: 0x1c (28)
+0000 Next : _MDL*
+0004 Size : Int2B
+0006 MdlFlags : Int2B
+0008 Process : _EPROCESS*
+000c MappedSystemVa : Void*
+0010 StartVa : Void*
+0014 ByteCount : ULong
+0018 ByteOffset : ULong
}}
!!!! 5.1.6 Типизированные переменные
*pykd* позволяет упростить работу с сложными типами, такими как классы и структуры. За это отвечает специальный класс *typedVar*. Получить экземпляр класса *typedVar* можно через методы класса *module*:
* typedVar( va )
* typedVar( symbolName )
* typedVar( typeName, va )
{{
>>> nt = module("nt")
>>> print nt.typedVar( "_LIST_ENTRY", nt.PsLoadedModuleList )
struct/class: _LIST_ENTRY at 0xfffff8000369c650
+0000 Flink : _LIST_ENTRY* 0xfffffa8003c64890
+0008 Blink : _LIST_ENTRY* 0xfffffa80092f8f30
}}
[Содержание|#table]
{anchor:moduleload}
!!! 5.2 Обработка событий загрузки и выгрузки модуля
Для обработки событий загрузки и выгрузки модуля надо создать наследника класса [eventHandler|#eventHandler].
Обработка события загрузки модуля осуществляется методом [onLoadModule|#eventHandler_onLoadModule]. Обработка события выгрузки модуля - [onUnloadModule|#eventHandler_onUnloadModule]
[Содержание|#table]
{anchor:syminfo}
! 6. Получение символьной информации
{anchor:pdbfile}
!!! 6.1 Символьные ( pdb ) файлы
При сборке модуля создается файл с символьной ( отладочной ) информацией ( обычно, с раширением pdb ). В зависимости от настроек компилятора он может содержать полную или обрезанную информацию ( т.н "публичные символы" ). Символьные файлы могут содержать следующую информацию:
* Имена, типы и относительные смещения глобальных переменных и констант
* Имена, параметры и относительные смещения функций и методов классов
* Имена типов, определенных пользователем ( структур, классов, перечислений )
* Значения констант
* Информацию о локальных переменных функций и методов классов.
Для работы с символьными файлами Microsoft предоставляет специальную библиотеку MS DIA. *Pykd* использует ее для работы с символами. Для непосредственного доступа к символьной инфорамции *pykd* реализует свой собственный интерфейс.
[Содержание|#table]
{anchor:types}
!!! 6.2 Информация о типах
{anchor:typeInfo}
!!!! 6.2.1 Класс представления типа
Для представления информации о типе в питон экспортируется класс *typeInfo*. Этим классом описываются структуры, классы, объединения, перечисления, битовые поля, указатели и базовые типы.
Класс представляет следующие методы:
* *name* - получение имени типа
* *size* - получение полного размера типа
* *staticOffset* - получение смещения статического поля
* *fieldOffset* - получение смещения поля
* *bitOffset* - получение смещения битового поля
* *bitWidth* - получение размера битового поля
* *field* - получение поля
* *asMap* - получение словаря для значения преречисления
* *deref* - разыменование указателя
* *ptrTo* - формирование указателя на тип
* *arrayOf* - формирование массива, элементами которого является тип
* *append* - добавление поля (метод для структур и перечислений, созданных с использованием [typeBuilder|#typeBuilder])
[Содержание|#table]
{anchor:get_typeInfo}
!!!! 6.2.2 Получение объекта типа
Объект типа можно получить вызовом конструктора, передав в него имя типа. Передаваемая строка может содержать как полную спецификацию типа ("имя_модуля!имя_типа"), так и просто "имя_типа".
Объект типа можно получить _косвенным_ образом:
* метод *type* у объекта типа *module* - формирование объекта типа по имени
* метод *type* у объекта типа *typedVar* - формирование объекта типа, который имеет переменная
Пример (печать структуры {"_UNICODE_STRING"} из ntdll):
{{
>>> us = module("ntdll").type("_UNICODE_STRING")
>>> print us
class/struct : _UNICODE_STRING Size: 0x10 (16)
+0000 Length : UInt2B
+0002 MaximumLength : UInt2B
+0008 Buffer : UInt2B*
}}
Для получения всех типов модуля можно использовать *enumTypes* у объекта типа *module*, который возвращает список имен типов, информация о которых представлена в отладочных символах модуля.
[Содержание|#table]
{anchor:typeBuilder}
!!!! 6.2.3 Создание типов, не представленных в отладочных символах
Часто при отладке нужные типы данных отсутствуют в отладочных символах. Для удобства работы был написан отдельный класс по созданию собственных структур и объединений - *typeBuilder*.
Сложные типы данных, как правило, сводятся к набору базовых типов. Для получения базовых типов объект *typeBuilder* имеет следующие поля:
* UInt1B
* UInt2B
* UInt4B
* UInt8B
* Int1B
* Int2B
* Int4B
* Int8B
* Long
* ULong
* Bool
* Char
* WChar
* VoidPtr
Отдельно стоит сказать и поле VoidPtr - указатель на void. Размер этого типа зависит от платформы и текущего режима отладки. Но для удобства при создании объекта *typeBuilder* в конструктор можно передать желаемый размер указателя.
Имея базовые типы, а так же типы из отладочной информации модулей, можно строить произвольные типы. Для этого объект *typeBuilder* имеет два метода: *createStruct* и *createUnion*. Оба метода первым параметром методы принимают строку имени типа. При создании структуры вторым необязательным параметром можно указать желаемое выравнивание.
Пример (создание и печать собственной структуры {"_UNICODE_STRING"}):
{{
>>> tb = typeBuilder()
>>> us = tb.createStruct("_UNICODE_STRING")
>>> us.append("Length", tb.UInt2B)
>>> us.append("MaximumLength", tb.UInt2B)
>>> us.append("Buffer", tb.WChar.ptrTo())
>>> print us
class/struct : _UNICODE_STRING Size: 0x10 (16)
+0000 Length : UInt2B
+0002 MaximumLength : UInt2B
+0008 Buffer : WChar*
}}
[Содержание|#table]
{anchor:typedVar}
! 7. Типизированные переменные
{anchor:typedVar}
!!! 7.1 Класс typedVar
Ранее мы рассмотрели пример, как можно прочесть из памяти структурированные данные:
{{
from struct import unpack
shortField1, shortField2, longField = unpack('hhl', loadChars( addr, 8 ) )
}}
Очевидно, что работать с большими структрами, содержащими сотни полей, так не очень удобно. Поэтому в *pykd* реализован специальный класс: *typedVar*, позволяющий работать со сложными структурами данных. Информацию о типе данных *typedVar* получает из символьной информации.
[Содержание|#table]
{anchor:typedVarClass}
!!! 7.2 Создание экземпляра класса typedVar
Существует несколько перегруженных конструкторов класса *typedVar*:
* typedVar( symbolName )
* typedVar( typeName, va )
* typedVar( typeInfo, va )
{{
t1 = typedVar( "MyModule!MyVar" )
t2 = typedVar( "MyModule!MyType", addr )
ti = typeInfo( "MyModule!MyType" )
t3 = typedVar( ti, addr )
}}
Все три способа приведут к одинаковому результату, если _addr_ - адрес переменной MyVar. Отметим, что все эти способы ( и в особенности первый ) не являются оптимальными в плане производительности, так как существенное время может уходить на поиск символьной информации. Если есть возможность, лучше воспользоваться методами класса *module*:
* module.typedVar( va )
* module.typedVar( symbolName )
* module.typedVar( typeName, va )
{{
mod = module("MyModule")
t4 = mod.typedVar( addr )
t5 = mod.typedVar( "MyVar" )
t6 = mod.typedVar( "MyType", addr )
}}
Результат будет аналогичный прямому вызову конструктора. Однако экземпляр класса *module* оптимизирует доступ к символьной информации.
В случае ошибки в задании имени переменной или типа будет возбуждено исключение *SymbolException*.
{{
try:
typedVar( "MyModule!NotExistVar")
except SymbolException:
print "The var does not exist"
}}
[Содержание|#table]
{anchor: typedVarMethod}
!!! 7.3 Методы класса typedVar
* getAddress() - возвращает адрес переменной
* sizeof() - возвращает размер переменной
* offset() - если переменная является полем родительской структуры, то возвращает смещение относительно родителя.
* field( fieldName ) - возвращает поле структуры как экземпляр класса *typedVar*
* deref() - для указателей выполняет т.н. разыменование и возвращает результат в виде экземпляра класса *typedVar*
* type() - возвращает тип переменной в виде экземпляра класса *typeInfo*
[Содержание|#table]
{anchor:typedVarUDT}
!!! 7.4 Классы и структуры
Получить доступ к полям структуры можно с помощью метода *field*. Для удобства использования добавлен метод для доступ к полям как к аттрибутам класса:
{{
>>>tv = typedVar( "structVar")
>>>tv.field("m_field) == tv.m_field
True
}}
Кроме того, можно получить доступ к полям структуры по индексу:
{{
tv = typedVar( "structVar")
for i in range(0,len(tv) )
fieldName, fieldValue = tv[i]
print fieldName, fieldValue
}}
Как видно из примера, в этом случае возвращается кортеж ( tuple ) из имени поля и его значения. Тот же пример можно записать короче:
{{
tv = typedVar( "structVar")
for fieldName, fieldValue in tv:
print fieldName, fieldValue
}}
Переменные типа *typedVar* могут участвовать в арифметических операцих. В качестве значения берется адрес переменной. *Внимание:* при арифметических операциях *не действуют* правила адресной аримфетики Си. Адрес будет трактоваться просто как число и , соответственно, var+1 просто инкрементирует знаечние адреса.
[Содержание|#table]
{anchor:typedVarArray}
!!! Массивы и указатели
Класс *typedVar* позволяет работать с массивами, в том числе многомерными. Для доступа к элементам массива нужно использовать оператор индекса []:
{{
>>> tv = typedVar( "intMatrix" )
>>> print tv
Int4B[2][3] at 0x13f159150
>>> print tv[1]
Int4B[3] at 0x13f15915c
>>> print tv[1][2]
Int4B at 0x13f159164 Value: 0x5 (5)
}}
Класс *typedVar* может работать так же и с указателями. Для "разыменования" указателя служит функция *deref()*:
{{
>>> tv = typedVar("ptrIntMatrix")
>>> print tv
Ptr Int4B(*)[2][3] at 0x13f1591c0 Value: 0x13f159150
>>> print tv.deref()
Int4B[2][3] at 0x13f159150
>>> print tv.deref()[1][2]
Int4B at 0x13f159164 Value: 0x5 (5)
}}
Переменные *typedVar* могут участвовать в арифметический выражениях. Для массивов в качестве значения берется его адрес, для указателя - значение указателя ( т.е куда он указывает ). *Внимание:* при арифметических операциях *не действуют* правила адресной аримфетики Си.
[Содержание|#table]
{anchor:typedVarEnum}
!!! 7.6 Энумераторы
Для работы с энумераторами будет полезно получить доуступ к информации о типе энумератора, так как именно через нее можно получить соответствие численных констант и символьных имен. Сделать это можно через метод *type*, который возвращает ссылку на переменную типа *typeInfo*:
{{
var = typedVar( "myStruct" )
if var.structType == var.structType.type().TYPE_ONE:
print "TYPE_ONE"
else:
print "ANOTHER_TYPE"
}}
Класс *typeInfo* имеет метод *asMap()*, который для энумераторов возвращает объект типа dict, в котором ключами являются числовые константы, а значениями - их символьное представление:
{{
var = typedVar( "myStruct" )
{
"TYPE_ONE" : lambda var.field_one
"TYPE_TWO" : lambda var.field_two
}[ var.type().asMap[ var.structType ] ]()
}}
Данный пример во-первых демонстрирует как сделать на python логическую структуру, аналогичную оператору switch в Cи: для этого можно использовать тип dict, значения полей которого являются лямбда-выражениями. Во-вторых, он показывает как для энумератора получить соответствие численной константы и символьного имени.
Экземпляры *typedVar*, содержащие энумераторы, можно использовать в арифметических операциях:
{{
>>>var = typedVar( "myStruct" )
>>>print var.structType *2 + 10
}}
[Содержание|#table]
{anchor:typedVarCast}
!!! 7.7 Приведениe к другим типам
Класс *typedVar* имеет операторы приведения к строке (__str__) и к целому числу ( __long__ ).
{{
>>> print str( typedVar("g_struct") )
struct/class: struct3 at 0x13f4391f8
+0000 m_arrayField : Int4B[2]
+0008 m_noArrayField : Int4B 0x3 (3)
}}
Целочисленное значение, возвращаемое функций long(), зависит от типа данных, хранящизся в typedVar:
* Базовые типы - возвращается непосредственное значение
* Структуры, классы и объединения - возвращается значение указателя на начало данных
* Энумераторы - возвращается непосредственное занчение
* Указатели - возвращается непосредственное значение
* Массивы - возвращается значение указателя на начало данных
{{
>>> long( typedVar("g_struct").m_noArrayField )
3L
>>> hex( long( typedVar("g_struct").m_arrayField ) )
'0x13f4391f8L'
>>>
>>> long( typedVar("g_struct").m_arrayField[1] )
2L
}}
[Содержание|#table]
{anchor:ProcessThreads}
! 8. Процессы и потоки
{anchor:UserModeThreads}
!!! 8.1 Потоки в пользовательском режиме
В пользовательском режиме отладчик работает в контексте отлаживаемого процесса. Если процесс имеет несколько потоков, в режиме отладки можно переключить контекст на другой поток. Нужно различать текущий поток и поток, на который переключился отладчик. В оригинале они называются: "current thread" - поток который продолжит выполнение после возобновления отладки, "implicit thread" - поток, в контексте которого находится отладчик. Контекстом потока мы называем совокупность регистров процессора, в том числе и указатель текущей инструкции и стека.
Для смены контекста потока служит функция *setImplicitThread*. В качестве параметра она принимает указатель на TEB (thread enviroment block). Получить указатель на TEB можно с помощью:
* *getImplicitThread* - TEB текущего потока
* *getProcessThreads* - список TEB-ов всех потоков процесса
[Содержание|#table]
{anchor:KernelModeThreads}
!!! 8.2 Потоки в режиме ядра
В режиме ядра есть некоторые особенности.
Во-первых, функции *setImplicitThread* и *getImplicitThread* работают с указателями на ETHREAD, а не с TEB, как в пользовательском режиме. Во-вторых, ф. *getProcessThreads* для режима ядра не доступна. Если нужно получить список потоков какого либо процесса придется делать это вручную разбирая структуру EPROCESS. К счастью, это не сложно:
{{
nt = module("nt")
process = nt.typedVar( "_EPROCESS", processAddr )
threadLst = nt.typedVarList(process.ThreadListHead, "_ETHREAD", "ThreadListEntry")
}}
Остается добавить, что переключение контекста отлаживаемого потока не приводит к переключению контекста процесса. Подробнее об этом в следующем разделе.
[Содержание|#table]
{anchor:KernelModeProcess}
!!! 8.3 Процессы в режиме ядра
[Содержание|#table]
{anchor:Locals}
! 9. Локальные переменные
{anchor:CurrentLocals}
!!! 9.1 Текущие локальные переменные
При отладке приложения, ядра системы или анализе аварийного дампа, всегда присутствует текущий поток, а в этом потоке есть текущий фрейм. Если у нас есть отладочная информация о модуле, котрому принадлежит данный фрейм и в отладочной информации присутствует информация о локальных переменных, то мы можем получить доступ к ним в удобной форме, без явных операций с регистрами и стеком. Для этого служит ф. *getLocals()*. Она возвращает объект типа *dict*, ключом является имя переменной, а значением - экземпляр класса *typedVar*:
{{
# print local variable "argc"
print getLocals()["argc"]
# print all local vairables in the current frame
for varName, varValue in getLocals().items():
print varName, varValue
}}
[Содержание|#table]
{anchor:breakpoints}
! 10. Точки останова
{anchor:setBreakpoints}
!!! 10.1 Задание точек останова
Для задания точки останова служит функция *setBp*. Она позволяет устанавливать как программные точки останова, так и аппаратные. Функция возвращает числовой идентификатор, который можно в последствии использовать для удаления точки останова через ф. *removeBp*.
Установка программной точки останова:
{{
nt = module("nt")
bpid = setBp( nt.NtCreateFile )
}}
Установка аппаратной точки останова:
{{
nt = module("nt")
bpid = setBp( nt.NtCreateFile, 1, 4 )
}}
Второй параметр - размер памяти, к которой осуществляется доступ, Третий параметр - тип доступа ( 1 - чтение, 2- запись, 4 - исполнение, типы доступа работают как флаги и могут быть объединены. Например, 3 - чтение + запись ).
[Содержание|#table]
{anchor:condBreakpoints}
!!! 10.2 Условные точки останова
Для организации точек останова с условием используется функция обратного вызова, которая передается в качестве параметра в вызов *setBp*. Эта функция обязана принимать один параметр ( туда передается идентификатор сработавшей точки останова ). Чтобы точка останова сработала, функция обратного вызова должна вернуть *True*
{{
import fnmatch
from pykd import *
nt = module('nt')
objAttrType = nt.type( "_OBJECT_ATTRIBUTES" )
def onCreateFile( id ):
objattr = typedVar( objAttrType, ptrPtr( reg('esp') + 0xC ) )
return fnmatch.fnmatch( loadUnicodeString( objattr.ObjectName ), '*.exe' )
setBp( nt.NtCreateFile, onCreateFile )
}}
Нужно обратить внимание, что в качестве функции обратного вызова может выступать лямбда-функция:
{{
setBp( myAddr, lambda id: reg('rax') > 0x1000 )
}}
Помните о времени жизни объектов, создаваемых в скриптах! Функции - в python такие же объекты и при завершении виртуальной машины python они будут удалены. И тут возможна следующая ловушка: выполнив предыдущий скрипт с помощью команды "!py setmybreak.py" мы не получим ожидаемого срабатывания точки останова, она будет удалена во время завершения работы скрипта. Что же делать? Есть два варианта:
1. Использовать в скрипте управление отладкой, примерно так:
{{
setBp( nt.NtCreateFile, onCreateFile )
go()
}}
В таком случае, мы поймаем ровно одно срабатывание точки останова, далее скрипт завершится.
2. Использовать для установки точки останова команду !pycmd.
Напомним данная команда создает _глобальный_ интерпретатор python и все объекты python продолжают быть доступными даже после выполнения команды _quit()_:
{{
>!pycmd
>>>import setmybreak
>>>quit()
>g
}}
При импортировании модуля будут выполнены все действия по установке точке останова и даже после выхода из консоли функции обратного вызова на python будут работать!
*Внимание!*
Функции обратного вызова имеют некоторые ограничения на использование API *pykd*:
* Нельзя вызывать функции, которые могут изменить состояние отладчика: go, breakin, trace
* Нельзя вызывать функции, которые могут привести к появлению или уничтожению отладочных сессий: startProcess, killProcess, openDump и.т.д
* Нельзя манипулировать контекстами потоков и процессов ( setCurrentProcess, setImplicitThread )
[Содержание|#table]
{anchor:eventHandler}
! 11. Отладочные события
[Содержание|#table]
{anchor:eventHandler_onBreakpoint}
!!! 11.1 Обработка точек останова (метод onBreakpoint)
[Содержание|#table]
{anchor:eventHandler_onException}
!!! 11.2 Обработка исключительных ситуаций (метод onException)
[Содержание|#table]
{anchor:eventHandler_onLoadModule}
!!! 11.3 Обработка события загрузки исполняемого модуля (метод onLoadModule)
[Содержание|#table]
{anchor:eventHandler_onUnloadModule}
!!! 11.4 Отработка события выгрузки исполняемого модуля (метод onUnloadModule)
[Содержание|#table]
{anchor:disasm}
! 12. Класс disasm
[disasm reference|PYKD 0.1. API Reference#disasm]
Класс *disasm* является оболочкой над дизассемблером из Debug Engine. Соответственно, результаты его работы такие же, как и у команды u в kd/cdb/windbg.
Класс *disasm* имеет следующие методы:
* {"__init__()"} - создает дизассемблер, который начнет работу с текущей инструкции
* {"__init__( offset )"} - создает дизассемблер, который начнет работу с указанного смещения
* disasm() - возвращает дизассемблированное представление инструкции CPU с текущего смещения и переходит к следующей инструкции
* disasm( offest ) - возвращает дизассмблированное представление инструкции CPU с текущего смещения и переходит к следующей инструкции
* asm( code ) - ассемблирует указанную инструкциб и меняет машинный код по указанному смещению
* begin() - возвращает смещение, заданное при созданни экземпляра класса *disasm*
* current() - возвращает текущее смещение
* length() - возвращает длину текущей инструкции CPU
* instruction() - возвращает дизассемблированное представление инструкции CPU по текущему смещению
* ea() - возвращает эффективный адрес последней дизассемблированной инструкции или 0
* reset() - аналог вызова self.disasm( self.begin() )
Эффективный адрес - это адрес операнда находящегося в памяти. Например, для инструкции
{"mov ecx, [esi+0x10]"}
Эффективным адресом будет занчение esi + 0x10. Очевидно, что это значение имеет смысл при дизассемблировании текущей инструкции.
[Содержание|#table]

125
docs/ru/tutorial.txt Normal file
View File

@ -0,0 +1,125 @@
!! Введение
!!!! Шаг 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!"
}}