目的
将来x86上でOSを介さずに動く簡単な3Dエンジンを開発したいと思っています。
浮動小数を使うことになりますが、現在の環境で生成される浮動小数の命令が、x87 FPUを使用しているかSSEを使用しているかを確かめます。
参考にした筆者の過去記事
ブートローダや、32ビットへ移行する方法及びC言語でのベアメタル開発の手順は以下に纏めています。
DVDから直接起動してC言語を動かす手順
x86ベアメタル直線描画
boot.asm,swtc.asm,linker.ldは前記事中のものを流用しています。
C言語及び生成された機械語(逆アセンブル結果)
gcc -m32 -ffreestanding -c float.c -o float.o
objdump -d -M intel float.o
float.c
float add(float f1,float f2){
return f1+f2;
}
float sub(float f1,float f2){
return f1-f2;
}
float mul(float f1, float f2) {
return f1 * f2;
}
float div(float f1, float f2) {
return f1 / f2;
}
int main(){
float num1=1.234;
float num2=5.678;
float num3=add(num1,num2);
float num4=sub(num1,num2);
float num5=mul(num1,num2);
float num6=div(num1,num2);
}
逆アセンブル結果
test@test-fujitsu:~/kaihatsu$ gcc -m32 -ffreestanding -c float.c -o float.o
test@test-fujitsu:~/kaihatsu$ objdump -d -M intel float.o
float.o: 文件格式 elf32-i386
Disassembly of section .text:
00000000 <add>:
0: 55 push ebp
1: 89 e5 mov ebp,esp
3: e8 fc ff ff ff call 4 <add+0x4>
8: 05 01 00 00 00 add eax,0x1
d: d9 45 08 fld DWORD PTR [ebp+0x8]
10: d8 45 0c fadd DWORD PTR [ebp+0xc]
13: 5d pop ebp
14: c3 ret
00000015 <sub>:
15: 55 push ebp
16: 89 e5 mov ebp,esp
18: e8 fc ff ff ff call 19 <sub+0x4>
1d: 05 01 00 00 00 add eax,0x1
22: d9 45 08 fld DWORD PTR [ebp+0x8]
25: d8 65 0c fsub DWORD PTR [ebp+0xc]
28: 5d pop ebp
29: c3 ret
0000002a <mul>:
2a: 55 push ebp
2b: 89 e5 mov ebp,esp
2d: e8 fc ff ff ff call 2e <mul+0x4>
32: 05 01 00 00 00 add eax,0x1
37: d9 45 08 fld DWORD PTR [ebp+0x8]
3a: d8 4d 0c fmul DWORD PTR [ebp+0xc]
3d: 5d pop ebp
3e: c3 ret
0000003f <div>:
3f: 55 push ebp
40: 89 e5 mov ebp,esp
42: e8 fc ff ff ff call 43 <div+0x4>
47: 05 01 00 00 00 add eax,0x1
4c: d9 45 08 fld DWORD PTR [ebp+0x8]
4f: d8 75 0c fdiv DWORD PTR [ebp+0xc]
52: 5d pop ebp
53: c3 ret
00000054 <main>:
54: 55 push ebp
55: 89 e5 mov ebp,esp
57: 83 ec 20 sub esp,0x20
5a: e8 fc ff ff ff call 5b <main+0x7>
5f: 05 01 00 00 00 add eax,0x1
64: d9 80 00 00 00 00 fld DWORD PTR [eax+0x0]
6a: d9 5d fc fstp DWORD PTR [ebp-0x4]
6d: d9 80 04 00 00 00 fld DWORD PTR [eax+0x4]
73: d9 5d f8 fstp DWORD PTR [ebp-0x8]
76: ff 75 f8 push DWORD PTR [ebp-0x8]
79: ff 75 fc push DWORD PTR [ebp-0x4]
7c: e8 fc ff ff ff call 7d <main+0x29>
81: 83 c4 08 add esp,0x8
84: d9 5d f4 fstp DWORD PTR [ebp-0xc]
87: ff 75 f8 push DWORD PTR [ebp-0x8]
8a: ff 75 fc push DWORD PTR [ebp-0x4]
8d: e8 fc ff ff ff call 8e <main+0x3a>
92: 83 c4 08 add esp,0x8
95: d9 5d f0 fstp DWORD PTR [ebp-0x10]
98: ff 75 f8 push DWORD PTR [ebp-0x8]
9b: ff 75 fc push DWORD PTR [ebp-0x4]
9e: e8 fc ff ff ff call 9f <main+0x4b>
a3: 83 c4 08 add esp,0x8
a6: d9 5d ec fstp DWORD PTR [ebp-0x14]
a9: ff 75 f8 push DWORD PTR [ebp-0x8]
ac: ff 75 fc push DWORD PTR [ebp-0x4]
af: e8 fc ff ff ff call b0 <main+0x5c>
b4: 83 c4 08 add esp,0x8
b7: d9 5d e8 fstp DWORD PTR [ebp-0x18]
ba: 90 nop
bb: c9 leave
bc: c3 ret
Disassembly of section .text.__x86.get_pc_thunk.ax:
00000000 <__x86.get_pc_thunk.ax>:
0: 8b 04 24 mov eax,DWORD PTR [esp]
3: c3 ret
加減乗除それぞれの命令
x87 FPUを使用する命令に変換されていることが分かった。
加算
d: d9 45 08 fld DWORD PTR [ebp+0x8]
10: d8 45 0c fadd DWORD PTR [ebp+0xc]
減算
22: d9 45 08 fld DWORD PTR [ebp+0x8]
25: d8 65 0c fsub DWORD PTR [ebp+0xc]
乗算
37: d9 45 08 fld DWORD PTR [ebp+0x8]
3a: d8 4d 0c fmul DWORD PTR [ebp+0xc]
除算
4c: d9 45 08 fld DWORD PTR [ebp+0x8]
4f: d8 75 0c fdiv DWORD PTR [ebp+0xc]
FPU の初期化
BIOS内でFPUを初期化しているかも知れないが、念の為に明示的に初期化しておく。
mov eax, cr0
; EM(bit2)=0, TS(bit3)=0, MP(bit1)=1
and eax, 11111111111111111111111111110011b
or eax, 00000000000000000000000000000010b
mov cr0, eax
fninit ; FPUの初期化
動作検証
QEMU
qemu-system-i386 -cdrom os.iso -m 512 -boot d
dynabook SS M10 11L/2
sudo dvd+rw-format -blank=fast /dev/sr0
sudo growisofs -dvd-compat -Z /dev/sr0=os.iso
sudo dvd+rw-format -blank=fast /dev/sr0
sudo growisofs -dvd-compat -Z /dev/sr0=os.iso
コード
ビルドコマンド
rmdir tmp
mkdir tmp
nasm -f bin boot.asm -o tmp/boot.bin
gcc -m32 -ffreestanding -c kernel.c -o tmp/kernel.o
nasm -f elf32 swtc.asm -o tmp/swtc.o
ld -m elf_i386 -T linker.ld -o tmp/kernel.elf tmp/swtc.o tmp/kernel.o
objcopy -O binary tmp/kernel.elf tmp/kernel.bin
cat tmp/boot.bin tmp/kernel.bin > tmp/disk.img
mkdir iso
cp tmp/disk.img iso/
xorriso -as mkisofs \
-o os.iso \
-b disk.img \
-c boot.cat \
-no-emul-boot \
-boot-load-size 4 \
iso
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
jmp 0x0000:0x7E00
disk_error:
hlt
jmp disk_error
times 512-($-$$) db 0 ;CDの為0xAA55は不要
swtc.asm
swtc.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
;mov ax, 0x0013
;int 0x10
; VGAリアルモード表示
mov ax, 0xB800
mov es, ax
; GDTロード
lgdt [gdt_descriptor]
; プロテクトモード有効化
mov eax, cr0
or eax, 1
mov cr0, eax
; FPU有効化
mov eax, cr0
; EM(bit2)=0, TS(bit3)=0, MP(bit1)=1
and eax, 11111111111111111111111111110011b
or eax, 00000000000000000000000000000010b
mov cr0, eax
fninit ; FPUの初期化
; 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)
}
}
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++);
}
/*
unsigned int の整数を表示する
(再帰)
put_uint(123)
└ if (123 >= 10) → put_uint(12)
└ if (12 >= 10) → put_uint(1)
└ if (1 >= 10)? NO → putc('1')
putc('2')
putc('3')
*/
void put_uint(unsigned int n) {
if (n >= 10) put_uint(n / 10);
putc('0' + (n % 10)); // '0'を足して数字を対応するASCIIコードに変換している
}
void put_int(int n) {
if (n < 0) {
putc('-');
n = -n;
}
put_uint((unsigned int)n);
}
// float を表示(小数点以下 max_digits 桁)
void putf(float f, int max_digits) {
if (f < 0.0f) {
putc('-');
f = -f;
}
// 整数部
int int_part = (int)f;
put_int(int_part);
putc('.');
// 小数部
float frac = f - (float)int_part; // 浮動小数から整数部を引いて小数点以下を取り出す
for (int i = 0; i < max_digits; i++) {
frac *= 10.0f; // 小数の次の桁を、整数の一の位に持ってくる
int digit = (int)frac; // 整数部だけを切り取る
putc('0' + digit);
frac -= (float)digit; // 既に表示した1の位の数を消す
}
}
void kernel_main(void)
{
puts("float test: ");
float num1=1.234;
float num2=5.678;
float num3=num1+num2;
putf(num3, 4);
puts("\n");
}