"Higher Half x86 Bare Bones" from the Wiki triple faults

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.
ForceBru
Posts: 9
Joined: Thu Aug 20, 2020 2:02 pm

"Higher Half x86 Bare Bones" from the Wiki triple faults

Post by ForceBru »

Hello everybody!

I'm messing around with OS development and have successfully created a simple kernel with the help of the OSDev wiki. Initially I was using GDT for memory set up, but now decided to move to paging.

Looks like this tutorial https://wiki.osdev.org/Higher_Half_x86_Bare_Bones is about setting up paging, so I started with it. Unfortunately, the OS started to triple fault (according to VirtualBox).

I copied the code verbatim from the OSDev Wiki, created a fresh directory with all the files, stripped the kernel to the bare minimum, but it's still triple-faulting.
So I created an MCVE as a gist here: https://gist.github.com/ForceBru/4e3dd6 ... b03004efaa. It includes all the code needed to try it yourself.

The first issue is that it doesn't link:

Code: Select all

i386-elf-gcc -T linker.ld -ffreestanding -nostdlib -o kernel.elf boot.o kernel.o -lgcc
/usr/local/Cellar/i386-elf-gcc/9.2.0/lib/gcc/i386-elf/9.2.0/../../../../i386-elf/bin/ld: section .text LMA [0000000000100000,0000000000100049] overlaps section .multiboot.data LMA [0000000000100000,000000000010000b]
/usr/local/Cellar/i386-elf-gcc/9.2.0/lib/gcc/i386-elf/9.2.0/../../../../i386-elf/bin/ld: section .multiboot.text LMA [000000000010000c,0000000000100076] overlaps section .text LMA [0000000000100000,0000000000100049]
/usr/local/Cellar/i386-elf-gcc/9.2.0/lib/gcc/i386-elf/9.2.0/../../../../i386-elf/bin/ld: section .eh_frame LMA [000000000010004c,0000000000100083] overlaps section .multiboot.text LMA [000000000010000c,0000000000100076]
collect2: error: ld returned 1 exit status
I think this is because this code from the linker script:

Code: Select all

. = 0xC0100000;
/* Add a symbol that indicates the start address of the kernel. */
_kernel_start = .;
.text ALIGN (4K) : AT (ADDR (.text) - 0xC0000000)
{
	*(.text)
}
...puts .text at address 0x00100000, which is where .multiboot.data is loaded.

If I "fix" this by putting .text after .multiboot.text like this:

Code: Select all

/* Change linker.ld */
.text ALIGN (4K) : AT (ADDR (.text) - 0xC0000000 + __multiboot_end - 0x00100000)
{
	*(.text)
}
...it triplefaults. It constantly reboots in Qemu (more precisely, the text in the window just keeps randomly jumping around) when run with

Code: Select all

qemu-system-i386 -kernel kernel.elf
and causes Guru Meditation in VBox, whose logs tell me that it triple-faulted. If I run the iso with Qemu (qemu-system-i386 -cdrom kernel.iso), it constantly reboots.


Why is it triple-faulting (because I set up paging wrong, I guess) and how do I set up paging properly?
sj95126
Member
Member
Posts: 151
Joined: Tue Aug 11, 2020 12:14 pm

Re: "Higher Half x86 Bare Bones" from the Wiki triple faults

Post by sj95126 »

Can you clarify what this means?
Initially I was using GDT for memory set up, but now decided to move to paging.
It's not one or the other - you always have to have segments and a GDT in protected mode. What did you strip out of the example kernel?
Octocontrabass
Member
Member
Posts: 5574
Joined: Mon Mar 25, 2013 7:01 pm

Re: "Higher Half x86 Bare Bones" from the Wiki triple faults

Post by Octocontrabass »

ForceBru wrote:Why is it triple-faulting (because I set up paging wrong, I guess)
Take the guesswork out. Add "-d int" to your QEMU command line and examine the faults leading up to the triple fault. You'll be able to see what kind of fault it is at the very least, and it might be enough to tell you exactly what's wrong. (And if you can't figure it out, showing that log to us will help us locate the problem.)
nexos
Member
Member
Posts: 1081
Joined: Tue Feb 18, 2020 3:29 pm
Libera.chat IRC: nexos

Re: "Higher Half x86 Bare Bones" from the Wiki triple faults

Post by nexos »

I revitalized the linker script on that page a while back. When doing so, I introduced a bug in it. Take a look again and it should be fixed.
PS: I personally don't think that article does things optimally. If you continue having issues, take a look at the solution presented in this forum post.
"How did you do this?"
"It's very simple — you read the protocol and write the code." - Bill Joy
Projects: NexNix | libnex | nnpkg
ForceBru
Posts: 9
Joined: Thu Aug 20, 2020 2:02 pm

Re: "Higher Half x86 Bare Bones" from the Wiki triple faults

Post by ForceBru »

Octocontrabass wrote:
ForceBru wrote:Why is it triple-faulting (because I set up paging wrong, I guess)
Take the guesswork out. Add "-d int" to your QEMU command line and examine the faults leading up to the triple fault. You'll be able to see what kind of fault it is at the very least, and it might be enough to tell you exactly what's wrong. (And if you can't figure it out, showing that log to us will help us locate the problem.)
Here's a snippet of the output with "-d int,cpu_reset" (as suggested in another post viewtopic.php?f=1&t=25523):

Code: Select all

// <Stuff omitted...>

SMM: enter
EAX=000000b5 EBX=00007cf0 ECX=00005678 EDX=07fa9674
ESI=07fbdc62 EDI=07ef09d0 EBP=00006910 ESP=00006910
EIP=000f7cef EFL=00000006 [-----P-] CPL=0 II=0 A20=1 SMM=0 HLT=0
ES =0010 00000000 ffffffff 00c09300 DPL=0 DS   [-WA]
CS =0008 00000000 ffffffff 00c09b00 DPL=0 CS32 [-RA]
SS =0010 00000000 ffffffff 00c09300 DPL=0 DS   [-WA]
DS =0010 00000000 ffffffff 00c09300 DPL=0 DS   [-WA]
FS =0010 00000000 ffffffff 00c09300 DPL=0 DS   [-WA]
GS =0010 00000000 ffffffff 00c09300 DPL=0 DS   [-WA]
LDT=0000 00000000 0000ffff 00008200 DPL=0 LDT
TR =0000 00000000 0000ffff 00008b00 DPL=0 TSS32-busy
GDT=     000f6070 00000037
IDT=     000f60ae 00000000
CR0=00000011 CR2=00000000 CR3=00000000 CR4=00000000
DR0=00000000 DR1=00000000 DR2=00000000 DR3=00000000 
DR6=ffff0ff0 DR7=00000400
CCS=00000004 CCD=000068fc CCO=EFLAGS  
EFER=0000000000000000
SMM: after RSM
EAX=000000b5 EBX=00007cf0 ECX=00005678 EDX=07fa9674
ESI=07fbdc62 EDI=07ef09d0 EBP=00006910 ESP=00006910
EIP=00007cf0 EFL=00000002 [-------] CPL=0 II=0 A20=1 SMM=0 HLT=0
ES =dc00 000dc000 ffffffff 00809300
CS =f000 000f0000 ffffffff 00809b00
SS =0000 00000000 ffffffff 00809300
DS =0000 00000000 ffffffff 00809300
FS =0000 00000000 ffffffff 00809300
GS =ca00 000ca000 ffffffff 00809300
LDT=0000 00000000 0000ffff 00008200
TR =0000 00000000 0000ffff 00008b00
GDT=     00000000 00000000
IDT=     00000000 000003ff
CR0=00000010 CR2=00000000 CR3=00000000 CR4=00000000
DR0=00000000 DR1=00000000 DR2=00000000 DR3=00000000 
DR6=ffff0ff0 DR7=00000400
CCS=00000000 CCD=00000001 CCO=EFLAGS  
EFER=0000000000000000
check_exception old: 0xffffffff new 0xe
     0: v=0e e=0000 i=0 cpl=0 IP=0010:c0100000 pc=c0100000 SP=0018:0007ff00 CR2=2bb0cbaf
EAX=2badb002 EBX=00010000 ECX=c0100000 EDX=0010b003
ESI=0010c000 EDI=00106430 EBP=00000000 ESP=0007ff00
EIP=c0100000 EFL=00000086 [--S--P-] CPL=0 II=0 A20=1 SMM=0 HLT=0
ES =0018 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
CS =0010 00000000 ffffffff 00cf9a00 DPL=0 CS32 [-R-]
SS =0018 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
DS =0018 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
FS =0018 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
GS =0018 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=     000010b0 00000020
IDT=     00000000 00000000
CR0=80010011 CR2=2bb0cbaf CR3=00105000 CR4=00000000
DR0=00000000 DR1=00000000 DR2=00000000 DR3=00000000 
DR6=ffff0ff0 DR7=00000400
CCS=0010b100 CCD=80010011 CCO=LOGICL  
EFER=0000000000000000
check_exception old: 0xe new 0xd
     1: v=08 e=0000 i=0 cpl=0 IP=0010:c0100000 pc=c0100000 SP=0018:0007ff00 env->regs[R_EAX]=2badb002
EAX=2badb002 EBX=00010000 ECX=c0100000 EDX=0010b003
ESI=0010c000 EDI=00106430 EBP=00000000 ESP=0007ff00
EIP=c0100000 EFL=00000086 [--S--P-] CPL=0 II=0 A20=1 SMM=0 HLT=0
ES =0018 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
CS =0010 00000000 ffffffff 00cf9a00 DPL=0 CS32 [-R-]
SS =0018 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
DS =0018 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
FS =0018 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
GS =0018 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=     000010b0 00000020
IDT=     00000000 00000000
CR0=80010011 CR2=2bb0cbaf CR3=00105000 CR4=00000000
DR0=00000000 DR1=00000000 DR2=00000000 DR3=00000000 
DR6=ffff0ff0 DR7=00000400
CCS=0010b100 CCD=80010011 CCO=LOGICL  
EFER=0000000000000000
check_exception old: 0x8 new 0xd
Triple fault
CPU Reset (CPU 0)
EAX=2badb002 EBX=00010000 ECX=c0100000 EDX=0010b003
ESI=0010c000 EDI=00106430 EBP=00000000 ESP=0007ff00
EIP=c0100000 EFL=00000086 [--S--P-] CPL=0 II=0 A20=1 SMM=0 HLT=0
ES =0018 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
CS =0010 00000000 ffffffff 00cf9a00 DPL=0 CS32 [-R-]
SS =0018 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
DS =0018 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
FS =0018 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
GS =0018 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=     000010b0 00000020
IDT=     00000000 00000000
CR0=80010011 CR2=2bb0cbaf CR3=00105000 CR4=00000000
DR0=00000000 DR1=00000000 DR2=00000000 DR3=00000000 
DR6=ffff0ff0 DR7=00000400
CCS=0010b100 CCD=80010011 CCO=LOGICL  
EFER=0000000000000000
FCW=037f FSW=0000 [ST=0] FTW=00 MXCSR=00001f80
FPR0=0000000000000000 0000 FPR1=0000000000000000 0000
FPR2=0000000000000000 0000 FPR3=0000000000000000 0000
FPR4=0000000000000000 0000 FPR5=0000000000000000 0000
FPR6=0000000000000000 0000 FPR7=0000000000000000 0000
XMM00=00000000000000000000000000000000 XMM01=00000000000000000000000000000000
XMM02=00000000000000000000000000000000 XMM03=00000000000000000000000000000000
XMM04=00000000000000000000000000000000 XMM05=00000000000000000000000000000000
XMM06=00000000000000000000000000000000 XMM07=00000000000000000000000000000000
     2: v=03 e=0000 i=1 cpl=0 IP=0008:000f17ab pc=000f17ab SP=0010:00000fb4 env->regs[R_EAX]=000f6006
EAX=000f6006 EBX=00000000 ECX=00000000 EDX=00000cf9
ESI=00000000 EDI=00100000 EBP=00000000 ESP=00000fb4
EIP=000f17ab EFL=00000006 [-----P-] CPL=0 II=0 A20=1 SMM=0 HLT=0
ES =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
CS =0008 00000000 ffffffff 00cf9b00 DPL=0 CS32 [-RA]
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=     000f6070 00000037
IDT=     000f60ae 00000000
CR0=00000011 CR2=00000000 CR3=00000000 CR4=00000000
DR0=00000000 DR1=00000000 DR2=00000000 DR3=00000000 
DR6=ffff0ff0 DR7=00000400
CCS=000f60b8 CCD=00009f44 CCO=SUBL    
EFER=0000000000000000
check_exception old: 0xffffffff new 0xd
     3: v=0d e=001a i=0 cpl=0 IP=0008:000f17ab pc=000f17ab SP=0010:00000fb4 env->regs[R_EAX]=000f6006
EAX=000f6006 EBX=00000000 ECX=00000000 EDX=00000cf9
ESI=00000000 EDI=00100000 EBP=00000000 ESP=00000fb4
EIP=000f17ab EFL=00000006 [-----P-] CPL=0 II=0 A20=1 SMM=0 HLT=0
ES =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
CS =0008 00000000 ffffffff 00cf9b00 DPL=0 CS32 [-RA]
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=     000f6070 00000037
IDT=     000f60ae 00000000
CR0=00000011 CR2=00000000 CR3=00000000 CR4=00000000
DR0=00000000 DR1=00000000 DR2=00000000 DR3=00000000 
DR6=ffff0ff0 DR7=00000400
CCS=000f60b8 CCD=00009f44 CCO=SUBL    
EFER=0000000000000000
check_exception old: 0xd new 0xd
     4: v=08 e=0000 i=0 cpl=0 IP=0008:000f17ab pc=000f17ab SP=0010:00000fb4 env->regs[R_EAX]=000f6006
EAX=000f6006 EBX=00000000 ECX=00000000 EDX=00000cf9
ESI=00000000 EDI=00100000 EBP=00000000 ESP=00000fb4
EIP=000f17ab EFL=00000006 [-----P-] CPL=0 II=0 A20=1 SMM=0 HLT=0
ES =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
CS =0008 00000000 ffffffff 00cf9b00 DPL=0 CS32 [-RA]
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=     000f6070 00000037
IDT=     000f60ae 00000000
CR0=00000011 CR2=00000000 CR3=00000000 CR4=00000000
DR0=00000000 DR1=00000000 DR2=00000000 DR3=00000000 
DR6=ffff0ff0 DR7=00000400
CCS=000f60b8 CCD=00009f44 CCO=SUBL    
EFER=0000000000000000
check_exception old: 0x8 new 0xd
Triple fault
CPU Reset (CPU 0)

// <More logs...>
The full log is here: https://gist.github.com/ForceBru/4e3dd6 ... g_full-log.

And uhh, I'm not sure what I'm looking at here. "EIP=000f17ab" is not in the upper half. GDT and IDT are provided by GRUB, I think. Or do I need to explicitly create a flat memory model?
ForceBru
Posts: 9
Joined: Thu Aug 20, 2020 2:02 pm

Re: "Higher Half x86 Bare Bones" from the Wiki triple faults

Post by ForceBru »

sj95126 wrote:Can you clarify what this means?
Initially I was using GDT for memory set up, but now decided to move to paging.
It's not one or the other - you always have to have segments and a GDT in protected mode. What did you strip out of the example kernel?
Initially I didn't use paging at all and had GDT set up like this:

Code: Select all

; Setting up GDT:
; * http://www.osdever.net/tutorials/view/the-world-of-protected-mode
; * https://wiki.osdev.org/GDT
; * https://stackoverflow.com/questions/23978486/far-jump-in-gdt-in-bootloader
section .GDT_data
gdt_begin:

gdt_null:
	dq 0 ; qword (64 bytes) filled with zeros

gdt32_code:
	dw 0xFFFF ; limit[0:15]=4GiB
	dw 0x0000 ; base[00:15]=0
	
	db 0b00000000 ; base[16:23]=0
	db 0b10011010 ; access byte
	;    ^| |||||--- present
	;     ^^|||||--- ring 0
	;       ^||||--- code or data
	;        ^|||--- executable
	;         ^||--- nonconforming
	;          ^|--- readable
	;           ^ -- was accessed by CPU
	
	db 0b11001111 ; flags and limit[16:19]
	;    ^|| |   --- G: granularity: 4KiB
	;     ^| |   --- DB: 32-bit code 
	;      ^^|   --- R: 0=32-bit, 1=64-bit
	;        ^^^^--- limit[16:19]
	db 0b00000000 ; base[24:31]=0

gdt32_data:
	dw 0xFFFF ; limit=4GiB
	dw 0x0000 ; base[00:15]=0
	
	db 0b00000000 ; base[16:23]=0
	db 0b10010010 ; access byte
	;    ^| |||||--- present
	;     ^^|||||--- ring 0
	;       ^||||--- code or data
	;        ^|||--- non-executable
	;         ^||--- grows up
	;          ^|--- writable
	;           ^ -- was accessed by CPU
	
	db 0b11001111 ; flags and limit[16:19]
	;    ^|| |   --- granularity: 4KiB
	;     ^| |   --- 32-bit code 
	;      ^^|   --- 0 for 32-bit, 1 for 64-bit
	;        ^^^^--- limit[16:19]
	db 0b00000000 ; base[24:31]=0

gdt_end:

gdt_descriptor:
	dw gdt_end - gdt_begin - 1 ; size - 1
	dd gdt_begin               ; beginning of GDT


section .text
global _start:function (_start.end - _start)
_start:
	cli

	lgdt [gdt_descriptor] ; load GDT descriptor
	; set CS segment register
	; 0x08 = 0b0000000000001000
	;          ^^^^^^^^^^^^^||  --- index 1 of table (code segment)
	;                       ^|  --- index into GDT (not LDT)
	;                        ^^ --- privileged access (ring 0)
	jmp 0x08:.clear_pipeline
.clear_pipeline:
	; set all other segment registers
	; 0x10 = 0b0000000000010000
	;          ^^^^^^^^^^^^^||  --- index 2 of table (data segment)
	;                       ^|  --- index into GDT (not LDT)
	;                        ^^ --- privileged access (ring 0)
	mov ax, 0x10 ; load index 2 of GDT
	mov ds, ax
	mov es, ax
	mov fs, ax
	mov gs, ax
	mov ss, ax

	; Now we're in Protected mode with a proper GDT!

	; Set up the stack
	mov esp, stack_top

        call kernel_main

; infinite loop after the kernel returns
By "stripping out" I meant making the C code as simple as possible, just to test that it got to C code.

Do I need GDT setup to use paging, or is GRUB's GDT sufficient?

EDIT: added this GDT setup right after "_start" (before setting up paging - still triple-faults(
sj95126
Member
Member
Posts: 151
Joined: Tue Aug 11, 2020 12:14 pm

Re: "Higher Half x86 Bare Bones" from the Wiki triple faults

Post by sj95126 »

The GDT seems OK so the problem must be in enabling paging.

I would strongly recommend not just dropping in example code as a starting point. It's good as a reference, but paging is so fundamental that you have to understand it thoroughly. A load-high kernel is even more complicated. It's so easy to get confused in your head whether you're using linear or physical addresses as you create or update a page table.

When I adding paging to my kernel, I started by simply enabling a mapping of the lower 4 MB of the linear space to map to the equivalent physical space. Only once I felt comfortable with that did I move on to a load-high design, and I made a LOT of stumbles along the way. You'll probably want to do this on a copy or branch of your code tree rather than try to remove and then put back all the high-load references (e.g. 0xC0000000 and above). You'll want to do a lot of breakpoints and single-stepping to manually examine your page table in memory before and after enabling it.

I'd also recommend not using any protections or advanced features until you've got the basics. Don't worry about setting certain pages read-only. Don't try to exec-proof your stacks. None of those things will matter if your translations don't work.
Octocontrabass
Member
Member
Posts: 5574
Joined: Mon Mar 25, 2013 7:01 pm

Re: "Higher Half x86 Bare Bones" from the Wiki triple faults

Post by Octocontrabass »

ForceBru wrote:Do I need this GDT setup to use paging, or is GRUB's GDT sufficient?
You don't need to set up your own GDT in order to turn on paging, but after you get paging set up the way you want, you'll need to set up your own GDT in order to do much of anything useful.
ForceBru wrote:And uhh, I'm not sure what I'm looking at here. "EIP=000f17ab" is not in the upper half. GDT and IDT are provided by GRUB, I think. Or do I need to explicitly create a flat memory model?
That's after your kernel has triple-faulted and rebooted. Scroll up a bit to the first exception that happens while your kernel is running:

Code: Select all

check_exception old: 0xffffffff new 0xe
     0: v=0e e=0000 i=0 cpl=0 IP=0010:c0100000 pc=c0100000 SP=0018:0007ff00 CR2=2bb0cbaf
It's a page fault (v=0e) caused by reading a not-present page (e=0000) at a very strange address (CR2=2bb0cbaf), possibly caused by attempting to execute something that is not code.

Incidentally, if you execute your multiboot header, the first six bytes (02 b0 ad 1b 03 00) translates to "mov dh, [eax+0x31bad]". That would explain the strange address in CR2, wouldn't it?

But if you're jumping to the multiboot header, that means an address calculation somewhere must be wrong. The example code just adds 3GB to the physical address to get the virtual address, and the multiboot header is the first thing loaded at the 1MiB physical address, so the virtual address of the garbage instruction (IP=0010:c0100000) lines up. Sounds like a problem in the linker script.

Now, I'm no expert in linker scripts, but I think you want to replace this:

Code: Select all

. = 0xC0100000;
With this:

Code: Select all

. += 0xC0000000;
ForceBru
Posts: 9
Joined: Thu Aug 20, 2020 2:02 pm

Re: "Higher Half x86 Bare Bones" from the Wiki triple faults

Post by ForceBru »

sj95126 wrote:I would strongly recommend not just dropping in example code as a starting point. It's good as a reference, but paging is so fundamental that you have to understand it thoroughly.
I just wanted to see what the end result should like. But apparently this "good reference" doesn't actually work...
sj95126 wrote:You'll want to do a lot of breakpoints and single-stepping to manually examine your page table in memory before and after enabling it.
I'm doing it right now, trying to understand where it triple-faults. Here's the code and its disassembly:

Code: Select all

section .multiboot.text

global _start:function
_start:
	cli

	.gdt_setup:
		lgdt [gdt_descriptor]
                jmp 0x08:.clear_pipeline
	
	.clear_pipeline:
		mov ax, 0x10 ; load index 2 of GDT
		mov ds, ax
		mov es, ax
		mov fs, ax
		mov gs, ax
		mov ss, ax
	
	; Now we're using the flat memory model set up via GDT

	.setup_paging:
	; Physical address of boot_page_table1.
	mov edi, boot_page_table1 - 0xC0000000
	; First address to map is address 0.
	mov esi, 0
	; Map 1023 pages. The 1024th will be the VGA text buffer.
	mov ecx, 1023

._1:
	; Only map the kernel.
	cmp esi, (_kernel_start - 0xC0000000)
	jl ._2

	cmp esi, (_kernel_end - 0xC0000000)
	jge ._3

	; Map physical address as "present, writable"
	mov edx, esi
	or edx, 0x003
	mov [edi], edx

._2:
	add esi, 4096 ; Size of page is 4096 bytes.
	add edi, 4 ; Size of entries in boot_page_table1 is 4 bytes.
	loop ._1

._3:
	; Map VGA video memory to 0xC03FF000 as "present, writable".
	mov DWORD [boot_page_table1 - 0xC0000000 + 1023 * 4], (0x000B8000 | 0x003)

	; Map the page table to both virtual addresses 0x00000000 and 0xC0000000.
	mov DWORD [boot_page_directory - 0xC0000000 + 0], (boot_page_table1 - 0xC0000000 + 0x003)
	mov DWORD [boot_page_directory - 0xC0000000 + 768 * 4], (boot_page_table1 - 0xC0000000 + 0x003)

	; Set cr3 to the address of the boot_page_directory.
	mov ecx, (boot_page_directory - 0xC0000000)
	mov cr3, ecx

	; Enable paging and the write-protect bit.
	mov ecx, cr0
	or ecx, 0x80010000
	mov cr0, ecx

	; Jump to higher half with an absolute jump. 
	lea ecx, [_4]
	jmp ecx
.end:

Code: Select all

0x10002a    cli                                                                                                                
0x10002b    lgdtl  0x100024                                                                                                    
0x100032    ljmp   $0x8,$0x100039                                                                                              
0x100039    mov    $0x10,%ax                                                                                                   
0x10003d    mov    %eax,%ds                                                                                                    
0x10003f    mov    %eax,%es                                                                                                    
0x100041    mov    %eax,%fs                                                                                                    
0x100043    mov    %eax,%gs                                                                                                    
0x100045    mov    %eax,%ss                                                                                                    
0x100047    mov    $0x104000,%edi                                                                                              
0x10004c    mov    $0x0,%esi                                                                                                   
0x100051    mov    $0x3ff,%ecx                                                                                                 
0x100056    cmp    $0x1000b2,%esi                                                                                              
0x10005c    jl     0x10006d                                                                                                    
0x10005e    cmp    $0x109000,%esi                                                                                              
0x100064    jge    0x100078                                                                                                    
0x100066    mov    %esi,%edx                                                                                                   
0x100068    or     $0x3,%edx                                                                                                   
0x10006b    mov    %edx,(%edi)                                                                                                 
0x10006d    add    $0x1000,%esi                                                                                                
0x100073    add    $0x4,%edi                                                                                                   
0x100076    loop   0x100056
// _start._3:
B+ 0x100078    movl   $0xb8003,0x104ffc                                                                                          
0x100082    movl   $0x104003,0x103000                                                                                           
0x10008c    movl   $0x104003,0x103c00                                                                                          
0x100096    mov    $0x103000,%ecx
0x10009b    mov    %ecx,%cr3
0x10009e    mov    %cr0,%ecx
0x1000a1    or     $0x80010000,%ecx
>0x1000a7    mov    %ecx,%cr0     
0x1000aa    lea    0xc0101000,%ecx       ; lea ecx, [_4] 
0x1000b0    jmp    *%ecx
So I put a breakpoint at label "_start._3", after writing the page tables and stuff:

Code: Select all

(gdb) b *0x00100078
Breakpoint 2 at 0x100078
(gdb) c
Continuing.

Breakpoint 2, 0x00100078 in _start._3 ()
(gdb) si
0x00100082 in _start._3 ()
0x0010008c in _start._3 ()
0x00100096 in _start._3 ()
0x0010009b in _start._3 ()
0x0010009e in _start._3 ()
0x001000a1 in _start._3 ()
0x001000a7 in _start._3 ()
0x001000aa in _start._3 ()
Cannot access memory at address 0x100078
(gdb) libc++abi.dylib: terminating with uncaught exception of type gdb_exception_error

fish: 'gdb kernel.elf' terminated by signal SIGABRT (Abort)
So apparently "lea ecx, [_4]" (aka "lea 0xc0101000,%ecx") attempts to access address 0x100078???? Given that 0x00100078 is the address of the breakpoint, I'd say GDB is getting confused and printing nonsense. But there is an invalid memory access at this instruction anyway.
ForceBru
Posts: 9
Joined: Thu Aug 20, 2020 2:02 pm

Re: "Higher Half x86 Bare Bones" from the Wiki triple faults

Post by ForceBru »

Octocontrabass wrote:

Code: Select all

check_exception old: 0xffffffff new 0xe
     0: v=0e e=0000 i=0 cpl=0 IP=0010:c0100000 pc=c0100000 SP=0018:0007ff00 CR2=2bb0cbaf
It's a page fault (v=0e) caused by reading a not-present page (e=0000) at a very strange address (CR2=2bb0cbaf), possibly caused by attempting to execute something that is not code.

Now, I'm no expert in linker scripts, but I think you want to replace this:

Code: Select all

. = 0xC0100000;
With this:

Code: Select all

. += 0xC0000000;
OK, now my linker script looks like this:

Code: Select all

ENTRY (_start)

SECTIONS
{
    . = 0x00100000;
	/* The kernel will live at 3GB + 1MB in the virtual address space, */
	/* which will be mapped to 1MB in the physical address space. */
	/* Note that we page-align the sections. */

    .multiboot.data : {
        *(.multiboot.data)
    }

    .multiboot.text : {
        *(.multiboot.text)
    }

	. += 0xC0000000;
	/* Add a symbol that indicates the start address of the kernel. */
	_kernel_start = .;
	.text ALIGN (4K) : AT (ADDR (.text) - 0xC0000000)
	{
		*(.text)
	}
	.rodata ALIGN (4K) : AT (ADDR (.rodata) - 0xC0000000)
	{
		*(.rodata)
	}
	.data ALIGN (4K) : AT (ADDR (.data) - 0xC0000000)
	{
		*(.data)
	}
	.bss ALIGN (4K) : AT (ADDR (.bss) - 0xC0000000)
	{
		*(COMMON)
		*(.bss)
		*(.bootstrap_stack)
	}
	/* Add a symbol that indicates the end address of the kernel. */
	_kernel_end = .;
}
And the code is the same as in the Wiki https://wiki.osdev.org/Higher_Half_x86_ ... nes#boot.s plus GDT setup. Now the triple-fault looks like this:

Code: Select all

check_exception old: 0xffffffff new 0xe
     0: v=0e e=0000 i=0 cpl=0 IP=0008:001000aa pc=001000aa SP=0010:00006f00 CR2=001000aa
So "IP=0008:001000aa", and at this address I find:

Code: Select all

00100078 <_start._3>:
  100078:	c7 05 fc 4f 10 00 03 	movl   $0xb8003,0x104ffc
  10007f:	80 0b 00 
  100082:	c7 05 00 30 10 00 03 	movl   $0x104003,0x103000
  100089:	40 10 00 
  10008c:	c7 05 00 3c 10 00 03 	movl   $0x104003,0x103c00
  100093:	40 10 00 
  100096:	b9 00 30 10 00       	mov    $0x103000,%ecx
  10009b:	0f 22 d9             	mov    %ecx,%cr3
  10009e:	0f 20 c1             	mov    %cr0,%ecx
  1000a1:	81 c9 00 00 01 80    	or     $0x80010000,%ecx
  1000a7:	0f 22 c1             	mov    %ecx,%cr0
!!1000aa:	8d 0d 00 10 10 c0    	lea    0xc0101000,%ecx  <=== HERE!!!
  1000b0:	ff e1                	jmp    *%ecx
So, as I said in a comment above, the fault is caused by "lea 0xc0101000,%ecx", which is part of jumping to the higher kernel:

Code: Select all

; Jump to higher half with an absolute jump. 
lea ecx, [_4]
jmp ecx
I think I'll give up debugging this and go roll my own
nullplan
Member
Member
Posts: 1791
Joined: Wed Aug 30, 2017 8:24 am

Re: "Higher Half x86 Bare Bones" from the Wiki triple faults

Post by nullplan »

ForceBru wrote:So "IP=0008:001000aa",
You missed an important clue: CR2 is also 1000aa. It page faulted trying to read the instruction that is currently executing. And that just so happens to be the first instruction after enabling paging. Is it possible your page tables fail to include a proper identity mapping for the remaining trampoline area?
Carpe diem!
ForceBru
Posts: 9
Joined: Thu Aug 20, 2020 2:02 pm

Re: "Higher Half x86 Bare Bones" from the Wiki triple faults

Post by ForceBru »

nullplan wrote:a proper identity mapping for the remaining trampoline area
Wait, I need to decipher this :shock: "Identity mapping" is one-to-one mapping between a virtual and a physical address, so that virtual address, say, 0xbeef, maps to the same physical address 0xbeef, right?

What's the "trampoline area"? The code where I jump to the higher kernel?

As for whether the page table doesn't include this mapping - very much possible. How can I check this?
Octocontrabass
Member
Member
Posts: 5574
Joined: Mon Mar 25, 2013 7:01 pm

Re: "Higher Half x86 Bare Bones" from the Wiki triple faults

Post by Octocontrabass »

ForceBru wrote:"Identity mapping" is one-to-one mapping between a virtual and a physical address, so that virtual address, say, 0xbeef, maps to the same physical address 0xbeef, right?
Right.
ForceBru wrote:What's the "trampoline area"? The code where I jump to the higher kernel?
Yes.
ForceBru wrote:As for whether the page table doesn't include this mapping - very much possible. How can I check this?
Your code to create the page tables only includes addresses between "_kernel_start" and "_kernel_end". Take a look at your linker script: which sections are between those two labels? Which sections are not? Which section contains the trampoline code?
sj95126
Member
Member
Posts: 151
Joined: Tue Aug 11, 2020 12:14 pm

Re: "Higher Half x86 Bare Bones" from the Wiki triple faults

Post by sj95126 »

ForceBru wrote:
nullplan wrote:a proper identity mapping for the remaining trampoline area
Wait, I need to decipher this :shock: "Identity mapping" is one-to-one mapping between a virtual and a physical address, so that virtual address, say, 0xbeef, maps to the same physical address 0xbeef, right?

What's the "trampoline area"? The code where I jump to the higher kernel?
It's the remapped area that corresponds to the pre-paging and post-paging space. When you enable paging and jump to a load-high address (say, 0xC0000000) you have to maintain, for a short while, translations so that your previous addressing (starting at, say, 0x0) continues to be valid. The moment after you enable paging, whatever EIP points to has to continue to point somewhere, at least for a short while. So you have to have two linear spaces pointing to the same physical space, briefly.

Once you've reset EIP to run from the high load instruction path, you can remove that excess mapping from the page table.

So, for example:

- map 0xC0000000+ to point to physical pages 0x0+
- map 0x0+ to also point to physical pages 0x0+ (an additional entry in the PD pointing to the same PT as above)
- enable paging
- jump to [current position]+0xC0000000 (or, if you like, somewhere else in that linear range)
- remove mappings for 0x0+

Here's the excerpt from my boot sector (yes, I wrote my own boot sector and enable load-high paging there)

Code: Select all

        # trampoline into the adjusted linear space so we can run without
        # the lower page mappings
        pushl   $(KERNEL_BASE+finish)
        ret
finish:
        # a few last things: reload the GDT at the adjusted linear address
        addl    $KERNEL_BASE, gdt_data+2
        lgdt    gdt_data
        # and then remove the page directory entry for the lower pages
        movl    $PD_BASE, %eax
        movl    $0x0, (%eax)
As for whether the page table doesn't include this mapping - very much possible. How can I check this?
Well, you'll have to use whatever facilities your virtual machine has to examine the page table and see if both linear pages 0x0 and 0xC0000000 translate to physical page 0x0.
ForceBru
Posts: 9
Joined: Thu Aug 20, 2020 2:02 pm

Re: "Higher Half x86 Bare Bones" from the Wiki triple faults

Post by ForceBru »

Octocontrabass wrote:Your code to create the page tables only includes addresses between "_kernel_start" and "_kernel_end".
Mapping only such and such addresses looked too complicated, so I decided to remap everything from 0x0+ to 0xc0000000+, and I actually got it working!

I stuffed the page table and the page directory directly into the binary with NASM macros so that I could see what it looks like without remotely connecting to Qemu with GDB and poking around in memory.

For anyone else who will ever stumble upon the same problem, here's what I ended up with:

boot.s:

Code: Select all

; Declare a multiboot header that marks the program as a kernel.
MBALIGN  equ 1<<0  ; align loaded modules on page boundaries
MEMINFO  equ 1<<1    ; provide memory map
FLAGS    equ MBALIGN | MEMINFO   ; this is the Multiboot 'flag' field
MAGIC    equ 0x1BADB002    ; 'magic number' lets bootloader find the header
CHECKSUM equ -(MAGIC + FLAGS) ; checksum of above, to prove we are multiboot

section .multiboot.data
align 4
dd MAGIC
dd FLAGS
dd CHECKSUM


PAGE_SIZE equ 4096
align PAGE_SIZE
boot_page_table1:
    ; Identity map everything
	%assign physical_address 0
    %rep 1024
        %assign table_entry (physical_address | 0x003)
        ; %warning DDing table_entry
        dd table_entry
        %assign physical_address physical_address + PAGE_SIZE
    %endrep

%assign diff ($ - boot_page_table1)
%if diff != PAGE_SIZE
    %error Boot page table size is strange: diff, not PAGE_SIZE
%endif

boot_page_directory:
    %assign space 767 * 4
    dd (boot_page_table1 + 0x03) ; map to virtual address 0x00000000 (4 bytes) (entry #0)
	resb space
    dd (boot_page_table1 + 0x03) ; map to virtual address 0xC0000000 (4 bytes) (entry #768)
    ; `.multiboot.data` initially loaded at 0x00100000, so the mapping will point to
    ; (entry 768 * 0x00100000 * 4 bytes) = 0xc0000000
    ; => that section will be at virtual address 0xc0000000
    resb PAGE_SIZE - 4 * 2 - space

%assign diff ($ - boot_page_directory)
%if diff != PAGE_SIZE
    %error Boot page directory size is strange: diff, not PAGE_SIZE
%endif


gdt_begin:
	.gdt_null:
		dq 0

	.gdt32_code:
		dw 0xFFFF ; limit[0:15]=4GiB
		dw 0x0000 ; base[00:15]=0

		db 0b00000000 ; base[16:23]=0
		db 0b10011010 ; access byte

		db 0b11001111 ; flags and limit[16:19]=1111

		db 0b00000000 ; base[24:31]=0

	.gdt32_data:
		dw 0xFFFF ; limit[0:15]=4GiB
		dw 0x0000 ; base[00:15]=0

		db 0b00000000 ; base[16:23]=0
		db 0b10010010 ; access byte

		db 0b11001111 ; flags and limit[16:19]=1111

		db 0b00000000 ; base[24:31]=0
gdt_end:

gdt_descriptor:
	dw gdt_end - gdt_begin - 1 ; size - 1
	dd gdt_begin               ; beginning of GDT


; Allocate the initial stack.
section .bootstrap_stack nobits
stack_bottom:
resb 16384 ; 16 KiB
stack_top:


extern _kernel_start
extern _kernel_end
extern kernel_main


; The kernel entry point.
section .multiboot.text
VGA_COLOR_WHITE equ 15
VGA_COLOR_RED equ 4
VGA_COLOR_ERR equ VGA_COLOR_WHITE | (VGA_COLOR_RED << 4)

%macro putchar 2
    mov word [%1], %2
    inc %1
    mov word [%1], VGA_COLOR_ERR
    inc %1
%endmacro

global _start:function
_start:
	cli

	.gdt_setup:
		lgdt [gdt_descriptor]

		; set CS segment register
		; 0x08 = 0b[0000000000001] [0] [00]
		;          ^ --------------|---|---- index 1 of table (code segment)
		;                          ^ --|---- index into GDT (not LDT)
		;                              ^---- privileged access (ring 0 of 3)
		jmp 0x08:.clear_pipeline
	
	.clear_pipeline:
		; set all other segment registers (see format above)
		mov ax, 0x10 ; load index 2 of GDT
		mov ds, ax
		mov es, ax
		mov fs, ax
		mov gs, ax
		mov ss, ax

    mov eax, 0xb8000 + 2
    putchar eax, 'G'
	
	; Now we're using the flat memory model set up via GDT
    .setup_paging:
        mov ecx, boot_page_directory
        mov cr3, ecx

        putchar eax, 'B'

        ; Enable paging and write-protect bit
        mov ecx, cr0
        or ecx, 0x80010000
        mov cr0, ecx

        putchar eax, 'P'
        lea ecx, [_higher_half_start.halt]
        ;jmp .hang

        ; Jump to higher-half
        lea ecx, [_higher_half_start]
        jmp ecx
.hang:
    cli
    hlt
    jmp .hang


section .text

_higher_half_start:
	; At this point, paging is fully set up and enabled.
    ; Identity mapping is still valid

	; Unmap the identity mapping as it is now unnecessary. 
	mov DWORD [boot_page_directory + 0], 0

	; Reload crc3 to force a TLB flush so the changes to take effect.
	mov ecx, cr3
	mov cr3, ecx

	; Set up the stack.
	mov esp, stack_top

	; Enter the high-level kernel.
	call kernel_main

	; Infinite loop if the system has nothing more to do.
.halt:
	cli
        hlt
	jmp .halt
And "linker.ld":

Code: Select all

ENTRY (_start)

SECTIONS
{
    . = 0x00100000;
	/* The kernel will live at 3GB + 1MB in the virtual address space, */
	/* which will be mapped to 1MB in the physical address space. */
	/* Note that we page-align the sections. */

    .multiboot.data : {
        *(.multiboot.data)
    }

    .multiboot.text : {
        *(.multiboot.text)
    }

	. += 0xC0000000; /* THIS IS IMPORTANT! */

	/* Add a symbol that indicates the start address of the kernel. */
	_kernel_start = .;
	.text ALIGN (4K) : AT (ADDR (.text) - 0xC0000000)
	/*
	 * Will be put at LMA (offset in executable) (0x00100000 + sizeof(.multiboot) + 0xC0000000) - 0xC0000000 = 0x00100000 + sizeof(.multiboot)
	 * Will be loaded at ALIGN(0x00100000 + sizeof(.multiboot) + 0xC0000000; 4K) >= c0100000
	 */
	{
		*(.text)
	}
	.rodata ALIGN (4K) : AT (ADDR (.rodata) - 0xC0000000)
	{
		*(.rodata)
	}
	.data ALIGN (4K) : AT (ADDR (.data) - 0xC0000000)
	{
		*(.data)
	}
	.bss ALIGN (4K) : AT (ADDR (.bss) - 0xC0000000)
	{
		*(COMMON)
		*(.bss)
		*(.bootstrap_stack)
	}
	/* Add a symbol that indicates the end address of the kernel. */
	_kernel_end = .;
}
How I fixed it:
  • No clue whatsoever
  • Put the tables at the beginning of the binary, after Multiboot stuff (in ".multiboot.data") and also in the same LMA. No more ugly physical address calculation!
  • Got rid of seemingly unnecessary logic to map only the kernel. Instead mapped everything
  • The code

    Code: Select all

    lea ecx, [_4]
    jmp ecx
    
    ...somehow transformed into jumping to EAX (!) - no wonder it was triple-faulting! No idea how EAX got there in the first place, it was fine in my original gist.
  • The iso creation got messed up too, so while "qemu-system-i386 -kernel kernel.elf" worked, booting from the iso triple-faulted.
I feel like all of this is a huge hack and shouldn't really work, but right now it does, so hell yeah! =D> =D>
Post Reply