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: <env tid>, current thread id: <current tid> 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