aboutsummaryrefslogtreecommitdiff
path: root/agent/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt
diff options
context:
space:
mode:
Diffstat (limited to 'agent/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt')
-rw-r--r--agent/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt165
1 files changed, 88 insertions, 77 deletions
diff --git a/agent/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt b/agent/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt
index 33d02263..f9b026f1 100644
--- a/agent/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt
+++ b/agent/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt
@@ -16,38 +16,35 @@
package com.code_intelligence.jazzer.agent
+import com.code_intelligence.jazzer.driver.Opt
import com.code_intelligence.jazzer.instrumentor.CoverageRecorder
+import com.code_intelligence.jazzer.instrumentor.Hooks
import com.code_intelligence.jazzer.instrumentor.InstrumentationType
-import com.code_intelligence.jazzer.instrumentor.loadHooks
-import com.code_intelligence.jazzer.runtime.ManifestUtils
+import com.code_intelligence.jazzer.runtime.NativeLibHooks
+import com.code_intelligence.jazzer.runtime.TraceCmpHooks
+import com.code_intelligence.jazzer.runtime.TraceDivHooks
+import com.code_intelligence.jazzer.runtime.TraceIndirHooks
import com.code_intelligence.jazzer.utils.ClassNameGlobber
+import com.code_intelligence.jazzer.utils.ManifestUtils
import java.io.File
import java.lang.instrument.Instrumentation
+import java.net.URI
import java.nio.file.Paths
import java.util.jar.JarFile
import kotlin.io.path.ExperimentalPathApi
import kotlin.io.path.exists
import kotlin.io.path.isDirectory
-val KNOWN_ARGUMENTS = listOf(
- "instrumentation_includes",
- "instrumentation_excludes",
- "custom_hook_includes",
- "custom_hook_excludes",
- "trace",
- "custom_hooks",
- "id_sync_file",
- "dump_classes_dir",
-)
-
private object AgentJarFinder {
- private val agentJarPath = AgentJarFinder::class.java.protectionDomain?.codeSource?.location?.toURI()
- val agentJarFile = agentJarPath?.let { JarFile(File(it)) }
+ val agentJarFile = jarUriForClass(AgentJarFinder::class.java)?.let { JarFile(File(it)) }
}
-private val argumentDelimiter = if (System.getProperty("os.name").startsWith("Windows")) ";" else ":"
+fun jarUriForClass(clazz: Class<*>): URI? {
+ return clazz.protectionDomain?.codeSource?.location?.toURI()
+}
@OptIn(ExperimentalPathApi::class)
+@Suppress("UNUSED_PARAMETER")
fun premain(agentArgs: String?, instrumentation: Instrumentation) {
// Add the agent jar (i.e., the jar out of which we are currently executing) to the search path of the bootstrap
// class loader to ensure that instrumented classes can find the CoverageMap class regardless of which ClassLoader
@@ -57,37 +54,25 @@ fun premain(agentArgs: String?, instrumentation: Instrumentation) {
} else {
println("WARN: Failed to add agent JAR to bootstrap class loader search path")
}
- val argumentMap = (agentArgs ?: "")
- .split(',')
- .mapNotNull {
- val splitArg = it.split('=', limit = 2)
- when {
- splitArg.size != 2 -> {
- if (splitArg[0].isNotEmpty())
- println("WARN: Ignoring argument ${splitArg[0]} without value")
- null
- }
- splitArg[0] !in KNOWN_ARGUMENTS -> {
- println("WARN: Ignoring unknown argument ${splitArg[0]}")
- null
- }
- else -> splitArg[0] to splitArg[1].split(argumentDelimiter)
- }
- }.toMap()
- val manifestCustomHookNames = ManifestUtils.combineManifestValues(ManifestUtils.HOOK_CLASSES).flatMap {
- it.split(':')
+
+ val manifestCustomHookNames =
+ ManifestUtils.combineManifestValues(ManifestUtils.HOOK_CLASSES).flatMap {
+ it.split(':')
+ }.filter { it.isNotBlank() }
+ val allCustomHookNames = (manifestCustomHookNames + Opt.customHooks).toSet()
+ val disabledCustomHookNames = Opt.disabledHooks.toSet()
+ val customHookNames = allCustomHookNames - disabledCustomHookNames
+ val disabledCustomHooksToPrint = allCustomHookNames - customHookNames.toSet()
+ if (disabledCustomHooksToPrint.isNotEmpty()) {
+ println("INFO: Not using the following disabled hooks: ${disabledCustomHooksToPrint.joinToString(", ")}")
}
- val customHookNames = manifestCustomHookNames + (argumentMap["custom_hooks"] ?: emptyList())
- val classNameGlobber = ClassNameGlobber(
- argumentMap["instrumentation_includes"] ?: emptyList(),
- (argumentMap["instrumentation_excludes"] ?: emptyList()) + customHookNames
- )
+
+ val classNameGlobber = ClassNameGlobber(Opt.instrumentationIncludes, Opt.instrumentationExcludes + customHookNames)
CoverageRecorder.classNameGlobber = classNameGlobber
- val dependencyClassNameGlobber = ClassNameGlobber(
- argumentMap["custom_hook_includes"] ?: emptyList(),
- (argumentMap["custom_hook_excludes"] ?: emptyList()) + customHookNames
- )
- val instrumentationTypes = (argumentMap["trace"] ?: listOf("all")).flatMap {
+ val customHookClassNameGlobber = ClassNameGlobber(Opt.customHookIncludes, Opt.customHookExcludes + customHookNames)
+ // FIXME: Setting trace to the empty string explicitly results in all rather than no trace types
+ // being applied - this is unintuitive.
+ val instrumentationTypes = (Opt.trace.takeIf { it.isNotEmpty() } ?: listOf("all")).flatMap {
when (it) {
"cmp" -> setOf(InstrumentationType.CMP)
"cov" -> setOf(InstrumentationType.COV)
@@ -106,13 +91,13 @@ fun premain(agentArgs: String?, instrumentation: Instrumentation) {
}
}
}.toSet()
- val idSyncFile = argumentMap["id_sync_file"]?.let {
- Paths.get(it.single()).also { path ->
+ val idSyncFile = Opt.idSyncFile.takeUnless { it.isEmpty() }?.let {
+ Paths.get(it).also { path ->
println("INFO: Synchronizing coverage IDs in ${path.toAbsolutePath()}")
}
}
- val dumpClassesDir = argumentMap["dump_classes_dir"]?.let {
- Paths.get(it.single()).toAbsolutePath().also { path ->
+ val dumpClassesDir = Opt.dumpClassesDir.takeUnless { it.isEmpty() }?.let {
+ Paths.get(it).toAbsolutePath().also { path ->
if (path.exists() && path.isDirectory()) {
println("INFO: Dumping instrumented classes into $path")
} else {
@@ -120,41 +105,67 @@ fun premain(agentArgs: String?, instrumentation: Instrumentation) {
}
}
}
+ val includedHookNames = instrumentationTypes
+ .mapNotNull { type ->
+ when (type) {
+ InstrumentationType.CMP -> TraceCmpHooks::class.java.name
+ InstrumentationType.DIV -> TraceDivHooks::class.java.name
+ InstrumentationType.INDIR -> TraceIndirHooks::class.java.name
+ InstrumentationType.NATIVE -> NativeLibHooks::class.java.name
+ else -> null
+ }
+ }
+ val coverageIdSynchronizer = if (idSyncFile != null)
+ FileSyncCoverageIdStrategy(idSyncFile)
+ else
+ MemSyncCoverageIdStrategy()
+
+ val (includedHooks, customHooks) = Hooks.loadHooks(includedHookNames.toSet(), customHookNames.toSet())
+ // If we don't append the JARs containing the custom hooks to the bootstrap class loader,
+ // third-party hooks not contained in the agent JAR will not be able to instrument Java standard
+ // library classes. These classes are loaded by the bootstrap / system class loader and would
+ // not be considered when resolving references to hook methods, leading to NoClassDefFoundError
+ // being thrown.
+ customHooks.hookClasses
+ .mapNotNull { jarUriForClass(it) }
+ .toSet()
+ .map { JarFile(File(it)) }
+ .forEach { instrumentation.appendToBootstrapClassLoaderSearch(it) }
+
val runtimeInstrumentor = RuntimeInstrumentor(
instrumentation,
classNameGlobber,
- dependencyClassNameGlobber,
+ customHookClassNameGlobber,
instrumentationTypes,
- idSyncFile,
+ includedHooks.hooks,
+ customHooks.hooks,
+ customHooks.additionalHookClassNameGlobber,
+ coverageIdSynchronizer,
dumpClassesDir,
)
- instrumentation.apply {
- addTransformer(runtimeInstrumentor)
- }
- val relevantClassesLoadedBeforeCustomHooks = instrumentation.allLoadedClasses
- .map { it.name }
- .filter { classNameGlobber.includes(it) || dependencyClassNameGlobber.includes(it) }
- .toSet()
- val customHooks = customHookNames.toSet().flatMap { hookClassName ->
- try {
- loadHooks(Class.forName(hookClassName)).also {
- println("INFO: Loaded ${it.size} hooks from $hookClassName")
- }
- } catch (_: ClassNotFoundException) {
- println("WARN: Failed to load hooks from $hookClassName")
- emptySet()
+ // These classes are e.g. dependencies of the RuntimeInstrumentor or hooks and thus were loaded
+ // before the instrumentor was ready. Since we haven't enabled it yet, they can safely be
+ // "retransformed": They haven't been transformed yet.
+ val classesToRetransform = instrumentation.allLoadedClasses
+ .filter {
+ classNameGlobber.includes(it.name) ||
+ customHookClassNameGlobber.includes(it.name) ||
+ customHooks.additionalHookClassNameGlobber.includes(it.name)
}
- }
- val relevantClassesLoadedAfterCustomHooks = instrumentation.allLoadedClasses
- .map { it.name }
- .filter { classNameGlobber.includes(it) || dependencyClassNameGlobber.includes(it) }
- .toSet()
- val nonHookClassesLoadedByHooks = relevantClassesLoadedAfterCustomHooks - relevantClassesLoadedBeforeCustomHooks
- if (nonHookClassesLoadedByHooks.isNotEmpty()) {
- println("WARN: Hooks were not applied to the following classes as they are dependencies of hooks:")
- println("WARN: ${nonHookClassesLoadedByHooks.joinToString()}")
- }
+ .filter {
+ instrumentation.isModifiableClass(it)
+ }
+ .toTypedArray()
+
+ instrumentation.addTransformer(runtimeInstrumentor, true)
- runtimeInstrumentor.registerCustomHooks(customHooks)
+ if (classesToRetransform.isNotEmpty()) {
+ if (instrumentation.isRetransformClassesSupported) {
+ instrumentation.retransformClasses(*classesToRetransform)
+ } else {
+ println("WARN: Instrumentation was not applied to the following classes as they are dependencies of hooks:")
+ println("WARN: ${classesToRetransform.joinToString()}")
+ }
+ }
}