|  | // 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<uword*>(original_pc_); | 
|  | uword* fp = reinterpret_cast<uword*>(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<uword>(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<uword>(fp); | 
|  |  | 
|  | if (!Append(pc_value, reinterpret_cast<uword>(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<uword*>(*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<uword*>(*caller_fp_ptr); | 
|  | } | 
|  |  | 
|  | bool ValidFramePointer(uword* fp) const { | 
|  | if (fp == nullptr) { | 
|  | return false; | 
|  | } | 
|  | if (!Utils::IsAligned(fp, kWordSize)) { | 
|  | return false; | 
|  | } | 
|  | uword cursor = reinterpret_cast<uword>(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<ucontext_t*>(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*>(context); | 
|  | #if defined(HOST_ARCH_IA32) | 
|  | uword pc = static_cast<uword>(ctx->Eip); | 
|  | uword fp = static_cast<uword>(ctx->Ebp); | 
|  | uword sp = static_cast<uword>(ctx->Esp); | 
|  | #elif defined(HOST_ARCH_X64) | 
|  | uword pc = static_cast<uword>(ctx->Rip); | 
|  | uword fp = static_cast<uword>(ctx->Rbp); | 
|  | uword sp = static_cast<uword>(ctx->Rsp); | 
|  | #elif defined(HOST_ARCH_ARM) | 
|  | uword pc = static_cast<uword>(ctx->Pc); | 
|  | uword fp = static_cast<uword>(ctx->R11); | 
|  | uword sp = static_cast<uword>(ctx->Sp); | 
|  | #elif defined(HOST_ARCH_ARM64) | 
|  | uword pc = static_cast<uword>(ctx->Pc); | 
|  | uword fp = static_cast<uword>(ctx->Fp); | 
|  | uword sp = static_cast<uword>(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<uintptr_t> 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<intptr_t>(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<uword>(source->snapshot_instructions), | 
|  | vm_source == nullptr | 
|  | ? 0 | 
|  | : reinterpret_cast<uword>(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<bool> 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_); | 
|  | SetSamplePeriod(FLAG_profile_period); | 
|  | // 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); | 
|  | } | 
|  | ThreadInterrupter::Init(); | 
|  | 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) { | 
|  | 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::SetSamplePeriod(intptr_t period) { | 
|  | const int kMinimumProfilePeriod = 50; | 
|  | if (period < kMinimumProfilePeriod) { | 
|  | FLAG_profile_period = kMinimumProfilePeriod; | 
|  | } else { | 
|  | FLAG_profile_period = period; | 
|  | } | 
|  | ThreadInterrupter::SetInterruptPeriod(FLAG_profile_period); | 
|  | } | 
|  |  | 
|  | void Profiler::UpdateSamplePeriod() { | 
|  | SetSamplePeriod(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<Sample*>(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<intptr_t>(pc() - code_.PayloadStart()); | 
|  | } | 
|  |  | 
|  | uint8_t* CodePointer(intptr_t offset) { | 
|  | const intptr_t size = code_.Size(); | 
|  | ASSERT(offset < size); | 
|  | uint8_t* code_pointer = reinterpret_cast<uint8_t*>(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<intptr_t>(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<uword*>(pc)), | 
|  | fp_(reinterpret_cast<uword*>(fp)), | 
|  | sp_(reinterpret_cast<uword*>(sp)), | 
|  | lr_(reinterpret_cast<uword*>(lr)) {} | 
|  |  | 
|  | void walk() { | 
|  | RELEASE_ASSERT(StubCode::HasBeenInitialized()); | 
|  | if (thread_->isolate()->IsDeoptimizing()) { | 
|  | sample_->set_ignore_sample(true); | 
|  | return; | 
|  | } | 
|  |  | 
|  | uword* exit_fp = reinterpret_cast<uword*>(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<uword>(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<uword>(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<uword>(pc_))); | 
|  | } | 
|  |  | 
|  | if (!Append(reinterpret_cast<uword>(pc_), reinterpret_cast<uword>(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<uword*>(*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<uword*>(*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<uword*>(*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<uword*>(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<void*>(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(); | 
|  |  | 
|  | // 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 (isolate->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 |