Отладка кода режима ядра существенно сложнее, чем отладка обычных программ.
Если для отладки пользовательского кода ты можешь использовать стандартный
дебаггер, например, в Visual Studio или в IDA, то для отладки кода режима ядра
требуются спецсредства. Короче, без бутылки не разобраться. Равно, как и без
этой статьи.

Использование Windbg в качестве средства для отладки предоставит большие
возможности по исследованию драйверов, кода ядра и системных DLL. Применяя при
этом VMware, процесс отладки можно сделать куда более простым и приятным (уж
точно приятнее, чем тестирование драйвера на физической машине). С этими
инструментами ты сможешь исследовать поведение системных компонентов также
хорошо, как и своих драйверов.

Сама идея трассирования кода ОС (в состав которой входят и драйверы)
реализуется с помощью двух подходов: отладчик находится на той же машине, что и
трассируемая ОС или отладчик установлен на другой машине (host), которая связана
через порт с трассируемой ОС (target). Первый подход реализован в самом лучшем
отладчике SoftICE, другой в Visual SoftICE и Windbg (они не менее хороши).
Отладка кода на двух машинах мало кому представляется возможной, поэтому в
большинстве случаев прибегают к помощи виртуальных машин, которые соединяют
через pipe c host-системой.

 

Определяемся со средством

Дам ответ на самый наболевший вопрос: какое средство выбрать для отладки.
Compuware Visual SoftICE
или Windbg – отчасти дело привычки, но выбор
в сторону Windbg дает следующие преимущества:

  • Windbg является "родным" для NT средством. Windbg разрабатывался Microsoft
    специально как отладчик любого кода под NT;
  • Windbg содержит множество команд расширений с осмысленной семантикой
    (поставляемых в стандартных библиотеках DLL расширений), которые упрощают
    отладку кода;
  • Удобный оконный интерфейс для отладки кода;
  • Удобная система рабочего пространства отлаживаемого кода (workspace ~
    воркспейс);
  • Поддержка host-target отладки, в том числе и через VMware.
 

Подготовка соединения

Перед тем, как начать тестирование драйвера или трассирование (исследование)
кода ядра, необходимо настроить соединение между отладчиком, который расположен
в host-машине и target виртуальной машиной.

Вначале настраивается target-машина. Первое, что необходимо сделать, это
создать именованный канал для общения target-системы с host. Заходим в меню
настроек виртуальной машины (Ctrl-D). Жмем на Add и выбираем Serial Port. В
следующем окне надо выбрать Output to named pipe. Далее указываем имя пайпа
(оставляем по умолчанию) и в двух списках выбираем This end is the server и The
other end is an application, ставим галочку на Connect at power on. После того
как пайп создался, поставь галочку на Yield CPU on poll.

Теперь осталось настроить саму target-систему. Для этого необходимо
загрузиться, открыть boot.ini и вписать туда еще одну строку. Вначале необходимо
скопировать строку, с помощью которой загружается система, потом вставить ее и
дописать к ней следующее содержание:

/debug /debugport=com1 /baudrate=115200

Пример файла boot.ini с такими параметрами может выглядеть так:

[boot loader]
timeout=30
default=multi(0)disk(0)rdisk(0)partition(1)\WINDOWS
[operating systems]
multi(0)disk(0)rdisk(0)partition(1)\WINDOWS="Microsoft Windows XP Professional"
/noexecute=optin /fastdetect /sos
multi(0)disk(0)rdisk(0)partition(1)\WINDOWS="Microsoft Windows XP Professional"
/fastdetect /sos /debug /debugport=com1 /baudrate=115200

Загрузчик при считывании второй строки отобразит в квадратных скобках после
имени системы надпись [debugger enabled].

При отладке драйвера обычно используют checked build (проверочную) версию
системы. Точнее, checked build не всей системы, а только двух файлов ntoskrnl и
hal (соответствующие внутренние образы могут варьироваться в зависимости от
параметров системы, например, много- или однопроцессорная, ACPI или нет). Почему
следует использовать такую версию?

  • В коде (ntoskrnl и hal) активированы макросы ASSERT, что позволяет сразу
    же выявлять множество ошибок при передаче функциям неверных
    аргументов/адресов;
  • В таком коде ассемблерные инструкции более понятны для исследования, так
    как при его компиляции была отключена оптимизация.

Для загрузки виртуальной машины под проверочным выпуском необходимо получить
исходные (внутренние) имена файлов ntoskrnl и hal. Это можно сделать, например,
зайдя в свойства файла (скажем, ntoskrnl) -> вкладка Version, Internal Name
(внутреннее имя). После этого скачай с сайта Microsoft checked-версию NT, найди
файлы с соответствующими внутренними именами и переименуй их – допустим, в
hal.chk и ntoskrnl.chk. Затем скопируй их в SystemRoot\system32. И в конце
добавь к необходимой строке в boot.ini /KERNEL=ntoskrnl.chk /HAL=hal.chk. К
примеру, так:

multi(0)disk(0)rdisk(0)partition(1)\WINDOWS="Microsoft Windows XP
Professional [Checked Build]" /fastdetect /sos /debug /debugport=com1
/baudrate=115200 /KERNEL=ntoskrnl.chk /HAL=hal.chk

Теперь у тебя есть настроенная версия системы для отладки kernel mode кода.
Учти, что на современных компьютерах работает DEP, а это значит, что загружается
PAE-версия ядра, то есть образ ntkrnlpa. Убедиться в этом можно, просмотрев в
разделе HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management,
параметр PhysicalAddressExtension.

 

Получение символов

Для исследования кода ядра нужны символы – для правильного отображения
идентификаторов вместо голых адресов переменных и функций. Вовсе необязательно
сливать весь пакет с символами целиком (от 150 до 250 MB) с сайта Microsoft – ты
можешь слить символьные файлы под конкретные файлы (например, под конкретную
версию ntoskrnl). Это можно сделать двумя программами. Первая, входящая в
Debugging Tools for Windows, называется symchk. Не вдаваясь в подробности ключей
(описания, которых можно найти в документации), ее можно использовать следующим
образом.

"c:\Program Files\Debugging Tools for Windows\symchk" /r
D:\Work\SymbolsShare\EXE\ /s
srv*D:\Work\SymbolStorage*http://msdl.microsoft.com/download/symbols

Соответственно, нужно указать правильный путь к установленной у тебя symchk.
В директорию (она может быть любой) D:\Work\SymbolsShare\EXE\ ты скидываешь
файлы, для которых необходимо получить символы. В папке D:\Work\SymbolStorage
оказываются символы для файлов. Файлы из директорий в D:\Work\SymbolStorage
необходимо скопировать в одну папку, в которую затем будет направлен поиск
символов в windbg.

Вторым способом является использование входящей в Compuware Driver Studio
программы SymbolRetriver, которая обладает интуитивно понятным интерфейсом.

Microsoft также предоставляет более гибкий способ управления символами – на
основе symbols storage (хранилища символов): он полезен при работе с несколькими
виртуальными машинами, на которых установлены разные версии NT, но также полезен
и при работе с одной виртуальной машиной. Напрямую к хранилищу обращаться
нельзя. Для этого предназначен сервер символов (symbol server), который и нужно
вызывать для получения доступа к хранилищу. В хранилище могут содержаться
произвольные идентификаторы (pdb-файлы), но при использовании сервера символов
(стандартный symsrv.dll) Windbg (или dbghelp.dll) можно получить символьную
информацию для нужного образа автоматически. Сервер символов позволяет отладчику
самому получать корректные символьные файлы. Для его активации в каком-либо пути
к символам нужно указать строку symsrv*symsrv.dll*CacheStore*Server
(symsrv*symsrv.dll можно свернуть в srv, а если хранилище локальное, то –
использовать srv*LocalPath). Самое удобное – указать эту строку в
_NT_SYMBOL_PATH, которую используют библиотеки отладки. Если получать символы
каждый раз через сервер Microsoft и кэшировать их в локальном хранилище
(стандартный способ), то путь к символам может выглядеть так:

srv*C:\storage*http://msdl.microsoft.com/download/symbols

 

Настройка интерфейса Windbg

Понятие интерфейса Windbg входит в более широкое понятие воркспейса
(workspace ~ рабочее пространство). В Windbg они бывают двух видов: по умолчанию
и именованные (именованные также могут содержаться в файлах). Отладчик имеет
несколько типов воркспейсов по умолчанию:

  • Базовый (base workspace). Используется, когда Windbg
    находится в бездействующем режиме, то есть код не отлаживается;
  • По умолчанию для программ пользовательского режима (default
    user-mode workspace
    ). Используется для отладки уже запущенных; процессов,
    то есть когда отладчик присоединяется к уже работающему процессу;
  • По умолчанию для режима ядра (default kernel-mode).
    Используется при старте сессии отладки для режима ядра;
  • Процессоро-зависимый (processor-specific workspace).
    Используется при отладке кода режима ядра при соединении к target-системе.

Существуют раздельные воркспейсы для процессоров на базе x86, Itanium и x64.

Когда Windbg создает процесс пользовательского режима для отладки, воркспейс
создается для исполняемого файла. Каждый отлаживаемый exe файл имеет свой
воркспейс. Также, если происходит анализ дампа, то для каждого дампа создается
своя сессия отладки (и свой воркспейс).

Когда сессия отладки начинается, соответствующий проект загружается. По
окончании сессии отладки windbg выводит окно с вопросом о сохранении воркспейса.

Воркспейсы загружаются совокупно, то есть для одной сессии отладки
загружается несколько воркспейсов. Первым загружается базовый воркспейс, так как
в любом случае отладчик начинает свою работу в бездействующем режиме. Когда
начинается конкретная сессия отладки, загружается второй воркспейс. Отладка кода
режима ядра требует загрузки и третьего воркспейса (базовый, по умолчанию для
кода режима ядра, процессоро-зависимый).

Именованные воркспейсы могут использоваться для индивидуальной загрузки.

Воркспейсы содержат важную информацию о текущем сеансе отладки – информацию о
брейкпоинтах (включая их адреса и статусы), обработке исключений и событиях (эта
информация загружается совокупно, начиная с базового воркспейса и заканчивая
последним загруженным воркспейсом). Все открытые файлы с исходным кодом. Если
такой файл не найден, выводится сообщение об ошибке.

Воркспейсы сохраняют информацию о конфигурации отладчика. Эта информация
также загружается совокупно, начиная с базового воркспейса и заканчивая
последним загруженным воркспейсом.

Все воркспейсы по умолчанию и именованные воркспейсы содержат информацию о
графическом интерфейсе Windbg. Информация также загружается совокупно.

А вот следующая информация о GUI Windbg не загружается совокупно – она
зависит от последнего загруженного воркспейса:

  • Размер и позиция окна Windbg на рабочем столе;
  • Какие окна Windbg открыты;
  • Размер и позиция каждого открытого окна, включая его размер, статус и
    является ли окно пристыкованным (dock) или плавающим (float);
  • Настройка окна регистров;
  • Флаги в окне вызовов (calls window);
  • Выражения в окне просмотра (watch window);
  • Положение курсора в каждом окне исходников.

При отладке программ в Windbg следует вначале настроить интерфейс главного
окна – то есть положения окон – и сохранить это в воркспейсе по умолчанию. При
отладке пользовательской программы Windbg создает конкретный, под ее exe-файл,
проект, в котором можно несколько изменить положение окна, добавив в него
файл(ы) с исходным кодом. Сюда же следует добавить брейкпоинты в (w)main и часто
используемых (тестируемых) функциях. Тогда не нужно будет с нуля создавать
положение окон для отладки новых программ, но в то же время, учитывая специфику
отладки конкретной программы, можно добавить в ее воркспейс специфичную
информацию, например, положение окна сырцов и брейкпоинты.

При отладке кода режима ядра все выглядит несколько смешанно. Так как для
каждого отлаживаемого драйвера новый воркспейс не создается (он один для всей
target-системы), то все брейкпоинты, положения окон, открытые файлы с сырцами
будут делить один воркспейс.

Для присоединения к VMware и отладке драйвера Windbg нужно запустить со
следующими параметрами:

-k com:port=\\.\pipe\com_1,pipe.

 

Отладка

После того как все подготовительные меры были тобой предприняты, можно
переходить к самой отладке. Загрузи NT в VMware в отладочном режиме. Когда часть
драйверов будет загружена, ядро остановит свою работу в ожидании отладчика ядра
(и будет ждать его подключения несколько минут). Если ты не подключишь отладчик
в течение этого таймаута, система продолжит загружаться. Затем ты сможешь
подключить отладчик или в момент продолжения загрузки, или уже во время ее
работы.

После присоединения отладчика система либо продолжит выполняться под
присмотром отладчика, либо сразу же отдаст ему управление (initial breakpoint).
По умолчанию, система продолжает выполнение, но если необходимо, чтобы отладчик
сразу же получил управление, используй в командной строке Windbg опцию –b.

По умолчанию стартовый брейкопинт пробуждает отладчик в функции
ExpInitializeExecutive (уже после того, как выполнились DriverEntry boot
драйверов). Для того чтобы в Windbg управление передалось сразу, как только это
возможно (при инициализации HAL), используй параметр /break в boot.init. Так как
инициализация HAL – это первое, что делает ядро, то данный брейкпоинт является
первым из всех возможных (он генерируется в HalInitSystem -> HalpGetParameter ->
DbgBreakPoint).

Для отладки драйверов это имеет два важных последствия. Если тебе нужно
отлаживать драйвер, параметр Start которого равен SERVICE_BOOT_START, то ты
должен поставить в boot.ini параметр /break. Для остальных случаев подойдет
стандартный подход (без /break). Просмотреть список загруженных образов ядра
можно в меню Debug -> Modules.

Как только получишь управление в отладчик, тебе надо поставить брейкоинт на
DriverEntry отлаживаемого драйвера (который еще не загружен), например, так: bp
driver!DriverEntry, где driver – имя драйвера без расширения. Так как драйвер
еще не загружен, то отладчик добавит брейкпоинт со статусом unresolved (список
всех брейкпоинтов выводится по команде bl).

Как только драйвер будет загружен и ядро передаст управление в его
DriverEntry, вызовется отладчик, который сразу же должен отобразить
соответствующий файл с исходным текстом (загружаемый драйвер в checked-варианте
содержит путь к .pdb-файлу и Windbg вполне способен его прочесть). Если окно с
сырцом не появилось, то посмотри, во-первых, checked ли версию драйвера ты
загружаешь, во-вторых, установлен ли переключатель работы отладчика в режиме
сырцов (выполни команду l+t). Если это все есть, то укажи Windbg, где ему искать
символы (команда .sympath+ Path или меню File -> Symbol File Path), а затем
выполни команду .reload driver_name, где driver_name – имя драйвера с
расширением.

После того как код отобразился, ты увидишь, что фигурная скобка начала
функции подсвечивается. Поставить другие брейкпоинты можно, передвигаясь по
файлу и ставя брейкоинты "ладошкой" на панели инструментов (F9). Windbg
переключается из режима сырцов в asm режим, когда ты переходишь в окно
disassembly. Здесь также можно ставить брейкоинты на голых инструкциях. Чтобы
поставить брейкпоинт в сырцах из окна команд, используй синтаксис bp
`src_file:line_num`, где src_file – исходный файл с расширением и line_num –
номер строки, на которую нужно поставить брейкпоинт. Либо, если есть голый
адрес, – использовать bp addr. Чтобы открыть визуальное окно брейкпоинтов, нужно
переместить фокус в окно команд и нажать <F9>. После расстановки брейкпоинтов,
чтобы дать системе продолжить выполнение, можно нажимать <F5> (g в окне команд).

Для работы с отладчиком необходимо, чтобы само ядро на target-машине отдало
ему управление и работало с ним. В режиме, когда ты нажал <F5>, и в окне команд
высветилось Debuggee is running…, никакое окно отладчика не будет давать
правильную информацию. Брейкопинты также будет ставить нельзя и нельзя добавлять
выражения в Watch-окно. Чтобы target-систему передала управление на отладчик,
нужно нажать на панели управления Break (или нажать s<Ctrl+Break>). Тогда
отладчик получит управление над target-системой.

Отладка на основе host-target может быть очень полезна при отладке сервисов,
которые нельзя отлаживать без ограничений локальным отладчиком. Windbg через
VMware предоставляет возможность полного контроля над отлаживаемым сервисом.
Если появилась необходимость в отладке службы, то вставь в ее стартовую функцию
вызов DebugBreak. Единственное его предназначение – выполнить int 0x3. Но так
как диспетчер исключений предоставляет отладчику ядра первому обработать
исключение, то управление будет передано напрямую в подключенный Windbg.

 

И это все?!

Поздравляю, коллега. Только что ты научился работать с kernel mode кодом,
используя лишь Windbg и VmWare. Но нет предела совершенству – как нет пределов
изучению новых отладочных технологий. И их я непременно освещу в ближайшее время
в твоем любимом журнале.

Оставить мнение

Check Also

А ты знал? 10 фактов о Python

Python — язык программирования с достаточно низким порогом вхождения, поэтому его часто вы…