在引导扇区中使用 INT 0x10打印字符串



我想创建printl函数,允许我在ax寄存器中打印字符串。我处于 16 位实模式,找不到任何打印消息的方法。我用int 0x10打印一封信。

我尝试在寄存器中传递参数(要打印bx字符串),然后在循环中逐个字母打印,然后使用 poparet 返回。我的代码并没有真正起作用——要么它创建了一个无限循环,要么打印了一个奇怪的标志。

如果你知道更有效的方法,那么这不是问题。我还想问一下你的代码,如果你给了任何评论

这是我的代码

引导.asm:

start:
    mov bx, welcome    ;put argument to bx
    call printl        ;call printl function in sysf.asm
    hlt                ;halt cpu
welcome db 'Hello', 0
include 'sysf.asm'
times 510 - ($-$$) db 0
db 0x55
db 0xAA

sysf.asm:

;print function
; al is one letter argument (type Java:char)
;
print:
        pusha
        mov ah, 0x0e
        int 0x10
        popa
        ret              ; go back
;printl function
; bx is argument of type Java:String
;
printl:
        pusha
        jmp printl001
printl001:
        lodsb             ; I was working with si register but i would like to use bx register
        or al,al
        jz printl002
        mov ah, 0x0e
        int 0x10
        jmp printl001 
printl002:
        popa
        ret

lodsb 指令加载 DS 和 SI 寄存器指向的字节,但您尚未加载有效值。 由于这是一个引导加载程序,您还需要使用 ORG 指令,否则汇编程序将不知道您的代码以及welcome消息加载到内存中的位置。尝试将程序的开头更改为:

ORG 0x7c00
start:
    push cs
    pop ds
    mov si, welcome

根据 BIOS int 0x10 的文档:

电传打字输出:AH=0Eh,AL = 字符,BH = 页码,BL = 颜色(仅在图形模式下)

如果BH不为零,它将写入未显示的视频页面。 当然,除非您已翻转以显示BH中的任何页面。 您可能想要修改打印功能:

print:
        pusha
        mov ah, 0x0e
        xor bx, bx       ; BX = 0
        int 0x10
        popa
        ret              ; go back

如果输出导致屏幕滚动,则BP可能会被销毁,尽管它不会对代码造成问题,因为它会保留所有寄存器。

我对此完全陌生,我不确定这是否是执行此操作的有效方法,但它对我有用

print_string:
    pusha
    mov ah, 0x0e
    mov al, [bx]
    loop:
        cmp al, 0
        je break
        int 0x10
        add bx, 0x01
        mov al, [bx]
        jmp loop
    break:
        popa
        ret

如果在 sysf.asm 中,你已经有一个单字符打印例程,为什么不在 printl 例程中调用它?
这并不重要,所以请继续阅读...

编写引导加载程序

BIOS 将引导加载

程序加载到内存中,地址为 7C00h,BIOS 传递给代码的唯一寄存器是DL寄存器中的引导驱动器号。没有其他寄存器可以信任来保存您希望在那里找到的任何价值!

现在,如果您不在引导加载程序代码上使用 ORG 指令,那么汇编程序 (FASM) 将得出结论,您需要一个隐式ORG 0。您需要相应地设置段寄存器。打印字符串过程中的代码取决于DS段寄存器。其正确值为 07C0h.
但是,大多数人更喜欢使用显式ORG 7C00h启动引导加载程序代码(如果只是为了让它立即可识别)。在这种情况下,DS段寄存器需要加载0000h。

使用 BIOS API

生物简介。电传打字功能需要以下参数:

  • BL GraphicsColor;仅当显示器处于图形模式时才使用
  • BH显示页面;显示页面的数量取决于视频模式
  • AL字符代码;要显示的字符的 ASCII 代码
  • AH 函数编号;数字 0Eh (0x0E)

由于BL寄存器和BH寄存器是BX寄存器的一部分,因此在编写此类 PrintString 过程时,我们绝不应使用BX来寻址字符串!

而且,由于在引导加载程序中,显示器通常处于文本视频模式的显示页 0,因此我们可以省略 BL GraphicsColor 参数,但我们仍应设置 DisplayPage BH。如果我们不这样做,那么字符可能会登陆任何替代显示页面,甚至根本不会出现。

我想要什么

我想创建 printl 函数,允许我在ax寄存器中打印字符串。

printl这样的名字很难读!像printl001这样的标签真的伤害了我的眼睛!最好使用像PrintString这样的东西来传达它的目的是什么。

AX寄存器中传递参数没有任何好处。 SI通常是引导加载程序代码中的最佳选择,因为它有助于使用lodsb字符串原语指令。这可以减少代码的占用空间。但请注意,明智的做法是使用 cld 指令一次,以确保方向标志被重置,以便SI可以按照我们想要的方式递增。

        ORG     7C00h
        xor     ax, ax
        mov     ds, ax
        cld                 ; So `lodsb` will increment SI
        mov     si, welcome
        call    PrintString
        hlt
; --------------------------
; IN (si) OUT ()
PrintString:
        pusha               ; Preserving all registers
        mov     bh, 0       ; DisplayPage
        jmp     .While
.Do:    mov     ah, 0Eh     ; BIOS.Teletype
        int     10h
.While: lodsb
        test    al, al      ; Test for the end of the zero-terminated string
        jnz     .Do         ; Not yet
        popa
        ret
; --------------------------
welcome db 'Hello', 0
; --------------------------
times 510 - ($-$$) db 0
dw 0xAA55

由于此代码必须以 512 字节运行,因此使用 1 字节指令保留多个寄存器是有意义的 pusha/popa .但是,如果程序允许,不保留甚至会减少这 2 个字节。

下面是传递字符串的另一种方法,它允许不必显式指定字符串的地址,从而再减少 3 个字节。

        ORG     7C00h
        xor     ax, ax
        mov     ds, ax
        cld                 ; So `lodsb` will increment SI
        call    PrintString ; -> (AX BX SI)
        db      'Hello', 0
        hlt
; --------------------------
; IN () OUT () MOD (ax,bx,si)
PrintString:
        pop     si          ; -> SI is the address of the message 'Hello', 0
        mov     bh, 0       ; DisplayPage
        jmp     .While
.Do:    mov     ah, 0Eh     ; BIOS.Teletype
        int     10h
.While: lodsb
        test    al, al      ; Test for the end of the zero-terminated string
        jnz     .Do         ; Not yet
        push    si          ; SI holds the address where execution resumes (here its `hlt`)
        ret
; --------------------------
times 510 - ($-$$) db 0
dw 0xAA55

请注意:所有这些"剃掉"在一次性引导加载程序代码中都很好,其中代码大小就是一切。它不应该在你的日常编码中使用。

最新更新