Szukaj…


Tryb rzeczywisty

Kiedy Intel zaprojektował oryginalny x86, model 8086 (i pochodną 8088), obejmował on segmentację, aby umożliwić 16-bitowemu procesorowi dostęp do adresu o wartości większej niż 16 bitów. Zrobili to, ustawiając 16-bitowe adresy względem danego 16-bitowego rejestru segmentów, którego zdefiniowali cztery: segment kodu ( CS ), segment danych ( DS ), dodatkowy segment ( ES ) i segment stosu ( SS ) .

Większość instrukcji sugerowała, którego rejestru segmentów użyć: instrukcje zostały pobrane z segmentu kodu, PUSH i POP sugerowały segment stosu, a proste odniesienia do danych sugerowały segment danych - chociaż można to zmienić, aby uzyskać dostęp do pamięci w dowolnym innym segmencie.

Implementacja była prosta: dla każdego dostępu do pamięci procesor pobierał domyślny (lub jawny) rejestr segmentów, przesunął go o cztery miejsca w lewo, a następnie dodał wskazany adres:

        +-------------------+---------+
Segment | 16-bit value      | 0 0 0 0 |
        +-------------------+---------+
                   PLUS
        +---------+-------------------+
Address | 0 0 0 0 | 16-bit value      |
        +---------+-------------------+
                  EQUALS
        +-----------------------------+
Result  |  20-bit memory address      |
        +-----------------------------+

Pozwoliło to na różne techniki:

  • Umożliwianie wzajemnego dostępu do kodu, danych i stosu (wszystkie CS , DS i SS miały tę samą wartość);
  • Utrzymywanie kodu, danych i stosu całkowicie oddzielnie od siebie ( CS , DS i SS wszystkie 4K (lub więcej) oddzielnie od siebie - pamiętaj, że zostanie pomnożony przez 16, więc to 64K).

Umożliwiło to również dziwne nakładanie się i różnego rodzaju dziwne rzeczy!

Kiedy wynaleziono 80286, wspierał on ten starszy tryb (teraz nazywany „trybem rzeczywistym”), ale dodał nowy tryb o nazwie „Tryb chroniony” (qv).

Ważne jest, aby zauważyć, że w trybie rzeczywistym:

  • Dowolny adres pamięci był dostępny, po prostu umieszczając poprawną wartość w rejestrze segmentu i uzyskując dostęp do adresu 16-bitowego;
  • Zakres „ochrony” polegał na umożliwieniu programiście oddzielenia różnych obszarów pamięci do różnych celów i utrudnienia przypadkowego zapisania niewłaściwych danych - jednocześnie umożliwiając to.

Innymi słowy ... wcale nie bardzo chroniony!

Tryb obronny

Wprowadzenie

Kiedy wynaleziono 80286, wspierał on starszą segmentację 8086 (teraz nazywaną „trybem rzeczywistym”) i dodał nowy tryb o nazwie „tryb chroniony”. Ten tryb był obecny w każdym procesorze x86, aczkolwiek został wzbogacony o różne ulepszenia, takie jak adresowanie 32- i 64-bitowe.

Projekt

W trybie chronionym proste „Dodaj adres do wartości przesuniętego rejestru segmentów” zostało całkowicie usunięte. Zachowali rejestry segmentów, ale zamiast ich używać do obliczania adresu, użyli ich do indeksowania w tabeli (właściwie jednej z dwóch ...), która definiowała dostęp do segmentu. Definicja ta nie tylko opisywała, gdzie w pamięci był Segment (używając Podstawy i Limit), ale także jaki to był typ (Kod, Dane, Stos, a nawet System) i jakie programy mogły do niego uzyskać dostęp (jądro systemu operacyjnego, normalny program , Sterownik urządzenia itp.).

Rejestr segmentowy

Każdy 16-bitowy rejestr segmentów przyjął następującą formę:

+------------+-----+------+
| Desc Index | G/L | Priv |
+------------+-----+------+
 Desc Index = 13-bit index into a Descriptor Table (described below)
 G/L        = 1-bit flag for which Descriptor Table to Index: Global or Local
 Priv       = 2-bit field defining the Privilege level for access

Globalny / lokalny

Bit Globalny / Lokalny określa, czy dostęp był do Globalnej Tabeli Deskryptorów (nazywanej nieoczekiwanie Globalną Tabelą Deskryptorów lub GDT), czy Lokalnej Tabeli Deskryptorów (LDT). Idea LDT polegała na tym, że każdy program może mieć własną tabelę deskryptorów - system operacyjny definiuje globalny zestaw segmentów, a każdy program ma swój własny zestaw lokalnego kodu, danych i segmentów stosu. System operacyjny zarządza pamięcią między różnymi tabelami deskryptorów.

Tabela deskryptorów

Każda tabela deskryptorów (globalna lub lokalna) zawierała 64 tys. Tablic z 8192 deskryptorami: każdy 8-bajtowy rekord, który definiował wiele aspektów opisywanego segmentu. Pola indeksu deskryptorów rejestrów segmentów pozwoliły na 8192 deskryptorów: bez zbiegów okoliczności!

Deskryptor

Deskryptor zawierał następujące informacje - zwróć uwagę, że format deskryptora zmienił się wraz z wydaniem nowych procesorów, ale w każdym z nich zachowano ten sam rodzaj informacji:

  • Baza
    To zdefiniowało adres początkowy segmentu pamięci.
  • Limit
    To określa rozmiar segmentu pamięci - w pewnym sensie. Musieli podjąć decyzję: czy rozmiar 0x0000 oznaczałby rozmiar 0 , więc niedostępny? Czy maksymalny rozmiar?
    Zamiast tego wybrali trzecią opcję: pole Limit było ostatnią adresowalną lokalizacją w segmencie. Oznaczało to, że można zdefiniować segment pożegnalny; lub maksymalny rozmiar adresu.
  • Rodzaj
    Było wiele rodzajów Segmentów: tradycyjny Kod, Dane i Stos (patrz poniżej), ale zdefiniowano również inne Segmenty Systemu:
    • Lokalne segmenty tabeli deskryptorów określają, ile lokalnych deskryptorów można uzyskać;
    • Segmenty stanów zadań można wykorzystać do przełączania kontekstu zarządzanego sprzętowo;
    • Kontrolowane „bramki połączeń”, które mogą umożliwiać programom wywoływanie w systemie operacyjnym - ale tylko poprzez starannie zarządzane punkty wejścia.
  • Atrybuty
    Zachowano także niektóre atrybuty segmentu, w stosownych przypadkach:
    • Tylko do odczytu vs. do odczytu i zapisu;
    • Niezależnie od tego, czy segment był obecnie obecny, czy nie - umożliwiając zarządzanie pamięcią na żądanie;
    • Jaki poziom kodu (OS vs Sterownik vs program) może uzyskać dostęp do tego segmentu.

Nareszcie prawdziwa ochrona!

Jeśli system operacyjny zatrzyma tabele deskryptorów w segmentach, do których dostęp nie mogą uzyskać zwykłe programy, wówczas może ściśle zarządzać, które segmenty zostały zdefiniowane oraz jaka pamięć została przypisana i dostępna dla każdego z nich. Program mógłby wytworzyć dowolną wartość rejestru segmentu, jaką mu się podoba - ale gdyby miał zuchwałość, aby faktycznie załadować go do rejestru segmentu ! ... sprzęt CPU rozpoznałby, że proponowana wartość deskryptora złamała jedną z wielu reguł, i zamiast zrealizować żądanie, zgłosi wyjątek do systemu operacyjnego, aby umożliwić mu obsługę błędnego programu.

Ten wyjątek był zwykle nr 13, Ogólny wyjątek ochronny - rozsławiony przez Microsoft Windows na całym świecie ... (Czy ktoś myśli, że inżynier Intela był przesądny?)

Błędy

Do rodzajów błędów, które mogą się zdarzyć, należą:

  • Jeśli proponowany Indeks deskryptorów był większy niż rozmiar tabeli;

  • Jeśli proponowany deskryptor był deskryptorem systemu, a nie kodem, danymi lub stosem;

  • Jeśli proponowany deskryptor był bardziej uprzywilejowany niż program żądający;

  • Jeśli proponowany deskryptor został oznaczony jako Nieczytelny (taki jak segment kodu), ale próbowano go raczej odczytać niż wykonać;

  • Jeśli proponowany deskryptor został oznaczony jako Nieobecny.

    Zauważ, że ten ostatni może nie być poważnym problemem dla programu: system operacyjny może zanotować flagę, przywrócić segment, oznaczyć go jako „Obecny”, a następnie pozwolić na pomyślne wykonanie błędnej instrukcji.

Lub może Deskryptor został pomyślnie załadowany do rejestru segmentów, ale później przyszły dostęp z nim złamał jedną z wielu zasad:

  • Rejestr segmentów został załadowany z 0x0000 deskryptorów 0x0000 dla GDT. Zostało to zarezerwowane przez sprzęt jako NULL ;
  • Jeśli załadowany deskryptor został oznaczony jako tylko do odczytu, ale podjęto próbę zapisu.
  • Jeśli jakakolwiek część dostępu (1, 2, 4 lub więcej bajtów) była poza limitem segmentu.

Przełączanie w tryb chroniony

Przejście do trybu chronionego jest łatwe: wystarczy ustawić jeden bit w rejestrze kontrolnym. Ale pozostanie w trybie chronionym, bez procesora wznoszącego ręce i resetującego się z powodu niewiedzy, co robić dalej, wymaga dużo przygotowania.

Krótko mówiąc, wymagane kroki są następujące:

  • Należy zdefiniować obszar pamięci dla globalnej tabeli deskryptorów, aby zdefiniować co najmniej trzy deskryptory:

    1. Deskryptor zero, NULL ;
    2. Kolejny deskryptor dla segmentu kodu;
    3. Kolejny deskryptor dla segmentu danych.

      Można tego użyć zarówno dla danych, jak i stosu.

  • Rejestr tabeli globalnych deskryptorów ( GDTR ) musi zostać zainicjowany, aby wskazywał na ten zdefiniowany obszar pamięci;

     GDT_Ptr    dw      SIZE GDT
                dd      OFFSET GDT
    
                ...
    
                lgdt    [GDT_Ptr]
    
  • Bit PM w CR0 należy ustawić:

         mov   eax, cr0      ; Get CR0 into register
         or    eax, 0x01     ; Set the Protected Mode bit
         mov   cr0, eax      ; We're now in Protected Mode!
    
  • Rejestry segmentów należy załadować z GDT, aby usunąć bieżące wartości trybu rzeczywistego:

         jmp   0x0008:NowInPM  ; This is a FAR Jump. 0x0008 is the Code Descriptor
    
    NowInPM:
         mov   ax, 0x0010      ; This is the Data Descriptor
         mov   ds, ax
         mov   es, ax
         mov   ss, ax
         mov   sp, 0x0000      ; Top of stack!
    

Zauważ, że jest to absolutne minimum, tylko po to, aby procesor przeszedł w tryb chroniony. Aby faktycznie przygotować cały system, może wymagać wielu dodatkowych kroków. Na przykład:

  • Konieczne może być włączenie górnych obszarów pamięci - wyłączenie bramki A20 ;
  • Przerwania powinny być zdecydowanie wyłączone - ale być może różne moduły obsługi błędów mogłyby zostać skonfigurowane przed przejściem do trybu chronionego, aby pozwolić na błędy na wczesnym etapie przetwarzania.

Oryginalny autor tej sekcji napisał cały samouczek na temat wchodzenia w tryb chroniony i pracy z nim.

Tryb nierealny

Tryb nierealny wykorzystuje dwa fakty dotyczące ładowania zarówno procesorów Intel, jak i AMD i zapisywania informacji w celu opisania segmentu.

  1. Procesor buforuje informacje o deskryptorze pobierane podczas ruchu w rejestrze selektora w trybie chronionym.
    Informacje te są przechowywane w niewidocznej architektonicznie części rejestru selektora.

  2. W trybie rzeczywistym rejestry selektorów nazywane są rejestrami segmentowymi, ale poza tym wyznaczają ten sam zestaw rejestrów i jako takie mają również niewidoczną część. Części te są wypełnione stałymi wartościami, ale dla podstawy, która pochodzi z właśnie załadowanej wartości.

W takim widoku tryb rzeczywisty jest tylko szczególnym przypadkiem trybu chronionego: gdzie informacje o segmencie, takie jak baza i limit, są pobierane bez GDT / LDT, ale nadal odczytywane z ukrytej części rejestru segmentu.


Przełączając w trybie chronionym i wytwarzając GDT, można utworzyć segment z pożądanymi atrybutami, na przykład podstawa 0 i limit 4GiB.
Poprzez kolejne ładowanie rejestru selektora takie atrybuty są buforowane, wówczas możliwe jest przełączenie z powrotem w trybie rzeczywistym i posiadanie rejestru segmentowego, przez który uzyskuje dostęp do całej 32-bitowej przestrzeni adresowej.

BITS 16

jmp 7c0h:__START__

__START__:
 push cs
 pop ds
 push ds
 pop ss
 xor sp, sp

 
 lgdt [GDT]            ;Set the GDTR register
 

 cli                ;We don't have an IDT set, we can't handle interrupts


 ;Entering protected mode

 mov eax, cr0
 or ax, 01h            ;Set bit PE (bit 0) of CR0
 mov cr0, eax            ;Apply

 ;We are now in Protected mode

 mov bx, 08h           ;Selector to use, RPL = 0, Table = 0 (GDT), Index = 1

 mov fs, bx            ;Load FS with descriptor 1 info
 mov gs, bx            ;Load GS with descriptor 1 info

 ;Exit protected mode

 and ax, 0fffeh            ;Clear bit PE (bit0) of CR0
 mov cr0, eax                   ;Apply

 sti                

 ;Back to real mode

 ;Do nothing
 cli
 hlt 



 GDT:
    ;First entry, number 0
    ;Null descriptor
    ;Used to store a m16&32 object that tells the GDT start and size

    dw 0fh                 ;Size in byte -1 of the GDT (2 descriptors = 16 bytes)
    dd GDT + 7c00h         ;Linear address of GDT start (24 bits)
    dw 00h                 ;Pad 

    dd 0000ffffh           ;Base[15:00] = 0, Limit[15:00] = 0ffffh
    dd 00cf9200h           ;Base[31:24] = 0, G = 1, B = 1, Limit[19:16] = 0fh, 
               ;P = 1, DPL = 0, E = 0, W = 1, A = 0, Base[23:16] = 00h


 TIMES 510-($-$$) db 00h
 dw 0aa55h 

Uwagi

  • Po ponownym załadowaniu rejestru segmentu, nawet z tą samą wartością, procesor ponownie ładuje ukryte atrybuty zgodnie z bieżącym trybem. Dlatego powyższy kod używa fs i gs do przechowywania „rozszerzonych” segmentów: takie rejestry rzadziej będą wykorzystywane / zapisywane / odtwarzane przez różne usługi 16-bitowe.
  • Instrukcja lgdt nie ładuje dalekiego wskaźnika do GDT, zamiast tego ładuje 24-bitowy (może być zastąpiony do 32-bitowego) adres liniowy . To nie jest bliski adres , to adres fizyczny (ponieważ stronicowanie musi być wyłączone). To jest powód GDT+7c00h .
  • Powyższy program jest bootloaderem (dla MBR, nie ma BPB), który ustawia cs / ds / ss tp 7c00h i uruchamia licznik lokalizacji od 0. Więc bajt przy przesunięciu X w pliku ma przesunięcie X w segmencie 7c00h i pod adresem liniowym 7c00h + X.
  • Przerwania muszą być wyłączone, ponieważ IDT nie jest ustawione dla krótkiej podróży w obie strony w trybie chronionym.
  • Kod używa hacka, aby zapisać 6 bajtów kodu. Struktura ładowana przez lgdt jest zapisywana w samym ... GDT, w deskryptorze zerowym (pierwszy deskryptor).

Opis deskryptorów GDT znajduje się w rozdziale 3.4.3 Intel Manual Volume 3A .



Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow