Calling main

The program in Figure 1 contains a function called main. This is common for programs written in C. When a C-program is executed under the control of an operating system, the main-function is called automatically when the program is started. When a C-program is executed as a bare metal program, the main-function will not be called automatically.

The main function in a bare-metal program can be called by adding extra code, outside of the program, and making sure that this added code calls main. The added code is referred to as startup code. Of course it must be ensured that the startup code is executed.

The purpose of the startup code is to perform a minimum of initialization, and when this is done, call the main-function. This initialization often involves operations that are specific for the chosen hardware, such as writing certain values to processor registers. For this reason, it may be convenient to write the startup code in assembly.

When using a personal computer with an Intel-processor, it is possible to execute a program by placing the program, together with its startup code, on a bootable medium. Examples of bootable media are a bootable USB stick or a bootable CD. This requires that yet another piece of code, here referred to as boot code, is executed. The boot code is executed when the computer is started, and it may be referred to as BIOS or UEFI. The boot code may start other code, referred to as a boot loader. As an example boot loader, we mention GRUB. By letting the boot loader call the startup code, which in turn calls the main-function, the program is started.

An example implementation of startup code for a 32-bit Intel processor is shown in Figure 2.

.global start

# GRUB multiboot header, see
# and
.set MAGIC, 0x1BADB002 
.set FLAGS, 0x00000000

.align 4
.long MAGIC
.long FLAGS

# make room for stack
.skip 0x10000

    movl  $stack_bottom, %esp # set up stack
    call  main
    jmp   forever

Figure 2. Startup code for an Intel 32-bit processor.

This the Intel-x86 view - other views are ARM

The startup code in Figure 2 defines a symbol called start. The definition is done using an assembler directive denoted .global. The symbol start defines the start of the program. The instructions to be executed when the program starts can be seen on the lines following the start label, which is annotated using the word start followed by a colon (:). There is an instruction that assigns the value of a symbol, referred to using the notation $stack_bottom, to a register with the name esp, referred to using the notation %esp. Following this instruction is a call to the main-function. This call has the effect that the program code in Figure 1 is executed.

The assignment of a value to the register esp makes it possible to use a stack. A stack can be useful when a program needs a temporary area to store data, for example when storing the address of a place in the program to where it will return at a later stage. The stack is defined using the symbol stack_bottom, and the size of the stack is chosen as the the hexadecimal value 0x10000.

The startup code in Figure 2 requires the use of the GRUB boot loader. This is seen, for example, on the lines defining the symbols MAGIC, FLAGS, and CHECKSUM, which are included in the Multiboot Specification.

The startup code in Figure 2 is inspired by an example from a tutorial on how to create a small program that can be started during boot.

  • Intel-x86
  • ARM