Оглавление
- 1. Введение
- 2. Команды для windbg
- 3. Управление отладкой
- 4. Работа с памятью и регистрами
- 5. Модули
- 6. Получение символьной информации
- 7. Типизированные переменные
- 8. Процессы и потоки
- 9. Локальные переменные
- 10. Точки останова
- 11. Отладочные события
- 12. Класс disasm
- API Reference
1. Введение
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. Последний режим может быть полезен для создания автоматических средств разбора креш-дампов например.
Содержание
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
Если все получилось, можно переходить к написанию собственных скриптов.
Содержание
1.3 Сборка из исходников
Забираем исходники из репозитория.
Устанавливаем python
Скачать нужную версию можно здесь.Устанавливаем и настраиваем boost.
Если кто не знает, где взять: 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, то скачать его можно здесь
Содержание
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 также на месте.
Содержание
1.5 Изменения в API
loadModule
Функция loadModule убрана. Вместо нее необходимо использовать конструктор класса module# mod = loadModule('mymodule") mod = module("mymodule")
Содержание
2. Команды для windbg
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 будет загружаться автоматически для данного воркспейса.
Содержание
2.2 Запуск скрипта
Запуск скрипта осуществляется с помощью команды !pykd>!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]
Содержание
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 >>>
Содержание
3. Управление отладкой
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не имеет смысла. Для того, чтобы можно было останавливать процесс отладки из скрипта, придется создать отдельный поток в котором и вызывать эту функцию.
Содержание
3.2 Пошаговое выполнение
Для пошаговой отладки ( трассировки ) служат две функции:step()
trace()
Из действие аналогично командам отладки "trace into" и "trace over". Обе функции могут вернуть исключение DbgException, если отлаживаемый процесс уже завершился.
Содержание
3.3 Управление отладкой из python приложений.
Если вы хотите исполнять свои скрипты вне windbg, то первым шагом, который вам необходимо сделать, будет создание отладочной сессии. Более подробно управление сессиями будет рассмотрено в соответствующем разделе. Если ваше приложение не планрует использовать несколько сессий отладки, то заботится об этом не надо - первая сессия будет создана автоматически при следующих вызовах:loadDump( dumpName ) - загружает креш-дамп
id startProcess( imageName) - запускает в режиме отладки новый процесс
id attachProcess( processId) - присоединяет отладчик к существующему процессу
attachKernel( parameterStr) - присоединяет отладчик к ядру отлаживаемой системы
Для отсоединения отладчика от отлаживаемого процесса служит вызов detachProcess(id)
Для остановки отладки и удаления отлаживаемого процесса служит
функция killProcess(id)
Узнать, режим отладчика можно с помощью вызовов:
- bool isDumpAnalyzing()
- bool isKernelDebugging()
Содержание
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)
Содержание
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 )
Содержание
3.6 Создание креш-дампа
Сохранить состояние системы в виде креш-дампа можно с помощью ф. writeDump. Функция доступна для режима ядра и пользовательского режима. Второй параметр задает тип дампа ( False - полный дамп, True - минидамп )writeDump( r"C:\dump\fulldump.dmp", False ) writeDump( r"C:\dump\minidump.dmp", True )
Содержание
4. Работа с памятью и регистрами
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()
r = reg("eax") print r / 10 * 234
Внимание:
Текущая реализация pykdподдерживает работу только с целочисленными регистрами. Работа с FPU, MMX, SSE регистрами не поддерживается. Поддержка планируется в следующих версиях.
Содержание
4.2 Доступ к модельно-специфичным регистрам ( MSR )
Доступ к модельно-специфичный регистрам осуществляется через ф. rdmsr( msrNumber ):>>> print findSymbol( rdmsr( 0x176 ) ) nt!KiFastCallEntry
Содержание
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"
Содержание
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)
Часто требуется прочесть блок памяти. Для этого служат функции:
- 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 )
Содержание
4.5 Ошибки доступа к памяти
Все функции работы с памятью при невозможности прочесть данные по указанному адресу возвращают исключение MemoryException.try: a = ptrByte( 0 ) except MemoryException: print "memory exception ocurred"
Проверить валидность виртуально адреса можно с помощью ф. isValid(va)
Содержание
4.6 Чтение строк из памяти
Часто приходится читать из памяти строковые данные. Конечно, для этого можно было бы использовать ф. loadBytes, но это не всегда удобно. Поэтому в pykdдобавлен набор функций, возвращающих данные в виде строки.В первую очередь это:
- loadChars( va, count )
- loadWChars( va, count )
from struct import unpack shortField1, shortField2, longField = unpack('hhl', loadChars( addr, 8 ) )
Для чтения 0-терминированных строк из памяти служат функции:
- loadСStr( va )
- loadWStr( va )
В ядре Windows для представления строк используются структуры UNICODE_STRING и ANSI_STRING. Для работы с ними существуют соответствующие функции:
- loadAnsiString
- loadUnicodeString
5. Модули
5.1 Класс module
Модуль - это исполняемый файл, отображенный на память. Обычная программа состоит из главного модуля ( как правило, с расширением .exe ) и набора библиотек. Работа с модулями осуществляется с помощью класса module.5.1.1 Создание экземпляра класса module:
Класс moduleимеет две формы конструктора:- module( moduleName )
- module( va )
Пример:
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
Содержание
5.2 Обработка событий загрузки и выгрузки модуля
Для обработки событий загрузки и выгрузки модуля надо создать наследника класса eventHandler.Обработка события загрузки модуля осуществляется методом onLoadModule. Обработка события выгрузки модуля - onUnloadModule
Содержание
6. Получение символьной информации
6.1 Символьные ( pdb ) файлы
При сборке модуля создается файл с символьной ( отладочной ) информацией ( обычно, с раширением pdb ). В зависимости от настроек компилятора он может содержать полную или обрезанную информацию ( т.н "публичные символы" ). Символьные файлы могут содержать следующую информацию:- Имена, типы и относительные смещения глобальных переменных и констант
- Имена, параметры и относительные смещения функций и методов классов
- Имена типов, определенных пользователем ( структур, классов, перечислений )
- Значения констант
- Информацию о локальных переменных функций и методов классов.
Содержание
6.2 Информация о типах
6.2.1 Класс представления типа
Для представления информации о типе в питон экспортируется класс typeInfo. Этим классом описываются структуры, классы, объединения, перечисления, битовые поля, указатели и базовые типы.Класс представляет следующие методы:
- name - получение имени типа
- size - получение полного размера типа
- staticOffset - получение смещения статического поля
- fieldOffset - получение смещения поля
- bitOffset - получение смещения битового поля
- bitWidth - получение размера битового поля
- field - получение поля
- asMap - получение словаря для значения преречисления
- deref - разыменование указателя
- ptrTo - формирование указателя на тип
- arrayOf - формирование массива, элементами которого является тип
- append - добавление поля (метод для структур и перечислений, созданных с использованием typeBuilder)
Содержание
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, который возвращает список имен типов, информация о которых представлена в отладочных символах модуля.
Содержание
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*
Содержание
7. Типизированные переменные
7.1 Класс typedVar
Ранее мы рассмотрели пример, как можно прочесть из памяти структурированные данные:from struct import unpack shortField1, shortField2, longField = unpack('hhl', loadChars( addr, 8 ) )
Очевидно, что работать с большими структрами, содержащими сотни полей, так не очень удобно. Поэтому в pykdреализован специальный класс: typedVar, позволяющий работать со сложными структурами данных. Информацию о типе данных typedVarполучает из символьной информации.
Содержание
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"
Содержание
7.3 Методы класса typedVar
- getAddress() - возвращает адрес переменной
- sizeof() - возвращает размер переменной
- offset() - если переменная является полем родительской структуры, то возвращает смещение относительно родителя.
- field( fieldName ) - возвращает поле структуры как экземпляр класса typedVar
- deref() - для указателей выполняет т.н. разыменование и возвращает результат в виде экземпляра класса typedVar
- type() - возвращает тип переменной в виде экземпляра класса typeInfo
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 просто инкрементирует знаечние адреса.
Содержание
Массивы и указатели
Класс 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могут участвовать в арифметический выражениях. Для массивов в качестве значения берется его адрес, для указателя - значение указателя ( т.е куда он указывает ). Внимание:при арифметических операциях не действуютправила адресной аримфетики Си.
Содержание
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
Содержание
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
Содержание
8. Процессы и потоки
Содержание9. Локальные переменные
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
Содержание
10. Точки останова
10.1 Задание точек останова
Для задания точки останова служит функция setBp. Она позволяет устанавливать как программные точки останова, так и аппаратные. Функция возвращает числовой идентификатор, который можно в последствии использовать для удаления точки останова через ф. removeBp.Установка программной точки останова:
nt = module("nt") bpid = setBp( nt.NtCreateFile )
Установка аппаратной точки останова:
nt = module("nt") bpid = setBp( nt.NtCreateFile, 1, 4 )
Второй параметр - размер памяти, к которой осуществляется доступ, Третий параметр - тип доступа ( 1 - чтение, 2- запись, 4 - исполнение, типы доступа работают как флаги и могут быть объединены. Например, 3 - чтение + запись ).
Содержание
10.2 Условные точки останова
Для организации точек останова с условием используется функция обратного вызова, которая передается в качестве параметра в вызов setBp. Эта функция обязана принимать один параметр ( туда передается идентификатор сработавшей точки останова ). Чтобы точка останова сработала, функция обратного вызова должна вернуть Trueimport 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 )
Содержание
11. Отладочные события
Содержание11.1 Обработка точек останова (метод onBreakpoint)
Содержание11.2 Обработка исключительных ситуаций (метод onException)
Содержание11.3 Обработка события загрузки исполняемого модуля (метод onLoadModule)
Содержание11.4 Отработка события выгрузки исполняемого модуля (метод onUnloadModule)
Содержание12. Класс disasm
disasm referenceКласс 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. Очевидно, что это значение имеет смысл при дизассемблировании текущей инструкции.
Содержание