-
Notifications
You must be signed in to change notification settings - Fork 1.6k
/
Copy pathdwarf_stack_trace_test.dart
286 lines (237 loc) · 9.19 KB
/
dwarf_stack_trace_test.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
// Copyright (c) 2017, 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.
// VMOptions=--dwarf-stack-traces --save-debugging-info=$TEST_COMPILATION_DIR/dwarf.so
import 'dart:convert';
import 'dart:io';
import 'package:expect/config.dart';
import 'package:expect/expect.dart';
import 'package:native_stack_traces/native_stack_traces.dart';
import 'package:path/path.dart' as path;
@pragma("vm:prefer-inline")
bar() {
// Keep the 'throw' and its argument on separate lines.
throw // force linebreak with dart format
"Hello, Dwarf!";
}
@pragma("vm:never-inline")
foo() {
bar();
}
Future<void> main() async {
String rawStack = "";
try {
foo();
} catch (e, st) {
rawStack = st.toString();
}
if (!isVmAotConfiguration) {
return; // Not running from an AOT compiled snapshot.
}
if (Platform.isAndroid) {
return; // Generated dwarf.so not available on the test device.
}
final dwarf = Dwarf.fromFile(
path.join(Platform.environment["TEST_COMPILATION_DIR"]!, "dwarf.so"))!;
await checkStackTrace(rawStack, dwarf, expectedCallsInfo);
}
Future<void> checkStackTrace(String rawStack, Dwarf dwarf,
List<List<DartCallInfo>> expectedCallsInfo) async {
print("");
print("Raw stack trace:");
print(rawStack);
final rawLines =
await Stream.value(rawStack).transform(const LineSplitter()).toList();
final pcOffsets = collectPCOffsets(rawLines).toList();
Expect.isNotEmpty(pcOffsets);
print('PCOffsets:');
for (final offset in pcOffsets) {
print('* $offset');
}
print('');
// We should have at least enough PC addresses to cover the frames we'll be
// checking.
Expect.isTrue(pcOffsets.length >= expectedCallsInfo.length);
final isolateStart = dwarf.isolateStartAddress(pcOffsets.first.architecture);
Expect.isNotNull(isolateStart);
print('Isolate start offset: 0x${isolateStart!.toRadixString(16)}');
// The addresses of the stack frames in the separate DWARF debugging info.
final virtualAddresses =
pcOffsets.map((o) => dwarf.virtualAddressOf(o)).toList();
print('Virtual addresses from PCOffsets:');
for (final address in virtualAddresses) {
print('* 0x${address.toRadixString(16)}');
}
print('');
// Some double-checks using other information in the non-symbolic stack trace.
final dsoBase = dsoBaseAddresses(rawLines).single;
print('DSO base address: 0x${dsoBase.toRadixString(16)}');
final absoluteIsolateStart = isolateStartAddresses(rawLines).single;
print('Absolute isolate start address: '
'0x${absoluteIsolateStart.toRadixString(16)}');
final absolutes = absoluteAddresses(rawLines);
// The relocated addresses of the stack frames in the loaded DSO. These is
// only guaranteed to be the same as virtualAddresses if the built-in ELF
// generator was used to create the snapshot.
final relocatedAddresses = absolutes.map((a) => a - dsoBase);
print('Relocated absolute addresses:');
for (final address in relocatedAddresses) {
print('* 0x${address.toRadixString(16)}');
}
print('');
// Explicits will be empty if not generating ELF snapshots directly, which
// means we can't depend on virtual addresses in the snapshot lining up with
// those in the separate debugging information.
final explicits = explicitVirtualAddresses(rawLines);
if (explicits.isNotEmpty) {
print('Explicit virtual addresses:');
for (final address in explicits) {
print('* 0x${address.toRadixString(16)}');
}
print('');
// Direct-to-ELF snapshots should have a build ID.
Expect.isNotNull(dwarf.buildId);
Expect.deepEquals(explicits, virtualAddresses);
// This is an ELF snapshot, so check that these two are the same.
Expect.deepEquals(virtualAddresses, relocatedAddresses);
}
final gotCallsInfo = <List<DartCallInfo>>[];
for (final offset in pcOffsets) {
final externalCallInfo = dwarf.callInfoForPCOffset(offset);
Expect.isNotNull(externalCallInfo);
final allCallInfo =
dwarf.callInfoForPCOffset(offset, includeInternalFrames: true);
Expect.isNotNull(allCallInfo);
for (final call in externalCallInfo!) {
Expect.isTrue(call is DartCallInfo, "got non-Dart call info ${call}");
Expect.isFalse(call.isInternal);
Expect.isTrue(allCallInfo!.contains(call),
"External call info ${call} is not among all calls");
}
for (final call in allCallInfo!) {
if (!call.isInternal) {
Expect.isTrue(externalCallInfo.contains(call),
"External call info ${call} is not among external calls");
}
}
gotCallsInfo.add(externalCallInfo.cast<DartCallInfo>().toList());
}
print("");
print("Call information for PC addresses:");
for (var i = 0; i < virtualAddresses.length; i++) {
print("For PC 0x${virtualAddresses[i].toRadixString(16)}:");
print(" Calls corresponding to user or library code:");
gotCallsInfo[i].forEach((frame) => print(" ${frame}"));
}
// Remove empty entries which correspond to skipped internal frames.
gotCallsInfo.removeWhere((calls) => calls.isEmpty);
checkFrames(gotCallsInfo, expectedCallsInfo);
final gotSymbolizedLines = await Stream.fromIterable(rawLines)
.transform(DwarfStackTraceDecoder(dwarf, includeInternalFrames: false))
.toList();
final gotSymbolizedCalls =
gotSymbolizedLines.where((s) => s.startsWith('#')).toList();
print("");
print("Symbolized stack trace:");
gotSymbolizedLines.forEach(print);
print("");
print("Extracted calls:");
gotSymbolizedCalls.forEach(print);
final expectedStrings = extractCallStrings(expectedCallsInfo);
// There are two strings in the list for each line in the output.
final expectedCallCount = expectedStrings.length ~/ 2;
Expect.isTrue(gotSymbolizedCalls.length >= expectedCallCount);
// Strip off any unexpected lines, so we can also make sure we didn't get
// unexpected calls prior to those calls we expect.
final gotCallsTrace =
gotSymbolizedCalls.sublist(0, expectedCallCount).join('\n');
Expect.containsInOrder(expectedStrings, gotCallsTrace);
}
final expectedCallsInfo = <List<DartCallInfo>>[
// The first frame should correspond to the throw in bar, which was inlined
// into foo (so we'll get information for two calls for that PC address).
[
DartCallInfo(
function: "bar",
filename: "dwarf_stack_trace_test.dart",
line: 17,
column: 3,
inlined: true),
DartCallInfo(
function: "foo",
filename: "dwarf_stack_trace_test.dart",
line: 23,
column: 3,
inlined: false)
],
// The second frame corresponds to call to foo in main.
[
DartCallInfo(
function: "main",
filename: "dwarf_stack_trace_test.dart",
line: 29,
column: 5,
inlined: false)
],
// Don't assume anything about any of the frames below the call to foo
// in main, as this makes the test too brittle.
];
void checkFrames(
List<List<DartCallInfo>> gotInfo, List<List<DartCallInfo>> expectedInfo) {
// There may be frames below those we check.
Expect.isTrue(gotInfo.length >= expectedInfo.length);
// We can't just use deep equality, since we only have the filenames in the
// expected version, not the whole path, and we don't really care if
// non-positive line numbers match, as long as they're both non-positive.
for (var i = 0; i < expectedInfo.length; i++) {
for (var j = 0; j < expectedInfo[i].length; j++) {
final got = gotInfo[i][j];
final expected = expectedInfo[i][j];
Expect.equals(expected.function, got.function);
Expect.equals(expected.inlined, got.inlined);
Expect.equals(expected.filename, path.basename(got.filename));
if (expected.isInternal) {
Expect.isTrue(got.isInternal);
} else {
Expect.equals(expected.line, got.line);
}
}
}
}
List<String> extractCallStrings(List<List<CallInfo>> expectedCalls) {
var ret = <String>[];
for (final frame in expectedCalls) {
for (final call in frame) {
if (call is DartCallInfo) {
ret.add(call.function);
if (call.isInternal) {
ret.add("${call.filename}:??");
} else {
ret.add("${call.filename}:${call.line}");
}
}
}
}
return ret;
}
Iterable<int> parseUsingAddressRegExp(RegExp re, Iterable<String> lines) sync* {
for (final line in lines) {
final match = re.firstMatch(line);
if (match == null) continue;
final s = match.group(1);
if (s == null) continue;
yield int.parse(s, radix: 16);
}
}
final _absRE = RegExp(r'abs ([a-f\d]+)');
Iterable<int> absoluteAddresses(Iterable<String> lines) =>
parseUsingAddressRegExp(_absRE, lines);
final _virtRE = RegExp(r'virt ([a-f\d]+)');
Iterable<int> explicitVirtualAddresses(Iterable<String> lines) =>
parseUsingAddressRegExp(_virtRE, lines);
final _dsoBaseRE = RegExp(r'isolate_dso_base: ([a-f\d]+)');
Iterable<int> dsoBaseAddresses(Iterable<String> lines) =>
parseUsingAddressRegExp(_dsoBaseRE, lines);
final _isolateStartRE = RegExp(r'isolate_instructions: ([a-f\d]+)');
Iterable<int> isolateStartAddresses(Iterable<String> lines) =>
parseUsingAddressRegExp(_isolateStartRE, lines);