diff options
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.kt | 165 |
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()}") + } + } } |