diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d8014f4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,28 @@ +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* +replay_pid* +/.project +/.classpath +/.settings +/target diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..98b2d48 --- /dev/null +++ b/LICENSE @@ -0,0 +1,28 @@ +BSD 3-Clause License + +Copyright (c) 2023, Johannes Kuhn + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..0c56ad0 --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# lamarr +Attach JShell to an existing Java Process diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..8ea7a31 --- /dev/null +++ b/pom.xml @@ -0,0 +1,35 @@ + + 4.0.0 + pw.dasbrain + lamarr + 1.0 + Attach JShell to existing Java Processes + jar + + + + org.apache.maven.plugins + maven-jar-plugin + 3.3.0 + + + true + + pw.dasbrain.lamarr.agent.AgentMain + true + + + + + + + + 9 + 9 + 9 + UTF-8 + UTF-8 + + \ No newline at end of file diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java new file mode 100644 index 0000000..384a7a4 --- /dev/null +++ b/src/main/java/module-info.java @@ -0,0 +1,12 @@ +module pw.dasbrain.lamarr { + + requires transitive java.instrument; + requires jdk.jshell; + requires jdk.attach; + + exports pw.dasbrain.lamarr.agent to java.instrument; + opens pw.dasbrain.lamarr.agent to java.instrument; + + provides jdk.jshell.spi.ExecutionControlProvider with + pw.dasbrain.lamarr.InstrumentationExecutionControlProvider; +} \ No newline at end of file diff --git a/src/main/java/pw/dasbrain/lamarr/InstrumentationExecutionControlProvider.java b/src/main/java/pw/dasbrain/lamarr/InstrumentationExecutionControlProvider.java new file mode 100644 index 0000000..f270d10 --- /dev/null +++ b/src/main/java/pw/dasbrain/lamarr/InstrumentationExecutionControlProvider.java @@ -0,0 +1,61 @@ +package pw.dasbrain.lamarr; + +import java.io.File; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.URISyntaxException; +import java.util.HashMap; +import java.util.Map; + +import com.sun.tools.attach.AgentInitializationException; +import com.sun.tools.attach.AgentLoadException; +import com.sun.tools.attach.AttachNotSupportedException; +import com.sun.tools.attach.VirtualMachine; + +import jdk.jshell.execution.StreamingExecutionControl; +import jdk.jshell.spi.ExecutionControl; +import jdk.jshell.spi.ExecutionControlProvider; +import jdk.jshell.spi.ExecutionEnv; + +public class InstrumentationExecutionControlProvider implements ExecutionControlProvider { + + @Override + public String name() { + return "instrumentation"; + } + + @Override + public ExecutionControl generate(ExecutionEnv env, Map parameters) + throws Throwable { + ServerSocket so = new ServerSocket(0); + so.setSoTimeout(5000); + attach(parameters.get("pid"), so.getLocalPort()); + Socket s = so.accept(); + ObjectOutputStream out = new ObjectOutputStream(s.getOutputStream()); + out.flush(); + ObjectInputStream in = new ObjectInputStream(s.getInputStream()); + return new StreamingExecutionControl(out, in); + } + + @Override + public Map defaultParameters() { + Map result = new HashMap<>(); + result.put("pid", ""); + return result; + } + + private static void attach(String pid, int port) throws AttachNotSupportedException, + IOException, AgentLoadException, AgentInitializationException, URISyntaxException { + VirtualMachine vm = VirtualMachine.attach(pid); + try { + vm.loadAgent(new File(InstrumentationExecutionControlProvider.class + .getProtectionDomain().getCodeSource().getLocation().toURI()).toString(), + port + ""); + } finally { + vm.detach(); + } + } +} diff --git a/src/main/java/pw/dasbrain/lamarr/agent/AgentMain.java b/src/main/java/pw/dasbrain/lamarr/agent/AgentMain.java new file mode 100644 index 0000000..e04a442 --- /dev/null +++ b/src/main/java/pw/dasbrain/lamarr/agent/AgentMain.java @@ -0,0 +1,12 @@ +package pw.dasbrain.lamarr.agent; + +import java.lang.instrument.Instrumentation; + +public class AgentMain { + + public static void agentmain(String arg, Instrumentation inst) { + Thread t = new Thread(new InstrumentationRemoteExecutionControl(inst, arg), "rjshell"); + t.setDaemon(false); + t.start(); + } +} diff --git a/src/main/java/pw/dasbrain/lamarr/agent/InstrumentationRemoteExecutionControl.java b/src/main/java/pw/dasbrain/lamarr/agent/InstrumentationRemoteExecutionControl.java new file mode 100644 index 0000000..c0315d8 --- /dev/null +++ b/src/main/java/pw/dasbrain/lamarr/agent/InstrumentationRemoteExecutionControl.java @@ -0,0 +1,56 @@ +package pw.dasbrain.lamarr.agent; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.lang.instrument.ClassDefinition; +import java.lang.instrument.Instrumentation; +import java.lang.instrument.UnmodifiableClassException; +import java.net.InetAddress; +import java.net.Socket; + +import jdk.jshell.execution.RemoteExecutionControl; +import jdk.jshell.execution.Util; + +class InstrumentationRemoteExecutionControl extends RemoteExecutionControl implements Runnable { + + private final Instrumentation inst; + private final String arg; + + public InstrumentationRemoteExecutionControl(Instrumentation inst, String arg) { + this.inst = inst; + this.arg = arg; + } + + @Override + public void redefine(ClassBytecodes[] cbcs) + throws ClassInstallException, NotImplementedException, EngineTerminationException { + try { + ClassDefinition[] defs = new ClassDefinition[cbcs.length]; + for (int i = 0; i < cbcs.length; i++) { + defs[i] = new ClassDefinition(findClass(cbcs[i].name()), cbcs[i].bytecodes()); + } + inst.redefineClasses(defs); + } catch (ClassNotFoundException | UnmodifiableClassException + | UnsupportedOperationException e) { + // In this case no classes have been redefined. + ClassInstallException cie = new ClassInstallException(e.getMessage(), new boolean[cbcs.length]); + cie.initCause(e); + throw cie; + } + super.redefine(cbcs); + } + + @Override + public void run() { + try (Socket s = new Socket(InetAddress.getLoopbackAddress(), Integer.parseInt(arg)); + ObjectOutputStream out = new ObjectOutputStream(s.getOutputStream()); + ObjectInputStream in = new ObjectInputStream(s.getInputStream());) { + out.flush(); + Util.forwardExecutionControl(this, in, out); + } catch (IOException e) { + // TODO: Better error handling + throw new InternalError(e); + } + } +}