2017-01-21 50 views
5

Obecnie studiuję niskopoziomową organizację systemów operacyjnych. Aby to osiągnąć, staram się zrozumieć, jak ładowane jest jądro Linuksa.Przejście z trybu rzeczywistego do chronionego w jądrze Linuksa

Rzecz, której nie mogę zrozumieć, to przejście z 16-bitowego (tryb rzeczywisty) do 32-bitowego (tryb chroniony). Zdarza się to w this file.

Funkcja protected_mode_jump wykonuje różne obliczenia pomocnicze dla 32-bitowego kodu, który jest wykonywany później, a następnie umożliwia PE bit w CR0 reguster

movl %cr0, %edx 
    orb $X86_CR0_PE, %dl # Protected mode 
    movl %edx, %cr0 

a potem wykonuje długi skok do kodu 32-bitowego:

# Transition to 32-bit mode 
    .byte 0x66, 0xea  # ljmpl opcode 
2: .long in_pm32   # offset 
    .word __BOOT_CS  # segment 

o ile mi zrozumieć in_pm32 jest adres funkcji 32-bitowym, który oświadczył tuż poniżej protected_mode_jump:

.code32 
    .section ".text32","ax" 
GLOBAL(in_pm32) 
    # some code 
    # ... 
    # some code 
ENDPROC(in_pm32) 

Podstawa __BOOT_CS sektor 0 (GDT jest ustawiony wcześniej here), więc to oznacza, że ​​przesunięcie powinno być w zasadzie absolutny adres funkcji in_pm32.

To jest problem. Podczas generowania kodu maszynowego asembler/linker nie powinien znać adresu bezwzględnego funkcji in_pm32, ponieważ nie wie, gdzie będzie załadowany do pamięci w trybie rzeczywistym (różne programy ładujące mogą zajmować różne ilości miejsca, a tryb rzeczywisty jądro jest ładowane tuż po bootloaderze).

Ponadto skrypt linkera (setup.ld w tym samym folderze) określa pochodzenie kodu jako 0, więc wygląda na to, że adres będzie przesunięciem od początku jądra trybu rzeczywistego. Powinien działać dobrze z 16-bitowym kodem, ponieważ rejestr CS jest ustawiony prawidłowo, ale kiedy dojdzie do skoku w dal, procesor jest już w trybie chronionym, więc względne przesunięcie nie powinno działać.

Moje pytanie: Dlaczego skok w dal w trybie chronionym (.byte 0x66, 0xea) określa prawidłową pozycję kodu, jeśli przesunięcie (.long in_pm32) jest względne?

Wygląda na to, że brakuje mi czegoś naprawdę ważnego.

+1

Już idę do łóżka, jak to jest 3:30 rano. Widziałem tylko część dotyczącą JMP. Ten skok jest 32-bitowym FAR JMP, który ustawia selektor _CS_. Ustawienie bitów trybu chronionego w CR0 powoduje przejście do quasi-16-bitowego trybu chronionego. Aby przejść do trybu chronionego 32-bitowym, musisz wykonać FAR JMP, który przyjmuje selektor (__BOOT_CS) i offset i przeskakuje do niego. __BOOT_CS powinien być selektorem, który wskazuje na 32-bitowy deskryptor segmentu kodu (prawdopodobnie z podstawą 0 i limitem 0xffffffff) w GDT. Po zakończeniu FAR JMP będzie on w trybie chronionym 32-bitowym. –

+1

Zasadniczo FAR JMP jest wymagany, aby przejść z trybu quasi 16-bitowego do trybu chronionego 32-bitowo. –

+0

Rzeczywiście, podstawa selektora '__BOOT_CS' wynosi 0. Przypuszczam, że oznacza to, że FAR JUMP powinien przyjmować adres bezwzględny żądanej funkcji/etykiety (ponieważ' 0 + offset' będzie po prostu 'offsetem '). Ale FAR JUMP jest tutaj wywoływany z względnym przesunięciem ('.long in_pm32' jest adresem funkcji' in_mp32' od początku na binarnym jądrze trybu rzeczywistego) - i nie rozumiem, dlaczego na końcu funkcja 'in_pm32' jest wykonywany. Skok daleki powinien być niedopasowany na bajtach 0x7C00 + bootloader_size. – Alexander

Odpowiedz

5

Wydaje się, że sprawa jest naprawdę o tym, jak przesunięcie przechowywane w następującym wierszu mogą ewentualnie pracować, ponieważ jest względem początku segmentu, niekoniecznie początku pamięci:

2: .long in_pm32   # offset 

Jest prawda, że ​​in_pm32 jest względna względem przesunięcia zastosowań linker script. W szczególności skrypt linkera posiada:

. = 0; 
.bstext  : { *(.bstext) } 
.bsdata  : { *(.bsdata) } 

. = 495; 
.header  : { *(.header) } 
.entrytext : { *(.entrytext) } 
.inittext : { *(.inittext) } 
.initdata : { *(.initdata) } 
__end_init = .; 

.text  : { *(.text) } 
.text32  : { *(.text32) } 

Wirtualny adres pamięci jest ustawiony na zero (a następnie 495), więc można by pomyśleć, że coś w sekcji .text32 będą musiały być ustalone w małej ilości pamięci.Byłby to prawidłowa obserwacja gdyby nie dla tych instrukcji w protected_mode_jump:

xorl %ebx, %ebx 
    movw %cs, %bx 
    shll $4, %ebx 
    addl %ebx, 2f 

[snip] 

    # Transition to 32-bit mode 
    .byte 0x66, 0xea  # ljmpl opcode 
2: .long in_pm32   # offset 
    .word __BOOT_CS  # segment 

Jest ręcznie kodowane FAR JMP na końcu, który jest używany do ustawiania CS wybieraka 32-bitowy deskryptor kodu, aby sfinalizować przejście do trybu chronionego 32-bitowym. Ale najważniejsze jest, aby obserwować to w tych liniach specjalnie:

xorl %ebx, %ebx 
    movw %cs, %bx 
    shll $4, %ebx 
    addl %ebx, 2f 

ta przyjmuje wartość w CS i przesuwa go w lewo o 4 bitów (pomnożyć przez 16), a następnie dodaje ją do wartości przechowywanej w etykiecie 2f . W ten sposób parujesz real mode segment:offset i przekształcasz go w adres liniowy (który w tym przypadku jest taki sam jak adres fizyczny). Etykieta 2f w rzeczywistości jest przesunięcie in_pm32 w tej linii:

2: .long in_pm32   # offset 

Kiedy te wskazówki są kompletne, długa wartość słowo in_pm32 w FAR JMP zostanie skorygowany (w czasie pracy) poprzez dodanie liniowego adres bieżący segment kodu trybu rzeczywistego do wartości in_pm32. Ta wartość .long (DWORD) zostanie zastąpiona wartością (CS < < 4) + in_pm32.

Ten kod został zaprojektowany do przeniesienia do dowolnego segmentu trybu rzeczywistego. Ostateczny adres liniowy jest obliczany w czasie wykonywania przed FAR JMP. Jest to w efekcie samodyfikujący się kod.