// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. #include "vm/stack_frame.h" #include "platform/memory_sanitizer.h" #include "vm/code_descriptors.h" #include "vm/compiler/runtime_api.h" #include "vm/heap/become.h" #include "vm/isolate.h" #include "vm/object.h" #include "vm/object_store.h" #include "vm/os.h" #include "vm/parser.h" #include "vm/raw_object.h" #include "vm/reusable_handles.h" #include "vm/reverse_pc_lookup_cache.h" #include "vm/scopes.h" #include "vm/stub_code.h" #include "vm/visitor.h" #if !defined(DART_PRECOMPILED_RUNTIME) #include "vm/deopt_instructions.h" #endif // !defined(DART_PRECOMPILED_RUNTIME) namespace dart { const FrameLayout invalid_frame_layout = { /*.first_object_from_fp = */ -1, /*.last_fixed_object_from_fp = */ -1, /*.param_end_from_fp = */ -1, /*.last_param_from_entry_sp = */ -1, /*.first_local_from_fp = */ -1, /*.dart_fixed_frame_size = */ -1, /*.saved_caller_pp_from_fp = */ -1, /*.saved_caller_fp_from_fp = */ -1, /*.saved_caller_pc_from_fp = */ -1, /*.code_from_fp = */ -1, /*.exit_link_slot_from_entry_fp = */ -1, }; const FrameLayout default_frame_layout = { /*.first_object_from_fp = */ kFirstObjectSlotFromFp, /*.last_fixed_object_from_fp = */ kLastFixedObjectSlotFromFp, /*.param_end_from_fp = */ kParamEndSlotFromFp, /*.last_param_from_entry_sp = */ kLastParamSlotFromEntrySp, /*.first_local_from_fp = */ kFirstLocalSlotFromFp, /*.dart_fixed_frame_size = */ kDartFrameFixedSize, /*.saved_caller_pp_from_fp = */ kSavedCallerPpSlotFromFp, /*.saved_caller_fp_from_fp = */ kSavedCallerFpSlotFromFp, /*.saved_caller_pc_from_fp = */ kSavedCallerPcSlotFromFp, /*.code_from_fp = */ kPcMarkerSlotFromFp, /*.exit_link_slot_from_entry_fp = */ kExitLinkSlotFromEntryFp, }; const FrameLayout bare_instructions_frame_layout = { /*.first_object_from_pc =*/kFirstObjectSlotFromFp, // No saved PP slot. /*.last_fixed_object_from_fp = */ kLastFixedObjectSlotFromFp + 2, // No saved CODE, PP slots /*.param_end_from_fp = */ kParamEndSlotFromFp, /*.last_param_from_entry_sp = */ kLastParamSlotFromEntrySp, /*.first_local_from_fp =*/kFirstLocalSlotFromFp + 2, // No saved CODE, PP slots. /*.dart_fixed_frame_size =*/kDartFrameFixedSize - 2, // No saved CODE, PP slots. /*.saved_caller_pp_from_fp = */ 0, // No saved PP slot. /*.saved_caller_fp_from_fp = */ kSavedCallerFpSlotFromFp, /*.saved_caller_pc_from_fp = */ kSavedCallerPcSlotFromFp, /*.code_from_fp = */ 0, // No saved CODE /*.exit_link_slot_from_entry_fp = */ kExitLinkSlotFromEntryFp, }; namespace compiler { namespace target { FrameLayout frame_layout = invalid_frame_layout; } } // namespace compiler FrameLayout runtime_frame_layout = invalid_frame_layout; intptr_t FrameLayout::FrameSlotForVariable( const LocalVariable* variable) const { ASSERT(!variable->is_captured()); return this->FrameSlotForVariableIndex(variable->index().value()); } intptr_t FrameLayout::FrameSlotForVariableIndex(intptr_t variable_index) const { // Variable indices are: // [1, 2, ..., M] for the M parameters. // [0, -1, -2, ... -(N-1)] for the N [LocalVariable]s // See (runtime/vm/scopes.h) return variable_index <= 0 ? (variable_index + first_local_from_fp) : (variable_index + param_end_from_fp); } void FrameLayout::Init() { // By default we use frames with CODE_REG/PP in the frame. compiler::target::frame_layout = default_frame_layout; runtime_frame_layout = default_frame_layout; if (FLAG_precompiled_mode) { compiler::target::frame_layout = bare_instructions_frame_layout; } #if defined(DART_PRECOMPILED_RUNTIME) if (FLAG_precompiled_mode) { compiler::target::frame_layout = invalid_frame_layout; runtime_frame_layout = bare_instructions_frame_layout; } #endif } bool StackFrame::IsBareInstructionsDartFrame() const { if (!FLAG_precompiled_mode) { return false; } NoSafepointScope no_safepoint; Code code; code = ReversePc::Lookup(this->isolate_group(), pc(), /*is_return_address=*/true); if (!code.IsNull()) { auto const cid = code.OwnerClassId(); ASSERT(cid == kNullCid || cid == kClassCid || cid == kFunctionCid); return cid == kFunctionCid; } return false; } bool StackFrame::IsBareInstructionsStubFrame() const { if (!FLAG_precompiled_mode) { return false; } NoSafepointScope no_safepoint; Code code; code = ReversePc::Lookup(this->isolate_group(), pc(), /*is_return_address=*/true); if (!code.IsNull()) { auto const cid = code.OwnerClassId(); ASSERT(cid == kNullCid || cid == kClassCid || cid == kFunctionCid); return cid == kNullCid || cid == kClassCid; } return false; } bool StackFrame::IsStubFrame() const { if (is_interpreted()) { return false; } if (FLAG_precompiled_mode) { return IsBareInstructionsStubFrame(); } ASSERT(!(IsEntryFrame() || IsExitFrame())); #if !defined(DART_HOST_OS_WINDOWS) && !defined(DART_HOST_OS_FUCHSIA) // On Windows and Fuchsia, the profiler calls this from a separate thread // where Thread::Current() is nullptr, so we cannot create a NoSafepointScope. NoSafepointScope no_safepoint; #endif CodePtr code = GetCodeObject(); ASSERT(code != Object::null()); auto const cid = Code::OwnerClassIdOf(code); ASSERT(cid == kNullCid || cid == kClassCid || cid == kFunctionCid); return cid == kNullCid || cid == kClassCid; } const char* StackFrame::ToCString() const { ASSERT(thread_ == Thread::Current()); Zone* zone = Thread::Current()->zone(); const char* name = nullptr; uword start = 0; if (is_interpreted()) { #if defined(DART_BYTECODE_INTERPRETER) if (IsEntryFrame()) { name = "[Interpreter] Entry frame"; } else if (IsExitFrame()) { name = "[Interpreter] Exit frame"; } else { const Bytecode& bytecode = Bytecode::Handle(zone, LookupDartBytecode()); if (bytecode.IsNull()) { name = "Cannot find bytecode object"; } else { name = bytecode.FullyQualifiedName(); start = bytecode.PayloadStart(); } } #else UNREACHABLE(); #endif // defined(DART_BYTECODE_INTERPRETER) } else if (IsEntryFrame()) { name = "[Stub] Entry frame"; } else if (IsExitFrame()) { name = "[Stub] Exit frame"; } else { const Code& code = Code::Handle(zone, GetCodeObject()); if (code.IsNull()) { name = "Cannot find code object"; } else { name = code.QualifiedName(NameFormattingParams(Object::kInternalName)); start = code.PayloadStart(); } } const uword offset = start == 0 ? 0 : pc() - start; if (offset == 0) { return zone->PrintToString(" pc 0x%" Pp " fp 0x%" Pp " sp 0x%" Pp " %s", pc(), fp(), sp(), name); } else { return zone->PrintToString(" pc 0x%" Pp " fp 0x%" Pp " sp 0x%" Pp " %s+0x%" Px, pc(), fp(), sp(), name, offset); } } void ExitFrame::VisitObjectPointers(ObjectPointerVisitor* visitor) { ASSERT(visitor != nullptr); // Visit pc marker and saved pool pointer, or, for interpreted frame, code // object and function object. ObjectPtr* last_fixed = reinterpret_cast(fp()) + (is_interpreted() ? kKBCLastFixedObjectSlotFromFp : runtime_frame_layout.first_object_from_fp); ObjectPtr* first_fixed = reinterpret_cast(fp()) + (is_interpreted() ? kKBCFirstObjectSlotFromFp : runtime_frame_layout.last_fixed_object_from_fp); if (first_fixed <= last_fixed) { visitor->VisitPointers(first_fixed, last_fixed); } else { ASSERT(runtime_frame_layout.first_object_from_fp == runtime_frame_layout.first_local_from_fp); } } void EntryFrame::VisitObjectPointers(ObjectPointerVisitor* visitor) { ASSERT(visitor != nullptr); // Visit objects between SP and (FP - callee_save_area). ObjectPtr* first = is_interpreted() ? reinterpret_cast(fp()) + kKBCSavedArgDescSlotFromEntryFp : reinterpret_cast(sp()); ObjectPtr* last = is_interpreted() ? reinterpret_cast(sp()) : reinterpret_cast(fp()) + kExitLinkSlotFromEntryFp - 1; // There may not be any pointer to visit; in this case, first > last. visitor->VisitPointers(first, last); } void StackFrame::VisitObjectPointers(ObjectPointerVisitor* visitor) { ASSERT(visitor != nullptr); // NOTE: This code runs while GC is in progress and runs within // a NoHandleScope block. Hence it is not ok to use regular Zone or // Scope handles. We use direct stack handles, the raw pointers in // these handles are not traversed. The use of handles is mainly to // be able to reuse the handle based code and avoid having to add // helper functions to the raw object interface. NoSafepointScope no_safepoint; Code code; CompressedStackMaps::RawPayloadHandle maps; CompressedStackMaps::RawPayloadHandle global_table; uword code_start; if (FLAG_precompiled_mode) { const UntaggedCompressedStackMaps::Payload* global_table_payload; maps = ReversePc::FindStackMap(isolate_group(), pc(), /*is_return_address=*/true, &code_start, &global_table_payload); global_table = global_table_payload; } else { ObjectPtr pc_marker = *(reinterpret_cast( fp() + ((is_interpreted() ? kKBCPcMarkerSlotFromFp : runtime_frame_layout.code_from_fp) * kWordSize))); // May forward raw code. Note we don't just visit the pc marker slot first // because the visitor's forwarding might not be idempotent. visitor->VisitPointer(&pc_marker); if (pc_marker->GetClassId() == kCodeCid) { code ^= pc_marker; code_start = code.PayloadStart(); ASSERT(code.compressed_stackmaps() != CompressedStackMaps::null()); maps = code.compressed_stackmaps(); if (maps.UsesGlobalTable()) { global_table = isolate_group()->object_store()->canonicalized_stack_map_entries(); } } else { ASSERT(pc_marker == Object::null() || (is_interpreted() && (!pc_marker->IsHeapObject() || (pc_marker->GetClassId() == kBytecodeCid)))); } } if (!maps.IsNull()) { // Optimized frames have a stack map. We need to visit the frame based // on the stack map. CompressedStackMaps::Iterator it( maps, global_table); const uint32_t pc_offset = pc() - code_start; if (it.Find(pc_offset)) { if (is_interpreted()) { UNIMPLEMENTED(); } ObjectPtr* first = reinterpret_cast(sp()); ObjectPtr* last = reinterpret_cast( fp() + (runtime_frame_layout.first_local_from_fp * kWordSize)); // A stack map is present in the code object, use the stack map to // visit frame slots which are marked as having objects. // // The layout of the frame is (lower addresses to the right): // | spill slots | outgoing arguments | saved registers | slow-path args | // |XXXXXXXXXXXXX|--------------------|XXXXXXXXXXXXXXXXX|XXXXXXXXXXXXXXXX| // // The spill slots and any saved registers are described in the stack // map. The outgoing arguments are assumed to be tagged; the number // of outgoing arguments is not explicitly tracked. // Spill slots are at the 'bottom' of the frame. intptr_t spill_slot_count = it.SpillSlotBitCount(); for (intptr_t bit = 0; bit < spill_slot_count; ++bit) { if (it.IsObject(bit)) { visitor->VisitPointer(last); } --last; } // The live registers at the 'top' of the frame comprise the rest of the // stack map. for (intptr_t bit = it.Length() - 1; bit >= spill_slot_count; --bit) { if (it.IsObject(bit)) { visitor->VisitPointer(first); } ++first; } // The last slot can be one slot (but not more) past the last slot // in the case that all slots were covered by the stack map. ASSERT((last + 1) >= first); visitor->VisitPointers(first, last); // Now visit other slots which might be part of the calling convention. first = reinterpret_cast( fp() + ((runtime_frame_layout.first_local_from_fp + 1) * kWordSize)); last = reinterpret_cast( fp() + (runtime_frame_layout.first_object_from_fp * kWordSize)); visitor->VisitPointers(first, last); return; } // If we are missing a stack map for a given PC offset, this must either be // unoptimized code, code with no stack map information at all, or the entry // to an osr function. In each of these cases, all stack slots contain // tagged pointers, so fall through. #if defined(DEBUG) if (FLAG_precompiled_mode) { ASSERT(IsStubFrame()); } else { ASSERT(!code.is_optimized() || (pc_offset == code.EntryPoint() - code.PayloadStart())); } #endif // defined(DEBUG) } // For normal unoptimized Dart frames and Stub frames each slot // between the first and last included are tagged objects. if (is_interpreted()) { // Do not visit caller's pc or caller's fp. ObjectPtr* first = reinterpret_cast(fp()) + kKBCFirstObjectSlotFromFp; ObjectPtr* last = reinterpret_cast(fp()) + kKBCLastFixedObjectSlotFromFp; visitor->VisitPointers(first, last); } ObjectPtr* first = reinterpret_cast(is_interpreted() ? fp() : sp()); ObjectPtr* last = reinterpret_cast( is_interpreted() ? sp() : fp() + (runtime_frame_layout.first_object_from_fp * kWordSize)); visitor->VisitPointers(first, last); } FunctionPtr StackFrame::LookupDartFunction() const { if (is_interpreted()) { ObjectPtr result = *(reinterpret_cast( fp() + kKBCFunctionSlotFromFp * kWordSize)); ASSERT((result == Object::null()) || (result->GetClassId() == kFunctionCid)); return static_cast(result); } const Code& code = Code::Handle(LookupDartCode()); if (!code.IsNull()) { const Object& owner = Object::Handle(code.owner()); if (owner.IsFunction()) { return Function::Cast(owner).ptr(); } } return Function::null(); } CodePtr StackFrame::LookupDartCode() const { // We add a no gc scope to ensure that the code below does not trigger // a GC as we are handling raw object references here. It is possible // that the code is called while a GC is in progress, that is ok. #if !defined(DART_HOST_OS_WINDOWS) && !defined(DART_HOST_OS_FUCHSIA) // On Windows and Fuchsia, the profiler calls this from a separate thread // where Thread::Current() is nullptr, so we cannot create a NoSafepointScope. NoSafepointScope no_safepoint; #endif CodePtr code = GetCodeObject(); if ((code != Code::null()) && Code::OwnerClassIdOf(code) == kFunctionCid) { return code; } return Code::null(); } CodePtr StackFrame::GetCodeObject() const { ASSERT(!is_interpreted()); #if defined(DART_PRECOMPILED_RUNTIME) if (FLAG_precompiled_mode) { if (pc() == 0) { return Code::null(); } NoSafepointScope no_safepoint; CodePtr code = ReversePc::Lookup(isolate_group(), pc(), /*is_return_address=*/true); ASSERT(code != Code::null()); return code; } #endif // defined(DART_PRECOMPILED_RUNTIME) ObjectPtr pc_marker = *(reinterpret_cast( fp() + runtime_frame_layout.code_from_fp * kWordSize)); ASSERT((pc_marker == Object::null()) || (pc_marker->GetClassId() == kCodeCid)); return static_cast(pc_marker); } BytecodePtr StackFrame::LookupDartBytecode() const { // We add a no gc scope to ensure that the code below does not trigger // a GC as we are handling raw object references here. It is possible // that the code is called while a GC is in progress, that is ok. #if !defined(HOST_OS_WINDOWS) && !defined(HOST_OS_FUCHSIA) // On Windows and Fuchsia, the profiler calls this from a separate thread // where Thread::Current() is NULL, so we cannot create a NoSafepointScope. NoSafepointScope no_safepoint; #endif return GetBytecodeObject(); } BytecodePtr StackFrame::GetBytecodeObject() const { ASSERT(is_interpreted()); ASSERT(!IsEntryFrame() && !IsExitFrame()); ObjectPtr pc_marker = *( reinterpret_cast(fp() + kKBCPcMarkerSlotFromFp * kWordSize)); ASSERT((pc_marker == Object::null()) || (pc_marker->GetClassId() == kBytecodeCid)); return static_cast(pc_marker); } bool StackFrame::FindExceptionHandler(Thread* thread, uword* handler_pc, bool* needs_stacktrace, bool* has_catch_all, bool* is_optimized) const { REUSABLE_CODE_HANDLESCOPE(thread); Code& code = reused_code_handle.Handle(); REUSABLE_BYTECODE_HANDLESCOPE(thread); Bytecode& bytecode = reused_bytecode_handle.Handle(); REUSABLE_EXCEPTION_HANDLERS_HANDLESCOPE(thread); ExceptionHandlers& handlers = reused_exception_handlers_handle.Handle(); REUSABLE_PC_DESCRIPTORS_HANDLESCOPE(thread); PcDescriptors& descriptors = reused_pc_descriptors_handle.Handle(); uword start; if (is_interpreted()) { bytecode = LookupDartBytecode(); ASSERT(!bytecode.IsNull()); start = bytecode.PayloadStart(); handlers = bytecode.exception_handlers(); } else { code = LookupDartCode(); if (code.IsNull()) { return false; // Stub frames do not have exception handlers. } start = code.PayloadStart(); handlers = code.exception_handlers(); descriptors = code.pc_descriptors(); *is_optimized = code.is_optimized(); } { SafepointMutexLocker ml(thread->isolate_group()->cache_mutex()); HandlerInfoCache* cache = thread->isolate_group()->handler_info_cache(); ExceptionHandlerInfo* info = cache->Lookup(pc()); if (info != nullptr) { *handler_pc = start + info->handler_pc_offset; *needs_stacktrace = (info->needs_stacktrace != 0); *has_catch_all = (info->has_catch_all != 0); return true; } intptr_t try_index = -1; if (handlers.num_entries() != 0) { if (is_interpreted()) { try_index = bytecode.GetTryIndexAtPc(pc()); } else { uword pc_offset = pc() - code.PayloadStart(); PcDescriptors::Iterator iter(descriptors, UntaggedPcDescriptors::kAnyKind); while (iter.MoveNext()) { const intptr_t current_try_index = iter.TryIndex(); if ((iter.PcOffset() == pc_offset) && (current_try_index != -1)) { try_index = current_try_index; break; } } } } if (try_index == -1) { if (handlers.has_async_handler()) { *handler_pc = StubCode::AsyncExceptionHandler().EntryPoint(); *needs_stacktrace = true; *has_catch_all = true; return true; } return false; } ExceptionHandlerInfo handler_info; handlers.GetHandlerInfo(try_index, &handler_info); *handler_pc = start + handler_info.handler_pc_offset; *needs_stacktrace = (handler_info.needs_stacktrace != 0); *has_catch_all = (handler_info.has_catch_all != 0); cache->Insert(pc(), handler_info); } return true; } TokenPosition StackFrame::GetTokenPos() const { if (is_interpreted()) { const Bytecode& bytecode = Bytecode::Handle(LookupDartBytecode()); if (bytecode.IsNull()) { return TokenPosition::kNoSource; // Stub frames do not have token_pos. } return bytecode.GetTokenIndexOfPC(pc()); } const Code& code = Code::Handle(LookupDartCode()); if (code.IsNull()) { return TokenPosition::kNoSource; // Stub frames do not have token_pos. } uword pc_offset = pc() - code.PayloadStart(); const PcDescriptors& descriptors = PcDescriptors::Handle(code.pc_descriptors()); ASSERT(!descriptors.IsNull()); PcDescriptors::Iterator iter(descriptors, UntaggedPcDescriptors::kAnyKind); while (iter.MoveNext()) { if (iter.PcOffset() == pc_offset) { return TokenPosition(iter.TokenPos()); } } return TokenPosition::kNoSource; } bool StackFrame::IsValid() const { if (IsEntryFrame() || IsExitFrame() || IsStubFrame()) { return true; } if (is_interpreted()) { return (LookupDartBytecode() != Bytecode::null()); } return (LookupDartCode() != Code::null()); } void StackFrame::DumpCurrentTrace() { StackFrameIterator frames(ValidationPolicy::kDontValidateFrames, Thread::Current(), StackFrameIterator::kNoCrossThreadIteration); StackFrame* frame = frames.NextFrame(); while (frame != nullptr) { OS::PrintErr("%s\n", frame->ToCString()); frame = frames.NextFrame(); } } void StackFrame::DumpCurrentTrace(uword sp, uword fp, uword pc) { StackFrameIterator frames(fp, sp, pc, ValidationPolicy::kDontValidateFrames, Thread::Current(), StackFrameIterator::kNoCrossThreadIteration); StackFrame* frame = frames.NextFrame(); while (frame != nullptr) { OS::PrintErr("%s\n", frame->ToCString()); frame = frames.NextFrame(); } } void StackFrameIterator::SetupLastExitFrameData() { ASSERT(thread_ != nullptr); uword exit_marker = thread_->top_exit_frame_info(); frames_.fp_ = exit_marker; frames_.sp_ = 0; frames_.pc_ = 0; #if defined(DART_BYTECODE_INTERPRETER) frames_.CheckIfInterpreted(exit_marker); #endif frames_.Unpoison(); } void StackFrameIterator::SetupNextExitFrameData() { ASSERT(entry_.fp() != 0); uword exit_address = entry_.fp() + ((entry_.is_interpreted() ? kKBCExitLinkSlotFromEntryFp : kExitLinkSlotFromEntryFp) * kWordSize); uword exit_marker = *reinterpret_cast(exit_address); frames_.fp_ = exit_marker; frames_.sp_ = 0; frames_.pc_ = 0; #if defined(DART_BYTECODE_INTERPRETER) frames_.CheckIfInterpreted(exit_marker); #endif frames_.Unpoison(); } StackFrameIterator::StackFrameIterator(ValidationPolicy validation_policy, Thread* thread, CrossThreadPolicy cross_thread_policy) : validate_(validation_policy == ValidationPolicy::kValidateFrames), entry_(thread), exit_(thread), frames_(thread), current_frame_(nullptr), thread_(thread) { ASSERT(cross_thread_policy == kAllowCrossThreadIteration || thread_ == Thread::Current()); SetupLastExitFrameData(); // Setup data for last exit frame. } StackFrameIterator::StackFrameIterator(uword last_fp, ValidationPolicy validation_policy, Thread* thread, CrossThreadPolicy cross_thread_policy) : validate_(validation_policy == ValidationPolicy::kValidateFrames), entry_(thread), exit_(thread), frames_(thread), current_frame_(nullptr), thread_(thread) { ASSERT(cross_thread_policy == kAllowCrossThreadIteration || thread_ == Thread::Current()); frames_.fp_ = last_fp; frames_.sp_ = 0; frames_.pc_ = 0; #if defined(DART_BYTECODE_INTERPRETER) frames_.CheckIfInterpreted(last_fp); #endif frames_.Unpoison(); } StackFrameIterator::StackFrameIterator(uword fp, uword sp, uword pc, ValidationPolicy validation_policy, Thread* thread, CrossThreadPolicy cross_thread_policy) : validate_(validation_policy == ValidationPolicy::kValidateFrames), entry_(thread), exit_(thread), frames_(thread), current_frame_(nullptr), thread_(thread) { ASSERT(cross_thread_policy == kAllowCrossThreadIteration || thread_ == Thread::Current()); frames_.fp_ = fp; frames_.sp_ = sp; frames_.pc_ = pc; #if defined(DART_BYTECODE_INTERPRETER) frames_.CheckIfInterpreted(fp); #endif frames_.Unpoison(); } StackFrameIterator::StackFrameIterator(const StackFrameIterator& orig) : validate_(orig.validate_), entry_(orig.thread_), exit_(orig.thread_), frames_(orig.thread_), current_frame_(nullptr), thread_(orig.thread_) { frames_.fp_ = orig.frames_.fp_; frames_.sp_ = orig.frames_.sp_; frames_.pc_ = orig.frames_.pc_; frames_.Unpoison(); } StackFrame* StackFrameIterator::NextFrame() { // When we are at the start of iteration after having created an // iterator object, current_frame_ will be nullptr as we haven't seen // any frames yet (unless we start iterating in the simulator from a given // triplet of fp, sp, and pc). At this point, if NextFrame is called, it tries // to set up the next exit frame by reading the top_exit_frame_info // from the isolate. If we do not have any dart invocations yet, // top_exit_frame_info will be 0 and so we would return nullptr. // current_frame_ will also be nullptr, when we are at the end of having // iterated through all the frames. If NextFrame is called at this // point, we will try and set up the next exit frame, but since we are // at the end of the iteration, fp_ will be 0 and we would return nullptr. if (current_frame_ == nullptr) { if (!HasNextFrame()) { return nullptr; } if (frames_.pc_ == 0) { // Iteration starts from an exit frame given by its fp. current_frame_ = NextExitFrame(); } else if (*(reinterpret_cast( frames_.fp_ + ((frames_.is_interpreted() ? kKBCSavedCallerFpSlotFromFp : kSavedCallerFpSlotFromFp) * kWordSize))) == 0) { // Iteration starts from an entry frame given by its fp, sp, and pc. current_frame_ = NextEntryFrame(); } else { // Iteration starts from a Dart or stub frame given by its fp, sp, and pc. current_frame_ = frames_.NextFrame(validate_); } return current_frame_; } ASSERT(!validate_ || current_frame_->IsValid()); if (current_frame_->IsEntryFrame()) { if (HasNextFrame()) { // We have another chained block. current_frame_ = NextExitFrame(); return current_frame_; } current_frame_ = nullptr; // No more frames. return current_frame_; } ASSERT(!validate_ || current_frame_->IsExitFrame() || current_frame_->IsDartFrame(validate_) || current_frame_->IsStubFrame()); // Consume dart/stub frames using StackFrameIterator::FrameSetIterator // until we are out of dart/stub frames at which point we return the // corresponding entry frame for that set of dart/stub frames. current_frame_ = (frames_.HasNext()) ? frames_.NextFrame(validate_) : NextEntryFrame(); return current_frame_; } #if defined(DART_BYTECODE_INTERPRETER) void StackFrameIterator::FrameSetIterator::CheckIfInterpreted( uword exit_marker) { Interpreter* interpreter = thread_->interpreter(); is_interpreted_ = (interpreter != nullptr) && interpreter->HasFrame(exit_marker); } #endif // defined(DART_BYTECODE_INTERPRETER) // Tell MemorySanitizer that generated code initializes part of the stack. void StackFrameIterator::FrameSetIterator::Unpoison() { // When using a simulator, all writes to the stack happened from MSAN // instrumented C++, so there is nothing to unpoison. Additionally, // fp_ will be somewhere in the simulator's stack instead of the OSThread's // stack. #if !defined(DART_INCLUDE_SIMULATOR) if (fp_ == 0) return; // Note that Thread::os_thread_ is cleared when the thread is descheduled. ASSERT(is_interpreted() || (thread_->os_thread() == nullptr) || ((thread_->os_thread()->stack_limit() < fp_) && (thread_->os_thread()->stack_base() > fp_))); uword lower; if (sp_ == 0) { // Exit frame: guess sp. lower = fp_ - kDartFrameFixedSize * kWordSize; } else { lower = sp_; } uword upper = fp_ + kSavedCallerPcSlotFromFp * kWordSize; // Both lower and upper are inclusive, so we add one word when computing size. MSAN_UNPOISON(reinterpret_cast(lower), upper - lower + kWordSize); #endif // !defined(DART_INCLUDE_SIMULATOR) } StackFrame* StackFrameIterator::FrameSetIterator::NextFrame(bool validate) { StackFrame* frame; ASSERT(HasNext()); frame = &stack_frame_; frame->sp_ = sp_; frame->fp_ = fp_; frame->pc_ = pc_; #if defined(DART_BYTECODE_INTERPRETER) frame->is_interpreted_ = is_interpreted(); #endif sp_ = frame->GetCallerSp(); fp_ = frame->GetCallerFp(); pc_ = frame->GetCallerPc(); Unpoison(); ASSERT(is_interpreted() == frame->is_interpreted()); ASSERT(!validate || frame->IsValid()); return frame; } ExitFrame* StackFrameIterator::NextExitFrame() { exit_.sp_ = frames_.sp_; exit_.fp_ = frames_.fp_; exit_.pc_ = frames_.pc_; #if defined(DART_BYTECODE_INTERPRETER) exit_.is_interpreted_ = frames_.is_interpreted(); #endif frames_.sp_ = exit_.GetCallerSp(); frames_.fp_ = exit_.GetCallerFp(); frames_.pc_ = exit_.GetCallerPc(); frames_.Unpoison(); ASSERT(frames_.is_interpreted() == exit_.is_interpreted()); ASSERT(!validate_ || exit_.IsValid()); return &exit_; } EntryFrame* StackFrameIterator::NextEntryFrame() { ASSERT(!frames_.HasNext()); entry_.sp_ = frames_.sp_; entry_.fp_ = frames_.fp_; entry_.pc_ = frames_.pc_; #if defined(DART_BYTECODE_INTERPRETER) entry_.is_interpreted_ = frames_.is_interpreted(); #endif SetupNextExitFrameData(); // Setup data for next exit frame in chain. ASSERT(!validate_ || entry_.IsValid()); return &entry_; } InlinedFunctionsIterator::InlinedFunctionsIterator(const Code& code, uword pc) : index_(0), num_materializations_(0), dest_frame_size_(0), code_(Code::Handle(code.ptr())), deopt_info_(TypedData::Handle()), function_(Function::Handle()), pc_(pc), deopt_instructions_(), object_table_(ObjectPool::Handle()) { ASSERT(code_.is_optimized()); ASSERT(pc_ != 0); ASSERT(code.ContainsInstructionAt(pc)); #if defined(DART_PRECOMPILED_RUNTIME) ASSERT(deopt_info_.IsNull()); function_ = code_.function(); #else ICData::DeoptReasonId deopt_reason = ICData::kDeoptUnknown; uint32_t deopt_flags = 0; deopt_info_ = code_.GetDeoptInfoAtPc(pc, &deopt_reason, &deopt_flags); if (deopt_info_.IsNull()) { // This is the case when a call without deopt info in optimized code // throws an exception. (e.g. in the parameter copying prologue). // In that case there won't be any inlined frames. function_ = code_.function(); } else { // Unpack deopt info into instructions (translate away suffixes). const Array& deopt_table = Array::Handle(code_.deopt_info_array()); ASSERT(!deopt_table.IsNull()); DeoptInfo::Unpack(deopt_table, deopt_info_, &deopt_instructions_); num_materializations_ = DeoptInfo::NumMaterializations(deopt_instructions_); dest_frame_size_ = DeoptInfo::FrameSize(deopt_info_); object_table_ = code_.GetObjectPool(); Advance(); } #endif // defined(DART_PRECOMPILED_RUNTIME) } void InlinedFunctionsIterator::Advance() { // Iterate over the deopt instructions and determine the inlined // functions if any and iterate over them. ASSERT(!Done()); #if defined(DART_PRECOMPILED_RUNTIME) ASSERT(deopt_info_.IsNull()); SetDone(); return; #else if (deopt_info_.IsNull()) { SetDone(); return; } ASSERT(deopt_instructions_.length() != 0); while (index_ < deopt_instructions_.length()) { DeoptInstr* deopt_instr = deopt_instructions_[index_++]; if (deopt_instr->kind() == DeoptInstr::kRetAddress) { pc_ = DeoptInstr::GetRetAddress(deopt_instr, object_table_, &code_); function_ = code_.function(); return; } } SetDone(); #endif // defined(DART_PRECOMPILED_RUNTIME) } #if !defined(DART_PRECOMPILED_RUNTIME) // Finds the potential offset for the current function's FP if the // current frame were to be deoptimized. intptr_t InlinedFunctionsIterator::GetDeoptFpOffset() const { ASSERT(deopt_instructions_.length() != 0); for (intptr_t index = index_; index < deopt_instructions_.length(); index++) { DeoptInstr* deopt_instr = deopt_instructions_[index]; if (deopt_instr->kind() == DeoptInstr::kCallerFp) { return index - num_materializations_ - kSavedCallerFpSlotFromFp; } } UNREACHABLE(); return 0; } #endif // !defined(DART_PRECOMPILED_RUNTIME) #if defined(DEBUG) void ValidateFrames() { StackFrameIterator frames(ValidationPolicy::kValidateFrames, Thread::Current(), StackFrameIterator::kNoCrossThreadIteration); StackFrame* frame = frames.NextFrame(); while (frame != nullptr) { frame = frames.NextFrame(); } } #endif } // namespace dart