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