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

Development flow based on the GNU Assembler

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.

  1. /**************************************************************************
  2. **
  3. **  File        : add.s
  4. **
  5. **  Abstract    : Add 2-numbers in registers 
  6. **
  7. **  Target      : Cortex-M3
  8. **
  9. **  Environment : GNU Tools
  10. **
  11. **************************************************************************/
  12.         .syntax unified
  13.         .thumb
  15.         .global pfnVectors
  16.         .global Default_Handler
  18.         .equ Top_Of_Stack, 0x20004000    /* end of 16K RAM */
  20. /****************************************************************************
  21. *
  22. * The minimal vector table for a Cortex M3.  Note that the proper constructs
  23. * must be placed on this to ensure that it ends up at physical address
  24. * 0x0000.0000.
  25. *
  26. ****************************************************************************/
  27.         .text
  29. pfnVectors:
  30.         .word Top_Of_Stack          @ msp = 0x20004000
  31.         .word _start                @ Starting Program address
  32.         .word Default_Handler	    @ NMI_Handler
  33.         .word Default_Handler	    @ HardFault_Handler
  34.         .word Default_Handler	    @ MemManage_Handler
  35.         .word Default_Handler	    @ BusFault_Handler
  36.         .word Default_Handler	    @ UsageFault_Handler
  37.         .word 0                     @ 7
  38.         .word 0                     @ To
  39.         .word 0                     @ 10 
  40.         .word 0                     @ Reserved
  41.         .word Default_Handler	    @ SVC_Handler
  42.         .word Default_Handler	    @ DebugMon_Handler
  43.         .word 0                     @ Reserved
  44.         .word Default_Handler	    @ PendSV_Handler
  45.         .word Default_Handler	    @ SysTick_Handler
  46.         .word Default_Handler	    @ IRQ_Handler
  47.         .word Default_Handler	    @ IRQ_andler
  49. /**
  50.  * This is the code that gets called when the processor first
  51.  * starts execution following a reset. 
  52. */
  53.         .type _start, %function
  55. _start:                              @ _start label is required by the linker
  56.         mov r0, #5
  57.         mov r1, #4
  58.         add r2, r1, r0
  60. stop:   bl stop
  62. /**
  63.  * This is the code that gets called when the processor receives an
  64.  * unexpected interrupt.  This simply enters an infinite loop, preserving
  65.  * the system state for examination by a debugger.
  66.  *
  67. */
  68.         .type Default_Handler, %function
  70. Default_Handler:
  71. Infinite_Loop:
  73.         b Infinite_Loop
  75.         .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

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>
  • Single step through the code
  • 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.