Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
ДП.docx
Скачиваний:
11
Добавлен:
23.09.2019
Размер:
4.64 Mб
Скачать

2.2.5 Управление перемещаемостью кода в драйвере

Системы Windows работают на компьютерах с поддержкой виртуального адресного пространства. Виртуальные адреса ото­бражаются либо на физическую память, либо (по крайней мере, на концептуаль­ном уровне) на страничные блоки в файле подкачки на диске. Сильно упрощая, можно представить себе адресное пространство разделенным на две части: часть режима ядра и часть пользовательского режима, как показано на рис. 2.3.

Рисунок 2.3 – Составляющие адресного пространства Windows

Каждый процесс пользовательского режима обладает собственным адресным контекстом, то есть отображением виртуальных адресов пользовательского ре­жима на уникальную совокупность физических страничных блоков. Другими словами, смысл виртуального адреса изменяется по мере того, как планировщик Windows переключается с потоков одного процесса на потоки другого про­цесса. Одной из стадий переключения потоков является замена таблиц страниц, используемых процессором, и приведение их в соответствие с контекстом про­цесса нового потока [2.2].

Таким образом, основной целью систем виртуальной памяти является возможность создания виртуального адресного пространства, значительно превышающего объем физи­ческой памяти компьютера. Для достижения этой цели менеджер памяти (Memory Manager) выгружает страничные блоки из физической памяти и загру­жает их обратно по мере надобности. Некоторые компоненты операционной системы выгружаться не могут, потому что они необходимы для обеспечения работы самого менеджера памяти [2.2].

Итак, одни части драйвера должны постоянно оставаться резидентными, а дру­гие могут выгружаться. Традиционно для передачи компилятору инструкций о размещении кода в оп­ределенной секции использовалась директива #pragma alloc_text. Поскольку эта директива поддерживается не всеми компиляторами, в заголовочных файлах DDK преду­смотрена специальная константа ALLOC_PRAGMA. Далее директива alloc_text вызывается для определения размещения отдельных функций драйвера по секциям, как в следующем фрагменте:

#ifdef ALLOC_PRAGMA

#pragma alloc_text(PAGE, AddDevice)

#pragma alloc_text(PAGE, DispatchPnp)

#endif

Эти директивы обеспечивают размещение функций AddDevice и DispatchPnp в перемещаемой памяти.

Однако я предпочитаю определять размещение секций для целых блоков программного кода при помощи директивы Microsoft #pragma code_seg. Иначе говоря, следующий фрагмент приказывает компилятору Microsoft при­ступить к размещению функций в перемещаемой памяти:

#pragma code_seg("PAGE")

NTSTATUS AddDeviсе(...){...}

NTSTATUS DispatchPnp(...){...}

Обе функции, AddDevice и DispatchPnp, попадают в перемещаемую память. Чтобы вернуться к кодовой секции по умолчанию, необходимо использовать директиву #pragma code_seg без аргумента:

#pragma code_seg()

2.3 Разработка алгоритмического обеспечения

2.3.1 Инициализация драйвера

Процедура DriverEntry присутствует в любом драйвере и имеет данное стандартное имя. Драйверы «в-стиле-NT» выполняют в DriverEntry большую работу, нежели более «старые» WDM драйвера — последние откладывают часть работы по инициализа­ции устройства до момента обнаружения устройства системой, когда будет вызва­на процедура AddDevice.

Таблица 2.8 - Параметры вызова функции DriverEntry

NTSTATUS DriverEntry

IRQL == PASSIVE_LEVEL

Параметры

Описание

IN PDRIVER_OBJECT DriverObject

Адрес объекта драйвера

IN PUNICODE_STRING RegistryPath

Путь в регистре к подразделу драйвера

Возвращаемое значение

  • STATUS_SUCCESS

  • STATUS_XXX — код ошибки

Получив от Диспетчера ввода/вывода указатель на структуру DRIVER_OBJECT, драйвер должен заполнить в ней определенные поля, а именно [2.3]:

  • Поле pDriverObject->DriverUnload — для регистрации собственной функции Unload, которая вызывается перед выгрузкой драйвера.

  • Поле pDriverObject->DriverStartIo — для регистрации собственной функции Startlo, которая необходима для организации обработки очереди необрабо­танных запросов System Queuing.

  • Поле pDriverObject->DriverExtension->AddDevice — в структуре расширения объекта драйвера DRIVER_EXTENSION, в котором WDM драйвер регистрирует собственную процедуру AddDevice.

  • В массиве pDriverObject->MajorFunction[IRP_MJ_Xxx] драйвер регистрирует точки входа в собственные рабочие процедуры.

Помимо регистрации функций, процедура DriverEntry драйвера «в стиле-NT» может выполнять следующую работу [2.3]:

  • DriverEntry определяет аппаратное обеспечение, которое драйвер будет кон­тролировать. Это аппаратное обеспечение выделяется драйверу, то есть по­мечается как находящееся под управлением данного драйвера.

  • Если драйвер управляет многокомпонентным (multiunit) или многофункциональным контроллером, используется IoCreateController для создания объекта контроллера, после чего инициализируется структура расширения контроллера.

  • Выполняет вызов IoCreateDevice для создания объекта устройства для каждо­го физического или логического устройства под управлением данного драйве­ра, в процессе которого инициализируется структура расширения устройства для каждого созданного объекта устройства. Рекомендуется сразу же после этого вызова явно установить флаги (поле Flags в объекте устройства), описы­вающие способ буферизации, используемый данным устройством.

  • Созданные устройства затем делаются видимыми для приложений пользова­тельского режима путем выполнения вызова IoCreateSymbolicLink.

  • Устройство подключается к объекту прерываний. В случае, если ISR процеду­ра требуют использования объекта DPC (отложенного процедурного вызо­ва), то он создается и инициализируется на этом этапе.

  • Шаги с 3 по 5 повторяются для каждого физического или логического уст­ройства, работающего под управлением данного драйвера.

  • В случае успешного завершения, функция DriverEntry должна возвратить Диспетчеру ввода/вывода значение STATUS_SUCCESS.

2.3.2 Обработка запросов ввода\вывода

Пакеты ввода / вывода (IRP‑пакеты) используются для передачи запросов к драйверу от его клиентов. Они являются структурами данных переменной длины, и состоят из стандартного заголовка, содержащего общую учетную информацию, и одного или нескольких блоков параметров, называемых ячейками стека ввода/вывода (I/O Stack Location) [2.3].

В таблице 2.9 приведена структура заголовка IRP‑пакета [2.3]:

Таблица 2.9 − Структура заголовка IRP‑пакета

Поля

Описание

IO_STATUS_BLOCK IoStatus

Статус запроса

PVOID

AssociatedIrp.SystemBuffer

Указатель на системный буфер для случая, если устройство поддерживает буферизованный ввод / вывод

PMDL MdlAddress

Указатель на MDL‑список в случае, если устройство поддерживает прямой ввод/вывод

PVOID UserBuffer

Адрес пользовательского буфера для ввода/вывода

BOOLEAN Cancel

Индикатор того, что IRP‑пакет должен быть аннулирован

Основное назначение ячеек стека ввода/вывода состоит в том, чтобы хранить функциональный код и параметры запроса на ввод/вывод. Ниже, в таблице 2.10 приводятся поля ячеек стека ввода/вывода, к которым драйвер может обращаться непосредственно по указателю (чего не рекомендуется делать для остальных полей) [2.3]:

Таблица 2.10 − Структура ячейки стека ввода/вывода

Поля

Описание

UCHAR MajorFunction

Код IRP_MJ_XXX, описывающий назначение операции

UCHAR MinorFunction

Субкод операции

PDEVICE_OBJECT DeviceObject

Указатель на объект устройства, которому был адресован данный объект IRP

PFILE_OBJECT FileObject

Файловый объект для данного запроса, если он задан

union Parameters (трактовка определяется значением MajorFunction)

struct Read

Параметры для IRP типа IRP_MJ_READ:

ULONG Length

ULONG Key

LARGE_INTEGER ByteOffset

struct Write

Параметры для IRP типа IRP_MJ_WRITE:

ULONG Length

ULONG Key

LARGE_INTEGER ByteOffset

struct DeviceControl

Параметры для IRP типа IRP_MJ_DEVICE_CONTROL:

ULONG OutputBufferLength

ULONG InputBufferLength

ULONG IoControlCode

PVOID Type3InputBuffer

Приведем графическое представление структуры IRP‑пакета (рис. 2.4):

Рисунок 2.4 − Структура IRP пакета

Общение с USB‑накопителями в ОС Windows NT на уровне драйверов происходит посредством передачи URB‑пакетов. Указатели на URB‑пакеты содержат ячейки стека IRP‑пакета, доступ к этим указателям осуществляется следующим образом [2.3]:

PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);

PURB Urb = IrpSp->Parameters. Others. Argument1;

Приведем частичное объявление структуры из справочной документации Microsoft. Отметим только поля, использование которых необходимо в рамках данной темы [2.3]:

typedef struct _URB

{

union

{

struct _URB_HEADER UrbHeader;

struct _URB_SELECT_INTERFACE UrbSelectInterface;

struct _URB_SELECT_CONFIGURATION UrbSelectConfiguration;

struct _URB_BULK_OR_INTERRUPT_TRANSFER UrbBulkOrInterruptTransfer;

}

} URB, *PURB;

Поле UrbHeader хранит информацию о коде URB‑пакета, по которому можно определить, какая операция запрашивается.

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

Поле UrbBulkOrInterruptTransfer несет наиболее важную в рамках данной курсовой работы информацию – указатели на блоки ввода / вывода USB‑устройства. Приведем описание структуры _URB_BULK_OR_INTERRUPT_TRANSFER [2.3]:

struct _URB_BULK_OR_INTERRUPT_TRANSFER

{

struct _URB_HEADER Hdr;

USBD_PIPE_HANDLE PipeHandle;

ULONG TransferFlags;

ULONG TransferBufferLength;

PVOID TransferBuffer;

PMDL TransferBufferMDL;

struct _URB *UrbLink;

...

...

};

Поля этой структуры описаны в следующей таблице 2.11:

Таблица 2.11 − Поля структуры URB_BULK_OR_INTERRUPT_TRANSFER

Поле

Описание

struct _URB_HEADER Hdr

Стандартный заголовок URB‑пакета, содержащий код запроса

USBD_PIPE_HANDLE PipeHandle

Дескриптор канала, на который передаются данные

ULONG TransferFlags

Флаги, определяющие направление передачи данных и способ обработки ошибок

ULONG TransferBufferLength

Длина передаваемого блока данных в байтах

PVOID TransferBuffer

Указатель на передаваемый буфер. Буфер находится в нестраничной памяти

PMDL TransferBufferMDL

Указатель на MDL‑список, несущий передаваемую информацию. Буфер находится в страничной памяти

Следует отметить, что один из указателей TransferBuffer или TransferBufferMDL равен NULL, то есть в пределах одного пакета передается только одна порция данных. Задача протоколирования обмена информацией сводится к перехвату и сохранению буферов TransferBuffer и TransferBufferMDL.