DeepSeek Harness Plugin Hub

Publish and manage complete Harness Profiles. Discover Plugins for your next setup.

Explore

PluginsPresetsDocsNews

Community

Publish a pluginContactReport an issue

Resources

Plugin Hub on GitHubDeepSeek HarnessSystem statusPrivacy notice
© 2026 DeepSeek Harness Plugin HubPowered byPaxTech

Independent and unofficial. Not affiliated with, authorized by, or endorsed by DeepSeek.

Wch Debug — DSH Plugin for DeepSeek Harness
← Plugins
W

@local/dsh-wch-debug

Wch Debug

DSH plugin: Debug WCH microcontrollers (CH32 RISC-V / ARM) with OpenOCD + CLI. Includes an isolated copy of WCH-customized OpenOCD, covering environment probing, device libraries, private workspace sessions, real-device flashing and verification, breakpoint/step/register/memory debugging, and struct

The plugin will be installed here. Keep web if you are unsure.

npx -y @deepseek-ai/dsh plugin --profile web add github:lishuo4096/dsh-wch-debug#0c28a97865eee9a762add0e10270566ee7d592a4
READMECompatibilityVersions

Description

DSH plugin: Debug WCH microcontrollers (CH32 RISC-V / ARM) with OpenOCD + CLI. Includes an isolated copy of WCH-customized OpenOCD, covering environment probing, device libraries, private workspace sessions, real-device flashing and verification, breakpoint/step/register/memory debugging, and structural safeguards against issues such as workspace contamination causing the program to become inaccessible after flashing. Pure JS with zero dependencies.

Compatibility and provenance

Wch Debug is published as @local/dsh-wch-debug and currently resolves to version 0.1.0. The Hub verifies its manifest and preserves the exact installation source for reproducible installs.

DSH compatibility
*
Runtime surfaces
any
Release source
github
Registry updated
10/15/2026

Versions

0.1.0stable
10/15/2026

Related plugins

Loading related plugins…

Latest
0.1.0
DSH
*
HMR
Process restart
Tree shaking
Safe tree shaking not declared
Unpacked size
Unavailable
Files
Unavailable
Surface
any
License
MIT
Source
github
GitHub
★ 0
Weekly downloads
0
Last push
10/15/2026
View source ↗
README badge

Click the badge to copy Markdown for your README.

Do you maintain this Plugin?Claim benefit · Priority security scan

Verify the GitHub repository declared in package.json to manage this listing. After you claim it, Hub will prioritize a security scan of the current version and publish the result when it passes.

Claim this Plugin →
Report an issue
DeepSeek Harness Plugin Hub
ProfilesPluginsCategoriesNewsDocsSign inManage Profiles
ProfilesPluginsCategoriesNewsDocsSign in

Related plugins

More verified plugins in developer-tools.

Web App@deepseek-ai/dsh-web-appThe dsh browser-surface bundle: the web patch layer over dsh-base plus the runtime glue plugin (frontend dist serving, web-surface prompt, bash runtime variables, URL line)Sdk Minimal@deepseek-ai/dsh-sdk-minimalThe standalone minimal SDK profile bundle: JSON-RPC, one DeepSeek adapter, persistent shell, and JSONL sessionsSdk App@deepseek-ai/dsh-sdk-appThe dsh SDK profile bundle: stdio JSON-RPC serving and process lifecycle over dsh-baseExperimental Inspector@deepseek-ai/dsh-experimental-inspectorExperimental cross-realm CDP hub for Host debugging and Client Runtime inspection

README

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_devicesCH32 型号表(20 个,key 如 ch32v307):core / flash / RAM / 该用哪个 cfg / 需要 WCH-Link 处于哪种模式 / 证据来源(local = 本机实测,public = 公开资料)。不传参数列全部
wch_projects扫 C:\MounRiver\Project 下的 Link.ld,列出各工程的 FLASH / RAM / entry / 推断型号
wch_workspaceaction: 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_infoflash 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_resetmode: 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_cleanaction: 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 在本驱动上(), 裸着写就会把后面的命令连 一起带走。 —— 套上之后链是保住了,扇区表却( 变 ), 于是 静默丢掉几何。这个 bug 静态看不出来,只有真机跑才露馅。 A–E 五组对照实验(,每组尾部挂 当探针):

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

写法为什么不用
foreach a [bp] { rbp $a }(清断点)实测是空操作:bp 无参返回的是给人看的字符串,rbp 拿到非法 addr 报错、被 catch 吞掉,断点一个都没清。改用 rbp all(本构建 help rbp 原文支持,实测一次清空)
bp <addr>(只给地址)实测报用法行、不设断点。必须带长度:bp <addr> 4;要硬件断点再加 hw
rwp allhelp rwp 只给了 rwp address,没有 all。改为从 [wp] 里用 {0x[0-9a-fA-F]{8}} 抠地址逐个删
flash erase_sector 0 0 lastlast 未证实。改用扇区号自己算(注意扇区是 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 KiB16 KiB32 KiB边际
flash read_bank2011 B/s—3065 B/s326 µs/B
dump_image1676 B/s2620 B/s2718 B/s335 µ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") 会报得更干净 —— 这是预期的,不是副本缺文件。
打完扇区表就会报错
Flash protection check is not implemented
shutdown
但「套上 catch」不是解法
一个字节都不打
parsed.flash.sectors
undefined
wch_flash_info
scripts/chain-ab.mjs
reg pc
组命令exit尾随 pcshutdown扇区表行数
A裸 flash protect_check 00✅✅—
B裸 flash info 01❌❌288
Ccatch 包住两条0✅✅0
D裸 protect_check + catch {flash info 0}0✅✅0
Ecatch {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 能证明探针活着。

  • 判据也不能只看「文本里有没有 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,并附一条注说明它为什么无害。 白名单必须逐条实测过才准进 —— 往里塞一个真错误,等于把判据阉了。