Linux Build/Debug Guide

Last updated: January 7th, 2019

Intro

This guide is about setting up a debug environment for the Microchip SAME70 Xplained board and compiling Hermes. Other boards might need modification to these proceedures.

Download and Build OpenOCD

OpenOCD is the interface to Atmel's debugger. You need at least version 0.10.0 to get support for the SAME70 board. Note: if you need support for a different debugger interface, you can run ./configure --help to se a list of all supported interfaces. You need to enable your interface at the configuration stage.

Note for SAME54

The Microchip/Atmel SAME54 (Cortex-M4) is not supported by the mainline OpenOCD. You need a patched version of OpenOCD v0.10.0 to work with this device (including the SAME54 Xplained Pro dev board). The patches have been written but not merged into the mainline source. Patches are available here. You can also download a pre-patched version here. If you need to use the patched version of OpenOCD, download and untar instead of git cloning from the stock repo in the second line below.

Clone the OpenOCD Repo:

# sudo apt install libhidapi-dev libtool autotools-dev pkg-config autoconf automake texinfo libusb-1.0
# git clone https://git.code.sf.net/p/openocd/code openocd-code
# cd openocd-code
# autoreconf -i
# ./bootstrap
# ./configure --enable-cmsis-dap
# make
# sudo make install
                                        
udev Rules

We now need to add a udev rule for the CMSIS debugger. First thing we need to do is find the Vendor ID and Product ID for our device using lsusb. If you don't have lsusb, run apt install usbutils in Ubuntu.

# lsusb
Bus 001 Device 008: ID 03eb:2111 Atmel Corp.

lsusb will give you a bunch of lines of output. Look for the one that corresponds to your debugger. In the example above, my Vendor ID is 03eb and Product ID is 2111. Now make a new file called /etc/udev/rules.d/98-debugger.rules with the following contents, replacing with your vendor ID and product ID:

# mbed CMSIS-DAP
ATTRS{idVendor}=="03eb", ATTRS{idProduct}=="2111", MODE="664", GROUP="plugdev"
KERNEL=="hidraw*", ATTRS{idVendor}=="03eb", ATTRS{idProduct}=="2111", MODE="664", GROUP="plugdev"

Reload the udev rules and replug your debugger

# sudo udevadm control --reload-rules
Start OpenOCD

From the openocd-code directory, test your new OpenOCD binary:

# openocd -f ./tcl/board/atmel_same70_xplained.cfg &

This will start OpenOCD listening on port 3333. We will need to connect to it with gdb. It will need to stay running during our debug session, so open another terminal for the next bit.

GDB

OpenOCD is just a daemon that provides a communication interface to the board. We will need to use gdb to load software and step through code. First, we will install ARM gdb. We can use this opportunity to install the ARM cross compiler too:

Note about Buggy gcc in Ubuntu 18.04

Ubuntu 18.04 has a buggy version of arm-none-eabi-gcc that causes errors linking startup code from crt0.o:

/usr/lib/gcc/arm-none-eabi/6.3.1/../../../arm-none-eabi/bin/ld: error: /usr/lib/gcc/arm-none-eabi/6.3.1/../../../arm-none-eabi/lib/crt0.o: Conflicting CPU architectures 13/1 /usr/lib/gcc/arm-none-eabi/6.3.1/../../../arm-none-eabi/bin/ld: failed to merge target specific data of file /usr/lib/gcc/arm-none-eabi/6.3.1/../../../arm-none-eabi/lib/crt0.o

This will not be a problem for Hermes, since it does not use crt0.o, but some other apps might. If you have this problem, do the following:

sudo apt remove gcc-arm-none-eabi binutils-arm-none-eabi sudo add-apt-repository ppa:team-gcc-arm-embedded/ppa sudo apt-get update sudo apt-get install gcc-arm-embedded
# sudo apt install gdb-arm-none-eabi gcc-arm-none-eabi
Test the Toolchain

We will now write a simple C program consisting of a stack, a vector table, and a program loop to test gdb and our connection to the board. This has nothing to do with Hermes. We're just checking to see if we can build, load, and run a program with our newly installed tools.


int main(void) ;

unsigned char stack[1024];

void *vectors[]  __attribute__((section(".vectors"))) = {stack+1024, (void*)main};


int main(void) {
    unsigned long long i = 0;
    while(1) {
        i++;
    }
    return 0;
}
                                        

Now compile this with the gcc cross compiler. You will need the right linker script for your device to tell the compiler where code and data should be placed in memory. Hermes comes with linker scripts for the devices it supports, and we'll use the one for the SAME70:

# arm-none-eabi-gcc -o test.elf -mthumb -T hermes/src/devices/microchip/sam/same70q21.ld test.c -nostartfiles -g -Wl,-emain

Now start up gdb and load the test.elf binary


# arm-none-eabi-gdb a.out
(gdb) target remote localhost:3333
(gdb) load test.elf

                                        

You should be able to run this simple program now using standard gdb features. You can use layout asm or layout src to view assembly or source for the program as you're stepping through it. You can use layout regs to view the registers.

Hermes

Firmware based on Hermes comes in several separate pieces. The Hermes hypervisor is built as an independent library. Each guest is compiled into separate ELF files. They are all linked together into a single object file which is loaded into flash memory on the board.

Download and Build Hermes

# git clone https://bitbucket.org/naklingensmi/hermes.git
# cd hermes
# make
                                        

This will produce an object file called libhermes.a

Building a Guest

Each guest will be built in a separate directory tree with its own makefile.

A simple guest

Below is a simple guest similar to the test program we used above to test gdb. It contains (1) a vector table, (2) startup code, and (3) a main program that runs in an infinite loop.



#include <stdint.h>

void dummyHandler();
void dummyguest();
void __start();

uint8_t dummyStack[1024] __attribute__ ((aligned (32)));

uint32_t *dummyVectorTable[] = {

(void*)dummyStack + sizeof(dummyStack),
(void*)__start,
(void*)dummyHandler,
(void*)dummyHandler,
(void*)dummyHandler,
(void*)dummyHandler,
(void*)dummyHandler,
(void*)dummyHandler,
(void*)dummyHandler,
(void*)dummyHandler,
(void*)dummyHandler,
(void*)dummyHandler,
(void*)dummyHandler,
(void*)dummyHandler,
(void*)dummyHandler,
(void*)dummyHandler };

void __start(){
    asm("ldr r0,=dummyVectorTable\n\
         ldr sp,[r0]"  : : : "r0");
    dummyguest();
}

void dummyHandler() {
    while(1);
}

void dummyguest(){
    uint16_t counter = 0;

    while(1) {
        counter++;
    }
}
                                        

dummyStack is the 1-kB stack for this guest. Each guest must have its own stack, declared as a C-array. dummyVectorTable is the vector table for this guest. We have truncated it to 16 entries, but real apps should have complete vector tables. This should be filled out in exactly the same way as it would be if it were running on the bare metal (not in a hypervisor). __start is the guest's startup code, which initializes the guest's stack pointer and calls the main function.

# arm-none-eabi-gcc -o test.elf -mthumb -T hermes/src/devices/microchip/sam/same70q21.ld test.c -nostartfiles -g -Wl,-emain

Now start up gdb and load the test.elf binary


# arm-none-eabi-gdb a.out
(gdb) target remote localhost:3333
(gdb) load test.elf

                                        

You should be able to run this simple program now using standard gdb features. You can use layout asm or layout src to view assembly or source for the program as you're stepping through it. You can use layout regs to view the registers.

Any questions?

Instance Theme

If you're running into problems, get in touch.

naklingensmi at wisc dot edu