Сборка 32-битной печати для отображения кода запускается на qemu, не работает на реальном оборудовании
Я написал небольшой фрагмент кода на языке ассемблера x86, который работает на "голом" оборудовании, который на данный момент выходит за рамки включения защищенного 32-разрядного режима.
Однако я столкнулся с проблемой печати на экране. Я читал, что для этого без прерываний можно загружать символы в специальную область памяти, а именно адрес ОЗУ 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
РЕДАКТИРОВАТЬ: Установка CL в 0 при запуске 32: исправлена проблема
1 ответ
Ваш код имеет ряд серьезных проблем:
- Это все в верхнем регистре и трудно читать.
- Кажется, нет понимания того, как работает сегмент реальной моды : смещение адресации работает в реальном режиме.
- Регистры используются без инициализации.
- Неправильная настройка сегментных регистров.
Версия кода, которая делает что-то подобное, является кодом ниже. Код комментируется по большей части. Важные вещи, чтобы понять:
- Когда BIOS печатает символы, он обновляет текущую строку и столбец в области данных BIOS (BDA). В защищенном режиме вы можете прочитать байт в ячейке памяти 0x450 для столбца и 0x451 для строки. Вы можете использовать эту информацию, чтобы продолжить с того места, где остановился BIOS.
- 16-разрядное слово по адресу памяти 0x44a - это ширина экрана текущего режима видео, предварительно установленного BIOS.
- Каждая ячейка на экране составляет два байта. Текущее смещение байта в видеопамяти может быть вычислено как 0xb8000+(cur_row * screen_width + cur_col) * 2
- Включите блок параметров BIOS (BPB), чтобы обеспечить правильную загрузку образа при использовании USB-носителя в режиме эмуляции дискеты (FDD). Этот код обеспечивает BPB для дискеты 1,44 МБ.
- Для правильного обращения ко всей памяти вы должны активировать линию A20. Предоставленный код использует метод быстрого включения, но может быть несовместим со всем оборудованием, но должен работать на большинстве эмуляторов.
- Когда строка печатается с использованием
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
Вывод должен выглядеть примерно так: