SKZ*_*KZI 1 x86 assembly gcc nasm osdev
我正在制作一个运行 Snake 的操作系统,并且我处于 32 位保护模式,但我无法在屏幕上绘制像素。我在实模式下切换到模式 0x13 (320x200x256),屏幕一片空白。进入保护模式后,内核运行并且我正在绘制的像素不会出现。
我从这篇OSDev 文章中汲取灵感,该文章切换到保护模式并将像素绘制到视频显示上。
这是kernel.c
#include "display.h" // Include Display Drivers
#include "constants.h" // Include constants that C doesn't have included
#include "keyboard.h" // Include the custom keyboard driver
void _start(){} // Remove LD Warning
DisplayDetails globalDisplayDetails; // The display details.
int main(){
// Init the display details
DisplayDetails details = display_init();
globalDisplayDetails = details;
bool running = true;
while(running){
// This is the OS loop
putpixel(100, 100, 0x00FFFFFF);
}
// return 0;
}
Run Code Online (Sandbox Code Playgroud)
这是display.h(实际的显示驱动程序)
#define VRAM 0xA0000
typedef struct DisplayModeDetails {
int width; // Width of one line of pixels
int height; // Height of display in pixels
int colors; // How many colors are supported
int pitch; // How many bytes of VRAM to skip when going 1 pixel down
int pixelWidth; // How many bytes of VRAM to skip when going 1 pixel right
} DisplayDetails;
struct DisplayModeDetails display_init(){
struct DisplayModeDetails details; // Setup display mode details
details.width = 640;
details.height = 480;
details.colors = 16;
details.pitch = 1;
details.pixelWidth = 1;
return details;
}
void putpixel(int pos_x, int pos_y, unsigned long VGA_COLOR)
{
unsigned char* location = (unsigned char*)0xA0000 + 320 * pos_y + pos_x;
*location = VGA_COLOR;
}
Run Code Online (Sandbox Code Playgroud)
boot_sect.asm:
[bits 16]
[org 0x7C00]
mov [BOOTDRIVE], dl
; call clear_screen ; Clear screen
call load_kernel
load_kernel:
mov bx, KERNELOFFSET
mov dh, 0x05
mov dl, [BOOTDRIVE]
; mov dl, 0x80
call clear_screen ; Clear Screen
call disk_load ; Load From Disk
mov ah, 0x00 ; Start setting video mode
mov al, 0x13 ; 320x200 256 color graphics
int 0x10
cli ; Disable Interrupts
lgdt [gdt_descriptor] ; GDT start address
mov eax, cr0
or eax, 1
mov cr0, eax ; Jump to Protected 32 bit mode
jmp CODESEG:start_protected_mode
jmp $
clear_screen:
pusha
mov ah, 0x07 ; Scroll al lines; 0 = all
mov bh, 0x0f ; white on black
mov cx, 0x00 ; row=0, col=0
mov dx, 0x184f ; row = 24, col = 79
int 0x10 ; Call interrupt
mov ah, 0x02
mov dh, 0x00
mov dl, 0x00
mov bh, 0x00
int 0x10
popa
ret
disk_load:
pusha
push dx
mov ah, 0x02 ; read mode
mov al, dh ; read dh number of sects
mov cl, 0x02 ; read from sect 2 (1 = boot)
mov ch, 0x00 ; cylinder 0
mov dh, 0x00 ; head 0
int 0x13
jc disk_error
pop dx
cmp al, dh
jne sectors_error
popa
ret
disk_error:
mov ah, '1' ; Error Code
jmp err_loop
sectors_error:
mov ah, '2' ; Error Code
jmp err_loop
err_loop:
call clear_screen
mov dh, ah ; Print Error Message
mov ah, 0x0e
mov al, 'E'
int 0x10
mov al, 'r'
int 0x10
int 0x10
mov al, ' '
int 0x10
mov al, dh ; Print Error Code
int 0x10
jmp $ ; create infinite loop
; Constants
KERNELOFFSET equ 0x1000
CODESEG equ gdt_code - gdt_start
DATASEG equ gdt_data - gdt_start
gdt_start:
dq 0x0
gdt_null:
dd 0x0
dd 0x0
gdt_code:
dw 0xffff
dw 0x0
db 0x0
db 0b10011010
db 0b11001111
db 0x0
gdt_data:
dw 0xffff
dw 0x0
db 0x0
db 0b10010010
db 0b11001111
db 0x0
gdt_end:
gdt_descriptor:
dw gdt_end - gdt_start
dd gdt_start
[bits 32]
start_protected_mode:
; Load the kernel
mov ax, DATASEG
mov dx, ax
mov ss, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ebp, 0x9000
mov esp, ebp
call KERNELOFFSET
jmp $
BOOTDRIVE db 0
; Marking as bootable.
times 510-($-$$) db 0
dw 0xaa55
Run Code Online (Sandbox Code Playgroud)
kernel_entry.asm:
[bits 32]
[extern main]
call main
jmp $
Run Code Online (Sandbox Code Playgroud)
我用这个脚本构建和测试:
gcc -m32 -fno-pie -ffreestanding -g -c kernel.c -o obj/main.o
nasm -f elf kernel_entry.asm -o obj/entry.o
nasm -f bin boot_sect.asm -o obj/boot.bin
ld -m elf_i386 -o obj/kernel.bin -Ttext 0x1000 obj/main.o obj/entry.o --oformat binary
cat obj/boot.bin obj/kernel.bin > bin/os.bin
qemu-system-x86_64 -drive format=raw,file=bin/os.bin -display sdl
Run Code Online (Sandbox Code Playgroud)
首先,模式 13 是每个像素一个字节,所以我不确定您为什么要使用它,0x00ffffff除非您认为它是 RGB/RGBA 值,但该模式并非如此。
当将此值作为单个字节推入内存时,它将变为0xff,根据模式 13 维基百科页面,这是使用的调色板:
在我看来,0xff(右下)和0x00(左上)一样黑。因此,我怀疑如果你想要白色,你应该使用0x0f(右上角)。这将是我要尝试的第一件事。
除此之外,您可能需要确保在 32 位平面模式(或同等模式)下运行,其中当前使用的选择器基于物理地址零并且足够大以到达视频内存,即正常的 32 位平面模式 4G应该可以解决问题:-)
那是因为您使用的起始位置为A0000。自从我进行这种级别的图形编程以来已经有一段时间了,足够长的时间我使用段寄存器而不是选择器,因此将寄存器设置A000为零并将偏移量设置为零。但我确实记得您需要正确解释逻辑地址如何变成物理地址。
进一步研究一下底层操作系统代码,假设您按照链接使用此处的代码,我注意到的一件事是 GDT 的设置方式。数据段条目为:
gdt_data:
dw 0xffff # Segment limit b0-15 = ffff.
dw 0x0 # Segment base b0-15 = 0000.
db 0x0 # Segment base b16-23 = 00.
db 0b10010010
db 0b11001111 # Segment limit b16-19 = f, granularity 4K.
db 0x0 # Segment base b24-31 = 00
Run Code Online (Sandbox Code Playgroud)
这意味着您的数据选择器的0x00000000限制为0xfffff(由于粒度是 4K 而不是单个字节,因此这是整个 4G 空间)。
因此,为了到达物理地址A0000,您将使用(正如您所拥有的那样)0xA0000-那里似乎没有问题。但是,我确实注意到该代码中其他地方有一行奇怪的行:
start_protected_mode:
; Load the kernel
mov ax, DATASEG
mov dx, ax ;; <<-- this one.
mov ss, ax
mov es, ax
mov fs, ax
mov gs, ax
Run Code Online (Sandbox Code Playgroud)
此代码段似乎正在设置所有非 CS 选择器,以便它们使用您的数据段,这是预期的。但是,上面的第二个mov将选择器移至dx而不是ds,并且似乎没有其他代码可以修改ds。
当 BIOS 将控制权转移到引导扇区时,关于系统状态的标准化内容很少。唯一(几乎)确定的是引导扇区代码已加载并在物理地址运行
0x7c00,CPU 处于 16 位实模式,称为 DL 的 CPU 寄存器包含“驱动器号”,并且只有 512 字节引导扇区已加载。
那里没有提到ds寄存器内容,而且我见过的大多数引导代码都没有做出任何假设,而是显式地设置了它需要的一切。
如果ds没有引用正确的选择器,则逻辑到物理的映射可能无法工作,并且写入A0000将到达预期之外的位置(或由于选择器无效而出现故障)。所以这条线可能应该设置ds而不是dx。
并且,为了完整起见,将Michael Petch提出的其他问题纳入评论中:
您的构建还存在另一个严重问题。确保在链接时首先obj/entry.o列出,以便代码正确启动内核(请参见下面的第二行):
ld -m elf_i386 -o obj/kernel.bin -Ttext 0x1000
obj/entry.o obj/main.o
--oformat binary
Run Code Online (Sandbox Code Playgroud)
此外,由于 x86 和 x86-64 软件模拟在用户模式下运行时可能会跳过某些检查,例如超出段限制,因此设置不ds正确qemu可能不会陷入困境。
但是,使用该--enable-kvm选项或在完整系统模式(当然,或者在实际硬件上)运行它可能会出现错误。