Allow dumping instrumented classes
With --dump_classes_dir=<dir>, all classes that are instrumented by the
agent at runtime will be dumped into a subdirectory of <dir> according
to their internal class name.
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 bb27763..71c20e7 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
@@ -23,6 +23,9 @@
import java.lang.instrument.Instrumentation
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",
@@ -32,6 +35,7 @@
"trace",
"custom_hooks",
"id_sync_file",
+ "dump_classes_dir",
)
private object AgentJarFinder {
@@ -39,6 +43,7 @@
val agentJarFile = agentJarPath?.let { JarFile(File(it)) }
}
+@OptIn(ExperimentalPathApi::class)
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
@@ -97,12 +102,22 @@
println("INFO: Synchronizing coverage IDs in ${path.toAbsolutePath()}")
}
}
+ val dumpClassesDir = argumentMap["dump_classes_dir"]?.let {
+ Paths.get(it.single()).toAbsolutePath().also { path ->
+ if (path.exists() && path.isDirectory()) {
+ println("INFO: Dumping instrumented classes into $path")
+ } else {
+ println("ERROR: Cannot dump instrumented classes into $path; does not exist or not a directory")
+ }
+ }
+ }
val runtimeInstrumentor = RuntimeInstrumentor(
instrumentation,
classNameGlobber,
dependencyClassNameGlobber,
instrumentationTypes,
- idSyncFile
+ idSyncFile,
+ dumpClassesDir,
)
instrumentation.apply {
addTransformer(runtimeInstrumentor)
diff --git a/agent/src/main/java/com/code_intelligence/jazzer/agent/RuntimeInstrumentor.kt b/agent/src/main/java/com/code_intelligence/jazzer/agent/RuntimeInstrumentor.kt
index b6bed2a..64a5ca5 100644
--- a/agent/src/main/java/com/code_intelligence/jazzer/agent/RuntimeInstrumentor.kt
+++ b/agent/src/main/java/com/code_intelligence/jazzer/agent/RuntimeInstrumentor.kt
@@ -122,6 +122,7 @@
private val dependencyClassesToInstrument: ClassNameGlobber,
private val instrumentationTypes: Set<InstrumentationType>,
idSyncFile: Path?,
+ private val dumpClassesDir: Path?,
) : ClassFileTransformer {
private val coverageIdSynchronizer = if (idSyncFile != null)
@@ -166,6 +167,15 @@
// https://docs.oracle.com/javase/9/docs/api/java/lang/instrument/ClassFileTransformer.html
t.printStackTrace()
throw t
+ }.also { instrumentedByteCode ->
+ // Only dump classes that were instrumented.
+ if (instrumentedByteCode != null && dumpClassesDir != null) {
+ val relativePath = "$internalClassName.class"
+ val absolutePath = dumpClassesDir.resolve(relativePath)
+ val dumpFile = absolutePath.toFile()
+ dumpFile.parentFile.mkdirs()
+ dumpFile.writeBytes(instrumentedByteCode)
+ }
}
}
diff --git a/driver/jvm_tooling.cpp b/driver/jvm_tooling.cpp
index 805596b..1d27897 100644
--- a/driver/jvm_tooling.cpp
+++ b/driver/jvm_tooling.cpp
@@ -72,6 +72,9 @@
"path to a file that should be used to synchronize coverage IDs "
"between parallel fuzzing processes. Defaults to a temporary file "
"created for this purpose if running in parallel.");
+DEFINE_string(
+ dump_classes_dir, "",
+ "path to a directory in which Jazzer should dump the instrumented classes");
DECLARE_bool(hooks);
@@ -140,6 +143,7 @@
{"custom_hook_excludes", FLAGS_custom_hook_excludes},
{"trace", FLAGS_trace},
{"id_sync_file", FLAGS_id_sync_file},
+ {"dump_classes_dir", FLAGS_dump_classes_dir},
}) {
if (!flag_pair.second.empty()) {
args.push_back(flag_pair.first + "=" + flag_pair.second);