Calling Global Constructors [C++]
Calling Global Constructors [C++]
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
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
- Combuster
- 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++]
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.
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.
Re: Calling Global Constructors [C++]
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.
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.
Re: Calling Global Constructors [C++]
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.
I have not expected C++ to make so much trouble when it comes to kernel development.
Re: Calling Global Constructors [C++]
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.
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.
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) }
Edit: Manually call constructor use placement new is probably not preferable because it's error-prone.
Re: Calling Global Constructors [C++]
Thank you very much for your answer
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.
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 ).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 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.
Re: Calling Global Constructors [C++]
Did you place the sections .ctors into the link script? Probably you need to disassemble your final output and see what's wrong.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.
Re: Calling Global Constructors [C++]
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.Did you place the sections .ctors into the link script? Probably you need to disassemble your final output and see what's wrong.
I will provide further updates soon
Re: Calling Global Constructors [C++]
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:blacky wrote: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.Did you place the sections .ctors into the link script? Probably you need to disassemble your final output and see what's wrong.
I will provide further updates soon
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 :[/size]
Hello World in Brainfuck :
Code: Select all
++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.>---.+++++++..+++.>>.<-.<.+++.------.--------.>>+.>++.
Re: Calling Global Constructors [C++]
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'.
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'.