The First Tutorial
had a very small startup.s
file and a very small C program.
When compiled, it produced tons of
compiled code. It was then re-compiled after changing compile
options to remove 'debug-info' form the code. Yet the compiled
binary code was quite large and did a lot of things, the user
apparently did not code.
If the user wants to get rid of all the extra code just to see what
their code compiles, then they must write all of the code in
assembly language only.
However if the user must write a 'C' program, the compiler/linker
will perform a lot of 'behind-the-scenes' steps, usually to help the
user. For example memory management. That is to say that the
compiler will take care where to place the local variables, where to
place the global variables, where to place the loop variables, and
make sure that the memory spaces do not overlap, or the memory does
not gets corrupted. The compiler does give the user options to have
some control over where to place things, but if the user isn't
expert, its always best to stick to what compiler does, and not try
to change things.
This tutorial is more theoretical in nature, as it explains the
startup flow in brief, i.e. what the extra code is all about, and
what the compiler does when a c-program is compiled. The full
details of the startup flow is beyond the scope of this tutorial.
The default sequence looks something like this:
1. Calls __main
This is the entry point of the user's program. This __main function
is pre-defined (though the user can write their own __main). Note
that this __main is different from the main() in the user's
C-program. If the user intends to write their own '__main' they can
use their own name for it. However, then the user must update the
Linker's default for '--startup' option as it defaults to
'--startup=__main'. The user can also use '--no_startup' if they so
wish. However the consequences of doing so is beyond the scope of
The purpose of this function is to do some code relocation. It
copies the non-root execution region code from their load addresses
to their execution addresses. More about regions, root, non-root
code, and load/execution regions is described here:
This function also initializes certain region by zeros. For example
the startup.s code defines a 'stack' region. This stack region is
2. Calls __rt_entry
It sets up the stack and heap.
Calls __rt_lib_init which in turn initializes library functions.
3. Calls main().
This is when the user's code inside main() is executed.
4. Calls exit().
NEXT => (no next)