RK3588 调试工具使用指南
1. 概述
1.1 功能简介
这份指南面向基于 RK3588 处理器的 SHIMETAPI-M5 系统,整理一套完整的调试方法和工具链。内容覆盖串口调试、GDB 远程调试、内核跟踪(ftrace/perf/strace)、NPU 调试、内存调试、GPIO/I2C/SPI 硬件调试以及崩溃转储(kdump/ramoops)等核心场景,帮你快速定位软件和硬件层面的问题。
1.2 应用场景
遇到不同类型的问题,用不同的工具组合,下表是快速索引:
| 场景 | 说明 |
|---|---|
| 系统启动失败 | 通过串口控制台捕获 bootrom/uboot/kernel 日志,定位启动卡死或 panic 原因 |
| 内核驱动调试 | 使用 ftrace、perf、printk 追踪驱动执行路径、中断延迟和调度行为 |
| 用户态应用崩溃 | 利用 GDB 远程调试、strace、AddressSanitizer 定位段错误和内存越界 |
| NPU 推理异常 | 通过 rknpu debugfs 和 RKNN Toolkit2 profiling 分析 NPU 推理耗时和内存占用 |
| GPIO/I2C/SPI 通信问题 | 借助 debugfs 和 i2cdetect 等工具验证总线状态和信号完整性 |
| 内存泄漏排查 | 使用 valgrind 和 AddressSanitizer 检测堆内存越界和泄漏 |
| 系统崩溃恢复 | 配置 kdump 和 ramoops 在内核 panic 后保留现场信息 |
1.3 工作原理
RK3588 的调试体系分三层:
硬件层,通过 UART2 串口(物理地址 0xfe660000,波特率 1500000)连接调试终端,通过 JTAG 接口连接硬件调试器实现片上调试。
内核层,Linux 6.1 内核提供 ftrace(函数跟踪)、perf(性能计数器)、printk/kmsg(内核日志)、debugfs(调试文件系统,挂在 /sys/kernel/debug 下)等机制,这些机制运行时可动态开关,对系统性能的影响可控。
用户层,通过 GDB + gdbserver 实现远程断点调试(gdbserver 跑在板子上,GDB 在主机上连过去),通过 strace 跟踪系统调用,通过 valgrind/AddressSanitizer 检测内存错误,通过 RKNN Toolkit2 做 NPU 模型 profiling。
2. 硬件准备
2.1 硬件清单
| 序号 | 名称 | 型号/规格 | 用途 |
|---|---|---|---|
| 1 | 核心板 | C-3588LQ V1.0 | RK3588 主处理器模块 |
| 2 | 底板 | SHIMETAPI-M5-R2A V1.0 | 外设接口扩展与信号转接 |
| 3 | USB 转串口线 | CP2102/CH340,支持 1.5Mbps | 连接 UART2 调试串口 |
| 4 | 调试器(可选) | J-Link / DAP-Link | JTAG 硬件断点调试 |
| 5 | 以太网线 | CAT5e/CAT6 | GDB 网络调试、文件传输 |
| 6 | 电源适配器 | 12V/3A DC | 系统供电 |
| 7 | SD 卡(可选) | 32GB+ Class10 | 系统启动与日志存储 |
2.2 调试串口连接
RK3588 的调试串口为 UART2,对应设备树节点 uart2@fe660000,波特率 1500000。接线注意 TX/RX 要交叉接:
- 将 USB 转串口模块的 TX 接到底板 UART2_RX 引脚
- 将 USB 转串口模块的 RX 接到底板 UART2_TX 引脚
- 将 GND 连接到共地引脚
- 将 USB 转串口模块插入主机 USB 口
接好后确认主机识别到了串口设备,再用 1500000 波特率打开终端:
# Linux 主机
ls -l /dev/ttyUSB*
# macOS 主机
ls -l /dev/tty.usbserial*
# 打开串口终端(1500000 波特率)
sudo picocom -b 1500000 /dev/ttyUSB0能看到板子的启动日志滚动,就说明串口通了。
2.3 硬件确认
这个脚本把串口、网络两项前置条件一次检查完:
#!/bin/bash
# hardware_check.sh - RK3588 调试硬件确认
echo "===== RK3588 调试硬件检查 ====="
[ -c /dev/ttyUSB0 ] && echo "[OK] /dev/ttyUSB0 存在" || echo "[WARN] 串口未检测到"
lsof /dev/ttyUSB0 2>/dev/null || echo "[OK] 串口未被占用"
ping -c 1 -W 2 192.168.1.100 &> /dev/null && echo "[OK] 目标板网络可达" || echo "[WARN] 网络不可达"
echo "===== 检查完成 ====="3. 软件准备
3.1 调试工具安装
在主机上装齐交叉编译、串口终端、跟踪和抓包工具,板端调试再装 NPU 工具:
# Linux 主机(Ubuntu/Debian)
sudo apt-get update && sudo apt-get install -y \
build-essential gcc-aarch64-linux-gnu gdb-multiarch \
minicom picocom screen strace ltrace valgrind \
linux-tools-common linux-tools-$(uname -r) \
trace-cmd kernelshark nmap tcpdump wireshark
# NPU 调试工具
pip3 install rknn-toolkit23.2 内核调试配置
调试功能大多要内核配合,编译时确认以下选项开启,每项的作用见注释:
# 内核 .config 关键调试配置项
CONFIG_DEBUG_INFO=y # 生成调试信息
CONFIG_GDB_SCRIPTS=y # 生成 GDB 辅助脚本
CONFIG_FRAME_POINTER=y # 保留帧指针(便于栈回溯)
CONFIG_KGDB=y # 内核 GDB 调试支持
CONFIG_KGDB_SERIAL_CONSOLE=y # 通过串口进行 KGDB 调试
CONFIG_MAGIC_SYSRQ=y # SysRq 魔术键支持
CONFIG_FTRACE=y # 函数跟踪
CONFIG_FUNCTION_TRACER=y # 函数级跟踪
CONFIG_FUNCTION_GRAPH_TRACER=y # 函数图跟踪
CONFIG_KPROBES=y # 动态探针
CONFIG_PERF_EVENTS=y # 性能事件
CONFIG_SCHED_DEBUG=y # 调度器调试
CONFIG_DEBUG_FS=y # debugfs 文件系统
CONFIG_KDUMP=y # 内核崩溃转储
CONFIG_PSTORE=y # 持久化存储(ramoops)
CONFIG_PSTORE_RAM=y # RAM 中的 pstore已有的系统可以直接核对,不用重新编译:
zcat /proc/config.gz | grep -E "CONFIG_(DEBUG_INFO|KGDB|FTRACE|KPROBES|PERF_EVENTS|DEBUG_FS|KDUMP|PSTORE|MAGIC_SYSRQ)"4. 操作步骤
4.1 串口调试
串口是最基础的调试手段,即使内核崩溃也能从串口拿到日志,所以先把它跑通。
提示:minicom / picocom / screen 出厂均未安装(which 全部无输出),sudo apt install -y picocom 后可用;网络环境下更方便的是直接 SSH。
# minicom
sudo minicom -D /dev/ttyUSB0 -b 1500000 # Ctrl+A X 退出
# picocom(推荐,先安装)
sudo apt install -y picocom
sudo picocom -b 1500000 /dev/ttyUSB0 # Ctrl+A Ctrl+X 退出
# 串口日志保存
sudo picocom -b 1500000 /dev/ttyUSB0 | tee serial_$(date +%Y%m%d).txt串口通了之后还能用 SysRq(内核提供的魔术调试键,通过 /proc/sysrq-trigger 触发)做应急诊断:
echo 1 > /proc/sys/kernel/sysrq # 启用 SysRq
echo t > /proc/sysrq-trigger # 打印所有任务状态
echo l > /proc/sysrq-trigger # 显示所有 CPU 调用栈
echo w > /proc/sysrq-trigger # 打印阻塞任务
echo c > /proc/sysrq-trigger # 触发内核 crash(测试 kdump)4.2 GDB 远程调试
用户态程序用 gdbserver 远程调试:板端起 gdbserver 挂着程序,主机端的 gdb-multiarch 连过去下断点:
# === 目标板端 ===
gdbserver 0.0.0.0:1234 /path/to/program
# === 主机端 ===(⚠ 语法勘误:是 "target remote" 两个词,不是 set target-remote)
gdb-multiarch /path/to/program
(gdb) target remote 192.168.49.60:1234 # 板子 IP
(gdb) set architecture aarch64 # x86 主机需要;板上原生 gdb 免设
(gdb) break main
(gdb) continue
(gdb) info registers # 查看寄存器
(gdb) backtrace # 查看调用栈
(gdb) print variable_name # 打印变量值
(gdb) x/16xg 0xaddr # 查看内存下面这段是板上 gdb 13.1 + gdbserver 13.1 本机自连验证的全流程,断点、取值、回溯都正常:
(gdb) target remote localhost:2346
(gdb) break compute
(gdb) continue
Breakpoint 1, compute (n=100) at gdb_target.c:5
(gdb) print n
$1 = 100
(gdb) bt 3
#0 compute (n=100) at gdb_target.c:5
#1 0x... in main () at gdb_target.c:10要点:gdbserver 用
setsid ... &完全脱离终端;无调试符号时断点能设但栈不可读;目标程序必须-g编译。
内核问题则用 KGDB:目标板触发后,主机用串口当调试通道连进去:
# 目标板触发 KGDB
echo g > /proc/sysrq-trigger
# 主机端连接
gdb-multiarch vmlinux
(gdb) target remote /dev/ttyUSB0
(gdb) set baud 1500000
(gdb) break start_kernel
(gdb) bt4.3 ftrace 函数跟踪
ftrace 是内核自带的跟踪器,不用装任何东西,挂上 debugfs 就能用。常用的 function_graph 模式能看到函数调用关系和每个函数的耗时:
# 挂载 debugfs
mount -t debugfs none /sys/kernel/debug
# 函数图跟踪(显示调用关系和耗时)
echo function_graph > /sys/kernel/debug/tracing/current_tracer
echo "i2c_*" > /sys/kernel/debug/tracing/set_ftrace_filter
echo 1 > /sys/kernel/debug/tracing/tracing_on
tail -f /sys/kernel/debug/tracing/trace_pipe
echo 0 > /sys/kernel/debug/tracing/tracing_on # 停止
# 事件跟踪
echo 1 > /sys/kernel/debug/tracing/events/sched/sched_switch/enable
echo 1 > /sys/kernel/debug/tracing/events/irq/irq_handler_entry/enable
echo 1 > /sys/kernel/debug/tracing/tracing_on
# trace-cmd 工具(可用,出厂已装)
trace-cmd record -e sched_switch -e irq_handler_entry sleep 5
trace-cmd report | head -30
# (function_graph + i2c_* 过滤器,配合 i2cdetect 触发):
echo function_graph > /sys/kernel/debug/tracing/current_tracer
echo "i2c_*" > /sys/kernel/debug/tracing/set_ftrace_filter
echo 1 > /sys/kernel/debug/tracing/tracing_on
i2cdetect -y 1; echo 0 > /sys/kernel/debug/tracing/tracing_on
cat /sys/kernel/debug/tracing/trace | head -8
# 输出:
# 7) 7.583 us | i2c_get_adapter();
# 7) 1.750 us | i2c_verify_client();
# 7) 1.167 us | i2c_verify_client();
# 7) | i2c_smbus_xfer() {
# 保存并可视化分析
trace-cmd record -p function_graph -g i2c_transfer -- sleep 10
kernelshark trace.dat像上面那样在 trace 里看到函数名和微秒级耗时,就说明过滤器配对了。
4.4 perf 性能分析
perf 用硬件计数器做统计和热点分析。注意出厂未装,apt 装 linux-perf 时它的版本(6.1.187)和内核(6.1.141)不同,基本统计可用,个别事件符号可能缺失:
# ⚠ perf 出厂未装,apt 装 linux-perf(注意其版本 6.1.187 与内核 6.1.141 不同,
# 基本统计可用,个别事件符号可能缺失)
sudo apt install -y linux-perf
# 系统级/进程级统计
perf stat -a sleep 5
perf stat ./busy_loop # (忙循环程序):
# 508.37 msec task-clock:u # 2 CPUs utilized
# 1,103,218,060 cycles:u # 2.170 GHz
# 1,200,086,333 instructions:u # 1.09 insn per cycle
# 函数级热点分析
perf record -a -g sleep 10
perf report
# 事件跟踪
perf record -e sched:sched_switch -a sleep 3
perf stat -e cache-references,cache-misses -a sleep 5
# 火焰图生成
perf script -i perf.data | stackcollapse-perf.pl | flamegraph.pl > flamegraph.svg4.5 strace 系统调用跟踪
strace 记录程序和内核之间的每次系统调用,程序卡在文件、网络还是内存上,一眼就能看出来:
strace -p <PID> # 跟踪进程
strace /path/to/program # 跟踪程序启动
strace -e trace=network -p <PID> # 仅跟踪网络调用
strace -e trace=file -p <PID> # 仅跟踪文件调用
strace -tt -T -p <PID> # 带时间戳
strace -f -p <PID> # 多线程跟踪
strace -c -p <PID> # 统计汇总下面是 strace -c ls / 的汇总(出厂已装),按耗时排序列出各调用的占比:
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
21.17 0.000249 16 15 mmap
15.65 0.000184 23 8 mprotect
9.52 0.000112 16 7 munmap
9.10 0.000107 21 5 read4.6 NPU 调试
NPU 驱动在 debugfs 下暴露了状态节点,能看版本、负载和电源;模型层面的耗时分析交给 RKNN Toolkit2 的 perf 接口:
# debugfs NPU 调试信息(可用节点共 7 个:
# delayms freq load power reset version volt —— info/usage/mem_info 不存在!)
ls /sys/kernel/debug/rknpu/
sudo cat /sys/kernel/debug/rknpu/version
# 输出: RKNPU driver: v0.9.8
sudo cat /sys/kernel/debug/rknpu/load
# 输出: NPU load: Core0: 0%, Core1: 0%, Core2: 0%,
sudo cat /sys/kernel/debug/rknpu/power
# 输出: off(空闲时 RK806 已给 NPU 下电,推理时变 on)
cat /sys/kernel/debug/rknpu/dvfs
# RKNN Toolkit2 模型 profiling
python3 << 'EOF'
from rknn.api import RKNN
rknn = RKNN()
rknn.config(mean_values=[[123.675, 116.28, 103.53]],
std_values=[[58.395, 57.12, 57.375]],
target_platform='rk3588')
rknn.load_rknn('model.rknn')
rknn.init_runtime(target=None)
import numpy as np
input_data = np.random.randint(0, 255, [1, 224, 224, 3], dtype=np.uint8)
print(rknn.perf(inputs=[input_data], n=100))
rknn.release()
EOFversion 读出 RKNPU driver: v0.9.8、推理时 load 从 0% 跳上去,就说明观测链路正常。
4.7 内存调试
内存问题分两类手段:编译期插桩(AddressSanitizer,编译时加检查代码,越界访问当场报错)和运行期检测(valgrind 模拟执行找泄漏):
# AddressSanitizer
aarch64-linux-gnu-gcc -fsanitize=address -g -O1 program.c -o program
LD_PRELOAD=/usr/lib/aarch64-linux-gnu/libasan.so.5 ./program
# Valgrind 内存泄漏检测
valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes ./program
# 内存监控
watch -n 1 'cat /proc/meminfo | grep -E "MemTotal|MemFree|MemAvailable"'
# OOM 调试
dmesg | grep -i "oom\|out of memory\|killed process"4.8 GPIO/I2C/SPI 硬件调试
硬件总线问题先看内核视角的状态:GPIO 看电平和引脚配置,I2C 用 i2cdetect 扫总线、i2cget/i2cset 读写寄存器,SPI 用 spi-pipe 收发:
# GPIO
cat /sys/kernel/debug/gpio
cat /sys/kernel/debug/pinctrl/fdd90000.pinctrl/pins
# I2C
i2cdetect -l # 列出 I2C 总线
i2cdetect -y 0 # 扫描 i2c-0
i2cget -y 1 0x50 0x00 # 读取寄存器
i2cset -y 1 0x50 0x00 0x01 # 写入寄存器
# SPI
ls /dev/spidev*
spi-pipe -d /dev/spidev0.0 -s 1000000 -b 8 -c 0 -r 164.9 崩溃转储
内核 panic 后现场转瞬即逝,kdump 把崩溃时的内存完整存下来,ramoops 则利用一块预留 RAM 在重启后保留最后的日志:
# kdump 配置
sudo apt-get install kdump-tools makedumpfile
sudo dpkg-reconfigure kdump-tools
sudo reboot
# ramoops/pstore
ls /sys/fs/pstore/
cat /sys/fs/pstore/console-ramoops-0
# crash dump 分析
crash /usr/lib/debug/boot/vmlinux-$(uname -r) /var/crash/*/vmcore
# crash 常用命令:bt(调用栈) log(日志) ps(进程) kmem(内存)5. 代码详解
5.1 调试方法论
调试讲究由简到繁,能用日志解决的别上调试器。五级策略如下,从上往下逐级升级:
第一级:printk + 串口日志
└─ 适用:驱动加载、初始化、基础功能验证
└─ 优点:无额外依赖,内核崩溃也能看到
第二级:strace + 系统调用跟踪
└─ 适用:用户态程序行为分析、文件/网络操作追踪
└─ 优点:无需重编译,运行时挂载
第三级:ftrace + 内核函数跟踪
└─ 适用:内核驱动执行路径、中断延迟、调度行为分析
└─ 优点:性能开销极小,可动态开关
第四级:GDB + 断点调试
└─ 适用:复杂逻辑错误、内存越界、多线程竞态
└─ 优点:精确控制执行流程
第五级:KGDB + 内核调试
└─ 适用:内核级 bug、驱动底层问题
└─ 优点:内核级断点、单步执行5.2 各工具使用场景对比
各工具的层级、开销和适用面放在一张表里对比,选型时按性能影响和复杂度权衡:
| 工具 | 层级 | 性能影响 | 信息密度 | 适用场景 | 复杂度 |
|---|---|---|---|---|---|
| printk | 内核 | 极低 | 低 | 启动调试、驱动基础验证 | 低 |
| strace | 用户态 | 中 | 高 | 系统调用追踪、程序行为分析 | 低 |
| ftrace | 内核 | 低 | 高 | 驱动路径分析、调度研究 | 中 |
| perf | 内核+用户 | 低 | 高 | 性能分析、热点函数定位 | 中 |
| GDB | 用户态 | 高 | 极高 | 逻辑错误、内存问题 | 中高 |
| KGDB | 内核 | 极高 | 极高 | 内核级 bug、底层驱动问题 | 高 |
| valgrind | 用户态 | 高 | 高 | 内存泄漏、越界访问 | 中 |
| ASan | 用户态 | 中 | 高 | 内存错误实时检测 | 中 |
5.3 自动化调试信息采集
报问题时与其口头描述,不如一键打包现场。这个脚本把系统版本、内存、日志、GPIO 和 I2C 扫描结果收集成压缩包:
#!/bin/bash
# rk3588_debug_collect.sh - RK3588 调试信息一键采集
OUT="debug_$(date +%Y%m%d_%H%M%S)"
mkdir -p "$OUT"
uname -a > "$OUT/uname.txt"
cat /proc/meminfo > "$OUT/meminfo.txt"
dmesg > "$OUT/dmesg.txt"
dmesg | grep -iE "error|panic|oops|fail" > "$OUT/errors.txt"
cat /proc/slabinfo > "$OUT/slabinfo.txt"
cat /sys/kernel/debug/gpio > "$OUT/gpio.txt" 2>/dev/null
for bus in $(ls /dev/i2c-* 2>/dev/null | sed 's|/dev/i2c-||'); do
i2cdetect -y $bus >> "$OUT/i2c.txt" 2>/dev/null
done
tar czf "${OUT}.tar.gz" "$OUT" && rm -rf "$OUT"
echo "完成: ${OUT}.tar.gz"运行结束输出 tar.gz 的路径,把文件发出去即可。
6. 常见问题
6.1 串口无输出或乱码
现象是连上串口后没有任何输出,或者满屏乱码。最常见的原因是波特率,RK3588 是 1500000,不是常见的 115200。排查顺序:
# 1. 确认波特率(RK3588 为 1500000,不是常见的 115200)
# 2. 检查 TX/RX 交叉连接(非直连),确认 GND 共地
# 3. 检查 USB 转串口芯片驱动
dmesg | grep -i "tty\|usb\|serial\|cp210\|ch34"
# 4. 尝试降低波特率测试基本通信
# 5. 检查设备树中 UART2 配置,确认 uart2@fe660000 节点已启用6.2 GDB 连接超时
主机 GDB 连不上板端 gdbserver。先确认网络通,再查防火墙和监听地址,网络不可用时还能退回串口:
# 1. 确认网络连通:ping 192.168.1.100
# 2. 检查防火墙:sudo iptables -A INPUT -p tcp --dport 1234 -j ACCEPT
# 3. gdbserver 监听地址使用 0.0.0.0(非 127.0.0.1)
# 4. 无网络时使用串口作为 GDB 通道
gdb-multiarch vmlinux
(gdb) target remote /dev/ttyUSB0
(gdb) set baud 15000006.3 ftrace 无输出
启用了跟踪但 trace 文件是空的。依次确认内核支持、过滤器拼写、开关状态,最后清掉过滤器重测:
# 1. 确认内核支持:zcat /proc/config.gz | grep CONFIG_FTRACE
# 2. 检查 filter 名称是否与函数名完全匹配
# 3. 确认 tracing_on 状态为 1
# 4. 清除 filter 重新测试
echo > /sys/kernel/debug/tracing/set_ftrace_filter
echo 1 > /sys/kernel/debug/tracing/tracing_on6.4 NPU 调试信息无法读取
读取 /sys/kernel/debug/rknpu/ 下的文件报错或为空。按顺序查挂载、驱动和权限:
# 1. 确认 debugfs 已挂载
mount -t debugfs none /sys/kernel/debug
# 2. 确认 rknpu 驱动已加载:lsmod | grep rknpu
# 3. 检查初始化日志:dmesg | grep -i rknpu
# 4. 需要 root 权限:sudo cat /sys/kernel/debug/rknpu/version # info 节点不存在6.5 内核 panic 后无法获取 crash dump
panic 之后重启,找不到 crash dump。kdump、crashkernel 参数和 pstore 逐项核对:
# 1. 确认 kdump 服务:systemctl status kdump-tools
# 2. 检查 crashkernel 参数:cat /proc/cmdline | grep crashkernel
# 3. 检查 pstore:mount -t pstore none /sys/fs/pstore
# 4. 设置 panic 后等待时间:GRUB_CMDLINE_LINUX="panic=10"6.6 perf 报 "Permission denied"
运行 perf 提示权限不足,原因是内核的 perf_event_paranoid 限制。三种处理方式任选:
# 1. 临时允许:sudo sysctl -w kernel.perf_event_paranoid=-1
# 2. 永久生效:echo "kernel.perf_event_paranoid=-1" >> /etc/sysctl.conf
# 3. 或使用 sudo:sudo perf stat -a sleep 57. 相关文档
| 文档名称 | 路径 | 说明 |
|---|---|---|
| RK3588 Linux 系统开发指南 | ../Debian-Ubuntu/system-development/ch01-linux-sdk-build.md | Linux 系统整体开发参考 |
| RK3588 内核编译指南 | 01Linux系统/01内核编译指南.md | 内核编译与配置 |
| RK3588 驱动开发指南 | 01Linux系统/02驱动开发指南.md | Linux 驱动开发参考 |
| RK3588 设备树配置指南 | 01Linux系统/03设备树配置指南.md | DTS 配置与调试 |
| RK3588 网络功能配置指南 | 01Linux系统/04网络功能配置指南.md | 网络相关调试 |
| RK3588 多媒体调试指南 | 01Linux系统/05多媒体调试指南.md | 视频/音频/显示调试 |
| RK3588 NPU 开发指南 | 01_Linux系统/06_NPU开发指南.md | NPU 模型部署与调试 |
| RK3588 GPIO 使用指南 | 02_外设驱动/../../peripheral-drivers/ch01-gpio-driver-and-control.md | GPIO 调试方法 |
| RK3588 I2C 使用指南 | 02_外设驱动/../../peripheral-drivers/ch02-i2c-driver.md | I2C 调试方法 |
| RK3588 SPI 使用指南 | 02_外设驱动/../../peripheral-drivers/ch03-spi-driver.md | SPI 调试方法 |
