Contents|Index|Previous|Next

crt0, the main startup file

The crt0 (C RunTime 0) file contains the initial startup code.

Cygnus Solutions provides a crt0 file, although you may want to write your own crt0 file for each target. The crt0 file is usually written in assembler as ‘crt0.S’, and its object gets linked in first and bootstraps the rest of your application. The crt0 file defines a special symbol like _start that is both the default base address for the application and the first symbol in the executable binary image.

If you plan to use any routines from the standard C library, you’ll also need to implement the functions on which libgloss depends. The crt0 file accomplishes the following results. See also I/O support code.

- crt0 initializes everything in your program that needs it
  This initialization section varies. If you are developing an application that gets downloaded to a ROM monitor, there is usually no need for special initialization because the ROM monitor handles it for you. If you plan to burn your code in a ROM, the crt0 file typically does all of the hardware initialization required to run an application. This can include things like initializing serial ports and running a memory check; however, results vary depending on your hardware.

The following is a typical basic initialization of crt0.S.

1.
Set up concatenation macros.

    #define CONCAT1(a, b) CONCAT2(a, b)
    #define CONCAT2(a, b) a ## b

Later, you’ll use these macros.

2.
Set up label macros, using the following example’s input.

    #ifndef __USER_LABEL_PREFIX__
    #define __USER_LABEL_PREFIX__ _
    #endif

    #define SYM(x) CONCAT1 (__USER_LABEL_PREFIX__, x)

These macros make the code portable between ‘coff’ and ‘a.out’. ‘coff’ always has an __ (underline) prepended to the front of its global symbol names. ‘a.out’ has none.

3.
Set up register names (with the right prefix), using the following example’s input.

    #ifndef __REGISTER_PREFIX__
    #define __REGISTER_PREFIX__
    #endif

    /* Use the right prefix for registers. */
    #define REG(x) CONCAT1 (__REGISTER_PREFIX__, x)

    #define d0 REG (d0)
    #define d1 REG (d1)
    #define d2 REG (d2)
    #define d3 REG (d3)
    #define d4 REG (d4)
    #define d5 REG (d5)
    #define d6 REG (d6)
    #define d7 REG (d7)
    #define a0 REG (a0)
    #define a1 REG (a1)
    #define a2 REG (a2)
    #define a3 REG (a3)
    #define a4 REG (a4)
    #define a5 REG (a5)
    #define a6 REG (a6)
    #define fp REG (fp)
    #define sp REG (sp)

Register names are for portability between assemblers. Some register names have a % or $ prepended to them.

4.
Set up space for the stack and grab a chunk of memory.

    .set stack_size, 0x2000 .
    comm SYM (stack), stack_size

This can also be done in the linker script, but it typically gets done at this point.

5.
Define an empty space for the environment, using the following example’s input.

         .data
         .align 2
    SYM (environ):
         .long 0

This is bogus on almost any ROM monitor, but we set up a valid address for it and pass the address to main (). This way, if an application checks for an empty environment, it finds one.

6.
Set up a few global symbols that get used elsewhere.

    .align 2
    .text
    .global SYM (stack)

    .global SYM (main)
    .global SYM (exit)
    .global __bss_start

This really should be __bss_start, not SYM (__bss_start). __bss_start needs to be declared this way because its value is set in the linker script.

7.
Set up the global symbol, start, for the linker to use as the default address for the .text section. This helps your program run.

    SYM (start):
    link a6, #-8
    moveal #SYM (stack) + stack_size, sp

- crt0 zeroes the .bss section
  Make sure the .bss section is cleared for uninitialized data, using the following example’s input. All of the addresses in the .bss section need to be initialized to zero so programs that forget to check new variables’ default values will get predictable results.

    moveal #__bss_start, a0
    moveal #SYM (end), a1
    1:
    movel #0, (a0)
    leal 4(a0), a0
    cmpal a0, a1
    bne 1b

Applications can get wild side effects from the .bss section being left uncleared, and it can cause particular problems with some implementations of malloc().

- crt0 calls main()
  If your ROM monitor supports it, set up argc and argv for command line arguments and an environment pointer before the call to main(), using the following example’s input.

For G++, the code generator inserts a branch to __main at the top of your main() routine. G++ uses __main to initialize its internal tables and then returns control to your main() routine.

For crt0 to call your main() routine, use the following example’s input. First, set up the environment pointer and jump to main(). Call the main routine from the application to get it going, using the following example’s input with main (argc, argv, environ)., using argv as a pointer to NULL.

             pea 0
             pea SYM (environ)
             pea sp@(4)
             pea 0
    jsr SYM (main)
    movel d0, sp@-4

- crt0 calls (exit)
  After main() has run, the crt0 file cleans things up and returns control of the hardware from the application. On some hardware there is nothing to return to—especially if your program is in ROM— and if that’s the case, you need to do a hardware reset or branch back to the original start address.

If you’re using a ROM monitor, you can usually call a user trap to make the ROM take over. Pick a safe vector with no sides effects. Some ROM’s have a built-in trap handler just for this case.

Implementing (exit) here is easy.. First, with _exit, exit from the application. Normally, this causes a user trap to return to the ROM monitor for another run. Then, using the following example’s input, you proceed with the call.

    SYM (exit):
    trap #0

Both rom68k and bug can handle a user-caused exception of 0 with no side effects. Although the bug monitor has a user-caused trap that returns control to the ROM monitor, the bug monitor is more portable.