1 /** 2 * Copyright (c) 2021-2022 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16 #ifndef PANDA_TOOLING_PT_HOOKS_WRAPPER_H 17 #define PANDA_TOOLING_PT_HOOKS_WRAPPER_H 18 19 #include <atomic> 20 #include "runtime/include/tooling/debug_interface.h" 21 #include "os/mutex.h" 22 #include "runtime/include/mtmanaged_thread.h" 23 #include "pt_thread_info.h" 24 #include "pt_hook_type_info.h" 25 26 namespace panda::tooling { 27 28 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init) 29 class PtHooksWrapper : public PtHooks { 30 public: SetHooks(PtHooks * hooks)31 void SetHooks(PtHooks *hooks) 32 { 33 // Atomic with release order reason: data race with hooks_ 34 hooks_.store(hooks, std::memory_order_release); 35 } 36 EnableGlobalHook(PtHookType hookType)37 void EnableGlobalHook(PtHookType hookType) 38 { 39 global_hook_type_info_.Enable(hookType); 40 } 41 DisableGlobalHook(PtHookType hookType)42 void DisableGlobalHook(PtHookType hookType) 43 { 44 global_hook_type_info_.Disable(hookType); 45 } 46 EnableAllGlobalHook()47 void EnableAllGlobalHook() 48 { 49 global_hook_type_info_.EnableAll(); 50 } 51 DisableAllGlobalHook()52 void DisableAllGlobalHook() 53 { 54 global_hook_type_info_.DisableAll(); 55 } 56 57 // Wrappers for hooks Breakpoint(PtThread thread,Method * method,const PtLocation & location)58 void Breakpoint(PtThread thread, Method *method, const PtLocation &location) override 59 { 60 // Atomic with acquire order reason: data race with hooks_ 61 auto *loaded_hooks = hooks_.load(std::memory_order_acquire); 62 if (loaded_hooks == nullptr || !HookIsEnabled(PtHookType::PT_HOOK_TYPE_BREAKPOINT)) { 63 return; 64 } 65 // Atomic with acquire order reason: data race with vmdeath_did_not_happen_ 66 ASSERT(vmdeath_did_not_happen_.load(std::memory_order_acquire)); 67 loaded_hooks->Breakpoint(thread, method, location); 68 } 69 LoadModule(std::string_view pandaFile)70 void LoadModule(std::string_view pandaFile) override 71 { 72 // Atomic with acquire order reason: data race with hooks_ 73 auto *loaded_hooks = hooks_.load(std::memory_order_acquire); 74 if (loaded_hooks == nullptr || !HookIsEnabled(PtHookType::PT_HOOK_TYPE_LOAD_MODULE)) { 75 return; 76 } 77 // Atomic with acquire order reason: data race with vmdeath_did_not_happen_ 78 ASSERT(vmdeath_did_not_happen_.load(std::memory_order_acquire)); 79 loaded_hooks->LoadModule(pandaFile); 80 } 81 Paused(PauseReason reason)82 void Paused(PauseReason reason) override 83 { 84 // Atomic with acquire order reason: data race with hooks_ 85 auto *loaded_hooks = hooks_.load(std::memory_order_acquire); 86 if (loaded_hooks == nullptr || !HookIsEnabled(PtHookType::PT_HOOK_TYPE_PAUSED)) { 87 return; 88 } 89 // Atomic with acquire order reason: data race with vmdeath_did_not_happen_ 90 ASSERT(vmdeath_did_not_happen_.load(std::memory_order_acquire)); 91 loaded_hooks->Paused(reason); 92 } 93 Exception(PtThread thread,Method * method,const PtLocation & location,ObjectHeader * exceptionObject,Method * catchMethod,const PtLocation & catchLocation)94 void Exception(PtThread thread, Method *method, const PtLocation &location, ObjectHeader *exceptionObject, 95 Method *catchMethod, const PtLocation &catchLocation) override 96 { 97 // Atomic with acquire order reason: data race with hooks_ 98 auto *loaded_hooks = hooks_.load(std::memory_order_acquire); 99 if (loaded_hooks == nullptr || !HookIsEnabled(PtHookType::PT_HOOK_TYPE_EXCEPTION)) { 100 return; 101 } 102 // Atomic with acquire order reason: data race with vmdeath_did_not_happen_ 103 ASSERT(vmdeath_did_not_happen_.load(std::memory_order_acquire)); 104 loaded_hooks->Exception(thread, method, location, exceptionObject, catchMethod, catchLocation); 105 } 106 ExceptionCatch(PtThread thread,Method * method,const PtLocation & location,ObjectHeader * exceptionObject)107 void ExceptionCatch(PtThread thread, Method *method, const PtLocation &location, 108 ObjectHeader *exceptionObject) override 109 { 110 // Atomic with acquire order reason: data race with hooks_ 111 auto *loaded_hooks = hooks_.load(std::memory_order_acquire); 112 if (loaded_hooks == nullptr || !HookIsEnabled(PtHookType::PT_HOOK_TYPE_EXCEPTION_CATCH)) { 113 return; 114 } 115 // Atomic with acquire order reason: data race with vmdeath_did_not_happen_ 116 ASSERT(vmdeath_did_not_happen_.load(std::memory_order_acquire)); 117 loaded_hooks->ExceptionCatch(thread, method, location, exceptionObject); 118 } 119 PropertyAccess(PtThread thread,Method * method,const PtLocation & location,ObjectHeader * object,PtProperty property)120 void PropertyAccess(PtThread thread, Method *method, const PtLocation &location, ObjectHeader *object, 121 PtProperty property) override 122 { 123 // Atomic with acquire order reason: data race with hooks_ 124 auto *loaded_hooks = hooks_.load(std::memory_order_acquire); 125 if (loaded_hooks == nullptr || !HookIsEnabled(PtHookType::PT_HOOK_TYPE_PROPERTY_ACCESS)) { 126 return; 127 } 128 // Atomic with acquire order reason: data race with vmdeath_did_not_happen_ 129 ASSERT(vmdeath_did_not_happen_.load(std::memory_order_acquire)); 130 loaded_hooks->PropertyAccess(thread, method, location, object, property); 131 } 132 PropertyModification(PtThread thread,Method * method,const PtLocation & location,ObjectHeader * object,PtProperty property,VRegValue newValue)133 void PropertyModification(PtThread thread, Method *method, const PtLocation &location, ObjectHeader *object, 134 PtProperty property, VRegValue newValue) override 135 { 136 // Atomic with acquire order reason: data race with hooks_ 137 auto *loaded_hooks = hooks_.load(std::memory_order_acquire); 138 if (loaded_hooks == nullptr || !HookIsEnabled(PtHookType::PT_HOOK_TYPE_PROPERTY_MODIFICATION)) { 139 return; 140 } 141 // Atomic with acquire order reason: data race with vmdeath_did_not_happen_ 142 ASSERT(vmdeath_did_not_happen_.load(std::memory_order_acquire)); 143 loaded_hooks->PropertyModification(thread, method, location, object, property, newValue); 144 } 145 FramePop(PtThread thread,Method * method,bool wasPoppedByException)146 void FramePop(PtThread thread, Method *method, bool wasPoppedByException) override 147 { 148 // Atomic with acquire order reason: data race with hooks_ 149 auto *loaded_hooks = hooks_.load(std::memory_order_acquire); 150 if (loaded_hooks == nullptr || !HookIsEnabled(PtHookType::PT_HOOK_TYPE_FRAME_POP)) { 151 return; 152 } 153 // Atomic with acquire order reason: data race with vmdeath_did_not_happen_ 154 ASSERT(vmdeath_did_not_happen_.load(std::memory_order_acquire)); 155 loaded_hooks->FramePop(thread, method, wasPoppedByException); 156 } 157 GarbageCollectionFinish()158 void GarbageCollectionFinish() override 159 { 160 // TODO(dtrubenkov): Add an assertion when 2125 issue is resolved 161 // ASSERT(ManagedThread::GetCurrent() == nullptr) 162 // Atomic with acquire order reason: data race with hooks_ 163 auto *loaded_hooks = hooks_.load(std::memory_order_acquire); 164 if (loaded_hooks == nullptr || !GlobalHookIsEnabled(PtHookType::PT_HOOK_TYPE_GARBAGE_COLLECTION_FINISH)) { 165 return; 166 } 167 // Atomic with acquire order reason: data race with vmdeath_did_not_happen_ 168 ASSERT(vmdeath_did_not_happen_.load(std::memory_order_acquire)); 169 // Called in an unmanaged thread 170 loaded_hooks->GarbageCollectionFinish(); 171 } 172 GarbageCollectionStart()173 void GarbageCollectionStart() override 174 { 175 // TODO(dtrubenkov): Add an assertion when 2125 issue is resolved 176 // ASSERT(ManagedThread::GetCurrent() == nullptr) 177 // Atomic with acquire order reason: data race with hooks_ 178 auto *loaded_hooks = hooks_.load(std::memory_order_acquire); 179 if (loaded_hooks == nullptr || !GlobalHookIsEnabled(PtHookType::PT_HOOK_TYPE_GARBAGE_COLLECTION_START)) { 180 return; 181 } 182 // Atomic with acquire order reason: data race with vmdeath_did_not_happen_ 183 ASSERT(vmdeath_did_not_happen_.load(std::memory_order_acquire)); 184 // Called in an unmanaged thread 185 loaded_hooks->GarbageCollectionStart(); 186 } 187 MethodEntry(PtThread thread,Method * method)188 void MethodEntry(PtThread thread, Method *method) override 189 { 190 // Atomic with acquire order reason: data race with hooks_ 191 auto *loaded_hooks = hooks_.load(std::memory_order_acquire); 192 if (loaded_hooks == nullptr || !HookIsEnabled(PtHookType::PT_HOOK_TYPE_METHOD_ENTRY)) { 193 return; 194 } 195 // Atomic with acquire order reason: data race with vmdeath_did_not_happen_ 196 ASSERT(vmdeath_did_not_happen_.load(std::memory_order_acquire)); 197 loaded_hooks->MethodEntry(thread, method); 198 } 199 MethodExit(PtThread thread,Method * method,bool wasPoppedByException,VRegValue returnValue)200 void MethodExit(PtThread thread, Method *method, bool wasPoppedByException, VRegValue returnValue) override 201 { 202 // Atomic with acquire order reason: data race with hooks_ 203 auto *loaded_hooks = hooks_.load(std::memory_order_acquire); 204 if (loaded_hooks == nullptr || !HookIsEnabled(PtHookType::PT_HOOK_TYPE_METHOD_EXIT)) { 205 return; 206 } 207 // Atomic with acquire order reason: data race with vmdeath_did_not_happen_ 208 ASSERT(vmdeath_did_not_happen_.load(std::memory_order_acquire)); 209 loaded_hooks->MethodExit(thread, method, wasPoppedByException, returnValue); 210 } 211 SingleStep(PtThread thread,Method * method,const PtLocation & location)212 void SingleStep(PtThread thread, Method *method, const PtLocation &location) override 213 { 214 // Atomic with acquire order reason: data race with hooks_ 215 auto *loaded_hooks = hooks_.load(std::memory_order_acquire); 216 if (loaded_hooks == nullptr || !HookIsEnabled(PtHookType::PT_HOOK_TYPE_SINGLE_STEP)) { 217 return; 218 } 219 // Atomic with acquire order reason: data race with vmdeath_did_not_happen_ 220 ASSERT(vmdeath_did_not_happen_.load(std::memory_order_acquire)); 221 loaded_hooks->SingleStep(thread, method, location); 222 } 223 ThreadStart(PtThread thread)224 void ThreadStart(PtThread thread) override 225 { 226 // Atomic with acquire order reason: data race with hooks_ 227 auto *loaded_hooks = hooks_.load(std::memory_order_acquire); 228 if (loaded_hooks == nullptr || !HookIsEnabled(PtHookType::PT_HOOK_TYPE_THREAD_START)) { 229 return; 230 } 231 // Atomic with acquire order reason: data race with vmdeath_did_not_happen_ 232 ASSERT(vmdeath_did_not_happen_.load(std::memory_order_acquire)); 233 loaded_hooks->ThreadStart(thread); 234 } 235 ThreadEnd(PtThread thread)236 void ThreadEnd(PtThread thread) override 237 { 238 // Atomic with acquire order reason: data race with hooks_ 239 auto *loaded_hooks = hooks_.load(std::memory_order_acquire); 240 if (loaded_hooks == nullptr || !HookIsEnabled(PtHookType::PT_HOOK_TYPE_THREAD_END)) { 241 return; 242 } 243 // Atomic with acquire order reason: data race with vmdeath_did_not_happen_ 244 ASSERT(vmdeath_did_not_happen_.load(std::memory_order_acquire)); 245 loaded_hooks->ThreadEnd(thread); 246 } 247 VmStart()248 void VmStart() override 249 { 250 // Atomic with acquire order reason: data race with hooks_ 251 auto *loaded_hooks = hooks_.load(std::memory_order_acquire); 252 if (loaded_hooks == nullptr || !HookIsEnabled(PtHookType::PT_HOOK_TYPE_VM_START)) { 253 return; 254 } 255 // Atomic with acquire order reason: data race with vmdeath_did_not_happen_ 256 ASSERT(vmdeath_did_not_happen_.load(std::memory_order_acquire)); 257 loaded_hooks->VmStart(); 258 } 259 VmInitialization(PtThread thread)260 void VmInitialization(PtThread thread) override 261 { 262 // Atomic with acquire order reason: data race with hooks_ 263 auto *loaded_hooks = hooks_.load(std::memory_order_acquire); 264 if (loaded_hooks == nullptr || !HookIsEnabled(PtHookType::PT_HOOK_TYPE_VM_INITIALIZATION)) { 265 return; 266 } 267 // Atomic with acquire order reason: data race with vmdeath_did_not_happen_ 268 ASSERT(vmdeath_did_not_happen_.load(std::memory_order_acquire)); 269 loaded_hooks->VmInitialization(thread); 270 } 271 VmDeath()272 void VmDeath() override 273 { 274 // Atomic with acquire order reason: data race with hooks_ 275 auto *loaded_hooks = hooks_.load(std::memory_order_acquire); 276 if (loaded_hooks == nullptr || !HookIsEnabled(PtHookType::PT_HOOK_TYPE_VM_DEATH)) { 277 return; 278 } 279 // Atomic with acquire order reason: data race with vmdeath_did_not_happen_ 280 ASSERT(vmdeath_did_not_happen_.load(std::memory_order_acquire)); 281 #ifndef NDEBUG 282 // Atomic with release order reason: data race with vmdeath_did_not_happen_ 283 vmdeath_did_not_happen_.store(false, std::memory_order_release); 284 #endif 285 loaded_hooks->VmDeath(); 286 SetHooks(nullptr); 287 } 288 ExceptionRevoked(ExceptionWrapper reason,ExceptionID exceptionId)289 void ExceptionRevoked(ExceptionWrapper reason, ExceptionID exceptionId) override 290 { 291 // Atomic with acquire order reason: data race with hooks_ 292 auto *loaded_hooks = hooks_.load(std::memory_order_acquire); 293 if (loaded_hooks == nullptr || !HookIsEnabled(PtHookType::PT_HOOK_TYPE_EXCEPTION_REVOKED)) { 294 return; 295 } 296 // Atomic with acquire order reason: data race with vmdeath_did_not_happen_ 297 ASSERT(vmdeath_did_not_happen_.load(std::memory_order_acquire)); 298 loaded_hooks->ExceptionRevoked(reason, exceptionId); 299 } 300 ExecutionContextCreated(ExecutionContextWrapper context)301 void ExecutionContextCreated(ExecutionContextWrapper context) override 302 { 303 // Atomic with acquire order reason: data race with hooks_ 304 auto *loaded_hooks = hooks_.load(std::memory_order_acquire); 305 if (loaded_hooks == nullptr || !HookIsEnabled(PtHookType::PT_HOOK_TYPE_EXECUTION_CONTEXT_CREATEED)) { 306 return; 307 } 308 // Atomic with acquire order reason: data race with vmdeath_did_not_happen_ 309 ASSERT(vmdeath_did_not_happen_.load(std::memory_order_acquire)); 310 loaded_hooks->ExecutionContextCreated(context); 311 } 312 ExecutionContextDestroyed(ExecutionContextWrapper context)313 void ExecutionContextDestroyed(ExecutionContextWrapper context) override 314 { 315 // Atomic with acquire order reason: data race with hooks_ 316 auto *loaded_hooks = hooks_.load(std::memory_order_acquire); 317 if (loaded_hooks == nullptr || !HookIsEnabled(PtHookType::PT_HOOK_TYPE_EXECUTION_CONTEXT_DESTROYED)) { 318 return; 319 } 320 // Atomic with acquire order reason: data race with vmdeath_did_not_happen_ 321 ASSERT(vmdeath_did_not_happen_.load(std::memory_order_acquire)); 322 loaded_hooks->ExecutionContextDestroyed(context); 323 } 324 ExecutionContextsCleared()325 void ExecutionContextsCleared() override 326 { 327 // Atomic with acquire order reason: data race with hooks_ 328 auto *loaded_hooks = hooks_.load(std::memory_order_acquire); 329 if (loaded_hooks == nullptr || !HookIsEnabled(PtHookType::PT_HOOK_TYPE_EXECUTION_CONTEXTS_CLEARED)) { 330 return; 331 } 332 // Atomic with acquire order reason: data race with vmdeath_did_not_happen_ 333 ASSERT(vmdeath_did_not_happen_.load(std::memory_order_acquire)); 334 loaded_hooks->ExecutionContextsCleared(); 335 } 336 InspectRequested(PtObject object,PtObject hints)337 void InspectRequested(PtObject object, PtObject hints) override 338 { 339 // Atomic with acquire order reason: data race with hooks_ 340 auto *loaded_hooks = hooks_.load(std::memory_order_acquire); 341 if (loaded_hooks == nullptr || !HookIsEnabled(PtHookType::PT_HOOK_TYPE_INSPECT_REQUESTED)) { 342 return; 343 } 344 // Atomic with acquire order reason: data race with vmdeath_did_not_happen_ 345 ASSERT(vmdeath_did_not_happen_.load(std::memory_order_acquire)); 346 loaded_hooks->InspectRequested(object, hints); 347 } 348 ClassLoad(PtThread thread,BaseClass * klass)349 void ClassLoad(PtThread thread, BaseClass *klass) override 350 { 351 // Atomic with acquire order reason: data race with hooks_ 352 auto *loaded_hooks = hooks_.load(std::memory_order_acquire); 353 if (loaded_hooks == nullptr || !HookIsEnabled(PtHookType::PT_HOOK_TYPE_CLASS_LOAD)) { 354 return; 355 } 356 // Atomic with acquire order reason: data race with vmdeath_did_not_happen_ 357 ASSERT(vmdeath_did_not_happen_.load(std::memory_order_acquire)); 358 loaded_hooks->ClassLoad(thread, klass); 359 } 360 ClassPrepare(PtThread thread,BaseClass * klass)361 void ClassPrepare(PtThread thread, BaseClass *klass) override 362 { 363 // Atomic with acquire order reason: data race with hooks_ 364 auto *loaded_hooks = hooks_.load(std::memory_order_acquire); 365 if (loaded_hooks == nullptr || !HookIsEnabled(PtHookType::PT_HOOK_TYPE_CLASS_PREPARE)) { 366 return; 367 } 368 // Atomic with acquire order reason: data race with vmdeath_did_not_happen_ 369 ASSERT(vmdeath_did_not_happen_.load(std::memory_order_acquire)); 370 loaded_hooks->ClassPrepare(thread, klass); 371 } 372 MonitorWait(PtThread thread,ObjectHeader * object,int64_t timeout)373 void MonitorWait(PtThread thread, ObjectHeader *object, int64_t timeout) override 374 { 375 // Atomic with acquire order reason: data race with hooks_ 376 auto *loaded_hooks = hooks_.load(std::memory_order_acquire); 377 if (loaded_hooks == nullptr || !HookIsEnabled(PtHookType::PT_HOOK_TYPE_MONITOR_WAIT)) { 378 return; 379 } 380 // Atomic with acquire order reason: data race with vmdeath_did_not_happen_ 381 ASSERT(vmdeath_did_not_happen_.load(std::memory_order_acquire)); 382 loaded_hooks->MonitorWait(thread, object, timeout); 383 } 384 MonitorWaited(PtThread thread,ObjectHeader * object,bool timedOut)385 void MonitorWaited(PtThread thread, ObjectHeader *object, bool timedOut) override 386 { 387 // Atomic with acquire order reason: data race with hooks_ 388 auto *loaded_hooks = hooks_.load(std::memory_order_acquire); 389 if (loaded_hooks == nullptr || !HookIsEnabled(PtHookType::PT_HOOK_TYPE_MONITOR_WAITED)) { 390 return; 391 } 392 // Atomic with acquire order reason: data race with vmdeath_did_not_happen_ 393 ASSERT(vmdeath_did_not_happen_.load(std::memory_order_acquire)); 394 loaded_hooks->MonitorWaited(thread, object, timedOut); 395 } 396 MonitorContendedEnter(PtThread thread,ObjectHeader * object)397 void MonitorContendedEnter(PtThread thread, ObjectHeader *object) override 398 { 399 // Atomic with acquire order reason: data race with hooks_ 400 auto *loaded_hooks = hooks_.load(std::memory_order_acquire); 401 if (loaded_hooks == nullptr || !HookIsEnabled(PtHookType::PT_HOOK_TYPE_MONITOR_CONTENDED_ENTER)) { 402 return; 403 } 404 // Atomic with acquire order reason: data race with vmdeath_did_not_happen_ 405 ASSERT(vmdeath_did_not_happen_.load(std::memory_order_acquire)); 406 loaded_hooks->MonitorContendedEnter(thread, object); 407 } 408 MonitorContendedEntered(PtThread thread,ObjectHeader * object)409 void MonitorContendedEntered(PtThread thread, ObjectHeader *object) override 410 { 411 // Atomic with acquire order reason: data race with hooks_ 412 auto *loaded_hooks = hooks_.load(std::memory_order_acquire); 413 if (loaded_hooks == nullptr || !HookIsEnabled(PtHookType::PT_HOOK_TYPE_MONITOR_CONTENDED_ENTERED)) { 414 return; 415 } 416 // Atomic with acquire order reason: data race with vmdeath_did_not_happen_ 417 ASSERT(vmdeath_did_not_happen_.load(std::memory_order_acquire)); 418 loaded_hooks->MonitorContendedEntered(thread, object); 419 } 420 ObjectAlloc(BaseClass * klass,ObjectHeader * object,PtThread thread,size_t size)421 void ObjectAlloc(BaseClass *klass, ObjectHeader *object, PtThread thread, size_t size) override 422 { 423 // Atomic with acquire order reason: data race with hooks_ 424 auto *loaded_hooks = hooks_.load(std::memory_order_acquire); 425 if (loaded_hooks == nullptr || !HookIsEnabled(PtHookType::PT_HOOK_TYPE_OBJECT_ALLOC)) { 426 return; 427 } 428 // Atomic with acquire order reason: data race with vmdeath_did_not_happen_ 429 ASSERT(vmdeath_did_not_happen_.load(std::memory_order_acquire)); 430 loaded_hooks->ObjectAlloc(klass, object, thread, size); 431 } 432 433 private: GlobalHookIsEnabled(PtHookType type)434 bool GlobalHookIsEnabled(PtHookType type) const 435 { 436 return global_hook_type_info_.IsEnabled(type); 437 } 438 HookIsEnabled(PtHookType type)439 bool HookIsEnabled(PtHookType type) const 440 { 441 if (GlobalHookIsEnabled(type)) { 442 return true; 443 } 444 445 ManagedThread *managed_thread = ManagedThread::GetCurrent(); 446 ASSERT(managed_thread != nullptr); 447 448 // Check local value 449 return managed_thread->GetPtThreadInfo()->GetHookTypeInfo().IsEnabled(type); 450 } 451 452 std::atomic<PtHooks *> hooks_ {nullptr}; 453 454 PtHookTypeInfo global_hook_type_info_ {true}; 455 456 #ifndef NDEBUG 457 std::atomic_bool vmdeath_did_not_happen_ {true}; 458 #endif 459 }; 460 } // namespace panda::tooling 461 462 #endif // PANDA_TOOLING_PT_HOOKS_WRAPPER_H 463