How to use the GDB (Gnu Debugger) and OpenOCD for microcontroller debugging - from the terminal?
Asked Answered
B

5

79

The standard (low-cost) way to program ARM microcontrollers is using Eclipse with a complex toolchain plugged into it. Eclipse has definitely its merits, but I'd like to feel independent from this IDE. I'd like to discover what happens behind the scenes when I build (compile - link - flash) my software, and when I run a debug session. To get such deeper understanding, it would be wonderful to run the whole procedure from the command line.

Note: I'm using 64-bit Windows 10. But most stuff explained here also applies on Linux systems. Please open all the command terminals with admin rights. This can save you lots of problems.

1. Building the software

The first 'mission' is accomplished. I am now able to compile and link my software into a binary .bin and a .elf image through the command line. The key to success was finding out where Eclipse puts its make-files for a specific project. Once you know where they are, all you've got to do is opening a command terminal, and type the GNU make command.

enter image description here

You don't need Eclipse anymore for that! Especially if you can read (and understand) the makefile and tweak it to your needs when your project advances.

Note that I found the GNU tools (compiler, linker, make utility, GDB, ...) in the following folder, after installing SW4STM32 (System Workbench for STM32):

C:\Ac6\SystemWorkbench\plugins\fr.ac6.mcu.externaltools.arm-none.win32_1.7.0.201602121829\tools\compiler\

Next I made a new folder on my harddrive and copied all these GNU tools into it:

C:\Apps\AC6GCC
           |-> arm-none-eabi
           |-> bin
           '-> lib

And I add these entries to the "Environment Path variable":

 - C:\Apps\AC6GCC\bin
 - C:\Apps\AC6GCC\lib\gcc\arm-none-eabi\5.2.1

Huray, now I got all the GNU tools up and running on my system! I put the following build.bat file in the same folder as the makefile:

@echo off
echo.
echo."--------------------------------"
echo."-           BUILD              -"
echo."--------------------------------"
echo.

make -j8 -f makefile all

echo.

Running this bat-file should do the job! If all goes well, you get one .bin and one .elf binary file as the result of the compilation.

2. Flashing and debugging the firmware

The natural following step is flashing the firmware to the chip and start a debug session. In Eclipse it's just one 'click on a button' - at least if Eclipse is configured correctly for your microcontroller. But what happens behind the scenes? I've read (part of) the Master Thesis from Dominic Rath - the developer of OpenOCD. You can find it here: http://openocd.net/ . This is what I learned:

  • Eclipse starts the OpenOCD software when you click the 'debug' icon. Eclipse also provides some configuration files to OpenOCD - such that OpenOCD knows how to connect to your microcontroller. 'How to connect' is not a trivial thing. OpenOCD needs to find the proper USB driver to connect to the JTAG adapter (for example STLink). Both the JTAG adapter and its USB driver are usually delivered by your chip manufacturer (for example STMicroelectronics). Eclipse also hands over a configuration file to OpenOCD that describes the specifications of the microcontroller. Once OpenOCD knows about all these things, it can make a reliable JTAG connection to the target device.

  • OpenOCD starts two servers. The first one is a Telnet server on TCP port 4444. It gives access to the OpenOCD CLI (Command Line Interface). A Telnet client can connect and send commands to OpenOCD. Those commands can be a simple 'stop', 'run', 'set breakpoint', ...

  • Such commands could be sufficient for debugging your microcontroller, but many people were already familiar with the Gnu Debugger (GDB). This is why OpenOCD also starts a GDB server on TCP port 3333. A GDB client can connect to that port, and start debugging the microcontroller!

  • The Gnu Debugger is a command line software. Many people prefer a visual interface. That is exactly what Eclipse does. Eclipse starts a GDB client that connects to OpenOCD - but that is all hidden to the user. Eclipse provides a graphical interface that interacts with the GDB client behind the scenes.

I've made a figure to explain all these things:

enter image description here

>> Starting up OpenOCD

I managed to start up OpenOCD from the command line. I'll explain how.

  1. First make sure that your STLink-V2 JTAG programmer is properly installed. You can test the installation with the "STLink Utility tool" from STMicroelectronics. It has a nice GUI, and you simply click the connect button. enter image description here
  2. Next download the OpenOCD software executable from this website: http://gnutoolchains.com/arm-eabi/openocd/ . Install it, and put it in a folder on your harddrive, like "C:\Apps\".
  3. Open a command terminal, and start OpenOCD. You will need to give OpenOCD a few configuration files, such that it knows where to look for your microcontroller. Typically you need to give a configuration file that describes the JTAG programmer, and a configuration file that defines your microcontroller. Pass those files to OpenOCD with the -f argument in the command line. You will also need to give OpenOCD access to the scripts folder, by passing it with the -s argument. This is how I start OpenOCD on my computer with the command line:

    > "C:\Apps\OpenOCD-0.9.0-Win32\bin\openocd" -f "C:\Apps\OpenOCD-0.9.0-Win32\share\openocd\scripts\interface\stlink-v2.cfg" -f "C:\Apps\OpenOCD-0.9.0-Win32\share\openocd\scripts\target\stm32f7x.cfg" -s "C:\Apps\OpenOCD-0.9.0-Win32\share\openocd\scripts"
    
  4. If you started OpenOCD properly (with the correct arguments), it will startup with the following message:

    Open On-Chip Debugger 0.9.0 (2015-08-15-12:41)
    Licensed under GNU GPL v2
    For bug reports, read
            http://openocd.org/doc/doxygen/bugs.html
    Info : auto-selecting first available session transport "hla_swd". To override use 'transport select <transport>'.
    Info : The selected transport took over low-level target control. The results might differ compared to plain JTAG/SWD
    adapter speed: 2000 kHz
    adapter_nsrst_delay: 100
    srst_only separate srst_nogate srst_open_drain connect_deassert_srst
    Info : Unable to match requested speed 2000 kHz, using 1800 kHz
    Info : Unable to match requested speed 2000 kHz, using 1800 kHz
    Info : clock speed 1800 kHz
    Info : STLINK v2 JTAG v24 API v2 SWIM v4 VID 0x0483 PID 0x3748
    Info : using stlink api v2
    Info : Target voltage: 3.231496
    Info : stm32f7x.cpu: hardware has 8 breakpoints, 4 watchpoints
    Info : accepting 'gdb' connection on tcp/3333
    Info : flash size probed value 1024
    
  5. Notice that your terminal window is now blocked. You can no longer type commands. But that's normal. OpenOCD is running in the background, and it blocks the terminal. Now you've got two options to interact with OpenOCD: you start a Telnet session in another terminal, and you log on to TCP port localhost:4444, so you can give commands to OpenOCD and receive feedback. Or you start a GDB client session, and connect it to TCP port localhost:3333.

>> Starting up a Telnet session to interact with OpenOCD

This is how you start a Telnet session to interact with the running OpenOCD program:

> dism /online /Enable-Feature /FeatureName:TelnetClient

> telnet 127.0.0.1 4444

If it works well, you'll get the following message on your terminal:

Open On-Chip Debugger
> ..

And you're ready to send commmands to OpenOCD! But I'll now switch to the GDB session, since that's the most convenient way to interact with OpenOCD.

>> Starting up a GDB client session to interact with OpenOCD

Open yet another terminal window, and type the following command:

> "C:\Apps\AC6GCC\bin\arm-none-eabi-gdb.exe"

This command simply starts the arm-none-eabi-gdb.exe GDB client. If all goes well, GDB starts up with the following message:

    GNU gdb (GNU Tools for ARM Embedded Processors) 7.10.1.20151217-cvs
    Copyright (C) 2015 Free Software Foundation, Inc.
    License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
    This is free software: you are free to change and redistribute it.
    There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
    and "show warranty" for details.
    This GDB was configured as "--host=i686-w64-mingw32 --target=arm-none-eabi".
    Type "show configuration" for configuration details.
    For bug reporting instructions, please see:
    <http://www.gnu.org/software/gdb/bugs/>.
    Find the GDB manual and other documentation resources online at:
    <http://www.gnu.org/software/gdb/documentation/>.
    For help, type "help".
    Type "apropos word" to search for commands related to "word".
    (gdb)..

Now connect this GDB client to the GDB server inside OpenOCD:

    (gdb) target remote localhost:3333

Now you're connected to OpenOCD! Good to know: if you want to use a native OpenOCD command (just like you would do in a Telnet session), just precede the command with the keyword monitor. This way the GDB server inside OpenOCD will not handle the command himself, but pass it on to the native OpenOCD deamon.

So, now it is time to reset the chip, erase it and halt it:

    (gdb) monitor reset halt
       target state: halted
       target halted due to debug-request, current mode: Thread
       xPSR: 0x01000000 pc: 0xfffffffe msp: 0xfffffffc
    (gdb) monitor halt

    (gdb) monitor flash erase_address 0x08000000 0x00100000
       erased address 0x08000000 (length 1048576) in 8.899024s (115.069 KiB/s)
    (gdb) monitor reset halt
       target state: halted
       target halted due to debug-request, current mode: Thread
       xPSR: 0x01000000 pc: 0xfffffffe msp: 0xfffffffc
    (gdb) monitor halt

The chip is now ready to get some instructions from us. First we will tell the chip that its flash sections 0 to 7 (that's all the flash sections in my 1Mb chip) should not be protected:

    (gdb) monitor flash protect 0 0 7 off

    (gdb) monitor flash info 0
       #0 : stm32f7x at 0x08000000, size 0x00100000, buswidth 0, chipwidth 0
            #  0: 0x00000000 (0x8000 32kB) not protected
            #  1: 0x00008000 (0x8000 32kB) not protected
            #  2: 0x00010000 (0x8000 32kB) not protected
            #  3: 0x00018000 (0x8000 32kB) not protected
            #  4: 0x00020000 (0x20000 128kB) not protected
            #  5: 0x00040000 (0x40000 256kB) not protected
            #  6: 0x00080000 (0x40000 256kB) not protected
            #  7: 0x000c0000 (0x40000 256kB) not protected

Next I halt the chip again. Just to be sure..

    (gdb) monitor halt

Finally I hand over the binary .elf file to GDB:

    (gdb) file C:\\..\\myProgram.elf
       A program is being debugged already.
       Are you sure you want to change the file? (y or n) y
       Reading symbols from C:\..\myProgram.elf ...done.

Now is the moment of truth. I ask GDB to load this binary into the chip. Fingers crossed:

    (gdb) load
       Loading section .isr_vector, size 0x1c8 lma 0x8000000
       Loading section .text, size 0x39e0 lma 0x80001c8
       Loading section .rodata, size 0x34 lma 0x8003ba8
       Loading section .init_array, size 0x4 lma 0x8003bdc
       Loading section .fini_array, size 0x4 lma 0x8003be0
       Loading section .data, size 0x38 lma 0x8003be4
       Error finishing flash operation

Sadly it was not successful. I get the following message in OpenOCD:

    Error: error waiting for target flash write algorithm
    Error: error writing to flash at address 0x08000000 at offset 0x00000000

EDIT : Hardware issue fixed.

Apparently it was a hardware issue. I had never thought that my chip would be defect, since loading the binary onto the chip with the STLink Utility tool worked without problem. Only OpenOCD was complaining and giving errors. So naturally I blamed OpenOCD - and not the chip itself. See my answer below for more details.


EDIT : Alternative elegant way to flash the chip - using makefile!

As the issue got fixed, I will now focus on an alternative way to execute the flash and debug of the chip. I believe this is really interesting for the community!

You might have noticed that I used Windows cmd commands to execute all the necessary steps. This can be automated in a batch file. But there is a more elegant way: to automate everything in a makefile! Mr./Mss. Othane has suggested the following makefile for his/her Cortex-M? chip. I suppose that the procedure for a Cortex-M7 chip is very similar:

            #################################################
            #        MAKEFILE FOR BUILDING THE BINARY       #
            #        AND EVEN FLASHING THE CHIP!            #
            # Author: Othane                                #
            #################################################

    # setup compiler and flags for stm32f373 build 
    SELF_DIR := $(dir $(lastword $(MAKEFILE_LIST))) 


    CROSS_COMPILE ?= arm-none-eabi- 
    export CC = $(CROSS_COMPILE)gcc 
    export AS = $(CROSS_COMPILE)gcc -x assembler-with-cpp 
    export AR = $(CROSS_COMPILE)ar 
    export LD = $(CROSS_COMPILE)ld 
    export OD   = $(CROSS_COMPILE)objdump 
    export BIN  = $(CROSS_COMPILE)objcopy -O ihex 
    export SIZE = $(CROSS_COMPILE)size 
    export GDB = $(CROSS_COMPILE)gdb 


    MCU = cortex-m4 
    FPU = -mfloat-abi=hard -mfpu=fpv4-sp-d16 -D__FPU_USED=1 -D__FPU_PRESENT=1 -DARM_MATH_CM4 
    DEFS = -DUSE_STDPERIPH_DRIVER -DSTM32F37X -DRUN_FROM_FLASH=1 -DHSE_VALUE=8000000 
    OPT ?= -O0  
    MCFLAGS = -mthumb -mcpu=$(MCU) $(FPU) 


    export ASFLAGS  = $(MCFLAGS) $(OPT) -g -gdwarf-2 $(ADEFS) 
    CPFLAGS += $(MCFLAGS) $(OPT) -gdwarf-2 -Wall -Wno-attributes -fverbose-asm  
    CPFLAGS += -ffunction-sections -fdata-sections $(DEFS) 
    export CPFLAGS 
    export CFLAGS += $(CPFLAGS) 


    export LDFLAGS  = $(MCFLAGS) -nostartfiles -Wl,--cref,--gc-sections,--no-warn-mismatch $(LIBDIR) 


    HINCDIR += ./STM32F37x_DSP_StdPeriph_Lib_V1.0.0/Libraries/CMSIS/Include/ \ 
        ./STM32F37x_DSP_StdPeriph_Lib_V1.0.0/Libraries/CMSIS/Device/ST/STM32F37x/Include/ \ 
        ./STM32F37x_DSP_StdPeriph_Lib_V1.0.0/Libraries/STM32F37x_StdPeriph_Driver/inc/ \ 
        ./ 
    export INCDIR = $(patsubst %,$(SELF_DIR)%,$(HINCDIR)) 




    # openocd variables and targets 
    OPENOCD_PATH ?= /usr/local/share/openocd/ 
    export OPENOCD_BIN = openocd 
    export OPENOCD_INTERFACE = $(OPENOCD_PATH)/scripts/interface/stlink-v2.cfg 
    export OPENOCD_TARGET = $(OPENOCD_PATH)/scripts/target/stm32f3x_stlink.cfg 


    OPENOCD_FLASH_CMDS = '' 
    OPENOCD_FLASH_CMDS += -c 'reset halt' 
    OPENOCD_FLASH_CMDS += -c 'sleep 10'  
    OPENOCD_FLASH_CMDS += -c 'stm32f1x unlock 0' 
    OPENOCD_FLASH_CMDS += -c 'flash write_image erase $(PRJ_FULL) 0 ihex' 
    OPENOCD_FLASH_CMDS += -c shutdown 
    export OPENOCD_FLASH_CMDS 


    OPENOCD_ERASE_CMDS = '' 
    OPENOCD_ERASE_CMDS += -c 'reset halt' 
    OPENOCD_ERASE_CMDS += -c 'sleep 10'  
    OPENOCD_ERASE_CMDS += -c 'sleep 10'  
    OPENOCD_ERASE_CMDS += -c 'stm32f1x mass_erase 0' 
    OPENOCD_ERASE_CMDS += -c shutdown 
    export OPENOCD_ERASE_CMDS 


    OPENOCD_RUN_CMDS = '' 
    OPENOCD_RUN_CMDS += -c 'reset halt' 
    OPENOCD_RUN_CMDS += -c 'sleep 10' 
    OPENOCD_RUN_CMDS += -c 'reset run' 
    OPENOCD_RUN_CMDS += -c 'sleep 10'  
    OPENOCD_RUN_CMDS += -c shutdown 
    export OPENOCD_RUN_CMDS 


    OPENOCD_DEBUG_CMDS = '' 
    OPENOCD_DEBUG_CMDS += -c 'halt' 
    OPENOCD_DEBUG_CMDS += -c 'sleep 10' 


    .flash: 
        $(OPENOCD_BIN) -f $(OPENOCD_INTERFACE) -f $(OPENOCD_TARGET) -c init $(OPENOCD_FLASH_CMDS) 


    .erase: 
        $(OPENOCD_BIN) -f $(OPENOCD_INTERFACE) -f $(OPENOCD_TARGET) -c init $(OPENOCD_ERASE_CMDS) 


    .run: 
        $(OPENOCD_BIN) -f $(OPENOCD_INTERFACE) -f $(OPENOCD_TARGET) -c init $(OPENOCD_RUN_CMDS) 


    .debug: 
        $(OPENOCD_BIN) -f $(OPENOCD_INTERFACE) -f $(OPENOCD_TARGET) -c init $(OPENOCD_DEBUG_CMDS) 

Dear Mr./Mss. Othane, could you explain how to use this makefile for the following steps:

  • Build the binary from the source code
  • Flash the chip

I know some basics about makefiles, but your makefile is really going quite deep. You seem to use quite some features of the GNU make utility. Please give us some more explanation, and I'll grant you the bonus ;-)

------------------------------

Brigettebrigg answered 25/6, 2016 at 21:29 Comment(5)
The makefile shown is really not too involved ... basically I have 1 of these files per architecture, it exports a bunch of variable like CC and CFLAGS to lower makefiles that include it, they can then compile the code generically using those flags ... In terms of programming we just add a some .erase, .flash etc commands so to do a full erase program etc you might run: make .erase && make .flash && make .debug ... then you could connect with gdb, or make .run to just run without debugGaddi
Waw, thank you so much for your help. Could I ask one more favour? Not just for me, but for all those people who could benefit from your wonderful answer. Could you copy those makefiles that apply to the Cortex-M7/M4 (so the 'parent' and the 'child' makefiles) in your answer? And the commands to use them, too? That would be truly awesome! The community will thank you for that :-) (A reference to github is good, but it is better to have it all visible on StackOverflow. That makes it more accessible, and people can leave comments, ask questions, ...)Brigettebrigg
Hi @Gaddi , I already awarded the bonus to you. You really deserve it! Please consider the idea to put your makefiles here on StackOverflow. I'd really be very happy :-)Brigettebrigg
Cheers mate .. sorry I missed this message, I don't think this is a good place to post files though... They should all be available on GitHub which I feel is more appropriate than stack overflowGaddi
Awesome question!Conqueror
G
11

As I remember it I had some trouble with the straight load command too, so I switched to "flash write_image erase my_project.hex 0 ihex" .. obviously I was using hex files but it looks like elf files should work to, see http://openocd.org/doc/html/Flash-Commands.html ... the good thing about this command is it also erases only the flash sections that are being written to which is really handy and you don't need an erase

Before you run the above command you will need to run "stm32f1x unlock 0" to ensure the chip is unlocked and your allowed to wire to the flash ... See The datasheet about this

Also for getting started the command "stm32f1x mass_erase 0" will erase the chip fully and quickly so it's good to ensure you start in a known state

I know some of these commands say they are for the f1's but trust me they work for the f4 series to

Btw this file contains most of the command I use to flash my f4 so it might be a good reference https://github.com/othane/mos/blob/master/hal/stm32f373/stm32f373.mk

I Hope that gets you unstuck

Gaddi answered 17/7, 2016 at 5:45 Comment(4)
Hi othane , thank you so much. I will try it out tomorrow. I notice you put some commands just before writing the image to the chip. Like a 'sleep' command. What is it for?Brigettebrigg
It's been a while since I wrote that, but I think it was there to get it working reliably on all the different programmers i was using, ie the STM SWD, blink JTAG etc ... Without it I think I would sometimes get a connection error ... This might ask be fixed in openocd by now thoughGaddi
Hi @Gaddi , I really appreciate your answers. Since the technical issue was related to the hardware (chip defect), I've now shifted my question a bit towards the flashing procedure itself. Especially your elegant way to flash the chip using a makefile is extremely interesting. I believe that I should give the bonus to you. Please take a look at the question (I've added your makefile at the bottom of my question) and help us to understand how to use your makefile.Brigettebrigg
Cool, glad your up and running !! ... I was thinking it might not be that it is a hardware problem ... did you try using the "stm32f1x mass_erase 0" and "flash write_image erase blah.elf 0" commands ... I was thinking it might be an option byte is stuck, or the flash is protected in some other way so doing a full erase and flash using the stm routines might bring it back to life (especially if you managed to program the chip the first time)Gaddi
G
3

This is a little brief and not great stackoverflow style but I would point you to my code where I have set this up for my "mos" library for the STM32F4 and STM32F1 (https://github.com/othane/mos) ... This is a big topic to answer so I though an example might be better

In short, my project is a tree of Makefiles, since you have your code compiling the main one of interest for you is found here https://github.com/othane/mos/blob/master/hal/stm32f373/stm32f373.mk ... basically you need openocd and then I have a series of commands to allow erasing the chip, or flashing and debugging the new code etc by simply typing make .erase or make .flash or make .debug

finally if you look in my unit tests (these are basically example programs) you will find the Makefile to build it + a gdbinit file like this one https://github.com/othane/mos/blob/master/utest/gpio/debug_gpio_utest.gdbinit ... then you simply do "make && make .flash && make .debug" in one terminal, and call your cross compilers gdb like this "arm-none-eabi-gdb -x ./debug_gpio_utest.gdbinit" in another ... this will start gdb after flashing the code and you can use the normal break and list commands from gdb etc to interact with the code (note how I defined a reset command in the .gdbinit file, checkout the help for the mon command ... basically it will let you send commands through gdb directly to openocd and is really useful)

Sorry the answer is quite brief and lots of links, but I hope it gets you going.

Gaddi answered 30/6, 2016 at 9:35 Comment(2)
Hi @Gaddi , I made a lot of progress, and I'm almost there. Please check out the update in my question. Sadly I get an error at the end of the flash procedure. If you have a suggestion, please help me. Thank you so much.Brigettebrigg
See my answers below for getting the flashing working ... Sorry it wouldn't fit in here ;) ... Hopefully it helpsGaddi
J
3

At first sight the distribution on gnutoolchains.com should be good enough. There are a number of build script to brew your own version. I do have mine to include the ARM7TDMI. It works fine under Linux and FreeBSD but MinGW failed last time I tried it :-(

Regarding OpenOCD, I would recommend to start it in the same directory as your GDB instance, so that the binary download seems transparent if you invoke it from within GDB (the easiest way). You also have the option to create a script that starts OpenOCD and load the code but then you would have to restart it after each compilation.

Joacimah answered 3/7, 2016 at 13:36 Comment(2)
Thank you @O. Gautherot . This is really helpful :-)Brigettebrigg
Hi @O. Gautherot , I made a lot of progress, and I'm almost there. Please check out the update in my question. Sadly I get an error at the end of the flash procedure. If you have a suggestion, please help me. Thank you so much.Brigettebrigg
U
1

You now just invoke "gdb" and connect it to the "remote server" (localhost if the server and gdb are running on the same machine). Configure GDB such that it knows the location of the source code and the location of the ELF file. There are a ton of websites that go through the basic usage of GDB..

There seems to be a GDB for Windows (http://www.equation.com/servlet/equation.cmd?fa=gdb)

The following commands in GDB should get you started:

target remote localhost:3333

directory /path/to/project

symbol-file /path/to/project.elf

Unbeknown answered 28/6, 2016 at 2:1 Comment(1)
Thank you so much. I got my GDB for Windows from this website: gnutoolchains.com/arm-eabi . How would you compare it to the one you get from equation.com/servlet/equation.cmd?fa=gdb ?Brigettebrigg
B
1

Apparently it was a hardware issue. I had never thought that my chip would be defect, since loading the binary onto the chip with the STLink Utility tool worked without problem. Only OpenOCD was complaining and giving errors. So naturally I blamed OpenOCD - and not the chip itself.

Today I tried the same procedure on with a new chip on the board, and now it works!

I get the following output in GDB when issuing the load command:

    (gdb) load
       Loading section .isr_vector, size 0x1c8 lma 0x8000000
       Loading section .text, size 0x39e0 lma 0x80001c8
       Loading section .rodata, size 0x34 lma 0x8003ba8
       Loading section .init_array, size 0x4 lma 0x8003bdc
       Loading section .fini_array, size 0x4 lma 0x8003be0
       Loading section .data, size 0x38 lma 0x8003be4
       Start address 0x8003450, load size 15388
       Transfer rate: 21 KB/sec, 2564 bytes/write.
    (gdb)

Thank you to everyone who did his/her best to help me :-)

Brigettebrigg answered 20/7, 2016 at 10:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.