Створення VxD на Visual C + + без асемблерних модулів, Різне, Програмування, статті

Віртуальні драйвери пристроїв (VxD) в Windows у багатьох випадках є єдиним “чесним” способом обходу обмежень, встановлених системою для додатків Win32: неможливість прямого доступу до портів вводу-виводу і службової пам’яті, ефективної обробки апаратних переривань, використання сервісних функцій існуючих VxD і т.п. Крім того, без VxD не обходиться практично жоден повноцінний драйвер фізичного або віртуального пристрою. Документація Microsoft і різні керівництва по створенню VxD вимагають, щоб розробка велася на мові асемблера. У той же час єдиною офіційно підтримуваної системою розробки є Microsoft Macro Assembler (MASM), синтаксис мови якого, а також надійність та зручність транслятора з початку 80-х років залишають бажати кращого. Набагато більше зручним засобом розробки є Borland Turbo Assembler (TASM), особливо його режим Ideal, проте поставляються Microsoft включаються файли, що містять необхідні для драйвера визначення, мають безліч специфічних для MASM (і, чесно кажучи, з біса потворних) конструкцій, що робить ці файли непридатними для трансляції за допомогою TASM.

Насправді ж використання мови асемблера для розробки закінчених програм під Windows абсолютно безглуздо. Будь-яка з систем Windows вносить в роботу комп’ютера просто жахливі (сотні-тисячі відсотків) накладні витрати, на тлі яких економія кількох десятків кілобайт або декількох тисяч тактів процесора на всю програму представляється краплею в морі. Виняток становлять лише окремі, дуже невеликі фрагменти програм, що виконуються в реальному часі сотні / тисячі разів в секунду, де економія навіть декількох десятків тактів дає відчутне зростання ефективності. Такі фрагменти зазвичай оформляються у вигляді зовнішніх асемблерних модулів, або асемблерних вставок в мовах C / C + +.


Microsoft не підтримує засобів розробки VxD на “чистих” C / C + +; набір для розробки драйверів (DDK) для Windows 95 містить деякі файли для створення окремих модулів на цих мовах, проте “кістяк” VxD, за задумом Microsoft, повинен будуватися на мові асемблера. Сторонні фірми випускають бібліотеки, що полегшують розробку VxD на C і C + + – VtoolsD/DriverStudio (NuMega), VxDWriter (TechSoft Pvt.), DriverX (Tetradyne Software) тощо, однак кожна з цих бібліотек побудована за певним принципом і накладає на програміста ряд не завжди зручних для нього правил.


У той же час розробка VxD на “чистих” C / C + + не представляє абсолютно ніякої складності, якщо трохи розібратися в структурі VxD і того коду, який створюється компіляторами. Більш того, 32-розрядні середовища Microsoft Visual C++ спочатку мають можливість побудови модулів VxD засобами самого середовища, не вдаючись до зовнішніх транслятора або утилітам.


Компілятор MS VC + + версії 4.1 містив помилку, не дозволяла будувати правильні VxD, від чого поширилася думка, ніби це неможливо в принципі. Однак версії 4.0, 4.2, 5.x і 6.x можуть бути успішно використані для створення VxD без виходу із середовища розробки.


Єдине, що неможливо зробити повністю на Visual C++ – Це побудувати VxD, який використовує 16-розрядний код, який 32-розрядний компілятор Visual C++ створювати не здатен. 16-розрядний код необхідний у процесі ініціалізації системи у фазі реального режиму (real mode), а також при розміщенні фрагментів коду всередині віртуальних машин V86. В цьому випадку потрібне підключення зовнішніх асемблерних модулів, що транслюються за допомогою MASM або TASM, однак основна частина драйвера все одно може бути зроблена в системі Visual C++.


У даній статті розглядаються питання розробки VxD на “чистому” C + +, в середовищі MS VC + + 4.2, вільної від згаданої помилки компілятора.


Стаття ні в якій мірі не претендує на повний опис питань розробки VxD. Тут дано лише основні відомості, що дозволяють зробити працездатний VxD “з нуля”.


Документацію з Windows DDK можна знайти в онлайновій бібліотеці Microsoft.


Основні властивості і особливості драйвера VxD


Сенс і призначення драйвера


VxD розшифровується як Virtual x Driver – Драйвер віртуального пристрою x. Оскільки Windows побудована на концепції віртуальних машин, кожній віртуальній машині потрібно надати власний “образ” кожного з наявних в системі пристроїв. Наприклад, віртуальний драйвер клавіатури VKD одноосібно управляє роботою фізичного пристрою – клавіатури, отримуючи все переривання при натисканні клавіш, включаючи / вимикаючи індикатори і т.п. Кожна з віртуальних машин – системна, в якій працюють програми Windows, і вікна DOS “бачать” тільки незалежні копії фізичного пристрою, кожна з віртуальних машин може вважати, що має в своєму користуванні повноцінний фізичний пристрій.


Поняття віртуалізації пристроїв (реальних чи штучних, віртуальних) означає лише те, що робота кожної віртуальної машини з цим пристроєм знаходиться під контролем драйвера пристрою. Найпростіший прийом віртуалізації – заборона іншим віртуальним машинам звертатися до регістрів і функцій пристрою, поки воно використовується “захопила” його віртуальною машиною. Таким чином віртуалізуються, наприклад, послідовні (COM) порти.


Більш складний і зручний для користувача вигляд віртуалізації – впорядкування доступу до пристрою, як це робиться для відеоадаптера в повноекранному режимі. Режим адаптера, стан екрану і інші параметри запам’ятовуються драйвером для кожної віртуальної машини, і відновлюються при перемиканні адаптера з однієї машини на іншу.


Найбільш складною і зручною є повна віртуалізація, яку можна спостерігати на прикладі роботи додатків DOS у вікнах Windows. При цьому кожна віртуальна машина DOS “бачить” практично повноцінний апаратний відеоадаптер, може звертатися до його регістрів, безпосередньо працювати з відеопам’яттю і т.п.; VxD, що створює цей образ, коректно обробляє всі стандартні види звернень, а стан відеопам’яті відображає у вікні Windows заданого розміру.


Крім свого основного призначення – віртуалізації пристроїв для віртуальних машин – VxD виконують в Windows безліч інших функцій. Можна сказати, що VxD в Windows 9x реалізує поняття “службовий привілейований процес “- з його допомогою реалізуються практично всі завдання, які неможливо коректно виконати за допомогою звичайного застосування або DLL. При відсутності для будь-якого апаратного пристрою стандартного системного уявлення (наприклад, вимірювального адаптера вузького застосування) для нього розробляється VxD, за допомогою якого додатки можуть отримати доступ до функцій пристрою, не заважаючи при цьому один одному.


Всі VxD в Windows управляються головним системним VxD – диспетчером віртуальних машин (VMM – Virtual Machine Manager). VMM надає основний набір сервісних функцій, за допомогою яких інші VxD виконують необхідні їм операції.


Ім’я та ідентифікатор драйвера


Кожен VxD в системі повинен мати ім’я (Name) і ідентифікатор (Id). Ім’я драйвера (пристрої) складається з восьми-менш символів; воно часто збігається з ім’ям файлу драйвера, однак це не обов’язково. Ідентифікатор драйвера являє собою 16-розрядне число, присвоєне Microsoft даного драйверу (власним або створеному сторонніми розробниками).


За ідентифікатором можливо однозначне знаходження драйвера в системі, що виключає колізії в іменах. Драйвери, що не мають офіційно присвоєного ідентифікатора, використовують нульовий ідентифікатор, від чого їх пошук можливий тільки по імені. Оскільки ім’я вибирається розробником – можливі збіги і, отже, – колізії при пошуку та обігу, тому рекомендується вибирати нетривіальні імена для драйверів, які будуть працювати “на всю систему”, а не використовуватися локально окремим додатком.


Драйвери з ненульовими ідентифікаторами вважаються глобальними, відомими всім, і тому можуть бути завантажені лише одного разу. Драйвери з нульовим ідентифікатором вважаються локальними, призначеними для приватного використання, і можуть завантажуватися будь-якими додатками довільне число раз.


Драйвери, що не мають ідентифікаторів, не можуть підтримувати механізм сервісних функцій. Розробник VxD може самостійно вибрати для свого драйвера ідентифікатор з числа вільних, якщо використання драйвера планується на обмеженій кількості комп’ютерів. Випуск в широке користування драйверів з “самостійних” ідентифікаторами не рекомендується.


Статичні і динамічні драйвери


За способом завантаження VxD поділяються на статичні – Файли один раз в процесі старту Windows і працюють до її закриття, і динамічні – Файли і вивантажується за запитом системи та програм. Динамічні VxD використовуються в тих випадках, коли постійна присутність драйвера в системі необов’язково, однак із зрозумілих причин вони не можуть брати участь в початковій ініціалізації системи. Статичні VxD можуть брати участь в ініціалізації системи, однак не можуть бути вивантажені в процесі її роботи.


Порядок завантаження статичних драйверів


Статичні драйвери завантажуються системою в певному порядку при цьому основні драйвери повинні завантажуватися першими, а після цього – залежні від них драйвери більш високого рівня. Для цієї процедури кожен драйвер має параметр Init Order – Числову константу, визначальну місце драйвера в списку завантаження, яка відбувається в порядку зростання значень параметра. Системним драйверам призначені певні значення, що відображають їх залежність один від одного. Якщо порядок завантаження не має сенсу – використовується нульове значення параметра; такі драйвери завантажуються після завершення ініціалізації “номерних” VxD.


Системні повідомлення драйверу


Система взаємодіє з драйвером шляхом передачі йому системних повідомлень про завантаження / розвантаження драйвера, а також при настанні певних системних подій, які можуть зажадати втручання драйвера (створення / видалення віртуальної машини, додатки, завдання (thread), зміна поточної віртуальної машини / завдання, перезавантаження системи, поява нового пристрою і т.п.).


Для обробки системних повідомлень кожен драйвер повинен містити диспетчер повідомлень – Процедуру, яка отримує управління при передачі повідомлення драйверу. Процедурі передається код повідомлення і його параметри, після завершення обробки процедура повертає результат, який визначає наступні дії системи.


Сервісні функції драйвера


Кожен драйвер, який має ідентифікатор, може підтримувати набір сервісних функцій, Доступних для інших VxD в системі. Якщо VxD представляє якесь віртуальне пристрій, ці функції служать для управління пристроєм або просто втілюють будь-які операції, заради яких створювався драйвер.


Сервісні функції драйвера мають номери починаючи з нуля, за якими їх можуть викликати інші VxD. Драйвер надає системі таблицю адрес процедур – обробників функцій, звернення до яких відбувається шляхом виклику процедури по індексу з таблиці.


Обов’язкова для підтримки лише функція з нульовим номером – Get Version (запит версії). Підтримка та призначення інших функцій залишена на розсуд розробника драйвера.


Механізм виклику сервісних функцій використовує ідентифікатор драйвера, за яким відбувається пошук потрібного драйвера в системі. Тому драйвери, що не мають ідентифікатора, не можуть бути викликані для обробки сервісних функцій.


Інтерфейс з прикладними програмами


Для взаємодії з прикладними програмами VxD може надавати три види API (Application Program Interface – інтерфейс прикладних програм):



Для обробки запитів від 16-розрядних програм в драйвері передбачаються дві різні функції – для запитів від віртуальних машин DOS і для запитів від додатків Win16. Запити від додатків Win32 передаються у вигляді системних повідомлень їх загального диспетчеру.


16-розрядні програми отримують доступ до своїх API допомогою функції 0x1684 програмного переривання 2F, Яка повертає адресу шлюзу (gate) для виклику VxD. Пошук потрібного драйвера можливий як за ідентифікатором, так і по імені; пошук по імені був введений пізніше, тому документований не у всіх описах функції int 2F.


При виконанні виклику через шлюз відбувається перемикання в 32-розрядний режим із збереженням всіх регістрів викликала віртуальної машини (клієнта) у спеціальній структурі, після чого управління передається відповідній процедурі обробки в VxD. При поверненні відбувається відновлення значень регістрів, які можуть бути модифіковані процедурою обробки.


Додатки Win32 отримують доступ до API за допомогою функції CreateFile, Що завантажує VxD, якщо він динамічний, і відкриває його інтерфейс. Пошук драйвера відбувається на ім’я драйвера, імені його файлу або по імені ключа реєстру, що описує драйвер.


Звернення до обробника Win32 API в драйвері відбувається при виклику додатком функції DeviceIoControl. При цьому виконується перемикання в режим ядра і передача диспетчеру системних повідомлень драйвера повідомлення W32_DEVICEIOCONTROL. Для обміну даними передаються два незалежних покажчика (буфер параметрів і буфер результату), які можуть посилатися і на одну і ту ж область пам’яті. Якщо драйвер підтримує механізм асинхронних (overlapped) операцій, фактичне завершення операції може відбуватися незалежно від моменту повернення управління з диспетчера.


Структура і функціонування драйвера


VxD являє собою 32-розрядний виконуваний файл формату LE (Linear Executable), який є окремим випадком DLL. Система може викликати VxD трьома різними способами:



  1. Через диспетчер системних повідомлень.

  2. Через таблицю обробників сервісних функцій.

  3. Через точки входу інтерфейсів прикладних програм.

Функції драйвера можуть також викликатися в результаті запиту самого драйвера – у відповідь на переривання від пристроїв, виклик перехоплених функцій або програмних переривань, при настанні різних подій і т.п.


Секції файлу драйвера


Завантаження драйвера стандартним чином може ділитися на секції (Сегменти) з різними атрибутами (резидентний, спочатку завантажений, знищуваний і т.п.). Документація Microsoft стверджує, що секції повинні мати певні імена, однак це зроблено лише для зручності користування стандартним макросами і фактично система розпізнає лише самі атрибути секцій.


Рекомендовані наступні імена і класи секцій для модуля VxD:



Блок описувача драйвера


Ключовим елементом драйвера є структура даних DDB (Device Descriptor Block – блок описувача пристрої), що описує параметри драйвера. DDB містить такі найважливіші поля:



Символічне ім’я, призначене DDB, повинно бути описано в модулі драйвера в якості першої експортованої точки входу (exported entry). Сам DDB повинен знаходитися в одній секції резидентного коду разом з диспетчером системних повідомлень.


Контексти


Контекстом називається стан процесора і системи, що відбиває стан будь-якої віртуальної машини, додатки або завдання. До контексту відноситься стан регістрів процесора, стека, віртуального адресного простору, системних таблиць і т.п. При перемиканні з задачі на задачу, з одного віртуальної машини на іншу відбувається зміна контексту – збереження колишнього та завантаження нового.


Оскільки VxD не є повноцінним системним процесом і не має власного контексту, його виклик завжди відбувається в контексті будь-якої віртуальної машини (системної або DOS). Якщо виклик відбувається в контексті системної віртуальної машини, то має місце також контекст поточного додатки, а також завдання (thread), якщо поточним є додаток Win32.


При виклику VxD система просто перемикає поточний стек і режим виконання, після чого передає управління відповідної функції VxD. Це економить час на перемикання, однак накладає на драйвер певні обмеження по поведінці в поточному контексті. Наприклад, постійно доступною є тільки інформація в системній області пам’яті – змінні самого VxD, системні таблиці, інші VxD тощо; відображення інших областей пам’яті, як правило, змінюється при зміні контексту.


Виклик драйвера, що відноситься до певної віртуальної машині або завданню, завжди відбувається в момент роботи цієї віртуальної машини / завдання; в таких випадках контекст називається певним, Або невипадковим (non-arbitrary context). Виклик, ініційований зовнішньої причиною – перериванням або іншою подією, може трапитися в момент роботи довільної віртуальної машини / завдання; в цьому випадку контекст називається невизначеним, або довільним (arbitrary context).


Доступ до пам’яті


В контексті програми Win32 або віртуальної машини DOS драйвер має прямий доступ до їх адресного простору. Для віртуальних машин вимагається лише приведення 16-розрядних адрес типу “сегмент: зсув” до лінійних, розташованим в межах першого мегабайта 32-розрядного адресного простору.


У контексті 16-розрядного програми Windows подібної “прямої видимості” немає, і для прямого доступу до даних додатка необхідно виконати відображення (Mapping) фрагментів 16-розрядного адресного простору програми в “плоске” (flat) 32-розрядне адресний простір драйвера. В результаті цієї операції в області системних адрес створюються сторінки, відображені на ті ж адреси фізичної пам’яті, що і задані адреси 16-розрядного програми.


Після завершення роботи з даними відображення необхідно припинити (unmap), щоб звільнити створені в системній області сторінки.


Оскільки системна область (system arena) доступна для читання всіх програм Win32, вони можуть зчитувати локальні дані VxD по повернутим їм вказівниками. Однак доступ додатків до системою області пам’яті в загальному випадку не рекомендується.


Повторна входимость


VMM в загальному випадку не є повторно-входимого (Реєнтерабельним) модулем. Функції VMM діляться на асинхронні, Які можуть бути викликані в будь-який момент (навіть всередині обробника переривання), і звичайні, Які можуть викликані лише всередині “вертикального” потоку управління, коли управління передається строго зверху вниз, без рекурсій.


Більшість функцій VxD, що викликаються ззовні, не вимагає забезпечення повторної вкладеності. Однак виклики функцій, що відбуваються в результаті асинхронних подій (зазвичай це переривання), можуть накладатися, якщо обробка попереднього виклику не встигла завершитися або реалізована некоректно. Тому при проектуванні VxD, що мають справу з асинхронними подіями, цьому питанню необхідно приділяти особливу увагу.


Завантаження, робота і вивантаження драйвера


Всі VxD завантажуються в системну область пам’яті (system memory arena), що починається з адреси 0xC0000000. Відразу після завантаження статичному драйверу – повідомлення DEVICE_INIT; динамічному драйверу передається повідомлення SYS_DYNAMIC_DEVICE_INIT.


Послідовність передачі повідомлень при ініціалізації статичного драйвера насправді трохи складніше; точний опис процесу можна знайти в документації DDK.


Фаза ініціалізації драйвера зазвичай складається в установці початкових значень змінних, запиті робочих областей пам’яті, налаштування режимів роботи пристроїв, призначення векторів переривань, каналів DMA і т.п.


Після відпрацювання фази ініціалізації драйвер може бути викликаний будь-яким з передбачених способів за запитами від системи та / або додатків. Передані системні повідомлення відображають відбуваються в системі події.


При розвантаженні динамічного драйвера він отримує повідомлення SYS_DYNAMIC_DEVICE_EXIT, за яким зазвичай виконується звільнення використовуваних областей пам’яті, закриття об’єктів, деактивація керованих пристроїв і т.п. Після відпрацювання цього повідомлення динамічний драйвер видаляється з пам’яті.


Відповідальність за коректну “чистку” перед вивантаженням динамічного драйвера покладено на його розробника. Система не в змозі перевірити, чи дійсно видалені всі посилання на об’єкти драйвера; якщо, наприклад, драйвер зробив запит на таймерні чи іншу подію, після чого був вивантажений і не анулював цього запиту – при настанні події VMM спробує викликати задану процедуру обробки, яка до цього часу вже не існує, що призведе до непередбачуваних наслідків, аж до повного зависання системи.


 

Особливості розробки VxD на C + +


Середа виконання


Середа виконання (Working environment) драйвера VxD сильно відрізняється від середовища, в якій виконуються програми Win32. VxD працюють в режимі ядра операційної системи на нульовому рівні привілеїв (ring 0), на відміну від додатків, що працюють на рівні 3. Для VxD в загальному випадку недоступні функції Windows API; замість них необхідно користуватися спеціальними сервісними функціями VMM та інших VxD.


Стандартні бібліотеки


Наявність спеціалізованої середовища виконання означає, що VxD, написаний на C або C + +, в загальному випадку не може користуватися функціями стандартної бібліотеки виконання (RTL). Можливе використання лише тих функцій, які завідомо не містять посилань до Windows API. Наприклад, в VxD можливе використання функцій strlen, strcpy, strset або strcmp, Однак функція strcoll в Visual C + + містить звернення до API, щоб визначити параметри мови, і тому для використання в VxD не годиться. Те ж відноситься до функцій sprintf, time і багатьом іншим. Серед сервісних функцій VMM міститься чимало аналогічних за змістом, але відмінних за форматом виклику і схемою роботи операцій.


Допоміжні функції (wrappers)


Оскільки багато сервісні функції VMM та інших VxD призначені для виклику на мові асемблера, при цьому отримують параметри і повертають результати в регістрах і прапорах процесора, їх безпосереднє використання в C + + утруднено. В таких випадках часто робляться невеликі допоміжні функції, звані обгортками (Wrappers), єдиним завданням яких є перенесення параметрів зі стеку в регістри, виклик потрібної сервісної функції та повернення результату стандартним для C + + способом.


Обгортки для ряду часто використовуваних сервісних функцій VMM та інших стандартних VxD визначені у відповідних включаються файлах з DDK – VMM.H, VTD.H, SHELL.H тощо, а також у файлі VXDWRAPS.H. Написання решти надається розробнику VxD. В кінці статті наведено приклад написання обгортки SelectorMapFlat.


Перед включенням файлів необхідно визначити символічне ім’я WANTVXDWRAPS, Інакше будуть визначені тільки константи і типи, але не самі функції.


Функції, що викликаються ззовні


Деякі внутрішні процедури VxD, оформлені у вигляді функцій C + +, повинні викликатися системою (Callback). До них відносяться, наприклад, диспетчер системних повідомлень, обробники сервісних функцій, переривань, подій і т.п. Угоди про зв’язки в таких викликах часто розраховані на використання мови асемблера, коли параметри передаються в регістрах, а результат повертається в регістрах і / або прапорах процесора. В такому випадку стандартне оформлення функції, прийняте в C + +, може стати суттєвою перешкодою.


Розширення Visual C + + містить кваліфікатор __declspec (naked), Приміщення якого в заголовку обумовленою функції забороняє генерацію прологу / епілогу функції – початкової (збереження регістрів, установка вказівника кадру) і завершальній (відновлення регістрів, команда повернення) послідовностей команд. Результат компіляції буде містити тільки код, присутній в тілі функції в явному вигляді. Це дозволяє програмісту виконати потрібні дії в необхідній послідовності, однак накладає вимоги щодо дотримання внутрішніх правил мови. Зокрема, повинні бути збережені регістри EBX, ESI, EDI, EBP; При використанні параметрів в такій функції повинен бути явно встановлений покажчик кадру в регістрі EBP, А при використанні локальних змінних – зарезервовано достатню кількість байтів в стеку.


Для повернення з naked – функції в її тіло має бути явно поміщена команда _asm ret, Якщо тільки функція не використовує засобів начебто VxDJmp, які самі виконують повернення в функцію, з якої був зроблений дзвінок.


Неявні звернення до функцій підтримки


Visual C + + може генерувати неявні звернення до функцій із стандартної бібліотеки при використанні деяких конструкцій мови, наприклад виключень (Exceptions) і RTTI, Тому для застосування цих можливостей необхідно написання власної підсистеми їх підтримки в VxD.


При використанні віртуальних функцій компілятор призначає кожній чисто віртуальної (pure virtual) функції посилання на бібліотечну функцію _purecall. Це робиться для того, щоб відловити всі помилкові звернення до чисто віртуальної функції, для якої не визначена реальна функція-адресат. Стандартна функція _purecall виводить діагностичне повідомлення і завершує роботу програми, використовуючи Windows API; щоб зробити можливим застосування віртуальних функцій в VxD, необхідно в одному з його модулів визначити свій варіант:

int _purecall (void) {
return 0;
}

При бажанні можна помістити всередину тіла функції висновок діагностичного повідомлення засобами, доступними для VxD.


Експорт посилання на DDB


Експорт посилання на DDB за номером зазвичай прийнято робити в DEF-файлі, проте в Visual C + + для цього є зручний кваліфікатор __declspec (dllexport), Який досить помістити в заголовку визначення структури DDB.


Структура DEF-файлу для побудови VxD


DEF-файл для побудови VxD може містити опції VXD, DESCRIPTION і SECTIONS.


Опція VXD має вигляд:


VXD ім’я тип



Опція DESCRIPTION:


DESCRIPTION строка_опісанія



Опція SECTIONS:


SECTIONS


ім’я [CLASS “клас”] спісок_атрібутов



Установки компілятора і компонувальника


В установках компілятора в середовищі Visual C + + необхідно заборонити обробку виключень і RTTI, встановити однозадачной (single-threaded) стандартну бібліотеку (RTL). Для коректної компіляції включаються файлів з DDK, які призначені не тільки для VxD (наприклад, MMSYSTEM.H) Необхідно визначити (в установках препроцесора або в тексті програми) символічне ім’я Win32_VxD.


В установках компонувальника (Linker) необхідно прибрати всі бібліотеки Windows API, бо вони призначені тільки для стандартної середовища виконання. При використанні стандартних функцій-обгорток з DDK необхідно підключити бібліотеку VXDWRAPS.CLB з DDK.


У командному рядку компонувальника, яка відображається внизу вікна налаштувань, необхідно вручну додати опцію /VXD. Також потрібно внести в список файлів проекту DEF-файл, керуючий побудовою модуля і містить назву драйвера і опис використовуваних секцій (сегментів). У середовищі Visual C + + є можливість обійтися без використання DEF-файлу, вносячи всі необхідні опції в командний рядок компонувальника, проте це менш зручно, так як рядок швидко захаращується і стає важкою для сприйняття і редагування.


Параметри секцій


Компілятор Visual C + + за замовчуванням створює секції чотирьох типів:



В DEF-файлі цим секціях повинні бути приписані атрибути EXECUTE, PRELOAD (Як для класу резидентного коду LCODE).


При необхідності можна поміщати код і дані в інші секції за допомогою директив #pragma code_seg, #pragma data_seg і #pragma alloc_text, Приписавши їм необхідні атрибути. Це може знадобитися, наприклад, для виділення частини коду / даних, що використовуються тільки при ініціалізації або дозволених для відкачування (атрибут DISCARDABLE).


Бібліотечні функції також можуть бути “розкладені” по секціях з іншими іменами, тому при їх використанні необхідно стежити, щоб атрибути секцій відповідали їх призначенням і поведінки при роботі VxD.


Функції, імпортовані з бібліотеки VXDWRAPS.CLB, Використовують в основному секції _LTEXT і _LDATA.


Налагодження


Унаслідок роботи в спеціалізованій середовищі налагодження VxD за допомогою вбудованого відладчика середовища Visual C + + неможлива. Для повного налагодження драйвера найзручніше використовувати відладчик SoftICE фірми NuMega. SoftICE сприймає всю налагоджувальну інформацію, сгенерированную компілятором, так що при розробці VxD, як і звичайних додатків, можна використовувати налагоджувальний (debug) і крайовий (release) види проектів. Для вилучення налагоджувальної інформації з модуля драйвера SoftICE має в комплекті утиліту nmsym, Виклик якої зручно включати в список спеціальних дій (custom build) середовища Visual C + +.


Для поверхневої налагодження допомогою трасувань повідомлень (Out_Debug_String і т.п.) можна використовувати різні системні монітори – наприклад Monitor (Vireo Software), DbgTerm (TechSoft Pvt.) І будь-які інші, які перехоплюють системні налагоджувальні повідомлення та відображають їх у вікні. Наприклад, драйвер KbdFlip я налагоджував виключно за допомогою утиліти Monitor, Ні разу не вдавшись до SoftICE.


Monitor і DbgTerm також дозволяють завантажувати і вивантажувати динамічні VxD, причому Monitor робить це більш надійно. При розробці динамічних VxD можна використовувати Monitor і SoftICE разом: перший – для завантаження / розвантаження драйвера, другий – для налагодження. SoftICE перехоплює весь налагоджувальний потік Windows, так що виводяться повідомлення будуть видні тільки в ньому.


Загальна схема драйвера VxD


Мінімальний віртуальний драйвер повинен містити секцію резидентного коду, в якій розташовані блок описувача пристрої, диспетчер системних повідомлень і обробники сервісних функцій.Обработчік системних повідомлень аналізує код повідомлення, переданий в EAX, виділяє його цікавлять повідомлення, обробляє їх і повертає скинутий прапор CF в разі успіху, і встановлений – у разі невдачі. Для всіх необроблюваних повідомлень повинен повертатися скинутий прапор CF.Обработчікі сервісних функцій викликаються по таблиці, так що кожної функції відповідає власний обробник. Спосіб доступу до параметрів і повернення результатів визначається разработчіком.Прі необхідності драйвер може містити обробники запитів V86 і PM API. Для доступу до даних віртуальних машин DOS, покажчики на які можуть передаватися в регістрах при запиті, достатньо перетворити їх в лінійні 32-розрядні адреси, бо перший мегабайт адресного простору поточної віртуальної машини безпосередньо “видно” з VxD. Для доступу до даними додатків Win16 потрібно виконати відображення адрес за допомогою функції VMM _SelectorMapFlat.


Програмування VxD


Засоби розробки, що включаються файли і бібліотекіМінімально необхідний набір включаються файлів і бібліотек міститься в Windows 95 DDK (підкаталоги Inc32 і Lib). Звичайно потрібно включення хоча б файлів BASEDEF.H і VMM.H. Файли VXDWRAPS.H, CONFIGMG.H і деякі інші оформлені в стилі звичайної мови C, тому при включенні їх у файли типу CPP директиви #include необхідно поміщати всередину кваліфікаторов extern “C”:


extern “C” {
#include <vxdwraps.h>
}


Файли з DDK можна включати і в тексти модулів звичайних додатків, визначивши перед цим символічне ім’я Not_VxD. При цьому визначаються тільки корисні константи та типи, а визначення специфічних для VxD конструкцій відключається.


Структури, які зазвичай використовуються в VxD


VxD_Desc_Block – блок описувача устройстваОпісивает структуру DDB. Заповнюється статично, щоб до моменту завантаження драйвера всі поля мали потрібні значення.


ULONG DDB_Next;
USHORT DDB_SDK_Version;
USHORT DDB_Req_Device_Number;
UCHAR DDB_Dev_Major_Version;
UCHAR DDB_Dev_Minor_Version;
USHORT DDB_Flags;
UCHAR DDB_Name [8];
ULONG DDB_Init_Order;
ULONG DDB_Control_Proc;
ULONG DDB_V86_API_Proc;
ULONG DDB_PM_API_Proc;
ULONG DDB_V86_API_CSIP;
ULONG DDB_PM_API_CSIP;
ULONG DDB_Reference_Data;
ULONG DDB_Service_Table_Ptr;
ULONG DDB_Service_Table_Size;
ULONG DDB_Win32_Service_Table;
ULONG DDB_Prev;
ULONG DDB_Size;
ULONG DDB_Reserved1;
ULONG DDB_Reserved2;
ULONG DDB_Reserved3;



Client_Reg_Struc – структура пакета регістрів клієнта


Описує стан регістрів процесора в викликала віртуальній машині / додатку (клієнта).


ULONG Client_EDI;
ULONG Client_ESI;
ULONG Client_EBP;
ULONG Client_res0;
ULONG Client_EBX;
ULONG Client_EDX;
ULONG Client_ECX;
ULONG Client_EAX;
ULONG Client_Error;
ULONG Client_EIP;
USHORT Client_CS;
USHORT Client_res1;
ULONG Client_EFlags;
ULONG Client_ESP;
USHORT Client_SS;
USHORT Client_res2;
USHORT Client_ES;
USHORT Client_res3;
USHORT Client_DS;
USHORT Client_res4;
USHORT Client_FS;
USHORT Client_res5;
USHORT Client_GS;
USHORT Client_res6;
ULONG Client_Alt_EIP;
USHORT Client_Alt_CS;
USHORT Client_res7;
ULONG Client_Alt_EFlags;
ULONG Client_Alt_ESP;
USHORT Client_Alt_SS;
USHORT Client_res8;
USHORT Client_Alt_ES;
USHORT Client_res9;
USHORT Client_Alt_DS;
USHORT Client_res10;
USHORT Client_Alt_FS;
USHORT Client_res11;
USHORT Client_Alt_GS;
USHORT Client_res12;


Поля з іменами Client_xxx містять значення відповідних регістрів на момент звернення віртуальної машини або додатки до системної функції. В поле Client_Error може бути занесений код ошібкі.Для структури введений синонім типу (typedef) з ім’ям CRS. У файлі VMM.H визначені також допоміжні структури Client_Word_Reg_Struc і Client_Byte_Reg_Struc і об’єднання всіх трьох структур CLIENT_STRUCT.


DIOCParams – параметри запиту DeviceIoControl


DWORD Internal1;
DWORD VMHandle;
DWORD Internal2;
DWORD dwIoControlCode;
DWORD lpvInBuffer;
DWORD cbInBuffer;
DWORD lpvOutBuffer;
DWORD cbOutBuffer;
DWORD lpcbBytesReturned;
DWORD lpoOverlapped;
DWORD hDevice;
DWORD tagProcess;











GETVERSION (0) Відкривання і опитування інтерфейсу. Якщо драйвер не підтримує Win32 API, він повинен повернути в EAX ненульове значення. В іншому випадку в EAX повертається нуль, а якщо задано буфер результату, то в нього заноситься номер версії драйвера.
CLOSEHANDLE (-1) Закривання інтерфейсу. Драйвер повинен перервати обробку всіх асинхронних запитів по цьому пристрою і звільнити пов’язані з нього ресурси.


Функції VxD, що викликаються з системи


Диспетчер системних сообщенійДіспетчер системних повідомлень драйвера являє собою функцію, яка одержує параметри в регістрах і повертає результат у прапорі процесора CF (carry flag). Код повідомлення передається в регістрі EAX. При поверненні прапор CF повинен бути скинутий, якщо повідомлення оброблено успішно, і встановлено, якщо відбулися помилка або відмову в обслуговуванні. Для всіх повідомлень, які не обробляються даними VxD, повинен повертатися скинутий прапор CF.Все регістри, не беруть участь в поверненні результату, повинні бути збережені. Рекомендується оформляти диспетчер у вигляді naked-функції, щоб гарантувати збереження стану прапора CF після повернення, або стежити за кодом, який породжується компілятором.


Обробники сервісних функцій


Обробники сервісних функцій зазвичай теж отримують параметри в регістрах і повертають результати в регістрах і прапорах процесора, однак вони можуть бути оформлені і відповідно до угод мов C.Обязательной є тільки функція з нульовим номером, через яку виконується запит версії драйвера, вона не отримує параметрів і повертає версію в регістрі EAX.Все регістри, не беруть участь в поверненні результату, повинні бути збережені.


Обробники викликів API


Обробники викликів API отримують в регістрі EBX ідентифікатор (handle) поточної віртуальної машини (VM) клієнта, а в регістрі EBP – адреса структури регістрів кліента.По стандартному угодою, при спілкуванні до API драйвера в регістрі AH передається номер функції, а в AL – номер підфункції. Решта регістри можуть передавати інші параметри запиту.


При необхідності повернути інформацію клієнту VxD модифікує поля відповідних регістрів клієнтської структури; в момент повернення управління клієнтові значення регістрів відновлюються з нее.Обработчік API може використовувати всі регістри, крім EBP та сегментні.


Системні засоби підтримки VxD


Деякі системні повідомлення


DEVICE_INIT – ініціалізація статичної драйвера



Повідомлення надсилається після завантаження статичного драйвера для його ініціалізації.


SYS_DYNAMIC_DEVICE_INIT – ініціалізація динамічного драйвераСообщеніе надсилається після завантаження динамічного драйвера для його ініціалізації.


SYS_DYNAMIC_DEVICE_EXIT – завершення динамічного драйвераСообщеніе надсилається перед вивантаженням динамічного драйвера для завершення його работи.CREATE_VM – створення нової віртуальної машини



Посилається в процесі створення нової віртуальної машини (VM), але до її фактичного запуску. На цьому етапі VxD може визначити, чи зможе він підтримувати створювану віртуальну машину, і запросити необхідні для підтримки ресурси. Повернення встановленого прапора CF запобігає створення машини.


VM_INIT – ініціалізація нової віртуальної машини



Посилається на початку роботи нової віртуальної машини, при її ініціалізації, в контексті цієї віртуальної машини. На цьому етапі VxD може ініціалізувати ресурси, виділені для підтримки нової віртуальної машіни.Флаг CF завжди повинен повертатися скинутим.


VM_TERMINATE – завершення віртуальної машини



Посилається на початку процесу завершення віртуальної машіни.Флаг CF завжди повинен повертатися скинутим.


DESTROY_VM – знищення віртуальної машини



Посилається в кінці процесу завершення машини, перед безпосереднім видаленням її з сістеми.Флаг CF завжди повинен повертатися скинутим.


CREATE_THREAD – створення нового завдання



Посилається при створенні в системі нового завдання. Створювана завдання в цей момент ще не є текущей.Возврат встановленого прапора CF запобігає створення задачі.THREAD_INIT – ініціалізація нового завдання EDI – Ідентифікатор задачі.Посилается при ініціалізації завдання, на початку її роботи, в контексті задачі (нова задача є поточною). Прапор CF завжди повинен повертатися сброшенним.TERMINATE_THREAD – завершення завдання


Посилається на початку процесу завершення завдання. Завершується завдання ще якийсь час може залишатися в системі, поки не будуть завершені всі чекають операції введення / вивода.Флаг CF завжди повинен повертатися скинутим.


DESTROY_THREAD – знищення завдання



Посилається в кінці процесу завершення завдання, перед безпосереднім видаленням завдання з сістеми.Флаг CF завжди повинен повертатися скинутим.


SYSTEM_EXIT – завершення роботи системи



Посилається на початку процесу завершення роботи системи, при запиті закриття системи (shutdown), перезавантаження (reboot) або при аварійному завершенні.


W32_DEVICEIOCONTROL – запит від програми Win32



0 – Обробка завершена успішно;-1 – Розпочато асинхронна операція. Повертається тільки в тому випадку, якщо параметру було поставлене ненульовий параметр lpoOverlapped.код помилки – Якщо операція завершена неудачно.Вместе з поверненням результату в EAX драйвер може заносити необхідну інформацію в буфер результату, якщо він зазначений в блоці параметрів.


 

Деякі сервісні функції VMM


VMMCall, VMMJmp, VxDCall, VxDJmp – виклик сервісних функцій VxD void xxxCall (DWORD Service); void xxxJmp (DWORD Service);Service – Код сервісної функції. Старші 16 розрядів є ідентифікатор VxD, молодші 15 розрядів – номер сервісної функції.


Служить для звернення до сервісних функцій VMM та інших VxD. Код функції одночасно визначає і VxD, до якого відбувається звернення, і саму функцію цього VxD, так що конструкції VMMxxx і VxDxxx рівнозначні. Константи для кодів функцій визначені під включаються файлах відповідних VxD; імена констант функцій VxD, відмінних від VMM, мають уточнюючі префікси, наприклад VPICD_Get_Version – Запит номера версії VPICD, драйвера віртуального контролера прериваній.Сервісние функції доступні тільки в драйверах, які мають ідентифікатори. До драйверам без ідентифікаторів “чесний” доступ можливий тільки з боку додатків. З боку інших VxD він можливий лише шляхом безпосереднього пошуку даного VxD в системному списку з подальшим прямим доступом до таблиці його сервісних функцій.


Параметри для сервісної функції, як правило, передаються в регістрах; результати повертаються в регістрах і прапорах процесора. Деякі функції розраховані на виклик в стилі мов C, з приміщенням параметрів в стек і поверненням результату в EAX. Імена функцій, оформлених у стилі C, починаються зі знака підкреслення (_).


Обидва виклику оформляються у вигляді команди переривання int 0x20, Слідом за якою розміщується код функції. При першій відпрацювання виклику VMM замінює цю конструкцію на команду far call / jmp до відповідного шлюз, що дає економію часу при наступних викликах. За Через конструкцію Int 20, якщо вона не розпізнається отладчиком як VMMxxx / VxDxxx, не можна проходити командою типу Step Over, так як стоп-точка буде встановлена ​​отладчиком відразу за командою Int і при цьому буде зіпсований розташований за нею код функції. Отладчик SoftICE коректно пізнає ці конструкції.


Різниця викликів Call і Jmp полягає в тому, що виклик Call запам’ятовує адресу повернення в стеку, а Jmp – Ні. Методом Jmp викликаються “фатальні” функції, які не потребують повернення, а також функції, після яких потрібен повернення відразу до викликала функції, без відновлення регістрів та інших завершальних дій. Без хорошого розуміння механізму роботи виклику Jmp краще обмежитися використанням виклику Call.


_SelectorMapFlat – Відображення сегментної адреси в лінійний


_SelectorMapFlat (
DWORD VMHandle,
DWORD Sel,
DWORD Reserved
);



Функція отримує параметри в стеці, в стилі мов C, і повертає в EAX лінійний адресу, відповідний початку сегмента, селектор якого заданий параметром Sel, Або – 1 у разі помилки. За допомогою цієї функції можливий доступ з VxD до даних додатків Win16.


Отриманий адресу дійсний до моменту повернення в VMM, після чого відображення може бути анульовано. Щоб зберігати відображення тривалий час, необхідно використовувати спеціальні засоби роботи зі сторінками – резервування сторінок, копіювання елементів таблиці сторінок і т.п.


Деякі функції-обгортки, визначені в VXDWRAPS


Out_Debug_String – висновок отладочного повідомлення void Out_Debug_String (char * String);



Системний налагоджувальний потік можна переглядати отладчиками WDEB386, SoftICE, А також будь-яким налагоджувальний монітором.


_Sprintf – Форматування рядка ULONG _Sprintf (char * Buffer, char * Format, …); Функція аналогічна стандартної функції sprintf мови C.


На жаль, VMM не надає функції, аналогічної vsprintf, Тому для реалізації функцій цільового призначення, в основі яких лежить специфікація формату і список аргументів змінної довжини (наприклад, функцій отладочного висновку або формування рядків спеціального виду), доводиться використовувати _Sprintf, Копіюючи змінну частину списку аргументів (наприклад, 10-20 подвійних слів) з стекового кадру цільової функції.


Приклад побудови функції-обгортки


Оскільки сервісна функція VMM _SelectorMapFlat не має стандартної обгортки, можлива функція-обгортка для неї могла б виглядати таким чином:


# Pragma warning (disable: 4035) / / Блокування попередження про неповернення

_declspec (naked)
void *SelectorMapFlat (DWORD VM, DWORD Sel, DWORD Rsv = 0) {

VMMJmp (_SelectorMapFlat) / / Передача управління VMM

(Void) (VM, Sel, Rsv); / / Імітація використання параметрів

}

# Pragma warning (default: 4035) / / Відновлення попереджень про неповернення


Кваліфікатор _declspec (naked) пригнічує генерацію прологу / епілогу, так що від функції залишається лише конструкція VMMJmp. При виклику цієї функції-обгортки параметри VM, Sel і Rsv поміщаються в стек, потім туди ж поміщається адресу повернення, управління передається у функцію-обгортку, при цьому VMMJmp передає управління сервісної функції VMM _SelectorMapFlat, Не запам’ятовуючи нового адреси повернення в стеку. VMM використовує значення параметрів з стека, потім виконує повернення за адресою, що знаходиться на верхівці стека, при цьому управління відразу повертається в точку за викликом функції-обгортки, де знаходиться код, що видаляє з стека параметри і використовує значення, повернене VMM в EAX.


Така схема типова для побудови функцій-обгорток, так як в повній мірі використовує особливості створення стекового кадру в мовах C, а також можливості компілятора Visual C + + з оформлення функцій. Без використання VMMJmp довелося б робити повернення двічі, а без використання кваліфікаторов naked – Двічі поміщати в стек список параметрів.

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*