程序集 32 位打印显示在 qemu 上运行的代码,无法在真实硬件上运行

标签 assembly x86 nasm qemu bootloader

我用 x86 汇编语言编写了一小段在裸硬件上运行的代码,此时,它已启用 protected 32 位模式

但是,我遇到了与打印到屏幕有关的问题。我读到,要在不中断的情况下执行此操作,可以将字符加载到特殊的内存区域,即 RAM 地址 0xb8000。

知道这一点后,我编写了一个函数来完成此任务,并且在 qemu 中测试时证明它是成功的。然而,问题来了,当我尝试在真机(即联想 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:解决了问题

最佳答案

您的代码存在许多严重问题:

  • 全部大写,难以阅读。
  • 似乎对实模式缺乏了解 segment: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

输出应该类似于:

enter image description here

关于程序集 32 位打印显示在 qemu 上运行的代码,无法在真实硬件上运行,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53861895/

相关文章:

assembly - PowerPC组件立即加载

assembly - 什么是大双字?

assembly - NEON 汇编手册/GNU 汇编器教程

assembly - 8086中段寄存器的值是多少?

c - 如何旋转 SSE/AVX vector

c - 是否可以从汇编文件中引用 C 枚举?

linux - x86 平台中的 KVM 影子页表处理

assembly - 为什么不将功能参数存储在XMM向量寄存器中?

assembly - NASM 与 GAS(实际差异)

assembly - 如何通过 x86 BIOS 调用移动光标?