This is Part 5 of my Cortex‑M7 without hardware series.

We already have an ELF (Parts 1-3) and understand what it holds (Part 4). Now we finally execute it (what we all came here for) in simulator known as Renode and attach GDB to see that the ELF is running while also demonstrating a couple of classic GDB debugging tricks (printing the loop variable, watchpoints, register/memory inspection).

Renode

Running the ELF executable produced by this project on standard operating systems is not possible due following message being printed:

…cannot execute binary file: Exec format error

As we want to run it without hardware, there is need for extra tooling and that where Renode comes in. 1

Installation

Renode is not a small project and it has plenty of dependencies. I recommend using the official packages / portable build as described in the Installation header on README. 2

The version used by the project is 1.16.0. 3

Minimal Renode script

The supported boards list shows STM32F7 targets, and the Renode repository contains STM32F746 platform descriptions. 4 5

Project contains renode/stm32f746.resc script, that utilizes said descriptions:

$name?="STM32F746"

# Create Machine & Load config
mach create $name
machine LoadPlatformDescription @platforms/cpus/stm32f746.repl

# Enable GDB
machine StartGdbServer 3333

macro reset
"""
    sysbus LoadELF @arm.elf
"""

runMacro $reset

Here is further explanation on what it does:

  1. Machine is created for emulation with mach create 6
    • At this point, machine only has sysbus peripheral
  2. STM32F746 platform is loaded with machine LoadPlatformDescription. 7
    • This adds further peripheral such as memory and CPU, among other things, to machine
  3. Enable debugger to connect to target with machine StartGdbServer 3333 8
  4. Create a reset macro variable with macro reset 9
    • This macro is used when machine Reset method is called
  5. Call the macro with runMacro $reset 9
  6. Macro executes sysbus LoadELF @arm.elf 10
    • This uploads the software to machine

Start Renode and the GDB server

Configure the project, just like in Part 3:

git clone \
--branch minimal --single-branch \
https://gitlab.com/sorhanp/arm.git \
arm-minimal

cd arm-minimal

cmake -DCMAKE_TOOLCHAIN_FILE=toolchain/arm-none-eabi.cmake \
-DCMAKE_BUILD_TYPE=Debug \
-B build

Build the target and run renode using CMake’s utility target:

cmake --build build/ --target run-renode

Renode’s documentation related to GDB debugging explains that emulation time does not progress while the CPU is in halted state (which is the default state if no additional parameters are set to StartGdbServer), making debugging deterministic. 8

GDB

Depending on the cross compiler toolchain, the command for debugger might differ, but main idea is to call the debugger that is designed for cross compile debugging, thus the command might be

arm-none-eabi-gdb build/arm.elf

Or

gdb-multiarch build/arm.elf

Which ever is used the GDB should start and this should be the last printout:

Reading symbols from build/arm.elf...
(gdb) target remote :3333
(gdb) monitor start
(gdb) b main
(gdb) continue

This is the standard remote attach flow along with starting the emulation. [11]

Read the loop variable (counter)

Because counter is declared volatile and build has debug info, it typically lives in memory and can be inspected.

(gdb) p counter
$1 = 0
(gdb) display counter
1: counter = 0

The print and display commands can be used inspecting current value variables. 11

Examine the raw memory backing counter

(gdb) p &counter
$2 = (int *) 0x2004ffec
(gdb) x/wx &counter
0x2004ffec:     0x00000000

The x command for examine memory and print formats are documented in GDB’s examining data section. 12

Watchpoints (data breakpoints)

Stop whenever counter changes:

(gdb) watch counter
Hardware watchpoint 2: counter
(gdb) continue
Continuing.

Hardware watchpoint 2: counter

Old value = 0
New value = 1
0x08000020 in main () at /home/sorhanp/projects/arm-minimal/arm/main.cpp:12
12          counter++;
1: counter = 1

Watchpoints stop execution when an expression changes and they can be hardware (like here) or software based. 13

Registers and instruction stream

(gdb) info registers
r0             0x0                 0
r1             0x0                 0
r2             0x0                 0
r3             0x1                 1
r4             0x0                 0
r5             0x0                 0
r6             0x0                 0
r7             0x2004ffe8          537198568
r8             0x0                 0
r9             0x0                 0
r10            0x0                 0
r11            0x0                 0
r12            0x0                 0
sp             0x2004ffe8          0x2004ffe8
lr             0x800002b           134217771
pc             0x8000020           0x8000020 <main()+16>
xpsr           0x1000000           16777216
fpscr          0x0                 0
msp            0x0                 0
psp            0x0                 0
primask        0x0                 0
basepri        0x0                 0
faultmask      0x0                 0
control        0x0                 0
(gdb) p/x $pc
$3 = 0x8000020
(gdb) x/i $pc
=> 0x8000020 <main()+16>:       b.n     0x800001a <main()+10>

info registers along with the $pc/$sp convenience names can be used to display machine’s registers. Here pc stands for program counter and it stores the address of next instruction to be run. 14

References