// Copyright (c) 2013, 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/profiler.h" #include "platform/address_sanitizer.h" #include "platform/atomic.h" #include "platform/memory_sanitizer.h" #include "platform/utils.h" #include "vm/allocation.h" #include "vm/code_patcher.h" #if !defined(DART_PRECOMPILED_RUNTIME) #include "vm/compiler/compiler_state.h" #endif #include "vm/debugger.h" #include "vm/instructions.h" #include "vm/isolate.h" #include "vm/json_stream.h" #include "vm/lockers.h" #include "vm/message_handler.h" #include "vm/native_symbol.h" #include "vm/object.h" #include "vm/os.h" #include "vm/profiler_service.h" #include "vm/reusable_handles.h" #include "vm/signal_handler.h" #include "vm/simulator.h" #include "vm/stack_frame.h" #include "vm/timeline.h" #include "vm/version.h" namespace dart { static constexpr intptr_t kMaxSamplesPerTick = 4; DEFINE_FLAG(bool, trace_profiled_isolates, false, "Trace profiled isolates."); DEFINE_FLAG(int, profile_period, 1000, "Time between profiler samples in microseconds. Minimum 50."); DEFINE_FLAG(int, max_profile_depth, Sample::kPCArraySizeInWords* kMaxSamplesPerTick, "Maximum number stack frames walked. Minimum 1. Maximum 255."); #if defined(USING_SIMULATOR) DEFINE_FLAG(bool, profile_vm, true, "Always collect native stack traces."); #else DEFINE_FLAG(bool, profile_vm, false, "Always collect native stack traces."); #endif DEFINE_FLAG(bool, profile_vm_allocation, false, "Collect native stack traces when tracing Dart allocations."); DEFINE_FLAG( int, sample_buffer_duration, 0, "Defines the size of the profiler sample buffer to contain at least " "N seconds of samples at a given sample rate. If not provided, the " "default is ~4 seconds. Large values will greatly increase memory " "consumption."); // Include native stack dumping helpers into AOT compiler even in PRODUCT // mode. This allows to report more informative errors when gen_snapshot // crashes. #if !defined(PRODUCT) || defined(DART_PRECOMPILER) ProfilerCounters Profiler::counters_ = {}; static void DumpStackFrame(uword pc, uword fp, const char* name, uword offset) { OS::PrintErr(" pc 0x%" Pp " fp 0x%" Pp " %s+0x%" Px "\n", pc, fp, name, offset); } void DumpStackFrame(intptr_t frame_index, uword pc, uword fp) { uword start = 0; // The pc for all frames except the top frame is a return address, which can // belong to a different inlining interval than the call. Subtract one to get // the symbolization for the call. uword lookup_pc = frame_index == 0 ? pc : pc - 1; if (auto const name = NativeSymbolResolver::LookupSymbolName(lookup_pc, &start)) { DumpStackFrame(pc, fp, name, pc - start); NativeSymbolResolver::FreeSymbolName(name); return; } const char* dso_name; uword dso_base; if (NativeSymbolResolver::LookupSharedObject(pc, &dso_base, &dso_name)) { DumpStackFrame(pc, fp, dso_name, pc - dso_base); NativeSymbolResolver::FreeSymbolName(dso_name); return; } #if !defined(DART_PRECOMPILED_RUNTIME) // This relies on heap iteration, which might fail if we're crashing because // of heap corruption. A nested crash symbolizing a JIT frame will prevent // seeing all caller frames, so only do this when we aren't able to use the // safer StackFrameIterator. Thread* thread = Thread::Current(); bool symbolize_jit_code = (thread != nullptr) && (thread->execution_state() != Thread::kThreadInNative) && (thread->execution_state() != Thread::kThreadInVM); if (symbolize_jit_code) { Code result; result = Code::FindCodeUnsafe(lookup_pc); if (!result.IsNull()) { DumpStackFrame( pc, fp, result.QualifiedName(NameFormattingParams(Object::kInternalName)), pc - result.PayloadStart()); return; } } #endif OS::PrintErr(" pc 0x%" Pp " fp 0x%" Pp " Unknown symbol\n", pc, fp); } class ProfilerStackWalker : public ValueObject { public: ProfilerStackWalker(Dart_Port port_id, Sample* head_sample, SampleBuffer* sample_buffer, intptr_t skip_count = 0) : port_id_(port_id), sample_(head_sample), sample_buffer_(sample_buffer), skip_count_(skip_count), frames_skipped_(0), frame_index_(0), total_frames_(0) { if (sample_ == nullptr) { ASSERT(sample_buffer_ == nullptr); } else { ASSERT(sample_buffer_ != nullptr); ASSERT(sample_->head_sample()); } } bool Append(uword pc, uword fp) { if (frames_skipped_ < skip_count_) { frames_skipped_++; return true; } if (sample_ == nullptr) { DumpStackFrame(frame_index_, pc, fp); frame_index_++; total_frames_++; return true; } if (total_frames_ >= FLAG_max_profile_depth) { sample_->set_truncated_trace(true); return false; } ASSERT(sample_ != nullptr); if (frame_index_ == Sample::kPCArraySizeInWords) { Sample* new_sample = sample_buffer_->ReserveSampleAndLink(sample_); if (new_sample == nullptr) { // Could not reserve new sample- mark this as truncated. sample_->set_truncated_trace(true); return false; } frame_index_ = 0; sample_ = new_sample; } ASSERT(frame_index_ < Sample::kPCArraySizeInWords); sample_->SetAt(frame_index_, pc); frame_index_++; total_frames_++; return true; } protected: Dart_Port port_id_; Sample* sample_; SampleBuffer* sample_buffer_; intptr_t skip_count_; intptr_t frames_skipped_; intptr_t frame_index_; intptr_t total_frames_; }; // The layout of C stack frames. #if defined(HOST_ARCH_IA32) || defined(HOST_ARCH_X64) || \ defined(HOST_ARCH_ARM) || defined(HOST_ARCH_ARM64) // +-------------+ // | saved IP/LR | // +-------------+ // | saved FP | <- FP // +-------------+ static constexpr intptr_t kHostSavedCallerPcSlotFromFp = 1; static constexpr intptr_t kHostSavedCallerFpSlotFromFp = 0; #elif defined(HOST_ARCH_RISCV32) || defined(HOST_ARCH_RISCV64) // +-------------+ // | | <- FP // +-------------+ // | saved RA | // +-------------+ // | saved FP | // +-------------+ static constexpr intptr_t kHostSavedCallerPcSlotFromFp = -1; static constexpr intptr_t kHostSavedCallerFpSlotFromFp = -2; #else #error What architecture? #endif // If the VM is compiled without frame pointers (which is the default on // recent GCC versions with optimizing enabled) the stack walking code may // fail. // class ProfilerNativeStackWalker : public ProfilerStackWalker { public: ProfilerNativeStackWalker(ProfilerCounters* counters, Dart_Port port_id, Sample* sample, SampleBuffer* sample_buffer, uword stack_lower, uword stack_upper, uword pc, uword fp, uword sp, intptr_t skip_count = 0) : ProfilerStackWalker(port_id, sample, sample_buffer, skip_count), counters_(counters), stack_upper_(stack_upper), original_pc_(pc), original_fp_(fp), original_sp_(sp), lower_bound_(stack_lower) {} void walk() { Append(original_pc_, original_fp_); uword* pc = reinterpret_cast(original_pc_); uword* fp = reinterpret_cast(original_fp_); uword* previous_fp = fp; if (!ValidFramePointer(fp)) { counters_->incomplete_sample_fp_bounds.fetch_add(1); return; } while (true) { pc = CallerPC(fp); previous_fp = fp; fp = CallerFP(fp); if (fp == nullptr) { return; } if (fp <= previous_fp) { // Frame pointer did not move to a higher address. counters_->incomplete_sample_fp_step.fetch_add(1); return; } if (!ValidFramePointer(fp)) { // Frame pointer is outside of isolate stack boundary. counters_->incomplete_sample_fp_bounds.fetch_add(1); return; } const uword pc_value = reinterpret_cast(pc); if ((pc_value + 1) < pc_value) { // It is not uncommon to encounter an invalid pc as we // traverse a stack frame. Most of these we can tolerate. If // the pc is so large that adding one to it will cause an // overflow it is invalid and it will cause headaches later // while we are building the profile. Discard it. counters_->incomplete_sample_bad_pc.fetch_add(1); return; } // Move the lower bound up. lower_bound_ = reinterpret_cast(fp); if (!Append(pc_value, reinterpret_cast(fp))) { return; } } } private: uword* CallerPC(uword* fp) const { ASSERT(fp != nullptr); uword* caller_pc_ptr = fp + kHostSavedCallerPcSlotFromFp; // This may actually be uninitialized, by design (see class comment above). MSAN_UNPOISON(caller_pc_ptr, kWordSize); ASAN_UNPOISON(caller_pc_ptr, kWordSize); return reinterpret_cast(*caller_pc_ptr); } uword* CallerFP(uword* fp) const { ASSERT(fp != nullptr); uword* caller_fp_ptr = fp + kHostSavedCallerFpSlotFromFp; // This may actually be uninitialized, by design (see class comment above). MSAN_UNPOISON(caller_fp_ptr, kWordSize); ASAN_UNPOISON(caller_fp_ptr, kWordSize); return reinterpret_cast(*caller_fp_ptr); } bool ValidFramePointer(uword* fp) const { if (fp == nullptr) { return false; } if (!Utils::IsAligned(fp, kWordSize)) { return false; } uword cursor = reinterpret_cast(fp); cursor += sizeof(fp); bool r = (cursor >= lower_bound_) && (cursor < stack_upper_); return r; } ProfilerCounters* const counters_; const uword stack_upper_; const uword original_pc_; const uword original_fp_; const uword original_sp_; uword lower_bound_; }; static bool ValidateThreadStackBounds(uintptr_t fp, uintptr_t sp, uword stack_lower, uword stack_upper) { if (stack_lower >= stack_upper) { // Stack boundary is invalid. return false; } if ((sp < stack_lower) || (sp >= stack_upper)) { // Stack pointer is outside thread's stack boundary. return false; } if ((fp < stack_lower) || (fp >= stack_upper)) { // Frame pointer is outside threads's stack boundary. return false; } return true; } #if !defined(PRODUCT) // Get |thread|'s stack boundary and verify that |sp| and |fp| are within // it. Return |false| if anything looks suspicious. static bool GetAndValidateThreadStackBounds(OSThread* os_thread, Thread* thread, uintptr_t fp, uintptr_t sp, uword* stack_lower, uword* stack_upper) { ASSERT(os_thread != nullptr); ASSERT(stack_lower != nullptr); ASSERT(stack_upper != nullptr); #if defined(USING_SIMULATOR) const bool use_simulator_stack_bounds = thread != nullptr && thread->IsExecutingDartCode(); if (use_simulator_stack_bounds) { Isolate* isolate = thread->isolate(); ASSERT(isolate != nullptr); Simulator* simulator = isolate->simulator(); *stack_lower = simulator->stack_limit(); *stack_upper = simulator->stack_base(); } #else const bool use_simulator_stack_bounds = false; #endif // defined(USING_SIMULATOR) if (!use_simulator_stack_bounds) { *stack_lower = os_thread->stack_limit(); *stack_upper = os_thread->stack_base(); } if ((*stack_lower == 0) || (*stack_upper == 0)) { return false; } if (!use_simulator_stack_bounds && (sp > *stack_lower)) { // The stack pointer gives us a tighter lower bound. *stack_lower = sp; } return ValidateThreadStackBounds(fp, sp, *stack_lower, *stack_upper); } #endif // !defined(PRODUCT) static bool GetAndValidateCurrentThreadStackBounds(uintptr_t fp, uintptr_t sp, uword* stack_lower, uword* stack_upper) { ASSERT(stack_lower != nullptr); ASSERT(stack_upper != nullptr); if (!OSThread::GetCurrentStackBounds(stack_lower, stack_upper)) { return false; } if ((*stack_lower == 0) || (*stack_upper == 0)) { return false; } if (sp > *stack_lower) { // The stack pointer gives us a tighter lower bound. *stack_lower = sp; } return ValidateThreadStackBounds(fp, sp, *stack_lower, *stack_upper); } void Profiler::DumpStackTrace(void* context) { if (context == nullptr) { DumpStackTrace(/*for_crash=*/true); return; } #if defined(DART_HOST_OS_LINUX) || defined(DART_HOST_OS_MACOS) || \ defined(DART_HOST_OS_ANDROID) ucontext_t* ucontext = reinterpret_cast(context); mcontext_t mcontext = ucontext->uc_mcontext; uword pc = SignalHandler::GetProgramCounter(mcontext); uword fp = SignalHandler::GetFramePointer(mcontext); uword sp = SignalHandler::GetCStackPointer(mcontext); DumpStackTrace(sp, fp, pc, /*for_crash=*/true); #elif defined(DART_HOST_OS_WINDOWS) CONTEXT* ctx = reinterpret_cast(context); #if defined(HOST_ARCH_IA32) uword pc = static_cast(ctx->Eip); uword fp = static_cast(ctx->Ebp); uword sp = static_cast(ctx->Esp); #elif defined(HOST_ARCH_X64) uword pc = static_cast(ctx->Rip); uword fp = static_cast(ctx->Rbp); uword sp = static_cast(ctx->Rsp); #elif defined(HOST_ARCH_ARM) uword pc = static_cast(ctx->Pc); uword fp = static_cast(ctx->R11); uword sp = static_cast(ctx->Sp); #elif defined(HOST_ARCH_ARM64) uword pc = static_cast(ctx->Pc); uword fp = static_cast(ctx->Fp); uword sp = static_cast(ctx->Sp); #else #error Unsupported architecture. #endif DumpStackTrace(sp, fp, pc, /*for_crash=*/true); #else // TODO(fschneider): Add support for more platforms. // Do nothing on unsupported platforms. #endif } void Profiler::DumpStackTrace(bool for_crash) { uintptr_t sp = OSThread::GetCurrentStackPointer(); uintptr_t fp = 0; uintptr_t pc = OS::GetProgramCounter(); COPY_FP_REGISTER(fp); DumpStackTrace(sp, fp, pc, for_crash); } static void DumpCompilerState(Thread* thread) { #if !defined(DART_PRECOMPILED_RUNTIME) if (thread != nullptr && thread->execution_state() == Thread::kThreadInVM && thread->HasCompilerState()) { thread->compiler_state().ReportCrash(); } #endif } void Profiler::DumpStackTrace(uword sp, uword fp, uword pc, bool for_crash) { if (for_crash) { // Allow only one stack trace to prevent recursively printing stack traces // if we hit an assert while printing the stack. static RelaxedAtomic started_dump = 0; if (started_dump.fetch_add(1u) != 0) { OS::PrintErr("Aborting reentrant request for stack trace.\n"); return; } } auto thread = Thread::Current(); // nullptr if no current isolate. auto isolate = thread == nullptr ? nullptr : thread->isolate(); auto isolate_group = thread == nullptr ? nullptr : thread->isolate_group(); auto source = isolate_group == nullptr ? nullptr : isolate_group->source(); auto vm_source = Dart::vm_isolate() == nullptr ? nullptr : Dart::vm_isolate()->source(); const char* isolate_group_name = isolate_group == nullptr ? "(nil)" : isolate_group->source()->name; const char* isolate_name = isolate == nullptr ? "(nil)" : isolate->name(); #ifdef SUPPORT_TIMELINE const intptr_t thread_id = OSThread::ThreadIdToIntPtr(OSThread::GetCurrentThreadTraceId()); #else const intptr_t thread_id = -1; #endif OS::PrintErr("version=%s\n", Version::String()); OS::PrintErr("pid=%" Pd ", thread=%" Pd ", isolate_group=%s(%p), isolate=%s(%p)\n", static_cast(OS::ProcessId()), thread_id, isolate_group_name, isolate_group, isolate_name, isolate); #if defined(DART_COMPRESSED_POINTERS) const char kCompressedPointers[] = "yes"; #else const char kCompressedPointers[] = "no"; #endif #if defined(USING_SIMULATOR) const char kUsingSimulator[] = "yes"; #else const char kUsingSimulator[] = "no"; #endif OS::PrintErr("os=%s, arch=%s, comp=%s, sim=%s\n", kHostOperatingSystemName, kTargetArchitectureName, kCompressedPointers, kUsingSimulator); OS::PrintErr("isolate_instructions=%" Px ", vm_instructions=%" Px "\n", source == nullptr ? 0 : reinterpret_cast(source->snapshot_instructions), vm_source == nullptr ? 0 : reinterpret_cast(vm_source->snapshot_instructions)); OS::PrintErr("fp=%" Px ", sp=%" Px ", pc=%" Px "\n", fp, sp, pc); uword stack_lower = 0; uword stack_upper = 0; if (!GetAndValidateCurrentThreadStackBounds(fp, sp, &stack_lower, &stack_upper)) { OS::PrintErr( "Stack dump aborted because GetAndValidateThreadStackBounds failed.\n"); if (pc != 0) { // At the very least dump the top frame. DumpStackFrame(0, pc, fp); } DumpCompilerState(thread); return; } ProfilerNativeStackWalker native_stack_walker( &counters_, ILLEGAL_PORT, nullptr, nullptr, stack_lower, stack_upper, pc, fp, sp, /*skip_count=*/0); native_stack_walker.walk(); OS::PrintErr("-- End of DumpStackTrace\n"); if (thread != nullptr) { if (thread->execution_state() == Thread::kThreadInNative) { TransitionNativeToVM transition(thread); StackFrame::DumpCurrentTrace(); } else if (thread->execution_state() == Thread::kThreadInVM) { StackFrame::DumpCurrentTrace(); } } DumpCompilerState(thread); } #endif // !defined(PRODUCT) || defined(DART_PRECOMPILER) #ifndef PRODUCT RelaxedAtomic Profiler::initialized_ = false; SampleBlockBuffer* Profiler::sample_block_buffer_ = nullptr; bool SampleBlockProcessor::initialized_ = false; bool SampleBlockProcessor::shutdown_ = false; bool SampleBlockProcessor::thread_running_ = false; ThreadJoinId SampleBlockProcessor::processor_thread_id_ = OSThread::kInvalidThreadJoinId; Monitor* SampleBlockProcessor::monitor_ = nullptr; void Profiler::Init() { // Place some sane restrictions on user controlled flags. SetSampleDepth(FLAG_max_profile_depth); if (!FLAG_profiler) { return; } ASSERT(!initialized_); // The profiler may have been shutdown previously, in which case the sample // buffer will have already been initialized. if (sample_block_buffer_ == nullptr) { intptr_t num_blocks = CalculateSampleBufferCapacity(); sample_block_buffer_ = new SampleBlockBuffer(num_blocks); } UpdateFlagProfilePeriod(FLAG_profile_period); ThreadInterrupter::Init(FLAG_profile_period); ThreadInterrupter::Startup(); SampleBlockProcessor::Init(); SampleBlockProcessor::Startup(); initialized_ = true; } class SampleBlockCleanupVisitor : public IsolateVisitor { public: SampleBlockCleanupVisitor() = default; virtual ~SampleBlockCleanupVisitor() = default; void VisitIsolate(Isolate* isolate) { isolate->set_current_allocation_sample_block(nullptr); isolate->set_current_sample_block(nullptr); } }; void Profiler::Cleanup() { if (!FLAG_profiler && !initialized_) { return; } ASSERT(initialized_); ThreadInterrupter::Cleanup(); SampleBlockProcessor::Cleanup(); SampleBlockCleanupVisitor visitor; Isolate::VisitIsolates(&visitor); initialized_ = false; } void Profiler::UpdateRunningState() { if (!FLAG_profiler && initialized_) { Cleanup(); } else if (FLAG_profiler && !initialized_) { Init(); } } void Profiler::SetSampleDepth(intptr_t depth) { const int kMinimumDepth = 2; const int kMaximumDepth = 255; if (depth < kMinimumDepth) { FLAG_max_profile_depth = kMinimumDepth; } else if (depth > kMaximumDepth) { FLAG_max_profile_depth = kMaximumDepth; } else { FLAG_max_profile_depth = depth; } } static intptr_t SamplesPerSecond() { const intptr_t kMicrosPerSec = 1000000; return kMicrosPerSec / FLAG_profile_period; } intptr_t Profiler::CalculateSampleBufferCapacity() { if (FLAG_sample_buffer_duration <= 0) { return SampleBlockBuffer::kDefaultBlockCount; } // Deeper stacks require more than a single Sample object to be represented // correctly. These samples are chained, so we need to determine the worst // case sample chain length for a single stack. const intptr_t max_sample_chain_length = FLAG_max_profile_depth / kMaxSamplesPerTick; const intptr_t sample_count = FLAG_sample_buffer_duration * SamplesPerSecond() * max_sample_chain_length; return (sample_count / SampleBlock::kSamplesPerBlock) + 1; } void Profiler::UpdateFlagProfilePeriod(intptr_t period) { const int kMinimumProfilePeriod = 50; if (period < kMinimumProfilePeriod) { FLAG_profile_period = kMinimumProfilePeriod; } else { FLAG_profile_period = period; } } void Profiler::UpdateSamplePeriod() { UpdateFlagProfilePeriod(FLAG_profile_period); ThreadInterrupter::SetInterruptPeriod(FLAG_profile_period); } SampleBlockBuffer::SampleBlockBuffer(intptr_t blocks, intptr_t samples_per_block) { const intptr_t size = Utils::RoundUp( blocks * samples_per_block * sizeof(Sample), VirtualMemory::PageSize()); const bool executable = false; const bool compressed = false; memory_ = VirtualMemory::Allocate(size, executable, compressed, "dart-profiler"); if (memory_ == nullptr) { OUT_OF_MEMORY(); } sample_buffer_ = reinterpret_cast(memory_->address()); blocks_ = new SampleBlock[blocks]; for (intptr_t i = 0; i < blocks; ++i) { blocks_[i].Init(&sample_buffer_[i * samples_per_block], samples_per_block); } capacity_ = blocks; cursor_ = 0; } SampleBlockBuffer::~SampleBlockBuffer() { delete[] blocks_; blocks_ = nullptr; delete memory_; memory_ = nullptr; capacity_ = 0; cursor_ = 0; } SampleBlock* SampleBlockBuffer::ReserveSampleBlock() { intptr_t capacity = capacity_; intptr_t start = cursor_.fetch_add(1) % capacity; intptr_t i = start; do { SampleBlock* block = &blocks_[i]; if (block->TryAllocateFree()) { return block; } i = (i + 1) % capacity; } while (i != start); // No free blocks: try for completed block instead. i = start; do { SampleBlock* block = &blocks_[i]; if (block->TryAllocateCompleted()) { return block; } i = (i + 1) % capacity; } while (i != start); return nullptr; } void SampleBlockBuffer::FreeCompletedBlocks() { for (intptr_t i = 0; i < capacity_; i++) { blocks_[i].FreeCompleted(); } } bool SampleBlock::HasStreamableSamples(const GrowableObjectArray& tag_table, UserTag* tag) { for (intptr_t i = 0; i < capacity_; ++i) { Sample* sample = At(i); uword sample_tag = sample->user_tag(); for (intptr_t j = 0; j < tag_table.Length(); ++j) { *tag ^= tag_table.At(j); if (tag->tag() == sample_tag && tag->streamable()) { return true; } } } return false; } static void FlushSampleBlocks(Isolate* isolate) { ASSERT(isolate != nullptr); SampleBlock* block = isolate->current_sample_block(); if (block != nullptr) { isolate->set_current_sample_block(nullptr); block->MarkCompleted(); } block = isolate->current_allocation_sample_block(); if (block != nullptr) { isolate->set_current_allocation_sample_block(nullptr); block->MarkCompleted(); } } ProcessedSampleBuffer* SampleBlockBuffer::BuildProcessedSampleBuffer( Isolate* isolate, SampleFilter* filter, ProcessedSampleBuffer* buffer) { ASSERT(isolate != nullptr); Thread* thread = Thread::Current(); Zone* zone = thread->zone(); if (buffer == nullptr) { buffer = new (zone) ProcessedSampleBuffer(); } FlushSampleBlocks(isolate); for (intptr_t i = 0; i < capacity_; ++i) { SampleBlock* block = &blocks_[i]; if (block->TryAcquireStreaming(isolate)) { block->BuildProcessedSampleBuffer(filter, buffer); if (filter->take_samples()) { block->StreamingToFree(); } else { block->StreamingToCompleted(); } } } return buffer; } Sample* SampleBlock::ReserveSample() { intptr_t slot = cursor_.fetch_add(1u); if (slot < capacity_) { return At(slot); } return nullptr; } Sample* SampleBlock::ReserveSampleAndLink(Sample* previous) { ASSERT(previous != nullptr); SampleBlockBuffer* buffer = Profiler::sample_block_buffer(); Isolate* isolate = owner_; ASSERT(isolate != nullptr); Sample* next = previous->is_allocation_sample() ? buffer->ReserveAllocationSample(isolate) : buffer->ReserveCPUSample(isolate); if (next == nullptr) { return nullptr; // No blocks left, so drop sample. } next->Init(previous->port(), previous->timestamp(), previous->tid()); next->set_head_sample(false); // Mark that previous continues at next. previous->SetContinuation(next); return next; } Sample* SampleBlockBuffer::ReserveCPUSample(Isolate* isolate) { return ReserveSampleImpl(isolate, false); } Sample* SampleBlockBuffer::ReserveAllocationSample(Isolate* isolate) { return ReserveSampleImpl(isolate, true); } Sample* SampleBlockBuffer::ReserveSampleImpl(Isolate* isolate, bool allocation_sample) { SampleBlock* block = allocation_sample ? isolate->current_allocation_sample_block() : isolate->current_sample_block(); Sample* sample = nullptr; if (block != nullptr) { sample = block->ReserveSample(); } if (sample != nullptr) { return sample; } SampleBlock* next = ReserveSampleBlock(); if (next == nullptr) { // We're out of blocks to reserve. Drop the sample. return nullptr; } next->set_owner(isolate); if (allocation_sample) { isolate->set_current_allocation_sample_block(next); } else { isolate->set_current_sample_block(next); } if (block != nullptr) { block->MarkCompleted(); if (!Isolate::IsSystemIsolate(isolate)) { Thread* mutator = isolate->mutator_thread(); // The mutator thread might be NULL if we sample in the middle of // Thread::Enter/ExitIsolate. if ((mutator != nullptr) && isolate->TrySetHasCompletedBlocks()) { mutator->ScheduleInterrupts(Thread::kVMInterrupt); } } } return next->ReserveSample(); } // Attempts to find the true return address when a Dart frame is being setup // or torn down. // NOTE: Architecture specific implementations below. class ReturnAddressLocator : public ValueObject { public: ReturnAddressLocator(Sample* sample, const Code& code) : stack_buffer_(sample->GetStackBuffer()), pc_(sample->pc()), code_(Code::ZoneHandle(code.ptr())) { ASSERT(!code_.IsNull()); ASSERT(code_.ContainsInstructionAt(pc())); } ReturnAddressLocator(uword pc, uword* stack_buffer, const Code& code) : stack_buffer_(stack_buffer), pc_(pc), code_(Code::ZoneHandle(code.ptr())) { ASSERT(!code_.IsNull()); ASSERT(code_.ContainsInstructionAt(pc_)); } uword pc() { return pc_; } // Returns false on failure. bool LocateReturnAddress(uword* return_address); // Returns offset into code object. intptr_t RelativePC() { ASSERT(pc() >= code_.PayloadStart()); return static_cast(pc() - code_.PayloadStart()); } uint8_t* CodePointer(intptr_t offset) { const intptr_t size = code_.Size(); ASSERT(offset < size); uint8_t* code_pointer = reinterpret_cast(code_.PayloadStart()); code_pointer += offset; return code_pointer; } uword StackAt(intptr_t i) { ASSERT(i >= 0); ASSERT(i < Sample::kStackBufferSizeInWords); return stack_buffer_[i]; } private: uword* stack_buffer_; uword pc_; const Code& code_; }; #if defined(TARGET_ARCH_IA32) || defined(TARGET_ARCH_X64) bool ReturnAddressLocator::LocateReturnAddress(uword* return_address) { ASSERT(return_address != nullptr); const intptr_t offset = RelativePC(); ASSERT(offset >= 0); const intptr_t size = code_.Size(); ASSERT(offset < size); const intptr_t prologue_offset = code_.GetPrologueOffset(); if (offset < prologue_offset) { // Before the prologue, return address is at the top of the stack. // TODO(johnmccutchan): Some intrinsics and stubs do not conform to the // expected stack layout. Use a more robust solution for those code objects. *return_address = StackAt(0); return true; } // Detect if we are: // push ebp <--- here // mov ebp, esp // on X64 the register names are different but the sequence is the same. ProloguePattern pp(pc()); if (pp.IsValid()) { // Stack layout: // 0 RETURN ADDRESS. *return_address = StackAt(0); return true; } // Detect if we are: // push ebp // mov ebp, esp <--- here // on X64 the register names are different but the sequence is the same. SetFramePointerPattern sfpp(pc()); if (sfpp.IsValid()) { // Stack layout: // 0 CALLER FRAME POINTER // 1 RETURN ADDRESS *return_address = StackAt(1); return true; } // Detect if we are: // ret <--- here ReturnPattern rp(pc()); if (rp.IsValid()) { // Stack layout: // 0 RETURN ADDRESS. *return_address = StackAt(0); return true; } return false; } #elif defined(TARGET_ARCH_ARM) || defined(TARGET_ARCH_ARM64) || \ defined(TARGET_ARCH_RISCV32) || defined(TARGET_ARCH_RISCV64) bool ReturnAddressLocator::LocateReturnAddress(uword* return_address) { ASSERT(return_address != nullptr); return false; } #else #error ReturnAddressLocator implementation missing for this architecture. #endif bool SampleFilter::TimeFilterSample(Sample* sample) { if ((time_origin_micros_ == -1) || (time_extent_micros_ == -1)) { // No time filter passed in, always pass. return true; } const int64_t timestamp = sample->timestamp(); int64_t delta = timestamp - time_origin_micros_; return (delta >= 0) && (delta <= time_extent_micros_); } bool SampleFilter::TaskFilterSample(Sample* sample) { const intptr_t task = static_cast(sample->thread_task()); if (thread_task_mask_ == kNoTaskFilter) { return true; } return (task & thread_task_mask_) != 0; } ClearProfileVisitor::ClearProfileVisitor(Isolate* isolate) : SampleVisitor(isolate->main_port()) {} void ClearProfileVisitor::VisitSample(Sample* sample) { sample->Clear(); } // Executing Dart code, walk the stack. class ProfilerDartStackWalker : public ProfilerStackWalker { public: ProfilerDartStackWalker(Thread* thread, Sample* sample, SampleBuffer* sample_buffer, uword pc, uword fp, uword sp, uword lr, bool allocation_sample, intptr_t skip_count = 0) : ProfilerStackWalker((thread->isolate() != nullptr) ? thread->isolate()->main_port() : ILLEGAL_PORT, sample, sample_buffer, skip_count), thread_(thread), pc_(reinterpret_cast(pc)), fp_(reinterpret_cast(fp)), sp_(reinterpret_cast(sp)), lr_(reinterpret_cast(lr)) {} void walk() { RELEASE_ASSERT(StubCode::HasBeenInitialized()); if (thread_->IsDeoptimizing()) { sample_->set_ignore_sample(true); return; } uword* exit_fp = reinterpret_cast(thread_->top_exit_frame_info()); bool has_exit_frame = exit_fp != nullptr; if (has_exit_frame) { // Exited from compiled code. pc_ = nullptr; fp_ = exit_fp; // Skip exit frame. pc_ = CallerPC(); fp_ = CallerFP(); } else { if (thread_->vm_tag() == VMTag::kDartTagId) { // Running compiled code. // Use the FP and PC from the thread interrupt or simulator; already set // in the constructor. } else { // No Dart on the stack; caller shouldn't use this walker. UNREACHABLE(); } const bool is_entry_frame = #if defined(TARGET_ARCH_IA32) || defined(TARGET_ARCH_X64) StubCode::InInvocationStub(Stack(0)) || StubCode::InInvocationStub(Stack(1)); #else StubCode::InInvocationStub(reinterpret_cast(lr_)); #endif if (is_entry_frame) { // During the prologue of a function, CallerPC will return the caller's // caller. For most frames, the missing PC will be added during profile // processing. However, during this stack walk, it can cause us to fail // to identify the entry frame and lead the stack walk into the weeds. // Do not continue the stalk walk since this might be a false positive // from a Smi or unboxed value. sample_->set_ignore_sample(true); return; } } sample_->set_exit_frame_sample(has_exit_frame); for (;;) { // Skip entry frame. if (StubCode::InInvocationStub(reinterpret_cast(pc_))) { pc_ = nullptr; fp_ = ExitLink(); if (fp_ == nullptr) { break; // End of Dart stack. } // Skip exit frame. pc_ = CallerPC(); fp_ = CallerFP(); // At least one frame between exit and next entry frame. RELEASE_ASSERT( !StubCode::InInvocationStub(reinterpret_cast(pc_))); } if (!Append(reinterpret_cast(pc_), reinterpret_cast(fp_))) { break; // Sample is full. } pc_ = CallerPC(); fp_ = CallerFP(); } } private: uword* CallerPC() const { ASSERT(fp_ != nullptr); uword* caller_pc_ptr = fp_ + kSavedCallerPcSlotFromFp; // MSan/ASan are unaware of frames initialized by generated code. MSAN_UNPOISON(caller_pc_ptr, kWordSize); ASAN_UNPOISON(caller_pc_ptr, kWordSize); return reinterpret_cast(*caller_pc_ptr); } uword* CallerFP() const { ASSERT(fp_ != nullptr); uword* caller_fp_ptr = fp_ + kSavedCallerFpSlotFromFp; // MSan/ASan are unaware of frames initialized by generated code. MSAN_UNPOISON(caller_fp_ptr, kWordSize); ASAN_UNPOISON(caller_fp_ptr, kWordSize); return reinterpret_cast(*caller_fp_ptr); } uword* ExitLink() const { ASSERT(fp_ != nullptr); uword* exit_link_ptr = fp_ + kExitLinkSlotFromEntryFp; // MSan/ASan are unaware of frames initialized by generated code. MSAN_UNPOISON(exit_link_ptr, kWordSize); ASAN_UNPOISON(exit_link_ptr, kWordSize); return reinterpret_cast(*exit_link_ptr); } uword Stack(intptr_t index) const { ASSERT(sp_ != nullptr); uword* stack_ptr = sp_ + index; // MSan/ASan are unaware of frames initialized by generated code. MSAN_UNPOISON(stack_ptr, kWordSize); ASAN_UNPOISON(stack_ptr, kWordSize); return *stack_ptr; } Thread* const thread_; uword* pc_; uword* fp_; uword* sp_; uword* lr_; }; static void CopyStackBuffer(Sample* sample, uword sp_addr) { ASSERT(sample != nullptr); uword* sp = reinterpret_cast(sp_addr); uword* buffer = sample->GetStackBuffer(); if (sp != nullptr) { for (intptr_t i = 0; i < Sample::kStackBufferSizeInWords; i++) { MSAN_UNPOISON(sp, kWordSize); ASAN_UNPOISON(sp, kWordSize); buffer[i] = *sp; sp++; } } } #if defined(DART_HOST_OS_WINDOWS) // On Windows this code is synchronously executed from the thread interrupter // thread. This means we can safely have a static fault_address. static uword fault_address = 0; static LONG GuardPageExceptionFilter(EXCEPTION_POINTERS* ep) { fault_address = 0; if (ep->ExceptionRecord->ExceptionCode != STATUS_GUARD_PAGE_VIOLATION) { return EXCEPTION_CONTINUE_SEARCH; } // https://goo.gl/p5Fe10 fault_address = ep->ExceptionRecord->ExceptionInformation[1]; // Read access. ASSERT(ep->ExceptionRecord->ExceptionInformation[0] == 0); return EXCEPTION_EXECUTE_HANDLER; } #endif // All memory access done to collect the sample is performed in CollectSample. static void CollectSample(Isolate* isolate, bool exited_dart_code, bool in_dart_code, Sample* sample, ProfilerNativeStackWalker* native_stack_walker, ProfilerDartStackWalker* dart_stack_walker, uword pc, uword fp, uword sp, ProfilerCounters* counters) { ASSERT(counters != nullptr); #if defined(DART_HOST_OS_WINDOWS) // Use structured exception handling to trap guard page access on Windows. __try { #endif if (in_dart_code) { // We can only trust the stack pointer if we are executing Dart code. // See http://dartbug.com/20421 for details. CopyStackBuffer(sample, sp); } if (FLAG_profile_vm) { // Always walk the native stack collecting both native and Dart frames. counters->stack_walker_native.fetch_add(1); native_stack_walker->walk(); } else if (StubCode::HasBeenInitialized() && exited_dart_code) { counters->stack_walker_dart_exit.fetch_add(1); // We have a valid exit frame info, use the Dart stack walker. dart_stack_walker->walk(); } else if (StubCode::HasBeenInitialized() && in_dart_code) { counters->stack_walker_dart.fetch_add(1); // We are executing Dart code. We have frame pointers. dart_stack_walker->walk(); } else { counters->stack_walker_none.fetch_add(1); sample->SetAt(0, pc); } #if defined(DART_HOST_OS_WINDOWS) // Use structured exception handling to trap guard page access. } __except (GuardPageExceptionFilter(GetExceptionInformation())) { // NOLINT // Sample collection triggered a guard page fault: // 1) discard entire sample. sample->set_ignore_sample(true); // 2) Reenable guard bit on page that triggered the fault. // https://goo.gl/5mCsXW DWORD new_protect = PAGE_READWRITE | PAGE_GUARD; DWORD old_protect = 0; BOOL success = VirtualProtect(reinterpret_cast(fault_address), sizeof(fault_address), new_protect, &old_protect); USE(success); ASSERT(success); ASSERT(old_protect == PAGE_READWRITE); } #endif } static Sample* SetupSample(Thread* thread, bool allocation_sample, ThreadId tid) { ASSERT(thread != nullptr); Isolate* isolate = thread->isolate(); SampleBlockBuffer* buffer = Profiler::sample_block_buffer(); Sample* sample = allocation_sample ? buffer->ReserveAllocationSample(isolate) : buffer->ReserveCPUSample(isolate); if (sample == nullptr) { return nullptr; } sample->Init(isolate->main_port(), OS::GetCurrentMonotonicMicros(), tid); uword vm_tag = thread->vm_tag(); #if defined(USING_SIMULATOR) // When running in the simulator, the runtime entry function address // (stored as the vm tag) is the address of a redirect function. // Attempt to find the real runtime entry function address and use that. uword redirect_vm_tag = Simulator::FunctionForRedirect(vm_tag); if (redirect_vm_tag != 0) { vm_tag = redirect_vm_tag; } #endif sample->set_vm_tag(vm_tag); sample->set_user_tag(isolate->user_tag()); sample->set_thread_task(thread->task_kind()); return sample; } static bool CheckIsolate(Isolate* isolate) { if ((isolate == nullptr) || (Dart::vm_isolate() == nullptr)) { // No isolate. return false; } return isolate != Dart::vm_isolate(); } void Profiler::SampleAllocation(Thread* thread, intptr_t cid, uint32_t identity_hash) { ASSERT(thread != nullptr); OSThread* os_thread = thread->os_thread(); ASSERT(os_thread != nullptr); Isolate* isolate = thread->isolate(); if (!CheckIsolate(isolate)) { return; } const bool exited_dart_code = thread->HasExitedDartCode(); SampleBlockBuffer* buffer = Profiler::sample_block_buffer(); if (buffer == nullptr) { // Profiler not initialized. return; } uintptr_t sp = OSThread::GetCurrentStackPointer(); uintptr_t fp = 0; uintptr_t pc = OS::GetProgramCounter(); uintptr_t lr = 0; COPY_FP_REGISTER(fp); uword stack_lower = 0; uword stack_upper = 0; if (!GetAndValidateThreadStackBounds(os_thread, thread, fp, sp, &stack_lower, &stack_upper)) { // Could not get stack boundary. return; } Sample* sample = SetupSample(thread, /*allocation_block*/ true, os_thread->trace_id()); if (sample == nullptr) { // We were unable to assign a sample for this allocation. counters_.sample_allocation_failure++; return; } sample->SetAllocationCid(cid); sample->set_allocation_identity_hash(identity_hash); if (FLAG_profile_vm_allocation) { ProfilerNativeStackWalker native_stack_walker( &counters_, (isolate != nullptr) ? isolate->main_port() : ILLEGAL_PORT, sample, isolate->current_allocation_sample_block(), stack_lower, stack_upper, pc, fp, sp); native_stack_walker.walk(); } else if (exited_dart_code) { ProfilerDartStackWalker dart_exit_stack_walker( thread, sample, isolate->current_allocation_sample_block(), pc, fp, sp, lr, /* allocation_sample*/ true); dart_exit_stack_walker.walk(); } else { // Fall back. uintptr_t pc = OS::GetProgramCounter(); sample->SetAt(0, pc); } } void Profiler::SampleThreadSingleFrame(Thread* thread, Sample* sample, uintptr_t pc) { ASSERT(thread != nullptr); OSThread* os_thread = thread->os_thread(); ASSERT(os_thread != nullptr); Isolate* isolate = thread->isolate(); ASSERT(Profiler::sample_block_buffer() != nullptr); // Increment counter for vm tag. VMTagCounters* counters = isolate->vm_tag_counters(); ASSERT(counters != nullptr); if (thread->IsDartMutatorThread()) { counters->Increment(sample->vm_tag()); } // Write the single pc value. sample->SetAt(0, pc); } void Profiler::SampleThread(Thread* thread, const InterruptedThreadState& state) { ASSERT(thread != nullptr); OSThread* os_thread = thread->os_thread(); ASSERT(os_thread != nullptr); Isolate* isolate = thread->isolate(); // Double check if interrupts are disabled // after the thread interrupter decided to send a signal. if (!os_thread->ThreadInterruptsEnabled()) { return; } // Thread is not doing VM work. if (thread->task_kind() == Thread::kUnknownTask) { counters_.bail_out_unknown_task.fetch_add(1); return; } if (StubCode::HasBeenInitialized() && StubCode::InJumpToFrameStub(state.pc)) { // The JumpToFrame stub manually adjusts the stack pointer, frame // pointer, and some isolate state. It is not safe to walk the // stack when executing this stub. counters_.bail_out_jump_to_exception_handler.fetch_add(1); return; } const bool in_dart_code = thread->IsExecutingDartCode(); uintptr_t sp = 0; uintptr_t fp = state.fp; uintptr_t pc = state.pc; uintptr_t lr = state.lr; #if defined(USING_SIMULATOR) Simulator* simulator = nullptr; #endif if (in_dart_code) { // If we're in Dart code, use the Dart stack pointer. #if defined(USING_SIMULATOR) simulator = isolate->simulator(); sp = simulator->get_register(SPREG); fp = simulator->get_register(FPREG); pc = simulator->get_pc(); lr = simulator->get_lr(); #else sp = state.dsp; #endif } else { // If we're in runtime code, use the C stack pointer. sp = state.csp; } if (!CheckIsolate(isolate)) { counters_.bail_out_check_isolate.fetch_add(1); return; } SampleBlockBuffer* sample_block_buffer = Profiler::sample_block_buffer(); if (sample_block_buffer == nullptr) { // Profiler not initialized. return; } // Setup sample. Sample* sample = SetupSample(thread, /*allocation_block*/ false, os_thread->trace_id()); if (sample == nullptr) { // We were unable to assign a sample for this profiler tick. counters_.sample_allocation_failure++; return; } if (thread->IsDartMutatorThread()) { if (thread->IsDeoptimizing()) { counters_.single_frame_sample_deoptimizing.fetch_add(1); SampleThreadSingleFrame(thread, sample, pc); return; } } uword stack_lower = 0; uword stack_upper = 0; if (!GetAndValidateThreadStackBounds(os_thread, thread, fp, sp, &stack_lower, &stack_upper)) { counters_.single_frame_sample_get_and_validate_stack_bounds.fetch_add(1); // Could not get stack boundary. SampleThreadSingleFrame(thread, sample, pc); return; } // At this point we have a valid stack boundary for this isolate and // know that our initial stack and frame pointers are within the boundary. // Increment counter for vm tag. VMTagCounters* counters = isolate->vm_tag_counters(); ASSERT(counters != nullptr); if (thread->IsDartMutatorThread()) { counters->Increment(sample->vm_tag()); } ProfilerNativeStackWalker native_stack_walker( &counters_, (isolate != nullptr) ? isolate->main_port() : ILLEGAL_PORT, sample, isolate->current_sample_block(), stack_lower, stack_upper, pc, fp, sp); const bool exited_dart_code = thread->HasExitedDartCode(); ProfilerDartStackWalker dart_stack_walker( thread, sample, isolate->current_sample_block(), pc, fp, sp, lr, /* allocation_sample*/ false); // All memory access is done inside CollectSample. CollectSample(isolate, exited_dart_code, in_dart_code, sample, &native_stack_walker, &dart_stack_walker, pc, fp, sp, &counters_); } CodeDescriptor::CodeDescriptor(const AbstractCode code) : code_(code) {} uword CodeDescriptor::Start() const { return code_.PayloadStart(); } uword CodeDescriptor::Size() const { return code_.Size(); } int64_t CodeDescriptor::CompileTimestamp() const { return code_.compile_timestamp(); } CodeLookupTable::CodeLookupTable(Thread* thread) { Build(thread); } class CodeLookupTableBuilder : public ObjectVisitor { public: explicit CodeLookupTableBuilder(CodeLookupTable* table) : table_(table) { ASSERT(table_ != nullptr); } ~CodeLookupTableBuilder() {} void VisitObject(ObjectPtr raw_obj) override { if (raw_obj->IsCode() && !Code::IsUnknownDartCode(Code::RawCast(raw_obj))) { table_->Add(Code::Handle(Code::RawCast(raw_obj))); } } private: CodeLookupTable* table_; }; void CodeLookupTable::Build(Thread* thread) { ASSERT(thread != nullptr); Isolate* vm_isolate = Dart::vm_isolate(); ASSERT(vm_isolate != nullptr); // Clear. code_objects_.Clear(); thread->CheckForSafepoint(); // Add all found Code objects. { TimelineBeginEndScope tl(Timeline::GetIsolateStream(), "CodeLookupTable::Build HeapIterationScope"); HeapIterationScope iteration(thread); CodeLookupTableBuilder cltb(this); iteration.IterateVMIsolateObjects(&cltb); iteration.IterateOldObjects(&cltb); } thread->CheckForSafepoint(); // Sort by entry. code_objects_.Sort(CodeDescriptor::Compare); #if defined(DEBUG) if (length() <= 1) { return; } ASSERT(FindCode(0) == nullptr); ASSERT(FindCode(~0) == nullptr); // Sanity check that we don't have duplicate entries and that the entries // are sorted. for (intptr_t i = 0; i < length() - 1; i++) { const CodeDescriptor* a = At(i); const CodeDescriptor* b = At(i + 1); ASSERT(a->Start() < b->Start()); ASSERT(FindCode(a->Start()) == a); ASSERT(FindCode(b->Start()) == b); ASSERT(FindCode(a->Start() + a->Size() - 1) == a); ASSERT(FindCode(b->Start() + b->Size() - 1) == b); } #endif } void CodeLookupTable::Add(const Object& code) { ASSERT(!code.IsNull()); ASSERT(code.IsCode()); CodeDescriptor* cd = new CodeDescriptor(AbstractCode(code.ptr())); code_objects_.Add(cd); } const CodeDescriptor* CodeLookupTable::FindCode(uword pc) const { intptr_t first = 0; intptr_t count = length(); while (count > 0) { intptr_t current = first; intptr_t step = count / 2; current += step; const CodeDescriptor* cd = At(current); if (pc >= cd->Start()) { first = ++current; count -= step + 1; } else { count = step; } } // First points to the first code object whose entry is greater than PC. // That means the code object we need to check is first - 1. if (first == 0) { return nullptr; } first--; ASSERT(first >= 0); ASSERT(first < length()); const CodeDescriptor* cd = At(first); if (cd->Contains(pc)) { return cd; } return nullptr; } ProcessedSampleBuffer* SampleBuffer::BuildProcessedSampleBuffer( SampleFilter* filter, ProcessedSampleBuffer* buffer) { Thread* thread = Thread::Current(); Zone* zone = thread->zone(); if (buffer == nullptr) { buffer = new (zone) ProcessedSampleBuffer(); } const intptr_t length = capacity(); for (intptr_t i = 0; i < length; i++) { thread->CheckForSafepoint(); Sample* sample = At(i); if (sample->ignore_sample()) { // Bad sample. continue; } if (!sample->head_sample()) { // An inner sample in a chain of samples. continue; } if (sample->timestamp() == 0) { // Empty. continue; } if (sample->At(0) == 0) { // No frames. continue; } if (filter != nullptr) { // If we're requesting all the native allocation samples, we don't care // whether or not we're in the same isolate as the sample. if (sample->port() != filter->port()) { // Another isolate. continue; } if (!filter->TimeFilterSample(sample)) { // Did not pass time filter. continue; } if (!filter->TaskFilterSample(sample)) { // Did not pass task filter. continue; } if (!filter->FilterSample(sample)) { // Did not pass filter. continue; } } buffer->Add(BuildProcessedSample(sample, buffer->code_lookup_table())); } return buffer; } ProcessedSample* SampleBuffer::BuildProcessedSample( Sample* sample, const CodeLookupTable& clt) { Thread* thread = Thread::Current(); Zone* zone = thread->zone(); ProcessedSample* processed_sample = new (zone) ProcessedSample(); // Copy state bits from sample. processed_sample->set_timestamp(sample->timestamp()); processed_sample->set_tid(sample->tid()); processed_sample->set_vm_tag(sample->vm_tag()); processed_sample->set_user_tag(sample->user_tag()); if (sample->is_allocation_sample()) { processed_sample->set_allocation_cid(sample->allocation_cid()); processed_sample->set_allocation_identity_hash( sample->allocation_identity_hash()); } processed_sample->set_first_frame_executing(!sample->exit_frame_sample()); // Copy stack trace from sample(s). bool truncated = false; Sample* current = sample; while (current != nullptr) { for (intptr_t i = 0; i < Sample::kPCArraySizeInWords; i++) { if (current->At(i) == 0) { break; } processed_sample->Add(current->At(i)); } truncated = truncated || current->truncated_trace(); current = Next(current); } if (!sample->exit_frame_sample()) { processed_sample->FixupCaller(clt, /* pc_marker */ 0, sample->GetStackBuffer()); } processed_sample->set_truncated(truncated); return processed_sample; } Sample* SampleBuffer::Next(Sample* sample) { if (!sample->is_continuation_sample()) return nullptr; Sample* next_sample = sample->continuation_sample(); // Sanity check. ASSERT(sample != next_sample); // Detect invalid chaining. if (sample->port() != next_sample->port()) { return nullptr; } if (sample->timestamp() != next_sample->timestamp()) { return nullptr; } if (sample->tid() != next_sample->tid()) { return nullptr; } return next_sample; } ProcessedSample::ProcessedSample() : pcs_(Sample::kPCArraySizeInWords), timestamp_(0), vm_tag_(0), user_tag_(0), allocation_cid_(-1), allocation_identity_hash_(0), truncated_(false) {} void ProcessedSample::FixupCaller(const CodeLookupTable& clt, uword pc_marker, uword* stack_buffer) { const CodeDescriptor* cd = clt.FindCode(At(0)); if (cd == nullptr) { // No Dart code. return; } if (cd->CompileTimestamp() > timestamp()) { // Code compiled after sample. Ignore. return; } CheckForMissingDartFrame(clt, cd, pc_marker, stack_buffer); } void ProcessedSample::CheckForMissingDartFrame(const CodeLookupTable& clt, const CodeDescriptor* cd, uword pc_marker, uword* stack_buffer) { ASSERT(cd != nullptr); const Code& code = Code::Handle(Code::RawCast(cd->code().ptr())); ASSERT(!code.IsNull()); // Some stubs (and intrinsics) do not push a frame onto the stack leaving // the frame pointer in the caller. // // PC -> STUB // FP -> DART3 <-+ // DART2 <-| <- TOP FRAME RETURN ADDRESS. // DART1 <-| // ..... // // In this case, traversing the linked stack frames will not collect a PC // inside DART3. The stack will incorrectly be: STUB, DART2, DART1. // In Dart code, after pushing the FP onto the stack, an IP in the current // function is pushed onto the stack as well. This stack slot is called // the PC marker. We can use the PC marker to insert DART3 into the stack // so that it will correctly be: STUB, DART3, DART2, DART1. Note the // inserted PC may not accurately reflect the true return address into DART3. // The pc marker is our current best guess of a return address. uword return_address = pc_marker; // Attempt to find a better return address. ReturnAddressLocator ral(At(0), stack_buffer, code); if (!ral.LocateReturnAddress(&return_address)) { ASSERT(return_address == pc_marker); if (code.GetPrologueOffset() == 0) { // Code has the prologue at offset 0. The frame is already setup and // can be trusted. return; } // Could not find a better return address than the pc_marker. if (code.ContainsInstructionAt(return_address)) { // PC marker is in the same code as pc, no missing frame. return; } } if (clt.FindCode(return_address) == nullptr) { // Return address is not from a Dart code object. Do not insert. return; } if (return_address != 0) { InsertAt(1, return_address); } } ProcessedSampleBuffer::ProcessedSampleBuffer() : code_lookup_table_(new CodeLookupTable(Thread::Current())) { ASSERT(code_lookup_table_ != nullptr); } void SampleBlockProcessor::Init() { ASSERT(!initialized_); if (monitor_ == nullptr) { monitor_ = new Monitor(); } ASSERT(monitor_ != nullptr); initialized_ = true; shutdown_ = false; } void SampleBlockProcessor::Startup() { ASSERT(initialized_); ASSERT(processor_thread_id_ == OSThread::kInvalidThreadJoinId); MonitorLocker startup_ml(monitor_); OSThread::Start("Dart Profiler SampleBlockProcessor", ThreadMain, 0); while (!thread_running_) { startup_ml.Wait(); } ASSERT(processor_thread_id_ != OSThread::kInvalidThreadJoinId); } void SampleBlockProcessor::Cleanup() { { MonitorLocker shutdown_ml(monitor_); if (shutdown_) { // Already shutdown. return; } shutdown_ = true; // Notify. shutdown_ml.Notify(); ASSERT(initialized_); } // Join the thread. ASSERT(processor_thread_id_ != OSThread::kInvalidThreadJoinId); OSThread::Join(processor_thread_id_); processor_thread_id_ = OSThread::kInvalidThreadJoinId; initialized_ = false; ASSERT(!thread_running_); } class StreamableSampleFilter : public SampleFilter { public: StreamableSampleFilter(Dart_Port port, const Isolate* isolate) : SampleFilter(port, kNoTaskFilter, -1, -1, true), isolate_(isolate) {} bool FilterSample(Sample* sample) override { const UserTag& tag = UserTag::Handle(UserTag::FindTagById(isolate_, sample->user_tag())); return tag.streamable(); } private: const Isolate* isolate_; }; void Profiler::ProcessCompletedBlocks(Isolate* isolate) { if (!Service::profiler_stream.enabled()) return; auto thread = Thread::Current(); if (Isolate::IsSystemIsolate(isolate)) return; TIMELINE_DURATION(thread, Isolate, "Profiler::ProcessCompletedBlocks") DisableThreadInterruptsScope dtis(thread); StackZone zone(thread); HandleScope handle_scope(thread); StreamableSampleFilter filter(isolate->main_port(), isolate); Profile profile; profile.Build(thread, isolate, &filter, Profiler::sample_block_buffer()); ServiceEvent event(isolate, ServiceEvent::kCpuSamples); event.set_cpu_profile(&profile); Service::HandleEvent(&event); } void Profiler::IsolateShutdown(Thread* thread) { FlushSampleBlocks(thread->isolate()); ProcessCompletedBlocks(thread->isolate()); } void SampleBlockProcessor::ThreadMain(uword parameters) { ASSERT(initialized_); { // Signal to main thread we are ready. MonitorLocker startup_ml(monitor_); OSThread* os_thread = OSThread::Current(); ASSERT(os_thread != nullptr); processor_thread_id_ = OSThread::GetCurrentThreadJoinId(os_thread); thread_running_ = true; startup_ml.Notify(); } MonitorLocker wait_ml(monitor_); // Wakeup every 100ms. const int64_t wakeup_interval = 1000 * 100; while (true) { wait_ml.WaitMicros(wakeup_interval); if (shutdown_) { break; } IsolateGroup::ForEach([&](IsolateGroup* group) { if (group == Dart::vm_isolate_group()) return; const bool kBypassSafepoint = false; Thread::EnterIsolateGroupAsHelper(group, Thread::kSampleBlockTask, kBypassSafepoint); group->ForEachIsolate([&](Isolate* isolate) { if (isolate->TakeHasCompletedBlocks()) { Profiler::ProcessCompletedBlocks(isolate); } }); Thread::ExitIsolateGroupAsHelper(kBypassSafepoint); }); } // Signal to main thread we are exiting. thread_running_ = false; } #endif // !PRODUCT } // namespace dart