Skip to content

Commit 2cf9161

Browse files
authored
fix: debug images not loaded for split debug info only builds (#3104)
* Update * Update * Update CHANGELOG
1 parent 0a7552c commit 2cf9161

10 files changed

+127
-13
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
## Unreleased
44

5+
### Fixes
6+
7+
- Debug meta not loaded for split debug info only builds ([#3104](https://github.com/getsentry/sentry-dart/pull/3104))
8+
59
### Dependencies
610

711
- Bump Android SDK from v8.13.2 to v8.17.0 ([#2977](https://github.com/getsentry/sentry-dart/pull/2977))

dart/lib/src/load_dart_debug_images_integration.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ class LoadDartDebugImagesIntegration extends Integration<SentryOptions> {
1919
@override
2020
void call(Hub hub, SentryOptions options) {
2121
if (options.enableDartSymbolication &&
22-
options.runtimeChecker.isAppObfuscated() &&
22+
(options.runtimeChecker.isAppObfuscated() ||
23+
options.runtimeChecker.isSplitDebugInfoBuild()) &&
2324
!options.platform.isWeb) {
2425
options.addEventProcessor(
2526
LoadDartDebugImagesIntegrationEventProcessor(options));

dart/lib/src/runtime_checker.dart

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import 'dart:async';
22

3+
import 'utils/stacktrace_utils.dart';
4+
35
/// Helper to check in which environment the library is running.
46
/// The environment checks (release/debug/profile) are mutually exclusive.
57
class RuntimeChecker {
@@ -31,6 +33,12 @@ class RuntimeChecker {
3133
return !typeName.contains('RuntimeChecker');
3234
}
3335

36+
/// Check if the current build has been built with --split-debug-info
37+
bool isSplitDebugInfoBuild() {
38+
final str = StackTrace.current.toString();
39+
return buildIdRegex.hasMatch(str) || absRegex.hasMatch(str);
40+
}
41+
3442
final bool isRootZone;
3543

3644
String get compileMode {

dart/lib/src/sentry_stack_trace_factory.dart

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,13 @@ import 'package:stack_trace/stack_trace.dart';
44
import 'origin.dart';
55
import 'protocol.dart';
66
import 'sentry_options.dart';
7+
import 'utils/stacktrace_utils.dart';
78

89
/// converts [StackTrace] to [SentryStackFrame]s
910
class SentryStackTraceFactory {
1011
final SentryOptions _options;
1112

12-
static final _absRegex = RegExp(r'^\s*#[0-9]+ +abs +([A-Fa-f0-9]+)');
1313
static final _frameRegex = RegExp(r'^\s*#', multiLine: true);
14-
static final _buildIdRegex = RegExp(r"build_id[:=] *'([A-Fa-f0-9]+)'");
1514
static final _baseAddrRegex = RegExp(r'isolate_dso_base[:=] *([A-Fa-f0-9]+)');
1615
static final SentryStackFrame _asynchronousGapFrameJson =
1716
SentryStackFrame(absPath: '<asynchronous suspension>');
@@ -91,7 +90,7 @@ class SentryStackTraceFactory {
9190
final chain = Chain.parse(
9291
startOffset == 0 ? stackTrace : stackTrace.substring(startOffset));
9392
final info = _StackInfo(chain.traces);
94-
info.buildId = _buildIdRegex.firstMatch(stackTrace)?.group(1);
93+
info.buildId = buildIdRegex.firstMatch(stackTrace)?.group(1);
9594
info.baseAddr = _baseAddrRegex.firstMatch(stackTrace)?.group(1);
9695
if (info.baseAddr != null) {
9796
info.baseAddr = '0x${info.baseAddr}';
@@ -111,7 +110,7 @@ class SentryStackTraceFactory {
111110
// #00 abs 000000723d6346d7 _kDartIsolateSnapshotInstructions+0x1e26d7
112111

113112
// we are only interested on the #01, 02... items which contains the 'abs' addresses.
114-
final match = _absRegex.firstMatch(member);
113+
final match = absRegex.firstMatch(member);
115114
if (match != null) {
116115
return SentryStackFrame(
117116
instructionAddr: '0x${match.group(1)!}',

dart/lib/src/utils/stacktrace_utils.dart

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,11 @@ import 'package:meta/meta.dart';
88
// https://github.com/getsentry/sentry/blob/master/src/sentry/utils/sdk_crashes/README.rst
99
@internal
1010
StackTrace getCurrentStackTrace() => StackTrace.current;
11+
12+
/// Regex that matches an “abs …” stack-frame line emitted by split-debug-info builds.
13+
@internal
14+
final absRegex = RegExp(r'^\s*#[0-9]+ +abs +([A-Fa-f0-9]+)');
15+
16+
/// Regex that matches the header of a stacktrace emitted by split-debug-info builds.
17+
@internal
18+
final buildIdRegex = RegExp(r"build_id[:=] *'([A-Fa-f0-9]+)'");

dart/test/load_dart_debug_images_integration_test.dart

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -193,9 +193,32 @@ isolate_dso_base: 10000000
193193
});
194194
}
195195

196-
test('does not add itself to sdk.integrations if app is not obfuscated', () {
196+
test('does add itself to sdk.integrations if split debug info is true', () {
197197
final fixture = Fixture()
198-
..options.runtimeChecker = MockRuntimeChecker(isObfuscated: false);
198+
..options.runtimeChecker = MockRuntimeChecker(isSplitDebugInfo: true);
199+
fixture.callIntegration();
200+
expect(
201+
fixture.options.sdk.integrations
202+
.contains(LoadDartDebugImagesIntegration.integrationName),
203+
isTrue,
204+
);
205+
});
206+
207+
test('does add itself to sdk.integrations if obfuscation is true', () {
208+
final fixture = Fixture()
209+
..options.runtimeChecker = MockRuntimeChecker(isObfuscated: true);
210+
fixture.callIntegration();
211+
expect(
212+
fixture.options.sdk.integrations
213+
.contains(LoadDartDebugImagesIntegration.integrationName),
214+
isTrue,
215+
);
216+
});
217+
218+
test(
219+
'does not add itself to sdk.integrations if app obfuscation and split debug info is false',
220+
() {
221+
final fixture = Fixture()..options.runtimeChecker = MockRuntimeChecker();
199222
fixture.callIntegration();
200223
expect(
201224
fixture.options.sdk.integrations
@@ -204,9 +227,24 @@ isolate_dso_base: 10000000
204227
);
205228
});
206229

207-
test('does not add event processor to options if app is not obfuscated', () {
230+
test('does add event processor to options if split debug info is true', () {
208231
final fixture = Fixture()
209-
..options.runtimeChecker = MockRuntimeChecker(isObfuscated: false);
232+
..options.runtimeChecker = MockRuntimeChecker(isSplitDebugInfo: true);
233+
fixture.callIntegration();
234+
expect(fixture.options.eventProcessors.length, 1);
235+
});
236+
237+
test('does add event processor to options if obfuscation is true', () {
238+
final fixture = Fixture()
239+
..options.runtimeChecker = MockRuntimeChecker(isObfuscated: true);
240+
fixture.callIntegration();
241+
expect(fixture.options.eventProcessors.length, 1);
242+
});
243+
244+
test(
245+
'does not add event processor to options if app obfuscation and split debug info is false',
246+
() {
247+
final fixture = Fixture()..options.runtimeChecker = MockRuntimeChecker();
210248
fixture.callIntegration();
211249
expect(fixture.options.eventProcessors.length, 0);
212250
});
@@ -239,7 +277,8 @@ isolate_dso_base: 40000000
239277

240278
class Fixture {
241279
final options = defaultTestOptions()
242-
..runtimeChecker = MockRuntimeChecker(isObfuscated: true);
280+
..runtimeChecker =
281+
MockRuntimeChecker(isObfuscated: true, isSplitDebugInfo: true);
243282
late final factory = SentryStackTraceFactory(options);
244283

245284
void callIntegration() {

dart/test/mocks/mock_runtime_checker.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,15 @@ class MockRuntimeChecker extends RuntimeChecker with NoSuchMethodProvider {
88
this.isProfile = false,
99
this.isRelease = false,
1010
this.isObfuscated = false,
11+
this.isSplitDebugInfo = false,
1112
bool isRootZone = true,
1213
}) : super(isRootZone: isRootZone);
1314

1415
final bool isDebug;
1516
final bool isProfile;
1617
final bool isRelease;
1718
final bool isObfuscated;
19+
final bool isSplitDebugInfo;
1820

1921
@override
2022
bool isDebugMode() => isDebug;
@@ -27,4 +29,7 @@ class MockRuntimeChecker extends RuntimeChecker with NoSuchMethodProvider {
2729

2830
@override
2931
bool isAppObfuscated() => isObfuscated;
32+
33+
@override
34+
bool isSplitDebugInfoBuild() => isSplitDebugInfo;
3035
}

flutter/lib/src/integrations/native_load_debug_images_integration.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ class LoadNativeDebugImagesIntegration
2323
@override
2424
void call(Hub hub, SentryFlutterOptions options) {
2525
// ignore: invalid_use_of_internal_member
26-
if (options.runtimeChecker.isAppObfuscated()) {
26+
if (options.runtimeChecker.isAppObfuscated() ||
27+
options.runtimeChecker.isSplitDebugInfoBuild()) {
2728
options.addEventProcessor(
2829
_LoadNativeDebugImagesIntegrationEventProcessor(options, _native),
2930
);

flutter/test/integrations/load_native_debug_images_integration_test.dart

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,33 @@ void main() {
107107
});
108108
});
109109

110-
test('does not add itself to sdk.integrations if app is not obfuscated',
110+
test('does add itself to sdk.integrations if app split debug info is true',
111+
() async {
112+
final fixture =
113+
IntegrationTestFixture(LoadNativeDebugImagesIntegration.new);
114+
fixture.options.runtimeChecker = MockRuntimeChecker(isSplitDebugInfo: true);
115+
await fixture.registerIntegration();
116+
expect(
117+
fixture.options.sdk.integrations
118+
.contains(LoadNativeDebugImagesIntegration.integrationName),
119+
isTrue,
120+
);
121+
});
122+
123+
test('does add itself to sdk.integrations if obfuscation is true', () async {
124+
final fixture =
125+
IntegrationTestFixture(LoadNativeDebugImagesIntegration.new);
126+
fixture.options.runtimeChecker = MockRuntimeChecker(isObfuscated: true);
127+
await fixture.registerIntegration();
128+
expect(
129+
fixture.options.sdk.integrations
130+
.contains(LoadNativeDebugImagesIntegration.integrationName),
131+
isTrue,
132+
);
133+
});
134+
135+
test(
136+
'does not add itself to sdk.integrations if app obfuscation and split debug info is false',
111137
() async {
112138
final fixture =
113139
IntegrationTestFixture(LoadNativeDebugImagesIntegration.new);
@@ -120,7 +146,25 @@ void main() {
120146
);
121147
});
122148

123-
test('does not add event processor to options if app is not obfuscated',
149+
test('does add event processor to options if split debug info is true',
150+
() async {
151+
final fixture =
152+
IntegrationTestFixture(LoadNativeDebugImagesIntegration.new);
153+
fixture.options.runtimeChecker = MockRuntimeChecker(isSplitDebugInfo: true);
154+
await fixture.registerIntegration();
155+
expect(fixture.options.eventProcessors.length, 1);
156+
});
157+
158+
test('does add event processor to options if obfuscation is true', () async {
159+
final fixture =
160+
IntegrationTestFixture(LoadNativeDebugImagesIntegration.new);
161+
fixture.options.runtimeChecker = MockRuntimeChecker(isObfuscated: true);
162+
await fixture.registerIntegration();
163+
expect(fixture.options.eventProcessors.length, 1);
164+
});
165+
166+
test(
167+
'does not add event processor to options if app obfuscation and split debug info is false',
124168
() async {
125169
final fixture =
126170
IntegrationTestFixture(LoadNativeDebugImagesIntegration.new);

flutter/test/mocks.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,11 +80,13 @@ class MockRuntimeChecker with NoSuchMethodProvider implements RuntimeChecker {
8080
MockRuntimeChecker({
8181
this.buildMode = MockRuntimeCheckerBuildMode.debug,
8282
this.isObfuscated = false,
83+
this.isSplitDebugInfo = false,
8384
this.isRoot = true,
8485
});
8586

8687
final MockRuntimeCheckerBuildMode buildMode;
8788
final bool isObfuscated;
89+
final bool isSplitDebugInfo;
8890
final bool isRoot;
8991

9092
@override
@@ -99,6 +101,9 @@ class MockRuntimeChecker with NoSuchMethodProvider implements RuntimeChecker {
99101
@override
100102
bool isAppObfuscated() => isObfuscated;
101103

104+
@override
105+
bool isSplitDebugInfoBuild() => isSplitDebugInfo;
106+
102107
@override
103108
bool get isRootZone => isRoot;
104109
}

0 commit comments

Comments
 (0)