mirror of
https://github.com/ivellioscolin/pykd.git
synced 2025-04-09 11:13:22 +08:00
[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:
parent
d90c4a2776
commit
6ece37ff95
898
docs/ru/documentation.txt
Normal file
898
docs/ru/documentation.txt
Normal 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
125
docs/ru/tutorial.txt
Normal 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!"
|
||||
}}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user