diff --git a/flutter/ios/Classes/SentryFlutterPlugin.h b/flutter/ios/Classes/SentryFlutterPlugin.h index d1a1af035..3611a1461 100644 --- a/flutter/ios/Classes/SentryFlutterPlugin.h +++ b/flutter/ios/Classes/SentryFlutterPlugin.h @@ -4,5 +4,11 @@ #import #endif +#import + @interface SentryFlutterPlugin : NSObject @end + +@interface SentryDebugImageProvider () +- (NSArray * _Nonnull)getDebugImagesForAddresses:(NSSet * _Nonnull)addresses isCrash:(BOOL)isCrash; +@end diff --git a/flutter/ios/Classes/SentryFlutterPluginApple.swift b/flutter/ios/Classes/SentryFlutterPluginApple.swift index 24e50cc16..85c7a8d7e 100644 --- a/flutter/ios/Classes/SentryFlutterPluginApple.swift +++ b/flutter/ios/Classes/SentryFlutterPluginApple.swift @@ -96,7 +96,7 @@ public class SentryFlutterPluginApple: NSObject, FlutterPlugin { loadContexts(result: result) case "loadImageList": - loadImageList(result: result) + loadImageList(call, result: result) case "initNativeSdk": initNativeSdk(call, result: result) @@ -277,8 +277,15 @@ public class SentryFlutterPluginApple: NSObject, FlutterPlugin { } } - private func loadImageList(result: @escaping FlutterResult) { - let debugImages = PrivateSentrySDKOnly.getDebugImages() as [DebugMeta] + private func loadImageList(_ call: FlutterMethodCall, result: @escaping FlutterResult) { + var debugImages: [DebugMeta] = [] + + if let imageAddresses = call.arguments as? Set { + debugImages = SentryDependencyContainer.sharedInstance().debugImageProvider.getDebugImages(forAddresses: imageAddresses, isCrash: false) as [DebugMeta] + } else { + debugImages = PrivateSentrySDKOnly.getDebugImages() as [DebugMeta] + } + result(debugImages.map { $0.serialize() }) } diff --git a/flutter/lib/src/integrations/load_image_list_integration.dart b/flutter/lib/src/integrations/load_image_list_integration.dart index 776c86640..b4442f5b2 100644 --- a/flutter/lib/src/integrations/load_image_list_integration.dart +++ b/flutter/lib/src/integrations/load_image_list_integration.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:io'; import 'package:sentry/sentry.dart'; import '../native/sentry_native_binding.dart'; @@ -33,7 +34,29 @@ class _LoadImageListIntegrationEventProcessor implements EventProcessor { @override Future apply(SentryEvent event, Hint hint) async { if (event.needsSymbolication()) { - final images = await _native.loadDebugImages(); + Set instructionAddresses = {}; + var exceptions = event.exceptions; + if (exceptions != null && exceptions.isNotEmpty) { + for (var e in exceptions) { + if (e.stackTrace != null) { + instructionAddresses.addAll( + _collectImageAddressesFromStackTrace(e.stackTrace!), + ); + } + } + } + + if (event.threads != null && event.threads!.isNotEmpty) { + for (var thread in event.threads!) { + if (thread.stacktrace != null) { + instructionAddresses.addAll( + _collectImageAddressesFromStackTrace(thread.stacktrace!), + ); + } + } + } + + final images = await _native.loadDebugImages(instructionAddresses); if (images != null) { return event.copyWith(debugMeta: DebugMeta(images: images)); } @@ -41,4 +64,14 @@ class _LoadImageListIntegrationEventProcessor implements EventProcessor { return event; } + + Set _collectImageAddressesFromStackTrace(SentryStackTrace trace) { + Set instructionAddresses = {}; + for (var frame in trace.frames) { + if (frame.imageAddr != null) { + instructionAddresses.add(frame.instructionAddr!); + } + } + return instructionAddresses; + } } diff --git a/flutter/lib/src/native/sentry_native_binding.dart b/flutter/lib/src/native/sentry_native_binding.dart index 44ee6432b..1e9ad2e6e 100644 --- a/flutter/lib/src/native/sentry_native_binding.dart +++ b/flutter/lib/src/native/sentry_native_binding.dart @@ -52,7 +52,7 @@ abstract class SentryNativeBinding { Future?> collectProfile( SentryId traceId, int startTimeNs, int endTimeNs); - Future?> loadDebugImages(); + Future?> loadDebugImages(Set instructionAddresses); Future pauseAppHangTracking(); diff --git a/flutter/lib/src/native/sentry_native_channel.dart b/flutter/lib/src/native/sentry_native_channel.dart index 4b2fa464e..11241c82a 100644 --- a/flutter/lib/src/native/sentry_native_channel.dart +++ b/flutter/lib/src/native/sentry_native_channel.dart @@ -178,10 +178,11 @@ class SentryNativeChannel }); @override - Future?> loadDebugImages() => + Future?> loadDebugImages(Set instructionAddresses) => tryCatchAsync('loadDebugImages', () async { - final images = await channel - .invokeListMethod>('loadImageList'); + final images = + await channel.invokeListMethod>>( + 'loadImageList', instructionAddresses); return images ?.map((e) => e.cast()) .map(DebugImage.fromJson) diff --git a/flutter/test/integrations/load_image_list_test.dart b/flutter/test/integrations/load_image_list_test.dart index 35e59b759..214ad2395 100644 --- a/flutter/test/integrations/load_image_list_test.dart +++ b/flutter/test/integrations/load_image_list_test.dart @@ -26,7 +26,7 @@ void main() { setUp(() async { fixture = IntegrationTestFixture(LoadImageListIntegration.new); - when(fixture.binding.loadDebugImages()) + when(fixture.binding.loadDebugImages(any)) .thenAnswer((_) async => imageList); await fixture.registerIntegration(); }); @@ -44,14 +44,14 @@ void main() { await fixture.hub.captureException(StateError('error'), stackTrace: StackTrace.current); - verifyNever(fixture.binding.loadDebugImages()); + verifyNever(fixture.binding.loadDebugImages({})); }); test('Native layer is not called if the event has no stack traces', () async { await fixture.hub.captureException(StateError('error')); - verifyNever(fixture.binding.loadDebugImages()); + verifyNever(fixture.binding.loadDebugImages({})); }); test('Native layer is called because stack traces are not symbolicated', @@ -67,7 +67,9 @@ void main() { #01 abs 000000723d637527 virt 00000000001f0527 _kDartIsolateSnapshotInstructions+0x1e5527 '''); - verify(fixture.binding.loadDebugImages()).called(1); + verify( + fixture.binding.loadDebugImages(any), + ).called(1); }); test('Event processor adds image list to the event', () async { @@ -100,13 +102,13 @@ void main() { expect(fixture.options.eventProcessors.length, 1); await fixture.hub.captureMessage('error'); - verifyNever(fixture.binding.loadDebugImages()); + verifyNever(fixture.binding.loadDebugImages({"0x6f80b000"})); }); }); } SentryEvent _getEvent() { - final frame = SentryStackFrame(platform: 'native'); + final frame = SentryStackFrame(platform: 'native', imageAddr: "0x6f80b000"); final st = SentryStackTrace(frames: [frame]); return SentryEvent(threads: [SentryThread(stacktrace: st)]); } diff --git a/flutter/test/mocks.mocks.dart b/flutter/test/mocks.mocks.dart index 3f94ca027..6491fca0d 100644 --- a/flutter/test/mocks.mocks.dart +++ b/flutter/test/mocks.mocks.dart @@ -1536,10 +1536,12 @@ class MockSentryNativeBinding extends _i1.Mock ) as _i7.Future?>); @override - _i7.Future?> loadDebugImages() => (super.noSuchMethod( + _i7.Future?> loadDebugImages( + Set? instructionAddresses) => + (super.noSuchMethod( Invocation.method( #loadDebugImages, - [], + [instructionAddresses], ), returnValue: _i7.Future?>.value(), ) as _i7.Future?>); diff --git a/flutter/test/sentry_native_channel_test.dart b/flutter/test/sentry_native_channel_test.dart index 0428349d4..1e2a92f18 100644 --- a/flutter/test/sentry_native_channel_test.dart +++ b/flutter/test/sentry_native_channel_test.dart @@ -279,7 +279,7 @@ void main() { when(channel.invokeMethod('loadImageList')) .thenAnswer((invocation) async => json); - final data = await sut.loadDebugImages(); + final data = await sut.loadDebugImages({"0x6f80b000"}); expect(data?.map((v) => v.toJson()), json); });