Система прерываний персональной ЭВМ IBM PC в защищенном

advertisement
ФЕДЕРАЛЬНОЕ АГЕНТСТВО ПО ОБРАЗОВАНИЮ
ГОСУДАРСТВЕННОЕ ОБРАЗОВАТЕЛЬНОЕ УЧРЕЖДЕНИЕ
ВЫСШЕГО ПРОФЕССИОНАЛЬНОГО ОБРАЗОВАНИЯ
«САМАРСКИЙ ГОСУДАРСТВЕННЫЙ АЭРОКОСМИЧЕСКИЙ УНИВЕРСИТЕТ им.
АКАДЕМИКА С.П. КОРОЛЕВА» (СГАУ)
Кафедра информационных систем и технологий
Пояснительная записка
к индивидуальному заданию
Тема работы:
«Система прерываний персональной ЭВМ IBM PC в
защищенном режиме. Обработка аппаратных прерываний при
работе на 0 уровне привилегий».
Выполнил:
Кузнецов Дмитрий
661 группа
Самара 2008
Содержание
1
2
3
Постановка задачи ..........................................................................................................................3
Описание реализации .....................................................................................................................3
Инструкция по снятию трассы ....................................................................................................13
2
1
Постановка задачи
Продемонстрировать работу процессора при возникновении аппаратного прерывания в
защищенном режиме, в случае если сигнал пришел в момент исполнения кода в сегменте с
нулевым уровнем привилегий.
Задание можно разделить на следующие блоки:
1.Скомпоновать исходник программы выполняющей:
а) подготовку и переход из реального режима в защищенный;
б) обработку аппаратного прерывания по сигналу нажатия клавиши и переход в реальный
режим.
Кроме перечисленных пунктов, следует еще отметить, что программа будет запущена в
эмуляторе. Эмуляция будет
2.Скомпилировать полученный исходник в бинарный файл образа диска (дискеты).
3.Выполнить трассировку программы в среде эмулятора с выводом состояния CPU и стека.
2 Описание реализации
Поскольку процессор при загрузке работает в реальном режиме с 16битной адресацией,
первоначально необходимо будет переключить процессор в защищенный режим с 32битной
адресацией, установить обработчики прерываний и считать символ с клавиатуры. Так же будет
продемонстрирован процесс переключения из защищенного режима обратно в реальный.
Структура исходника представлена на схеме 1.
3
Смещение на 0x7C00
Переход на метку init:
Описание таблицы GDT
Описание таблицы IDT
init:
Загрузка тела программы в память после самотестирования процессора
Открытие 20ой адресной линии
1.
2.
3.
4.
Маскирование прерываний (включая NMI)
Загрузка таблиц в соответствующие регистры
Установка нулевого бита регистра CR0
Переход на сегмент кода защищенного режима (jmp
00001000b:PROTECTED_ENTRY)
PROTECTED_ENTRY:
1. Инициализация регистров
2. Вызов подпрограммы перепрограммирования контроллера APIC
3. Разрешение немаскируемых и маскируемых прерываний
4. Вывод информационной строки
5. Бесконечное ожидание прерываний
1.
2.
3.
4.
IRQ 0 обработчик прерывания от системного таймера
Пустой обработчик сбрасывающий заявку в контроллере
Обработчик исключения #GP
Системный вызов INT 1 - печать строки
Подпрограмма перепрограммирования контроллера APIC
IRQ 1 обработчик прерывания от клавиатуры:
1.
2.
3.
4.
5.
6.
Запрет не маскируемых прерываний
Вызов подпрограммы перепрограммирования контроллера APIC
Загрузка IDT реального режима
Загрузка 16-битного селектора сегмента в CS
Сброс бита PE в регистре CR0
jmp 0:REAL_ENTRY)
REAL_ENTRY:
1. Инициализация регистров
2. Разрешение немаскируемые и маскируемые прерывания
3. Бесконечное ожидание прерываний
4
Писать мы будем код для транслятора FASM, сгенерируем чистый бинарный файл и
используем его как образ загрузочной дискеты в эмуляторе Bochs.
Cначала необходимо выполнить некоторые подготовительные действия. Поскольку код
программы будет загружен по адресу 7C00 и не уместится в пределы одного сектора (512 байт),
надо обеспечить загрузку всей остальной части программы с диска.
ORG 0x7C00
use16
start:
jmp init
....
init:
; очистка экрана
mov ax,3
int 10h
; инициализация RM-сегментов и стека
mov ax, cs
mov ds, ax
mov es, ax
mov ss, ax
mov sp, start
mov bp, sp
; Подгрузка всех остальных секторов (по 512 байт каждый) с кодом нашей программы в память
; выполняется посредством вызова 13го прерывания
mov ah, 2 ; AH = Код функции : 2- прочитать сектора в память
mov al, 10 ; AL = Число читаемый секторов (1-128) : 10 ( с запасом ;-) )
xor ch, ch ; CH = Номер цилиндра (0-1023)
:0
mov cl, 2 ; CL = Номер сектора (1-17)
:2
xor dx, dx ; DH = Номер читающей головки (0-15)
:0
; DL = Код устройства (Floppy) (0-A:, 1-B:)
: 0 (A:)
mov bx, start + 512
int 13h
jnc continue_loading
; ошибка чтения. покажем сообщение и уйдем в бесконечный цикл
jmp display_read_error
read_error db 'R',7, 'e',7, 'a',7, 'd',7, ' ',7, 'e',7, 'r',7, 'r',7, 'o',7, 'r',7
read_err_l dw $-read_error
display_read_error:
mov ax, 0B800h
mov es, ax
xor di, di
mov si, read_error
mov cx, word [read_err_l]
5
rep movsb
jmp $
ends: rb 510-(ends-start)
db 055h, 0aah
; чтение успешно, продолжаем иницилизацию
continue_loading:
Теперь в памяти располагается весь код программы. Далее необходимо включить 20ю адресную
линию, т.к., после включения компьютера функционируют только A0-A19. Для этого
установим бит 1 на порту ввода-вывода 92h:
; открываем адресную линию A20
in al, 92h
or al, 2
out 92h, al
На время переключения режимов обязательно надо отключить все прерывания, т.к., первый же
тик таймера приведет к падению системы. Отключить нужно не только аппаратные
прерывания, но и NMI установкой 7-го бита (отсчет ведеться с нулевого) в порту 70h:
; запрет всех прерываний
cli
in al, 70h
or al, 80h
out 70h, al ; запрет NMI
Далее необходимо построить GDT и IDT. Для перехода из одного режима работы процессора в
другой с выводом некоторой текстовой информации на экран, достаточно будет описать 4
дескриптора:
; Global Descriptor Table
GDT:
dd 0,0 ; пустой дескриптор
;
[ LIMIT | BASE | PDLSTYPE GD0ALIMT | BASE ]
db 0FFh, 0FFh, 00h, 00h, 00h, 10011010b, 11001111b, 00 ; 32 разряднысй код (селектор = 8h)
db 0FFh, 0FFh, 00h, 00h, 00h, 10010010b, 11001111b, 00 ; данные (селектор = 10h)
db 0FFh, 0FFh, 00h, 80h, 0Bh, 10010010b, 01000000b, 00 ; видеобуфер (селектор = 18h)
db 0FFh, 0FFh, 00h, 00h, 00h, 10011010b, 00000000b, 00 ; код реального режима, base=0, 16bit (D=0), limit=0FFFFh (G=0), селектор = 20h
GDT_size
equ $-GDT
GDTR dw GDT_size-1
dd GDT
Нужно отметить, что поскольку базы и лимиты у сегментов кода и данных одинаковы, то код и
данные будут храниться в одной и тоже области памяти и различаться будут только с помощью
дескрипторов.
Создадим таблицу прерываний из 34 записей. При вызове неопределенных в ней прерываний
будет сгенерировано исключение общей защиты. Определим обработчики для следующих
прерываний:
6




1 - системный сервис вывода строки на экран, будет использоваться для вывода с
текущего положения курсора ASCIIZ-строки, адрес которой будет взят из ESI
13 - обработчик исключения общей защиты #GP. Будет выводить строку "** GENERAL
PROTECTION FAULT **" на экран
32 - IRQ0 - системный таймер. будет по сигналу от таймера менять символ в верхнем
левом углу экрана (просто для проверки что обработчик правильно установлен)
33 - IRQ1 – прием сигнала нажатия клавиши. Будем отображать символы на экране
один за одним, не различая регистр и не воспринимая функциональные клавиши, за
одним исключением. Когда будет нажата клавиша <Esc>, будет произведен возврат в
реальный режим и вход в бесконечный цикл.
Составим таблицу дескрипторов прерываний:
; Interrupt Descriptor Table
IDT:
dd 0,0 ; 0
dw syscall_handler, 08h, 1000111000000000b, 0 ; 1
dd 0,0 ; 2
dd 0,0 ; 3
dd 0,0 ; 4
dd 0,0 ; 5
dd 0,0 ; 6
dd 0,0 ; 7
dd 0,0 ; 8
dd 0,0 ; 9
dd 0,0 ; 10
dd 0,0 ; 11
dd 0,0 ; 12
dw exGP_handler, 08h, 1000111000000000b, 0 ; 13 #GP
dd 0,0 ; 14
dd 0,0 ; 15
dd 0,0 ; 16
dd 0,0 ; 17
dd 0,0 ; 18
dd 0,0 ; 19
dd 0,0 ; 20
dd 0,0 ; 21
dd 0,0 ; 22
dd 0,0 ; 23
dd 0,0 ; 24
dd 0,0 ; 25
dd 0,0 ; 26
dd 0,0 ; 27
dd 0,0 ; 28
dd 0,0 ; 29
dd 0,0 ; 30
dd 0,0 ; 31
dw int8_handler, 08h, 1000111000000000b, 0 ; IRQ 0 - системный таймер
dw int9_handler, 08h, 1000111000000000b, 0 ; IRQ 1 - клавиатура
IDT_size equ $-IDT
IDTR
dw IDT_size-1
dd IDT
7
REAL_IDTR dw 3FFh
dd 0
Следует обратить внимание на REAL_IDTR. Он содержит base=0, limit=3ffh, эти данные при
необходимо будет загрузить в IDTR при переключении в реальный режим.
Загрузка таблиц в соответствующие регистры:
; загрузка GDTR
lgdt fword [GDTR]
; загрузка IDTR
lidt fword [IDTR]
Далее необходимо включить защищенный режим установкой младшего бита служебного
регистра CR0:
; переключение в PM
mov eax, cr0
or al, 1
mov cr0, eax
Теперь процессор находится в защищенном режиме, однако код продолжает выполняться в
16битном сегменте (так как теневая часть CS все еще хранит 16битный дескриптор сегмента,
реального режима).
Нужно перезагрузить дескриптор CS командой дальнего перехода на 32 разрядный сегмент
кода защищенного режима( в нашей GDT это 1 дескриптор, т.е. для переключения нам
потребуется селектор на него - 00001000b):
; загружаем новый селектор в CS
jmp 00001000b:PROTECTED_ENTRY
Весь код расположенный после метки PROTRCTED_ENTRY должен иметь 32 разрядную
адресацию. Директива use32 указывает транслятору на то, что теперь код выполняется в
32битном режиме. Все остальные сегментные регистры, помимо CS также должны быть
переинициализированны селекторами новых сегментов:
use32
PROTECTED_ENTRY:
; мы в PM, инициализируем селекторы 32-битных сегментов
mov ax, 00010000b ; DATA
mov ds, ax
mov ss, ax
mov ax, 00011000b ; VIDEO
mov es, ax
Т.к., новая IDT загружена в IDTR, можно разрешить аппаратные прерывания и NMI,
запрещенные на время перехода:
; разрешаем аппаратные прерывания и NMI
in al, 70h
and al, 7Fh
out 70h, al
sti
Теперь процессор переведен в полноценный 32битный защищенный режим. Воспользуемся
сервисом INT 1 и выведем на экран сообщение "Switched to ProtectedMode. Press to clear display"
на экран:
; выводим строку
mov esi, string
int 1
...
8
string db ' Switched to ProtectedMode. Press to clear display', 0
Далее установим положение вывода символов на 160 (третья строчка экрана в 3 текстовом
режиме) и переходим в бесконечный цикл ожидания прерываний:
; переходим на 3 строчку
mov dword [cursor], 160
; бесконечное ожидание прерываний ...
jmp $
Ниже будут рассмотрены используемые обработчики. Системный сервис INT 1:
;
; Системный вызов INT 1 - печать строки
;
; входные параметры: DS:ESI указывает на ASCIIZ-строку
;
syscall_handler:
pushad
_puts:
lodsb
mov edi, dword [cursor]
mov [es:edi*2], al
inc dword [cursor]
test al, al
jnz _puts
popad
iretd
Селектор ES должен указывать в сегмент видеобуфера.
Обработчик #GP – выводит строку с сообщением об ошибке и возвращает управление. Стоит
заметить, что управление возвращается на ту же инструкцию, которая и вызвала исключение.
Так же необходимо вытолкнуть из стека 4х байтный код ошибки.
;
; Обработчик исключения #GP
;
exGP_handler:
pop eax ; код ошибки
mov esi, gp
int 1
iretd
gp db '** GENERAL PROTECTION FAULT **',0
Далее приведен обработчик IRQ0 от системного таймера. Будем инкрементировать байт ES:[0],
который является самым первым байтом видеобуфера и будет отображаться в левом верхнем
углу экрана:
;
; IRQ 0 обработчик - системный таймер
;
int8_handler:
inc byte [es:0] ; увеличим символ в левом верхнем углу экрана
jmp int_EOI ; сбросим заявку на прерывание
9
Где int_EOI - обработчик-заглушка, который просто сбрасывает заявку в контроллере
прерываний.
Минимальный обработчик внешних IRQ прерываний должен посылать сигнал неопределенного
сброса контроллера прерываний обоим контроллерам (сведения о работе с контроллером
приведены в Справочных материалах):
;
; Пустой обработчик. сбрасывает заявку в контроллере
;
int_EOI:
; сброс заявки в контроллере прерываний: посылка End-Of-Interrupt (EOI) ...
push ax
mov al, 20h
out 020h, al ; ... в ведущий (Master) контроллер ...
out 0a0h, al ; ... и в ведомый (Slave) контроллер.
pop ax
iretd
; возврат из прерывания
Теперь рассмотрим обработчик прерывания IRQ1 от клавиатуры. Прерывание IRQ1
генерируется контроллером клавиатуры каждый раз при нажатии клавиши. Обработчику
клавиатуры скан-код считанной клавиши доступен для чтения через порт 060h. Скан-код нужно
преобразовать в соответствующий ему ASCII-код символа (если он печатный) и отобразить на
экране. Преобразование произведем по следующей таблице:
; Таблица преобразования печатаемых скан-кодов в ASCII
ascii db 0,'1234567890-+',0,0,'QWERTYUIOP[]',0,0,'ASDFGHJKL;',"'`",0,0,'ZXCVBNM,./',\
0,'*',0,' ',0, 0,0,0,0,0,0,0,0,0,0, 0,0, '789-456+1230.', 0,0
Эта таблица содержит символы, индексы которых в таблице соответствуют их сканкодам, либо
нули, если символы непечатаемые. Поскольку нажатие клавиш Shift и Caps Lock обработчиком
не обрабатываются, регистр букв различаться не будет. Нужно проверить, нажата ли клавиша
(её скан-код равен еденице) и, если это ESC, вызвать процедуру переключения в реальный
режим со входом в бесконечный цикл . Если это не так, отобразим символ на экране. В любом
случае нужно перед сбросом заявки на прерывание в контроллере прерываний послать
подтверждение обработки прерывания контроллеру клавиатуры в порт 061h - необходимо
установить и сразу сбросить 7 бит этого порта. После чего необходимо сбросить заявку на
прерывание и вернуть управление:
;
; IRQ 1 обработчик - клавиатура
;
int9_handler:
push ax
push edi
xor ax, ax
; запрашиваем позиционный код клавиши
in al, 060h
dec al ; Нажат ли ? (его сканкод = 1)
jnz _continue_handling
; Esc нажат - пробуем переключиться в реальный режим, вызвать там
; прерывание 10h с кодом AH=3 (очистка экрана) и вернуться обратно
10
pushfd
cli
in
al, 70h
or
al, 80h
out 70h, al ; запрет NMI
; переключаемся обратно в реальный режим...
lidt fword [REAL_IDTR]
; загружаем в CS селектор 16-битного сегмента с лимитом 64к
jmp 00100000b:__CONT
_continue_handling:
; отжатия не обрабатываем, только нажатия
mov ah, al
and ah, 80h
jnz clear_request
; преобразуем позиционный код в ASCII по таблице
and al, 7Fh
push edi
mov edi, ascii
add di, ax
mov al, [edi]
pop edi
; выводим символы на экран один за другим
mov edi, dword [cursor]
shl edi, 1
mov byte [es:edi], al
inc dword [cursor]
; посылка подтверждения обрабоки в порт клавиатуры
; (установка и сброс 7 бита порта 061h)
Ack:
in al, 061h
or al, 80
out 061h, al
xor al, 80
out 061h, al
clear_request:
pop edi
pop ax
jmp int_EOI
Далее отключаем аппаратные прерывания и NMI уже известным способом. После этого
загружаем в IDTR регистр, описывающий таблицу векторов прерываний в реальном режиме:
; переключаемся обратно в реальный режим...
lidt fword [REAL_IDTR]
Теперь нам нужно передать управление в 16битный сегмент с лимитом 64К. Это нужно
обязательно сделать перед (!) переключением в реальный режим. Иначе процессор буде
переведен в Unreal Mode.
11
Перезагрузка регистра CS новым селектором:
; загружаем в CS селектор 16-битного сегмента с лимитом 64к
jmp 00100000b:__CONT
use16
__CONT:
Т.к. дальнейший код выполняется уже в 16битном сегменте нужно поставить директиву use16.
Теперь можно переключиться в реальный режим сбросом бита Protect Enable (PE) в
управляющем регистре CR0:
; мы в 16битном сегменте. переключаемся в реальный режим.
mov eax, cr0
and al, 0FEh
mov cr0, eax
jmp 0:REAL_ENTRY
; Код реального режима
REAL_ENTRY:
Когда управление передано в эту точку кода - процессор уже находится в реальном режиме и в
теневой части регистра CS содержится дескриптор 16битного малого сегмента (64Кб сегмента
кода ). Однако, регистры DS,SS,ES все еще хранят старые значения, для чего их сразу же нужно
перезагрузить. Также необходимо разрешить аппаратные прерывания и NMI.
REAL_ENTRY:
mov ax, cs
mov ds, ax
mov ss, ax
mov es, ax
; разрешаем аппаратные прерывания и NMI
in al, 70h
and al, 7Fh
out 70h, al
sti
Замечание.
Нужно учитывать тот факт, что вектора 0..1F заняты исключениями. Поэтому IRQ желательно
перенести, перепрограммировав контроллер следующей подпрограммой:
use32
redirect_IRQ:
; BX = { BL = Начало для IRQ 0..7, BH = начало для IRQ 8..15 }
; DX = Маска прерываний IRQ ( DL - для IRQ 0..7, DH - IRQ 8..15 )
; APIC Off
mov ecx,1bh
rdmsr
or ah,1000b
wrmsr
mov al,11h
out 0a0h,al
out 20h,al
mov
al,bh
12
out 0a1h,al
mov al,bl
out 21h,al
mov al,02
out 0a1h,al
mov al,04
out 21h,al
mov al,01
out 0a1h,al
out 21h,al
mov al,dh
out 0a1h,al
mov al,dl
out 21h,al
; APIC On
mov ecx,1bh
rdmsr
and ah,11110111b
wrmsr
ret
В код загрузки после инициализации ЗР, но перед включением прерываний нужно добавить
mov dx, 0FFFFh
mov bx, 2820h
call redirect_IRQ
для переноса IRQ на 20..27 для ведущего и 28..2F для ведомого контроллеров.
Соответственно, при переключении обратно в реальный режим все необходимо вернуть
обратно:
mov dx, 0FFFFh
mov bx,1008h
call redirect_IRQ
3 Инструкция по снятию трассы
Форматы команд приведены в приложении к пояснительной записке к компонентам системы
исследований. Откомпилированный файл образа дискеты ring0.bin должен находиться в той же
папке, что и отладчик. Для отображения состояния основных регистров процессора после
выполнения каждой команды можно воспользоваться командой отладчика «trace on».
1.Запустить bochsdbg.
2.В текстовом меню выбрать пункт Begin Simulation, нажав клавишу 6.
13
рис.1
При этом на экране должна появится статусная строка, содержащая первую команду в очереди
на выполение, ее линейный и «виртуальный » адреса:
«[0xfffffff0] f000:fff0 …: jum far f000:e05b» (под виртуальным адресом подразумевается
сегмент и смещение).
Курсор должен сместиться в строку ввода, начинающуюся с приглашения <bochs:1> (см. рис.2).
рис.2
3.Начальную точку останова выставим с помощью команды установки линейного брейкпойнта
– «lbreak». В качестве аргумента используем линейный адрес, на который будет передано
управление сразу после самотестирования компьютера - 0x7C00 (см. Справочные сведения по
порядку загрузки компьютера)..
4.Запустим эмуляцию без пошагового возвращения управления отладчику (команда «c» или
«continue»). Эмулятор передаст управление отладчику при срабатывании установленного ранее
брейкпоинта:
14
рис.3
5.Далее, необходимо выполнять команду «next» (или «n») до тех пор пока следующей не будет
команда загрузки IDT в регистр IDTR (см рис. 4).
рис.4
15
После этого можно будет получить адрес обработчика прерывания от клавиатуры. Что бы
вывести информацию о дескрипторах IDT необходимо воспользоваться командой «info idt».
Среди прочей выведенной информации будет содержаться селектор и смещение обработчика
прерываний от клавиатуры, имеющего номер 33 (шестнадцатеричное представление - 0x21) в
загруженной нами таблице(см. рис. 5).
рис.5
6.Установим виртуальный брейкпоинт командой «vbreak», передав ей в качестве параметров
данные о селекторе и смещении обработчика прерывания от клавиатуры.
7.Далее необходимо продолжить выполнение программы командой «с» и послать сигнал
нажатия клавиши. Для этого перейдем в окном эмуляции и нажмем клавишу ESC. Управление
будет передано в окно отладчика (рис. 6).
16
Для просмотра состояния стека можно воспользоваться командой «print-stack».
Для просмотра флагов процессора используем команду «info eflags» (см. рис.8).
17
Как видно на рисунке, флаг разрешения прерывания сброшен (выведен в нижнем регистре), что
может служить косвенным признаком того, что произошло именно аппаратное прерывание.
Далее следует код программы обеспечивающий возврат в реальный режим и вход в
бесконечный цикл.
18
Список сокращений и условных обозначений
1.
2.
3.
4.
РР –Реальный режим;
ЗР –Защищенный режим;
<fasm folder>- корневая папка для файлов транслятора fasm;
<bochs folder>- корневая папка для файлов эмулятора bochs.
19
Download