Starting Cortex-M3 Development Using the GNU Tool Chain - Part 1
From EdWiki
Setting up the ARM Lab
Please setup an Embedded ARM Lab as described here.
Assembly Language Development Flow
Hello ARM
In this section, we will learn to assemble a simple ARM Cortex-M3 program, and test it using Qemu.
The assembly program source file consists of a sequence of statements, one per line. Each statement has the following format.
label: instruction @ comment
Each of the components is optional.
- label
- The label is a convenient way to refer to the location of the instruction in memory. The label can be used where ever an address can appear, for example as an operand of branch instruction. The label name should consist of alphabets, digits, _ and $.
- comment
- A comment starts with an @, and the characters that appear after an @ are ignored.
- instruction
- The instruction could be an ARM instruction or an assembler directive. Assembler directives are commands to the assembler. Assembler directives always start with a . (period).
Here is a very simple ARM assembly program to add two numbers.
/**************************************************************************
**
** File : add.s
**
** Abstract : Add 2-numbers in registers
**
** Target : Cortex-M3
**
** Environment : GNU Tools
**
**************************************************************************/
.syntax unified
.thumb
.global pfnVectors
.global Default_Handler
.equ Top_Of_Stack, 0x20004000 /* end of 16K RAM */
/****************************************************************************
*
* The minimal vector table for a Cortex M3. Note that the proper constructs
* must be placed on this to ensure that it ends up at physical address
* 0x0000.0000.
*
****************************************************************************/
.text
pfnVectors:
.word Top_Of_Stack @ msp = 0x20004000
.word _start @ Starting Program address
.word Default_Handler @ NMI_Handler
.word Default_Handler @ HardFault_Handler
.word Default_Handler @ MemManage_Handler
.word Default_Handler @ BusFault_Handler
.word Default_Handler @ UsageFault_Handler
.word 0 @ 7
.word 0 @ To
.word 0 @ 10
.word 0 @ Reserved
.word Default_Handler @ SVC_Handler
.word Default_Handler @ DebugMon_Handler
.word 0 @ Reserved
.word Default_Handler @ PendSV_Handler
.word Default_Handler @ SysTick_Handler
.word Default_Handler @ IRQ_Handler
.word Default_Handler @ IRQ_andler
/**
* This is the code that gets called when the processor first
* starts execution following a reset.
*/
.type _start, %function
_start: @ _start label is required by the linker
mov r0, #5
mov r1, #4
add r2, r1, r0
stop: bl stop
/**
* This is the code that gets called when the processor receives an
* unexpected interrupt. This simply enters an infinite loop, preserving
* the system state for examination by a debugger.
*
*/
.type Default_Handler, %function
Default_Handler:
Infinite_Loop:
b Infinite_Loop
.end
Building the Binary
Save the program in a file say add.s. To assemble the file, invoke the GNU Toolchain’s assembler as, as shown in the following command.
$ arm-none-eabi-as -mcpu=cortex-m3 -mthumb -gwarf2 -o add.o add.s
- arm-none-eabi- Cross toolchain prefix
- -mcpu=cortex-m3 Specifies the core architecture used
- -thumb Specifies the code generation.
- -gwarf2 Specifies the debug information output.
- -o Specifies the output file
To generate the executable file, invoke the GNU Toolchain’s linker ld, as shown in the following command.
$ arm-none-eabi-ld -Ttext=0x0 -Tdata=0x20000000 -o add.elf add.o
arm-none-eabi- Cross toolchain prefix
-Ttext=0x0 Addresses should be assigned to instructions.
-Tdata=0x20000000 Addresses should be assigned to data sections also..
-o Specifies the output file
To view the address assignment for various labels, the nm command can be used as shown below.
$ arm-none-eabi-nm add.elf
00000058 T Default_Handler 00000058 t Infinite_Loop 20000000 A __bss_end__ 20000000 A __bss_start 20000000 A __bss_start__ 20000000 T __data_start 20000000 A __end__ 20000000 A _bss_end__ 20000000 A _edata 20000000 A _end 20004000 a _estack 00080000 N _stack 00000048 W _start 00000054 t stop 00000000 T pfnVectors
Note: Except for _start, stop, and pfnVectors are linker generated symbols. The address assignment for the labels _start is 0x00000048 and _estack (Top_Of_Stack) is 0x20004000.
The output file created by ld is in a format called ELF. Various file formats are available for storing executable code.
Executing in Qemu
Qemu supports many cpu models, since we are developing programs for ARM, we will use - qemu-system-arm. We can Execute/Debug in Qemu by attaching to GDB. Open a terminal window and execute the following command.
$ qemu-system-arm -cpu cortex-m3 -S -gdb tcp::1234 -nographic -kernel add.elf
- -cpu cortex-m3 is the option for simulating cortex-m3 processor
- -S Do not start CPU at startup
- -gdb, tcp::1234 Attach to GDB through tcp port 1234
- -nographic disable features we don't need
- -kernel add.elf tells qemu to execute directly from our object file.
Run/Debug the program (in another terminal window)
$ arm-none-eabi-gdb add.elf $ (gdb) target remote localhost:1234 $ (gdb) load $ (gdb) break _start $ (gdb) continue
The program should break at _start label. Now, we can exmine the memory, registers, or dis-assemble the code.
- Dumping memory contents
(gdb) x/18wx 0x0 0x0 <pfnVectors>: 0x20004000 0x00000049 0x00000059 0x00000059 0x10 <pfnVectors+16>: 0x00000059 0x00000059 0x00000059 0x00000000 0x20 <pfnVectors+32>: 0x00000000 0x00000000 0x00000000 0x00000059 0x30 <pfnVectors+48>: 0x00000059 0x00000000 0x00000059 0x00000059 0x40 <pfnVectors+64>: 0x00000059 0x00000059 (gdb)
The above memory dump is vector table. The 1st entry is 0x20004000 which is our Top_Of_Stack (_estack) and the 2nd entry is our program _start address - 0x00000048 (instead of 0x00000048 it is 0x00000049 is stored why?). We can verify these addresses from our above symbol table dump.
- Dis-assembling
(gdb) x/4iw _start => 0x48 <_start>: mov.w r0, #5 0x4c <_start+4>: mov.w r1, #4 0x50 <_start+8>: add.w r2, r1, r0 0x54 <stop>: bl 0x54 <stop> (gdb)
- Single step through the code
(gdb)step
- Verify Registers
(gdb)info registers
- Quit gdb
(gdb) quit A debugging session is active. Inferior 1 [Remote target] will be killed. Quit anyway? (y or n) y
Typing y followed by return takes you back to the terminal prompt.
Please note that when you quit gdb, qemu is also terminated.
QEMU: Terminated via GDBstub
This start-up guide clearly demonstrated that without the hardware, we can write programs for ARM and simulate it in Qemu.