Page 1 of 1

C++ Higher Half Kernel Loader

Posted: Sat Sep 01, 2007 4:09 pm
by paul
I've been rewriting my kernel and it now lives in the higher half (using paging, not the gdt trick), however I have a problem. The following code works fine:

Code: Select all

extern "C" void kmain(void *mboot, uint magic)
{
	VGAConsole cout;

	cout << "Phobos Version 0.1" << endl;
However, if I move the declaration of cout outside the method (which I need to do so it can be used elsewhere), then the code locks up qemu on boot (haven't tried bochs yet as I need to build it with pse support).

I don't know how the output of GCC differs for the declarations inside & outside of a method, but figured someone here will and might be able to hint at what I'm doing wrong. The asm loader for my kernel is below, where I call the constructors for globals, I'm not sure if this is whats wrong or not...

Code: Select all

global _loader								; ASM Entry Point
extern kmain								; C++ Code Entry Point
extern start_ctors, end_ctors, start_dtors, end_dtors

MODULEALIGN equ  1<<0							; Align Modules on Page Boundaries Flag
MEMINFO     equ  1<<1							; Memory Map Flag
FLAGS       equ  MODULEALIGN | MEMINFO					; Flags
MAGIC       equ  0x1BADB002						; Multiboot Magic Number
CHECKSUM    equ -(MAGIC + FLAGS)					; Checksum

KERNEL_VIRTUAL_BASE equ 0xC0000000					; 3GiB
KERNEL_PAGE_NUMBER equ (KERNEL_VIRTUAL_BASE >> 22)			; Page directory index of kernel's 4MB PTE.

section .data
align 0x1000
BootPageDirectory:
	; 0x00000083 = PS,RW,P

	dd 0x00000083							; Low kernel space
	times (KERNEL_PAGE_NUMBER - 1) dd 0				; Pages before kernel space.
	dd 0x00000083							; High kernel space
	times (1024 - KERNEL_PAGE_NUMBER - 1) dd 0			; Pages after the kernel image.

section .text
align 4
MultiBootHeader:
	dd MAGIC
	dd FLAGS
	dd CHECKSUM

STACKSIZE equ 0x4000							; Initial kernel stack (16KiB)

_loader:
	mov ecx, (BootPageDirectory - KERNEL_VIRTUAL_BASE)
	mov cr3, ecx							; Load Page Directory Base Register

	mov ecx, cr4
	or ecx, 0x00000010						; Set PSE bit in CR4 to enable 4MB pages
	mov cr4, ecx

	mov ecx, cr0
	or ecx, 0x80000000						; Set PG bit in CR0 to enable paging
	mov cr0, ecx

	lea ecx, [StartInHigherHalf]					; Begin instructing in higher half
	jmp ecx								; Must be absolute jump!

StartInHigherHalf:
	mov esp, stack+STACKSIZE					; Setup Stack

	add ebx, KERNEL_VIRTUAL_BASE					; Convert multiboot info physical addr to virtual

	mov ecx, ebx							; Copy multiboot info addr to ecx
	add ecx, 16							; Add 16 (ecx now holds address of cmdline pointer)
	mov edx, [ecx]							; Move cmdline pointer to edx
	add edx, KERNEL_VIRTUAL_BASE					; Convert cmdline addr to virtual
	mov [ecx], edx							; Copy virtual addr back to multiboot info struct

	push eax							; Pass multiboot magic number
	push ebx							; Pass multiboot info struct, may not be in 1st 4mb

static_ctors_loop:							; Run constructors of global objects
	mov ebx, start_ctors
	jmp .test
.body:
	call [ebx]
	add ebx,4
.test:
	cmp ebx, end_ctors
	jb .body

	mov dword [BootPageDirectory], 0				; Set page dir. entry 0 to null
	invlpg [0]							; Invalidate page dir. entry 0

	call  kmain							; Call C++ code (should never return)

	cli								; Stop interrupts
	hlt								; Halt the machine

section .bss
align 32
stack:
	resb STACKSIZE							; Reserve 16KiB stack on a quadword boundary
The asm is largely borrowed from the higher half example on the wiki, but modified to call the constructors and to pass the multiboot structure as a virtual address rather than physical (so the C++ code doesn't need to translate).

One thought: can GCC emit calls to the new operator in cases like this? If so, mine currently returns 0 which might be the problem.

Also, in the above code I convert the multiboot cmdline pointer to a virtual address, but I'm far from an assembly expert and wondered if there's any better way to do it? The code I've written works fine, but it seems like a bodge to me (moving addresses around, adding & moving them back), so I wondered if any assembler gurus know of a better way (not a major issue, I just dislike my current code and don't know any way to improve it). It'd be useful to know of any better method to offset the pointers held by the structure before I do the same for the memory map.

Finally, I'll just state that yeah I know the multiboot structure isn't guaranteed to be inside the first 4MiB of physical memory and if it isn't my code will cause a page fault, but with grub legacy it always is and thats good enough for now.

Posted: Sat Sep 01, 2007 4:18 pm
by paul
OK, I just figured out I can do:

Code: Select all

	mov ecx, ebx							; Copy multiboot info addr to ecx
	add ecx, 16							; Add 16 (ecx now holds address of cmdline pointer)
	add [ecx], LONG KERNEL_VIRTUAL_BASE				; Convert cmdline addr to virtual
which is a little neater to offset the cmdline pointer, would be nice to scrap the first add somehow though.

EDIT:

Much better :D

Code: Select all

	add [ebx+16], dword KERNEL_VIRTUAL_BASE				; Convert cmdline addr to virtual
Still got the globals issue though

Posted: Sat Sep 01, 2007 6:19 pm
by frank
I hope your not relying on anything like memory management in the constructor for cout. In my kernel I try to avoid using Constructors unless I know I will be dynamically allocating the class. That way I know exactly when the constructor is going to be called.

Posted: Sat Sep 01, 2007 8:19 pm
by paul
The VGAConsole constructor just sets some private variables (colors, vga memory pointer), clears the screen & moves the cursor to 0,0.

Code: Select all

VGAConsole::VGAConsole()
{
	// VGA Memory
	//   Phys: 0x000B8000;
	//   Virt: 0xC00B8000;
	vgaMem = (char *)0xC00B8000;

	screenWidth = 80;
	screenHeight = 25;
	
	backColor = Black;
	foreColor = White;
	
	Clear();
}

void VGAConsole::Clear()
{
	unsigned short blank = 0x20 | (((backColor << 4) | (foreColor & 0x0F)) << 8);
	mem.SetW(vgaMem, blank, screenWidth * screenHeight);

	xPos = 0;
	yPos = 0;

	MoveCursor();
}

Posted: Sat Sep 01, 2007 10:35 pm
by frank
You could try writing characters to the screen to see how far you get. Example:

Code: Select all

mov BYTE PTR[0xB8000], '1'
; some code
mov BYTE PTR[0xB8002], '2' 
That way you can establish the exact location it locks up or alternatively you could use cli hlt but since it locks up anyways I don't think that would help much.

Posted: Sun Sep 02, 2007 3:09 am
by JamesM
You need to provide global initialisation functions for global constructors to be called. See the C++-barebones article on the wiki. Basically, at startup, G++ will normally add some code that calls all your globals' constructors for you. It can't do that, because you have specified -fno-builtin, so YOU have to do it yourself. It basically involves trawling through the __init_ctors section of your ELF file and calling what you find.

JamesM

Posted: Sun Sep 02, 2007 6:09 am
by paul
frank:

Thanks, I'll try that. I suspect it's locking up calling the constructors, I'll find out when I get time later today.

JamesM:
JamesM wrote:You need to provide global initialisation functions for global constructors to be called. See the C++-barebones article on the wiki. Basically, at startup, G++ will normally add some code that calls all your globals' constructors for you. It can't do that, because you have specified -fno-builtin, so YOU have to do it yourself. It basically involves trawling through the __init_ctors section of your ELF file and calling what you find.
Which is in the asm above (and yes the areas are in my linker script):

Code: Select all

static_ctors_loop:                     ; Run constructors of global objects
   mov ebx, start_ctors
   jmp .test
.body:
   call [ebx]
   add ebx,4
.test:
   cmp ebx, end_ctors
   jb .body 

Posted: Sun Sep 02, 2007 6:32 am
by urxae
You wouldn't happen to be using '--gc-sections' and forgetting to KEEP() the constructor sections?
(Perhaps you should show your linker script and ld command line)

Posted: Sun Sep 02, 2007 7:44 am
by paul
urxae wrote:(Perhaps you should show your linker script and ld command line)

Code: Select all

ENTRY(_loader)
OUTPUT_FORMAT(elf32-i386)

SECTIONS {
   /* 3GiB + 1MiB */
   . = 0xC0100000;

   .text : AT(ADDR(.text) - 0xC0000000) {
       *(.text)
       *(.gnu.linkonce.t.*)		/* C++ Templates ? */
       *(.rodata*)
       *(.gnu.linkonce.r.*)		/* Read Only Data (ELF) */

       start_ctors = .;
	   *(SORT(.ctor*))
	   end_ctors = .;
	   start_dtors = .;
	   *(SORT(.dtor*))
	   end_dtors = .;
	   . = ALIGN(4096);
   }

   .data ALIGN (0x1000) : AT(ADDR(.data) - 0xC0000000) {
       *(.data)
   }

   .bss : AT(ADDR(.bss) - 0xC0000000) {
       _sbss = .;
       *(COMMON)
       *(.bss)
       *(.gnu.linkonce.b.*)
       _ebss = .;
   }
}
ld is called from a makefile:

Code: Select all

	@ld -T arch/$(ARCH)/linker.ld --oformat elf32-i386 -o $(DESTBIN) $(ASMOBJFILES) $(OBJFILES)

Posted: Sun Sep 02, 2007 2:04 pm
by paul
OK, the system locks up once the call [ebx] instruction is executed, so its definately something inside the constructor which is being called.

VGAConsole inherits from Console, and its constructor as given by objdump is as follows:

Code: Select all

c0100a86 <_ZN10VGAConsoleC2Ev>:
c0100a86:	83 ec 0c             	sub    esp,0xc
c0100a89:	8b 44 24 10          	mov    eax,DWORD PTR [esp+0x10]
c0100a8d:	89 04 24             	mov    DWORD PTR [esp],eax
c0100a90:	e8 0b 05 00 00       	call   c0100fa0 <_ZN7ConsoleC2Ev>
c0100a95:	ba e8 20 10 c0       	mov    edx,0xc01020e8
c0100a9a:	8b 44 24 10          	mov    eax,DWORD PTR [esp+0x10]
c0100a9e:	89 10                	mov    DWORD PTR [eax],edx
c0100aa0:	8b 44 24 10          	mov    eax,DWORD PTR [esp+0x10]
c0100aa4:	c7 40 04 00 80 0b c0 	mov    DWORD PTR [eax+0x4],0xc00b8000
c0100aab:	8b 44 24 10          	mov    eax,DWORD PTR [esp+0x10]
c0100aaf:	c7 40 10 50 00 00 00 	mov    DWORD PTR [eax+0x10],0x50
c0100ab6:	8b 44 24 10          	mov    eax,DWORD PTR [esp+0x10]
c0100aba:	c7 40 14 19 00 00 00 	mov    DWORD PTR [eax+0x14],0x19
c0100ac1:	8b 44 24 10          	mov    eax,DWORD PTR [esp+0x10]
c0100ac5:	c7 40 08 00 00 00 00 	mov    DWORD PTR [eax+0x8],0x0
c0100acc:	8b 44 24 10          	mov    eax,DWORD PTR [esp+0x10]
c0100ad0:	c7 40 0c 0f 00 00 00 	mov    DWORD PTR [eax+0xc],0xf
c0100ad7:	8b 44 24 10          	mov    eax,DWORD PTR [esp+0x10]
c0100adb:	89 04 24             	mov    DWORD PTR [esp],eax
c0100ade:	e8 bd fe ff ff       	call   c01009a0 <_ZN10VGAConsole5ClearEv>
c0100ae3:	83 c4 0c             	add    esp,0xc
c0100ae6:	c3                   	ret
The call to _ZN7ConsoleC2Ev is what interests me, as Console has no constructor, so I assume G++ generates one automatically... The code for that is:

Code: Select all

Disassembly of section .text._ZN7ConsoleC2Ev:

c0100fa0 <_ZN7ConsoleC2Ev>:
c0100fa0:	ba c8 20 10 c0       	mov    edx,0xc01020c8
c0100fa5:	8b 44 24 04          	mov    eax,DWORD PTR [esp+0x4]
c0100fa9:	89 10                	mov    DWORD PTR [eax],edx
c0100fab:	c3                   	ret
As you can tell from above, I'm far from an assembly expert (I know very few instructions right now), but the above looks to be changing the value of eip stored on the stack by the call instruction? Then returning to the address 0xc01020c8?

Changing the EIP value seems very odd to me in itself, but even weirder is that the code objdump gives ends at c0101012, so why does it want to return to 0xc01020c8?

Sorry if I'm wrong about it changing EIP, thats just what it looks like to me.

Posted: Sun Sep 02, 2007 9:35 pm
by frank
Are you using virtual functions in your classes? That could be one reason for changing EIP. Virtual classes have a jump table at the beginning of the class.

Maybe you should try taking everything out of the constructor and moving it too a function called Init that gets called at the start of main and see if that works.

Posted: Mon Sep 03, 2007 8:17 am
by JamesM

Code: Select all

c0100fa0 <_ZN7ConsoleC2Ev>:
c0100fa0:   ba c8 20 10 c0          mov    edx,0xc01020c8
c0100fa5:   8b 44 24 04             mov    eax,DWORD PTR [esp+0x4]
c0100fa9:   89 10                   mov    DWORD PTR [eax],edx
c0100fab:   c3                      ret
This is very strange. If I'm reading it correctly, it
1. loads the constant 0xc01020c8 into edx
2. loads the value at esp+0x4, that is, eax now contains the return address.
3. writes the constant 0xc01020c8 into the return address
4. returns

So, it should return, and hit the opcode 0xc01020c8, which is obviously total rubbish, and cause a SIGILL exception.

Have I got this incorrect? It's the dereference at address 0xc0100fa5 that's confusing me.

Posted: Mon Sep 03, 2007 8:28 am
by urxae
JamesM wrote:Have I got this incorrect? It's the dereference at address 0xc0100fa5 that's confusing me.
Yes, that dereference does seem to be confusing you. It's loading the first parameter into eax, not the return address (which would be at [esp]).
It's probably just initializing the vtable pointer...

Posted: Mon Sep 03, 2007 9:01 am
by JamesM
Ah yes, I was getting confused. Normally to access the return address it's [ebp+0x4] if you've pushed ebp immediately.

I always get confused whether the processor, when doing a POP:

Code: Select all

reads esp->target register
esp++
or

Code: Select all

esp++
read esp->target register
I know its the first one but i get confuzzled sometimes :P