我正在尝试制作自己的自定义操作系统,我需要一些代码方面的帮助。这是我的bootloader.asm:
[ORG 0x7c00]
start:
cli
xor ax, ax
mov ds, ax
mov ss, ax
mov es, ax
mov [BOOT_DRIVE], dl
mov bp, 0x8000
mov sp, bp
mov bx, 0x9000
mov dh, 5
mov dl, [BOOT_DRIVE]
call load_kernel
call enable_A20
call graphics_mode
lgdt [gdtr]
mov eax, cr0
or al, 1
mov cr0, eax
jmp CODE_SEG:init_pm
[bits 32]
init_pm:
mov ax, DATA_SEG
mov ds, ax
mov ss, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ebp, 0x90000
mov esp, ebp
jmp 0x9000
[BITS 16]
graphics_mode:
mov ax, 0013h
int 10h
ret
load_kernel:
; load DH sectors to ES:BX from drive DL
push dx ; Store DX on stack so later we can recall
; how many sectors were request to be read ,
; even if it is altered in the meantime
mov ah , 0x02 ; BIOS read sector function
mov al , dh ; Read DH sectors
mov ch , 0x00 ; Select cylinder 0
mov dh , 0x00 ; Select head 0
mov cl , 0x02 ; Start reading from second sector ( i.e.
; after the boot sector )
int 0x13 ; BIOS interrupt
jc disk_error ; Jump if error ( i.e. carry flag set )
pop dx ; Restore DX from the stack
cmp dh , al ; if AL ( sectors read ) != DH ( sectors expected )
jne disk_error ; display error message
ret
disk_error :
mov bx , ERROR_MSG
call print_string
hlt
[bits 32]
; prints a null - terminated string pointed to by EDX
print_string :
pusha
mov edx , VIDEO_MEMORY ; Set edx to the start of vid mem.
print_string_loop :
mov al , [ ebx ] ; Store the char at EBX in AL
mov ah , WHITE_ON_BLACK ; Store the attributes in AH
cmp al , 0 ; if (al == 0) , at end of string , so
je print_string_done ; jump to done
mov [edx] , ax ; Store char and attributes at current
; character cell.
add ebx , 1 ; Increment EBX to the next char in string.
add edx , 2 ; Move to next character cell in vid mem.
jmp print_string_loop ; loop around to print the next char.
print_string_done :
popa
ret ; Return from the function
[bits 16]
; Variables
ERROR_MSG db "Error!" , 0
BOOT_DRIVE: db 0
VIDEO_MEMORY equ 0xb8000
WHITE_ON_BLACK equ 0x0f
%include "a20.inc"
%include "gdt.inc"
times 510-($-$$) db 0
db 0x55
db 0xAA
字符串
我用这个编译它:
nasm -f bin -o boot.bin bootloader.asm
型
这是kernel.c:
call_main(){main();}
void main(){}
型
我用这个编译它:
gcc -ffreestanding -o kernel.bin kernel.c
型
然后:
cat boot.bin kernel.bin > os.bin
型
我想知道我做错了什么,因为当我用 QEMU 测试时,它不工作。有人能给予一些提示来改进kernel.c
,这样我就不必使用call_main()函数了吗?
测试时,我用途:
qemu-system-i386 -kernel os.bin
型
我的其他文件
a20.inc:
enable_A20:
call check_a20
cmp ax, 1
je enabled
call a20_bios
call check_a20
cmp ax, 1
je enabled
call a20_keyboard
call check_a20
cmp ax, 1
je enabled
call a20_fast
call check_a20
cmp ax, 1
je enabled
mov bx, [ERROR]
call print_string
enabled:
ret
check_a20:
pushf
push ds
push es
push di
push si
cli
xor ax, ax ; ax = 0
mov es, ax
not ax ; ax = 0xFFFF
mov ds, ax
mov di, 0x0500
mov si, 0x0510
mov al, byte [es:di]
push ax
mov al, byte [ds:si]
push ax
mov byte [es:di], 0x00
mov byte [ds:si], 0xFF
cmp byte [es:di], 0xFF
pop ax
mov byte [ds:si], al
pop ax
mov byte [es:di], al
mov ax, 0
je check_a20__exit
mov ax, 1
check_a20__exit:
pop si
pop di
pop es
pop ds
popf
ret
a20_bios:
mov ax, 0x2401
int 0x15
ret
a20_fast:
in al, 0x92
or al, 2
out 0x92, al
ret
[bits 32]
[section .text]
a20_keyboard:
cli
call a20wait
mov al,0xAD
out 0x64,al
call a20wait
mov al,0xD0
out 0x64,al
call a20wait2
in al,0x60
push eax
call a20wait
mov al,0xD1
out 0x64,al
call a20wait
pop eax
or al,2
out 0x60,al
call a20wait
mov al,0xAE
out 0x64,al
call a20wait
sti
ret
a20wait:
in al,0x64
test al,2
jnz a20wait
ret
a20wait2:
in al,0x64
test al,1
jz a20wait2
ret
型
gdt.inc:
gdt_start:
dd 0 ; null descriptor--just fill 8 bytes dd 0
gdt_code:
dw 0FFFFh ; limit low
dw 0 ; base low
db 0 ; base middle
db 10011010b ; access
db 11001111b ; granularity
db 0 ; base high
gdt_data:
dw 0FFFFh ; limit low (Same as code)
dw 0 ; base low
db 0 ; base middle
db 10010010b ; access
db 11001111b ; granularity
db 0 ; base high
end_of_gdt:
gdtr:
dw end_of_gdt - gdt_start - 1 ; limit (Size of GDT)
dd gdt_start ; base of GDT
CODE_SEG equ gdt_code - gdt_start
DATA_SEG equ gdt_data - gdt_start
型
2条答案
按热度按时间yvgpqqbh1#
有很多问题,但总的来说,你的汇编代码确实可以工作。我写了一个StackOverflow的答案,其中有一般 Bootstrap 开发的提示。
不要假设段寄存器设置正确
你的问题中的原始代码没有设置 SS 堆栈段寄存器。我给予的提示#1是:
当BIOS跳转到你的代码时,你不能依赖CS,DS,ES,SS,SP寄存器的有效值或期望值。当你的 Bootstrap 启动时,它们应该被适当地设置。
如果你需要 ES,它也应该被设置。尽管在你的代码中似乎不是这样的(除了我稍后讨论的print_string函数)。
正确定义GDT
阻止您进入保护模式的最大错误是,您在 gdt.inc 中设置了全局描述符表(GDT),并以以下开头:
字符串
每个全局描述符需要8个字节,但
dd 0
只定义了4个字节(双字)。它应该是:型
实际上,第二个
dd 0
是意外添加到上一行注解的末尾的。16位真实的模式下不要使用32位代码
你已经写了一些
print_string
代码,但它是32位代码:型
您在16位代码中调用 print_string 作为错误处理程序,因此您在这里所做的可能会强制重新启动计算机。您不能使用32位寄存器和寻址。代码可以通过一些调整变为16位:
型
主要区别(在16位代码中)是我们不再使用 EAX 和 EDX 32位寄存器。为了访问视频RAM @ * 0xb 8000 ,我们需要使用表示相同内容的段:偏移对。 0xb 8000 * 可以表示为段:偏移 * 0xb 800:0x 0 *(计算为(0xb 800 <<4)+0x0)= * 0xb 8000 * 物理地址。我们可以使用此知识将 b800 存储在 ES 寄存器中,并使用 DI 寄存器作为偏移来更新视频内存。我们现在用途:使用:
型
把一个字移入视频存储器。
组装链接内核和Bootloader
在构建内核时遇到的一个问题是,你没有正确地生成一个可以直接加载到内存中的平面二进制映像。与其使用
gcc -ffreestanding -o kernel.bin kernel.c
,我建议这样做:型
这将使用调试信息将 kernel.c 组装到 kernel.o(
-g
)。然后,链接器将 kernel.o(32位 ELF 二进制)并生成一个名为 kernel.elf 的 ELF 可执行文件(如果您想调试内核,这个文件会很方便)。然后,我们使用 objcopy 获取ELF 32可执行文件 kernel.elf,并将其转换为平面二进制映像 * kernel.bin *。可以由BIOS加载。需要注意的一点是,使用-Tlinker.ld
选项,我们要求 LD(链接器)从文件 linker.ld 中读取选项。这是一个简单的linker.ld
,您可以使用它开始:型
这里需要注意的是,
. = 0x9000
告诉链接器它应该产生一个将在内存地址 * 0x 9000 * 加载的可执行文件。0x9000
似乎是你在问题中放置内核的地方。其余的行提供了需要包含到内核中才能正常工作的 C 部分。我建议在使用 NASM 时做一些类似的事情,而不是这样做
nasm -f bin -o boot.bin bootloader.asm
:型
这类似于编译 C 内核。我们在这里不使用链接器脚本,但我们确实告诉链接器生成我们的代码,假设代码( Bootstrap )将在 * 0x 7 c 00 * 加载。
要做到这一点,你需要从 bootloader.asm 中删除这一行:
型
编译内核(kernel.c)
将 kernel.c 文件修改为:
型
在 bootloader.asm 中,我们应该调用
main
函数(它将被放置在0x 9000处),而不是跳到它。而不是:型
将其更改为:
型
调用后的代码将在 C 函数main返回时执行。这是一个简单的循环,它将有效地停止处理器并无限期地保持这种状态,因为我们没有地方可以返回。
完成所有建议更改后的代码
bootloader.asm:
型
gdt.inc:
型
a20.inc:
型
kernel.c:
型
linker.ld
型
使用QEMU使用DD /DD 2创建磁盘镜像
如果您使用上面的文件,并使用这些命令生成所需的引导加载程序和内核文件(如前所述)
型
您可以使用以下命令生成磁盘映像(在本例中,我们将其设置为软盘大小):
型
这将创建一个大小为5122880字节(1.44 MB软盘的大小)的填充零的磁盘映像。
dd if=boot.bin of=disk.img bs=512 conv=notrunc
将 * Boot .bin 写入文件的第一个扇区而不截断磁盘映像。dd if=kernel.bin of=disk.img bs=512 seek=1 conv=notrunc
将 kernel.bin 从第二个扇区开始放入磁盘映像。seek=1
在写入之前跳过第一个块(bs=512)。如果你想运行你的内核,你可以在 QEMU 中以软盘驱动器A:(
-fda
)的形式启动它,如下所示:型
您还可以使用 QEMU 和GNU调试器(GDB)调试您的32位内核,这些调试信息是我们在使用上述说明编译/组装代码时生成的。
型
此示例使用远程调试器启动 QEMU,并使用文件
disk.img
模拟软盘(我们用 DD 创建)。GDB 使用kernel.elf启动(我们用调试信息生成的文件),然后连接到 QEMU,并在 C 中的 function main()处设置断点当调试器最终准备就绪时,你会被提示按<return>
继续。幸运的话,你应该在调试器中查看函数 main。yebdmbv42#
我想我知道为什么它不工作, Bootstrap 必须理解ISO 9660结构,当你把它放入ISO文件,因为作为一个新兴的文件,不理解ISO文件系统,它不知道内核在哪里,我宁愿你测试原始文件在qemu一起。