Skip to content

Commit

Permalink
feat: iOS upload DebugMeta with exceptions (#823)
Browse files Browse the repository at this point in the history
  • Loading branch information
vaind authored Sep 9, 2022
1 parent ad4d975 commit 5a6e8fc
Show file tree
Hide file tree
Showing 14 changed files with 446 additions and 378 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

### Features

- Prepare future support for iOS and macOS obfuscated app symbolication using dSYM (requires Flutter `master` channel) ([#823](https://github.com/getsentry/sentry-dart/pull/823))
- Bump Android SDK from v6.3.1 to v6.4.1 ([#989](https://github.com/getsentry/sentry-dart/pull/989))
- [changelog](https://github.com/getsentry/sentry-java/blob/main/CHANGELOG.md#641)
- [diff](https://github.com/getsentry/sentry-java/compare/6.3.1...6.4.1)
Expand Down Expand Up @@ -458,7 +459,7 @@ This should not break anything since the Dart's min. version is already 2.12.0 a
### Breaking Changes:

* Fix: Plugin Registrant class moved to barrel file (#358)
* This changed the import from `import 'package:sentry_flutter/src/sentry_flutter_web.dart';`
* This changed the import from `import 'package:sentry_flutter/src/sentry_flutter_web.dart';`
to `import 'package:sentry_flutter/sentry_flutter_web.dart';`
* This could lead to breaking changes. Typically it shouldn't because the referencing file is auto-generated.
* Fix: Prefix classes with Sentry (#357)
Expand Down
25 changes: 24 additions & 1 deletion dart/lib/src/protocol/debug_image.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,23 @@ import 'package:meta/meta.dart';
class DebugImage {
final String? uuid;

/// Required. Type of the debug image. Must be "macho".
/// Required. Type of the debug image.
final String type;

// Name of the image. Sentry-cocoa only.
final String? name;

/// Required. Identifier of the dynamic library or executable. It is the value of the LC_UUID load command in the Mach header, formatted as UUID.
final String? debugId;

/// Required. Memory address, at which the image is mounted in the virtual address space of the process.
/// Should be a string in hex representation prefixed with "0x".
final String? imageAddr;

/// Optional. Preferred load address of the image in virtual memory, as declared in the headers of the image.
/// When loading an image, the operating system may still choose to place it at a different address.
final String? imageVmAddr;

/// Required. The size of the image in virtual memory. If missing, Sentry will assume that the image spans up to the next image, which might lead to invalid stack traces.
final int? imageSize;

Expand All @@ -40,7 +47,9 @@ class DebugImage {

const DebugImage({
required this.type,
this.name,
this.imageAddr,
this.imageVmAddr,
this.debugId,
this.debugFile,
this.imageSize,
Expand All @@ -54,7 +63,9 @@ class DebugImage {
factory DebugImage.fromJson(Map<String, dynamic> json) {
return DebugImage(
type: json['type'],
name: json['name'],
imageAddr: json['image_addr'],
imageVmAddr: json['image_vmaddr'],
debugId: json['debug_id'],
debugFile: json['debug_file'],
imageSize: json['image_size'],
Expand All @@ -79,6 +90,10 @@ class DebugImage {
json['debug_id'] = debugId;
}

if (name != null) {
json['name'] = name;
}

if (debugFile != null) {
json['debug_file'] = debugFile;
}
Expand All @@ -91,6 +106,10 @@ class DebugImage {
json['image_addr'] = imageAddr;
}

if (imageVmAddr != null) {
json['image_vmaddr'] = imageVmAddr;
}

if (imageSize != null) {
json['image_size'] = imageSize;
}
Expand All @@ -108,22 +127,26 @@ class DebugImage {

DebugImage copyWith({
String? uuid,
String? name,
String? type,
String? debugId,
String? debugFile,
String? codeFile,
String? imageAddr,
String? imageVmAddr,
int? imageSize,
String? arch,
String? codeId,
}) =>
DebugImage(
uuid: uuid ?? this.uuid,
name: name ?? this.name,
type: type ?? this.type,
debugId: debugId ?? this.debugId,
debugFile: debugFile ?? this.debugFile,
codeFile: codeFile ?? this.codeFile,
imageAddr: imageAddr ?? this.imageAddr,
imageVmAddr: imageVmAddr ?? this.imageVmAddr,
imageSize: imageSize ?? this.imageSize,
arch: arch ?? this.arch,
codeId: codeId ?? this.codeId,
Expand Down
153 changes: 76 additions & 77 deletions dart/lib/src/sentry_stack_trace_factory.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,8 @@ import 'sentry_options.dart';
class SentryStackTraceFactory {
final SentryOptions _options;

final _absRegex = RegExp('abs +([A-Fa-f0-9]+)');
static const _stackTraceViolateDartStandard =
'This VM has been configured to produce stack traces that violate the Dart standard.';
final _absRegex = RegExp(r'^\s*#[0-9]+ +abs +([A-Fa-f0-9]+)');
final _frameRegex = RegExp(r'^\s*#', multiLine: true);

static const _sentryPackagesIdentifier = <String>[
'sentry',
Expand All @@ -24,39 +23,22 @@ class SentryStackTraceFactory {

/// returns the [SentryStackFrame] list from a stackTrace ([StackTrace] or [String])
List<SentryStackFrame> getStackFrames(dynamic stackTrace) {
final chain = (stackTrace is StackTrace)
? Chain.forTrace(stackTrace)
: (stackTrace is String)
? Chain.parse(stackTrace)
: Chain.parse('');

final chain = _parseStackTrace(stackTrace);
final frames = <SentryStackFrame>[];
var symbolicated = true;

for (var t = 0; t < chain.traces.length; t += 1) {
final trace = chain.traces[t];

for (final frame in trace.frames) {
// we don't want to add our own frames
if (_sentryPackagesIdentifier.contains(frame.package)) {
if (frame.package != null &&
_sentryPackagesIdentifier.contains(frame.package)) {
continue;
}

final member = frame.member;
// ideally the language would offer us a native way of parsing it.
if (member != null && member.contains(_stackTraceViolateDartStandard)) {
symbolicated = false;
}

final stackTraceFrame = encodeStackTraceFrame(
frame,
symbolicated: symbolicated,
);

if (stackTraceFrame == null) {
continue;
final stackTraceFrame = encodeStackTraceFrame(frame);
if (stackTraceFrame != null) {
frames.add(stackTraceFrame);
}
frames.add(stackTraceFrame);
}

// fill asynchronous gap
Expand All @@ -68,67 +50,84 @@ class SentryStackTraceFactory {
return frames.reversed.toList();
}

/// converts [Frame] to [SentryStackFrame]
@visibleForTesting
SentryStackFrame? encodeStackTraceFrame(
Frame frame, {
bool symbolicated = true,
}) {
final member = frame.member;

SentryStackFrame? sentryStackFrame;

if (symbolicated) {
final fileName = frame.uri.pathSegments.isNotEmpty
? frame.uri.pathSegments.last
: null;
Chain _parseStackTrace(dynamic stackTrace) {
if (stackTrace is Chain || stackTrace is Trace) {
return Chain.forTrace(stackTrace);
}

final abs = '$eventOrigin${_absolutePathForCrashReport(frame)}';
// We need to convert to string and split the headers manually, otherwise
// they end up in the final stack trace as "unparsed" lines.
// Note: [Chain.forTrace] would call [stackTrace.toString()] too.
if (stackTrace is StackTrace) {
stackTrace = stackTrace.toString();
}

sentryStackFrame = SentryStackFrame(
absPath: abs,
function: member,
// https://docs.sentry.io/development/sdk-dev/features/#in-app-frames
inApp: isInApp(frame),
fileName: fileName,
package: frame.package,
);
if (stackTrace is String) {
// Remove headers (everything before the first line starting with '#').
// *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
// pid: 19226, tid: 6103134208, name io.flutter.ui
// os: macos arch: arm64 comp: no sim: no
// isolate_dso_base: 10fa20000, vm_dso_base: 10fa20000
// isolate_instructions: 10fa27070, vm_instructions: 10fa21e20
// #00 abs 000000723d6346d7 _kDartIsolateSnapshotInstructions+0x1e26d7
// #01 abs 000000723d637527 _kDartIsolateSnapshotInstructions+0x1e5527

final startOffset = _frameRegex.firstMatch(stackTrace)?.start ?? 0;
return Chain.parse(
startOffset == 0 ? stackTrace : stackTrace.substring(startOffset));
}
return Chain([]);
}

if (frame.line != null && frame.line! >= 0) {
sentryStackFrame = sentryStackFrame.copyWith(lineNo: frame.line);
}
/// converts [Frame] to [SentryStackFrame]
@visibleForTesting
SentryStackFrame? encodeStackTraceFrame(Frame frame) {
final member = frame.member;

if (frame.column != null && frame.column! >= 0) {
sentryStackFrame = sentryStackFrame.copyWith(colNo: frame.column);
}
} else if (member != null) {
if (frame is UnparsedFrame && member != null) {
// if --split-debug-info is enabled, thats what we see:
// warning: This VM has been configured to produce stack traces that violate the Dart standard.
// *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
// unparsed pid: 30930, tid: 30990, name 1.ui
// unparsed build_id: '5346e01103ffeed44e97094ff7bfcc19'
// unparsed isolate_dso_base: 723d447000, vm_dso_base: 723d447000
// unparsed isolate_instructions: 723d452000, vm_instructions: 723d449000
// unparsed #00 abs 000000723d6346d7 virt 00000000001ed6d7 _kDartIsolateSnapshotInstructions+0x1e26d7
// unparsed #01 abs 000000723d637527 virt 00000000001f0527 _kDartIsolateSnapshotInstructions+0x1e5527
// unparsed #02 abs 000000723d4a41a7 virt 000000000005d1a7 _kDartIsolateSnapshotInstructions+0x521a7
// unparsed #03 abs 000000723d624663 virt 00000000001dd663 _kDartIsolateSnapshotInstructions+0x1d2663
// unparsed #04 abs 000000723d4b8c3b virt 0000000000071c3b _kDartIsolateSnapshotInstructions+0x66c3b
// *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
// pid: 19226, tid: 6103134208, name io.flutter.ui
// os: macos arch: arm64 comp: no sim: no
// isolate_dso_base: 10fa20000, vm_dso_base: 10fa20000
// isolate_instructions: 10fa27070, vm_instructions: 10fa21e20
// #00 abs 000000723d6346d7 _kDartIsolateSnapshotInstructions+0x1e26d7
// #01 abs 000000723d637527 _kDartIsolateSnapshotInstructions+0x1e5527

// we are only interested on the #01, 02... items which contains the 'abs' addresses.
final matches = _absRegex.allMatches(member);

if (matches.isNotEmpty) {
final abs = matches.elementAt(0).group(1);
if (abs != null) {
sentryStackFrame = SentryStackFrame(
instructionAddr: '0x$abs',
platform: 'native', // to trigger symbolication
);
}
final match = _absRegex.firstMatch(member);
if (match != null) {
return SentryStackFrame(
instructionAddr: '0x${match.group(1)!}',
platform: 'native', // to trigger symbolication & native LoadImageList
);
}

// We shouldn't get here. If we do, it means there's likely an issue in
// the parsing so let's fall back and post a stack trace as is, so that at
// least we get an indication something's wrong and are able to fix it.
}

final fileName =
frame.uri.pathSegments.isNotEmpty ? frame.uri.pathSegments.last : null;
final abs = '$eventOrigin${_absolutePathForCrashReport(frame)}';

var sentryStackFrame = SentryStackFrame(
absPath: abs,
function: member,
// https://docs.sentry.io/development/sdk-dev/features/#in-app-frames
inApp: isInApp(frame),
fileName: fileName,
package: frame.package,
);

if (frame.line != null && frame.line! >= 0) {
sentryStackFrame = sentryStackFrame.copyWith(lineNo: frame.line);
}

if (frame.column != null && frame.column! >= 0) {
sentryStackFrame = sentryStackFrame.copyWith(colNo: frame.column);
}
return sentryStackFrame;
}

Expand Down
2 changes: 2 additions & 0 deletions dart/test/protocol/debug_image_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,9 @@ void main() {

final copy = data.copyWith(
type: 'type1',
name: 'name',
imageAddr: 'imageAddr1',
imageVmAddr: 'imageVmAddr1',
debugId: 'debugId1',
debugFile: 'debugFile1',
imageSize: 2,
Expand Down
Loading

0 comments on commit 5a6e8fc

Please sign in to comment.