Программирование процессоров Intel x86 в защищённом режиме
Первая преамбула: одна задача

Автор: sergh
The RSDN Group

Версия текста: 1.0

Сегмент состояния задачи
Дескриптор сегмента состояния задачи
Инициализация
Пример

Я один, но это не значит что я одинок..

Виктор Цой

За двумя зайцами погонишься – ни одного не поймаешь

Пословица

Первая глава-преамбула, подготавливающая почву для темы «Защита: передача управления».

Задача главы предельно проста: объяснить, как правильно создать одну задачу (task) и как проинициализировать регистр задачи (Task Register, TR). В данный момент механизм задач интересен нам не сам по себе, а только как некоторая инфраструктура, которую процессор использует при передаче управления между сегментами кода с различными уровнями привилегий.

Заданий к главе нет.

Сегмент состояния задачи

Сегмент состояния задачи (task state segment, TSS) содержит «состояние задачи» – некоторые специфичные для задачи данные, определяющие её поведение. Грубо и упрощённо, можно разбить эти данные на две части:

Минимальный размер TSS – 104 байта, именно столько занимает стандартная структура, содержащая все обязательные данные. Поскольку в течение этой главы мы не будем делать с задачами ничего интересного, структура и содержимое TSS пока не имеют значения, то есть он может состоять из 104-х байт абсолютно произвольного содержания. Для простоты, пусть будут нули.

Выглядит это так:

tss_seg   db 104 dup(0)

Дескриптор сегмента состояния задачи

Естественно, сегмент не может существовать без дескриптора. Дескриптор сегмента состояния задачи (task state segment descriptor) должен находиться в GDT, его формат таков:

Положение Название Краткое описание
Два младших байта (нулевой и первый) Segment Limit (part 1) Младшие 16 бит поля Segment Limit.
Второй, третий, четвёртый байты Base Address (part 1) Младшие три байта поля Base Address.
0-й – 3-й биты пятого байта Type Дескриптору сегмента состояния задачи соответствует 1001 (на самом деле, тут немного интереснее, более подробно в настоящей главе про многозадачность).
4-й бит пятого байта S Это системный дескриптор, поэтому 0.
5-й – 6-й биты пятого байта DPL Пока 0, подробнее в главе про многозадачность.
7-й бит пятого байта P Устанавливайте в 1, подробнее в приложении.
0-й – 3-й биты шестого байта Segment Limit (part 2) Старшие 4 бита поля Segment Limit.
4-й – 6-й биты шестого байта ?? Устанавливайте в 0.
7-й бит шестого байта G Granularity. Флаг гранулярности. Теоретически, вам может потребоваться сегмент состояния задачи, превышающий 1 мегабайт, но в рамках курса этого не произойдёт.
Седьмой байт Base Address (part 2) Старший байт поля Base Address.
Таблица 1. Формат дескриптора сегмента состояния задачи.

На картинке это выглядит так:


Рисунок 1. Формат дескриптора сегмента состояния задачи.

В качестве примера – дескриптор сегмента состояния задачи, размер сегмента 104 байта, базовый адрес 01020304h:

    db      103         ; Segment Limit
    db      0 
    db      4           ; base address
    db      3 
    db      2 
    db      10001001b   ; 1, DPL – 00, S - 0, 1001
    db      0           ; G - 0, 000, Limit - 0
    db      1           ; base address

Или так:

    ; Дескриптор сегмента состояния задачи, 104 байта, базовый адрес 01020304h
    segment_descriptor <103, 0304h, 2h, 10001001b, 0,  1h>

Инициализация

Для того, чтобы корректно инициализировать механизм управления задачами, нужно выполнить три действия:

Инструкция ltr относится к привилегированным и имеет следующий формат:

		ltr selector

Где selector это 16-ти разрядный регистр или переменная типа dw.

Пример

Замечательный пример, сложностью и объёмом возвращающий нас примерно ко второй главе. Коротенькая инициализация, две инструкции, ради которых всё это затевалось, возврат в реальный режим. Всегда бы так!

; task.asm
; Программа, инициализирующая TR

        .model tiny
        .code
        .386p
        org     100h

;;;;;;;;;;;;;;;;;;;;;;;;;;;        
;
; Структуры
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;

; Сегментный дескриптор
segment_descriptor struct
    limit_low   dw      0    ; Младшие два байта поля Segment limit
    base_low    dw      0    ; Младшие два байта поля Base Address
    base_high0  db      0    ; Второй байт поля Base Address
    type_and_permit db  0    ; Флаги
    flags       db      0    ; Ещё одни флаги
    base_high1  db      0    ; Старший байт поля Base Address
segment_descriptor ends

; Регистр, описывающий таблицу дескриптров
table_register struct
    limit       dw      0    ; Table Limit
    base        dd      0    ; Linear Base Address
table_register ends

;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; Код
;
;;;;;;;;;;;;;;;;;;;;;;;;;;

start:
        ; Подготавливаем DS
        push    cs
        pop     ds

        ; Получаем адрес начала сегмента TSS
        call    cs_to_eax
        add     eax, offset tss_seg

        ; Записываем его в дескриптор
        mov     word ptr tss_dsc.base_low, ax
        shr     eax, 16
        mov     byte ptr tss_dsc.base_high0, al

        ; Запрещаем прерывания
        call disable_interrupts
        ; Инициалиируем GDT
        call initialize_gdt
        ; Переключаем режим
        call set_PE

        ; загрузить селектор дескриптора TSS в TR
        mov  ax, 8
        ltr  ax

        call clear_PE
        call enable_interrupts

        ret

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; Данные
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; Глобальная таблица дескрипторов
GDT       label   byte
          ; Нулевой дескриптор
          segment_descriptor <> 
          ; Дескриптор TSS, 104 байта, базовый адрес 0
tss_dsc   segment_descriptor <103, 0, 0, 10001001b, 0,  0>

; Данные для загрузки в GDTR
gdtr      table_register <$ - GDT - 1, 0>

; Сегмент TSS. Можно было его вообще не объявлять и оставить 
; базовый адрес 0, всё равно бы сработало. Но в будущем пригодится. 
tss_seg   db 104 dup(0)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; Служебные функции
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; Инициализирует GDT
initialize_gdt:
        ; Вычисляем линейный адрес начала массива дескрипторов
        call    cs_to_eax
        add     eax, offset GDT
        ; Записываем его в структуру
        mov     dword ptr gdtr.base, eax

        ; Загружаем GDTR
        lgdt    fword ptr gdtr
        ret

; Запрещает маскируемые и немаскируемые прерывания
disable_interrupts:
        cli              ; запретить прерывания
        in      al, 70h  ; индексный порт CMOS
        or      al, 80h  ; установка бита 7 в нем запрещает NMI
        out     70h, al
        ret

; Разрешает маскируемые и немаскируемые прерывания
enable_interrupts:
        in      al, 70h  ; индексный порт CMOS
        and     al, 7Fh  ; сброс бита 7 отменяет блокирование NMI
        out     70h, al
        sti              ; разрешить прерывания
        ret

; Устанавливает флаг PE
set_PE:
        mov     eax, cr0 ; прочитать регистр CR0
        or      al, 1    ; установить бит PE,
        mov     cr0, eax ; с этого момента мы в защищенном режиме
        ret

; Сбрасывает флаг PE
clear_PE:
        mov     eax, cr0 ; прочитать CR0
        and     al, 0FEh ; сбросить бит PE
        mov     cr0, eax ; с этого момента мы в реальном режиме
        ret

; Вычисляет линейный адрес начала сегмента кода
cs_to_eax:
        mov     eax, 0
        mov     ax, cs
        shl     eax, 4
        ret

        end     start


Любой из материалов, опубликованных на этом сервере, не может быть воспроизведен в какой бы то ни было форме и какими бы то ни было средствами без письменного разрешения владельцев авторских прав.