Far jump to protected mode causes triple-fault on real hardware

Question about which tools to use, bugs, the best way to implement a function, etc should go here. Don't forget to see if your question is answered in the wiki first! When in doubt post here.
Post Reply
atomtables
Posts: 2
Joined: Fri Nov 08, 2024 5:08 pm
Libera.chat IRC: atomtables

Far jump to protected mode causes triple-fault on real hardware

Post by atomtables »

Hello. I wanted to test my OS on real hardware, since it seems to work in QEMU and Bochs. However, any time I boot it on my computer (boot using USB on a Lenovo Thinkpad T440P, legacy+csm), it triple-faults while performing a far jump. I also tested the OS on VMWare Workstation (using a virtual floppy disk) with the same result. I couldn't seem to find a reason why it shouldn't work; the GDT was good enough for Bochs to not return an error, and all other aspects of the bootloader seemed to be good enough. Please help.

My GDT:

Code: Select all

ALIGN 8

gdt_start:  ; don't remove the labels, they're needed to compute sizes and jumps
            ; the GDT starts with a null 8-byte
    dd  0x0 ; 4 byte
    dd  0x0 ; 4 byte

; GDT for kernel code segment. base = 0x00000000, length = 0xfffff
; for flags, refer to os-dev.pdf document, page 36
gdt_code:
    dw  0xffff    ; segment length, bits 0-15 (16-bit value)
    dw  0x0       ; segment base, bits 0-15 (16-bit value)
    db  0x0       ; segment base, bits 16-23 (8-bit value)
    db  10011010b ; flags (8 bits)
    db  11001111b ; flags (4 bits) + segment length, bits 16-19
    db  0x0       ; segment base, bits 24-31

; GDT for kernel data segment. base and length identical to code segment
; some flags changed, again, refer to os-dev.pdf
gdt_data:
    dw  0xffff
    dw  0x0
    db  0x0
    db  10010010b
    db  11001111b
    db  0x0

; Base = 0
; Limit = 0xFFFFF
; Access Byte = 0xFA
; Flags = 0xC
gdt_user_code:
    dw  0xffff
    dw  0x0
    db  0x0
    db  10011010b
    db  11001111b
    db  0x0

; Base = 0
; Limit = 0xFFFFF
; Access Byte = 0xF2
; Flags = 0xC
gdt_user_data:
    dw  0xffff
    dw  0x0
    db  0x0
    db  10010010b
    db  11001111b
    db  0x0

; gdt taask segment
gdt_tss:
    dw  0x67
    dw  0x0
    db  0x0
    db  10001001b
    db  0x0
    db  0x0

gdt_end:

; GDT descriptor
gdt_descriptor:
    dw  gdt_end - gdt_start - 1 ; size (16 bit), always one less of its true size
    dd  gdt_start ; address (32 bit)

; define some constants for later use
CODE_SEG equ gdt_code - gdt_start
DATA_SEG equ gdt_data - gdt_start
Main Bootloader:

Code: Select all

[bits   16]
[org    0x7c00]

; print hello world
mov     ah, 0x0e
mov     al, 'H'
int     0x10
mov     al, 'e'
int     0x10
mov     al, 'l'
int     0x10
mov     al, 'l'
int     0x10
mov     al, 'o'
int     0x10

KERNEL_OFFSET equ 0x10000 ; The same one we used when linking the kernel

mov     bx, 0x1000 ; Read from disk and store in 0x1000
mov     es, bx
mov     bx, 0x0000;es:bx = 0x1000:0x0000 = 0x10000
mov     ah, 0x02 ; ah <- int 0x13 function. 0x02 = 'read'
mov     al, 50   ; al <- number of sectors to read (0x01 .. 0x80)
mov     cl, 0x02 ; cl <- sector (0x01 .. 0x11)
mov     ch, 0x00 ; ch <- cylinder (0x0 .. 0x3FF, upper 2 bits in 'cl')
mov     dh, 0x00 ; dh <- head number (0x0 .. 0xF)

int     0x13     ; BIOS interrupt

mov     bp, 0x9000
mov     sp, bp

call    switch_to_32bit ; disable interrupts, load GDT,  etc. Finally jumps to 'BEGIN_PM'
jmp     $               ; Never executed

%include "gdt.asm"
%include "a20.asm"

[bits 16]
switch_to_32bit:
    call enable_a20             ; 0. enable A20 line
    cli                         ; 1. disable interrupts
    nop                        ; 1.1. some CPUs require a delay after cli
    lgdt    [gdt_descriptor]    ; 2. load the GDT descriptor
    mov     eax, cr0
    or      eax, 0x1            ; 3. set 32-bit mode bit in cr0
    mov     cr0, eax
    jmp     CODE_SEG:init_32bit ; 4. far jump by using a different segment

[bits 32]
init_32bit:                 ; we are now using 32-bit instructions
    mov     ax, DATA_SEG    ; 5. update the segment registers
    mov     ds, ax
    mov     ss, ax
    mov     es, ax
    mov     fs, ax
    mov     gs, ax

    mov     ebp, 0x90000    ; 6. update the stack right at the top of the free space
    mov     esp, ebp

    call    BEGIN_32BIT     ; 7. Call a well-known label with useful code

[bits 32]
BEGIN_32BIT:
    call    0x10000         ; Give control to the kernel
    jmp     $               ; Stay here when the kernel returns control to us (if ever)


BOOT_DRIVE db 0             ; It is a good idea to store it in memory because 'dl' may get overwritten

times 446 - ($-$$) db 0
partition_1:
    db 0x80                    ; Drive attribute
    db 0x00, 0x01, 0x01        ; CHS address of partition start
    db 0xFF                    ; Partition type
    db 0x00, 0x0F, 0x12        ; CHS address of partition end 
    dd 0x00000001              ; LBA of partition start
    dd 0x000005A0              ; Number of sectors in partition 
; padding
times 16 db 0                ; Entry 2
times 16 db 0                ; Entry 3
times 16 db 0                ; Entry 4

dw 0xaa55
Thank you
MichaelPetch
Member
Member
Posts: 794
Joined: Fri Aug 26, 2016 1:41 pm
Libera.chat IRC: mpetch

Re: Far jump to protected mode causes triple-fault on real hardware

Post by MichaelPetch »

Your code makes assumptions about the state of the segment registers (like DS) and SS:SP. If DS is set to something other than 0x000 by the BIOS that could affect the LGDT instruction that may look for the GDT record at the wrong memory location which in turn would cause an invalid GDT to be loaded. Make sure you set DS to 0x0000 when using an ORG 0x7C00. I'd highly recommend setting up a stack SS:SP since you don't know where the BIOS placed its own stack and you don't want your disk read to inadvertently clobber the stack. Putting SS:SP at 0x0000:0x7c00 would make sure it doesn't collide with the kernel being read at 0x1000:0x0000. I have some general bootloader tips here: https://stackoverflow.com/a/32705076/3857942 . Since you are booting with USB there is another possibility on real hardware. It is possible if booting as USB FDD Media that your bootloader code is being clobbered by the BIOS blindly writing disk geometry on top of it. You may need to add a BIOS Parameter Block (even if it is all blank). I have some information on this here: https://stackoverflow.com/a/47320115/3857942 .

Edit 1:I see you have a partition table, however if your BIOS is set for USB FDD (floppy disk drive) emulated booting the partition table may be ignored. Check the BIOS and see what boot method your laptop is using to boot the USB. If you are currently booting as USB FDD you could try changing to USB HDD. BIOSes seem to vary on what/how they deal with USB bootable media. Some try to auto detect whether it is FDD/HDD media; some may not; some may auto detect badly. I was a bit confused by the fact you were booting floppy media in VMWare but you have a partition table in your code. Floppy media is generally not partitioned and usually has VBR (with a BPB instead of a partition table) rather than an MBR with a partition table.

Edit 2: I just noticed this when doing the disk read `mov al, 50`. A value of 50 may not be supported by the BIOS when doing Int 0x13/AH=2 but this is all dependent on the number of sectors per track and heads reported by the BIOS/media. On real hardware you may not be able to read a number of sectors that cross a cylinder boundary. On very ancient hardware you may not be able to cross a track boundary (you are likely not going to be dealing with hardware that doesn't have multitrack read/write support). Some virtual environments like QEMU and BOCHS may allow higher values in AL. I believe BOCHS and QEMU might allow up to 72 sectors to be read at once so 50 would appear to work. You might want to check whether Int 0x13/AH=2 returned successfully (the carry flag is set on error).
rdos
Member
Member
Posts: 3294
Joined: Wed Oct 01, 2008 1:55 pm

Re: Far jump to protected mode causes triple-fault on real hardware

Post by rdos »

Yes, the only thing you can be sure about in a boot sector is that it is loaded at 0x7C00. I find it more convinient to load CS with 0x7C0 and then the code doesn't need orgs, since the first byte is at offset 0. Note that BIOS might pass control to the boot sector with CS = 07C0, and then your orgs will not be valid for code offsets unless you do a far jump to set CS to 0. If you use DS or ES for addressing, these must also be loaded to be consistent with how the code is orged (or not).
atomtables
Posts: 2
Joined: Fri Nov 08, 2024 5:08 pm
Libera.chat IRC: atomtables

Re: Far jump to protected mode causes triple-fault on real hardware

Post by atomtables »

Thanks for all of your help! What I think the problem was was the org statement throwing the code off, so I removed the orgs and used offsets for everything instead. I did have to manually offset the gdt_address, but it ended up working on real hardware.

By the way, I am using a USB in "USB HDD" mode, and the only way to get my computer to even look at it as bootable media was to mark a partition as active/bootable. I put it as a floppy disk on VMWare because on VMWare it didn't boot as a CD. It did boot as a CD on QEMU though. I did change the number of read sectors to 32 instead, and added a check for a failed read attempt.
Octocontrabass
Member
Member
Posts: 5549
Joined: Mon Mar 25, 2013 7:01 pm

Re: Far jump to protected mode causes triple-fault on real hardware

Post by Octocontrabass »

atomtables wrote: Sun Nov 10, 2024 2:56 pmIt did boot as a CD on QEMU though.
Are you sure? CD booting requires El Torito structures, and you don't have those.
rdos
Member
Member
Posts: 3294
Joined: Wed Oct 01, 2008 1:55 pm

Re: Far jump to protected mode causes triple-fault on real hardware

Post by rdos »

Octocontrabass wrote: Sun Nov 10, 2024 6:10 pm
atomtables wrote: Sun Nov 10, 2024 2:56 pmIt did boot as a CD on QEMU though.
Are you sure? CD booting requires El Torito structures, and you don't have those.
Not tried it, but traditional boot sectors does not rely on which filesystems are on the drive. El Torito is a filesystem. The exception of course is EFI and GPT which rely on the EFI system partition.

When I boot from USB, I put the same structures on the drive as when I boot from a hard drive, and it works.
Octocontrabass
Member
Member
Posts: 5549
Joined: Mon Mar 25, 2013 7:01 pm

Re: Far jump to protected mode causes triple-fault on real hardware

Post by Octocontrabass »

rdos wrote: Mon Nov 11, 2024 2:04 pmNot tried it, but traditional boot sectors does not rely on which filesystems are on the drive.
Legacy BIOS boot does not rely on any filesystems.
rdos wrote: Mon Nov 11, 2024 2:04 pmEl Torito is a filesystem.
El Torito is not a filesystem.
rdos wrote: Mon Nov 11, 2024 2:04 pmThe exception of course is EFI and GPT which rely on the EFI system partition.
EFI requires GPT and an EFI system partition on internal disks, but not on removable disks.
rdos wrote: Mon Nov 11, 2024 2:04 pmWhen I boot from USB, I put the same structures on the drive as when I boot from a hard drive, and it works.
Right, but I was talking about booting from a CD.
rdos
Member
Member
Posts: 3294
Joined: Wed Oct 01, 2008 1:55 pm

Re: Far jump to protected mode causes triple-fault on real hardware

Post by rdos »

EFI requires GPT and system partition regardless of media. If the media use MBR then it might boot in legacy mode, but certainly not in native mode.

In the beginning USB discs were special too, but nowadays you can use the same tools as for internal discs.

I don’t know if CDs have adapted too, or still use the old special ways. I think it depends on how Windows handles it.
Octocontrabass
Member
Member
Posts: 5549
Joined: Mon Mar 25, 2013 7:01 pm

Re: Far jump to protected mode causes triple-fault on real hardware

Post by Octocontrabass »

rdos wrote: Wed Nov 13, 2024 1:28 pmEFI requires GPT and system partition regardless of media. If the media use MBR then it might boot in legacy mode, but certainly not in native mode.
Microsoft requires UEFI to support MBR and generic FAT32. The USB flash drive version of the Windows installer uses MBR and a generic FAT32 partition. And yes, it boots in UEFI mode.

The actual UEFI specification is pretty contradictory, but it does require the firmware to support MBR and support generic FAT32.
rdos wrote: Wed Nov 13, 2024 1:28 pmIn the beginning USB discs were special too, but nowadays you can use the same tools as for internal discs.
I don't think USB boot has ever required special tools, other than maybe to work around firmware bugs.
rdos wrote: Wed Nov 13, 2024 1:28 pmI don’t know if CDs have adapted too, or still use the old special ways. I think it depends on how Windows handles it.
CDs still use El Torito, with the same options for a floppy disk image, a hard disk image, or a "no emulation" image. Windows uses a "no emulation" image for its legacy bootloader and a floppy disk image for its UEFI bootloader.
Post Reply