Calling Global Constructors [C++]

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.
Post Reply
blacky
Posts: 20
Joined: Mon Apr 06, 2015 3:59 am

Calling Global Constructors [C++]

Post by blacky »

Hello everyone,

I would like to write an own OS kernel in C++. Despite, following the wiki entry from http://wiki.osdev.org/Calling_Global_Constructors, as well as the forum discussions in http://forum.osdev.org/viewtopic.php?f= ... hilit=crtn, my kernel seems not to execute the global constructors - despite the crti.o and the crtn.o object files are included into the build process. This leads to the fact that all class method invocations use not initialized class variables and pointers and thus force the Qemu to crash.

In short: My build-system uses an i686-elf cross compiler (though, I need to mention that I did not build the cross-compiler by myself but rather downloaded a pre-built version from http://wiki.osdev.org/GCC_Cross-Compiler - i686-elf 4.9.2 target, x86_64 Linux host). To enable the C++ constructors to be called, I have built the crti.o and crtn.o files, as mentioned in the wiki entry above. Since it was not mentioned anywhere, I have not created any special linker entries for the .init/.fini sections - they seem to be correctly linked into the kernel object file. The purpose of these runtime related files (together with crtbegin.o and crtend.o) generate the function "_init", which is called right at the beginning of my bootstrap code; This means, even before any initialization of GDT, IDT, etc. Right after the invocation of "_init", I am calling the custom function "k_early_init" to initialize the VGA (class Video) for the beginning. However, since "video" is a global class object, it fails at this point (The cursor remains blinking, but the debug interface of qemu reveals that there was an error).

The following links provide further information about my current state:

It would be amazing if you could help me with my concern.
Thank you very much in advance.

crt*.S: http://pastebin.com/WbiWmgWq
boot.S: http://pastebin.com/DFuhLANc
kernel.cpp: http://pastebin.com/fZ1ZWqt7
Makefile: http://pastebin.com/zsH4cYsF
linker.ld: http://pastebin.com/hetKcuQ5

Best regards
User avatar
Combuster
Member
Member
Posts: 9301
Joined: Wed Oct 18, 2006 3:45 am
Libera.chat IRC: [com]buster
Location: On the balcony, where I can actually keep 1½m distance
Contact:

Re: Calling Global Constructors [C++]

Post by Combuster »

I see the relevant section on the relevant linker script changes for C++ support has vanished from the wiki. You'll at least want to have ctors/dtors/init/fini sections linked into your final executable as necessary.


EDIT: The problem is that the original C++ bare bones uses a completely different method of invoking global constructors compared to the newer global constructors page, so be careful as I haven't changed from the "deleted" method and I can't attest if and how the newer version works.
"Certainly avoid yourself. He is a newbie and might not realize it. You'll hate his code deeply a few years down the road." - Sortie
[ My OS ] [ VDisk/SFS ]
blacky
Posts: 20
Joined: Mon Apr 06, 2015 3:59 am

Re: Calling Global Constructors [C++]

Post by blacky »

First of all: Thank you very much for your help :)
After I have included the .ctors and .dtors sections into the linker script as stated in the C++ Bare Bones Tutorial, my kernel was able to use global C++ objects as intended.

Regarding your comment within the "EDIT" part:
Well, what I am currently doing is quite a mix of both versions: The original C++ Bare Bones Tutorial seems to manually invoke the constructors/destructors within the bootstrap code in boot.S. However, since the generated function _init basically provides similar functionality, it is possible to simply invoke the _init function (which in turn calls the function __do_global_ctors_aux and so on) and thus get the same results - or do you see any potential troubles with this method?

---
EDIT:
I have been glad too early :(
I created another global class object - and things started to get ugly: Qemu crashes even if I do not execute any class methods of the second global object. My proposed method (which simply invokes the function _init) does not work that well after all. It seems that the second global object overrides the first one (dependent on the initialization order of the two global objects and on their size in terms of member variables, the kernel proceeds with correct operation). To cope with this issue, I will try to adopt the 'initial' example provided by C++ Bare Bones Tutorial.

In case someone has experienced similar behavior, I would appreciate any input :)
Again, thanks in advance.

Best regards.
blacky
Posts: 20
Joined: Mon Apr 06, 2015 3:59 am

Re: Calling Global Constructors [C++]

Post by blacky »

In the meantime, I have tried different approaches to enable the global constructor invocation, such as calling the constructors manually as presented in the C++ Bare Bones Tutorial. The same can of course be performed in C++ code. I made sure that the .ctors and .dtors entries are 4 byte aligned but unfortunately the upper issues remain.

I have not expected C++ to make so much trouble when it comes to kernel development.
nbdd0121
Member
Member
Posts: 60
Joined: Thu Jul 25, 2013 8:10 am

Re: Calling Global Constructors [C++]

Post by nbdd0121 »

You would have to manually call _init. It will not be called automatically because you controlled the entry point of kernel, not the linker nor gcc. Personally I call this function when I get memory / allocation / GDT / IDT / Paging ready. (Note that you should be very cautious in these initializing functions since the environment is not well initialized, you probably should only use limited C++ functionality).

I have sections like this in my link script, it may be helpful to you.

Code: Select all

/* Global ctors and dtors */
.ctors : {*(.ctors)}
.dtors : {*(.dtors)}

/* Just make crtstuff happy */
.tm_clone_table : {*(.tm_clone_table)}
.jcr : {*(.jcr)}
.got.plt : {*(.got.plt)}

.init : {*(.init)}
.fini : {*(.fini)}

/* Enable exception handling */
.eh_frame_hdr : {*(.eh_frame_hdr) }
.eh_frame : {*(.eh_frame)}
.gcc_except_table : { *(.gcc_except_table) }
Once you link your c++ code with libcxxrt (or libsupc++, but former one is easier to port), you will be able to use C++ exceptions as well. (Note that you should not use exception before _init, because _init calls frame_dummy which will initialize the exception handling stuff)

Edit: Manually call constructor use placement new is probably not preferable because it's error-prone.
blacky
Posts: 20
Joined: Mon Apr 06, 2015 3:59 am

Re: Calling Global Constructors [C++]

Post by blacky »

Thank you very much for your answer :)
Personally I call this function when I get memory / allocation / GDT / IDT / Paging ready. (Note that you should be very cautious in these initializing functions since the environment is not well initialized, you probably should only use limited C++ functionality).
In the meantime I decided to do just that - I thought it might be a better idea to cope with the dynamic support for C++ after bootstrapping the system. My current implementation provides basic initialization of the GDT, IDT, and of the page tables (although paging is not finished yet). After I have finished the process of bootstrapping, I will definitely place similar sections into my linker script (thank you very much for this :) ).

I will provide updates concerning my progress soon :)

PS: my initial implementation has called the function '_init', which already tried to initialize all global objects. However, it seemed that the constructors of the global objects have not been called.
nbdd0121
Member
Member
Posts: 60
Joined: Thu Jul 25, 2013 8:10 am

Re: Calling Global Constructors [C++]

Post by nbdd0121 »

blacky wrote:PS: my initial implementation has called the function '_init', which already tried to initialize all global objects. However, it seemed that the constructors of the global objects have not been called.
Did you place the sections .ctors into the link script? Probably you need to disassemble your final output and see what's wrong.
blacky
Posts: 20
Joined: Mon Apr 06, 2015 3:59 am

Re: Calling Global Constructors [C++]

Post by blacky »

Did you place the sections .ctors into the link script? Probably you need to disassemble your final output and see what's wrong.
Yes, the sections .ctors and .dtors have been within the linker script. I will give it another shot after finishing the initial bootstrapping process: First, I would like to initialize paging, provide rudimentary memory management and jump into the long mode.

I will provide further updates soon :)
User avatar
KemyLand
Member
Member
Posts: 213
Joined: Mon Jun 16, 2014 5:33 pm
Location: Costa Rica

Re: Calling Global Constructors [C++]

Post by KemyLand »

blacky wrote:
Did you place the sections .ctors into the link script? Probably you need to disassemble your final output and see what's wrong.
Yes, the sections .ctors and .dtors have been within the linker script. I will give it another shot after finishing the initial bootstrapping process: First, I would like to initialize paging, provide rudimentary memory management and jump into the long mode.

I will provide further updates soon :)
If it may help, this is my early bootstrap code. It's 300 lines of (almost illegible) assembly. It is for the x86 (i.e: it does not setup long mode). It enables paging, and (TODO) parses information from the Multiboot header. Some parts of its code are not tested (specially criticalError() and friends). Hope this code helps:

Code: Select all

.set ALIGN,	1<<0			 # align loaded modules on page boundaries
.set MEMINFO,  1<<1			 # provide memory map
.set FLAGS,	ALIGN | MEMINFO  # this is the Multiboot 'flag' field
.set MAGIC,	0x1BADB002		# 'magic number' lets bootloader find the header
.set CHECKSUM, -(MAGIC + FLAGS) # checksum of above, to prove we are multiboot

.section .multiboot
.long MAGIC
.long FLAGS
.long CHECKSUM

.section .stack, "aw", @nobits
stackBottom:
	.skip 16384
stackTop:

.set KVIRT_BASE, 0xC0000000
.set KPAGE_NUM,	(KVIRT_BASE >> 12)

.section .bss
.align 0x1000
pageDirectory:
	.skip 0x1000 # Page directory
pageTables:
	.skip 0x1000 # First identity-mapped 4MiB
	.skip 0x1000 * 256 # 3GiB to 4GiB area

.section .boot
.global __start__
__start__:
	# A Multiboot-compliant bootloader just yielded
	# execution to Bluesky here. At this moment,
	# everything is loaded at 1MiB, but the code
	# thinks it's on 3GiB! The solution? Subtract
	# 3GiB from *each* address used, so it resolves
	# to the correct physical address. That's the job
	# of the 'subl $KVIRT_BASE, %edx' stuff all around
	# this function. Once the paging structures are fully
	# calculated and loaded, __start__() can work with virtual
	# addresses. It then passes control to realStart(), the
	# routine responsible for parsing the Multiboot information (TODO),
	# and finally calling KernelInit. If something fails,
	# criticalError() is a minimalistic asm version of KTPipe
	# that writes some string in red/white to VGA memory and halts.
		
	# Load the stack (I can't live up from the registers!)
	movl $stackTop, %esp
	subl $KVIRT_BASE, %esp
	
	# Save Multiboot Information (%ebx) and Bootloader Magic (%eax) 
	push %eax
	push %ebx
	
	# Map the first 4MiB to the first page table
	movl $pageDirectory, %edx
	subl $KVIRT_BASE, %edx
	movl $pageTables, %eax
	subl $KVIRT_BASE, %eax
	orl $0x00000003, %eax
	movl %eax, (%edx)
	
	# Identity map the first page table
	movl $pageTables, %edi
	subl $KVIRT_BASE, %edi
	movl $1024, %ecx
	movl %ecx, %ebx
	.identmap:
		movl %ebx, %eax
		subl %ecx, %eax
		movl %eax, %esi
		shll $12, %esi
		orl $0x00000003, %esi
		movl $4, %edx
		mull %edx
		movl %edi, %edx
		addl %eax, %edx
		movl %esi, (%edx)
		loop .identmap
	
	# Map the first Gigabyte to 3GiB upwards
	movl $pageTables, %edi
	subl $KVIRT_BASE, %edi
	addl $0x1000, %edi
	movl $256, %ecx
	movl $0, %edx
	.highmap:
		movl %ecx, %ebx
		movl $1024, %ecx
		
		.highmap2:
			movl %edx, %eax
			shll $12, %eax
			orl $0x00000003, %eax
			movl %eax, (%edi)
			incl %edx
			addl $4, %edi
			loop .highmap2
		
		movl %ebx, %ecx
		loop .highmap
	
	# Load the higher-half tables
	movl $pageDirectory, %edx
	subl $KVIRT_BASE, %edx
	addl $3072, %edx
	movl $256, %ecx
	movl %ecx, %ebx
	.filltop:
		movl %ebx, %esi
		subl %ecx, %esi
		movl %edx, %edi
		movl $0x1000, %eax
		mull %esi
		movl %edi, %edx
		movl $pageTables, %edi
		subl $KVIRT_BASE, %edi
		addl $0x1000, %edi
		addl %eax, %edi
		orl $0x00000003, %edi
		movl %edx, %ebp
		movl $4, %eax
		mull %esi
		movl %ebp, %edx
		addl %edx, %eax
		movl %edi, (%eax)
		loop .filltop
	
	# Load the page directory
	movl $pageDirectory, %edx
	subl $KVIRT_BASE, %edx
	movl %edx, %cr3
	
	# Enable paging
	movl %cr0, %edx
	orl $0x80000000, %edx
	movl %edx, %cr0
	
	# Jump to some code that *really* assumes it's on $KVIRT_BASE
	movl $realStart, %edx
	jmp *%edx

.text
realStart:
	# Load the virtual value of $stackTop
	# And, to comply with the ABI, load %esp into %ebp
	addl $KVIRT_BASE, %esp
	movl %esp, %ebp
	
	# Get Multiboot Information (%ebx) and Bootloader Magic (%eax)
	pop %ebx
	pop %eax
	
	# Check the Bootloader magic. If it's invalid, panic!
	cmp $0x2BADB002, %eax
	je .noerror
	# Error! **** it up!
	push $.ENOTMULTIBOOT # "Not using a Multiboot-compliant bootloader"
	push $0x01 # Error Code
	call criticalError
	.noerror:
	
	# Parse the first-level Multiboot Structure
	addl $KVIRT_BASE, %ebx # Physical to Virtual
	
	# Call trivial global constructors
	call _init
	
	# Pass it to KernelInit()...
	push %ebx
	
	# Should I put a comment here? Anyway, I did it.
	call KernelInit
	
	# If KernelInit() somehow returns, halt the machine.
	# Non-maskable interrupt? Don't worry! Just loop forever.
	.hang:
		cli
		hlt
		jmp .hang

criticalError:
	# %ebx is the offset over VGA memory used
	# by the criticalPrint* functions.
	movl $0, %ebx
	
	# "Fatal Error Code "
	movl $.EERROR, %eax
	call criticalPrintString
	
	pop %eax # Error Code
	call criticalPrintHex
	
	movb $0x3A, %al # ':' Character
	call criticalPrintChar
	movb $0x20, %al # ' ' Character
	call criticalPrintChar
	
	pop %eax # Error Message
	call criticalPrintString
	
	movl $.EHALT, %eax
	call criticalPrintString
	
	# Halt
	cli
	hlt

criticalPrintString:
	# Parameters:
	#	%eax: Address of null-terminated string to print
	
	.strloop:
		movb (%eax), %al
		cmp $0x00, %al
		je .strloopend
		call criticalPrintChar
		incl %eax
		incl %ebx
		jmp .strloop
	.strloopend:
		ret

criticalPrintHex:
	# Parameters:
	#	%eax: Integer to print
	
	movb $0x30, %al # '0' Character
	call criticalPrintChar 
	incl %ebx
	movb $0x78, %al # 'x' Character
	call criticalPrintChar
	incl %ebx
	
	movl $8, %ecx
	.hexloop:
		movl %ecx, %esi
		decl %esi
		push %eax
		movl %eax, %edi
		movl $4, %eax
		mull %esi
		xchg %eax, %ecx
		shrl %cl, %edi
		xchg %eax, %ecx
		movl %edi, %edx
		
		push %ecx
		movl $2, %ecx
		.hexbitloop:
			push %edx
			andb $0x0F, %dl
			
			# Get the real value...
			.hextest:
				cmpb $0x0A, %dl
				jb .hextestdecimal
				jae .hextesthexa
			.hextestdecimal:
				addb $0x30, %dl # Calculates character between '0' and '9'
				jmp .hextestend
			.hextesthexa:
				subb $0x0A, %dl # For offsetting
				addb $0x41, %dl
				jmp .hextestend
			.hextestend:
				movb %dl, %al
				call criticalPrintChar
				incl %ebx
				pop %edx
				shrl $4, %edx
				loop .hexbitloop
		
		pop %ecx
		pop %eax
		loop .hexloop
	ret

criticalPrintChar:
	# Parameters:
	#	%al: Character to print
	#	%ebx: Offset over VGA Memory
	# Uses:
	#	%dx: Actual value written to VGA Memory
	
	movb $0x4F, %dh				# White text on red background
	movb %al, %dl				# Mix it...
	movw %dx, 0xB8000(,%ebx, 2)	# Print!
	
	ret

.section rodata
# Concatenated on every error
.EERROR:
	.asciz "Fatal Error Code "
.EHALT:
	.asciz ". System halted! Please reboot manually."

# The actual errors
.ENOTMULTIBOOT:
.asciz "Not using a Multiboot-compliant bootloader"
Happy New Code!
Hello World in Brainfuck :D:

Code: Select all

++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.>---.+++++++..+++.>>.<-.<.+++.------.--------.>>+.>++.
[/size]
blacky
Posts: 20
Joined: Mon Apr 06, 2015 3:59 am

Re: Calling Global Constructors [C++]

Post by blacky »

Nice, thank you very much :)
Any additional information is considered as a potential learn-base and is very helpful, as I am a beginner when it comes to OS development ;)

Edit: As I have seen it in your code as well, I have right away a follow-up question concerning linkers and the higher-half kernel.
Since my follow-up question does not concern the initial issue of this thread, the follow-up question can be found here: Linkers and the 'Load Memory Address'.
Post Reply