пятница, 15 февраля 2008 г.

PE Kernel Loader

На днях баловался с загрузчиком для ОС. Требованиями были минимум выполняемых действий (остальное должно выполняться уже в ядре) и удобный и готовый к страничной адресации формат загружаемого ядра.
После некоторых размышлений пришел к такому варианту:
1. в первичном загрузчике считываем в память вторичный загрузчик и ядро. Прыгаем во вторичный загрузчик;
2. по адресу 2k (0x800) размещаем GDT;
3. переходим в защищенный режим (куда уж без него то :) );
4. загружаем PE ядро. Прыгаем в него.

Довольно удачный компромис, на мой взгляд. И загрузчик получился довольно простой и ядро я теперь могу писать в любимой Visual Studio, как почти обычное 32бит приложение.
Недостатки компилятора от Майкрософт для такого рода занятий это уже вопрос второй ;)

Основной код:

include 'struct.inc'
include 'pe.inc'
include 'boot.inc'

PAGE_SIZE = 0x1000
GDT_OFFSET = 0x800

; Global Descriptor Table
GDT:
dd 0,0
db 0FFh, 0FFh, 00h, 00h, 00h, 10011010b, 11001111b, 00 ; code
db 0FFh, 0FFh, 00h, 00h, 00h, 10010010b, 11001111b, 00 ; data
GDT_size equ $-GDT
GDTR dw GDT_size-1
dd GDT_OFFSET

loader_entry:
; open address line A20
in al, 92h
or al, 2
out 92h, al

; copy GDT to the 2nd kb RAM
; это удобно, поскольку в первых 2к расположим IDT
mov cx, GDT_size
mov di, GDT_OFFSET
mov si, GDT
rep movsb
lgdt fword [GDTR]

; disable all ints
; в том числе и немаскируемые
cli
in al, 70h
or al, 80h
out 70h, al

; jump to PM
mov eax, cr0
or al, 1
mov cr0, eax

; load new selector to the CS
jmp 00001000b:.protected_entry
use32
.protected_entry:
; init selectors
mov ax,00010000b
mov ds, ax
mov es, ax
mov ss, ax

;
; simple PE loader for loading PE kernel.
;
mov ebx, kernel_image

cmp word [ebx + IMAGE_DOS_HEADER.e_magic], IMAGE_DOS_SIGNATURE
jne .image_error
add ebx, [ebx + IMAGE_DOS_HEADER.e_lfanew]

cmp dword [ebx + IMAGE_NT_HEADERS.Signature], IMAGE_NT_SIGNATURE
jne .image_error

mov dx, [ebx + IMAGE_NT_HEADERS.FileHeader.NumberOfSections]
mov eax, ebx ; PE header
add ebx, sizeof.IMAGE_NT_HEADERS

cld
@@:
mov ecx, [ebx + IMAGE_SECTION_HEADER.SizeOfRawData]

mov esi, [ebx + IMAGE_SECTION_HEADER.PointerToRawData]
add esi, kernel_image

mov edi, [ebx + IMAGE_SECTION_HEADER.VirtualAddress]
add edi, [eax + IMAGE_NT_HEADERS.OptionalHeader.ImageBase] ; ImageBase

rep movsb

add ebx, sizeof.IMAGE_SECTION_HEADER
dec dx
jnz @b

mov ebx, [eax + IMAGE_NT_HEADERS.OptionalHeader.ImageBase]
add ebx, [eax + IMAGE_NT_HEADERS.OptionalHeader.AddressOfEntryPoint]
jmp ebx ; Jump to kernel entry point

.image_error:
jmp $

kernel_image:
; вот это собственно наше ядро
file 'kernel.exe'

Хороший пример кода fasm для перевода в PM и обработки прерываний сделал и описал Great на wasm.ru.
Пример похожего решения, но для gcc и nasm можно найти на классном сайте lowlevel.ru