Skip to content

Commit

Permalink
extract crash processing code
Browse files Browse the repository at this point in the history
  • Loading branch information
fractalwrench committed Nov 21, 2024
1 parent 597da7c commit 11b97e3
Show file tree
Hide file tree
Showing 2 changed files with 183 additions and 140 deletions.
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
package io.embrace.android.embracesdk.internal.ndk

import android.annotation.SuppressLint
import android.content.Context
import android.os.Build
import android.os.Handler
import android.os.Looper
import android.util.Base64
import io.embrace.android.embracesdk.internal.DeviceArchitecture
import io.embrace.android.embracesdk.internal.SharedObjectLoader
import io.embrace.android.embracesdk.internal.Systrace
import io.embrace.android.embracesdk.internal.TypeUtils
import io.embrace.android.embracesdk.internal.capture.metadata.MetadataService
import io.embrace.android.embracesdk.internal.capture.session.SessionPropertiesService
import io.embrace.android.embracesdk.internal.capture.user.UserService
Expand All @@ -20,25 +17,17 @@ import io.embrace.android.embracesdk.internal.logging.InternalErrorType
import io.embrace.android.embracesdk.internal.ndk.jni.JniDelegate
import io.embrace.android.embracesdk.internal.payload.AppFramework
import io.embrace.android.embracesdk.internal.payload.NativeCrashData
import io.embrace.android.embracesdk.internal.payload.NativeCrashDataError
import io.embrace.android.embracesdk.internal.payload.NativeCrashMetadata
import io.embrace.android.embracesdk.internal.payload.NativeSymbols
import io.embrace.android.embracesdk.internal.serialization.PlatformSerializer
import io.embrace.android.embracesdk.internal.session.id.SessionIdTracker
import io.embrace.android.embracesdk.internal.session.lifecycle.ProcessStateListener
import io.embrace.android.embracesdk.internal.session.lifecycle.ProcessStateService
import io.embrace.android.embracesdk.internal.storage.StorageService
import io.embrace.android.embracesdk.internal.utils.Uuid
import io.embrace.android.embracesdk.internal.worker.BackgroundWorker
import java.io.BufferedReader
import java.io.File
import java.io.FileInputStream
import java.io.FileNotFoundException
import java.io.IOException
import java.io.InputStreamReader

internal class EmbraceNdkService(
private val context: Context,
context: Context,
private val storageService: StorageService,
private val metadataService: MetadataService,
private val processStateService: ProcessStateService,
Expand All @@ -55,15 +44,19 @@ internal class EmbraceNdkService(
private val handler: Handler = Handler(checkNotNull(Looper.getMainLooper())),
) : NdkService, ProcessStateListener {

private val processor: NativeCrashProcessorImpl = NativeCrashProcessorImpl(
context,
sharedObjectLoader,
logger,
repository,
delegate,
deviceArchitecture,
serializer
)

override var unityCrashId: String? = null
override val symbolsForCurrentArch by lazy {
val nativeSymbols = getNativeSymbols()
if (nativeSymbols != null) {
val arch = deviceArchitecture.architecture
return@lazy nativeSymbols.getSymbolByArchitecture(arch)
}
null
}
override val symbolsForCurrentArch
get() = processor.symbolsForCurrentArch

override fun initializeService(sessionIdTracker: SessionIdTracker) {
Systrace.traceSynchronous("init-ndk-service") {
Expand Down Expand Up @@ -182,124 +175,11 @@ internal class EmbraceNdkService(
backgroundWorker.submit(runnable = ::updateDeviceMetaData)
}

/**
* Find and parse a native error File to NativeCrashData Error List
*
* @return List of NativeCrashData error
*/
private fun getNativeCrashErrors(errorFile: File?): List<NativeCrashDataError?>? {
if (errorFile != null) {
val absolutePath = errorFile.absolutePath
val errorsRaw = delegate.getErrors(absolutePath)
if (errorsRaw != null) {
runCatching {
val type = TypeUtils.typedList(NativeCrashDataError::class)
return serializer.fromJson(errorsRaw, type)
}
}
}
return null
}

/**
* Process map file for crash to read and return its content as String
*/
private fun getMapFileContent(mapFile: File?): String? {
if (mapFile != null) {
val mapContents = readMapFile(mapFile)
if (mapContents != null) {
return mapContents
}
}
return null
}

override fun getLatestNativeCrash(): NativeCrashData? = getAllNativeCrashes(repository::deleteFiles).lastOrNull()

override fun getNativeCrashes(): List<NativeCrashData> = getAllNativeCrashes()

override fun deleteAllNativeCrashes() {
getAllNativeCrashes(repository::deleteFiles)
}

private fun getAllNativeCrashes(
cleanup: CleanupFunction? = null,
): List<NativeCrashData> {
val nativeCrashes = mutableListOf<NativeCrashData>()
if (sharedObjectLoader.loaded.get()) {
val matchingFiles = repository.sortNativeCrashes(false)
for (crashFile in matchingFiles) {
try {
val path = crashFile.path
delegate.getCrashReport(path)?.let { crashRaw ->
val nativeCrash = serializer.fromJson(crashRaw, NativeCrashData::class.java)
val errorFile = repository.errorFileForCrash(crashFile)?.apply {
getNativeCrashErrors(this).let { errors ->
nativeCrash.errors = errors
}
}
val mapFile = repository.mapFileForCrash(crashFile)?.apply {
nativeCrash.map = getMapFileContent(this)
}
nativeCrash.symbols = symbolsForCurrentArch?.toMap()

nativeCrashes.add(nativeCrash)
cleanup?.invoke(crashFile, errorFile, mapFile, nativeCrash)
} ?: {
logger.trackInternalError(
type = InternalErrorType.NATIVE_CRASH_LOAD_FAIL,
throwable = FileNotFoundException("Failed to load crash report at $path")
)
}
} catch (t: Throwable) {
crashFile.delete()
logger.trackInternalError(
type = InternalErrorType.NATIVE_CRASH_LOAD_FAIL,
throwable = RuntimeException(
"Failed to read native crash file {crashFilePath=" + crashFile.absolutePath + "}.",
t
)
)
}
}
}
return nativeCrashes
}
override fun getLatestNativeCrash(): NativeCrashData? = processor.getLatestNativeCrash()

@SuppressLint("DiscouragedApi")
private fun getNativeSymbols(): NativeSymbols? {
val resources = context.resources
val resourceId = resources.getIdentifier(KEY_NDK_SYMBOLS, "string", context.packageName)
if (resourceId != 0) {
try {
val encodedSymbols: String = Base64.decode(
context.resources.getString(resourceId),
Base64.DEFAULT
).decodeToString()
return serializer.fromJson(encodedSymbols, NativeSymbols::class.java)
} catch (ex: Exception) {
logger.trackInternalError(InternalErrorType.INVALID_NATIVE_SYMBOLS, ex)
}
}
return null
}
override fun getNativeCrashes(): List<NativeCrashData> = processor.getNativeCrashes()

private fun readMapFile(mapFile: File): String? {
try {
FileInputStream(mapFile).use { fin ->
BufferedReader(InputStreamReader(fin)).use { reader ->
val sb = StringBuilder()
var line: String?
while (reader.readLine().also { line = it } != null) {
sb.append(line).append("\n")
}
return sb.toString()
}
}
} catch (e: IOException) {
return null
}
}
override fun deleteAllNativeCrashes() = processor.deleteAllNativeCrashes()

Check warning on line 182 in embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/ndk/EmbraceNdkService.kt

View check run for this annotation

Codecov / codecov/patch

embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/ndk/EmbraceNdkService.kt#L182

Added line #L182 was not covered by tests

private fun updateAppState(newAppState: String) {
if (sharedObjectLoader.loaded.get()) {
Expand Down Expand Up @@ -349,10 +229,6 @@ internal class EmbraceNdkService(
*/
private const val APPLICATION_STATE_BACKGROUND = "background"

/**
* The NDK symbols name that matches with the resource name injected by the plugin.
*/
private const val KEY_NDK_SYMBOLS = "emb_ndk_symbols"
internal const val NATIVE_CRASH_FILE_PREFIX = "emb_ndk"
internal const val NATIVE_CRASH_FILE_SUFFIX = ".crash"
internal const val NATIVE_CRASH_ERROR_FILE_SUFFIX = ".error"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
package io.embrace.android.embracesdk.internal.ndk

import android.annotation.SuppressLint
import android.content.Context
import android.util.Base64
import io.embrace.android.embracesdk.internal.DeviceArchitecture
import io.embrace.android.embracesdk.internal.SharedObjectLoader
import io.embrace.android.embracesdk.internal.TypeUtils
import io.embrace.android.embracesdk.internal.logging.EmbLogger
import io.embrace.android.embracesdk.internal.logging.InternalErrorType
import io.embrace.android.embracesdk.internal.ndk.jni.JniDelegate
import io.embrace.android.embracesdk.internal.payload.NativeCrashData
import io.embrace.android.embracesdk.internal.payload.NativeCrashDataError
import io.embrace.android.embracesdk.internal.payload.NativeSymbols
import io.embrace.android.embracesdk.internal.serialization.PlatformSerializer
import java.io.BufferedReader
import java.io.File
import java.io.FileInputStream
import java.io.FileNotFoundException
import java.io.IOException
import java.io.InputStreamReader

internal class NativeCrashProcessorImpl(
private val context: Context,
private val sharedObjectLoader: SharedObjectLoader,
private val logger: EmbLogger,
private val repository: NdkServiceRepository,
private val delegate: JniDelegate,
private val deviceArchitecture: DeviceArchitecture,
private val serializer: PlatformSerializer,
) {

val symbolsForCurrentArch by lazy {
val nativeSymbols = getNativeSymbols()
if (nativeSymbols != null) {
val arch = deviceArchitecture.architecture
return@lazy nativeSymbols.getSymbolByArchitecture(arch)
}
null

Check warning on line 39 in embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/ndk/NativeCrashProcessorImpl.kt

View check run for this annotation

Codecov / codecov/patch

embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/ndk/NativeCrashProcessorImpl.kt#L39

Added line #L39 was not covered by tests
}

fun getLatestNativeCrash(): NativeCrashData? = getAllNativeCrashes(repository::deleteFiles).lastOrNull()

fun getNativeCrashes(): List<NativeCrashData> = getAllNativeCrashes()

fun deleteAllNativeCrashes() {
getAllNativeCrashes(repository::deleteFiles)

Check warning on line 47 in embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/ndk/NativeCrashProcessorImpl.kt

View check run for this annotation

Codecov / codecov/patch

embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/ndk/NativeCrashProcessorImpl.kt#L47

Added line #L47 was not covered by tests
}

private fun getAllNativeCrashes(
cleanup: CleanupFunction? = null,
): List<NativeCrashData> {
val nativeCrashes = mutableListOf<NativeCrashData>()
if (sharedObjectLoader.loaded.get()) {
val matchingFiles = repository.sortNativeCrashes(false)
for (crashFile in matchingFiles) {
try {
val path = crashFile.path
delegate.getCrashReport(path)?.let { crashRaw ->
val nativeCrash = serializer.fromJson(crashRaw, NativeCrashData::class.java)
val errorFile = repository.errorFileForCrash(crashFile)?.apply {
getNativeCrashErrors(this).let { errors ->
nativeCrash.errors = errors
}
}

Check warning on line 65 in embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/ndk/NativeCrashProcessorImpl.kt

View check run for this annotation

Codecov / codecov/patch

embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/ndk/NativeCrashProcessorImpl.kt#L62-L65

Added lines #L62 - L65 were not covered by tests
val mapFile = repository.mapFileForCrash(crashFile)?.apply {
nativeCrash.map = getMapFileContent(this)
}

Check warning on line 68 in embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/ndk/NativeCrashProcessorImpl.kt

View check run for this annotation

Codecov / codecov/patch

embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/ndk/NativeCrashProcessorImpl.kt#L67-L68

Added lines #L67 - L68 were not covered by tests
nativeCrash.symbols = symbolsForCurrentArch?.toMap()

nativeCrashes.add(nativeCrash)
cleanup?.invoke(crashFile, errorFile, mapFile, nativeCrash)
} ?: {
logger.trackInternalError(
type = InternalErrorType.NATIVE_CRASH_LOAD_FAIL,
throwable = FileNotFoundException("Failed to load crash report at $path")

Check warning on line 76 in embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/ndk/NativeCrashProcessorImpl.kt

View check run for this annotation

Codecov / codecov/patch

embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/ndk/NativeCrashProcessorImpl.kt#L74-L76

Added lines #L74 - L76 were not covered by tests
)
}
} catch (t: Throwable) {
crashFile.delete()
logger.trackInternalError(
type = InternalErrorType.NATIVE_CRASH_LOAD_FAIL,
throwable = RuntimeException(
"Failed to read native crash file {crashFilePath=" + crashFile.absolutePath + "}.",
t
)
)
}
}
}
return nativeCrashes
}

@SuppressLint("DiscouragedApi")
private fun getNativeSymbols(): NativeSymbols? {
val resources = context.resources
val resourceId = resources.getIdentifier(KEY_NDK_SYMBOLS, "string", context.packageName)
if (resourceId != 0) {
try {
val encodedSymbols: String = Base64.decode(
context.resources.getString(resourceId),
Base64.DEFAULT
).decodeToString()
return serializer.fromJson(encodedSymbols, NativeSymbols::class.java)
} catch (ex: Exception) {
logger.trackInternalError(InternalErrorType.INVALID_NATIVE_SYMBOLS, ex)

Check warning on line 106 in embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/ndk/NativeCrashProcessorImpl.kt

View check run for this annotation

Codecov / codecov/patch

embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/ndk/NativeCrashProcessorImpl.kt#L106

Added line #L106 was not covered by tests
}
}
return null

Check warning on line 109 in embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/ndk/NativeCrashProcessorImpl.kt

View check run for this annotation

Codecov / codecov/patch

embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/ndk/NativeCrashProcessorImpl.kt#L109

Added line #L109 was not covered by tests
}

/**
* Find and parse a native error File to NativeCrashData Error List
*
* @return List of NativeCrashData error
*/
private fun getNativeCrashErrors(errorFile: File?): List<NativeCrashDataError?>? {
if (errorFile != null) {
val absolutePath = errorFile.absolutePath
val errorsRaw = delegate.getErrors(absolutePath)

Check warning on line 120 in embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/ndk/NativeCrashProcessorImpl.kt

View check run for this annotation

Codecov / codecov/patch

embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/ndk/NativeCrashProcessorImpl.kt#L118-L120

Added lines #L118 - L120 were not covered by tests
if (errorsRaw != null) {
runCatching {
val type = TypeUtils.typedList(NativeCrashDataError::class)
return serializer.fromJson(errorsRaw, type)

Check warning on line 124 in embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/ndk/NativeCrashProcessorImpl.kt

View check run for this annotation

Codecov / codecov/patch

embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/ndk/NativeCrashProcessorImpl.kt#L123-L124

Added lines #L123 - L124 were not covered by tests
}
}
}
return null

Check warning on line 128 in embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/ndk/NativeCrashProcessorImpl.kt

View check run for this annotation

Codecov / codecov/patch

embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/ndk/NativeCrashProcessorImpl.kt#L128

Added line #L128 was not covered by tests
}

/**
* Process map file for crash to read and return its content as String
*/
private fun getMapFileContent(mapFile: File?): String? {
if (mapFile != null) {
val mapContents = readMapFile(mapFile)

Check warning on line 136 in embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/ndk/NativeCrashProcessorImpl.kt

View check run for this annotation

Codecov / codecov/patch

embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/ndk/NativeCrashProcessorImpl.kt#L136

Added line #L136 was not covered by tests
if (mapContents != null) {
return mapContents

Check warning on line 138 in embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/ndk/NativeCrashProcessorImpl.kt

View check run for this annotation

Codecov / codecov/patch

embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/ndk/NativeCrashProcessorImpl.kt#L138

Added line #L138 was not covered by tests
}
}
return null

Check warning on line 141 in embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/ndk/NativeCrashProcessorImpl.kt

View check run for this annotation

Codecov / codecov/patch

embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/ndk/NativeCrashProcessorImpl.kt#L141

Added line #L141 was not covered by tests
}

private fun readMapFile(mapFile: File): String? {
try {
FileInputStream(mapFile).use { fin ->
BufferedReader(InputStreamReader(fin)).use { reader ->
val sb = StringBuilder()
var line: String?

Check warning on line 149 in embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/ndk/NativeCrashProcessorImpl.kt

View check run for this annotation

Codecov / codecov/patch

embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/ndk/NativeCrashProcessorImpl.kt#L145-L149

Added lines #L145 - L149 were not covered by tests
while (reader.readLine().also { line = it } != null) {
sb.append(line).append("\n")

Check warning on line 151 in embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/ndk/NativeCrashProcessorImpl.kt

View check run for this annotation

Codecov / codecov/patch

embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/ndk/NativeCrashProcessorImpl.kt#L151

Added line #L151 was not covered by tests
}
return sb.toString()

Check warning on line 153 in embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/ndk/NativeCrashProcessorImpl.kt

View check run for this annotation

Codecov / codecov/patch

embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/ndk/NativeCrashProcessorImpl.kt#L153

Added line #L153 was not covered by tests
}
}
} catch (e: IOException) {
return null

Check warning on line 157 in embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/ndk/NativeCrashProcessorImpl.kt

View check run for this annotation

Codecov / codecov/patch

embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/ndk/NativeCrashProcessorImpl.kt#L157

Added line #L157 was not covered by tests
}
}

internal companion object {
/**
* The NDK symbols name that matches with the resource name injected by the plugin.
*/
private const val KEY_NDK_SYMBOLS = "emb_ndk_symbols"
}
}

0 comments on commit 11b97e3

Please sign in to comment.