ウォンツテック

そでやまのーと

x86_64のLongモードをやってみる

なんとなく64bitCPUのブートシーケンスが気になったのでx86_64(AMD64)のブートを調べてみました。
AMD64アーキテクチャ互換のCPUが無いため実行環境はエミュレータqemu-system-x86_64で。
開発環境も32bitしか無いため32bitホスト上に以下のようなクロスコンパイル環境を作ります。

binutils

今回はCコードまではいかないのでassemblerとlinkerさえあれば十分というわけで、GNU binutilsのsourceを拾ってきて以下のようにコンパイルします。

% ./configure -prefix=/usr/local/m64 --disable-nls --enable-64-bit-bfd --enable-targets=all
% make
% sudo make install

「--enable-64-bit-bfd」で64bitに対応したassemblerをコンパイルします。アセンブルするときはas --64とすれば64bitでアセンブル出来るようになります。

環境が整ったので次はAMD64の情報を以下のサイトから拾ってきます。
http://www.amd.com/us-en/Processors/TechnicalResources/0,,30_182_739_7044,00.html
Volume2にsystem回りの情報が載っているのでそれをDLします。

まずはx86_64ってどんなのよ?というのを確認するためにchapter1のoverviewを見ると

    • 64bitのモードには2種類ありCompability Modeと64-bit Modeというのがある。総称してLong mode。
    • TSSを使ったハードウェアswitchは無くなった
      • TSS構造体自体はPrivilege Levelの異なるswitch時にkernel stackを変えるときなどに使用する
    • Long mode(64bit)への移行にはpaging必須
    • virtual machineを想定したSecure Virtual Machineなる機能がついた
    • etc..

TSSを使ったswitchが無くなったのは当然かな。誰も使ってないし。あとpaging必須(Longモード移行時に設定しなければいけないっぽいので必須だと思われます)ってのがちょっと試すには厳しいです。。

というわけで早速コードを書いてみたのですが、、うまくいきません。まぁpagingの設定で失敗してます。64bitのpage table構造複雑杉。4段階って。。 まともにやるにはAMDのマニュアル熟読しないといけないので今回はこの辺で。

makefile

CFLAGS = -nostdlib -fno-exceptions -ffreestanding -fno-builtin -Wall -m64
LFLAGS = --oformat binary

AS = /usr/local/m64/bin/as
LD = /usr/local/m64/bin/ld

all: boot64.o boot64sec.o
    $(LD) -Ttext 0x0 ${LFLAGS} -o boot64.bin boot64.o
    $(LD) -Ttext 0x200 ${LFLAGS} -o boot64sec.bin boot64sec.o
    cat boot64.bin boot64sec.bin > boot.bin

boot64.o: boot64.S
    $(AS) --64 -o boot64.o boot64.S

boot64sec.o: boot64sec.S
    $(AS) --64 -o boot64sec.o boot64sec.S

clean:
    rm boot64.o boot64sec.o boot64.bin boot64sec.bin boot.bin

boot64.S

/*                                                                                                                                             
 *  @Author  sodex                                                                                                                             
 *  AMD64 architecture test                                                                                                                    
 */

BOOTSEG         = 0x07C0
KERNEL_STACKSEG = 0x8000
INITSEG         = 0x9000

.code16
.text
.global _start
_start:
        cli
        jmpl    $BOOTSEG, $start2

start2:
        xorw    %si, %si
        xorw    %di, %di
        movw    $BOOTSEG, %ax
        movw    %ax, %ds
        movw    $INITSEG, %ax
        movw    %ax, %es
        movw    $0x100, %cx
        cld
        rep     movsw
        jmpl    $INITSEG, $next

next:
        movw    %ax, %ds
        movw    $KERNEL_STACKSEG, %ax
        movw    %ax, %ss
        xorw    %sp, %sp

        movb    $0x3, %al       # Clear the display
        movb    $0, %ah
        int     $0x10
        movw    $loader_mes, %si
        call    printstr

read_middleboot:
reset_fd:
        xorw    %ax, %ax
        xorb    %dl, %dl        # dl is num of drive
        int     $0x13
        jnc     fd_reset_ok
        movw    $fd_reset_error_mes, %si
        call    printstr
        jmp     reset_fd
fd_reset_ok:

        movw    $0x200, %bx     # "es" is already set at 0x9000

        movb    $0x2, %ah           # AH is BIOS COMMAND, The no.2 indicates
                                    # reading disk sectors

        movb    $6, %al             # AL is Num of sectors to read
        movb    $2, %cl             # CL is the sector
        movb    $0, %ch             # CH is the track
        movb    $0, %dh             # DH is the head
        movb    $0, %dl             # DL is the drive(always 0)
        int     $0x13

        ljmp    $0x9000, $0x200

forever:
        jmp     forever
printstr:
        pusha
printstr_start:
        lodsb
        cmpb    $0, %al
        jz      printstr_end
        movb    $0x0e, %ah
        movb    $0, %bh
        int     $0x10
        jmp     printstr_start
printstr_end:
        popa
        ret

loader_mes:
        .ascii  "Loading...\r\n\0"
fd_reset_error_mes:
        .ascii  "The floppy drive failed reset..\r\n\0"
fd_read_error_mes:
        .ascii  "read error from FD..\r\n\0"
fd_over64k_error_mes:
        .ascii  "over 64k..\r\n\0"

        .org 510
        .word 0xAA55

boot64sec.S

/*                                                                                                                                             
 *  @Author  sodex                                                                                                                             
 *  AMD64 architecture test                                                                                                                    
 */

KERNEL_CS       = 0x08
KERNEL_DS       = 0x10
KERNEL_STACKSEG = 0x8000
INITSEG         = 0x9000

.code16
.text
.global _start
_start:
        movw    %cs, %ax
        movw    %ax, %ds
        movw    %ax, %es
        movw    %ax, %fs
        movw    %ax, %gs
        xorl    %eax, %eax
        movl    %eax, %fs:(0x0)

        xorl    %eax, %eax
        movw    %ds, %ax
        shll    $4, %eax
        addl    $gdt, %eax
        movl    %eax, (gdtr+2)

        lgdt    gdtr
        lidt    idtr

        movl    $0x11, %eax
        movl    %eax, %cr0
        jmp     flush_pipe_queue16

flush_pipe_queue16:
        .byte   0x66, 0xea
        .long   start32 + 0x90000
        .word   KERNEL_CS

.code32
.text
start32:
        movw    $KERNEL_DS, %ax
        movw    %ax, %ds
        movw    %ax, %es
        movw    %ax, %fs
        movw    %ax, %gs
        movw    %ax, %ss
        movl    $0x9c000, %esp

        movl    %cr4, %eax
        bts     $5, %eax
        movl    %eax, %cr4

        movl    $0xc0000080, %ecx
        rdmsr
        bts     $8, %eax
        wrmsr

        movl    $level4_pgt, %eax
        movl    %eax, %cr3
        movl    %cr0, %eax
        bts     $31, %eax
        movl    %eax, %cr0

        jmp     flush_pipe_queue32

flush_pipe_queue32:
        .byte   0xea
        .long   start64 + 0x90000
        .word   KERNEL_CS
.code64
.text
start64:
        #movq    $0x90000, %rsp

forever:
        jmp     forever



gdtr:   .word   gdt_end - gdt - 1
        .long   0       # filled in code
gdt:
gdt00h:                 #It's dummy for cpu
        .word   0       #selector 00h
        .word   0
        .byte   0
        .byte   0
        .byte   0
        .byte   0
gdt08h:                 #code segment
        .word   0xFFFF  #selector 08h
        .word   0
        .byte   0
        .byte   0x9A    # P=1, DPL=0, S=1, TYPE=5, A=0
        .byte   0xCF    # G=1, D=1
        .byte   0
gdt10h:                 #data segment
        .word   0xFFFF  #selector 10h
        .word   0
        .byte   0
        .byte   0x92    # P=1, DPL=0, S=1, TYPE=1, A=0
        .byte   0xCF    # G=1, D=1
        .byte   0
        .byte   0xCF    # G=1, D=1
        .byte   0
gdt18h:                 #stack segment
        .word   0       #selector 18h
        .word   0x0
        .byte   0x0
        #.byte   0x96    # P=1, DPL=0, S=1, TYPE=3, A=0
        .byte   0x92    # P=1, DPL=0, S=1, TYPE=1, A=0
        .byte   0xC0    # G=1, D=1
        .byte   0
gdt_end:

idtr:   .word   0
        .word   0, 0
        .org 800

.data
.code32

        $page = 0
        $page = $page + 1
        .org   $page * 0x1000
.global level2_pgt
        level2_pgt = $page*0x1000
        i = 0
        .rept 20
        .quad i << 21 | 0x083
        i = i + 1
        .endr
        .fill 492,8,0

        $page = $page + 1
        .org   $page * 0x1000
.global level3_pgt
        level3_pgt = $page*0x1000
        .quad level2_pgt | 0x007
        .fill 511,8,0

        $page = $page + 1
        .org   $page * 0x1000
.global level4_pgt
        level4_pgt = $page*0x1000
        .quad level3_pgt | 0x007
        .fill 255,8,0