• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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