Babystep2
Babystep2: 使用BIOS打印消息 | |
Tutorial | |
上一个 | 下一个 |
Babystep1 | Babystep3 |
使用BIOS打印消息
快速回顾:
- BIOS加载的引导扇区为512字节
- 磁盘引导扇区中的代码由BIOS在0000:7c00加载
- 机器开始于 Real Mode 实模式
- 请注意,除非你发出 CLI 汇编命令,否则CPU仍可接受中断
许多 (但不是全部) BIOS中断需要在DS寄存器填充Real Mode(实模式)段值。(译者注:这里的BIOS中断是指调用BIOS功能的INT指令) 这就是许多BIOS中断在保护模式下不起作用的原因。 因此,如果你想使用int 10h/ah=0eh打印字符到屏幕上,那么你需要确保要打印的字符的seg:offset(段偏移)是正确的。
在实模式下,地址计算为 segment段 * 16 + offset偏移量。 由于偏移量可以远大于16(译者注:而使计算后的内存地址偏移到其它段中去), 所以可以有许多对的段和偏移量指向相同的地址。 例如,可以说引导加载程序在0000:7C00加载,而也可以说位置是在07C0:0000。 因为这两个实际上是同一个地址: 16 * 0x0000 + 0x7C00 = 16 * 0x07C0 + 0x0000 = 0x7C00。
无论你使用0000:7c00还是07c0:0000都是一样的, 但是,如果你使用ORG指令(译者注:这是一条伪指令,若有ORG伪指令,编译器则把其后的指令代码放到ORG伪指令指定的偏移地址。),则需要了解正在发生的事情。 默认情况下,原始二进制文件的起始位置为偏移量0,但是如果需要,可以将偏移量更改为不同的内容并使其正常工作。 例如,以下代码段访问具有段0x7C0的变量msg。
Asm示例:
; boot.asm
mov ax, 0x07c0
mov ds, ax
mov si, msg
cld
ch_loop:lodsb
or al, al ; zero=end of str
jz hang ; get out
mov ah, 0x0E
mov bh, 0
int 0x10
jmp ch_loop
hang:
jmp hang
msg db 'Hello World', 13, 10, 0
times 510-($-$$) db 0
db 0x55
db 0xAA
以下是用ORG伪指令的版本。 这次,使用segment段基址0访问msg。(译者注:因为机器指令被载入的位置已经被ORG指令改变了) 请注意,你仍然需要设置DS的值,因为它开始可能会被初始化为任何值。
[ORG 0x7c00]
xor ax, ax ; make it zero
mov ds, ax
mov si, msg
cld
ch_loop:lodsb
or al, al ; zero=end of string
jz hang ; get out
mov ah, 0x0E
mov bh, 0
int 0x10
jmp ch_loop
hang:
jmp hang
msg db 'Hello World', 13, 10, 0
times 510-($-$$) db 0
db 0x55
db 0xAA
Procedures过程
为了保护写入空间,通常使用CALL/RET将传统上的 “过程” 与代码分开,如下所示:
[ORG 0x7c00]
xor ax, ax ;make it zero
mov ds, ax
cld
mov si, msg
call bios_print
hang:
jmp hang
msg db 'Hello World', 13, 10, 0
bios_print:
lodsb
or al, al ;zero=end of str
jz done ;get out
mov ah, 0x0E
mov bh, 0
int 0x10
jmp bios_print
done:
ret
times 510-($-$$) db 0
db 0x55
db 0xAA
出于某种莫名其妙的原因,加载SI(译者注:lodsb指令做的事情) 然后 跳到过程总是困扰着我。 幸运的是,对于像我这样的精神病,NASM的宏让我可以使用一种假装正在传递参数的写法 (调用前必须先定义宏)。
%macro BiosPrint 1
mov si, word %1
ch_loop:lodsb
or al, al
jz done
mov ah, 0x0E
int 0x10
jmp ch_loop
done:
%endmacro
[ORG 0x7c00]
xor ax, ax
mov ds, ax
cld
BiosPrint msg
hang:
jmp hang
msg db 'Hello World', 13, 10, 0
times 510-($-$$) db 0
db 0x55
db 0xAA
如果你的代码变得很长且不可读,则可以将其分解为多个文件, 然后在主代码的开头包含文件。就像这样:
jmp main
%include "othercode.inc"
main:
; ... rest of code here
不要忘记写开头的jmp main - 否则将调用一些包含的其他过程。