Next: , Up: Libraries   [Contents]


3.1 Crt0, the main startup file

To make a program that has been compiled with GCC to run, you need to write some startup code. The initial piece of startup code is called a crt0. (C RunTime 0) This is usually written in assembler, and it’s object gets linked in first, and bootstraps the rest of the application when executed. This file needs to do the following things.

  1. Initialize anything that needs it. This init section varies. If you are developing an application that gets download to a ROM monitor, then there is usually no need for any special initialization. The ROM monitor handles it for you.

    If you plan to burn your code in a ROM, then the crt0 typically has to do all the hardware initialization that is required to run an application. This can include things like initializing serial ports or run a memory check. It all depends on the hardware.

  2. Zero the BSS section. This is for uninitialized data. All the addresses in this section need to be initialized to zero so that programs that forget to check new variables default value will get unpredictable results.
  3. Call main() This is what basically starts things running. If your ROM monitor supports it, then first setup argc and argv for command line arguments and an environment pointer. Then branch to main(). For G++ the the main routine gets a branch to __main inserted by the code generator at the very top. __main() is used by G++ to initialize it’s internal tables. __main() then returns back to your original main() and your code gets executed.
  4. Call exit() After main() has returned, you need to cleanup things and return control of the hardware from the application. On some hardware, there is nothing to return to, especially if your program is in ROM. Sometimes the best thing to do in this case is do a hardware reset, or branch back to the start address all over again.

    When there is a ROM monitor present, usually a user trap can be called and then the ROM takes over. Pick a safe vector with no side effects. Some ROMs have a builtin trap handler just for this case.

portable between all the m68k based boards we have here. Example Crt0.S.

/* ANSI concatenation macros.  */

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

These we’ll use later.

/* These are predefined by new versions of GNU cpp.  */

#ifndef __USER_LABEL_PREFIX__
#define __USER_LABEL_PREFIX__ _
#endif

/* Use the right prefix for global labels.  */
#define SYM(x) CONCAT1 (__USER_LABEL_PREFIX__, x)

These macros are to make this code portable between both COFF and a.out. COFF always has an _ (underline) prepended on the front of all global symbol names. a.out has none.

#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)

This is for portability between assemblers. Some register names have a % or $ prepended to the register name.

/*
 * Set up some room for a stack. We just grab a chunk of memory.
 */
	.set	stack_size, 0x2000
	.comm	SYM (stack), stack_size

Set up space for the stack. This can also be done in the linker script, but it typically gets done here.

/*
 * Define an empty environment.
 */
        .data
        .align 2
SYM (environ):
        .long 0

Set up an empty space for the environment. This is bogus on any most ROM monitor, but we setup a valid address for it, and pass it to main. At least that way if an application checks for it, it won’t crash.

 	.align	2
	.text
	.global SYM (stack)

	.global SYM (main)
	.global SYM (exit)
/* 
 * This really should be __bss_start, not SYM (__bss_start).
 */
	.global __bss_start

Setup a few global symbols that get used elsewhere. __bss_start needs to be unchanged, as it’s setup by the linker script.

/*
 * start -- set things up so the application will run.
 */
SYM (start):
	link	a6, #-8
	moveal	#SYM (stack) + stack_size, sp

/*
 * zerobss -- zero out the bss section
 */
	moveal	#__bss_start, a0
	moveal	#SYM (end), a1
1:
	movel	#0, (a0)
	leal	4(a0), a0
	cmpal	a0, a1
	bne	1b

The global symbol start is used by the linker as the default address to use for the .text section. then it zeros the .bss section so the uninitialized data will all be cleared. Some programs have wild side effects from having the .bss section let uncleared. Particularly it causes problems with some implementations of malloc.

/*
 * Call the main routine from the application to get it going.
 * main (argc, argv, environ)
 * We pass argv as a pointer to NULL.
 */
        pea     0
        pea     SYM (environ)
        pea     sp@(4)
        pea     0
	jsr	SYM (main)
	movel	d0, sp@-

Setup the environment pointer and jump to main(). When main() returns, it drops down to the exit routine below.

/*
 * _exit -- Exit from the application. Normally we cause a user trap
 *          to return to the ROM monitor for another run.
 */
SYM (exit):
	trap	#0

Implementing exit here is easy. Both the rom68k and bug can handle a user caused exception of zero with no side effects. Although the bug monitor has a user caused trap that will return control to the ROM monitor, this solution has been more portable.


Next: , Up: Libraries   [Contents]