如何从终端使用 GDB(Gnu 调试器)和 OpenOCD 进行微控制器调试?

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

对 ARM 微控制器进行编程的标准(低成本)方法是使用 Eclipse 并插入一个复杂的工具链。 Eclipse 绝对有它的优点,但我想独立于此 IDE。我想了解当我构建(编译 - link - 闪存)我的软件以及 运行 调试会话时幕后发生的事情。要获得更深入的理解,最好从命令行 运行 整个过程。

注意:我使用的是 64 位 Windows10。但是这里解释的大部分内容也适用于 Linux 系统。请打开所有具有管理员权限的命令终端。这可以为您省去很多问题。

1。构建软件

第一个'mission'完成。我现在可以通过命令行将我的软件 link 编译成二进制 .bin.elf 图像。成功的关键是找出 Eclipse 将其用于特定项目的 make 文件放在哪里。一旦你知道它们在哪里,你所要做的就是打开一个命令终端,然后输入 GNU make 命令。

为此您不再需要 Eclipse!特别是如果您可以阅读(并理解)makefile 并在项目推进时根据您的需要对其进行调整。

请注意,在安装 SW4STM32(STM32 的系统 Workbench 之后,我在以下文件夹中找到了 GNU 工具(编译器、linker、make 实用程序、GDB 等):

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

接下来我在我的硬盘驱动器上创建了一个新文件夹并将所有这些 GNU 工具复制到其中:

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

然后我将这些条目添加到 "Environment Path variable":

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

太棒了,现在我在我的系统上安装了所有 GNU 工具并 运行ning!我将以下 build.bat 文件放在与 makefile 相同的文件夹中:

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

make -j8 -f makefile all

echo.

运行 这个 bat 文件应该可以完成这项工作!如果一切顺利,你将得到一个 .bin 和一个 .elf 二进制文件作为编译结果。

2。烧写和调试固件

自然而然的下一步是将固件闪存到芯片并开始调试会话。在 Eclipse 中它只是一个 'click on a button' - 至少如果 Eclipse 为您的微控制器正确配置。但是幕后发生了什么? 我读过(部分)来自 Dominic Rath 的硕士论文——OpenOCD 的开发者。您可以在这里找到它:http://openocd.net/。这是我学到的:

我做了一个图来解释所有这些事情:

>> 启动 OpenOCD

我设法从命令行启动了 OpenOCD。我会解释如何。

  1. 首先确保您的STLink-V2 JTAG编程器安装正确。您可以使用 STMicroelectronics 的 "STLink Utility tool" 测试安装。它有一个漂亮的 GUI,您只需单击连接按钮即可。
  2. 接下来从该网站下载 OpenOCD 软件可执行文件:http://gnutoolchains.com/arm-eabi/openocd/。安装它,并将它放在硬盘上的文件夹中,例如 "C:\Apps\".
  3. 打开命令终端,启动 OpenOCD。您需要为 OpenOCD 提供一些配置文件,以便它知道在哪里寻找您的微控制器。通常,您需要提供一个描述 JTAG 编程器的配置文件,以及一个定义您的微控制器的配置文件。在命令行中使用 -f 参数将这些文件传递给 OpenOCD。您还需要通过将 -s 参数传递给 OpenOCD 来授予 OpenOCD 对 scripts 文件夹的访问权限。这是我在计算机上使用命令行启动 OpenOCD 的方式:

    > "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. 如果您正确启动 OpenOCD(使用正确的参数),它将启动并显示以下消息:

    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. 请注意,您的终端 window 现在已被阻止。您不能再键入命令。但这是正常的。 OpenOCD 在后台 运行ning,它会阻塞终端。现在您有两种与 OpenOCD 交互的选择:在另一个终端启动 Telnet 会话,然后登录到 TCP 端口 localhost:4444,这样您就可以向 OpenOCD 发出命令并接收反馈。或者您启动一个 GDB 客户端会话,并将其连接到 TCP 端口 localhost:3333.

>> 启动 Telnet 会话以与 OpenOCD 交互

这是启动 Telnet 会话与 运行ning OpenOCD 程序交互的方式:

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

> telnet 127.0.0.1 4444

如果运行良好,您将在终端上收到以下消息:

Open On-Chip Debugger
> ..

您已准备好向 OpenOCD 发送命令!但我现在将切换到 GDB 会话,因为这是与 OpenOCD 交互的最方便的方式。

>> 启动 GDB 客户端会话与 OpenOCD 交互

打开另一个终端 window,然后输入以下命令:

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

此命令只是启动 arm-none-eabi-gdb.exe GDB 客户端。如果一切顺利,GDB 将启动并显示以下消息:

    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)..

现在将此 GDB 客户端连接到 OpenOCD 中的 GDB 服务器:

    (gdb) target remote localhost:3333

现在您已连接到 OpenOCD!提示:如果您想使用本机 OpenOCD 命令(就像您在 Telnet 会话中所做的那样),只需在命令前加上关键字 monitor。这样 OpenOCD 内部的 GDB 服务器将不会自己处理命令,而是将其传递给本机 OpenOCD 守护进程。

所以,现在是重置芯片、擦除并停止它的时候了:

    (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

芯片现在已准备好接收我们的一些指令。首先我们会告诉芯片它的闪存部分 0 到 7(这是我 1Mb 芯片中的所有闪存部分)不应该被保护:

    (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

接下来我再次停止芯片。只是为了确定..

    (gdb) monitor halt

最后我将二进制 .elf 文件交给 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.

现在是关键时刻。我要求 GDB 将这个二进制文件加载到芯片中。手指交叉:

    (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

遗憾的是没有成功。我在 OpenOCD 中收到以下消息:

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

编辑:硬件问题已修复。

显然是硬件问题。我从没想过我的芯片会出现故障,因为使用 STLink Utility 工具将二进制文件加载到芯片上没有问题。只有 OpenOCD 在抱怨并给出错误。所以我很自然地责怪 OpenOCD——而不是芯片本身。有关详细信息,请参阅下面的回答。


编辑:闪存芯片的另一种优雅方式 - 使用 makefile!

随着问题得到解决,我现在将重点介绍执行闪存和芯片调试的替代方法。我相信这对社区来说真的很有趣!

您可能已经注意到我使用了 Windows cmd 命令来执行所有必要的步骤。这可以在批处理文件中自动执行。但是有一种更优雅的方法:在 makefile 中自动执行所有操作! 先生/女士Othane 为 his/her Cortex-M 建议了以下 makefile?芯片。我想 Cortex-M7 芯片的程序非常相似:

            #################################################
            #        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) 

尊敬的先生/女士。 Othane,你能解释一下如何在以下步骤中使用这个 makefile 吗:

我知道一些关于 makefile 的基础知识,但是你的 makefile 真的很深。您似乎使用了 GNU make 实用程序的很多功能。请给我们更多的解释,我会给你奖金;-)

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

您现在只需调用 "gdb" 并将其连接到 "remote server"(如果服务器和 gdb 在同一台机器上 运行,则为本地主机)。配置 GDB,使其知道源代码的位置和 ELF 文件的位置。有大量网站介绍了 GDB 的基本用法..

Windows (http://www.equation.com/servlet/equation.cmd?fa=gdb)

似乎有一个 GDB

GDB 中的以下命令应该可以帮助您入门:

远程目标localhost:3333

目录/path/to/project

符号文件/path/to/project.elf

这有点简短,不是很好的 Whosebug 风格,但我会向您指出我的代码,我在其中为我的 "mos" 库设置了它,用于 STM32F4 和 STM32F1 (https://github.com/othane/mos)。 .. 这是一个需要回答的大话题,所以我虽然举个例子可能会更好

简而言之,我的项目是一棵 Makefile 树,因为您的代码正在编译您感兴趣的主要代码可以在这里找到 https://github.com/othane/mos/blob/master/hal/stm32f373/stm32f373.mk ...基本上您需要 openocd,然后我有一个系列命令允许擦除芯片,或闪烁和调试新代码等,只需键入 make .erase 或 make .flash 或 make .debug

最后,如果您查看我的单元测试(这些基本上是示例程序),您会找到用于构建它的 Makefile + 一个像这样的 gdbinit 文件 https://github.com/othane/mos/blob/master/utest/gpio/debug_gpio_utest.gdbinit ... 然后您只需执行 "make && make .flash && make .debug" 在一个终端中,然后在另一个终端中像这样 "arm-none-eabi-gdb -x ./debug_gpio_utest.gdbinit" 调用交叉编译器 gdb ...这将在闪烁代码后启动 gdb,您可以使用 gdb 等中的正常 break 和 list 命令与代码交互(请注意我是如何在 .gdbinit 文件中定义重置命令的,查看 mon 命令的帮助...基本上它可以让您通过 gdb 直接向 openocd 发送命令并且非常有用)

抱歉,答案很简短,链接很多,但我希望它能让你继续。

乍一看 gnutoolchains.com 上的分布应该足够好。有许多构建脚本来酿造你自己的版本。我确实有我的,包括 ARM7TDMI。它在 Linux 和 FreeBSD 下运行良好,但我上次尝试时 MinGW 失败了:-(

关于 OpenOCD,我建议在与 GDB 实例相同的目录中启动它,这样如果您从 GDB 中调用它(最简单的方法),二进制下载看起来是透明的。您还可以选择创建一个脚本来启动 OpenOCD 并加载代码,但您必须在每次编译后重新启动它。

我记得我在使用直接加载命令时也遇到了一些问题,所以我切换到 "flash write_image erase my_project.hex 0 ihex" .. 显然我使用的是 hex 文件,但看起来 elf 文件应该可以工作,请参阅 http://openocd.org/doc/html/Flash-Commands.html ...这个命令的好处是它也只擦除正在写入的闪存部分,这非常方便,你不需要擦除

在你 运行 上面的命令之前你需要 运行 "stm32f1x unlock 0" 来确保芯片被解锁并且你允许连接到闪存......参见数据表关于这个

同样对于开始,命令 "stm32f1x mass_erase 0" 将完全快速地擦除芯片,因此最好确保您以已知状态开始

我知道其中一些命令说它们适用于 f1,但相信我它们适用于 f4 系列

顺便说一下,这个文件包含了我用来刷新 f4 的大部分命令,所以它可能是一个很好的参考 https://github.com/othane/mos/blob/master/hal/stm32f373/stm32f373.mk

我希望这能让你摆脱困境

显然是硬件问题。我从没想过我的芯片会出现故障,因为使用 STLink Utility 工具将二进制文件加载到芯片上没有问题。只有 OpenOCD 在抱怨并给出错误。所以我很自然地责怪 OpenOCD - 而不是芯片本身。

今天我在电路板上的新芯片上尝试了相同的程序,现在可以了!

发出 load 命令时,我在 GDB 中得到以下输出:

    (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)

感谢所有 his/her 帮助我的人:-)