我想创建printl
函数,允许我在ax
寄存器中打印字符串。我处于 16 位实模式,找不到任何打印消息的方法。我用int 0x10
打印一封信。
我尝试在寄存器中传递参数(要打印bx
字符串),然后在循环中逐个字母打印,然后使用 popa
和 ret
返回。我的代码并没有真正起作用——要么它创建了一个无限循环,要么打印了一个奇怪的标志。
如果你知道更有效的方法,那么这不是问题。我还想问一下你的代码,如果你给了任何评论
这是我的代码
引导.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
请注意:所有这些"剃掉"在一次性引导加载程序代码中都很好,其中代码大小就是一切。它不应该在你的日常编码中使用。