diff --git a/CHANGELOG.md b/CHANGELOG.md index 4da8060b6b2..0a2dde544f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Fixes - Android: Add proguard rules to prevent error about missing Replay classes ([#5153](https://github.com/getsentry/sentry-java/pull/5153)) +- Android: Remove the dependency on protobuf-lite for tombstones ([#5157](https://github.com/getsentry/sentry-java/pull/5157)) ## 8.34.0 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index dd2a471f695..61fbefd9152 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -41,7 +41,6 @@ spotless = "7.0.4" gummyBears = "0.12.0" camerax = "1.3.0" openfeature = "1.18.2" -protobuf = "3.25.8" [plugins] kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } @@ -61,7 +60,6 @@ spotless = { id = "com.diffplug.spotless", version.ref = "spotless" } detekt = { id = "io.gitlab.arturbosch.detekt", version = "1.23.8" } jacoco-android = { id = "com.mxalbert.gradle.jacoco-android", version = "0.2.0" } kover = { id = "org.jetbrains.kotlinx.kover", version = "0.7.3" } -protobuf = { id = "com.google.protobuf", version = "0.9.5" } vanniktech-maven-publish = { id = "com.vanniktech.maven.publish", version = "0.30.0" } springboot2 = { id = "org.springframework.boot", version.ref = "springboot2" } springboot3 = { id = "org.springframework.boot", version.ref = "springboot3" } @@ -145,8 +143,7 @@ otel-javaagent-extension-api = { module = "io.opentelemetry.javaagent:openteleme otel-semconv = { module = "io.opentelemetry.semconv:opentelemetry-semconv", version.ref = "otelSemanticConventions" } otel-semconv-incubating = { module = "io.opentelemetry.semconv:opentelemetry-semconv-incubating", version.ref = "otelSemanticConventionsAlpha" } p6spy = { module = "p6spy:p6spy", version = "3.9.1" } -protobuf-javalite = { module = "com.google.protobuf:protobuf-javalite", version.ref = "protobuf"} -protoc = { module = "com.google.protobuf:protoc", version.ref = "protobuf" } +epitaph = { module = "com.abovevacant:epitaph", version = "0.1.0" } quartz = { module = "org.quartz-scheduler:quartz", version = "2.3.0" } reactor-core = { module = "io.projectreactor:reactor-core", version = "3.5.3" } retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" } diff --git a/sentry-android-core/build.gradle.kts b/sentry-android-core/build.gradle.kts index 23dc964d3d6..1134e948226 100644 --- a/sentry-android-core/build.gradle.kts +++ b/sentry-android-core/build.gradle.kts @@ -8,7 +8,6 @@ plugins { alias(libs.plugins.jacoco.android) alias(libs.plugins.errorprone) alias(libs.plugins.gradle.versions) - alias(libs.plugins.protobuf) } android { @@ -84,7 +83,7 @@ dependencies { implementation(libs.androidx.lifecycle.common.java8) implementation(libs.androidx.lifecycle.process) implementation(libs.androidx.core) - implementation(libs.protobuf.javalite) + implementation(libs.epitaph) errorprone(libs.errorprone.core) errorprone(libs.nopen.checker) @@ -113,10 +112,3 @@ dependencies { testRuntimeOnly(libs.androidx.fragment.ktx) testRuntimeOnly(libs.timber) } - -protobuf { - protoc { artifact = libs.protoc.get().toString() } - generateProtoTasks { - all().forEach { task -> task.builtins { create("java") { option("lite") } } } - } -} diff --git a/sentry-android-core/proguard-rules.pro b/sentry-android-core/proguard-rules.pro index 2b49c949db9..aca674442bf 100644 --- a/sentry-android-core/proguard-rules.pro +++ b/sentry-android-core/proguard-rules.pro @@ -54,9 +54,6 @@ -keepnames class io.sentry.android.core.ApplicationNotResponding -# protobuf-java lite -# https://github.com/protocolbuffers/protobuf/blob/5d876c9fec1a6f2feb0750694f803f89312bffff/java/lite.md#r8-rule-to-make-production-app-builds-work --keep class * extends com.google.protobuf.GeneratedMessageLite { *; } ##---------------End: proguard configuration for android-core ---------- diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/internal/tombstone/TombstoneParser.java b/sentry-android-core/src/main/java/io/sentry/android/core/internal/tombstone/TombstoneParser.java index 3235b566556..1f142b52c9a 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/internal/tombstone/TombstoneParser.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/internal/tombstone/TombstoneParser.java @@ -1,6 +1,13 @@ package io.sentry.android.core.internal.tombstone; import androidx.annotation.NonNull; +import com.abovevacant.epitaph.core.BacktraceFrame; +import com.abovevacant.epitaph.core.MemoryMapping; +import com.abovevacant.epitaph.core.Register; +import com.abovevacant.epitaph.core.Signal; +import com.abovevacant.epitaph.core.Tombstone; +import com.abovevacant.epitaph.core.TombstoneThread; +import com.abovevacant.epitaph.wire.TombstoneDecoder; import io.sentry.SentryEvent; import io.sentry.SentryLevel; import io.sentry.SentryStackTraceFactory; @@ -27,7 +34,7 @@ public class TombstoneParser implements Closeable { - private final InputStream tombstoneStream; + @Nullable private final InputStream tombstoneStream; @NotNull private final List inAppIncludes; @NotNull private final List inAppExcludes; @Nullable private final String nativeLibraryDir; @@ -38,7 +45,14 @@ private static String formatHex(long value) { } public TombstoneParser( - @NonNull final InputStream tombstoneStream, + @NotNull List inAppIncludes, + @NotNull List inAppExcludes, + @Nullable String nativeLibraryDir) { + this(null, inAppIncludes, inAppExcludes, nativeLibraryDir); + } + + public TombstoneParser( + @Nullable final InputStream tombstoneStream, @NotNull List inAppIncludes, @NotNull List inAppExcludes, @Nullable String nativeLibraryDir) { @@ -58,10 +72,14 @@ public TombstoneParser( @NonNull public SentryEvent parse() throws IOException { - @NonNull - final TombstoneProtos.Tombstone tombstone = - TombstoneProtos.Tombstone.parseFrom(tombstoneStream); + if (tombstoneStream == null) { + throw new IOException("No InputStream provided; use parse(Tombstone) instead."); + } + return parse(TombstoneDecoder.decode(tombstoneStream)); + } + @NonNull + public SentryEvent parse(@NonNull final Tombstone tombstone) { final SentryEvent event = new SentryEvent(); event.setLevel(SentryLevel.FATAL); @@ -79,19 +97,18 @@ public SentryEvent parse() throws IOException { @NonNull private List createThreads( - @NonNull final TombstoneProtos.Tombstone tombstone, @NonNull final SentryException exc) { + @NonNull final Tombstone tombstone, @NonNull final SentryException exc) { final List threads = new ArrayList<>(); - for (Map.Entry threadEntry : - tombstone.getThreadsMap().entrySet()) { - final TombstoneProtos.Thread threadEntryValue = threadEntry.getValue(); + for (Map.Entry threadEntry : tombstone.threads.entrySet()) { + final TombstoneThread threadEntryValue = threadEntry.getValue(); final SentryThread thread = new SentryThread(); thread.setId(Long.valueOf(threadEntry.getKey())); - thread.setName(threadEntryValue.getName()); + thread.setName(threadEntryValue.name); final SentryStackTrace stacktrace = createStackTrace(threadEntryValue); thread.setStacktrace(stacktrace); - if (tombstone.getTid() == threadEntryValue.getId()) { + if (tombstone.tid == threadEntryValue.id) { thread.setCrashed(true); // even though we refer to the thread_id from the exception, // the backend currently requires a stack-trace in exception @@ -104,30 +121,30 @@ private List createThreads( } @NonNull - private SentryStackTrace createStackTrace(@NonNull final TombstoneProtos.Thread thread) { + private SentryStackTrace createStackTrace(@NonNull final TombstoneThread thread) { final List frames = new ArrayList<>(); - for (TombstoneProtos.BacktraceFrame frame : thread.getCurrentBacktraceList()) { - if (frame.getFileName().endsWith("libart.so")) { + for (BacktraceFrame frame : thread.backtrace) { + if (frame.fileName.endsWith("libart.so")) { // We ignore all ART frames for time being because they aren't actionable for app developers continue; } - if (frame.getFileName().startsWith(" registers = new HashMap<>(); - for (TombstoneProtos.Register register : thread.getRegistersList()) { - registers.put(register.getName(), formatHex(register.getU64())); + for (Register register : thread.registers) { + registers.put(register.name, formatHex(register.value)); } stacktrace.setRegisters(registers); @@ -160,17 +177,17 @@ private SentryStackTrace createStackTrace(@NonNull final TombstoneProtos.Thread } @NonNull - private List createException(@NonNull TombstoneProtos.Tombstone tombstone) { + private List createException(@NonNull Tombstone tombstone) { final SentryException exception = new SentryException(); - if (tombstone.hasSignalInfo()) { - final TombstoneProtos.Signal signalInfo = tombstone.getSignalInfo(); - exception.setType(signalInfo.getName()); - exception.setValue(excTypeValueMap.get(signalInfo.getName())); + if (tombstone.hasSignal()) { + final Signal signalInfo = tombstone.signal; + exception.setType(signalInfo.name); + exception.setValue(excTypeValueMap.get(signalInfo.name)); exception.setMechanism(createMechanismFromSignalInfo(signalInfo)); } - exception.setThreadId((long) tombstone.getTid()); + exception.setThreadId((long) tombstone.tid); final List exceptions = new ArrayList<>(1); exceptions.add(exception); @@ -178,8 +195,7 @@ private List createException(@NonNull TombstoneProtos.Tombstone } @NonNull - private static Mechanism createMechanismFromSignalInfo( - @NonNull final TombstoneProtos.Signal signalInfo) { + private static Mechanism createMechanismFromSignalInfo(@NonNull final Signal signalInfo) { final Mechanism mechanism = new Mechanism(); mechanism.setType(NativeExceptionMechanism.TOMBSTONE.getValue()); @@ -187,38 +203,38 @@ private static Mechanism createMechanismFromSignalInfo( mechanism.setSynthetic(true); final Map meta = new HashMap<>(); - meta.put("number", signalInfo.getNumber()); - meta.put("name", signalInfo.getName()); - meta.put("code", signalInfo.getCode()); - meta.put("code_name", signalInfo.getCodeName()); + meta.put("number", signalInfo.number); + meta.put("name", signalInfo.name); + meta.put("code", signalInfo.code); + meta.put("code_name", signalInfo.codeName); mechanism.setMeta(meta); return mechanism; } @NonNull - private Message constructMessage(@NonNull final TombstoneProtos.Tombstone tombstone) { + private Message constructMessage(@NonNull final Tombstone tombstone) { final Message message = new Message(); - final TombstoneProtos.Signal signalInfo = tombstone.getSignalInfo(); + final Signal signalInfo = tombstone.signal; // reproduce the message `debuggerd` would use to dump the stack trace in logcat - String command = String.join(" ", tombstone.getCommandLineList()); - if (tombstone.hasSignalInfo()) { - String abortMessage = tombstone.getAbortMessage(); + String command = String.join(" ", tombstone.commandLine); + if (tombstone.hasSignal()) { + String abortMessage = tombstone.abortMessage; message.setFormatted( String.format( Locale.ROOT, "%sFatal signal %s (%d), %s (%d), pid = %d (%s)", !abortMessage.isEmpty() ? abortMessage + ": " : "", - signalInfo.getName(), - signalInfo.getNumber(), - signalInfo.getCodeName(), - signalInfo.getCode(), - tombstone.getPid(), + signalInfo.name, + signalInfo.number, + signalInfo.codeName, + signalInfo.code, + tombstone.pid, command)); } else { message.setFormatted( - String.format(Locale.ROOT, "Fatal exit pid = %d (%s)", tombstone.getPid(), command)); + String.format(Locale.ROOT, "Fatal exit pid = %d (%s)", tombstone.pid, command)); } return message; @@ -236,11 +252,11 @@ private static class ModuleAccumulator { long beginAddress; long endAddress; - ModuleAccumulator(TombstoneProtos.MemoryMapping mapping) { - this.mappingName = mapping.getMappingName(); - this.buildId = mapping.getBuildId(); - this.beginAddress = mapping.getBeginAddress(); - this.endAddress = mapping.getEndAddress(); + ModuleAccumulator(MemoryMapping mapping) { + this.mappingName = mapping.mappingName; + this.buildId = mapping.buildId; + this.beginAddress = mapping.beginAddress; + this.endAddress = mapping.endAddress; } void extendTo(long newEndAddress) { @@ -266,7 +282,7 @@ DebugImage toDebugImage() { } } - private DebugMeta createDebugMeta(@NonNull final TombstoneProtos.Tombstone tombstone) { + private DebugMeta createDebugMeta(@NonNull final Tombstone tombstone) { final List images = new ArrayList<>(); // Coalesce memory mappings into modules similar to how sentry-native does it. @@ -277,27 +293,27 @@ private DebugMeta createDebugMeta(@NonNull final TombstoneProtos.Tombstone tombs // combined with non-empty build_id as a proxy for this check. ModuleAccumulator currentModule = null; - for (TombstoneProtos.MemoryMapping mapping : tombstone.getMemoryMappingsList()) { + for (MemoryMapping mapping : tombstone.memoryMappings) { // Skip mappings that are not readable - if (!mapping.getRead()) { + if (!mapping.read) { continue; } // Skip mappings with empty name or in /dev/ - final String mappingName = mapping.getMappingName(); + final String mappingName = mapping.mappingName; if (mappingName.isEmpty() || mappingName.startsWith("/dev/")) { continue; } - final boolean hasBuildId = !mapping.getBuildId().isEmpty(); - final boolean isFileStart = mapping.getOffset() == 0; + final boolean hasBuildId = !mapping.buildId.isEmpty(); + final boolean isFileStart = mapping.offset == 0; if (hasBuildId && isFileStart) { // Check for duplicated mappings: On Android, the same ELF can have multiple // mappings at offset 0 with different permissions (r--p, r-xp, r--p). // If it's the same file as the current module, just extend it. if (currentModule != null && mappingName.equals(currentModule.mappingName)) { - currentModule.extendTo(mapping.getEndAddress()); + currentModule.extendTo(mapping.endAddress); continue; } @@ -313,7 +329,7 @@ private DebugMeta createDebugMeta(@NonNull final TombstoneProtos.Tombstone tombs currentModule = new ModuleAccumulator(mapping); } else if (currentModule != null && mappingName.equals(currentModule.mappingName)) { // Extend the current module with this mapping (same file, continuation) - currentModule.extendTo(mapping.getEndAddress()); + currentModule.extendTo(mapping.endAddress); } } @@ -333,6 +349,8 @@ private DebugMeta createDebugMeta(@NonNull final TombstoneProtos.Tombstone tombs @Override public void close() throws IOException { - tombstoneStream.close(); + if (tombstoneStream != null) { + tombstoneStream.close(); + } } } diff --git a/sentry-android-core/src/main/proto/io/sentry/android/core/internal/tombstone/tombstone.proto b/sentry-android-core/src/main/proto/io/sentry/android/core/internal/tombstone/tombstone.proto deleted file mode 100644 index 2f9cbe52850..00000000000 --- a/sentry-android-core/src/main/proto/io/sentry/android/core/internal/tombstone/tombstone.proto +++ /dev/null @@ -1,218 +0,0 @@ -// Added and adapted from: https://android.googlesource.com/platform/system/core/+/refs/heads/main/debuggerd/proto/tombstone.proto -// Sentry changes: -// * change the java_package -// -// Copyright (C) 2020 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// Protobuf definition for Android tombstones. -// -// An app can get hold of these for any `REASON_CRASH_NATIVE` instance of -// `android.app.ApplicationExitInfo`. -// -// https://developer.android.com/reference/android/app/ApplicationExitInfo#getTraceInputStream() -// -syntax = "proto3"; -option java_package = "io.sentry.android.core.internal.tombstone"; -option java_outer_classname = "TombstoneProtos"; -// NOTE TO OEMS: -// If you add custom fields to this proto, do not use numbers in the reserved range. -// NOTE TO CONSUMERS: -// With proto3 -- unlike proto2 -- HasValue is unreliable for any field -// where the default value for that type is also a valid value for the field. -// This means, for example, that a boolean that is false or an integer that -// is zero will appear to be missing --- but because they're not actually -// marked as `optional` in this schema, consumers should just use values -// without first checking whether or not they're "present". -// https://protobuf.dev/programming-guides/proto3/#default -message CrashDetail { - bytes name = 1; - bytes data = 2; - reserved 3 to 999; -} -message StackHistoryBufferEntry { - BacktraceFrame addr = 1; - uint64 fp = 2; - uint64 tag = 3; - reserved 4 to 999; -} -message StackHistoryBuffer { - uint64 tid = 1; - repeated StackHistoryBufferEntry entries = 2; - reserved 3 to 999; -} -message Tombstone { - Architecture arch = 1; - Architecture guest_arch = 24; - string build_fingerprint = 2; - string revision = 3; - string timestamp = 4; - uint32 pid = 5; - uint32 tid = 6; - uint32 uid = 7; - string selinux_label = 8; - repeated string command_line = 9; - // Process uptime in seconds. - uint32 process_uptime = 20; - Signal signal_info = 10; - string abort_message = 14; - repeated CrashDetail crash_details = 21; - repeated Cause causes = 15; - map threads = 16; - map guest_threads = 25; - repeated MemoryMapping memory_mappings = 17; - repeated LogBuffer log_buffers = 18; - repeated FD open_fds = 19; - uint32 page_size = 22; - bool has_been_16kb_mode = 23; - StackHistoryBuffer stack_history_buffer = 26; - reserved 27 to 999; -} -enum Architecture { - ARM32 = 0; - ARM64 = 1; - X86 = 2; - X86_64 = 3; - RISCV64 = 4; - NONE = 5; - reserved 6 to 999; -} -message Signal { - int32 number = 1; - string name = 2; - int32 code = 3; - string code_name = 4; - bool has_sender = 5; - int32 sender_uid = 6; - int32 sender_pid = 7; - bool has_fault_address = 8; - uint64 fault_address = 9; - // Note, may or may not contain the dump of the actual memory contents. Currently, on arm64, we - // only include metadata, and not the contents. - MemoryDump fault_adjacent_metadata = 10; - reserved 11 to 999; -} -message HeapObject { - uint64 address = 1; - uint64 size = 2; - uint64 allocation_tid = 3; - repeated BacktraceFrame allocation_backtrace = 4; - uint64 deallocation_tid = 5; - repeated BacktraceFrame deallocation_backtrace = 6; -} -message MemoryError { - enum Tool { - GWP_ASAN = 0; - SCUDO = 1; - reserved 2 to 999; - } - Tool tool = 1; - enum Type { - UNKNOWN = 0; - USE_AFTER_FREE = 1; - DOUBLE_FREE = 2; - INVALID_FREE = 3; - BUFFER_OVERFLOW = 4; - BUFFER_UNDERFLOW = 5; - reserved 6 to 999; - } - Type type = 2; - oneof location { - HeapObject heap = 3; - } - reserved 4 to 999; -} -message Cause { - string human_readable = 1; - oneof details { - MemoryError memory_error = 2; - } - reserved 3 to 999; -} -message Register { - string name = 1; - uint64 u64 = 2; - reserved 3 to 999; -} -message Thread { - int32 id = 1; - string name = 2; - repeated Register registers = 3; - repeated string backtrace_note = 7; - repeated string unreadable_elf_files = 9; - repeated BacktraceFrame current_backtrace = 4; - repeated MemoryDump memory_dump = 5; - int64 tagged_addr_ctrl = 6; - int64 pac_enabled_keys = 8; - reserved 10 to 999; -} -message BacktraceFrame { - uint64 rel_pc = 1; - uint64 pc = 2; - uint64 sp = 3; - string function_name = 4; - uint64 function_offset = 5; - string file_name = 6; - uint64 file_map_offset = 7; - string build_id = 8; - reserved 9 to 999; -} -message ArmMTEMetadata { - // One memory tag per granule (e.g. every 16 bytes) of regular memory. - bytes memory_tags = 1; - reserved 2 to 999; -} -message MemoryDump { - string register_name = 1; - string mapping_name = 2; - uint64 begin_address = 3; - bytes memory = 4; - oneof metadata { - ArmMTEMetadata arm_mte_metadata = 6; - } - reserved 5, 7 to 999; -} -message MemoryMapping { - uint64 begin_address = 1; - uint64 end_address = 2; - uint64 offset = 3; - bool read = 4; - bool write = 5; - bool execute = 6; - string mapping_name = 7; - string build_id = 8; - uint64 load_bias = 9; - reserved 10 to 999; -} -message FD { - int32 fd = 1; - string path = 2; - string owner = 3; - uint64 tag = 4; - reserved 5 to 999; -} -message LogBuffer { - string name = 1; - repeated LogMessage logs = 2; - reserved 3 to 999; -} -message LogMessage { - string timestamp = 1; - uint32 pid = 2; - uint32 tid = 3; - uint32 priority = 4; - string tag = 5; - string message = 6; - reserved 7 to 999; -} diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/internal/tombstone/TombstoneParserTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/internal/tombstone/TombstoneParserTest.kt index 516b9190022..34e704188c4 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/internal/tombstone/TombstoneParserTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/internal/tombstone/TombstoneParserTest.kt @@ -1,9 +1,13 @@ package io.sentry.android.core.internal.tombstone +import com.abovevacant.epitaph.core.BacktraceFrame +import com.abovevacant.epitaph.core.MemoryMapping +import com.abovevacant.epitaph.core.Signal +import com.abovevacant.epitaph.core.Tombstone +import com.abovevacant.epitaph.core.TombstoneThread import io.sentry.ILogger import io.sentry.JsonObjectWriter import io.sentry.protocol.DebugMeta -import java.io.ByteArrayInputStream import java.io.StringWriter import java.util.zip.GZIPInputStream import kotlin.test.Test @@ -56,12 +60,15 @@ class TombstoneParserTest { val nativeLibraryDir = "/data/app/~~gu-2hA9_Zg6tfIuDAbLpKA==/io.sentry.samples.android-MFqmKAMnl9AjNlHcO3mejA==/lib/arm64" + val parser = TombstoneParser(inAppIncludes, inAppExcludes, nativeLibraryDir) + @Test fun `parses a snapshot tombstone into Event`() { val tombstoneStream = GZIPInputStream(TombstoneParserTest::class.java.getResourceAsStream("/tombstone.pb.gz")) - val parser = TombstoneParser(tombstoneStream, inAppIncludes, inAppExcludes, nativeLibraryDir) - val event = parser.parse() + val streamParser = + TombstoneParser(tombstoneStream, inAppIncludes, inAppExcludes, nativeLibraryDir) + val event = streamParser.parse() // top-level data assertNotNull(event.eventId) @@ -137,87 +144,82 @@ class TombstoneParserTest { val buildId = "f1c3bcc0279865fe3058404b2831d9e64135386c" val tombstone = - TombstoneProtos.Tombstone.newBuilder() - .setPid(1234) - .setTid(1234) - .setSignalInfo( - TombstoneProtos.Signal.newBuilder() - .setNumber(11) - .setName("SIGSEGV") - .setCode(1) - .setCodeName("SEGV_MAPERR") - ) + Tombstone.Builder() + .pid(1234) + .tid(1234) + .signal(Signal(11, "SIGSEGV", 1, "SEGV_MAPERR", false, 0, 0, false, 0, null)) // First mapping: r--p at offset 0 (ELF header, has build_id) - .addMemoryMappings( - TombstoneProtos.MemoryMapping.newBuilder() - .setBuildId(buildId) - .setMappingName("/system/lib64/libc.so") - .setBeginAddress(0x7000000000) - .setEndAddress(0x7000001000) - .setOffset(0) - .setRead(true) - .setWrite(false) - .setExecute(false) + .addMemoryMapping( + MemoryMapping( + 0x7000000000, + 0x7000001000, + 0, + true, + false, + false, + "/system/lib64/libc.so", + buildId, + 0, + ) ) // Second mapping: r-xp at offset 0x1000 (executable segment) - .addMemoryMappings( - TombstoneProtos.MemoryMapping.newBuilder() - .setBuildId(buildId) - .setMappingName("/system/lib64/libc.so") - .setBeginAddress(0x7000001000) - .setEndAddress(0x7000010000) - .setOffset(0x1000) - .setRead(true) - .setWrite(false) - .setExecute(true) + .addMemoryMapping( + MemoryMapping( + 0x7000001000, + 0x7000010000, + 0x1000, + true, + false, + true, + "/system/lib64/libc.so", + buildId, + 0, + ) ) // Third mapping: r--p at offset 0x10000 (read-only data) - .addMemoryMappings( - TombstoneProtos.MemoryMapping.newBuilder() - .setBuildId(buildId) - .setMappingName("/system/lib64/libc.so") - .setBeginAddress(0x7000010000) - .setEndAddress(0x7000011000) - .setOffset(0x10000) - .setRead(true) - .setWrite(false) - .setExecute(false) + .addMemoryMapping( + MemoryMapping( + 0x7000010000, + 0x7000011000, + 0x10000, + true, + false, + false, + "/system/lib64/libc.so", + buildId, + 0, + ) ) // Fourth mapping: rw-p at offset 0x11000 (writable data) - .addMemoryMappings( - TombstoneProtos.MemoryMapping.newBuilder() - .setBuildId(buildId) - .setMappingName("/system/lib64/libc.so") - .setBeginAddress(0x7000011000) - .setEndAddress(0x7000012000) - .setOffset(0x11000) - .setRead(true) - .setWrite(true) - .setExecute(false) + .addMemoryMapping( + MemoryMapping( + 0x7000011000, + 0x7000012000, + 0x11000, + true, + true, + false, + "/system/lib64/libc.so", + buildId, + 0, + ) ) - .putThreads( - 1234, - TombstoneProtos.Thread.newBuilder() - .setId(1234) - .setName("main") - .addCurrentBacktrace( - TombstoneProtos.BacktraceFrame.newBuilder() - .setPc(0x7000001100) - .setFunctionName("crash") - .setFileName("/system/lib64/libc.so") - ) - .build(), + .addThread( + TombstoneThread( + 1234, + "main", + emptyList(), + emptyList(), + emptyList(), + listOf(BacktraceFrame(0, 0x7000001100, 0, "crash", 0, "/system/lib64/libc.so", 0, "")), + emptyList(), + 0, + 0, + ) ) .build() - val parser = - TombstoneParser( - ByteArrayInputStream(tombstone.toByteArray()), - inAppIncludes, - inAppExcludes, - nativeLibraryDir, - ) - val event = parser.parse() + val event = parser.parse(tombstone) // All 4 mappings should be coalesced into a single module val images = event.debugMeta!!.images!! @@ -238,77 +240,69 @@ class TombstoneParserTest { val buildId = "f1c3bcc0279865fe3058404b2831d9e64135386c" val tombstone = - TombstoneProtos.Tombstone.newBuilder() - .setPid(1234) - .setTid(1234) - .setSignalInfo( - TombstoneProtos.Signal.newBuilder() - .setNumber(11) - .setName("SIGSEGV") - .setCode(1) - .setCodeName("SEGV_MAPERR") - ) + Tombstone.Builder() + .pid(1234) + .tid(1234) + .signal(Signal(11, "SIGSEGV", 1, "SEGV_MAPERR", false, 0, 0, false, 0, null)) // First mapping: r--p at offset 0 - .addMemoryMappings( - TombstoneProtos.MemoryMapping.newBuilder() - .setBuildId(buildId) - .setMappingName("/system/lib64/libdl.so") - .setBeginAddress(0x7000000000) - .setEndAddress(0x7000001000) - .setOffset(0) - .setRead(true) - .setWrite(false) - .setExecute(false) + .addMemoryMapping( + MemoryMapping( + 0x7000000000, + 0x7000001000, + 0, + true, + false, + false, + "/system/lib64/libdl.so", + buildId, + 0, + ) ) // Second mapping: r-xp at offset 0 (duplicate!) - .addMemoryMappings( - TombstoneProtos.MemoryMapping.newBuilder() - .setBuildId(buildId) - .setMappingName("/system/lib64/libdl.so") - .setBeginAddress(0x7000001000) - .setEndAddress(0x7000002000) - .setOffset(0) - .setRead(true) - .setWrite(false) - .setExecute(true) + .addMemoryMapping( + MemoryMapping( + 0x7000001000, + 0x7000002000, + 0, + true, + false, + true, + "/system/lib64/libdl.so", + buildId, + 0, + ) ) // Third mapping: r--p at offset 0 (another duplicate!) - .addMemoryMappings( - TombstoneProtos.MemoryMapping.newBuilder() - .setBuildId(buildId) - .setMappingName("/system/lib64/libdl.so") - .setBeginAddress(0x7000002000) - .setEndAddress(0x7000003000) - .setOffset(0) - .setRead(true) - .setWrite(false) - .setExecute(false) + .addMemoryMapping( + MemoryMapping( + 0x7000002000, + 0x7000003000, + 0, + true, + false, + false, + "/system/lib64/libdl.so", + buildId, + 0, + ) ) - .putThreads( - 1234, - TombstoneProtos.Thread.newBuilder() - .setId(1234) - .setName("main") - .addCurrentBacktrace( - TombstoneProtos.BacktraceFrame.newBuilder() - .setPc(0x7000001100) - .setFunctionName("crash") - .setFileName("/system/lib64/libdl.so") - ) - .build(), + .addThread( + TombstoneThread( + 1234, + "main", + emptyList(), + emptyList(), + emptyList(), + listOf(BacktraceFrame(0, 0x7000001100, 0, "crash", 0, "/system/lib64/libdl.so", 0, "")), + emptyList(), + 0, + 0, + ) ) .build() - val parser = - TombstoneParser( - ByteArrayInputStream(tombstone.toByteArray()), - inAppIncludes, - inAppExcludes, - nativeLibraryDir, - ) - val event = parser.parse() + val event = parser.parse(tombstone) - // All duplicate mappings should be coalesced into a single module val images = event.debugMeta!!.images!! assertEquals(1, images.size) @@ -327,59 +321,52 @@ class TombstoneParserTest { val validBuildId = "f1c3bcc0279865fe3058404b2831d9e64135386c" val tombstone = - TombstoneProtos.Tombstone.newBuilder() - .setPid(1234) - .setTid(1234) - .setSignalInfo( - TombstoneProtos.Signal.newBuilder() - .setNumber(11) - .setName("SIGSEGV") - .setCode(1) - .setCodeName("SEGV_MAPERR") - ) - .addMemoryMappings( - TombstoneProtos.MemoryMapping.newBuilder() - .setBuildId(invalidBuildId) - .setMappingName("/system/lib64/libc.so") - .setBeginAddress(0x7000000000) - .setEndAddress(0x7000001000) - .setOffset(0) - .setRead(true) - .setExecute(true) + Tombstone.Builder() + .pid(1234) + .tid(1234) + .signal(Signal(11, "SIGSEGV", 1, "SEGV_MAPERR", false, 0, 0, false, 0, null)) + .addMemoryMapping( + MemoryMapping( + 0x7000000000, + 0x7000001000, + 0, + true, + false, + true, + "/system/lib64/libc.so", + invalidBuildId, + 0, + ) ) - .addMemoryMappings( - TombstoneProtos.MemoryMapping.newBuilder() - .setBuildId(validBuildId) - .setMappingName("/system/lib64/libm.so") - .setBeginAddress(0x7000002000) - .setEndAddress(0x7000003000) - .setOffset(0) - .setRead(true) - .setExecute(true) + .addMemoryMapping( + MemoryMapping( + 0x7000002000, + 0x7000003000, + 0, + true, + false, + true, + "/system/lib64/libm.so", + validBuildId, + 0, + ) ) - .putThreads( - 1234, - TombstoneProtos.Thread.newBuilder() - .setId(1234) - .setName("main") - .addCurrentBacktrace( - TombstoneProtos.BacktraceFrame.newBuilder() - .setPc(0x7000000100) - .setFunctionName("crash") - .setFileName("/system/lib64/libc.so") - ) - .build(), + .addThread( + TombstoneThread( + 1234, + "main", + emptyList(), + emptyList(), + emptyList(), + listOf(BacktraceFrame(0, 0x7000000100, 0, "crash", 0, "/system/lib64/libc.so", 0, "")), + emptyList(), + 0, + 0, + ) ) .build() - val parser = - TombstoneParser( - ByteArrayInputStream(tombstone.toByteArray()), - inAppIncludes, - inAppExcludes, - nativeLibraryDir, - ) - val event = parser.parse() + val event = parser.parse(tombstone) val images = event.debugMeta!!.images!! assertEquals(2, images.size) @@ -400,8 +387,9 @@ class TombstoneParserTest { // test against a full snapshot so that we can track regressions in the VMA -> module reduction val tombstoneStream = GZIPInputStream(TombstoneParserTest::class.java.getResourceAsStream("/tombstone.pb.gz")) - val parser = TombstoneParser(tombstoneStream, inAppIncludes, inAppExcludes, nativeLibraryDir) - val event = parser.parse() + val streamParser = + TombstoneParser(tombstoneStream, inAppIncludes, inAppExcludes, nativeLibraryDir) + val event = streamParser.parse() val actualJson = serializeDebugMeta(event.debugMeta!!) val expectedJson = readGzippedResourceFile("/tombstone_debug_meta.json.gz")