Embedded reverse: code tracing via SPI-flash

What for?!

This is probably the first emotion of most people who read the title of the article. However, let’s imagine the following situation: in the process of researching a device, you come to the point where you can execute your code inside the device, and you want to output the cherished “Hello, Habr!” Through the UART, blink the LEDs on the board like on a Christmas tree, or turn on JTAG, but all this is not at your disposal.

In this article, we will show you an unusual way to trace device firmware using an SPI flash drive emulator.


In the article Reversing a USB-SATA adapter (the story of one trainee), we already talked about the SPI emulator EM100-Pro… Its main application is to simulate the operation of various SPI-flash memory chips, as well as to save a traffic log on the SPI bus. This is very convenient if you need to frequently change the contents of the memory, since you do not need to unsolder the microcircuits from the board or connect with special clips. It is enough to change the memory image and press the download button in the emulator GUI.

So what about SPI tracing? The emulator also has such a function. For some reason she is not very advertised in leadership, and in the only presentations, which we managed to find, says that the specification can be requested by mail from the developer company. Not very convenient, right?

Here’s how it works.

Now about everything in order

The SPI emulator is designed to quickly change the firmware or configuration stored in an external SPI flash drive of the device. In addition to its direct purpose, the emulator is able to interact with the master device using an additional protocol that provides support for debugging functions. To test these capabilities, a laboratory bench was assembled.

On the SM32F4DISCOVERY debug board, the SPI1 interface is enabled and an emulator is connected to these pins. Naturally, in real studies, the emulator is connected directly instead of the SPI-Flash ROM.

From the documentation read, it is clear that you can control the SPI emulator from a microcontroller. Below is a table with the available commands and their format.

To organize the debug interface, we need the Write uFIFO command. uFIFO is a 512 byte buffer inside the emulator, which is used to transfer data from the microcontroller to the PC. This buffer can be filled with data, and it can be read from a PC. The most interesting thing is that if you fill uFIFO with data of a special format, then the emulator GUI will automatically recognize them. The table below shows the packet format and data types.

In general, sending data to uFIFO looks like this:

techCmdCMD write uFIFOpreByte1preByte2preByte3preByte4typeDatalenDataData …..
11hdon’t careC0h40h44h36h47h1-7x

As a magic sequence, Dediprog decided to use the word “@ D6G”, clearly alluding to the English “debug”.

ASCII mode

For starters, it was very interesting to replace the UART. Usually some debug lines are thrown there or program execution is traced. From the table above, you can see that ASCII strings are recognized by the emulator if you write code 05h to uFIFO in the Data Type field. Below is a listing that allows you to organize the output of text information in ASCII encoding.

// Функция записи данных в uFIFO
void SpiFlash_WriteUFifo(unsigned int count, unsigned char* buffer)
    // 0x11 - custom-команда эмулятора
    // 0x00 - don't care byte
    // 0xC0 - выбираем запись в uFIFO
    char writeFifoCmd[3] = {0x11, 0x00, 0xC0};
    HAL_SPI_Transmit(&hspi1, writeFifoCmd, sizeof(writeFifoCmd), HAL_MAX_DEALY);
    HAL_SPI_Transmit(&hspi1, buffer, count, HAL_MAX_DEALY);     // Отправляем данные

// Функция отправки ASCII строки
void SpiFlash_TxString(char* str)
    // preamble data packet, 0x05 - ASCII type
    char premsg[6] = {0x40, 0x44, 0x36, 0x47, 0x05};  
    premsg[5] = strlen(str);
    SpiFlash_WriteUFifo(sizeof(premsg), premsg);    // Отправляем "магию"
    SpiFlash_WriteUFifo(strlen(str), str);          // Выводим строку

// Вызов функции
SpiFlash_TxString("Hello, Habr!n");
SpiFlash_TxString("Program start!n");
SpiFlash_TxString("While loop:n");

We compile, flash the MK, turn on the GUI of the emulator, launch the SPI Hyper Terminal and when the MK starts, we see our lines in the terminal.

HEX mode

Of course, the emulator does not only support ASCII output. It can be used to organize the display, for example, of the contents of the internal Flash-memory of the MK. To do this, set the data type 04h in the Data Type field. Below is the code for sending data in HEX format.

// Функция отправки HEX данных
void SpiFlash_TxHexArray(unsigned char* hexArray, unsigned int count){
    char premsg[6] = {0x40, 0x44, 0x36, 0x47, 0x04};
    premsg[5] = count;
    SpiFlash_WriteUFifo(sizeof(premsg), premsg);
    SpiFlash_WriteUFifo(count, hexArray);

// Небольшой массив-счетчик
unsigned char array[0x10];
for(int i = 0; i < 0x10; i++)
    array[i] = i;
SpiFlash_TxString("Array dump:");
SpiFlash_TxHexArray(array, 0x10);
// Дампим внутреннюю флешку МК
Spi_Flash_TxString("Memory dump:");
Spi_Flash_TxHexArray((char*)0x08000000, 0x10);

The figure below shows the code in action, as well as the window from ST-LINK Utility. You can observe some differences in the output of information. This is due to the fact that inside the STM32 there is a Cortex-M core, in which information is stored in little-endian...

Checkpoint Mode

It is needed to add breakpoints to debug information, and allows you to organize a convenient and compact tracing of function execution. In this case, the emulator software will search the root directory for the "Checkpoint.txt" file in the ini-format containing the names of the checkpoints. In this case, an additional listing of breakpoints will open in the software window, allowing you to control the sequence of code execution and perform quick navigation in the debug console.

// Установка checkpoint-a 
void SpiFlash_SetCheckPoints(unsigned char* points, unsigned char countPoints)
    char premsg[6] = {0x40, 0x44, 0x36, 0x47, 0x01};    
    premsg[5] = countPoints;    
    SpiFlash_WriteUFifo(sizeof(premsg), premsg);
    SpiFlash_WriteUFifo(countPoints, points);   

//Вызов функции 
unsigned char checkPoint[1];
checkPoint[0] = 0x1;
SpiFlash_SetCheckPoints(checkPoint, 1);
//...немного кода
checkPoint[0] = 0x2;
SpiFlash_SetCheckPoints(checkPoint, 1);
//...немного кода
checkPoint[0] = 0x3;
SpiFlash_SetCheckPoints(checkPoint, 1);

Now it is enough to call the function anywhere in the firmware SpiFlash_SetCheckPoints(checkPoint, 1), and quietly trace the flow of execution.

Lookup table mode

Lookup table can be used as an additional debugging tool. This mode is a symbiosis of ASCII and Checkpoint modes. It allows you to pull in pre-prepared messages from a file selected in the GUI configuration, and add a dynamic line to them from the code called in the MK. This approach saves precious space in the MC and allows, for example, to show the current values ​​of variables, framing them with an informative signature.

// Функция вывода строки из справочной таблицы 
void SpiFlash_LookUpTable(uint16_t index, uint16_t AsciiChar)
    char premsg[10] = {0x40, 0x44, 0x36, 0x47, 0x07, 0x04};  
    premsg[6] = (index >> 8) & 0xFF;
    premsg[7] = index & 0xFF;
    premsg[8] = (AsciiChar >> 8) & 0xFF;
    premsg[9] = AsciiChar & 0xFF;
    SpiFlash_WriteUFifo(10, premsg);

// Вызов функции
uint16_t varShow = 0x3031;              // ASCII: 01
SpiFlash_LookUpTable(0x0, varShow);     // Выбрать 0-ю строку из таблицы
varShow++;                              // Increment-> ASCII: 02
SpiFlash_LookUpTable(0x0, varShow);
SpiFlash_LookUpTable(0x1, 0x3535);      // Выбрать 1-ю строку из таблицы (ASCII: 55)

In the picture below, we have displayed the current value of the variable varShowby adding the appropriate signature to it. In our opinion, this mode lacks support for the HEX data output mode.

Timestamp mode

It so happens that it is necessary not only to trace the execution flow, but also to measure the time it takes to execute a block of code. For such cases, the emulator portfolio has a special Timestamp mode that supports the output of timestamps. We try the mode, having previously started a timer on the MK. If you configure the increment of the variable associated with the timer so that it increases by 1 every 10 ns, then the emulator GUI will display the time in real time. Below is the code of the function that creates a uFIFO time stamp in the buffer.

// Функция отправки временной метки 
void SpiFlash_TxTimeStamp(int timeValue)
    char premsg[] = {0x40, 0x44, 0x36, 0x47, 0x06, 0x04};   
    SpiFlash_WriteUFifo(sizeof(premsg), premsg);

    unsigned char timeBuffer[4];
    timeBuffer[0] = (timeValue >> 24) & 0xFF;
    timeBuffer[1] = (timeValue >> 16) & 0xFF;
    timeBuffer[2] = (timeValue >> 8) & 0xFF;
    timeBuffer[3] = timeValue & 0xFF;
    SpiFlash_WriteUFifo(sizeof(timeBuffer), timeBuffer);

// Вызов функции
SpiFlash_TxTimeStamp(100000000);    // 100000000 * 10 нс = 1 сек

During test output to a function SpiFlash_TxTimeStamp the value of 100,000,000 samples is transmitted, multiplied by 10 ns and we get 1 second. This creates a timestamp corresponding to 1 second.

A period of 10 ns corresponds to a frequency of 100,000,000 Hz = 100MHz, which may not be supported by your device. In this case, it is enough to recalculate our magic constant according to your needs.


In this short article, we tried to describe an unusual way of tracing devices, in which there is no UART or JTAG familiar to everyone. Of course, the method is not universal, since not all research objects have an SPI flash drive on board, or at least the interface itself. In addition, the cost of the emulator we use (~ $ 600) may seem like enough "biting" for independent researchers, but no one bothers to collect their own 🙂

What's the most unusual debugging technique you've used? We invite you to share your research history and discuss them in the comments.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *