Page 1 of 1

Real computer reboots while setting ds in long mode

Posted: Sat Aug 03, 2024 9:14 am
by Xthoa
I use multiboot2 as the bootloader. After getting control from it I switch to long mode, and set the segment registers ds, es, etc. to the 64-bit segment selector. On virtual machines, it works all well but on 2 real computers i've tested on, it leads to a reboot at just the instruction to set ds: putting hlt before it makes the computer hang normally.

The segment descriptors look fine, the page tables are correctly set, I couldn't think of any other possible cause. Does anyone have ideas?

Tested vm: qemu6.2.0, vmware17 and bochs2.8

entry.s, in nasm syntax, 'entry32' is the entry point:

Code: Select all

section .entry align=16

%macro Descriptor 3
	dw %2 & 0FFFFh
	dw %1 & 0FFFFh
	db ( %1 >> 16) & 0FFh
	dw (( %2 >> 8) & 0F00h) | ( %3 & 0F0FFh)
	db ( %1 >> 24) & 0FFh
%endmacro

SEG_RPL equ 1
DPL equ 0x20
SEG64 equ 0x2000
SEG32 equ 0x4000
SEG_PG4K equ 0x8000
SEG_P equ 0x80
SEG_SEG equ 0x10
SEG_AC equ 0x1	;accessed
SEG_RW equ 0x2
SEG_DR equ 0x4	;direction
SEG_EX equ 0x8

SEG_BASE equ SEG_P|SEG_SEG
SEG_DATA equ SEG_BASE|SEG_RW
SEG_CODE equ SEG_BASE|SEG_RW|SEG_EX
SEG_DATA32 equ SEG_DATA|SEG32|SEG_PG4K
SEG_CODE32 equ SEG_CODE|SEG32|SEG_PG4K
SEG_DATA64 equ SEG_DATA|SEG32   ; ?
SEG_CODE64 equ SEG_CODE|SEG64
SEG_CODE16 equ SEG_CODE

GDT_DEST_PHY equ 0x40000
IDT_PHY equ 0x41000

RM16_ENTRY_PHY equ 0x4000

GDT:
	Descriptor 0,0,0
	Descriptor 0,0xfffff,SEG_CODE64
	Descriptor 0,0,SEG_DATA64
	Descriptor 0,0,SEG_DATA64+DPL*3
	Descriptor 0,0,SEG_CODE64+DPL*3
	Descriptor 0,0xfffff,SEG_DATA32
	Descriptor 0,0xfffff,SEG_CODE32
	Descriptor 0,0xfffff,SEG_DATA32+DPL*3
	Descriptor 0,0xfffff,SEG_CODE32+DPL*3
    Descriptor 0,0xffff,SEG_CODE16
align 8
GDTR32:
	dw 0xff
	dd GDT-$$+0x100000

bits 32

PML4_64_PHY equ 0x20000
PDPT_64_PHY equ 0x21000
PDPTS_64_PHY equ 0x22000    ; bootstub pdpt
PD0_64_PHY equ 0x23000
PT0_64_PHY equ 0x24000
PDS_64_PHY equ 0x25000

BOOT_ARG_PHY equ 0x2c000
BA_VIDEO_OFF equ 0x200
BA_CMDLINE_OFF equ 0x380
BA_ARDS_OFF equ 0x400   ; bootargs ARDS offset
BA_XSDP_OFF equ 0x500
BA_XSDP_AVAIL_OFF equ 8
BA_XSDP_PHY_OFF equ 12
BA_ARDS_COUNT_OFF equ 0
BA_CMDLINE_LEN_OFF equ 4
BA_ARCHPAG_OFF equ 0x14
BA_ARCHPHY_OFF equ 0x10

STACK1_PHY equ 0x10000
STACK_RM_PHY equ 0xe000

align 16
extern entry32
entry32:
; reset 32bit environment
lgdt [GDTR32-$$+0x100000]
jmp 0x30: .next32-$$+0x100000

.next32:
mov ax, 0x28
mov ds, ax
mov es, ax
mov ss, ax
mov esp, STACK1_PHY
mov ebp, BOOT_ARG_PHY

; Parse Multiboot2 info

    multiboot_magic equ 0xe85250d6 ; Multiboot 2 magic number

    ; 设置Multiboot 2 magic number和info结构指针
    mov eax, multiboot_magic

    ; 检查Multiboot 2 magic number
    cmp eax, 0xe85250d6
    jne invalid_multiboot ; 如果不匹配,跳转到错误处理

    ; 解析Multiboot 2信息
    add ebx, 8

parse_tags:
    mov eax, [ebx] ; 读取tag的类型字段
    cmp eax, 0 ; 如果为0,表示Tag结束
    je end_parse_tags

    ; 解析内存表信息 (Tag 6)
    cmp eax, 6
    je parse_memory_tag

    ; 解析内核命令行 (Tag 1)
    cmp eax, 1
    je parse_cmdline_tag

    ; 解析framebuffer信息 (Tag 8)
    cmp eax, 8
    je parse_framebuffer_tag

    cmp eax, 3
    je parse_archive_tag

    cmp eax, 14
    je parse_rsdp_tag

    cmp eax, 15
    je parse_xsdp_tag

    jmp next_tag ; 跳转到下一个tag

parse_memory_tag:
    mov ecx, [ebx + 4] ; 获取内存表的大小
    sub ecx, 32
    mov edx, ecx
    shr ecx, 2  ; /4
    lea esi, [ebx + 16]
    lea edi, [ebp + BA_ARDS_OFF]
    rep movsd   ; move once per 4byte
    mov eax, 0xaaaaaaab     ; /24
    mul edx
    shr edx, 4
    mov [ebp + BA_ARDS_COUNT_OFF], edx
    jmp next_tag

parse_cmdline_tag:
    mov esi, ebx
    mov ecx, [ebx + 4] ; 获取命令行字符串的length
    sub ecx, 8
    lea edi, [ebp + BA_CMDLINE_OFF]
    rep movsb
    jmp next_tag

parse_framebuffer_tag:
    lea esi, [ebx + 8]
    lea edi, [ebp + BA_VIDEO_OFF]
    mov ecx, 21
    rep movsb
    jmp next_tag

parse_archive_tag:
    mov esi, [ebx + 8]
    mov ecx, [ebx + 12]
    sub ecx, esi
    add ecx, 0xfff
    shr ecx, 12
    mov [ebp + BA_ARCHPHY_OFF], esi
    mov [ebp + BA_ARCHPAG_OFF], ecx
    jmp next_tag

parse_rsdp_tag:
    mov edi, [ebp + BA_XSDP_AVAIL_OFF]
    test edi, edi   ; prioritize xsdp
    jnz next_tag
parse_xsdp_tag:
    lea esi, [ebx + 8]
    mov ecx, [ebx + 4]
    lea edi, [ebp + BA_XSDP_OFF]
    rep movsb
    lea edi, [ebp + BA_XSDP_AVAIL_OFF]
    mov dword [edi], 1

next_tag:
    add ebx, [ebx + 4] ; 跳到下一个tag
    add ebx, 7
    and ebx, 0xfffffff8 ; align ebx to 8 (tags start at 8-aligned addr)
    jmp parse_tags

end_parse_tags:

; find xsdp ourselves if not provided

mov eax, [ebp + BA_XSDP_AVAIL_OFF]
test eax, eax
jnz skip_find_rsdp

call find_rsdp32
mov [ebp + BA_XSDP_PHY_OFF], eax
jmp skip_find_rsdp

find_rsdp32:
    ; ...
    ret

find_rsdp_tag32:
    ; ...
    ret
skip_find_rsdp:

; clear screen
mov eax, 0x07000700
mov edi, 0xb8000
mov ecx, 80*25*2
rep stosd

; copy GDT
mov esi, GDT-$$+0x100000
mov edi, GDT_DEST_PHY
mov ecx, 10*8/4
rep movsd

; setup page tables
mov eax,PML4_64_PHY
mov cr3,eax
xor ecx, ecx    ; ecx = 0

; zeroize page table memory !

mov dword [eax+0xff8],0x3+PML4_64_PHY   ; trick on self-refing
mov [eax+0xffc], ecx
mov dword [eax+0xfe8],0x3+PDPT_64_PHY
mov [eax+0xfec], ecx
mov ebx,PDPT_64_PHY
mov dword [ebx],0x3+PD0_64_PHY
mov [ebx+4], ecx
mov ebx,PD0_64_PHY
mov dword [ebx],0x3+PT0_64_PHY
mov [ebx+4], ecx

mov dword [eax],0x3+PDPTS_64_PHY
mov [eax+4], ecx
mov ebx,PDPTS_64_PHY
mov dword [ebx],0x3+PDS_64_PHY
mov [ebx+4], ecx
mov ebx,PDS_64_PHY
mov dword [ebx],0x83
mov [ebx+4], ecx

xor ebp, ebp    ; used by fill_pt32
mov edi,PT0_64_PHY
mov eax,0x100003 ; kernel
mov ecx,128
call _boot_fill_pt32
mov eax,3   ; low 0.5m mem
mov ecx,128
call _boot_fill_pt32

; enter 64bit
cli
mov eax,cr4
or eax,0x2b0
mov cr4,eax
mov ecx,0xc0000080
rdmsr
or eax,0x901
wrmsr
mov eax,cr0
or eax, 0x80000000
mov cr0,eax
jmp 8: entry64-$$+0x100000

_boot_fill_pt32:
    stosd
    add edi,4
    mov [edi], ebp
    add eax,0x1000
    loop _boot_fill_pt32
    ret

align 8
GDTR64:
	dw 0x3ff
	dq 0xfffffe8000080000+GDT_DEST_PHY
IDTR64:
	dw 0xfff
	dq 0xfffffe8000080000+IDT_PHY

bits 64
entry64:

lgdt [rel GDTR64]
lidt [rel IDTR64]
mov ax,16
mov ds,ax    ; <--- The Exact Crash Point
mov es,ax
mov ss,ax
mov fs,ax
mov gs,ax
mov rbx, 0xfffffe80_00000000
mov rax, rbx
add rax, .higher-$$
jmp rax

.higher:
or rbx, 0x80000     ; add
lea rsp, [rbx + STACK1_PHY]
or rbx, BOOT_ARG_PHY    ; add
mov rdi, rbx

extern kmain
call kmain

invalid_multiboot:
_boot_error:
xchg bx, bx
cli
hlt

Re: Real computer reboots while setting ds in long mode

Posted: Sat Aug 03, 2024 11:13 pm
by Octocontrabass
Have you tried loading DS with a null segment instead of a data segment?

Have you tried using the virtual machines to make sure GDTR actually points to your GDT when you load DS?

You have an awful lot of fixed physical addresses in your code. Did you check the memory map to make sure those physical addresses are available on your PCs?

When you load a segment register, the CPU checks the "accessed" bit in the descriptor. If that bit is clear, the CPU tries to set it. If the GDT is located in a read-only page, there will be a page fault. Does it still reboot if you add SEG_AC to the segment descriptor?

Re: Real computer reboots while setting ds in long mode

Posted: Sun Aug 04, 2024 3:54 am
by Xthoa
Thanks for reply!

It turns out that the memory where i place the page tables isn't accessible. I shifted the address to a higher area and it no longer reboots.

Still, I wonder why that area (0x20000 ~ 0x25fff) is unaccessible. it should be available for all x86Pc according to [wiki]Memory_Map_(x86)[/wiki].

Re: Real computer reboots while setting ds in long mode

Posted: Sun Aug 04, 2024 2:40 pm
by Octocontrabass
Are you sure the problem is inaccessible memory and not uninitialized memory? It looks like your _boot_fill_pt32 function doesn't work correctly.

Re: Real computer reboots while setting ds in long mode

Posted: Mon Aug 05, 2024 12:40 am
by rdos
Emulators tend not to check segment register loads & limits properly, while real hardware does. This is probably particularly the case for long mode, where people believe selector loads doesn't work the normal way (they do).

Re: Real computer reboots while setting ds in long mode

Posted: Mon Aug 05, 2024 2:13 am
by nullplan
I am not the biggest fan of all these hardcoded addresses. Particularly the code address calculations seem to assume one specific load address for the loader binary, and I don't know where that is coming from. I am not quite certain whether multiboot2 guarantees a load address, but your code is just indecisive. If load address is guaranteed, then, for example
"jmp 0x30:.next32-$$+0x100000" should just be "jmp 0x30:.next32" and you should link everything correctly. If load address is not guaranteed, then you need an indirect jump or self-modifying code. I'd go for the former, and it would be

Code: Select all

start32ptr: dd .next32-$$
  dw 0x30
  ...
  call .getip
 .getip:
   pop ecx
   sub ecx, .getip-$$
   add dword [start32ptr-$$+ecx], ecx
   jmp far [start32ptr]
Your code simulates being able to be loaded anywhere, but isn't, actually. Another thing is that you don't really check the multiboot magic. You do that probably because you overwrite ax before checking the magic, so how about you do it the other way around?

I am also not the biggest fan of placing the paging structures just in a fixed place. Just get the multiboot info, it tells you where there's free memory. In fact, I tell you to separate the 32-bit and 64-bit parts of the kernel into different binaries. That way, the tool chains don't get confused.

I just saw that you load the GDT again in 64-bit mode. In that case, I don't know why you are doing that at all in 32-bit mode in the first place.