Index: runtime/vm/profiler.cc |
diff --git a/runtime/vm/profiler.cc b/runtime/vm/profiler.cc |
index b518cb6253fba40cf86d8221137fbce9ddb206ca..f049ad49a98d2af508cecd01aafda6064400ca5c 100644 |
--- a/runtime/vm/profiler.cc |
+++ b/runtime/vm/profiler.cc |
@@ -9,6 +9,7 @@ |
#include "vm/allocation.h" |
#include "vm/atomic.h" |
#include "vm/code_patcher.h" |
+#include "vm/instructions.h" |
#include "vm/isolate.h" |
#include "vm/json_stream.h" |
#include "vm/lockers.h" |
@@ -30,7 +31,6 @@ namespace dart { |
DEFINE_FLAG(bool, profile, true, "Enable Sampling Profiler"); |
#endif |
DEFINE_FLAG(bool, trace_profiled_isolates, false, "Trace profiled isolates."); |
-DEFINE_FLAG(bool, trace_profiler, false, "Trace profiler."); |
DEFINE_FLAG(int, profile_period, 1000, |
"Time between profiler samples in microseconds. Minimum 50."); |
DEFINE_FLAG(int, profile_depth, 8, |
@@ -253,30 +253,324 @@ Sample* SampleBuffer::ReserveSample() { |
} |
-static void SetPCMarkerIfSafe(Sample* sample) { |
- ASSERT(sample != NULL); |
+// 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) |
+ : sample_(sample), |
+ code_(Code::ZoneHandle(code.raw())), |
+ is_optimized_(code.is_optimized()) { |
+ ASSERT(!code_.IsNull()); |
+ ASSERT(code_.ContainsInstructionAt(pc())); |
+ } |
- uword* fp = reinterpret_cast<uword*>(sample->fp()); |
- uword* sp = reinterpret_cast<uword*>(sample->sp()); |
+ bool is_code_optimized() { |
+ return is_optimized_; |
+ } |
- // If FP == SP, the pc marker hasn't been pushed. |
- if (fp > sp) { |
-#if defined(TARGET_OS_WINDOWS) |
- // If the fp is at the beginning of a page, it may be unsafe to access |
- // the pc marker, because we are reading it from a different thread on |
- // Windows. The marker is below fp and the previous page may be a guard |
- // page. |
- const intptr_t kPageMask = VirtualMemory::PageSize() - 1; |
- if ((sample->fp() & kPageMask) == 0) { |
- return; |
+ uword pc() { |
+ return sample_->pc(); |
+ } |
+ |
+ // Returns false on failure. |
+ bool LocateReturnAddress(uword* return_address); |
+ |
+ // Returns offset into code object. |
+ uword RelativePC() { |
+ return pc() - code_.EntryPoint(); |
+ } |
+ |
+ uint8_t* CodePointer(uword offset) { |
+ const uword size = code_.Size(); |
+ ASSERT(offset < size); |
+ uint8_t* code_pointer = reinterpret_cast<uint8_t*>(code_.EntryPoint()); |
+ code_pointer += offset; |
+ return code_pointer; |
+ } |
+ |
+ uword StackAt(intptr_t i) { |
+ ASSERT(i >= 0); |
+ ASSERT(i < Sample::kStackBufferSizeInWords); |
+ return sample_->GetStackBuffer()[i]; |
+ } |
+ |
+ private: |
+ Sample* sample_; |
+ const Code& code_; |
+ const bool is_optimized_; |
+}; |
+ |
+ |
+#if defined(TARGET_ARCH_IA32) |
+bool ReturnAddressLocator::LocateReturnAddress(uword* return_address) { |
+ ASSERT(return_address != NULL); |
+ const uword offset = RelativePC(); |
+ const uword size = code_.Size(); |
+ if (is_optimized_) { |
+ // 0: push ebp |
+ // 1: mov ebp, esp |
+ // 3: ... |
+ if (offset == 0x0) { |
+ // Stack layout: |
+ // 0 RETURN ADDRESS. |
+ *return_address = StackAt(0); |
+ return true; |
+ } |
+ if (offset == 0x1) { |
+ // Stack layout: |
+ // 0 CALLER FRAME POINTER |
+ // 1 RETURN ADDRESS |
+ *return_address = StackAt(1); |
+ return true; |
} |
+ ReturnPattern rp(pc()); |
+ if (rp.IsValid()) { |
+ // Stack layout: |
+ // 0 RETURN ADDRESS. |
+ *return_address = StackAt(0); |
+ return true; |
+ } |
+ return false; |
+ } else { |
+ // 0x00: mov edi, function |
+ // 0x05: incl (inc usage count) <-- this is optional. |
+ // 0x08: cmpl (compare usage count) |
+ // 0x0f: jump to optimize function |
+ // 0x15: push ebp |
+ // 0x16: mov ebp, esp |
+ // 0x18: ... |
+ ASSERT(size >= 0x08); |
+ const uword incl_offset = 0x05; |
+ const uword incl_length = 0x03; |
+ const uint8_t incl_op_code = 0xFF; |
+ const bool has_incl = (*CodePointer(incl_offset) == incl_op_code); |
+ const uword push_fp_offset = has_incl ? 0x15 : 0x15 - incl_length; |
+ if (offset <= push_fp_offset) { |
+ // Stack layout: |
+ // 0 RETURN ADDRESS. |
+ *return_address = StackAt(0); |
+ return true; |
+ } |
+ if (offset == (push_fp_offset + 1)) { |
+ // Stack layout: |
+ // 0 CALLER FRAME POINTER |
+ // 1 RETURN ADDRESS |
+ *return_address = StackAt(1); |
+ return true; |
+ } |
+ ReturnPattern rp(pc()); |
+ if (rp.IsValid()) { |
+ // Stack layout: |
+ // 0 RETURN ADDRESS. |
+ *return_address = StackAt(0); |
+ return true; |
+ } |
+ return false; |
+ } |
+ UNREACHABLE(); |
+ return false; |
+} |
+#elif defined(TARGET_ARCH_X64) |
+bool ReturnAddressLocator::LocateReturnAddress(uword* return_address) { |
+ ASSERT(return_address != NULL); |
+ const uword offset = RelativePC(); |
+ const uword size = code_.Size(); |
+ if (is_optimized_) { |
+ // 0x00: leaq (load pc marker) |
+ // 0x07: movq (load pool pointer) |
+ // 0x0c: push rpb |
+ // 0x0d: movq rbp, rsp |
+ // 0x10: ... |
+ const uword push_fp_offset = 0x0c; |
+ if (offset <= push_fp_offset) { |
+ // Stack layout: |
+ // 0 RETURN ADDRESS. |
+ *return_address = StackAt(0); |
+ return true; |
+ } |
+ if (offset == (push_fp_offset + 1)) { |
+ // Stack layout: |
+ // 0 CALLER FRAME POINTER |
+ // 1 RETURN ADDRESS |
+ *return_address = StackAt(1); |
+ return true; |
+ } |
+ ReturnPattern rp(pc()); |
+ if (rp.IsValid()) { |
+ // Stack layout: |
+ // 0 RETURN ADDRESS. |
+ *return_address = StackAt(0); |
+ return true; |
+ } |
+ return false; |
+ } else { |
+ // 0x00: leaq (load pc marker) |
+ // 0x07: movq (load pool pointer) |
+ // 0x0c: movq (load function) |
+ // 0x13: incl (inc usage count) <-- this is optional. |
+ // 0x16: cmpl (compare usage count) |
+ // 0x1d: jl + 0x |
+ // 0x23: jmp [pool pointer] |
+ // 0x27: push rbp |
+ // 0x28: movq rbp, rsp |
+ // 0x2b: ... |
+ ASSERT(size >= 0x16); |
+ const uword incl_offset = 0x13; |
+ const uword incl_length = 0x03; |
+ const uint8_t incl_op_code = 0xFF; |
+ const bool has_incl = (*CodePointer(incl_offset) == incl_op_code); |
+ const uword push_fp_offset = has_incl ? 0x27 : 0x27 - incl_length; |
+ if (offset <= push_fp_offset) { |
+ // Stack layout: |
+ // 0 RETURN ADDRESS. |
+ *return_address = StackAt(0); |
+ return true; |
+ } |
+ if (offset == (push_fp_offset + 1)) { |
+ // Stack layout: |
+ // 0 CALLER FRAME POINTER |
+ // 1 RETURN ADDRESS |
+ *return_address = StackAt(1); |
+ return true; |
+ } |
+ ReturnPattern rp(pc()); |
+ if (rp.IsValid()) { |
+ // Stack layout: |
+ // 0 RETURN ADDRESS. |
+ *return_address = StackAt(0); |
+ return true; |
+ } |
+ return false; |
+ } |
+ UNREACHABLE(); |
+ return false; |
+} |
+#elif defined(TARGET_ARCH_ARM) |
+bool ReturnAddressLocator::LocateReturnAddress(uword* return_address) { |
+ ASSERT(return_address != NULL); |
+ return false; |
+} |
+#elif defined(TARGET_ARCH_ARM64) |
+bool ReturnAddressLocator::LocateReturnAddress(uword* return_address) { |
+ ASSERT(return_address != NULL); |
+ return false; |
+} |
+#elif defined(TARGET_ARCH_MIPS) |
+bool ReturnAddressLocator::LocateReturnAddress(uword* return_address) { |
+ ASSERT(return_address != NULL); |
+ return false; |
+} |
+#else |
+#error ReturnAddressLocator implementation missing for this architecture. |
#endif |
- uword* pc_marker_ptr = fp + kPcMarkerSlotFromFp; |
- // MSan/ASan are unaware of frames initialized by generated code. |
- MSAN_UNPOISON(pc_marker_ptr, kWordSize); |
- ASAN_UNPOISON(pc_marker_ptr, kWordSize); |
- sample->set_pc_marker(*pc_marker_ptr); |
+ |
+ |
+PreprocessVisitor::PreprocessVisitor(Isolate* isolate) |
+ : SampleVisitor(isolate), |
+ vm_isolate_(Dart::vm_isolate()) { |
+} |
+ |
+ |
+void PreprocessVisitor::VisitSample(Sample* sample) { |
+ if (sample->processed()) { |
+ // Already processed. |
+ return; |
} |
+ // Mark that we've processed this sample. |
+ sample->set_processed(true); |
+ |
+ if (sample->exit_frame_sample()) { |
+ // Exit frame sample, no preprocessing required. |
+ return; |
+ } |
+ REUSABLE_CODE_HANDLESCOPE(isolate()); |
+ // Lookup code object for leaf frame. |
+ Code& code = reused_code_handle.Handle(); |
+ code = FindCodeForPC(sample->At(0)); |
+ sample->set_leaf_frame_is_dart(!code.IsNull()); |
+ if (!code.IsNull() && (code.compile_timestamp() > sample->timestamp())) { |
+ // Code compiled after sample. Ignore. |
+ return; |
+ } |
+ if (sample->leaf_frame_is_dart()) { |
+ CheckForMissingDartFrame(code, sample); |
+ } |
+} |
+ |
+ |
+void PreprocessVisitor::CheckForMissingDartFrame(const Code& code, |
+ Sample* sample) const { |
+ // 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. |
+ ASSERT(!code.IsNull()); |
+ |
+ // The pc marker is our current best guess of a return address. |
+ uword return_address = sample->pc_marker(); |
+ |
+ // Attempt to find a better return address. |
+ ReturnAddressLocator ral(sample, code); |
+ |
+ if (!ral.LocateReturnAddress(&return_address)) { |
+ ASSERT(return_address == sample->pc_marker()); |
+ // 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 (!ContainedInDartCodeHeaps(return_address)) { |
+ // PC marker is not from the Dart heap. Do not insert. |
+ return; |
+ } |
+ } |
+ |
+ if (return_address != 0) { |
+ sample->InsertCallerForTopFrame(return_address); |
+ } |
+} |
+ |
+ |
+bool PreprocessVisitor::ContainedInDartCodeHeaps(uword pc) const { |
+ return isolate()->heap()->CodeContains(pc) || |
+ vm_isolate()->heap()->CodeContains(pc); |
+} |
+ |
+ |
+RawCode* PreprocessVisitor::FindCodeForPC(uword pc) const { |
+ // Check current isolate for pc. |
+ if (isolate()->heap()->CodeContains(pc)) { |
+ return Code::LookupCode(pc); |
+ } |
+ // Check VM isolate for pc. |
+ if (vm_isolate()->heap()->CodeContains(pc)) { |
+ return Code::LookupCodeInVmIsolate(pc); |
+ } |
+ return Code::null(); |
+} |
+ |
+ |
+ClearProfileVisitor::ClearProfileVisitor(Isolate* isolate) |
+ : SampleVisitor(isolate) { |
+} |
+ |
+ |
+void ClearProfileVisitor::VisitSample(Sample* sample) { |
+ sample->Clear(); |
} |
@@ -575,6 +869,49 @@ class ProfilerNativeStackWalker : public ValueObject { |
}; |
+static void CopyPCMarkerIfSafe(Sample* sample) { |
+ ASSERT(sample != NULL); |
+ |
+ uword* fp = reinterpret_cast<uword*>(sample->fp()); |
+ uword* sp = reinterpret_cast<uword*>(sample->sp()); |
+ |
+ // If FP == SP, the pc marker hasn't been pushed. |
+ if (fp > sp) { |
+#if defined(TARGET_OS_WINDOWS) |
+ COMPILE_ASSERT(kPcMarkerSlotFromFp < 0); |
+ // If the fp is at the beginning of a page, it may be unsafe to access |
+ // the pc marker, because we are reading it from a different thread on |
+ // Windows. The marker is below fp and the previous page may be a guard |
+ // page. |
+ const intptr_t kPageMask = VirtualMemory::PageSize() - 1; |
+ if ((sample->fp() & kPageMask) == 0) { |
+ return; |
+ } |
+#endif |
+ uword* pc_marker_ptr = fp + kPcMarkerSlotFromFp; |
+ // MSan/ASan are unaware of frames initialized by generated code. |
+ MSAN_UNPOISON(pc_marker_ptr, kWordSize); |
+ ASAN_UNPOISON(pc_marker_ptr, kWordSize); |
+ sample->set_pc_marker(*pc_marker_ptr); |
+ } |
+} |
+ |
+ |
+static void CopyStackBuffer(Sample* sample) { |
+ ASSERT(sample != NULL); |
+ uword* sp = reinterpret_cast<uword*>(sample->sp()); |
+ uword* buffer = sample->GetStackBuffer(); |
+ if (sp != NULL) { |
+ for (intptr_t i = 0; i < Sample::kStackBufferSizeInWords; i++) { |
+ MSAN_UNPOISON(sp, kWordSize); |
+ ASAN_UNPOISON(sp, kWordSize); |
+ buffer[i] = *sp; |
+ sp++; |
+ } |
+ } |
+} |
+ |
+ |
void Profiler::RecordSampleInterruptCallback( |
const InterruptedThreadState& state, |
void* data) { |
@@ -679,10 +1016,14 @@ void Profiler::RecordSampleInterruptCallback( |
sample->set_user_tag(isolate->user_tag()); |
sample->set_sp(sp); |
sample->set_fp(state.fp); |
+ sample->set_lr(state.lr); |
+ CopyStackBuffer(sample); |
#if !(defined(TARGET_OS_WINDOWS) && defined(TARGET_ARCH_X64)) |
// It is never safe to read other thread's stack unless on Win64 |
// other thread is inside Dart code. |
- SetPCMarkerIfSafe(sample); |
+ if (vm_tag != VMTag::kDartTagId) { |
+ CopyPCMarkerIfSafe(sample); |
+ } |
#endif |
// Walk the call stack. |