【问题标题】:Printing a string of characters from a boot sector only displays first character从引导扇区打印字符串仅显示第一个字符
【发布时间】:2016-04-25 17:58:46
【问题描述】:

作为学习操作系统开发的一部分,我正在 x86 引导扇区中开发代码。我希望我的代码将其打印到控制台:

你好

我得到了这个:

H

为什么只打印一个字符而不是整个字符串?我该如何解决这个问题?

这是我的代码的 sn-p:

mov ah, 0x0e
mov bx, string_p
add bx, 0x7c00
mov al, [bx]
int 0x10
 jmp $
string_p:
       db 'Hello',0
"then padding and magic number"

【问题讨论】:

  • 你在运行什么?
  • 我的 ubuntu 虚拟机中的 Bochs
  • Int 10h function 0Eh 是一个 MS-BIOS 函数,它将一个 single 字符写入终端。我从标签中看到您正在 Linux 上运行仿真。我建议您参考 MS-BIOS 和 MS-DOS 参考,例如 Ralf Brown,它解释了这些功能。警告:某些字符串输出函数需要$ 终止符而不是0
  • @MichaelPetch 谢谢我刚刚注意到“bootsector”。有 Int 10h 函数 13h 可能会这样做,但编写循环可能会更容易。
  • @Weathervane :我稍微改写了这个问题并添加了适当的标签。起初我也错过了标题中的引导扇区。 int 10h/ah=13h 是大多数 PC/AT 和基于克隆的系统或更高版本的合理替代方案。我会按照你的建议用一个循环来做,这样它就可以在古老的 8086/8088 PC 上使用。

标签: assembly bootloader bios bochs x86-16


【解决方案1】:

中断10H,将寄存器AH设置为0EHINT 10h/AH=0eh),将打印寄存器AL中的当前字符。 Ralf Brown 的Interrupt List 被认为是 DOS 和 BIOS 中断的圣经。它是关于哪些中断可用、它们如何工作以及它们的副作用的宝贵信息来源。

如果您使用 INT 10h/AH=0eh,您需要手动推进每个字符的字符串指针并一次打印出一个。这样的代码应该可以工作:

org 0x7c00             ; starting address
bits 16                ; 16-Bit mode

main:
  cli                  ; disable interrupts
  cld                  ; clear direction flags
  xor ax, ax           ; set AX to 0
  mov ds, ax           ; set DS to 0
  mov ah, 0x0e         ; call 0EH bios call
  mov si, string       ; move starting address of `string` into SI

loop:
  lodsb                ; load byte at DS into AL, update (increment) SI
  or al, al            ; check if AL is 0 (ORing will do nothing, but set the right flags
  jz hltloop           ; if zero jump to end
  int 0x10             ; do the print call
  jmp loop             ; jump back to loop start

hltloop:
  hlt                  ; halt and catch fire
  jmp hltloop          ; jump back to halt, if an interrupt occurred anyway

string:
       db 'Hello',0

times 510-($-$$) db 0
dw 0xAA55

本示例使用LODSB 指令读取字符串的每个字符。 LODS 指令记录为:

将源操作数中的字节、字或双字分别加载到AL、AX 或EAX 寄存器中。源操作数是一个内存位置,其地址从 DS:ESI 或 DS:SI 寄存器中读取(取决于指令的地址大小属性,32 或 16 ,分别)。 DS 段可能会被段覆盖前缀覆盖。

【讨论】:

  • 如果你打算使用hlt 而不是jmp $,我建议至少在它前面加上cli 来关闭中断。 hlt 只会进入暂停状态,直到下一次中断发生,然后继续执行内存中的任何内容。为了处理 NMI 的可能性,我通常这样做 cli hltloop: hlt jmp hltloop
  • 您不能保证 Direction Flag 已经为您清除,LODSB(和类似的)在指令之后使用它来增加或减少 SI。最好保存标志寄存器,然后CLD,之后恢复标志。或者将其保留为清除状态,即默认重置状态。
  • 您的代码还假定 DS 寄存器也已正确设置。除了 sn-p 中提到的填充和 0xaa55(签名)之外,不确定 OP 是否在他的示例中留下了任何代码。
  • 我是支持者。在问题范围之外,我可能会明确地将堆栈设置到已知位置。对于这里的琐碎代码,一个人不会被打扰(但无论如何这样做是个好主意)。如果需要更多信息,我之前分享了一些General Bootloader Tips
  • 嗯,是的,但是,不,但是,您在将ah 加载到 BIOS 功能号之后 执行了有问题的 ds 的事情。无论文档如何,我都会在int 10h 之前立即加载ah。我想@MichaelPetch 的意思是cli 紧接在hlt 之前。但是+1。
【解决方案2】:

这已经晚了,但可能会对某人有所帮助。我在 ubuntu 上开发操作系统时遇到了同样的问题。这对我有用。我创建了一个打印函数并在将我的字符串地址移动到 bx 后调用它:

print_function:
pusha
mov ah, 0x0e
mov al, [bx]
int 0x10
cmp al, 0
jne increment
jmp the_end

increment:  
add bx , 1
mov al, [bx]    
int 0x10
cmp al, 0
jne increment
jmp the_end 

the_end:    
popa    
ret

【讨论】:

  • 这段代码有两个问题: 通过正确跳转,后续的 int 10h 调用可以折叠到第一个调用中。这会显示终止 NUL 字符,这可能是不想要的。 (虽然它可能看起来不可见或像空白一样。)
猜你喜欢
  • 1970-01-01
  • 2013-02-08
  • 2021-05-11
  • 1970-01-01
  • 2017-11-23
  • 1970-01-01
  • 1970-01-01
  • 2015-08-26
  • 1970-01-01
相关资源
最近更新 更多