【问题标题】:Print string using INT 0x10 in bootsector在引导扇区中使用 INT 0x10 打印字符串
【发布时间】:2015-06-14 19:07:19
【问题描述】:

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

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

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

这是我的代码

boot.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

【问题讨论】:

  • 真棒的问题,我没有太多的组装知识,但我希望我有(CompSci 学位)。
  • 您使用的是哪个汇编程序?您的代码不适用于 TASM,它看起来更像 NASM,但include 'sysf.asm' 也不适用于 NASM。
  • 抱歉我的错误我使用的是 FASM 而不是 TASM

标签: assembly x86 fasm


【解决方案1】:

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

ORG 0x7c00

start:
    push cs
    pop ds
    mov si, welcome

【讨论】:

  • 我试过了,但没用。我什么也没打印出来。 printl001的代码是在网上找到的,我不太了解
  • 这个例子假设加载器输入cs:ip等于0:7C00h。这个is not necessarily true 但是,cs 可能是 7C0h。
【解决方案2】:

根据documentation for 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

如果您的输出导致屏幕滚动,BPmight be destroyed,尽管它不会对您的代码造成问题,因为它保留了所有寄存器。

【讨论】:

    【解决方案3】:

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

    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
    

    【讨论】:

    • 为了效率,只能push bx/pop bx(比pusha/popa效率高很多),让函数覆盖AX。此外,inc bxadd bx, 1 更紧凑。您还可以通过将条件放在底部 (Why are loops always compiled into "do...while" style (tail jump)?) 将指令保存在循环内,尽管 int 0x10 比此循环体花费更多时间。尽管如此,如果您在底部使用无条件的jmp 加载 + test al,al / jnz loop,则总代码大小大致相等。
    • 一般建议在使用ah=0x0e/int 0x10之前设置BH和BL,以防代码在图形模式下与VGA一起使用。 (I can't print out a character in assembly 显示了一个以有用的方式设置它们的示例)。因此,您可以在 SI 而不是 BX 中传递指针(如果需要,可以启用 lodsb 作为代码大小优化,尽管在现代 CPU 上比 movzxinc si 慢。)
    • @PeterCordes 感谢您提供优化建议。您能否提供更多关于如何使用 BL 和 BH 进行 VGA 输出的信息?
    • 我不太了解过时的传统 BIOS 接口,尤其是结合使用 BIOS 视频模式设置调用 (int 10h/ah=00),这就是为什么我将 Sep 的答案链接到一个合理值的示例经过。有关 Ralf Brown 的中断列表条目,请参阅 ctyme.com/intr/rb-0106.htm,该条目涵盖了一些实际 BIOS 的一般情况和怪癖。
    • 另外,wallyk's answer on this same question 链接相同的文档并有一些描述。不确定 BH 页码在所有视频模式中是否真的很重要,或者是否可能取决于 BIOS。
    【解决方案4】:

    如果在你的 sysf.asm 中,你已经有一个单一的字符 print 例程,你为什么不在你的 printl 中调用它em> 例行公事?
    没关系,请继续阅读...

    编写引导加载程序

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

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

    使用 BIOS api

    BIOS.Teletype 函数需要以下参数:

    • BL图形颜色;这仅在显示器处于图形模式时使用
    • BH显示页;显示页数取决于视频模式
    • AL 字符代码;要显示的字符的 ASCII 码
    • AH函数号;数字 0Eh (0x0E)

    因为BLBH 寄存器是BX 寄存器的一部分,所以在编写这种PrintString 过程时,我们不应该使用BX 来寻址字符串! p>

    因为,在引导加载程序中,显示通常会在文本视频模式的显示页面 0,我们可以省略 BL GraphicsColor 参数,但我们仍应设置 BH DisplayPage。如果我们不这样做,那么这些角色可能会出现在任何其他显示页面上,甚至根本不会出现在任何地方。

    我想要什么

    我想创建 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
    

    请注意:在代码大小就是一切的一次性引导加载程序代码中,所有这些“削减”都很好。它不适用于您的日常编码。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-03-26
      • 2015-02-04
      • 2015-08-26
      • 2016-03-09
      • 1970-01-01
      • 2015-07-19
      • 2018-04-18
      • 1970-01-01
      相关资源
      最近更新 更多