程序集 32 位打印以显示在 qemu 上运行的代码,无法在真实硬件上运行
Assembly 32-bit print to display code runs on qemu, fails to work on real hardware
我在裸硬件上用 x86 汇编语言编写了一小段代码 运行,此时,它甚至可以启用受保护的 32 位模式
我 运行 遇到了与屏幕打印有关的问题。我读过要不间断地这样做,可能会将字符加载到一个特殊的内存区域,即 RAM 地址 0xb8000。
知道了这一点,我写了一个函数来做这件事,并在 qemu 中测试时证明是成功的。但是,问题来了,当我尝试在真机(即 Lenovo G470 笔记本电脑)上 运行 这个程序时,它无法通过写入显示内存区域来显示我想要显示的字符串。使用 BIOS 中断显示的所有其他字符串都按预期工作,唉,32 位打印功能似乎没有做任何事情。虽然程序没有崩溃,但在使用中断打印的行之后出现了一个闪烁的光标。
如果它很重要,我正在尝试从 USB 驱动器启动它
更直白地说,字符串 S3 没有打印在我的笔记本电脑中,它在 qemu 中,我不知道为什么。
这是我的代码,我为缺乏技巧提前道歉:
[BITS 16] ;NASM 16-BIT MODE
[ORG 0x7C00] ;OFFSET MEMORY LOCATIONS TO BSECTOR/ BIOS LOADS MBR INTO THIS ADDRESS.
XOR AX, AX ;INITIALIZE SEGMENT REGISTERS BY AX
MOV DS, AX ;INDIRECTLY INITIALIZING DS REGISTER
MOV ES, AX ;INDIRECTLY INITIALIZING ES REGISTER
MOV GS, AX ;INDIRECTLY SETTING GS REGISTER
MOV FS, AX ;INDIRECTLY SETTING DS REGISTER
MOV SP, 0X900 ;SETTING STACK POINTER TO 0X900, OFFSET FROM 0X0 = 0X7C00
MOV BP, SP ;
JMP WORD 0x0:START16 ;JUMP TO START OF PROGRAM
PSR16B: ;16-BIT PRINT ROUTINE
MOV SI, BX ;ASSIGN SI POSITION OF STRING
MOV AH, 0XE ;TTY MODE
PSR16BLOP0: ;PRINT LOOP
LODSB ;LOAD SI INTO AL AND +1 ADDRESS OF STRING
CMP AL, 0X0 ;CONDITIONAL
JE PSR16LOP0END ;END LOOP
INT 0X10 ;BIOS INTERRUPT, PRINT AL TO SCREEN
JMP PSR16BLOP0 ;LOOP TO LOP
PSR16LOP0END: ;END OF LOOPA
MOV AL, 0XA ;NEW LINE ASCII
INT 0X10 ;RAISING PRINT BIOS INTERRUPT
MOV AL, 0XD ;NEWLINE 'CHARACTER' ASCII
INT 0X10 ;RAISING PRINT BIOS INTERRUPT
ADD CH, 0X1 ;ADD ONE TO COUNTER
RET ;RETURN TO LAST ADRESS BEFORE CALL
PSR16BEND: ;END OF FUNCTION, UNUSED
S0: ;STRING
DB 'BOOTING SEQUENCE INITIALIZED',0
S1: ;STRING
DB 'LOADING GDT',0
S2: ;STRING
DB 'ENTERING 32-BIT PROTECTED MODE',0
GDTS: ;START OF GLOBAL DESCRIPTOS TABLE
GDTN: ;NULL BEGGINING, 8 BYTES
DQ 0X0
GDTC: ;TABLE FOR CODE SEGMENT
DW 0XFFFF ;BITS 0-15 OF LIMIT(MAXADRESSUNIT) ||0X0-0XF
DW 0X0 ;BASE(SEGMENTSTART), BITS 0-15 ||0XF-0X1F
DB 0X0 ;BASE, BITS 16-23 ||0X20-0X27
DB 10011010B ;ACCESS BYTE ||0X28-0X2F
DB 11001111B ;SECOND4BITS:LIMITBITS 16-19/FIRST4BITS:FLAGS= ||0X30-0X37
DB 0X0 ;BASE, BITS 24-31||0X38-0X3F
GDTD: ;TABLE FOR DATA SEGMENT
DW 0XFFFF ;BITS 0-15 OF LIMIT(MAXADRESSUNIT) ||0X0-0XF
DW 0X0 ;BASE(SEGMENTSTART), BITS 0-15 ||0XF-0X1F
DB 0X0 ;BASE, BITS 16-23 ||0X20-0X27
DB 10010010B ;ACCESS BYTE ||0X28-0X2F
DB 11001111B ;SECOND4BITS:LIMITBITS 16-19/FIRST4BITS:FLAGS= ||0X30-0X37
DB 0X0 ;BASE, BITS 24-31||0X38-0X3F
GDTE: ;END OF GLOBAL DESCRIPTION TABLE
GDTDESC: ;GDT DESCRIPTOR
DW GDTE - GDTS - 1 ;SIZE OF GDT, 2 BYTE, MUST BE LESS THAN 1
DD GDTS ;ADRESS OF GDT, 4 BYTE
GDTDESCEND: ;END OF GDTDESC, UNUSED
CODESEG EQU GDTC - GDTS ;CODE SEGMENT ADDRESS CONSTANT
DATASEG EQU GDTD - GDTS ;DATA SEGMENT ADDRESS CONSTANT
START16: ;START OF BOOTSECTOR PROGRAM
MOV CH, 0X0 ;LINES COUNTER.
MOV BX, S0 ;SET STRING POINTER
CALL PSR16B ;CALL PRINT FUNCTION
MOV BX, S1 ;SET STRING POINTER
CALL PSR16B ;CALL PRINT FUNCTION
LGDT [GDTDESC] ;LOADING GDT DESCRIPTOR
MOV BX, S2 ;SET STRING POINTER
CALL PSR16B ;CALL PRINT FUNCTION
CLI ;SEVERING INTERRUPTS
MOV EAX, CR0 ;INDIRECTLY SETTING PROTECTED MODE BIT
OR EAX, 0X1 ;SETTING PMODE BIT
MOV CR0, EAX ;PROTECTED MODE ENABLED
JMP CODESEG:START32 ;FAR JUMP TO 32 BIT LAND
[BITS 32] ;NASM 32-BIT MODE
S3: ;STRING
DB '32-BIT PROTECTED MODE ENABLED', 0
PSR32B: ;PRINT TO DISPLAY ROUTINE FOR 32-BIT PREOTECTED MODE
PSR32BLOP0: ;INITIALIZE VGA REGION POINTER
CMP CL, 0X0 ;CONDITIONAL, IF FALSE SKIP INITIALIZATION
JNE PSR32BLOP0END ;END LOOP
MOV EBX, 0XB8000 ;INITIALIZING POINTER TO VGA MEMORY REGION
ADD CL, 0X1 ;ADD TO COUNTER
JMP PSR32BLOP0 ;LOOP
PSR32BLOP0END: ;END OF FUNCTION
PSR32BLOP1: ;USED TO INTIALIZE VGA MEMORY POINTER, NEWLINE OFFSET FROM 16-BIT LINES
CMP CH, 0X0 ;END CONDITIONAL
JE PSR32BLOP1END; ;JUMP TO END OF LOOP
ADD EBX, 0XA0 ;ADD EQUIVALENT OF ONE LINE TO POINTER
SUB CH, 0X1 ;LOOP END COUNTER
JMP PSR32BLOP1 ;LOOP
PSR32BLOP1END: ;USED TO INTIALIZE VGA MEMORY POINTER, END
MOV ESI, EDX ;LOAD LODSW STRING POINTER WITH APPROPIATE ADDRESS
MOV AH, 0X0F ;BLACK BACKGROUND, WHITE LETTERS
PSR32BLOP2: ;PRNTINH LOOP
LODSB ;LOAD CHARACTER INTO AL
CMP AL, 0X0 ;CHECK FOR END OF STRING
JE PSR32BLOP2END ;IF AX IS 0 JUMP TO END OF LOOP
MOV [EBX], AX ;LOAD WORD CHARACTER INTO VGA MEMORY
ADD EBX, 0X2 ;MOVE TO NEXT CHARACTER WORD IN MEMORY ADDRESS
JMP PSR32BLOP2 ;LOOP BACK TO START
PSR32BLOP2END: ;END OF LOOP
RET
ENDPSR32B: ;END OF FUNCTION, UNUSED
START32: ;START OF 32 BIT PROTECTED PROGRAM
MOV AX, DATASEG ;SET DATA SEGMENT ADDRESS TO POINTER
MOV DS, AX ;INITIALIZING SEGMENT REGISTERS
MOV SS, AX ;INITIALIZING SEGMENT REGISTERS
MOV [ES:DI], DL ;INITIALIZING SEGMENT REGISTERS
MOV DS, AX ;INITIALIZING SEGMENT REGISTERS
MOV GS, AX ;INITIALIZING SEGMENT REGISTERS
MOV EDX, S3 ;STRING POINTER DX
CALL PSR32B ;CALL PRINT ROUTINE// THIS IS A TEST
JMP $ ;LOOP TO INFINITY
PAD: ;BOOTSECTOR PADDING & MAGIC NUMBER
TIMES 510-($-$$) DB 0 ;FILL 0S TO END OF SECTOR
DW 0xAA55 ;BOOT SIGNATURE
编辑:在 start32 时将 CL 设置为 0:修复了问题
您的代码存在一些严重问题:
- 全部大写,难以阅读。
- 似乎对realmodesegment:offset addressing在实模式下的工作方式缺乏了解。
- 未初始化就使用的寄存器。
- 段寄存器设置不当。
执行类似操作的代码版本是下面的代码。大部分代码都有注释。需要了解的重要事项:
- 当 BIOS 打印字符时,它会更新 BIOS Data Area (BDA) 中的当前行和列。在保护模式下,您可以读取列的内存位置 0x450 和行的 0x451 中的字节。您可以使用此信息从 BIOS 停止的地方继续。
- 内存地址0x44a处的16位字是BIOS先前设置的当前视频模式的屏幕宽度。
- 屏幕上的每个单元格都是两个字节。当前显存中的字节偏移量可以计算为 0xb8000+(cur_row * screen_width + cur_col) * 2
- 包含一个 BIOS Parameter Block (BPB) 以允许在软盘 (FDD) 仿真模式下使用 USB 磁盘介质时正确加载图像。此代码为 1.44MiB 软盘提供 BPB。
- 要正确寻址所有内存,您应该 enable the 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
输出应该类似于:
我在裸硬件上用 x86 汇编语言编写了一小段代码 运行,此时,它甚至可以启用受保护的 32 位模式
我 运行 遇到了与屏幕打印有关的问题。我读过要不间断地这样做,可能会将字符加载到一个特殊的内存区域,即 RAM 地址 0xb8000。
知道了这一点,我写了一个函数来做这件事,并在 qemu 中测试时证明是成功的。但是,问题来了,当我尝试在真机(即 Lenovo G470 笔记本电脑)上 运行 这个程序时,它无法通过写入显示内存区域来显示我想要显示的字符串。使用 BIOS 中断显示的所有其他字符串都按预期工作,唉,32 位打印功能似乎没有做任何事情。虽然程序没有崩溃,但在使用中断打印的行之后出现了一个闪烁的光标。
如果它很重要,我正在尝试从 USB 驱动器启动它
更直白地说,字符串 S3 没有打印在我的笔记本电脑中,它在 qemu 中,我不知道为什么。
这是我的代码,我为缺乏技巧提前道歉:
[BITS 16] ;NASM 16-BIT MODE
[ORG 0x7C00] ;OFFSET MEMORY LOCATIONS TO BSECTOR/ BIOS LOADS MBR INTO THIS ADDRESS.
XOR AX, AX ;INITIALIZE SEGMENT REGISTERS BY AX
MOV DS, AX ;INDIRECTLY INITIALIZING DS REGISTER
MOV ES, AX ;INDIRECTLY INITIALIZING ES REGISTER
MOV GS, AX ;INDIRECTLY SETTING GS REGISTER
MOV FS, AX ;INDIRECTLY SETTING DS REGISTER
MOV SP, 0X900 ;SETTING STACK POINTER TO 0X900, OFFSET FROM 0X0 = 0X7C00
MOV BP, SP ;
JMP WORD 0x0:START16 ;JUMP TO START OF PROGRAM
PSR16B: ;16-BIT PRINT ROUTINE
MOV SI, BX ;ASSIGN SI POSITION OF STRING
MOV AH, 0XE ;TTY MODE
PSR16BLOP0: ;PRINT LOOP
LODSB ;LOAD SI INTO AL AND +1 ADDRESS OF STRING
CMP AL, 0X0 ;CONDITIONAL
JE PSR16LOP0END ;END LOOP
INT 0X10 ;BIOS INTERRUPT, PRINT AL TO SCREEN
JMP PSR16BLOP0 ;LOOP TO LOP
PSR16LOP0END: ;END OF LOOPA
MOV AL, 0XA ;NEW LINE ASCII
INT 0X10 ;RAISING PRINT BIOS INTERRUPT
MOV AL, 0XD ;NEWLINE 'CHARACTER' ASCII
INT 0X10 ;RAISING PRINT BIOS INTERRUPT
ADD CH, 0X1 ;ADD ONE TO COUNTER
RET ;RETURN TO LAST ADRESS BEFORE CALL
PSR16BEND: ;END OF FUNCTION, UNUSED
S0: ;STRING
DB 'BOOTING SEQUENCE INITIALIZED',0
S1: ;STRING
DB 'LOADING GDT',0
S2: ;STRING
DB 'ENTERING 32-BIT PROTECTED MODE',0
GDTS: ;START OF GLOBAL DESCRIPTOS TABLE
GDTN: ;NULL BEGGINING, 8 BYTES
DQ 0X0
GDTC: ;TABLE FOR CODE SEGMENT
DW 0XFFFF ;BITS 0-15 OF LIMIT(MAXADRESSUNIT) ||0X0-0XF
DW 0X0 ;BASE(SEGMENTSTART), BITS 0-15 ||0XF-0X1F
DB 0X0 ;BASE, BITS 16-23 ||0X20-0X27
DB 10011010B ;ACCESS BYTE ||0X28-0X2F
DB 11001111B ;SECOND4BITS:LIMITBITS 16-19/FIRST4BITS:FLAGS= ||0X30-0X37
DB 0X0 ;BASE, BITS 24-31||0X38-0X3F
GDTD: ;TABLE FOR DATA SEGMENT
DW 0XFFFF ;BITS 0-15 OF LIMIT(MAXADRESSUNIT) ||0X0-0XF
DW 0X0 ;BASE(SEGMENTSTART), BITS 0-15 ||0XF-0X1F
DB 0X0 ;BASE, BITS 16-23 ||0X20-0X27
DB 10010010B ;ACCESS BYTE ||0X28-0X2F
DB 11001111B ;SECOND4BITS:LIMITBITS 16-19/FIRST4BITS:FLAGS= ||0X30-0X37
DB 0X0 ;BASE, BITS 24-31||0X38-0X3F
GDTE: ;END OF GLOBAL DESCRIPTION TABLE
GDTDESC: ;GDT DESCRIPTOR
DW GDTE - GDTS - 1 ;SIZE OF GDT, 2 BYTE, MUST BE LESS THAN 1
DD GDTS ;ADRESS OF GDT, 4 BYTE
GDTDESCEND: ;END OF GDTDESC, UNUSED
CODESEG EQU GDTC - GDTS ;CODE SEGMENT ADDRESS CONSTANT
DATASEG EQU GDTD - GDTS ;DATA SEGMENT ADDRESS CONSTANT
START16: ;START OF BOOTSECTOR PROGRAM
MOV CH, 0X0 ;LINES COUNTER.
MOV BX, S0 ;SET STRING POINTER
CALL PSR16B ;CALL PRINT FUNCTION
MOV BX, S1 ;SET STRING POINTER
CALL PSR16B ;CALL PRINT FUNCTION
LGDT [GDTDESC] ;LOADING GDT DESCRIPTOR
MOV BX, S2 ;SET STRING POINTER
CALL PSR16B ;CALL PRINT FUNCTION
CLI ;SEVERING INTERRUPTS
MOV EAX, CR0 ;INDIRECTLY SETTING PROTECTED MODE BIT
OR EAX, 0X1 ;SETTING PMODE BIT
MOV CR0, EAX ;PROTECTED MODE ENABLED
JMP CODESEG:START32 ;FAR JUMP TO 32 BIT LAND
[BITS 32] ;NASM 32-BIT MODE
S3: ;STRING
DB '32-BIT PROTECTED MODE ENABLED', 0
PSR32B: ;PRINT TO DISPLAY ROUTINE FOR 32-BIT PREOTECTED MODE
PSR32BLOP0: ;INITIALIZE VGA REGION POINTER
CMP CL, 0X0 ;CONDITIONAL, IF FALSE SKIP INITIALIZATION
JNE PSR32BLOP0END ;END LOOP
MOV EBX, 0XB8000 ;INITIALIZING POINTER TO VGA MEMORY REGION
ADD CL, 0X1 ;ADD TO COUNTER
JMP PSR32BLOP0 ;LOOP
PSR32BLOP0END: ;END OF FUNCTION
PSR32BLOP1: ;USED TO INTIALIZE VGA MEMORY POINTER, NEWLINE OFFSET FROM 16-BIT LINES
CMP CH, 0X0 ;END CONDITIONAL
JE PSR32BLOP1END; ;JUMP TO END OF LOOP
ADD EBX, 0XA0 ;ADD EQUIVALENT OF ONE LINE TO POINTER
SUB CH, 0X1 ;LOOP END COUNTER
JMP PSR32BLOP1 ;LOOP
PSR32BLOP1END: ;USED TO INTIALIZE VGA MEMORY POINTER, END
MOV ESI, EDX ;LOAD LODSW STRING POINTER WITH APPROPIATE ADDRESS
MOV AH, 0X0F ;BLACK BACKGROUND, WHITE LETTERS
PSR32BLOP2: ;PRNTINH LOOP
LODSB ;LOAD CHARACTER INTO AL
CMP AL, 0X0 ;CHECK FOR END OF STRING
JE PSR32BLOP2END ;IF AX IS 0 JUMP TO END OF LOOP
MOV [EBX], AX ;LOAD WORD CHARACTER INTO VGA MEMORY
ADD EBX, 0X2 ;MOVE TO NEXT CHARACTER WORD IN MEMORY ADDRESS
JMP PSR32BLOP2 ;LOOP BACK TO START
PSR32BLOP2END: ;END OF LOOP
RET
ENDPSR32B: ;END OF FUNCTION, UNUSED
START32: ;START OF 32 BIT PROTECTED PROGRAM
MOV AX, DATASEG ;SET DATA SEGMENT ADDRESS TO POINTER
MOV DS, AX ;INITIALIZING SEGMENT REGISTERS
MOV SS, AX ;INITIALIZING SEGMENT REGISTERS
MOV [ES:DI], DL ;INITIALIZING SEGMENT REGISTERS
MOV DS, AX ;INITIALIZING SEGMENT REGISTERS
MOV GS, AX ;INITIALIZING SEGMENT REGISTERS
MOV EDX, S3 ;STRING POINTER DX
CALL PSR32B ;CALL PRINT ROUTINE// THIS IS A TEST
JMP $ ;LOOP TO INFINITY
PAD: ;BOOTSECTOR PADDING & MAGIC NUMBER
TIMES 510-($-$$) DB 0 ;FILL 0S TO END OF SECTOR
DW 0xAA55 ;BOOT SIGNATURE
编辑:在 start32 时将 CL 设置为 0:修复了问题
您的代码存在一些严重问题:
- 全部大写,难以阅读。
- 似乎对realmodesegment:offset addressing在实模式下的工作方式缺乏了解。
- 未初始化就使用的寄存器。
- 段寄存器设置不当。
执行类似操作的代码版本是下面的代码。大部分代码都有注释。需要了解的重要事项:
- 当 BIOS 打印字符时,它会更新 BIOS Data Area (BDA) 中的当前行和列。在保护模式下,您可以读取列的内存位置 0x450 和行的 0x451 中的字节。您可以使用此信息从 BIOS 停止的地方继续。
- 内存地址0x44a处的16位字是BIOS先前设置的当前视频模式的屏幕宽度。
- 屏幕上的每个单元格都是两个字节。当前显存中的字节偏移量可以计算为 0xb8000+(cur_row * screen_width + cur_col) * 2
- 包含一个 BIOS Parameter Block (BPB) 以允许在软盘 (FDD) 仿真模式下使用 USB 磁盘介质时正确加载图像。此代码为 1.44MiB 软盘提供 BPB。
- 要正确寻址所有内存,您应该 enable the 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
输出应该类似于: