// Задаем печать отладочных сообщений
#if DBG
DbgPrint("IRQDMA: in Write now\n");
#endif
ULONG BytesTxd = 0;// Число //переданных/полученных байт (пока 0)
NTSTATUS status = STATUS_SUCCESS; //Завершение с кодом status
#if DBG
DbgPrint("InputLength: %d", InputLength);
#endif
UCHAR *buff = (PUCHAR)pIrp->AssociatedIrp.SystemBuffer;
pDevExt->OutBufferLength = InputLength;
while(InputLength > 0)
{
RtlFillMemory((PVOID)((ULONG)pDevExt->deviceOutBuffer + BytesTxd), 1, *buff);
InputLength--;
BytesTxd++;
buff++;
}
WRITE_PORT_ULONG((PULONG)(pDevExt->portBase + START_ADR_REG), (ULONG)MmGetPhysicalAddress(pDevExt->deviceOutBuffer).QuadPart);
WRITE_PORT_ULONG((PULONG)(pDevExt->portBase + SIZE_REG), pDevExt->OutBufferLength);
WRITE_PORT_ULONG((PULONG)(pDevExt->portBase + COMAND_REG), READ_CMD);
pDevExt->OutBufferLength = 0;
pIrp->IoStatus.Status = status;
pIrp->IoStatus.Information = BytesTxd;
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
//=================================================
// Функция: Read
// Назначение: Обрабатывает запрос по поводу Win32 //вызова ReadFile
// Аргументы: pDevObj - поступает от Диспетчера //ввода/вывода
// pIrp - поступает от Диспетчера ввода/вывода
// Возвращаемое значение:
// NTSTATUS - в случае нормального //завершения STATUS_SUCCESS
// или код ошибки STATUS_Xxx
NTSTATUS Read (IN PDEVICE_OBJECT pDevObj, IN PIRP pIrp)
{
PDEVICE_EXTENSION pDevExt =
(PDEVICE_EXTENSION) pDevObj->DeviceExtension;
PIO_STACK_LOCATION pIrpStack = IoGetCurrentIrpStackLocation(pIrp);
UCHAR *buff = (PUCHAR)pIrp->AssociatedIrp.SystemBuffer;
// Задаем печать отладочных сообщений
#if DBG
DbgPrint("IRQDMA: in Read now\n");
#endif
ULONG BytesTxd = 0;// Число //переданных/полученных байт (пока 0)
NTSTATUS status = STATUS_SUCCESS; //Завершение с кодом status
if(READ_PORT_ULONG((PULONG)(pDevExt->portBase + STATUS_REG)) == (ULONG)DMA_WRITE_DONE)
{
while(pDevExt->InBufferLength > 0)
{
*(buff + BytesTxd) = *(PUCHAR)((ULONG)pDevExt->deviceInBuffer + BytesTxd);
DbgPrint("data: %c", *(buff + BytesTxd));
pDevExt->InBufferLength--;
BytesTxd++;
}
}
pIrp->IoStatus.Status = status;
pIrp->IoStatus.Information = BytesTxd;
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
Рабочая процедура обработки запросов открытия драйвера
Процедура Create предназначена для обработки запросов Диспетчера ввода/вывода, которые он формирует в виде IRP пакетов с кодами IRP_MJ_CREATE по результатам обращения к драйверу из пользовательских приложений с вызовами CreateFile или из кода режима ядра с вызовами ZwCreateFile. В нашем примере эта функция не выполняет никаких особых действий (хотя можно было бы завести счетчик открытых дескрипторов и т.п.), однако без регистрации данной процедуры система просто не позволила бы клиенту "открыть" драйвер для работы с ним (хотя сам драйвер мог бы успешно загружаться и стартовать).
Листинг 18.
//=================================================
// Функция: Create
// Назначение: Обрабатывает запрос по поводу Win32 //вызова CreateFile
// Аргументы: pDevObj - поступает от Диспетчера //ввода/вывода
// pIrp - поступает от Диспетчера ввода/вывода
// Возвращаемое значение: STATUS_SUCCESS
NTSTATUS Create (IN PDEVICE_OBJECT pDevObj, IN PIRP pIrp)
{
#if DBG
DbgPrint("IRQDMA: in Create now\n");
#endif
pIrp->IoStatus.Status = STATUS_SUCCESS;
pIrp->IoStatus.Information = 0; // ни одного //байта не передано
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
Рабочая процедура обработки запросов закрытия драйвера
Процедура Close предназначена для обработки запросов Диспетчера ввода/вывода, которые он формирует в виде IRP пакетов с кодом IRP_MJ_CLOSE по результатам обращения к драйверу из пользовательских приложений с вызовами CloseHandle или из кода режима ядра с вызовами ZwClose. В нашем примере эта функция не выполняет никаких особых действий, однако, выполнив регистрацию процедуры открытия файла, мы теперь просто обязаны зарегистрировать процедуру завершения работы клиента с открытым дескриптором. Заметим, что если клиент пользовательского режима забывает закрыть полученный при открытии доступа к драйверу дескриптор, то за него эти запросы выполняет операционная система (впрочем, как и в отношении всех открытых приложениями файлов, когда приложения завершаются без явного закрытия открытых файлов).
Листинг 19.
//=================================================
// Функция: Close
// Назначение: Обрабатывает запрос по поводу //Win32 вызова CloseHandle
// Аргументы: pDevObj - поступает от Диспетчера //ввода/вывода
// pIrp - поступает от Диспетчера ввода/вывода
// Возвращаемое значение: STATUS_SUCCESS
//
NTSTATUS Close (IN PDEVICE_OBJECT pDevObj, IN PIRP pIrp)
{
#if DBG
DbgPrint("IRQDMA: in Close now\n");
#endif
pIrp->IoStatus.Status = STATUS_SUCCESS;
pIrp->IoStatus.Information = 0; // ни одного //байта не передано
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
Рабочая процедура выгрузки драйвера
Процедура DriverUnload выполняет завершающую работу.
Листинг 20.
//=================================================
// Функция: DriverUnload
// Назначение: Останавливает и удаляет объекты //устройств, отключает
// прерывания, подготавливает драйвер к выгрузке.
// Аргументы: pDriverObject - поступает от //Диспетчера ввода/вывода
// Возвращаемое значение: нет
VOID DriverUnload (IN PDRIVER_OBJECT pDriverObject)
{
#if DBG
DbgPrint("IRQDMA: in DriverUnload now\n");
#endif
PDEVICE_OBJECT pNextObj = pDriverObject->DeviceObject;
//Проход по всем устройствам, контролирумым //драйвером
for(; pNextObj!=NULL;)
{
PDEVICE_EXTENSION pDevExt =
(PDEVICE_EXTENSION)pNextObj->DeviceExtension;
// Удаляем объект прерываний:
if (pDevExt->pIntObj)
{
IoDisconnectInterrupt(pDevExt->pIntObj);
}
// Удаляем символьную ссылку:
IoDeleteSymbolicLink(&pDevExt->ustrSymLinkName);
#if DBG
DbgPrint("IRQDMA: SymLink %ws deleted\n",
pDevExt->ustrSymLinkName.Buffer);
#endif
// Сохраняем ссылку на следующее //устройство и удаляем
// текущий объект устройства:
pNextObj = pNextObj->NextDevice;
IoDeleteDevice(pDevExt->pDevice);
}
}
Заголовочный файл Driver.h
В заголовочном файле описывается структура расширения объекта устройства, которую разработчик драйвера определяет самостоятельно. В этой структуре сохранены имена устройства, символьная ссылка и базовый адрес устройства. Так же в заголовочном файле описаны макроопределения, необходимые для работы с устройством, такие как смещение, относительно базового адреса, регистров устройства и значения статусов устройства
Первым полем структуры расширения объекта устройства является указатель на сам объект устройства. Это является общепринятой традицией "правописания" драйверов, поскольку достаточно часто указатель на расширение передается в качестве контекстных указателей разным процедурам, которые, в конечном счете, нуждаются и в получении ссылки на сам объект устройства.
Листинг 21.
//=================================================
// Driver.h - заголовочный файл для драйвера
//=================================================
#pragma once
extern "C" {
#include <NTDDK.h>
}
#define MAX_BUFFER_SIZE (4096)
/* Регистры PCI устройства */
#define STATUS_REG 0x0
#define COMAND_REG 0x0
#define START_ADR_REG 0x1
#define SIZE_REG 0x2
#define DATA_REG 0x3
/* Комманды PCI */
#define WRONG_CMD 0x00000000
#define WRITE_CMD 0x00000001 //записать буфер //приёма в память
#define READ_CMD 0x00000002 //считать в //буфер отправки из памяти
#define SEND_CMD 0x00000003 //отправить //содержимое буфера отправки
#define START_READ_CMD 0x00000004 //начать приём //пакетов => вкл. прерывания
/* Статус устройства PCI */
#define DMA_READ_DONE 0x00000001
#define DMA_WRITE_DONE 0x00000002
#define DATA_RECVD 0x00000003
#define BUFF_IS_EMPTY 0x00000004
typedef struct _DEVICE_EXTENSION
{
PDEVICE_OBJECT pDevice;
UNICODE_STRING ustrDeviceName;
// внутреннее имя устройства
UNICODE_STRING ustrSymLinkName;
// внешнее имя (символьная ссылка)
PVOID deviceOutBuffer, //указатель на буфер отправки deviceInBuffer;
//указатель на буфер приема
ULONG OutBufferLength, // объем буфера отправки
InBufferLength; // объем буфера приема
PUCHAR portBase; // адрес порта ввода/вывода
ULONG Irq; // Irq в терминах шины PCIBus
} DEVICE_EXTENSION, *PDEVICE_EXTENSION;
Для компиляции и сборки драйвера утилитой Build пакета DDK потребуется создать два файла описания проекта — Makefile и Sources.
Файл Makefile. Этот файл управляет работой программы Build и в нашем случае имеет стандартный вид (его можно найти практически в любой директории примеров DDK), а именно:
!INCLUDE $(NTMAKEENV)\makefile.def
Файл Sources. Файл sources отражает индивидуальные настройки процесса компиляции и сборки. В нашем случае файл Sources чрезвычайно прост и имеет вид:
TARGETNAME=IRQDMA
TARGETTYPE=DRIVER
TARGETPATH=obj
SOURCES=init.cpp
Данный файл задает имя выходного файла Example, параметр TARGETNAME. Поскольку проект (TARGETTYPE) имеет тип DRIVER, то выходной файл будет иметь расширение .sys. Промежуточные файлы будут размещены во вложенной директории .\obj. Строка SOURCES задает единственный файл с исходным текстом — это файл init.cpp.
Для компиляции «чистовой» версии драйвера нужно запустить .exe файл:
Пуск – Все программы – Development Kits – Windows DDK 3790.1830 – Build Environments – Windows XP – Windows XP Free Build Environment.exe
Для компиляции отладочной версии (данная версия позволяет получать отладочные сообщения от драйвера в программе DebugView) драйвера нужно запустить:
Пуск – Все программы – Development Kits – Windows DDK 3790.1830 – Build Environments – Windows XP – Windows XP Checked Build Environment.exe
Когда программы запущена, нужно выполнить консольную команду перехода к директории, в которой находятся файлы с кодом драйвера и файлы описания проекта и вызвать команду build (например):
C:\WINDDK\3790.1830>cd C:\Example\
C:\Example>build
После выполнения этих действий начнется компиляция и сборка драйвера. В случае ошибок компиляции или сборки вывод будет содержать и их диагностику. Рабочее окно сборки драйвера под Windows XP DDK версии checked показано ниже.
Рабочее окно сборки драйвера под Windows XP DDK версии checked
Работа с драйвером IRQDMA.sys
Как уже было сказано, из всех возможных способов инсталляции и запуска драйвера IRQDMA.sys, ниже будет использован способ тестирования с применением тестирующего консольного приложения, которое само будет выполнять инсталляцию и удаление драйвера (прибегая к вызовам SCM Менеджера). Для поэтапного ознакомления с процессом взаимодействия драйвера и обращающегося к нему приложения рекомендуется запустить программу IRQDMATest под отладчиком в пошаговом режиме.
Перед запуском тестирующей программы IRQDMATest рекомендуется загрузить программу DebugView, чтобы в ее рабочем окне наблюдать сообщения, поступающие непосредственно из кода драйвера IRQDMA.sys (отладочной сборки).
Протокол полученных программой DebugView отладочных сообщений драйвера можно сохранить в файле для последующего анализа. Ниже приведена информация из такого файла, отражающая события в драйвере IRQDMA.sys с момента его загрузки и вызова процедуры DriverEntry до момента выгрузки и вызова процедуры UnloadRoutine.
Приложение, работающее с драйвером
Перед тем, как приступить к тестированию драйвера путем вызова его сервисов из приложения, следует это приложение создать, хотя бы в минимальном виде, как это предлагается ниже. И хотя драйвер можно успешно запускать программой Monitor, воспользуемся функциями SCM, поскольку это будет существенно полезнее для будущей практики. Для загрузки и выгрузки драйверов используется диспетчер управления службами SC Manager (Service Control Manager). Прежде чем начать работау с интерфейсом SC, необходимо получить дескриптор диспетчера служб. Для этого следует обратиться к функции OpenSCManager(). Дескриптор диспетчера служб необходимо использовать при обращении к функциям CreateServise() и OpenService(). Дескрипторы, возвращаемые этими функциями необходимо использовать при обращении к вызовам, имеющим отношение к конкретной службе. К подобным вызовам относятся функции ControlService(), DeleteService() и StartService(). Для освобождения дескрипторов обоих типов используется вызов CloseServiceHandle().
Загрузка и запуск службы подразумевает выполнение следующих действий:
Обращение к функции OpenSCManager() для получения дескриптора диспетчера.
Обращение к CreateServise() для того, чтобы добавить службу в систему. Если такой сервис уже существует, то CreateServise() выдаст ошибку с кодом 1073 (код ошибки можно прочитать GetLastError()) данная ошибка означает, что сервис уже существует и надо вместо CreateServise() использовать OpenService().
Обращение к StartService() для того, чтобы перевести службу в состояние функционирования.
Если служба запустилась успешно, то можно вызвать CreateFile(), для получения дескриптора, который мы будем использовать уже непосредственно при обращении к драйверу.
По окончании работы необходимо дважды обратиться к CloseServiceHandle() для того, чтобы освободить дескрипторы диспетчера и службы.
Если на каком-то шаге этой последовательности возникла ошибка, нужно выполнить действия обратные тем, которые были выполнены до возникновения ошибки.
Надо помнить о том, что при обращении к функциям подобным CreateServise(), необходимо указывать полное имя исполняемого файла службы (в нашем случае полный путь и имя IRQDMA.sys).
Листинг 22.
///////////////////////////////////////////////////
// (Файл ExampleTest.cpp)
///////////////////////////////////////////////////
// Заголовочные файлы, которые необходимы в данном //приложении:
#include <windows.h>
#include <stdio.h>
#include <winioctl.h>
#include <tchar.h>
#include <stdio.h>
// Внимание! Файл Ioctl.h должен быть получен из //файла Driver.h
// (см. комментрарии к Driver.h) и размещен в одной //директории с
// данным файлом (TestExam.cpp).
#include "Ioctl.h"
// Имя объекта драйвера и местоположение //загружаемого файла
#define DRIVERNAME _T("IRQDMA")
#define DRIVERBINARY _T("C:\\IRQDMA.sys")
// Функция установки драйвера на основе SCM вызовов
BOOL InstallDriver( SC_HANDLE scm, LPCTSTR DriverName, LPCTSTR driverExec )
{
SC_HANDLE Service =
CreateService (scm, // открытый дескриптор к SCManager
DriverName, // имя сервиса - IRQDMA
DriverName, // для вывода на экран
SERVICE_ALL_ACCESS, // желаемый доступ
SERVICE_KERNEL_DRIVER, // тип сервиса
SERVICE_DEMAND_START, // тип запуска
SERVICE_ERROR_NORMAL, // как обрабатывается ошибка
driverExec, // путь к бинарному файлу
//Остальные параметры не используются - укажем NULL
NULL, // Не определяем группу загрузки
NULL, NULL, NULL, NULL);
if (Service == NULL) // неудача
{
DWORD err = GetLastError();
if (err == ERROR_SERVICE_EXISTS)
{/*уже установлен */}
// более серьезная ощибка:
else printf ("ERR: Can’t create service. Err=%d\n",err);
// (^^ Этот код ошибки можно подставить в ErrLook):
return FALSE;
}
CloseServiceHandle (Service);
return TRUE;
}
// Функция удаления драйвера на основе SCM вызовов
BOOL RemoveDriver(SC_HANDLE scm, LPCTSTR DriverName)
{
SC_HANDLE Service = OpenService (scm, DriverName, SERVICE_ALL_ACCESS);
if (Service == NULL) return FALSE;
BOOL ret = DeleteService (Service);