更改为 "Unreal" 模式,处理器崩溃
Changing to "Unreal" mode, processor crash
我正在编写一个简单的引导加载程序。它的主要任务是加载内核,并将处理器切换到虚幻模式。我的问题是当我打开虚幻模式时,处理器崩溃了。这是我的代码(从 MikeOS 使用的一些代码)。我用 NASM.
BITS 16
jmp short bootloader_start ; Jump past disk description section
nop ; Pad out before disk description
; ------------------------------------------------------------------
; Disk description table, to make it a valid floppy
; Note: some of these values are hard-coded in the source!
; Values are those used by IBM for 1.44 MB, 3.5" diskette
OEMLabel db "16DOSRUN" ; Disk label
BytesPerSector dw 512 ; Bytes per sector
SectorsPerCluster db 1 ; Sectors per cluster
ReservedForBoot dw 1 ; Reserved sectors for boot record
NumberOfFats db 2 ; Number of copies of the FAT
RootDirEntries dw 224 ; Number of entries in root dir
; (224 * 32 = 7168 = 14 sectors to read)
LogicalSectors dw 2880 ; Number of logical sectors
MediumByte db 0F0h ; Medium descriptor byte
SectorsPerFat dw 9 ; Sectors per FAT
SectorsPerTrack dw 18 ; Sectors per track (36/cylinder)
Sides dw 2 ; Number of sides/heads
HiddenSectors dd 0 ; Number of hidden sectors
LargeSectors dd 0 ; Number of LBA sectors
DriveNo dw 0 ; Drive No: 0
Signature db 41 ; Drive signature: 41 for floppy
VolumeID dd 00000000h ; Volume ID: any number
VolumeLabel db "16DOS "; Volume Label: any 11 chars
FileSystem db "FAT12 " ; File system type: don't change!
; ------------------------------------------------------------------
; Main bootloader code
bootloader_start:
xor ax, ax ; make it zero
mov ds, ax ; DS=0
mov ss, ax ; stack starts at seg 0
mov sp, 0x9c00 ; 2000h past code start,
; making the stack 7.5k in size
;***********HERE I TRY TO SWITCH INTO "UNREAL" MODE***********;
cli ; no interrupts
push ds ; save real mode
lgdt [gdtinfo] ; load gdt register
mov eax, cr0 ; switch to pmode by
or al,1 ; set pmode bit
mov cr0, eax
jmp $+2 ; tell 386/486 to not crash
mov bx, 0x08 ; select descriptor 1
mov ds, bx ; 8h = 1000b
and al,0xFE ; back to realmode
mov cr0, eax ; by toggling bit again
pop ds ; get back old segment
sti
;***********END***********;
mov ax, 07C0h ; Set up 4K of stack space above buffer
add ax, 544 ; 8k buffer = 512 paragraphs + 32 paragraphs (loader)
cli ; Disable interrupts while changing stack
mov ss, ax
mov sp, 4096
sti ; Restore interrupts
mov ax, 07C0h ; Set data segment to where we're loaded
mov ds, ax
; NOTE: A few early BIOSes are reported to improperly set DL
cmp dl, 0
je no_change
mov [bootdev], dl ; Save boot device number
mov ah, 8 ; Get drive parameters
int 13h
jc fatal_disk_error
and cx, 3Fh ; Maximum sector number
mov [SectorsPerTrack], cx ; Sector numbers start at 1
movzx dx, dh ; Maximum head number
add dx, 1 ; Head numbers start at 0 - add 1 for total
mov [Sides], dx
no_change:
mov eax, 0 ; Needed for some older BIOSes
; First, we need to load the root directory from the disk. Technical details:
; Start of root = ReservedForBoot + NumberOfFats * SectorsPerFat = logical 19
; Number of root = RootDirEntries * 32 bytes/entry / 512 bytes/sector = 14
; Start of user data = (start of root) + (number of root) = logical 33
floppy_ok: ; Ready to read first block of data
mov ax, 19 ; Root dir starts at logical sector 19
call l2hts
mov si, buffer ; Set ES:BX to point to our buffer (see end of code)
mov bx, ds
mov es, bx
mov bx, si
mov ah, 2 ; Params for int 13h: read floppy sectors
mov al, 14 ; And read 14 of them
pusha ; Prepare to enter loop
read_root_dir:
popa ; In case registers are altered by int 13h
pusha
stc ; A few BIOSes do not set properly on error
int 13h ; Read sectors using BIOS
jnc search_dir ; If read went OK, skip ahead
call reset_floppy ; Otherwise, reset floppy controller and try again
jnc read_root_dir ; Floppy reset OK?
search_dir:
popa
mov ax, ds ; Root dir is now in [buffer]
mov es, ax ; Set DI to this info
mov di, buffer
mov cx, word [RootDirEntries] ; Search all (224) entries
mov ax, 0 ; Searching at offset 0
next_root_entry:
xchg cx, dx ; We use CX in the inner loop...
mov si, kern_filename ; Start searching for kernel filename
mov cx, 11
rep cmpsb
je found_file_to_load ; Pointer DI will be at offset 11
add ax, 32 ; Bump searched entries by 1 (32 bytes per entry)
mov di, buffer ; Point to next entry
add di, ax
xchg dx, cx ; Get the original CX back
loop next_root_entry
mov si, file_not_found ; If kernel is not found, bail out
call print_string
found_file_to_load: ; Fetch cluster and load FAT into RAM
mov ax, word [es:di+0Fh] ; Offset 11 + 15 = 26, contains 1st cluster
mov word [cluster], ax
mov ax, 1 ; Sector 1 = first sector of first FAT
call l2hts
mov di, buffer ; ES:BX points to our buffer
mov bx, di
mov ah, 2 ; int 13h params: read (FAT) sectors
mov al, 9 ; All 9 sectors of 1st FAT
pusha ; Prepare to enter loop
read_fat:
popa ; In case registers are altered by int 13h
pusha
stc
int 13h ; Read sectors using the BIOS
jnc read_fat_ok ; If read went OK, skip ahead
call reset_floppy ; Otherwise, reset floppy controller and try again
jnc read_fat ; Floppy reset OK?
; ******************************************************************
fatal_disk_error:
; ******************************************************************
mov si, disk_error
read_fat_ok:
popa
mov ax, 2000h ; Segment where we'll load the kernel
mov es, ax
mov bx, 0
mov ah, 2 ; int 13h floppy read params
mov al, 1
push ax ; Save in case we (or int calls) lose it
; Now we must load the FAT from the disk. Here's how we find out where it starts:
; FAT cluster 0 = media descriptor = 0F0h
; FAT cluster 1 = filler cluster = 0FFh
; Cluster start = ((cluster number) - 2) * SectorsPerCluster + (start of user)
; = (cluster number) + 31
load_file_sector:
mov ax, word [cluster] ; Convert sector to logical
add ax, 31
call l2hts ; Make appropriate params for int 13h
mov ax, 2000h ; Set buffer past what we've already read
mov es, ax
mov bx, word [pointer]
pop ax ; Save in case we (or int calls) lose it
push ax
stc
int 13h
jnc calculate_next_cluster ; If there's no error...
call reset_floppy ; Otherwise, reset floppy and retry
jmp load_file_sector
; In the FAT, cluster values are stored in 12 bits, so we have to
; do a bit of maths to work out whether we're dealing with a byte
; and 4 bits of the next byte -- or the last 4 bits of one byte
; and then the subsequent byte!
calculate_next_cluster:
mov ax, [cluster]
mov dx, 0
mov bx, 3
mul bx
mov bx, 2
div bx ; DX = [cluster] mod 2
mov si, buffer
add si, ax ; AX = word in FAT for the 12 bit entry
mov ax, word [ds:si]
or dx, dx ; If DX = 0 [cluster] is even; if DX = 1 then it's odd
jz even ; If [cluster] is even, drop last 4 bits of word
; with next cluster; if odd, drop first 4 bits
odd:
shr ax, 4 ; Shift out first 4 bits (they belong to another entry)
jmp short next_cluster_cont
even:
and ax, 0FFFh ; Mask out final 4 bits
next_cluster_cont:
mov word [cluster], ax ; Store cluster
cmp ax, 0FF8h ; FF8h = end of file marker in FAT12
jae end
add word [pointer], 512 ; Increase buffer pointer 1 sector length
jmp load_file_sector
end: ; We've got the file to load!
pop ax ; Clean up the stack (AX was pushed earlier)
mov dl, byte [bootdev] ; Provide kernel with boot device info
jmp 2000h:0000h ; Jump to entry point of loaded kernel!
; ------------------------------------------------------------------
; BOOTLOADER SUBROUTINES
print_string: ; Output string in SI to screen
pusha
mov ah, 0Eh ; int 10h teletype function
.repeat:
lodsb ; Get char from string
cmp al, 0
je .done ; If char is zero, end of string
int 10h ; Otherwise, print it
jmp short .repeat
.done:
popa
ret
reset_floppy: ; IN: [bootdev] = boot device; OUT: carry set on error
push ax
push dx
mov ax, 0
mov dl, byte [bootdev]
stc
int 13h
pop dx
pop ax
ret
l2hts: ; Calculate head, track and sector settings for int 13h
; IN: logical sector in AX, OUT: correct registers for int 13h
push bx
push ax
mov bx, ax ; Save logical sector
mov dx, 0 ; First the sector
div word [SectorsPerTrack]
add dl, 01h ; Physical sectors start at 1
mov cl, dl ; Sectors belong in CL for int 13h
mov ax, bx
mov dx, 0 ; Now calculate the head
div word [SectorsPerTrack]
mov dx, 0
div word [Sides]
mov dh, dl ; Head/side
mov ch, al ; Track
pop ax
pop bx
mov dl, byte [bootdev] ; Set correct device
ret
; ------------------------------------------------------------------
; STRINGS AND VARIABLES
kern_filename db "KERNEL SYS" ; MikeOS kernel filename
disk_error db "Error.", 0
file_not_found db "Error.", 0
bootdev db 0 ; Boot device number
cluster dw 0 ; Cluster of the file we want to load
pointer dw 0 ; Pointer into Buffer, for loading kernel
gdtinfo:
dw gdt_end - gdt - 1 ;last byte in table
dd gdt ;start of table
gdt dd 0,0 ; entry 0 is always unused
flatdesc db 0xff, 0xff, 0, 0, 0, 10010010b, 11001111b, 0
gdt_end:
; ------------------------------------------------------------------
; END OF BOOT SECTOR AND BUFFER START
times 510-($-$$) db 0 ; Pad remainder of boot sector with zeros
dw 0AA55h ; Boot signature (DO NOT CHANGE!)
buffer: ; Disk buffer begins (8k after this, stack starts)
; ==================================================================
那么如何修复此代码?如果在我的情况下无法切换到 "unreal" 模式,我如何在实模式下访问整个内存(4GiB 会持续)?我在内核代码中打开了 A20。
几年后:发现 SmallerC 支持进入虚幻模式,所以实际上并不需要所有程序集,我可以用 C 编写它。
MikeOS comes with a bootloader that assumes a segment of 0x07c0 and offset of 0x0000 (0x07c0:0x0000). The offset portion is also the origin point (ORG
value in NASM). In 20-bit segment:offset addressing: 0x07c0 和偏移量 0x0000 的段是物理地址 0x07c00 (0x07c0<<4+0x0000=0x07c00),这是引导加载程序在内存中的预期位置。
看起来,当您使用 MikeOS 时,您拼接了一些 unreal mode code from OSDev Wiki,假设原点基于 segment:offset 地址 0x0000:0x7c00。这也表示物理地址 0x07c00 (0x0000<<4+0x7c00=0x7c00)。在这种情况下,您需要 NASM 代码中的 ORG
0x7c00。
使用 -f bin
选项与 NASM 进行汇编时(如果未指定输出格式,则为默认设置):如果您不t 指定 ORG
指令,默认为 ORG 0x0000
.
您将需要使用其中之一,而不是两者。由于大多数 MikeOS 引导加载程序代码依赖于 0x07c0 的段和 0x0000 的偏移量,因此更容易将代码更改为类似于 MikeOS 引导加载程序最初使用的代码。以下代码
bootloader_start:
xor ax, ax ; make it zero
mov ds, ax ; DS=0
mov ss, ax ; stack starts at seg 0
mov sp, 0x9c00 ; 2000h past code start,
; making the stack 7.5k in size
;***********HERE I TRY TO SWITCH INTO "UNREAL" MODE***********;
cli ; no interrupts
可改为:
bootloader_start:
; Modify all segment setup code to assume an ORG of 0x0000
mov ax, 07C0h ; Set data segment to where we're loaded
mov ds, ax
add ax, 544 ; 8k buffer = 512 paragraphs + 32 paragraphs (loader)
cli ; Disable interrupts while changing stack and entering
; protected mode, turn them on after when in unreal mode
mov ss, ax
mov sp, 4096
然后,您可以删除在完成虚幻模式设置后出现的所有这些重复代码。这些行需要删除:
mov ax, 07C0h ; Set up 4K of stack space above buffer
add ax, 544 ; 8k buffer = 512 paragraphs + 32 paragraphs (loader)
cli ; Disable interrupts while changing stack
mov ss, ax
mov sp, 4096
sti ; Restore interrupts
mov ax, 07C0h ; Set data segment to where we're loaded
mov ds, ax
通常虚幻模式将所有数据寄存器设置为平面内存模型。除了更新 DS 以指向平面 4gb 选择器之外,您还可以设置 DS/ES/FS/GS 寄存器。修改代码做:
mov bx, 0x08 ; select descriptor 1
mov ds, bx ; 8h = 1000b
mov es, bx ; 8h = 1000b
mov fs, bx ; 8h = 1000b
mov gs, bx ; 8h = 1000b
完成此操作后,需要对 gdtinfo
结构进行一项更改。您将其放入引导加载程序:
gdtinfo:
dw gdt_end - gdt - 1 ;last byte in table
dd gdt ;start of table
gdt dd 0,0 ; entry 0 is always unused
flatdesc db 0xff, 0xff, 0, 0, 0, 10010010b, 11001111b, 0
gdt_end:
现在的问题是我们正在使用 0x07c0 的段并且 GDT 的基数现在是相对于偏移量 0x0000(不是 0x7c00)。我们将加载到 GDT register 中的 gdtinfo
结构中的基地址是线性地址(不是 segment:offset 地址)。在实模式下,线性地址和物理地址是一回事。为了使 gdt
成为线性地址,我们将 0x7c00 添加到 gdt
。我们修改一行:
dd gdt ;start of table
因此现在显示为:
dd gdt+0x7c00 ;start of table
完成代码更改
您文件的修订版本可能是:
BITS 16
jmp short bootloader_start ; Jump past disk description section
nop ; Pad out before disk description
; ------------------------------------------------------------------
; Disk description table, to make it a valid floppy
; Note: some of these values are hard-coded in the source!
; Values are those used by IBM for 1.44 MB, 3.5" diskette
OEMLabel db "16DOSRUN" ; Disk label
BytesPerSector dw 512 ; Bytes per sector
SectorsPerCluster db 1 ; Sectors per cluster
ReservedForBoot dw 1 ; Reserved sectors for boot record
NumberOfFats db 2 ; Number of copies of the FAT
RootDirEntries dw 224 ; Number of entries in root dir
; (224 * 32 = 7168 = 14 sectors to read)
LogicalSectors dw 2880 ; Number of logical sectors
MediumByte db 0F0h ; Medium descriptor byte
SectorsPerFat dw 9 ; Sectors per FAT
SectorsPerTrack dw 18 ; Sectors per track (36/cylinder)
Sides dw 2 ; Number of sides/heads
HiddenSectors dd 0 ; Number of hidden sectors
LargeSectors dd 0 ; Number of LBA sectors
DriveNo dw 0 ; Drive No: 0
Signature db 41 ; Drive signature: 41 for floppy
VolumeID dd 00000000h ; Volume ID: any number
VolumeLabel db "16DOS "; Volume Label: any 11 chars
FileSystem db "FAT12 " ; File system type: don't change!
; ------------------------------------------------------------------
; Main bootloader code
bootloader_start:
; Modify all segment setup code to assume an ORG of 0x0000
mov ax, 07C0h ; Set up 4K of stack space above buffer
mov ds, ax ; Set DS segment to where we're loaded
add ax, 544 ; 8k buffer = 512 paragraphs + 32 paragraphs (loader)
cli ; Disable interrupts while changing stack
mov ss, ax
mov sp, 4096
; Enter unreal mode
; Keep interrupts off while we switch to real mode
push ds ; Switch to real mode detroys DS. We need to save it
lgdt [gdtinfo] ; load gdt register
mov eax, cr0 ; switch to pmode by
or al,1 ; set pmode bit
mov cr0, eax
jmp $+2 ; Clear the instruction pre-fetch queue
; Set DS=ES=FS=GS to descriptor with 4gb limit
mov bx, 0x08 ; select descriptor 1
mov ds, bx ; 8h = 1000b
mov es, bx ; 8h = 1000b
mov fs, bx ; 8h = 1000b
mov gs, bx ; 8h = 1000b
and al,0xFE ; back to realmode
mov cr0, eax ; by toggling bit again
sti ; enable interrupts
pop ds ; Retsore DS to original value
;***********END OF UNREAL MODE SWITCH ***********;
; NOTE: A few early BIOSes are reported to improperly set DL
cmp dl, 0
je no_change
mov [bootdev], dl ; Save boot device number
mov ah, 8 ; Get drive parameters
int 13h
jc fatal_disk_error
and cx, 3Fh ; Maximum sector number
mov [SectorsPerTrack], cx ; Sector numbers start at 1
movzx dx, dh ; Maximum head number
add dx, 1 ; Head numbers start at 0 - add 1 for total
mov [Sides], dx
no_change:
mov eax, 0 ; Needed for some older BIOSes
; First, we need to load the root directory from the disk. Technical details:
; Start of root = ReservedForBoot + NumberOfFats * SectorsPerFat = logical 19
; Number of root = RootDirEntries * 32 bytes/entry / 512 bytes/sector = 14
; Start of user data = (start of root) + (number of root) = logical 33
floppy_ok: ; Ready to read first block of data
mov ax, 19 ; Root dir starts at logical sector 19
call l2hts
mov si, buffer ; Set ES:BX to point to our buffer (see end of code)
mov bx, ds
mov es, bx
mov bx, si
mov ah, 2 ; Params for int 13h: read floppy sectors
mov al, 14 ; And read 14 of them
pusha ; Prepare to enter loop
read_root_dir:
popa ; In case registers are altered by int 13h
pusha
stc ; A few BIOSes do not set properly on error
int 13h ; Read sectors using BIOS
jnc search_dir ; If read went OK, skip ahead
call reset_floppy ; Otherwise, reset floppy controller and try again
jnc read_root_dir ; Floppy reset OK?
search_dir:
popa
mov ax, ds ; Root dir is now in [buffer]
mov es, ax ; Set DI to this info
mov di, buffer
mov cx, word [RootDirEntries] ; Search all (224) entries
mov ax, 0 ; Searching at offset 0
next_root_entry:
xchg cx, dx ; We use CX in the inner loop...
mov si, kern_filename ; Start searching for kernel filename
mov cx, 11
rep cmpsb
je found_file_to_load ; Pointer DI will be at offset 11
add ax, 32 ; Bump searched entries by 1 (32 bytes per entry)
mov di, buffer ; Point to next entry
add di, ax
xchg dx, cx ; Get the original CX back
loop next_root_entry
mov si, file_not_found ; If kernel is not found, bail out
call print_string
found_file_to_load: ; Fetch cluster and load FAT into RAM
mov ax, word [es:di+0Fh] ; Offset 11 + 15 = 26, contains 1st cluster
mov word [cluster], ax
mov ax, 1 ; Sector 1 = first sector of first FAT
call l2hts
mov di, buffer ; ES:BX points to our buffer
mov bx, di
mov ah, 2 ; int 13h params: read (FAT) sectors
mov al, 9 ; All 9 sectors of 1st FAT
pusha ; Prepare to enter loop
read_fat:
popa ; In case registers are altered by int 13h
pusha
stc
int 13h ; Read sectors using the BIOS
jnc read_fat_ok ; If read went OK, skip ahead
call reset_floppy ; Otherwise, reset floppy controller and try again
jnc read_fat ; Floppy reset OK?
; ******************************************************************
fatal_disk_error:
; ******************************************************************
mov si, disk_error
read_fat_ok:
popa
mov ax, 2000h ; Segment where we'll load the kernel
mov es, ax
mov bx, 0
mov ah, 2 ; int 13h floppy read params
mov al, 1
push ax ; Save in case we (or int calls) lose it
; Now we must load the FAT from the disk. Here's how we find out where it starts:
; FAT cluster 0 = media descriptor = 0F0h
; FAT cluster 1 = filler cluster = 0FFh
; Cluster start = ((cluster number) - 2) * SectorsPerCluster + (start of user)
; = (cluster number) + 31
load_file_sector:
mov ax, word [cluster] ; Convert sector to logical
add ax, 31
call l2hts ; Make appropriate params for int 13h
mov ax, 2000h ; Set buffer past what we've already read
mov es, ax
mov bx, word [pointer]
pop ax ; Save in case we (or int calls) lose it
push ax
stc
int 13h
jnc calculate_next_cluster ; If there's no error...
call reset_floppy ; Otherwise, reset floppy and retry
jmp load_file_sector
; In the FAT, cluster values are stored in 12 bits, so we have to
; do a bit of maths to work out whether we're dealing with a byte
; and 4 bits of the next byte -- or the last 4 bits of one byte
; and then the subsequent byte!
calculate_next_cluster:
mov ax, [cluster]
mov dx, 0
mov bx, 3
mul bx
mov bx, 2
div bx ; DX = [cluster] mod 2
mov si, buffer
add si, ax ; AX = word in FAT for the 12 bit entry
mov ax, word [ds:si]
or dx, dx ; If DX = 0 [cluster] is even; if DX = 1 then it's odd
jz even ; If [cluster] is even, drop last 4 bits of word
; with next cluster; if odd, drop first 4 bits
odd:
shr ax, 4 ; Shift out first 4 bits (they belong to another entry)
jmp short next_cluster_cont
even:
and ax, 0FFFh ; Mask out final 4 bits
next_cluster_cont:
mov word [cluster], ax ; Store cluster
cmp ax, 0FF8h ; FF8h = end of file marker in FAT12
jae end
add word [pointer], 512 ; Increase buffer pointer 1 sector length
jmp load_file_sector
end: ; We've got the file to load!
pop ax ; Clean up the stack (AX was pushed earlier)
mov dl, byte [bootdev] ; Provide kernel with boot device info
jmp 2000h:0000h ; Jump to entry point of loaded kernel!
; ------------------------------------------------------------------
; BOOTLOADER SUBROUTINES
print_string: ; Output string in SI to screen
pusha
mov ah, 0Eh ; int 10h teletype function
.repeat:
lodsb ; Get char from string
cmp al, 0
je .done ; If char is zero, end of string
int 10h ; Otherwise, print it
jmp short .repeat
.done:
popa
ret
reset_floppy: ; IN: [bootdev] = boot device; OUT: carry set on error
push ax
push dx
mov ax, 0
mov dl, byte [bootdev]
stc
int 13h
pop dx
pop ax
ret
l2hts: ; Calculate head, track and sector settings for int 13h
; IN: logical sector in AX, OUT: correct registers for int 13h
push bx
push ax
mov bx, ax ; Save logical sector
mov dx, 0 ; First the sector
div word [SectorsPerTrack]
add dl, 01h ; Physical sectors start at 1
mov cl, dl ; Sectors belong in CL for int 13h
mov ax, bx
mov dx, 0 ; Now calculate the head
div word [SectorsPerTrack]
mov dx, 0
div word [Sides]
mov dh, dl ; Head/side
mov ch, al ; Track
pop ax
pop bx
mov dl, byte [bootdev] ; Set correct device
ret
; ------------------------------------------------------------------
; STRINGS AND VARIABLES
kern_filename db "KERNEL SYS" ; MikeOS kernel filename
disk_error db "Error.", 0
file_not_found db "Error.", 0
bootdev db 0 ; Boot device number
cluster dw 0 ; Cluster of the file we want to load
pointer dw 0 ; Pointer into Buffer, for loading kernel
gdtinfo:
dw gdt_end - gdt - 1 ;last byte in table
dd gdt+0x7c00 ;start of table
gdt dd 0,0 ; entry 0 is always unused
flatdesc db 0xff, 0xff, 0, 0, 0, 10010010b, 11001111b, 0
gdt_end:
; ------------------------------------------------------------------
; END OF BOOT SECTOR AND BUFFER START
times 510-($-$$) db 0 ; Pad remainder of boot sector with zeros
dw 0AA55h ; Boot signature (DO NOT CHANGE!)
buffer: ; Disk buffer begins (8k after this, stack starts)
; ==================================================================
赏金
在赏金中你是这样说的:
I'd want bounty winner to explain me why such code is wrong and help me to fix it (to make it work, by switching to unreal mode aka flat real mode and load kernel file named KERNEL.SYS and execute it. Kernel WILL use interrupts so protected mode aint option.)
保护模式支持软件和硬件中断。我相信您的意思是您的内核将使用 BIOS 中断。在保护模式下 运行 时 BIOS 中断不可用,除非您创建 VM86 任务或切换回实模式。
Real/Unreal 模式内核可以制作,但它们会因缺乏虚拟内存、内存保护和保护模式下可用的分页机制而受到限制。
进入虚幻模式需要暂时进入保护模式;使用指向具有 4gb 限制(而不是 64k)的 16 位数据描述符的选择器设置 DS/ES/GS/FS;然后关闭保护模式。在切换到保护模式期间,必须禁用中断,因为没有设置保护模式中断向量。
KERNEL.SYS
MikeOS 引导加载程序需要将名为 KERNEL.SYS 的文件放入格式为 FAT12 的磁盘映像的根目录中。我会假设你知道如何做到这一点。 Windows 和 Linux 之间执行此操作的方法不同,并且超出了此答案的范围。测试虚幻模式是否启用和工作的示例kernel.asm
如下:
bits 16
; MikeOS bootloader loads our code at 0x2000:0x0000 so we need org of 0x0000
; for the kernel code to work properly.
org 0x0000
kernel_start:
; Set DS, ES, FS, GS to 0x0000. In Unreal mode these segment registers
; are not limited to 64kb. We can address full 4gb of memory
xor ax, ax
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
; This code will not work in normal real mode. We emit instructions
; that use 0xb8000 as an offset. This offset is >= 65536 and normally
; isn't addressable directly in real mode. This should display MDP white
; on purple to upper left of screen. 0xb8000 is pointer to first cell
; of the text mode video display
;
; In Real AND Unreal mode on a 386 you are allowed to use 32-bit registers
; and memory operands. Real mode is limited to an offset that computes
; to a value below 64kb (65536) unlike unreal mode with a 4gb limit
mov edi, 0xb8000
mov word [edi], 0x57<<8 | 'M';
mov word [edi+2], 0x57<<8 | 'D';
mov word [edi+4], 0x57<<8 | 'P';
cli
.endloop:
hlt
jmp .endloop
它可以组装成KERNEL.SYS
:
nasm -f bin kernel.asm -o KERNEL.SYS
当生成带有此引导加载程序和 KERNEL.SYS 文件的磁盘映像并在 QEMU 中 运行(Bochs 将类似)时,输出将类似于:
如果处理器不处于虚幻模式,则写入左上角的字符将不会出现,或者hardware/emulator可能会达到其他类型的未定义状态。
其他观察和信息
- 实际上不需要在 MikeOS 引导程序中将开关置于 Unreal 模式。您可以保持 MikeOS 引导加载程序不变,并将开关移动到 Unreal 模式进入 KERNEL.SYS .
- 如果您打算访问任何奇数兆字节内存区域(0x100000-0x1fffff,0x300000-0x3fffff,...)中超过 1MiB 的内存中的数据,那么您还需要确保 A20 gate 是已启用。
- 您在引导加载程序中使用的虚幻模式代码设置了 4gb 限制的数据段。它不会以相同的方式设置 CS。这个版本的虚幻模式叫做大虚幻模式。
- 在 Big Unreal 模式下处理或调用中断(即 BIOS 中断)与在实模式下相同
- 如果你想创建一个中断处理程序,它的位置被限制在 0x0000:0x0000 和 0xFFFF:0xFFFF 之间的低内存区域。代码段中的所有代码都具有与实模式相同的限制。
- @RossRidge 的评论说 这意味着您不能在虚幻模式下启用中断或使用 BIOS 调用,因为它们可以更改 DS 中的值,这是不正确的大虚幻模式。
- 可以在代码段上使用 4gb 的限制,但是不能可靠地使用超过 64kb 的代码,因为中断只保存 CS:IP 而不是 CS:EIP。 EIP 可以是 0 到 4gb 之间的任何值,但是第一个 64kb 之外的任何值都不能可靠地完成,除非您在 运行ning 此类代码时禁用中断。这是非常严格的,也是很少使用这种模式的原因。这种模式通常被称为 Huge Unreal mode.
- @AlexeyFrunze has a number of bootloaders that support loading of kernels and supports Unreal mode. Alex also developed the Smaller C Compiler 可用于生成可从他的引导加载程序启动的代码,并支持为 Unreal 模式生成代码。
我正在编写一个简单的引导加载程序。它的主要任务是加载内核,并将处理器切换到虚幻模式。我的问题是当我打开虚幻模式时,处理器崩溃了。这是我的代码(从 MikeOS 使用的一些代码)。我用 NASM.
BITS 16
jmp short bootloader_start ; Jump past disk description section
nop ; Pad out before disk description
; ------------------------------------------------------------------
; Disk description table, to make it a valid floppy
; Note: some of these values are hard-coded in the source!
; Values are those used by IBM for 1.44 MB, 3.5" diskette
OEMLabel db "16DOSRUN" ; Disk label
BytesPerSector dw 512 ; Bytes per sector
SectorsPerCluster db 1 ; Sectors per cluster
ReservedForBoot dw 1 ; Reserved sectors for boot record
NumberOfFats db 2 ; Number of copies of the FAT
RootDirEntries dw 224 ; Number of entries in root dir
; (224 * 32 = 7168 = 14 sectors to read)
LogicalSectors dw 2880 ; Number of logical sectors
MediumByte db 0F0h ; Medium descriptor byte
SectorsPerFat dw 9 ; Sectors per FAT
SectorsPerTrack dw 18 ; Sectors per track (36/cylinder)
Sides dw 2 ; Number of sides/heads
HiddenSectors dd 0 ; Number of hidden sectors
LargeSectors dd 0 ; Number of LBA sectors
DriveNo dw 0 ; Drive No: 0
Signature db 41 ; Drive signature: 41 for floppy
VolumeID dd 00000000h ; Volume ID: any number
VolumeLabel db "16DOS "; Volume Label: any 11 chars
FileSystem db "FAT12 " ; File system type: don't change!
; ------------------------------------------------------------------
; Main bootloader code
bootloader_start:
xor ax, ax ; make it zero
mov ds, ax ; DS=0
mov ss, ax ; stack starts at seg 0
mov sp, 0x9c00 ; 2000h past code start,
; making the stack 7.5k in size
;***********HERE I TRY TO SWITCH INTO "UNREAL" MODE***********;
cli ; no interrupts
push ds ; save real mode
lgdt [gdtinfo] ; load gdt register
mov eax, cr0 ; switch to pmode by
or al,1 ; set pmode bit
mov cr0, eax
jmp $+2 ; tell 386/486 to not crash
mov bx, 0x08 ; select descriptor 1
mov ds, bx ; 8h = 1000b
and al,0xFE ; back to realmode
mov cr0, eax ; by toggling bit again
pop ds ; get back old segment
sti
;***********END***********;
mov ax, 07C0h ; Set up 4K of stack space above buffer
add ax, 544 ; 8k buffer = 512 paragraphs + 32 paragraphs (loader)
cli ; Disable interrupts while changing stack
mov ss, ax
mov sp, 4096
sti ; Restore interrupts
mov ax, 07C0h ; Set data segment to where we're loaded
mov ds, ax
; NOTE: A few early BIOSes are reported to improperly set DL
cmp dl, 0
je no_change
mov [bootdev], dl ; Save boot device number
mov ah, 8 ; Get drive parameters
int 13h
jc fatal_disk_error
and cx, 3Fh ; Maximum sector number
mov [SectorsPerTrack], cx ; Sector numbers start at 1
movzx dx, dh ; Maximum head number
add dx, 1 ; Head numbers start at 0 - add 1 for total
mov [Sides], dx
no_change:
mov eax, 0 ; Needed for some older BIOSes
; First, we need to load the root directory from the disk. Technical details:
; Start of root = ReservedForBoot + NumberOfFats * SectorsPerFat = logical 19
; Number of root = RootDirEntries * 32 bytes/entry / 512 bytes/sector = 14
; Start of user data = (start of root) + (number of root) = logical 33
floppy_ok: ; Ready to read first block of data
mov ax, 19 ; Root dir starts at logical sector 19
call l2hts
mov si, buffer ; Set ES:BX to point to our buffer (see end of code)
mov bx, ds
mov es, bx
mov bx, si
mov ah, 2 ; Params for int 13h: read floppy sectors
mov al, 14 ; And read 14 of them
pusha ; Prepare to enter loop
read_root_dir:
popa ; In case registers are altered by int 13h
pusha
stc ; A few BIOSes do not set properly on error
int 13h ; Read sectors using BIOS
jnc search_dir ; If read went OK, skip ahead
call reset_floppy ; Otherwise, reset floppy controller and try again
jnc read_root_dir ; Floppy reset OK?
search_dir:
popa
mov ax, ds ; Root dir is now in [buffer]
mov es, ax ; Set DI to this info
mov di, buffer
mov cx, word [RootDirEntries] ; Search all (224) entries
mov ax, 0 ; Searching at offset 0
next_root_entry:
xchg cx, dx ; We use CX in the inner loop...
mov si, kern_filename ; Start searching for kernel filename
mov cx, 11
rep cmpsb
je found_file_to_load ; Pointer DI will be at offset 11
add ax, 32 ; Bump searched entries by 1 (32 bytes per entry)
mov di, buffer ; Point to next entry
add di, ax
xchg dx, cx ; Get the original CX back
loop next_root_entry
mov si, file_not_found ; If kernel is not found, bail out
call print_string
found_file_to_load: ; Fetch cluster and load FAT into RAM
mov ax, word [es:di+0Fh] ; Offset 11 + 15 = 26, contains 1st cluster
mov word [cluster], ax
mov ax, 1 ; Sector 1 = first sector of first FAT
call l2hts
mov di, buffer ; ES:BX points to our buffer
mov bx, di
mov ah, 2 ; int 13h params: read (FAT) sectors
mov al, 9 ; All 9 sectors of 1st FAT
pusha ; Prepare to enter loop
read_fat:
popa ; In case registers are altered by int 13h
pusha
stc
int 13h ; Read sectors using the BIOS
jnc read_fat_ok ; If read went OK, skip ahead
call reset_floppy ; Otherwise, reset floppy controller and try again
jnc read_fat ; Floppy reset OK?
; ******************************************************************
fatal_disk_error:
; ******************************************************************
mov si, disk_error
read_fat_ok:
popa
mov ax, 2000h ; Segment where we'll load the kernel
mov es, ax
mov bx, 0
mov ah, 2 ; int 13h floppy read params
mov al, 1
push ax ; Save in case we (or int calls) lose it
; Now we must load the FAT from the disk. Here's how we find out where it starts:
; FAT cluster 0 = media descriptor = 0F0h
; FAT cluster 1 = filler cluster = 0FFh
; Cluster start = ((cluster number) - 2) * SectorsPerCluster + (start of user)
; = (cluster number) + 31
load_file_sector:
mov ax, word [cluster] ; Convert sector to logical
add ax, 31
call l2hts ; Make appropriate params for int 13h
mov ax, 2000h ; Set buffer past what we've already read
mov es, ax
mov bx, word [pointer]
pop ax ; Save in case we (or int calls) lose it
push ax
stc
int 13h
jnc calculate_next_cluster ; If there's no error...
call reset_floppy ; Otherwise, reset floppy and retry
jmp load_file_sector
; In the FAT, cluster values are stored in 12 bits, so we have to
; do a bit of maths to work out whether we're dealing with a byte
; and 4 bits of the next byte -- or the last 4 bits of one byte
; and then the subsequent byte!
calculate_next_cluster:
mov ax, [cluster]
mov dx, 0
mov bx, 3
mul bx
mov bx, 2
div bx ; DX = [cluster] mod 2
mov si, buffer
add si, ax ; AX = word in FAT for the 12 bit entry
mov ax, word [ds:si]
or dx, dx ; If DX = 0 [cluster] is even; if DX = 1 then it's odd
jz even ; If [cluster] is even, drop last 4 bits of word
; with next cluster; if odd, drop first 4 bits
odd:
shr ax, 4 ; Shift out first 4 bits (they belong to another entry)
jmp short next_cluster_cont
even:
and ax, 0FFFh ; Mask out final 4 bits
next_cluster_cont:
mov word [cluster], ax ; Store cluster
cmp ax, 0FF8h ; FF8h = end of file marker in FAT12
jae end
add word [pointer], 512 ; Increase buffer pointer 1 sector length
jmp load_file_sector
end: ; We've got the file to load!
pop ax ; Clean up the stack (AX was pushed earlier)
mov dl, byte [bootdev] ; Provide kernel with boot device info
jmp 2000h:0000h ; Jump to entry point of loaded kernel!
; ------------------------------------------------------------------
; BOOTLOADER SUBROUTINES
print_string: ; Output string in SI to screen
pusha
mov ah, 0Eh ; int 10h teletype function
.repeat:
lodsb ; Get char from string
cmp al, 0
je .done ; If char is zero, end of string
int 10h ; Otherwise, print it
jmp short .repeat
.done:
popa
ret
reset_floppy: ; IN: [bootdev] = boot device; OUT: carry set on error
push ax
push dx
mov ax, 0
mov dl, byte [bootdev]
stc
int 13h
pop dx
pop ax
ret
l2hts: ; Calculate head, track and sector settings for int 13h
; IN: logical sector in AX, OUT: correct registers for int 13h
push bx
push ax
mov bx, ax ; Save logical sector
mov dx, 0 ; First the sector
div word [SectorsPerTrack]
add dl, 01h ; Physical sectors start at 1
mov cl, dl ; Sectors belong in CL for int 13h
mov ax, bx
mov dx, 0 ; Now calculate the head
div word [SectorsPerTrack]
mov dx, 0
div word [Sides]
mov dh, dl ; Head/side
mov ch, al ; Track
pop ax
pop bx
mov dl, byte [bootdev] ; Set correct device
ret
; ------------------------------------------------------------------
; STRINGS AND VARIABLES
kern_filename db "KERNEL SYS" ; MikeOS kernel filename
disk_error db "Error.", 0
file_not_found db "Error.", 0
bootdev db 0 ; Boot device number
cluster dw 0 ; Cluster of the file we want to load
pointer dw 0 ; Pointer into Buffer, for loading kernel
gdtinfo:
dw gdt_end - gdt - 1 ;last byte in table
dd gdt ;start of table
gdt dd 0,0 ; entry 0 is always unused
flatdesc db 0xff, 0xff, 0, 0, 0, 10010010b, 11001111b, 0
gdt_end:
; ------------------------------------------------------------------
; END OF BOOT SECTOR AND BUFFER START
times 510-($-$$) db 0 ; Pad remainder of boot sector with zeros
dw 0AA55h ; Boot signature (DO NOT CHANGE!)
buffer: ; Disk buffer begins (8k after this, stack starts)
; ==================================================================
那么如何修复此代码?如果在我的情况下无法切换到 "unreal" 模式,我如何在实模式下访问整个内存(4GiB 会持续)?我在内核代码中打开了 A20。 几年后:发现 SmallerC 支持进入虚幻模式,所以实际上并不需要所有程序集,我可以用 C 编写它。
MikeOS comes with a bootloader that assumes a segment of 0x07c0 and offset of 0x0000 (0x07c0:0x0000). The offset portion is also the origin point (ORG
value in NASM). In 20-bit segment:offset addressing: 0x07c0 和偏移量 0x0000 的段是物理地址 0x07c00 (0x07c0<<4+0x0000=0x07c00),这是引导加载程序在内存中的预期位置。
看起来,当您使用 MikeOS 时,您拼接了一些 unreal mode code from OSDev Wiki,假设原点基于 segment:offset 地址 0x0000:0x7c00。这也表示物理地址 0x07c00 (0x0000<<4+0x7c00=0x7c00)。在这种情况下,您需要 NASM 代码中的 ORG
0x7c00。
使用 -f bin
选项与 NASM 进行汇编时(如果未指定输出格式,则为默认设置):如果您不t 指定 ORG
指令,默认为 ORG 0x0000
.
您将需要使用其中之一,而不是两者。由于大多数 MikeOS 引导加载程序代码依赖于 0x07c0 的段和 0x0000 的偏移量,因此更容易将代码更改为类似于 MikeOS 引导加载程序最初使用的代码。以下代码
bootloader_start:
xor ax, ax ; make it zero
mov ds, ax ; DS=0
mov ss, ax ; stack starts at seg 0
mov sp, 0x9c00 ; 2000h past code start,
; making the stack 7.5k in size
;***********HERE I TRY TO SWITCH INTO "UNREAL" MODE***********;
cli ; no interrupts
可改为:
bootloader_start:
; Modify all segment setup code to assume an ORG of 0x0000
mov ax, 07C0h ; Set data segment to where we're loaded
mov ds, ax
add ax, 544 ; 8k buffer = 512 paragraphs + 32 paragraphs (loader)
cli ; Disable interrupts while changing stack and entering
; protected mode, turn them on after when in unreal mode
mov ss, ax
mov sp, 4096
然后,您可以删除在完成虚幻模式设置后出现的所有这些重复代码。这些行需要删除:
mov ax, 07C0h ; Set up 4K of stack space above buffer
add ax, 544 ; 8k buffer = 512 paragraphs + 32 paragraphs (loader)
cli ; Disable interrupts while changing stack
mov ss, ax
mov sp, 4096
sti ; Restore interrupts
mov ax, 07C0h ; Set data segment to where we're loaded
mov ds, ax
通常虚幻模式将所有数据寄存器设置为平面内存模型。除了更新 DS 以指向平面 4gb 选择器之外,您还可以设置 DS/ES/FS/GS 寄存器。修改代码做:
mov bx, 0x08 ; select descriptor 1
mov ds, bx ; 8h = 1000b
mov es, bx ; 8h = 1000b
mov fs, bx ; 8h = 1000b
mov gs, bx ; 8h = 1000b
完成此操作后,需要对 gdtinfo
结构进行一项更改。您将其放入引导加载程序:
gdtinfo:
dw gdt_end - gdt - 1 ;last byte in table
dd gdt ;start of table
gdt dd 0,0 ; entry 0 is always unused
flatdesc db 0xff, 0xff, 0, 0, 0, 10010010b, 11001111b, 0
gdt_end:
现在的问题是我们正在使用 0x07c0 的段并且 GDT 的基数现在是相对于偏移量 0x0000(不是 0x7c00)。我们将加载到 GDT register 中的 gdtinfo
结构中的基地址是线性地址(不是 segment:offset 地址)。在实模式下,线性地址和物理地址是一回事。为了使 gdt
成为线性地址,我们将 0x7c00 添加到 gdt
。我们修改一行:
dd gdt ;start of table
因此现在显示为:
dd gdt+0x7c00 ;start of table
完成代码更改
您文件的修订版本可能是:
BITS 16
jmp short bootloader_start ; Jump past disk description section
nop ; Pad out before disk description
; ------------------------------------------------------------------
; Disk description table, to make it a valid floppy
; Note: some of these values are hard-coded in the source!
; Values are those used by IBM for 1.44 MB, 3.5" diskette
OEMLabel db "16DOSRUN" ; Disk label
BytesPerSector dw 512 ; Bytes per sector
SectorsPerCluster db 1 ; Sectors per cluster
ReservedForBoot dw 1 ; Reserved sectors for boot record
NumberOfFats db 2 ; Number of copies of the FAT
RootDirEntries dw 224 ; Number of entries in root dir
; (224 * 32 = 7168 = 14 sectors to read)
LogicalSectors dw 2880 ; Number of logical sectors
MediumByte db 0F0h ; Medium descriptor byte
SectorsPerFat dw 9 ; Sectors per FAT
SectorsPerTrack dw 18 ; Sectors per track (36/cylinder)
Sides dw 2 ; Number of sides/heads
HiddenSectors dd 0 ; Number of hidden sectors
LargeSectors dd 0 ; Number of LBA sectors
DriveNo dw 0 ; Drive No: 0
Signature db 41 ; Drive signature: 41 for floppy
VolumeID dd 00000000h ; Volume ID: any number
VolumeLabel db "16DOS "; Volume Label: any 11 chars
FileSystem db "FAT12 " ; File system type: don't change!
; ------------------------------------------------------------------
; Main bootloader code
bootloader_start:
; Modify all segment setup code to assume an ORG of 0x0000
mov ax, 07C0h ; Set up 4K of stack space above buffer
mov ds, ax ; Set DS segment to where we're loaded
add ax, 544 ; 8k buffer = 512 paragraphs + 32 paragraphs (loader)
cli ; Disable interrupts while changing stack
mov ss, ax
mov sp, 4096
; Enter unreal mode
; Keep interrupts off while we switch to real mode
push ds ; Switch to real mode detroys DS. We need to save it
lgdt [gdtinfo] ; load gdt register
mov eax, cr0 ; switch to pmode by
or al,1 ; set pmode bit
mov cr0, eax
jmp $+2 ; Clear the instruction pre-fetch queue
; Set DS=ES=FS=GS to descriptor with 4gb limit
mov bx, 0x08 ; select descriptor 1
mov ds, bx ; 8h = 1000b
mov es, bx ; 8h = 1000b
mov fs, bx ; 8h = 1000b
mov gs, bx ; 8h = 1000b
and al,0xFE ; back to realmode
mov cr0, eax ; by toggling bit again
sti ; enable interrupts
pop ds ; Retsore DS to original value
;***********END OF UNREAL MODE SWITCH ***********;
; NOTE: A few early BIOSes are reported to improperly set DL
cmp dl, 0
je no_change
mov [bootdev], dl ; Save boot device number
mov ah, 8 ; Get drive parameters
int 13h
jc fatal_disk_error
and cx, 3Fh ; Maximum sector number
mov [SectorsPerTrack], cx ; Sector numbers start at 1
movzx dx, dh ; Maximum head number
add dx, 1 ; Head numbers start at 0 - add 1 for total
mov [Sides], dx
no_change:
mov eax, 0 ; Needed for some older BIOSes
; First, we need to load the root directory from the disk. Technical details:
; Start of root = ReservedForBoot + NumberOfFats * SectorsPerFat = logical 19
; Number of root = RootDirEntries * 32 bytes/entry / 512 bytes/sector = 14
; Start of user data = (start of root) + (number of root) = logical 33
floppy_ok: ; Ready to read first block of data
mov ax, 19 ; Root dir starts at logical sector 19
call l2hts
mov si, buffer ; Set ES:BX to point to our buffer (see end of code)
mov bx, ds
mov es, bx
mov bx, si
mov ah, 2 ; Params for int 13h: read floppy sectors
mov al, 14 ; And read 14 of them
pusha ; Prepare to enter loop
read_root_dir:
popa ; In case registers are altered by int 13h
pusha
stc ; A few BIOSes do not set properly on error
int 13h ; Read sectors using BIOS
jnc search_dir ; If read went OK, skip ahead
call reset_floppy ; Otherwise, reset floppy controller and try again
jnc read_root_dir ; Floppy reset OK?
search_dir:
popa
mov ax, ds ; Root dir is now in [buffer]
mov es, ax ; Set DI to this info
mov di, buffer
mov cx, word [RootDirEntries] ; Search all (224) entries
mov ax, 0 ; Searching at offset 0
next_root_entry:
xchg cx, dx ; We use CX in the inner loop...
mov si, kern_filename ; Start searching for kernel filename
mov cx, 11
rep cmpsb
je found_file_to_load ; Pointer DI will be at offset 11
add ax, 32 ; Bump searched entries by 1 (32 bytes per entry)
mov di, buffer ; Point to next entry
add di, ax
xchg dx, cx ; Get the original CX back
loop next_root_entry
mov si, file_not_found ; If kernel is not found, bail out
call print_string
found_file_to_load: ; Fetch cluster and load FAT into RAM
mov ax, word [es:di+0Fh] ; Offset 11 + 15 = 26, contains 1st cluster
mov word [cluster], ax
mov ax, 1 ; Sector 1 = first sector of first FAT
call l2hts
mov di, buffer ; ES:BX points to our buffer
mov bx, di
mov ah, 2 ; int 13h params: read (FAT) sectors
mov al, 9 ; All 9 sectors of 1st FAT
pusha ; Prepare to enter loop
read_fat:
popa ; In case registers are altered by int 13h
pusha
stc
int 13h ; Read sectors using the BIOS
jnc read_fat_ok ; If read went OK, skip ahead
call reset_floppy ; Otherwise, reset floppy controller and try again
jnc read_fat ; Floppy reset OK?
; ******************************************************************
fatal_disk_error:
; ******************************************************************
mov si, disk_error
read_fat_ok:
popa
mov ax, 2000h ; Segment where we'll load the kernel
mov es, ax
mov bx, 0
mov ah, 2 ; int 13h floppy read params
mov al, 1
push ax ; Save in case we (or int calls) lose it
; Now we must load the FAT from the disk. Here's how we find out where it starts:
; FAT cluster 0 = media descriptor = 0F0h
; FAT cluster 1 = filler cluster = 0FFh
; Cluster start = ((cluster number) - 2) * SectorsPerCluster + (start of user)
; = (cluster number) + 31
load_file_sector:
mov ax, word [cluster] ; Convert sector to logical
add ax, 31
call l2hts ; Make appropriate params for int 13h
mov ax, 2000h ; Set buffer past what we've already read
mov es, ax
mov bx, word [pointer]
pop ax ; Save in case we (or int calls) lose it
push ax
stc
int 13h
jnc calculate_next_cluster ; If there's no error...
call reset_floppy ; Otherwise, reset floppy and retry
jmp load_file_sector
; In the FAT, cluster values are stored in 12 bits, so we have to
; do a bit of maths to work out whether we're dealing with a byte
; and 4 bits of the next byte -- or the last 4 bits of one byte
; and then the subsequent byte!
calculate_next_cluster:
mov ax, [cluster]
mov dx, 0
mov bx, 3
mul bx
mov bx, 2
div bx ; DX = [cluster] mod 2
mov si, buffer
add si, ax ; AX = word in FAT for the 12 bit entry
mov ax, word [ds:si]
or dx, dx ; If DX = 0 [cluster] is even; if DX = 1 then it's odd
jz even ; If [cluster] is even, drop last 4 bits of word
; with next cluster; if odd, drop first 4 bits
odd:
shr ax, 4 ; Shift out first 4 bits (they belong to another entry)
jmp short next_cluster_cont
even:
and ax, 0FFFh ; Mask out final 4 bits
next_cluster_cont:
mov word [cluster], ax ; Store cluster
cmp ax, 0FF8h ; FF8h = end of file marker in FAT12
jae end
add word [pointer], 512 ; Increase buffer pointer 1 sector length
jmp load_file_sector
end: ; We've got the file to load!
pop ax ; Clean up the stack (AX was pushed earlier)
mov dl, byte [bootdev] ; Provide kernel with boot device info
jmp 2000h:0000h ; Jump to entry point of loaded kernel!
; ------------------------------------------------------------------
; BOOTLOADER SUBROUTINES
print_string: ; Output string in SI to screen
pusha
mov ah, 0Eh ; int 10h teletype function
.repeat:
lodsb ; Get char from string
cmp al, 0
je .done ; If char is zero, end of string
int 10h ; Otherwise, print it
jmp short .repeat
.done:
popa
ret
reset_floppy: ; IN: [bootdev] = boot device; OUT: carry set on error
push ax
push dx
mov ax, 0
mov dl, byte [bootdev]
stc
int 13h
pop dx
pop ax
ret
l2hts: ; Calculate head, track and sector settings for int 13h
; IN: logical sector in AX, OUT: correct registers for int 13h
push bx
push ax
mov bx, ax ; Save logical sector
mov dx, 0 ; First the sector
div word [SectorsPerTrack]
add dl, 01h ; Physical sectors start at 1
mov cl, dl ; Sectors belong in CL for int 13h
mov ax, bx
mov dx, 0 ; Now calculate the head
div word [SectorsPerTrack]
mov dx, 0
div word [Sides]
mov dh, dl ; Head/side
mov ch, al ; Track
pop ax
pop bx
mov dl, byte [bootdev] ; Set correct device
ret
; ------------------------------------------------------------------
; STRINGS AND VARIABLES
kern_filename db "KERNEL SYS" ; MikeOS kernel filename
disk_error db "Error.", 0
file_not_found db "Error.", 0
bootdev db 0 ; Boot device number
cluster dw 0 ; Cluster of the file we want to load
pointer dw 0 ; Pointer into Buffer, for loading kernel
gdtinfo:
dw gdt_end - gdt - 1 ;last byte in table
dd gdt+0x7c00 ;start of table
gdt dd 0,0 ; entry 0 is always unused
flatdesc db 0xff, 0xff, 0, 0, 0, 10010010b, 11001111b, 0
gdt_end:
; ------------------------------------------------------------------
; END OF BOOT SECTOR AND BUFFER START
times 510-($-$$) db 0 ; Pad remainder of boot sector with zeros
dw 0AA55h ; Boot signature (DO NOT CHANGE!)
buffer: ; Disk buffer begins (8k after this, stack starts)
; ==================================================================
赏金
在赏金中你是这样说的:
I'd want bounty winner to explain me why such code is wrong and help me to fix it (to make it work, by switching to unreal mode aka flat real mode and load kernel file named KERNEL.SYS and execute it. Kernel WILL use interrupts so protected mode aint option.)
保护模式支持软件和硬件中断。我相信您的意思是您的内核将使用 BIOS 中断。在保护模式下 运行 时 BIOS 中断不可用,除非您创建 VM86 任务或切换回实模式。
Real/Unreal 模式内核可以制作,但它们会因缺乏虚拟内存、内存保护和保护模式下可用的分页机制而受到限制。
进入虚幻模式需要暂时进入保护模式;使用指向具有 4gb 限制(而不是 64k)的 16 位数据描述符的选择器设置 DS/ES/GS/FS;然后关闭保护模式。在切换到保护模式期间,必须禁用中断,因为没有设置保护模式中断向量。
KERNEL.SYS
MikeOS 引导加载程序需要将名为 KERNEL.SYS 的文件放入格式为 FAT12 的磁盘映像的根目录中。我会假设你知道如何做到这一点。 Windows 和 Linux 之间执行此操作的方法不同,并且超出了此答案的范围。测试虚幻模式是否启用和工作的示例kernel.asm
如下:
bits 16
; MikeOS bootloader loads our code at 0x2000:0x0000 so we need org of 0x0000
; for the kernel code to work properly.
org 0x0000
kernel_start:
; Set DS, ES, FS, GS to 0x0000. In Unreal mode these segment registers
; are not limited to 64kb. We can address full 4gb of memory
xor ax, ax
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
; This code will not work in normal real mode. We emit instructions
; that use 0xb8000 as an offset. This offset is >= 65536 and normally
; isn't addressable directly in real mode. This should display MDP white
; on purple to upper left of screen. 0xb8000 is pointer to first cell
; of the text mode video display
;
; In Real AND Unreal mode on a 386 you are allowed to use 32-bit registers
; and memory operands. Real mode is limited to an offset that computes
; to a value below 64kb (65536) unlike unreal mode with a 4gb limit
mov edi, 0xb8000
mov word [edi], 0x57<<8 | 'M';
mov word [edi+2], 0x57<<8 | 'D';
mov word [edi+4], 0x57<<8 | 'P';
cli
.endloop:
hlt
jmp .endloop
它可以组装成KERNEL.SYS
:
nasm -f bin kernel.asm -o KERNEL.SYS
当生成带有此引导加载程序和 KERNEL.SYS 文件的磁盘映像并在 QEMU 中 运行(Bochs 将类似)时,输出将类似于:
如果处理器不处于虚幻模式,则写入左上角的字符将不会出现,或者hardware/emulator可能会达到其他类型的未定义状态。
其他观察和信息
- 实际上不需要在 MikeOS 引导程序中将开关置于 Unreal 模式。您可以保持 MikeOS 引导加载程序不变,并将开关移动到 Unreal 模式进入 KERNEL.SYS .
- 如果您打算访问任何奇数兆字节内存区域(0x100000-0x1fffff,0x300000-0x3fffff,...)中超过 1MiB 的内存中的数据,那么您还需要确保 A20 gate 是已启用。
- 您在引导加载程序中使用的虚幻模式代码设置了 4gb 限制的数据段。它不会以相同的方式设置 CS。这个版本的虚幻模式叫做大虚幻模式。
- 在 Big Unreal 模式下处理或调用中断(即 BIOS 中断)与在实模式下相同
- 如果你想创建一个中断处理程序,它的位置被限制在 0x0000:0x0000 和 0xFFFF:0xFFFF 之间的低内存区域。代码段中的所有代码都具有与实模式相同的限制。
- @RossRidge 的评论说 这意味着您不能在虚幻模式下启用中断或使用 BIOS 调用,因为它们可以更改 DS 中的值,这是不正确的大虚幻模式。
- 可以在代码段上使用 4gb 的限制,但是不能可靠地使用超过 64kb 的代码,因为中断只保存 CS:IP 而不是 CS:EIP。 EIP 可以是 0 到 4gb 之间的任何值,但是第一个 64kb 之外的任何值都不能可靠地完成,除非您在 运行ning 此类代码时禁用中断。这是非常严格的,也是很少使用这种模式的原因。这种模式通常被称为 Huge Unreal mode.
- @AlexeyFrunze has a number of bootloaders that support loading of kernels and supports Unreal mode. Alex also developed the Smaller C Compiler 可用于生成可从他的引导加载程序启动的代码,并支持为 Unreal 模式生成代码。