在引导扇区中使用 INT 0x10 打印字符串
Print string using INT 0x10 in bootsector
我想创建 printl
函数,允许我在 ax
寄存器中打印字符串。我在 16 位实模式下,找不到任何打印消息的方法。我使用 int 0x10
打印单个字母。
我尝试在 bx
寄存器中传递参数(要打印的字符串),然后在循环中逐个字母打印,然后返回使用 popa
和 ret
。我的代码并没有真正起作用——它要么创建了一个无限循环,要么打印了一个奇怪的符号。
如果您知道更有效的方法,那么这不是问题。我还想问一下评论你的代码,如果你给了
这是我的代码
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
lodsb
指令加载 DS 和 SI 寄存器指向的字节,但您还没有加载有效值。由于这是一个引导加载程序,您还需要使用 ORG 指令,否则汇编程序将不知道您的代码在哪里,因此 welcome
消息将被加载到内存中。尝试将程序的开头更改为:
ORG 0x7c00
start:
push cs
pop ds
mov si, welcome
根据documentation for BIOS int 0x10:
Teletype output: AH=0Eh, AL = Character, BH = Page Number, BL = Color (only in graphic mode)
如果BH
不为零,则写入不显示的视频页面。当然,除非您已翻转以显示 BH
中的任何页面。可能你会想要修改你的打印功能:
print:
pusha
mov ah, 0x0e
xor bx, bx ; BX = 0
int 0x10
popa
ret ; go back
如果您的输出导致屏幕滚动,BP
might be destroyed,尽管它不会对您的代码造成问题,因为它保留了所有寄存器。
我对此完全陌生,我不确定这是否是执行此操作的有效方法,但它对我有用
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 中你已经有一个字符 print 例程,你为什么不调用它来自你的 printl 例程?
这并不重要,所以请继续阅读...
编写引导加载程序
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)
因为BL
和BH
寄存器是BX
寄存器的一部分,所以在写这种PrintString 程序!
并且因为在引导加载程序中,显示器通常处于文本视频模式的显示页 0,我们可以省略 BL
GraphicsColor 参数,但我们仍应设置 BH
显示页面。如果我们不这样做,那么这些字符可能会出现在任何替代显示页面上,甚至根本不会出现在任何地方。
我想要什么
I want to create printl function that allow me to print string in the ax
register.
像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
请注意:在代码大小就是一切的一次性引导加载程序代码中,所有这些“剃掉”都很好。它不适用于您的日常编码。
我想创建 printl
函数,允许我在 ax
寄存器中打印字符串。我在 16 位实模式下,找不到任何打印消息的方法。我使用 int 0x10
打印单个字母。
我尝试在 bx
寄存器中传递参数(要打印的字符串),然后在循环中逐个字母打印,然后返回使用 popa
和 ret
。我的代码并没有真正起作用——它要么创建了一个无限循环,要么打印了一个奇怪的符号。
如果您知道更有效的方法,那么这不是问题。我还想问一下评论你的代码,如果你给了
这是我的代码
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
lodsb
指令加载 DS 和 SI 寄存器指向的字节,但您还没有加载有效值。由于这是一个引导加载程序,您还需要使用 ORG 指令,否则汇编程序将不知道您的代码在哪里,因此 welcome
消息将被加载到内存中。尝试将程序的开头更改为:
ORG 0x7c00
start:
push cs
pop ds
mov si, welcome
根据documentation for BIOS int 0x10:
Teletype output: AH=0Eh, AL = Character, BH = Page Number, BL = Color (only in graphic mode)
如果BH
不为零,则写入不显示的视频页面。当然,除非您已翻转以显示 BH
中的任何页面。可能你会想要修改你的打印功能:
print:
pusha
mov ah, 0x0e
xor bx, bx ; BX = 0
int 0x10
popa
ret ; go back
如果您的输出导致屏幕滚动,BP
might be destroyed,尽管它不会对您的代码造成问题,因为它保留了所有寄存器。
我对此完全陌生,我不确定这是否是执行此操作的有效方法,但它对我有用
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 中你已经有一个字符 print 例程,你为什么不调用它来自你的 printl 例程?
这并不重要,所以请继续阅读...
编写引导加载程序
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)
因为BL
和BH
寄存器是BX
寄存器的一部分,所以在写这种PrintString 程序!
并且因为在引导加载程序中,显示器通常处于文本视频模式的显示页 0,我们可以省略 BL
GraphicsColor 参数,但我们仍应设置 BH
显示页面。如果我们不这样做,那么这些字符可能会出现在任何替代显示页面上,甚至根本不会出现在任何地方。
我想要什么
I want to create printl function that allow me to print string in the
ax
register.
像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
请注意:在代码大小就是一切的一次性引导加载程序代码中,所有这些“剃掉”都很好。它不适用于您的日常编码。