LoginSignup
6
7

More than 5 years have passed since last update.

BIOS interrupt callを使う

Last updated at Posted at 2017-05-05

はじめに

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'を出力する例です。

boot.s
    .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をインストールします。

console
sudo apt-get install -y qemu

次のscript fileでboot.sをコンパイルしてboot sector imageを作成します。

mk.sh
#!/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を実行します。

console
$ qemu-system-x86_64 -hda boot.img -nographic
a

BIOS画面に'a'が表示されます。Ctl+a -> xでQEMUを終了します。

debug

gdbを使ったデバッグ方法を説明します。
次の.gdbinitを用意します。16bit codeを使用するためset architecture i8086を設定します。

.gdbinit
set architecture i8086
target remote localhost:1234
b *0x7c00
c
disas 0x7c00, +10

QEMUのオプションに-s -Sを追加します。

console
qemu-system-x86_64 -hda boot.img -nographic -s -S

gdbを実行します。

console
$ 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)の例です。

boot.s
    .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)を読み込む例です。

boot.s
    .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を発行します。

references

6
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
7