はじめに
BIOS interrupt callの使い方を説明します。
from Wikipedia
BIOS interrupt calls are a facility that operating systems and application programs use to invoke the facilities of the Basic Input/Output System on IBM PC compatible computers.
BIOSは入出力機能を提供します。BIOS Serviceといいます。
BIOS interrupt callを使うことでBIOS Serviceを呼び出します。
使い方
BIOS interrupt callの利用手順を説明します。
Serviceに対応したInterrupt vectorがあります。
利用者はInterrupt vectorを指定してSoftware interruptを発行します。
Interrupt vector | Service |
---|---|
0x10 | Video Services |
0x13 | Low Level Disk Services |
0x14 | Serial port services |
0x16 | Keyboard services |
Serviceには複数のfunctionがあります。function番号で区別します。
function番号は%ah registerに設定します。
例. Serial port servicesには次のfunctionがあります。
Transmit Characterを利用するときには%ah = 0x01を設定してint 0x14を発行します。
# | function |
---|---|
0x00 | Serial Port Initialization |
0x01 | Transmit Character |
0x02 | Receive Character |
0x03 | Status |
具体例を用いてBIOS interrupt callの使い方を説明します。
Serial Portに'a'を出力する例です。
.code16
main:
cli
mov $0x01, %ah # Transmit Character
mov $0x61, %al # Character 'a' = 0x61
mov $0x00, %dx # Serial port number (0=COM1, 1=COM2, 2=COM3, 3=COM4).
int $0x14 # Serial port services
hlt
上記内容を説明します。
- CPU起動直後はReal-address modeであり16bit codeのため".code16"を指定します。
- %ahに0x01を設定します。functionがTransmit Characterであることを示します。
- %alに送信データを設定します。'a'(0x61)を設定します。
- %dxにPort numberを設定します。COM1(0)を設定します。
- int 0x14でSerial port servicesを呼び出します。
- BIOS ServiceがSerial Port COM1に'a'を出力します。
他のService / functionについても上記例と類似の方法で
BIOS interrupt callを呼び出します。
PC boot sequence
BIOS interrupt callはCPU起動直後に利用します。
以降の説明のためここではPC boot sequenceを説明します。
次の順番で起動します。
- CPU reset
- Real-address modeで動作開始します。
- 0xFFFF0から処理を開始します。BIOSのプログラム(ROM)が配置されています。
- BIOSはPOST / デバイス初期化 を実行します。
- BIOSはboot deviceの先頭512byteをメモリ0x07C00に読み込みます。
- BIOSはIPを0x07C00に設定します。0x07C00から処理が開始します。
512byteのboot sectorにプログラムを記述してBIOS interrupt callを使います。
QEMU
QEMUを使って動作確認します。QEMUをインストールします。
sudo apt-get install -y qemu
次のscript fileでboot.sをコンパイルしてboot sector imageを作成します。
#!/bin/sh
as -o boot.o boot.s
objcopy -O binary boot.o boot_head.img
BOOT_SEC_SIZE=512
PROG_SIZE=`stat --printf="%s" boot_head.img`
FILL_SIZE=$(expr $BOOT_SEC_SIZE - $PROG_SIZE - 2)
dd if=/dev/zero of=zero.img bs=$FILL_SIZE count=1
cat boot_head.img zero.img > boot.img
/bin/echo -ne "\x55\xaa" >> boot.img
上記内容を説明します。
- boot.sをコンパイルしてboot.oを作成します。
- boot.oの命令を(elf形式ではなく)バイナリでboot_head.imgに出力します。
- (512 - プログラムのサイズ - 2) byteの値0のファイルzero.imgを作成します。
- boot_head.imgとzero.imgを結合してboot.imgを作成します。
- boot.imgの末尾に0x55 0xaaの2byteを付加します。boot sectorのmarkです。
QEMUを実行します。
$ qemu-system-x86_64 -hda boot.img -nographic
a
BIOS画面に'a'が表示されます。Ctl+a -> xでQEMUを終了します。
debug
gdbを使ったデバッグ方法を説明します。
次の.gdbinitを用意します。16bit codeを使用するためset architecture i8086を設定します。
set architecture i8086
target remote localhost:1234
b *0x7c00
c
disas 0x7c00, +10
QEMUのオプションに-s -Sを追加します。
qemu-system-x86_64 -hda boot.img -nographic -s -S
gdbを実行します。
$ gdb
GNU gdb (Ubuntu 7.7-0ubuntu3) 7.7
(snip)
The target architecture is assumed to be i8086
0x0000fff0 in ?? ()
Breakpoint 1 at 0x7c00
Breakpoint 1, 0x00007c00 in ?? ()
Dump of assembler code from 0x7c00 to 0x7c0a:
=> 0x00007c00: cli
0x00007c01: mov $0x1,%ah
0x00007c03: mov $0x61,%al
0x00007c05: mov $0x0,%dx
0x00007c08: int $0x14
End of assembler dump.
(gdb)
Serial port services(0x14)
Serialで受信したデータを送信する(echo)の例です。
.code16
cli
main:
call status
test $0x01, %ah # check 00000001b - Data ready.
jz main
call recv
call send
jmp main
hlt
send: # %al : send character
mov $0x01, %ah # 01h, indicating the Send Character Function.
mov $0x00, %dx # Serial port number (0=COM1, 1=COM2, 2=COM3, 3=COM4).
int $0x14 # Serial port services
ret
recv: # %al : received character, %ah : line status
mov $0x02, %ah # 02h, indicating the Receive Character Function.
mov $0x00, %dx # Serial port number (0=COM1, 1=COM2, 2=COM3, 3=COM4).
int $0x14 # Serial port services
ret
status:
mov $0x03, %ah # 03h, indicating the Read Serial Port Status Function.
mov $0x00, %dx # Serial port number (0=COM1, 1=COM2, 2=COM3, 3=COM4).
int $0x14 # Serial port services
ret
上記内容を説明します。
- statusで受信状態を確認します。受信できると%ahのbit0が1(Data ready)になります。
- Data readyであればrecvで受信します。
- sendで受信したデータを送信します。
- 上記を繰り返します。
Low Level Disk Services(0x13)
HDDから先頭の1 sector(512byte)を読み込む例です。
.code16
main:
cli
call read
hlt
read:
mov $0x02, %ah # 02h, indicating the Read Sectors Function.
mov $0x01, %al # Number of sectors.
mov $0x00, %ch # Bottom 8 bits of track number (0-based).
mov $0x01, %cl # ttssssss, as follows:
# tt = top two bits of 10-bit track number,
# ssssss = 6-bit sector number (1-based).
mov $0x00, %dh # Head number (0-based).
mov $0x80, %dl # Drive number. 0x80 : 1st hard disk.
xor %bx, %bx # ES:BX - Address of user buffer.
mov %bx, %es
mov $0x8c00, %bx
int $0x13
ret
上記内容を説明します。
- %ahに02を設定します。Read Sectors Functionを示します。
- 読み込むsector数を%alに設定します。
- %cl/%chに先頭sectorのtrack number / sector numberを設定します。
- %dhに先頭sectorのheader numberを設定します。
- %dlにDrive numberを設定します。0x80は1st HDDを示します。
- ES:BXに読み込み先メモリアドレスを設定します。ここでは0x8c00に読み込みます。
- int 0x13を発行します。