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