ФЕДЕРАЛЬНОЕ АГЕНТСТВО ПО ОБРАЗОВАНИЮ ГОСУДАРСТВЕННОЕ ОБРАЗОВАТЕЛЬНОЕ УЧРЕЖДЕНИЕ ВЫСШЕГО ПРОФЕССИОНАЛЬНОГО ОБРАЗОВАНИЯ «САМАРСКИЙ ГОСУДАРСТВЕННЫЙ АЭРОКОСМИЧЕСКИЙ УНИВЕРСИТЕТ им. АКАДЕМИКА С.П. КОРОЛЕВА» (СГАУ) Кафедра информационных систем и технологий Пояснительная записка к индивидуальному заданию Тема работы: «Система прерываний персональной ЭВМ 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