我如何编写一个在 NASM 16 位实模式下打印空终止字符串的函数?
How do i write a function that prints a null terminated string in NASM 16 bit real mode?
我有一个简单的程序,可以将一些以 null 结尾的字符串移动到 bx
寄存器:
[org 0x7c00] ; Tells the assembler where the code will be loaded
mov ah, 0x0e
mov bx, HELLO_MSG ; Moves string inside of HELLO_MSG to bx
call print_string ; Calls the print_string function inside of ./print_string.asm
mov bx, GOODBYE_MSG ; Moves string inside of GOODBYE_MSG to bx
call print_string
jmp $ ; Hangs
%include "print_string.asm"
HELLO_MSG:
db 'Hello, World!', 0
GOODBYE_MSG:
db 'Goodbye!', 0
times 510-($-$$) db 0
dw 0xaa55
和 ./print_string.asm
中的 print_string
函数:
print_string:
mov ah, 0x0e
int 0x10
ret
print_string 功能不起作用。根据我的理解,ah
存储了值 0x0e
,所以如果 al
的值是 X
而 int 0x10
是 运行,它告诉BIOS 在屏幕上显示 al
的值。我将如何为字符串复制它?
print_string:
mov ah, 0x0e
int 0x10
ret
您的 print_string 例程使用 BIOS.Teletype 函数 0Eh。此函数将显示保存在 AL
寄存器中的 单个字符 。由于此 BIOS 函数还要求您在 BH
中提供所需的 DisplayPage 并在 BL
中提供所需的 GraphicsColor(仅当显示器处于图形视频模式时),这可能不是最好的使用方法BX
注册为这个 print_string 例程的参数。
您的新例程将必须遍历字符串并对字符串中包含的每个字符使用单字符输出函数。因为您的字符串以零结尾,所以您一遇到那个零字节就停止循环。
[org 7C00h]
cld ; This makes sure that below LODSB works fine
mov si, HELLO_MSG
call print_string
mov si, GOODBYE_MSG
call print_string
jmp $
print_string:
push bx ; Preserve BX if you need to!
mov bx, 0007h ; DisplayPage BH=0, GraphicsColor BL=7 (White)
jmp .fetch
.print:
mov ah, 0Eh ; BIOS.Teletype
int 10h
.fetch:
lodsb ; Reads 1 character and also advances the pointer
test al, al ; Test if this is the terminating zero
jnz .print ; It's not, so go print the character
pop bx ; Restore BX
ret
这个问题已经很老了,但我使用的 document 我在网上找到的和你一样,所以我想我会分享我找到的解决方案。作者(英国伯明翰大学计算机科学学院的Nick Blundell)希望你使用寄存器bx来存储指向字符串开头的内存地址。然后在你打印一个值之后你递增 bx 等等直到零。这是我的解决方案。
[org 0x7c00]
mov bx, HELLO_MSG
call print_string_mem
mov bx, GOODBYE_MSG
call print_string_mem
jmp $ ; Hang
%include "print_string.asm"
; Data
HELLO_MSG:
db 'Hello, World!', 0
GOODBYE_MSG:
db 'Goodbye!', 0
times 510-($-$$) db 0
dw 0xaa55
print_string_mem:
jmp test_mem
test_mem:
mov al, [bx]
cmp al, 0
je end_mem
jmp print_mem
print_mem:
mov ah, 0x0e
int 0x10
add bx, 1
jmp test_mem
end_mem:
ret
ret
我上面的答案有效,更适合实际开发,但作者希望您使用在前几页中学到的工具来制定您的答案。我绞尽脑汁思考我到底应该如何得到这个答案,直到我阅读了他提到将 bx 设置为消息的内存地址的文档。希望这能帮助到我所在位置的人。
我几乎可以肯定你是在阅读 Nick Blundell 关于如何从头编写自己的 OS 的 this 文档的背景下问这个问题的,并试图找出问题 4第 21 页。
Sep Roland 的回答很好,但比作者试图通过练习教授的内容更高级。文本中的这一点尚未涵盖图形模式或 lodsb
和 test
指令。 Blundell 正在做一些更简单的事情,它试图利用你对迄今为止在阅读中学到的工具的理解:cmp
、jmp
、add
、标签和各种条件跳转(je
、jne
、等等...)。
我的解决方案如下所示,效果很好:
[org 0x7c00] ; tell NASM what address this will be loaded at
; execution starts here
mov ax, 0
mov ds, ax ; make segmentation agree with NASM about data addresses
; your code can start here, after the magic boilerplate
mov bx, HELLO_MSG
call print_string
mov bx, GOODBYE_MSG
call print_string
jmp $ ; infinite loop because there's nothing to exit to
print_string:
pusha ; preserve our general purpose registers on the stack
mov ah, 0x0e ; teletype function
.work:
mov al, [bx] ; move the value pointed at by bx to al
cmp al, 0 ; check for null termination
je .done ; jump to finish if null
int 0x10 ; fire our interrupt: int 10h / AH=0E
add bx, 1 ; increment bx pointer by one
jmp .work ; loop back
.done:
popa ; pop our preserved register values back from stack
ret
;; Data placed where execution won't fall into it
HELLO_MSG:
db `Hello World!\n\r`, 0 ; use backticks to allow C style escape
GOODBYE_MSG:
db 'Goodbye', 0
;; More boilerplate to make this a bootable MBR
times 510-($-$$) db 0 ; pad out to 510 bytes
dw 0xaa55 ; 2-byte signature so BIOS can recognize this as a bootable MBR
同样,这个答案显然可以做得更好 - 我只是按照作者最有可能希望您在这里学习的方式来回答这个问题。
我有一个简单的程序,可以将一些以 null 结尾的字符串移动到 bx
寄存器:
[org 0x7c00] ; Tells the assembler where the code will be loaded
mov ah, 0x0e
mov bx, HELLO_MSG ; Moves string inside of HELLO_MSG to bx
call print_string ; Calls the print_string function inside of ./print_string.asm
mov bx, GOODBYE_MSG ; Moves string inside of GOODBYE_MSG to bx
call print_string
jmp $ ; Hangs
%include "print_string.asm"
HELLO_MSG:
db 'Hello, World!', 0
GOODBYE_MSG:
db 'Goodbye!', 0
times 510-($-$$) db 0
dw 0xaa55
和 ./print_string.asm
中的 print_string
函数:
print_string:
mov ah, 0x0e
int 0x10
ret
print_string 功能不起作用。根据我的理解,ah
存储了值 0x0e
,所以如果 al
的值是 X
而 int 0x10
是 运行,它告诉BIOS 在屏幕上显示 al
的值。我将如何为字符串复制它?
print_string: mov ah, 0x0e int 0x10 ret
您的 print_string 例程使用 BIOS.Teletype 函数 0Eh。此函数将显示保存在 AL
寄存器中的 单个字符 。由于此 BIOS 函数还要求您在 BH
中提供所需的 DisplayPage 并在 BL
中提供所需的 GraphicsColor(仅当显示器处于图形视频模式时),这可能不是最好的使用方法BX
注册为这个 print_string 例程的参数。
您的新例程将必须遍历字符串并对字符串中包含的每个字符使用单字符输出函数。因为您的字符串以零结尾,所以您一遇到那个零字节就停止循环。
[org 7C00h]
cld ; This makes sure that below LODSB works fine
mov si, HELLO_MSG
call print_string
mov si, GOODBYE_MSG
call print_string
jmp $
print_string:
push bx ; Preserve BX if you need to!
mov bx, 0007h ; DisplayPage BH=0, GraphicsColor BL=7 (White)
jmp .fetch
.print:
mov ah, 0Eh ; BIOS.Teletype
int 10h
.fetch:
lodsb ; Reads 1 character and also advances the pointer
test al, al ; Test if this is the terminating zero
jnz .print ; It's not, so go print the character
pop bx ; Restore BX
ret
这个问题已经很老了,但我使用的 document 我在网上找到的和你一样,所以我想我会分享我找到的解决方案。作者(英国伯明翰大学计算机科学学院的Nick Blundell)希望你使用寄存器bx来存储指向字符串开头的内存地址。然后在你打印一个值之后你递增 bx 等等直到零。这是我的解决方案。
[org 0x7c00]
mov bx, HELLO_MSG
call print_string_mem
mov bx, GOODBYE_MSG
call print_string_mem
jmp $ ; Hang
%include "print_string.asm"
; Data
HELLO_MSG:
db 'Hello, World!', 0
GOODBYE_MSG:
db 'Goodbye!', 0
times 510-($-$$) db 0
dw 0xaa55
print_string_mem:
jmp test_mem
test_mem:
mov al, [bx]
cmp al, 0
je end_mem
jmp print_mem
print_mem:
mov ah, 0x0e
int 0x10
add bx, 1
jmp test_mem
end_mem:
ret
ret
我上面的答案有效,更适合实际开发,但作者希望您使用在前几页中学到的工具来制定您的答案。我绞尽脑汁思考我到底应该如何得到这个答案,直到我阅读了他提到将 bx 设置为消息的内存地址的文档。希望这能帮助到我所在位置的人。
我几乎可以肯定你是在阅读 Nick Blundell 关于如何从头编写自己的 OS 的 this 文档的背景下问这个问题的,并试图找出问题 4第 21 页。
Sep Roland 的回答很好,但比作者试图通过练习教授的内容更高级。文本中的这一点尚未涵盖图形模式或 lodsb
和 test
指令。 Blundell 正在做一些更简单的事情,它试图利用你对迄今为止在阅读中学到的工具的理解:cmp
、jmp
、add
、标签和各种条件跳转(je
、jne
、等等...)。
我的解决方案如下所示,效果很好:
[org 0x7c00] ; tell NASM what address this will be loaded at
; execution starts here
mov ax, 0
mov ds, ax ; make segmentation agree with NASM about data addresses
; your code can start here, after the magic boilerplate
mov bx, HELLO_MSG
call print_string
mov bx, GOODBYE_MSG
call print_string
jmp $ ; infinite loop because there's nothing to exit to
print_string:
pusha ; preserve our general purpose registers on the stack
mov ah, 0x0e ; teletype function
.work:
mov al, [bx] ; move the value pointed at by bx to al
cmp al, 0 ; check for null termination
je .done ; jump to finish if null
int 0x10 ; fire our interrupt: int 10h / AH=0E
add bx, 1 ; increment bx pointer by one
jmp .work ; loop back
.done:
popa ; pop our preserved register values back from stack
ret
;; Data placed where execution won't fall into it
HELLO_MSG:
db `Hello World!\n\r`, 0 ; use backticks to allow C style escape
GOODBYE_MSG:
db 'Goodbye', 0
;; More boilerplate to make this a bootable MBR
times 510-($-$$) db 0 ; pad out to 510 bytes
dw 0xaa55 ; 2-byte signature so BIOS can recognize this as a bootable MBR
同样,这个答案显然可以做得更好 - 我只是按照作者最有可能希望您在这里学习的方式来回答这个问题。