0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ブートローダのORG0x7C00とは何か?BIOSを改造して実験

0
Last updated at Posted at 2026-08-15

目的

IBM PC互換BIOSでは、ブートセクタを物理アドレス0x7C00に配置するのが一般的な仕様となっています。
(あくまでも互換性の為にそういう取り決めがあるだけで、ハード的な決まりではありません。)

SeaBIOS

ディスク読み込み時は07c0:0000指定で、その後JMPする時はu16 bootip = (bootseg & 0x0fff) << 4で調整して0000:7c00指定になっていますが、最終的な物理アドレスは同じです。(何故こんな指定の仕方なのかは知りません)

src/boot.c

...省略...

/****************************************************************
 * Boot code (int 18/19)
 ****************************************************************/

// Jump to a bootup entry point.
static void
call_boot_entry(struct segoff_s bootsegip, u8 bootdrv)
{
    dprintf(1, "Booting from %04x:%04x\n", bootsegip.seg, bootsegip.offset);
    struct bregs br;
    memset(&br, 0, sizeof(br));
    br.flags = F_IF;
    br.code = bootsegip;
    // Set the magic number in ax and the boot drive in dl.
    br.dl = bootdrv;
    br.ax = 0xaa55;
    farcall16(&br);
}

...省略...

// Boot from a disk (either floppy or harddrive)
static void
boot_disk(u8 bootdrv, int checksig)
{
    u16 bootseg = 0x07c0;

    // Read sector
    struct bregs br;
    memset(&br, 0, sizeof(br));
    br.flags = F_IF;
    br.dl = bootdrv;
    br.es = bootseg;
    br.ah = 2;
    br.al = 1;
    br.cl = 1;
    call16_int(0x13, &br);

    if (br.flags & F_CF) {
        printf("Boot failed: could not read the boot disk\n\n");
        return;
    }

    if (checksig) {
        struct mbr_s *mbr = (void*)0;
        if (GET_FARVAR(bootseg, mbr->signature) != MBR_SIGNATURE) {
            printf("Boot failed: not a bootable disk\n\n");
            return;
        }
    }

    tpm_add_bcv(bootdrv, MAKE_FLATPTR(bootseg, 0), 512);

    /* Canonicalize bootseg:bootip */
    u16 bootip = (bootseg & 0x0fff) << 4;
    bootseg &= 0xf000;

    call_boot_entry(SEGOFF(bootseg, bootip), bootdrv);
}
...省略...

    switch (ie->type) {
    case IPL_TYPE_FLOPPY:
        printf("Booting from Floppy...\n");
        boot_disk(0x00, CheckFloppySig);
        break;
    case IPL_TYPE_HARDDISK:
        printf("Booting from Hard Disk...\n");
        boot_disk(0x80, 1);
        break;
    case IPL_TYPE_CDROM:
        boot_cdrom((void*)ie->vector);
        break;
        
...省略...

昔のIBM-BIOS

ブートローダを0x7c00へ読み込んでjmpから起動しています。

PCBIOS.ASM

...省略...
	ORG	7C00H
BOOT_LOCN	LABEL	FAR

...省略...

;------ MUST LOAD SYSTEM FROM DISKETTE -- CX HAS RETRY COUNT

	MOV	CX,4		; SET RETRY COUNT
H1:				; IPL_SYSTEM
	PUSH	CX		; SAVE RETRY COUNT
	MOV	AH,0		; RESET THE DISKETTE SYSTEM
	INT	13H		; DISKETTE_IO
	JC	H2		; IF ERROR, TRY AGAIN
	MOV	AH,2		; READ IN THE SINGLE SECTOR
	MOV	BX,0		; TO THE BOOT LOCATION
	MOV	ES,BX
	MOV	BX,OFFSET BOOT_LOCN
	MOV	DX,0		; DRIVE 0, HEAD 0
	MOV	CX,1		; SECTOR 1, TRACK 0
	MOV	AL,1		; READ ONE SECTOR
	INT	13H		; DISKETTE_IO
H2:	POP	CX		; RECOVER RETRY COUNT
	JNC	H4		; CF SET BY UNSUCCESSFUL READ
	LOOP	H1		; DO IT FOR RETRY TIMES

;------ UNABLE TO IPL FROM THE DISKETTE

H3:					; CASSETTE_JUMP:
	INT	18H			; USE INTERRUPT VECTOR TO GET TO BASIC

;------ IPL WAS SUCCESSFUL

H4:
	JMP	BOOT_LOCN
	
...省略...

NASMでブートローダを書く際に決まり文句の用にorg 0x7C00と書きますが、これは実際にはどのような作用があるのかを調べます。
今回はブートローダに絞って話を進めます。

絶対アドレスと相対アドレス

アセンブリを書いているとjmp,call,データ等、アドレスを指定する場面が出てきます。
アドレスで指定しますが、場面によって相対アドレスの場合と絶対アドレス指定に分かれます。

まずは試しにコードを書いて逆アセンブリして見てみます。

test.asm
bits 16
org 0x7C00

start:
    
    jmp next1
    nop ; 相対アドレスは次のバイトからの命令数なのでNOPを入れないと0となって分かり辛い
next1:
    
    jmp 0x0000:next2
next2:

    call next3
    
    cmp ax,0
    jne next4
    nop
next4:
    
    mov si, test_msg
    
hang:
    cli
    hlt
    jmp hang
    
    
next3:
    ret

test_msg db "abcdefgtest", 0x0D, 0x0A, 0

times 510-($-$$) db 0
dw 0xAA55

NASMでコンパイルし、逆アセンブルして生成される機械語を確認します。

test@test-fujitsu:~/kaihatsu$ nasm -f bin test.asm -o test.bin
test@test-fujitsu:~/kaihatsu$ ndisasm -b 16 test.bin
00000000  EB01              jmp short 0x3
00000002  90                nop
00000003  EA087C0000        jmp 0x0:0x7c08
00000008  E80D00            call 0x18
0000000B  83F800            cmp ax,byte +0x0
0000000E  7501              jnz 0x11
00000010  90                nop
00000011  BE197C            mov si,0x7c19
00000014  FA                cli
00000015  F4                hlt
00000016  EBFC              jmp short 0x14
00000018  C3                ret

結果は以下の様になりました。

jmp next1(IPのみ書き換え):相対アドレス
jmp 0x0000:next2(CS:IP両方書き換え):絶対アドレス
call 0x18:相対アドレス
jnz 0x11:相対アドレス
mov si,0x7c19(データの指定):絶対アドレス

普段NASMでコードを書いている時はラベルを使うので意識しませんが機械語単位で見ると、相対アドレス指定と絶対アドレス指定の場合があります。
絶対アドレスはORGで指定した0x7C00+オフセットとなっており、ORGNASMが絶対アドレスの計算をする為の基準となっていることが分かります。

ORGは必須か?

org 0x7C00は絶対に必須なのでしょうか。
基本BIOSはブートローダをメモリ0x7C00へ書き込むという規則の元に作られているので問題はありませんが、仮に別の領域に書き込むBIOSがあったらどうでしょうか。

ブートローダ内で読み込まれたアドレスを特定し、その値を基準に絶対アドレスを計算することが出来ます。
call 0xXXXXはIPのアドレスをスタックに積んだ上で指定したアドレスへ飛びます。擬似命令で書くとpush ip,jmp 0xXXXXのようになります。(逆にretpop,jmp)
よってcallを実行した後でpopをすることでメモリ上のどこに読み込まれていたとしても、アドレスを特定する事が出来ます。
詳しくは筆者の以下の記事を参照して下さい。
callがスタックに積んだ戻りアドレス→ 8086:jmp/call/intによるSPとスタックの変化
call,popで現在のアドレスを取り出す実験→ __x86.get_pc_thunk関数とは(OS開発用調査)
ブートローダ上からメモリのどこに展開されているか調べる実験→ DVD起動のブートセクタが何処に読み込まれるか検証

0x7C00の前提に依存しないブートローダ

次にORGを書かずどこに読み込まれても実行できるブートローダを書いてみます。(ブートローダと言っても実際に何かをロードする訳ではありません。)
絶対アドレスが必要な部分はその場で計算しています。

実行した様子

nasm -f bin test.asm -o test.bin
qemu-system-i386 -hda test.bin
截图 2026-08-15 13-53-36
test.asm
bits 16

start:
    cli

    ; スタックの設定
    xor ax, ax
    mov ss, ax
    mov sp, 0x9000

    ; CSの値を取り出す
    push cs
    pop ds

    ; 文字表示の為VRAM(ES : DI)の設定
    mov ax, 0xB800
    mov es, ax
    xor di, di
    
    ; ORGに依存しないJMP(CS:IP書き換え)をretfで実現
    ; retfはスタックから CS:IP を同時にpopして跳ぶ命令
    call get_ip
get_ip:
    pop ax
    add ax, next2 - get_ip   ; AX = next2 のオフセット

    push cs
    push ax                  ; IPへnext2を設定
    retf                     ; 現在の CS:next2 へ far return
next2:
    
    ; 文字列表示(POPで取り出したIPから文字列の絶対アドレスを計算)
    call get_msg
get_msg:
    pop si
    add si, msg - get_msg
    call print_string

    ; CS表示
    mov ax, cs
    call print_hex16

    mov al, ':'
    call putc

    ; IPの値を取り出す
    call here          ; CALL=PUSH IP + JMP
here:
    pop ax              ; AX = 現在IP

    call print_hex16

.hang:
    cli
    hlt
    jmp .hang

; =========================================
; putc
; AL = 文字
; ※diはこの関数内でしか使用していないので保存しない
;   呼ばれる度に増えていく
; =========================================
putc:
    mov ah, 0x0C ; 赤 
    mov [es:di], ax
    add di, 2
    ret

; =========================================
; print_hex16
; AXを16進4桁で表示
; =========================================
print_hex16:
    push ax
    push bx
    push cx
    push dx

    mov bx, ax
    mov cx, 4 ; 2バイトの16進数表記は4文字

.loop:
    rol bx, 4

    mov dl, bl
    and dl, 0x0F

    cmp dl, 9
    jbe .num

    add dl, 'A' - 10
    jmp .out

.num:
    add dl, '0'

.out:
    mov al, dl
    call putc

    loop .loop

    pop dx
    pop cx
    pop bx
    pop ax
    ret
    
; =========================================
; print_string
; DS:SI = 0終端文字列
; =========================================
print_string:
.loop:
    lodsb               ; AL = [DS:SI], SI++
    test al, al
    jz .done

    call putc
    jmp .loop

.done:
    ret
    
; =========================================
; 表示する文字列
; =========================================
msg db "CS:IP=", 0

times 510-($-$$) db 0
dw 0xAA55

逆アセンブルすると0x7C00基準の絶対アドレスはありません。

test@test-fujitsu:~/kaihatsu$ ndisasm -b 16 test.bin
00000000  FA                cli
00000001  31C0              xor ax,ax
00000003  8ED0              mov ss,ax
00000005  BC0090            mov sp,0x9000
00000008  0E                push cs
00000009  1F                pop ds
0000000A  B800B8            mov ax,0xb800
0000000D  8EC0              mov es,ax
0000000F  31FF              xor di,di
00000011  E80000            call 0x14
00000014  58                pop ax
00000015  83C007            add ax,byte +0x7
00000018  0E                push cs
00000019  50                push ax
0000001A  CB                retf
0000001B  E80000            call 0x1e
0000001E  5E                pop si
0000001F  83C65A            add si,byte +0x5a
00000022  E84800            call 0x6d
00000025  8CC8              mov ax,cs
00000027  E81900            call 0x43
0000002A  B03A              mov al,0x3a
0000002C  E80B00            call 0x3a
0000002F  E80000            call 0x32
00000032  58                pop ax
00000033  E80D00            call 0x43
00000036  FA                cli
00000037  F4                hlt
00000038  EBFC              jmp short 0x36
0000003A  B40C              mov ah,0xc
0000003C  268905            mov [es:di],ax
0000003F  83C702            add di,byte +0x2
00000042  C3                ret
00000043  50                push ax
00000044  53                push bx
00000045  51                push cx
00000046  52                push dx
00000047  89C3              mov bx,ax
00000049  B90400            mov cx,0x4
0000004C  C1C304            rol bx,byte 0x4
0000004F  88DA              mov dl,bl
00000051  80E20F            and dl,0xf
00000054  80FA09            cmp dl,0x9
00000057  7605              jna 0x5e
00000059  80C237            add dl,0x37
0000005C  EB03              jmp short 0x61
0000005E  80C230            add dl,0x30
00000061  88D0              mov al,dl
00000063  E8D4FF            call 0x3a
00000066  E2E4              loop 0x4c
00000068  5A                pop dx
00000069  59                pop cx
0000006A  5B                pop bx
0000006B  58                pop ax
0000006C  C3                ret
0000006D  AC                lodsb
0000006E  84C0              test al,al
00000070  7405              jz 0x77
00000072  E8C5FF            call 0x3a
00000075  EBF6              jmp short 0x6d
00000077  C3                ret
00000078  43                inc bx
00000079  53                push bx
0000007A  3A4950            cmp cl,[bx+di+0x50]
0000007D  3D0000            cmp ax,0x0

BIOSを改造し、0x7C00以外にブートローダを展開

実際にブートローダを0x7c00以外に読み込むBIOSは見つからないのでSeaBIOSを改造して0x8000へ読み込む様に改造します。

SeaBIOSの改造方法は以下の記事を参照して下さい。
SeaBIOSをソースからビルドしQEMUで動作

make
qemu-system-i386 -bios out/bios.bin -drive file=test.bin,format=raw 
src/boot.c
// Boot from a disk (either floppy or harddrive)
static void
boot_disk(u8 bootdrv, int checksig)
{
    //u16 bootseg = 0x07c0;
    u16 bootseg = 0x0800;   // <<<<<今回書き換えた箇所はここだけ

    // Read sector
    struct bregs br;
    memset(&br, 0, sizeof(br));
    br.flags = F_IF;
    br.dl = bootdrv;
    br.es = bootseg;
    br.ah = 2;
    br.al = 1;
    br.cl = 1;
    call16_int(0x13, &br);

    if (br.flags & F_CF) {
        printf("Boot failed: could not read the boot disk\n\n");
        return;
    }

    if (checksig) {
        struct mbr_s *mbr = (void*)0;
        if (GET_FARVAR(bootseg, mbr->signature) != MBR_SIGNATURE) {
            printf("Boot failed: not a bootable disk\n\n");
            return;
        }
    }

    tpm_add_bcv(bootdrv, MAKE_FLATPTR(bootseg, 0), 512);

    /* Canonicalize bootseg:bootip */
    u16 bootip = (bootseg & 0x0fff) << 4;
    bootseg &= 0xf000;

    call_boot_entry(SEGOFF(bootseg, bootip), bootdrv);
}

BIOSからブートローダを0x8000へ読み込んでも正しく動作しました。
截图 2026-08-15 13-51-24

8086互換CPUで確認(V30)

実機で上のブートローダを0x2000:0x0000へ読み込みます。
実機のBIOSはチップ上に焼き込まれていて変更できない為、ブートローダを2段階にし、
BIOS0x7C00ブートローダ前章で作成したブートローダのように起動します。
test.asm側から見ると、BIOSに呼ばれたのか、別のブートローダに呼ばれたのかは判別できません。

nasm -f bin boot.asm -o boot.bin
cat boot.bin test.bin > os.img
lsblk
sudo dd if=os.img of=/dev/sdb bs=512 count=2 conv=notrunc
微信图片_20260815164846_114_12

以下のブートローダを間にかませることで、SeaBIOSを改造したように、ブートローダの読み込み先を変更します。

boot.asm
org 0x7C00

start:
    ; セグメント初期化
    xor ax, ax
    mov ds, ax
    mov es, ax

    ; スタック設定
    mov ss, ax
    mov sp, 0x7C00

    ; ディスクから第2セクタを 0x2000:0x0000 に読み込む
    mov ax, 0x2000 ; 読み込み先アドレス = ES:BX
    mov es, ax
    mov bx, 0x0000

    mov ah, 0x02        ; INT 13h サービス 2: 読み込み
    mov al, 1           ; 読み込むセクタ数 = 1
    mov ch, 0           ; シリンダ = 0
    mov cl, 2           ; セクタ番号 = 2(1始まり)
    mov dh, 0           ; ヘッド = 0
    mov dl, 0x80           ; ドライブ番号
    int 0x13
    jc disk_error       ; エラー時に無限ループ

    ; 読み込んだコードへ跳ぶ(第2セクタ)
    jmp 0x2000:0x0000

disk_error:
    jmp $

times 510-($-$$) db 0
dw 0xAA55
0
1
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
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?