汇编32位打印以显示代码在qemu上运行,无法在实际硬件上运行 [英] Assembly 32-bit print to display code runs on qemu, fails to work on real hardware
问题描述
我已经用x86汇编语言编写了一小段代码,该代码在裸机上运行,目前,它可以启用受保护的32位模式
I've written a small piece of code in x86 assembly language that runs on bare hardware which, at this point, goes as far as enabling protected 32-bit mode
但是,我遇到了与屏幕打印有关的问题.我读过要做到这一点而不会中断,可能会将字符加载到特殊的内存区域中,即RAM地址0xb8000.
I've run into a problem, however, pertaining printing to the screen. I've read that to do so without interrupts one may load characters into a special memory region, namely RAM address 0xb8000.
知道这一点后,我编写了一个函数来完成该任务,并且在qemu中进行测试时证明是成功的.但是,问题来了,当我尝试在真实计算机(即Lenovo G470笔记本电脑)上运行此程序时,它无法通过写入显示内存区域来显示想要显示的字符串.使用BIOS中断显示的所有其他字符串均按预期工作,可惜32位打印功能似乎无能为力.但是该程序不会崩溃,并且在使用中断打印的行之后会出现一个闪烁的光标.
Knowing this, I wrote a function that does exactly that, and it proved a success when tested in qemu. However, and here comes the problem, when I attempt to run this program on a real machine (namely a Lenovo G470 laptop) it fails to display the string I want shown by writing to the display memory region. All other strings displayed using BIOS interrupts work as intended, alas, the 32-bit printing function does not seem to do anything. The program doesn't crash though, and a blinking cursor appears after the lines printed using interrupts.
如果有任何重要意义,我正在尝试从USB驱动器启动它
If it's of any importance, I'm trying to boot this off a USB drive
坦率地说,字符串S3不在笔记本电脑中打印,而是在qemu中打印,我不知道为什么.
Put much more bluntly, string S3 isn't printing in my laptop, it is in qemu and I've no idea why.
这是我的代码,由于缺乏技巧,我向您致歉:
Here is my code, I apologize in advance for the lack of finesse:
[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:解决了问题
Setting CL to 0 at start32: fixed the problem
推荐答案
您的代码有很多严重的问题:
Your code has a number of serious problems:
- 全部为大写且难以阅读.
- 似乎缺少对实模式 segment:offset寻址在实模式下工作.
- 未初始化就使用的寄存器.
- 不正确地设置段寄存器.
- It is all upper case and hard to read.
- There seems to be lack of understanding of how realmode segment:offset addressing works in real mode.
- Registers used without being initialized.
- Improperly setting segment registers.
执行类似操作的代码版本如下.该代码大部分被注释.重要事项:
A version of the code that does something similar is the code below. The code is commented for the most part. Important things to understand:
- BIOS打印字符时,它会更新 BIOS数据区(BDA).在保护模式下,您可以读取存储位置0x450的列(字节)和0x451(行)的字节.您可以使用此信息在BIOS中断处继续操作.
- 内存地址0x44a的16位字是BIOS先前设置的当前视频模式的屏幕宽度.
- 屏幕上的每个单元格为两个字节.视频内存中的当前字节偏移可以计算为0xb8000 +(cur_row * screen_width + cur_col)* 2
- 包括 BIOS参数块(BPB),以便在以下情况下正确加载映像在软盘(FDD)仿真模式下使用USB磁盘介质.该代码为1.44MiB软盘提供了BPB.
- 要正确处理所有内存,您应该启用A20线.提供的代码使用快速启用方法,但可能不与所有硬件兼容,但应可在大多数仿真器上使用.
- 使用
print_string_pm
打印字符串时,将字符串放置在显示存储器中后,硬件光标位置将用set_cursor
更新.
- When the BIOS prints characters it updates the current row and column in the BIOS Data Area (BDA). When in protected mode you can read the byte in memory location 0x450 for the column and 0x451 for the row. You can use this information to continue where the BIOS left off.
- The 16-bit word at memory address 0x44a is the screen width of the current video mode previously set by the BIOS.
- Each cell on the screen is two bytes. The current byte offset in video memory can be computed as 0xb8000+(cur_row * screen_width + cur_col) * 2
- Include a BIOS Parameter Block (BPB) to allow the image to be properly loaded when using USB disk media in floppy disk (FDD) emulation mode. This code provides a BPB for a 1.44MiB floppy.
- To properly address all memory you should enable the A20 line.The code provided uses the fast enable method, but may not be compatible with all hardware but should work on most emulators.
- When a string is printed using
print_string_pm
the hardware cursor position is updated withset_cursor
after the string is placed in display memory.
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软盘映像中:
This code can be assembled and built into a 1.44MiB floppy disk image with these commands:
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
输出应类似于:
The output should look something like:
这篇关于汇编32位打印以显示代码在qemu上运行,无法在实际硬件上运行的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!