Code

Debugging IAR ELFs with GDB on the STM32F4xx

Posted on

With C-SPY it works, with GDB it doesn't?

There are situations where you may not want to use IAR’s C-SPY for debugging but rather would go with GDB.

So you tell yourself “an ELF is an ELF”, start up J-Link GDB server, attach GDB to it, load the ELF, continue and… nothing.

There are several things that can go wrong, read on to find out which…

Why use GDB instead of C-SPY?

My client uses IAR’s toolchain.

However, C-SPY currently (Sep 2016) has one major flaw: It takes forever to load big ELF files. Support told us they’ll improve it. GDB loads it instantly.

Also, you may want to run the ELF through a debugger in environments where you don’t have an IAR license.

My setup: Segger J-Link, J-Link GDB Server and gdb from the GNU ARM Embedded Toolchain.

What’s the problem, then? Maybe it’s one of the following reasons.

Reason #1: Program entry position

GDB honours the “Program entry position” from the ELF header and sets the program counter (PC) accordingly on load.

In my case the IAR linker sets the program entry position to the address of __iar_program_start. It is an entry position, sort of, but really it isn’t. Rather, it should be something like the address of Reset_Handler.

Except the PC, also the initial stack pointer (SP) may be bogus.

To understand what PC/SP really should be, this is what happens when an STM32F4xx starts:

  • Set SP to the word stored at VTOR+0
  • Set PC to the word stored at VTOR+4
  • Start

What’s VTOR? It’s the “Vector table offset register”. After reset, it’s 0.

Is there any memory at 0x00000000? Yes there is, for usual configurations (i.e. boot from flash), the flash memory is aliased to address 0x00000000.

So, to get the proper values for PC/SP, I’ve found that resetting the CPU after the ELF has been loaded works well.

To illustrate this, look at this GDB session output:

C:\Users\seb\...>arm-none-eabi-gdb.exe test.elf
GNU gdb (GNU Tools for ARM Embedded Processors) 7.10.1.20160210-cvs
...
Reading symbols from test.elf...done.
(gdb) target remote localhost:2331
Remote debugging using localhost:2331
0x08000e20 in ?? ()
(gdb) monitor reset
Resetting target
(gdb) load
Loading section A1 rw, size 0x410 lma 0x8000000
Loading section P2 rw, size 0x3c lma 0x8000410
Loading section P3 ro, size 0x1059f8 lma 0x800044c
Start address 0x8043ec8, load size 1072708
Transfer rate: 33792 KB/sec, 4063 bytes/write.
(gdb) stepi
0x08043eca in __iar_program_start ()

That’s not the right start address, it should be:

(gdb) x/x 0x00000004
0x4:    0x08000fd1

After a reset, it will fetch the correct PC:

(gdb) monitor reset
Resetting target
(gdb) stepi
0x08000fd2 in ?? ()

Then, you can just continue the program.

So something like the following in your gdbinit should work:

# Connect to GDB Server
target remote localhost:2331

# Reset CPU
monitor reset

# Load ELF
load

# Reset again
monitor reset

Reason #2: Boot loader configuration

Now, it still may not work. For example, we use a boot loader configuration: Our boot loader sits in the first 256k of the flash and the actual application comes after these 256k. The application has its own vector table at 0x08040000.

Still, for some magical reason, C-SPY is perfectly fine loading and running this application and it uses the proper PC and SP from the vector table at 0x08040000. How does it do that?

I stumbled over a comment in startup_stm32f429_439xx.s:

The name "__vector_table" has special meaning for C-SPY: it is where the SP start value is found, and the NVIC vector table register (VTOR) is initialized to this address if != 0.

So this is what C-SPY does:

  • Set VTOR to the address of the symbol __vector_table
  • Fetch and set SP and PC from there
  • Run

Of course GDB doesn’t know about this, that’s why it has to be done manually:

# Adjust VTOR (Vector table offset register)
set {int}0xE000ED08 = __vector_table
# Set SP/PC to the values from the actual vector table
set $sp = *(int*)__vector_table
set $pc = *((int*)__vector_table+1)

0xE000ED08 is the address of the VTOR on the STM32F439 we’re using. I haven’t checked, but it’s probably the same for other controllers of this family.

That’s all, thanks for reading!