Skip to content

Commit 1d814c2

Browse files
author
Lauri Alanko
committed
Add debug images to ANR events
The images are parsed from the build ids and filenames in the thread dump's stack frames.
1 parent 9e5ed24 commit 1d814c2

File tree

4 files changed

+80
-1
lines changed

4 files changed

+80
-1
lines changed

sentry-android-core/src/main/java/io/sentry/android/core/AnrV2Integration.java

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
import io.sentry.hints.AbnormalExit;
2424
import io.sentry.hints.Backfillable;
2525
import io.sentry.hints.BlockingFlushHint;
26+
import io.sentry.protocol.DebugImage;
27+
import io.sentry.protocol.DebugMeta;
2628
import io.sentry.protocol.Message;
2729
import io.sentry.protocol.SentryId;
2830
import io.sentry.protocol.SentryThread;
@@ -40,6 +42,7 @@
4042
import java.util.ArrayList;
4143
import java.util.Collections;
4244
import java.util.List;
45+
import java.util.Map;
4346
import java.util.concurrent.TimeUnit;
4447
import org.jetbrains.annotations.ApiStatus;
4548
import org.jetbrains.annotations.NotNull;
@@ -267,6 +270,11 @@ private void reportAsSentryEvent(
267270
event.setMessage(sentryMessage);
268271
} else if (result.type == ParseResult.Type.DUMP) {
269272
event.setThreads(result.threads);
273+
if (!result.debugImages.isEmpty()) {
274+
final DebugMeta debugMeta = new DebugMeta();
275+
debugMeta.setImages(new ArrayList<>(result.debugImages.values()));
276+
event.setDebugMeta(debugMeta);
277+
}
270278
}
271279
event.setLevel(SentryLevel.FATAL);
272280
event.setTimestamp(DateUtils.getDateTime(anrTimestamp));
@@ -319,7 +327,7 @@ private void reportAsSentryEvent(
319327
// fall back to not reporting them
320328
return new ParseResult(ParseResult.Type.NO_DUMP);
321329
}
322-
return new ParseResult(ParseResult.Type.DUMP, dump, threads);
330+
return new ParseResult(ParseResult.Type.DUMP, dump, threads, threadDumpParser.getDebugImages());
323331
} catch (Throwable e) {
324332
options.getLogger().log(SentryLevel.WARNING, "Failed to parse ANR thread dump", e);
325333
return new ParseResult(ParseResult.Type.ERROR, dump);
@@ -403,24 +411,36 @@ enum Type {
403411
final Type type;
404412
final byte[] dump;
405413
final @Nullable List<SentryThread> threads;
414+
final @Nullable Map<String, DebugImage> debugImages;
406415

407416
ParseResult(final @NotNull Type type) {
408417
this.type = type;
409418
this.dump = null;
410419
this.threads = null;
420+
this.debugImages = null;
411421
}
412422

413423
ParseResult(final @NotNull Type type, final byte[] dump) {
414424
this.type = type;
415425
this.dump = dump;
416426
this.threads = null;
427+
this.debugImages = null;
417428
}
418429

419430
ParseResult(
420431
final @NotNull Type type, final byte[] dump, final @Nullable List<SentryThread> threads) {
421432
this.type = type;
422433
this.dump = dump;
423434
this.threads = threads;
435+
this.debugImages = null;
436+
}
437+
438+
ParseResult(
439+
final @NotNull Type type, final byte[] dump, final @Nullable List<SentryThread> threads, final @Nullable Map<String, DebugImage> debugImages) {
440+
this.type = type;
441+
this.dump = dump;
442+
this.threads = threads;
443+
this.debugImages = debugImages;
424444
}
425445
}
426446
}

sentry-android-core/src/main/java/io/sentry/android/core/internal/threaddump/ThreadDumpParser.java

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,19 @@
2222
import io.sentry.SentryLockReason;
2323
import io.sentry.SentryOptions;
2424
import io.sentry.SentryStackTraceFactory;
25+
import io.sentry.protocol.DebugImage;
2526
import io.sentry.protocol.SentryStackFrame;
2627
import io.sentry.protocol.SentryStackTrace;
2728
import io.sentry.protocol.SentryThread;
29+
import java.math.BigInteger;
30+
import java.nio.ByteBuffer;
31+
import java.nio.ByteOrder;
32+
import java.nio.BufferUnderflowException;
2833
import java.util.ArrayList;
34+
import java.util.Collection;
2935
import java.util.Collections;
3036
import java.util.HashMap;
37+
import java.util.LinkedHashMap;
3138
import java.util.List;
3239
import java.util.Map;
3340
import java.util.regex.Matcher;
@@ -91,10 +98,36 @@ public class ThreadDumpParser {
9198

9299
private final @NotNull SentryStackTraceFactory stackTraceFactory;
93100

101+
private final @NotNull Map<String, DebugImage> debugImages;
102+
94103
public ThreadDumpParser(final @NotNull SentryOptions options, final boolean isBackground) {
95104
this.options = options;
96105
this.isBackground = isBackground;
97106
this.stackTraceFactory = new SentryStackTraceFactory(options);
107+
this.debugImages = new HashMap<String, DebugImage>();
108+
}
109+
110+
@NotNull
111+
public Map<String, DebugImage> getDebugImages() {
112+
return debugImages;
113+
}
114+
115+
@Nullable
116+
private static String buildIdToDebugId(String buildId) {
117+
try {
118+
// Abuse BigInteger as a hex string parser. Extra byte needed to handle leading zeros.
119+
final ByteBuffer buf = ByteBuffer.wrap(new BigInteger("10" + buildId, 16).toByteArray());
120+
buf.get();
121+
return String.format("%08x-%04x-%04x-%04x-%04x%08x",
122+
buf.order(ByteOrder.LITTLE_ENDIAN).getInt(),
123+
buf.getShort(),
124+
buf.getShort(),
125+
buf.order(ByteOrder.BIG_ENDIAN).getShort(),
126+
buf.getShort(),
127+
buf.getInt());
128+
} catch (NumberFormatException | BufferUnderflowException e) {
129+
return null;
130+
}
98131
}
99132

100133
@NotNull
@@ -217,6 +250,19 @@ private SentryStackTrace parseStacktrace(
217250
frame.setInstructionAddr("0x" + nativeRe.group("pc"));
218251
frame.setPlatform("native");
219252

253+
final String buildId = nativeRe.group("buildid");
254+
final String debugId = buildId == null ? null : buildIdToDebugId(buildId);
255+
if (debugId != null) {
256+
if (!debugImages.containsKey(debugId)) {
257+
final DebugImage debugImage = new DebugImage();
258+
debugImage.setDebugId(debugId);
259+
debugImage.setType("elf");
260+
debugImage.setCodeFile(nativeRe.group("filename"));
261+
debugImage.setCodeId(buildId);
262+
debugImages.put(debugId, debugImage);
263+
}
264+
}
265+
220266
frames.add(frame);
221267
lastJavaFrame = null;
222268
} else if (matches(javaRe, text)) {

sentry-android-core/src/test/java/io/sentry/android/core/AnrV2IntegrationTest.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,12 @@ class AnrV2IntegrationTest {
307307
assertEquals(64, firstFrame.lineno)
308308
assertEquals("0x00000000000530b8", firstFrame.instructionAddr)
309309
assertEquals("native", firstFrame.platform)
310+
311+
val image = it.debugMeta?.images?.find {
312+
it.debugId == "741f3301-bbb0-b92c-58bd-c15282b8ec7b"
313+
}
314+
assertNotNull(image)
315+
assertEquals("/apex/com.android.runtime/lib64/bionic/libc.so", image.codeFile)
310316
},
311317
argThat<Hint> {
312318
val hint = HintUtils.getSentrySdkHint(this)

sentry-android-core/src/test/java/io/sentry/android/core/internal/threaddump/ThreadDumpParserTest.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,13 @@ class ThreadDumpParserTest {
146146
assertEquals("kotlinx.coroutines.DispatchedTask.run", deletedFrame.function)
147147
assertEquals(1816, deletedFrame.lineno)
148148
assertEquals("0x00000000020b89d8", deletedFrame.instructionAddr)
149+
150+
val debugImages = parser.debugImages
151+
val image = debugImages["499d48ba-c085-17cf-3209-da67405662f9"]
152+
assertNotNull(image)
153+
assertEquals("499d48ba-c085-17cf-3209-da67405662f9", image.debugId)
154+
assertEquals("/apex/com.android.runtime/lib64/bionic/libc.so", image.codeFile)
155+
assertEquals("ba489d4985c0cf173209da67405662f9", image.codeId)
149156
}
150157

151158
@Test

0 commit comments

Comments
 (0)