Разработка dll-библиотеки для взаимодействия с драйверомdll-библиотека (Dynamic Link Library) - программный модуль, который может быть динамически подключен к выполняющемуся процессу. Dll - библиотека может содержать функции и данные. При подключении dll к процессу она отображается на адресное пространство этого процесса. Если говорить по-русски, то это означает: в любой момент времени программа может загрузить dll-библиотеку, получить указатели на функции и данные этой библиотеки. Потом приложение как-то использует функции и данные библиотеки, и когда они больше не нужны - выгружает библиотеку. Dll-библиотека содержит два вида функций: внешние (External) и внутренние (Internal). Внутренние функции могут вызываться только самой dll, а внешние может также вызывать приложение, подключившее библиотеку. В этом случае говорят, что dll-библиотека экспортирует функции и данные. Как было упомянуть выше, в настоящее время для связи с драйвером используется схема Приложение -> Библиотека dll -> Драйвер. При использовании такой архитектуры запрос приложения на операцию ввода-вывода поступает в dll-библиотеку, проходит там предварительную обработку и передается драйверу. Результат, возвращенный драйвером библиотеке dll, также обрабатывается и передается приложению. Преимущества такого подхода очевидны:
Естественно, такой подход имеет свои минусы. В данном случае за счет большего числа вызовов, через которые проходит запрос на ввод-вывод, снижается быстродействие работы системы. В нашем случае нам необходимо разработать dll-библиотеку, которая будет предоставлять приложению три функции: чтение памяти, запись в память и получение общего количества памяти устройства. Естественно, dll - библиотеку мы также будем проектировать в среде Visual C++. Запустите среду VC++ и создайте новый проект с названием XDSPInter. В качестве типа проекта выберите Win32 Dynamic-Link Library. Далее в качестве типа проекта выберите A Simple DLL (простая dll-библиотека). Среда VC++ создаст для Вас пустой проект с одной- единственной функцией DllMain(). Функция DllMain() вызывается при подключении и отключении dll процессом. DllMain() имеет возвращаемое значение BOOL APIENTRY (фактически, она возвращает значение типа BOOL) и три параметра - HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved. Параметры:
Функция DllMain() - единственная функция, которая обязательно должна присутствовать в библиотеке. Остальные функции и переменные добавляет программист в соответствии с решаемой задачей. В нашем случае dll - библиотека будет экспортировать следующие функции: bool IsDriverPresent(void). Функция будет определять, присутствует ли в системе необходимый драйвер и попытаться подключиться к нему. Если это удастся - функция вернет true, в противном случае - false. int ReadMem(char data, int len) - чтение данных из памяти устройства. Char* data - буфер для данных, int len - число 32-битных слов для чтения. Функция вернет число прочитанных слов. int WriteMem(char *data, int len) - аналогична предыдущей; запись данных в память. extern "C" __declspec (dllexport) Для того, чтобы при каждом объявлении функции не писать эту длинную малопонятную строку, определим ее, как директиву препроцессора: #define EXPORT extern "C" __declspec (dllexport) Теперь перед каждым объявлением функции просто следует писать слово EXPORT. Создадим заголовочный файл нашей dll-библиотеки, в котором перечислим все экспортируемые функции и директивы препроцессора: #define EXPORT extern "C" __declspec (dllexport) Теперь рассмотрим текст исходного срр - файла библиотеки. //В начале идут включения заголовочных файлов: Таким образом, наша библиотека экпортирует всего четыре функции для работы с устройством. Все они имеют простой синтаксис и просты в использовании. Использование dll в нашем случае позволяет программисту не думать о сложных системных вызовах, необходимых для общения с драйвером, о формате передаваемых ему данных, а сосредоточится на решении прикладных задач.
2.5 Подключение dll-библиотеки к приложению.После того, как написан драйвер и dll-библиотека для работы с ним, пришло время написать приложение пользоваеля, работающее с устройством. Оно будет взаимодействовать с драйвером через dll-библиотеку. Естественно, написано оно также будет в среде Visual C++. В принципе, его можно было бы реализовать в среде Visual Basic, Delphi или CВuilder, но это приведет к некоторым трудностям, прежде всего в использовании системных вызовов и структур данных. В данном разделе, в отличие от предыдущих, не рассматривается какое-либо конкретное приложение, а даются общие рекомендации по написанию такой программы. Подключение библиотеки к приложению не требует особых усилий. Библиотека под- ключается при помощи системного вызова HMODULE LoadLibrary(char *LibraryName), где LibraryName - строка с именем файла dll-библиотеки. Возвращаемое значение - хендл (дескриптор) бибилиотеки. Если функция возвратила NULL, то произошла ошибка при подключении библиотеки. После подключения библиотеки можно из нее импортировать функции. Импорт функции производится при помощи системного вызова FARPROC GetProcAdress(HMODULE hModule, char * ProcName)
Вызов GetProcAdress возвращает адрес функции с заданным именем и NULL, если такой функции нет в библиотеке. Так как из библиотеки импортируется не само тело функции, а ее адрес, то вызов такой функции превращается в непростое дело. Прежде всего, в программе объявляется не сама функция, а переменная, содержащая указатель на нее. Естественно, работа с таким указателем сильно отличается от работы с указателем на число или строку. Ведь функция в отличие от просто переменной возвращает значение и принимает некоторые параметры, поэтому указатель на нее должен быть объявлен специальным образом. Указатель на функцию, ипортируемую из dll-библиотеки должен также быть скомпилирован со специальным объявлением типа - __declspec(dllimport). Эту строку также удобно представить в виде директивы #define. #define XDSPINTER_API __declspec(dllimport). Мы импортируем из библиотеки четыре функции, поэтому необходимо определить их типы: параметры, передаваемые в функцию, возвращаемое значение. Это можно сделать при помощи директивы typedef: //Объявить тип-указательна функцию, возвращающую значение типа int Теперь пришло время создать сами указатели на функции: MemReadFun ReadMem; Теперь рассмотрим функцию, подключающую dll-библиотеку к приложению. Она будет подключать dll-библиотеку к приложению и пытаться установить связь с драйвером. Функция вернет true в случае успеха и false при неудаче. Т.к. VC++ - объектноориентированная среда, то эта функция будет методом одного из классов приложения (в нашем случае - класса представления). bool CXDSPView::ConnectToDriver() Вызов метода ConnectToDriver() целесообразно сделать в конструкторе класса. Там же надо реализовать и проверку, присутствует ли в системе драйвер. Тогда вся необходимая инициализация будет проведена еще при запуске приложения. CXDSPView::CXDSPView() Метод, производящий чтение памяти устройства может выглядеть следующим образом: void CXDSPView::OnRead() Аналогично может выглядеть метод записи в память устройство: void CXDSPView::OnWrite() Метод, возвращающий длину памяти устройтсва, совсем прост и, думаю, в комментариях не нуждается. int CXDSPView::GetTotalLen() Также введем еще один метод, который может быть полезным. Он будет очищать память устройства. void CXDSPView::OnClear() Конечно, написанные нами приложение и dll-библиотека весьма несовершенны. Например, сбои будут происходить, если будут заущены несколько приложений. Тогда они будут одновременно обращаться к одной и той же dll и обновременно работать с устройством. Это может породить множество сбоев. В лучшем случае данные, получаемые каждым из них будут неадекватными. В худшем - система зависнет. Впрочем, этот недостаток можно устранить, модифицировав драйвер способом, описанным выше. Также в нашем приложении производится работа только с первыми 1024 байтами памяти устройства. Конечно, коммерческая ценность такой системы равна нулю. Но она может быть хорошим учебным примером для ознакомления с программированием WDM - драйверов в Windows и DriverStudio.
2.6 Отладка драйверовРазговор о драйверах был бы неполным, если не упомянуть об отладке драйверов. Т.к. драйвера работают в нулевом кольце защиты процессора со всеми вытекающими последствиями, то обыкновенные отладчики пользовательских приложений не пригодны для отладки драйверов. Если, например, разрабатывать драйвер под ОС Linux, то ситуация там может быть немного хуже: в этой ОС вообще нет какой-либо возможности отлаживать драйвера, кроме как воспользоваться отладчиком gdb. Но в таком случае надо перекомпилировать ядро системы специальным образом и станцевать еще несколько подобных танцев с бубном. Поэтому зачастую отладка сводится к вызову функций printk, которые в великом множестве раскиданы по всему ядру системы. К счастью, хоть в этом Windows имеет преимущества. Для того, чтобы можно было отлаживать драйвера, отладчик должен сам работать в нулевом кольце защиты. Естественно, разработка такой программы является чрезвычайно сложной задачей, поэтому таких отладчиков на сегодняшний день известно всего два: WinDbg (поставляется с пакетом DDK) и SoftIce (входит в состав NuMega DriverStudio). SoftIce считается одним из лучших отладчиков для Windows всех типов. Это надежный, мощный и довольно удобный в использовании инструмент. SoftIce может применяться для различных целей: для отладки драйверов и приложений пользователя, для просмотра информации о системе и т.п. Мы рассмотрим, как применять SoftIce для отладки драйверов устройств. Будучи установленным в Win98, SoftIce прописывает в Autoexec.bat строку вида: c:\Progra~1\numega\driver~1\softice\winice Т.е. SoftIce загружается после загрузки DOS и сам грузит Windows. При работе Windows SoftIce активизируется лишь при каком-нибудь системном исключении или в точке останова, заданной программистом в драйвере. Также вызвать SoftIce можно, нажав Ctrl+D. На экране появляется окно отладчика. Пока окно SoftIce активно, вся деятельность ОС замирает; именно сейчас можно безболезненно отлаживать драйвера. Окно SoftIce разбито на несколько окон. Обычно в центре видно окно кода, над ним - окно регистров процессора и в самом низу - окно сообщений. Перемещаться в пределах окна можно, используя клавиши управления курсором или мышь. В самом низу окна SoftIce расположена командная строка. SoftIce не имеет графического интерфейса, и все команды управления отладчиком вводятся в командной строке. SoftIce имеет довольно неплохую систему помощи. Перечень команд выдается по команде help. Наверное, самая важная команда - это команда выхода из SoftIce. Для этого нужно нажать клавишу F5 или дать команду Х (регистр не имеет значения). Внимательно изучив окно сообщений, мы там увидим разнообразные системные сообщения и те сообщения, которые наш драйвер выводит через объект трассировки. Таким образом, можно просматривать все важные сведения, которые драйвер хочет сообщить нам. Если мы хочем, чтобы драйвер не выводил какие-то сообщения или выводил другие сообщения, нам надо отредактировать текст драйвера, добавив новые или удалив существующие трассировочные сообщения. После этого надо перекомпилировать драйвер и перезагрузить его. Но, естественно, программисту мало простого чтения сообщений, посланных драйвером. Для эффективной отладки любой программы надо установить точку останова (breakpoint), просмотреть значения регистров. К счастью, SoftIce предоставляет такую возможность. Универсальной точкой останова является использование прерывания INT 3. Как и в ОС MS-DOS, в Windows INT 3 также является прерыванием отладки. Для этого в тексте драйвера, где необходимо установить breakpoint, необходимо вставить следующий код: _asm При этом присходит вызов прерывания INT 3. Но по умолчанию SoftIce не реагирует на INT 3. Для того, чтобы по этому прерыванию активизировался отладчик, необходимо вызвать SoftIce и дать команду: SET I3HERE ON Теперь при вызове INT 3 произойдет <всплывание> этого кода в отладчике. Для отключения режима отладки по INT 3 следует дать команду SET I3HERE OFF. После того, как наш драйвер <всплыл> в SoftIce, мы можем контролировать выполнение программы при помощи команд:
Если драйвер был скомпилирован в отладочной конфигурации, то на экране будет виден текст драйвера, написанный на С++. SoftIce также может просматривать значения переменных пользователя. Для того, чтобы открыть/закрыть окно просмотра переменных (Watch), надо дать команду WW или нажать Alt+F4. Добавить/убрать переменную для просмотра можно при по- мощи команды WATCH. Это основные команды, применяемые для отладки драйверов устройств в SoftIce. А в общем, этот отладчик имеет огромное количество функциональных возможностей и его пол- ное описание пославляется с программой и занимает порядка двухсот страниц. Надеюсь, это руководство было для Вас интересно. Если даже не интересно - то, надеюсь, Вы узнали что-то новое для себя. |