Символьные устройства
Драйвер символьного устройства должен содержать в поле атрибутов драйвера (смещение 04 в заголовке) единицу в самом старшем бите. Тогда остальные биты трактуются следующим образом:
бит 15: 1
бит 14: драйвер поддерживает функции чтения/записи IOCTL
бит 13: драйвер поддерживает, функцию вывода до занятости
бит 12: 0
бит 11: драйвер поддерживает функции открыть/закрыть устройство
биты 10 – 8: 000
бит 7: драйвер поддерживает функцию запроса поддержки IOCTL
бит 6: драйвер поддерживает обобщенный IOCTL
бит 5: 0
бит 4: драйвер поддерживает быстрый вывод (через INT 29h)
бит 3: драйвер устройства «часы»
бит 2: драйвер устройства NUL
бит 1: драйвер устройства STDOUT
бит 0: драйвер устройства STDIN
IOCTL — это большой набор функций (свыше пятидесяти), доступных как различные подфункции INT 21h АН = 44h и предназначенных для прямого взаимодействия с драйверами. Но о IOCTL — чуть позже, а сейчас познакомимся с тем, как устроен, возможно, самый простой из реально полезных драйверов.
В качестве первого примера рассмотрим драйвер, который вообще не обслуживает никакое устройство, реальное или виртуальное, а просто увеличивает размер буфера клавиатуры BIOS до 256 (или больше) символов. Этого можно было бы добиться обычной резидентной программой,но BIOS хранит в своей области данных только ближние адреса для этого буфера, то есть смещения относительно сегментного адреса 0040h. Так как драйверы загружаются в память первыми, еще до командного интерпретатора, они обычно попадают в область линейных адресов 00400h – 10400h, в то время как с резидентными программами это может не получиться.
Наш драйвер будет обрабатывать только одну команду, команду инициализации драйвера 00h. Для нее буфер запроса выглядит следующим образом:
+00h: байт — 19h (длина буфера запроса)
+01h: байт — не используется
+02h: байт — 00h (код команды)
+03h: байт — слово состояния драйвера (заполняется драйвером)
+05h: 8 байт — не используется
+0Dh: байт — число обслуживаемых устройств (заполняется блочным драйвером)
+0Eh: 4 байта
на входе — конец доступной для драйвера памяти;
на выходе — адрес первого байта из той части драйвера, которая не будет резидентной (чтобы выйти без инсталляции — здесь надо записать адрес первого байта)
+12h: 4 байта
на входе — адрес строки в CONFIG.SYS, загрузившей драйвер;
на выходе — адрес массива ВРВ (для блочных драйверов)
+16h: байт — номер первого диска
+17h: 2 байта — сообщение об ошибке (0000h, если ошибки не было) — заполняется драйвером
Процедура инициализации может пользоваться функциями DOS 01h – 0Ch, 25h, 30h и 35h.
; kbdext.asm ; драйвер символьного устройства, увеличивающий буфер клавиатуры до BUF_SIZE ; (256 по умолчанию) символов ; BUF_SIZE equ 256 ; новый размер буфера
.model tiny .186 ; для сдвигов и push 0040h .code org 0 ; драйвер начинается с CS:0000 start: ; заголовок драйвера dd -1 ; адрес следующего драйвера - ; FFFFh:FFFFh для последнего dw 8000h ; атрибуты: символьное устройство, ; ничего не поддерживает dw offset strategy ; адрес процедуры стратегии dw offset interrupt ; адрес процедуры прерывания db "$$KBDEXT" ; имя устройства (не должно совпадать ; с каким-нибудь именем файла) request dd ? ; здесь процедура стратегии сохраняет адрес ; буфера запроса buffer db BUF_SIZE*2 dup (?) ; а это - наш новый буфер ; клавиатуры размером BUF_SIZE символов ; (два байта на символ) ; процедура стратегии ; на входе ES:BX = адрес буфера запроса strategy рroc far mov cs:word ptr request,bx ; сохранить этот адрес для mov cs:word ptr request+2,es ; процедуры прерывания ret strategy endp
; процедура прерывания interrupt proc far push ds ; сохранить регистры push bx push ax lds bx,dword ptr cs:request ; DS:BX - адрес запроса mov ah,byte ptr [bx+2] ; прочитать номер команды, or ah,ah ; если команда 00h (инициализация), jnz exit call init ; обслужить ее, ; иначе: exit: mov ax,100h ; установить бит 8 (команда обслужена) mov word ptr [bx+3],ax ; в слове состояния драйвера pop ах ; и восстановить регистры pop bx pop ds ret interrupt endp
; процедура инициализации ; вызывается только раз при загрузке драйвера init proc near push сx push dx
mov ax,offset buffer mov cx,cs ; CX:AX - адрес нашего буфера клавиатуры cmp cx,1000h ; если СХ слишком велик, jnc too_big ; не надо загружаться, shl cx,4 ; иначе: умножить сегментный адрес на 16, add cx,ax ; добавить смещение - получился ; линейный адрес, sub cx,400h ; вычесть линейный адрес начала данных BIOS push 0040h pop ds mov bx,1Ah ; DS:BX = 0040h:001Ah - адрес головы mov word ptr [bx],cx ; записать новый адрес головой буфера mov word ptr [bx+2],cx ; он же новый адрес хвоста mov bl,80h ; DS:BX = 0040h: ; 0080h - адрес начала буфера mov word ptr [bx],cx ; записать новый адрес начала, add cx,BUF_SIZE*2 ; добавить размер mov word ptr [bx+2],cx ; и записать новый адрес конца
mov ah,9 ; функция DOS 09h mov dx,offset succ_msg ; DS:DX - адрес строки push cs ; с сообщением об успешной установке pop ds int 21h ; вывод строки на экран lds bx,dword ptr cs:request ; DS:BX - адрес запроса
mov ax,offset init mov word ptr [bx+0Eh],ax ; CS:AX - следующий байт после mov word ptr [bx+10h],cs ; конца резидентной части jmp short done ; конец процедуры инициализации
; сюда передается управление, если мы загружены слишком низко в памяти too_big: mov ah,9 ; функция DOS 09h mov dx,offset fail_msg ; DS:DX - адрес строки push cs ; с сообщением о неуспешной pop ds ; установке int 21h ; вывод строки на экран lds bx,dword ptr cs:request ; DS:BX - адрес запроса mov word ptr [bx+0Eh],0 ; записать адрес начала драйвера mov word ptr [bx+10h],cs ; в поле "адрес первого ; освобождаемого байта" done: pop dx pop cx ret init endp ; сообщение об успешной установке (на английском, потому что в этот момент ; русские шрифты еще не загружены) succ_msg db "Keyboard extender loaded",0Dh,0Ah,'$' ; сообщение о неуспешной установке fail_msg db "Too many drivers in memory - " db "put kbdext.sys first " db "in config.sys",0Dh,0Ah,'$' end start
Теперь более подробно рассмотрим функции, которые должен поддерживать драйвер символьного устройства на примере драйвера устройства ROT 13. ROT 13 — это метод простой модификации английского текста, который применяется в электронной почте, чтобы текст нельзя было прочитать сразу. ROT 13 состоит в сдвиге каждой буквы латинского алфавита на 13 позиций (в любую сторону, так как всего 26 букв). Раскодирование, очевидно, выполняется такой же операцией. Когда наш драйвер загружен, команда DOS
сору encrypt.txt rot13
приведет к тому, что текст из encrypt.txt будет выведен на экран, зашифрованный или расшифрованный ROT 13, в зависимости от того, был ли он зашифрован до этого.
Рассмотрим все команды, которые может поддерживать символьное устройство, и буфера запросов, которые им передаются.
00h: Инициализация (уже рассмотрена)
03h: IOCTL-чтение (если установлен бит 14 атрибута)
+0Eh: 4 байта — адрес буфера
+12h: 2 байта
на входе — запрашиваемое число байт
на выходе — реально записанное в буфер число байт
04h: Чтение из устройства
Структура буфера для символьных устройств совпадает с 03h
05h: Чтение без удаления символа из буфера
+0Dh: на выходе — прочитанный символ, если символа нет — установить бит 9 слова состояния
06h: Определить состояние буфера чтения
Если в буфере нет символов для чтения — установить бит 9 слова состояния.
07h: Сбросить буфер ввода
08h: Запись в устройство
+0Eh: 4 байта — адрес буфера
+12h: 2 байта
на входе — число байт для записи
на выходе — число байт, которые были записаны
09h: Запись в устройство с проверкой
аналогично 08h
0Ah: Определите состояние буфера записи
Если в устройствоиельзя писать — установить бит 9 слова состояния.
0Bh: Сбросить буфер записи
0Ch: IOCTL-запись (если установлен бит 14 атрибута)
Аналогично 08h
0Dh: Открыть устройство (если установлен бит 11 атрибута)
0Eh: Закрыть устройство (если установлен бит 11 атрибута)
11h: Вывод, пока не занято (если установлен бит 13 атрибута)
Аналогично 08h, в отличие от функций записи здесь не считается ошибкой записать не все байты
13h: Обобщенный IOCTL (если установлен бит 6 атрибута)
+0Dh: байт — категория устройства (01, 03, 05 = COM, CON, LPT) 00h — неизвестная категория
+0Eh: байт — код подфункции:
45h: установить число повторных попыток
65h: определить число повторных попыток
4Ah: выбрать кодовую страницу
6Ah: определить активную кодовую страницу
4Ch: начало подготовки кодовой страницы
4Dh: конец подготовки кодовой страницы
6Bh: получить список готовых кодовых страниц
5Fh: установить информацию о дисплее
7Fh: получить информацию о дисплее
+0Fh: 4 байта — не используется
+13h: 4 байта — адрес структуры данных IOCTL — соответствует структуре, передающейся в DS:DX для INT 21h, АХ = 440Ch
19h: Поддержка функций IOCTL (если установлены биты 6 и 7 атрибута)
+0Dh: байт — категория устройства
+0Eh: код подфункции
Если эта комбинация подфункции и категории устройства не поддерживается драйвером — надо вернуть ошибку 03h в слове состояния.
Итак, теперь мы можем создать полноценный драйвер символьного устройства. Упрощая задачу, реализуем только функции чтения из устройства и будем возвращать соответствующие ошибки для других функций.
Еще одно отличие этого примера — в нем показано, как совместить в одной программе обычный исполнимый файл типа ЕХЕ и драйвер устройства. Если такую программу запустить обычным образом, она будет выполняться, начиная со своей точки входа (метка start в нашем примере), а если ее загрузить из CONFIG.SYS, DOS будет считать драйвером участок программы, начинающийся со смещения 0:
; rot13.asm ; Драйвер символьного устройства, выводящий посылаемые ему символы на экран ; после выполнения над ними преобразования ROT13 ; (каждая буква английского алфавита смещается на 13 позиций). ; Реализованы только функции записи в устройство ; ; Пример использования: ; сору encrypted.txt $rot13 ; загрузка - из CONFIG.SYS ; DEVICE=c:\rot13.exe, ; если rot13.exe находится в директории С:\ ; .model small ; модель для ЕХЕ-файла .code .186 ; для pusha/popa org 0 ; код драйвера начинается с CS:0000 dd -1 ; адрес следующего драйвера dw 0A800h ; атрибуты нашего устройства dw offset strategy ; адрес процедуры стратегии dw offset interrupt ; адрес процедуры прерывания db "$ROT13",20h,20h ; имя устройства, дополненное ; пробелами до восьми символов
request dd ? ; сюда процедура стратегии будет писать ; адрес буфера запроса
; таблица адресов обработчиков для всех команд command_table dw offset init ; 00h dw 3 dup(offset unsupported) ; 01, 02, 03 dw 2 dup(offset read) ; 04, 05 dw 2 dup(offset unsupported) ; 06, 07 dw 2 dup(offset write) ; 08h, 09h dw 6 dup(offset unsupported) ; 0Ah, 0Bh, 0Ch, ; 0Dh, 0Eh, 0Fh dw offset write ; 10h dw 2 dup(offset invalid) ; 11h, 12h dw offset unsupported ; 13h dw 3 dup(offset invalid) ; 14h, 15h, 16h dw 3 dup(offset unsupported) ; 17h, 18h, 19h
; процедура стратегии - одна и та же для всех драйверов strategy proc far mov word ptr cs:request,bx mov word ptr cs:request+2,es ret strategy endp
; процедура прерывания interrupt proc far pushf pusha ; сохранить регистры push ds ; и на всякий случай флаги push es
push cs pop ds ; DS = наш сегментный адрес les si,dword ptr request ; ES:SI = адрес буфера запроса xor bx,bx mov bl,byte ptr es:[si+2] ; BX = номер функции cmp bl,19h ; проверить, что команда jbe command_ok ; в пределах 00 - 19h, call invalid ; если нет - выйти с ошибкой jmp short interrupt_end command_ok: ; если команда находится в пределах 00 - 19h, shl bx,1 ; умножить ее на 2, чтобы получить смещение ; в таблице слов command_table, call word ptr command_table[bx] ; и вызвать обработчик interrupt_end: cmp al,0 ; AL = 0, если не было ошибок, je no_error or ah,80h ; если была ошибка - установить бит 15 в АХ, no_error: or ah,01h ; в любом случае установить бит 8 mov word ptr es:[si+3],ax ; и записать слово состояния pop es pop ds рора popf ret interrupt endp
; обработчик команд, предназначенных для блочных устройств unsupported proc near xor ax,ax ; не возвращать никаких ошибок ret unsupported endp
; обработчик команд чтения read proc near mov al,0Bh ; общая ошибка чтения ret read endp
; обработчик несуществующих команд invalid proc near mov ax,03h ; ошибка "неизвестная команда" ret invalid endp
; обработчик функций записи write proc near push si mov cx,word ptr es:[si+12h] ; длина буфера в СХ, jcxz write_finished ; если это 0 - нам делать нечего lds si,dword ptr es:[si+0Eh] ; адрес буфера в DS:SI
; выполнить ВОТ13- преобразование над буфером cld rot13_loop: ; цикл по всем символам буфера lodsb ; AL = следующий символ из буфера в ES:SI cmp al,'А' ; если он меньше "А", jl rot13_done ; это не буква, cmp al,'Z' ; если он больше "Z", jg rot13_low ; может быть, это маленькая буква, cmp al,('A'+13) ; иначе: если он больше "А" + 13, jge rot13_dec ; вычесть из него 13, jmp short rot13_inc ; а иначе - добавлять rot13_low: cmp al,'а' ; если символ меньше "а", jl rot13_done ; это не буква, cmp al,'z' ; если символ больше "z", jg rot13_done ; то же самое, cmp al,('a'+13) ; иначе: если он больше "а" + 13, jge rot13_dec ; вычесть из него 13, иначе: rot13_inc: add al,13 ; добавить 13 к коду символа, jmp short rot13_done rot13_dec: sub al,13 ; вычесть 13 из кода символа, rot13_done: int 29h ; вывести символ на экран loop rot13_loop ; и повторить для всех символов write_finished: xor ах,ах ; сообщить, что ошибок не было pop si ret write endp
; процедура инициализации драйвера init proc near mov ah,9 ; функция DOS 09h mov dx,offset load_msg ; DS:DX - сообщение об установке int 21h ; вывод строки на экран mov word ptr es:[si+0Eh],offset init ; записать адрес mov word ptr es:[si+10h],cs ; конца резидентной части xor ах,ах ; ошибок не произошло ret init endp
; сообщение об установке драйвера load_msg db "ROT13 device driver loaded",0Dh,0Ah,'$'
start: ; точка входа ЕХЕ-программы push cs pop ds mov dx,offset exe_msg ; DS:DX - адрес строки mov ah,9 ; функция DOS int 21h ; вывод строки на экран mov ah,4Ch ; функция DOS 4Ch int 21h ; завершение ЕХЕ-программы
; строка, которая выводится при запуске не из CONFIG.SYS: exe_msg db "Эту программу надо загружать как драйвер устройства из" db "CONFIG.SYS",0Dh,0Ah,'$'
.stack
end start
Содержание раздела