Page 1 of 1

C++ Global constructors and destructors

Posted: Sat Dec 27, 2008 2:45 pm
by Creature
Hello,

First off; hello, I've only just registered myself on these forums but have been watching them for quite some time now, nice to meet you ;-).

Now back on topic; I've been using C++ for my kernel quite some time now (this is fine, I prefer C++ over C and have my reasons for it). But I have a question relating the calling of global constructors and destructors. I've read a few articles (including the one on the wiki and have taken a look at various other OS' to see how they do it), but I can't seem to get it to work myself. I believe there is something small (as usual) that I'm not seeing here, or I'm possibly doing it all wrong. Have you got any idea what's wrong?

In my entry.asm file, I firstly loop through all global constructors (as the wiki showed) me and then move on to the C++ kernel entry point. After the kernel exits, I'm looping through the global destructors. However, when I use *(.ctor*) and *(.dtor*) in my linker file, the OS faults and immediatly reboots (I'm using BOCHS). Other combination of the constructor list such as *(.ctor) don't crash but I've noticed that none of my global variables are actually initialized.

Here's the code to the linker script:

Code: Select all

ENTRY(BootEntry)

SECTIONS
{
	.text 0x100000 :
	{
		code = .; _code = .; __code = .;
		*(.text)
		. = ALIGN(4096);
	}

	.data :
	{	
		ConstrStart = .; _ConstrStart = .; __ConstrStart = .;
		*(.ctor*)
		ConstrEnd = .; _ConstrEnd = .; __ConstrEnd = .;
		
		DestrStart = .; _DestrStart = .; __DestrStart = .;
		*(.dtor*)
		DestrEnd = .; _DestrEnd = .; __DestrEnd = .;
	
		data = .; _data = .; __data = .;
		*(.data)
		*(.rodata)
		. = ALIGN(4096);
	}

	.bss :
	{
		bss = .; _bss = .; __bss = .;
		*(.bss)
		. = ALIGN(4096);
	}

	end = .; _end = .; __end = .;
}

INPUT
(
	Entry.o
	AsmImp.o
	
	CppInit.o
	GUI.o
	Main.o
	GRUB.o
	Kernel.o
	Keyboard.o
	
	Heap.o
	Memory.o
	OrderedList.o
	Paging.o
	String.o
	
	CCWrite.o
	Video.o
)

OUTPUT(Kernel.bin)
And here is the Entry.asm source code:

Code: Select all

; * * * * * * * * * * * * * * * * * * * * *
; Entry.asm
; * * * * * * * * * * * * * * * * * * * * *
[BITS 32]

[GLOBAL BootEntry]
[GLOBAL Multiboot]

[EXTERN _Kernel]

ALIGN 4
Multiboot:
	MULTIBOOT_PAGE_ALIGN	equ 1<<0
	MULTIBOOT_MEMORY_INFO	equ 1<<1
	MULTIBOOT_AOUT_KLUDGE	equ 1<<16
	MULTIBOOT_HEADER_MAGIC	equ 0x1BADB002
	MULTIBOOT_HEADER_FLAGS	equ MULTIBOOT_PAGE_ALIGN | MULTIBOOT_MEMORY_INFO | MULTIBOOT_AOUT_KLUDGE
	MULTIBOOT_CHECKSUM		equ	-(MULTIBOOT_HEADER_MAGIC + MULTIBOOT_HEADER_FLAGS)
	EXTERN code, bss, end

	dd MULTIBOOT_HEADER_MAGIC
	dd MULTIBOOT_HEADER_FLAGS
	dd MULTIBOOT_CHECKSUM

	dd Multiboot
	dd code
	dd bss
	dd end
	dd BootEntry

;========================================
; InitStaticConstr
;   - Calls C++ static/global constructors.
; ========================================
[EXTERN ConstrStart]
[EXTERN ConstrEnd]

InitStaticConstr:
	mov ebx, ConstrStart		; Load the first constructor.
	jmp .Compare				; Jump to the compare label.
	
	.Body:						; This is the body, it calls the constructor and increments the 'pointer'.
		call [ebx]				; Call the constructor.
		add ebx, 4				; Move to the next one.
	
	.Compare:					; This checks if we've reached the end of the constructor 'list'.
		cmp ebx, ConstrEnd		; Check if the constructor inside ebx is the last constructor.
		jb .Body				; If it isn't, go back to Body.
		
	ret							; Return, we're done here.
	
;========================================
; InitStaticDestr
;   - Calls C++ static/global constructors.
; ========================================
[EXTERN DestrStart]
[EXTERN DestrEnd]

InitStaticDestr:
	mov ebx, DestrStart			; Load the first destructor.
	jmp .Compare				; Jump to the compare label.
	
	.Body:						; This is the body, it calls the destructor and increments the 'pointer'.
		call [ebx]				; Call the destructor.
		add ebx, 4				; Move to the next one.
	
	.Compare:					; This checks if we've reached the end of the destructor 'list'.
		cmp ebx, DestrEnd		; Check if the destructor inside ebx is the last destructor.
		jb .Body				; If it isn't, go back to Body.
		
	ret							; Return, we're done here.

;========================================
; BootEntry
;   - This is where it all begins. The BIOS passes the command to
;     this function.
; ========================================
[SECTION .text]					; We're in a text section.
BootEntry:
	mov esp, StackEnd			; Setup the stack pointer to the end of the stack.

	push eax					; Grab the multiboot magic number (last argument of KernelEntry).
	push ebx					; Grab the multiboot information (first argument of KernelEntry).

	cli							; No more interrupts.
	
	call InitStaticConstr		; Call C++ static/global constructors.
	call _Kernel				; Jump to the kernel entry.
	call InitStaticDestr		; Call C++ static/global destructors.

	jmp $						; Jump into an endless loop (to stop the CPU).

;========================================
; StackBegin and StackEnd
;   - The stack is initialized here and memory is reserved. The
;     stack grows downwards instead of upwards.
; ========================================	
[SECTION .bss]					; We're in a BSS section for memory initialization.
StackBegin:						; The beginning of the stack.
	resb 4096					; Reserve 4 kB of memory.
StackEnd:						; The end of the stack.
Thanks in advance for your help and time,
Creature

Re: C++ Global constructors and destructors

Posted: Sun Dec 28, 2008 5:26 am
by kasper
Hi creature and all others,
Creature wrote:[...]However, when I use *(.ctor*) and *(.dtor*) in my linker file, the OS faults and immediatly reboots (I'm using BOCHS).[...]
Really immediately? If your OS faults after executing one or more global constructors, the following might help.
Creature wrote:[...]

Code: Select all

   .Body:                  ; This is the body, it calls the constructor and increments the 'pointer'.
      call [ebx]            ; Call the constructor.
      add ebx, 4            ; Move to the next one.
[...]
The called constructor might change <tt>ebx</tt>, your code doesn't save it.

About the destructors, call them in reversed order, it avoids dependency problems.

Kasper

Re: C++ Global constructors and destructors

Posted: Sun Dec 28, 2008 5:34 am
by Creature
I have managed to fix one of the problems that has been bothering my OS for quite some time now. The global constructors work now, I changed the *(.rodata) section to *(.rdata*) and global variables are magically initialized now. I thought *(.rodata) was needed on my system because I used MinGW, but apparently another page on the wiki said MinGW on Windows needs *(.rdata*). Now the calling of the InitStaticDestr and InitStaticDestr functions doesn't crash the kernel anymore.

Both of you, thanks for your help in getting rid of a very annoying issue ;-). I will take your advice and call the destructors in reverse order (makes more sense also).

Will something like this work for reversing the destructor calls?

Code: Select all

;========================================
; InitStaticDestr
;   - Calls C++ static/global constructors.
; ========================================
[EXTERN DestrStart]
[EXTERN DestrEnd]

InitStaticDestr:
	mov ebx, DestrEnd - 4		; Load the last destructor.
	jmp .Compare				; Jump to the compare label.
	
	.Body:						; This is the body, it calls the destructor and decrements the 'pointer'.
		call [ebx]				; Call the destructor.
		sub ebx, 4				; Move to the previous one.
	
	.Compare:					; This checks if we've reached the beginning of the destructor 'list'.
		cmp ebx, DestrStart		; Check if the destructor inside ebx is the first destructor.
		jb .Body				; If it isn't, go back to Body.
		
	ret							; Return, we're done here.
Creature

Re: C++ Global constructors and destructors

Posted: Mon Dec 29, 2008 3:37 am
by AJ
kasper wrote:About the destructors, call them in reversed order, it avoids dependency problems.
Are you certain about that? I didn't think that could be guaranteed by any standards - an update to GCC could change that in future even if it works now.

@op: Some constructive criticism about your linker script. Firstly, see the Makefile tutorial on the wiki so you don't have to add objects to your makefile each time something changes. You will be glad of this when your project size increases.

Second, it may be intentional, but why "code = .; _code = .; __code = .;"? I know I'm being pedantic, but why would you need all 3 versions if you know what compiler you are using and therefore whether that compiler uses leading underscores or not?

Cheers,
Adam

Re: C++ Global constructors and destructors

Posted: Mon Dec 29, 2008 4:27 am
by Creature
AJ wrote:
kasper wrote:About the destructors, call them in reversed order, it avoids dependency problems.
Are you certain about that? I didn't think that could be guaranteed by any standards - an update to GCC could change that in future even if it works now.

@op: Some constructive criticism about your linker script. Firstly, see the Makefile tutorial on the wiki so you don't have to add objects to your makefile each time something changes. You will be glad of this when your project size increases.

Second, it may be intentional, but why "code = .; _code = .; __code = .;"? I know I'm being pedantic, but why would you need all 3 versions if you know what compiler you are using and therefore whether that compiler uses leading underscores or not?

Cheers,
Adam
I remember what Kasper said being written elsewhere as well (could have been on the wiki, but I can't really remember). But I think it does make more sense, you destroy the last variable you created before the others (don't know why but it just makes sense).

The extra 'underscored' versions were actually because I wasn't sure whether I needed them. In the meantime I know that I only need the 1-underscore version because of linkage, so I can probably remove the others.

Creature