Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## Unreleased

### Features

- Add Binder IPC call instrumentation ([#1159](https://github.com/getsentry/sentry-android-gradle-plugin/pull/1159))

## 6.4.0-alpha.4

### Internal Changes 🔧
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ fun ApplicationAndroidComponentsExtension.configure(
extension.includeSourceContext,
extension.dexguardEnabled,
extension.tracingInstrumentation.appStart.enabled,
extension.tracingInstrumentation.binderIpc.enabled,
)
/**
* We have to register SentryModulesService as a build event listener, so it will not be
Expand Down Expand Up @@ -235,6 +236,9 @@ fun ApplicationAndroidComponentsExtension.configure(
params.appStartEnabled.setDisallowChanges(
extension.tracingInstrumentation.appStart.enabled
)
params.binderIpcEnabled.setDisallowChanges(
extension.tracingInstrumentation.binderIpc.enabled
)
params.tmpDir.set(tmpDir)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package io.sentry.android.gradle.extensions

import javax.inject.Inject
import org.gradle.api.model.ObjectFactory
import org.gradle.api.provider.Property

open class BinderIpcExtension @Inject constructor(objects: ObjectFactory) {
/** Enables or disables Binder IPC call instrumentation. Defaults to true. */
val enabled: Property<Boolean> = objects.property(Boolean::class.java).convention(true)
}
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,12 @@ open class TracingInstrumentationExtension @Inject constructor(objects: ObjectFa
fun appStart(appStartExtensionAction: Action<AppStartExtension>) {
appStartExtensionAction.execute(appStart)
}

val binderIpc: BinderIpcExtension = objects.newInstance(BinderIpcExtension::class.java)

fun binderIpc(binderIpcAction: Action<BinderIpcExtension>) {
binderIpcAction.execute(binderIpc)
}
}

enum class InstrumentationFeature(val integrationName: String) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import io.sentry.android.gradle.instrumentation.androidx.sqlite.database.Android
import io.sentry.android.gradle.instrumentation.androidx.sqlite.statement.AndroidXSQLiteStatement
import io.sentry.android.gradle.instrumentation.appstart.Application
import io.sentry.android.gradle.instrumentation.appstart.ContentProvider
import io.sentry.android.gradle.instrumentation.binder.BinderIpc
import io.sentry.android.gradle.instrumentation.logcat.Logcat
import io.sentry.android.gradle.instrumentation.logcat.LogcatLevel
import io.sentry.android.gradle.instrumentation.okhttp.OkHttp
Expand Down Expand Up @@ -63,6 +64,8 @@ abstract class SpanAddingClassVisitorFactory :
@get:Input val logcatEnabled: Property<Boolean>

@get:Input val appStartEnabled: Property<Boolean>

@get:Input val binderIpcEnabled: Property<Boolean>
}

private val instrumentable: ClassInstrumentable
Expand Down Expand Up @@ -106,6 +109,7 @@ abstract class SpanAddingClassVisitorFactory :
RemappingInstrumentable().takeIf { sentryModulesService.isFileIOInstrEnabled() },
ComposeNavigation().takeIf { sentryModulesService.isComposeInstrEnabled() },
Logcat().takeIf { sentryModulesService.isLogcatInstrEnabled() },
BinderIpc().takeIf { sentryModulesService.isBinderIpcInstrEnabled() },
Application().takeIf { sentryModulesService.isAppStartInstrEnabled() },
ContentProvider().takeIf { sentryModulesService.isAppStartInstrEnabled() },
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package io.sentry.android.gradle.instrumentation.binder

import com.android.build.api.instrumentation.ClassContext
import io.sentry.android.gradle.instrumentation.ClassInstrumentable
import io.sentry.android.gradle.instrumentation.CommonClassVisitor
import io.sentry.android.gradle.instrumentation.SpanAddingClassVisitorFactory
import io.sentry.android.gradle.instrumentation.util.isSentryClass
import org.objectweb.asm.ClassVisitor

class BinderIpc : ClassInstrumentable {

companion object {
private const val CLASSNAME = "BinderIpc"
}

override fun getVisitor(
instrumentableContext: ClassContext,
apiVersion: Int,
originalVisitor: ClassVisitor,
parameters: SpanAddingClassVisitorFactory.SpanAddingParameters,
): ClassVisitor {
return CommonClassVisitor(
apiVersion,
originalVisitor,
CLASSNAME,
listOf(BinderIpcMethodInstrumentable()),
parameters,
)
}

override fun isInstrumentable(data: ClassContext) = !data.isSentryClass()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package io.sentry.android.gradle.instrumentation.binder

import io.sentry.android.gradle.instrumentation.MethodContext
import io.sentry.android.gradle.instrumentation.MethodInstrumentable
import io.sentry.android.gradle.instrumentation.SpanAddingClassVisitorFactory
import org.objectweb.asm.Label
import org.objectweb.asm.MethodVisitor
import org.objectweb.asm.Opcodes
import org.objectweb.asm.Type
import org.objectweb.asm.commons.GeneratorAdapter
import org.objectweb.asm.commons.Method

class BinderIpcMethodInstrumentable : MethodInstrumentable {

override fun getVisitor(
instrumentableContext: MethodContext,
apiVersion: Int,
originalVisitor: MethodVisitor,
parameters: SpanAddingClassVisitorFactory.SpanAddingParameters,
): MethodVisitor = BinderIpcMethodVisitor(apiVersion, originalVisitor, instrumentableContext)

override fun isInstrumentable(data: MethodContext): Boolean = true
}

private const val SENTRY_IPC_TRACER = "io/sentry/android/core/SentryIpcTracer"

class BinderIpcMethodVisitor(
apiVersion: Int,
originalVisitor: MethodVisitor,
instrumentableContext: MethodContext,
) :
GeneratorAdapter(
apiVersion,
originalVisitor,
instrumentableContext.access,
instrumentableContext.name,
instrumentableContext.descriptor,
) {

override fun visitMethodInsn(
opcode: Int,
owner: String,
name: String,
descriptor: String,
isInterface: Boolean,
) {
val spec = BinderMethodRegistry.lookup(owner, name)
if (spec == null) {
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface)
return
}

val isInstanceCall = opcode == Opcodes.INVOKEVIRTUAL || opcode == Opcodes.INVOKEINTERFACE
val isStaticCall = opcode == Opcodes.INVOKESTATIC
if (spec.isStatic && !isStaticCall || !spec.isStatic && !isInstanceCall) {
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface)
return
}

val argTypes = Method(name, descriptor).argumentTypes

// Save arguments from the stack into temp locals (reverse order for LIFO)
val argLocals = IntArray(argTypes.size)
for (i in argLocals.size - 1 downTo 0) {
argLocals[i] = newLocal(argTypes[i])
storeLocal(argLocals[i])
}

val receiverLocal =
if (!spec.isStatic) {
val local = newLocal(Type.getObjectType(owner))
storeLocal(local)
local
} else {
-1
}

mv.visitLdcInsn(spec.component)
mv.visitLdcInsn(name)
mv.visitMethodInsn(
Opcodes.INVOKESTATIC,
SENTRY_IPC_TRACER,
"onCallStart",
"(Ljava/lang/String;Ljava/lang/String;)I",
false,
)
val cookieLocal = newLocal(Type.INT_TYPE)
storeLocal(cookieLocal)

val tryStart = Label()
val tryEnd = Label()
val catchHandler = Label()
val afterFinally = Label()

mv.visitTryCatchBlock(tryStart, tryEnd, catchHandler, null)
mv.visitLabel(tryStart)

if (!spec.isStatic) {
loadLocal(receiverLocal)
}
for (local in argLocals) {
loadLocal(local)
}
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface)

mv.visitLabel(tryEnd)
loadLocal(cookieLocal)
mv.visitMethodInsn(Opcodes.INVOKESTATIC, SENTRY_IPC_TRACER, "onCallEnd", "(I)V", false)
mv.visitJumpInsn(Opcodes.GOTO, afterFinally)

// catch-all handler: call onCallEnd then re-throw
mv.visitLabel(catchHandler)
loadLocal(cookieLocal)
mv.visitMethodInsn(Opcodes.INVOKESTATIC, SENTRY_IPC_TRACER, "onCallEnd", "(I)V", false)
mv.visitInsn(Opcodes.ATHROW)

mv.visitLabel(afterFinally)
}
}
Loading