Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow instrumenting internal classes #98

Merged
merged 4 commits into from May 12, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
24 changes: 22 additions & 2 deletions agent/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt
Expand Up @@ -19,8 +19,10 @@ package com.code_intelligence.jazzer.agent
import com.code_intelligence.jazzer.instrumentor.InstrumentationType
import com.code_intelligence.jazzer.instrumentor.loadHooks
import com.code_intelligence.jazzer.runtime.ManifestUtils
import java.io.File
import java.lang.instrument.Instrumentation
import java.nio.file.Paths
import java.util.jar.JarFile

val KNOWN_ARGUMENTS = listOf(
"instrumentation_includes",
Expand All @@ -32,7 +34,20 @@ val KNOWN_ARGUMENTS = listOf(
"id_sync_file",
)

private object AgentJarFinder {
private val agentJarPath = AgentJarFinder::class.java.protectionDomain?.codeSource?.location?.toURI()
val agentJarFile = agentJarPath?.let { JarFile(File(it)) }
}

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
// they are using.
if (AgentJarFinder.agentJarFile != null) {
instrumentation.appendToBootstrapClassLoaderSearch(AgentJarFinder.agentJarFile)
} else {
println("WARN: Failed to add agent JAR to bootstrap class loader search path")
}
val argumentMap = (agentArgs ?: "")
.split(',')
.mapNotNull {
Expand Down Expand Up @@ -82,8 +97,13 @@ fun premain(agentArgs: String?, instrumentation: Instrumentation) {
println("INFO: Synchronizing coverage IDs in ${path.toAbsolutePath()}")
}
}

val runtimeInstrumentor = RuntimeInstrumentor(classNameGlobber, dependencyClassNameGlobber, instrumentationTypes, idSyncFile)
val runtimeInstrumentor = RuntimeInstrumentor(
instrumentation,
classNameGlobber,
dependencyClassNameGlobber,
instrumentationTypes,
idSyncFile
)
instrumentation.apply {
addTransformer(runtimeInstrumentor)
}
Expand Down
Expand Up @@ -24,6 +24,7 @@ import com.code_intelligence.jazzer.runtime.TraceCmpHooks
import com.code_intelligence.jazzer.runtime.TraceDivHooks
import com.code_intelligence.jazzer.runtime.TraceIndirHooks
import java.lang.instrument.ClassFileTransformer
import java.lang.instrument.Instrumentation
import java.nio.file.FileSystems
import java.nio.file.Path
import java.nio.file.Paths
Expand Down Expand Up @@ -66,6 +67,7 @@ internal class ClassNameGlobber(includes: List<String>, excludes: List<String>)
}

internal class RuntimeInstrumentor(
private val instrumentation: Instrumentation,
private val classesToInstrument: ClassNameGlobber,
private val dependencyClassesToInstrument: ClassNameGlobber,
private val instrumentationTypes: Set<InstrumentationType>,
Expand Down Expand Up @@ -96,7 +98,7 @@ internal class RuntimeInstrumentor(

@OptIn(kotlin.time.ExperimentalTime::class)
override fun transform(
loader: ClassLoader,
loader: ClassLoader?,
internalClassName: String,
classBeingRedefined: Class<*>?,
protectionDomain: ProtectionDomain?,
Expand All @@ -113,6 +115,36 @@ internal class RuntimeInstrumentor(
}
}

override fun transform(
module: Module?,
loader: ClassLoader?,
internalClassName: String,
classBeingRedefined: Class<*>?,
protectionDomain: ProtectionDomain?,
classfileBuffer: ByteArray
): ByteArray? {
if (module != null && !module.canRead(RuntimeInstrumentor::class.java.module)) {
// Make all other modules read our (unnamed) module, which allows them to access the classes needed by the
// instrumentations, e.g. CoverageMap. If a module can't be modified, it should not be instrumented as the
// injected bytecode might throw NoClassDefFoundError.
// https://mail.openjdk.java.net/pipermail/jigsaw-dev/2021-May/014663.html
if (!instrumentation.isModifiableModule(module)) {
val prettyClassName = internalClassName.replace('/', '.')
println("WARN: Failed to instrument $prettyClassName in unmodifiable module ${module.name}, skipping")
return null
}
instrumentation.redefineModule(
module,
/* extraReads */ setOf(RuntimeInstrumentor::class.java.module),
emptyMap(),
emptyMap(),
emptySet(),
emptyMap()
)
}
return transform(loader, internalClassName, classBeingRedefined, protectionDomain, classfileBuffer)
}

@OptIn(kotlin.time.ExperimentalTime::class)
fun transformInternal(internalClassName: String, classfileBuffer: ByteArray): ByteArray? {
val fullInstrumentation = when {
Expand Down
Expand Up @@ -19,7 +19,10 @@ private object JavaNoThrowMethods {
const val dataFileName = "java_no_throw_methods_list.dat"

fun readJavaNoThrowMethods(): Set<String> {
val resource = JavaNoThrowMethods.javaClass.classLoader.getResource("$dataFilePath/$dataFileName")!!
// If we successfully appended the agent JAR to the bootstrap class loader path in Agent, the classLoader
// property of JavaNoThrowMethods returns null and we have to use the system class loader instead.
val ownClassLoader = JavaNoThrowMethods::class.java.classLoader ?: ClassLoader.getSystemClassLoader()
val resource = ownClassLoader.getResource("$dataFilePath/$dataFileName")!!
return resource.openStream().bufferedReader().useLines { line -> line.toSet() }.also {
println("INFO: Loaded ${it.size} no-throw method signatures")
}
Expand All @@ -33,6 +36,4 @@ private object JavaNoThrowMethods {
* Note: Since methods only declare their thrown exceptions that are not subclasses of [java.lang.RuntimeException],
* this list cannot be generated purely based on information available via reflection.
*/
val JAVA_NO_THROW_METHODS by lazy {
JavaNoThrowMethods.readJavaNoThrowMethods()
}
val JAVA_NO_THROW_METHODS = JavaNoThrowMethods.readJavaNoThrowMethods()
Expand Up @@ -22,7 +22,7 @@ object ManifestUtils {
const val HOOK_CLASSES = "Jazzer-Hook-Classes"

fun combineManifestValues(attribute: String): List<String> {
val manifests = ManifestUtils::class.java.classLoader.getResources("META-INF/MANIFEST.MF")
val manifests = ClassLoader.getSystemResources("META-INF/MANIFEST.MF")
return manifests.asSequence().mapNotNull { url ->
url.openStream().use { inputStream ->
val manifest = Manifest(inputStream)
Expand Down