From ef330cf1323a6913deaac7940dd40c596067cd0c Mon Sep 17 00:00:00 2001 From: Cameron Carmichael Alonso Date: Wed, 30 Sep 2026 16:36:49 +0200 Subject: [PATCH 1/2] fix(android): Keep the build ID of ANR native frames without a function ThreadDumpParser matched "(BuildId: )" as the function name when a native frame had no symbol, for example a stripped library or an AOT library. The frame then had no build ID, so the parser did not add a debug image or set the relative address mode, and the frame could not be symbolicated. --- .../internal/threaddump/ThreadDumpParser.java | 5 +- .../threaddump/ThreadDumpParserTest.kt | 47 +++++++++++++++++++ 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/internal/threaddump/ThreadDumpParser.java b/sentry-android-core/src/main/java/io/sentry/android/core/internal/threaddump/ThreadDumpParser.java index bfb829ce1a9..fa55142924f 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/internal/threaddump/ThreadDumpParser.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/internal/threaddump/ThreadDumpParser.java @@ -79,8 +79,9 @@ public class ThreadDumpParser { // Optional function + ("(?:\\s+\\((?:" + "\\?\\?\\?" // " (???) marks a missing function, so don't capture it in a group - + "|(.*?)(?:\\+(\\d+))?" // " (func+1234)", offset is - // optional + // " (func+1234)", offset is optional. A frame without a function is followed + // directly by " (BuildId: ...)", so don't capture that as the function. + + "|(?!BuildId: )(.*?)(?:\\+(\\d+))?" + ")\\))?") // Optional " (BuildId: abcd1234abcd1234abcd1234abcd1234abcd1234)" + "(?:\\s+\\(BuildId: (.*?)\\))?"); diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/internal/threaddump/ThreadDumpParserTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/internal/threaddump/ThreadDumpParserTest.kt index e514ecbb88b..1eb38f0f82a 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/internal/threaddump/ThreadDumpParserTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/internal/threaddump/ThreadDumpParserTest.kt @@ -271,6 +271,53 @@ class ThreadDumpParserTest { assertEquals(true, frame.isNative) } + @Test + fun `keeps the build id of native frames without a function`() { + val lines = + Lines.readLines( + BufferedReader( + StringReader( + """ + ----- pid 123 at 2023-04-04 22:06:31.064728684+0200 ----- + "main" prio=5 tid=1 Native + | sysTid=123 nice=-10 cgrp=top-app sched=0/0 handle=0x7deceb74f8 + native: #00 pc 00000000012b5b58 /data/app/~~abc==/io.sentry.samples-xyz==/split_config.arm64_v8a.apk!libaot-app.dll.so (offset 0x7d4c000) (BuildId: c65d42d4e065806f7d454a78145f93371de5d567) + native: #01 pc 000000000022258c /apex/com.android.art/lib64/libart.so (BuildId: e6c658201ef1ec3760112fa1b838ab2c) + """ + .trimIndent() + ) + ) + ) + val parser = ThreadDumpParser(SentryOptions(), false) + + parser.parse(lines) + + val frames = parser.threads.single().stacktrace!!.frames!!.reversed() + + val apkFrame = frames[0] + assertEquals( + "/data/app/~~abc==/io.sentry.samples-xyz==/split_config.arm64_v8a.apk!libaot-app.dll.so " + + "(offset 0x7d4c000)", + apkFrame.`package`, + ) + assertNull(apkFrame.function) + assertEquals("0x00000000012b5b58", apkFrame.instructionAddr) + assertEquals("rel:d4425dc6-65e0-6f80-7d45-4a78145f9337", apkFrame.addrMode) + + val libFrame = frames[1] + assertEquals("/apex/com.android.art/lib64/libart.so", libFrame.`package`) + assertNull(libFrame.function) + assertEquals("rel:2058c6e6-f11e-37ec-6011-2fa1b838ab2c", libFrame.addrMode) + + val image = parser.debugImages.single { it.debugId == "d4425dc6-65e0-6f80-7d45-4a78145f9337" } + assertEquals( + "/data/app/~~abc==/io.sentry.samples-xyz==/split_config.arm64_v8a.apk!libaot-app.dll.so", + image.codeFile, + ) + assertEquals("c65d42d4e065806f7d454a78145f93371de5d567", image.codeId) + assertEquals("elf", image.type) + } + @Test fun `thread dump garbage`() { val lines = Lines.readLines(File("src/test/resources/thread_dump_bad_data.txt")) From a98b0206810a4b311af76eb900329ed44922e48b Mon Sep 17 00:00:00 2001 From: Cameron Carmichael Alonso Date: Wed, 30 Sep 2026 16:37:21 +0200 Subject: [PATCH 2/2] changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3147779ad42..c7b87abda2e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Fixes - Keep the videos of already captured session replay segments when the replay stops, so segments that are still queued are no longer sent without their video ([#6177](https://github.com/getsentry/sentry-java/pull/6177)) +- Keep the build ID of ANR native frames that have no function name, so frames in stripped or AOT-compiled libraries can be symbolicated ([#6196](https://github.com/getsentry/sentry-java/pull/6196)) ### Features