Skip to content

Commit 7475f6f

Browse files
committed
Add support for agent self-testing, to confirm JVM compat
1 parent 0cb14e3 commit 7475f6f

File tree

4 files changed

+65
-15
lines changed

4 files changed

+65
-15
lines changed

build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,4 +164,4 @@ task quickTest(type: Test) {
164164
task distTest(type: Test) {
165165
environment 'TEST_JAR', tasks.distJar.getArchiveFile().get().asFile.toString()
166166
dependsOn('distJar')
167-
}
167+
}

src/main/kotlin/tech/httptoolkit/javaagent/AgentMain.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,14 @@ fun agentmain(arguments: String?, instrumentation: Instrumentation) {
5151
if (arguments.isNullOrEmpty()) {
5252
throw Error("Can't attach proxy agent without configuration arguments")
5353
}
54+
55+
// If attached as a test, we don't intercept anything, we're just checking that it's
56+
// possible to attach in the first place with the current VM.
57+
if (arguments == "attach-test") {
58+
println("Agent attach test successful")
59+
return
60+
};
61+
5462
val config = getConfigFromArg(arguments)
5563
interceptAllHttps(config, instrumentation)
5664
}

src/main/kotlin/tech/httptoolkit/javaagent/AttachMain.kt

Lines changed: 41 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import com.sun.tools.attach.*
66
import kotlin.system.exitProcess
77
import java.lang.management.ManagementFactory
88
import java.io.File
9+
import java.lang.IllegalArgumentException
910

1011
// This file is the only one that uses com.sun.tools.attach.VirtualMachine. That's important because that
1112
// requires tools.jar to be in your classpath (i.e. it requires a JDK not a JRE). If you run this without
@@ -24,41 +25,67 @@ val x: Class<*> = try { // Var declaration required for static init for some rea
2425

2526
// If run directly, can either list potential targets (list-targets) or attach to a target (pid, ...config)
2627
fun main(args: Array<String>) {
27-
if (args.size == 1 && args[0] == "list-targets") {
28-
// This isn't guaranteed to work everywhere, but it should work in most places:
29-
val (pid) = ManagementFactory.getRuntimeMXBean().name.split("@")
30-
31-
val vms = VirtualMachine.list()
32-
if (vms.isEmpty()) {
33-
// VMs should never be empty, because at the very least _we_ should be in there! If it's empty then
34-
// scanning isn't working at all, and we should fail clearly.
35-
System.err.println("Can't scan for attachable JVMs. Are we running in a JRE instead of a JDK?")
36-
exitProcess(4)
28+
// Self-test ensures that the JVM we're using is capable of scanning & attachment. It *doesn't* fully
29+
// test its ability to transform classes as we'd like.
30+
if (args.size == 1 && args[0] == "self-test") {
31+
val selfAttachAllowed = System.getProperty("jdk.attach.allowAttachSelf")
32+
if (selfAttachAllowed != "true") {
33+
throw IllegalArgumentException("Cannot run self-test without -Djdk.attach.allowAttachSelf=true")
3734
}
3835

36+
getTargets() // Check we can scan for targets
37+
attachAgent(getOwnPid(), "attach-test") // Check we can attach (against ourselves)
38+
} else if (args.size == 1 && args[0] == "list-targets") {
39+
// List-targets prints a list of pid:name target paids
40+
val pid = getOwnPid()
41+
val vms = getTargets()
3942
vms.forEach { vmd ->
4043
if (vmd.id() != pid) {
4144
println("${vmd.id()}:${vmd.displayName()}")
4245
}
4346
}
4447

4548
exitProcess(0)
46-
} else if (args.size != 4) {
49+
} else if (args.size == 4) {
50+
// 4-args format attaches to a target pid with the given config values
51+
val (pid, proxyHost, proxyPort, certPath) = args
52+
attachAgent(pid, formatConfigArg(proxyHost, proxyPort, certPath))
53+
} else {
4754
System.err.println("Usage: java -jar <agent.jar> <target-PID> <proxyHost> <proxyPort> <path-to-certificate>")
55+
System.err.println("Or pass a single 'self-test' or 'list-target' arg to check capabilities or scan for pids")
4856
exitProcess(2)
4957
}
58+
}
59+
60+
fun getOwnPid(): String {
61+
// This should work in general, but it's implementation dependent:
62+
return ManagementFactory.getRuntimeMXBean().name.split("@")[0]
63+
}
5064

51-
val (pid, proxyHost, proxyPort, certPath) = args
65+
fun getTargets(): List<VirtualMachineDescriptor> {
66+
val vms = VirtualMachine.list()
67+
if (vms.isEmpty()) {
68+
// VMs should never be empty, because at the very least _we_ should be in there! If it's empty then
69+
// scanning isn't working at all, and we should fail clearly.
70+
System.err.println("Can't scan for attachable JVMs. Are we running in a JRE instead of a JDK?")
71+
exitProcess(4)
72+
}
73+
return vms
74+
}
5275

76+
fun attachAgent(
77+
pid: String,
78+
agentArg: String
79+
) {
5380
val jarPath = File(
5481
ConstantProxySelector::class.java // Any arbitrary class defined inside this JAR
5582
.protectionDomain.codeSource.location.path
5683
).absolutePath
5784

58-
// Inject the agent with our config arguments into the target VM
85+
// Inject the agent into the target VM
5986
try {
6087
val vm: VirtualMachine = VirtualMachine.attach(pid)
61-
vm.loadAgent(jarPath, formatConfigArg(proxyHost, proxyPort, certPath))
88+
vm.loadAgent(jarPath, agentArg)
6289
vm.detach()
6390
} catch (e: AgentLoadException) {
6491
if (e.message == "0") {

src/test/kotlin/IntegrationTests.kt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,21 @@ val wireMockServer = WireMockServer(options()
4242
val runningProcs = arrayListOf<Process>()
4343

4444
class IntegrationTests : StringSpec({
45+
"Launching a self test should return successfully" {
46+
val proc = ProcessBuilder(
47+
javaPath,
48+
"-Djdk.attach.allowAttachSelf=true",
49+
"-jar", AGENT_JAR_PATH,
50+
"self-test"
51+
).start()
52+
runningProcs.add(proc)
53+
54+
proc.waitFor(10, TimeUnit.SECONDS)
55+
56+
proc.isAlive.shouldBe(false)
57+
proc.exitValue().shouldBe(0)
58+
}
59+
4560
"Launching with list-targets should return successfully" {
4661
val proc = ProcessBuilder(
4762
javaPath,

0 commit comments

Comments
 (0)