DSH HUB
首页插件商店插件包社区排行榜资源发布指南
插件源码
返回插件目录

lishuo4096 /

lishuo4096/dsh-wch-debug

已验证

Drive WCH CH32 microcontrollers from the CLI through an isolated private copy of WCH's OpenOCD plus WCH-Link. DSH plugin: 20 tools + an agent skill. MIT.

★ 0 Stars0 Forks0 IssuesN/A 社区评分0 已确认安装
查看 GitHub项目主页
README来源: main@0c28a978

dsh-wch-debug —— WCH / CH32 单片机调试插件

用 OpenOCD + CLI 调试沁恒(WCH)CH32 系列单片机的 DSH 插件:把 WCH-Link 调试、 固件烧录、寄存器/内存读写、gdb 连调、常驻调试会话这一整套能力交给 agent, 并且从设计上不可能污染你的 IDE 与后续烧录。

插件 id   @local/dsh-wch-debug        (cordis id: wch-debug)
工具       20 个,分五组
技能       wch-debug
外部依赖   无 npm 依赖;需要本机 C:\MounRiver 里的 OpenOCD / 工具链 / WCH-Link

为什么需要这个插件(不是「又一个烧录脚本」)

1. 工作区污染 —— 一次手滑,之后所有烧录都坏

OpenOCD 在不给任何 -f 且不给 -c 时会自动 source 当前工作目录里的 ocd.cfg(搜索路径第 1 项就是 CWD)。而 MounRiver Studio 恰恰是以它自己的 ...\WCH\OpenOCD\OpenOCD\bin\ 为 CWD 启动 openocd 的 —— 那个目录里**出厂就躺着** 一个 ocd.cfg:

interface cmsis-dap
transport select swd
source [find target/ch32f1x.cfg]

它和 bin\wch32f103.cfg 逐字节相同,意思是 ARM / SW-DP。 谁在 bin 里试过一次命令、留下一个 cfg,之后所有 WCH-Link 操作都会被静默顶成 ARM 模式, 而目标却是 RISC-V 的 CH32V 系列 —— 连不上、或者连上了你也看不懂为什么。

本插件的对策

  • openocd 整棵子树(30.9 MB / 1816 文件)复制一份到插件内 vendor\openocd\(bin/、scripts/、share/ 相对布局照抄,默认搜索路径仍然可用)。
  • 永不写 IDE 树、永不把 IDE 树当 CWD。assertPrivateWorkdir() 是一道硬门禁:CWD 不在插件私有区就抛 WorkspaceViolation。
  • 每次执行都在 .run\sessions\<id>\ 这个一次性私有目录里,跑完即弃。
  • cfg 只走绝对路径 -f(argv 传参),既不依赖隐式 source,也不在 cfg 文件里写 source <路径>。
  • 主动剥掉子进程环境里的 OPENOCD_SCRIPTS(本机它指向 ~\.eide\tools\openocd-0.12.0\scripts,优先级高于自带脚本库,且那里没有 ch32f1x.cfg)。

2. 断点残值固化进固件 —— 芯片上电就停在断点,再也进不了程序

软件断点是改写目标内存里的指令实现的(RISC-V 上写 ebreak,ARM 上写 bkpt), 只有删除时才会写回原指令。而 flash 按扇区擦写,粒度远大于一个断点。于是:

会话里 bp 0x…            → 目标内存那条指令被改成 ebreak
会话异常终止 / 清了但没写回 → 这几个字节留在目标里
下一次烧录 program …      → flash 按扇区擦写,ebreak 随镜像一起被编程
下一次上电                → 停在断点,程序跑不起来

本插件的对策

  • 每次收尾都清断点再放跑:catch {rbp all} → reset run。
  • 清断点用 catch { … } 包住,避免「目标正在跑时清理报 Target not halted」被误判成失败。
  • 绝对不 taskkill 常驻会话:只有 telnet 已经不通(进程卡死)才退回强杀,并且报告里明说 「这次是强杀的,目标里可能残留断点,下次烧录前先清」。
  • 报告里始终打印这次到底跑了什么收尾(收尾(已写进 -c):清断点rbp all → `reset run)。

⚠️ 收尾引擎踩过的第二个坑:一次把所有行写进去,等于只发了第一行

第一版 telnet 实现是 sock.write(lines.join('\n') + '\n'),然后在第一个提示符上就 finish() + destroy()。后果非常隐蔽:

发出去:halt \n catch {rbp all} \n … \n shutdown
收到 halt 的提示符 → 立刻 destroy socket
→ openocd 还没来得及读第 4 行,剩下的行全丢

现象是 wch_session_close 每一步都回显 ✓ … → prompt(因为提示符确实拿到了), 但 12 秒后进程还活着、telnet 端口还开着,日志里根本没有 shutdown command invoked。 即「报告说清了、实际没清」—— 与下面 foreach a [bp] 那个坑是同一个形状: 看起来做了,其实没做。

现在改成:发一行 → 等这一行的提示符或断连 → 再发下一行,并把每条命令各自的结果收进 results, 报告里逐条打勾。shutdown 是特例(它不回提示符,直接关连接), 所以「连接被关掉」本身就是它生效的证据。

⚠️ 实证纠正:清断点的常见写法其实是空操作

本插件第一版用的是流传很广的 foreach a [bp] { rbp $a },前提是「bp 无参返回全部断点号」。 实测这个前提是错的:

$ bp                       ← 无参不是断点号列表,是给人看的字符串
Breakpoint(IVA): 0x20000010, 0x4, 1869182051

$ foreach a [bp] { rbp $a }
Invalid command argument
addr option value ('Breakpoint(IVA):') is not valid

$ bp                       ← 断点还在,一个都没清掉
Breakpoint(IVA): 0x20000010, 0x4, 1869182051

TCL 按空白切词,$a 第一个值是 Breakpoint(IVA):,rbp 拿到非法 addr 报错 —— 而外面套的 catch 把错吞了:一个断点都没清掉,却看不到任何异常。 这正是用户踩过的那类事故的形状(「声称清理了,其实没清」)。

正确写法是 rbp all。之前因为「手册里没见过」而回避了它,其实问本构建自己就有权威答案:

$ help rbp
rbp 'all' | address
      remove breakpoint

实测 rbp all 生效(两条断点一次清空,bp 随后回空)。教训: 能用官方文档字符串证实的,就别用「流传的写法」猜;catch 只能容忍无害失败, 不能用来包一个自己没验证过的命令。

实测:本机这条路径上「残值写进内存」没有复现(但纪律照旧)

本机 CH32V307 + 本构建,bp <addr> <len> 出来的是 Breakpoint(IVA) —— 走青稞 V4 的 IVA(硬件断点)机制。逐字节实测:下断点前 / halt 下设置后 / resume 之后再 halt / rbp all 清理后,RAM 与 flash 都完全相同,始终没有出现 ebreak(0x00100073)。

纪律照旧,三个理由:① 硬件断点槽位有限,用满后 OpenOCD 会退回软件断点,那时就真改指令字节了; ② ARM 侧(cortex_m)的软件断点就是 bkpt 改写;③ 断点残留在目标上会让后续 reset run 停在意外位置 —— 一样是「烧完不跑」。别把「这次没复现」当成「不用清」。

3. 判据 —— 退出码会骗人

openocd.exe 的命令失败时也可能返回 0。更糟的是 -l <log> 会把 Error: 从 stdout/stderr 里挪走,只看捕获流就漏判。所以:

  • 判据 = 文本里有 ^Error: → 失败(不论退出码);无 Error: 但退出码 ≠ 0 → 失败;两者干净才算过。
  • 默认不给 -l(我们自己已经捕获 stdout+stderr);给了 -l 就把日志读回来合并进判据文本。
  • 烧录的成功判据是 ** Verified OK ** + 收尾的 reset run,不是「命令返回了」。

安装

// 在 DSH 里(一次调用)
plugin_manager({ action: 'install_bundle', target: 'C:\\Users\\lishuo\\Documents\\DSH_Project\\插件开发\\dsh-wch-debug' })

看返回里的 application 字段:applied 才算生效。 (若返回 changed:false + application:'failed' + error.code:'ambiguous-install', 那是缓存的旧失败,不必重启:set_plugin(target:'include:wch-debug', enabled:false) 再 enabled:true。)

验证:

plugin_manager({ action: 'list_plugins' })                                    // 看 fiberPhase: 'active'
cordis_inspect_query({ platform:'host', provider:'Tool', method:'listTools' }) // 看 20 个 wch_* 工具

工具名是按依赖顺序注册的,插件加载时控制台会打印:

[wch-debug] 已注册 20 个工具: wch_env, wch_devices, … , wch_clean
[wch-debug] 已注册技能 provider: wch-debug(skills/ 下的 SKILL.md)

工具面(20 个)

第一次用,先跑 wch_env —— 它一次把插件根、隔离副本版本、IDE 原件位置、工具链、 WCH-Link 当前模式与串口、残留 openocd 进程、端口占用、以及「现在能不能干活」全报出来。

A 组:环境 / 清单 / 帮助(只读)

工具 干什么
wch_env 环境自检。隔离副本版本与 5 个 cfg、IDE 原件位置与出厂残留、工具链(gcc/gdb/objcopy…)、WCH-LinkUtility、WCH-Link 的 USB 节点与当前模式(RV / ARM)、残留 openocd 进程、3333/4444/6666 占用
wch_devices CH32 型号表(20 个,key 如 ch32v307):core / flash / RAM / 该用哪个 cfg / 需要 WCH-Link 处于哪种模式 / 证据来源(local = 本机实测,public = 公开资料)。不传参数列全部
wch_projects 扫 C:\MounRiver\Project 下的 Link.ld,列出各工程的 FLASH / RAM / entry / 推断型号
wch_workspace action: audit 卫生审计(P1/P2 分级 + 修复建议)· quarantine 把出厂残留 ocd.cfg / ocd_try.* 挪进隔离区(只动副本,不动 IDE 原件)· prune 清超过 24h 的会话目录
wch_help 排错剧本:first_time / not_connecting / flash_ok_but_dead / bp_residue / session / tools

B 组:只看不改(读完就 reset run 放跑,绝不把芯片留在调试态)

工具 干什么
wch_probe 连一次目标并报告:WCH-Link 型号/固件、时钟、调试模块(datacount / progbufsize)、RISC-V XLEN / misa、目标名、flash device id 与容量、PC/SP
wch_regs 读寄存器;RISC-V 还能读 CSR(mstatus / mcause / mepc)
wch_mem 读一段内存并本地排 hexdump(实现走 dump_image 落文件再排版,比解析 mdw 文本可靠)。约 2.8 kB/s,读 4 KiB ≈ 1.5 s
wch_flash_info flash probe 详情:device id、容量、bank 基址
wch_read_flash 读出一段(或整片)成 .bin,落到 backups/。走 flash read_bank。整片 256 KiB ≈ 88 s,超时按字节数自动算,报告里给预期秒数

C 组:写目标(都需要 confirm:true)

工具 干什么
wch_flash 烧录(elf / hex / bin / s19)。默认先备份原 flash 到 backups/,再清断点 → program … verify → 逐个清断点 → reset run。可传 address、verify:false、backup:false
wch_erase 擦除(整片 / 指定扇区区间)。页面数按 pageSize 自己算,不用未经证实的 last
wch_reset mode: run / halt / init / halt_run
wch_ocd 逃生舱:把任意 openocd 命令串喂给隔离副本执行。仍然强制私有 CWD、强制绝对 -f、不加载任何隐式 cfg。用来做本插件没封装的检查(看断点、mdw、自定义序列…)。footer 可选 run(默认)/ halt / none

D 组:常驻会话与连调

工具 干什么
wch_session_open 起一个常驻 openocd:3333 gdb / 4444 telnet / 6666 tcl。同一个 WCH-Link 同时只能被一个 openocd 占用 —— 起之前先 wch_session_list
wch_session_cmd 往会话里打命令(走 telnet),保持调试上下文:halt / bp / step / mdw / reg …
wch_session_close 先清断点再 shutdown(不是 taskkill)。只有 telnet 不通才 force 强杀,并警告可能残留
wch_session_list 列活的会话(PID / 端口 / 目标 / 存活判定)
wch_gdb 用 MounRiver 工具链里的 gdb 做批处理调试:action: symbols(不连硬件,只查 ELF 里的符号/地址 —— 找 main 地址去下硬件断点就用它)/ load(走 gdb 的 flash 编程)/ inspect(halt + 寄存器 + bt + 栈内存 + 放跑)/ raw

E 组:清理

工具 干什么
wch_clean action: audit 工作区卫生审计 · scan 列私有运行区与会话目录 · prune 清过期会话 · kill 清残留 openocd 进程

技能

skills/wch-debug/SKILL.md 是给 agent 看的操作手册(不是工具清单的复述):什么时候该用 一次性工具、什么时候该起会话、连不上按什么顺序排除、烧录成功但板子不跑怎么查、 断点残值怎么确认与清掉。工具描述里写不下的判断依据放这里。


目录结构

dsh-wch-debug/
├─ package.json            name @local/dsh-wch-debug / type module / dsh.bundle.patch
├─ cordis.patch.yml        - insert: - id: wch-debug / name: "@local/dsh-wch-debug"
├─ index.js                apply(ctx):注册 20 工具 + 技能 provider
├─ lib/
│  ├─ define-tool.js       零依赖的 defineTool 本地实现(见下「为什么自带」)
│  ├─ frontmatter.js       SKILL.md 的 YAML frontmatter 解析
│  ├─ skills.js            技能 provider(list / get 契约)
│  ├─ result.js            ok / fail / output / clip / kvBlock / hexDump … 统一返回形状
│  ├─ env.js               MounRiver 树探测、WCH-Link 模式、工具链、进程与端口
│  ├─ devices.js           CH32 型号表 + 目标解析 + 链接脚本扫描
│  ├─ hygiene.js           ★ 工作区门禁、会话目录、污染扫描、收尾命令常量
│  ├─ ocd.js               ★ argv 构造、私有 CWD 执行、判据、各序列
│  ├─ tools-env.js         A 组
│  ├─ tools-debug.js       B/C 组
│  └─ tools-session.js     D/E 组
├─ skills/wch-debug/SKILL.md
├─ references/
│  ├─ openocd-wch-commands.md   684 行:WCH 专用 cfg 矩阵、命令参考、污染机理专章、排错表
│  └─ raw/                     从 WCH-Link 手册 PDF 抽出的文本(中/英)
├─ scripts/
│  ├─ register-check.mjs   注册自检:工具数 / schema / 技能 provider(不用真机)
│  ├─ selfcheck.mjs        环境 + 门禁 + 真机只读探针
│  ├─ smoke-reload.mjs     重载冒烟:十几秒,只读,改完代码 reload 后跑一遍
│  ├─ e2e-hardware.mjs     真机端到端(回写校验 + 断点卫生来回验证 + IDE 树零写入)
│  ├─ e2e-protect.mjs      真机端到端(读保护闭环:开 → 裸烧必败 → unprotect 烧进 → 关)
│  ├─ chain-ab.mjs         实验:到底哪条命令会中断 `-c` 链(决定几何必须单独跑一次)
│  ├─ bench-read.mjs       读吞吐实测第一轮(路径反斜杠的坑就是它暴露的)
│  └─ bench-read2.mjs      读吞吐实测定稿(决定超时与 read_bank 选型)
├─ vendor/openocd/         ★ 隔离副本(30.9 MB / 1816 文件),永不与 IDE 共用
├─ .run/                   私有运行区:每个会话一个一次性目录(24h 后清)
└─ backups/                烧录前自动备份的原 flash

为什么自带一份 define-tool.js / skills.js

本地 link: 装载的插件,Node 按真实路径向上找 node_modules,而 @deepseek-ai/dsh-tools 等宿主包只存在于宿主模块图里(Desktop 下在 app.asar 内,磁盘上没有)。 所以插件里不能 import 任何 @deepseek-ai/*,否则整行 failed to import。 lib/define-tool.js 就是 defineTool 的等价本地实现(同一套 options:parameters DSL、 output.schema / output.render、execute(args, exec)、timeoutMs…)。


设计上的硬约束(改代码前请先读)

  1. CWD 必须在插件私有区。所有 openocd 子进程都经 runOpenocd();它先过 assertPrivateWorkdir()。forbiddenRoots() = MounRiver 根 + IDE OpenOCD 树 + 副本体。

  2. cfg 只走 -f 绝对路径,且所有路径用正斜杠。不要在 cfg 文件里写 source <路径> —— OpenOCD 0.11 的 TCL 从文件内容取的路径走窄字符 fopen,含非 ASCII 的路径必然 GBK 乱码: 插件开发 → 鎻掍欢寮€鍙? → No such file or directory。 同一路径经 argv(-f,宽字符)完全正常 —— 这是实测结论,别再改回去。 同理 -c 字符串里的 C:\... 会被 TCL 吃掉反斜杠(见上文「路径一律正斜杠」)。 buildArgs() 对双引号和反斜杠都设了硬闸门。

  3. 收尾必须清断点 + reset run。见上文第 2 条。并且要区分「写进了 argv」与「真的执行了」—— 见下面第 7 条。

  4. 写操作要 confirm:true。这不是权限确认,是防手滑的机械闸门。

  5. ARM 族用 bin\wch32f103.cfg,不要用 bin\wch-arm.cfg —— 后者第 3 行写死相对路径 source [find ../share/openocd/scripts/target/ch32f1x.cfg],CWD ≠ bin 就报 Can't find ../share/...。前者内容等价但走 find,与 CWD 无关。

  6. RV 与 ARM 是四项整体切换:RV = adapter driver wlinke + transport select sdi + wlink_set_address + sdi newtap … -irlen 5 -expected-id 0x00001; ARM = adapter driver cmsis-dap + transport select swd + ch32f1x.cfg。 WCH-Link 的硬件模式不匹配时不会报「模式不对」,只会报连不上 —— 用 wch_env 看 mode。

  7. -c 链会中途断掉,且断得毫无提示。OpenOCD 把多个 -c 串成一条 TCL 脚本: 任一命令报错 → 后面所有命令全部被静默丢掉。后果正是本插件最怕的那类 —— footer 里的 rbp 与 reset run 一起没了,断点残值留在 flash 里、目标停在调试态。 两条对策都已落地:

    • chainComplete(text):日志里必须有 shutdown command invoked(脚本走完的最后一步), 缺席就说明链断了。报告里会显式打出 ⚠️。这条比「有没有 Error:」更接近根因 —— 错误文本可能被 catch 吞掉,断链藏不住。

    • 收尾兜底:主链失败时 runOpenocd() 会自动另起一次 openocd 进程只做收尾 (init → halt → 清断点 → reset run),不指望主链能走到 footer。

    • 具体踩点:flash info 0 在本驱动上打完扇区表就会报错(Flash protection check is not implemented), 裸着写就会把后面的命令连 shutdown 一起带走。但「套上 catch」不是解法 —— 套上之后链是保住了,扇区表却一个字节都不打(parsed.flash.sectors 变 undefined), 于是 wch_flash_info 静默丢掉几何。这个 bug 静态看不出来,只有真机跑才露馅。 A–E 五组对照实验(scripts/chain-ab.mjs,每组尾部挂 reg pc 当探针):

      组 命令 exit 尾随 pc shutdown 扇区表行数
      A 裸 flash protect_check 0 0 ✅ ✅ —
      B 裸 flash info 0 1 ❌ ❌ 288
      C catch 包住两条 0 ✅ ✅ 0
      D 裸 protect_check + catch {flash info 0} 0 ✅ ✅ 0
      E catch {flash protect_check 0} 0 ✅ ✅ —

      结论两条路:要几何 → 裸 flash info 且单独跑一次(geometrySequence()); 要链完整 → 完全别放 flash info(套 catch 也拿不到表)。 所以 wch_flash_info 跑两次,主链只放 flash protect_check(裸,A 组证明它不断链)。

    • 同理 probeSequence 把 reg pc 排在 flash probe 0 前面:万一 flash 探失败, 至少还有 PC 能证明探针活着。

  8. 判据也不能只看「文本里有没有 Error:」 —— 这是第 3 条的镜像错误,一样会把成功判成失败。 实测:回写整片 flash 时日志里同时出现

    Error: checksum mismatch - attempting binary compare
    ** Verified OK **
    

    而退出码是 0。checksum mismatch - attempting binary compare 是「checksum 预校验不一致 → 退化为逐字节比对」的降级提示,不是失败结论(真失败会另有 Verify failed)。 为此 lib\ocd.js 加了 BENIGN_ERRORS + classifyError():parseOcd 把这类行收进 parsed.benign 而不是 parsed.errors,并附一条注说明它为什么无害。 白名单必须逐条实测过才准进 —— 往里塞一个真错误,等于把判据阉了。

未能证实、或实测有害的写法(代码里刻意避开)

写法 为什么不用
foreach a [bp] { rbp $a }(清断点) 实测是空操作:bp 无参返回的是给人看的字符串,rbp 拿到非法 addr 报错、被 catch 吞掉,断点一个都没清。改用 rbp all(本构建 help rbp 原文支持,实测一次清空)
bp <addr>(只给地址) 实测报用法行、不设断点。必须带长度:bp <addr> 4;要硬件断点再加 hw
rwp all help rwp 只给了 rwp address,没有 all。改为从 [wp] 里用 {0x[0-9a-fA-F]{8}} 抠地址逐个删
flash erase_sector 0 0 last last 未证实。改用扇区号自己算(注意扇区是 1 KiB,不是 256 B)
flash verify 单独命令 改用 program … verify 与 verify_image
-o / gdb_speed_override 未证实,不依赖
riscv set_mem_access sysbus / abstract 实测会把读打瘸:sysbus=skipped (unsupported size)、abstract=failed。显式指定会关掉 progbuf→sysbus→abstract 的自动回退链,而 WCH 驱动靠的正是默认的 progbuf 抽象访问。慢就慢,别提速
riscv set_prefer_sba on 已废弃(openocd 自己会警告改用 set_mem_access),且实测毫无加速
mdw 读大段再解析文本 实测约 2000 µs/B,比读文件慢一个数量级(dump_image / flash read_bank 是 335 / 326 µs/B)
-l <logFile> 指望 Error: 留在捕获流里 -l 会把消息重定向进文件,stdout/stderr 只剩 banner → 判据退化成「只看退出码」。默认不加 -l;给了就必须把文件读回来并入判据文本
在 cfg 里 source <绝对路径> 非 ASCII 路径必挂:Jim-TCL 走窄字符 fopen,插件开发 被按 GBK 解成 鎻掍欢寮€鍙? → couldn't read file。cfg 一律由 argv 的 -f 传(Windows 宽字符安全)
openocd 命令里出现 Windows 反斜杠路径 TCL 会吃转义:\U→U、\b→退格,路径直接烂掉。buildArgs() 现在硬拒绝含 \ 的命令串,要求先 toSlash()

实测性能(决定超时与用法)

WCH-Link 的 SDI 通道每次内存访问都要一趟 USB 往返,所以读吞吐只有 ~2.8 kB/s, 而且与适配器时钟几乎无关(把 6000 kHz 调低并不会更快)。

方式 4 KiB 16 KiB 32 KiB 边际
flash read_bank 2011 B/s — 3065 B/s 326 µs/B
dump_image 1676 B/s 2620 B/s 2718 B/s 335 µs/B
mdw(解析回显文本) — — — ≈ 2000 µs/B

推论:整片 256 KiB 读一次约 88 s。 这是物理限制,不是卡住。

所以:

  • wch_read_flash / wch_mem 的超时按字节数算(20000 + bytes × 0.35 ms × 2,下限 60 s), 不再用固定 90 s —— 第一版就是固定 90 s,整片备份正好超时。
  • 报告里始终打印「预期耗时 约 N s」,并说明「慢是 SDI 的物理限制,不是卡住」, 免得下一个人去杀进程、去重试,反而更糟。
  • 要快就传 length 只读要看的那一段。
  • 读 flash 用 flash read_bank(语义正确 + 略快),读 RAM 用 dump_image。
  • 烧录用 TIMEOUTS.flash = 900 s:program … verify = 擦 + 写 + 一轮完整回读比对, 256 KiB 全流程约 150 s 起。

⚠️ 给 openocd 的路径一律正斜杠

-c 里的字符串会先过 TCL 的转义处理,而反斜杠是 TCL 的转义引导符: \b 退格、\n 换行、\t 制表、\v/\f/\a、\0~\7、\x、\u…

于是:

-c "flash read_bank 0 C:\Users\lishuo\AppData\Local\Temp\wch-bench\bank.bin 0 4096"
                          ↑  \U→U  \A→A  \L→L  \T→T  \w→w  \b→(退格符)  ↑
实际变成  C:UserslishuoAppDataLocalTempwch-benchank.bin

openocd 只会报 Error: couldn't open C:Users...ank.bin / Could not open file —— 看不出原因。

规矩:所有路径先过 toSlash()(\ → /;openocd/Jim-TCL 完全接受 C:/a/b)。 buildArgs() 里已经加了闸门:任何 -c 命令串里出现反斜杠就直接抛错, 并在消息里给出正斜杠版本,而不是让 openocd 报天书。

反直觉的一点:同一个路径经 -f 传(-f C:\中文\...\wch-riscv.cfg)是正常的 —— 它走 Windows 宽字符 argv。而经文件内容 source <路径> 或**-c 字符串**就会坏。 详见下文「硬约束」第 2 条。


自检

cd C:\Users\lishuo\Documents\DSH_Project\插件开发\dsh-wch-debug

node scripts/register-check.mjs      # 不需要硬件:工具数 / schema / 技能 provider
node scripts/selfcheck.mjs           # 环境 + 工作区门禁 + 真机只读探针
node scripts/smoke-reload.mjs        # 重载冒烟:十几秒、只读,改代码后跑这个最快
node scripts/e2e-hardware.mjs        # 真机端到端(会回写 flash;--skip-flash 只跑只读)
node scripts/e2e-protect.mjs         # 真机端到端(读保护闭环:会短暂开关读保护)
node scripts/chain-ab.mjs            # 实验:哪条命令会中断 `-c` 链(几秒,只读)

e2e-hardware.mjs 里有两项是专门盯着 m00001 那个坑的:

  • 回写校验(identity round-trip):先把整片 flash 读出来存 .bin,再把同一份烧回去。 内容没变 → 板子状态零改变,但真的走了一遍 erase → write → verify → 清断点 → reset run。
  • 断点卫生来回验证:读 RAM 与 flash 基线 → 会话里下断点(bp <addr> 4)→ 在 halt 状态、 resume 之后、rbp all 清理之后各读一次并断言与基线相同。 实测结论:本机 CH32V307 走 Breakpoint(IVA) 硬件断点,全程没有出现 ebreak —— 也就是说「残值写进内存」这条路径在本机没有复现(细节与为什么纪律照旧,见上文第 2 条)。 这一步的价值在于把这些结论钉成可重跑的断言,换驱动/换型号时会立刻暴露差异。
  • IDE 树零写入:跑之前/之后对 ...\OpenOCD\OpenOCD\bin 做「文件名 + 大小 + mtime」快照, 断言完全一致。

环境

项 值
MounRiver 根 C:\MounRiver(可用 MOUNRIVER_ROOT 覆盖)
IDE OpenOCD 原件 C:\MounRiver\MounRiver_Studio2\resources\app\resources\win32\components\WCH\OpenOCD\OpenOCD
插件副本 <插件>\vendor\openocd(OpenOCD 0.11.0+dev-snapshot,version.txt = v2.12)
工具链 …\components\WCH\Toolchain\:riscv-wch-elf-(GCC 12.2)/ riscv32-wch-elf-(GCC 15.2)/ riscv-none-embed-(GCC 8.2)/ arm-none-eabi-(GCC 9.3.1)
WCH-LinkUtility …\components\WCH\Others\SWDTool\default\WCH-LinkUtility.exe(固件升级 / 手动下载,本插件不代跑)
默认目标 ch32v307(CH32V307EVT:FLASH 256K @ 0x00000000、RAM 64K @ 0x20000000、entry _start)
实测 WCH-LinkE mode:RV fw 2.18、6000 kHz、datacount=2 progbufsize=8、XLEN=32 misa=0x40901125、flash device id 0xf53a8220

容量口径:flash probe 报的 288 kB 是整片物理 flash(含厂商区),用户可用区按链接脚本 是 256 kB。地址计算以链接脚本 / ELF 为准。


真机验证记录

CH32V307EVT + WCH-LinkE(RV 模式)上跑通的完整闭环:

wch_env → wch_devices → wch_probe → wch_flash_info → wch_regs → wch_mem
        → wch_read_flash(262144 B) → wch_flash 回写同一份 → verify OK → reset run
        → session 内 bp(IVA 硬件断点;内存逐字节比对未变)→ close(rbp all → shutdown)
        → wch_gdb(info registers / x/4xw) → wch_clean audit → 无残留进程、端口全空
        → IDE bin 树 25 个文件名字/大小/mtime 全部未变

许可

MIT —— 见 LICENSE。覆盖本插件自己的代码与文档。

例外:vendor/openocd/ 与 references/raw/ 是第三方产物 (WCH 定制的 OpenOCD 0.11.0+dev-snapshot,与两份从 WCH-Link 手册 PDF 抽出的文本), 不适用 MIT —— 详见 THIRD-PARTY.md。 OpenOCD 遵循其上游 GNU GPL v2(vendor/openocd/distro-info/licenses/)。 打包它的唯一理由是设计上的那条硬要求:插件必须带自己的隔离副本,绝不碰 IDE 那棵树。

仓库里刻意没有的东西

.gitignore 排除的不是顺手清理,是两条有理由的:

  • backups/:从用户板子里读出来的整片固件镜像(CH32V307 一份 256 KiB)。 既是本地状态,也是能直接还原别人板子的二进制。
  • vendor/openocd/bin/ 里的 ocd.cfg / ocd_try.err / ocd_try.out: WCH 出厂就带在 IDE bin\ 里的污染文件,ocd.cfg 与 wch32f103.cfg 逐字节相同。 它正是「所有烧录被静默顶成 ARM/SWD」那条事故链的物证 —— 这个插件存在的头号理由就是防它,公开发布时不该把这份毒一起发出去。** 本地保留(保持副本与 IDE 树一致,便于复现原事故),repo 里没有。 因此新克隆的副本比 IDE 树少 3 个文件,wch_workspace(action:"audit") 会报得更干净 —— 这是预期的**,不是副本缺文件。
—/ 5

暂无评分

已验证 DSH bundle

Commit 0c28a97865ee

社区评论

还没有评论,来写第一条。

DSH HUB

社区维护的 DSH 插件索引。不是 GitHub 或 DeepSeek AI 的官方产品。

社区资源API关于