Qs about runtime support libs like crt0.o

Duane Ellis duane@duaneellis.com
Thu Aug 30 22:24:00 GMT 2007

Rick - Here's a Long mail with some details for you to get lost with.
I am not going to be reading/answering email for +1 week (vacation time)

You might also want to check out the cross-gcc mailing list.

>> 1) Does Newlib replace all the crtX.o files used by
>> a typical GCC build? At first I thought not, but then
>>  I noticed it does built crt0.o. However, I haven't
>> seen any of the other gcc-provided cr libraries
>> (crti.o, crtbegin.o, crtend.o, crtn.o).

More specifically:

>> Do I still need to use GCC's versions?

Yes, and no, it depends on: Are you using C++
  If YES - it gets a bit more complicated, sometimes very complicated.
  If NO - things are very super simple.

C++ is complicated due to constructors that need to be called, and
managing all those "vtable" things.
and C++ exception stuff.

What is your boot method? This code? Other code? Angel? Some boot rom?
Who clears memory? What sets up the stack pointer?
Who inits the high speed CPU clock? The RAM/FLASH/SDRAM?
Is this something you are going to write?

Generally - NEWLIB requires:
    (a) your startup code has done all init for RAM and FLASH wait states.
    (b) newlib assumes (as does the C standard) memory is zeroed.
    (c) Same with the "initialized [ram] data" variables.
    (d) On linux - the operating system does that.
    (e) in an embedded system, YOU do that.
    Note the "crt0.S" in newlib - does not do the above.

CRT0.o - is the traditional name for it.
    I think of it this way:  crt0.o ->  Run time startup for an os type
    bootrom.o -> is the my boot rom stub.

In case you wanted to know:
    Look carefully at those files, and determine
        (a) what symbols are they generating.
        (b) look carefully at what SEGMENTS they are located in.
        (c) corrilate that with the order they appear when you link.
        (d) and the order the segments appear in the linker script.

>> The problem is that I need to link with a linker script, and I think 
that requires calling ld directly, rather than letting gcc invoke collect2.

Yes, and no, you only need COLLECT2 - if and only if you are using C++ -
but not exactly... you do not need it if you understand how collect2
works. It works like this:
    Step 1 -Link the program, generate the output.
    Step 2 -Examine the resuling symbol table
                 Via  heuristic - figure out the C++ constructors and
                  (hence the term: Collect all of the constructors)
    Step 3 - Create a "C" file with array of pointers to constructors
and destructors functions.
    Step 4 - Compile that C file and link a 2nd time.

Remember above - what I said about the Symbols and Segments, the C++
startup code - knows about all of those things and - uses those symbols
to figure out the constructors destructor and exception table stuff.

>> 2) I'm a little overwhelmed by all the stuff a newlib build 
produces. There seem to be many options not
>> covered by configure --help (for example, what is "interwork?"), and 
the resulting build seems to include
>> a great many duplicates. It produced an "arm-elf" directory that 
contained several more directories
>> ("be," "fpu," "interwork," "newlib," "nofmult," "thumb"), many of 
which in turn contain a "newlib"
>> directory, etc. There are multiple copies of libc.a, crt0.o, etc, 
presumably with various differences.

You should know these -- but perhaps you do - but don't realize it.
ARM vrs THUMB ... inter work lets you use both in the same code, thumb
is thumb only.
BE - LE - arm works big/little endian... You have soft or hard floating
Think of the permutations - by default - NEWLIB builds the permutations
GCC knows how to build.

>> Is there something that explains the various additional options, and 
what gets built?

USE THE SOURCE LUKE....   I love that line... and hate it when people
use it on me.


Some helpful things about setting up a system in this area.

Exactly what CPU are you using?

1)    How exactly did you build GCC?
    Where did  you get <stdio.h> from - when you built arm-elf-gcc?
        Did you borrow it from /usr/include - That is a bad thing!

2) Generally to build GCC you do it in two stages.
         Stage 1 - You build a plain C only compiler.

      Important config options:

       Use "make  all-gcc"  then "make install-gcc"

   Anything more - has mental issues...

3) Now build newlib.
      STAGE 2
             --disable-newlib-supplied-syscalls      <--- VERY IMPORANT

   PROBLEMS (with newlib-1.15.0) - I have a patch but... its not
presentable right now.
       The file: "libgloss/arm/linux-syscalls0.S"
            bug: Macro SYSCALL6, Uses THUMB opcodes - line 68.
            bug: Macro SOCKETCALL - same type of problem - line 190
            bug: function: socketcall_tail - Same thing.

4) Now - go back and build GCC - the full system.
       STAGE 2 - GCC - C++

     Use "make" and "make install"

    Why? - Classic Chicken And the Egg Problem.
             C++ and the rest of GCC will not build without a C library.
             C library will not build without a C compiler
             C compiler assumes a C library
          Trick is - to build _only_ the C compiler in step 1, and
absolutely nothing else.

5) NOW - you can build your own C and C++ code  using newlib.


When you *LINK* your program, you will have some undefined symbols.
like for example:   _open, _read, _write, _close

This is because you specified:

Since *YOU* have no operating system - NEWLIB has no idea
what to do. In other situations - things like a BOOT ROM, or ANGLE
or something else might handle these functions for you, see for example:

         That one uses the ARM SWI 0x123456 to talk to the ROM monitor
         Or - GDB, or ... what ever to perform some actions.

       The Library of things that get glossed over...
       Well - not for you.... You have to write this.

In total, there are about 20 functions you must write.
Or - create "dummy versions"


Your next question is probably: How can I get printf() to work?

Look at for instance:    fopen()
           Calls the thread-safe version:  _fopen_r()

        _fopen_r() - calls _open_r()
              *YOU* must write that function.

    HINT:  Make:
                "/dev/uart0" - return the number 10
                "/dev/uart1" - return the number 11
                      .. .and so on ...
              Why not 0 1 or 2?
                Those are the numbers for stdin, stdout, and stderr.

       Later,when _write_r() is called - you know that fd=10 = uart0

       And ... make fd=0 or 1 or 2 = goto your debug uart!



Your code calls printf()

    Which calls - thread-safe: _printf_r()
         ... which puts stuff into a FILE buffer.
       (that part is complicated and I skip over it)

    In the end, fflush() gets called.

    NEWLIB's FILE structure has a bunch of callbacks that handle the
    actuall read/write - see for instance how sprintf() uses a FILE handle
    to write to a memory buffer.

    In the end, the function __swrite() is called

    And that calls the function _write_r() - *WHICH*YOU* must supply.

    Remember above - about the _open_r() - and the number it returns!


NEWLIB - needs a HEAP...
    printf() - calls malloc(). So does other things. You must make it work.

The trick is to impliment _sbrk_r()
Look carefully at the ARM syscalls implimentation.

It basically is using some global variables.

    heap_end => the end of the heap space.
       It is initilized to point at 'end' - the magic symbol
       that is the *END* of your program.
       That assumes a certain memory map you might not have.
       in a embedded world, you want to do this instead:

     _sbrk( int amount )

          if( heap_end == NULL ){
              heap_end = START_OF_HEAP_SPACE;

          old_end = heap_end;

          heap_end  += amount;

           if( heap_end > END_OF_HEAP_SPACE ){

           return old_end;


More information about the Newlib mailing list