Skip to content

ohosvscode/arkts-dap

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

24 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

arkts-dap

ArkTS Debug Adapter —— DAP↔CDP 桥接,让任意 DAP 客户端(VSCode、nvim-dap 等) 断点调试 ArkTS 应用。

背景

ArkTS 应用跑在 Ark JS 运行时上,运行时内置一个 CDP(Chrome DevTools Protocol 风格) 调试器, 以 WebSocket 暴露(previewer/应用以 -d -p <port> 启动时即在 127.0.0.1:<port> 开 raw CDP 服务)。 但编辑器/IDE 说的是 DAP(Debug Adapter Protocol)。本工具在两者之间做翻译, 即业界成熟的 DAP↔CDP 桥接模式(同 vscode-js-debug)。

它也补齐了 previewer-frontend 的缺口:rich Inspector 的实时组件树 (inspector 命令)需要有调试器 attach 到运行时才能产出。

VSCode 用户:装 vscode-extension/(ArkTS Debug),在编辑器里按 F5 即可, 无需命令行 —— 它把 launch.json 配置映射成本工具的参数并经 stdio 启动。

DAP 客户端 (VSCode...)  ──DAP(stdio/TCP)──►  arkts-dap  ──CDP(WebSocket)──►  Ark 运行时调试器
                                            (翻译器)                       (previewer -d -p <port>)

用法

cargo build --release

# 1) previewer / 本地 CDP 端口(DAP over stdio)
arkts-dap --cdp-port 29900

# 2) 真机 attach(hdc 全自动:aa attach + ConnectServer 发现 + fport → CDP)
arkts-dap --device com.huawei.hmos.sample          # 一键 attach 真机已运行 app(主线程+worker 自动发现)
arkts-dap --device <bundle> --serial <设备序列号>   # 多设备时指定
arkts-dap --device <bundle> --no-attach            # app 已在调试模式则跳过 aa attach

# 3) 真机 launch(适配器用 aa start -D 拉起 app 进调试,免手动先起)
arkts-dap --device <bundle> --launch --ability EntryAbility --module phone
#    可选 --hap:先装包再拉起(同 DevEco:force-stop + bm install + aa start -D)
arkts-dap --device <bundle> --launch --ability EntryAbility --module phone \
          --hap .../outputs/default/phone-default-signed.hap

# 4) DAP over TCP(自测)
arkts-dap --tcp 4711 --cdp-port 29900 --log

attach 目标:previewer 端口(-d -p)或真机(--device)。--cdp-port 为默认; 也可由 DAP attach 请求的 cdpPort 覆盖。

真机调试链路(已对真实设备验证 ✅)

--device <bundle> 自动完成:

  1. hdc list targets 选设备
  2. hdc shell aa attach -b <bundle> 让 app 进调试模式
  3. hdc shell pidof <bundle> 取 pid
  4. hdc fport tcp:<本地空闲端口> ark:<pid>@Debugger 转发设备调试 socket
  5. ws://127.0.0.1:<port>/ raw CDP(与 previewer 同协议)
  6. 退出(disconnect / 断开 / SIGINT/SIGTERM)时 hdc fport rm <local> <target>两参,单参会 "ruler not exist" 清不掉)清理

健壮性:CDP 连接断开(设备拔出 / app 崩溃)→ 主线程断发 DAP terminated 并结束会话、worker 断发 thread exited; in-flight CDP 调用在断开时立即失败(不等 15s 超时);信号退出经显式 cleanup()process::exit 不跑 Drop)。

设备准备延迟到 launch/attach 请求initialize 秒回(实测 0.17s),force-stop/装包/aa start -D/fport 放在 launch 请求阶段(spawn_blocking),VSCode 期间显示"正在启动",不会因准备耗时误判适配器无响应。

Ark 调试 socket 约定(来自 hdc fport ls,真机确认): ark:<pid>@<bundle>(ConnectServer,多实例发现)、ark:<pid>@Debugger(主线程 raw CDP,默认)、 ark:<pid>@<tid>@Debugger(worker 实例)。hdc 在 ~/Library/OpenHarmony/Sdk/*/toolchains/hdc 或 DevEco SDK 下,可用 --hdc <path> / HDC_PATH 指定。

实测(com.huawei.hmos.sample 真机):attach 后 Debugger.enable 回放 578 个真实脚本pause 暂停活动 app → 真实调用栈(TrackManager.ts:112)+ 真实局部变量(_event/node)+ continue。

前提:app 须为 debuggable(debug 签名)构建。未执行到的脚本断点为 pending(verified:false), 待该脚本运行时经 Debugger.breakpointResolved 自动 verify(标准行为)。

真机 launch 模式(--launch,已实测 ✅)

--launch --ability <Ability> [--module <m>] [--hap <signed.hap>]:适配器自己把 app 拉起进调试模式-D=启动即阻塞等调试器),无需手动先起 app。流程(同 DevEco 的 install→launch):

  1. aa force-stop <bundle>(干净重启,确保 -D 可靠阻塞在入口)
  2. --hap按需装包bm dump -n 判已装 + 比对 hap 指纹(size+mtime,不读内容)与本地缓存 ~/.cache/arkts-dap/install-<bundle>-<serial>.txt —— 已装且 hap 未变则跳过 bm install(省去每次传 300MB+); 否则 mkdir tmpfile sendbm install -prm tmp 并刷新缓存。设备上未装(卸载/刷机)必装。 强制重装:touch <hap> 或删该缓存文件。
  3. aa start -b <bundle> -a <Ability> [-m <m>] -D 拉起 → app 阻塞在入口
  4. 连 ConnectServer,等主线程实例连上(关键:否则 configurationDone 先于实例连接、漏发 runIfWaitingForDebugger,app 永久阻塞)
  5. configurationDone 广播 runIfWaitingForDebugger 放行(launch 下 attach_mode=false
  6. stopOnEntry 则停在第一条指令(实测停在 CommonEnums.ets:1),否则自动跨过启动期的 break-on-start

最早期生命周期断点(onCreate 等)已稳定命中 ✅(实测 6/6)。关键机制:

  • Debugger.enableoptions:["enableLaunchAccelerate"] → 开启 LaunchAccelerate:启用 saveAllPossibleBreakpointspending 断点池,脚本加载时自动解析命中)+ 关闭逐模块 break-on-start 风暴。
  • setBreakpoints 双轨下发:已加载脚本用 getPossibleAndSetBreakpointByUrl(立即 verified), 全量 saveAllPossibleBreakpoints(覆盖未加载脚本的早期断点)。
  • launch 前 force-stop轮询 pidof 直到进程真正退出,确保 -D 可靠阻塞在入口(否则 onCreate 已执行必漏)。

Ark 断点限制:只能给已加载脚本即时下断点(setBreakpointByUrl 对未知 url 报 "Unknown file name"、 getPossibleAndSetBreakpointByUrl 对未加载返回 id:"invalid");saveAllPossibleBreakpoints 是其 pending 池(仅 LaunchAccelerate 下可用,全量覆盖语义,对已加载脚本不即时解析)。

VSCode launch.json(attach)示例:

{ "type": "arkts", "request": "attach", "name": "Attach ArkTS", "cdpPort": 29900 }

支持的 DAP 能力

attach(previewer / 真机)· 断点(普通/条件,智能吸附 getPossibleAndSetBreakpointByUrl)· logpointlogMessage{expr} 插值,命中输出不中断)· hitCondition 断点>n/>=n/==n/<n/%n/裸数字,未满足自动放行)· pending 断点 + breakpointResolved→DAP breakpoint 事件 · 异常断点(all/uncaught)+ exceptionInfo · worker 多实例(ConnectServer 自动发现主线程+worker,多线程,断点广播,按 threadId 路由栈/变量/单步)· 暂停/继续 · 单步(over/into/out,stepIn 用 smartStepInto 跳过库/getter 帧)· restartFramedropFrame 回退顶帧重执行)· 调用栈(分页 startFrame/levels)· 作用域/变量(对象展开)· setVariable · evaluate/watch(clipboard 经 callFunctionOn 取完整值)· CPU Profiler.cpuprofile)/ Heap Snapshot.heapsnapshot)· console 输出转发Runtime.consoleAPICalled/exceptionThrown→DAP output)· sourcemap 行/列映射(编译 .ts 坐标 ↔ 源码 .ets,双向,--source-root 自动探索)· 源文件定位(记录名 → 磁盘 .ets 路径)· 源码获取 · stopOnEntry · attach-mode(不发 runIfWaitingForDebugger)。

worker 多实例(真机,ConnectServer 自动发现)✅

ArkTS 主线程与每个 Worker/TaskPool 子线程各跑独立 VM、各有独立 CDP 调试器。真机 --device 下, 适配器连 ConnectServerark:<pid>@<bundle>,发 {"type":"connected"})枚举实例:

  • 主线程 addInstance{instanceId:0, tid:pid}ark:<pid>@Debugger → DAP thread 1
  • worker addInstance{instanceId, tid, name:"workerThread_<tid>"}ark:<pid>@<tid>@Debugger → DAP thread 2/3/…
  • destroyInstance → 关连接、DAP thread exited;运行中新建的 worker 动态 thread started 并补设已有断点。

每个实例一条 CDP 连接;断点 setBreakpoints 广播到所有实例(各自 resolve、各存 cdp_id); stackTrace/scopes/variables/evaluate/单步/continue/pausethreadId 路由到对应实例。 (实测 com.huawei.hmos.sample:主线程 + workerThread_* 并列为多 DAP 线程,暂停 worker 得其独立调用栈 func_main_0 @ Logger.ets:16,断点广播到主线程与 worker 两条连接。)

源码映射(--source-root,真机必备)——两件事,分两个模块

真机/打包应用的运行时调试信息用 编译后 .ts 坐标,脚本 url 是归一化记录名 (如 phone|@ohos/common|1.0.0|src/main/ets/trackmanager/TrackManager.ts),且设备不支持 getScriptSource。 要把它对回编辑器里的 .ets,需要分别解决「哪个文件」和「哪一行」——这是两件不同的事:

  1. 源文件定位source.rs):记录名 → 磁盘 .ets 路径。扫描工程建「模块名/包名→目录」索引。

  2. 行/列映射sourcemap.rs):编译 .ts 行/列 ↔ 源码 .ets 行/列。必须用 sourcemap—— 运行时报告的是编译行,与 .ets 相差一个可变偏移(实测 TrackManager:运行时 185 行 ↔ 源码 212 行,差 27 行; 各处偏移不同)。读工程的 **/intermediates/source_map/default/sourceMaps.map(标准 Source Map v3), VLQ 解码后双向用:

    • 调用栈/断点回显:gen→.ets(显示正确源码行)
    • 下断点:.ets→gen(把用户打的 .ets 行换成编译行再发给运行时)

    多 product(phone/pc/tv)共享同一 .ets 源,下断点时按真机当前 product(scriptParsed 见过的 url 前缀)择优。 传 --source-root <工程根> 自动探索并加载 sourceMaps.map;也可 --source-map <文件> 显式指定。

无对应 sourcemap 条目的模块(previewer 本地编译:url 本身即 .ets 绝对路径、行号已是 .ets 基准)→ 自动回退恒等(路径解析 + 行号原样),previewer 流程不受影响。

CPU / Heap Profiler(自定义 DAP 请求,已实测 ✅)

Ark 支持 Profiler.*(CPU 采样)与 HeapProfiler.*(堆快照),适配器经自定义 DAP 请求暴露 (VSCode 扩展用命令触发):

customRequest 动作 产物
startCpuProfile {threadId?,interval?} Profiler.enable+setSamplingInterval+start —(开始采样)
stopCpuProfile {threadId?,path?} Profiler.stop → 写文件 .cpuprofile(VSCode 内置火焰图查看器)
takeHeapSnapshot {threadId?,path?} HeapProfiler.takeHeapSnapshot(收 addHeapSnapshotChunk 流)→ 写文件 .heapsnapshot(Chrome DevTools → Memory 加载)

path 缺省写到临时目录。实测真机 com.huawei.hmos.sample:CPU profile nodes=216 samples=11680(143 KB), 堆快照 30 MB(合法 Chrome 格式 {"snapshot":{"meta":…)。

结构

src/
├── main.rs          入口:clap;DAP over stdio 或 --tcp;--device 真机模式;请求循环
├── dap/transport.rs Content-Length 帧编解码(同 LSP)
├── cdp/client.rs    CDP WS 客户端:call(method,params) 关联应答;事件 channel
├── device.rs        真机编排:hdc list/attach/pidof/fport + ConnectServer 转发 + Forwarder(动态 worker fport)+ Drop 清理
├── source.rs        源文件定位:记录名 → 磁盘 .ets 路径(模块名/包名索引)
├── sourcemap.rs     行/列映射:sourceMaps.map(VLQ) 双向 编译.ts坐标 ↔ 源码.ets
└── session.rs       DebugSession:DAP↔CDP 翻译;实例表(instances)/帧表/变量引用(均编码 threadId)/断点(cdp_ids 多实例)

多实例模型:Instance{thread_id, cdp, frame_ids…};全局 frames(frameId→Frame,编码 threadId)与 var_refs(vref→(threadId,objectId))使 scopes/variables/evaluate(只带 frameId/vref)也能路由到正确实例。 主线程固定 thread 1,worker 自增(≥2,不复用)。

协议要点(逆向自 arkcompiler/toolchain,已源码确认)

📖 完整协议参考(含形式化描述:状态机 / 消息文法 / 不变量 / 时序图)见 docs/ark-cdp-protocol.md。下面是要点摘录。

  • 连接 ws://127.0.0.1:<port>/(任意 path,无 /json 发现端点),raw CDP,每实例一条连接
  • ConnectServerark:<pid>@<bundle>):连后发 {"type":"connected"},收 addInstance{instanceId,tid,name}/ destroyInstance{instanceId}。worker 的 instanceId==tid,socket = ark:<pid>@<tid>@Debugger(主线程 ark:<pid>@Debugger)。
  • debug 模式运行时启动即阻塞等调试器,收到 Runtime.runIfWaitingForDebugger 才继续。 ⇒ 适配器在 configurationDone(断点已设好)时才发该命令,确保命中早期断点。
  • 握手:Runtime.enableDebugger.enable(返回 {debuggerId,protocols},并回放已加载脚本 scriptParsed)。
  • 行列号 0-based(CDP)↔ DAP 1-based,适配器做 ±1。
  • scriptParsed.url:本地编译/previewer = 源码绝对路径(.ets),行号即 .ets 行; 真机/打包 = 归一化记录名(...|...ts),行号是编译后 .ts,须经 sourceMaps.map 映射回 .ets(见上)。
  • id 类(scriptId/callFrameId/objectId)一律按 serde_json::Value 透传,不假设字符串/整数。

验证

集成测试(DAP↔CDP 全链路,主回归)

cargo test --test e2e

tests/e2e.rs 起一个 mock CDP 服务器(按 Ark CDP 线格式回放 scriptParsed/paused/getProperties...), 驱动真实 arkts-dap 二进制走完整 DAP 流程,断言:断点 verified、stopped 事件、调用栈行号 ±1 正确、 作用域/变量(count=42 + 可展开对象)、evaluate、continue。这是协议翻译器正确性的权威验证

真机断点(已实测命中)✅

对真实 rich previewer(Stage 应用)attach,完整断点调试已跑通:

  • 断点 verified:true、命中 EntryAbility.ts:16reason:"breakpoint",行号 ±1 正确)
  • 真实调用栈(顶帧 onWindowStageCreate)、真实 local 变量(onWindowStageCreate/windowStage)、 evaluate(this)EntryAbility@74516c2e

已调通的 debug 启动配置(关键、非显然,见 scripts/run-debug-target.sh):

参数 说明
-j <intermediates>/loader_out/default/ets modules.abc 所在目录
-arp <intermediates>/res/default resources.index + module.json
-ljPath <intermediates>/loader/default/loader.json 旁加载 pkgContextInfo.json → ohmurl 解析
-abp @normalized:N&&&<module>/src/main/ets/entryability/<Ability>& 归一化 ohmurl 入口(注意含 src/main
其它 -pm Stage -d -p <port> -abn <Ability> -device phone

踩坑要点:-abp 必须是 归一化 ohmurl@normalized:N&&&...&),而非 @bundle: 或裸路径—— abc 记录名为 <module>/src/main/ets/...(preview 构建保留 src/main);@bundle: 不会被剥离 bundleName 而失败。 -ljPath 必需,否则 pkgContextInfo.json 不加载、ohmurl 无法解析(Cannot find module ... Entry Point)。

启动后运行时阻塞等调试器;adapter 在 configurationDonerunIfWaitingForDebugger。 首停为 break-on-startreason:"entry"),脚本随后 scriptParsed、断点 verifiedcontinue 即命中源码断点。

# 1) 起调试目标(运行时阻塞等调试器)
scripts/run-debug-target.sh ~/Library/OpenHarmony/Sdk/23/previewer \
  ~/DevEcoStudioProjects/MyApplication2/entry/build/default/intermediates \
  com.example.myapplication entry EntryAbility 29900
# 2) attach
arkts-dap --cdp-port 29900   # 或 --tcp 4711 自测

状态

MVP 完成(attach 核心调试),previewer 断点已实测命中(rich/Stage); 真机调试已打通(hdc 全自动 attach + fport + CDP;578 真实脚本 + 暂停真实栈/变量,实测)。

sourcemap(.ets↔.ts 行/列精确映射,VLQ 双向,多 product 择优)已实测打通 ✅。 worker 多实例(ConnectServer 发现 + 每实例独立连接 + 断点广播 + threadId 路由)已实测打通 ✅。 launch 模式(aa start -D 自动拉起 + 等主实例 + stopOnEntry + break-on-start 处理)已实测打通 ✅。 设备扩展方法 smartStepInto(stepIn)/dropFrame(restartFrame)/callFunctionOn(clipboard 完整值)已接入并实测 ✅。 VSCode 扩展(vscode-extension/,F5 即调;含 CPU/Heap Profiler 命令)已封装 ✅。 CPU/Heap Profiler(Profiler.*/HeapProfiler.*.cpuprofile/.heapsnapshot已接入并实测 ✅。 健壮性:CDP 断开检测(主线程断→DAP terminated+结束会话,worker 断→thread exited)、 SIGINT/SIGTERM 优雅退出清 fport、in-flight 调用断开即失败(不卡 15s)、hdc fport rm 正确清理(两参形式)已加固 ✅。 最早期生命周期断点(onCreate 等)经 LaunchAccelerate + saveAllPossibleBreakpoints + 等进程退出再 launch 已稳定(6/6) ✅。 Backlog(真机):热重载、CDP 自动重连、CPU/Heap profiler 火焰图内联展示。

已与 previewer-frontend 集成previewer-frontend/scripts/preview-and-debug.sh 一键启动同一个 Previewer,浏览器实时预览 + 本工具断点调试共存(命令/图像通道 vs CDP 通道互不干扰); debug 模式下 previewer-frontend 的 Inspector 可显示实时组件树(attach 后应用运行态可用)。

Backlog:break-on-start 自动跨过/stopOnEntry 选项、breakpointResolved→DAP breakpoint 事件(pending 断点 verified 更新)、 launch 模式(自 spawn previewer -d -p 再 attach)、CPU/Heap Profiler、热重载、多实例(worker/sessionId)、 设备 attach(hdc 端口转发)。

许可

拟 Apache-2.0,与 OpenHarmony 主仓一致。

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors