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?

GRUBの画面表示仕組み

0
Last updated at Posted at 2026-08-23
git clone https://gitlab.freedesktop.org/gnu-grub/grub.git

目的

前回GRUBがどうやってメニュー画面を描画しているかを調べると、最終的にint 0x10を呼び出していることが分かりました。
GRUBのメニュー画面がどう描画されているか調べる

今回はCのソースからどうやってBIOSの表示機能を呼び出しているのかに焦点を当てて調べました。

int10_9関数

ch :表示したいASCII 文字コード
n :その文字を何回繰り返して表示するか

grub-core/term/i386/pc/console.c

static grub_uint8_t grub_console_cur_color = 0x7;

static void
int10_9 (grub_uint8_t ch, grub_uint16_t n)
{
  struct grub_bios_int_registers regs;

  regs.eax = ch | 0x0900;                   // AH=0x09 AL=表示する文字コード
  regs.ebx = grub_console_cur_color & 0xff; // 文字属性(黒背景、白文字)
  regs.ecx = n;                             // 繰り返し回数
  regs.flags = GRUB_CPU_INT_FLAGS_DEFAULT;
  grub_bios_interrupt (0x10, &regs);
}

include/grub/i386/pc/int_types.h
#define  GRUB_CPU_INT_FLAGS_INTERRUPT 0x200

#ifdef GRUB_MACHINE_PCBIOS
#define  GRUB_CPU_INT_FLAGS_DEFAULT   GRUB_CPU_INT_FLAGS_INTERRUPT
#else
#define  GRUB_CPU_INT_FLAGS_DEFAULT   0

レジスタ群構造体(grub_bios_int_registers)

include/grub/i386/pc/int_types.c

struct grub_bios_int_registers
{
  grub_uint32_t eax;
  grub_uint16_t es;
  grub_uint16_t ds;
  grub_uint16_t flags;
  grub_uint16_t dummy;
  grub_uint32_t ebx;
  grub_uint32_t ecx;
  grub_uint32_t edi;
  grub_uint32_t esi;
  grub_uint32_t edx;
};

アセンブリからint 0x10の呼び出し

32bitモード → 16bitモードでint実行 → 32bitモード

include/grub/i386/pc/int.h

#ifdef GRUB_MACHINE_PCBIOS
void EXPORT_FUNC (grub_bios_interrupt) (grub_uint8_t intno,
					struct grub_bios_int_registers *regs)
     __attribute__ ((regparm(3)));

extern struct grub_i386_idt *EXPORT_VAR(grub_realidt);
#endif

regparm(3)がついているので0x10%al に、&regs%edxに入ってアセンブリに渡されます。
BIOSを呼ぶ前後のレジスタの保存処理は筆者も完全には理解できていません。
BIOSが残した値を保存したりもしているようですがint 0x10では書き込むだけでBIOSの値は使わないので無視します。
ややこしいですがやっていることは、一時的に16ビットへ移行し、C言語で設定したregsの値を各レジスタに格納してBIOS機能を呼び出したあと32ビットに戻しているだけです。

grub-core/kern/i386/int.S

FUNCTION(grub_bios_interrupt)
	pushf
	cli
	popf
	pushl    %ebp
	pushl    %ecx
	pushl    %eax
	pushl    %ebx
	pushl    %esi
	pushl    %edi	
	pushl    %edx
	
	movb     %al, intno                     # int XのXを保存
	movl	 (%edx), %eax                   # &regsから32ビットの値(regs->eax)を取り出してEAXへ格納
	movl	 %eax, LOCAL(bios_register_eax) # bios_register_eaxへregsreg->eaxを保存
	movw	 4(%edx), %ax
	movw	 %ax, LOCAL(bios_register_es)   # bios_register_esへregs->esを保存
	movw	 6(%edx), %ax
	movw	 %ax, LOCAL(bios_register_ds)   # bios_register_dsへregs->dsを保存
	movw	 8(%edx), %ax
	movw	 %ax, LOCAL(bios_register_flags)# bios_register_flagsへregs->flagsを保存

	movl 	12(%edx), %ebx # edx = reg->edx
	movl 	16(%edx), %ecx # ecx = reg->ecx
	movl 	20(%edx), %edi # edi = reg->edi
	movl 	24(%edx), %esi # esi = reg->esi
	movl 	28(%edx), %edx # edx = reg->edx

	/*
	Via C3 CPUs have cache coherence problems, so we need to call
	wbinvd at these 2 points. As wbinvd slows down boot, don't do
	it on non-VIA. 9090 is nop nop. */
VARIABLE(grub_bios_via_workaround1)
	.byte 0x90, 0x90

	PROT_TO_REAL  # 16bitモードへ切り替え
	.code16
	pushf
	cli
	
	mov	%ds, %ax
	push	%ax

	/* movw imm16, %ax*/
	.byte	0xb8
LOCAL(bios_register_es):     # ES復元
	.short 	0
	movw	%ax, %es
	/* movw imm16, %ax*/
	.byte	0xb8
LOCAL(bios_register_ds):
	.short 	0
	movw	%ax, %ds     # DS復元

	/* movw imm16, %ax*/
	.byte	0xb8
LOCAL(bios_register_flags):     # FLAGS復元
	.short 	0
	push	%ax
	popf

	/* movl imm32, %eax*/
	.byte	0x66, 0xb8
LOCAL(bios_register_eax):     # EAX復元(作業に使うから最後に復元)
	.long 	0
	
	/* int imm8.  */
	.byte   0xcd          # ここで各レジスタにはC側で設定したregsが入っているのでint Xを呼ぶ
intno:                        # BIOS機能番号(int XのX)。intnoは上で書き換えている
	.byte   0

	movl 	%eax, %cs:LOCAL(bios_register_eax)
	movw	%ds, %ax
	movw 	%ax, %cs:LOCAL(bios_register_ds)
	pop 	%ax
	mov	%ax, %ds
	pushf
	pop	%ax
	movw	%ax, LOCAL(bios_register_flags)
	mov 	%es, %ax
	movw	%ax, LOCAL(bios_register_es)

	popf

VARIABLE(grub_bios_via_workaround2)
	.byte 0x90, 0x90

	REAL_TO_PROT           # 32bitモードへ戻る
	.code32

	popl    %eax

	movl 	%ebx, 12(%eax)
	movl 	%ecx, 16(%eax)
	movl 	%edi, 20(%eax)
	movl 	%esi, 24(%eax)
	movl 	%edx, 28(%eax)

	movl     %eax, %edx

	movl	 LOCAL(bios_register_eax), %eax
	movl	 %eax, (%edx)
	movw	 LOCAL(bios_register_es), %ax
	movw	 %ax, 4(%edx)
	movw	 LOCAL(bios_register_ds), %ax
	movw	 %ax, 6(%edx)
	movw	 LOCAL(bios_register_flags), %ax
	movw	 %ax, 8(%edx)

	popl 	%edi
	popl 	%esi
	popl    %ebx
	popl    %eax
	popl    %ecx
	popl    %ebp
	ret

自分でC言語からint 0x10を呼ぶ実験

ソースコードを読むだけでは完全に理解できないので自分でも同じように実装してみます。
GRUBのレジスタ構造体をそのまま使ってC言語でレジスタ値を設定し、int 0x10を呼び出します。

nasm -f bin boot.asm -o boot.bin
nasm -f elf32 entry.asm -o entry.o
gcc -m32 -ffreestanding -c term.c -o term.o
nasm -f elf32 int10.asm -o int10.o

ld -m elf_i386 -T linker.ld -o kernel.elf entry.o term.o int10.o
objcopy -O binary kernel.elf kernel.bin
cat boot.bin kernel.bin > disk.img

qemu-system-i386 -drive format=raw,file=disk.img -monitor stdio
截图 2026-08-23 14-07-21
boot.asm
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    ; 読み込んだコードへjmp

disk_error:
    hlt
    jmp disk_error

times 510-($-$$) db 0
dw 0xAA55
entry.asm
entry.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 mode3
    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
linker.ld
linker.ld
ENTRY(start)

SECTIONS {
    . = 0x7E00;

    .text : {
        *(.text*)
    }

    .rodata : {
        *(.rodata*)
    }

    .data : {
        *(.data*)
    }

    .bss : {
        *(.bss*)
        *(COMMON)
    }
}
term.c
typedef unsigned char          grub_uint8_t; 
typedef unsigned short         grub_uint16_t;
typedef unsigned int           grub_uint32_t;

static grub_uint8_t grub_console_cur_color = 0x7;

struct grub_bios_int_registers
{
  grub_uint32_t eax;    // オフセット 0
  grub_uint16_t es;    // オフセット 4
  grub_uint16_t ds;    // オフセット 6
  grub_uint16_t flags;    // オフセット 8
  grub_uint16_t dummy;    // オフセット 10
  grub_uint32_t ebx;    // オフセット 12
  grub_uint32_t ecx;    // オフセット 16
  grub_uint32_t edi;    // オフセット 20
  grub_uint32_t esi;    // オフセット 24
  grub_uint32_t edx;    // オフセット 28
} __attribute__((packed));

/* Sファイル側の関数宣言 */
void int10(struct grub_bios_int_registers *regs)
    __attribute__((regparm(3)));

void kernel_main(){
  struct grub_bios_int_registers regs;
  
  //Cを100回表示する
  regs.eax = 'C' | 0x0900;
  regs.ebx = grub_console_cur_color & 0xff;
  regs.ecx = 100;
  
  int10(&regs); // 汎用的ではなく0x10に限定する為、引数は1つで良い
  
  for(;;); // ここへ戻ってくることはない 
}
int10.asm
global int10

bits 32

int10:
    pushf
    cli
    
    ; regparm(3) によりEAXに&regsが入っている
    mov edi, eax ; EAXは後続処理に使用する為EDIに保存する
    
    ; 構造体の順番で入っているのでオフセットをずらしながら取得
    mov eax, [edi]              ; regs->eax
    mov [bios_register_eax], eax
    
    mov eax, [edi + 12]         ; regs->ebx
    mov [bios_register_ebx], eax
    
    mov eax, [edi + 16]         ; regs->ecx
    mov [bios_register_ecx], eax
    
    ; ブートローダで設定したGDTを入れ替える        
    lgdt [gdt_descriptor]
    
    ; 16ビットデータセグメントを設定する 0x20 = GDT[4]
    ; D=0なので命令を16ビットととして実行する
    mov ax, 0x20
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    mov ss, ax
    
    ; CSを16ビットコードセグメントに変更する 0x18 = GDT[3] 
    jmp 0x18:tmpcseg
    
bits 16

tmpcseg:

    ; PE = 0
    mov eax, cr0
    and eax, 0xFFFFFFFE
    mov cr0, eax
    
    ; 16bitモードへ突入
    jmp 0:real_mode

real_mode:

    xor ax, ax
    mov ds, ax
    mov es, ax
    mov ss, ax
    mov fs, ax
    mov gs, ax
    mov sp, 0x7C00
        
    ; レジスタにregsで設定した値を設定
    mov eax, [bios_register_eax]
    mov ebx, [bios_register_ebx]
    mov ecx, [bios_register_ecx]
    
    int 0x10          ; BIOS 呼び出し
    
hang: ; スタック復元がややこしいので、今回はCには戻らずここで止める
    cli
    hlt
    loop hang

section .data

; C側で設定したregsを退避する場所
 bios_register_eax:
     dd 0
 
 bios_register_ebx:
     dd 0
 
 bios_register_ecx:
     dd 0
     
; ==================================================
; GDT
; ==================================================

align 8
gdt_start:
    dq 0

    ; GDT[1] 32-bit code
    dw 0xFFFF
    dw 0x0000
    db 0x00
    db 0x9A
    db 0xCF
    db 0x00

    ; GDT[2] 32-bit data
    dw 0xFFFF
    dw 0x0000
    db 0x00
    db 0x92
    db 0xCF
    db 0x00

    ; GDT[3] 16-bit code
    dw 0xFFFF
    dw 0x0000
    db 0x00
    db 0x9E
    db 0x00
    db 0x00

    ; GDT[4] 16-bit data
    dw 0xFFFF
    dw 0x0000
    db 0x00
    db 0x92
    db 0x00
    db 0x00

gdt_end:

gdt_descriptor:
    dw gdt_end - gdt_start - 1
    dd gdt_start
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?