| // 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 "lib/stacktrace.h" |
| #include "vm/bootstrap_natives.h" |
| #include "vm/debugger.h" |
| #include "vm/exceptions.h" |
| #include "vm/native_entry.h" |
| #include "vm/object_store.h" |
| #include "vm/runtime_entry.h" |
| #include "vm/stack_frame.h" |
| #include "vm/stack_trace.h" |
| |
| namespace dart { |
| |
| DECLARE_FLAG(bool, show_invisible_frames); |
| |
| static const intptr_t kDefaultStackAllocation = 8; |
| |
| static StackTracePtr CurrentSyncStackTraceLazy(Thread* thread, |
| intptr_t skip_frames = 1) { |
| Zone* zone = thread->zone(); |
| |
| const auto& code_array = GrowableObjectArray::ZoneHandle( |
| zone, GrowableObjectArray::New(kDefaultStackAllocation)); |
| const auto& pc_offset_array = GrowableObjectArray::ZoneHandle( |
| zone, GrowableObjectArray::New(kDefaultStackAllocation)); |
| |
| // Collect the frames. |
| StackTraceUtils::CollectFramesLazy(thread, code_array, pc_offset_array, |
| skip_frames); |
| |
| const auto& code_array_fixed = |
| Array::Handle(zone, Array::MakeFixedLength(code_array)); |
| const auto& pc_offset_array_fixed = |
| Array::Handle(zone, Array::MakeFixedLength(pc_offset_array)); |
| |
| return StackTrace::New(code_array_fixed, pc_offset_array_fixed); |
| } |
| |
| static StackTracePtr CurrentSyncStackTrace(Thread* thread, |
| intptr_t skip_frames = 1) { |
| Zone* zone = thread->zone(); |
| const Function& null_function = Function::ZoneHandle(zone); |
| |
| // Determine how big the stack trace is. |
| const intptr_t stack_trace_length = |
| StackTraceUtils::CountFrames(thread, skip_frames, null_function, nullptr); |
| |
| // Allocate once. |
| const Array& code_array = |
| Array::ZoneHandle(zone, Array::New(stack_trace_length)); |
| const Array& pc_offset_array = |
| Array::ZoneHandle(zone, Array::New(stack_trace_length)); |
| |
| // Collect the frames. |
| const intptr_t collected_frames_count = StackTraceUtils::CollectFrames( |
| thread, code_array, pc_offset_array, 0, stack_trace_length, skip_frames); |
| |
| ASSERT(collected_frames_count == stack_trace_length); |
| |
| return StackTrace::New(code_array, pc_offset_array); |
| } |
| |
| // Gets current stack trace for `thread`. |
| // This functions itself handles the --causel-async-stacks case. |
| // For --lazy-async-stacks see `CurrentSyncStackTraceLazy`. |
| // For --no-causel-async-stacks see `CurrentSyncStackTrace`. |
| // Extracts the causal async stack from the thread if any set, then prepends |
| // the current sync. stack up until the current async function (if any). |
| static StackTracePtr CurrentStackTrace( |
| Thread* thread, |
| bool for_async_function, |
| intptr_t skip_frames = 1, |
| bool causal_async_stacks = FLAG_causal_async_stacks) { |
| if (FLAG_lazy_async_stacks) { |
| return CurrentSyncStackTraceLazy(thread, skip_frames); |
| } |
| if (!causal_async_stacks) { |
| // Return the synchronous stack trace. |
| return CurrentSyncStackTrace(thread, skip_frames); |
| } |
| |
| Zone* zone = thread->zone(); |
| Code& code = Code::ZoneHandle(zone); |
| Smi& offset = Smi::ZoneHandle(zone); |
| Function& async_function = Function::ZoneHandle(zone); |
| StackTrace& async_stack_trace = StackTrace::ZoneHandle(zone); |
| Array& async_code_array = Array::ZoneHandle(zone); |
| Array& async_pc_offset_array = Array::ZoneHandle(zone); |
| |
| StackTraceUtils::ExtractAsyncStackTraceInfo( |
| thread, &async_function, &async_stack_trace, &async_code_array, |
| &async_pc_offset_array); |
| |
| // Determine the size of the stack trace. |
| const intptr_t extra_frames = for_async_function ? 1 : 0; |
| bool sync_async_end = false; |
| // Count frames until `async_function` and set whether the async function is |
| // running synchronously (i.e. hasn't yielded yet). |
| const intptr_t synchronous_stack_trace_length = StackTraceUtils::CountFrames( |
| thread, skip_frames, async_function, &sync_async_end); |
| |
| ASSERT(synchronous_stack_trace_length > 0); |
| |
| const intptr_t capacity = synchronous_stack_trace_length + |
| extra_frames; // For the asynchronous gap. |
| |
| // Allocate memory for the stack trace. |
| const Array& code_array = Array::ZoneHandle(zone, Array::New(capacity)); |
| const Array& pc_offset_array = Array::ZoneHandle(zone, Array::New(capacity)); |
| |
| intptr_t write_cursor = 0; |
| if (for_async_function) { |
| // Place the asynchronous gap marker at the top of the stack trace. |
| code = StubCode::AsynchronousGapMarker().raw(); |
| ASSERT(!code.IsNull()); |
| offset = Smi::New(0); |
| code_array.SetAt(write_cursor, code); |
| pc_offset_array.SetAt(write_cursor, offset); |
| write_cursor++; |
| } |
| |
| // Prepend: synchronous stack trace + (cached) async stack trace. |
| |
| write_cursor += |
| StackTraceUtils::CollectFrames(thread, code_array, pc_offset_array, |
| /*array_offset=*/write_cursor, |
| /*count=*/synchronous_stack_trace_length, |
| /*skip_frames=*/skip_frames); |
| |
| ASSERT(write_cursor == capacity); |
| |
| const StackTrace& result = StackTrace::Handle( |
| zone, |
| StackTrace::New(code_array, pc_offset_array, |
| /*async_link=*/async_stack_trace, |
| /*skip_sync_start_in_parent_stack=*/sync_async_end)); |
| |
| return result.raw(); |
| } |
| |
| StackTracePtr GetStackTraceForException() { |
| Thread* thread = Thread::Current(); |
| return CurrentStackTrace(thread, false, 0); |
| } |
| |
| DEFINE_NATIVE_ENTRY(StackTrace_current, 0, 0) { |
| return CurrentStackTrace(thread, false); |
| } |
| |
| DEFINE_NATIVE_ENTRY(StackTrace_asyncStackTraceHelper, 0, 1) { |
| if (!FLAG_causal_async_stacks) { |
| // If causal async stacks are not enabled we should recognize this method |
| // and never call to the NOP runtime. |
| // See kernel_to_il.cc. |
| UNREACHABLE(); |
| } |
| #if !defined(PRODUCT) |
| GET_NATIVE_ARGUMENT(Closure, async_op, arguments->NativeArgAt(0)); |
| Debugger* debugger = isolate->debugger(); |
| if (debugger != NULL) { |
| debugger->MaybeAsyncStepInto(async_op); |
| } |
| #endif |
| return CurrentStackTrace(thread, true); |
| } |
| |
| DEFINE_NATIVE_ENTRY(StackTrace_clearAsyncThreadStackTrace, 0, 0) { |
| thread->clear_async_stack_trace(); |
| return Object::null(); |
| } |
| |
| DEFINE_NATIVE_ENTRY(StackTrace_setAsyncThreadStackTrace, 0, 1) { |
| if (!FLAG_causal_async_stacks) { |
| return Object::null(); |
| } |
| |
| GET_NON_NULL_NATIVE_ARGUMENT(StackTrace, stack_trace, |
| arguments->NativeArgAt(0)); |
| thread->set_async_stack_trace(stack_trace); |
| return Object::null(); |
| } |
| |
| static void AppendFrames(const GrowableObjectArray& code_list, |
| const GrowableObjectArray& pc_offset_list, |
| int skip_frames) { |
| Thread* thread = Thread::Current(); |
| Zone* zone = thread->zone(); |
| StackFrameIterator frames(ValidationPolicy::kDontValidateFrames, thread, |
| StackFrameIterator::kNoCrossThreadIteration); |
| StackFrame* frame = frames.NextFrame(); |
| ASSERT(frame != NULL); // We expect to find a dart invocation frame. |
| Code& code = Code::Handle(zone); |
| Smi& offset = Smi::Handle(zone); |
| for (; frame != NULL; frame = frames.NextFrame()) { |
| if (!frame->IsDartFrame()) { |
| continue; |
| } |
| if (skip_frames > 0) { |
| skip_frames--; |
| continue; |
| } |
| |
| code = frame->LookupDartCode(); |
| offset = Smi::New(frame->pc() - code.PayloadStart()); |
| code_list.Add(code); |
| pc_offset_list.Add(offset); |
| } |
| } |
| |
| // Creates a StackTrace object from the current stack. |
| // |
| // Skips the first skip_frames Dart frames. |
| const StackTrace& GetCurrentStackTrace(int skip_frames) { |
| const GrowableObjectArray& code_list = |
| GrowableObjectArray::Handle(GrowableObjectArray::New()); |
| const GrowableObjectArray& pc_offset_list = |
| GrowableObjectArray::Handle(GrowableObjectArray::New()); |
| AppendFrames(code_list, pc_offset_list, skip_frames); |
| const Array& code_array = Array::Handle(Array::MakeFixedLength(code_list)); |
| const Array& pc_offset_array = |
| Array::Handle(Array::MakeFixedLength(pc_offset_list)); |
| const StackTrace& stacktrace = |
| StackTrace::Handle(StackTrace::New(code_array, pc_offset_array)); |
| return stacktrace; |
| } |
| |
| bool HasStack() { |
| Thread* thread = Thread::Current(); |
| StackFrameIterator frames(ValidationPolicy::kDontValidateFrames, thread, |
| StackFrameIterator::kNoCrossThreadIteration); |
| StackFrame* frame = frames.NextFrame(); |
| return frame != nullptr; |
| } |
| |
| } // namespace dart |