• Уменьшение отступа

    Обратная связь

    (info@ru-sfera.pw)

Практика по материалу 1-4 главы. Взаимодействие Программы Управления с Драйвером из Delphi. Включая Ввод-Вывод.


Maks Maksov

Пользователь
Форумчанин
Регистрация
10.09.2020
Сообщения
15
Репутация
5
Всем Бобра! В данной теме я приведу свой пример программы управления который закрепит знания полученные в ходе изучения первых глав книги, переводом которой занимается Virt и X-Shar. Пример будет полезен для новичков которым тяжело с чего-то начать свой грандиозный проект. Таким образом появится материал (работающий демо проект) от которого можно будет оттолкнуться и двигаться вперед. На мой взгляд некоторые моменты не были освещены в книге но найдут отражение в приведенном коде.
Драйвер собран в среде:
Microsoft Visual Studio Professional 2019
Версия 16.7.2
и вот его код:
код драйвера:
#include <ntddk.h>

//два макроса которые будут заменять в коде все места где найдут const_DEVName и
//и const_slDEVName на строку нужного формата в Юникоде стринге с указанием длинны.
//\\Device\\CDRV это путь в дереве объектов для нашего объекта Устройства он там появится
//на этапе инициализации процедурой DriverEntry. второй путь это символьная ссылка на наше
//Устройство. дело в том что ветвь \\Device\\ из ЮзерМоде недоступна, а Устройство хранится там и доступно
//только для кернел моде. а ветка \\??\\ видна для ЮзерМоде. Вот почему мы создаем ссылочку.
//в разделе \\??\\ создаем имя которое ссылается на устройство из \\Device\\. Система получив запрос
//на получение хендла объекта Устройства с использование ссылки отождествит его с объектом Устройства
//из каталога \\Device\\. все эти объекты Устройств и ссылки на них для ЮзерМоде можно посмотреть
//в программе WinObj
UNICODE_STRING const_DEVName = RTL_CONSTANT_STRING(L"\\Device\\CDRV");//путь к объекту устройства
UNICODE_STRING const_slDEVName = RTL_CONSTANT_STRING(L"\\??\\CDRV");//путь к ссылке на объект устройства
#define IOCTL_C_DEV_IO CTL_CODE(0x8000, 0x800, METHOD_NEITHER, FILE_ANY_ACCESS)

//объявляем прототипы функций
//функция вызывается автоматически при остановке драйвера
void CDRVUnload(_In_ PDRIVER_OBJECT p_DRVObj);
//функция инициализации ввода вывода
NTSTATUS DEVioPrepare(
    _In_ PDEVICE_OBJECT p_DevObj,//указатель на объект устройства
    _In_ PIRP p_IRP);//указатель на пакет I/O(IRP)
//основная функция ввода-вывода
NTSTATUS DEVio(
    _In_ PDEVICE_OBJECT p_DevObj,//указатель на объект устройства
    _In_ PIRP p_IRP);//указатель на пакет I/O(IRP)


extern "C"
NTSTATUS
DriverEntry(_In_ PDRIVER_OBJECT p_DRVObj, _In_ PUNICODE_STRING p_RegPath) {
    KdPrint(("выполняется DriverEntry"));

    UNREFERENCED_PARAMETER(p_RegPath);
    //регистрируем функцию остановки драйвера в объекте драйвера
    p_DRVObj->DriverUnload = CDRVUnload;
    //создаем в объекте драйвера (его структуре) массив функций диспетчерезации (dispatch routines),
    //предназначенные для обработки разных типов пакетов запроса ввода-вывода (IRP). Каждый элемент этого массива
    //соответствует своему типу IRP. Если, например, драйверу необходимо обрабатывать запрос типа IRP_MJ_SHUTDOWN,
    //уведомляющий о завершении работы системы, то он должен поместить в соответствующую позицию массива MajorFunction
    //указатель на функцию, которой запросы этого типа и будут направляться. Если такая функциональность драйверу не нужна,
    //как в нашем случае, то и заполнять этот элемент массива MajorFunction не требуется.
    //Все процедуры диспетчеризации имеют одинаковый прототип.
    p_DRVObj->MajorFunction[IRP_MJ_CREATE] = DEVioPrepare;//формируется диспетчером ввода-вывода при вызове кодом режима
                                                           //пользователя функции CreateFile;
    p_DRVObj->MajorFunction[IRP_MJ_CLOSE] = DEVioPrepare;//формируется диспетчером ввода-вывода при вызове кодом режима
                                                          //пользователя функции CloseHandle.(это не я придумал это копипаст
                                                        //и меня это удивило что закрытие хендла коих мы делаем много
                                                        //раз в клиенте приводит к IRP. однако наверное это когда
                                                        //закрываем хендл Устройства тогда логично)
    //основная процедура передачи информации клиент-драйвер.
    p_DRVObj->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DEVio;//формируется диспетчером ввода-вывода при вызове кодом режима
                                                           //пользователя заклинания DeviceIoControl

    //создаём объекта устройства
    PDEVICE_OBJECT p_DEVObj;
    NTSTATUS statusCreateDEV = IoCreateDevice(
        p_DRVObj, //указатель на объект Драйвера
        0,// no need for extra bytes,
        &const_DEVName, //указатель на имя устройства
        FILE_DEVICE_UNKNOWN, //тип Устройства
        0, // characteristics flags,
        FALSE, //монопольный доступ
        &p_DEVObj //OUT возвращаемый указатель на созданный объект Устройства
                  //этот аргумент описан в прототипе как PDEVICE_OBJECT  *DeviceObject
                  //*- означает взять значение которое хранится по адресу
        );
    //с ошибкой на выход
    if (!NT_SUCCESS(statusCreateDEV)) {
        KdPrint(("Failed to create device object (0x%08X)\n", statusCreateDEV));
        return statusCreateDEV;
    };
    //создаём ссылку на наш Объект Устройства в доступной для ЮзерМоде ветке //??
    //по этой ссылке управляющая программа из ЮзерМоде сможет получать хендл на устройство.
    //напрямую хендл на объекты в ветке //Device получать из ЮзерМоде нельзя.
    NTSTATUS statusCreateLink = IoCreateSymbolicLink(&const_slDEVName, &const_DEVName);
    //с ошибкой на выход
    if (!NT_SUCCESS(statusCreateLink)) {
        KdPrint(("Failed to create symbolic link (0x%08X)\n", statusCreateLink));
        IoDeleteDevice(p_DEVObj);
        return statusCreateLink;
    }

    //функция DriverEntry возвращает успех. в противном случае система ее заблокирует.
    //либо не даст запуститься, останется остановленной. либо вапще отменит регистрацию сервиса в системе.
    //точно не знаю.
    return STATUS_SUCCESS;

}

void CDRVUnload(_In_ PDRIVER_OBJECT p_DRVObj) {
    KdPrint(("выполняется UnLoad"));
    //в DriverEntry мы создали объект Устройства а также Ссылку на него. следует удалить их при остановке Драйвера.
    //удаляем ссылку
    IoDeleteSymbolicLink(&const_slDEVName);
    //удаляем объект устройства
    IoDeleteDevice(p_DRVObj->DeviceObject);
}

_Use_decl_annotations_
NTSTATUS
DEVioPrepare(_In_ PDEVICE_OBJECT p_DEVObj, _In_ PIRP p_IRP) {
    KdPrint(("Инициализация ввода/вывода\n"));
    UNREFERENCED_PARAMETER(p_DEVObj);
    //по окончании обработки IRP он вернется отправителю. который сможет сделать вывод об успешной обработке
    //посланного пакета данных. мы заполним структуру которую сможет прочесть клиент(отправитель)
    p_IRP->IoStatus.Status = STATUS_SUCCESS;
    p_IRP->IoStatus.Information = 0;
    //это заклинание завершает обработку IRP и структура заполненная выше улетит отправителю.
    IoCompleteRequest(p_IRP, IO_NO_INCREMENT);
    return STATUS_SUCCESS;
}

NTSTATUS
DEVio(_In_ PDEVICE_OBJECT p_DEVObj, _In_ PIRP p_IRP) {
    KdPrint(("Получен IRP пакет I/O.\n"));
    //подсмотрим точное значение "Кода Управления" и будем его использовать в дельфи у клиента
    //потомуто я несмог понять как его в дельфи собрать такойже как тут.
    KdPrint(("для справки ожидаемый CTRL_CODE=\n"));
    KdPrint(("%.8x", IOCTL_C_DEV_IO));


    UNREFERENCED_PARAMETER(p_DEVObj);
    //умные люди пишут:
    //при получениее IRP необходимо убедиться в доступе к области памяти с буферами.
    //на микрософте для этого рекомендуют применять специальные подпрограммы (они там даны)
    //проводить чтение и запись буферов внутри обработчиков исключений.

    // get our IO_STACK_LOCATION
        auto stack = IoGetCurrentIrpStackLocation(p_IRP); // IO_STACK_LOCATION*
        auto status = STATUS_SUCCESS;


        switch (stack->Parameters.DeviceIoControl.IoControlCode) {
        case IOCTL_C_DEV_IO: //создали этот код в начале модуля макросом
            KdPrint(("Свершилось! Получен IRP пакет данных с кодом $80002003!УРА!\n"));
            //опишем структуру буфера которую ожидаем получить в пакете с кодом 5
            struct TCData     //прототип структуры
                {
                int A;      //широта
                int B;      //долгота
                };
        
            //посмотрим сколько байт мы получили. это уже элемент отладки, потомучто у меня сразу не заработало
            //кол-во байт какое-то неверное пришло, не то что я ожидал. поэтому разбираемся сколько байт прилетело
            KdPrint(("%02d", stack->Parameters.DeviceIoControl.InputBufferLength));
            //и навсякий случай покажем сколько байт ожидаем.
            KdPrint(("%02d", sizeof(TCData)));
            //сравним размер полученного буфера с тем что мы ожидаем получить в пакете с кодом $80002003
            if (stack->Parameters.DeviceIoControl.InputBufferLength == sizeof(TCData))
                {
                KdPrint(("кол-во байт верное(как ожидалось)\n"));
                //если размер буфера такойже как ожидаемый то
                //прочтем буфер. сопоставим его нашей структуре. и таким образом мы будем иметь
                //не просто какието байты информации непонятные, а упорядоченные в структуру переменные.

                auto CData = (TCData*)stack->Parameters.DeviceIoControl.Type3InputBuffer;
                //Если указатель на структуру NULL, то мы должны выйти со статусом ошибки :
                if (CData == nullptr) {
                    status = STATUS_INVALID_PARAMETER;
                    break;
                    }

                KdPrint(("напарсили. вот что получилось..\n"));
                KdPrint(("%02d", CData->A));
                KdPrint(("%02d", CData->B));
                }
                                
            break;
        default:
            status = STATUS_INVALID_DEVICE_REQUEST;
            break;
        }

    //завершаем обработку IRP и отправляем клиенту отчет
    p_IRP->IoStatus.Status = STATUS_SUCCESS;//жестко для теста
    p_IRP->IoStatus.Information = 0;
    IoCompleteRequest(p_IRP, IO_NO_INCREMENT);
    return STATUS_SUCCESS;
}
все выполнено одним модулем и компилируется на моей машине без проблем. однако как создать пустой проект драйвера, чтобы потом вставить туда этот код, читайте материал главы 2.
в собранном виде он прикреплен в архиве к посту.
Как видно из кода, драйвер способен отправлять отладочные сообщения при старте и остановке. Эти сообщения возможно перехватить программой DBGView. Об настройке и использовании которой хорошо написано в главе 2 данной ветки форума. надо отметить DbgView это для моего примера единственное средство отображения информации о том что происходит с драйвером. поэтому не имеет смысла тестировать демо не разобравшись с запуском DbgView (по материалу главы 2). хотя работоспособность код не потеряет- однако для нас драйвер станет черным ящиком который что-то там сам в себе делает не имея видимого результата.

При создании клиента использовалась windows10 с установленной RadStudio 10.4
Итак создаем проект в дельфи с пустой формой стандартно. Размещаем на ней элементы:
1)четыре кнопки
2)таймер
3)Мемо лист(проверить свойство автоскрол=true)
никаких других свойств в инспекторе объектов менять не следует (кроме текста на кнопках и автоскрола в мемо, хотя это не повлияет на работоспособность).
все приведем к виду как показано на рисунке(можете что-то улучшить):
вид клиента Дельфи.jpg

нажмите на картинку чтобы сохранить зрение в порядке.

Сохраните проект там где вам будет удобно.
откомпилируйте, запустите этот пустой проект чтобы создались папки с откомпилированным файлом проекта (.exe).
поместите файл подопытного драйвера с именем СDRV.sys (приложен в архиве) в папку в которой создался откомпилированный проект. таким образом .exe и СDRV.sys файл окажутся в одной папке.
и целиком замените весь код модуля (единственного) на приведенный ниже:
код клиента:
unit Unit1;
//во многих коментариях встречается термин Сервис означающий для нас Драйвер
//а также Драйвер означающий Сервис. на самом деле эту путаницу ввели в Микрософте
//так как по их мнению Драйвер это Сервис. и его решили не отделять в какуюто
//отдельную группу. таким образом понимать под Сервисом можно как сервис для режима
//пользователя так и Драйвер режима ядра, также под Драйвером можно понимать Сервис.
//это одно и тоже, Драйвер скорее это разновидность сервиса (подгруппа чтоли).
//чтоб все стало ясно окончательно Служба- это тоже сервис и драйвер.-)
//запустив "командную строку" с правами администратора вы можете использовать
//следующие команды для анализа состояния нашего драйвера:
//sc create СDRV type= kernel binPath= c:\prog\СDRV.sys
//sc start СDRV
//sc stop СDRV
//sc delete СDRV
//sc query СDRV
//также загляните Редактор Реестра Виндоус и отыщите поиском запись об драйвере
//по его имени. она должна там появиться сразу после регистрации сервиса. а после
//удаления исчезнуть.
//DbgView софт- это то с чего вы должны начать. как его настроить подробно написано
//в главе 2. Дело в том, что Драйвер в процессе работы будет отчитываться о
//происходящих с ним событиях именно в логах которые перехватит эта программа.
//без него мы будем слепы и драйвер станет "черным ящиком" который что-то там делает
//сам в себе. Другими словами логи в DbgView это интерфейс для драйвера.
//WinObj.exe софт также относится к маст-хэв. там мы увидим как при старте драйвера
//создались две записи. одна в разделе Device с именем нашего Устройства (которое
//обслуживает драйвер) а также ссылка на него в разде ?Global
interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls,
  System.UITypes,//для диалоговых окон
  WinSvc;//для использования менеджера сервисов

type
  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    Button3: TButton;
    Memo1: TMemo;//используем мемо для вывода всякой полезной информации
                 //например значения переменных интерисующих нас для отладки
    Timer1: TTimer;
    Button4: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
    procedure Timer1Timer(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure Button4Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }

  end;

//опишем структуру для своих нужд. это порция информации которой будет обмениваться
//программа управления(эта) с драйвером (через устройство).Экземляр такой структуры
//будет отсылаться в пакете IRP от клиента к драйверу.
type
TCData = record
a:integer;
b:integer;
end;

var
  Form1: TForm1;
  function DRVInstall(): boolean;//регистрация и инициализация запуска драйва
  function DRVDelete(): boolean;//остановка и удаление драйва
  function DRVQuery(): boolean;//запрос состояния драйва
  function IOinit(): boolean;//инициализация ввода-вывода и сразу посылка IRP

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
begin
//регистрируем и запускаем сервис.
DrvInstall();
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
//сначала остановим потом удалим драйвер.
DRVDelete();
end;

procedure TForm1.Button3Click(Sender: TObject);
begin
//для демонстрации можно запросить статус сразу после регистрации сервиса.
//а потом дождавшись заданного интервала для запуска (5сек) повторить запрос
//и убедиться что статус изменится после старта драйва.-)
DRVQuery();//запрос состояния
end;


procedure TForm1.Button4Click(Sender: TObject);
begin
//инициализация Ввода-вывода и там же отсылка пробного IRP
IOinit();
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
//если таймер будет включен по умолчанию, то произойдет попытка запуска сервиса.
//конечно удобнее отключить его в настройках через панель ObjectInspector заранее
//но так как это демо-пример то необходимо свести к минимуму подготовительную работу,
//невыполнив которую код может работать криво. цель демо: copy-past и все работает.
timer1.Enabled:=False;
timer1.Interval:=5000;
end;

//будет установлен драйвер с именем CDRV.SYS
//файл которого должен лежать в каталоге программы управления(этой) там где .EXE
function DrvInstall(): boolean;
var
//может возникнуть вопрос. почему бы хендлы не обьявить для всего модуля, а специально
//объявлять их для регистрации сервиса а также (в будущем) для закрытия сервиса.
//ответ в том что сервис не закроетя до тех пор пока хотябы один хендл на него
//открыт. Объявив хендлы глобально- нам нужно очень внимательно следить за тем где
//хендл открывается и как он закрывается в противном случае удаление сервиса
//потерпит крах и возможны прочие непонятности поведения. их объявление локально
//в функции упрощает контроль за созданием и удалением хендлов.
  h_SCMtoCreate: SC_Handle;
  h_DRVtoCreate: SC_Handle;
  FuncRetVal: Boolean; //вспомогательная для возвращения значения
begin
form1.Memo1.Lines.Add('начинаем пытаться зарегать и запустить драйв..');
FuncRetVal := False;
h_SCMtoCreate := OpenSCManager(nil, nil, SC_MANAGER_CREATE_SERVICE);
if (h_SCMtoCreate <> 0) then
  begin
  form1.Memo1.Lines.Add('подключились к h_SCMtoCreate:' + inttostr(h_SCMtoCreate));
  //эта функция CreateService завершится успешно даже если указанного файла драйвера
  //несуществует!ого! Наверняка и при прочих ошибках использования этой функции
  //она вернет успех. Однако Ошибки возникнут в будущем при попытке запуска Сервиса
  //поэтому сразу после нее следует запустить наш сервис и убедиться что он запускается.
  //в противном случае обработать ошибки. Возвращаемое значение =0 в случае ошибки
  //при успехе возвращает Хендл объекта сервиса.
  form1.Memo1.Lines.Add(PChar(GetCurrentDir+'\CDRV.sys'));
  h_DRVtoCreate:= CreateService(h_SCMtoCreate, //хендл менеджера сервисов
                        PChar('CDRV'), //внутреннее имя нашего сервиса-драйвера в
                                        //терминах Виндос: драйвер-это сервис тоже
                        PChar('MyСDriver'), //экранное имя нашего Драйвера-сервиса
                                            //неуверен где его можно встретить.
                        SERVICE_ALL_ACCESS, //полные права на доступ к нашему Драйверу
                        SERVICE_KERNEL_DRIVER, //тип нашей службы: кернел-драйвер
                        SERVICE_DEMAND_START, //Служба, запускается диспетчером управления
                                              //службами, когда процесс вызывает функцию
                                              //StartService.
                        SERVICE_ERROR_NORMAL, //Программа запуска регистрирует ошибку и
                                              //показывает всплывающее окно сообщения, но
                                              //продолжает операцию запуска.
                        PChar(GetCurrentDir+'\CDRV.sys'), //путь к файлу драйвера и его имя
                        //я встречал много вариантов исполнения этой строки на форумах
                        //зачемто 0 какойто добавляют в конец вручную и т.д.
                        //помучившись пол дня пришел к этому варианту который работает.
                        //но боюсь не будет ли какойто утечки без нуля на конце.
                        //Мне кажется формат "строки" в дельфи уже содержит в себе
                        //завершающий ноль на конце. хотя неуверен. эксперементируйте.
                        nil, //lpLoadOrderGroup
                             //[in] Указатель на строку с завершающим нулем,  именующую группу
                             //очередности загрузки,  членом которой является эта служба.
                             //Задайте значение ПУСТО (NULL) или пустую строку, если служба
                             //не принадлежит группе.
                             //Программа запуска использует очередность загрузки групп,
                             //чтобы загрузить группы служб в указанном порядке по отношению
                             //к другим группам. Список очередности загрузки групп содержатся
                             //в следующем значении реестра:
                             //HKLM\System\CurrentControlSet\Control\ServiceGroupOrder
                        nil, //lpdwTagId
                             //[out] Указатель на переменную, которая получает значение
                             //признака, являющееся уникальным в группе, заданной в параметре
                             //lpLoadOrderGroup. Задайте значение ПУСТО (NULL),
                             //если Вы не изменяете существующий признак.
                             //Вы можете использовать признак для того, чтобы упорядочить
                             //запуск службы в пределах  очередности загрузки группы,
                             //определяя  вектор очередности признака в следующем значении
                             //реестра:HKLM\System\CurrentControlSet\Control\GroupOrderList
                             //Признаки вычисляются только для служб драйвера,
                             //которые имеют типы пуска SERVICE_BOOT_START или SERVICE_SYSTEM_START.
                        nil, //lpDependencies
                             //[in] Указатель на массив имен служб, разделенных нулем,
                             //с двойным  символом нуля в конце или очередности загрузки
                             //групп, которую система должна запустить перед этой службой.
                             //Задайте значение ПУСТО (NULL) или пустую строку, если служба
                             //не имеет никаких зависимостей. Зависимость от группы означает,
                             //что эта служба может запуститься тогда, если по крайней мере
                             //один член  группы запущен после попытки запустить все члены
                             //группы. Вы должны ставить в начале имен группы
                             //SC_GROUP_IDENTIFIER так, чтобы они могли отличаться
                             //от имени службы, потому что службы и группы служб совместно
                             //используют то же самое пространство имен.
                        nil, //lpServiceStartName
                             //[in] Указатель на строку с завершающим нулем, которая задает
                             //имя учетной записи, под которой должна запуститься служба.
                             //Если тип службы - SERVICE_WIN32_OWN_PROCESS, используйте имя
                             //учетной записи в форме DomainName\UserName. Процесс службы
                             //начнет работу как этот пользователь. Если учетная запись
                             //принадлежит встроенному домену, Вы можете задать .\UserName.
                             //Если этот параметр - значение ПУСТО (NULL), функция
                             //CreateService использует учетную запись LocalSystem.
                             //Если тип службы задан - SERVICE_INTERACTIVE_PROCESS,
                             //служба должна запуститься в учетной записи LocalSystem.
                             //Если этот параметр - NT AUTHORITY\LocalService, функция
                             //CreateService использует учетную запись LocalService.
                             //Если параметр - NT AUTHORITY\NetworkService,
                             //CreateService использует учетную запись NetworkService.
                             //Windows NT:  Если тип службы - SERVICE_WIN32_SHARE_PROCESS,
                             //Вы должны задать учетную запись LocalSystem.
                             //На более поздних версиях Windows, совместно используемый
                             //процесс может запуститься как любой пользователь.
                             //Если тип службы - SERVICE_KERNEL_DRIVER
                             //или SERVICE_FILE_SYSTEM_DRIVER, ее имя - имя объекта драйвера,
                             //который система использует, чтобы загрузить драйвер устройства.
                             //Задайте значение ПУСТО (NULL), если драйвер должен
                             //использовать имя объекта по умолчанию, созданное системой
                             //ввода / вывода (I/O).
                        nil);//lpPassword
                             //[in] Указатель на строку с завершающим нулем, которая имеет
                             //в своем составе пароль к имени учетной записи, заданному
                             //параметром lpServiceStartName. Задайте пустую строку,
                             //если учетная запись не имеет пароля или, если служба
                             //запускается в учетной записи LocalService, NetworkService
                             //или LocalSystem.
                             //Для служб драйвера пароли игнорируются
  //если драйвер зареган то освобождаем хенлд. если ошибка то выводим текст
  if h_DRVtoCreate<>0 then
              begin
              form1.Memo1.Lines.Add('зарегали h_DRVtoCreate:' + inttostr(h_DRVtoCreate));
              CloseServiceHandle(h_DRVtoCreate);//закрываем хендл драйвера
              FuncRetVal:=True;
              end
              else
              begin
              //почемуто захотелось не в лог Мемо1 вывести а такое окошко создать.
              //возможно кому-то пригодится просто знать что можно и так.
              MessageDlg(SysErrorMessage(GetLastError),mtError,[mbOk],0);
              end;
  CloseServiceHandle(h_SCMtoCreate);//закрываем интерфейс Менеджера Сервисов
                                    //ладно проще удаляем ненужный более хендл.
  end
  else
  begin
  //это если мы к менеджеру сервисов не подключились то вот вам и ошибка
  //вот вам и почему...
  FuncRetVal:=False;
  MessageDlg(SysErrorMessage(GetLastError),mtError,[mbOk],0);
  //тут бы бряку(break) поставить чтоб пулей на выход и таймер зря не гонять в конце.
  //но мы верим в бога и ошибок никаких у нас быть неможет!
  end;
DrvInstall:= FuncRetVal;
    //теперь сразу запускаем наш драйвер. Это необходимо чтобы быть уверенным
    //что он коректно работает. т.к. регистрация драйвера в системе- не дает никаких
    //гарантий что все зарегестрировано как надо. она завершается успешно даже без
    //файла драйвера а также при наличии прочих ошибок в подстановке аргументов.
    //Хороший способ проверить коректность заполнения текстовых полей с именами
    //и путями- это зайти в RegEdit после регистрации сервиса и поиском отыскать
    //свой сервис по имени сервиса. затем следует проверить как отображаются
    //поля с именем файла и именем сервиса. сравнить с соседними чужими
    //сервисами- посматреть как у них а как у нас.
    //так как регистрация сервиса может занять время, запускать
    //службу следует спустя некоторое время. таким образом если разместим код
    //запуска тут- то получим неудачу так как драйвер еще не закончил свою регистрацию.
    //видится возможность реализоть это 3мя путями.(1)разместить тут паузу к примеру
    //на 5 секунд(достаточную для регистрации сервиса)и следом запустить сервис. но
    //это подвесит основной поток выполнения и программа будет мертвой 5 секунд
    //что не очень хорошо смотрится.(2)прям тут запустить таймер на 5 секунд или
    //на 0,5 секунд и запускать сервис из его процедуры (мноократно до успеха, но без
    //фанатизма). (3)ну и есть возможность организовать 2-й поток- в который мы
    //можем перенести и функцию регистрации и запуска сервиса и там использовать
    //паузу 5 секунд не переживая что это подвесит нашу программу.
    //для меня подойдет 2-й вариант с таймером.
    //вопщем сделать вывод об успешной регистрации сервиса мы сможем только после
    //его запуска.
form1.timer1.Enabled:=True;
form1.Memo1.Lines.Add('включили таймер для запуска драйва..');
end;

procedure TForm1.Timer1Timer(Sender: TObject);
var
  h_SCMtoStart,h_DRVtoStart: SC_Handle; //хендлы Менеджера и Драйвера
  Temp: PChar;//тайное значение
begin
//этот таймер используется чтобы произвести запуск сервиса через интервал времени
//после его регистрации. мы сделаем одну попытку запуска через 5 секунд после
//регистрации сервиса. на это указывает свойство таймера Interval=5000.
//после этого остановим таймер. Однако возможно реализовать многократные попытки
//запуска сервиса до получения положительного результата, и лишь потом остановить таймер.
h_SCMtoStart:= OpenSCManager(nil, nil, SC_MANAGER_ALL_ACCESS);//подключаемся
                   //к менеджеру сервисов
if h_SCMtoStart > 0 then //если подключились к менеджеру (существует его хендл)
  begin
  //просим систему дать нам доступ к нашему драйверу вызовом WinApi OpenService
  h_DRVtoStart:= OpenService(h_SCMtoStart,//используем хендл открытого менеджера
                             PChar('CDRV'),//имя сервиса
                             SERVICE_ALL_ACCESS);//полный доступ
  if h_DRVtoStart > 0 then //если получили хендл на наш Драйвер это уже неплохо
    begin
    //и вот когда мы знаем его имя (хендл) мы имеем власть над ним.
    //и произносим следующее заклинание которое оживляет наше творение.
    Temp:=nil;//тайная сила сего действа в подстановке входного параметра через
    //промежуточную переменную- ускользнула от моего разума. но нужно именно так.
    if StartService(h_DRVtoStart,0,temp)=true then //если успешно попросили систему
                                                   //запустить наш драйвер
    //надо понимать что запуск драйвера занимает время. поэтому мы тут какбы
    //не запускаем сервис а просим систему его запустить. Для того чтобы
    //понять смогла система его запустить или нет мы можем послать запрос на
    //вывод статуса нашего сервиса. именно это мы попробуем реализовать на 3-й
    //кнопке на нашей форме.
    //однако позже я встретил определение что вышеизложенное заклинание(winapi)
    //вернет управление коду после себя только после старта сервиса (или ошибки)
    //таким образом возможно сразу начинать работать с ним и ничего не ждать.
      begin
      //отчитаемся в лог что к менеджеру мы подключились, хендл драйвера получили
      //а также попросили систему запустить наш драйвер.
      form1.Memo1.Lines.Add('подключились для старта h_SCMtoStart:'+inttostr(h_SCMtoStart));
      form1.Memo1.Lines.Add('нашли драйвер CDRV в системе h_DRVtoStart'+inttostr(h_DRVtoStart));
      form1.Memo1.Lines.Add('просим систему запустить CDRV');
      end;
    //убрать за собой мусор очень важно не только в программировании но и в жизни.
    CloseServiceHandle(h_DRVtoStart);//убираем за собой
    end;
  CloseServiceHandle(h_SCMtoStart);//убираем за собой
  end;
form1.Timer1.Enabled:=false;//выключаем таймер
end;

function DRVDelete(): boolean;
var
  h_DRVtoDel, h_SCMtoDel: SC_HANDLE;//хендлы Драйва и Менеджера сервисов
  DRV_status: TServiceStatus;//структура описывает состояние сервиса
                             //примерно то что мы видим в CMD если запустим
                             //команду "SC query СDrv"
  FunctRetVal: boolean;//функция чтото возвратит, на то она и функция
begin
  FunctRetVal := false;
  //подключаемся к менеджеру сервисов
  h_SCMtoDel := OpenSCManager(nil, nil, SC_MANAGER_ALL_ACCESS);
  if (h_SCMtoDel <> 0) then//если подключились
    begin
    form1.Memo1.Lines.Add('начинаем пытаться удалить сервис..');
    //получаем хендл на сервис по его имени
    h_DRVtoDel:= OpenService(
                    h_SCMtoDel,
                    PChar('CDRV'),
                    SERVICE_ALL_ACCESS
                    );
    if h_DRVtoDel<>0 then//хендл на сервис получен
        begin
        //вот собственно и удаление сервиса. Но оно не сработает!-)
        //система пометит сервис на удаление и запланирует это действие.
        //однако фактическое удаление сервиса произойдет только если
        //сервис остановлен а также все хендлы на него закрыты.
        //получаеся ситуация что команда на удаление использует Хендл на сервис,
        //но удалить не получится если этот Хендл существует!-)
        //в процессе отладки программы я столкнулся с ситуацией когда сервис
        //удалялся только с закрытием клиентской программы(этой). Это связано
        //с тем что при регистрации сервиса я забыл освободить хендл на
        //зарегистрированный сервис и он гдето телепался открытым мешая удалить
        //сервис из системы. Однако закрытие программы ЮзерМоде освобождает все
        //ресурсы которая программа насоздавала и тогда запланированное действие
        //удаления сервиса выполняется.
        //итак: 1)пометим сервис на удаление.
        //2)пошлем ему команду на остановку (неважно запущен он или нет)
        //3)освободим Хендлы связанные с ним.
        //после этого сервис должен удалиться. Мы верим в бога.
        DeleteService(h_DRVtoDel);
        ControlService(h_DRVtoDel, //хендл драйвера на удаление
                       SERVICE_CONTROL_STOP,//команда на удаление
                       DRV_status//структура с состоянием сервиса
                       );
        //возможно и скорее всего одна или обе предыдущие функции
        //вернут код ошибки. ведь служба может быть и не запущена(остановлена).
        //но мне кажется это неважно. мы останавливаем ее для гарантии
        //без анализа состояния.
        //2 шага пройдено, отчитаемся в лог об этом
        form1.Memo1.Lines.Add('h_SCMtoDel:'+inttostr(h_SCMtoDel)+' подключились');
        form1.Memo1.Lines.Add('пометили сервис h_DRVtoDel:'+inttostr(h_DRVtoDel)+' на удаление');
        form1.Memo1.Lines.Add('послали команду на остановку сервиса');

        //3-й этап освобождаем ресурсы связанные с сервисом.
        CloseServiceHandle(h_SCMtoDel);//хендлы закрываем
        CloseServiceHandle(h_DRVtoDel);
        form1.Memo1.Lines.Add('освободили Хендлы');
        //если все хорошо сервис скоро закроется. откиньтесь на спинку стула.
        FunctRetVal:=True;//ну.. надеемся что True. нужно обработать больше исключений
                          //чтобы так утверждать. Однако в рамках демо-примера
                          //и так сойдет.
        end
        else
        begin
        //неполучилось получить хендл драйвера для его закрытия
        form1.Memo1.Lines.Add('неполучилось получить хендл драйвера для его закрытия');
        end;
    end
    else
    begin
    form1.Memo1.Lines.Add('ошибка подключения к менеджеру служб');
    end;
  DRVDelete:= FunctRetVal;
end;

function DRVQuery(): boolean;
var
h_SCMtoQuery, h_DRVtoQuery: SC_HANDLE;
svc_status: TServiceStatus;//структура описывает статус сервиса
begin
form1.Memo1.Lines.Add('начинаем пытаться получить статус сервиса..');
h_SCMtoQuery := OpenSCManager(nil, nil, SC_MANAGER_ALL_ACCESS);
if (h_SCMtoQuery <> 0) then
    begin
    form1.Memo1.Lines.Add('подключились к менеджеру h_SCMtoQuery:'+inttostr(h_SCMtoQuery));
    //получаем хендл на сервис по его имени
    h_DRVtoQuery:= OpenService(
                    h_SCMtoQuery,
                    PChar('CDRV'),
                    SERVICE_ALL_ACCESS
                    );
    if h_DRVtoQuery<>0 then
        begin
        form1.Memo1.Lines.Add('получили хендл драйва по его имени h_DRVtoQuery:'+inttostr(h_DRVtoQuery));
        if (QueryServiceStatus(h_DRVtoQuery,svc_status)) then
           begin
           form1.Memo1.Lines.Add('успешно отправили запрос на статус MyDRV');
           form1.Memo1.Lines.Add('тип сервиса:'+ inttostr(svc_status.dwServiceType));
           form1.Memo1.Lines.Add('состояние:'+inttostr(svc_status.dwCurrentState));
           end
           else
           begin
           MessageDlg(SysErrorMessage(GetLastError),mtError,[mbOk],0);
           end;
        CloseServiceHandle(h_DRVtoQuery);//убираем за собой
        end;
    CloseServiceHandle(h_SCMtoQuery);//убираем за собой
    end;
DRVQuery:=true;
end;

//на воротах написано: "оставь надежду всяк сюда входящий"
//то что ниже написано мне как новичку далось очень сложно.. поэтому тут возможны
//какието неправильности. лучше неидеальности. однако я добился работоспособности этих
//заклятий! тут безусловно есть над чем еще работать!
function IOinit(): boolean;
var
DEVHandle: THandle;//хендл устройства драйвера
Status: bool;//вспомогательная
returned: DWORD;//вспомогательная
CData:TCData;//в начале юнита мы описали сами эту структуру, теперь создаем екземляр
//CTRL:LongWord;//будем использовать чтобы собрать Код Управления для IRP
              //(как потом выяснилось безуспешно. поэтому закоментим)
begin
//нужно вызвать winapi CreateFile это будет инициализация. подготовительный этап.
//мы тем самы скажем драйверу что нам есть о чём поговорить с ним тет-а-тет.
DEVHandle:=0;//возможно лишнее.
form1.Memo1.Lines.Add('пробуем подключиться к устройству..');
//драйверу будет направлен IRP типа IRP_MJ_CREATE
DEVHandle:= CreateFile(
    pchar('\\.\CDRV'),//указатель на строку, с именем ссылки указывающей на устройство.
                      //ОПА! вот я пол дня подбирал в каком виде скормить зверю
                      //этот аргумент! ну я не понимаю почему в Ц++ этот путь подругому?
                      //в ц++ надо использовать например вот:
        //[I]HANDLE hDevice = CreateFile(L"\\\\.\\PriorityBooster", GENERIC_WRITE,...
        //или вот:
        //UNICODE_STRING const_slDEVName = RTL_CONSTANT_STRING(L"\\??\\CDRV");
        //хотя на самом деле (мы то знаем) путь этот.. вы удивитесь если посмотрите в WinObj.exe
        // \GLOBAL??\CDRV
        //вопщем заклятие это специфично для среды разработки видимо,
        //что весьма страннонуто..
    GENERIC_READ + GENERIC_WRITE,//права доступа
    FILE_SHARE_WRITE,//определяет, может ли устройство быть совместо используемым с другим кодом.
      //Т.е. можно ли еще раз получить описатель устройства вызовом функции CreateFile.
    nil,//указатель на структуру SECURITY_ATTRIBUTES
    OPEN_EXISTING,//определяет действия в случае если устройство с заданным именем
                  //не найдено или уже существует. Для устройств используется только
                  //значение OPEN_EXISTING. Т.е. на момент вызова CreateFile устройство
                  //должно уже существовать.
    0,//определяет атрибуты и специальные флаги.У нас будет равен 0.
    0//описатель файла-прототипа.
    );
//посмотрим что получилось.. отчитаемся в лог..
form1.Memo1.Lines.Add(SysErrorMessage(GetLastError));
if (DEVHandle = INVALID_HANDLE_VALUE) then
            form1.Memo1.Lines.Add('кривой хендл')
              else
            form1.Memo1.Lines.Add('получили хендл устройства:'+inttostr(DEVHandle));
//если не смотря не на что, хендл всё-таки получен!, да к тому же он не ИНВАЛИД-)
//то давайте что-нибудь отправим драйверу. да побыстрее пока всё не сломалось!
if (DEVHandle <> INVALID_HANDLE_VALUE) then //хендл не инвалид
  begin
  form1.Memo1.Lines.Add('попробуем отправить данные устройству');
  //сформируем пакет данных с секретной информацией об расположении ядерных
  //объектов на территории врага. следует соблюдать объявленный формат структуры.
  CData.a:=100;//широта
  CData.b:=200;//долгота
  {*попробуем (нерешительно) сформировать Код Управления (ракетной шахтой).
  в отличии от ц++, в которой пишется драйвер, в дельфи отсутствует макрос
  который поможет нам это сделать легко. Однако мы сможем сделать аналогию
  этого макроса в run-time на дельфи.
  CTRL:=($8000 shl 16) or ($800 Shl 14) or (METHOD_NEITHER shl 2) or (FILE_ANY_ACCESS);
  однако это не работает и я Код Управления просто подглядел в драйвере. вывел его
  на экран в логах и вставил сюда такойже. почему не работает? незнаю. я пробывал CRTL
  описать как Cardinal а также как LongWord}
  Status:= DeviceIoControl(
               DEVHandle,
               $80002003,//код управления(ракетной шахтой) подсмотренный у драйвера
                         //в драйвере мы его сформировали макросом сами, вывели в лог
                         //и сюда копипаст.
               @CData, //указатель на структуру с секретной информацией
               sizeof(CData), // input buffer and length
               nil,//0,//nullptr, //вот так и живем. потыкались nil,//0,//nullptr,
                   //выбрали то что работает.
               0,// output buffer and length
               &returned,//в этом аргументе нам что-то возвратится.
                         //я буду с этим разбираться позже
               nil);//nullptr);
  if Status=true then
          form1.Memo1.Lines.Add('пакет данных улетел(куда-то)')
             else
          form1.Memo1.Lines.Add(SysErrorMessage(GetLastError));
  //дальнейший ход событий будет отображен драйвером в логах DbgView
  CloseHandle(DEVHandle);
  end;
IOinit:=true;
end;

end.

теперь сохраните откомпилируйте запустите и наблюдайте результат работы. Читая комментарии к коду вы сможете прояснить для себя некоторые моменты и поднять настроение. И научитесь регистрировать, запускать, останавливать, удалять драйверы а также отправлять пакет IRP от клиента к драйверу и принимать, обрабатывать его.
Не следует относиться к приведенному коду критично, так как он невкоем разе не претендует на то что именно так и надо делать. Более того для серьезных задач следует делать очень много всяких проверок на исключительные ситуации которым я уделил мало времени, однако некоторые (на мой взгляд важные) присутствуют.
Данный код следует воспринимать как ознакомительный- чтобы новичок мог понять как управляться с сервисами самоделками чтобы хоть что-то заработало и стало отправной точкой для продвижения вперед к своей цели.
P.S.> прошу не ругать меня за местами криво написанный не по шаблону код. Пользуйтесь на здоровье. Права на репост спрашивайте у владельцев этого великолепного Форума который и помог мне собрать этот пример в процессе изучения материала глав.
 

Вложения

  • CDRV.rar
    3.2 КБ · Просмотры: 1
Последнее редактирование:

X-Shar

:)
Администрация
Регистрация
03.06.2012
Сообщения
6 068
Репутация
8 176
@Maks Maksov, благодарю за статью.

Кстати если ваш проект станет более серьезным, лучше завидите аккаунт на гитхабе, например как я:

Ну либо в любой системе управления версиями и делится проектом можно потом на форумах, чем это полезно:

1. Вы можете независимо обновлять исходники, делая коммиты, при этом старые версии тоже будут доступны в системе.

2. Можете делать разные ветки, т.е. разные варианты вашей программы.

3. Любой может сделать форк, т.е. ответветвление от вашей версии и изменить её.

4. Также любой кому интересно может предложить свое решение, исправление багов и т.д.

Ну это так на будущее, может интересно будет.)
 

Maks Maksov

Пользователь
Форумчанин
Регистрация
10.09.2020
Сообщения
15
Репутация
5
на этом этапе обкуриваю информацию которую смею лицезреть на фото..
стек дров.jpg

я ожидал конечно увидеть стек мышиный. но не так много и с разными названиями. и вот mouclass и pointercalss .. пока просто размышляю откуда куда передается информация чтоб в результате мышь(указатель графический) двигалась по рабочему столу.
ну думаю еще денёк вдумчива вчитаюсь в эти мантры а потом спрошу у гугла.. покачто я незнаю что именно спросить у него.. как-бы..
наверное начну типа "что такое mouclass" "что такое pointerclass" Отдыхай!!!
 
Последнее редактирование:

X-Shar

:)
Администрация
Регистрация
03.06.2012
Сообщения
6 068
Репутация
8 176
Вот огромная база примеров для различных устройств:

А так по вашей задаче, может это подойдет:
 

Maks Maksov

Пользователь
Форумчанин
Регистрация
10.09.2020
Сообщения
15
Репутация
5
Жизненный цикл IRP..
Отдыхай!!!
kmd15-7.gif


Отдыхай!!!
это так шутки ради.. я долго не мог понять чтож так тяжело эта инфа вкуривается то.. пока не встретил это .. и всё стало ясно.. почему..
 
Последнее редактирование:

Maks Maksov

Пользователь
Форумчанин
Регистрация
10.09.2020
Сообщения
15
Репутация
5
Просьба осветить некоторые моменты по теме. Чтобы не заваливать вас вопросами я постарался, максимально вдумчиво 3 раза перечитал материал от Four-F на асме "Драйверы режима ядра" (части 1-5, 15-16 пропущенные статьи не имеют прямого отношения к теме). Таким образом получил некоторые фрагментарные представления о том как устроен стек устройств (драйверов). Но многое осталось непонятым.
Чтобы теорию приблизить к практике я начал с осмысления того как устроены стеки устройств клавиатуры и мыши на моей машине. Вижу из основной (не виртуальной) оси следующее..
рис. стек устройств обслуживающих мышь
стек мыши.jpg

и мне тут все понятно и ожидаемо.
а вот стек(стеки) клавиатуры вызывает вопросы.
рис. стек устройств обслуживающих клавиатуру
стек клавиатуры.jpg

я понимаю что верхнее устройство в стеке vmkbd3 является FiDO (filter device object) над KBDCLASS устройством. и установлено в мою систему с помощью VMWare(виртуальными машинами). KBDCLASS является устройством класса. а KBDHID функциональным устройством. еще ниже шинный интерфейс. далее цитата
"Драйвер класса определяет общую функциональность для всех устройств данного типа. Он ничего не знает о том, как управлять конкретным устройством, но, используя стандартизованные сервисы, взаимодействует с функциональным драйвером, который, в свою очередь, знает, как управляет конкретным типом устройств. В данном случае драйвером класса является kbdclass. Он исполняет роль своего рода буфера между функциональным драйвером i8042prt(в моём случае KBDHID) и подсистемой Win32"
FDO (function device object) и "Создается функциональным драйвером, который загружается диспетчером PnP для управления обнаруженным устройством. FDO представляет логический интерфейс устройства."(цит)
итак, вывод который я могу сам домыслить (поправте меня если надо) это.. любая софтина ЮзерМоде (или по нашему игра с геймгвардом) чтобы защититься от всяких кликеров может создать свой драйверный стек мыши и клавы для своего монопольного использования и таким образом игнорировать любые манипуляции происходящие с трафиком мыши в соседнем основном стеке мыши (который использует ось для отрисовки указателя). возможно но не факт что так и есть. я незнаю кто и зачем создал второй стек.
Так как драйверы обслуживающие устройства клавиатуры в обоих стеках одинаковые я делаю вывод что подключиться фильтром можно к обоим стекам (на примере vmware).
проанализировав ситуацию на виртуальных машинах (там где я играю в игры ММО с гейм гвардом) я обнаружил вообще 3 мышиных стека на верху которых устройства класса мыши со схожими названиями PointerClass0, PointerClass1, PointerClass2.
1)Итак, почему же у меня две клавиатуры в системе и два стека связанные с ними. хотя физически только одна? стеки у них одинаковые. какая необходимость какомуто софту делать такие дубли, для чего? и почему тогда мышь не дублирована? Конечно я не жду ответа кто мне их насоздавал(дубли) однако хочется понять в каких случаях и для чего и какой софт это может делать и что от этого получает (если можно пример)?
едем дальше..
цитата: "IRP формируется диспетчером в/в или драйвером не принадлежащим стеку и направляется на вершину стека. Если для обработки запроса драйверу требуется помощь нижестоящего драйвера, он перенаправляет IRP ниже по стеку и т.д. IRP всегда идет по стеку сверху вниз. Решение об окончании обработки IRP может быть принято на любом уровне. Мало того, любой драйвер в стеке может сформировать дополнительные IRP (например, разбить запрос чтения из файла на несколько запросов) и разслать его необходимым драйверам. Любой драйвер может отклонить запрос или может модифицировать передаваемые в нем данные. В общем случае, если драйвер получил IRP, то может делать с ним всё что угодно."
далее разбираемся как данные от физической мыши попадут на верх стека и смогут быть быть использованы в работе оси.
цитата: "Стек клавиатуры обрабатывает несколько типов запросов (полный список см. в разделе DDK "Kbdclass Major I/O Requests"). Нас будут интересовать только IRP типа IRP_MJ_READ, которые несут с собой коды клавиш. Генератором этих IRP является поток необработанного ввода RawInputThread системного процесса csrcc.exe. Этот поток открывает объект "устройство" драйвера класса клавиатуры для эксклюзивного использования и с помощью функции ZwReadFile направляет ему IRP типа IRP_MJ_READ. Получив IRP, драйвер Kbdclass, используя макрос IoMarkIrpPending, отмечает его как ожидающий завершения (pending), ставит в очередь и возвращает STATUS_PENDING. Потоку необработанного ввода придется ждать завершения IRP (если точнее, то RawInputThread получает клавиатурные события как вызов асинхронной процедуры (Asynchronous Procedure Call, APC)). Подключаясь к стеку, драйвер Kbdclass регистрирует у драйвера i8042prt процедуру обратного вызова KeyboardClassServiceCallback, направляя ему IRP IOCTL_INTERNAL_KEYBOARD_CONNECT. Драйвер i8042prt тоже регистрирует у системы свою процедуру обработки прерывания (Interrupt Service Routine, ISR) I8042KeyboardInterruptService, вызовом функции IoConnectInterrupt. Когда будет нажата или отпущена клавиша, контроллер клавиатуры выработает аппаратное прерывание. Его обработчик вызовет I8042KeyboardInterruptService, которая прочитает из внутренней очереди контроллера клавиатуры необходимые данные. Т.к. обработка аппаратного прерывания происходит на повышенном IRQL, ISR делает только самую неотложную работу и ставит в очередь вызов отложенной процедуры (Deferred Procedure Call, DPC). DPC работает при IRQL = DISPATCH_LEVEL. Когда IRQL понизится до DISPATCH_LEVEL, система вызовет процедуру I8042KeyboardIsrDpc, которая вызовет зарегистрированную драйвером Kbdclass процедуру обратного вызова KeyboardClassServiceCallback (также выполняется на IRQL = DISPATCH_LEVEL). KeyboardClassServiceCallback извлечет из своей очереди ожидающий завершения IRP, заполнит структуру KEYBOARD_INPUT_DATA (на самом деле i8042prt старается опустошить всю очередь контроллера клавиатуры, а Kbdclass соответственно заполнить столько структур KEYBOARD_INPUT_DATA, сколько влезет в буфер IRP), несущую всю необходимую информацию о нажатиях/отпусканиях клавиш и завершит IRP. Поток необработанного ввода пробуждается, обрабатывает полученную информацию и вновь посылает IRP типа IRP_MJ_READ драйверу класса, который опять ставится в очередь до следующего нажатия/отпускания клавиши. Таким образом, у стека клавиатуры всегда есть, по крайней мере, один, ожидающий завершения IRP и находится он в очереди драйвера Kbdclass. "Мышиный" стек ведет себя подобным же образом."
УХХХ! я две недели перевариваю эти мантры. итак. вот к чему я пришел!!!!
да этот мой визуальный шедевр порой смешит меня самого. такчто я непротив посмеяться над ним вместе с вами однако он хоть как-то помогает визуализировать то что там происходит. Ведь другой вариант визуализации вы можете видеть двумя постами выше-) Отдыхай!!! тут мы имеем дело с PS/2 моделью.
1)жизнь IRP картина первая.
жизнь ирп1.jpg

на этом этапе всё в режиме ожидания. IRP (вагонетка пустая) создан системным потоком RawInputThread с помощью функции ZwReadFile и со статусом PENDING ждёт. системный поток RawInputThread остановлен пока IRP вагонетка не наполнится данными и не вернется к нему полной информации.
2)жизнь IRP картина вторая.
жизнь ирп2.jpg

пользователь нажал кнопку. мышь сгенерировала IRQ (запрос на прерывание) и процедура I8042KeyboardInterruptService прочтет из контроллера данные которыми мыша готова поделиться о том что с ней произошло. порции этих данных - заполненные структуры, я представил мешками. так как обработку IRQ надо завершить как можно быстрее то ставится в очередь отложенный запуск другой процедуры DPC а обработка IRQ завершается.
3)жизнь IRP картина третья.
жизнь ирп3.jpg

управление получает DPC (процедура отложенного запуска) в функциональном драйвере. которая передаст управление функции обратного вызова в классовом драйвере. эта функция обратного вызова в классовом драйвере нагружает нашу пустую ожидающую вагонетку IRP мешками с данными (я так понимаю они туда попали в качестве аргументов ее вызова). и завершит обработку IRP.
4)жизнь IRP картина четвертая.
жизнь ирп4.jpg

застывший ожидающий системный поток RIT пробуждается получает IRP вагонетку полную данных. разгружает ее, обрабатывает. и сразу создает новую IRP на вход в классовый драйвер и засыпает снова. таким образом возвращаемся к картине первой.

я пожалуй прервусь.. для осмысления всего вышесказанного. если комуто не сложно прокоментируйте правильно ли я уловил суть происходящего? USB мыша работает схожим образом?
потом продолжу.. как в голове уляжется.
 
Последнее редактирование:

X-Shar

:)
Администрация
Регистрация
03.06.2012
Сообщения
6 068
Репутация
8 176
1)Итак, почему же у меня две клавиатуры в системе и два стека связанные с ними. хотя физически только одна? стеки у них одинаковые. какая необходимость какомуто софту делать такие дубли, для чего? и почему тогда мышь не дублирована? Конечно я не жду ответа кто мне их насоздавал(дубли) однако хочется понять в каких случаях и для чего и какой софт это может делать и что от этого получает (если можно пример)?
Конкретно по VMWare не скажу, зачем им еще один драйвер управления клавиатурой.

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

Да все будет как вы привели на картинках, НО в реальности все на много сложнее, по сути система имеет два драйвера, это драйвер USB и драйвер мыши которая подсоединяется к USB.

Если по простому, драйвер USB определяет что подключена мышь, далее все управление передается драйверу мыши, которая по интерфейсу USB взаимодействуя с драйвером USB уже управляет мышью.

Но используя абстракции, нам не нужно этим заморачиваться, ведь кто-то за нас уже написал как драйвер USB, так и драйвер мыши, обычно это делают разработчики оборудования, поэтому для нас важно понимать, то-что вы описали на картинках.)

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

Тем самым вы можете манипулировать данными, которыми обменивается система и устройство.

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

Да, конечно можно манипулировать данными с клавиатуры, мышы и т.д.

Собственно для этого такие драйвера и придуманы были.)
 

Maks Maksov

Пользователь
Форумчанин
Регистрация
10.09.2020
Сообщения
15
Репутация
5
да спасибо. у меня 10 лет был перерыв в программировании. ну и 10 лет назад я талантом не был и никогда не работал в этой сфере. мой портфолио это тетрис на визуал бейсик в 11-м классе (мне щас 39) а также клиент-серверная заготовка игры. я дошел до момента когда 3 клиента могли прицепиться к серверу и три космических корабля (клиенты персонажи) взаимодействуя через сервер могли стрелять в друг друга лазерами-) в 2д мире конечно. сервер всё просчитывал расстояния дистанции.. т.е. игровой мир по сути существовал в сервере.. а визуализировался у клиентов. ну так примерно все ММО функционируют.. ну потом я понял что тем дальше в лес тем задача сделать игру становится все сложнее. и одному непосилам. и все заброшено. ладно сори за офтоп.
по теме.. я могу предположить для чего VMWare нужен верхний надклассовый фильтр. ну к примеру если мы работаем в окне виртуальной машины то команды от устройства ввода не должны попадать в основную ось.. и их надо отрубать чтоб основная ось не ловила всякие нажатия , ведь они не для нее предназначены.
Однако непонятно другое, зачем несколько стеков для мыши и для клавы. не дров в стеке (с этим то все понятно.) а самих стеков одинаковых нафига несколько иметь? и кто их может плодить? и зачем? вот что непонятно. виндоус использует только 1? я предполагаю.
воть. а Вам спасибо за то что освещаете мне путь в этом тумане непонятностей.
 

X-Shar

:)
Администрация
Регистрация
03.06.2012
Сообщения
6 068
Репутация
8 176
Вам спасибо за то что освещаете мне путь в этом тумане непонятностей.
Я тоже со всем этим разбираюсь, в винде там чёрт ногу сломит, в линуксе более проще и понятней всё.)
Хотя если говорить про линукс, там другая проблема, костыльная сильно система...Dmeh-Smeh-Smeh!!!
 
Верх Низ