Академический Документы
Профессиональный Документы
Культура Документы
Bare metal programs run without an operating system beneath; coding on bare
metal is useful to deeply understand how a hardware architecture works and what
happens in the lowest levels of an operating system. I wanted to create a simple
example of bare metal program for ARM using free open source tools: RealView
Development Suite is the state of the art of ARM compilers, but it is expensive for
hobbyists; Codesourcery is a company that provides a free version of the GNU gcc
toolchain for ARM cores. In particular, the EABI toolchain must be downloaded
from their download page; I fetched the IA32 GNU/Linux installer. During the
graphical installation, the tools are installed in a sub-folder of the users home; this
is fine if only a single person wants to use the toolchain on that computer,
otherwise it is more efficient to install it system-wide. The path to the toolchain
binaries must be added to the PATH environmental variable; usually the
installation process does it for you, but if it doesnt, the standard installation path is
~/CodeSourcery/Sourcery_G++_Lite/bin.
I created a C file called test.c containing the simplest C code I wanted to compile:
1
2
3
int c_entry() {
return 0;
}
The -mcpu flag indicates the processor for which the code is compiled. I wanted to
target the ARM926EJ-S processor in this example for these reasons:
In order to create a bare metal program we must understand what does the
processor do when it is switched on. The ARM9 architecture begins to execute
code at a determined address, that could be 0 (usually allocated to RAM)
A brief explanation:
To compile this code into an object file (startup.o) run the following command:
$ arm-none-eabi-as -mcpu=arm926ej-s -g startup.s -o startup.o
Now we have test.o and startup.o, that must be linked together to become a
program. The linking process also defines the address where the program is going
to be executed and declares the placement of its sections. To give this information
to the linker, a linker script is used. I wrote this linker script, called test.ld,
following a simple example in the linker manual:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
ENTRY(_Reset)
SECTIONS
{
. = 0x0;
.text : {
startup.o (INTERRUPT_VECTOR)
*(.text)
}
.data : { *(.data) }
.bss : { *(.bss COMMON) }
. = ALIGN(8);
. = . + 0x1000; /* 4kB of stack memory */
stack_top = .;
}
The script tells the linker to place the INTERRUPT_VECTOR section at address
0, and then subsequently place the code (.text), initialized data (.data) and zeroinitialized and uninitialized data (.bss). Line 11 and 12 tells the linker to move
4kByte from the end of the useful sections and then place the stack_top symbol
there. Since the stack grows downwards the stack pointer should not exceed its
own zone, otherwise it will corrupt lower sections. The script on line 1 tells the
linker also that the entry point is at _Reset. To link the program, execute the
following command:
$ arm-none-eabi-ld -T test.ld test.o startup.o -o test.elf
This will generate an ELF binary for ARM that can be executed with a simulator,
or it can be loaded inside a real ARM core on a hardware board; for simplicity we
can use the Codesourcery version of thegdb debugger:
$ arm-none-eabi-gdb test.elf
[...]
This GDB was configured as "--host=i686-pc-linux-gnu --target=arm-noneeabi".
[...]
(gdb) target sim
Connected to the simulator.
(gdb) load
Loading section .text, size 0x50 vma 0x0
Start address 0x0
Transfer rate: 640 bits in <1 sec.
(gdb) break c_entry
Breakpoint 1 at 0x3c: file test.c, line 24.
(gdb) run
Starting program: /home/francesco/src/arm-none-eabi/startup/test.elf
The target sim command tells the debugger to use its internal ARM
simulator,
the load command fills the simulator memory with the binary code,
the debugger places a breakpoint at the beginning of the c_entry function,
the program is executed and stops at the breakpoint,
the program counter (pc register) of the ARM core is set to 0 to emulate a
software reset,
the execution flow can be examined step-by-step in the debugger.
An easier way to debug is using the ddd graphical front-end with the following
command:
$ ddd --debugger arm-none-eabi-gdb test.elf
This program is a starting point to begin to develop more elaborate solutions. The
next step I want to take is using QEMU as the development target: with it I can
interact with some peripherals, even if emulated, and create bare metal embedded
programs more useful in the real world using only free open source software.