4、Vitis系统调试入门:设置断点、单步执行、查看变量与寄存器、使用Xilinx System Debugger、调试视图介绍

调试,说白了就是给代码“看病”。

你写完了代码,烧进了板子,结果灯不亮、串口没输出、或者跑着跑着就挂了。这时候怎么办?靠猜?不行。靠printf大法?在嵌入式里还行,但在FPGA+ARM的异构系统里,printf有时候根本来不及打印程序就崩了。

所以,Vitis自带的调试工具——Xilinx System Debugger,就是你最趁手的“手术刀”。

我个人习惯,写代码的时间可能只占30%,剩下70%都在调试。尤其是第一次上板,几乎必出问题。嗯,这节课我就带你把这把刀磨快。

核心观点:调试不是“出问题了才做”,而是开发流程的一部分。学会用调试器,能帮你把“玄学问题”变成“逻辑问题”。

4.1 调试视图介绍:你的“驾驶舱”

打开Vitis,点击右上角的“Debug”透视图(Perspective)。你会看到几个关键窗口。我第一次用的时候也懵,窗口太多了。别慌,记住三个核心区域就行。

视图名称 作用 我的经验
Debug (调试栈) 显示当前暂停在哪一行、调用堆栈 程序卡死时,先看这里,看它停在哪条指令
Variables (变量) 查看局部变量、全局变量的当前值 我经常在这里发现指针飞了,值变成0xDEADBEEF
Registers (寄存器) 查看CPU内核寄存器(R0-R15、PC、SP等) 看PC指针能判断程序是不是跑飞了
Memory (内存) 查看指定地址的内存数据 检查DDR里数据对不对,或者外设寄存器值
Console (控制台) 显示printf输出和调试信息 有时候printf不输出,记得勾选“Auto Scroll”

你想想看,这几个视图就像汽车的仪表盘。速度表(PC指针)、油量表(堆栈指针)、故障灯(异常中断)。学会看它们,你就能知道车(程序)到底哪里出了毛病。

4.2 设置断点:让程序“停下来”

断点,就是你在代码里埋的一个“路障”。程序跑到这里,自动停下,等你检查。

怎么设置?很简单:

  • 在代码行号左侧双击,出现蓝色小圆点,就是断点。
  • 右键断点,可以设置条件(比如变量等于某个值才停下)。

我的小技巧:不要在中断服务函数(ISR)里设断点。为什么?因为中断频率太高,你根本来不及单步,程序会反复触发断点,卡死。我曾经犯过这个错,在定时器中断里设断点,结果调试器直接崩溃。后来我学乖了,用全局变量做标志位,在main函数里轮询这个标志位再设断点。

另外,Vitis支持硬件断点和软件断点。软件断点数量不限,但会修改代码(插入一条BKPT指令)。硬件断点数量有限(通常4-6个),但不会修改代码,适合在ROM里调试。我建议你平时用软件断点就够了。

4.3 单步执行:一步一步看代码怎么跑

程序停下来了,然后呢?你得一步一步看它怎么走。

Vitis的调试工具栏上有几个按钮,我列一下:

按钮 快捷键 作用
Step Into (F5) F5 进入函数内部
Step Over (F6) F6 跳过函数,不进入内部
Step Return (F7) F7 从当前函数跳出,回到调用处
Resume (F8) F8 继续运行,直到下一个断点

我个人习惯:

  • 如果怀疑某个函数内部有bug,就用Step Into进去看。
  • 如果只是想知道函数返回值对不对,就用Step Over跳过。
  • 如果发现进错了函数,赶紧Step Return跳出来。

嗯,这里要注意:单步执行时,如果程序跑到了延时循环(比如for(i=0;i<1000000;i++)),你会按到手指抽筋。这时候直接点Resume,或者把断点设在循环后面。

4.4 查看变量与寄存器:找到“罪魁祸首”

程序停下来了,变量值对不对?寄存器状态正不正常?

在Variables视图里,你可以看到所有局部变量。如果想看全局变量,直接在表达式窗口(Expressions)里输入变量名就行。

我遇到过一个问题:一个全局变量明明赋了值,但读出来却是0。后来发现是编译器优化了,变量被放到了寄存器里,没写回内存。解决办法:给变量加volatile关键字。嗯,这是C语言里一个经典坑,调试器里能看到变量值,但实际内存里没有更新。

再看Registers视图。这里显示的是CPU内核寄存器:

  • PC (Program Counter):程序当前执行到的地址。如果PC指向0x0或者某个异常向量表地址,说明程序跑飞了。
  • SP (Stack Pointer):堆栈指针。如果SP值异常(比如变成了0),说明堆栈溢出了。
  • LR (Link Register):函数返回地址。如果LR被破坏了,函数就回不去了。

避坑指南:我曾经调试一个Zynq项目,程序跑着跑着就进HardFault异常。查了半天,发现是中断嵌套太深,堆栈溢出了,把LR寄存器给覆盖了。解决办法:加大堆栈大小,或者减少中断嵌套层级。所以,调试时多看看SP寄存器的值,别等崩了才想起来。

4.5 使用Xilinx System Debugger:不止是GDB

Vitis的调试器底层是GDB(GNU Debugger),但Xilinx在上面封装了一层,叫Xilinx System Debugger。它比普通GDB多了几个功能:

  • 支持多核调试:可以同时调试ARM Cortex-A53、Cortex-R5,甚至MicroBlaze。
  • 支持硬件触发:可以设置硬件断点、数据观察点(变量变化时停下)。
  • 支持JTAG调试:通过Xilinx下载器(比如Platform Cable USB)连接板子。

怎么启动调试?

  1. 在Vitis里右键点击你的工程,选择“Debug As” -> “Launch on Hardware (System Debugger)”。
  2. Vitis会自动下载比特流(如果硬件配置变了)和ELF文件到板子上。
  3. 程序开始运行,停在main函数入口处(默认)。

如果你不想每次都重新下载比特流,可以勾选“Program FPGA”选项。我一般只在第一次调试时下载,后面修改代码只重新下载ELF,能省不少时间。

核心命令(在Debug Shell里用):

# 查看当前线程信息
info threads

# 查看堆栈回溯
backtrace

# 查看某个地址的内存内容
x/10x 0x40000000

# 修改变量值
set var my_var = 100

这些命令在图形界面里也能操作,但用命令行更快。我习惯把常用命令记在记事本里,调试时直接复制粘贴。

4.6 调试实战:一个典型的上板调试流程

说了这么多,咱们走一遍实际流程。假设你写了一个LED闪烁的程序,但灯不亮。

  1. 连接板子:插上JTAG下载器,打开板子电源。Vitis里能看到目标设备(比如Cortex-A53 #0)。
  2. 启动调试:右键工程 -> Debug As -> Launch on Hardware。
  3. 设置断点:在GPIO写寄存器的代码行设断点。
  4. 单步执行:按F6单步,看程序是否执行到了GPIO写操作。
  5. 查看寄存器:在Memory视图里输入GPIO控制寄存器的地址(比如0xE000A000),看值有没有变化。
  6. 发现问题:如果寄存器值没变,说明GPIO初始化没成功。检查时钟使能、MIO配置。
  7. 修改代码:退出调试,修改代码,重新编译,再次调试。

你看,整个过程就像侦探破案。一步步缩小范围,找到真凶。

4.7 本章知识体系图

下面这张图,是我自己总结的调试知识体系。你可以保存下来,调试时对照着看。

Vitis系统调试知识体系 Xilinx System Debugger 设置断点 硬件断点 / 软件断点 条件断点 / 数据观察点 单步执行 Step Into / Step Over Step Return / Resume 查看变量与寄存器 Variables / Registers Memory / Expressions 调试视图介绍 Debug / Variables / Registers Memory / Console / Expressions 调试实战流程 连接板子 → 启动调试 设断点 → 单步 → 查寄存器 调试 = 断点 + 单步 + 观察 + 分析 把“玄学问题”变成“逻辑问题”

这张图把调试的核心要素都串起来了。你调试时遇到问题,就对着这张图,看看自己卡在了哪一步。

4.8 避坑总结

最后,我把自己踩过的坑列一下,你遇到了可以直接翻出来看:

  • 断点不生效:检查是不是优化等级太高(-O2以上),编译器把代码优化掉了。改成-O0试试。
  • 单步执行跳到了奇怪的地方:可能是代码和数据在Cache里,调试器看到的是内存里的旧值。试试关掉Cache,或者加volatile。
  • 调试器连不上板子:检查JTAG驱动是否安装,板子是否上电,下载器是否被其他程序占用(比如Vivado的hw_server)。
  • 程序跑飞进HardFault:先看堆栈回溯(backtrace),找到最后调用的函数。多半是指针越界或者堆栈溢出。

调试这东西,说白了就是熟能生巧。你多调几次,自然就形成自己的套路了。嗯,这节课的内容就到这里,希望你能把调试器用起来,别再靠printf猜问题了。

可可精品资料库