On Designing Bootloaders and Grey-box-Testing Firmware (Part 1/2)  


I am currently TAing SE350. The students’ deliverable is a small real-time executive kernel (RTX) that runs on a Freescale Coldfire chip. We got the idea of building an automated embedded test suite for the students’ term projects. However, instead of having to compile the students’ code from scratch, we would only want to take their firmware binary directly and test it. This testing would involve injecting several test processes into the students’ OS. These tests would stress their implementation and dump the results to a serial port of the actual Coldfire board. Having worked in the embedded field, this problem is similar to integrating bootloaders with firmware. In the project, the bootloader is the testing code and the actual firmware is the code to be tested.

You end up with two pieces of binary code that will be programmed into your device. So the challenge is to make them talk to each other. In the case of the bootloader, you have the bootloader invoking the firmware, and in the case of the testing code, the testing code invokes the RTX. In the following sections, I describe the steps for:

  • Building a toolchain,
  • Developing the bootloader and firmware code,
  • And integrating the different SREC/S19 files

In the second part, I will describe how to leverage the established framework to design a native testing suite.

What Tools Do I Need?

In order to run code on bare (i.e., no existing OS) chips, you need to have a toolchain that translates your source code into ELF files (ELF = Executable and Linkable Format) and SREC/S19 files for flashing onto the device. We need:

A detailed description of how to build these tools is already here on my website.

Step 2: Where to Put My Firmware Code?

If you are going to integrate two pieces of code, you need to make sure they do not overlap in flash and do not access each other in an undesired fashion. Since you are developing on the bare hardware, you actually have complete control over the former property and can enforce the latter by careful code design. Your generated ELF file will consist of three major sections, as follows:

  • The text segment (.text), where your executable code goes,
  • The data segment (.data), where initialized global variables go,
  • The BSS segment (.bss), where uninitialized global variables go.

Note that the BSS segment exists for some historic reason, and in almost all OS lectures it is implied by the data segment (i.e., data := data + BSS). By convention, the text segment starts at a lower address than the data and BSS segments. When your program is executed, the values of the data and BSS segments are copied into the main memory. However, the size is not established at runtime. The program’s stack for function calls and local variables resides on the heap, which is by convention allocated after the BSS segment and grows dynamically. The GNU toolchain you just built includes the GNU Linker, which allows specifying these locations explicitly by linker scripts (i.e., LD files). GNU LD files have a simple structure describing:

  • The memory banks and locations,
  • How to spread your code across those locations.

The following simple example file describes an embedded system (i.e., in my case: CJDesign’s MCF5307 board). Most evaluation boards, like mine, come with a huge SRAM and actually have a ROM that allows you to load code into main memory. As such, we will dump all code into SRAM for testing purposes. The following example specifies the assignment of 1 MB at address 0x10100000 to SRAM and dumps all sections of the code into that segment. Hint: the space after the section names is required to ensure the uniqueness of the names. The actual code will execute from the SRAM start address, which is 0x10100000.

/* firmware.ld */
MEMORY
{
  sram        : ORIGIN = 0x10100000, LENGTH = 1M
}

SECTIONS
{
  .text :
  {
    *(.text)
    *(.rodata*)
  } > sram

  .data :
  {
    *(.data)
  } > sram

  .bss :
  {
    *(.bss)
  } > sram
}

A note for SE350 students: Please do not attempt to hack the linker file provided by the course. You may run into serious trouble by using my linker script or hacking the existing one!

Step 3: Building Your Firmware

To compile and link your source (firmware.c) with this file, use the following command:

m68k-elf-gcc -Tfirmware.ld -Map=firmware.map -o firmware.elf firmware.c

You may want to generate a listing of the file to see that everything is at the expected location, as follows:

m68k-elf-objdump -xdC firmware.elf > firmware.lst

In order to flash or deliver this file to the customer, we actually need to convert it into the Motorola S19/SREC file as follows:

m68k-elf-objcopy --output-format=srec firmware.elf firmware.s19

Step 4: Building the Other Piece of Code

What’s left to build is the bootloader. In order to ensure distinct flash and memory regions, you need to provide another linker script that puts all the bootloader code into a different location than the other code. A wise choice is to put this code as far away from the actual firmware as possible, possibly at the end of the available memory. The following code offsets the memory bank by 1MB and dumps the code there.

/* bootloader.ld */
MEMORY
{
  sram        : ORIGIN = 0x10200000, LENGTH = 1M
}

__FIRMWARE__ = 0x10100000;

SECTIONS
{
  .text :
  {
    *(.text)
    *(.rodata*)
  } > sram

  .data :
  {
    *(.data)
  } > sram

  .bss :
  {
    *(.bss)
  } > sram
}

In order to invoke the firmware, we need to put a symbol inside the linker script that identifies the expected starting address of the firmware, which is in this case called firmware. This symbol can be used from the C code directly as a function call. In order to avoid any compilation warnings, you should forward declare this function as external. The compilation and transformation into the S19 file is analogous to creating the firmware code. You should end up with a bootloader.s19.

Step 5: Throwing Things Together

In practice, when you build an embedded device, it should have the bootloader and some firmware programmed in when it leaves assembly. In many cases, the interface that the end-user has to the device (i.e., a USB connector) is different from what you have during assembly (e.g., an in-system flash tool). As such, it is necessary to combine the bootloader and the firmware together.

The S19 format is a simple ASCII data exchange format, originally developed by Motorola, for executable code. It is widely accepted by most programmers for Motorola-based embedded systems. The files are processed line by line; each line contains a control code, a record size, an address, an optional data sequence, and a checksum. You can find the details here.

GNU Object Copy usually outputs:

  • A block header (S0),
  • A sequence of data records (S1-S3),
  • And the start address (S5-S9).

The block header usually contains the name of the file (e.g., firmware.s19 or bootloader.s19). Most ROM loaders on evaluation boards will actually process the start address record, which is in our case the declared origin of the SRAM, and fail to load if they do not find it, so it needs to be included.

So in order to put the bootloader and the firmware together into one file, you need to provide one header, the data of both programs, and one starting address:

  • Header: any of the firmware/bootloader, or a custom header (see below)
  • Data: concatenate the data records of both original programs
  • Starting address: the start address of the bootloader

Step 5a: Composing Your Own Header

Yes, geeky people like me actually enjoy implementing checksum algorithms and branding their creation. In order to do so, we need to dive into the checksum procedure used by S-records. According to Wikipedia, the checksum is ”[…] the least significant byte of ones’ complement of the sum of the values represented by the two hex digit pairs for the byte count, address and data fields.” So guys, it’s time to dig out those algorithm-class notes and figure that out, … oh wait …, found it:

  • Sum up all bytes starting from the byte count record.
  • Set: checksum = 0xFF – (0x00FF & sum)

Why the hell would anyone use such a checksumming algorithm? The answer is simple: It can be easily checked! While processing the S19 records, you can actually sum everything up, including the provided checksum, and should get 0xFF. That is a simple compare operation and can be evaluated in no time.

Step 6: Testing Your Creation

If you build the Coldfire simulator according to my instructions, you can invoke the simulator as follows:

coldfire --board cjdesign-5307.board

and load the code like this:

Use CTRL-C (SIGINT) to cause autovector interrupt 7 (return to monitor)
Loading memory modules...
Loading board configuration...
Opened [/usr/local/coldfire/share/coldfire/cjdesign-5307.board]
Board ID: CJDesign
CPU: 5307 (Motorola Coldfire 5307)
unimplemented instructions: CPUSHL PULSE WDDATA WDEBUG
69 instructions registered
building instruction cache... done.

Memory segments: dram  timer0  timer1  uart0(on port 5206)
uart1(on port 5207)  sim  flash  sram

Remember to telnet to the above ports if you want to see any output!

Hard Reset...
Initializing monitor...
Enter 'help' for help.
dBug> dl merged.s19
Downloading S-Record...
Done downloading S-Record.

dBug> go 0x10100000 <- the actual firmware
... some garbage, because RTS returns to nowhere...
dBug> go 0x10200000 <- the boot-loader invoking the firmware
You should yield the following output on the terminal (telnet localhost 5206).
Trying ::1...
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.

uart0
Inside firmware! <- 1st go of the firmware
Inside bootloader! <- 2nd go inside firmware!
Inside firmware!
Back in bootloader!

Discussion

In this part of the how-to, I explained the basics of building two pieces of binary Coldfire code and integrating them into a single file that can be processed by most programmers and ROM loaders. A popular application is the integration of bootloader and firmware code for embedded system assembly. Another application is embedded grey-box testing. In this technique, instead of a bootloader, a test suite is evaluated against the firmware to check for potential defects. In the next post, I’ll describe how to design such a test framework.

You can find the sample code for this post below. The code will contain some modified linker scripts that deal with particular alignment problems of the simulator. Furthermore, the bootloader and the firmware should have different stacks, so some assembly files have been added to do so. The S19 merging is done by the Python script merge.py.


Cross posted from my old Blog


Published: 2010-02-06
Updated  : 2025-10-04
Not a spam bot? Want to leave comments or provide editorial guidance? Please click any of the social links below and make an effort to connect. I promise I read all messages and will respond at my choosing.
← On Designing Boot Loaders and Grey-box-Testing Firmware (Part 2/2) Building the GNU Compiler Toolchain for the Coldfire Target →