CPU Reset (CPU 0) and triple-fault when setting SS register to 0 in bootloader

290 views Asked by At

I am attempting to create a custom operating system (for educational purposes) for which I am also creating the bootloader (multiboot 2). I am attempting to create a 64-bit system.

After entering long mode, I want to "clean up" by loading all data segment registers with 0. To do this, I have the following code:

long_mode_start:
    mov ax, 0
    mov ss, ax
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    ret

The moment mov ss, ax is executed, the boot process fails and the CPU is reset (eventually leading up to a triple-fault). I'm using QEMU to emulate a X86_64 system. In the logs I get the following (I'm not sure how to fully interpret the logs): CPU Reset (CPU 0). For full log file, see: https://github.com/WJJongkind/TicTacTOS/blob/develop/log.txt

I'm really not sure what I am doing wrong in my code. The full assembly file that sets up long mode and paging can be found here: https://github.com/WJJongkind/TicTacTOS/blob/develop/Bootloader/boot.asm

Note: I'm very much a beginner if it comes to assembly language & OS programming, so my comments in the code are probably not 100% accurate.

What causes the CPU to reset when setting the SS register value?

1

There are 1 answers

0
Peter Cordes On

Correction, this answer was based on a misreading of the manual: the pseudo-code it contains only applies to 32-bit protected mode. See the linked duplicate where Michael Petch's answer covers this in much more detail.

This code will fault if executed in 32-bit protected mode, but not in 64-bit long mode at CPL=0. Or if you are executing this code in 32-bit mode before switching to long mode, then yes you should expect it to fault on the spot for SS, unlike later when a load or store tries to use it for the other segments.


Old answer, lightly edited to point out that it's answering the wrong question: 64-bit user space or 32-bit mode.


ds and es can be the null selector (0) in 64-bit mode (unlike 32-bit PM), as can fs and gs.
That's what Linux does, as I can confirm with GDB for a user-space process.

But SS needs to select a valid descriptor for some reason, maybe to set the stack address width to 64, even though that's the only valid choice when CS selects a 64-bit code segment. (Correction, no, 64-bit mode allows SS=0 when CPL<3, as long as CPL==RPL.)

Surprised QEMU doesn't give you more of a log about what exception triggered the reset. I think Bochs could do that, at least if you single-stepped with its built-in debugger.

According to Intel's manual (Operation section for mov-to-Sreg in protected mode) it should be a #GP(0), and then double-fault and then triple-fault, assuming you don't have IDT entries for those. Which is fine if you have a simulator that shows you exception details.

PROTECTED MODE ONLY; there is no pseudo-code in the manual for 64-bit mode

DEST ← SRC;
Loading a segment register while in protected mode results in special checks and actions, as described in the
following listing. These checks are performed on the segment selector and the segment descriptor to which it
points.
IF SS is loaded
    THEN
        IF segment selector is NULL
            THEN #GP(0); FI;
        IF segment selector index is outside descriptor table limits
        OR segment selector's RPL ≠ CPL
        OR segment is not a writable data segment
        OR DPL ≠ CPL
            THEN #GP(selector); FI;
        IF segment not marked present
            THEN #SS(selector);
            ELSE
                SS ← segment selector;
                SS ← segment descriptor; FI;
FI;

For details on what can cause exceptions in 64-bit mode, see that later section of the same manual entry: https://www.felixcloutier.com/x86/mov#64-bit-mode-exceptions