Babystep2

来自osdev
Zhang3讨论 | 贡献2021年12月27日 (一) 02:13的版本 (创建页面,内容为“__NOTOC__ {{Infobox Tutorial | name=Babystep2: 使用BIOS打印消息 | prev=Babystep1 | next=Babystep3 }} === 使用BIOS打印消息 === 快速回顾: # BIOS加载的引导扇区为512字节 # 磁盘引导扇区中的代码由BIOS在0000:7c00加载 # 机器开始于 [http://www.osdev.org/wiki/Real_Mode Real Mode 实模式] # 请注意,除非你发出 CLI 汇编命令,否则CPU仍可接受中断 许多 (但不是全部) BIOS中断需要在DS…”)
(差异) ←上一版本 | 最后版本 (差异) | 下一版本→ (差异)
跳到导航 跳到搜索


Babystep2: 使用BIOS打印消息

Tutorial

上一个下一个
Babystep1Babystep3

使用BIOS打印消息

快速回顾:

  1. BIOS加载的引导扇区为512字节
  2. 磁盘引导扇区中的代码由BIOS在0000:7c00加载
  3. 机器开始于 Real Mode 实模式
  4. 请注意,除非你发出 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 - 否则将调用一些包含的其他过程。