Skip to content

Commit 51d4e3f

Browse files
committed
Initial commit
0 parents  commit 51d4e3f

File tree

8 files changed

+234
-0
lines changed

8 files changed

+234
-0
lines changed

.gitignore

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Compiled class file
2+
*.class
3+
4+
# Log file
5+
*.log
6+
7+
# BlueJ files
8+
*.ctxt
9+
10+
# Mobile Tools for Java (J2ME)
11+
.mtj.tmp/
12+
13+
# Package Files #
14+
*.jar
15+
*.war
16+
*.nar
17+
*.ear
18+
*.zip
19+
*.tar.gz
20+
*.rar
21+
22+
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
23+
hs_err_pid*
24+
replay_pid*
25+
/.project
26+
/.classpath
27+
/.settings
28+
/target

LICENSE

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
BSD 3-Clause License
2+
3+
Copyright (c) 2023, Johannes Kuhn
4+
5+
Redistribution and use in source and binary forms, with or without
6+
modification, are permitted provided that the following conditions are met:
7+
8+
1. Redistributions of source code must retain the above copyright notice, this
9+
list of conditions and the following disclaimer.
10+
11+
2. Redistributions in binary form must reproduce the above copyright notice,
12+
this list of conditions and the following disclaimer in the documentation
13+
and/or other materials provided with the distribution.
14+
15+
3. Neither the name of the copyright holder nor the names of its
16+
contributors may be used to endorse or promote products derived from
17+
this software without specific prior written permission.
18+
19+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# lamarr
2+
Attach JShell to an existing Java Process

pom.xml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<project xmlns="http://maven.apache.org/POM/4.0.0"
2+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
4+
<modelVersion>4.0.0</modelVersion>
5+
<groupId>pw.dasbrain</groupId>
6+
<artifactId>lamarr</artifactId>
7+
<version>1.0</version>
8+
<description>Attach JShell to existing Java Processes</description>
9+
<packaging>jar</packaging>
10+
<build>
11+
<plugins>
12+
<plugin>
13+
<groupId>org.apache.maven.plugins</groupId>
14+
<artifactId>maven-jar-plugin</artifactId>
15+
<version>3.3.0</version>
16+
<configuration>
17+
<archive>
18+
<index>true</index>
19+
<manifestEntries>
20+
<Agent-Class>pw.dasbrain.lamarr.agent.AgentMain</Agent-Class>
21+
<Can-Redefine-Classes>true</Can-Redefine-Classes>
22+
</manifestEntries>
23+
</archive>
24+
</configuration>
25+
</plugin>
26+
</plugins>
27+
</build>
28+
<properties>
29+
<maven.compiler.release>9</maven.compiler.release>
30+
<maven.compiler.target>9</maven.compiler.target>
31+
<maven.compiler.source>9</maven.compiler.source>
32+
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
33+
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
34+
</properties>
35+
</project>

src/main/java/module-info.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
module pw.dasbrain.lamarr {
2+
3+
requires transitive java.instrument;
4+
requires jdk.jshell;
5+
requires jdk.attach;
6+
7+
exports pw.dasbrain.lamarr.agent to java.instrument;
8+
opens pw.dasbrain.lamarr.agent to java.instrument;
9+
10+
provides jdk.jshell.spi.ExecutionControlProvider with
11+
pw.dasbrain.lamarr.InstrumentationExecutionControlProvider;
12+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package pw.dasbrain.lamarr;
2+
3+
import java.io.File;
4+
import java.io.IOException;
5+
import java.io.ObjectInputStream;
6+
import java.io.ObjectOutputStream;
7+
import java.net.ServerSocket;
8+
import java.net.Socket;
9+
import java.net.URISyntaxException;
10+
import java.util.HashMap;
11+
import java.util.Map;
12+
13+
import com.sun.tools.attach.AgentInitializationException;
14+
import com.sun.tools.attach.AgentLoadException;
15+
import com.sun.tools.attach.AttachNotSupportedException;
16+
import com.sun.tools.attach.VirtualMachine;
17+
18+
import jdk.jshell.execution.StreamingExecutionControl;
19+
import jdk.jshell.spi.ExecutionControl;
20+
import jdk.jshell.spi.ExecutionControlProvider;
21+
import jdk.jshell.spi.ExecutionEnv;
22+
23+
public class InstrumentationExecutionControlProvider implements ExecutionControlProvider {
24+
25+
@Override
26+
public String name() {
27+
return "instrumentation";
28+
}
29+
30+
@Override
31+
public ExecutionControl generate(ExecutionEnv env, Map<String, String> parameters)
32+
throws Throwable {
33+
ServerSocket so = new ServerSocket(0);
34+
so.setSoTimeout(5000);
35+
attach(parameters.get("pid"), so.getLocalPort());
36+
Socket s = so.accept();
37+
ObjectOutputStream out = new ObjectOutputStream(s.getOutputStream());
38+
out.flush();
39+
ObjectInputStream in = new ObjectInputStream(s.getInputStream());
40+
return new StreamingExecutionControl(out, in);
41+
}
42+
43+
@Override
44+
public Map<String, String> defaultParameters() {
45+
Map<String, String> result = new HashMap<>();
46+
result.put("pid", "");
47+
return result;
48+
}
49+
50+
private static void attach(String pid, int port) throws AttachNotSupportedException,
51+
IOException, AgentLoadException, AgentInitializationException, URISyntaxException {
52+
VirtualMachine vm = VirtualMachine.attach(pid);
53+
try {
54+
vm.loadAgent(new File(InstrumentationExecutionControlProvider.class
55+
.getProtectionDomain().getCodeSource().getLocation().toURI()).toString(),
56+
port + "");
57+
} finally {
58+
vm.detach();
59+
}
60+
}
61+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package pw.dasbrain.lamarr.agent;
2+
3+
import java.lang.instrument.Instrumentation;
4+
5+
public class AgentMain {
6+
7+
public static void agentmain(String arg, Instrumentation inst) {
8+
Thread t = new Thread(new InstrumentationRemoteExecutionControl(inst, arg), "rjshell");
9+
t.setDaemon(false);
10+
t.start();
11+
}
12+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package pw.dasbrain.lamarr.agent;
2+
3+
import java.io.IOException;
4+
import java.io.ObjectInputStream;
5+
import java.io.ObjectOutputStream;
6+
import java.lang.instrument.ClassDefinition;
7+
import java.lang.instrument.Instrumentation;
8+
import java.lang.instrument.UnmodifiableClassException;
9+
import java.net.InetAddress;
10+
import java.net.Socket;
11+
12+
import jdk.jshell.execution.RemoteExecutionControl;
13+
import jdk.jshell.execution.Util;
14+
15+
class InstrumentationRemoteExecutionControl extends RemoteExecutionControl implements Runnable {
16+
17+
private final Instrumentation inst;
18+
private final String arg;
19+
20+
public InstrumentationRemoteExecutionControl(Instrumentation inst, String arg) {
21+
this.inst = inst;
22+
this.arg = arg;
23+
}
24+
25+
@Override
26+
public void redefine(ClassBytecodes[] cbcs)
27+
throws ClassInstallException, NotImplementedException, EngineTerminationException {
28+
try {
29+
ClassDefinition[] defs = new ClassDefinition[cbcs.length];
30+
for (int i = 0; i < cbcs.length; i++) {
31+
defs[i] = new ClassDefinition(findClass(cbcs[i].name()), cbcs[i].bytecodes());
32+
}
33+
inst.redefineClasses(defs);
34+
} catch (ClassNotFoundException | UnmodifiableClassException
35+
| UnsupportedOperationException e) {
36+
// In this case no classes have been redefined.
37+
ClassInstallException cie = new ClassInstallException(e.getMessage(), new boolean[cbcs.length]);
38+
cie.initCause(e);
39+
throw cie;
40+
}
41+
super.redefine(cbcs);
42+
}
43+
44+
@Override
45+
public void run() {
46+
try (Socket s = new Socket(InetAddress.getLoopbackAddress(), Integer.parseInt(arg));
47+
ObjectOutputStream out = new ObjectOutputStream(s.getOutputStream());
48+
ObjectInputStream in = new ObjectInputStream(s.getInputStream());) {
49+
out.flush();
50+
Util.forwardExecutionControl(this, in, out);
51+
} catch (IOException e) {
52+
// TODO: Better error handling
53+
throw new InternalError(e);
54+
}
55+
}
56+
}

0 commit comments

Comments
 (0)