• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# 使用Node-API接口产生的异常日志/崩溃分析
2<!--Kit: NDK-->
3<!--Subsystem: arkcompiler-->
4<!--Owner: @xliu-huanwei; @shilei123; @huanghello-->
5<!--Designer: @shilei123-->
6<!--Tester: @kirl75; @zsw_zhushiwei-->
7<!--Adviser: @fang-jinxu-->
8
9以下维测手段多数依赖于ArkTS运行时的多线程检测能力,因此建议在调试前启用此功能。启用方法参考文档[分析CppCrash(进程崩溃)](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/ide-multi-thread-check)10
11若无特殊说明,本章节描述的维测手段会在启用ArkTS运行时多线程检测开关的情况下,立即中断进程。
12
13## 数据在使用时,与创建该数据时所使用的env不一致
14
15### 各问题场景的关键日志
16
17该维测手段主要包含以下两种场景:
18
191. 调用napi方法的入参napi_env与创建napi数据结构时所使用的napi_env不一致。
20
21   > **关键日志**
22   > param env not equal to its owner.
23   >
24
252. 调用napi方法的入参napi_env与创建napi数据结构时所使用的napi_env一致,但原始napi_env已释放。
26
27   > **关键日志**
28   >
29   > 1. 除线程安全函数相关方法外,关键日志如下:
30   >
31   >    owner env has been destroyed, owner id: &lt;owner id&gt; , current    env id: &lt;current id&gt;.
32   >
33   > 2. 线程安全函数相关方法,关键日志如下:
34   >
35   >    current tsfn was created by dead env, owner id: &lt;owner id&gt;, current env id: &lt;current id&gt;
36
37该维测手段当前的覆盖范围如下:
38
391. napi_get_reference_value
402. napi_delete_reference*
413. napi_queue_async_work
424. napi_queue_async_work_with_qos
435. napi_cancel_async_work
446. napi_call_threadsafe_function*
457. napi_release_threadsafe_function*
46
47> 具有\*标志的接口,仅能触发第二种场景的维测信息,不含有\*标志的接口,能触发以上两种场景的维测信息。
48
49### 案例及示例代码
50
51> **注意:**
52>
53> 下面的代码仅用于构造异常场景,触发异常分支的DFX日志。在充分理解其意图前,请勿将其应用到业务场景中。
54
55**基础工具类**
56
57定义一个工具类,便于在后续构造两种异常场景。
58
59```cpp
60#define CHECK(cond)                                                 \
61    do {                                                            \
62        if (cond) {                                                 \
63            OH_LOG_FATAL(LOG_APP, "Failed to check `" #cond "`");   \
64            std::abort();                                           \
65        }                                                           \
66    } while(0)
67#define CHECK_EQ(lhs, rhs) CHECK(lhs == rhs)
68#define CHECK_NE(lhs, rhs) CHECK(lhs != rhs)
69#define CHECK_NOT_NULL(val) CHECK(val != nullptr)
70
71#define STRICT_NAPI_CALL(call)                                      \
72    do {                                                            \
73        napi_status ret = (call);                                   \
74        if (ret != napi_ok) {                                       \
75            OH_LOG_FATAL(LOG_APP, "Failed to execute `" #call "`, " \
76                "return code is: %{public}d", ret);                 \
77            std::abort();                                           \
78        }                                                           \
79    } while(0)
80
81
82class CallbackInfo {
83public:
84    CallbackInfo(napi_env env, napi_callback_info info)
85        : env_(env)
86    {
87        napi_get_cb_info(env, info, &argc_, nullptr, &thisVar_, &data_);
88        if (argc_ > 0) {
89            argv_ = new napi_value[argc_];
90            CHECK_NOT_NULL(argv_);
91            memset(argv_, nullptr, sizeof(argv_));
92            napi_get_cb_info(env, info, &argc_, argv_, nullptr, nullptr);
93        }
94    }
95    ~CallbackInfo()
96    {
97        if (argc_ > 0) {
98            delete[] argv_;
99            argv_ = nullptr;
100        }
101    }
102
103    inline size_t GetArgc() const { return argc_; }
104    inline napi_value* GetArgs() const { return argv_; }
105
106    inline napi_value GetArg(size_t index) const
107    {
108        if (index >= argc_) {
109            napi_value undefined = nullptr;
110            napi_get_undefined(env_, &undefined);
111            return undefined;
112        }
113        return argv_[index];
114    }
115    inline napi_value operator[](size_t index) const
116    {
117        return GetArg(index);
118    }
119
120private:
121    napi_env env_ { nullptr };
122    size_t argc_ { 0 };
123    napi_value* argv_ { nullptr };
124    napi_value thisVar_ { nullptr };
125    void* data_ { nullptr };
126};
127
128// 构造相同(或不同)地址的napi_env,以便能触发不同的DFX信息
129class EngineProxy {
130public:
131    EngineProxy()
132    {
133        STRICT_NAPI_CALL(napi_create_ark_runtime(&env_));
134        // 5: 使napi_env地址复用更容易
135        for (int i = 0; i < 5; i++) {
136            RecreateOnce();
137        }
138    }
139
140    ~EngineProxy()
141    {
142        STRICT_NAPI_CALL(napi_destroy_ark_runtime(&env_));
143    }
144
145    inline bool RecreateSame()
146    {
147        return Recreate(true);
148    }
149
150    inline bool RecreateDiff()
151    {
152        return Recreate(false);
153    }
154
155    inline operator napi_env() const
156    {
157        return env_;
158    }
159
160    // 重新创建napi_env,直到地址与原始env相同(或不同)
161    bool Recreate(bool requireSame)
162    {
163        const char* recreateTypeTag = requireSame ? "same" : "different";
164        napi_env old = env_;
165        for (int i = 0; i < MAX_RETRY_TIMES; i++) {
166            if (RecreateOnce(old) == requireSame) {
167                OH_LOG_INFO(LOG_APP, "Succeed to recreate env with %{public}s pointer "
168                    "address after retried %{public}d times.", recreateTypeTag, i);
169                return true;
170            }
171        }
172        OH_LOG_ERROR(LOG_APP, "Failed to recreate env with %{public}s pointer "
173            "address after retried %{public}d times.", recreateTypeTag, MAX_RETRY_TIMES);
174        return false;
175    }
176
177private:
178    // 重新创建napi_env,返回新地址是否与原地址相同
179    bool RecreateOnce(napi_env old = nullptr)
180    {
181        STRICT_NAPI_CALL(napi_destroy_ark_runtime(&env_));
182        STRICT_NAPI_CALL(napi_create_ark_runtime(&env_));
183        return env_ == old;
184    }
185
186    napi_env env_ {nullptr};
187
188    constexpr static int MAX_RETRY_TIMES = 1 << 8;
189};
190```
191
192**napi_ref相关接口**
193
194napi_get_reference_value 和 napi_delete_reference 的示例代码
195
196```cpp
197/*
198 * 接口声明 index.d.ts
199 * const triggerDFXGetRef: (samePtr: boolean) => void;
200 */
201napi_value TriggerDFXGetRef(napi_env env, napi_callback_info cbinfo)
202{
203    CallbackInfo info(env, cbinfo);
204    bool same = true;
205    STRICT_NAPI_CALL(napi_get_value_bool(env, info[0], &same));
206    std::thread([](bool same) {
207        EngineProxy localEnv;
208        napi_value obj = nullptr;
209        STRICT_NAPI_CALL(napi_create_object(localEnv, &obj));
210        napi_ref ref = nullptr;
211        // napi_create_reference为js对象创建了强引用,需要使用napi_delete_reference主动销毁,否则会导致js对象无法被回收,造成内存泄漏
212        napi_create_reference(localEnv, obj, 1, &ref);
213        if (!localEnv.Recreate(same)) {
214            if (ref != nullptr) {
215                napi_delete_reference(localEnv, ref);
216            }
217            return;
218        }
219        napi_value result = nullptr;
220        napi_get_reference_value(localEnv, ref, &result);
221        if (ref != nullptr) {
222            napi_delete_reference(localEnv, ref);
223        }
224    }, same).detach();
225    return nullptr;
226}
227
228/*
229 * 接口声明 index.d.ts
230 * const triggerDFXDelRef: () => void;
231 */
232napi_value TriggerDFXDelRef(napi_env, napi_callback_info info)
233{
234    std::thread([]() {
235        EngineProxy localEnv;
236        napi_value obj = nullptr;
237        STRICT_NAPI_CALL(napi_create_object(localEnv, &obj));
238        napi_ref ref = nullptr;
239        // 在使用完成后调用napi_delete_reference来释放引用,避免内存泄露
240        napi_create_reference(localEnv, obj, 1, &ref);
241        if (!localEnv.RecreateSame()) {
242            if (ref != nullptr) {
243                napi_delete_reference(localEnv, ref);
244            }
245            return;
246        };
247        if (ref != nullptr) {
248            napi_delete_reference(localEnv, ref);
249        }
250    }).detach();
251    return nullptr;
252}
253```
254
255**napi_async_work相关接口**
256
257napi_queue_async_work、napi_queue_async_work_with_qos 和 napi_cancel_async_work 的示例代码
258
259```cpp
260/*
261 * 宏 EXPAND_ASYNC_WORK_CASE 将为 op 提供如下变量
262 * @variable napi_env localEnv
263 * @variable napi_async_work work
264 */
265#define EXPAND_ASYNC_WORK_CASE(name, op)                                           \
266napi_value name(napi_env env, napi_callback_info cbinfo)                           \
267{                                                                                  \
268    CallbackInfo info(env, cbinfo);                                                \
269    bool same = true;                                                              \
270    STRICT_NAPI_CALL(napi_get_value_bool(env, info[0], &same));                    \
271    std::thread([](bool same) {                                                    \
272        EngineProxy localEnv;                                                      \
273        napi_async_work work = nullptr;                                            \
274        {                                                                          \
275            napi_value taskName = nullptr;                                         \
276            napi_create_string_utf8(localEnv, #name, NAPI_AUTO_LENGTH, &taskName); \
277            /* 不建议使用空的 execute 回调创建 napi_async_work */                    \
278            /* 此处可能出现内存泄漏,仅为复现 dfx 维测 */                            \
279            napi_create_async_work(localEnv, nullptr, taskName,                    \
280                [](napi_env, void*) {}, [](napi_env, napi_status, void* ) {},      \
281                nullptr, &work);                                                   \
282            if (!localEnv.Recreate(same)) {                                        \
283                if (work != nullptr) {                                             \
284                    napi_delete_async_work(localEnv, work);                        \
285                }                                                                  \
286                return;                                                            \
287            }                                                                      \
288        }                                                                          \
289        (op);                                                                      \
290        if (work != nullptr) {                                                     \
291            napi_delete_async_work(localEnv, work);                                \
292        }                                                                          \
293    }, same).detach();                                                             \
294    return nullptr;                                                                \
295}
296
297/*
298 * 接口声明 index.d.ts
299 * const triggerDFXQueueWork: (samePtr: boolean) => void;
300 * const triggerDFXQueueWorkWithQos: (samePtr: boolean) => void;
301 * const triggerDFXCancelWork: (samePtr: boolean) => void;
302 */
303EXPAND_ASYNC_WORK_CASE(TriggerDFXQueueWork,
304    napi_queue_async_work(localEnv, work))
305EXPAND_ASYNC_WORK_CASE(TriggerDFXQueueWorkWithQos,
306    napi_queue_async_work_with_qos(localEnv, work, napi_qos_default))
307EXPAND_ASYNC_WORK_CASE(TriggerDFXCancelWork,
308    napi_cancel_async_work(localEnv, work))
309
310#undef EXPAND_ASYNC_WORK_CASE
311```
312
313**napi_threadsafe_function相关接口**
314
315napi_call_threadsafe_function 和 napi_release_threadsafe_function 的示例代码
316
317```cpp
318/*
319 * 宏 EXPAND_THREADSAFE_FUNCTION_CASE 将为 op 提供如下变量
320 * @variable napi_env localEnv
321 * @variable napi_threadsafe_function tsfn
322 */
323#define EXPAND_THREADSAFE_FUNCTION_CASE(name, op)                                       \
324    napi_value name(napi_env, napi_callback_info info) {                                \
325        std::thread([]() {                                                              \
326            EngineProxy localEnv;                                                       \
327            napi_threadsafe_function tsfn = nullptr;                                    \
328            {                                                                           \
329                napi_value taskName = nullptr;                                          \
330                napi_create_string_utf8(localEnv, "Test", NAPI_AUTO_LENGTH, &taskName); \
331                // napi_create_threadsafe_function创建线程安全函数,任务执行完成后,      \
332                // 需调用napi_release_threadsafe_function释放
333                napi_create_threadsafe_function(                                        \
334                    localEnv, nullptr, nullptr, taskName, 0, 1, nullptr,                \
335                    [](napi_env, void *, void *) {}, nullptr,                           \
336                    [](napi_env, napi_value, void *, void *) {}, &tsfn);                \
337                if (status != napi_ok) {                                                \
338                    OH_INFO_ERROR(LOG_APP,"napi_create_threadsafe_function failed");    \
339                    return nullptr;                                                     \
340                }                                                                       \
341                if (!localEnv.RecreateSame()) {                                         \
342                    return;                                                             \
343                };                                                                      \
344            }                                                                           \
345            (op);                                                                       \
346        }).detach();                                                                    \
347        return nullptr;                                                                 \
348    }
349
350/*
351 * 接口声明 index.d.ts
352 * const triggerDFXTsfnCall: () => void;
353 * const triggerDFXTsfnRelease: () => void;
354 */
355EXPAND_THREADSAFE_FUNCTION_CASE(TriggerDFXTsfnCall,
356    napi_call_threadsafe_function(tsfn, nullptr, napi_tsfn_nonblocking))
357EXPAND_THREADSAFE_FUNCTION_CASE(TriggerDFXTsfnRelease,
358    napi_release_threadsafe_function(tsfn, napi_tsfn_release))
359
360#undef EXPAND_THREADSAFE_FUNCTION_CASE
361```
362
363## 跨线程调用
364
365### 覆盖范围及关键日志
366
367大多数napi接口都不是多线程安全的,因此为这些错误用法额外增加了定位手段。
368
369若无特殊说明,本章节描述的维测手段会在启用ArkTS运行时多线程检测开关后,立即中断进程。
370
371> **关键日志**
372>
373> current napi interface cannot run in multi-thread, thread id: &lt;env tid&gt;, current thread id: &lt;current tid&gt;
374
375该维测手段覆盖范围如下:
376
3771. napi_add_env_cleanup_hook*
3782. napi_remove_env_cleanup_hook*
3793. napi_add_async_cleanup_hook
3804. napi_set_instance_data
3815. napi_get_instance_data
382
383> \*:具有该标志的接口,在维测触发的情况下,仅打印带有调用栈信息的ERROR日志,并不会中断进程。
384
385### 案例及示例代码
386
387> **注意:**
388>
389> 下面的代码仅用于构造异常场景,触发异常分支的DFX日志。在充分理解其意图前,请勿将其应用到业务场景中。
390
391**env_cleanup_hook相关接口**
392
393napi_add_env_cleanup_hook 和 napi_remove_env_cleanup_hook 的示例代码
394
395```cpp
396static void EnvCLeanUpCallback(void *arg) {
397    char* data = reinterpret_cast<char *>(arg);
398    delete data;
399}
400
401/*
402 * 接口声明 index.d.ts
403 * const triggerDFXClnAddXT: () => void;
404 */
405napi_value TriggerDFXClnAddXT(napi_env env, napi_callback_info info)
406{
407    char* data = new char;
408    CHECK_NOT_NULL(data);
409    *data = nullptr;
410    std::thread([](napi_env env, char* data) {
411        napi_add_env_cleanup_hook(env, EnvCLeanUpCallback, reinterpret_cast<void *>(data));
412    }, env, data).join();
413    napi_remove_env_cleanup_hook(env, EnvCLeanUpCallback, reinterpret_cast<void *>(data));
414    delete data;
415    return nullptr;
416}
417
418/*
419 * 接口声明 index.d.ts
420 * const triggerDFXClnAddMT: () => void;
421 */
422napi_value TriggerDFXClnAddMT(napi_env env, napi_callback_info info)
423{
424    char* data = new char;
425    CHECK_NOT_NULL(data);
426    *data = nullptr;
427    napi_add_env_cleanup_hook(env, EnvCLeanUpCallback, reinterpret_cast<void *>(data));
428    napi_add_env_cleanup_hook(env, EnvCLeanUpCallback, reinterpret_cast<void *>(data));
429    napi_remove_env_cleanup_hook(env, EnvCLeanUpCallback, reinterpret_cast<void *>(data));
430    delete data;
431    return nullptr;
432}
433
434/*
435 * 接口声明 index.d.ts
436 * const triggerDFXClnRmXT: () => void;
437 */
438napi_value TriggerDFXClnRmXT(napi_env env, napi_callback_info info)
439{
440    char* data = new char;
441    CHECK_NOT_NULL(data);
442    *data = nullptr;
443    napi_add_env_cleanup_hook(env, EnvCLeanUpCallback, reinterpret_cast<void *>(data));
444    std::thread([](napi_env env, char* data) {
445        napi_remove_env_cleanup_hook(env, EnvCLeanUpCallback, reinterpret_cast<void *>(data));
446        delete data;
447    }, env, data).join();
448    return nullptr;
449}
450
451/*
452 * 接口声明 index.d.ts
453 * const triggerDFXClnRmMT: () => void;
454 */
455napi_value TriggerDFXClnRmMT(napi_env env, napi_callback_info info)
456{
457    char* data = new char;
458    CHECK_NOT_NULL(data);
459    *data = nullptr;
460    napi_add_env_cleanup_hook(env, EnvCLeanUpCallback, reinterpret_cast<void *>(data));
461    napi_remove_env_cleanup_hook(env, EnvCLeanUpCallback, reinterpret_cast<void *>(data));
462    // 解注册使用的参数与注册时的一致性,比重复解注册更值得关注
463    napi_remove_env_cleanup_hook(env, EnvCLeanUpCallback, reinterpret_cast<void *>(data));
464    delete data;
465    return nullptr;
466}
467```
468
469**async_cleanup_hook相关接口**
470
471napi_add_async_cleanup_hook示例代码
472
473```cpp
474static void AsyncCleanupCallback(napi_async_cleanup_hook_handle handle, void *)
475{
476    napi_remove_async_cleanup_hook(handle);
477}
478
479/*
480 * 接口声明 index.d.ts
481 * const triggerDFXAsyncAddXT: () => void;
482 */
483napi_value TriggerDFXAsyncAddXT(napi_env env, napi_callback_info info)
484{
485    std::thread([](napi_env env) {
486        napi_add_async_cleanup_hook(env, AsyncCleanupCallback, nullptr, nullptr);
487    }, env).join();
488    return nullptr;
489}
490```
491
492**instance_data相关接口**
493
494napi_set_instance_data、napi_get_instance_data示例代码
495
496```cpp
497/*
498 * 接口声明 index.d.ts
499 * const triggerDFXInsSetXT: () => void;
500 */
501napi_value TriggerDFXInsSetXT(napi_env env, napi_callback_info info)
502{
503    std::thread([](napi_env env) {
504        napi_set_instance_data(env, nullptr, [](napi_env, void *, void *) {}, nullptr);
505    }, env).join();
506    return nullptr;
507}
508
509/*
510 * 接口声明 index.d.ts
511 * const triggerDFXInsGetXT: () => void;
512 */
513napi_value TriggerDFXInsGetXT(napi_env env, napi_callback_info info)
514{
515    std::thread([](napi_env env) {
516        void *data = nullptr;
517        napi_get_instance_data(env, &data);
518    }, env).join();
519    return nullptr;
520}
521```
522