• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /**
2  * Copyright (c) 2021-2025 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 #include "signal_handler.h"
17 #include "utils/logger.h"
18 #include <algorithm>
19 #include <cstdlib>
20 #include "include/method.h"
21 #include "include/runtime.h"
22 #include "include/panda_vm.h"
23 #include <sys/ucontext.h>
24 #include "compiler_options.h"
25 #include "code_info/code_info.h"
26 #include "include/stack_walker.h"
27 #include "tooling/pt_thread_info.h"
28 #include "tooling/sampler/sampling_profiler.h"
29 #include "runtime/runtime_helpers.h"
30 
31 #ifdef PANDA_TARGET_AMD64
32 extern "C" void StackOverflowExceptionEntrypointTrampoline();
33 #endif
34 
35 namespace ark {
36 
UseDebuggerdSignalHandler(int sig)37 static void UseDebuggerdSignalHandler(int sig)
38 {
39     LOG(WARNING, RUNTIME) << "panda vm can not handle sig " << sig << ", call next handler";
40 }
41 
CallSignalActionHandler(int sig,siginfo_t * info,void * context)42 static bool CallSignalActionHandler(int sig, siginfo_t *info, void *context)
43 {  // NOLINT
44     return Runtime::GetCurrent()->GetSignalManager()->SignalActionHandler(sig, info, context);
45 }
46 
SignalActionHandler(int sig,siginfo_t * info,void * context)47 bool SignalManager::SignalActionHandler(int sig, siginfo_t *info, void *context)
48 {
49     ark::Logger::Sync();
50     LOG(DEBUG, RUNTIME) << "Handle signal " << sig << " pc: " << std::hex << SignalContext(info).GetPC();
51 
52     if (InCompiledCode(info, context, true)) {
53         for (const auto &handler : compiledCodeHandler_) {
54             if (handler->Action(sig, info, context)) {
55                 return true;
56             }
57         }
58     }
59     for (const auto &handler : otherHandlers_) {
60         if (handler->Action(sig, info, context)) {
61             return true;
62         }
63     }
64 
65     // Use the default exception handler function.
66     UseDebuggerdSignalHandler(sig);
67     return false;
68 }
69 
InCompiledCode(const siginfo_t * siginfo,const void * context,bool checkBytecodePc) const70 bool SignalManager::InCompiledCode([[maybe_unused]] const siginfo_t *siginfo, [[maybe_unused]] const void *context,
71                                    [[maybe_unused]] bool checkBytecodePc) const
72 {
73     // NOTE(00510180) leak judge GetMethodAndReturnPcAndSp
74     return true;
75 }
76 
AddHandler(SignalHandler * handler,bool oatCode)77 void SignalManager::AddHandler(SignalHandler *handler, bool oatCode)
78 {
79     if (oatCode) {
80         compiledCodeHandler_.push_back(handler);
81     } else {
82         otherHandlers_.push_back(handler);
83     }
84 }
85 
RemoveHandler(SignalHandler * handler)86 void SignalManager::RemoveHandler(SignalHandler *handler)
87 {
88     auto itOat = std::find(compiledCodeHandler_.begin(), compiledCodeHandler_.end(), handler);
89     if (itOat != compiledCodeHandler_.end()) {
90         compiledCodeHandler_.erase(itOat);
91         return;
92     }
93     auto itOther = std::find(otherHandlers_.begin(), otherHandlers_.end(), handler);
94     if (itOther != otherHandlers_.end()) {
95         otherHandlers_.erase(itOther);
96         return;
97     }
98     LOG(FATAL, RUNTIME) << "handler doesn't exist: " << handler;
99 }
100 
InitSignals()101 void SignalManager::InitSignals()
102 {
103     if (isInit_) {
104         return;
105     }
106 
107     sigset_t mask;
108     sigfillset(&mask);
109     sigdelset(&mask, SIGABRT);
110     sigdelset(&mask, SIGBUS);
111     sigdelset(&mask, SIGFPE);
112     sigdelset(&mask, SIGILL);
113     sigdelset(&mask, SIGSEGV);
114 
115     ClearSignalHooksHandlersArray();
116 
117     // if running in phone,Sigchain will work,AddSpecialSignalHandlerFn in sighook will not be used
118     SigchainAction sigchainAction = {
119         CallSignalActionHandler,
120         mask,
121         SA_SIGINFO,
122     };
123     AddSpecialSignalHandlerFn(SIGSEGV, &sigchainAction);
124 
125     isInit_ = true;
126 
127     for (auto tmp : compiledCodeHandler_) {
128         allocator_->Delete(tmp);
129     }
130     compiledCodeHandler_.clear();
131     for (auto tmp : otherHandlers_) {
132         allocator_->Delete(tmp);
133     }
134     otherHandlers_.clear();
135 }
136 
GetMethodAndReturnPcAndSp(const siginfo_t * siginfo,const void * context,const Method ** outMethod,const uintptr_t * outReturnPc,const uintptr_t * outSp)137 void SignalManager::GetMethodAndReturnPcAndSp([[maybe_unused]] const siginfo_t *siginfo,
138                                               [[maybe_unused]] const void *context,
139                                               [[maybe_unused]] const Method **outMethod,
140                                               [[maybe_unused]] const uintptr_t *outReturnPc,
141                                               [[maybe_unused]] const uintptr_t *outSp)
142 {
143     // just stub now
144 }
145 
DeleteHandlersArray()146 void SignalManager::DeleteHandlersArray()
147 {
148     if (isInit_) {
149         for (auto tmp : compiledCodeHandler_) {
150             allocator_->Delete(tmp);
151         }
152         compiledCodeHandler_.clear();
153         for (auto tmp : otherHandlers_) {
154             allocator_->Delete(tmp);
155         }
156         otherHandlers_.clear();
157         RemoveSpecialSignalHandlerFn(SIGSEGV, CallSignalActionHandler);
158         isInit_ = false;
159     }
160 }
161 
InAllocatedCodeRange(uintptr_t pc)162 bool InAllocatedCodeRange(uintptr_t pc)
163 {
164     Thread *thread = Thread::GetCurrent();
165     if (thread == nullptr) {
166         // Current thread is not attatched to any of the VMs
167         return false;
168     }
169 
170     if (Runtime::GetCurrent()->GetClassLinker()->GetAotManager()->InAotFileRange(pc)) {
171         return true;
172     }
173 
174     auto heapManager = thread->GetVM()->GetHeapManager();
175     if (heapManager == nullptr) {
176         return false;
177     }
178     auto codeAllocator = heapManager->GetCodeAllocator();
179     if (codeAllocator == nullptr) {
180         return false;
181     }
182     return codeAllocator->InAllocatedCodeRange(ToVoidPtr(pc));
183 }
184 
IsInvalidPointer(uintptr_t addr)185 bool IsInvalidPointer(uintptr_t addr)
186 {
187     if (addr == 0) {
188         return true;
189     }
190 
191     return !IsAligned(addr, alignof(uintptr_t));
192 }
193 
194 // This is the way to get compiled method entry point
195 // FP regsiter -> stack -> method -> compilerEntryPoint
FindCompilerEntrypoint(const uintptr_t * fp)196 static uintptr_t FindCompilerEntrypoint(const uintptr_t *fp)
197 {
198     // Compiled code stack frame:
199     // +----------------+
200     // | Return address |
201     // +----------------+ <- Frame pointer
202     // | Frame pointer  |
203     // +----------------+
204     // | ark::Method* |
205     // +----------------+
206     const int compiledFrameMethodOffset = BoundaryFrame<FrameKind::COMPILER>::METHOD_OFFSET;
207 
208     if (IsInvalidPointer(reinterpret_cast<uintptr_t>(fp))) {
209         return 0;
210     }
211     // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
212     uintptr_t pmethod = fp[compiledFrameMethodOffset];
213     if (IsInvalidPointer(pmethod)) {
214         return 0;
215     }
216 
217     // it is important for GetCompiledEntryPoint method to have a lock-free implementation
218     auto entrypoint = reinterpret_cast<uintptr_t>((reinterpret_cast<Method *>(pmethod))->GetCompiledEntryPoint());
219     if (IsInvalidPointer(entrypoint)) {
220         return 0;
221     }
222 
223     if (!InAllocatedCodeRange(entrypoint)) {
224         LOG(INFO, RUNTIME) << "Runtime SEGV handler: the entrypoint is not from JIT code";
225         return 0;
226     }
227 
228     if (!compiler::CodeInfo::VerifyCompiledEntry(entrypoint)) {
229         // what we have found is not a compiled method
230         return 0;
231     }
232 
233     return entrypoint;
234 }
235 
236 // Estimate if the PC belongs to FindCompilerEntrypoint
PCInFindCompilerEntrypoint(uintptr_t pc)237 static bool PCInFindCompilerEntrypoint(uintptr_t pc)
238 {
239     auto func = ToUintPtr(&FindCompilerEntrypoint);
240     const unsigned thisMethodSizeEstimation = 0x1000;  // there is no way to find exact compiled method size
241     return func < pc && pc < func + thisMethodSizeEstimation;
242 }
243 
UpdateReturnAddress(SignalContext & signalContext,uintptr_t newAddress)244 static void UpdateReturnAddress(SignalContext &signalContext, uintptr_t newAddress)
245 {
246 #if (defined(PANDA_TARGET_ARM64) || defined(PANDA_TARGET_ARM32))
247     signalContext.SetLR(newAddress);
248 #elif defined(PANDA_TARGET_AMD64)
249     auto *sp = reinterpret_cast<uintptr_t *>(signalContext.GetSP() - sizeof(uintptr_t));
250     *sp = newAddress;
251     signalContext.SetSP(reinterpret_cast<uintptr_t>(sp));
252 #endif
253 }
254 
255 struct NullCheckEPInfo {
256     uintptr_t handlerPc;
257     uintptr_t newPc;
258     bool isLLVM;
259 };
260 
LookupNullCheckEntrypoint(uintptr_t pc,uintptr_t entrypoint)261 static std::optional<NullCheckEPInfo> LookupNullCheckEntrypoint(uintptr_t pc, uintptr_t entrypoint)
262 {
263     compiler::CodeInfo codeinfo(compiler::CodeInfo::GetCodeOriginFromEntryPoint(ToVoidPtr(entrypoint)));
264     if ((pc < entrypoint) || (pc > entrypoint + codeinfo.GetCodeSize())) {
265         // we are not in a compiled method
266         return std::nullopt;
267     }
268 
269     for (auto const &icheck : codeinfo.GetImplicitNullChecksTable()) {
270         uintptr_t nullCheckAddr = entrypoint + icheck.GetInstNativePc();
271         auto offset = icheck.GetOffset();
272         static constexpr uint32_t LLVM_HANDLER_TAG = 1U << 31U;
273         if (pc == nullCheckAddr && (LLVM_HANDLER_TAG & offset) != 0) {
274             // Code was compiled by LLVM.
275             // We jump to the handler pc, which will call NullPointerException on its own.
276             // We do not jump to NullPointerExceptionBridge directly here because LLVM code does not have stackmap on
277             // memory instructions
278             uintptr_t handlerPc = (~LLVM_HANDLER_TAG & offset) + entrypoint;
279             return NullCheckEPInfo {handlerPc, nullCheckAddr, true};
280         }
281         // We insert information about implicit nullcheck after mem instruction,
282         // because encoder can insert memory calculation before the instruction, and we don't know real address:
283         //   addr |               |
284         //    |   +---------------+  <--- nullCheckAddr - offset
285         //    |   | address calc  |
286         //    |   | memory inst   |  <--- pc
287         //    V   +---------------+  <--- nullCheckAddr
288         //        |               |
289         if (pc < nullCheckAddr && pc + offset >= nullCheckAddr) {
290             uintptr_t handlerPc = ToUintPtr(NullPointerExceptionBridge);
291             return NullCheckEPInfo {handlerPc, nullCheckAddr, false};
292         }
293     }
294 
295     LOG(INFO, RUNTIME) << "SEGV can't be handled. No matching entry found in the NullCheck table.\n"
296                        << "PC: " << std::hex << pc;
297     for (auto const &icheck : codeinfo.GetImplicitNullChecksTable()) {
298         LOG(INFO, RUNTIME) << "nullcheck: " << std::hex << (entrypoint + icheck.GetInstNativePc());
299     }
300     return std::nullopt;
301 }
302 
SamplerSigSegvHandler(int sig,siginfo_t * siginfo,void * context)303 static void SamplerSigSegvHandler([[maybe_unused]] int sig, [[maybe_unused]] siginfo_t *siginfo,
304                                   [[maybe_unused]] void *context)
305 {
306     auto mthread = ManagedThread::GetCurrent();
307     ASSERT(mthread != nullptr);
308 
309     int numToReturn = 1;
310     // NOLINTNEXTLINE(cert-err52-cpp)
311     longjmp(mthread->GetPtThreadInfo()->GetSamplingInfo()->GetSigSegvJmpEnv(), numToReturn);
312 }
313 
Action(int sig,siginfo_t * siginfo,void * context)314 bool SamplingProfilerHandler::Action(int sig, [[maybe_unused]] siginfo_t *siginfo, void *context)
315 {
316     if (sig != SIGSEGV) {
317         return false;
318     }
319     auto *thread = ManagedThread::GetCurrent();
320     if (thread == nullptr) {
321         return false;
322     }
323 
324     auto *sampler = Runtime::GetCurrent()->GetTools().GetSamplingProfiler();
325     if (sampler == nullptr || !sampler->IsSegvHandlerEnable()) {
326         return false;
327     }
328 
329     auto *samplingInfo = thread->GetPtThreadInfo()->GetSamplingInfo();
330     if (samplingInfo == nullptr || !samplingInfo->IsThreadSampling()) {
331         return false;
332     }
333 
334     SignalContext signalContext(context);
335     signalContext.SetPC(reinterpret_cast<uintptr_t>(&SamplerSigSegvHandler));
336     return true;
337 }
338 
339 static constexpr uintptr_t MAX_MANAGED_OBJECT_SIZE = 1U << 30U;
340 
Action(int sig,siginfo_t * siginfo,void * context)341 bool NullPointerHandler::Action(int sig, siginfo_t *siginfo, void *context)
342 {
343     if (sig != SIGSEGV) {
344         return false;
345     }
346     SignalContext signalContext(context);
347 
348     uintptr_t const pc = signalContext.GetPC();
349     if (!InAllocatedCodeRange(pc) || PCInFindCompilerEntrypoint(pc)) {
350         return false;
351     }
352     if (ToUintPtr(siginfo->si_addr) > MAX_MANAGED_OBJECT_SIZE) {  // NOLINT(cppcoreguidelines-pro-type-union-access)
353         return false;
354     }
355     uintptr_t const entrypoint = FindCompilerEntrypoint(signalContext.GetFP());
356     if (entrypoint == 0) {
357         return false;
358     }
359 
360     if (std::optional<NullCheckEPInfo> info = LookupNullCheckEntrypoint(pc, entrypoint); info.has_value()) {
361         LOG(DEBUG, RUNTIME) << "Transition to NullCheck entrypoint, signal: " << sig << " new pc:" << std::hex
362                             << info->newPc;
363         if (!info->isLLVM) {
364             UpdateReturnAddress(signalContext, info->newPc);
365         }
366         signalContext.SetPC(info->handlerPc);
367         EVENT_IMPLICIT_NULLCHECK(info->newPc);
368         /* NullPointer has been check in aot or here now,then return to interpreter, so exception not build here
369          * issue #1437
370          * ark::ThrowNullPointerException();
371          */
372         return true;
373     }
374     return false;
375 }
376 
377 NullPointerHandler::~NullPointerHandler() = default;
378 
Action(int sig,siginfo_t * siginfo,void * context)379 bool StackOverflowHandler::Action(int sig, [[maybe_unused]] siginfo_t *siginfo, [[maybe_unused]] void *context)
380 {
381     if (sig != SIGSEGV) {
382         return false;
383     }
384     auto *thread = ManagedThread::GetCurrent();
385     if (thread == nullptr) {
386         return false;
387     }
388 
389     SignalContext signalContext(context);
390     auto memCheckLocation = signalContext.GetSP() - ManagedThread::GetStackOverflowCheckOffset();
391     // NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access)
392     auto memFaultLocation = ToUintPtr(siginfo->si_addr);
393     if (memCheckLocation != memFaultLocation) {
394         return false;
395     }
396 
397     LOG(DEBUG, RUNTIME) << "Stack overflow occurred";
398 
399     // StackOverflow stackmap has zero address
400     thread->SetNativePc(0);
401     // Set compiler Frame in Thread
402     thread->SetCurrentFrame(reinterpret_cast<Frame *>(signalContext.GetFP()));
403 #ifdef PANDA_TARGET_AMD64
404     signalContext.SetPC(reinterpret_cast<uintptr_t>(StackOverflowExceptionEntrypointTrampoline));
405 #else
406     /* To save/restore callee-saved regs we get into StackOverflowExceptionEntrypoint
407      * by means of StackOverflowExceptionBridge.
408      * The bridge stores LR to ManagedThread.npc, which is used by StackWalker::CreateCFrame,
409      * and it must be 0 in case of StackOverflow.
410      */
411     signalContext.SetLR(0);
412     signalContext.SetPC(reinterpret_cast<uintptr_t>(StackOverflowExceptionBridge));
413 #endif
414 
415     return true;
416 }
417 
Action(int sig,siginfo_t * siginfo,void * context)418 bool CrashFallbackDumpHandler::Action(int sig, [[maybe_unused]] siginfo_t *siginfo, void *context)
419 {
420     if (sig != SIGSEGV) {
421         return false;
422     }
423     SignalContext signalContext(context);
424 
425     auto thread = ManagedThread::GetCurrent();
426     if (thread == nullptr) {
427         auto vmThread = Thread::GetCurrent();
428         if (vmThread == nullptr) {
429             LOG(ERROR, RUNTIME) << "SIGSEGV in unknown thread";
430             return false;
431         }
432         LOG(ERROR, RUNTIME) << "SIGSEGV in runtime thread: threadType="
433                             << helpers::ToUnderlying(vmThread->GetThreadType());
434         PrintStack(Logger::Message(Logger::Level::ERROR, Logger::Component::RUNTIME, false).GetStream());
435         return false;
436     }
437     if (thread->IsInNativeCode()) {
438         LOG(ERROR, RUNTIME) << "SIGSEGV in managed thread (native code)";
439         return false;
440     }
441     if (InAllocatedCodeRange(signalContext.GetPC())) {
442         LOG(ERROR, RUNTIME) << "SIGSEGV in managed thread (managed compiled code)";
443     } else {
444         LOG(ERROR, RUNTIME) << "SIGSEGV in managed thread (managed code)";
445     }
446     ark::PrintStackTrace();
447     PrintStack(Logger::Message(Logger::Level::ERROR, Logger::Component::RUNTIME, false).GetStream());
448     return false;
449 }
450 
451 }  // namespace ark
452