Прерывания в защищенном режиме процессора IA-32

Тема в разделе "WASM.OS.DEVEL", создана пользователем wasm_test, 17 мар 2007.

  1. wasm_test

    wasm_test wasm test user

    Публикаций:
    0
    Регистрация:
    24 ноя 2006
    Сообщения:
    5.582
    Обработка прерываний в защищенном режиме процессора. Перевод в защищенный режим и обратно. Написание простого загрузчика.

    Вообщем-то на написание этой статьи меня толкнула незавершённость цикла статей http://www.wasm.ru/series.php?sid=20 "Процессор Intel в защищенном режиме". К сожалению, автор не успел рассмотреть детально сам процесс переключения режимов, смены адресации, а так же обработку прерываний, без которой невозможна полноценная работа программы в защищенном режиме.
    Писать мы будем код для транслятора FASM, сгенерируем чистый бинарный файл и используем его как образ загрузочной дискеты в эмуляторе Bochs или запишем на дискету и загрузим с нее реальный компьютер.

    После Power-On-Self-Test процессор генерирует прерывание 19h, обработчик которого управляет дальнейшим ходом загрузки. Он находит первый (в порядке приоритетов, устанавливаемых в BIOS Setup) загрузочный диск, считывает его первый сектор по линейному адресу 07C00 и передает ему управление.
    Поскольку процессор при загрузке работает в реальном режиме с 16битной адресацией, нашей задачей будет переключение процессора в защищенный режим с 32битной адресацией, установка обработчиков прерываний и считывание символов с клавиатуры. Так же мы рассмотрим процесс переключения из защищенного режима обратно в реальный.

    Итак, сначала некоторые подготовительные действия. Поскольку наше тело загрузят по адресу 7C00 и мы вряд ли уместимся в пределы одного сектора (512 байт), надо обеспечить загрузку всего остального кода с диска.
    Этот код приведу без дополнительных пояснений, потому что он не совсем в тему нашей статьи:
    Код (Text):
    1. ORG 0x7C00
    2. use16
    3.  
    4. start:
    5.     jmp init
    6.  
    7. ....
    8.  
    9. init:
    10.     ; очистка экрана
    11.     mov  ax,3
    12.     int  10h
    13.  
    14.     ; иинициализация RM-сегментов и стека
    15.     mov  ax, cs
    16.     mov  ds, ax
    17.     mov  es, ax
    18.     mov  ss, ax
    19.     mov  sp, start
    20.     mov  bp, sp
    21.  
    22.     ; сие безобразие никак не умещается в пределы одного сектора (512 байт)...
    23.     ; поэтому подгрузим остальные сектора в память
    24.     mov  ah, 2  ; AH = Function                          : Read sectors
    25.     mov  al, 10 ; AL = Number of sectors to read (1-128) : 10 ( с запасом ;-) )
    26.     xor  ch, ch ; CH = Cylinder number (0-1023)          : 0
    27.     mov  cl, 2  ; CL = Sector number (1-17)              : 2
    28.     xor  dx, dx ; DH = Head number (0-15)                : 0
    29.                 ; DL = Drive number (0-A:, 1-B:)         : 0 (A:)
    30.     mov  bx, start + 512
    31.     int  13h
    32.     jnc  continue_loading
    33.  
    34.     ; ошибка чтения. покажем сообщение и грохнем процессор
    35.     jmp display_read_error
    36. read_error db 'R',7, 'e',7, 'a',7, 'd',7, ' ',7, 'e',7, 'r',7, 'r',7, 'o',7, 'r',7
    37. read_err_l dw $-read_error
    38. display_read_error:
    39.     mov  ax, 0B800h
    40.     mov  es, ax
    41.     xor  di, di
    42.     mov  si, read_error
    43.     mov  cx, word [read_err_l]
    44.     rep  movsb
    45.     jmp  $
    46.  
    47.  
    48. ends: rb 510-(ends-start)
    49. db 055h, 0aah
    50.  
    51.     ; чтение успешно, продолжаем иницилизацию
    52. continue_loading:
    Теперь в памяти аккуратно распологается весь наш код.
    Далее нам необходимо включить отключенные (опять же для совместимости) адресные линии, потому что после включения компьютера функционируют только адресные линии A0-A19. Для использования полноценной 32битной адресации нам нужно включить адресную линию A20, установкой бита 1 на порту ввода-вывода 92h:
    Код (Text):
    1.     ; открываем адресную линию A20
    2.     in   al, 92h
    3.     or   al, 2
    4.     out  92h, al
    На время переключения режимов обязательно надо отключить все прерывания, ибо первый же тик таймера свалит нашу систему. Мы отключим не только аппаратные прерывания, но и замаскируем NMI установкой 7-го бита (отсчет веду с нулевого, как всегда; по счету он, конечно, восьмой) в порту 70h:
    Код (Text):
    1.     ; запрет всех прерываний
    2.     cli
    3.     in   al, 70h
    4.     or   al, 80h
    5.     out  70h, al  ; запрет NMI
    Далее нам необходимо построить GDT и IDT. Если с GDT все понятно, то про IDT стоит рассказать подробнее. И в реальном и в защищенном режиме в регистре IDTR процессор хранит адрес и лимит таблицы прерываний.
    В реальном режиме база IDTR = 00000h, а лимит - 3FFh (размер 400h байт минус еденица). По адресу 00000 находится так называемая таблица векторов прерываний (Interrupt Vector Table), состоящая из 256 векторов. Каждый вектор содержит смещение и сегмент своего обработчика. Обе компоненты занимают 2 байта, таким образом общий размер таблицы составляет 256*2*2 = 1024 = 400h байт.
    В защищенном режиме дело обстоит совершенно по-другому. IDTR должен указывать на так называемую дескрипторную таблицу прерываний (Interrupt Descriptor Table, IDT), состоящую из 8байтных дескрипторов для каждого прерывания, которая может содержать шлюзы задачи, прерывания и ловушки. Мы рассмотрим только шлюз прерывания.
    Шлюз прерывания описывается следующей структурой:
    [​IMG]
    Как нетрудно заметить, его формат напоминает дескриптор из GDT/LDT, но есть некоторые изменения.
    Первое и последнее слова дескриптора шлюза прерывания содержат 32битный адрес обработчика прерывания (зеленое поле "смещение" на картинке). Второе слово содержит селектор сегмента кода, где находится код обработчика. Из дескрипторов сегмента унаследованы только следующие биты:
    P (Present) - бит присутствия. Если =1, прерывание обрабатывается, если =0 генерируется исключение общей защиты.
    DPL (Descriptor Privilege Level) - уровень привилегий, о нем позже.
    D - разрядность.

    При генерации прерывания происходит следующее. Из IDTR извлекается база таблицы дескрипторов прерываний. В этой таблице по номеру прерывания находится дескриптор шлюза прерывания. Если его бит Present сброшен, генерируется исключение общей защиты. Если текущий уровень привилегий отличается от уровня привилегий обработчика, происходит переключение стека и в стеке обработчика сохраняется указатель на стек прерванной задачи (SS и ESP). В стек помещаются регистры EFLAGS, CS, EIP. Для некоторых исключений последним в стек помещается еще и код ошибки, который, кстати, должен вытолкнуть обработчик исключения после обработки. Очищается бит TF, для программного прерывания или исключения сбрасываются биты VM, RF и NT. При вызове обработчика через шлюз прерывания очищается бит IF, блокируя дальнейшие маскируемые аппаратные прерывания.
    После обработки прерывания обработчик должен вытолкнуть из стека код ошибки, если он там есть, и выполнить инструкцию IRETD, которая восстанавливает регистр флагов из стека (поле IOPL меняется, если CPL=0, IF меняется, если CPL<=IOPL). Если уровень привилегий прерванной задачи не равен уровню привилегий обработчика, выталкиваются регистры SS и ESP (обратное восстановление стека).
    При вызове прерывания действуют механизмы защиты:
    - не позволяется передача управления к менее привилегированному коду, если DPL сегмента кода обработчика больше, чем CPL
    - в отличие от обычной передачи управления, не проверяется поле RPL селектора
    - поле DPL шлюза прерывания проверяется только при генерации программного прерывания (INT, INT3, INTO). Для исключений и аппаратных прерываний оно игнорируется

    Мы создадим таблицу прерываний только из 34 записей. Процессору, грубо говоря, наплевать, что их количество не равно 256. Просто при вызове неопределенных в ней прерываний будет исключение общей защиты. Мы определим обработчики для следующих прерываний:
    1 - это у нас будет системный сервис вывода строки на экран =) будет выводить с текущего места ASCIIZ-строку, адрес которой лежит в ESI
    13 - обработчик исключения общей защиты #GP. Покажем строчку "** GENERAL PROTECTION FAULT **" на экране
    32 - IRQ0 - системный таймер. будем по тику таймера менять буковку на экране (смотрится прикольно ;))
    33 - IRQ1 - клавиатура. мы будем отображать символы на экране один за одним, не различая регистр и не воспринимая функциональные клавиши, за одним исключением. Когда будет нажата клавиша <Esc>, мы попробуем переключиться в реальный режим, вызвать прерывание 10h BIOS с кодом AH=3 для очистки экрана и вернуться обратно в защищенный.

    Вот такие нехитрые обработчики. Теперь самое время составить таблицу дескрипторов прерываний для нашего загрузчика:
    Код (Text):
    1. ; Interrupt Descriptor Table
    2. IDT:
    3.          dd 0,0 ; 0
    4.          dw syscall_handler, 08h, 1000111000000000b, 0   ; 1
    5.          dd 0,0 ; 2
    6.          dd 0,0 ; 3
    7.          dd 0,0 ; 4
    8.          dd 0,0 ; 5
    9.          dd 0,0 ; 6
    10.          dd 0,0 ; 7
    11.          dd 0,0 ; 8
    12.          dd 0,0 ; 9
    13.          dd 0,0 ; 10
    14.          dd 0,0 ; 11
    15.          dd 0,0 ; 12
    16.          dw exGP_handler, 08h, 1000111000000000b, 0   ; 13  #GP
    17.          dd 0,0 ; 14
    18.          dd 0,0 ; 15
    19.          dd 0,0 ; 16
    20.          dd 0,0 ; 17
    21.          dd 0,0 ; 18
    22.          dd 0,0 ; 19
    23.          dd 0,0 ; 20
    24.          dd 0,0 ; 21
    25.          dd 0,0 ; 22
    26.          dd 0,0 ; 23
    27.          dd 0,0 ; 24
    28.          dd 0,0 ; 25
    29.          dd 0,0 ; 26
    30.          dd 0,0 ; 27
    31.          dd 0,0 ; 28
    32.          dd 0,0 ; 29
    33.          dd 0,0 ; 30
    34.          dd 0,0 ; 31
    35.          dw int8_handler, 08h, 1000111000000000b, 0   ; IRQ 0 - системный таймер
    36.          dw int9_handler, 08h, 1000111000000000b, 0   ; IRQ 1 - клавиатура
    37.  
    38.     IDT_size     equ $-IDT
    39.     IDTR         dw IDT_size-1
    40.                  dd IDT
    41.     REAL_IDTR    dw 3FFh
    42.                  dd 0
    Обращаю внимание на второй IDTR - REAL_IDTR. Он как раз содержит base=0, limit=3ffh, чтобы загрузить его потом при переключении в реальный режим.

    Вернемся к загрузке. Мы пока что только разрешили адресную линию A20. Самое время загрузить GDTR и IDTR:

    Код (Text):
    1.     ; загрузка GDTR
    2.     lgdt fword [GDTR]
    3.  
    4.     ; загрузка IDTR
    5.     lidt fword [IDTR]
    Далее нам необходимо включить защищенный режим установкой младшего бита служебного регистра CR0:
    Код (Text):
    1.     ; переключение в PM
    2.     mov  eax, cr0
    3.     or   al, 1
    4.     mov  cr0, eax
    Что же теперь мы имеем? Процессор уже находится в защищенном режиме, однако код продолжает выполняться в 16битном сегменте (мы ведь помним про так называемые теневые части сегментых регистров. Теневая часть CS все еще хранит 16битный дескриптор сегмента, который нам достался "в наследство" от реального режима).
    Самое время перезагрузить дескриптор CS командой дальнего джампа и перейти, наконец, в 32битный режим:
    Код (Text):
    1.     ; загружаем новый селектор в CS
    2.     jmp  00001000b:PROTECTED_ENTRY
    Теперь смело ставим директиву use32 в fasm'е, указывая ему на то, что теперь код выполняется в 32битном режиме и стоит изменить режим генерации префиксов 66 и 67 у опкодов.
    Но мы поменяли только регистр CS, в остальные сегментные регистры так и содержат до сих пор старые значения. Пора бы и их переинициализировать селекторами новых сегментов:
    Код (Text):
    1. use32
    2. PROTECTED_ENTRY:
    3.     ; мы в PM, инициализируем селекторы 32-битных сегментов
    4.     mov  ax, 00010000b  ; DATA
    5.     mov  ds, ax
    6.     mov  ss, ax
    7.     mov  ax, 00011000b  ; VIDEO
    8.     mov  es, ax
    Раз мы загрузили нормальный регистр IDTR, мы можем со спокойной совестью, наконец, разрешить аппаратные прерывания и NMI, которые мы запретили на время перехода:
    Код (Text):
    1.     ; разрешаем аппаратные прерывания и NMI
    2.     in   al, 70h
    3.     and  al, 7Fh
    4.     out  70h, al
    5.     sti
    Теперь процессор переведен в полноценный 32битный защищенный режим. Воспользуемся нашим системным сервисом INT 1 и проинформируем об этом пользователя выводом строки "Switched to ProtectedMode. Press <Esc> to clear display" на экране:
    Код (Text):
    1.     ; выводим строку
    2.     mov  esi, string
    3.     int 1
    4.  
    5. ...
    6.  
    7. string   db '  Switched to ProtectedMode. Press <Esc> to clear display', 0
    Далее ставим положение последующего вывода строк на 160 (третья строчка экрана) и переходим в бесконечный цикл ожидания прерываний:
    Код (Text):
    1.     ; переходим на 3 строчку
    2.     mov  dword [cursor], 160
    3.  
    4.     ; бесконечное ожидание прерываний ...
    5.     jmp  $
    Теперь рассмотрим наши обработчики. Начнем с простого - системный сервис INT 1:
    Код (Text):
    1. ;
    2. ; Системный вызов INT 1 - печать строки
    3. ;
    4. ; входные параметры: DS:ESI указывает на ASCIIZ-строку
    5. ;
    6.  
    7. syscall_handler:
    8.     pushad
    9.   _puts:
    10.     lodsb
    11.     mov  edi, dword [cursor]
    12.     mov  [es:edi*2], al
    13.     inc  dword [cursor]
    14.     test al, al
    15.     jnz  _puts
    16.     popad
    17.     iretd
    Я думаю, ничего пояснять не надо ) Кроме того, что селектор ES у нас по-прежнему должен указывать в сегмент видеобуфера.

    Далее обработчик #GP - покажем злостные ругательства и возвратим управление. Стоит заметить, что управление возвращается на ту же инструкцию, которая и вызвала исключение. Не забываем так же вытолкнуть из стека 4х байтный код ошибки.
    Код (Text):
    1. ;
    2. ; Обработчик исключения #GP
    3. ;
    4.  
    5. exGP_handler:
    6.     pop  eax ; код ошибки
    7.     mov  esi, gp
    8.     int  1
    9.     iretd
    10.  
    11.    
    12. gp db '** GENERAL PROTECTION FAULT **',0
    Далее мы напишем обработчик IRQ0 от системного таймера. Мы будем инкрементировать байт ES:[0], который является самым первым байтом видеобуфера и будет отображаться в левом верхнем углу экрана:
    Код (Text):
    1. ;
    2. ; IRQ 0 обработчик - системный таймер
    3. ;
    4.  
    5. int8_handler:
    6.     inc  byte [es:0] ; увеличим символ в левом верхнем углу экрана
    7.     jmp  int_EOI     ; сбросим заявку на прерывание
    Где int_EOI - наш "ничегонеделающий" обработчик, который просто сбрасывает заявку в контроллере прерываний.
    Рассмотрим подробнее аппаратные прерывания. В компьютере есть программируемый контроллер прерываний, котогрый тесно взаимосвязан с процессором:
    [​IMG]
    При возникновении аппаратного прерывания инициируется выход #INT контроллера. Он напрямую соединен с входом #INTR процессора. Если флаг IF=0, прерывание отбрасыватеся. Процессор опрашивает вход #INTR после выполнения каждой инструкции. Как только обнаруживается сигнал, процессор сразу же подтверждает прерывание через выход #INTA. Контроллер прерываний принимает сигнал INTA и выставляет на шину данных значние номера прерывания. Процессор считывает номер прерывания и входит в прерывание по описанной выше схеме.
    Контроллер прерываний 8259A имеет восемь входов IRQ0-IRQ7, открытых для внешних источников, выход INT и вход INTA, соединенные с #INTR и #INTA входом и выходом процессора соответственно. При получении внешнего прерывания на шине данных формируется номер прерывания из суммы IRQ-номера входа и некоторого базового значения, которое обычно равно восьми, а в защищенном режиме придется базу сдвинуть до 20h, как именно - смотри ниже. Таким образом, при получении сигнала на входе IRQ0 генерируется прерывания 8 (у нас будет 32), IRQ1 - 9 (33) и так далее.
    При поступлении нескольких заявок от разных источников они обрабатываются по порядку начиная с меньшего номера IRQ. Можно также выборочно заблокировать некоторые заявки от отдельных IRQ-входов. Блокировку, или маскирование заявок, а так же выбор заявки с наибольним приоритетом обеспечивают три байтовых регистра контроллера - interrupt Mask register (iMr), interrupt Request register (iRr), interrupt Service register (iSr) и арбитр приоритетов Page Resolver (PR).
    Каждый вход IRQ блокируется отдельным битом регистра масок iMr. Если прерывания на входе IRQn разрешены, бит n регистра iMr сброшен. Регистр маски iMr подключен к порту 21h процессора.
    Пусть поступление запросов на вход IRQn разрешено - бит n регистра iMr сброшен. Бит n регистра запросов на прерывание iRr установится, когда придет сигнал на вход IRQn. Арбитр приоритетов PR по значению регистра iSr принимает решение о возможности обслуживания запроса. Заявка может быть принята к обслуживанию, если в регистре iSr не зафиксировано заявок с равным или большим номером. Если все нормально, контроллер выставляет сигнал INT. Заявка принимается к обслуживанию при получении сигнала INTA от процессора, на шину данных выставляется номер прерывания, бит n регистра iRr сбрасывается, бит n регистра iSr устанавливается. Установленный бит n регистра iSr теперь блокирует прерывания от входов с номерами большими либо равными n. Блокировку необходимо снять вручную в процедуре обсдуживания прерывания сбросом бита x в регистре iSr, который подключен к порту 20h (нужно записать значение 3Nh, где N - номер входа). На практике обычно пользуются командой неопределенного сброса, реализуемой посылкой в порт 20h значения 20h. По этой команде в iSr сбрасывается заявка с наименьшим номером - которая обслуживается в данный момент как наиболее приоритетная.
    8 входов слишком мало, поэтому чаще всего используются два контроллера прерываний 8259A - так называемые ведомый контроллер, выход INT которого подключен к входу IRQ2 ведущего контроллера, который уже в свою очередь общается с процессором. Это называется каскадным включением контроллеров прерываний. Регистр iMr ведомого контроллера доступен через порт 0a1h, а регистр iSr - через 0a0h.

    Теперь стало ясно, как должен выглядеть минимальный обработчик внешних IRQ прерываний - посылка неопределенного сброса контроллеру прерываний обоим контроллерам:
    Код (Text):
    1. ;
    2. ; Пустой обработчик. сбрасывает заявку в контроллере
    3. ;
    4.  
    5. int_EOI:
    6.     ; сброс заявки в контроллере прерываний: посылка End-Of-Interrupt (EOI) ...
    7.     push ax
    8.     mov  al, 20h
    9.     out  020h, al   ; ... в ведущий (Master) контроллер ...
    10.     out  0a0h, al   ; ... и в ведомый (Slave) контроллер.
    11.     pop  ax
    12.     iretd           ; возврат из прерывания
    Теперь рассмотрим обработчик прерывания IRQ1 от клавиатуры.
    Прерывание IRQ1 генерируется контроллером клавиатуры каждый раз при нажатии клавиши. Обработчику клавиатуры скан-код считанной клавиши доступен для чтения через порт 060h. Скан-код нужно преобразовать в соответствующий ему ASCII-код символа (если он печатаем) и отобразить на экране. Преобразование произведем по следующей таблице:
    Код (Text):
    1. ; Таблица преобразования печатаемых скан-кодов в ASCII
    2. 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> (Её скан-код равен еденице). Если это действительно так, то вызовем процедуру переключения в реальный режим и генерации там прерывания 10h для очистки экрана. Если это не так, отобразим символ на экране. В любом случае нужно перед сбросом заявки на прерывание в контроллере прерываний послать подтверздение обработки прерывания контроллеру клавиатуры в порт 061h - необходимо установить и сразу сбросить 7 бит этого порта. После чего необходимо сбросить заявку на прерывание и вернуть управление:
    Код (Text):
    1. ;
    2. ; IRQ 1 обработчик - клавиатура
    3. ;
    4.  
    5. int9_handler:
    6.     push ax
    7.     push edi
    8.     xor  ax, ax
    9.  
    10.     ; запрашиваем позиционный код клавиши
    11.     in   al, 060h
    12.  
    13.     dec  al   ; Нажат ли <Esc> ? (его сканкод = 1)
    14.     jnz _continue_handling
    15.  
    16.     ; Esc нажат - пробуем переключиться в реальный режим, вызвать там
    17.     ; прерывание 10h с кодом AH=3 (очистка экрана) и вернуться обратно
    18.  
    19.     mov  ax, 3
    20.     push 10h
    21.     call REAL_MODE_SWITCH_SERVICE
    22.  
    23.     jmp Ack
    24.  
    25. _continue_handling:
    26.     ; отжатия не обрабатываем, только нажатия
    27.     mov  ah, al
    28.     and  ah, 80h
    29.     jnz clear_request
    30.  
    31.     ; преобразуем позиционный код в ASCII по таблице
    32.     and  al, 7Fh
    33.     push edi
    34.     mov  edi, ascii
    35.     add  di, ax
    36.     mov  al, [edi]
    37.     pop  edi
    38.  
    39.     ; выводим символы на экран один за другим
    40.     mov  edi, dword [cursor]
    41.     shl  edi, 1
    42.     mov  byte [es:edi], al
    43.     inc  dword [cursor]
    44.  
    45.     ; посылка подтверждения обрабоки в порт клавиатуры
    46.     ; (установка и сброс 7 бита порта 061h)
    47.    Ack:
    48.     in   al, 061h
    49.     or   al, 80
    50.     out  061h, al
    51.     xor  al, 80
    52.     out  061h, al
    53.  
    54. clear_request:
    55.     pop  edi
    56.     pop  ax
    57.     jmp  int_EOI
    Нам осталось определить функцию REAL_MODE_SWITCH_SERVICE, которой в стеке нужно передать номер прерывания и которая будет делать следующее:
    - полноценный переход в 16битный реальный режим
    - генерация прерывания с заданным номером с сохранением регистров
    - переход обратно в 32битный защищенный режим и возврат управления.
    Чтобы не портить контекст перед вызовом прерывания, нам нужно позаботиться о сохранении тех регистров, которые мы собираемся изменять в процессе перехода:
    Код (Text):
    1. use32
    2. old_ax dw ?
    3. old_cl db ?
    4. REAL_MODE_SWITCH_SERVICE:
    5.     mov  [old_cl], cl
    6.     mov  [old_ax], ax
    Далее достаем из стека номер прерывания командой mov cl, [esp+4]. После этого отключаем аппаратные прерывания и NMI уже известным нам способом, предварительно запомнив командой pushfd старые флаги. После этого загружаем в IDTR регистр, описывающий таблицу векторов прерываний в реальном режиме:
    Код (Text):
    1.     ; переключаемся обратно в реальный режим...
    2.     lidt fword [REAL_IDTR]
    Теперь нам нужно передать управление в 16битный сегмент с лимитом 64К. Это нужно обязательно сделать перед (!) переключением в реальный режим. Иначе мы получим не реальный режим, а так называемый Unreal Mode, который нас не интересует. Итак, перезагрузка регистра CS новым селектором:
    Код (Text):
    1.     ; загружаем в CS селектор 16-битного сегмента с лимитом 64к
    2.     jmp  00100000b:__CONT
    3.  
    4. use16
    5.     __CONT:
    Конечно, нужно поставить директиву use16, т.к. дальнейший код выполняется уже в 16битном сегменте. Теперь можно переключиться в реальный режим сбросом бита Protect Enable (PE) в управляющем регистре CR0:
    Код (Text):
    1.     ; мы в 16битном сегменте. переключаемся в реальный режим.
    2.     mov  eax, cr0
    3.     and  al, 0FEh
    4.     mov  cr0, eax
    5.     jmp  0:REAL_ENTRY
    6.  
    7. ;    Код реального режима
    8. REAL_ENTRY:
    Теперь мы находимся в реальном режиме и в теневой части регистра CS находится дескрикптор 16битного малого сегмента. Однако, регистры DS,SS,ES все еще хранят старые значения, для чего их сразу же нужно перезагрузить. Заодно разрешаем аппаратные прерывания и NMI.
    Код (Text):
    1. REAL_ENTRY:
    2.     mov  ax, cs
    3.     mov  ds, ax
    4.     mov  ss, ax
    5.     mov  es, ax
    6.  
    7.     ; разрешаем аппаратные прерывания и NMI
    8.     in   al, 70h
    9.     and  al, 7Fh
    10.     out  70h, al
    11.     sti
    Теперь динамически сформируем команду INT xx, потому что система команд архитектуры IA-32 поддерживает только команду INT с фиксированным номером прерывания. Нам придется сформировать ее динамически: опкод команды INT xx равен CD xx. Перед генерацией прерывания заодно восстановим регистры ax и cl, которые подвергались изменению в процессе переключения:
    Код (Text):
    1.     mov  [int_no], cl
    2.  
    3.     mov  ax, [old_ax]
    4.     mov  cl, [old_cl]
    5.  
    6.     db 0CDh       ; INT xx
    7.     int_no db 0
    8.  
    9.     mov  [old_ax], ax
    После возврата из прерывания запомним возвращенное значение ax, т.к. мы его поменяем в процессе обратного переключения. Далее все по-новой: запрет аппаратных прерываний и NMI, инициализация защищенного режима, прыжок в 32битный сегмент кода, перезагрузка селекторов DS,ES,SS.
    Далее восстанавливаем AX, восстановление флагов командой popfd (нельзя тупо сделать sti, что казалось бы более очевидным, потому как перед во время вызова REAL_MODE_SWTICH_SERVICE прерывания отключены!) и делаем RET с выталкиванием четырех байт из стека (номер прерывания):
    Код (Text):
    1.      mov  ax, [old_ax]
    2.  
    3.     ; разрешаем аппаратные прерывания и NMI
    4.     in   al, 70h
    5.     and  al, 7Fh
    6.     out  70h, al
    7.     popfd
    8.  
    9.     ret  4
    На этом мы, собственно, закончим наше и так затянувшееся повествование. Исходный код загрузчика и файл конфигурации Bochs (только в нем надо пути сменить на другие к ROM-BIOS) можно найти здесь: http://gr8.cih.ms/uploads/loader.rar

    Пока!

    (C) Great, 2007

    В статье использованы материалы wasm.ru, dims.karelia.ru и книги А.Жуков, А.Авдюхин "Ассемблер", СПб.:"БХВ-Петербург", 2003.

    ЗЫ. Как было справедливо замечено, вектора 0..1F заняты исключениями. Поэтому IRQ желательно перенсести, перепрограммировав контроллер следующей подпрограммой:
    Код (Text):
    1. use32
    2. redirect_IRQ:
    3. ; BX = { BL = Начало для IRQ 0..7, BH = начало для IRQ 8..15 }
    4. ; DX = Маска прерываний IRQ ( DL - для IRQ 0..7, DH - IRQ 8..15 )
    5.  
    6.         ; APIC Off
    7.         mov     ecx,1bh
    8.         rdmsr
    9.         or      ah,1000b
    10.         wrmsr
    11.  
    12.         mov     al,11h
    13.         out     0a0h,al
    14.         out     20h,al
    15.  
    16.         mov     al,bh
    17.         out     0a1h,al
    18.         mov     al,bl
    19.         out     21h,al
    20.  
    21.         mov     al,02
    22.         out     0a1h,al
    23.         mov     al,04
    24.         out     21h,al
    25.  
    26.         mov     al,01
    27.         out     0a1h,al
    28.         out     21h,al
    29.  
    30.         mov     al,dh
    31.         out     0a1h,al
    32.         mov     al,dl
    33.         out     21h,al
    34.  
    35.         ; APIC On
    36.         mov     ecx,1bh
    37.         rdmsr
    38.         and     ah,11110111b
    39.         wrmsr
    40.  
    41.         ret
    В код загрузки после инициализации протектед моде но перед включением прерываний придется добавить
    Код (Text):
    1.     mov  dx, 0FFFFh
    2.     mov  bx, 2820h
    3.     call redirect_IRQ
    для переноса IRQ на 20..27 для ведущего и 28..2F для ведомого контроллеров.
     
  2. Pavia

    Pavia Well-Known Member

    Публикаций:
    0
    Регистрация:
    17 июн 2003
    Сообщения:
    2.409
    Адрес:
    Fryazino
    Great
    Вектора прерываний с 0 по 31 заняты. Нужно перепрограммировать конролер прерываний у тебя это не описано.
    Сам конролер и схема описаны не точно. Начнем с того что я уверен что описанно не верно. А во вторых надо бы говорить про современное оборудование. Контролер PIC эмулируется котролерам APIC.
     
  3. wasm_test

    wasm_test wasm test user

    Публикаций:
    0
    Регистрация:
    24 ноя 2006
    Сообщения:
    5.582
    для примера использовались стандартные номера, база=8. хотя они, конечно, заняты исключениями.

    перепрограммирование осуществляется следующей подпрограммой:


    Код (Text):
    1. use32
    2. redirect_IRQ:
    3. ; BX = { BL = Начало для IRQ 0..7, BH = начало для IRQ 8..15 }
    4. ; DX = Маска прерываний IRQ ( DL - для IRQ 0..7, DH - IRQ 8..15 )
    5.  
    6.         ; APIC Off
    7.         mov     ecx,1bh
    8.         rdmsr
    9.         or      ah,1000b
    10.         wrmsr
    11.  
    12.         mov     al,11h
    13.         out     0a0h,al
    14.         out     20h,al
    15.  
    16.         mov     al,bh
    17.         out     0a1h,al
    18.         mov     al,bl
    19.         out     21h,al
    20.  
    21.         mov     al,02
    22.         out     0a1h,al
    23.         mov     al,04
    24.         out     21h,al
    25.  
    26.         mov     al,01
    27.         out     0a1h,al
    28.         out     21h,al
    29.  
    30.         mov     al,dh
    31.         out     0a1h,al
    32.         mov     al,dl
    33.         out     21h,al
    34.  
    35.         ; APIC On
    36.         mov     ecx,1bh
    37.         rdmsr
    38.         and     ah,11110111b
    39.         wrmsr
    40.  
    41.         ret
    В код загрузки после инициализации протектед моде но перед включением прерываний придется добавить
    Код (Text):
    1.     mov  dx, 0FFFFh
    2.     mov  bx, 2820h
    3.     call redirect_IRQ
    для переноса IRQ на 20..27 для ведущего и 28..2F для ведомого контроллеров.
     
  4. St

    St New Member

    Публикаций:
    0
    Регистрация:
    11 авг 2006
    Сообщения:
    72
    Спасибо! Доступно, легко.
    Код (Text):
    1. ; иинициализация RM-сегментов и стека
    2.     cli              [quote]; может быть  добавить?[/quote]
    3. mov  ax, cs
    4.     mov  ds, ax
    5.     mov  es, ax
    6.     mov  ss, ax
    7.     mov  sp, start ; [quote]а если указать 0х7С00 на всякий случай?[/quote]
    8. mov  bp, sp
     
  5. wasm_test

    wasm_test wasm test user

    Публикаций:
    0
    Регистрация:
    24 ноя 2006
    Сообщения:
    5.582
    ну вообще нам на тот момент прерывания не мешают. если тебе не нравится, отключи :P
    поскольку указано org 0x7c00 а сразу на ней метка start, очевидно, что это одно и то же.
    по желанию можно пихнуть 7C00h.
     
  6. St

    St New Member

    Публикаций:
    0
    Регистрация:
    11 авг 2006
    Сообщения:
    72
    Код (Text):
    1. ; сие безобразие никак не умещается в пределы одного сектора (512 байт)...
    2.     ; поэтому подгрузим остальные сектора в память
    3.     mov  ah, 2  ; AH = Function                  
    4.  и так далее
    Сильно ли отличается загрузка с жесткого диска?
     
  7. wasm_test

    wasm_test wasm test user

    Публикаций:
    0
    Регистрация:
    24 ноя 2006
    Сообщения:
    5.582
    St
    нет
    номер диска задается в регистре DL. Седьмой бит устанавливается для жесткого диска, иначе подразумевается дискета. Остальные биты 0..6 определяют номер дисковода

    Еще есть вот такая функция того же прерывания для жесткого диска:
    За подробностями обращайся к описанию прерывания 13h в Interrupt List'е Ralf'а Brown'а
     
  8. Mika0x65

    Mika0x65 New Member

    Публикаций:
    0
    Регистрация:
    30 июл 2005
    Сообщения:
    1.384
    Точнее сказать, "если DPL сегмента кода обработчика численно больше чем CPL", я думаю.
     
  9. wasm_test

    wasm_test wasm test user

    Публикаций:
    0
    Регистрация:
    24 ноя 2006
    Сообщения:
    5.582
    ой ) конечно, больше (в числах). а привилегий меньше :P
     
  10. wasm_test

    wasm_test wasm test user

    Публикаций:
    0
    Регистрация:
    24 ноя 2006
    Сообщения:
    5.582
    Хм, я забыл одну деталь. Поскольку мы перепрограммировали APIC на другие вектора, включать прерывания в реальном режиме уже нельзя ) Первый же тик таймера вызовет совсем не тот обработчик. Нужно либо не разрешать прерывания на время переключения в реальный режим вообще, либо переносить их для реального режима обратно на старые вектора. Проще их не включать :)
     
  11. Nouzui

    Nouzui New Member

    Публикаций:
    0
    Регистрация:
    17 ноя 2006
    Сообщения:
    856
    жжошь
     
  12. wasm_test

    wasm_test wasm test user

    Публикаций:
    0
    Регистрация:
    24 ноя 2006
    Сообщения:
    5.582
    ненуачо.. а так придется переносить IRQ обратно на 8..F и 10..17, а потом снова на 20..27 и 28..2F
    нафиг такой геморрой, если обслуживать прерывания в реальном режиме не приходится. тем более что код реального режима состоит из одного INT xx практически.
     
  13. Nouzui

    Nouzui New Member

    Публикаций:
    0
    Регистрация:
    17 ноя 2006
    Сообщения:
    856
    да я так просто
     
  14. wasm_test

    wasm_test wasm test user

    Публикаций:
    0
    Регистрация:
    24 ноя 2006
    Сообщения:
    5.582
    Сие творение теперь доступно тут: http://www.wasm.ru/article.php?article=ia32int
     
  15. quirk

    quirk New Member

    Публикаций:
    0
    Регистрация:
    21 окт 2006
    Сообщения:
    12
    В статье написано что проц вызывает 19-е прерывание, значит в памяти что-то уже есть. Хотелось бы знать как оно туда попадает, а в идеале все шаги загрузки компа начиная с момента подачи питания.
     
  16. rei3er

    rei3er maxim

    Публикаций:
    0
    Регистрация:
    15 янв 2007
    Сообщения:
    917
    Адрес:
    minsk
    IA-32 Intel Architecture Software Developer's Manual, Volume 3, Chapter 9
     
  17. Pavia

    Pavia Well-Known Member

    Публикаций:
    0
    Регистрация:
    17 июн 2003
    Сообщения:
    2.409
    Адрес:
    Fryazino
    quirk
    На это отдельная, статья нужна. После падания питания процессор начинает выполнять код с адреса FFFF:0000 где расположен BIOS. Происходит тестирование и настройка оборудования. После уже загружается MBR.
     
  18. Mika0x65

    Mika0x65 New Member

    Публикаций:
    0
    Регистрация:
    30 июл 2005
    Сообщения:
    1.384
    Насколько я помню, первая инструкция начинает выполняться по адресу 0xFFFFFFF0. Т.е. адрес используется в форме 0xFFFF:0000, но при подаче его на адресные линии он автоматически дополняется справа до 0xFFFFFFF0. Это происходит до первого длинного перехода, если я правильно понял Григорьева. Но вот зачем так сделано я так и не понял.
     
  19. wasm_test

    wasm_test wasm test user

    Публикаций:
    0
    Регистрация:
    24 ноя 2006
    Сообщения:
    5.582
    обработчик находится в BIOS
    Наверное, по умолчанию режим 32битный. После длинного прыжка перезагружается CS и адресация становится 16битной. Хотя это только догадки :)
     
  20. rei3er

    rei3er maxim

    Публикаций:
    0
    Регистрация:
    15 янв 2007
    Сообщения:
    917
    Адрес:
    minsk
    после RESET#
    база в CS = 0xFFFF0000; лимит = 0xFFFF
    EIP = 0x0000FFF0
    отсюда получаем адрес первой инструкции 0xFFFF0000 + 0x0000FFF0 = 0xFFFFFFF0