Особенности отладки драйверов
6.8. Особенности отладки драйверов
Драйверы достаточно сложны для отладки. Это связано прежде всего с тем, что Вы не сможете использовать такие отладчики, как CodeView. На этапе инициализации драйвера (при выполнении команды инициализации) загрузка операционной системы еще не завершена, и воспользоваться обычным отладчиком невозможно.
Прикладная программа также не вызывает драйвер напрямую, а делает это через прерывания DOS. Отладчик CodeView не позволит Вам трассировать прерывание 21h, даже если Вы и сможете это сделать при помощи другого отладчика (например, отладчик Advanced Fullscreen Debugger фирмы IBM позволяет трассировать операционную систему), Вам придется очень долго "добираться" до программы прерывания Вашего драйвера.
Малейшие ошибки в программе инициализации могут привести к невозможности завершения загрузки операционной системы. В этом случае Вам придется загрузиться с дискеты и удалить строку, описывающую драйвер из файла CONFIG.SYS, затем повторить загрузку с диска.
Можно использовать специально подготовленную системную дискету, записать на нее отлаживаемый драйвер и загрузить операционную систему с дискеты. Если произойдет зависание системы, загрузите DOS с жесткого диска.
Можно порекомендовать следующую методику отладки драйвера.
Программа стратегии обычно очень проста и проблем не вызывает.
Для отладки программы инициализации можно подготовить специальные процедуры, отображающие на экране содержимое наиболее важных переменных и областей памяти. Такие же процедуры можно использовать и для отладки других частей драйвера.
В качестве примера приведем текст процедуры, выводящей на экран содержимое всех регистров процессора. После вывода программа ожидает нажатия любой клавиши.
Текст этой процедуры следует поместить в ту часть драйвера, которая останется резидентной, тогда Вы сможете вызывать ее не только при инициализации, но и при выполнении других команд.
;========================================== ; Процедура выводит на экран содержимое ; всех регистров и ожидает нажатия на ; любую клавишу. ; После возвращения из процедуры ; все регистры восстанавливаются.
ntrace proc near
; Сохраняем в стеке регистры, ; содержимое которых будет изменяться
pushf push ax push bx push cx push dx push ds push bp
push cs pop ds
mov bp,sp ; Выводим сообщение об останове mov dx,offset cs:trace_msg @@out_str ; Выводим содержимое всех регистров mov ax,cs ; cs call Print_word @@out_ch ':' mov ax,[bp]+14 ; ip call Print_word @@out_ch 13,10,13,10,'A','X','=' mov ax,[bp]+10 call Print_word @@out_ch ' ','B','X','=' mov ax,[bp]+8 call Print_word @@out_ch ' ','C','X','=' mov ax,[bp]+6 call Print_word @@out_ch ' ','D','X','=' mov ax,[bp]+4 call Print_word @@out_ch ' ','S','P','=' mov ax,bp add ax,16 call Print_word @@out_ch ' ','B','P','=' mov ax,[bp] call Print_word @@out_ch ' ','S','I','=' mov ax,si call Print_word @@out_ch ' ','D','I','=' mov ax,di call Print_word @@out_ch 13,10,'D','S','=' mov ax,[bp]+2 call Print_word @@out_ch ' ','E','S','=' mov ax,es call Print_word @@out_ch ' ','S','S','=' mov ax,ss call Print_word @@out_ch ' ','F','=' mov ax,[bp]+12 call Print_word lea dx,cs:hit_msg @@out_str ; Ожидаем нажатия на любую клавишу mov ax,0 int 16h ; Восстанавливаем содержимое регистров pop bp pop ds pop dx pop cx pop bx pop ax popf ret trace_msg db 13,10,'>---- BREAK ----> At address ','$' hit_msg db 13,10,'Hit any key...','$' ntrace endp ;========================================== ; Процедура выводит на экран содержимое AX Print_word proc near push ax push bx push dx push ax mov cl,8 rol ax,cl call Byte_to_hex mov bx,dx @@out_ch bh @@out_ch bl pop ax call Byte_to_hex mov bx,dx @@out_ch bh @@out_ch bl pop dx pop bx pop ax ret Print_word endp Byte_to_hex proc near ;-------------------- ; al - input byte ; dx - output hex ;-------------------- push ds push cx push bx lea bx,tabl mov dx,cs mov ds,dx push ax and al,0fh xlat mov dl,al pop ax mov cl,4 shr al,cl xlat mov dh,al pop bx pop cx pop ds ret tabl db '0123456789ABCDEF' Byte_to_hex endp end
Для вывода строки на экран в этой процедуре используется макро @@out_str и @@out_ch, которые определены в файле sysp.inc: @@out_ch MACRO c1,c2,c3,c4,c5,c6,c7,c8,c9,c10 mov ah,02h IRP chr,<c1,c2,c3,c4,c5,c6,c7,c8,c9,c10> IFB <chr> EXITM ENDIF mov dl,chr int 21h ENDM ENDM @@out_str MACRO mov ah,9 int 21h ENDM
Другой метод - построить макет драйвера в виде COM-программы. Используя свой любимый отладчик, Вы сможете проверить работу большинства входящих в драйвер модулей.
Если у Вас есть отладчик Advanced Fullscreen Debugger, можно использовать его способность оставаться резидентным и вызываться по нажатию комбинации клавиш CTRL+ESC.
В интересующее Вас место драйвера поместите вызов прерывания 16h, ожидающий ввода с клавиатуры, например: push ax mov ax,0 int 16h pop ax
Можно сохранить в стеке и регистр флагов, если его изменение нежелательно.
После того, как драйвер повиснет на ожидании ввода, активизируйте отладчик, нажав комбинацию клавиш CTRL+ESC. Вы окажетесь в теле обработчика прерывания 16h. Выполняя программу по шагам, довольно скоро Вы достигнете выхода из этого обработчика - команды IRET. После выполнения команды IRET управление будет передано команде, следующей за командой int16h. Эта команда (в приведенном примере - pop ax) принадлежит Вашему драйверу!
Используя известное теперь значение адреса заголовка запроса (регистры ES:BX), можно определить, какая команда выполняется драйвером, и просмотреть сам запрос.
Выполнив все необходимые отладочные действия, запустите программу на выполнение без отладки. В нужный момент времени Вы снова сможете вызвать отладчик тем же способом.
Теоретически возможно создать такой отладчик, который сам оформлен в виде драйвера, и подключить его в файле CONFIG.SYS до проверяемого драйвера. Тогда весь процесс загрузки и инициализации будет производиться под контролем этого драйвера-отладчика.
Программа-драйвер использует системный стек, имеющий довольно небольшой размер. Поэтому при необходимости организуйте свой стек в области памяти, принадлежащей драйверу.
Очень важно, чтобы драйвер перед началом работы сохранил содержимое всех регистров, включая регистр флагов, а перед возвратом управления операционной системе восстановил старое содержимое регистров.
Критичные участки драйвера должны выполняться с замаскированными прерываниями.