2

When far jumping into the main32 (my 32-bit entry point) QEMU starts rebooting the kernel constantly, like a loop.

This is from a custom OS project composed of a 2 stage bootloader and the kernel, which is the faulty one. As an x86 emulator I'm using QEMU.

The kernel, after disabling interrupts, activating the A20 line and setting the GDT, in 16 bits, does a far jump into the new code segment with 32 bits instructions to print some characters via VGA, or at least this is my intention.

Something has to be wrong with the jump or the labels because the emulator just starts going again over the bootloader code, printing some message in 16 bits, preparing the 32-bit transition and goes back to the QEMU booting messages, in a cycle.

I thought the far jump is jumping to a previous address and continues to execute from there, but coding an infinite loop after the main32 label doesn't cause the booting loop, stays nicely in the infinite loop after main32, showing the expected control flow.

This is the kernel.asm:

[BITS 16]

KERNEL_LOAD_SEGMENT     EQU 0x100

;set data segment   ; the code segment is already set by the jmp in real mode in the bootloader.stage2
    mov ax, KERNEL_LOAD_SEGMENT
    mov ds, ax

    ;hide cursor
    mov ah, 0x01
    mov cx, 0x2607
    int 0x10

    ;move cursor at top left position
    mov ah, 0x02
    xor bx, bx
    xor dx, dx
    int 0x10

    ;clear screen
    mov ah, 0x06
    xor al, al
    xor bx, bx
    mov bh, 0x07
    xor cx, cx
    mov dh, 24
    mov dl, 79
    int 0x10

    mov si, kernel_hello_world_string
    call BIOS_print_string

    ;enable A20 gate
    in al, 0x92
    or al, 2
    out 0x92, al

    ;disable interrupts
    cli

    ;load gdt
    lgdt [gdt_desc]

    ;set bit 1(32bit protected mode) in cr0
    mov eax, cr0
    or eax, 1
    mov cr0, eax

    jmp CODE_SEGMENT:main32

BIOS_print_newline:
    ; removed for clarity
    ret
BIOS_print_string:
    ; removed for clarity
    ret

kernel_hello_world_string:
    db "[KERNEL] Hello world! Entering 32-bit protected mode", 0x0


[BITS 32]

gdt_start:
gdt_null_segment:
    dq 0
gdt_code_segment:
    dw 0xFFFF
    dw 0x1000
    db 0
    db 10011010b
    db 11001111b
    db 0
gdt_data_segment:
    dw 0xFFFF
    dw 0x1000
    db 0
    db 10010010b
    db 11001111b
    db 0
gdt_end:

gdt_desc:
    db gdt_end - gdt_start
    dw gdt_start

CODE_SEGMENT equ gdt_code_segment - gdt_start
DATA_SEGMENT equ gdt_data_segment - gdt_start

print32:
    ; removed for clarity
    ret

main32:
    jmp $ ;;; WITH THIS JUMP THE REBOOTING IS GONE
    mov ax, DATA_SEGMENT
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    mov ss, ax

    mov ebp, 0x2000
    mov esp, ebp

    mov ebx, message
    ;call print32

    jmp $

message:
    db "32 bit protected mode entry point", 0

;pad
times 512 - ($ - $$) db 0

Has anyone some idea of why is this happening? The labels and the addresses doesn't look wrong to me.

Michael Petch
  • 46,082
  • 8
  • 107
  • 198
Burst
  • 23
  • 4
  • 1
    Are you triple-faulting? Use BOCHS to debug it; it has a built-in debugger that understands segmentation, so it's more useful than attaching GDB to qemu as a GDB-remote. – Peter Cordes Jul 16 '19 at 16:05
  • 1
    Does the print work? It seems you are setting `ds` to `0x100` and missing an `ORG` directive to make the GDT linear address match its effective address (i.e. offset as computed by NASM). – Margaret Bloom Jul 16 '19 at 16:34
  • @MargaretBloom The 16 bit message does work. I set ds to 0x100 because the stage2 of the bootloader loads the kernel at address 0x1000. All the labels work fine – Burst Jul 16 '19 at 17:19
  • @MichaelPetch used an org 0x1000 directive and changed ds to 0. Exact same behaviour. – Burst Jul 16 '19 at 17:24
  • 1
    There appears to be another issue. Your GDTR is `gdt_desc:` `db gdt_end - gdt_start` `dw gdt_start` . The problem is that the length needs to be a WORD and the offset a DWORD. It should be `gdt_desc:` `dw gdt_end - gdt_start - 1` `dd gdt_start` You should also be subtracting 1 from the length, but in this case it won't hurt anything being 1 byte longer. – Michael Petch Jul 16 '19 at 17:31
  • @MichaelPetch Corrected the sizes and applied the org 0x1000 and ds=0. Same behaviour. Im going to take a look at the answer you linked – Burst Jul 16 '19 at 17:41
  • Look at the answer to understand the issue of linear addresses but do not use it as a solution – Michael Petch Jul 16 '19 at 17:43
  • Oh I see what you were attempting to do. I didn't notice you set the base to 0x1000 in the GDT. You'd have to also change the 0x1000 to 0x0000 in the GDT for the code and data descriptors. The solution in my other answer combined with using a base of 0x1000 in the GDT may work but I don't suggest you do it that way. – Michael Petch Jul 16 '19 at 17:49
  • In the other answer they weren't adjusting the base inside the descriptors (it was 0x0000) in the GDT so in your code you wouldn't have to change the FAR JMP and it would remain as `jmp CODE_SEGMENT:main32` (nothing needs to be added because the base in your descriptors is 0x1000). You would have to still use `dd gdt_start+0x1000` in `gdt_desc`. But again, although that should work - having a flat model that has a base of 0x1000 isn't typical. Performance wise the CPU does better when the base is 0x00000000. – Michael Petch Jul 16 '19 at 18:00
  • To sum it all up. The preferred way would be set `org 0x1000` at the top, set `KERNEL_LOAD_SEGMENT` to 0x0000. Modify the code and segment descriptors (in the GDT) so that the base set with `dw 0x1000` is changed to `dw 0x0000` (for both code and data) and you have to fix the `gdt_desc` to be `gdt_desc:` `dw gdt_end - gdt_start - 1` `dd gdt_start` – Michael Petch Jul 16 '19 at 18:10
  • If you want your current code to work and you are intent on using a non-zero segment (0x100) the only change you have to make is to `gdt_desc`. It would have to look like: `gdt_desc:` `dw gdt_end - gdt_start - 1` `dd gdt_start + 0x1000` That should be the only change to the code you presented here to make things work assuming the code you don't show us (what you removed for clarity) is correct. – Michael Petch Jul 16 '19 at 18:21
  • 1
    One other minor note: The `mov esp` instruction should be _immediately after_ the `mov ss` instruction (which disables interrupts for the following instruction except on early 8086 CPUs), or you should use `lss esp` to load both in one instruction. Otherwise you have the (slight) risk of the stack not being set up when an interrupt happens. – 1201ProgramAlarm Jul 16 '19 at 20:03
  • @1201ProgramAlarm : Interrupts are already turned off at that point that he has already done a CLI because he has no IDT set up at that point in protected mode so can't re-enable them. As long as interrupts are off there, there is no issue. – Michael Petch Jul 16 '19 at 20:19
  • 2
    @MichaelPetch So they are (I missed that), but setting up the stack improperly is still a bad habit to get in to. – 1201ProgramAlarm Jul 16 '19 at 20:23
  • 2
    @MichaelPetch Yes, it worked. Just to sum up for anyone reading afterwards: the solution presented is setting org 0x1000, changing KERNEL_LOAD_SEGMENT to 0x0, the base of both segments of the gdt set to 0x0 and the length of the gdt changed to gdt_end - gdt_start - 1. Thank you – Burst Jul 19 '19 at 16:11

0 Answers0