1// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
2// for details. All rights reserved. Use of this source code is governed by a
3// BSD-style license that can be found in the LICENSE file.
4
5#include "lib/stacktrace.h"
6#include "vm/bootstrap_natives.h"
7#include "vm/debugger.h"
8#include "vm/exceptions.h"
9#include "vm/native_entry.h"
10#include "vm/object_store.h"
11#include "vm/runtime_entry.h"
12#include "vm/stack_frame.h"
13#include "vm/stack_trace.h"
14
15namespace dart {
16
17DECLARE_FLAG(bool, show_invisible_frames);
18
19static const intptr_t kDefaultStackAllocation = 8;
20
21static StackTracePtr CurrentSyncStackTraceLazy(Thread* thread,
22 intptr_t skip_frames = 1) {
23 Zone* zone = thread->zone();
24
25 const auto& code_array = GrowableObjectArray::ZoneHandle(
26 zone, GrowableObjectArray::New(kDefaultStackAllocation));
27 const auto& pc_offset_array = GrowableObjectArray::ZoneHandle(
28 zone, GrowableObjectArray::New(kDefaultStackAllocation));
29
30 // Collect the frames.
31 StackTraceUtils::CollectFramesLazy(thread, code_array, pc_offset_array,
32 skip_frames);
33
34 const auto& code_array_fixed =
35 Array::Handle(zone, Array::MakeFixedLength(code_array));
36 const auto& pc_offset_array_fixed =
37 Array::Handle(zone, Array::MakeFixedLength(pc_offset_array));
38
39 return StackTrace::New(code_array_fixed, pc_offset_array_fixed);
40}
41
42static StackTracePtr CurrentSyncStackTrace(Thread* thread,
43 intptr_t skip_frames = 1) {
44 Zone* zone = thread->zone();
45 const Function& null_function = Function::ZoneHandle(zone);
46
47 // Determine how big the stack trace is.
48 const intptr_t stack_trace_length =
49 StackTraceUtils::CountFrames(thread, skip_frames, null_function, nullptr);
50
51 // Allocate once.
52 const Array& code_array =
53 Array::ZoneHandle(zone, Array::New(stack_trace_length));
54 const Array& pc_offset_array =
55 Array::ZoneHandle(zone, Array::New(stack_trace_length));
56
57 // Collect the frames.
58 const intptr_t collected_frames_count = StackTraceUtils::CollectFrames(
59 thread, code_array, pc_offset_array, 0, stack_trace_length, skip_frames);
60
61 ASSERT(collected_frames_count == stack_trace_length);
62
63 return StackTrace::New(code_array, pc_offset_array);
64}
65
66static StackTracePtr CurrentStackTrace(
67 Thread* thread,
68 bool for_async_function,
69 intptr_t skip_frames = 1,
70 bool causal_async_stacks = FLAG_causal_async_stacks) {
71 if (FLAG_lazy_async_stacks) {
72 return CurrentSyncStackTraceLazy(thread, skip_frames);
73 }
74 if (!causal_async_stacks) {
75 // Return the synchronous stack trace.
76 return CurrentSyncStackTrace(thread, skip_frames);
77 }
78
79 Zone* zone = thread->zone();
80 Code& code = Code::ZoneHandle(zone);
81 Smi& offset = Smi::ZoneHandle(zone);
82 Function& async_function = Function::ZoneHandle(zone);
83 StackTrace& async_stack_trace = StackTrace::ZoneHandle(zone);
84 Array& async_code_array = Array::ZoneHandle(zone);
85 Array& async_pc_offset_array = Array::ZoneHandle(zone);
86
87 StackTraceUtils::ExtractAsyncStackTraceInfo(
88 thread, &async_function, &async_stack_trace, &async_code_array,
89 &async_pc_offset_array);
90
91 // Determine the size of the stack trace.
92 const intptr_t extra_frames = for_async_function ? 1 : 0;
93 bool sync_async_end = false;
94 const intptr_t synchronous_stack_trace_length = StackTraceUtils::CountFrames(
95 thread, skip_frames, async_function, &sync_async_end);
96
97 const intptr_t capacity = synchronous_stack_trace_length +
98 extra_frames; // For the asynchronous gap.
99
100 // Allocate memory for the stack trace.
101 const Array& code_array = Array::ZoneHandle(zone, Array::New(capacity));
102 const Array& pc_offset_array = Array::ZoneHandle(zone, Array::New(capacity));
103
104 intptr_t write_cursor = 0;
105 if (for_async_function) {
106 // Place the asynchronous gap marker at the top of the stack trace.
107 code = StubCode::AsynchronousGapMarker().raw();
108 ASSERT(!code.IsNull());
109 offset = Smi::New(0);
110 code_array.SetAt(write_cursor, code);
111 pc_offset_array.SetAt(write_cursor, offset);
112 write_cursor++;
113 }
114
115 // Append the synchronous stack trace.
116 const intptr_t collected_frames_count = StackTraceUtils::CollectFrames(
117 thread, code_array, pc_offset_array, write_cursor,
118 synchronous_stack_trace_length, skip_frames);
119
120 write_cursor += collected_frames_count;
121
122 ASSERT(write_cursor == capacity);
123
124 const StackTrace& result = StackTrace::Handle(
125 zone, StackTrace::New(code_array, pc_offset_array, async_stack_trace,
126 sync_async_end));
127
128 return result.raw();
129}
130
131StackTracePtr GetStackTraceForException() {
132 Thread* thread = Thread::Current();
133 return CurrentStackTrace(thread, false, 0);
134}
135
136DEFINE_NATIVE_ENTRY(StackTrace_current, 0, 0) {
137 return CurrentStackTrace(thread, false);
138}
139
140DEFINE_NATIVE_ENTRY(StackTrace_asyncStackTraceHelper, 0, 1) {
141 if (!FLAG_causal_async_stacks) {
142 // If causal async stacks are not enabled we should recognize this method
143 // and never call to the NOP runtime.
144 // See kernel_to_il.cc/bytecode_reader.cc/interpreter.cc.
145 UNREACHABLE();
146 }
147#if !defined(PRODUCT)
148 GET_NATIVE_ARGUMENT(Closure, async_op, arguments->NativeArgAt(0));
149 Debugger* debugger = isolate->debugger();
150 if (debugger != NULL) {
151 debugger->MaybeAsyncStepInto(async_op);
152 }
153#endif
154 return CurrentStackTrace(thread, true);
155}
156
157DEFINE_NATIVE_ENTRY(StackTrace_clearAsyncThreadStackTrace, 0, 0) {
158 thread->clear_async_stack_trace();
159 return Object::null();
160}
161
162DEFINE_NATIVE_ENTRY(StackTrace_setAsyncThreadStackTrace, 0, 1) {
163 if (!FLAG_causal_async_stacks) {
164 return Object::null();
165 }
166
167 GET_NON_NULL_NATIVE_ARGUMENT(StackTrace, stack_trace,
168 arguments->NativeArgAt(0));
169 thread->set_async_stack_trace(stack_trace);
170 return Object::null();
171}
172
173static void AppendFrames(const GrowableObjectArray& code_list,
174 const GrowableObjectArray& pc_offset_list,
175 int skip_frames) {
176 Thread* thread = Thread::Current();
177 Zone* zone = thread->zone();
178 StackFrameIterator frames(ValidationPolicy::kDontValidateFrames, thread,
179 StackFrameIterator::kNoCrossThreadIteration);
180 StackFrame* frame = frames.NextFrame();
181 ASSERT(frame != NULL); // We expect to find a dart invocation frame.
182 Code& code = Code::Handle(zone);
183 Bytecode& bytecode = Bytecode::Handle(zone);
184 Smi& offset = Smi::Handle(zone);
185 for (; frame != NULL; frame = frames.NextFrame()) {
186 if (!frame->IsDartFrame()) {
187 continue;
188 }
189 if (skip_frames > 0) {
190 skip_frames--;
191 continue;
192 }
193
194 if (frame->is_interpreted()) {
195 bytecode = frame->LookupDartBytecode();
196 if (bytecode.function() == Function::null()) {
197 continue;
198 }
199 offset = Smi::New(frame->pc() - bytecode.PayloadStart());
200 code_list.Add(bytecode);
201 } else {
202 code = frame->LookupDartCode();
203 offset = Smi::New(frame->pc() - code.PayloadStart());
204 code_list.Add(code);
205 }
206 pc_offset_list.Add(offset);
207 }
208}
209
210// Creates a StackTrace object from the current stack.
211//
212// Skips the first skip_frames Dart frames.
213const StackTrace& GetCurrentStackTrace(int skip_frames) {
214 const GrowableObjectArray& code_list =
215 GrowableObjectArray::Handle(GrowableObjectArray::New());
216 const GrowableObjectArray& pc_offset_list =
217 GrowableObjectArray::Handle(GrowableObjectArray::New());
218 AppendFrames(code_list, pc_offset_list, skip_frames);
219 const Array& code_array = Array::Handle(Array::MakeFixedLength(code_list));
220 const Array& pc_offset_array =
221 Array::Handle(Array::MakeFixedLength(pc_offset_list));
222 const StackTrace& stacktrace =
223 StackTrace::Handle(StackTrace::New(code_array, pc_offset_array));
224 return stacktrace;
225}
226
227} // namespace dart
228