Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате |
Форум программистов > Delphi: WinAPI и системное программирование > Драйверы Windows режима ядра |
Автор: ama_kid 22.8.2008, 12:00 | ||||||||||||||||||||||||||
1. Введение Данная статья является, по своей сути, продолжением известной статьи Геннадия Порева "http://rsdn.ru/article/delphi/kmdelphi.xml". Все моё дальнейшее повествование будет предполагать, что читатель по крайней мере ознакомился с представленным в ней материалом, поэтому если это еще не сделано - то сейчас самое время. К статье прилагается архив со всеми исходными текстами, описанными и разобранными здесь, при этом хотелось бы упомянуть, что некоторые вещи, которые можно найти в исходниках - здесь не описаны, например логирование действий драйвера в файл на диске, которая мало чем отличается от работы с логами в user-mode, или найденная мной в интернете ручная приближенная реализация cdecl-формата вызова функции DbgPrint, которая на языке С++ имеет переменное число параметров с возможностью вставлять управляющие символы для вывода значений переменных (Delphi этого не умеет). Не описано это просто потому, что нет желания останавливаться на этом, при необходимости - это можно самому посмотреть и понять. Также, при цитировании кода в тексте статьи я буду опускать те участки или параллельные вызовы функций (в основном, конечно, отладочный вывод), которые не нужны для понимания той части, о которой говорю в данный момент, поэтому не стоит удивляться, если обнаружите, что исходном коде наличествует еще какой-то "мусор" по сравнению со статьей. 2. Инструментарий Прежде всего, хочу немного времени уделить описанию инструментария, который я использую у себя. В принципе, в изначальной статье он был достаточно подробно описан, так что я не буду повторяться, просто на всякий случай пошагово опишу его применение. Средой разработки является любой текстовый редактор (кстати, почитателям компоненто-на-форму-кидательства дальнейшее не рекомендуется к прочтению). Лично я пользуюсь встроенным редактором Far'а, но можно использовать любой удобный и привычный инструмент. В принципе, впоследствии я нашел на просторах интернета "Delphi Driver Development Kit" (где вроде как используется компилятор от 7-й версии дельфи), но к тому моменту уже привык к изначальной среде окружения, поэтому не стал его особо изучать (кроме того на него тявкает мой антивирус, а после замены инфицированных компилятора и линкера на рабочие варианты - мне так и не удалось заставить его работать). Кому надо - может погуглить: Как правило, для разработки драйвера я копирую целиком папку, содержащую нижеописанные файлы и работаю уже в ней: а) Файл ntoskrnl.lib - самый главный файл, содержащий все импортируемые ядром Windows функции. Данный файл не только жизненно необходим для работы линковщика, но также может помочь узнать формат вызова конкретной необходимой функции, о чем я расскажу ниже. б) dcc32.exe, rlink32.dll, system.dcu и sysinit.dcu - собственно компилятор Delphi3 с сопутствующими файлами. в) link.exe, mspdb71.dll - линковщик от мелкомягких с необходимой для его работы библиотекой г) DrvInst.exe - инсталлятор\деинсталлятор драйвера. Исходник данного приложения на языке Delphi можно найти в прилагающемся архиве и в нем мной реализованы два способа установки драйвера: моментальный (по умолчанию) и с перезагрузкой (во многих случаях может потребоваться и это). Второй способ в исходнике закомментирован, но рекомендуется его проверить для того, чтобы понимать, как это делается. д) Пакетные файлы: _clear.bat (очистка папки), _copy.bat (копирование драйвера в системную папку), _Inst.bat (запуск драйвера), Make.bat и _make.bat (компилирование и сборка драйвера) и _Uninst.bat (очистка системы от следов драйвера) - собственно представляют из себя управление циклом жизни драйвера. Каждый из них вызывает соответствующие команды и исполняемые файлы для реализации своей задачи. Для корректной работы перед началом использования надо в них заменить все названия драйвера на своё собственное (заменив, естесственно, и соответствующие названия файлов). Затем, при наличии в этой же папке файла-исходника драйвера, запускать надо в следующем порядке: - _make.bat (если все прошло успешно - в папке появится sys-файл драйвера) - _copy.bat (этот файл скопируется в системную папку) - (в данном месте я обычно запускаю утилиту DbgView.exe) - _Inst.bat (драйвер запустится и начнет свою работу) - _UnInst.bat (драйвер выгрузится и удалится из системы и текущей папки) е) Ну и не могу не упомянуть скрытых игроков нашей команды - это предустановленный DDK, http://msdn.microsoft.com/en-us/default.aspx и http://www.goggle.com. 99% информации - оттуда. Естественно, этот способ организации процесса не является истиной в последней инстанции и можно его оптимизировать, но для того, чтобы дальнейшие описания были понятны и, самое главное, вопроизводимы - примем такую среду за отправную точку. 3. Первые шаги В данной главе я постараюсь показать, как можно работать в kernel-mode и что делать с доставшимся правом "делать все, что захочется". Исходный код тестового драйвера, рассмотренный здесь, а также user-mode'ного приложения для его тестирования, доступен в прилагающемся архиве в папке под именем MyDrv. 3.1 Перевод функций DDK Итак, автор первоначальной статьи оставил нас наедине с "голым" драйвером tiny, который может только загружаться и выгружаться. Куда двигаться дальше? Надо сказать, что несмотря на использовании компилятора дельфи, все его RTTL-вкусности типа функций trunc, sqrt, random, равно как классов, объектов и других полезностей в ООП, остаются за бортом. Да что там функции - даже банальное сложение двух строковых переменных недопустимо, потому что для этой несложной с точки зрения программиста операции компилятору приходится генерировать вызовы нескольких функций, которых просто нет в режиме ядра! Ну и особую грусть вызывает невозможность использования стандартных обработчиков исключений try...except...finally... по той же самой причине. Поэтому необходимо посмотреть в сторону того, что разрешено. Здесь на помощь приходят файлы ntddk.h, ntdef.h (файлы описаний из WinDDK) и ntoskrnl.lib (описанный выше). Этих файлов конечно недостаточно для того, чтобы написать какой-то навороченный драйвер файловой системы низкого уровня, но для написания более-менее полезного драйвера в учебных целях их хватит (при этом h-файлы используются только как справочный материал по объявлениям констант и необходимых нам функций). Для начала работы необходимо явно указать компилятору, какие функции режима ядра можно использовать - делается это путем статической линковки функций файла ntoskrnl.lib. Для тех, кто не очень четко представляет себе этот механизм - поясню сказанное: возьмем, к примеру, функцию http://msdn.microsoft.com/en-us/library/aa490468.aspx, которая впоследствие нам еще пригодится. Объявление этой функции в файле ntddk.h выглядит следующим образом:
3.2. Создание объекта "устройство" Сам по себе драйвер, который умеет только загружаться и выгружаться - бесполезен. Как правило, все драйверы работают с какими-либо устройствами (но не обязательно, и впоследствии я покажу пример этого), поэтому самое первое, что необходимо сделать для того, чтобы труд не был напрасным - создать объект "устройство", к которому можно будет впоследствии обращаться из приложения. Создание устройства в принципе может происходить на любом этапе работы драйвера, но как правило это делают при начальной инициализации в функции DriverEnry:
3.3. Общение с приложением user-mode Но просто создать\удалить устройство - это еще полдела. Зачастую требуется получить некоторую информацию от процесса, исполняющегося в режиме ядра или передать туда порцию своей информации. Для начала рассмотрим самый простой способ - организацию функций-диспетчеров запросов IRP_MJ_READ ($03) и IRP_MJ_WRITE ($04), которые приходят драйверу при выполнении команд http://msdn.microsoft.com/en-us/library/aa365467(VS.85).aspx и http://msdn.microsoft.com/en-us/library/aa365747(VS.85).aspx соответственно. Для начала - необходимо назначить эти самые диспетчеры в функции DriverEntry:
Теперь рассмотрим методы приема\передачи буфера данных. При создании устройства можно выбрать тип метода передачи этого буфера: буферизированный (DO_BUFFERED_IO = $00000004) или прямой (DO_DIRECT_IO = $00000010) путем установки поля Flags объекта-устройств:
а) Запрос чтения (ReadFile): Сам запрос от приложения-инициатора выглядит следующим образом:
Теперь рассмотрим обработку этого запроса в нашем тестовом драйвере:
б) Запрос записи (WriteFile): Запрос от приложения-инициатора:
В драйвере:
|
Автор: ama_kid 22.8.2008, 12:13 | ||||||||||||||||||||||
3.4 Продвинутое общение Рассмотренный нами в предыдущей главе метод общения драйвера с внешним миром, конечно, прост и понятен, но что делать, если задача требует не просто считать\записать данные, а произвести несколько более сложные действия, например, передать управляющие команды, обеспечить диалог драйвер-приложение или выполнить что-либо ещё специфическое для данного драйвера? Можно, конечно, при желании все организовать через ReadFile\WriteFile, но есть более универсальное решение - функция http://msdn.microsoft.com/en-us/library/aa363216(VS.85).aspx. Для примера, рассмотрим следующие функции:
![]() Расшифровка полей управляющего слова следующая: 1. Поле DeviceType определяет тип объекта-устройства, которому предназначен запрос. Это тот самый тип устройства, который передается функции IoCreateDevice() при создании устройства. 2. Поле Function идентифицирует конкретные действия, которые должно предпринять устройство при получении запроса. Значения поля Function должны быть уникальны внутри устройства. 3. Поле Method указывает метод передачи буферов данных. Для задания этого поля можно использовать константы: METHOD_BUFFERED = 0; METHOD_IN_DIRECT = 1; METHOD_OUT_DIRECT = 2; METHOD_NEITHER = 3; 4. Поле Access указывает тип доступа, который должен был быть запрошен (и предоставлен) при открытии объекта-файла, для которого передается данный код Управления вводом/выводом. Возможные значения: FILE_ANY_ACCESS = $0000, FILE_READ_ACCESS = $0001 и FILE_WRITE_ACCESS = $0002. Для нас самое важное значение имеют биты 0-2 (в которых указывается метод передачи буфера) и 3-12 (в которых указывается передаваемый код управления драйвером). Остальные байты можно формировать по желанию и необходимости, в нашем случае такой необходимости нет. Теперь рассмотрим обработку этого запроса со стороны драйвера: Для начала необходимо назначить функцию-обработчик данного запроса. Это выполняется, как обычно, в DriverEntry:
4. Из жизни Предыдущая глава была посвящена решению проблем, выдуманных нами фактически на пустом месте. Это не сильно интересно, поэтому в данной главе я хотел бы разобрать пару примеров, основа которых была мной написана для реальных проектов. Надеюсь, они помогут в более быстром освоении темы. Оба примера будут рассмотрены, естественно, на языке Delphi, но если кому интересно - то в соответствующих папках в архиве можно найти и C++ - варианты этих драйверов, которые собираются из-под DDKXP. 4.1 Пример №1: Перехват клавиатуры Рассмотрим частный пример распространенной проблемы глобального перехвата клавиатуры - отлов сочетания CAD. В операционных системах семейства NT это сочетание является единственным, которое не отлавливается из user-mode (исключение составляет способ отлова через MSGINA.dll, но он обладает своими недостатками), поэтому я и взял его для примера, но в принципе данный подход применим для отлова любого требуемого сочетания клавиш. Основной идеей является установка драйвера-фильтра клавиатуры верхнего уровня. Что такое драйвер-фильтр и чем верхний уровень отличается от нижнего я объяснять не буду, об этом можно почитать в соответствующих источниках, но суть сводится к тому, что мы "посадим" наш драйвер поверх устройства основного драйвера клавиатуры, оставляя всю рутинную работу по работе с портами ввода-вывода и первичной обработке результатов ему, а сами будем только фильтровать запросы, проходящие между режимом пользователя и этим драйвером. Данный пример был написан мною по мотивам и на основе широко известного примера Ctrl2Cap, но с некоторыми изменениями, на которых я остановлюсь подробнее. Исходный код драйвера находится в прилагающемся архиве в папке под названием KbdIntcpt. Итак, рассмотрим функцию DriverEntry: первым делом мы создаем объект "устройство", как было показано ранее, с помощью функций IoCreateDevice и IoCreateSymbolicLink.
Затем идёт кусок кода по созданию объекта-евента:
а) Дело в том, что в обработке драйвера клавиатуры всегда находится как минимум один IRP-пакет, поэтому когда мы присоединяемся в цепочку фильтров - то неизбежно пропустим тот пакет, который уже сидит в обработке, потому что на него еще не была установлена наша функция возврата. Фактически это означает что первое после загрузки драйвера нажатие клавиши не будет отловлено. б) Опять же, благодаря тому, что в обработке всегда наличествует как минимум один пакет, в момент выгрузки драйвера в этом пакете сидит привязанная к нему наша callback-функция ReadCompleteCallback (если, конечно, между загрузкой и выгрузкой драйвера происходили нажатия клавиш, что должно было привести к вызову IoSetCompletionRoutine) и если мы выгрузимся до того, как драйвер клавиатуры вызовет эту функцию, мы неизбежно получим BSOD. Для того, чтобы этого избежать - я и использовал евент, сигнализирующий о получении очередного пакета и не дающий выгрузиться драйверу, если он не сработал. Этой же цели служит булевская переменная UnloadProcess, которая не дает установить процедуру завершения, если пошел процесс выгрузки драйвера. Но все это приводит к небольшому неудобству - при выгрузке драйвера необходимо нажать клавишу. Упомянутый мной пример Ctrl2Cap этот момент обходит другим способом - он для своей установки\выгрузки требует перезагрузки компьютера, кому интересно - может погуглить и посмотреть, как это реализовано. в) Ну и еще один достаточно тонкий момент, который не относится именно к данному драйверу, но который я впервые испытал на своей шкуре именно в этом месте, поэтому упомяну здесь. Дело в том, что callback-функция ReadCompleteCallback вызывается на уровне http://www.books.everonit.ru/PogrammingSistemSecurity/Glava%202/Index26.htm, в то время как остальной код драйвера по умолчанию работает на самом низкоприоритетном уровне PASSIVE_LEVEL. Это различие аукнулось тем, что попытка записи в лог-файл внутри этой функции приводила к BSOD, потому что работа с файлами производится тоже на уровне PASSIVE_LEVEL. Поэтому при разработке механизмов взаимодействия рекомендую не забывать о таких вещах, как приоритет выполнения. Ну а что же собственно выполняет функция ReadCompleteCallback? Её код выглядит следующим образом:
4.2 Пример №2: Перехват системных функций ядра Ну и еще один пример, который я хотел бы рассмотреть - это пример осуществления перехвата функций ядра на примере функции http://msdn.microsoft.com/en-us/library/ms801001.aspx для обеспечения "невидимости" требуемого файла\папки. Делается это путем модификации системной таблицы служебных дескрипторов (SSDT). Данный метод зачастую называют "грязным", но иногда приходится прибегать и к таким способам, благо из режима ядра это разрешено. Этот пример тоже был написан под большим влиянием найденного в сети драйвера, осуществляющего какую-то работу с таблицей дескрипторов, но, к сожалению, я уже не помню что это был за драйвер, поэтому авторство идеи оставляю за "неизвестным героем". Исходный код находится в прилагающемся архиве в папке под названием FileHider и полный код я приводить здесь не буду, просто поясню основные моменты. Итак, хук устанавливается в функции DriverEntry с помощью экспортируемой недокументированной глобальной переменной http://he4dev.e1.bmstu.ru/HookSysCall/ (со скачиваемыми примерами рекомендую проявлять осторожность - антивирус ругается на скомпилированные файлы, принимая rootkit-технологии за зверьё). Тонким моментом здесь является использование бита WP системного регистра cr0 при установке\снятии хука. Этот бит управляет защитой от записи системных страниц памяти. Если у вас на машине стоит физической памяти больше 256 Мб (WinXP) или 128 Мб (Win2k), тогда данный код никак не влияет на функциональность (т.е. если его убрать - никаких отличий не найти). Но! Если на машине стоит памяти меньше чем указано - тогда система осуществляет защиту системных страниц памяти от записи и при попытке что-то записать в область системного кода будет генерироваться исключение и это вызовет BSOD (подробнее об этом можно прочитать в журнале Хакер, глава "http://www.xakep.ru/post/40549/default.asp"). Именно для того, чтобы это учесть, и сделан сброс бита WP перед записью в область системного кода - тогда системные страницы с любом случае будут доступны для модификации. После установки хука - остальное дело техники. Все запросы, благодаря наличию в SSDT адреса нашей функции, перенаправляются ей, затем она тут же вызывает оригинальную системную процедуру по сохраненному указателю, а возвращаемый результат анализирует на предмет соответствия требуемым условиям. В примере я сделал проверку по соответствии имени файла\папки определенному шаблону, а также закомментировал проверку по атрибутам файла. Анализ осуществляется путем перебора всего списка элементов типа http://msdn.microsoft.com/en-us/library/ms791526.aspx, который возвращается оригнальной функцией ZwQueryDirectoryFile. В случае выполнения условий проверки - проверяемый элемент удаляется из списка путем перенаправления указателя предыдущего элемента списка на следующий. При завершении работы драйвера - в функции DriverUnload адрес оригинальной процедуры возвращается на место : Конечно, для того, чтобы полностью "спрятать" требуемый файл - просто перехвата ZwQueryDirectoryFile недостаточно, даже если спрятать папку таким образом - в неё все равно можно будет зайти путем ввода команды "cd". Но это уже вопрос наращивания функциональности. 5. Заключение Данная статья не охватывает многие и многие аспекты темы разработки драйверов. Например, не были рассмотрены вопросы обмена с реальными устройствами, не приведено примеров работы с файловой системой на низком уровне, даже не рассмотрен вопрос построения inf-файлов, необходимых для полноценной установки устройства в систему, или других узкоспециализированных моментов. Но и не в этом её цель. Целью является дать дополнительный импульс для тех, кто хочет осилить эту необъятную тему, с тем, чтобы дальше можно было двигаться самостоятельно, дать начальные познания при помощи привычной "среды", а также дать своеобразные "направляющие" для правильного составления конкретных вопросов на соответствующих форумах. Если не получилось - прошу строго не судить P.S. Выражаю огромную благодарность http://vingrad.ru/@Snowy за помощь в подготовке и оформлении статьи. |
Автор: THandle 22.8.2008, 12:23 |
ama_kid, ![]() Ага. Бывает ![]() + тебе за статью. Когда нибудь точно пригодится ![]() |
Автор: MetalFan 22.8.2008, 12:25 |
ama_kid, сам написал?! ну ты ![]() |
Автор: ama_kid 22.8.2008, 12:39 |
THandle, MetalFan, спасибо ![]() |
Автор: Riply 22.8.2008, 21:36 |
ama_kid, Спасибо. Когда при решении задач мне стало довольно сильно мешать мое неумение писать драйвера, я решила обратиться к С, считая что в родной и любимой Delphi это будет "не естественно" ![]() Еще раз спасибо, за избавление от столь пагубного заблуждения ![]() |
Автор: ne0n 22.8.2008, 21:56 |
Riply, ну на самом деле - это не такое пагубное заблуждение...переводить ddk на паскаль, никто не будет...а самому эту задачу не осилить, хотя может и найдуться интузиасты ![]() ничего нового не узнал, но за стотью в любом случаи респек и + |
Автор: Riply 8.9.2008, 19:06 |
ama_kid, Наконец-то выдалось свободное время и можно заняться всякими вкусностями ![]() Начала смотреть код (Delphi версия) и у меня сразу появилась куча вопросов: 1. STRSIZE = 115; Что это за число (115) и откуда оно взялось ? 2. NTSTATUS спецально объявлен как ULong ? Если да, то почему ? 3. Вытекает из второго Реализация ф-ии NT_Success ( Result := (NTS=STATUS_SUCCESS) ) обусловлена именно таким объявлением NTSTATUS ? 4. Почему, после вызовов BCAnsiStringAllocate не всегда вызывается BCAnsiStringFree (например в WriteDispatch) ? 5. Rtl-кие функции для работы со строками валидны в нашем случае ? (Имеется ввиду совместимость способов выделения и освобождения памяти Rtl-кими функциями и BC-функциями) ? P.S. При ответах, просьба учесть нулевой уровень вопрошающей в данной области ![]() |
Автор: Riply 8.9.2008, 23:48 | ||||||||
Я только начала разбираться, но мне кажется, что можно работать с блоками размером до MAXWORD shr 1. Я пыталась исходить из размерности полей.
К сожалению не знаю как обстоят дела в мире драйверов, но в обычном приложении, работающем с Nt-функциями знаковость NTSTATUS очень важна. А NT_SUCCESS должна иметь такой вид:
Дело в том, что некоторые (если не многие) функции могут в случае успеха возвращать не STATUS_SUCCESS а "некий код", содержащий STATUS_SEVERITY_INFORMATIONAL. В этом случае фунция выполнена успешно, а в ее результате дается дополнительная информация. Как правило это используется в Query функциях (например STATUS_MORE_ENTRIES говорит, что все удачно записано, но меня можно вызвать еще раз для дозаписи). Но встречается и в другин. Например я сталкивалась с STATUS_PREDEFINED_HANDLE, STATUS_EVENT_PENDING или при работе с секциями (мутантами) STATUS_OBJECT_NAME_EXISTS а вот здесь это становится довольно важно.
Ой. Прошу маленький тай-аут. Еще чуть поразбираюсь и сформулирую вопрос более точно. Но пока мне не понятно, чем обусловлен выбор MmAllocateNonCachedMemory вместо Rtl - Heap - функций ? (Они кстати "совместимы" с такими как RtlFreeUnicodeString) |
Автор: ama_kid 9.9.2008, 09:26 | ||||||||||||
![]() Вот как это дело расписано в DDK:
![]() Я наконец понял, что ты имеешь ввиду. ![]()
![]()
|
Автор: Riply 9.9.2008, 17:24 |
IMHO, довольно опасный способ проверки. Например при создании секции такая проверка может показать неудачу, в то время как все успешно и мы получили на руки открытый Handle. Который еще и закрыть надо ![]() Я почему спрашиваю. Мы ведь в наших Dispatch функциях предполагаем многократное выделение/освобождение. Соответственно меня и интересует какой из способов работы с памятью лучше всего подходит для этого случая. С MmAllocateNonCachedMemory я еще не окончательно разобралась, но меня сильно смущает следующая фраза из MSDN: "A device driver that must use noncached memory should allocate only what it needs during driver initialization because nonpaged pool is likely to become fragmented as the system runs." Насчет же функций типа RtlAnsiStringToUnicodeString (а они предназначены для частого использования) , мое копательство в них показало, что они работают именно через Rtl-Heap. Поэтому я и говорила о "совместимости". Если бы BC функции тоже работали через Heap, то их можно было бы использовать "вперемежку" с Rtl-скими (проверяла в юзер-моде). P.S. Я ни в коем случае не спорю, а просто пытаюсь понять что к чему и с чем едят. Соответственно многие мои утверждения приведены не как контр-аргументы, а для того, чтобы мне показали ошибку в моих рассуждениях и пояснили "как надо на самом деле" ![]() Где еще найдешь бесплатное образование, кроме как не на таких форумах ![]() |
Автор: ama_kid 9.9.2008, 18:54 | ||||
![]()
![]() Но вообще, режим ядра предоставляет гораздо большие возможности для работы с памятью, чем user-mode и на эту тему есть интересные http://www.wasm.ru/author.php?author=Four-F (главы "Работа с памятью"). Можешь почитать, если интересно... |
Автор: Riply 9.9.2008, 19:47 | ||
Спасибо. Издеваетесь ? ![]() |
Автор: ama_kid 10.9.2008, 15:35 |
ни в коем разе ![]() |
Автор: Riply 11.9.2008, 10:19 |
ama_kid, никак не могу понять: Вот пишем мы лог из Dispatch функций. А на чем основана наша уверенность в том, что это потоко-безопасно ? |
Автор: ama_kid 11.9.2008, 14:01 |
Хм... Для начала два встречных вопроса: 1) Я где-нибудь это утверждал? 2) Я где-нибудь использовал потоки? Естественно, разработка многопоточных приложений может наложить свой отпечаток, в том числе и в режиме ядра, но все-таки это не является предметом рассмотрения в даной статье... Ну а ответом на поставленный вопрос, думаю, может служить тот факт, что посылка запросов IRP_MJ_READ\WRITE\DEVICE_CONTROL осуществляется синхронно, т.е. системный менеджер запросов не пошлет драйверу следующий IRP-пакет до тех пор, пока им не будет обработан предыдущий. Поэтому, допустим, вызов ReadFile из одного потока при обработке ReadFile из другого потока вызовет просто задержку на исполнение первого вызова. Отчасти поэтому и не рекомендуется задерживать обработку IRP-пакетов (а на высоких уровнях приоритета - вообще нельзя) - т.к. это приводит к performance reducing... |
Автор: Riply 11.9.2008, 14:20 | ||
Это смотря что считать утверждением ![]() Я посчитала, что раз ты не побеспокоился о потоко-безопасности, значит считаешь, что это излишне. Вот я и пыталась понять на чем это заключение основано ![]()
Спасибо. Именно это меня и интересовало. Т.е. сколько бы приложений одновременно не открыло наше устройство для связи с драйвером и с какой интенсивностью они бы ни читали(писали) мы будем получать только по одному запросу и пока его не обработаем, следующий нам никто не даст. Я правильно поняла ? |
Автор: ama_kid 11.9.2008, 15:16 |
Да на таком уровне и беспокоиться не надо - BSOD быстренько бы обо всём напомнил ![]() вполне ![]() |
Автор: Riply 12.9.2008, 04:25 | ||
И очень похоже, что ты оказался прав в своем выборе ![]() Вот что гласит MSDN: To allocate I/O buffer space, the best memory allocation routines to use are MmAllocateNonCachedMemory, MmAllocateContiguousMemorySpecifyCache, AllocateCommonBuffer (if the driver's device uses bus-master DMA or a system DMA controller's auto-initialize mode), or ExAllocatePoolWithTag. Nonpaged pool typically becomes fragmented as the system runs, so a driver's DriverEntry routine should call these routines to set up any long-term I/O buffers the driver needs. Each of these routines, except ExAllocatePoolWithTag, allocates memory that is aligned on a processor-specific boundary (determined by the processor's data-cache-line size) to provide best performance. |
Автор: Riply 16.9.2008, 18:18 |
ama_kid, что-то мне не удается найти в ntoskrnl.lib ничего похожего на KeGetCurrentIrql :( Может она называется там совсем по-другому ? Подскажи, пожалуйста, как ее импортировать ? |
Автор: ama_kid 16.9.2008, 19:11 | ||
![]() |
Автор: Riply 16.9.2008, 19:49 |
Спасибо. Скачала, буду смотреть. С DDK мы еще не знакомы. Пока не разобралась с какой стороны к нему подходить ![]() |
Автор: Riply 21.9.2008, 13:10 |
ama_kid, Хочу вернутся к вопросу потоко-безопасности. Пару раз заметила, что при записи лога KbdIntcpt (правда чуть переделанного ![]() искажение информации, причем на определенных сообщениях. Редко но бывает. Тогда решила добавить в лог ThreadID (первый столбец во второй таблице) и посмотреть в каком порядке добавляются записи. Выдержка из лога Dbgview: 00000034 11:43:50 DriverRead _ 0 0 0 00000035 11:43:50 DriverRead IoCallDriver 103 0 0 -->00000036 11:43:54 DriverUnload _ 0 0 0 00000037 11:43:56 DRIVER_NAME : DriverReadCallBack 00000038 11:43:56 DriverUnload KeWaitForSingleObject 0 0 0 00000039 11:43:56 DriverUnload ZwClose 0 0 0 00000040 11:43:56 DriverRead _ 0 0 0 00000041 11:43:56 DriverUnload IoDeleteSymbolicLink 0 0 0 00000042 11:43:56 DriverRead IoCallDriver 103 0 0 00000043 11:43:56 DriverUnload IoDetachDevice 0 0 2262795872 00000044 11:43:56 DriverUnload IoDeleteDevice 0 0 2240838328 00000045 11:43:56 SnakeLog Finalizate 0 43 1024 Соответствующие строчки из лога KbdIntcpt: 0000000748 21.09 07:43:50.500 DriverRead IoCallDriver Overlapped I/O operation is in progress ( $000003E5 ) 0000000000 0000000000 0000000025 0000000046 -->0000000052 21.09 07:43:54.062 DriverUnload _ The operation completed successfully 0000000000 0000000000 0000000026 0000000047 0000000052 21.09 07:43:56.593 DriverUnload KeWaitForSingleObject The operation completed successfully 0000000000 0000000000 0000000027 0000000048 0000000052 21.09 07:43:56.593 DriverUnload ZwClose The operation completed successfully 0000000000 0000000000 0000000028 0000000049 0000000748 21.09 07:43:56.593 DriverRead IoCallDriver Overlapped I/O operation is in progress ( $000003E5 ) 0000000000 0000000000 0000000031 0000000051 0000000052 21.09 07:43:56.593 DriverUnload IoDetachDevice The operation completed successfully 0000000000 2262795872 0000000032 0000000052 0000000052 21.09 07:43:56.593 DriverUnload IoDeleteDevice The operation completed successfully 0000000000 2240838328 0000000033 0000000053 0000000052 21.09 07:43:56.593 SnakeLog Finalizate The operation completed successfully 0000000043 0000001024 0000000034 0000000054 "-->" - помечен вход в процедуру DriverUnload Видно, что паралельно с ней у нас отрабатывает и процедура DriverRead, причем работают они "из разных потоков" (ID одного - 52, другого - 748). Т.е. у нас получается следующая последовательность действий: DriverUnload --> KeWaitForSingleObject DriverUnload --> ZwClose DriverRead --> входим в процедуру DriverUnload --> IoDeleteSymbolicLink DriverRead --> IoCallDriver DriverUnload --> IoDetachDevice DriverUnload --> IoDeleteDevice Вот такие пироги. P.S. Очень надеюсь, что ты найдешь ошибку в рассуждениях и мне не придется разбираться еще и потоко-безопасностью, вместо похода на дискотеку ![]() P.P.S. У меня еще возникли вопросы по работе с памятью, но это после того как с этим разберемся ![]() Добавлено через 5 минут и 1 секунду Вот черт. У меня такие аккуратные таблички были, а тут все съехало. Sorry. Надеюсь они стали не очень нечитабельны ![]() |
Автор: Riply 23.9.2008, 07:35 | ||
Если кому интересно... ![]() Попробовала чуть более детально разобраться с нашей потоко-безопасностью. Вот что получилось: Для синхронизации выбрала (после очень долгих и тяжких раздумий) объект ERESOURCE. (Выбирала с учетом своих планов, так что кому-то может лучше подойдут объекты другого типа) Эффект искажения данных в логе исчез сразу после заключения "логовых" функций в "критические обертки". Для проверки, что одновременная запись в лог разными нитями мне не померещилась в кошмарном сне ![]()
Вот выдержка из лога Dbgview: 00000043 07:48:45.484 DriverRead IoCallDriver 103 0 Free 00000044 07:48:45.562 ReadCompleteCallback 00000045 07:48:45.562 DriverRead _ 0 0 Free 00000046 07:48:45.562 DriverRead IoCallDriver 103 0 Free 00000047 07:48:48.718 DriverUnload _ 0 0 Free 00000048 07:48:50.859 ReadCompleteCallback 00000049 07:48:50.859 DriverUnload KeWaitForSingleObject 0 0 Free 00000050 07:48:50.859 DriverUnload ZwClose 0 0 Free 00000051 07:48:50.859 DriverRead _ 0 0 Locked(!) 00000052 07:48:50.859 DriverUnload IoDeleteSymbolicLink 0 0 Locked(!) 00000053 07:48:50.859 DriverRead IoCallDriver 103 0 Locked(!) 00000054 07:48:50.859 DriverUnload IoDetachDevice 0 0 Free 00000055 07:48:50.859 DriverUnload IoDeleteDevice 0 0 Free 00000056 07:48:50.859 LatchFile LatchFile_Finalizate 0 38 1024 Как мне кажется, из нее (выдержки) видно как происходит борьба двух нитей за ресурс ![]() P.S. То, что мне помогло использование ERESOURCE (это утверждение основано на отсутствии BSOD в течении трех часов ![]() Требуется более тщательная проверка. |
Автор: ama_kid 29.9.2008, 10:32 |
Ой, что-то как-то я упустил свою тему из виду, извиняюсь, просто уже несколько дней не заглядывал на форум... ![]() Имхо, нет в твоих рассуждениях ошибки, насколько я разобрался в той каше, которую ты вывалила ![]() Насколько я понял, у тебя вызвал непонятки момент одновременной записи в лог из DriverRead и DriverUnload? Не забывай (или имей ввиду), что функции DriverEntry и DriverUnload - это не функции-диспетчеры, а функции, выполняющиеся в контексте одного из системных потоков (случайным образом выбирается один из потоков процесса SYSTEM). А функции-диспетчеры выполняются в контексте процесса, выполняющего запрос ввода-вывода . А раз так - то для синхронизации между функциями-диспетчерами и системным потоком конечно нужно использовать объекты синхронизации... Кроме того, меня смутила твоя фраза про "немного переделанный вариант" KbdIntcpt. Насколько я понял, ты ввела Overlapped-режим? Если я правильно помню - то отложенные операции выполняются тоже в контексте системного потока (аналогично callback-функциям), поэтому между двумя системными потоками тоже неплохо бы вводить синхронизацию при использовании ими общих ресурсов ![]() |
Автор: microxa 2.12.2008, 05:49 | ||||||||
hi, all ![]() Относительно темы delphi драйверов... Надо отметить что некоторым недостатком является сборка драйверов из obj-ектников, или использованием сторонних тулз (omf2d, m$-link). Вобщем куда удобней использовать собственный линкер delphi, устраняя некоторые неровности в производимых им PE dll-ках, таким вот напильником:
Дабы компиль не напихал вредных зависимостей, разюмеется для сборки необходимы минимальные System/Sysinit
Особености реализации модулей позволяют использовать блоки try/except (хотя от всех видов исключений (например GPF) - они не спасут). Ну и собственно как использовать:
удачи ![]() |
Автор: Pavia 6.12.2008, 19:13 | ||
Использую DDDK0004 Никак не пойму почему bsod. Пробую пообщаться с IO-APIC
вылитает на этих строчках. Может кто подскажет в чем дело? А еще dbgprint не работает. |
Автор: Pavia 7.12.2008, 03:57 | ||
Судя по всему срыв стека. Такое впечатление что неправильно экспортирую функции.
|
Автор: ReInit 7.12.2008, 11:01 |
ama_kid Спасибо! Отличная инфа! microxa, Т.е. ты собираешь ДЛЛ и используешь ее как драйвер? Какую версию Делфи ты используешь? Потому что слышал, если собирать МС линкером, то он понимает обьектники только 3-й Делфи, т.к. после 3-й пошел неправильный COFF формат... И спасибо за System/SysInit, я юзал несколько попроще, без поддержки исключений ![]() |
Автор: ama_kid 7.12.2008, 12:41 | ||
Есть подозрение, что экспортировать их надо следующим образом:
Не сильно понятное описание ошибки, телепатировать сложно... ![]() Вообще, для тематических вопросов есть соответствующий раздел форума ![]() Не за что ![]() microxa, действительно, достаточно ценное замечание, использование try...except...end сильно упростит жизнь во многих случаях... |
Автор: Riply 7.12.2008, 18:12 | ||
А можно меня ткнуть носом в тот потолок, с которого взялись все (без исключения) константы в напильнике ? ![]() P.S. На всякий случай оговорюсь: я не утверждаю, что что-то "не так", просто не умею воспринимать любые магические числа в коде, акромя разве что нуля и единицы ![]() |
Автор: bems 7.12.2008, 18:37 |
ну как минимум там добавляется not pageable Добавлено через 5 минут и 9 секунд и еще discardable |
Автор: ama_kid 7.12.2008, 18:58 |
Больше похоже что просто копируются значения, генерируемые мелкомягким линкером. Потому что если этот напильник натравить на нормальный драйвер - эти магические числа равны содержащимся в образе... |
Автор: Riply 7.12.2008, 20:07 | ||
Если это так (просто копируются), то где гарантии, что и у другого драйвера в образе тоже самое ? Или под другим SP-ом или под другой версией Win ? |
Автор: ReInit 8.12.2008, 00:15 | ||
Riply, Если ты про характеристики секций, то это комбинации констант IMAGE_SCN_... http://www.wasm.ru/article.php?article=green2red02
|
Автор: Riply 8.12.2008, 01:22 |
Да я про все, ибо если мне вместо SizeOf(BYTE) напишут 1, то я перестану понимать ![]() Хотя, себе любимой, иногда такое и позволяю ![]() Во. Уже что-то. Спасибо ![]() |
Автор: microxa 8.12.2008, 02:42 | ||||||||||||
Приветствую адептов столь продвинутой "дельфийской магии" ![]()
Насчет констант - мне просто не хотелось перегружать прогу километровыми PAGE_EXECUTE_READ or IMAGE_SCN_MEM_NOT_PAGED и тп. (имеються в модуле windows) Напильник пришлось немного доработать в области "фиксатора" import table:
(гм.. FirstThunk OriginalFirstThunk взяты из описания структур PE формата из windows.h) Переделаные таким образом DLL, работают (по моему опыту) ничуть не хуже слинкованых мc-линкером. Ну и какбы в качестве примера.. Одним из возможных применений драйвера - вызов Ring0 процедур непосредственно в самой программе. Понятное дело - трюк связан с определенным риском, и расчитан на "железячников" более менее знакомых с архитектурой PC/х86 (или желающих изучать это дело). В режиме ядра снимаються проблемы только с операциями in/out и доступом к привелигированиым регистрам и инструкциям CPU. Это в ряде случаев может оказаться недостаточным, например для обращения к физическим адресам памяти, что потребует вызова системных ф-кций. В противном случае несогласование этого вопроса с системой, может приведет к bsod (и даже SEH не пикнет)))... Пример простого драйвера:
Собираеться в любой среде (IDE delphi/CMD32), главное в папку с проектом драйвера не забыть поместить облегченные версии RTL. Для упрощения, здесь адрес процедуры передаеться в драйвер в качестве параметра в DeviceIOControl, а так как user-space программа, экспортировать функции ядра не может, между драйвером и программкой будет некоторый общий интерфейс:
Тестовый пример "хелловорлда" работает исключительно в режиме консольного fullscreen'а (80x25), поскольку маппит район $B8000 (видеопамять текстового режима, стандарт времен царя гороха))). /added Riply, спасибо! благодаря Вашему замечанию теперь я буду поступать так ![]()
Для полноты художественной картины Осталось от DeviceIoControl избавиться (в нем куда больше странностей, касаемо нюансах в параметрах ;) ) Шерлок Холмс был прав, насчет того что голова не чердак, и не стоит ее захламлять! ![]() ммм... даже интересно стало прикрутить к драйверу IRP_MJ_READ/ IRP_MJ_WRITE |
Автор: Riply 8.12.2008, 03:26 | ||
microxa, Здравствуйте ![]()
Вот скажи-ка мне честно: неужели, ты в следующей строчке сразу понимаешь, что и как создается ? Если да, то не кажется ли тебе, что уж лучше "перегружать прогу километровыми константами", чем свой мозг ? ![]() P.S. Кстати, Шерлок Холмс сравнивал человеческий мозг с чердаком, на котором не так уж и много места ![]() |
Автор: ReInit 8.12.2008, 13:02 | ||
Riply,
Шерлок Холмс (точнее Конан Дойль) заблуждался... |
Автор: EagleXK 18.1.2009, 17:08 |
Хмм... Странно, но Ctrl+Alt+Del не перехватился на XP SP2... |
Автор: Riply 21.2.2009, 18:26 |
День добрый ! ama_kid, а ты не мог бы дать файлы sysinit.pas и system.pas, которые послужили "прототипом" для dcu-шек ? Ну и командную строчку, с которой собирались ![]() |
Автор: bems 21.2.2009, 22:50 |
Riply, в командной строке нужен ключ -Y Это работало в 7ке. |
Автор: jungle 16.6.2009, 12:10 |
Добрый день! Можно ли использовать 64-битовый линкер для сборки 64-разрядного драйвера? |
Автор: Alexeis 16.6.2009, 12:36 | ||
Разве он проглотит 32х разрядные бинарники? Кроме того как работать с указателями, ведь они 32х разрядные. Так что скорее всего нельзя. |
Автор: jungle 16.6.2009, 14:59 |
OK. И еще вопрос, если позволите, многоуваемый All. Автор статьи говорит об отсутствии возможности применить блок try-except-end, но вот в сырцах драйвера тов. microxa для считывания физ. памяти этот блок присутствует. Причем, microxa заявляет о нормальной компиляции и т.д. И кто не прав? |
Автор: Alexeis 16.6.2009, 15:12 |
Есть такие соображения, что если код драйвера вызывается при обработке прерывания, то аппаратное исключение может не сработать ведь его приоритет может оказаться более низким, чем у кода обработчика прерывания, вот он и будет ждать окончания работы. |
Автор: Riply 16.6.2009, 15:17 | ||
Присутствует - не означает работает. Надо у него спросить на чем основано его утверждение о "нормальной компиляции". На том, что успешно создается sys - файл ? Если так, то этого мало. Код ama-kid`а тоже будет успешно собираться и компилироваться с try/finally блоками. Но вот если "перехватить" жалобные крики dcc32.exe в процессе компиляции, ![]() то там можно увидеть примерно следующие строки: Shd_QuerySys.obj: error LNK2001: uresolved external symbol @@HandleFinally$qqrv |
Автор: Alexeis 16.6.2009, 15:20 | ||
Это уже ошибка линкера. Но ведь линкер используется другой, может он подставляет туда то что нужно из RTL. |
Автор: Riply 16.6.2009, 15:30 | ||
Угу. Перепутала. Sorry.
Полностью согласна. Именно поэтому я и написала, что "Надо у него спросить на чем основано его утверждение" ![]() А без его пояснений, нам остается только гадать на кофейной гуще ![]() |
Автор: Акакий 6.7.2009, 13:31 | ||
Всем здравствуйте! Пишу (точнее - пытаюсь написать ![]() Перехватываю ZwMapViewOfSection. Новая функция NewZwMapViewOfSection при выполнении некоторых условий должна изменять содержимое памяти, возвращенное "оригинальной" ZwMapViewOfSection. Код замещающей функции:
В данном примере изменение памяти происходит в случае, если ее 2-й байт равен 81 (буква "Q"). Открываю блокнотом текстовый файл, начинающийся с "QQQQQ....". Блокнот запускается после чего виндовоз уходит в перезагрузку. Если убрать присваивание BaseAddress:=pp, то перезагрузки не происходит. Я не делаю там Unmap(BaseAddress), т.к. это отладочный вариант. Попытка воспользоваться ZwAllocateVirtualMemory не помогла. Пожалуйста, объясните мне, как нужно правильно делать. P.S. Сильно подозреваю, что в моем коде, мягко выражаясь, много неправильного, а более точно - там вообще один бред :( Но все-равно я верю в то, что найдется добрый человек и укажет мне на мои ошибки ![]() |
Автор: Riply 6.7.2009, 18:38 | ||||
Т.е. при любом вызове ZwMapViewOfSection (кем угодно !) она с вероятностью 1/256 возвращает бред, а мы удивляемся почему падает система ? ![]() Т.е. при отладке память бесконечна ? ![]()
Осознание сего факта - большой шаг вперед (без смайлика) Для того чтобы перехватывать, надо очень хорошо понимать и чуствовать как работает перехватываемая функция. Какие, где и когда будут последствия от твоего изменения. У тебя этого понимания пока нет. И двумя, тремя словами его не дать. Так что, садись за книги. |
Автор: Акакий 6.7.2009, 20:18 | ||||||
Riply, благодарю за отзыв.
Да это я знаю, просто это "сиюминутный" отладочный вариант. Ну добавлю я анализ хендла на принадлежность его блокноту - это ничего не изменит. Я гружу драйвер, открываю блокнотом все файлы, начинающиеся не с "QQQQQ..." и все прекрасно открывается, но как только я открою "QQQQQ..." - сразу reboot. Сделал перехват ZwRead/WriteFile, все прекрасно работает(происходит расшифровка/шифрование "на лету"). А блокнот пользуется "маппингом" вот и встала задача перехвата ZwMapViewOfSection.
См. выше ![]()
Ну читал я все эти книги (заранее принимаю аплодисменты, но все же не верю в свою безнадежность ![]() Перехватом я занимаюсь уже давно, только вот в ринг 3. В ринг 0 кодил, испрользуя Call Gate (шлюз вызова в GDT). |
Автор: Riply 6.7.2009, 21:22 |
Так. Давай с самого начала. Ты спрашиваешь почему "виндовоз уходит в перезагрузку". Я дала тебе (для начала) пару причин, по которым это может происходить. 1. При "заргузке блокнота" ZwMapViewOfSection может вызываться отнюдь не один раз и не только для чтения содержимого твоего файла. Откуда у тебя может быть уверенность, что при загрузке какой-нибудь библиотеки, ты не возвращаешь мусор вместо ее образа ? 2. ZwMapViewOfSection может быть вызвана не только блокнотом, а кем угодно (Система живет и работает). Откуда у тебя уверенность, что в течении твоего опыта она не вызвалась 567 раз и суммарный объем выделенной тобой памяти при этом не привысил максимально возможный ? (Кстати и здесь существует вероятность возврата мусора вместо образа) Мне кажется, что сначала надо исключить возможность перезагрузки по любой из этих причин, и только потом идти дальше. |
Автор: Акакий 6.7.2009, 22:09 | ||
1. Сделал проверку на принадлежность хэндла процессу блокнота. 2. Заменил символ "Q" уникальной комбинацией из 100 символов (сильно сомневаюсь, что данная последовательность байтов может быть где-то еще). Результат тот же. Уточню свой вопрос: параметр BaseAddress функции ZwMapViewOfSection содержит адрес переменной, в которую ZwMapViewOfSection засылает адрес спроецированного объекта (в моем случае - файла) или ZwMapViewOfSection шлет этот адрес непосредственно в переменную BaseAddress (Var BaseAddress)? Мелкомягкие пишут, что BaseAddress - это "Pointer to a variable that receives the base address of the view". Значит, вроде как BaseAddress должен указывать на переменную с адресом. Да и у Рема в его NativeAPI.pas этот параметр идет без "var". Только вот я пробовал оба варианта - результат одинаковый - при попытке подменить BaseAddress, или записать по адресу BaseAddress новый адрес "спроецированной" памяти виндовоз уходит в ребут. |
Автор: Riply 6.7.2009, 23:13 | ||||||
В данном вопросе я их слушаюсь и все работает ![]()
Устрани и остальные скользкие моменты:
у меня нет уверенности в том, что при любом вызове, ViewSize <> nil, BaseAddress <> nil, BaseAddress^ <> nil. Так же у меня нет уверенности, что ViewSize^ < максимальный размер памяти, который мы можем выделить. А почему тогда сначала не оттестировать перехват в user mode ? (Тем более, если нет уверенности в правильном понимании параметров ф-ии ZwMapViewOfSection) |
Автор: Riply 7.7.2009, 00:06 | ||
А на основании чего мы считаем, что адрес, полученный нами через MmAllocateNonCachedMemory, может быть безболезненно передан NotePad`у ? |
Автор: Акакий 7.7.2009, 06:22 | ||||
Хорошая мысь, спасибо, нужно попробовать. В user mode я перехватывал MapViewOfFile/Ex, но блокнот ее не вызывает, что есть несколько странно(блокнот пользуется только функциями Zw?). Попробую перехватить ZwMapViewOfSection из ring3, но у меня странное предчуствие, что из блокнота она не будет вызываться.
Это да, при выделении памяти в драйвере я получаю память в сегменте данных нулевого кольца(селектор с RPL=0), а в блокноте, скорее всего, имеем 3-е кольцо (RPL=2) и может получаться некорректное смещение относительно базы сегмента данных блокнота. Пробовал делать ZwAllocateVirtualMemory, но она не работает, по крайней мере при вызове из ring 3. |
Автор: Акакий 7.7.2009, 10:29 |
Перехватил ZwMapViewOfSection в ринг 3. Блокнот вызывает эту функцию, но использует ее только для вывода списка файлов, а сами файлы он читает с помощью чего-то другого. Интересно, при перехвате этой функции в ринг 0 она вызывается и при загрузке блокнотом файлов. Отсюда и происходят мои сомнения о корректности перехвата Zw функций в ринг 3. Кстати, на форуме rsdn, кто-то "жаловался", что от блокнота не приходит IRP_MJ_READ. |
Автор: Акакий 7.7.2009, 13:40 | ||
Все, разобрался. ![]()
Riply, спасибо. |
Автор: Акакий 20.7.2009, 15:04 |
Хочу выразить огромную благодарность ama_kid за то, что он бескорыстно делится с нами своими знаниями и своим трудом. Уважаемый ama_kid, если бы Вы еще написали статью о создании драйвера виртуального диска, то это была бы такая Вещь, переоценить которую было бы просто невозможно. Во всем нете нет ни одной подобной статьи. Все только для С++, а с этого, осмелюсь заявить, кривого языка, у меня не получается переводить на Delphi. Товарищи, да здравствует Delphi+Assembler! |
Автор: Акакий 11.2.2010, 13:52 |
Ура! После долгих изысканий, изучений и мучений я все-таки сделал драйвер виртуального диска! Жалко только, что драйвер этот я сделал на С++. Итого, со всей ответственностью могу заявить, что сделать драйвер диска на Delphi в принципе невозможно, для этого, как минимум, прийдется переводить ПОЧТИ ВСЕ сишные библиотеки на Delphi. Точнее - теоретически написать драйвер на Delphi можно, но только теоретически(примерно так же, как и создать машину времени). Конечно, статья, написанная ama_kid может пригодиться для начального ознакомления "дельфистов" с драйверами. Но, к сожалению, в практическом плане статья эта абсолютно бесполезна. Все то, что могут делать "драйвера", приведенные в этой статье(и вообще, написанные на Delphi), я смогу сделать на Delphi через Call Gate безо всяких драйверов. Модератор: Акакий сможет продолжить сообщать неправдивые сведения не раньше чем через два дня. |
Автор: FalseMaster 1.2.2011, 03:31 |
Модератор: Сообщение скрыто. |
Автор: birks 13.11.2011, 15:58 |
А как с помощью данного примера отправить допустим нажатие клавиши f1 ? |
Автор: FalseMaster 24.8.2013, 20:32 |
Модератор: SDK не SDK, а тема не создана для ссылок |