我想创建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
答案 0 :(得分:2)
lodsb
指令加载DS和SI寄存器指向的字节,但是没有加载有效值。由于这是一个引导加载程序,您还需要使用ORG指令,否则汇编程序将不知道您编码的位置,因此welcome
会被加载到内存中。尝试将程序的开头更改为:
ORG 0x7c00
start:
push cs
pop ds
mov si, welcome
答案 1 :(得分: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
如果您的输出导致屏幕滚动BP
might be destroyed,但它不会导致代码出现问题,因为它会保留所有寄存器。
答案 2 :(得分:2)
我对此完全陌生,我不确定这是否是做到这一点的有效方法,但它对我有用
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
答案 3 :(得分:1)
如果在您的 sysf.asm 中,您已经有一个单字符 print 例程,为什么不在您的 printl中调用它? em> 例行公事?
这并不重要,所以请继续阅读...
BIOS 将您的引导加载程序加载到内存中的地址 7C00h,并且 BIOS 传递给您的代码的唯一寄存器是 DL
寄存器中的 BootDrive 编号。没有其他寄存器可以让您相信它可以保存您希望在那里找到的任何值!
现在,如果您不在引导加载程序代码中使用 ORG
指令,那么汇编程序 (FASM) 将得出您需要隐式 ORG 0
的结论。您需要相应地设置段寄存器。 PrintString 过程中的代码取决于 DS
段寄存器。它的正确值将是 07C0h。
但是,大多数人更愿意使用显式 ORG 7C00h
启动引导加载程序代码(如果只是为了使其可立即识别)。在这种情况下,DS
段寄存器需要加载 0000h。
BIOS.Teletype 函数需要以下参数:
BL
图形颜色;这仅在显示处于图形模式时使用BH
显示页面;显示页数取决于视频模式AL
字符代码;要显示的字符的ASCII码AH
函数编号;数字 0Eh (0x0E)因为 BL
和 BH
寄存器是 BX
寄存器的一部分,所以在编写这种 PrintString 时我们不应该使用 BX
来寻址字符串 程序!
并且因为,在引导加载程序中,显示通常会在文本视频模式的显示页面 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
请注意:在代码大小决定一切的一次性引导加载程序代码中,所有这些“削减”都很好。它并不意味着在您的日常编码中使用。