0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【OS自作の第一歩】16bit ブートローダから 32bit プロテクトモードへ移行し、C 関数を呼び出すまで

0
Last updated at Posted at 2025-12-11

目的

これまでベアメタル環境では、全てアセンブリだけでプログラムを書いてきました。
しかし、今後更に多くの実験を行うにはアセンブリだけでは負担が大きくなってきます。
そこで本記事では、ベアメタル環境でC言語を使えるようにする手順をまとめます。

今回はブートローダ → 32bit プロテクトモードへ移行 → C 言語の関数呼び出しまでを実装します。
Hello from C! が出力されれば成功です。

コード

boot.asm
[org 0x7C00]
bits 16

start:
    cli
    xor ax, ax
    mov ds, ax
    mov es, ax
    mov ss, ax
    mov sp, 0x7C00
    sti

    ; --- INT 13h CHS 読み込み ---
    mov ah, 0x02         ; BIOS: Read Sectors
    mov al, 1            ; 読み込むセクタ数 (1)
    mov ch, 0            ; シリンダ = 0
    mov dh, 0            ; ヘッド = 0
    mov cl, 2            ; セクタ = 2 (ブートローダの次)
    mov dl, 0x80         ; ドライブ番号=HDD
    mov bx, 0x7E00       ; ES:BX = 読み込み先
    int 0x13
    jc disk_error
    
    jmp 0x0000:0x7E00    ; 読み込んだコードへジャンプ

disk_error:
    hlt
    jmp disk_error

times 510-($-$$) db 0
dw 0xAA55
test.asm
bits 16
global start
extern kernel_main


start:
    cli
    xor ax, ax
    mov ds, ax
    mov es, ax
    mov ss, ax
    mov sp, 0x7C00
    
    mov ax, 0x0003
    int 0x10 

    ; VGAリアルモード表示
    mov ax, 0xB800
    mov es, ax

    ; GDTロード
    lgdt [gdt_descriptor]

    ; プロテクトモード有効化
    mov eax, cr0
    or eax, 1
    mov cr0, eax

    ; 32ビットコードへ移行
    jmp 0x08:pm_start

; -----------------------------
gdt_start:
    dq 0x0000000000000000            ; NULL

gdt_code:
    dw 0xFFFF                        ; limit 0-15
    dw 0x0000                        ; base 0-15
    db 0x00                          ; base 16-23
    db 0x9A                          ; code segment
    db 0xCF                          ; flags
    db 0x00                          ; base 24-31

gdt_data:
    dw 0xFFFF
    dw 0x0000
    db 0x00
    db 0x92                          ; data segment
    db 0xCF
    db 0x00
gdt_end:

gdt_descriptor:
    dw gdt_end - gdt_start - 1
    dd gdt_start

; -----------------------------
[bits 32]
pm_start:
    ; データセグメント設定
    mov ax, 0x10
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    mov ss, ax
    mov esp, 0x9FC00
    
    call kernel_main

.hlt_loop:
    hlt
    jmp .hlt_loop

kernel.c
void putc(char c) {
    static int pos = 0;
    volatile unsigned short* vram = (unsigned short*)0xB8000;
    vram[pos++] = 0x0F00 | c; //属性 背景色 = 0x0 = 黒 文字色 = 0xF = 白
}

void puts(const char* s) {
    while (*s) putc(*s++);
}

void kernel_main() {
    puts("Hello from C!");

    while(1);
}
linker.ld
ENTRY(start)

SECTIONS {
    . = 0x7E00;

    .text : {
        *(.text*)
    }

    .rodata : {
        *(.rodata*)
    }

    .data : {
        *(.data*)
    }

    .bss : {
        *(.bss*)
        *(COMMON)
    }
}

コンパイル及び実行方法

nasm -f bin boot.asm -o boot.bin

gcc -m32 -ffreestanding -c kernel.c -o kernel.o
nasm -f elf32 test.asm -o test.o
ld -m elf_i386 -T linker.ld -o kernel.elf test.o kernel.o
objcopy -O binary kernel.elf kernel.bin
cat boot.bin kernel.bin > disk.img

結合の処理

extern kernel_mainの意味

kernel.ckernel.o 中に kernel_mainというラベルがある前提。

ldtest.okernel.o を結合するので
test.asmcall kernel_main が C の関数と正しく繋がる。

linker.ldの意味

test.asm + kernel.c0x7E00 に配置される前提でリンクする。

test.asmstart = 0x7E00
pm_start0x7E00 からの相対位置
kernel_main0x7E00 からの相対位置
として 絶対アドレスが決まる。

実行

QEMU

qemu-system-i386 -hda disk.img

图片.png

ThinkPad X280

lsblk
sudo dd if=disk.img of=/dev/sdb bs=512 count=2 conv=notrunc

USBから起動しても正常に動作した。
05281dbafc183.jpg
f7a1d1a6ee9188.jpg

kernel.cをコンパイルして得られるアセンブリを確認する

gcc -m32 -ffreestanding -S -masm=intel kernel.c -o kernel.s
kernel.s
	.file	"kernel.c"
	.intel_syntax noprefix
	.text
	.globl	putc
	.type	putc, @function
putc:
.LFB0:
	.cfi_startproc
	push	ebp
	.cfi_def_cfa_offset 8
	.cfi_offset 5, -8
	mov	ebp, esp
	.cfi_def_cfa_register 5
	push	ebx
	sub	esp, 20
	.cfi_offset 3, -12
	call	__x86.get_pc_thunk.ax
	add	eax, OFFSET FLAT:_GLOBAL_OFFSET_TABLE_
	mov	edx, DWORD PTR 8[ebp]
	mov	BYTE PTR -24[ebp], dl
	mov	DWORD PTR -8[ebp], 753664
	movsx	dx, BYTE PTR -24[ebp]
	mov	ebx, edx
	or	bh, 15
	mov	edx, DWORD PTR pos.0@GOTOFF[eax]
	lea	ecx, 1[edx]
	mov	DWORD PTR pos.0@GOTOFF[eax], ecx
	mov	eax, edx
	lea	edx, [eax+eax]
	mov	eax, DWORD PTR -8[ebp]
	add	eax, edx
	mov	edx, ebx
	mov	WORD PTR [eax], dx
	nop
	mov	ebx, DWORD PTR -4[ebp]
	leave
	.cfi_restore 5
	.cfi_restore 3
	.cfi_def_cfa 4, 4
	ret
	.cfi_endproc
.LFE0:
	.size	putc, .-putc
	.globl	puts
	.type	puts, @function
puts:
.LFB1:
	.cfi_startproc
	push	ebp
	.cfi_def_cfa_offset 8
	.cfi_offset 5, -8
	mov	ebp, esp
	.cfi_def_cfa_register 5
	call	__x86.get_pc_thunk.ax
	add	eax, OFFSET FLAT:_GLOBAL_OFFSET_TABLE_
	jmp	.L3
.L4:
	mov	eax, DWORD PTR 8[ebp]
	lea	edx, 1[eax]
	mov	DWORD PTR 8[ebp], edx
	movzx	eax, BYTE PTR [eax]
	movsx	eax, al
	push	eax
	call	putc
	add	esp, 4
.L3:
	mov	eax, DWORD PTR 8[ebp]
	movzx	eax, BYTE PTR [eax]
	test	al, al
	jne	.L4
	nop
	nop
	leave
	.cfi_restore 5
	.cfi_def_cfa 4, 4
	ret
	.cfi_endproc
.LFE1:
	.size	puts, .-puts
	.section	.rodata
.LC0:
	.string	"Hello from C!"
	.text
	.globl	kernel_main
	.type	kernel_main, @function
kernel_main:
.LFB2:
	.cfi_startproc
	push	ebp
	.cfi_def_cfa_offset 8
	.cfi_offset 5, -8
	mov	ebp, esp
	.cfi_def_cfa_register 5
	call	__x86.get_pc_thunk.ax
	add	eax, OFFSET FLAT:_GLOBAL_OFFSET_TABLE_
	lea	eax, .LC0@GOTOFF[eax]
	push	eax
	call	puts
	add	esp, 4
.L6:
	jmp	.L6
	.cfi_endproc
.LFE2:
	.size	kernel_main, .-kernel_main
	.local	pos.0
	.comm	pos.0,4,4
	.section	.text.__x86.get_pc_thunk.ax,"axG",@progbits,__x86.get_pc_thunk.ax,comdat
	.globl	__x86.get_pc_thunk.ax
	.hidden	__x86.get_pc_thunk.ax
	.type	__x86.get_pc_thunk.ax, @function
__x86.get_pc_thunk.ax:
.LFB3:
	.cfi_startproc
	mov	eax, DWORD PTR [esp]
	ret
	.cfi_endproc
.LFE3:
	.ident	"GCC: (Ubuntu 11.4.0-1ubuntu1~22.04.2) 11.4.0"
	.section	.note.GNU-stack,"",@progbits

mov DWORD PTR -8[ebp], 753664753664を16進数で表すと0xB8000となり、テキストモードのVRAM開始アドレスと一致する。

生成されたバイナリ(disk.img)を逆アセンブルして確認する

boot.asm

16ビットとして逆アセンブルした結果

ndisasm -b 16 -o 0x7C00 disk.img | less
00007C00  FA                cli
00007C01  31C0              xor ax,ax
00007C03  8ED8              mov ds,ax
00007C05  8EC0              mov es,ax
00007C07  8ED0              mov ss,ax
00007C09  BC007C            mov sp,0x7c00
00007C0C  FB                sti
00007C0D  B402              mov ah,0x2
00007C0F  B001              mov al,0x1
00007C11  B500              mov ch,0x0
00007C13  B600              mov dh,0x0
00007C15  B102              mov cl,0x2
00007C17  B280              mov dl,0x80
00007C19  BB007E            mov bx,0x7e00
00007C1C  CD13              int 0x13
00007C1E  7205              jc 0x7c25
00007C20  EA007E0000        jmp 0x0:0x7e00
00007C25  F4                hlt
00007C26  EBFD              jmp short 0x7c25
00007C28  0000              add [bx+si],al
00007C2A  0000              add [bx+si],al
00007C2C  0000              add [bx+si],al

disk.imgの32ビットモードへ入る前

513バイト〜の領域にはtest.asmの内容が入っている

16ビットとして逆アセンブルした結果

dd if=disk.img of=sec1.bin bs=512 skip=1 count=1
ndisasm -b 16 -o 0x7E00 sec1.bin | less
00007E00  FA                cli
00007E01  31C0              xor ax,ax
00007E03  8ED8              mov ds,ax
00007E05  8EC0              mov es,ax
00007E07  8ED0              mov ss,ax
00007E09  BC007C            mov sp,0x7c00
00007E0C  B80300            mov ax,0x3
00007E0F  CD10              int 0x10
00007E11  B800B8            mov ax,0xb800
00007E14  8EC0              mov es,ax
00007E16  0F0116427E        lgdt [0x7e42]
00007E1B  0F20C0            mov eax,cr0
00007E1E  6683C801          or eax,byte +0x1
00007E22  0F22C0            mov cr0,eax
00007E25  EA487E0800        jmp 0x8:0x7e48

※これより下はデータや32ビットの機械語なので16ビットとして逆アセンブリ出来ない。
00007E25 EA487E0800 jmp 0x8:0x7e48を実行すると32ビットモードへ移行する。

disk.imgのC言語呼び出し部分

jmp 0x8:0x7e48から32ビットコードの位置が分かるので、48バイト以降を切り出す。

dd if=sec1.bin of=pm.bin bs=1 skip=$((0x48)) status=none
ndisasm -b 32 pm.bin

32ビットとして逆アセンブルする。

dd if=sec1.bin of=sec1_7e9c.bin bs=1 skip=$((0x9C)) status=none
ndisasm -b 32 -o 0x7E9C sec1_7e9c.bin | less

call 0x9cがCの入り口と見える。※元々call kernel_mainと書いている。
0x9cからのコードを見てみると上に載せたkernel.sと一致している。

00000000  66B81000          mov ax,0x10
00000004  8ED8              mov ds,eax
00000006  8EC0              mov es,eax
00000008  8EE0              mov fs,eax
0000000A  8EE8              mov gs,eax
0000000C  8ED0              mov ss,eax
0000000E  BC00FC0900        mov esp,0x9fc00
00000013  E884000000        call 0x9c
00000018  F4                hlt
00000019  EBFD              jmp short 0x18
0000001B  55                push ebp
0000001C  89E5              mov ebp,esp
0000001E  53                push ebx
0000001F  83EC14            sub esp,byte +0x14
00000022  E893000000        call 0xba
00000027  0531010000        add eax,0x131
0000002C  8B5508            mov edx,[ebp+0x8]
0000002F  8855E8            mov [ebp-0x18],dl
00000032  C745F800800B00    mov dword [ebp-0x8],0xb8000
00000039  660FBE55E8        movsx dx,[ebp-0x18]
0000003E  89D3              mov ebx,edx
00000040  80CF0F            or bh,0xf
00000043  8B900C000000      mov edx,[eax+0xc]
00000049  8D4A01            lea ecx,[edx+0x1]
0000004C  89880C000000      mov [eax+0xc],ecx
00000052  89D0              mov eax,edx
00000054  8D1400            lea edx,[eax+eax]
00000057  8B45F8            mov eax,[ebp-0x8]
0000005A  01D0              add eax,edx
0000005C  89DA              mov edx,ebx
0000005E  668910            mov [eax],dx
00000061  90                nop
00000062  8B5DFC            mov ebx,[ebp-0x4]
00000065  C9                leave
00000066  C3                ret
00000067  55                push ebp
00000068  89E5              mov ebp,esp
0000006A  E84B000000        call 0xba
0000006F  05E9000000        add eax,0xe9
00000074  EB18              jmp short 0x8e
00000076  8B4508            mov eax,[ebp+0x8]
00000079  8D5001            lea edx,[eax+0x1]
0000007C  895508            mov [ebp+0x8],edx
0000007F  0FB600            movzx eax,byte [eax]
00000082  0FBEC0            movsx eax,al
00000085  50                push eax
00000086  E890FFFFFF        call 0x1b
0000008B  83C404            add esp,byte +0x4
0000008E  8B4508            mov eax,[ebp+0x8]
00000091  0FB600            movzx eax,byte [eax]
00000094  84C0              test al,al
00000096  75DE              jnz 0x76
00000098  90                nop
00000099  90                nop
0000009A  C9                leave
0000009B  C3                ret
0000009C  55                push ebp
0000009D  89E5              mov ebp,esp
0000009F  E816000000        call 0xba
000000A4  05B4000000        add eax,0xb4
000000A9  8D8066FFFFFF      lea eax,[eax-0x9a]
000000AF  50                push eax
000000B0  E8B2FFFFFF        call 0x67
000000B5  83C404            add esp,byte +0x4
000000B8  EBFE              jmp short 0xb8
000000BA  8B0424            mov eax,[esp]
000000BD  C3                ret
000000BE  48                dec eax
000000BF  656C              gs insb
000000C1  6C                insb
000000C2  6F                outsd
000000C3  206672            and [esi+0x72],ah
000000C6  6F                outsd
000000C7  6D                insd
000000C8  204321            and [ebx+0x21],al
000000CB  001400            add [eax+eax],dl
000000CE  0000              add [eax],al
000000D0  0000              add [eax],al
000000D2  0000              add [eax],al
000000D4  017A52            add [edx+0x52],edi
000000D7  0001              add [ecx],al
000000D9  7C08              jl 0xe3
000000DB  011B              add [ebx],ebx
000000DD  0C04              or al,0x4
000000DF  0488              add al,0x88
000000E1  0100              add [eax],eax
000000E3  0020              add [eax],ah
000000E5  0000              add [eax],al
000000E7  001C00            add [eax+eax],bl
000000EA  0000              add [eax],al
000000EC  2F                das
000000ED  FF                db 0xff
000000EE  FF                db 0xff
000000EF  FF4C0000          dec dword [eax+eax+0x0]
000000F3  0000              add [eax],al
000000F5  41                inc ecx
000000F6  0E                push cs
000000F7  088502420D05      or [ebp+0x50d4202],al
000000FD  44                inc esp
000000FE  830302            add dword [ebx],byte +0x2
00000101  44                inc esp
00000102  C5                db 0xc5
00000103  C3                ret
00000104  0C04              or al,0x4
00000106  0400              add al,0x0
00000108  1C00              sbb al,0x0
0000010A  0000              add [eax],al
0000010C  40                inc eax
0000010D  0000              add [eax],al
0000010F  0057FF            add [edi-0x1],dl
00000112  FF                db 0xff
00000113  FF3500000000      push dword [dword 0x0]
00000119  41                inc ecx
0000011A  0E                push cs
0000011B  088502420D05      or [ebp+0x50d4202],al
00000121  71C5              jno 0xe8
00000123  0C04              or al,0x4
00000125  0400              add al,0x0
00000127  0018              add [eax],bl
00000129  0000              add [eax],al
0000012B  006000            add [eax+0x0],ah
0000012E  0000              add [eax],al
00000130  6C                insb
00000131  FF                db 0xff
00000132  FF                db 0xff
00000133  FF1E              call far [esi]
00000135  0000              add [eax],al
00000137  0000              add [eax],al
00000139  41                inc ecx
0000013A  0E                push cs
0000013B  088502420D05      or [ebp+0x50d4202],al
00000141  0000              add [eax],al
00000143  0010              add [eax],dl
00000145  0000              add [eax],al
00000147  007C0000          add [eax+eax+0x0],bh
0000014B  006EFF            add [esi-0x1],ch
0000014E  FF                db 0xff
0000014F  FF0400            inc dword [eax+eax]
00000152  0000              add [eax],al
00000154  0000              add [eax],al
00000156  0000              add [eax],al
00000158  0000              add [eax],al
0000015A  0000              add [eax],al
0000015C  0000              add [eax],al
0000015E  0000              add [eax],al
00000160  0000              add [eax],al
00000162  0000              add [eax],al

よって各ソースがコンパイル後に正しく結合されていることが分かる。

開発環境

test@test-fujitsu:~/kaihatsu/c$ uname -a
Linux test-fujitsu 6.8.0-87-generic #88~22.04.1-Ubuntu SMP PREEMPT_DYNAMIC Tue Oct 14 14:03:14 UTC 2 x86_64 x86_64 x86_64 GNU/Linux
test@test-fujitsu:~/kaihatsu/c$ gcc -m32 --version
gcc (Ubuntu 11.4.0-1ubuntu1~22.04.2) 11.4.0
Copyright (C) 2021 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

test@test-fujitsu:~/kaihatsu/c$ ld --version
GNU ld (GNU Binutils for Ubuntu) 2.38
Copyright (C) 2022 Free Software Foundation, Inc.
这个程序是自由软件;您可以遵循GNU 通用公共授权版本 3 或
(您自行选择的) 稍后版本以再次散布它。
这个程序完全没有任何担保。
test@test-fujitsu:~/kaihatsu/c$ qemu-system-i386 --version
QEMU emulator version 6.2.0 (Debian 1:6.2+dfsg-2ubuntu6.27)
Copyright (c) 2003-2021 Fabrice Bellard and the QEMU Project developers
test@test-fujitsu:~/kaihatsu/c$ 
0
0
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
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?