/* * Copyright (c) 2021 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "runtime/include/stack_walker-inl.h" #include "runtime/entrypoints/entrypoints.h" #include "runtime/include/runtime.h" #include "runtime/include/thread.h" #include "runtime/include/panda_vm.h" #include <iomanip> namespace panda { StackWalker::StackWalker(ManagedThread *thread, UnwindPolicy policy) : StackWalker(thread->GetCurrentFrame(), thread->IsCurrentFrameCompiled(), thread->GetNativePc(), policy) { #ifndef NDEBUG if (Runtime::GetOptions().IsVerifyCallStack()) { StackWalker(thread->GetCurrentFrame(), thread->IsCurrentFrameCompiled(), thread->GetNativePc(), policy) .Verify(); } #endif } // NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init) StackWalker::StackWalker(void *fp, bool is_frame_compiled, uintptr_t npc, UnwindPolicy policy) { frame_ = GetTopFrameFromFp(fp, is_frame_compiled, npc); if (policy == UnwindPolicy::SKIP_INLINED) { inline_depth_ = -1; } } void StackWalker::Reset(ManagedThread *thread) { frame_ = GetTopFrameFromFp(thread->GetCurrentFrame(), thread->IsCurrentFrameCompiled(), thread->GetNativePc()); } /* static */ typename StackWalker::FrameVariant StackWalker::GetTopFrameFromFp(void *ptr, bool is_frame_compiled, uintptr_t npc) { if (!is_frame_compiled) { return reinterpret_cast<Frame *>(ptr); } if (IsBoundaryFrame<FrameKind::INTERPRETER>(ptr)) { auto bp = GetPrevFromBoundary<FrameKind::INTERPRETER>(ptr); if (GetBoundaryFrameMethod<FrameKind::COMPILER>(bp) == BYPASS) { return CreateCFrame(GetPrevFromBoundary<FrameKind::COMPILER>(bp), GetReturnAddressFromBoundary<FrameKind::COMPILER>(bp), GetCalleeStackFromBoundary<FrameKind::COMPILER>(bp)); } return CreateCFrame( GetPrevFromBoundary<FrameKind::INTERPRETER>(ptr), GetReturnAddressFromBoundary<FrameKind::INTERPRETER>(ptr), // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) reinterpret_cast<SlotType *>(ptr) + BoundaryFrame<FrameKind::INTERPRETER>::CALLEES_OFFSET); // NOLINT } return CreateCFrame(reinterpret_cast<SlotType *>(ptr), npc, nullptr); } Method *StackWalker::GetMethod() { ASSERT(HasFrame()); if (!IsCFrame()) { return GetIFrame()->GetMethod(); } auto &cframe = GetCFrame(); ASSERT(cframe.IsJni()); return cframe.GetMethod(); } template <bool create> StackWalker::CFrameType StackWalker::CreateCFrameForC2IBridge(Frame *frame) { auto prev = GetPrevFromBoundary<FrameKind::INTERPRETER>(frame); ASSERT(GetBoundaryFrameMethod<FrameKind::COMPILER>(prev) != FrameBridgeKind::BYPASS); // NOLINTNEXTLINE(readability-braces-around-statements, bugprone-suspicious-semicolon) if constexpr (create) { return CreateCFrame(reinterpret_cast<SlotType *>(prev), GetReturnAddressFromBoundary<FrameKind::INTERPRETER>(frame), GetCalleeStackFromBoundary<FrameKind::INTERPRETER>(frame)); } return CFrameType(prev); } StackWalker::CFrameType StackWalker::CreateCFrame(void *ptr, [[maybe_unused]] uintptr_t npc, [[maybe_unused]] SlotType *callee_stack, [[maybe_unused]] CalleeStorage *prev_callees) { CFrameType cframe(ptr); ASSERT(cframe.IsNativeMethod()); return cframe; } void StackWalker::InitCalleeBuffer(SlotType *callee_stack, CalleeStorage *prev_callees) { if (callee_stack == nullptr && prev_callees == nullptr) { return; } bool prev_is_jni = IsCFrame() ? GetCFrame().IsJni() : false; size_t callee_regs_count = GetCalleeRegsCount(ARCH, false); // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) auto start_slot = callee_stack - callee_regs_count; for (size_t reg = GetFirstCalleeReg(ARCH, false); reg <= GetLastCalleeReg(ARCH, false); reg++) { size_t offset = reg - GetFirstCalleeReg(ARCH, false); // if it's a top cframe or previous frame has saved the register, then copy it from previous frame's stack if (prev_callees == nullptr || prev_is_jni || (prev_callees->callee_regs_mask & (1U << reg)) != 0) { // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) callee_stack_.stack[offset] = // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) start_slot + (callee_regs_count - Popcount(callee_stack_.callee_regs_mask >> reg)); } else { callee_stack_.stack[offset] = prev_callees->stack[offset]; } } size_t callee_vregs_count = GetCalleeRegsCount(ARCH, true); start_slot -= callee_vregs_count; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) for (size_t reg = GetFirstCalleeReg(ARCH, true); reg <= GetLastCalleeReg(ARCH, true); reg++) { size_t offset = callee_regs_count + reg - GetFirstCalleeReg(ARCH, true); // if it's a top cframe or previous frame has saved the register, then copy it from previous frame's stack if (prev_callees == nullptr || prev_is_jni || (prev_callees->callee_fp_regs_mask & (1U << reg)) != 0) { callee_stack_.stack[offset] = // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) start_slot + (callee_vregs_count - Popcount(callee_stack_.callee_fp_regs_mask >> reg)); } else { callee_stack_.stack[offset] = prev_callees->stack[offset]; } } } Frame::VRegister StackWalker::GetVRegValue(size_t vreg_num) { ASSERT(!IsCFrame()); ASSERT(vreg_num < GetIFrame()->GetSize()); return GetIFrame()->GetVReg(vreg_num); } template <typename T> void StackWalker::SetVRegValue(VRegInfo reg_info, T value) { if (IsCFrame()) { auto &cframe = GetCFrame(); if constexpr (sizeof(T) == sizeof(uint64_t)) { // NOLINT cframe.SetVRegValue(reg_info, bit_cast<uint64_t>(value), callee_stack_.stack.data()); } else { // NOLINT static_assert(sizeof(T) == sizeof(uint32_t)); cframe.SetVRegValue(reg_info, static_cast<uint64_t>(bit_cast<uint32_t>(value)), callee_stack_.stack.data()); } } else { auto &vreg = GetIFrame()->GetVReg(reg_info.GetIndex()); if constexpr (std::is_same_v<T, ObjectHeader *>) { // NOLINT ASSERT(vreg.HasObject() && "Trying to change object variable by scalar value"); vreg.SetReference(value); } else { // NOLINT ASSERT(!vreg.HasObject() && "Trying to change object variable by scalar value"); vreg.Set(value); } } } template void StackWalker::SetVRegValue(VRegInfo reg_info, uint32_t value); template void StackWalker::SetVRegValue(VRegInfo reg_info, int32_t value); template void StackWalker::SetVRegValue(VRegInfo reg_info, uint64_t value); template void StackWalker::SetVRegValue(VRegInfo reg_info, int64_t value); template void StackWalker::SetVRegValue(VRegInfo reg_info, float value); template void StackWalker::SetVRegValue(VRegInfo reg_info, double value); template void StackWalker::SetVRegValue(VRegInfo reg_info, ObjectHeader *value); void StackWalker::NextFrame() { if (IsCFrame()) { NextFromCFrame(); } else { NextFromIFrame(); } } void StackWalker::NextFromCFrame() { if (IsInlined()) { if (policy_ != UnwindPolicy::SKIP_INLINED) { inline_depth_--; return; } inline_depth_ = -1; } if (policy_ == UnwindPolicy::ONLY_INLINED) { frame_ = nullptr; return; } auto prev = GetCFrame().GetPrevFrame(); if (prev == nullptr) { frame_ = nullptr; return; } auto frame_method = GetBoundaryFrameMethod<FrameKind::COMPILER>(prev); switch (frame_method) { case FrameBridgeKind::INTERPRETER_TO_COMPILED_CODE: { auto prev_frame = reinterpret_cast<Frame *>(GetPrevFromBoundary<FrameKind::COMPILER>(prev)); if (prev_frame != nullptr && IsBoundaryFrame<FrameKind::INTERPRETER>(prev_frame)) { frame_ = CreateCFrameForC2IBridge<true>(prev_frame); break; } frame_ = reinterpret_cast<Frame *>(prev_frame); break; } case FrameBridgeKind::BYPASS: { auto prev_frame = reinterpret_cast<Frame *>(GetPrevFromBoundary<FrameKind::COMPILER>(prev)); if (prev_frame != nullptr && IsBoundaryFrame<FrameKind::INTERPRETER>(prev_frame)) { frame_ = CreateCFrameForC2IBridge<true>(prev_frame); break; } frame_ = CreateCFrame(reinterpret_cast<SlotType *>(GetPrevFromBoundary<FrameKind::COMPILER>(prev)), GetReturnAddressFromBoundary<FrameKind::COMPILER>(prev), GetCalleeStackFromBoundary<FrameKind::COMPILER>(prev)); break; } default: prev_callee_stack_ = callee_stack_; frame_ = CreateCFrame(reinterpret_cast<SlotType *>(prev), GetCFrame().GetLr(), GetCFrame().GetCalleeSaveStack(), &prev_callee_stack_); break; } } void StackWalker::NextFromIFrame() { if (policy_ == UnwindPolicy::ONLY_INLINED) { frame_ = nullptr; return; } auto prev = GetIFrame()->GetPrevFrame(); if (prev == nullptr) { frame_ = nullptr; return; } if (IsBoundaryFrame<FrameKind::INTERPRETER>(prev)) { auto bp = GetPrevFromBoundary<FrameKind::INTERPRETER>(prev); if (GetBoundaryFrameMethod<FrameKind::COMPILER>(bp) == BYPASS) { frame_ = CreateCFrame(GetPrevFromBoundary<FrameKind::COMPILER>(bp), GetReturnAddressFromBoundary<FrameKind::COMPILER>(bp), GetCalleeStackFromBoundary<FrameKind::COMPILER>(bp)); } else { frame_ = CreateCFrameForC2IBridge<true>(prev); } } else { frame_ = reinterpret_cast<Frame *>(prev); } } FrameAccessor StackWalker::GetNextFrame() { if (IsCFrame()) { if (IsInlined()) { return FrameAccessor(frame_); } auto prev = GetCFrame().GetPrevFrame(); if (prev == nullptr) { return FrameAccessor(nullptr); } auto frame_method = GetBoundaryFrameMethod<FrameKind::COMPILER>(prev); switch (frame_method) { case FrameBridgeKind::INTERPRETER_TO_COMPILED_CODE: { auto prev_frame = reinterpret_cast<Frame *>(GetPrevFromBoundary<FrameKind::COMPILER>(prev)); if (prev_frame != nullptr && IsBoundaryFrame<FrameKind::INTERPRETER>(prev_frame)) { return FrameAccessor(CreateCFrameForC2IBridge<false>(prev_frame)); } return FrameAccessor(prev_frame); } case FrameBridgeKind::BYPASS: { auto prev_frame = reinterpret_cast<Frame *>(GetPrevFromBoundary<FrameKind::COMPILER>(prev)); if (prev_frame != nullptr && IsBoundaryFrame<FrameKind::INTERPRETER>(prev_frame)) { return FrameAccessor(CreateCFrameForC2IBridge<false>(prev_frame)); } return FrameAccessor( CFrameType(reinterpret_cast<SlotType *>(GetPrevFromBoundary<FrameKind::COMPILER>(prev)))); } default: return FrameAccessor(CFrameType(reinterpret_cast<SlotType *>(prev))); } } else { auto prev = GetIFrame()->GetPrevFrame(); if (prev == nullptr) { return FrameAccessor(nullptr); } if (IsBoundaryFrame<FrameKind::INTERPRETER>(prev)) { auto bp = GetPrevFromBoundary<FrameKind::INTERPRETER>(prev); if (GetBoundaryFrameMethod<FrameKind::COMPILER>(bp) == BYPASS) { return FrameAccessor(CreateCFrame(GetPrevFromBoundary<FrameKind::COMPILER>(bp), GetReturnAddressFromBoundary<FrameKind::COMPILER>(bp), GetCalleeStackFromBoundary<FrameKind::COMPILER>(bp))); } return FrameAccessor(CreateCFrameForC2IBridge<false>(prev)); } return FrameAccessor(reinterpret_cast<Frame *>(prev)); } } FrameKind StackWalker::GetPreviousFrameKind() const { if (IsCFrame()) { auto prev = GetCFrame().GetPrevFrame(); if (prev == nullptr) { return FrameKind::NONE; } if (IsBoundaryFrame<FrameKind::COMPILER>(prev)) { return FrameKind::INTERPRETER; } return FrameKind::COMPILER; } auto prev = GetIFrame()->GetPrevFrame(); if (prev == nullptr) { return FrameKind::NONE; } if (IsBoundaryFrame<FrameKind::INTERPRETER>(prev)) { return FrameKind::COMPILER; } return FrameKind::INTERPRETER; } bool StackWalker::IsCompilerBoundFrame(SlotType *prev) { if (IsBoundaryFrame<FrameKind::COMPILER>(prev)) { return true; } if (GetBoundaryFrameMethod<FrameKind::COMPILER>(prev) == FrameBridgeKind::BYPASS) { auto prev_frame = reinterpret_cast<Frame *>(GetPrevFromBoundary<FrameKind::COMPILER>(prev)); // Case for clinit: // Compiled code -> C2I -> InitializeClass -> call clinit -> I2C -> compiled code for clinit if (prev_frame != nullptr && IsBoundaryFrame<FrameKind::INTERPRETER>(prev_frame)) { return true; } } return false; } Frame *StackWalker::ConvertToIFrame([[maybe_unused]] FrameKind *prev_frame_kind, [[maybe_unused]] uint32_t *num_inlined_methods) { if (!IsCFrame()) { return GetIFrame(); } UNREACHABLE(); } bool StackWalker::IsDynamicMethod() const { // Dynamic method may have no class return GetMethod()->GetClass() == nullptr || Runtime::GetCurrent()->GetLanguageContext(*GetMethod()).IsDynamicLanguage(); } void StackWalker::Verify() { #ifndef NDEBUG for (; HasFrame(); NextFrame()) { ASSERT(GetMethod() != nullptr); [[maybe_unused]] bool is_dynamic = IsDynamicMethod(); IterateVRegsWithInfo([this, is_dynamic]([[maybe_unused]] const auto ®_info, const auto &vreg) { if (vreg.HasObject()) { // In dynamic methods all reg_infos are generic values. // Use Frame::VRegister::HasObject() to detect objects ASSERT(is_dynamic || reg_info.IsObject()); if (ObjectHeader *object = vreg.GetReference(); object != nullptr && IsCFrame()) { // CODECHECK-NOLINTNEXTLINE(C_RULE_ID_FUNCTION_NESTING_LEVEL) if (auto *bcls = object->ClassAddr<BaseClass>(); bcls != nullptr && !(bcls->IsDynamicClass())) { auto cls = static_cast<Class *>(bcls); cls->GetName(); } } } else { ASSERT(!reg_info.IsObject()); vreg.GetLong(); } return true; }); if (IsCFrame()) { IterateObjects([](const auto &vreg) { ASSERT(vreg.HasObject()); if (ObjectHeader *object = vreg.GetReference(); object != nullptr) { // CODECHECK-NOLINTNEXTLINE(C_RULE_ID_FUNCTION_NESTING_LEVEL) if (auto *bcls = object->ClassAddr<BaseClass>(); bcls != nullptr && !(bcls->IsDynamicClass())) { auto cls = static_cast<Class *>(bcls); cls->GetName(); } } return true; }); } } #endif // ifndef NDEBUG } // Dump function change StackWalker object-state, that's why it may be called only // with rvalue reference. // CODECHECK-NOLINTNEXTLINE(C_RULE_ID_COMMENT_ADDSPASE,C_RULE_ID_FUNCTION_SIZE) void StackWalker::Dump(std::ostream &os, bool print_vregs /* = false */) && { [[maybe_unused]] static constexpr size_t WIDTH_INDEX = 4; [[maybe_unused]] static constexpr size_t WIDTH_REG = 4; [[maybe_unused]] static constexpr size_t WIDTH_FRAME = 8; [[maybe_unused]] static constexpr size_t WIDTH_LOCATION = 12; [[maybe_unused]] static constexpr size_t WIDTH_TYPE = 20; size_t frame_index = 0; os << "Panda call stack:\n"; for (; HasFrame(); NextFrame()) { os << std::setw(WIDTH_INDEX) << std::setfill(' ') << std::right << std::dec << frame_index << ": " << std::setfill('0'); os << std::setw(WIDTH_FRAME) << std::hex; os << (IsCFrame() ? reinterpret_cast<Frame *>(GetCFrame().GetFrameOrigin()) : GetIFrame()) << " in "; DumpFrame(os); os << std::endl; if (print_vregs) { IterateVRegsWithInfo([this, &os](auto reg_info, auto vreg) { os << " " << std::setw(WIDTH_REG) << std::setfill(' ') << std::right << (reg_info.IsAccumulator() ? "acc" : (std::string("v") + std::to_string(reg_info.GetIndex()))); os << " = " << std::left; os << std::setw(WIDTH_TYPE) << std::setfill(' '); switch (reg_info.GetType()) { case VRegInfo::Type::INT64: case VRegInfo::Type::INT32: os << std::dec << vreg.GetLong(); break; case VRegInfo::Type::FLOAT64: case VRegInfo::Type::FLOAT32: os << vreg.GetDouble(); break; case VRegInfo::Type::BOOL: os << (vreg.Get() ? "true" : "false"); break; case VRegInfo::Type::OBJECT: os << vreg.GetReference(); break; case VRegInfo::Type::UNDEFINED: os << "undefined"; break; default: os << "unknown"; break; } os << std::setw(WIDTH_LOCATION) << std::setfill(' ') << reg_info.GetTypeString(); // NOLINT if (IsCFrame()) { os << reg_info.GetLocationString() << ":" << std::dec << helpers::ToSigned(reg_info.GetValue()); } else { os << '-'; } os << std::endl; return true; }); } frame_index++; } } void StackWalker::DumpFrame(std::ostream &os) { auto method = GetMethod(); os << method->GetFullName(); if (IsCFrame()) { if (GetCFrame().IsJni()) { os << " (native)"; } else { os << " (compiled" << (GetCFrame().IsOsr() ? "/osr" : "") << ": npc=" << GetNativePc() << (IsInlined() ? ", inlined) " : ") "); } } else { os << " (managed)"; } } } // namespace panda