Программирование драйверов Windows

       

Функции для работы с файлами


Невозможно представить полноценное средство разработки кода, если в нем нет способов работы с файлами. Для чего файлы могут понадобиться в драйвере? Два примера, первыми приходящие на ум: необходимость считывания кода прошивок внешних устройств (которые могут быть размером до десятков килобайт) и ведение протоколирования в файле на диске.

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

Специфика системной функции ZwCreateFile состоит в том, что она имеет протокольных параметров даже больше, чем пользовательский вызов CreateFile. Существенная часть входной информации об открываемом объекте поступает внутри структуры OBJECT_ATTRIBUTES, которую следует предварительно создать и заполнить соответствующим корректными данными. Для ведения учетной информации открытого объекта используется структура данных IO_STATUS_BLOCK, которую следует предоставить при вызове (инициализировать ее не следует).

Поскольку вызов ZwCreateFile имеет большое разнообразие флаговых значений практически для каждого из входных параметров, ограничимся кратким общим обзором, представленным в таблице ниже. Более полную информацию обо всех возможных значения параметров вызова ZwCreateFile следует получить из документации пакета DDK. (Джозеф Ньюкамер уделил в своей книге описанию этой функции 7 страниц!)

Таблица 7.39. Прототип вызова ZwCreateFile



NTSTATUS ZwCreateFile IRQL == PASSIVE_LEVEL
Параметры Предоставляет доступ к системным ресурсам (в том числе файлам) в режиме ядра
OUT PHANDLE pHandle Указатель на переменную, куда следует поместить дескриптор открытого объекта (файла, подраздела реестра и т.п.)
IN ACCESS_MASK DesiredAccess Характеристика доступа к объекту. Для файлов вполне приемлемы значения GENERIC_READ или GENERIC_WRITE, которые представляют сложные комбинации из более простых масок доступа (типа FILE_APPEND_DATA и т.п.)
IN POBJECT_ATTRIBUTES pObjAttributes Указатель на заполненную вызывающим кодом структуру данных, которая описывает имя, местоположение и некоторые другие характеристики открываемого объекта (см. ниже)
OUT PIO_STATUS_BLOCK

pIOStatus

Указатель на буфер, в котором будет размещена информация об открытом объекте в формате структуры IO_STATUS_BLOCK
IN PLARGE_INTEGER

AllocationSize OPTIONAL

Начальный размер файла в байтах. Ненулевое значение принимается во внимание только при создании и перезаписи файла
IN ULONG FileAttributes Атрибуты открываемого файла. Типовым является значение FILE_ATTRIBUTE_NORMAL
IN ULONG SharedAccessFlags Описывает, разрешен ли совместный доступ, например, FILE_SHARE_READ &#8212 для чтения
IN ULONG CreateDispositionFlags Способ открытия файла, например, FILE_OPEN_IF &#8212 если не существует, создать
IN ULONG CreateOptions Комбинация флагов создания, например, FILE_SYNCHRONOUS_IO_NONALERT &#8212 все операции над файлом выполняются как синхронные (DesiredAccess должен включать флаг SYNCHRONIZE)
IN PVOID EaBuffer OPTIONAL Для драйверов устройств и драйверов средних слоев следует указывать NULL
IN ULONG EaLength Для драйверов устройств и драйверов средних слоев следует указывать 0
Возвращаемое значение STATUS_SUCCESS или код ошибки (несколько более подробную информацию можно найти в структуре IO_STATUS_BLOCK)
<
Поле pIOStatus-&#62Information ( структуры IO_STATUS_BLOCK) может после вызова иметь одно из следующих значений: FILE_CREATED, FILE_OPENED, FILE_OVERWRITTEN, FILE_SUPERSEDED, FILE_EXISTS или FILE_DOES_NOT_EXIST.

Ниже приводится пример создания (или открытия &#8212 при повторных обращениях) файла C:\Example\testfile.txt и запись в него строки " string : test write!\"

NTSTATUS status; UNICODE_STRING fullFileName; HANDLE fileHandle; IO_STATUS_BLOCK iostatus; OBJECT_ATTRIBUTES oa;

RtlInitUnicodeString( &fullFileName, L"\\??\\C:\\Example\\testfile.txt");

InitializeObjectAttributes( &oa, &fullFileName, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL );

status = ZwCreateFile ( &fileHandle, GENERIC_WRITE | SYNCHRONIZE, &oa, &iostatus, 0, // alloc size = none FILE_ATTRIBUTE_NORMAL, FILE_SHARE_WRITE, FILE_OPEN_IF, FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0); // Здесь: // GENERIC_WRITE равно STANDARD(0x40000000L) // // FILE_GENERIC_WRITE равно STANDARD_RIGHTS_WRITE|FILE_WRITE_DATA | // FILE_WRITE_ATTRIBUTES | FILE_WRITE_EA | FILE_APPEND_DATA | // SYNCHRONIZE, что можно увидеть в заголовочном файле winnt.h

if( NT_SUCCESS(status)) { // Строка для записи в файл char myString[100]="string : test write!\r\n"; // Структура, которая поможет определить длину файла: FILE_STANDARD_INFORMATION fileInfo;

status = // Получаем информацию о файле ZwQueryInformationFile( fileHandle, &iostatus, &fileInfo, sizeof(FILE_STANDARD_INFORMATION), FileStandardInformation ); ULONG len = strlen(myString); if( NT_SUCCESS(status) ) { LARGE_INTEGER ByteOffset = fileInfo.EndOfFile; status = ZwWriteFile(fileHandle, NULL, NULL, NULL, &iostatus, myString, len, // Записываемая строка &ByteOffset, // a если NULL? см. ниже NULL); if( !NT_SUCCESS(status) || iostatus.Information != len ) { DbgPrint("Error on writing. Status = %x.", status); } } ZwClose(fileHandle); break;

}

Следует заметить, что без магической комбинации "\\??\\" в начале имени файла, вызов ZwCreateFile непременно возвратит ошибку.


Этот префикс является обязательным для файлов на диске.

Таблица 7.40. Прототип вызова ZwWriteFile

NTSTATUS ZwWriteFile IRQL == PASSIVE_LEVEL
Параметры Производит модификацию объекта (файла), указанного открытым дескриптором
IN HANDLE FileHandle Дескриптор открытого для модификации файлового объекта
IN HANDLE Event OPTIONAL Драйверы устройств и драйверы средних слоев должны установить этот параметр равным NULL
IN PIO_APC_ROUTINE

pApcRoutine OPTIONAL
Драйверы устройств и драйверы средних слоев должны установить этот параметр равным NULL
IN PVOID ApcContext OPTIONAL Драйверы устройств и драйверы средних слоев должны установить этот параметр равным NULL
OUT PIO_STATUS_BLOCK pIoStatusBlock В поле pIoStatusBlock-&#62Information по завершении вызова находится число реально записанных байт
IN PVOID Buffer Буфер с данными для записи
IN ULONG Length Размер записываемой порции данных
IN PLARGE_INTEGER pByteOffset OPTIONAL Указатель на переменную, где содержится смещение в файле (от начала), по которому следует производить запись данных
IN PULONG Key OPTIONAL Драйверы устройств и драйверы средних слоев должны установить этот параметр равным NULL
Возвращаемое значение STATUS_SUCCESS или код ошибки
При помощи ZwCreateFile можно получать и доступ к драйверам, как это делается из вызова CreateFile пользовательского режима. Таким образом, в частности, предлагается получать доступ к драйверу отладочной печати DebugPrint. Разумеется, вид имени для использования будет несколько другим. Для драйвера DebugPrint это будет имя L"\\Device\\PHDDebugPrint". По данному имени через ZwCreateFile можно получить доступ к драйверу из другого драйвера, несмотря на то, что символьная ссылка этим драйвером не создается. Аналогично, к нашему драйверу Example можно получить доступ из другого драйвера по имени L"\\Device\\Example", но ничего не получится, если пытаться использовать имя L"\\\\.\\Example", как в вызове CreateFile.



Рассмотрим подробнее некоторые функции, использованные в приведенном выше примере.

Макроопределение InitializeObjectAttributes используется для заполнения полей структуры OBJECT_ATTRIBUTES, что делает эту операцию компактнее. Это макроопределение вводится в заголовочном файле ntdef.h пакета DDK. Там же описана и внутренняя организация OBJECT_ATTRIBUTES.

Собственно запись в файл в приведенном выше примере выполняется системной функцией ZwWriteFile.

Способ вызова ZwReadFile во многом повторяет прототип для ZwWriteFile, приведенный в таблице 7.40.

Если параметр pByteOffset указан равным NULL, то в большинстве случаев это воспринимается как нулевое смещение, и запись была бы выполнена (скажем, в приведенном примере) с начала файла. Однако на это допущение лучше не полагаться. Например, если бы мы пытались произвести запись в упомянутое выше устройство PHDDebugPrint, то нас преследовали бы ошибки, пока значение *pByteOffset не было бы задано явно, то есть:

pByteOffset-&#62QuadPart = 0i64; // 0 для LARGE_INTEGER.

Упоминания в документации DDK XР, по поводу того, что при наличии в параметре DesiredAccess (при вызове ZwCreateFile для получения доступа к файлу) флага FILE_APPEND_DATA запись всегда производится в конец файла, мягко говоря, не совсем справедливы. При наличии этого флага (среди параметров вызова ZwCreateFile) значение ByteOffset играет по-прежнему ту же роль в вызове ZwWriteFile, что и 6eз использования этого флага при открытии файла.
В примере, представленном выше, для получения информации о размере файла был использован системный вызов ZwQueryInformationFile. Данный вызов достаточно универсален, и возвращаемая им информация зависит от его последнего параметра, который может принимать следующие значения:

  • FileBasicInformation &#8212 для получения общих параметров открываемого файла, времени создания, времени последнего обращения, аргументов.


  • FileStandardInformation &#8212 для получения размера файла и признака того, что данный объект может быть директорией и т.п.


  • FilePositionInformation &#8212 для получения текущей позиции в файле.


  • FileAlignmentInformation &#8212 для получения информации о способе выравнивания буфера для работы с данным объектом.


  • FileNameInformation &#8212 для получения системной информации об имени файла.


  • Разумеется, для каждого типа вызова должна быть предоставлена своя структура, для чего предусмотрен предпоследний параметр, где при вызове указывается ее размер. То есть больше указанного таким образом размера вызов ZwQueryInformationFile

    использовать не может. Недостаток места для записи всей информации может быть причиной неудачного завершения.


    Содержание раздела