# 使用Node-API接口产生的异常日志/崩溃分析 以下维测手段多依赖于ArkTS运行时的多线程检测能力,因此建议在调试前启用此功能。启用方法参考文档[分析CppCrash(进程崩溃)](../dfx/cppcrash-guidelines.md#工具二方舟多线程检测)。 若无特殊说明,本章节所描述的维测手段,在启用ArkTS运行时多线程检测开关的前提下,会在第一现场中断进程。 ## 数据在使用时,与创建该数据时所使用的env不一致 ### 各问题场景关键日志 该维测手段主要包含以下两种场景: 1. 调用napi方法使用已创建的napi数据结构时,入参napi_env与创建时不一致。 > **关键日志** > param env not equal to its owner. > 2. 调用napi方法使用已创建的napi数据结构时,入参napi_env与创建时地址一致,但原始napi_env已释放。 > **关键日志** > > 1. 除线程安全函数相关方法外,关键日志如下: > > owner env has been destroyed, owner id: <owner id> , current env id: <current id>. > > 2. 线程安全函数相关方法,关键日志如下: > > current tsfn was created by dead env, owner id: <owner id>, current env id: <current id> 该维测手段目前覆盖范围如下: 1. napi_get_reference_value 2. napi_delete_reference* 3. napi_queue_async_work 4. napi_queue_async_work_with_qos 5. napi_cancel_async_work 6. napi_call_threadsafe_function* 7. napi_release_threadsafe_function* > \*:具有该标志的接口,仅能触发第二种场景的维测信息。 ### 案例及示例代码 > **注意:** 如下代码仅用于构造异常场景,触发异常分支的DFX日志。在您充分理解其意图前,请勿将其应用到业务场景中。 #### 基础工具类 定义一个工具类,便于在后续构造两种异常场景。 ```cpp #define CHECK(cond) \ do { \ if (cond) { \ OH_LOG_FATAL(LOG_APP, "Failed to check `" #cond "`"); \ std::abort(); \ } \ } while(0) #define CHECK_EQ(lhs, rhs) CHECK(lhs == rhs) #define CHECK_NE(lhs, rhs) CHECK(lhs != rhs) #define CHECK_NOT_NULL(val) CHECK(val != nullptr) #define STRICT_NAPI_CALL(call) \ do { \ napi_status ret = (call); \ if (ret != napi_ok) { \ OH_LOG_FATAL(LOG_APP, "Failed to execute `" #call "`, " \ "return code is: %{public}d", ret); \ std::abort(); \ } \ } while(0) class CallbackInfo { public: CallbackInfo(napi_env env, napi_callback_info info) : env_(env) { napi_get_cb_info(env, info, &argc_, nullptr, &thisVar_, &data_); if (argc_ > 0) { argv_ = new napi_value[argc_]; CHECK_NOT_NULL(argv_); memset(argv_, nullptr, sizeof(argv_)); napi_get_cb_info(env, info, &argc_, argv_, nullptr, nullptr); } } ~CallbackInfo() { if (argc_ > 0) { delete[] argv_; argv_ = nullptr; } } inline size_t GetArgc() const { return argc_; } inline napi_value* GetArgs() const { return argv_; } inline napi_value GetArg(size_t index) const { if (index >= argc_) { napi_value undefined = nullptr; napi_get_undefined(env_, &undefined); return undefined; } return argv_[index]; } inline napi_value operator[](size_t index) const { return GetArg(index); } private: napi_env env_ { nullptr }; size_t argc_ { 0 }; napi_value* argv_ { nullptr }; napi_value thisVar_ { nullptr }; void* data_ { nullptr }; }; // 构造相同(或不同)地址的napi_env,以便能触发不同的DFX信息 class EngineProxy { public: EngineProxy() { STRICT_NAPI_CALL(napi_create_ark_runtime(&env_)); // 5: 使napi_env地址复用更容易 for (int i = 0; i < 5; i++) { RecreateOnce(); } } ~EngineProxy() { STRICT_NAPI_CALL(napi_destroy_ark_runtime(&env_)); } inline bool RecreateSame() { return Recreate(true); } inline bool RecreateDiff() { return Recreate(false); } inline operator napi_env() const { return env_; } // 重新创建napi_env,直到地址与原始env相同(或不同) bool Recreate(bool requireSame) { const char* recreateTypeTag = requireSame ? "same" : "different"; napi_env old = env_; for (int i = 0; i < MAX_RETRY_TIMES; i++) { if (RecreateOnce(old) == requireSame) { OH_LOG_INFO(LOG_APP, "Succeed to recreate env with %{public}s pointer " "address after retried %{public}d times.", recreateTypeTag, i); return true; } } OH_LOG_ERROR(LOG_APP, "Failed to recreate env with %{public}s pointer " "address after retried %{public}d times.", recreateTypeTag, MAX_RETRY_TIMES); return false; } private: // 重新创建napi_env,返回新地址是否与原地址相同 bool RecreateOnce(napi_env old = nullptr) { STRICT_NAPI_CALL(napi_destroy_ark_runtime(&env_)); STRICT_NAPI_CALL(napi_create_ark_runtime(&env_)); return env_ == old; } napi_env env_ {nullptr}; constexpr static int MAX_RETRY_TIMES = 1 << 8; }; ``` #### napi_ref相关接口 napi_get_reference_value、napi_delete_reference示例代码 ```cpp /* * 接口声明 index.d.ts * const triggerDFXGetRef: (samePtr: boolean) => void; */ napi_value TriggerDFXGetRef(napi_env env, napi_callback_info cbinfo) { CallbackInfo info(env, cbinfo); bool same = true; STRICT_NAPI_CALL(napi_get_value_bool(env, info[0], &same)); std::thread([](bool same) { EngineProxy localEnv; napi_value obj = nullptr; STRICT_NAPI_CALL(napi_create_object(localEnv, &obj)); napi_ref ref = nullptr; napi_create_reference(localEnv, obj, 1, &ref); if (!localEnv.Recreate(same)) { return; }; napi_value result = nullptr; napi_get_reference_value(localEnv, ref, &result); }, same).detach(); return nullptr; } /* * 接口声明 index.d.ts * const triggerDFXDelRef: () => void; */ napi_value TriggerDFXDelRef(napi_env, napi_callback_info) { std::thread([]() { EngineProxy localEnv; napi_value obj = nullptr; STRICT_NAPI_CALL(napi_create_object(localEnv, &obj)); napi_ref ref = nullptr; napi_create_reference(localEnv, obj, 1, &ref); if (!localEnv.RecreateSame()) { return; }; napi_delete_reference(localEnv, ref); }).detach(); return nullptr; } ``` #### napi_async_work相关接口 napi_queue_async_work、napi_queue_async_work_with_qos、napi_cancel_async_work示例代码 ```cpp /* * 宏 EXPAND_ASYNC_WORK_CASE 将为 op 提供如下变量 * @variable napi_env localEnv * @variable napi_async_work work */ #define EXPAND_ASYNC_WORK_CASE(name, op) \ napi_value name(napi_env env, napi_callback_info cbinfo) \ { \ CallbackInfo info(env, cbinfo); \ bool same = true; \ STRICT_NAPI_CALL(napi_get_value_bool(env, info[0], &same)); \ std::thread([](bool same) { \ EngineProxy localEnv; \ napi_async_work work = nullptr; \ { \ napi_value taskName = nullptr; \ napi_create_string_utf8(localEnv, #name, NAPI_AUTO_LENGTH, &taskName); \ /* 不建议使用空的 execute 回调创建 napi_async_work */ \ napi_create_async_work(localEnv, nullptr, taskName, \ [](napi_env, void*) {}, [](napi_env, napi_status, void* ) {}, \ nullptr, &work); \ if (!localEnv.Recreate(same)) { \ return; \ } \ } \ (op); \ }, same).detach(); \ return nullptr; \ } /* * 接口声明 index.d.ts * const triggerDFXQueueWork: (samePtr: boolean) => void; * const triggerDFXQueueWorkWithQos: (samePtr: boolean) => void; * const triggerDFXCancelWork: (samePtr: boolean) => void; */ EXPAND_ASYNC_WORK_CASE(TriggerDFXQueueWork, napi_queue_async_work(localEnv, work)) EXPAND_ASYNC_WORK_CASE(TriggerDFXQueueWorkWithQos, napi_queue_async_work_with_qos(localEnv, work, napi_qos_default)) EXPAND_ASYNC_WORK_CASE(TriggerDFXCancelWork, napi_cancel_async_work(localEnv, work)) #undef EXPAND_ASYNC_WORK_CASE ``` #### napi_threadsafe_function相关接口 napi_call_threadsafe_function、napi_release_threadsafe_function示例代码 ```cpp /* * 宏 EXPAND_THREADSAFE_FUNCTION_CASE 将为 op 提供如下变量 * @variable napi_env localEnv * @variable napi_threadsafe_function tsfn */ #define EXPAND_THREADSAFE_FUNCTION_CASE(name, op) \ napi_value name(napi_env, napi_callback_info) { \ std::thread([]() { \ EngineProxy localEnv; \ napi_threadsafe_function tsfn = nullptr; \ { \ napi_value taskName = nullptr; \ napi_create_string_utf8(localEnv, "Test", NAPI_AUTO_LENGTH, &taskName); \ napi_create_threadsafe_function( \ localEnv, nullptr, nullptr, taskName, 0, 1, nullptr, \ [](napi_env, void *, void *) {}, nullptr, \ [](napi_env, napi_value, void *, void *) {}, &tsfn); \ if (!localEnv.RecreateSame()) { \ return; \ }; \ } \ (op); \ }).detach(); \ return nullptr; \ } /* * 接口声明 index.d.ts * const triggerDFXTsfnCall: () => void; * const triggerDFXTsfnRelease: () => void; */ EXPAND_THREADSAFE_FUNCTION_CASE(TriggerDFXTsfnCall, napi_call_threadsafe_function(tsfn, nullptr, napi_tsfn_nonblocking)) EXPAND_THREADSAFE_FUNCTION_CASE(TriggerDFXTsfnRelease, napi_release_threadsafe_function(tsfn, napi_tsfn_release)) #undef EXPAND_THREADSAFE_FUNCTION_CASE ``` ## 跨线程调用 ### 覆盖范围及关键日志 大多数napi接口都不是多线程安全的,因此为这些错误用法额外增加了定位手段。 若无特殊说明,本章节所描述的维测手段,在启用ArkTS运行时多线程检测开关的前提下,会在第一现场中断进程。 > **关键日志** > > current napi interface cannot run in multi-thread, thread id: <env tid>, current thread id: <current tid> 该维测手段覆盖范围如下: 1. napi_add_env_cleanup_hook* 2. napi_remove_env_cleanup_hook* 3. napi_add_async_cleanup_hook 4. napi_set_instance_data 5. napi_get_instance_data > \*:具有该标志的接口,在维测触发的情况下,仅打印带有调用栈信息的ERROR日志,并不会中断进程。 ### 案例及示例代码 > **注意:** 如下代码仅用于构造异常场景,触发异常分支的DFX日志。在您充分理解其意图前,请勿将其应用到业务场景中。 #### env_cleanup_hook相关接口 napi_add_env_cleanup_hook、napi_remove_env_cleanup_hook示例代码 ```cpp static void EnvCLeanUpCallback(void *arg) { char* data = reinterpret_cast(arg); delete data; } /* * 接口声明 index.d.ts * const triggerDFXClnAddXT: () => void; */ napi_value TriggerDFXClnAddXT(napi_env env, napi_callback_info) { char* data = new char; CHECK_NOT_NULL(data); *data = nullptr; std::thread([](napi_env env, char* data) { napi_add_env_cleanup_hook(env, EnvCLeanUpCallback, reinterpret_cast(data)); }, env, data).join(); napi_remove_env_cleanup_hook(env, EnvCLeanUpCallback, reinterpret_cast(data)); delete data; return nullptr; } /* * 接口声明 index.d.ts * const triggerDFXClnAddMT: () => void; */ napi_value TriggerDFXClnAddMT(napi_env env, napi_callback_info) { char* data = new char; CHECK_NOT_NULL(data); *data = nullptr; napi_add_env_cleanup_hook(env, EnvCLeanUpCallback, reinterpret_cast(data)); napi_add_env_cleanup_hook(env, EnvCLeanUpCallback, reinterpret_cast(data)); napi_remove_env_cleanup_hook(env, EnvCLeanUpCallback, reinterpret_cast(data)); delete data; return nullptr; } /* * 接口声明 index.d.ts * const triggerDFXClnRmXT: () => void; */ napi_value TriggerDFXClnRmXT(napi_env env, napi_callback_info) { char* data = new char; CHECK_NOT_NULL(data); *data = nullptr; napi_add_env_cleanup_hook(env, EnvCLeanUpCallback, reinterpret_cast(data)); std::thread([](napi_env env, char* data) { napi_remove_env_cleanup_hook(env, EnvCLeanUpCallback, reinterpret_cast(data)); delete data; }, env, data).join(); return nullptr; } /* * 接口声明 index.d.ts * const triggerDFXClnRmMT: () => void; */ napi_value TriggerDFXClnRmMT(napi_env env, napi_callback_info) { char* data = new char; CHECK_NOT_NULL(data); *data = nullptr; napi_add_env_cleanup_hook(env, EnvCLeanUpCallback, reinterpret_cast(data)); napi_remove_env_cleanup_hook(env, EnvCLeanUpCallback, reinterpret_cast(data)); // 解注册使用的参数与注册时的一致性,比重复解注册更值得关注 napi_remove_env_cleanup_hook(env, EnvCLeanUpCallback, reinterpret_cast(data)); delete data; return nullptr; } ``` #### async_cleanup_hook相关接口 napi_add_async_cleanup_hook示例代码 ```cpp static void AsyncCleanupCallback(napi_async_cleanup_hook_handle handle, void *) { napi_remove_async_cleanup_hook(handle); } /* * 接口声明 index.d.ts * const triggerDFXAsyncAddXT: () => void; */ napi_value TriggerDFXAsyncAddXT(napi_env env, napi_callback_info) { std::thread([](napi_env env) { napi_add_async_cleanup_hook(env, AsyncCleanupCallback, nullptr, nullptr); }, env).join(); return nullptr; } ``` #### instance_data相关接口 napi_set_instance_data、napi_get_instance_data示例代码 ```cpp /* * 接口声明 index.d.ts * const triggerDFXInsSetXT: () => void; */ napi_value TriggerDFXInsSetXT(napi_env env, napi_callback_info) { std::thread([](napi_env env) { napi_set_instance_data(env, nullptr, [](napi_env, void *, void *) {}, nullptr); }, env).join(); return nullptr; } /* * 接口声明 index.d.ts * const triggerDFXInsGetXT: () => void; */ napi_value TriggerDFXInsGetXT(napi_env env, napi_callback_info) { std::thread([](napi_env env) { void *data = nullptr; napi_get_instance_data(env, &data); }, env).join(); return nullptr; } ```