JSVM-API调试&定位
=============

JSVM，即标准JS引擎，是严格遵守ECMAScript规范的JavaScript代码执行引擎。详情参考：[JSVM](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/capi-jsvm)。

基于JSVM的JS代码调试调优能力包括：Debugger、CPU Profiler、Heap Snapshot、Heap Statistics。涉及以下接口：

| 接口名 | 接口功能 |
| :-- | :-- |
| OH\_JSVM\_GetVM | 获取给定环境的虚拟机实例。 |
| OH\_JSVM\_GetHeapStatistics | 返回一组虚拟机堆的统计数据。 |
| OH\_JSVM\_StartCpuProfiler | 创建并启动一个CPU profiler。 |
| OH\_JSVM\_StopCpuProfiler | 停止CPU profiler并将结果输出到流。 |
| OH\_JSVM\_TakeHeapSnapshot | 获取当前堆快照并将其输出到流。 |
| OH\_JSVM\_OpenInspector | 在指定的主机和端口上激活inspector，将用来调试JS代码。 |
| OH\_JSVM\_OpenInspectorWithName | 基于传入的 pid 和 name 激活 inspector。 |
| OH\_JSVM\_CloseInspector | 尝试关闭剩余的所有inspector连接。 |
| OH\_JSVM\_WaitForDebugger | 等待主机与inspector建立socket连接，连接建立后程序将继续运行。执行Runtime.runIfWaitingForDebugger命令。 |

本文将介绍调试方法、CPU Profiler使用方法和Heap Snapshot使用方法。

#### 调试能力使用方法

#### \[h2\]使用 OH\_JSVM\_OpenInspector

1.  在应用工程配置文件module.json中配置网络权限：
    
    "requestPermissions": \[{
      "name": "ohos.permission.INTERNET",
      "reason": "$string:app\_name",
      "usedScene": {
        "abilities": \[
          "FromAbility"
        \],
        "when": "inuse"
      }
    }\]
    
2.  为避免debugger过程中的暂停被误报为无响应异常，可以开启DevEco Studio的Debug模式，参考[debug启动调试](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/ide-debug-arkts-debug-V5)（无需设置断点），或者可以在非主线程的其它线程中运行JSVM。
    
    // 在非主线程的其他线程中运行JSVM示例代码
    static napi\_value RunTest(napi\_env env, napi\_callback\_info info)
    {
        std::thread testJSVMThread(TestJSVM);
        testJSVMThread.detach();
        return  nullptr;
    }
    
3.  在执行JS代码之前，调用OH\_JSVM\_OpenInspector在指定的主机和端口上激活inspector，创建socket。例如OH\_JSVM\_OpenInspector(env, "localhost", 9225)，在端侧本机端口9225创建socket。
    
4.  调用OH\_JSVM\_WaitForDebugger，等待建立socket连接。
    
5.  检查端侧端口是否打开成功。hdc shell "netstat -anp | grep 9225"。结果为9225端口状态为“LISTEN"即可。
    
6.  转发端口。hdc fport tcp:9229 tcp:9225。转发开发者个人计算机侧端口9229到端侧端口9225。结果为"Forwardport result:OK"即可。
    
7.  在chrome浏览器地址栏输入"localhost:9229/json"，回车。获取端口连接信息。拷贝"devtoolsFrontendUrl"字段url内容到地址栏，回车，进入DevTools源码页，将看到在应用中通过OH\_JSVM\_RunScript执行的JS源码，此时暂停在第一行JS源码处。(注："devtoolsFrontendUrl"字段url只支持使用Chrome、Edge浏览器打开，不支持使用Firefox、Safari等浏览器打开。)
    
8.  用户可在源码页打断点，通过按钮发出各种调试命令控制JS代码执行，并查看变量。
    
9.  调用OH\_JSVM\_CloseInspector关闭inspector，结束socket连接。
    

**示例代码**

JSVM-API接口开发流程参考[使用JSVM-API实现JS与C/C++语言交互开发流程](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/use-jsvm-process)，本文仅对接口对应C++相关代码进行展示。

#include "ark\_runtime/jsvm.h"

#include <string>

using namespace std;

// 待调试的JS源码
static string srcDebugger = R"JS(
const concat = (...args) => args.reduce((a, b) => a + b);
var dialogue = concat('"What ', 'is ', 'your ', 'name ', '?"');
dialogue = concat(dialogue, ' --', '"My ', 'name ', 'is ', 'Bob ', '."');
)JS";

// 开启debugger
static void EnableInspector(JSVM\_Env env) {
    // 在指定的主机和端口上激活inspector，创建socket。
    OH\_JSVM\_OpenInspector(env, "localhost", 9225);
    // 等待建立socket连接。
    OH\_JSVM\_WaitForDebugger(env, true);
}

// 关闭debugger
static void CloseInspector(JSVM\_Env env) {
    // 关闭inspector，结束socket连接。
    OH\_JSVM\_CloseInspector(env);
}

static void RunScript(JSVM\_Env env) {
    JSVM\_HandleScope handleScope;
    OH\_JSVM\_OpenHandleScope(env, &handleScope);

    JSVM\_Value jsSrc;
    OH\_JSVM\_CreateStringUtf8(env, srcDebugger.c\_str(), srcDebugger.size(), &jsSrc);

    JSVM\_Script script;
    OH\_JSVM\_CompileScript(env, jsSrc, nullptr, 0, true, nullptr, &script);

    JSVM\_Value result;
    OH\_JSVM\_RunScript(env, script, &result);

    OH\_JSVM\_CloseHandleScope(env, handleScope);
}

void TestJSVM() {
    JSVM\_InitOptions initOptions{};
    OH\_JSVM\_Init(&initOptions);

    JSVM\_VM vm;
    OH\_JSVM\_CreateVM(nullptr, &vm);
    JSVM\_VMScope vmScope;
    OH\_JSVM\_OpenVMScope(vm, &vmScope);

    JSVM\_Env env;
    OH\_JSVM\_CreateEnv(vm, 0, nullptr, &env);
    // 执行JS代码之前打开debugger。
    EnableInspector(env);
    JSVM\_EnvScope envScope;
    OH\_JSVM\_OpenEnvScope(env, &envScope);

    // 执行JS代码。
    RunScript(env);

    OH\_JSVM\_CloseEnvScope(env, envScope);
    // 执行JS代码之后关闭debugger。
    CloseInspector(env);
    OH\_JSVM\_DestroyEnv(env);
    OH\_JSVM\_CloseVMScope(vm, vmScope);
    OH\_JSVM\_DestroyVM(vm);
}

#### \[h2\]使用 OH\_JSVM\_OpenInspectorWithName

1.  在应用工程配置文件module.json中配置网络权限：
    
    "requestPermissions": \[{
      "name": "ohos.permission.INTERNET",
      "reason": "$string:app\_name",
      "usedScene": {
        "abilities": \[
          "FromAbility"
        \],
        "when": "inuse"
      }
    }\]
    
2.  为避免debugger过程中的暂停被误报为无响应异常，可以[开启DevEco Studio的Debug模式](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/ide-debug-arkts-debug-V5)（无需设置断点），或者可以在非主线程的其他线程中运行JSVM。
    
3.  打开 inspector 端口，连接 devtools 用于调试，其流程如下：在执行JS代码之前，调用OH\_JSVM\_OpenInspector在指定的主机和端口上激活inspector，创建socket。例如OH\_JSVM\_OpenInspectorWithName(env, 123, "test")，创建 tcp socket 及其对应的 unixdomain 端口。
    
4.  调用OH\_JSVM\_WaitForDebugger，等待建立socket连接。
    
5.  检查端侧端口是否打开成功。hdc shell "cat /proc/net/unix | grep jsvm"。结果出现可用的 unix 端口即可，如：jsvm\_devtools\_remote\_9229\_123，其中 9229 为 tcp 端口号，123 为对应的 pid。
    
6.  转发端口。hdc fport tcp:9229 tcp:9229。转发开发者个人计算机侧端口9229到端侧端口9229。结果为"Forwardport result:OK"即可。
    
7.  在 chrome 浏览器地址栏输入 "localhost:9229/json"，回车。获取端口连接信息。打开Chrome开发者工具，拷贝"devtoolsFrontendUrl"字段url内容到地址栏，回车，进入DevTools源码页，将看到在应用中通过OH\_JSVM\_RunScript执行的JS源码，此时暂停在第一行JS源码处。(注："devtoolsFrontendUrl"字段url只支持使用Chrome、Edge浏览器打开，不支持使用Firefox、Safari等浏览器打开。)
    
8.  用户可在源码页打断点，通过按钮发出各种调试命令控制JS代码执行，并查看变量。
    
9.  调用OH\_JSVM\_CloseInspector关闭inspector，结束socket连接。
    

**代码示例**

对应的 enable inspector 替换为下面的即可

// 开启debugger
static void EnableInspector(JSVM\_Env env) {
    // 在指定的主机和端口上激活inspector，创建socket。
    OH\_JSVM\_OpenInspectorWithName(env, 123, "test");
    // 等待建立socket连接。
    OH\_JSVM\_WaitForDebugger(env, true);
}

#### \[h2\]使用 Chrome inspect 页面进行调试

除了使用上述打开"devtoolsFrontendUrl"字段url的方法调试代码之外，也可以直接通过Chrome浏览器的 chrome://inspect/#devices 页面进行调试。方法如下：

1.  Chrome浏览器中打开 chrome://inspect/#devices，勾选以下内容：
    
    ![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/fb/v3/A1H6o0aCQR20UDKchQtprA/zh-cn_image_0000002634010140.png?HW-CC-KV=V1&HW-CC-Date=20260709T132822Z&HW-CC-Expire=86400&HW-CC-Sign=6B83FB1C9B424B0CDA81E4AA44917F24E0E366931E7880FDD3E1DFE67961EAE6)
2.  执行端口转发命令：hdc fport \[开发者个人计算机侧端口号\] \[端侧端口号\]
    
    例如：hdc fport tcp:9227 tcp:9226
    
3.  点击Port forwarding按钮，左侧输入开发者个人计算机侧端口，右侧输入端侧端口号，点击done。如下图所示：
    
    ![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/92/v3/jYU4ql2rTouLTmTJ5lBESA/zh-cn_image_0000002664209289.png?HW-CC-KV=V1&HW-CC-Date=20260709T132822Z&HW-CC-Expire=86400&HW-CC-Sign=DEF0125857E8D23C514A301A04307091D3F6E01315B7B0124CCACD9F842C4753)
4.  点击Configure按钮，输入开发者个人计算机侧的端口号，如localhost:9227。如下图所示：
    
    ![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/36/v3/swjCMcdBS1WD9wXZAZDhZw/zh-cn_image_0000002664329349.png?HW-CC-KV=V1&HW-CC-Date=20260709T132822Z&HW-CC-Expire=86400&HW-CC-Sign=D7C41DAA29417DBC6EBF8385DB23C356CB141E85B4806BC00FA8FF3D96B5A8F5)
5.  稍等片刻，会在target下出现调试的内容，点击inspect即可调试。如下图所示：
    
    ![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/e9/v3/s8Q-9uZZTX2gxYRhgmWg1w/zh-cn_image_0000002633850238.png?HW-CC-KV=V1&HW-CC-Date=20260709T132822Z&HW-CC-Expire=86400&HW-CC-Sign=A9FA03A3EC59038DD02E1A0F259B66CC55CA9E2B6153D756F996FA6860B4D77C)

#### \[h2\]使用 websocket 端口进行调试

除了使用上述打开 "devtoolsFrontendUrl" 字段url的方法通过网页端 chrome devtools 调试代码之外，如果读者了解如何使用 CDP 协议代替网页端 devtools 功能，也可以通过连接 inspector 提供的 websocket 端口进行调试。

其中连接 websocket 的方法为，根据前面提供的网页端调试步骤，在做完端口映射之后（如映射到 9229 端口），在 chrome 浏览器地址栏输入 "localhost:9229/json"，回车，获取"webSocketDebuggerUrl" 字段所对应的 url，然后使用标准的 websocket 客户端连接这个 url 即可发送 CDP 调试协议进行调试。需要注意的是，当前版本 inspector 提供的websocket 端口仅支持接收 Text Frame, Ping Frame 和 Connection Close Frame，所有其他类型的帧都会被视为错误帧而导致 websocket 连接中断。

CDP 协议可以参考 chrome 的[官方文档](https://chromedevtools.github.io/devtools-protocol/)

#### CPU Profiler及Heap Snapshot使用方法

#### \[h2\]CPU Profiler接口使用方法

1.  在执行JS代码之前，调用OH\_JSVM\_StartCpuProfiler开始采样并返回JSVM\_CpuProfiler。
2.  在执行JS代码后，调用OH\_JSVM\_StopCpuProfiler，传入1中返回的JSVM\_CpuProfiler，传入输出流回调及输出流指针。数据将会写入指定的输出流中。
3.  输出数据为JSON字符串。可存入.cpuprofile文件中。该文件类型可导入Chrome浏览器-DevTools-JavaScript Profiler工具中解析成性能分析视图。

#### \[h2\]Heap Snapshot接口使用方法

1.  为分析某段JS代码的堆对象创建情况，可在执行JS代码前后，分别调用一次OH\_JSVM\_TakeHeapSnapshot。传入输出流回调及输出流指针。数据将会写入指定的输出流中。
2.  输出数据可存入.heapsnapshot文件中。该文件类型可导入Chrome浏览器-DevTools-Memory工具中解析成内存分析视图。

#### \[h2\]示例代码

JSVM-API接口开发流程参考[使用JSVM-API实现JS与C/C++语言交互开发流程](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/use-jsvm-process)，本文仅对接口对应C++相关代码进行展示。

#include "ark\_runtime/jsvm.h"

#include <fstream>
#include <iostream>

using namespace std;

// 待调优的JS代码。
static string srcProf = R"JS(
function sleep(delay) {
    var start = (new Date()).getTime();
    while ((new Date()).getTime() - start < delay) {
        continue;
    }
}

function work3() {
    sleep(300);
}

function work2() {
    work3();
    sleep(200);
}

function work1() {
    work2();
    sleep(100);
}

work1();
)JS";

// 数据输出流回调，用户自定义，处理返回的调优数据，此处以写入文件为例。
static bool OutputStream(const char \*data, int size, void \*streamData) {
    auto &os = \*reinterpret\_cast<ofstream \*>(streamData);
    if (data) {
        os.write(data, size);
    } else {
        os.close();
    }
    return true;
}

static JSVM\_CpuProfiler ProfilingBegin(JSVM\_VM vm) {
    // 文件输出流，保存调优数据，/data/storage/el2/base/files为沙箱路径。以包名为com.example.helloworld为例。
    // 实际文件会保存到/data/app/el2/100/base/com.example.helloworld/files/heap-snapshot-begin.heapsnapshot。
    ofstream heapSnapshot("/data/storage/el2/base/files/heap-snapshot-begin.heapsnapshot",
                          ios::out | ios::binary | ios::trunc);
    // 执行JS前获取一次Heap Snapshot数据。
    OH\_JSVM\_TakeHeapSnapshot(vm, OutputStream, &heapSnapshot);
    JSVM\_CpuProfiler cpuProfiler;
    // 开启CPU Profiler。
    OH\_JSVM\_StartCpuProfiler(vm, &cpuProfiler);
    return cpuProfiler;
}

// 关闭调优数据采集工具
static void ProfilingEnd(JSVM\_VM vm, JSVM\_CpuProfiler cpuProfiler) {
    // 文件输出流，保存调优数据，/data/storage/el2/base/files为沙箱路径。以包名为com.example.helloworld为例。
    // 实际文件会保存到/data/app/el2/100/base/com.example.helloworld/files/cpu-profile.cpuprofile。
    ofstream cpuProfile("/data/storage/el2/base/files/cpu-profile.cpuprofile",
                        ios::out | ios::binary | ios::trunc);
    // 关闭CPU Profiler，获取数据。
    OH\_JSVM\_StopCpuProfiler(vm, cpuProfiler, OutputStream, &cpuProfile);
    ofstream heapSnapshot("/data/storage/el2/base/files/heap-snapshot-end.heapsnapshot",
                              ios::out | ios::binary | ios::trunc);
    // 执行JS后再获取一次Heap Snapshot数据，与执行前数据作对比，以分析内存问题或者进行内存调优。
    OH\_JSVM\_TakeHeapSnapshot(vm, OutputStream, &heapSnapshot);
}

static JSVM\_Value RunScriptWithStatistics(JSVM\_Env env, JSVM\_CallbackInfo info) {
    JSVM\_VM vm;
    OH\_JSVM\_GetVM(env, &vm);

    // 开始调优。
    auto cpuProfiler = ProfilingBegin(vm);

    JSVM\_HandleScope handleScope;
    OH\_JSVM\_OpenHandleScope(env, &handleScope);

    JSVM\_Value jsSrc;
    OH\_JSVM\_CreateStringUtf8(env, srcProf.c\_str(), srcProf.size(), &jsSrc);

    JSVM\_Script script;
    OH\_JSVM\_CompileScript(env, jsSrc, nullptr, 0, true, nullptr, &script);

    JSVM\_Value result;
    // 执行JS代码。
    OH\_JSVM\_RunScript(env, script, &result);

    OH\_JSVM\_CloseHandleScope(env, handleScope);

    // 结束调优。
    ProfilingEnd(vm, cpuProfiler);
    return nullptr;
}
static JSVM\_CallbackStruct param\[\] = {
    {.data = nullptr, .callback = RunScriptWithStatistics},
};
static JSVM\_CallbackStruct \*method = param;
// runScriptWithStatistics方法别名，供JS调用
static JSVM\_PropertyDescriptor descriptor\[\] = {
    {"runScriptWithStatistics", nullptr, method++, nullptr, nullptr, nullptr, JSVM\_DEFAULT},
};

样例测试JS

const char \*srcCallNative = R"JS(runScriptWithStatistics();)JS";

预计的输出结果：

在对应鸿蒙设备内生成两个文件用于后续调优：
heap-snapshot-end.heapsnapshot,
cpu-profile.cpuprofile
文件功能见上文接口使用方法介绍