Gcc - Geek went Freak!


Ubuntu: Cross-compile baremetal Cortex-M assembly program

In this post, we will cross-compile a small baremetal program for ARM processor on an Ubuntu machine.

ARM cross-compile toolchain

First step is to install the ARM cross-compiler toolchain. Luckily Ubuntu already has it in its software repository. Execute the following command in the terminal to install ARM EABI compatible tool chain:

sudo apt install gcc-arm-none-eabi

Check the version of the installed compiler using the following command:

arm-none-eabi-gcc --version

Sample baremetal program

Now, we need a sample baremetal program to compile. I have choosen a very simple assembly program.


.global _start
  B _reset /* Reset */
  B . /* Undefined */
  B . /* SWI */
  B . /* Prefetch Abort */
  B . /* Data Abort */
  B . /* reserved */
  B . /* IRQ */
  B . /* FIQ */

  mov r1, #10
  ldr r0, =0x20000000
  str r1, [r0]
  ldr r2, [r0]
  B .


Lets assemble the assembly file using GCC assembler.

arm-none-eabi-as -mcpu=cortex-m3 -g startup.S -o startup.out


Finally lets link the object file startup.out generated by the assembler.

arm-none-eabi-ld -Ttext=0x0 -o startup.elf startup.out

Note: Since the program is very simple, I haven’t used any linker script here.

-Ttext=0x0 option instructs the linker to use 0x0 as the starting address of the instructions.

Ubuntu: Emulate baremetal Cortex-M program

In this post, we will emulate a baremetal program for Cortex-M on Ubuntu PC.


We will need

  1. QEMU emulator for ARM
  2. GDB

Fortunately both of them are available through Ubuntu software repository.

Install them using the following command:

sudo apt install qemu-system-arm
sudo apt install gdb-arm-none-eabi


We will use QEMU for emulation. GDB is used to control and inspect QEMU.

Launch QEMU

qemu-system-arm -monitor stdio -machine lm3s811evb -cpu cortex-m3 -s -S -kernel startup.elf
  • -monitor stdio
    Access QEMU HMI monitor from terminal
  • -machine lm3s811evb -cpu cortex-m3
    Select machine lm3s811evb and CPU cortex-m3
  • -s
    Start GDB server on localhost:1234
  • -S
    Don’t start execution. This is used so we can start and control execution from GDB
  • -kernel startup.elf
    The executable file to execute

Launch GDB client

arm-none-eabi-gdb startup.elf

You should now be in GDB interactive console.

Connect to QEMU

Lets connect to GDB server hosted by QEMU from the GDB client

target remote localhost:1234

Run the program



Press <Ctrl-c> to stop execution.

Check registers

In lines 13, 14 and 16, we update registers r1, r0 and r2 respectively. They should hold values 0x20000000, 10 and 10 respectively.

info reg r0 r1 r2

Should print:

r0 0x20000000 536870912
r1 0xa 10
r2 0xa 10

Check memory

We write value 10 to memory address 0x20000000. Lets check if that worked correctly:

x/4wx 0x20000000

0x20000000: 0x0000000a 0x00000000 0x00000000 0x00000000

Mac OS X: Install GCC

Mac OS X comes with LLVM compiler tool chain with clang. Hence, you will have the pseudo gcc command aliased to clang.

If you want real gcc compiler tool chain, execute the following commands:

brew install gcc

But this won’t install important commands like gdb, objdump, etc. To install them,

brew install gdb brew install binutils

GCC: Disassemble with opcodes

objfmt displays opcodes by default. But the problem with objdump (discussed in this post) is that it doesn’t allow look up of specific function or address range.

objfmt -d one.o

The above command displays the following output:

Disassembly of section .text:


: 0: e3a00000 mov r0, #0 4: eafffffe b 0

gdb lets us display disassembled code of specific functions and address range. But it doesn’t display opcodes by default. To display opcodes, /r switch can be used:

gdb.exe --batch --ex 'file one.o' --ex 'disassemble /r main'

The above command displays the following output:

Dump of assembler code for function main: 0x00000000 <+0>: 00 00 a0 e3 mov r0, #0 0x00000004 <+4>: fe ff ff ea b 0x4 End of assembler dump.

GCC Linker script

In this post, we will find out how linker scripts handle RAM sections.

We know that RAM is volatile. Hence, the linker/programmer cannot store anything on RAM. Anything that needs to be on RAM, should be stored in a persistent memory and loaded upon the OS/application startup.

In embedded systems, the persistent memory is usually flash. The initialization code copies the RAM data from the flash to the RAM before invoking main.

RAM usually contains 4 types of data:

  1. Initialized global and static variables (.data)
  2. Uninitialized or zero initialized global and static variables (.bss)
  3. Heap (.heap)
  4. Stack (.stack)

Both heap and stack are runtime oriented. Compiler doesn’t provide any guarantees on initial data of these sections. .bss section is initialized to zero, so the linker doesn’t have to store any data regarding it. The initialization code clears the .bss section in RAM.

.data section is the most interesting section. The data required to initialize this section is stored on flash and moved to the RAM during startup. Lets see how this is setup in linker script.

Consider the following memory map:

	  FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1024K
	  RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 256K

The tricky part with .data section is, the space required by it should be allocated on RAM and the required data should be stored on flash. One could write linker script output sections like this:

.ramdata :

.data :
} >RAM

What this basically does is duplicate input data sections in both flash(ramdata) and RAM(data). Eventhough this works, it creates a huge hex file. Also it might cause problems if flash routines are used to write to RAM address during programming.

GCC linker script provides a neat way to achieve this. Enter “AT” command! Using “AT” command, we can ask the linker to allocate space at one place(typically RAM) and actually store the data at another place (typically flash).

} >RAM

If you would like to specify a memory area instead of an address, this syntax can be used:

.ramdata :