1. 项目概述与核心价值如果你正在折腾一块基于龙芯2K1000处理器的迅为开发板并且已经深入到需要调试其底层引导程序PMON的汇编启动阶段那么这篇文章就是为你准备的。这通常意味着你遇到了一个相当棘手的问题系统在非常早期的阶段就“卡住”了串口没有任何输出或者出现了无法解释的异常行为。常规的软件调试手段在这里完全失效因为此时C语言环境尚未建立甚至内存控制器都可能没有正确初始化。这时唯一能依赖的“手术刀”就是Ejtag调试器。我手头这块迅为2K1000开发板搭载的是一颗双核龙芯2K1000处理器它运行着PMON作为Bootloader。PMON本身功能强大但它的启动过程尤其是从处理器上电复位到跳转到C语言main函数之前的这段汇编代码是系统最脆弱、也最不透明的部分。一旦这里的代码有bug或者板级的硬件初始化如DDR参数配置不当板子就会变成一块“砖”——症状往往是上电后电流异常或者串口死寂。最近我就在为一块新设计的载板适配PMON时栽在了这个阶段最终正是通过Ejtag的单步调试才定位到一个隐蔽的缓存配置错误。所以今天我想分享的不仅仅是如何连接Ejtag、如何设置调试环境这些操作步骤。更重要的是我想带你理解在龙芯平台上利用Ejtag对PMON汇编阶段进行单步调试的完整逻辑链条、背后的硬件原理以及那些在官方文档里不会写但实践中能救命的“野路子”和避坑指南。无论你是在进行裸机开发、BSP移植还是仅仅想深入理解龙芯处理器的启动过程掌握这套方法都将让你拥有直接“透视”硬件的能力。2. 调试环境搭建与硬件连接要点工欲善其事必先利其器。对PMON汇编阶段进行调试意味着我们需要在处理器执行第一条指令之前就介入这要求调试器必须具备极高的权限和对处理器核心的直接控制能力。EjtagEnhanced Joint Test Action Group正是龙芯处理器内置的这种底层调试接口。2.1 硬件准备清单与选型逻辑你需要准备以下硬件每一件都有其不可替代的作用迅为2K1000开发板调试对象。确保板子有裸露的Ejtag调试接口通常是一个10针或14针的排母。我使用的迅为板子提供的是一个标准的14针2.54mm间距接口。Ejtag调试器这是核心工具。市面上有开源方案如基于FT2232H芯片的调试器和商业产品。对于龙芯2K1000我强烈推荐使用龙芯官方认可的调试器例如龙芯俱乐部推出的“智龙调试器”或其兼容产品。原因有三一是其固件对龙芯处理器的Ejtag协议支持最为完善二是配套的调试软件如LS2K-Ejtag兼容性好三是能稳定支持处理器核心的暂停、单步、内存访问等关键操作。贪便宜使用不兼容的调试器很可能在连接阶段就失败白白浪费时间。调试主机一台运行Linux的电脑。Windows环境下工具链复杂且易出问题Linux是首选。我使用的是Ubuntu 20.04 LTS。串口调试线用于观察PMON的正常输出和调试过程中的打印信息。虽然Ejtag可以窥视一切但串口输出作为一个独立的、实时的信息流对于交叉验证和了解系统状态依然至关重要。杜邦线用于连接Ejtag调试器与开发板。务必使用质量好的线劣质线材可能导致信号不稳定造成时好时坏的玄学问题。注意在连接任何线缆之前务必确保开发板和调试器均处于断电状态。热插拔Ejtag接口有损坏处理器调试模块的风险。2.2 软件工具链安装与配置调试环境的核心软件是ls2k-ejtag工具集。以下是在Ubuntu系统上的安装和验证步骤# 1. 安装必要的编译依赖 sudo apt-get update sudo apt-get install build-essential libusb-1.0-0-dev git # 2. 克隆ls2k-ejtag仓库假设你从龙芯官方或可信社区仓库获取 git clone https://gitee.com/loongson-community/ls2k-ejtag.git cd ls2k-ejtag # 3. 编译安装 make sudo make install # 4. 验证安装查看Ejtag调试器是否被系统识别 # 先将Ejtag调试器通过USB连接到电脑然后给开发板上电 lsusb | grep -i ftdi如果看到类似Bus 003 Device 004: ID 0403:6010 Future Technology Devices International, Ltd FT2232H Dual HS USB-UART/FIFO IC的信息说明调试器已被识别。接下来需要配置USB设备的访问权限避免每次使用都需要sudo。# 创建udev规则文件 sudo vim /etc/udev/rules.d/99-ejtag.rules在文件中加入以下内容具体vendor:product ID请根据lsusb结果修改SUBSYSTEMusb, ATTRS{idVendor}0403, ATTRS{idProduct}6010, MODE0666保存后重新加载udev规则并重新插拔调试器sudo udevadm control --reload-rules sudo udevadm trigger2.3 硬件连接与信号解读现在将Ejtag调试器通过杜邦线连接到开发板。龙芯2K1000的Ejtag接口通常是14针其关键信号定义如下引脚号信号名方向说明1TRST#输入调试复位低电平有效。重要通常需要接上拉电阻调试器驱动此信号。3TDI输入测试数据输入数据从调试器移入处理器。5TDO输出测试数据输出数据从处理器移出到调试器。7TMS输入测试模式选择控制Ejtag状态机。9TCK输入测试时钟由调试器提供。11RTCK输出返回时钟可选用于同步。龙芯2K1000可能未引出可悬空。2,4,6,8,10,12,14GND-接地。必须保证至少连接2-3根GND线确保信号回流。13VREF输入接口电压参考。必须连接通常接开发板的3.3V或1.8V具体看板子设计。实操心得GND是关键很多连接不稳定的问题源于地线连接不良。务必用多根杜邦线将调试器和开发板的GND引脚可靠连接。VREF必须接不接VREF调试器无法识别接口电平会导致通信完全失败。请查阅你的开发板原理图找到正确的电压测试点。线序核对三遍接错线可能烧毁调试器或处理器的调试模块。最稳妥的方法是同时对照调试器说明书和开发板原理图进行连接。连接完成后先不要给开发板上电。将调试器USB插入电脑打开一个终端尝试扫描处理器ejtag_debug_usb -p /dev/ttyUSB0 -c scan/dev/ttyUSB0可能需要根据你的实际设备节点调整可能是ttyUSB1等。如果配置正确你应该能看到工具识别到了Ejtag调试器。此时再给开发板上电。如果一切顺利在扫描结果中可以看到探测到的龙芯2K1000处理器核心。如果扫描不到请按上述要点逐一检查硬件连接和电源。3. PMON汇编启动阶段深度解析要调试必须先知道调试对象在干什么。PMON的汇编启动阶段通常指从处理器上电复位地址对于龙芯2K1000通常是0xbfc00000这是处理器内部一段Boot ROM的地址开始执行到跳转到C语言编写的main()函数之前的全部代码。这段代码通常用汇编语言MIPS汇编编写位于PMON源码的arch/loongson/loongson3/start.S或类似文件中。3.1 启动流程关键节点拆解我们可以将这段汇编启动流程分解为几个关键阶段每个阶段都有其明确的任务和潜在的故障点异常向量表初始化处理器复位后首先跳转到复位向量。这里的代码必须立即设置全局状态例如关闭中断、设置初始栈指针尽管此时内存可能还未初始化栈指针可能指向一段SRAM。这是调试器介入的第一个时机。核心基础配置包括设置处理器状态寄存器如Status寄存器关闭缓存因为内存尚未初始化缓存操作是危险的设置临时异常处理程序。如果这一步配置错误可能导致后续任何一条指令都触发异常。内存控制器初始化这是最复杂、最容易出错的一步。龙芯2K1000的DDR2/3 SDRAM控制器需要一系列严格的配置序列包括发送NOP、预充电、设置模式寄存器等。这些配置参数时序参数如tRCD, tRP, tRAS, CL等必须与板上使用的内存颗粒严格匹配。代码会通过读取SPD或使用硬编码参数来配置。一旦这里出错后续所有需要内存的代码都无法运行系统“变砖”。缓存初始化与使能内存可用后需要初始化数据缓存和指令缓存并正确设置缓存一致性协议Coherency Attribute。对于多核的2K1000还需要考虑二级共享缓存L2 Cache的初始化。环境准备与跳转设置C语言运行环境包括清除BSS段未初始化的全局变量区域设置好栈指针指向可用的内存区域然后最终调用jal main指令跳转到C语言的main()函数。3.2 定位需要调试的汇编代码你的PMON源码可能包含多个平台的启动文件。你需要找到针对龙芯2K1000或其所属的Loongson-3A系列的start.S文件。一个典型的查找和初步分析命令如下# 进入你的PMON源码目录 cd pmon-loongson3/ # 查找启动汇编文件 find . -name start.S -o -name head.S | grep -v .git # 通常路径类似于 ./arch/loongson/loongson3/start.S # 使用文本编辑器或cat命令查看其开头部分 vim ./arch/loongson/loongson3/start.S在start.S的开头你会看到类似于.set noreorder、.set mips64的指令集设置以及LEAF(_start)或TEXT_ENTER这样的标签这就是汇编代码的入口点_start。为什么需要单步调试这里想象一个场景你为新的载板修改了DDR参数编译烧写后板子上电无任何输出。串口是静的指示灯状态也不对。问题出在哪里是参数根本不对导致内存初始化失败还是参数基本正确但在某个细微的步骤比如设置模式寄存器时发生了超时或者是缓存配置导致后续取指错误没有Ejtag的单步调试你只能盲目地尝试修改参数、重新编译、烧写、上电效率极低。而单步调试允许你像播放电影一样一帧一帧地观察处理器的执行过程查看每一步执行后寄存器的变化以及访问内存时是否成功。4. Ejtag单步调试实战操作理论准备就绪现在让我们开始真正的调试。我们的目标是让处理器在_start标签处也就是第一条指令之前暂停然后我们可以控制它一条一条地执行汇编指令。4.1 建立调试连接与加载符号首先确保开发板断电Ejtag和串口线已连接好。启动调试服务器我们使用ejtag_debug_usb作为调试服务器。在一个终端中运行ejtag_debug_usb -p /dev/ttyUSB0 -c “server”这个命令会启动一个GDB兼容的调试服务器默认监听端口3333。保持这个终端运行。启动GDB并连接打开另一个终端使用交叉编译工具链中的GDB例如mips64el-linux-gdb。你需要先编译PMON生成带有调试符号的pmon文件通常位于zloader.ls2k或target/ls2k/目录下是一个.elf或没有后缀的可执行文件。# 进入你的PMON编译输出目录 cd /path/to/pmon-loongson3/zloader.ls2k/ # 启动GDB并加载符号文件 mips64el-linux-gdb pmon在GDB界面中连接到调试服务器(gdb) target remote localhost:3333如果连接成功你会看到类似Remote debugging using localhost:3333和处理器当前状态的输出。此时处理器可能已经在运行或处于复位状态。暂停处理器与设置断点连接后首先让处理器暂停。(gdb) monitor halt这条命令通过Ejtag向处理器发送“暂停”请求。成功后GDB会显示当前程序计数器PC的位置。这个地址很可能不是_start因为处理器可能已经跑飞了或者运行了一段Boot ROM代码。 接下来在汇编入口点设置断点。你需要知道_start的链接地址。这个地址通常在PMON的链接脚本ld.script或.lds文件中定义对于PMON其加载地址和运行地址通常都在内存中如0x80200000。但汇编启动代码在内存初始化前是运行在“非映射”的物理地址上的。更常见的做法是直接在复位向量地址0xbfc00000处设置断点或者使用符号如果GDB正确加载了符号且地址映射正确。# 方法1使用符号推荐如果符号加载正确 (gdb) break _start # 方法2使用绝对地址如果知道确切的物理地址 (gdb) break *0xbfc00000重要提示在内存初始化之前处理器运行在“非一致性”Uncached或“非映射”的地址空间。GDB看到的地址和处理器实际执行的物理地址可能存在映射关系。有时需要告诉GDB偏移量或者直接使用物理地址操作。这是底层调试中最容易混淆的一点。复位并运行到断点设置好断点后让处理器复位并从起点开始执行。(gdb) monitor reset (gdb) continue执行continue后处理器会从复位状态开始运行并在遇到你设置的断点_start时自动暂停。此时你就成功地“抓住”了处理器在执行PMON第一条汇编指令之前的状态。4.2 单步执行与状态观察现在你可以开始单步调试了。单步执行指令(gdb) stepi # 或者 (gdb) si每执行一次stepi处理器就执行一条汇编指令。GDB会显示下一条即将执行的指令。查看寄存器状态单步过程中最重要的就是观察寄存器的变化。# 查看所有通用寄存器 (gdb) info registers # 查看特定寄存器如栈指针sp (gdb) print/x $sp # 查看处理器状态寄存器如CP0 Status (gdb) print/x $status查看内存内容在内存初始化后你可以检查配置是否生效。# 从内存地址0x80000000假设为DDR起始地址读取16个字64位 (gdb) x/16gx 0x80000000如果内存初始化成功读出的应该是可预期的值例如如果之前向该地址写过数据。如果内存初始化失败读取操作可能会超时或者返回全0/全1等错误数据。反汇编当前代码随时查看当前和后续的汇编代码。(gdb) disassemble # 或者反汇编指定范围 (gdb) disassemble $pc, $pc50一个具体的调试场景假设你在单步执行到DDR初始化函数dram_init时想查看写入内存控制器的配置寄存器比如0x1fe1c040的值是否正确。# 执行到dram_init函数内部 (gdb) break dram_init (gdb) continue # 进入函数后单步 (gdb) stepi ... # 假设某条指令是向0x1fe1c040写入值执行后查看该内存映射的IO寄存器 (gdb) x/wx 0x1fe1c040通过这种方式你可以验证每一行配置代码的实际效果。4.3 调试技巧与脚本自动化手动单步几千条汇编指令是不现实的。你需要结合断点和条件判断。设置关键断点不要在_start单步到底。而是在关键函数入口设置断点如dram_init、cache_init、tlb_init等。运行到断点后再在函数内部进行精细的单步或nexti跳过函数调用。(gdb) break *dram_init (gdb) break *cache_init (gdb) commands print/x $a0 # 假设a0寄存器是传入dram_init的参数 continue end上面的commands命令可以为断点设置自动执行的命令例如打印参数。使用调试脚本对于重复性的调试任务比如每次复位后都要执行一系列命令可以编写GDB脚本。# 文件debug_pmon_start.gdb target remote localhost:3333 monitor halt break _start monitor reset continue # 此时停在_start可以开始交互式调试或继续执行脚本 echo “已停止在PMON汇编入口点\n”在GDB中通过source debug_pmon_start.gdb加载脚本。观察串口输出辅助调试同时打开一个串口终端如minicom或screen。当单步执行到初始化串口、并发送第一个字符的代码时你可以在串口终端上看到输出。这是一个重要的里程碑证明最低限度的处理器和外围设备初始化是成功的。5. 常见问题排查与实战心得即使按照指南操作你也一定会遇到各种问题。下面是我在多次调试中积累的“避坑指南”。5.1 连接与识别问题问题现象可能原因排查步骤ejtag_debug_usb -c scan无输出1. USB驱动问题2. 调试器未供电或损坏3. 开发板未上电或Ejtag接口VREF未接4. 线序错误1. 检查lsusb确认调试器被识别。2. 用万用表测量调试器VCC和GND是否有电。3.重点确认开发板已上电且VREF引脚连接了正确的电压通常3.3V。4. 对照原理图一根一根检查TDI, TDO, TMS, TCK, TRST#的连接。扫描到处理器但GDB连接失败1. 调试服务器未启动或端口被占用2. 防火墙阻止3. GDB与调试服务器版本不兼容1. 确保ejtag_debug_usb server正在运行并用netstat -tlnp查看3333端口是否在监听。2. 暂时关闭防火墙sudo ufw disable测试后请重新开启。3. 尽量使用与工具链配套的GDB。GDB连接后monitor halt无响应1. 处理器处于低功耗或异常状态2. Ejtag连接不稳定3. 处理器型号选择错误1. 尝试先monitor reset再halt。2. 检查杜邦线连接尤其是GND缩短线长。3. 确认ejtag_debug_usb支持龙芯2K1000。5.2 调试操作中的“玄学”问题单步执行时“跳飞”你单步stepi后发现PC指针跳到了一个完全不相关的地址比如0xaaaaaaaa。这几乎可以肯定是缓存一致性问题。在缓存未正确初始化或使能的情况下指令缓存I-Cache中的数据可能是旧的或无效的。解决方案是在单步通过缓存初始化代码之前禁用单步而是使用break命令在关键点设置断点然后continue运行过去。或者在初始化缓存之前通过Ejtag命令手动无效化指令缓存。读取内存返回错误数据在DDR初始化过程中或之后通过GDB的x命令读取内存返回的不是你写入的值。首先确认你访问的地址是物理地址还是虚拟地址。在MMU内存管理单元启用前后地址含义不同。其次确认访问属性是否可缓存。最稳妥的方法是在PMON汇编代码中在你想查看的内存地址处写入一个特殊的魔数如0xdeadbeef然后立即通过GDB读取看是否一致。断点无法命中在_start设置的断点复位后运行却没有停下。这是因为处理器可能从Boot ROM开始执行而Boot ROM的代码可能会在跳转到_start之前修改PC或者_start的地址并非你想象的那个。实操技巧不要只依赖符号断点。先用break *0xbfc00000在复位向量处断下单步跟踪Boot ROM的代码看它最终如何跳转到你的PMON代码区。找到真正的跳转指令如jr或jal和目标地址在那个目标地址上设置断点。5.3 针对PMON汇编调试的专项建议准备好源码和交叉参考一边用GDB单步一边在编辑器里看着start.S的源码。GDB的layout asm和layout src视图在汇编调试中不太好用不如分屏操作一个终端运行GDB另一个终端开着源码。重点关注“第一次”第一次配置缓存、第一次访问内存、第一次设置协处理器寄存器。这些操作往往有副作用且一旦出错后续状态全乱。善用monitor命令ejtag_debug_usb的GDB服务器支持很多monitor命令可以查询和修改底层状态。例如monitor cp0可以查看CP0寄存器monitor help列出所有支持的命令。这些命令比GDB原生命令更直接。记录寄存器快照在关键步骤前后如进入/退出dram_init用info registers记录所有寄存器的值。对比差异可以快速定位是哪条指令导致了寄存器的异常改变。调试PMON的汇编阶段就像在黑暗的迷宫中摸索。Ejtag是你唯一的光源。这个过程充满挑战但当你通过单步跟踪亲眼看到处理器执行完内存初始化代码然后成功读取到第一条来自DDR的指令时那种对系统了如指掌的成就感是无与伦比的。这套方法不仅适用于解决启动问题也是你深入学习龙芯处理器架构和计算机系统启动原理的绝佳途径。