Помощничек
Главная | Обратная связь


Археология
Архитектура
Астрономия
Аудит
Биология
Ботаника
Бухгалтерский учёт
Войное дело
Генетика
География
Геология
Дизайн
Искусство
История
Кино
Кулинария
Культура
Литература
Математика
Медицина
Металлургия
Мифология
Музыка
Психология
Религия
Спорт
Строительство
Техника
Транспорт
Туризм
Усадьба
Физика
Фотография
Химия
Экология
Электричество
Электроника
Энергетика

Пример 1. Драйвер, выводящий сообщение «Hello world»

Лабораторная работа № 4. Сборка и запуск драйвера

Драйверы, в отличие от остальных программ, выполняются в особом режиме, где они имеют ряд возможностей, недоступных для обычных приложений Windows. Их отладка осложняется тем, что в этом режиме практически отсутствуют средства для взаимодействия с пользователем. Для тестирования драйвера можно пользоваться функцией DbgPrint, выдающей отладочные сообщения отладчику. Но отладчика может не оказаться и этих сообщений никто в таком случае не увидит. Для обычного же взаимодействия с пользователем драйвер должен полагаться на прикладные приложения, обмениваясь с ними запросами ввода/вывода.

Сообщения, выдаваемые отладчику, проще всего увидеть при помощи программы DebugView, которую нужно запустить перед сеансом проверки.

Пример 1. Драйвер, выводящий сообщение «Hello world».

Создаем папку DrvHello, где будем разрабатывать приложение-драйвер. Внутри нее последовательно создаем следующие файлы.

1. makefile

#
# DO NOT EDIT THIS FILE!!! Edit .\sources. if you want to add a new source
# file to this component. This file merely indirects to the real make file
# that is shared by all the driver components of the Windows NT DDK
#
!INCLUDE $(NTMAKEENV)\makefile.def

Файл должен присутствовать в данном проекте. В других проектах его может и не быть, либо он будет работать другим образом.
2. sources

TARGETNAME=drvhello
TARGETTYPE=DRIVER
TARGETPATH=obj
SOURCES=main.c

Файл задает параметры сборки. Здесь в параметре SOURCES необходимо перечислить через пробел все файлы с исходным кодом, которые нужно будет скомпилировать.
3. main.c – это файл с исходным кодом. Он может носить любое имя. Таких файлов может быть любое количество. Главное, чтобы они все были перечислены в файле sources.

#include "ntddk.h"

#pragma

code_seg("INIT")
NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject,

IN PUNICODE_STRING RegistryPath)

{
DbgPrint("Hello, world!"); // Выдача отладочного сообщения
return STATUS_DEVICE_CONFIGURATION_ERROR; // Выдача ошибки заставит систему сразу же выгрузить драйвер

}
#pragma code_seg()

Основная функция у нас здесь носит название DriverEntry. Запускаться она будет при попытке загрузки драйвера. Для того, чтобы сделать такую попытку, требуется программа-загрузчик, которая будет рассмотрена позднее.

Откомпилируем драйвер. Для того, чтобы можно было что-то увидеть в отладчике, драйвер должен быть собран в отладочном режиме. Отладочный режим у программистов драйверов называется checked, а финальный - free (также эти режимы называют debug и release, соответственно). В этом режиме программный код не оптимизируется и исполняемый файл содержит много избыточной информации, совершенно не нужной для работы, но очень полезной при изучении того, что будет делать этот код.

Для сборки открываем меню Start, затем Programs/Windows Driver Kits/WDK <номер версии>/Build Environment/Windows Server 2003 (или Windows XP)/ x86 Checked Build Environment. Щелкаем и попадаем в консольное окно. Если FAR был добавлен в PATH, то можно набрать far и перейти в директорию проекта обычным способом, иначе туда придется добираться используя команду cd (и может быть смену диска). После входа в папку DrvHello (там где лежат три файла, которые были созданы выше) – набираем команду nmake. Если сборка прошла без ошибок, то будет созданы директории objchk_wnet_x86/i386, а в ней уже можно обнаружить файл drvhello.sys. Это и есть собранный драйвер.

Вернемся к загрузчику. Это у нас будет консольное приложение. Дадим ему имя DrvHelloloader и разместим, как всегда, в c:\Projects. Имя файла с исходным кодом такое же – main.c, содержимое:

#include <windows.h>
#include <shlwapi.h>
#pragma comment(lib, "shlwapi.lib")

void main(int argc, char* argv[])
{
char serviceName[] = "drvhello";
char driverBinary[MAX_PATH] = "";
SC_HANDLE hSc;
SC_HANDLE hService;
// Чтобы запустить драйвер, потребуется полный путь к нему
// Предполагаем, что он лежит в той же папке, что и экзешник
strcpy(driverBinary, argv[0]); // argv[0] - здесь будет имя экзешника с полным путем
PathRemoveFileSpec(driverBinary); // Выкидываем имя экзешника, остается только путь к папке
strcat(driverBinary, "\\drvhello.sys"); // Добавляем имя драйвера.
// Бэкслэш в строках Си надо удваивать, из-за его специального значения.
hSc = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS); // Открываем SCM (Service Control Management эта функция позволяет запускать драйверы из пользовательского режима
CreateService(hSc, serviceName, serviceName,
SERVICE_ALL_ACCESS, SERVICE_KERNEL_DRIVER, SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL,
driverBinary, NULL, NULL, NULL, NULL, NULL);

// Загрузка в 3 этапа - создаем службу
hService = OpenService(hSc, serviceName, SERVICE_ALL_ACCESS); // Открываем ее
StartService(hService, 0, NULL); // Запускаем службу
}

Выбираем в меню cтудии пункт Build/Rebuild Solution. Произойдет пересборка проекта без запуска (в принципе, можно и запустить его). Находим экзешник в папке проекта, добавляем в ту же папку файл drvhello.sys. Далее запускаем DebugView, включаем галочку Capture/Capture Kernel, как показано на рисунке:


Рис. 3.1 – Настройка DebugView

Теперь запускаем программу. Если все прошло успешно – видим следующую картину:

Рис. 3.2 – Проверка драйвера

Приведенный код позволяет работать на уровне ядра. Но следует помнить, что если допустить ошибку в прикладной программе – упадет она одна; если в драйвере – вся система. К примеру, если в код драйвера перед DbgPrint добавить строку:

int a = *((int *)0);

которая делает попытку прочесть содержимое памяти по нулевому адресу – то получаемое содержимое экрана будет уже иное:

Рис. 3.3 – Попытка чтения из нулевого адреса

Если бы это был не драйвер, а просто программа пользовательского режима, то при выполнении этого кода она бы просто закрылась, выдав ошибку:

Рис. 3.4 – Та же ошибка в программе пользовательского режима

Пример 2. Драйвер, выводящий сообщения «Hello world!» и «Goodbye!»

Рассмотрим разработку простой программы-драйвера, которая делает только то, что пишет отладочное сообщение «Hello world!» при старте драйвера и «Goodbye!» при завершении.

Текст программы, выполняющей заданные действия.

// TestDriver.c

#include <ntddk.h>

NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath);

VOID UnloadRoutine(IN PDRIVER_OBJECT DriverObject);

#pragma alloc_text(INIT, DriverEntry)

#pragma alloc_text(PAGE, UnloadRoutine)

NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath)

{

DriverObject->DriverUnload = UnloadRoutine;

 

DbgPrint(«Hello world!\n»);

 

return STATUS_SUCCESS;

}

 

VOID UnloadRoutine(IN PDRIVER_OBJECT DriverObject)

{

DbgPrint(«Goodbye!\n»);

}

 

Рассмотрим смысл каждой инструкции.

1. Подключаемый заголовочный файл ntddk.h - один из базовых подключаемых файлов во всех драйверах: в нем содержатся объявления типов NTSTATUS, PDRIVER_OBJECT, PUNICODE_STRING, а также функции DbgPrint.

2. Объявление двух функций: DriverEntry и UnloadRoutine. Рассмотрим подробнее первую из них. В каждой программе есть точка входа, в программах на языке C это функция main или WinMain. В драйвере роль точки входа выполняет функция DriverEntry, которая получает на вход указатель на структуру DriverObject, а также указатель на строку реестра, соответствующую загружаемому драйверу. Структура DriverObject содержит множество полей, которые определяют поведение будущего драйвера. Наиболее ключевые из них — это указатели на так называемые вызываемые (или callback) функции, то есть функции, которые будут вызываться при наступлении определенного события. Одну из таких функций мы определяем: это функция UnloadRoutine. Указатель на данную функцию помещается в поле DriverUnload. Таким образом при выгрузке драйвера сначала будет вызвана функция UnloadRoutine. Это очень удобно, когда драйвер имеет какие-то временные данные, которые следует очистить перед завершением работы. В нашем примере эта функция нужна только чтобы отследить сам факт завершения работы драйвера.

3. Для того, чтобы выводить отладочные сообщения, использована функция DbgPrint, которая имеет синтаксис, аналогичной функции printf из пользовательского режима (userspace).

4. Также использованы директивы #pragma alloc_text(INIT, DriverEntry) и #pragma alloc_text(PAGE, UnloadRoutine). Первая помещает функцию DriverEntry в INIT секцию, то есть как бы говорит, что DriverEntry будет выполнена один раз и после этого код функции можно спокойно выгрузить из памяти. Вторая помечает код функции UnloadRoutine как выгружаемый, т.е. при необходимости, система может переместить его в файл подкачки, а потом забрать его оттуда. Это делается по следующей причине. Каждый процесс в системе имеет такой параметр, как IRQL (Interrupt request level), то есть некоторый параметр, отвечающий за возможность прерывания процесса: чем выше IRQL тем меньше шансов прервать выполнение процесса. Возможности процесса так же зависят от IRQL: чем выше IRQL тем меньше возможности процесса, это вполне логично, т.е. такой подход побуждает разработчиков выполнять только самые необходимые операции при высоком IRQL, а все остальные действия делать при низком. Вернемся к основной теме, о том, почему мы делаем для функции UnloadRoutine возможность выгрузки в файл подкачки: все опять же сводится к оптимизации: работа с файлом подкачки недоступна при высоком IRQL, а процедура выгрузки драйвера гарантированно выполняется при низком IRQL, поэтому мы специально указываем что код функции выгрузки драйвера можно поместить в своп.

Рассмотрим порядок компиляции и запуска драйвера. Для этого нам понадобится:

  • WDK или DDK
  • Текстовый редактор
  • Программа DbgView — бесплатная программа для просмотра отладочных сообщений, получаемых от драйверов, ее можно найти на сайте sysinternals
  • Программа KmdManager — бесплатная программа для регистрации, запуска и тестирования драйвера, ее можно найти на сайте wasm.ru

Теперь последовательность действий: сначала мы пишем два файла/

1. Первый называется makefile, имеет следующее содержимое:

##################################################
# DO NOT EDIT THIS FILE!!! Edit .\sources. if you want to add a new source
# file to this component. This file merely indirects to the real make file
# that is shared by all the driver components of the Windows NT DDK
#
!INCLUDE $(NTMAKEENV)\makefile.def

##################################################

2. Второй называется sources и содержит в себе следующее:


##################################################
TARGETNAME=TestDriver
TARGETTYPE=DRIVER

SOURCES=TestDriver.c
##################################################

Эти файлы нужны для сборки драйвера.

В WDK нет встроенной среды разработки, поэтому и нужен текстовый редактор, чтобы набирать текст драйверов. Для этой цели можно использовать и Visual Studio (некоторые даже интегрируют возможность сборки драйверов из VS), и любой другой текстовый редактор.

Сохраняем код драйвера в файл TestDriver.c и кладем его в ту же директорию, что и файлы makefile и souces. После этого запускаем установленный build environment (это командная строка с заданными переменными окружения для компиляции драйвера; она входит в WDK, и запустить ее можно следующим образом: «Пуск->Программы->Windows Driver Kits->....->Build Environments->WindowsXP->Windows XP x86 Checked Build Environment»).

Затем необходимо перейти в ту директорию, куда быд записан файл с драйвером (например, C:\Drivers\TestDriver) с помощью команды cd (например, команда выглядит следующим образом: cd C:\Drivers\TestDriver) и набираем команду build. Данная команда соберет нам драйвер TestDriver.sys и положит его в папку «objchk_wxp_x86\i386».

Теперь нужно запустить программу DbgView чтобы увидеть сообщения, которые будет выдавать драйвер. После запуска данной программы нужно указать, что мы хотим просматривать сообщения из ядра (Capture->Capture Kernel).

Запускаем программу KmdManager, указываем путь к нашему драйверу (файл TestDriver.sys) нажимаем кнопку Register, затем Run. Теперь драйвер зарегистрирован в системе и запущен. В программе DbgView мы должны увидеть наше сообщение «Hello World!». Теперь завершаем работу драйвера кнопкой Stop и убираем регистрацию драйвера кнопкой Unregister. Кстати, в DbgView дожна появиться еще одна строка.

Таким образом, написан, скомпилирован и запущен Windows-драйвер.

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

 




Поиск по сайту:

©2015-2020 studopedya.ru Все права принадлежат авторам размещенных материалов.