Page 1 of 1

Calling global constructors in userspace (building programs)

Posted: Fri May 23, 2014 3:14 pm
by max
Good evening :)

I've implemented a microkernel in C++. All my programs are by now elf binaries that are loaded from the ramdisk. I'm compiling these userspace programs as explained in the following, but I've now come to the point where I need the global constructors called and well, this lead to further questions :mrgreen:

At the moment I'm compiling all the userspace programs with my normal i686-elf-gcc, and I've built an API library with the follwing contents:
- libgcc and its headers, precompiled for my i686-elf-target
- newlib and its headers, also precompiled
- a copy of the kernels system call definition header
- some higher-level interface util to use the system calls easier in userspace
- my own "crt0.asm" that by now only calls main and exit
- the "glue" functions that newlib needs, like C version of exit and so on

So the outcome when compiling this is that I have libgcc, newlib and my libghostapi ready for linking with my userspace programs. Compiling a program then looks like this (my vfs as an example):

Code: Select all

// Compiling...
i686-elf-g++ -c -fno-exceptions -fno-rtti -o "src/VFS.o" "../src/VFS.cpp"
    -I"/Applications/eclipse/workspace/GhostApps/GhostApp-VFS/src"
    -I"/Applications/eclipse/workspace/GhostAPI/libgcc/include"
    -I"/Applications/eclipse/workspace/GhostAPI/libnewlib/include"
    -I"/Applications/eclipse/workspace/GhostAPI/src"

// Linking...
i686-elf-g++ -nostdlib -o "vfs.bin" ./src/VFS.o -lgcc -lc -lghostapi
    -L"/Applications/eclipse/workspace/GhostAPI/libgcc"
    -L"/Applications/eclipse/workspace/GhostAPI/libnewlib"
    -L"/Applications/eclipse/workspace/GhostAPI/bin"
So basically nothing special (or is it? :D), I just tell the program where the headers and libraries are, and that I don't want to use exceptions/runtime. It works fine so far, but my problem is, that due to using the "nostdlib", theres also no start files, so it doesnt link with a crtbegin/crtend, and therefore none of my constructors are called.

My question is basically, how can I get my global constructors called? I've already tried finding out where GCC links the ctor section in its default linker script by using the verbose flag. I tried using "extern "C" crtbegin" and "extern "C" crtend" to get the linker symbols linked there and then call the constructors from my CRT0 before calling the main, but these symbols didn't work out.. How do I achieve this, or what is the recommended way? Should I write a crtbegin.asm that does the constructor calling and crtend.asm that does the destruction? Do I need a custom linker script, or do I need to build a completely new toolchain (as suggested in the wiki - also, whats the advantage)?

Thank you a lot in advance =)

Re: Building userspace applications

Posted: Fri May 23, 2014 3:31 pm
by sortie
You want a glorious OS Specific Toolchain!

Think of it as growing up. You started out with a cross-compiler, which is great as a base, but now you want real, true control of the compiler. You are going to do that by modifying GCC to do whatever you want to. This is actually the first part of porting GCC, btw. The great advantage is that i686-elf doesn't really have a user-space. It's a bare target and constructing a user-space on it is hacky and leads to issues like you are having. The solution is to teach GCC what your operating system is.

If you set up a proper OS Specific Toolchain and install all the libraries and header files into the system root, you can compile and link programs using just:

Code: Select all

i686-ghost-g++ -fno-exceptions -fno-rtti -o vfs.bin ../src/VFS.cpp
    -I/Applications/eclipse/workspace/GhostApps/GhostApp-VFS/src
    -I/Applications/eclipse/workspace/GhostAPI/src
Note how all libraries are found in the standard system library directories, how the standard libraries are linked in automatically, and how the system includes are automatically found. This is done by setting up a system root in which you install everything - you then teach the compiler your directory structure and what libraries are the standard libraries. Suddenly you become able to cross-compile programs just by invoking

Code: Select all

i686-ghost-gcc hello.c -o hello # !
This is really, really cool: You can port third party software by just compiling with your custom compiler in place of gcc. With autoconf-based packages you can just set --host=i686-ghost and if you set up the default system root properly (and you have the needed libc stuff), the package will cross-compile to your OS!

Re: Building userspace applications

Posted: Fri May 23, 2014 3:53 pm
by max
Wow sortie, you are a master in convincing people :D

That really sounds awesome, i'll go for it. So I guess that when doing it this way, the crtbegin and crtend-files come from libgcc, and my ctor-problem is solved?

But one more thing: When I do all these changes to the config files and whatnot, and then wan't to provide the finished toolchain to other people, or simply compile it again on another system, I don't want to do all these changes again - do you distribute the binary, or do you create patches to patch the GCC/other sources, or do you create one big repository containing the pre-patched sources?

EDIT: Also, I don't use this specific toolchain to build my kernel then, right? I'll still use my bare i686-elf-toolchain, because I don't want the userspace-specific stuff there

Thanks dude ^-^

Re: Building userspace applications

Posted: Fri May 23, 2014 4:57 pm
by sortie
max wrote:That really sounds awesome, i'll go for it. So I guess that when doing it this way, the crtbegin and crtend-files come from libgcc, and my ctor-problem is solved?
Indeed. Note that crtbegin.o and crtend.o are not part of libgcc, they are stand-alone objects on their own right. The correct phrasing is that the compiler provides crtbegin.o, crtend.o and libgcc.a. The standard library provides the additional start files. You can decide exactly what startfiles you desire in your OS specific toolchain. Check out my Creating a C Library article, it covers the startup files semantics.
max wrote:But one more thing: When I do all these changes to the config files and whatnot, and then wan't to provide the finished toolchain to other people, or simply compile it again on another system, I don't want to do all these changes again - do you distribute the binary, or do you create patches to patch the GCC/other sources, or do you create one big repository containing the pre-patched sources?
I maintain my OS specific toolchain as a fork of GCC. When I do releases of my operating system, I do matching sortix-binutils, sortix-gcc (and sortix-libstdc++) releases (I do occasional -rc releases of the upcoming toolchain during the development period). I tell people to use this compiler release instead of the upstream gcc release (I got a guide, not entirely unlike the wiki one, for how to build a cross-toolchain for my OS). I don't distribute binary versions of my cross-compiler because 1) my compiler changes now and then 2) it's bothersome 3) it's not entirely trivial to build cross-compilers that work universally 4) people shouldn't be running random executables.

For those that are more security-conscious than most, my GCC fork is maintained in a git repository. In that I store the upstream GCC releases in the upstream branch (after having deleted temporary files I am regenerating). The master branch has the patches for my OS. When a new GCC release comes along, I import that tarball into the upstream branch, and merge master with that. Thus you can confirm that the upstream branch is identical to upstream GCC releases, then use git diff to inspect my patches, then use autoconf to regenerate the configure scripts, and you get something that you can compare to my official sortix-gcc releases - now you can trust I didn't add a trojan horse.
max wrote:EDIT: Also, I don't use this specific toolchain to build my kernel then, right? I'll still use my bare i686-elf-toolchain, because I don't want the userspace-specific stuff there
No, you'll actually want to build the entire operating system with your new fancy compiler. This is an advantage, as you don't need to build multiple compilers and that the kernel can take advantage of compiler customization. Indeed, the kernel will be able to include headers from the system root's standard include directory. You won't include user-space stuff into the kernel because you are will using -nostdlib (when linking) and -ffreestanding when you build the kernel - so it should be pretty much equivalent to using i686-elf except in name. We are still using a cross-compiler and the compiler knows this is not Linux and it's not pulling in stuff you don't want, so everything is good. Indeed, if you port gcc to your operating system, you will be able to build your kernel with the system gcc (and you don't need a cross-compiler): You are already using a compiler that knows what is going on and have been taught what your OS is (unlike Linux compilers).

Mind that setting up an OS-specific toolchain is a bunch of work and involves learning a number of technologies and specifics. It's definitely worth it when you get the hang of it and the get the advantages - though. The amount of effort involved is probably worth it now, compared to the amount of effort involved in keeping doing user-space using i686-elf.