Skip to content
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions sentry-android-core/api/sentry-android-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@ public abstract class io/sentry/android/core/EnvelopeFileObserverIntegration : i
public abstract interface class io/sentry/android/core/IDebugImagesLoader {
public abstract fun clearDebugImages ()V
public abstract fun loadDebugImages ()Ljava/util/List;
public abstract fun loadDebugImagesForAddresses (Ljava/util/Set;)Ljava/util/Set;
}

public final class io/sentry/android/core/InternalSentrySdk {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import io.sentry.protocol.DebugImage;
import java.util.List;
import java.util.Set;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;

Expand All @@ -11,5 +12,8 @@ public interface IDebugImagesLoader {
@Nullable
List<DebugImage> loadDebugImages();

@Nullable
Set<DebugImage> loadDebugImagesForAddresses(Set<Long> addresses);

void clearDebugImages();
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import io.sentry.protocol.DebugImage;
import java.util.List;
import java.util.Set;
import org.jetbrains.annotations.Nullable;

final class NoOpDebugImagesLoader implements IDebugImagesLoader {
Expand All @@ -19,6 +20,11 @@ public static NoOpDebugImagesLoader getInstance() {
return null;
}

@Override
public @Nullable Set<DebugImage> loadDebugImagesForAddresses(Set<Long> addresses) {
return null;
}

@Override
public void clearDebugImages() {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,8 @@ class SentryAndroidOptionsTest {

private class CustomDebugImagesLoader : IDebugImagesLoader {
override fun loadDebugImages(): List<DebugImage>? = null
override fun loadDebugImagesForAddresses(addresses: Set<Long>?): Set<DebugImage>? = null

override fun clearDebugImages() {}
}
}
1 change: 1 addition & 0 deletions sentry-android-ndk/api/sentry-android-ndk.api
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ public final class io/sentry/android/ndk/DebugImagesLoader : io/sentry/android/c
public fun <init> (Lio/sentry/android/core/SentryAndroidOptions;Lio/sentry/ndk/NativeModuleListLoader;)V
public fun clearDebugImages ()V
public fun loadDebugImages ()Ljava/util/List;
public fun loadDebugImagesForAddresses (Ljava/util/Set;)Ljava/util/Set;
}

public final class io/sentry/android/ndk/NdkScopeObserver : io/sentry/ScopeObserverAdapter {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
import io.sentry.util.AutoClosableReentrantLock;
import io.sentry.util.Objects;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.VisibleForTesting;
Expand Down Expand Up @@ -75,7 +77,74 @@ public DebugImagesLoader(
return debugImages;
}

/** Clears the caching of debug images on sentry-native and here. */
/**
* Loads debug images for the given set of addresses.
*
* @param addresses Set of memory addresses to find debug images for
* @return Set of debug images, or null if debug images couldn't be loaded
*/
public @Nullable Set<DebugImage> loadDebugImagesForAddresses(@NotNull Set<Long> addresses) {
try (final @NotNull ISentryLifecycleToken ignored = debugImagesLock.acquire()) {
List<DebugImage> allDebugImages = loadDebugImages();
if (allDebugImages == null) {
return null;
}

Set<DebugImage> referencedImages = new HashSet<>();
for (Long addr : addresses) {
DebugImage image = findImageByAddress(addr, allDebugImages);
if (image != null) {
referencedImages.add(image);
}
}

if (referencedImages.isEmpty()) {
options
.getLogger()
.log(
SentryLevel.WARNING,
"No debug images found for any of the %d addresses.",
addresses.size());
return null;
}

return referencedImages;
}
}

/**
* Finds a debug image containing the given address using binary search. Requires the images to be
* sorted.
*
* @return The matching debug image or null if not found
*/
private @Nullable DebugImage findImageByAddress(long address, List<DebugImage> images) {
int left = 0;
int right = images.size() - 1;

while (left <= right) {
int mid = left + (right - left) / 2;
DebugImage image = images.get(mid);

if (image.getImageAddr() == null || image.getImageSize() == null) {
return null;
}

long imageStart = Long.parseLong(image.getImageAddr().replace("0x", ""), 16);
long imageEnd = imageStart + image.getImageSize();

if (address >= imageStart && address < imageEnd) {
return image;
} else if (address < imageStart) {
right = mid - 1;
} else {
left = mid + 1;
}
}

return null;
}

@Override
public void clearDebugImages() {
try (final @NotNull ISentryLifecycleToken ignored = debugImagesLock.acquire()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
import kotlin.test.assertNull
import kotlin.test.assertTrue
Expand Down Expand Up @@ -77,4 +78,78 @@ class DebugImagesLoaderTest {

assertNull(sut.cachedDebugImages)
}

@Test
fun `find images by address`() {
val sut = fixture.getSut()

val image1 = io.sentry.ndk.DebugImage().apply {
imageAddr = "0x1000"
imageSize = 0x1000L
}

val image2 = io.sentry.ndk.DebugImage().apply {
imageAddr = "0x2000"
imageSize = 0x1000L
}

val image3 = io.sentry.ndk.DebugImage().apply {
imageAddr = "0x3000"
imageSize = 0x1000L
}

whenever(fixture.nativeLoader.loadModuleList()).thenReturn(arrayOf(image1, image2, image3))

val result = sut.loadDebugImagesForAddresses(
setOf(0x1500L, 0x2500L)
)!!.toList()

assertEquals(2, result.size)
assertEquals(image1.imageAddr, result[0].imageAddr)
assertEquals(image2.imageAddr, result[1].imageAddr)
}

@Test
fun `find images with invalid addresses are not added to the result`() {
val sut = fixture.getSut()

val image1 = io.sentry.ndk.DebugImage().apply {
imageAddr = "0x1000"
imageSize = 0x1000L
}

val image2 = io.sentry.ndk.DebugImage().apply {
imageAddr = "0x2000"
imageSize = 0x1000L
}

whenever(fixture.nativeLoader.loadModuleList()).thenReturn(arrayOf(image1, image2))

val hexAddresses = setOf(-100, 0x1500L)
val result = sut.loadDebugImagesForAddresses(hexAddresses)

assertEquals(1, result!!.size)
}

@Test
fun `find images by address returns null if result is empty`() {
val sut = fixture.getSut()

val image1 = io.sentry.ndk.DebugImage().apply {
imageAddr = "0x1000"
imageSize = 0x1000L
}

val image2 = io.sentry.ndk.DebugImage().apply {
imageAddr = "0x2000"
imageSize = 0x1000L
}

whenever(fixture.nativeLoader.loadModuleList()).thenReturn(arrayOf(image1, image2))

val hexAddresses = setOf(-100, 0x10500L)
val result = sut.loadDebugImagesForAddresses(hexAddresses)

assertNull(result)
}
}
Loading