无法跳转或调用加载到 0x8000 的内核
Can't jump or call kernel loaded at 0x8000
我正在尝试开发一个操作系统。设计是这样的:我在 0x7c00 加载了一个引导加载程序,它加载第二阶段并在 0x7e00 跳转到它。第二阶段也是实模式,做很多事情,比如加载 gdt、启用 A20 和切换到保护模式。它还在 0x8000 加载了一个非常简单的 32 位内核。现在的问题是我无法调用或跳转到 0x8000,因为内核似乎没有加载(我在 VirtualBox 中进行了内存转储)。我已经在第二阶段完成了 FAR JMP 来设置 CS 寄存器。我正在 VirtualBox.
中测试我的 OS
我的引导加载程序代码:
org 0x7c00
bits 16
Start:
jmp Reset
bpbOEM DB "SKULLOS "
bpbBytesPerSector: DW 512
bpbSectorsPerCluster: DB 1
bpbReservedSectors: DW 1
bpbNumberOfFATs: DB 2
bpbRootEntries: DW 224
bpbTotalSectors: DW 2880
bpbMedia: DB 0xF0
bpbSectorsPerFAT: DW 9
bpbSectorsPerTrack: DW 18
bpbHeadsPerCylinder: DW 2
bpbHiddenSectors: DD 0
bpbTotalSectorsBig: DD 0
bsDriveNumber: DB 0
bsUnused: DB 0
bsExtBootSignature: DB 0x29
bsSerialNumber: DD 0xa0a1a2a3
bsVolumeLabel: DB "MOS FLOPPY "
bsFileSystem: DB "SKFS "
Set:
mov al , 02h
mov ah , 00h
int 10h
jmp Print
Print:
mov al , 'A'
mov bl , 0Fh
mov cx , 01h
mov ah , 09h
int 10h
jmp Reset
Reset:
; mov dl , 0x00
mov [0x500] , dl
mov ah , 0x00
int 0x13
jc Reset
mov ax , 0x7E0
mov es , ax
xor bx , bx
mov ah , 0x02
mov al , 1
mov ch , 0
mov cl , 2
mov dh , 0
mov dl , [0x500]
int 0x13
jmp 0x0000 :0x7e00
times 510-($-$$) db 0
db 0x55
db 0xAA
第二阶段代码:
org 0x7E00
bits 16
Start:
jmp Setup
;;;;;;;;;;;;;stack;;;;;;;;;;
Setup:
cli
xor ax , ax
mov ds , ax
mov es , ax
mov ax , 0x9000
mov ss , ax
mov sp , 0xFFFF
sti
jmp Set
;;;;;;;;;;;;;video;;;;;;;;;;;
Set:
mov al , 03h
mov ah , 00h
int 10h
mov ah , 09h
mov al , 'A'
mov bh , 00h
mov bl , 0x0F
mov cx , 01h
int 10h
jmp loadgdt
;;;;;;;;;;;;gdt;;;;;;;;;;;;;;;
gdt_start:
null:
dd 0
dd 0
code:
dw 0FFFFh
dw 0
db 0
db 10011010b
db 11001111b
db 0
data:
dw 0FFFFh
dw 0
db 0
db 10010010b
db 11001111b
db 0
end:
load: dw end - gdt_start -1
dd null
;;;;;;;;;;;;;loadgdt;;;;;;;;;;
loadgdt:
lgdt [load]
jmp A20
;;;;;;;;;;;;A20;;;;;;;;;;;;;;;
A20:
mov ax , 0x2401
int 0x15
jc A20
jmp Reset
;;;;;;;;;;;;;floppy;;;;;;;;;;;
Reset:
mov ah , 00h
mov dl , [0x500]
int 13h
jc Reset
jmp Read
Read:
mov ah , 02h
mov al , 01h
mov ch , 00h
mov cl , 03h
mov dh , 00h
mov dl , [0x500]
mov ax , 0x800
mov es , ax
xor bx , bx
int 13h
jc Read
jmp Begin
Begin:
mov ah , 09h
mov al , 'G'
mov bh , 00h
mov bl , 0x0F
mov cx , 01h
int 10h
jmp protected
;;;;;;;;;;;switching to protected;;;;
protected:
mov ah , 09h
mov al , 'P'
mov bh , 00h
mov bl , 0x0F
mov cx , 01h
int 10h
xor ax, ax
mov ds, ax
cli
mov eax, cr0
or eax , 1
mov cr0 , eax
jmp (code-gdt_start):transfer_control
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
bits 32
transfer_control:
mov ax, (data-gdt_start)
mov ds, ax
mov ss, ax
mov es, ax
mov esp, 90000h
mov [0xB8000], word 0x0F58 ; Print 'X'
call 0x8000
hlt
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
times 512-($-$$) db 0
内核代码:
org 0x8000
bits 32
jmp Start
Start:
mov ax , 0x10
mov ds , ax
mov ss, ax
mov es, ax
mov esp, 90000h
mov [0xB8002], word 0x0F58 ; Print 'X'
ret
times 512-($-$$) db 0
目前只有一个'X'正在印刷中。但是,应该打印两个 'X'。
用于创建软盘的命令:
dd seek=0 if=boot of=os.img
dd seek=1 if=second_stage of=os.img
dd seek=2 if=third_stage of=os.img
问题阅读第 3 阶段
在第二阶段加载第三阶段这样做:
Read:
mov ah , 02h ; Setup AH
mov al , 01h ; Setup AL
mov ch , 00h
mov cl , 03h
mov dh , 00h
mov dl , [0x500]
mov ax , 0x800 ; Destroy contents of AX
mov es , ax ; Setup ES=0x800
xor bx , bx
int 13h
我已经标记了有问题的行。您有效地设置了 AX 以准备读取,然后用 0x800 覆盖这些值以设置 ES。在设置 AH 和 AL 之前移动设置 ES。将代码修改为:
Read:
mov ax , 0x800
mov es , ax ; Setup ES=0x800
mov ah , 02h ; Setup AH
mov al , 01h ; Setup AL
mov ch , 00h
mov cl , 03h
mov dh , 00h
mov dl , [0x500]
xor bx , bx
int 13h
这可能会阻止您的第二阶段正确加载第三阶段。
其他问题
在引导加载程序的末尾,您有:
db 0xAA
db 0x55
这是向后的,应该是:
db 0x55
db 0xAA
你可以这样写:
dw 0xAA55
问题似乎是您在定义这些字节时没有考虑小字节序。
您正确地跳过了引导加载程序中的 BIOS 参数块,但 BPB 需要从引导扇区的第 4 个字节开始。您可以使用 short
修饰符强制使用 2 字节 JMP。然后,您可以在跳转后放置一个 nop
,以便 BPB 从第 4 个字节开始。
变化:
jmp Reset
bpbOEM DB "SKULLOS "
收件人:
jmp short Reset
nop ; 1 byte padding as BPB needs to start at 4th byte (short jmp takes 2 bytes)
bpbOEM DB "SKULLOS "
mov sp , 0xFFFF
应该是 mov sp, 0x0000
。这只是一个小问题。将堆栈放在 WORD 边界(偶数地址)上在 8086 处理器上性能更好。由于您没有长时间处于实模式,因此根本无关紧要。通常你会在你的情况下使用 mov sp, 0x0000
因为第一个 WORD 推送将在 0x9000:0xfffe 因为 2 首先从 SP 中减去,然后 WORD 压入堆栈。实际上,SP=0x0000 时,堆栈将从环绕到 64k 段的顶部开始。
如果标签就在 JMP 之后,则不需要从一个标签到另一个标签 JMP。指令如:
jmp Set
Set:
只会浪费 space 并占用 CPU 个周期。我注意到你在很多地方都这样做了。这不是你问题的一部分,只是一个观察。 FAR JUMP jmp (code-gdt_start):transfer_control
后跟标签很好,因为它用于正确设置 CS 描述符(对于保护模式)
当使用 int 13h
进行磁盘访问时,您应该使用 BIOS 传递给引导加载程序的引导驱动器编号作为 DL 的值。您的第一阶段和第二阶段的代码如下:
mov dl , 00h
这始终假设您正在从第一张软盘 (A:) 读取。如果你想在软盘以外的引导驱动器上使用你的代码 A: 你需要删除这个
我正在尝试开发一个操作系统。设计是这样的:我在 0x7c00 加载了一个引导加载程序,它加载第二阶段并在 0x7e00 跳转到它。第二阶段也是实模式,做很多事情,比如加载 gdt、启用 A20 和切换到保护模式。它还在 0x8000 加载了一个非常简单的 32 位内核。现在的问题是我无法调用或跳转到 0x8000,因为内核似乎没有加载(我在 VirtualBox 中进行了内存转储)。我已经在第二阶段完成了 FAR JMP 来设置 CS 寄存器。我正在 VirtualBox.
中测试我的 OS我的引导加载程序代码:
org 0x7c00
bits 16
Start:
jmp Reset
bpbOEM DB "SKULLOS "
bpbBytesPerSector: DW 512
bpbSectorsPerCluster: DB 1
bpbReservedSectors: DW 1
bpbNumberOfFATs: DB 2
bpbRootEntries: DW 224
bpbTotalSectors: DW 2880
bpbMedia: DB 0xF0
bpbSectorsPerFAT: DW 9
bpbSectorsPerTrack: DW 18
bpbHeadsPerCylinder: DW 2
bpbHiddenSectors: DD 0
bpbTotalSectorsBig: DD 0
bsDriveNumber: DB 0
bsUnused: DB 0
bsExtBootSignature: DB 0x29
bsSerialNumber: DD 0xa0a1a2a3
bsVolumeLabel: DB "MOS FLOPPY "
bsFileSystem: DB "SKFS "
Set:
mov al , 02h
mov ah , 00h
int 10h
jmp Print
Print:
mov al , 'A'
mov bl , 0Fh
mov cx , 01h
mov ah , 09h
int 10h
jmp Reset
Reset:
; mov dl , 0x00
mov [0x500] , dl
mov ah , 0x00
int 0x13
jc Reset
mov ax , 0x7E0
mov es , ax
xor bx , bx
mov ah , 0x02
mov al , 1
mov ch , 0
mov cl , 2
mov dh , 0
mov dl , [0x500]
int 0x13
jmp 0x0000 :0x7e00
times 510-($-$$) db 0
db 0x55
db 0xAA
第二阶段代码:
org 0x7E00
bits 16
Start:
jmp Setup
;;;;;;;;;;;;;stack;;;;;;;;;;
Setup:
cli
xor ax , ax
mov ds , ax
mov es , ax
mov ax , 0x9000
mov ss , ax
mov sp , 0xFFFF
sti
jmp Set
;;;;;;;;;;;;;video;;;;;;;;;;;
Set:
mov al , 03h
mov ah , 00h
int 10h
mov ah , 09h
mov al , 'A'
mov bh , 00h
mov bl , 0x0F
mov cx , 01h
int 10h
jmp loadgdt
;;;;;;;;;;;;gdt;;;;;;;;;;;;;;;
gdt_start:
null:
dd 0
dd 0
code:
dw 0FFFFh
dw 0
db 0
db 10011010b
db 11001111b
db 0
data:
dw 0FFFFh
dw 0
db 0
db 10010010b
db 11001111b
db 0
end:
load: dw end - gdt_start -1
dd null
;;;;;;;;;;;;;loadgdt;;;;;;;;;;
loadgdt:
lgdt [load]
jmp A20
;;;;;;;;;;;;A20;;;;;;;;;;;;;;;
A20:
mov ax , 0x2401
int 0x15
jc A20
jmp Reset
;;;;;;;;;;;;;floppy;;;;;;;;;;;
Reset:
mov ah , 00h
mov dl , [0x500]
int 13h
jc Reset
jmp Read
Read:
mov ah , 02h
mov al , 01h
mov ch , 00h
mov cl , 03h
mov dh , 00h
mov dl , [0x500]
mov ax , 0x800
mov es , ax
xor bx , bx
int 13h
jc Read
jmp Begin
Begin:
mov ah , 09h
mov al , 'G'
mov bh , 00h
mov bl , 0x0F
mov cx , 01h
int 10h
jmp protected
;;;;;;;;;;;switching to protected;;;;
protected:
mov ah , 09h
mov al , 'P'
mov bh , 00h
mov bl , 0x0F
mov cx , 01h
int 10h
xor ax, ax
mov ds, ax
cli
mov eax, cr0
or eax , 1
mov cr0 , eax
jmp (code-gdt_start):transfer_control
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
bits 32
transfer_control:
mov ax, (data-gdt_start)
mov ds, ax
mov ss, ax
mov es, ax
mov esp, 90000h
mov [0xB8000], word 0x0F58 ; Print 'X'
call 0x8000
hlt
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
times 512-($-$$) db 0
内核代码:
org 0x8000
bits 32
jmp Start
Start:
mov ax , 0x10
mov ds , ax
mov ss, ax
mov es, ax
mov esp, 90000h
mov [0xB8002], word 0x0F58 ; Print 'X'
ret
times 512-($-$$) db 0
目前只有一个'X'正在印刷中。但是,应该打印两个 'X'。 用于创建软盘的命令:
dd seek=0 if=boot of=os.img
dd seek=1 if=second_stage of=os.img
dd seek=2 if=third_stage of=os.img
问题阅读第 3 阶段
在第二阶段加载第三阶段这样做:
Read:
mov ah , 02h ; Setup AH
mov al , 01h ; Setup AL
mov ch , 00h
mov cl , 03h
mov dh , 00h
mov dl , [0x500]
mov ax , 0x800 ; Destroy contents of AX
mov es , ax ; Setup ES=0x800
xor bx , bx
int 13h
我已经标记了有问题的行。您有效地设置了 AX 以准备读取,然后用 0x800 覆盖这些值以设置 ES。在设置 AH 和 AL 之前移动设置 ES。将代码修改为:
Read:
mov ax , 0x800
mov es , ax ; Setup ES=0x800
mov ah , 02h ; Setup AH
mov al , 01h ; Setup AL
mov ch , 00h
mov cl , 03h
mov dh , 00h
mov dl , [0x500]
xor bx , bx
int 13h
这可能会阻止您的第二阶段正确加载第三阶段。
其他问题
在引导加载程序的末尾,您有:
db 0xAA
db 0x55
这是向后的,应该是:
db 0x55
db 0xAA
你可以这样写:
dw 0xAA55
问题似乎是您在定义这些字节时没有考虑小字节序。
您正确地跳过了引导加载程序中的 BIOS 参数块,但 BPB 需要从引导扇区的第 4 个字节开始。您可以使用 short
修饰符强制使用 2 字节 JMP。然后,您可以在跳转后放置一个 nop
,以便 BPB 从第 4 个字节开始。
变化:
jmp Reset
bpbOEM DB "SKULLOS "
收件人:
jmp short Reset
nop ; 1 byte padding as BPB needs to start at 4th byte (short jmp takes 2 bytes)
bpbOEM DB "SKULLOS "
mov sp , 0xFFFF
应该是 mov sp, 0x0000
。这只是一个小问题。将堆栈放在 WORD 边界(偶数地址)上在 8086 处理器上性能更好。由于您没有长时间处于实模式,因此根本无关紧要。通常你会在你的情况下使用 mov sp, 0x0000
因为第一个 WORD 推送将在 0x9000:0xfffe 因为 2 首先从 SP 中减去,然后 WORD 压入堆栈。实际上,SP=0x0000 时,堆栈将从环绕到 64k 段的顶部开始。
如果标签就在 JMP 之后,则不需要从一个标签到另一个标签 JMP。指令如:
jmp Set
Set:
只会浪费 space 并占用 CPU 个周期。我注意到你在很多地方都这样做了。这不是你问题的一部分,只是一个观察。 FAR JUMP jmp (code-gdt_start):transfer_control
后跟标签很好,因为它用于正确设置 CS 描述符(对于保护模式)
当使用 int 13h
进行磁盘访问时,您应该使用 BIOS 传递给引导加载程序的引导驱动器编号作为 DL 的值。您的第一阶段和第二阶段的代码如下:
mov dl , 00h
这始终假设您正在从第一张软盘 (A:) 读取。如果你想在软盘以外的引导驱动器上使用你的代码 A: 你需要删除这个