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: <owner id> , current env id: <current id>. 32 > 33 > 2. 线程安全函数相关方法,关键日志如下: 34 > 35 > current tsfn was created by dead env, owner id: <owner id>, current env id: <current id> 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: <env tid>, current thread id: <current tid> 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