您的代码存在许多严重问题:
- 它全部是大写并且难以阅读。
- 似乎缺乏对实模式如何理解段:偏移寻址 https://thestarman.pcministry.com/asm/debug/Segments.html在实模式下工作。
- 寄存器在未初始化的情况下使用。
- 段寄存器设置不正确。
执行类似操作的代码版本如下。代码大部分都带有注释。需要了解的重要事项:
- 当 BIOS 打印字符时,它会更新当前行和列BIOS 数据区 (BDA) http://stanislavs.org/helppc/bios_data_area.html。在保护模式下,您可以读取内存位置 0x450(列)和 0x451(行)中的字节。您可以使用此信息从 BIOS 中断处继续。
- 内存地址0x44a处的16位字是BIOS先前设置的当前视频模式的屏幕宽度。
- 屏幕上的每个单元格都是两个字节。视频内存中的当前字节偏移量可以计算为 0xb8000+(cur_row * screen_width + cur_col) * 2
- 包括一个BIOS 参数块 (BPB) https://en.wikipedia.org/wiki/BIOS_parameter_block允许在软盘 (FDD) 模拟模式下使用 USB 磁盘介质时正确加载映像。此代码为 1.44MiB 软盘提供 BPB。
- 要正确寻址所有内存,您应该启用A20线 https://wiki.osdev.org/A20_Line.提供的代码使用快速启用方法,但可能不与所有硬件兼容,但应该适用于大多数模拟器。
- 当使用打印字符串时
print_string_pm
硬件光标位置更新为set_cursor
将字符串放入显示内存后。
bpb.inc:
global bpb_disk_info
jmp boot_start
TIMES 3-($-$$) DB 0x90 ; Support 2 or 3 byte encoded JMPs before BPB.
bpb_disk_info:
; Dos 4.0 EBPB 1.44MB floppy
OEMname: db "mkfs.fat" ; mkfs.fat is what OEMname mkdosfs uses
bytesPerSector: dw 512
sectPerCluster: db 1
reservedSectors: dw 1
numFAT: db 2
numRootDirEntries: dw 224
numSectors: dw 2880
mediaType: db 0xf0
numFATsectors: dw 9
sectorsPerTrack: dw 18
numHeads: dw 2
numHiddenSectors: dd 0
numSectorsHuge: dd 0
driveNum: db 0
reserved: db 0
signature: db 0x29
volumeID: dd 0x2d7e5a1a
volumeLabel: db "NO NAME "
fileSysType: db "FAT12 "
boot.asm:
bits 16
ORG 0x7c00
VIDEO_TEXT_ADDR EQU 0xb8000 ; Hard code beginning of text video memory
ATTR_WHITE_ON_BLACK EQU 0x07 ; White on black attribute
CR EQU 0x0d ; Carriage return
LF EQU 0x0a ; Line feed
; Include a BPB (1.44MB floppy with FAT12) to be more comaptible with USB floppy media
%include "bpb.inc"
boot_start:
xor ax, ax ; DS=SS=0. Real mode code below doesn't use ES
mov ds, ax
mov ss, ax ; Stack at 0x0000:0x7c00 below bootloader
mov sp, 0x7c00
cld ; Set string instructions to use forward movement
mov si, boot_init_msg ; Print boot initialization message
call print_string_rm
; Fast method of enabling A20 may not work on all x86 BIOSes
; It is good enough for emulators and most modern BIOSes
; See: https://wiki.osdev.org/A20_Line
cli ; Disable interrupts
in al, 0x92
or al, 2
out 0x92, al ; Enable A20 using Fast Method
mov si, load_gdt_msg ; Print loading GDT message
call print_string_rm
lgdt [gdtr] ; Load our GDT
mov si, enter_pm_msg ; Print protected mode message
call print_string_rm
mov eax, cr0
or eax, 1
mov cr0, eax ; Set protected mode flag
jmp CODE32_SEL:start32 ; FAR JMP to set CS
bits 32
start32:
mov ax, DATA32_SEL ; Setup the segment registers with data selector
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
mov esp, 0x9c000 ; Set the stack to grow down from area under
; EBDA/Video memory
xor eax, eax ; Clear EAX for the instructions below
mov al, [0x450] ; Byte at address 0x450 = last BIOS column position
mov [cur_col], eax ; Copy to current column
mov al, [0x451] ; Byte at address 0x451 = last BIOS row position
mov [cur_row], eax ; Copy to current row
mov ax, [0x44a] ; Word at address 0x44a = # of columns (screen width)
mov [screen_width], eax ; Copy to screen width
mov eax, in_pm_msg ; Print message we are in protected mode
call print_string_pm ; EAX = first parameter
end_loop:
hlt
jmp end_loop
; Function: set_cursor
; set the hardware cursor position based on the
; current column (cur_col) and current row (cur_row) coordinates
; See: https://wiki.osdev.org/Text_Mode_Cursor#Moving_the_Cursor_2
;
; Inputs: None
; Clobbers: EAX, ECX, EDX
set_cursor:
mov ecx, [cur_row] ; EAX = cur_row
imul ecx, [screen_width] ; ECX = cur_row * screen_width
add ecx, [cur_col] ; ECX = cur_row * screen_width + cur_col
; Send low byte of cursor position to video card
mov edx, 0x3d4
mov al, 0x0f
out dx, al ; Output 0x0f to 0x3d4
inc edx
mov al, cl
out dx, al ; Output lower byte of cursor pos to 0x3d5
; Send high byte of cursor position to video card
dec edx
mov al, 0x0e
out dx, al ; Output 0x0e to 0x3d4
inc edx
mov al, ch
out dx, al ; Output higher byte of cursor pos to 0x3d5
ret
; Function: print_string_pm
; Display a string to the console on display page 0 in protected mode.
; Handles carriage return and line feed.
; Doesn't handle tabs, backspace, wrapping and scrolling.
;
; Inputs: EAX = Offset of address to print
; Clobbers: EAX, ECX, EDX
print_string_pm:
push edi
push esi
push ebx
mov esi, eax ; Set ESI to beginning of string
; Assume base of text video memory is ALWAYS 0xb8000
mov ebx, VIDEO_TEXT_ADDR ; EBX = beginning of video memory
mov eax, [cur_row] ; EAX = cur_row
mul dword [screen_width] ; EAX = cur_row * screen_width
mov edx, eax ; EDX = copy of offset to beginning of line
add eax, [cur_col] ; EAX = cur_row * screen_width + cur_col
lea edi, [ebx + eax * 2] ; EDI = memory location of current screen cell
mov ah, ATTR_WHITE_ON_BLACK ; Set attribute
jmp .getch
.repeat:
cmp al, CR ; Is the character a carriage return?
jne .chk_lf ; If not skip and check for line feed
lea edi, [ebx + edx * 2] ; Set current video memory pointer to beginning of line
mov dword [cur_col], 0 ; Set current column to 0
jmp .getch ; Process next character
.chk_lf:
cmp al, LF ; Is the character a line feed?
jne .write_chr ; If not then write character
mov eax, [screen_width]
lea edi, [edi + eax * 2] ; Set current video memory ptr to same pos on next line
inc dword [cur_row] ; Set current row to next line
mov ah, ATTR_WHITE_ON_BLACK ; Reset attribute
jmp .getch ; Process next character
.write_chr:
inc dword [cur_col] ; Update current column
stosw
.getch:
lodsb ; Get character from string
test al, al ; Have we reached end of string?
jnz .repeat ; if not process next character
.end:
call set_cursor ; Update hardware cursor position
pop ebx
pop esi
pop edi
ret
bits 16
; Function: print_string_rm
; Display a string to the console on display page 0 in real mode
;
; Inputs: SI = Offset of address to print
; Clobbers: AX, BX, SI
print_string_rm:
mov ah, 0x0e ; BIOS tty Print
xor bx, bx ; Set display page to 0 (BL)
jmp .getch
.repeat:
int 0x10 ; print character
.getch:
lodsb ; Get character from string
test al,al ; Have we reached end of string?
jnz .repeat ; if not process next character
.end:
ret
cur_row: dd 0x00
cur_col: dd 0x00
screen_width: dd 0x00
boot_init_msg:
db "Booting sequence initialized...", CR, LF, 0
load_gdt_msg:
db "Loading GDT...", CR, LF, 0
enter_pm_msg:
db "Entering 32-bit Protected Mode...", CR, LF, 0
in_pm_msg:
db "Executing code in protected mode!", CR, LF, 0
align 8
gdt_start:
dd 0 ; null descriptor
dd 0
gdt32_code:
dw 0FFFFh ; limit low
dw 0 ; base low
db 0 ; base middle
db 10011010b ; access
db 11001111b ; 32-bit, 4kb granularity, limit 0xffffffff bytes
db 0 ; base high
gdt32_data:
dw 0FFFFh ; limit low (Same as code)
dw 0 ; base low
db 0 ; base middle
db 10010010b ; access
db 11001111b ; 32-bit, 4kb granularity, limit 0xffffffff bytes
db 0 ; base high
end_of_gdt:
gdtr:
dw end_of_gdt - gdt_start - 1
; limit (Size of GDT - 1)
dd gdt_start ; base of GDT
CODE32_SEL equ gdt32_code - gdt_start
DATA32_SEL equ gdt32_data - gdt_start
; Pad boot sector to 510 bytes and add 2 byte boot signature for 512 total bytes
TIMES 510-($-$$) db 0
dw 0xaa55
可以使用以下命令将该代码组装并构建到 1.44MiB 软盘映像中:
nasm -f bin boot.asm -o boot.bin
# Build 1.44MB disk image
dd if=/dev/zero of=disk.img bs=1024 count=1440
dd if=boot.bin of=disk.img conv=notrunc
输出应该类似于: