nasmqemuisoosdevgeneral-protection-fault

general protection fault when running os on iso


I have the following bootloader code which seems to run perfectly fine on a hard disk:

[bits 16]
[org 0x7c00]

bootld_start:
    KERNEL_OFFSET equ 0x2000

    xor ax, ax      ; Explicitly set ES = DS = 0
    mov ds, ax
    mov es, ax
    mov bx, 0x8C00  ; Set SS:SP to 0x8C00:0x0000 . The stack will exist
                    ;     between 0x8C00:0x0000 and 0x8C00:0xFFFF
    mov ss, bx
    mov sp, ax

    mov [BOOT_DRIVE], dl

    mov bx, boot_msg
    call print_string

    mov dl, [BOOT_DRIVE]
    call disk_load

    jmp pm_setup

    jmp $

BOOT_DRIVE:
    db 0

disk_load:
    mov si, dap
    mov ah, 0x42

    int 0x13

    ;cmp al, 4
    ;jne disk_error_132

    ret

dap:
    db 0x10             ; Size of DAP
    db 0
    ; You can only read 46 sectors into memory between 0x2000 and
    ; 0x7C00. Don't read anymore or we overwrite the bootloader we are
    ; executing from. (0x7c00-0x2000)/512 = 46
    dw 46               ; Number of sectors to read
    dw KERNEL_OFFSET    ; Offset
    dw 0                ; Segment
    dd 1
    dd 0

disk_error_132:
    mov bx, disk_error_132_msg
    call print_string

    jmp $

disk_error_132_msg:
    db 'Error! Error! Something is VERY wrong! (0x132)', 0

gdt_start:

gdt_null:
    dd 0x0
    dd 0x0

gdt_code:
    dw 0xffff
    dw 0x0
    db 0x0
    db 10011010b
    db 11001111b
    db 0x0

gdt_data:
    dw 0xffff
    dw 0x0
    db 0x0
    db 10010010b
    db 11001111b
    db 0x0

gdt_end:

gdt_descriptor:
    dw gdt_end - gdt_start
    dd gdt_start

CODE_SEG equ gdt_code - gdt_start
DATA_SEG equ gdt_data - gdt_start

boot_msg:
    db 'OS is booting files... ', 0

done_msg:
    db 'Done! ', 0

%include "boot/print_string.asm"

pm_setup:
    mov bx, done_msg
    call print_string

    mov ax, 0
    mov ss, ax
    mov sp, 0xFFFC

    mov ax, 0
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax

    cli
    lgdt[gdt_descriptor]
    mov eax, cr0
    or eax, 0x1
    mov cr0, eax
    jmp CODE_SEG:b32

    [bits 32]

    VIDEO_MEMORY equ 0xb8000
    WHITE_ON_BLACK equ 0x0f

    print32:
        pusha
        mov edx, VIDEO_MEMORY
    .loop:
        mov al, [ebx]
        mov ah, WHITE_ON_BLACK
        cmp al, 0
        je .done
        mov [edx], ax
        add ebx, 1
        add edx, 2
        jmp .loop
    .done:
        popa
        ret

    b32:
        mov ax, DATA_SEG
        mov ds, ax
        mov es, ax
        mov fs, ax
        mov gs, ax
        mov ss, ax

        ; Place stack below EBDA in lower memory
        mov ebp, 0x9c000
        mov esp, ebp

        mov ebx, pmode_msg
        call print32

        call KERNEL_OFFSET

        jmp $

    pmode_msg:
        db 'Protected mode enabled!', 0

kernel:
    mov ebx, pmode_msg
    call print32
    jmp $

pmode_tst:
    db 'Testing...'

times 510-($-$$) db 0
db 0x55
db 0xAA

The problem is that when I convert it to an ISO with these commands:

mkdir iso
mkdir iso/boot
cp image.flp iso/boot/boot
xorriso -as mkisofs -R -J -c boot/bootcat \
                    -b boot/boot -no-emul-boot -boot-load-size 4 \
                    -o image.iso iso

...it fails with a triple fault. When I run it with qemu-system-i386 -boot d -cdrom os-image.iso -m 512 -d int -no-reboot -no-shutdown, it outputs (excluding useless SMM exceptions):

check_exception old: 0xffffffff new 0xd
     0: v=0d e=0000 i=0 cpl=0 IP=0008:0000000000006616 
pc=0000000000006616 
SP=0010:000000000009bff8 env->regs[R_EAX]=0000000000000000
EAX=00000000 EBX=00007d72 ECX=00000000 EDX=000000e0
ESI=00007cb0 EDI=00000010 EBP=0009c000 ESP=0009bff8
EIP=00006616 EFL=00000083 [--S---C] CPL=0 II=0 A20=1 SMM=0 HLT=0
ES =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
CS =0008 00000000 ffffffff 00cf9a00 DPL=0 CS32 [-R-]
SS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
DS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
FS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
GS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
LDT=0000 00000000 0000ffff 00008200 DPL=0 LDT
TR =0000 00000000 0000ffff 00008b00 DPL=0 TSS32-busy
GDT=     00007c73 00000018
IDT=     00000000 000003ff
CR0=00000011 CR2=00000000 CR3=00000000 CR4=00000000
DR0=0000000000000000 DR1=0000000000000000 DR2=0000000000000000         DR3=0000000000000000 
DR6=00000000ffff0ff0 DR7=0000000000000400
CCS=000000e0 CCD=000001b3 CCO=ADDB    
EFER=0000000000000000
check_exception old: 0xd new 0xd
     1: v=08 e=0000 i=0 cpl=0 IP=0008:0000000000006616     pc=0000000000006616 SP=0010:000000000009bff8 env-        >regs[R_EAX]=0000000000000000
EAX=00000000 EBX=00007d72 ECX=00000000 EDX=000000e0
ESI=00007cb0 EDI=00000010 EBP=0009c000 ESP=0009bff8
EIP=00006616 EFL=00000083 [--S---C] CPL=0 II=0 A20=1 SMM=0 HLT=0
ES =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
CS =0008 00000000 ffffffff 00cf9a00 DPL=0 CS32 [-R-]
SS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
DS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
FS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
GS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
LDT=0000 00000000 0000ffff 00008200 DPL=0 LDT
TR =0000 00000000 0000ffff 00008b00 DPL=0 TSS32-busy
GDT=     00007c73 00000018
IDT=     00000000 000003ff
CR0=00000011 CR2=00000000 CR3=00000000 CR4=00000000
DR0=0000000000000000 DR1=0000000000000000 DR2=0000000000000000        DR3=0000000000000000 
DR6=00000000ffff0ff0 DR7=0000000000000400
CCS=000000e0 CCD=000001b3 CCO=ADDB    
EFER=0000000000000000
check_exception old: 0x8 new 0xd

Which means that I got a 0x0d (general protection fault), then a 0x08 (double fault), then it triple faulted. Why is this happening?

EDIT: I have changed the command to:

xorriso -as mkisofs -R -J -c boot/bootcat -b boot/boot.flp -o nmos.iso nmos.flp

But I am now getting the following error:

xorriso : FAILURE : Cannot find in ISO image: -boot_image ... bin_path='/boot/boot.flp'
xorriso : NOTE : -return_with SORRY 32 triggered by problem severity FAILURE

Does anyone know what this means?

EDIT 2:

I have changed the code to read using ah=0x02 like this:

mov bx, KERNEL_OFFSET
mov ah, 0x02
mov al, 46
mov ch, 0x00
mov dh, 0x00
mov cl, 0x02
mov dl, [BOOT_DRIVE]

int 0x13

But it is still triple-faulting. Why?


Solution

  • The primary cause of all the triple faults in your question really come down to the fact that your kernel isn't being loaded properly into the memory at 0x0000:0x2000. When you transfer control to this location with a JMP you end up running what happens to be in the memory region and the CPU executes until it hits an instruction that causes a fault.


    Bootable CDs are strange beasts that have a number of different modes, and there are many BIOSes that boot such CDs but they too may have their own quirks. When you use -no-emul-boot with XORRISO you are requesting the disk neither be treated as a floppy nor hard disk. You could remove -no-emul-boot -boot-load-size 4 that should generate an ISO that gets treated as a floppy. The problem with that is many real BIOSes, Emulators (BOCHs and QEMU) and Virtual machines do not support Int 13h/AH=42h extended disk reads when the CD is booted using floppy emulation. You may be forced to use regular disk read via Int 13h/AH=02h.

    You should be able to use extended disk reads via Int 13h/AH=42h if you use -no-emul-boot -boot-load-size 4 but it will require some changes to your bootloader. When using -no-emul-boot -boot-load-size 4 CDROMs sector sizes are 2048 bytes, not 512. This will require a bit of modification to your bootloader and kernel. The -boot-load-size 4 writes information to the ISO that informs the BIOS to read 4 512-byte chunks from the beginning of the disk image inside the ISO. The 0xaa55 boot signature is no longer needed.

    If you use -no-emul-boot there is one other snag that needs to be dealt with. On the CD-ROM LBA 0 isn't where the disk image gets placed in the final ISO. The question is, how can you get the LBA where the disk image is in the ISO? You can have XORRISO write this information into a special section of the bootloader you create, and you enable this feature with -boot-info-table.

    Creating the special section at the beginning of the bootloader is relatively easy. In the El Torito Specification Supplement they mention this:

    EL TORITO BOOT INFORMATION TABLE
    ...
           The  format of this table is as follows; all integers are in sec-
           tion 7.3.1 ("little endian") format.
    
             Offset    Name           Size      Meaning
              8        bi_pvd         4 bytes   LBA of primary volume descriptor
             12        bi_file        4 bytes   LBA of boot file
             16        bi_length      4 bytes   Boot file length in bytes
             20        bi_csum        4 bytes   32-bit checksum
             24        bi_reserved    40 bytes  Reserved
    
           The 32-bit checksum is the sum of all the  32-bit  words  in  the
           boot file starting at byte offset 64.  All linear block addresses
           (LBAs) are given in CD sectors (normally 2048 bytes).
    

    This is talking about the 56 bytes at offset 8 of the virtual disk we create holding our bootloader. If we modify the top of your bootloader code to look like this we effectively create a blank boot information table:

    start:
      jmp bootld_start
      times 8-($-$$) db 0          ; Pad out first 8 bytes
    
      ; Boot info table
      bi_pvd    dd  0
      bi_file   dd  0
      bi_kength dd  0
      bi_csum   dd  0
      bi_reserved times 40 db 0    ; 40 bytes reserved
    

    When using XORRISO with -boot-info-table this table will be filled in once the ISO is generated. bi_file is the important piece of information we will need since it is the LBA where our disk image is placed inside the ISO. We can use this to fill in the Disk Access Packet used by extended disk reads to read from the proper location of the ISO.

    To make the DAP a little more readable and to account for 2048 byte sectors I've amended it to look like:

    dap:
    dap_size:    db 0x10                ; Size of DAP
    dap_zero     db 0
        ; You can only read 11 2048 byte sectors into memory between 0x2000 and
        ; 0x7C00. Don't read anymore or we overwrite the bootloader we are
        ; executing from. (0x7c00-0x2000)/2048 = 11 (rounded down)
    dap_numsec:  dw 11                  ; Number of sectors to read
    dap_offset:  dw KERNEL_OFFSET       ; Offset
    dap_segment: dw 0                   ; Segment
    dap_lba_low: dd 0
    dap_lba_high:dd 0
    

    One issue is that the LBA placed into the Boot Information table is from the start of the disk image (sector with our bootloader). We need to increment that LBA by 1 and place it into the DAP so we are using the LBA where our kernel starts. Using 32-bit instruction we can just read the 32-bit value from the Boot Information Table, add 1 and save it to the DAP. If using strictly 16-bit instructions add one to a 32-bit value is more complex. Since we are going into 386 protected mode we can assume instruction with 32-bit operands are supported in real mode. The code to update the DAP with the LBA of the kernel could look like:

        mov ebx, [bi_file]       ; Get LBA of our disk image in ISO
        inc ebx                  ; Add sector to get LBA for start of kernel
        mov [dap_lba_low], ebx   ; Update DAP with LBA of kernel in the ISO
    

    The only other issue is that the bootloader sector needs to be padded out to 2048 (the size of a CD-ROM sector) rather than 512 and we can remove the boot signature. Change:

    times 510-($-$$) db 0
    db 0x55
    db 0xAA
    

    To:

    times 2048-($-$$) db 0
    

    The modified bootloader code could look like:

    [bits 16]
    [org 0x7c00]
    
    KERNEL_OFFSET equ 0x2000
    
    start:
      jmp bootld_start
      times 8-($-$$) db 0          ; Pad out first 8 bytes
    
      ;     Boot info table
      bi_pvd    dd  0
      bi_file   dd  0
      bi_kength dd  0
      bi_csum   dd  0
      bi_reserved times 40 db 0    ; 40 bytes reserved
    
    bootld_start:
    
            xor ax, ax      ; Explicitly set ES = DS = 0
            mov ds, ax
            mov es, ax
            mov bx, 0x8C00  ; Set SS:SP to 0x8C00:0x0000 . The stack will exist
                            ;     between 0x8C00:0x0000 and 0x8C00:0xFFFF
            mov ss, bx
            mov sp, ax
    
            mov ebx, [bi_file]       ; Get LBA of our disk image in ISO
            inc ebx                  ; Add sector to get LBA for start of kernel
            mov [dap_lba_low], ebx   ; Update DAP with LBA of kernel in the ISO
    
            mov [BOOT_DRIVE], dl    
            mov bx, boot_msg
            call print_string
    
            mov dl, [BOOT_DRIVE]
            call disk_load
    
            jmp pm_setup
    
            jmp $
    
    BOOT_DRIVE:
            db 0
    
    disk_load:
            mov si, dap
            mov ah, 0x42
    
            int 0x13
    
            ;cmp al, 4
            ;jne disk_error_132
    
            ret
    
    dap:
    dap_size:    db 0x10                ; Size of DAP
    dap_zero     db 0
        ; You can only read 11 2048 byte sectors into memory between 0x2000 and
        ; 0x7C00. Don't read anymore or we overwrite the bootloader we are
        ; executing from. (0x7c00-0x2000)/2048 = 11 (rounded down)
    dap_numsec:  dw 11                  ; Number of sectors to read
    dap_offset:  dw KERNEL_OFFSET       ; Offset
    dap_segment: dw 0                   ; Segment
    dap_lba_low: dd 0
    dap_lba_high:dd 0
    
    disk_error_132:
            mov bx, disk_error_132_msg
            call print_string
    
            jmp $
    
    disk_error_132_msg:
            db 'Error! Error! Something is VERY wrong! (0x132)', 0
    
    gdt_start:
    
    gdt_null:
        dd 0x0
        dd 0x0
    
    gdt_code:
        dw 0xffff
        dw 0x0
        db 0x0
        db 10011010b
        db 11001111b
        db 0x0
    
    gdt_data:
        dw 0xffff
        dw 0x0
        db 0x0
        db 10010010b
        db 11001111b
        db 0x0
    
    gdt_end:
    
    gdt_descriptor:
        dw gdt_end - gdt_start
        dd gdt_start
    
    CODE_SEG equ gdt_code - gdt_start
    DATA_SEG equ gdt_data - gdt_start
    
    boot_msg:
            db 'OS is booting files... ', 0
    
    done_msg:
            db 'Done! ', 0
    
    %include "boot/print_string.asm"
    
    pm_setup:
            mov bx, done_msg
            call print_string
    
        mov ax, 0
        mov ss, ax
        mov sp, 0xFFFC
    
        mov ax, 0
        mov ds, ax
        mov es, ax
        mov fs, ax
        mov gs, ax
    
        cli
        lgdt[gdt_descriptor]
        mov eax, cr0
        or eax, 0x1
        mov cr0, eax
        jmp CODE_SEG:b32
    
            [bits 32]
    
            VIDEO_MEMORY equ 0xb8000
            WHITE_ON_BLACK equ 0x0f
    
            print32:
                pusha
                mov edx, VIDEO_MEMORY
            .loop:
                mov al, [ebx]
                mov ah, WHITE_ON_BLACK
                cmp al, 0
                je .done
                mov [edx], ax
                add ebx, 1
                add edx, 2
                jmp .loop
            .done:
                popa
                ret
    
            b32:
                mov ax, DATA_SEG
                mov ds, ax
                mov es, ax
                mov fs, ax
                mov gs, ax
                mov ss, ax
    
            ; Place stack below EBDA in lower memory
                mov ebp, 0x9c000
                mov esp, ebp
    
                mov ebx, pmode_msg
                call print32
    
                    call KERNEL_OFFSET
    
                jmp $
    
            pmode_msg:
                    db 'Protected mode enabled!', 0
    
    kernel:
            mov ebx, pmode_msg
            call print32
            jmp $
    
    pmode_tst:
            db 'Testing...'
    
    times 2048-($-$$) db 0
    

    You can then modify your original XORRISO command to be:

    xorriso -as mkisofs -R -J -c boot/bootcat \
                        -b boot/boot -no-emul-boot -boot-load-size 4 \
                        -boot-info-table -o image.iso iso