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