【问题标题】:OS development, issues with floating point division resulting NaN操作系统开发,浮点除法问题导致 NaN
【发布时间】:2020-09-11 09:22:30
【问题描述】:

如果我的问题没有以最佳方式格式化,我提前道歉,我是新手在这里提问。

我最近对学习操作系统开发很感兴趣,并且在 C 中遇到了一些关于浮点除法的奇怪问题。即使像 4.0f / 2.0f 这样简单的事情也会给我一个 NaN 结果。我怀疑这可能与编译器有关,但是我不知道如何验证这一点,我非常感谢帮助解决这个问题,因为我已经在这工作了几个小时并且几乎没有谷歌搜索的进展。 该项目的 Github,如果您想构建它:https://github.com/AsherBearce/ToyOperatingSystem

我项目的相关部分如下:

内核/kernelmain.c:

#include "screen.h"

void main(){   
    enableCursor(1, 14);
    clearScreen();
    double a = 4.0f;
    double b = 2.0f;
    double c = a / b;
    double ans = 2.0f;
    //Division is the ONLY operation that isn't yielding the correct results, in fact c turns out to be NaN!
    if (c == 2.0f){ 
        char string[] = "Hardcoded values were correct\n\0";
        print(string);
    }

    char out[] = "End output\0";
    print(out);

    while (1){

    }
}

boot/bootsector.asm

org 0x7c00
bits 16
mov ax, HELLO_MSG ;Print a simple hello message :D
call _printString
xor ax, ax
;Here, we'll load the kernel into RAM
call LoadKernel
;Enter protected mode
call EnterProtMode

EnterProtMode:
    cli ;Disable interrupts
    lgdt [gdt_pointer] ;Load the GDT register with the start address of the GDT
    mov eax, cr0
    or al, 1 ;Set PE (protection enable) bit in CR0
    mov cr0, eax
    jmp 08h:Stage2 ;Jump to stage 2

LoadKernel:
    mov bx, KERNEL_OFFSET ;Load the kernel offset into bx
    mov dh, 16 ;Load 16 sectors 
    mov dl, [BOOT_DRIVE] ;The disk to read from
    call diskload ;Load the kernel
    ret

bits 32
KERNEL_OFFSET equ 0x1000
BOOT_DRIVE: db 0

Stage2:
    mov ax, DATA_SEG
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    mov ss, ax
    mov ebp, 0x90000
    mov esp, ebp
    ;Kernel entry here
    jmp KERNEL_OFFSET ;Call the kernel finally

%include 'boot/printUtils.asm'
%include 'boot/gdt.asm'
%include 'boot/diskload.asm'

HELLO_MSG: db "Booted successfully, loading kernel.", 0
times 510 - ($ - $$) db 0
dw 0xaa55

生成文件

BOOTOUTPUT = boot.bin
OSOUTPUT = os.bin
SRCS = $(shell find . -name '*.c')
CINC = $(shell find . -name '*.h')
COBJS = $(patsubst %.c, %.o, $(SRCS))
OBJDIR = build

#Final step in the build process
$(OSOUTPUT): kernel.bin $(BOOTOUTPUT)
    cat $(BOOTOUTPUT) kernel.bin > $(OSOUTPUT)

#Assemble the boot sector code
$(BOOTOUTPUT): boot/bootsector.asm
    nasm -f bin boot/bootsector.asm -o $(BOOTOUTPUT)

#Compile all the kernel C files
%.o:%.c $(CINC)
    gcc -m32 -ffreestanding -fno-pie -fno-stack-protector -nostdlib -c $< -o $@

#Assemble the IRQ code
irq.o: kernel/irq.asm
    nasm kernel/irq.asm -f elf32 -o irq.o

#Assemble the kernel entry code
kernelEntry.o: boot/kernelEntry.asm
    nasm boot/kernelEntry.asm -f elf32 -o kernelEntry.o

#Link all the .o files with the kernel entry
kernel.bin: kernelEntry.o irq.o $(COBJS)
    ld -melf_i386 -o kernel.bin -Ttext 0x1000 $^ --oformat binary

run: 
    qemu-system-x86_64 -fda $(OSOUTPUT)

clean:
    rm -f *.bin *.o $(COBJS)

编辑:我决定包含 kernelmain.c 的反汇编


kernelmain.o:     file format elf32-i386


Disassembly of section .text:

00000000 <main>:
   0:   f3 0f 1e fb             endbr32 
   4:   8d 4c 24 04             lea    0x4(%esp),%ecx
   8:   83 e4 f0                and    $0xfffffff0,%esp
   b:   ff 71 fc                pushl  -0x4(%ecx)
   e:   55                      push   %ebp
   f:   89 e5                   mov    %esp,%ebp
  11:   51                      push   %ecx
  12:   83 ec 54                sub    $0x54,%esp
  15:   e8 fc ff ff ff          call   16 <main+0x16>
  1a:   83 ec 08                sub    $0x8,%esp
  1d:   6a 0e                   push   $0xe
  1f:   6a 01                   push   $0x1
  21:   e8 fc ff ff ff          call   22 <main+0x22>
  26:   83 c4 10                add    $0x10,%esp
  29:   e8 fc ff ff ff          call   2a <main+0x2a>
  2e:   e8 fc ff ff ff          call   2f <main+0x2f>
  33:   dd 05 00 00 00 00       fldl   0x0
  39:   dd 5d f0                fstpl  -0x10(%ebp)
  3c:   dd 05 08 00 00 00       fldl   0x8
  42:   dd 5d e8                fstpl  -0x18(%ebp)
  45:   dd 45 f0                fldl   -0x10(%ebp)
  48:   dc 75 e8                fdivl  -0x18(%ebp)
  4b:   dd 5d e0                fstpl  -0x20(%ebp)
  4e:   dd 05 08 00 00 00       fldl   0x8
  54:   dd 5d d8                fstpl  -0x28(%ebp)
  57:   dd 45 e0                fldl   -0x20(%ebp)
  5a:   dd 05 08 00 00 00       fldl   0x8
  60:   df e9                   fucomip %st(1),%st
  62:   dd d8                   fstp   %st(0)
  64:   7a 56                   jp     bc <main+0xbc>
  66:   dd 45 e0                fldl   -0x20(%ebp)
  69:   dd 05 08 00 00 00       fldl   0x8
  6f:   df e9                   fucomip %st(1),%st
  71:   dd d8                   fstp   %st(0)
  73:   75 47                   jne    bc <main+0xbc>
  75:   c7 45 ac 48 61 72 64    movl   $0x64726148,-0x54(%ebp)
  7c:   c7 45 b0 63 6f 64 65    movl   $0x65646f63,-0x50(%ebp)
  83:   c7 45 b4 64 20 76 61    movl   $0x61762064,-0x4c(%ebp)
  8a:   c7 45 b8 6c 75 65 73    movl   $0x7365756c,-0x48(%ebp)
  91:   c7 45 bc 20 77 65 72    movl   $0x72657720,-0x44(%ebp)
  98:   c7 45 c0 65 20 63 6f    movl   $0x6f632065,-0x40(%ebp)
  9f:   c7 45 c4 72 72 65 63    movl   $0x63657272,-0x3c(%ebp)
  a6:   c7 45 c8 74 0a 00 00    movl   $0xa74,-0x38(%ebp)
  ad:   83 ec 0c                sub    $0xc,%esp
  b0:   8d 45 ac                lea    -0x54(%ebp),%eax
  b3:   50                      push   %eax
  b4:   e8 fc ff ff ff          call   b5 <main+0xb5>
  b9:   83 c4 10                add    $0x10,%esp
  bc:   c7 45 cc 45 6e 64 20    movl   $0x20646e45,-0x34(%ebp)
  c3:   c7 45 d0 6f 75 74 70    movl   $0x7074756f,-0x30(%ebp)
  ca:   c7 45 d4 75 74 00 00    movl   $0x7475,-0x2c(%ebp)
  d1:   83 ec 0c                sub    $0xc,%esp
  d4:   8d 45 cc                lea    -0x34(%ebp),%eax
  d7:   50                      push   %eax
  d8:   e8 fc ff ff ff          call   d9 <main+0xd9>
  dd:   83 c4 10                add    $0x10,%esp
  e0:   eb fe                   jmp    e0 <main+0xe0>

【问题讨论】:

  • 首先,永远不要f用于double字面量!它的意思是浮动
  • 计算移动到一个函数中并反汇编它,并将它与那些看似有效的计算的反汇编进行比较。
  • 另外,在 osdev 方面有一些经验,我建议您在验证可以编写引导加载程序后,停止使用引导加载程序并使用 Grub+multiboot反而。好多了:D
  • 最后是我最讨厌的:*(VMEM + index) = fullchar; 应该写成VMEM[index] = fullchar; - 更容易阅读。
  • 所以加法有效但除法无效?关于初始化浮点堆栈,查看finit指令,它初始化了浮点单元。

标签: c linux operating-system


【解决方案1】:

查看各种资源后,我在 OSdev 上找到了这个论坛页面: forum.osdev.org/viewtopic.php?f=1&t=21813 描述了首先检查 FPU,然后对其进行初始化的过程。事实证明,无论出于何种原因,我的目标平台都没有 FPU,我猜这是未定义行为的原因。

【讨论】:

    最近更新 更多