GDB 调试指南

本文档介绍如何使用 GDB 调试 ARCS SDK 应用程序,包括环境配置、调试方式和常用命令。

Tip

使用 J-Link 对 flash 进行烧录、擦除和回读,见 J-Link 烧录工具。 本文档侧重调试环境搭建与 GDB 用法。

Note

ARCS SDK 使用 RISC-V 架构的 Nuclei 工具链,调试时需要使用对应的 GDB 工具。

调试概述

GDB(GNU Debugger)是一个功能强大的调试工具,支持断点、单步执行、变量查看等调试功能。在嵌入式开发中,通常使用 GDB 配合 JTAG/SWD 调试器进行远程调试。

支持的调试方式

  • JTAG 调试:通过 JTAG 接口连接调试器

  • 远程 GDB 调试:使用 GDB Server 进行远程调试

  • 命令行调试:使用 GDB 命令行界面进行调试

环境准备

安装调试工具

  1. 确认工具链安装

    确保已按照 快速入门 中的步骤安装了 Nuclei 工具链,并**在当前 shell 中导出工具链路径**:

    export NUCLEI_TOOLCHAIN_PATH=~/.listenai/gcc
    

    Important

    build.sh 内部虽然会 export 该变量,但那只在其自身进程内有效,不会传给 你的交互 shell。若不手动导出,后续 ${NUCLEI_TOOLCHAIN_PATH}/bin/... 会展开成 /bin/... 并报 没有那个文件或目录

    建议写入 ~/.bashrc 以免每次重设。

  2. 安装 GDB 运行依赖

    Nuclei GDB 链接的是 libtinfo.so.5libncursesw.so.5,而 Ubuntu 24.04 仅提供 v6 且仓库已移除 v5,需从 22.04 归档安装:

    cd /tmp
    B=http://archive.ubuntu.com/ubuntu/pool/universe/n/ncurses
    curl -fsSLO $B/libtinfo5_6.3-2ubuntu0.2_amd64.deb
    curl -fsSLO $B/libncursesw5_6.3-2ubuntu0.2_amd64.deb
    sudo apt install -y ./libtinfo5_6.3-2ubuntu0.2_amd64.deb \
                        ./libncursesw5_6.3-2ubuntu0.2_amd64.deb
    

    Note

    两个包都要装,只装 libtinfo5 会接着报 libncursesw.so.5 缺失。 二者 SONAME 与系统自带的 v6 不同,可以共存,不影响其它程序。

    未安装时的现象:

    riscv64-unknown-elf-gdb: error while loading shared libraries:
    libtinfo.so.5: cannot open shared object file: No such file or directory
    
  3. 安装 J-Link 软件

    J-LINK 下载对应平台的 J-Link 软件包进行安装,推荐安装 V7.98 及以上版本。

    安装完成后,检查 J-Link 是否正常安装:

    JLinkGDBServerCLExe --version
    
  4. 配置 J-Link 设备支持

    J-Link 需要设备描述文件才能识别芯片。SDK 在 tools/jlink/ 下提供了这些文件 (来自芯片原厂,含 ARCS 与 VenusA),部署到 J-Link 的固定搜索目录即可:

    rm -rf ~/.config/SEGGER/JLinkDevices
    mkdir -p ~/.config/SEGGER/JLinkDevices
    cp -r tools/jlink/. ~/.config/SEGGER/JLinkDevices/
    

    Important

    JLinkExe 只从 ~/.config/SEGGER/JLinkDevices/ 读取设备描述,不支持 -JLinkDevicesXMLPath 参数,也不读取当前工作目录。仓库里的 tools/jlink/ 必须拷贝到该目录才会生效。

    部署后目录结构:

    ~/.config/SEGGER/JLinkDevices/
    ├── JLinkDevices.xml                    设备描述
    ├── Devices/Arcs/flashloader.elf        ARCS 烧写算法
    ├── Devices/Venusa/flashloader.elf      VenusA 烧写算法
    └── scripts/jtagscan0.JLinkScript       JTAG 链扫描脚本
        scripts/jtagscan1.JLinkScript
    

    验证设备是否被识别(应输出 Connected to target):

    JLinkGDBServerCLExe -device VENUSA -if cJTAG -speed 4000 -port 2331 \
      -jlinkscriptfile ~/.config/SEGGER/JLinkDevices/scripts/jtagscan0.JLinkScript
    
  5. 验证 GDB 工具

    检查 GDB 工具是否可用:

    ${NUCLEI_TOOLCHAIN_PATH}/bin/riscv64-unknown-elf-gdb --version
    

    应该看到类似以下输出:

    GNU gdb (GDB) 13.2.90.20230712-git
    Copyright (C) 2023 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.
    

硬件要求

Important

需要 J-Link 仿真器 V11 或更高版本。

准备调试文件

编译时需要生成包含调试信息的 ELF 文件:

./build.sh -C -S samples/helloworld -DBOARD=arcs_evb

Important

切换 sample 或板型时必须加 -C``(清理构建目录)。``build/ 中残留的 CMake 缓存记录了上一个工程的源码路径,不清理会报:

CMake Error: The source ".../helloworld/CMakeLists.txt" does not match
the source ".../<上一个工程>/CMakeLists.txt" used to generate cache.

编译成功后,在 build 目录下会生成:

  • helloworld:包含调试符号的可执行文件

  • helloworld.bin:用于烧录的二进制文件

Note

默认情况下,SDK 编译生成的 ELF 文件已包含调试信息(-g 选项)。

开始调试

硬件连接

将 J-Link 仿真器连接到开发板。ARCS 与 VenusA 接线相同,均为 cJTAG 2 线

J-Link 引脚

目标板引脚

说明

SWCLK

PA00

cJTAG TCK

SWDIO

PA01

cJTAG TMS

GND

GND

地线

VTref

3.3V

参考电压

Warning

VTref 必须接。 J-Link 依赖该引脚检测目标电压,未接会直接报无法识别目标。

ARCS J-Link 调试器连接示意图

接口速率固定 4000 kHz,不要使用自适应速率。

PA00/PA01 的 Function 0(复位默认功能)就是 cJTAG,芯片上电即可调试,固件侧不需要 任何”使能 JTAG”的代码。若工程启用的驱动占用了这两个引脚,见 调试口引脚占用(全板型通用)

调试步骤

完整的 GDB 调试流程如下:

  1. 启动 J-Link GDB Server

    # 调试 AP 核(Core0),监听 2331
    JLinkGDBServerCLExe -device VENUSA -if cJTAG -speed 4000 -port 2331 \
      -jlinkscriptfile ~/.config/SEGGER/JLinkDevices/scripts/jtagscan0.JLinkScript
    
    # 调试 CP 核(Core1),监听 3331
    JLinkGDBServerCLExe -device VENUSA -if cJTAG -speed 4000 -port 3331 \
      -jlinkscriptfile ~/.config/SEGGER/JLinkDevices/scripts/jtagscan1.JLinkScript
    

    ARCS 芯片把 -device 换成 ARCS 即可,其余相同。接了多个探针时用 -select USB=<序列号> 指定,序列号可通过 echo ShowEmuList | JLinkExe -NoGui 1 查询。

    成功启动后会输出 Connected to target,随后停在 Waiting for GDB connection...

    Important

    停在 Waiting for GDB connection... 是正常状态,不是卡死。 GDB Server 会持续占用该终端等待客户端接入,请保持它不关闭,在新终端中 继续下一步。

    Important

    两颗芯片的 sample 默认运行核相反,照抄示例调另一颗芯片会连错核:

    板型

    默认核

    JLinkScript

    端口

    venusa_rd_evb

    AP(Core0)

    jtagscan0

    2331

    arcs_evb

    CP(Core1)

    jtagscan1

    3331

    连接后用 p/x $mhartid 自检:AP 为 0x0,CP 为 0x1。值不符说明 JLinkScript 选错了。

    另需确保 ELF 与目标芯片一致 —— 用 arcs_evb 编出的 ELF 连到 VenusA 上,寄存器能读但符号地址全错,断点会下到无效位置。

  2. 启动 GDB 并加载 ELF 文件

    在另一个终端中启动 GDB:

    ${NUCLEI_TOOLCHAIN_PATH}/bin/riscv64-unknown-elf-gdb build/helloworld
    

    启动后会进入 GDB 命令行界面:

    GNU gdb (GDB) 13.2.90.20230712-git
    ...
    Reading symbols from build/helloworld...
    (gdb)
    
  3. 连接到 GDB Server

    在 GDB 命令行中连接到 J-Link GDB Server:

    (gdb) target remote localhost:2331
    

    Note

    • 如果 GDB Server 运行在其他机器上,将 localhost 替换为对应的 IP 地址

    • 默认端口为 2331,如果使用其他端口,请相应修改

  4. 加载程序到目标设备

    连接成功后,将程序加载到目标设备:

    (gdb) load
    Loading section .text, size 0x1234 lma 0x20000000
    ...
    Start address 0x20000000, load size 4660
    Transfer rate: 1165 bytes/sec, 1165 bytes/write.
    
  5. 开始调试

    设置断点并运行程序:

    (gdb) break main
    Breakpoint 1 at 0x20000a34: file main.c, line 15.
    (gdb) continue
    Continuing.
    

快速调试脚本

为了简化调试流程,可以创建一个 .gdbinit 文件来自动执行常用命令:

# 连接到 GDB Server
target remote localhost:2331

Note

出于安全考虑,GDB 可能不会自动加载当前目录的 .gdbinit 文件。可以在用户主目录的 ~/.gdbinit 中添加:

set auto-load safe-path /

目标核心与 JLinkScript 选择

ARCS 与 VenusA 均为双核 RISC-V,两个核心挂在同一条 JTAG 链的不同 TAP 上, 必须加载对应的 JLinkScript,否则报 CPU-TAP not found in JTAG chain

目标核

JLinkScript

GDB 端口

JTAG IDCODE

mhartid

AP(Core0)

jtagscan0.JLinkScript

2331

0x10300A6D

0

CP(Core1)

jtagscan1.JLinkScript

3331

0x10200A6D

1

Important

只有调试 CP 核时用 jtagscan1,其余一律用 jtagscan0 —— 包括烧录、 读 flash、读寄存器。

自动判定当前工程跑在哪个核:

grep -E "CONFIG_(ARCS|VENUSA)_(AP|CP)_CORE=y" build/.config

Note

两颗芯片的默认核相反:ARCS 的 sample 默认运行在 CP 核, VenusA 的 sample 默认运行在 AP 核(CONFIG_HARTID 默认 0)。 连接后可用 p/x $mhartid 确认实际所在核心。

启动 GDB Server 时按上表选择对应的 -jlinkscriptfile-port

双核调试

CP(Core1)由 AP 通过 CMN_SYSCFG.SW_RESET_CFG1 控制,寄存器地址 0x43100010

写入值

作用

0xCAFE000A

把 CP 摁在复位里

0xCAFE000B

释放 CP 正常运行

只调试 AP 时,建议先把 CP 摁住,避免其在后台运行干扰:

(gdb) monitor memU32 0x43100010 = 0xCAFE000A

双核联调顺序:

  1. 先起 AP 会话,下载固件,停在 _start

  2. resume AP,由它释放 CP

  3. 再起 CP 会话 attach 接入(只加载符号,不重复下载固件)

SDK 侧对应实现见 soc/venusa/soc.cvenusa_start_cp()

调试口引脚占用(全板型通用)

PA00/PA01 的 Function 0(复位默认功能)本就是 cJTAG TCK/TMS,芯片上电即可调试, 固件侧不需要任何”使能 JTAG”的代码。会导致 J-Link 掉线的只有一种情况:板级 pinmux 把这两个引脚复用成了别的功能。

关键在于 lisa_xxx_pinmux() 这类板级函数只在对应驱动被启用时才执行samples/helloworld 这类最小工程既不启用 GPIOA 也不启用 PWM,两个函数都不会被 调用,PA00/PA01 一直停在 cJTAG 上——所以按前面的流程接好线就能直接调试。

一旦工程启用了会占用调试口的驱动,就会出现典型现象:上电瞬间能连上,固件跑起来 立刻掉线,或直接报 CPU could not be halted

各板型的占用情况:

板型

抢 PA00 的函数

抢 PA01 的函数

重写代价

arcs_evb

lisa_pwm_pinmux (func12 背光)

lisa_gpioa_pinmux (LCD_RST)

背光调节 + 屏复位

arcs_mini

lisa_gpioa_pinmux (CAMERA_RST)

lisa_gpioa_pinmux (PA_EN)

摄像头复位 + 外放

venusa_rd_evb

lisa_gpioa_pinmux (PA_EN)

外放

解决办法是按 SDK 惯例在应用代码里重写对应的 weak 函数(各 sample 处理引脚 共用时都采用这种做法):

#include "IOMuxManager.h"

/* 覆盖板级默认:保住 PA00/PA01 的 cJTAG 功能 */
void lisa_gpioa_pinmux(void)
{
    IOMuxManager_PinConfigure(CSK_IOMUX_PAD_A, 0, CSK_IOMUX_FUNC_DEFAULT); /* cjtag_tck */
    IOMuxManager_PinConfigure(CSK_IOMUX_PAD_A, 1, CSK_IOMUX_FUNC_DEFAULT); /* cjtag_tms */
}

Important

重写时必须保留原函数里其它引脚的配置,只去掉占用 PA00/PA01 的那几行。 arcs_evb 尤其要注意:lisa_gpioa_pinmux() 里还有 TP_INT(PA24)、 TP_RST(PA25)、PA_EN(PA27) 三行,漏掉会导致触摸失效;而且它的 PA00 是被 lisa_pwm_pinmux() 占的,需要同时重写两个函数

venusa_rd_evb 最简单,原函数只有 PA_EN 一行,重写不会丢失其它配置。

Note

代价是被占引脚的原功能失效:venusa_rd_evb 上是功放使能(无音频输出), arcs_evb 上是背光和屏复位。这是引脚复用的物理必然,软件绕不开。

venusa_rd_evb 另有硬件层面的共用:PA00 经 J53 跳线三选一接 PA_EN / 摄像头 PWDN / LCD_RST,PA01 经 H6 焊桥接 TP_INT。详见 boards/venusa_rd_evb/README.md

Note

CP 进入深睡后调试器掉线:CP 深睡时 Debug Module 时钟被关。置位 0x43100088N300_CORE1_CTRLcore1_override_dm_sleep 位, 可让 DM 在深睡时保持可访问。

RTT 日志

SEGGER RTT(Real-Time Transfer)通过调试器读写目标 RAM 传输日志:目标端只是往 RAM 写字节,J-Link 在后台把数据搬到主机。它不占用 UART 引脚,吞吐远高于 串口,对实时性的干扰也小得多,适合串口被业务占用、引脚不够,或波特率不足以承载 日志量的场景。

RTT 是 lisa_log 的一个后端,开启后与 console(UART)后端并存,两路同时 输出,不需要修改任何业务代码。ARCS 与 VenusA 均已支持。

开启方式

在工程 prj.conf 中加两行:

CONFIG_LOG=y
CONFIG_LOG_BACKEND_SEGGER_RTT=y

CONFIG_LOG_BACKEND_SEGGER_RTT 会自动 select SUPPORT_SEGGER_RTT。可调项 位于 menuconfig 的 Third-party ModulesSEGGER RTT Support

配置项

默认

说明

CONFIG_SEGGER_RTT_BUFFER_SIZE_UP

1024

目标→主机缓冲区字节数,占用 SRAM

CONFIG_SEGGER_RTT_BUFFER_SIZE_DOWN

16

主机→目标缓冲区字节数

CONFIG_SEGGER_RTT_MODE_DEFAULT

2

缓冲区满时:0=丢弃,1=截断,2=阻塞

Warning

默认的模式 2 保证日志不丢,但主机没有读取时缓冲区写满会阻塞在 SEGGER_RTT_Write() 。量产固件若要常开 RTT 后端,应改为 0 或 1。

开启后 SRAM 多占用约 BUFFER_SIZE_UP + BUFFER_SIZE_DOWN + 168 字节。

读取日志

连接参数

RTT 日志跟着固件走:固件跑在哪个核,就连那个核。先确认当前工程的核:

grep -E "^CONFIG_(HARTID|.*_(AP|CP)_CORE)=" build/.config
# CONFIG_ARCS_CP_CORE=y
# CONFIG_HARTID=1          → CP 核

两颗芯片的默认核相反:ARCS 默认 CP,VenusA 默认 AP。下表为使用默认内存分区时的连接参数:

芯片 / 核

-device

JLinkScript

端口

VenusA / AP(默认)

Venusa

jtagscan0

2331

VenusA / CP

Venusa

jtagscan1

3331

ARCS / CP(默认)

Arcs

jtagscan1

3331

ARCS / AP

Arcs

jtagscan0

2331

Tip

可用日志中的 hartid 自检读到的是哪个核:0 为 AP、1 为 CP。 与预期不符说明搜索范围或 JLinkScript 指向了另一个核。

操作步骤

下面以 ARCS / CP 核(默认配置)为例,其它组合按上表替换 -device、 JLinkScript 与端口。端口在终端 1 与终端 3 各出现一次,必须同时改。

终端 1 —— 启动 GDB Server,保持运行:

JLinkGDBServerCLExe -device Arcs -if cJTAG -speed 4000 -port 3331 \
  -jlinkscriptfile ~/.config/SEGGER/JLinkDevices/scripts/jtagscan1.JLinkScript

终端 2 —— 挂上 RTT 客户端,等待数据:

JLinkRTTClient

终端 3 —— GDB Server 连接时会 halt 内核,需放行才有日志产生。这一步还要告知 J-Link 控制块所在的 SRAM 区域,取值为该核镜像的 SRAM 分区,从构建产物 读出:

grep -E "^CONFIG_MEM_SRAM_(BASE|SIZE)=" build/.config
# CONFIG_MEM_SRAM_BASE=0x20010000
# CONFIG_MEM_SRAM_SIZE=0x00024800

两个值依次填入 SetRTTSearchRanges

${NUCLEI_TOOLCHAIN_PATH}/bin/riscv64-unknown-elf-gdb -q -batch \
  -ex 'target remote localhost:3331' \
  -ex 'monitor exec SetRTTSearchRanges 0x20010000 0x24800' \
  -ex 'monitor reset' \
  -ex 'continue' \
  build/helloworld

终端 2 随即输出:

I/elog            [00:00:00.048 1 elog_async] EasyLogger V2.2.99 is initialize success.
I/logger_sample   [00:00:00.049 1 main] Hello, world!

Tip

时间戳后面那一位是产生该日志的核mhartid):0 为 AP、 1 为 CP。上面输出中为 1,说明读到的是 CP 核的日志。连错核时终端 2 不会有任何输出,而不会显示另一个核的内容——每个核的 RTT 缓冲区是独立的。

也可在终端 3 的 GDB 中用 p/x $mhartid 直接确认当前连接的核。

Important

必须先启动终端 2,再执行终端 3。RTT 缓冲区读一次即清空,启动日志只在 复位那一刻产生;顺序反了会错过,需要再复位一次。

Note

  • 终端 3 的 GDB 在 continue 后会一直阻塞,这是正常的——它在等目标停下来。 确认日志已输出后 Ctrl-C 退出即可,目标与 RTT 都会继续运行。

  • 末尾的 ELF 可省略,但带上后能直接看出内核停在哪,排查时有用。

  • GDB Server 不要加 -singlerun,否则 GDB 断开时它会退出并带走 19021 上的 RTT 服务。

双核工程

RTT 需在每个镜像的 prj.conf 中单独开启,只在需要观察的核上开即可, 另一个核走 UART 或下面的 IPC 转发。两个核都开启时,必须按上一节指定搜索范围, 否则可能读到另一个核的日志。

SRAM 占用

每个开启 RTT 的核占用 BUFFER_SIZE_UP + BUFFER_SIZE_DOWN + 168 字节。SRAM 分区 较小的核需调小缓冲区,否则链接失败:

region `SRAM' overflowed by 736 bytes

调小 CONFIG_SEGGER_RTT_BUFFER_SIZE_UP 即可。VenusA CP 核仅 40 KB,默认 1 KB 的上行缓冲区放不下。

在一个终端看两个核的日志

一次只能连接一个核。需要同时观察两个核时,用 lisa_log 的 IPC 后端在固件侧汇聚:

# CP 核镜像
CONFIG_LOG_BACKEND_IPC_WRITER=y

# AP 核镜像
CONFIG_LOG_BACKEND_IPC_READER=y
CONFIG_LOG_BACKEND_SEGGER_RTT=y

对端日志带 [REMOTE] 前缀。两个后端可对调,哪个核便于接调试器就让哪个作 READER。需要工程已启用 AMP IPC。

Note

IPC 汇聚方式未在实板验证,仅依据 system/log/Kconfigsystem/log/lisa_log_backend_ipc.c 的实现说明。

常见问题

现象

处理

终端 2 停在 Connecting to J-Link RTT Server ... 无输出

ARCS 漏了 SetRTTSearchRanges 参数;或终端 3 尚未执行

Connection refused - There already is an active connection

19021 同时只允许一个客户端,pkill JLinkRTTClient 后重试

could not connect: 连接超时

终端 3 的端口与终端 1 不一致

Failed to get index for device name 'xxx'

-device 要填芯片名(Arcs / Venusa),不是板型名

CPU-TAP not found in JTAG chain

JLinkScript 选错,按上表核对

日志重复出现两遍

复位了两次,属正常

目标核跑飞、软复位无法恢复

断电重上电。切勿用 jtagscan1 执行烧录,会使 CP 核停不下来

确认固件是否真的在输出

工具链有问题时,可绕开 RTT 工具直接读控制块,判断是固件问题还是工具问题:

# 取控制块地址
${NUCLEI_TOOLCHAIN_PATH}/bin/riscv64-unknown-elf-nm build/helloworld | grep _SEGGER_RTT
# 20011f00 B _SEGGER_RTT

JLinkExe -NoGui 1 -Device Arcs -IF cJTAG -Speed 4000 -AutoConnect 1 -JTAGConf -1,-1 \
    -JLinkScriptFile ~/.config/SEGGER/JLinkDevices/scripts/jtagscan1.JLinkScript <<'EOF'
mem8 20011F00 0x10
exit
EOF

看到 "SEGGER RTT" 魔数即说明固件已正常初始化 RTT,问题在主机侧:

20011F00 = 53 45 47 47 45 52 20 52 54 54 00 00 00 00 00 00
           S  E  G  G  E  R     R  T  T

Warning

JLinkRTTLogger 不能用于 ARCS/VenusA——该工具不支持 cJTAG, -If cJTAG 会报 Could not parse target interface。请使用上面的 GDB Server + JLinkRTTClient 方式。

Note

读取 RISC-V cJTAG 需 J-Link 软件 V9.46 及以上,旧版会误识别为 ARM7。

常用 GDB 命令

本节介绍嵌入式调试中最常用的 GDB 命令。完整的 GDB 命令参考请查阅 参考资料

基本调试命令

命令

说明

break main

在 main 函数设置断点

break main.c:25

在 main.c 第 25 行设置断点

info breakpoints

查看所有断点

delete 1

删除编号为 1 的断点

continue (c)

继续执行

next (n)

单步执行(不进入函数)

step (s)

单步执行(进入函数)

finish

执行完当前函数并返回

查看变量和内存

(gdb) print variable_name           # 打印变量值(简写: p)
(gdb) print /x variable_name        # 以十六进制打印
(gdb) x/10xw 0x20000000             # 查看内存(10个字,十六进制)
(gdb) display variable_name         # 每次停止时自动显示变量

调用栈和寄存器

(gdb) backtrace                     # 显示调用栈(简写: bt)
(gdb) info locals                   # 显示局部变量
(gdb) info registers                # 显示所有寄存器
(gdb) print $pc                     # 打印程序计数器

实用命令

(gdb) list                          # 查看源代码(简写: l)
(gdb) info threads                  # 显示所有线程(RTOS 环境)
(gdb) help                          # 显示帮助信息
(gdb) quit                          # 退出 GDB(简写: q)

调试示例

以调试 helloworld 示例为例,展示完整的调试流程:

  1. 编译项目

    ./build.sh -C -S samples/helloworld -DBOARD=arcs_evb
    
  2. 启动 J-Link GDB Server

    参考 开始调试

    JLinkGDBServerCLExe -device ARCS -if cJTAG -speed 4000 -port 2331 -jlinkscriptfile $HOME/.config/SEGGER/JLinkDevices/scripts/arcs/jtagscan1.JLinkScript
    
  3. 启动 GDB 并调试

    ${NUCLEI_TOOLCHAIN_PATH}/bin/riscv64-unknown-elf-gdb build/helloworld
    

    在 GDB 中执行:

    (gdb) target remote localhost:2331
    (gdb) break main
    (gdb) continue
    (gdb) next                          # 单步执行
    (gdb) print variable_name           # 查看变量
    (gdb) backtrace                     # 查看调用栈
    (gdb) info registers                # 查看寄存器
    

常见调试场景

内存问题调试

(gdb) x/10xw $sp                    # 查看栈顶内存
(gdb) x/20i $pc                     # 查看当前指令
(gdb) watch *0x20001000             # 监视内存变化

函数调用跟踪

(gdb) break func_name
(gdb) backtrace                     # 查看调用栈
(gdb) info args                     # 查看函数参数
(gdb) finish                        # 执行完函数

调试技巧

条件断点和监视点

条件断点只在满足条件时才停止,监视点可以监视变量或内存的变化:

(gdb) break main.c:30 if counter > 100    # 条件断点
(gdb) watch variable_name                 # 监视变量变化
(gdb) watch *(int *)0x20000100            # 监视内存地址

查看汇编代码

对于底层调试,可以查看汇编代码:

(gdb) disassemble main              # 反汇编 main 函数
(gdb) disassemble /m main           # 混合显示源码和汇编
(gdb) stepi                         # 汇编级单步执行

常见问题

  1. 找不到调试符号

    问题现象:

    Reading symbols from build/helloworld...
    (No debugging symbols found)
    

    解决方法:

    • 确保编译时包含调试信息(-g 选项)

    • 检查是否误用了 .bin 文件而非 .elf 文件

  2. 无法连接到 GDB Server

    问题现象:

    (gdb) target remote localhost:2331
    localhost:2331: Connection refused.
    

    解决方法:

    • 确认 J-Link GDB Server 已正确启动

    • 检查端口号是否正确(默认 2331)

    • 检查防火墙设置

    • 确认 J-Link 仿真器已正确连接到开发板

  3. 断点无法命中

    可能原因:

    • 代码被优化掉(使用 -O0 编译选项禁用优化)

    • 断点位置不正确

    • 程序未正确加载到目标设备

    解决方法:

    (gdb) info breakpoints              # 检查断点状态
    (gdb) break *0x20000a34             # 使用绝对地址设置断点
    
  4. 程序执行位置与源代码不对应

    解决方法:

    • 确保 ELF 文件与烧录的 BIN 文件版本一致

    • 重新编译并烧录程序

    • 检查是否有多个版本的源文件

  5. 查看变量显示 optimized out

    问题原因:

    编译优化导致变量被优化掉。

    解决方法:

    • 使用 -O0 编译选项禁用优化(仅用于调试)

    • 在 CMakeLists.txt 中添加:

      add_compile_options(-O0 -g)
      
  6. CPU-TAP not found in JTAG chain

    启动 GDB Server 时漏了 -jlinkscriptfile。ARCS/VenusA 是双 TAP 链, 必须加载 JLinkScript,见 目标核心与 JLinkScript 选择

  7. 设备名无法识别

    The selected device "VENUSA" is unknown to this software version.
    

    tools/jlink/ 未部署到 ~/.config/SEGGER/JLinkDevices/,或部署不完整。 按 环境准备 重新拷贝。

  8. 固件一跑起来 J-Link 就掉线

    工程启用的某个驱动把 PA00/PA01 复用走了,典型表现是上电瞬间能连、固件跑起来 立刻掉线,或报 CPU could not be halted。见 调试口引脚占用(全板型通用)

  9. 断点不够用

    代码 XIP 跑在 flash 里时,软件断点(ebreak)无法写入,只能用硬件断点。 实测硬件断点为 4 个 —— J-Link 报告 HW instruction/data BPs: 4, 芯片官方文档给出的保守值是 2 个。用尽后需先删除已有断点,或把待调试的函数 放到 SRAM 里执行。

    另外 J-Link 报告 Support set/clr BPs while running: No,增删断点需先暂停目标。

  10. 探针识别不到

    lsusb | grep -i segger        # 确认 USB 枚举
    id -nG | grep plugdev         # 确认用户组
    ls /etc/udev/rules.d/*jlink*  # 确认 udev 规则
    

    在虚拟机中使用时,需先把 J-Link 设备从宿主机直通给虚拟机。

  11. 强杀 J-Link 进程后出现各种诡异报错

    若上一次会话被超时强杀,探针可能残留异常状态,随后出现 Could not start CPU coreJTAG communication errorcJTAG is not supported by the connected probe、USB 反复重新枚举等现象。

    这些是偶发的残留效应,并非稳定故障。处理顺序:直接重试通常即恢复; 目标核被留在 halt 导致程序不运行时用 cskburn --chip-id 复位芯片; 探针仍不响应则拔插 USB。

    Tip

    遇到这类报错先重复 2~3 次确认是否稳定复现,不要当作真实缺陷排查。

参考资料

GDB 文档

RISC-V 相关

J-Link 文档

ARCS SDK 相关