undefect. CWE-407 — 63 sites patched across 27 ecosystems

Authors: russell@unturf.com · brackishbert@gmail.com · foxhop.net · TimeHexOn.com

Patches, unit tests, benchmarks, whitepaper, and outreach briefs.
Public domain — no copyright claimed. Use freely.
This commit is contained in:
russell@unturf.com 2026-03-26 17:11:57 -04:00
commit 0a580b313d
70422 changed files with 17213626 additions and 0 deletions

View file

@ -0,0 +1,79 @@
/*
* Copyright (c) 2008, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2013 SAP SE. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package sun.tools.attach;
import com.sun.tools.attach.VirtualMachine;
import com.sun.tools.attach.VirtualMachineDescriptor;
import com.sun.tools.attach.AttachNotSupportedException;
import java.io.IOException;
// Based on linux/classes/sun/tools/attach/AttachProviderImpl.java.
/*
* An AttachProvider implementation for Aix that uses a UNIX domain
* socket.
*/
public class AttachProviderImpl extends HotSpotAttachProvider {
public AttachProviderImpl() {
}
public String name() {
return "sun";
}
public String type() {
return "socket";
}
public VirtualMachine attachVirtualMachine(String vmid)
throws AttachNotSupportedException, IOException
{
// AttachNotSupportedException will be thrown if the target VM can be determined
// to be not attachable.
testAttachable(vmid);
return new VirtualMachineImpl(this, vmid);
}
public VirtualMachine attachVirtualMachine(VirtualMachineDescriptor vmd)
throws AttachNotSupportedException, IOException
{
if (vmd.provider() != this) {
throw new AttachNotSupportedException("provider mismatch");
}
// To avoid re-checking if the VM if attachable, we check if the descriptor
// is for a hotspot VM - these descriptors are created by the listVirtualMachines
// implementation which only returns a list of attachable VMs.
if (vmd instanceof HotSpotVirtualMachineDescriptor) {
assert ((HotSpotVirtualMachineDescriptor)vmd).isAttachable();
return new VirtualMachineImpl(this, vmd.id());
} else {
return attachVirtualMachine(vmd.id());
}
}
}

View file

@ -0,0 +1,252 @@
/*
* Copyright (c) 2008, 2025, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2015, 2019 SAP SE. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package sun.tools.attach;
import com.sun.tools.attach.AgentLoadException;
import com.sun.tools.attach.AttachNotSupportedException;
import com.sun.tools.attach.spi.AttachProvider;
import java.io.InputStream;
import java.io.IOException;
import java.io.File;
import static java.nio.charset.StandardCharsets.UTF_8;
/*
* Aix implementation of HotSpotVirtualMachine
*/
@SuppressWarnings("restricted")
public class VirtualMachineImpl extends HotSpotVirtualMachine {
// "/tmp" is used as a global well-known location for the files
// .java_pid<pid>. and .attach_pid<pid>. It is important that this
// location is the same for all processes, otherwise the tools
// will not be able to find all Hotspot processes.
// Any changes to this needs to be synchronized with HotSpot.
private static final String tmpdir = "/tmp";
String socket_path;
private OperationProperties props = new OperationProperties(VERSION_1);
/**
* Attaches to the target VM
*/
VirtualMachineImpl(AttachProvider provider, String vmid)
throws AttachNotSupportedException, IOException
{
super(provider, vmid);
// This provider only understands pids
int pid = Integer.parseInt(vmid);
if (pid < 1) {
throw new AttachNotSupportedException("Invalid process identifier: " + vmid);
}
// Find the socket file. If not found then we attempt to start the
// attach mechanism in the target VM by sending it a QUIT signal.
// Then we attempt to find the socket file again.
File socket_file = new File(tmpdir, ".java_pid" + pid);
socket_path = socket_file.getPath();
if (!socket_file.exists()) {
// Keep canonical version of File, to delete, in case target process ends and /proc link has gone:
File f = createAttachFile(pid).getCanonicalFile();
try {
sendQuitTo(pid);
// give the target VM time to start the attach mechanism
final int delay_step = 100;
final long timeout = attachTimeout();
long time_spend = 0;
long delay = 0;
do {
// Increase timeout on each attempt to reduce polling
delay += delay_step;
try {
Thread.sleep(delay);
} catch (InterruptedException x) { }
time_spend += delay;
if (time_spend > timeout/2 && !socket_file.exists()) {
// Send QUIT again to give target VM the last chance to react
sendQuitTo(pid);
}
} while (time_spend <= timeout && !socket_file.exists());
if (!socket_file.exists()) {
throw new AttachNotSupportedException(
String.format("Unable to open socket file %s: " +
"target process %d doesn't respond within %dms " +
"or HotSpot VM not loaded", socket_path, pid,
time_spend));
}
} finally {
f.delete();
}
}
// Check that the file owner/permission to avoid attaching to
// bogus process
checkPermissions(socket_path);
// Check that we can connect to the process
// - this ensures we throw the permission denied error now rather than
// later when we attempt to enqueue a command.
if (isAPIv2Enabled()) {
props = getDefaultProps();
} else {
// Check that we can connect to the process
// - this ensures we throw the permission denied error now rather than
// later when we attempt to enqueue a command.
int s = socket();
try {
connect(s, socket_path);
} finally {
close(s);
}
}
}
/**
* Detach from the target VM
*/
public void detach() throws IOException {
synchronized (this) {
if (socket_path != null) {
socket_path = null;
}
}
}
/**
* Execute the given command in the target VM.
*/
InputStream execute(String cmd, Object ... args) throws AgentLoadException, IOException {
checkNulls(args);
// did we detach?
synchronized (this) {
if (socket_path == null) {
throw new IOException("Detached from target VM");
}
}
// create UNIX socket
int s = socket();
// connect to target VM
try {
connect(s, socket_path);
} catch (IOException x) {
close(s);
throw x;
}
IOException ioe = null;
// connected - write request
// <ver> <cmd> <args...>
try {
SocketOutputStream writer = new SocketOutputStream(s);
writeCommand(writer, props, cmd, args);
} catch (IOException x) {
ioe = x;
}
// Create an input stream to read reply
SocketInputStreamImpl sis = new SocketInputStreamImpl(s);
// Process the command completion status
processCompletionStatus(ioe, cmd, sis);
// Return the input stream so that the command output can be read
return sis;
}
private static class SocketOutputStream implements AttachOutputStream {
private int fd;
public SocketOutputStream(int fd) {
this.fd = fd;
}
@Override
public void write(byte[] buffer, int offset, int length) throws IOException {
VirtualMachineImpl.write(fd, buffer, offset, length);
}
}
/*
* InputStream for the socket connection to get target VM
*/
private static class SocketInputStreamImpl extends SocketInputStream {
public SocketInputStreamImpl(long fd) {
super(fd);
}
@Override
protected int read(long fd, byte[] bs, int off, int len) throws IOException {
return VirtualMachineImpl.read((int)fd, bs, off, len);
}
@Override
protected void close(long fd) throws IOException {
VirtualMachineImpl.close((int)fd);
}
}
// On Aix a simple handshake is used to start the attach mechanism
// if not already started. The client creates a .attach_pid<pid> file in the
// target VM's working directory (or temp directory), and the SIGQUIT handler
// checks for the file.
private File createAttachFile(int pid) throws IOException {
String fn = ".attach_pid" + pid;
String path = "/proc/" + pid + "/cwd/" + fn;
File f = new File(path);
try {
f.createNewFile();
} catch (IOException x) {
f = new File(tmpdir, fn);
f.createNewFile();
}
return f;
}
//-- native methods
static native void sendQuitTo(int pid) throws IOException;
static native void checkPermissions(String path) throws IOException;
static native int socket() throws IOException;
static native void connect(int fd, String path) throws IOException;
static native void close(int fd) throws IOException;
static native int read(int fd, byte buf[], int off, int bufLen) throws IOException;
static native void write(int fd, byte buf[], int off, int bufLen) throws IOException;
static {
System.loadLibrary("attach");
}
}

View file

@ -0,0 +1,254 @@
/*
* Copyright (c) 2008, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2015, 2024 SAP SE. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
#include "jni_util.h"
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/un.h>
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "sun_tools_attach_VirtualMachineImpl.h"
#define ROOT_UID 0
/*
* Class: sun_tools_attach_VirtualMachineImpl
* Method: socket
* Signature: ()I
*/
JNIEXPORT jint JNICALL Java_sun_tools_attach_VirtualMachineImpl_socket
(JNIEnv *env, jclass cls)
{
int fd = socket(PF_UNIX, SOCK_STREAM, 0);
if (fd == -1) {
JNU_ThrowIOExceptionWithLastError(env, "socket");
}
return (jint)fd;
}
/*
* Class: sun_tools_attach_VirtualMachineImpl
* Method: connect
* Signature: (ILjava/lang/String;)I
*/
JNIEXPORT void JNICALL Java_sun_tools_attach_VirtualMachineImpl_connect
(JNIEnv *env, jclass cls, jint fd, jstring path)
{
jboolean isCopy;
const char* p = GetStringPlatformChars(env, path, &isCopy);
if (p != NULL) {
struct sockaddr_un addr;
int err = 0;
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
/* strncpy is safe because addr.sun_path was zero-initialized before. */
strncpy(addr.sun_path, p, sizeof(addr.sun_path) - 1);
if (connect(fd, (struct sockaddr*)&addr, SUN_LEN(&addr)) == -1) {
err = errno;
}
if (isCopy) {
JNU_ReleaseStringPlatformChars(env, path, p);
}
/*
* If the connect failed then we throw the appropriate exception
* here (can't throw it before releasing the string as can't call
* JNI with pending exception)
*/
if (err != 0) {
if (err == ENOENT) {
JNU_ThrowByName(env, "java/io/FileNotFoundException", NULL);
} else {
char* msg = strdup(strerror(err));
JNU_ThrowIOException(env, msg);
if (msg != NULL) {
free(msg);
}
}
}
}
}
/*
* Class: sun_tools_attach_VirtualMachineImpl
* Method: sendQuitTo
* Signature: (I)V
*/
JNIEXPORT void JNICALL Java_sun_tools_attach_VirtualMachineImpl_sendQuitTo
(JNIEnv *env, jclass cls, jint pid)
{
if (kill((pid_t)pid, SIGQUIT)) {
JNU_ThrowIOExceptionWithLastError(env, "kill");
}
}
/*
* Class: sun_tools_attach_VirtualMachineImpl
* Method: checkPermissions
* Signature: (Ljava/lang/String;)V
*/
JNIEXPORT void JNICALL Java_sun_tools_attach_VirtualMachineImpl_checkPermissions
(JNIEnv *env, jclass cls, jstring path)
{
jboolean isCopy;
const char* p = GetStringPlatformChars(env, path, &isCopy);
if (p != NULL) {
struct stat sb;
uid_t uid, gid;
int res;
memset(&sb, 0, sizeof(struct stat));
/*
* Check that the path is owned by the effective uid/gid of this
* process. Also check that group/other access is not allowed.
*/
uid = geteuid();
gid = getegid();
res = stat(p, &sb);
if (res != 0) {
/* save errno */
res = errno;
}
if (res == 0) {
char msg[100];
jboolean isError = JNI_FALSE;
if (sb.st_uid != uid && uid != ROOT_UID) {
snprintf(msg, sizeof(msg),
"file should be owned by the current user (which is %d) but is owned by %d", uid, sb.st_uid);
isError = JNI_TRUE;
} else if (sb.st_gid != gid && uid != ROOT_UID) {
snprintf(msg, sizeof(msg),
"file's group should be the current group (which is %d) but the group is %d", gid, sb.st_gid);
isError = JNI_TRUE;
} else if ((sb.st_mode & (S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH)) != 0) {
snprintf(msg, sizeof(msg),
"file should only be readable and writable by the owner but has 0%03o access", sb.st_mode & 0777);
isError = JNI_TRUE;
}
if (isError) {
char buf[256];
snprintf(buf, sizeof(buf), "well-known file %s is not secure: %s", p, msg);
JNU_ThrowIOException(env, buf);
}
} else {
char* msg = strdup(strerror(res));
JNU_ThrowIOException(env, msg);
if (msg != NULL) {
free(msg);
}
}
if (isCopy) {
JNU_ReleaseStringPlatformChars(env, path, p);
}
}
}
/*
* Class: sun_tools_attach_VirtualMachineImpl
* Method: close
* Signature: (I)V
*/
JNIEXPORT void JNICALL Java_sun_tools_attach_VirtualMachineImpl_close
(JNIEnv *env, jclass cls, jint fd)
{
int res;
shutdown(fd, SHUT_RDWR);
RESTARTABLE(close(fd), res);
}
/*
* Class: sun_tools_attach_VirtualMachineImpl
* Method: read
* Signature: (I[BI)I
*/
JNIEXPORT jint JNICALL Java_sun_tools_attach_VirtualMachineImpl_read
(JNIEnv *env, jclass cls, jint fd, jbyteArray ba, jint off, jint baLen)
{
unsigned char buf[128];
size_t len = sizeof(buf);
ssize_t n;
size_t remaining = (size_t)(baLen - off);
if (len > remaining) {
len = remaining;
}
RESTARTABLE(read(fd, buf, len), n);
if (n == -1) {
JNU_ThrowIOExceptionWithLastError(env, "read");
} else {
if (n == 0) {
n = -1; // EOF
} else {
(*env)->SetByteArrayRegion(env, ba, off, (jint)n, (jbyte *)(buf));
}
}
return n;
}
/*
* Class: sun_tools_attach_VirtualMachineImpl
* Method: write
* Signature: (I[B)V
*/
JNIEXPORT void JNICALL Java_sun_tools_attach_VirtualMachineImpl_write
(JNIEnv *env, jclass cls, jint fd, jbyteArray ba, jint off, jint bufLen)
{
size_t remaining = bufLen;
do {
unsigned char buf[128];
size_t len = sizeof(buf);
int n;
if (len > remaining) {
len = remaining;
}
(*env)->GetByteArrayRegion(env, ba, off, len, (jbyte *)buf);
RESTARTABLE(write(fd, buf, len), n);
if (n > 0) {
off += n;
remaining -= n;
} else {
JNU_ThrowIOExceptionWithLastError(env, "write");
return;
}
} while (remaining > 0);
}

View file

@ -0,0 +1,76 @@
/*
* Copyright (c) 2005, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package sun.tools.attach;
import com.sun.tools.attach.VirtualMachine;
import com.sun.tools.attach.VirtualMachineDescriptor;
import com.sun.tools.attach.AttachNotSupportedException;
import java.io.IOException;
/*
* An AttachProvider implementation for Linux that uses a UNIX domain
* socket.
*/
public class AttachProviderImpl extends HotSpotAttachProvider {
public AttachProviderImpl() {
}
public String name() {
return "sun";
}
public String type() {
return "socket";
}
public VirtualMachine attachVirtualMachine(String vmid)
throws AttachNotSupportedException, IOException
{
// AttachNotSupportedException will be thrown if the target VM can be determined
// to be not attachable.
testAttachable(vmid);
return new VirtualMachineImpl(this, vmid);
}
public VirtualMachine attachVirtualMachine(VirtualMachineDescriptor vmd)
throws AttachNotSupportedException, IOException
{
if (vmd.provider() != this) {
throw new AttachNotSupportedException("provider mismatch");
}
// To avoid re-checking if the VM if attachable, we check if the descriptor
// is for a hotspot VM - these descriptors are created by the listVirtualMachines
// implementation which only returns a list of attachable VMs.
if (vmd instanceof HotSpotVirtualMachineDescriptor) {
assert ((HotSpotVirtualMachineDescriptor)vmd).isAttachable();
return new VirtualMachineImpl(this, vmd.id());
} else {
return attachVirtualMachine(vmd.id());
}
}
}

View file

@ -0,0 +1,435 @@
/*
* Copyright (c) 2005, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package sun.tools.attach;
import com.sun.tools.attach.AgentLoadException;
import com.sun.tools.attach.AttachNotSupportedException;
import com.sun.tools.attach.AttachOperationFailedException;
import com.sun.tools.attach.spi.AttachProvider;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Optional;
import java.util.regex.Pattern;
import static java.nio.charset.StandardCharsets.UTF_8;
import sun.jvmstat.monitor.MonitoredHost;
import sun.jvmstat.monitor.MonitorException;
/*
* Linux implementation of HotSpotVirtualMachine
*/
@SuppressWarnings("restricted")
public class VirtualMachineImpl extends HotSpotVirtualMachine {
// "/tmp" is used as a global well-known location for the files
// .java_pid<pid>. and .attach_pid<pid>. It is important that this
// location is the same for all processes, otherwise the tools
// will not be able to find all Hotspot processes.
// Any changes to this needs to be synchronized with HotSpot.
private static final Path TMPDIR = Path.of("/tmp");
private static final Path PROC = Path.of("/proc");
private static final Path STATUS = Path.of("status");
private static final Path ROOT_TMP = Path.of("root/tmp");
String socket_path;
private OperationProperties props = new OperationProperties(VERSION_1); // updated in ctor
/**
* Attaches to the target VM
*/
VirtualMachineImpl(AttachProvider provider, String vmid) throws AttachNotSupportedException, IOException
{
super(provider, vmid);
// This provider only understands pids
int pid = Integer.parseInt(vmid);
if (pid < 1) {
throw new AttachNotSupportedException("Invalid process identifier: " + vmid);
}
// Try to resolve to the "inner most" pid namespace
final long ns_pid = getNamespacePid(pid);
// Find the socket file. If not found then we attempt to start the
// attach mechanism in the target VM by sending it a QUIT signal.
// Then we attempt to find the socket file again.
final File socket_file = findSocketFile(pid, ns_pid);
socket_path = socket_file.getPath();
if (!socket_file.exists()) {
// Keep canonical version of File, to delete, in case target process ends and /proc link has gone:
File f = createAttachFile(pid, ns_pid).getCanonicalFile();
boolean timedout = false;
try {
checkCatchesAndSendQuitTo(pid, false);
// give the target VM time to start the attach mechanism
final int delay_step = 100;
final long timeout = attachTimeout();
long time_spent = 0;
long delay = 0;
do {
// Increase timeout on each attempt to reduce polling
delay += delay_step;
try {
Thread.sleep(delay);
} catch (InterruptedException x) { }
timedout = (time_spent += delay) > timeout;
if (time_spent > timeout/2 && !socket_file.exists()) {
// Send QUIT again to give target VM the last chance to react
checkCatchesAndSendQuitTo(pid, !timedout);
}
} while (!timedout && !socket_file.exists());
if (!socket_file.exists()) {
throw new AttachNotSupportedException(
String.format("Unable to open socket file %s: " +
"target process %d doesn't respond within %dms " +
"or HotSpot VM not loaded", socket_path, pid, time_spent));
}
} finally {
f.delete();
}
}
// Check that the file owner/permission to avoid attaching to
// bogus process
checkPermissions(socket_path);
if (isAPIv2Enabled()) {
props = getDefaultProps();
} else {
// Check that we can connect to the process
// - this ensures we throw the permission denied error now rather than
// later when we attempt to enqueue a command.
int s = socket();
try {
connect(s, socket_path);
} finally {
close(s);
}
}
}
/**
* Detach from the target VM
*/
public void detach() throws IOException {
synchronized (this) {
if (socket_path != null) {
socket_path = null;
}
}
}
/**
* Execute the given command in the target VM.
*/
InputStream execute(String cmd, Object ... args) throws AgentLoadException, IOException {
checkNulls(args);
// did we detach?
synchronized (this) {
if (socket_path == null) {
throw new IOException("Detached from target VM");
}
}
// create UNIX socket
int s = socket();
// connect to target VM
try {
connect(s, socket_path);
} catch (IOException x) {
close(s);
throw x;
}
IOException ioe = null;
// connected - write request
try {
SocketOutputStream writer = new SocketOutputStream(s);
writeCommand(writer, props, cmd, args);
} catch (IOException x) {
ioe = x;
}
// Create an input stream to read reply
SocketInputStreamImpl sis = new SocketInputStreamImpl(s);
// Process the command completion status
processCompletionStatus(ioe, cmd, sis);
// Return the input stream so that the command output can be read
return sis;
}
private static class SocketOutputStream implements AttachOutputStream {
private int fd;
public SocketOutputStream(int fd) {
this.fd = fd;
}
@Override
public void write(byte[] buffer, int offset, int length) throws IOException {
VirtualMachineImpl.write(fd, buffer, offset, length);
}
}
/*
* InputStream for the socket connection to get target VM
*/
private static class SocketInputStreamImpl extends SocketInputStream {
public SocketInputStreamImpl(long fd) {
super(fd);
}
@Override
protected int read(long fd, byte[] bs, int off, int len) throws IOException {
return VirtualMachineImpl.read((int)fd, bs, off, len);
}
@Override
protected void close(long fd) throws IOException {
VirtualMachineImpl.close((int)fd);
}
}
// Return the socket file for the given process.
private File findSocketFile(long pid, long ns_pid) throws AttachNotSupportedException, IOException {
return new File(findTargetProcessTmpDirectory(pid), ".java_pid" + ns_pid);
}
// On Linux a simple handshake is used to start the attach mechanism
// if not already started. The client creates a .attach_pid<pid> file in the
// target VM's working directory (or temp directory), and the SIGQUIT handler
// checks for the file.
private File createAttachFile(long pid, long ns_pid) throws AttachNotSupportedException, IOException {
Path fn = Path.of(".attach_pid" + ns_pid);
Path path = PROC.resolve(Path.of(Long.toString(pid), "cwd")).resolve(fn);
File f = new File(path.toString());
try {
// Do not canonicalize the file path, or we will fail to attach to a VM in a container.
f.createNewFile();
} catch (IOException _) {
f = new File(findTargetProcessTmpDirectory(pid), fn.toString());
f.createNewFile();
}
return f;
}
private String findTargetProcessTmpDirectory(long pid) throws IOException {
final var tmpOnProcPidRoot = PROC.resolve(Long.toString(pid)).resolve(ROOT_TMP);
/* We need to handle at least 4 different cases:
* 1. Caller and target processes share PID namespace and root
* filesystem (host to host or container to container with both /tmp
* mounted between containers).
* 2. Caller and target processes share PID namespace and root
* filesystem but the target process has elevated privileges
* (host to host).
* 3. Caller and target processes share PID namespace but NOT root
* filesystem (container to container).
* 4. Caller and target processes share neither PID namespace nor root
* filesystem (host to container)
*
* if target is elevated, we cant use /proc/<pid>/... so we have to
* fallback to /tmp, but that may not be shared with the target/attachee
* process, so we should check whether /tmp on both is same. This method
* would throw AttachOperationFailedException if they are different
* because we cannot make a connection with target VM.
*/
try {
if (Files.isWritable(tmpOnProcPidRoot)) {
return tmpOnProcPidRoot.toString();
} else if (Files.isSameFile(tmpOnProcPidRoot, TMPDIR)) {
return TMPDIR.toString();
} else {
throw new AttachOperationFailedException("Unable to access the filesystem of the target process");
}
} catch (IOException ioe) {
try {
boolean found = MonitoredHost.getMonitoredHost("//localhost")
.activeVms()
.stream()
.anyMatch(i -> pid == i.intValue());
if (found) {
// We can use /tmp because target process is on same host
// even if we cannot access /proc/<PID>/root.
// The process with capsh/setcap would fall this pattern.
return TMPDIR.toString();
}
// Throw original IOE if target process not found on localhost.
throw ioe;
} catch (URISyntaxException e) {
// URISyntaxException is defined as a checked exception at
// MonitoredHost.getMonitoredHost() if the URI string poorly
// formed. However "//localhost" is hard-coded at here, so the
// exception should not happen.
throw new AssertionError("Unexpected exception", e);
} catch (MonitorException e) {
// Other exceptions (happened at MonitoredHost) would be wrapped
// with AttachOperationFailedException.
throw new AttachOperationFailedException("Unable to find target proces", e);
}
}
}
// Return the inner most namespaced PID if there is one,
// otherwise return the original PID.
private long getNamespacePid(long pid) throws AttachNotSupportedException, IOException {
// Assuming a real procfs sits beneath, reading this doesn't block
// nor will it consume a lot of memory.
final var statusFile = PROC.resolve(Long.toString(pid)).resolve(STATUS).toString();
File f = new File(statusFile);
if (!f.exists()) {
return pid; // Likely a bad pid, but this is properly handled later.
}
Path statusPath = Paths.get(statusFile);
try {
for (String line : Files.readAllLines(statusPath)) {
String[] parts = line.split(":");
if (parts.length == 2 && parts[0].trim().equals("NSpid")) {
parts = parts[1].trim().split("\\s+");
// The last entry represents the PID the JVM "thinks" it is.
// Even in non-namespaced pids these entries should be
// valid. You could refer to it as the inner most pid.
return Long.parseLong(parts[parts.length - 1]);
}
}
// Old kernels may not have NSpid field (i.e. 3.10).
// Fallback to original pid in the event we cannot deduce.
return pid;
} catch (NumberFormatException | IOException x) {
throw new AttachNotSupportedException("Unable to parse namespace");
}
}
private static final String FIELD = "field";
private static final String MASK = "mask";
private static final Pattern SIGNAL_MASK_PATTERN = Pattern.compile("(?<" + FIELD + ">Sig\\p{Alpha}{3}):\\s+(?<" + MASK + ">\\p{XDigit}{16}).*");
private static final long SIGQUIT = 0b100; // mask bit for SIGQUIT
private static boolean checkCatchesAndSendQuitTo(int pid, boolean throwIfNotReady) throws AttachNotSupportedException, IOException {
var quitIgn = false;
var quitBlk = false;
var quitCgt = false;
final var procPid = PROC.resolve(Integer.toString(pid));
var readBlk = false;
var readIgn = false;
var readCgt = false;
if (!Files.exists(procPid)) throw new IOException("non existent JVM pid: " + pid);
for (var line : Files.readAllLines(procPid.resolve("status"))) {
if (!line.startsWith("Sig")) continue; // to speed things up ... avoids the matcher/RE invocation...
final var m = SIGNAL_MASK_PATTERN.matcher(line);
if (!m.matches()) continue;
var sigmask = m.group(MASK);
final var slen = sigmask.length();
sigmask = sigmask.substring(slen / 2 , slen); // only really interested in the non r/t signals ...
final var sigquit = (Long.valueOf(sigmask, 16) & SIGQUIT) != 0L;
switch (m.group(FIELD)) {
case "SigBlk": { quitBlk = sigquit; readBlk = true; break; }
case "SigIgn": { quitIgn = sigquit; readIgn = true; break; }
case "SigCgt": { quitCgt = sigquit; readCgt = true; break; }
}
if (readBlk && readIgn && readCgt) break;
}
final boolean okToSendQuit = (!quitIgn && quitCgt); // ignore blocked as it may be temporary ...
if (okToSendQuit) {
sendQuitTo(pid);
} else if (throwIfNotReady) {
Optional<String> cmdline = Optional.empty();
try (final var clf = Files.lines(procPid.resolve("cmdline"))) {
cmdline = clf.findFirst();
}
var cmd = "null"; // default
if (cmdline.isPresent()) {
cmd = cmdline.get();
cmd = cmd.substring(0, cmd.length() - 1); // remove trailing \0
}
throw new AttachNotSupportedException("pid: " + pid + " cmd: '" + cmd + "' state is not ready to participate in attach handshake!");
}
return okToSendQuit;
}
//-- native methods
static native void sendQuitTo(int pid) throws IOException;
static native void checkPermissions(String path) throws IOException;
static native int socket() throws IOException;
static native void connect(int fd, String path) throws IOException;
static native void close(int fd) throws IOException;
static native int read(int fd, byte buf[], int off, int bufLen) throws IOException;
static native void write(int fd, byte buf[], int off, int bufLen) throws IOException;
static {
System.loadLibrary("attach");
}
}

View file

@ -0,0 +1,258 @@
/*
* Copyright (c) 2005, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
#include "jni_util.h"
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/un.h>
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "sun_tools_attach_VirtualMachineImpl.h"
#define ROOT_UID 0
/*
* Declare library specific JNI_Onload entry if static build
*/
DEF_STATIC_JNI_OnLoad
/*
* Class: sun_tools_attach_VirtualMachineImpl
* Method: socket
* Signature: ()I
*/
JNIEXPORT jint JNICALL Java_sun_tools_attach_VirtualMachineImpl_socket
(JNIEnv *env, jclass cls)
{
int fd = socket(PF_UNIX, SOCK_STREAM, 0);
if (fd == -1) {
JNU_ThrowIOExceptionWithLastError(env, "socket");
}
return (jint)fd;
}
/*
* Class: sun_tools_attach_VirtualMachineImpl
* Method: connect
* Signature: (ILjava/lang/String;)I
*/
JNIEXPORT void JNICALL Java_sun_tools_attach_VirtualMachineImpl_connect
(JNIEnv *env, jclass cls, jint fd, jstring path)
{
jboolean isCopy;
const char* p = GetStringPlatformChars(env, path, &isCopy);
if (p != NULL) {
struct sockaddr_un addr;
int err = 0;
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
/* strncpy is safe because addr.sun_path was zero-initialized before. */
strncpy(addr.sun_path, p, sizeof(addr.sun_path) - 1);
if (connect(fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
err = errno;
}
if (isCopy) {
JNU_ReleaseStringPlatformChars(env, path, p);
}
/*
* If the connect failed then we throw the appropriate exception
* here (can't throw it before releasing the string as can't call
* JNI with pending exception)
*/
if (err != 0) {
if (err == ENOENT) {
JNU_ThrowByName(env, "java/io/FileNotFoundException", NULL);
} else {
char* msg = strdup(strerror(err));
JNU_ThrowIOException(env, msg);
if (msg != NULL) {
free(msg);
}
}
}
}
}
/*
* Class: sun_tools_attach_VirtualMachineImpl
* Method: sendQuitTo
* Signature: (I)V
*/
JNIEXPORT void JNICALL Java_sun_tools_attach_VirtualMachineImpl_sendQuitTo
(JNIEnv *env, jclass cls, jint pid)
{
if (kill((pid_t)pid, SIGQUIT)) {
JNU_ThrowIOExceptionWithLastError(env, "kill");
}
}
/*
* Class: sun_tools_attach_VirtualMachineImpl
* Method: checkPermissions
* Signature: (Ljava/lang/String;)V
*/
JNIEXPORT void JNICALL Java_sun_tools_attach_VirtualMachineImpl_checkPermissions
(JNIEnv *env, jclass cls, jstring path)
{
jboolean isCopy;
const char* p = GetStringPlatformChars(env, path, &isCopy);
if (p != NULL) {
struct stat sb;
uid_t uid, gid;
int res;
memset(&sb, 0, sizeof(struct stat));
/*
* Check that the path is owned by the effective uid/gid of this
* process. Also check that group/other access is not allowed.
*/
uid = geteuid();
gid = getegid();
res = stat(p, &sb);
if (res != 0) {
/* save errno */
res = errno;
}
if (res == 0) {
char msg[100];
jboolean isError = JNI_FALSE;
if (sb.st_uid != uid && uid != ROOT_UID) {
snprintf(msg, sizeof(msg),
"file should be owned by the current user (which is %d) but is owned by %d", uid, sb.st_uid);
isError = JNI_TRUE;
} else if (sb.st_gid != gid && uid != ROOT_UID) {
snprintf(msg, sizeof(msg),
"file's group should be the current group (which is %d) but the group is %d", gid, sb.st_gid);
isError = JNI_TRUE;
} else if ((sb.st_mode & (S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH)) != 0) {
snprintf(msg, sizeof(msg),
"file should only be readable and writable by the owner but has 0%03o access", sb.st_mode & 0777);
isError = JNI_TRUE;
}
if (isError) {
char buf[256];
snprintf(buf, sizeof(buf), "well-known file %s is not secure: %s", p, msg);
JNU_ThrowIOException(env, buf);
}
} else {
char* msg = strdup(strerror(res));
JNU_ThrowIOException(env, msg);
if (msg != NULL) {
free(msg);
}
}
if (isCopy) {
JNU_ReleaseStringPlatformChars(env, path, p);
}
}
}
/*
* Class: sun_tools_attach_VirtualMachineImpl
* Method: close
* Signature: (I)V
*/
JNIEXPORT void JNICALL Java_sun_tools_attach_VirtualMachineImpl_close
(JNIEnv *env, jclass cls, jint fd)
{
shutdown(fd, SHUT_RDWR);
close(fd);
}
/*
* Class: sun_tools_attach_VirtualMachineImpl
* Method: read
* Signature: (I[BI)I
*/
JNIEXPORT jint JNICALL Java_sun_tools_attach_VirtualMachineImpl_read
(JNIEnv *env, jclass cls, jint fd, jbyteArray ba, jint off, jint baLen)
{
unsigned char buf[128];
size_t len = sizeof(buf);
ssize_t n;
size_t remaining = (size_t)(baLen - off);
if (len > remaining) {
len = remaining;
}
RESTARTABLE(read(fd, buf, len), n);
if (n == -1) {
JNU_ThrowIOExceptionWithLastError(env, "read");
} else {
if (n == 0) {
n = -1; // EOF
} else {
(*env)->SetByteArrayRegion(env, ba, off, (jint)n, (jbyte *)(buf));
}
}
return n;
}
/*
* Class: sun_tools_attach_VirtualMachineImpl
* Method: write
* Signature: (I[B)V
*/
JNIEXPORT void JNICALL Java_sun_tools_attach_VirtualMachineImpl_write
(JNIEnv *env, jclass cls, jint fd, jbyteArray ba, jint off, jint bufLen)
{
size_t remaining = bufLen;
do {
unsigned char buf[128];
size_t len = sizeof(buf);
int n;
if (len > remaining) {
len = remaining;
}
(*env)->GetByteArrayRegion(env, ba, off, len, (jbyte *)buf);
RESTARTABLE(write(fd, buf, len), n);
if (n > 0) {
off += n;
remaining -= n;
} else {
JNU_ThrowIOExceptionWithLastError(env, "write");
return;
}
} while (remaining > 0);
}

View file

@ -0,0 +1,76 @@
/*
* Copyright (c) 2005, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package sun.tools.attach;
import com.sun.tools.attach.VirtualMachine;
import com.sun.tools.attach.VirtualMachineDescriptor;
import com.sun.tools.attach.AttachNotSupportedException;
import java.io.IOException;
/*
* An AttachProvider implementation for Bsd that uses a UNIX domain
* socket.
*/
public class AttachProviderImpl extends HotSpotAttachProvider {
public AttachProviderImpl() {
}
public String name() {
return "sun";
}
public String type() {
return "socket";
}
public VirtualMachine attachVirtualMachine(String vmid)
throws AttachNotSupportedException, IOException
{
// AttachNotSupportedException will be thrown if the target VM can be determined
// to be not attachable.
testAttachable(vmid);
return new VirtualMachineImpl(this, vmid);
}
public VirtualMachine attachVirtualMachine(VirtualMachineDescriptor vmd)
throws AttachNotSupportedException, IOException
{
if (vmd.provider() != this) {
throw new AttachNotSupportedException("provider mismatch");
}
// To avoid re-checking if the VM if attachable, we check if the descriptor
// is for a hotspot VM - these descriptors are created by the listVirtualMachines
// implementation which only returns a list of attachable VMs.
if (vmd instanceof HotSpotVirtualMachineDescriptor) {
assert ((HotSpotVirtualMachineDescriptor)vmd).isAttachable();
return new VirtualMachineImpl(this, vmd.id());
} else {
return attachVirtualMachine(vmd.id());
}
}
}

View file

@ -0,0 +1,272 @@
/*
* Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package sun.tools.attach;
import com.sun.tools.attach.AgentLoadException;
import com.sun.tools.attach.AttachNotSupportedException;
import com.sun.tools.attach.spi.AttachProvider;
import sun.jvmstat.PlatformSupport;
import java.io.InputStream;
import java.io.IOException;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import static java.nio.charset.StandardCharsets.UTF_8;
/*
* Bsd implementation of HotSpotVirtualMachine
*/
@SuppressWarnings("restricted")
public class VirtualMachineImpl extends HotSpotVirtualMachine {
/**
* HotSpot PerfData file prefix
*/
private static final String HSPERFDATA_PREFIX = "hsperfdata_";
/**
* Use platform specific methods for looking up temporary directories.
*/
private static final PlatformSupport platformSupport = PlatformSupport.getInstance();
String socket_path;
private OperationProperties props = new OperationProperties(VERSION_1); // updated in ctor
/**
* Attaches to the target VM
*/
VirtualMachineImpl(AttachProvider provider, String vmid)
throws AttachNotSupportedException, IOException
{
super(provider, vmid);
// This provider only understands pids
int pid = Integer.parseInt(vmid);
if (pid < 1) {
throw new AttachNotSupportedException("Invalid process identifier: " + vmid);
}
// Find the socket file. If not found then we attempt to start the
// attach mechanism in the target VM by sending it a QUIT signal.
// Then we attempt to find the socket file again.
// In macOS the socket file is located in per-user temp directory.
String tempdir = getTempDirFromPid(pid);
File socket_file = new File(tempdir, ".java_pid" + pid);
socket_path = socket_file.getPath();
if (!socket_file.exists()) {
File f = createAttachFile(tempdir, pid);
try {
checkCatchesAndSendQuitTo(pid, false);
// give the target VM time to start the attach mechanism
final int delay_step = 100;
final long timeout = attachTimeout();
long time_spent = 0;
long delay = 0;
boolean timedout = false;
do {
// Increase timeout on each attempt to reduce polling
delay += delay_step;
try {
Thread.sleep(delay);
} catch (InterruptedException x) { }
timedout = (time_spent += delay) > timeout;
if (time_spent > timeout/2 && !socket_file.exists()) {
// Send QUIT again to give target VM the last chance to react
checkCatchesAndSendQuitTo(pid, !timedout);
}
} while (!timedout && !socket_file.exists());
if (!socket_file.exists()) {
throw new AttachNotSupportedException(
String.format("Unable to open socket file %s: " +
"target process %d doesn't respond within %dms " +
"or HotSpot VM not loaded", socket_path,
pid, time_spent));
}
} finally {
f.delete();
}
}
// Check that the file owner/permission to avoid attaching to
// bogus process
checkPermissions(socket_path);
if (isAPIv2Enabled()) {
props = getDefaultProps();
} else {
// Check that we can connect to the process
// - this ensures we throw the permission denied error now rather than
// later when we attempt to enqueue a command.
int s = socket();
try {
connect(s, socket_path);
} finally {
close(s);
}
}
}
/**
* Detach from the target VM
*/
public void detach() throws IOException {
synchronized (this) {
if (socket_path != null) {
socket_path = null;
}
}
}
/**
* Execute the given command in the target VM.
*/
InputStream execute(String cmd, Object ... args) throws AgentLoadException, IOException {
checkNulls(args);
// did we detach?
synchronized (this) {
if (socket_path == null) {
throw new IOException("Detached from target VM");
}
}
// create UNIX socket
int s = socket();
// connect to target VM
try {
connect(s, socket_path);
} catch (IOException x) {
close(s);
throw x;
}
IOException ioe = null;
// connected - write request
try {
SocketOutputStream writer = new SocketOutputStream(s);
writeCommand(writer, props, cmd, args);
} catch (IOException x) {
ioe = x;
}
// Create an input stream to read reply
SocketInputStreamImpl sis = new SocketInputStreamImpl(s);
// Process the command completion status
processCompletionStatus(ioe, cmd, sis);
// Return the input stream so that the command output can be read
return sis;
}
private static class SocketOutputStream implements AttachOutputStream {
private int fd;
public SocketOutputStream(int fd) {
this.fd = fd;
}
@Override
public void write(byte[] buffer, int offset, int length) throws IOException {
VirtualMachineImpl.write(fd, buffer, offset, length);
}
}
/*
* InputStream for the socket connection to get target VM
*/
private static class SocketInputStreamImpl extends SocketInputStream {
public SocketInputStreamImpl(long fd) {
super(fd);
}
@Override
protected int read(long fd, byte[] bs, int off, int len) throws IOException {
return VirtualMachineImpl.read((int)fd, bs, off, len);
}
@Override
protected void close(long fd) throws IOException {
VirtualMachineImpl.close((int)fd);
}
}
private File createAttachFile(String tmpdir, int pid) throws IOException {
File f = new File(tmpdir, ".attach_pid" + pid);
createAttachFile0(f.getPath());
return f;
}
/*
* Returns a platform-specific temporary directory for a given process.
* In VMs running as unprivileged user it returns the default platform-specific
* temporary directory. In VMs running as root it searches over the list of
* temporary directories for one containing HotSpot PerfData directory.
*/
private String getTempDirFromPid(int pid) {
ProcessHandle ph = ProcessHandle.of(pid).orElse(null);
if (ph != null) {
String user = ph.info().user().orElse(null);
if (user != null) {
for (String dir : platformSupport.getTemporaryDirectories(pid)) {
Path fullPath = Path.of(dir, HSPERFDATA_PREFIX + user, String.valueOf(pid));
if (Files.exists(fullPath)) {
return dir;
}
}
}
}
return PlatformSupport.getTemporaryDirectory();
}
//-- native methods
static native boolean checkCatchesAndSendQuitTo(int pid, boolean throwIfNotReady) throws IOException, AttachNotSupportedException;
static native void checkPermissions(String path) throws IOException;
static native int socket() throws IOException;
static native void connect(int fd, String path) throws IOException;
static native void close(int fd) throws IOException;
static native int read(int fd, byte buf[], int off, int bufLen) throws IOException;
static native void write(int fd, byte buf[], int off, int bufLen) throws IOException;
static native void createAttachFile0(String path);
static {
System.loadLibrary("attach");
}
}

View file

@ -0,0 +1,337 @@
/*
* Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
#include "jni_util.h"
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/syslimits.h>
#include <sys/sysctl.h>
#include <sys/types.h>
#include <sys/un.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "sun_tools_attach_VirtualMachineImpl.h"
#define ROOT_UID 0
/*
* Declare library specific JNI_Onload entry if static build
*/
DEF_STATIC_JNI_OnLoad
/*
* Class: sun_tools_attach_VirtualMachineImpl
* Method: socket
* Signature: ()I
*/
JNIEXPORT jint JNICALL Java_sun_tools_attach_VirtualMachineImpl_socket
(JNIEnv *env, jclass cls)
{
int fd = socket(PF_UNIX, SOCK_STREAM, 0);
if (fd == -1) {
JNU_ThrowIOExceptionWithLastError(env, "socket");
}
return (jint)fd;
}
/*
* Class: sun_tools_attach_VirtualMachineImpl
* Method: connect
* Signature: (ILjava/lang/String;)I
*/
JNIEXPORT void JNICALL Java_sun_tools_attach_VirtualMachineImpl_connect
(JNIEnv *env, jclass cls, jint fd, jstring path)
{
jboolean isCopy;
const char* p = GetStringPlatformChars(env, path, &isCopy);
if (p != NULL) {
struct sockaddr_un addr;
int err = 0;
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
/* strncpy is safe because addr.sun_path was zero-initialized before. */
strncpy(addr.sun_path, p, sizeof(addr.sun_path) - 1);
if (connect(fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
err = errno;
}
if (isCopy) {
JNU_ReleaseStringPlatformChars(env, path, p);
}
/*
* If the connect failed then we throw the appropriate exception
* here (can't throw it before releasing the string as can't call
* JNI with pending exception)
*/
if (err != 0) {
if (err == ENOENT) {
JNU_ThrowByName(env, "java/io/FileNotFoundException", NULL);
} else {
char* msg = strdup(strerror(err));
JNU_ThrowIOException(env, msg);
if (msg != NULL) {
free(msg);
}
}
}
}
}
/*
* Class: sun_tools_attach_VirtualMachineImpl
* Method: checkCatchesAndSendQuitTo
* Signature: (I)V
*/
JNIEXPORT jboolean JNICALL Java_sun_tools_attach_VirtualMachineImpl_checkCatchesAndSendQuitTo
(JNIEnv *env, jclass cls, jint pid, jboolean throwIfNotReady)
{
int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, (int)pid };
struct kinfo_proc kiproc;
size_t kipsz = sizeof(struct kinfo_proc);
/*
* Early in the lifetime of a JVM it has not yet initialized its signal handlers, in particular the QUIT
* handler, note that the default behavior of QUIT is to terminate the receiving process, if unhandled.
*
* Since we use QUIT to initiate an attach operation, if we signal a JVM during this period early in its
* lifetime before it has initialized its QUIT handler, such a signal delivery will terminate the JVM we
* are attempting to attach to!
*
* The following code guards the QUIT delivery by testing the current signal masks. It is okay to send QUIT
* if the signal is caught but not ignored, as that implies a handler has been installed.
*/
if (sysctl(mib, sizeof(mib) / sizeof(int), &kiproc, &kipsz, NULL, 0) == 0) {
const bool ignored = (kiproc.kp_proc.p_sigignore & sigmask(SIGQUIT)) != 0;
const bool caught = (kiproc.kp_proc.p_sigcatch & sigmask(SIGQUIT)) != 0;
// note: obviously the masks could change between testing and signalling however this is not the
// observed behavior of the current JVM implementation.
if (caught && !ignored) {
if (kill((pid_t)pid, SIGQUIT) != 0) {
JNU_ThrowIOExceptionWithLastError(env, "kill");
} else {
return JNI_TRUE;
}
} else if (throwIfNotReady) {
char msg[100];
snprintf(msg, sizeof(msg), "pid: %d, state is not ready to participate in attach handshake!", (int)pid);
JNU_ThrowByName(env, "com/sun/tools/attach/AttachNotSupportedException", msg);
}
} else {
JNU_ThrowIOExceptionWithLastError(env, "sysctl");
}
return JNI_FALSE;
}
/*
* Class: sun_tools_attach_VirtualMachineImpl
* Method: checkPermissions
* Signature: (Ljava/lang/String;)V
*/
JNIEXPORT void JNICALL Java_sun_tools_attach_VirtualMachineImpl_checkPermissions
(JNIEnv *env, jclass cls, jstring path)
{
jboolean isCopy;
const char* p = GetStringPlatformChars(env, path, &isCopy);
if (p != NULL) {
struct stat sb;
uid_t uid, gid;
int res;
memset(&sb, 0, sizeof(struct stat));
/*
* Check that the path is owned by the effective uid/gid of this
* process. Also check that group/other access is not allowed.
*/
uid = geteuid();
gid = getegid();
res = stat(p, &sb);
if (res != 0) {
/* save errno */
res = errno;
}
if (res == 0) {
char msg[100];
jboolean isError = JNI_FALSE;
if (sb.st_uid != uid && uid != ROOT_UID) {
snprintf(msg, sizeof(msg),
"file should be owned by the current user (which is %d) but is owned by %d", uid, sb.st_uid);
isError = JNI_TRUE;
} else if (sb.st_gid != gid && uid != ROOT_UID) {
snprintf(msg, sizeof(msg),
"file's group should be the current group (which is %d) but the group is %d", gid, sb.st_gid);
isError = JNI_TRUE;
} else if ((sb.st_mode & (S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH)) != 0) {
snprintf(msg, sizeof(msg),
"file should only be readable and writable by the owner but has 0%03o access", sb.st_mode & 0777);
isError = JNI_TRUE;
}
if (isError) {
char buf[256];
snprintf(buf, sizeof(buf), "well-known file %s is not secure: %s", p, msg);
JNU_ThrowIOException(env, buf);
}
} else {
char* msg = strdup(strerror(res));
JNU_ThrowIOException(env, msg);
if (msg != NULL) {
free(msg);
}
}
if (isCopy) {
JNU_ReleaseStringPlatformChars(env, path, p);
}
}
}
/*
* Class: sun_tools_attach_VirtualMachineImpl
* Method: close
* Signature: (I)V
*/
JNIEXPORT void JNICALL Java_sun_tools_attach_VirtualMachineImpl_close
(JNIEnv *env, jclass cls, jint fd)
{
shutdown(fd, SHUT_RDWR);
close(fd);
}
/*
* Class: sun_tools_attach_VirtualMachineImpl
* Method: read
* Signature: (I[BI)I
*/
JNIEXPORT jint JNICALL Java_sun_tools_attach_VirtualMachineImpl_read
(JNIEnv *env, jclass cls, jint fd, jbyteArray ba, jint off, jint baLen)
{
unsigned char buf[128];
size_t len = sizeof(buf);
ssize_t n;
size_t remaining = (size_t)(baLen - off);
if (len > remaining) {
len = remaining;
}
RESTARTABLE(read(fd, buf, len), n);
if (n == -1) {
JNU_ThrowIOExceptionWithLastError(env, "read");
} else {
if (n == 0) {
n = -1; // EOF
} else {
(*env)->SetByteArrayRegion(env, ba, off, (jint)n, (jbyte *)(buf));
}
}
return n;
}
/*
* Class: sun_tools_attach_VirtualMachineImpl
* Method: write
* Signature: (I[B)V
*/
JNIEXPORT void JNICALL Java_sun_tools_attach_VirtualMachineImpl_write
(JNIEnv *env, jclass cls, jint fd, jbyteArray ba, jint off, jint bufLen)
{
size_t remaining = bufLen;
do {
unsigned char buf[128];
size_t len = sizeof(buf);
int n;
if (len > remaining) {
len = remaining;
}
(*env)->GetByteArrayRegion(env, ba, off, len, (jbyte *)buf);
RESTARTABLE(write(fd, buf, len), n);
if (n > 0) {
off += n;
remaining -= n;
} else {
JNU_ThrowIOExceptionWithLastError(env, "write");
return;
}
} while (remaining > 0);
}
/*
* Class: sun_tools_attach_BSDVirtualMachine
* Method: createAttachFile
* Signature: (Ljava.lang.String;)V
*/
JNIEXPORT void JNICALL Java_sun_tools_attach_VirtualMachineImpl_createAttachFile0(JNIEnv *env, jclass cls, jstring path)
{
const char* _path;
jboolean isCopy;
int fd, rc;
_path = GetStringPlatformChars(env, path, &isCopy);
if (_path == NULL) {
JNU_ThrowIOException(env, "Must specify a path");
return;
}
RESTARTABLE(open(_path, O_CREAT | O_EXCL, S_IWUSR | S_IRUSR), fd);
if (fd == -1) {
/* release p here before we throw an I/O exception */
if (isCopy) {
JNU_ReleaseStringPlatformChars(env, path, _path);
}
JNU_ThrowIOExceptionWithLastError(env, "open");
return;
}
RESTARTABLE(chown(_path, geteuid(), getegid()), rc);
close(fd);
/* release p here */
if (isCopy) {
JNU_ReleaseStringPlatformChars(env, path, _path);
}
}

View file

@ -0,0 +1,97 @@
/*
* Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.tools.attach;
/**
* The exception thrown when an agent fails to initialize in the target
* Java virtual machine.
*
* <p> This exception is thrown by
* {@link com.sun.tools.attach.VirtualMachine#loadAgent VirtualMachine.loadAgent},
* {@link com.sun.tools.attach.VirtualMachine#loadAgentLibrary
* VirtualMachine.loadAgentLibrary},
* {@link com.sun.tools.attach.VirtualMachine#loadAgentPath VirtualMachine.loadAgentPath}
* methods if an agent, or agent library, cannot be initialized.
* When thrown by {@code VirtualMachine.loadAgentLibrary}, or
* {@code VirtualMachine.loadAgentPath} then the exception encapsulates
* the error returned by the agent's {@code Agent_OnAttach} function.
* This error code can be obtained by invoking the {@link #returnValue() returnValue} method.
*
* @since 1.6
*/
public class AgentInitializationException extends Exception {
/** use serialVersionUID for interoperability */
static final long serialVersionUID = -1508756333332806353L;
/** @serial */
private int returnValue;
/**
* Constructs an {@code AgentInitializationException} with
* no detail message.
*/
public AgentInitializationException() {
super();
this.returnValue = 0;
}
/**
* Constructs an {@code AgentInitializationException} with
* the specified detail message.
*
* @param s the detail message.
*/
public AgentInitializationException(String s) {
super(s);
this.returnValue = 0;
}
/**
* Constructs an {@code AgentInitializationException} with
* the specified detail message and the return value from the
* execution of the agent's {@code Agent_OnAttach} function.
*
* @param s the detail message.
* @param returnValue the return value
*/
public AgentInitializationException(String s, int returnValue) {
super(s);
this.returnValue = returnValue;
}
/**
* If the exception was created with the return value from the agent
* {@code Agent_OnAttach} function then this returns that value,
* otherwise returns {@code 0}.
*
* @return the return value
*/
public int returnValue() {
return returnValue;
}
}

View file

@ -0,0 +1,64 @@
/*
* Copyright (c) 2005, 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.tools.attach;
/**
* The exception thrown when an agent cannot be loaded into the target
* Java virtual machine.
*
* <p> This exception is thrown by {@link
* com.sun.tools.attach.VirtualMachine#loadAgent VirtualMachine.loadAgent} or
* {@link com.sun.tools.attach.VirtualMachine#loadAgentLibrary
* VirtualMachine.loadAgentLibrary}, {@link
* com.sun.tools.attach.VirtualMachine#loadAgentPath loadAgentPath} methods
* if the agent, or agent library, cannot be loaded.
*
* @since 1.6
*/
public class AgentLoadException extends Exception {
/** use serialVersionUID for interoperability */
static final long serialVersionUID = 688047862952114238L;
/**
* Constructs an <code>AgentLoadException</code> with
* no detail message.
*/
public AgentLoadException() {
super();
}
/**
* Constructs an <code>AgentLoadException</code> with
* the specified detail message.
*
* @param s the detail message.
*/
public AgentLoadException(String s) {
super(s);
}
}

View file

@ -0,0 +1,65 @@
/*
* Copyright (c) 2005, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.tools.attach;
import com.sun.tools.attach.spi.AttachProvider; // for javadoc
/**
* Thrown by {@link com.sun.tools.attach.VirtualMachine#attach
* VirtualMachine.attach} when attempting to attach to a Java virtual machine
* for which a compatible {@link com.sun.tools.attach.spi.AttachProvider
* AttachProvider} does not exist. It is also thrown by {@link
* com.sun.tools.attach.spi.AttachProvider#attachVirtualMachine
* AttachProvider.attachVirtualMachine} if the provider attempts to
* attach to a Java virtual machine with which it not comptatible.
*
* @since 1.6
*/
public class AttachNotSupportedException extends Exception {
/** use serialVersionUID for interoperability */
static final long serialVersionUID = 3391824968260177264L;
/**
* Constructs an <code>AttachNotSupportedException</code> with
* no detail message.
*/
public AttachNotSupportedException() {
super();
}
/**
* Constructs an <code>AttachNotSupportedException</code> with
* the specified detail message.
*
* @param s the detail message.
*/
public AttachNotSupportedException(String s) {
super(s);
}
}

View file

@ -0,0 +1,67 @@
/*
* Copyright (c) 2014, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.tools.attach;
import java.io.IOException;
/**
* Exception type to signal that an attach operation failed in the target VM.
*
* <p> This exception can be thrown by the various operations of
* {@link com.sun.tools.attach.VirtualMachine} when the operation
* fails in the target VM. If there is a communication error,
* a regular IOException will be thrown.
*
* @since 9
*/
public class AttachOperationFailedException extends IOException {
private static final long serialVersionUID = 2140308168167478043L;
/**
* Constructs an <code>AttachOperationFailedException</code> with
* the specified detail message.
*
* @param message the detail message.
*/
public AttachOperationFailedException(String message) {
super(message);
}
/**
* Constructs an <code>AttachOperationFailedException</code> with
* the specified cause.
*
* @param message the detail message.
* @param cause the cause of this exception.
*
* @since 27
*/
public AttachOperationFailedException(String message, Throwable cause) {
super(message, cause);
}
}

View file

@ -0,0 +1,89 @@
/*
* Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.tools.attach;
/**
* An <code>AttachPermission</code> object contains a name (also referred
* to as a "target name") but no actions list; you either have the
* named permission or you don't.
*
* @apiNote
* This permission cannot be used for controlling access to resources
* as the Security Manager is no longer supported.
* Consequently this class is deprecated for removal in a future release.
*
* @see com.sun.tools.attach.VirtualMachine
* @see com.sun.tools.attach.spi.AttachProvider
* @since 1.6
*
* @deprecated This class was only useful in conjunction with the Security Manager,
* which is no longer supported. There is no replacement for this class.
*/
@Deprecated(since="25", forRemoval=true)
public final class AttachPermission extends java.security.BasicPermission {
/** use serialVersionUID for interoperability */
static final long serialVersionUID = -4619447669752976181L;
/**
* Constructs a new AttachPermission object.
*
* @param name Permission name. Must be either "attachVirtualMachine",
* or "createAttachProvider".
*
* @throws NullPointerException if name is <code>null</code>.
* @throws IllegalArgumentException if the name is invalid.
*/
public AttachPermission(String name) {
super(name);
if (!name.equals("attachVirtualMachine") && !name.equals("createAttachProvider")) {
throw new IllegalArgumentException("name: " + name);
}
}
/**
* Constructs a new AttachPermission object.
*
* @param name Permission name. Must be either "attachVirtualMachine",
* or "createAttachProvider".
*
* @param actions Not used and should be <code>null</code>, or
* the empty string.
*
* @throws NullPointerException if name is <code>null</code>.
* @throws IllegalArgumentException if arguments are invalid.
*/
public AttachPermission(String name, String actions) {
super(name);
if (!name.equals("attachVirtualMachine") && !name.equals("createAttachProvider")) {
throw new IllegalArgumentException("name: " + name);
}
if (actions != null && actions.length() > 0) {
throw new IllegalArgumentException("actions: " + actions);
}
}
}

View file

@ -0,0 +1,709 @@
/*
* Copyright (c) 2005, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.tools.attach;
import com.sun.tools.attach.spi.AttachProvider;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.io.IOException;
/**
* A Java virtual machine.
*
* <p> A {@code VirtualMachine} represents a Java virtual machine to which this
* Java virtual machine has attached. The Java virtual machine to which it is
* attached is sometimes called the <i>target virtual machine</i>, or <i>target VM</i>.
* An application (typically a tool such as a managemet console or profiler) uses a
* VirtualMachine to load an agent into the target VM. For example, a profiler tool
* written in the Java Language might attach to a running application and load its
* profiler agent to profile the running application. </p>
*
* <p> A VirtualMachine is obtained by invoking the {@link #attach(String) attach} method
* with an identifier that identifies the target virtual machine. The identifier is
* implementation-dependent but is typically the process identifier (or pid) in
* environments where each Java virtual machine runs in its own operating system process.
* Alternatively, a {@code VirtualMachine} instance is obtained by invoking the
* {@link #attach(VirtualMachineDescriptor) attach} method with a {@link
* com.sun.tools.attach.VirtualMachineDescriptor VirtualMachineDescriptor} obtained
* from the list of virtual machine descriptors returned by the {@link #list list} method.
* Once a reference to a virtual machine is obtained, the {@link #loadAgent loadAgent},
* {@link #loadAgentLibrary loadAgentLibrary}, and {@link #loadAgentPath loadAgentPath}
* methods are used to load agents into target virtual machine. The {@link
* #loadAgent loadAgent} method is used to load agents that are written in the Java
* Language and deployed in a {@link java.util.jar.JarFile JAR file}. (See
* {@link java.lang.instrument} for a detailed description on how these agents
* are loaded and started). The {@link #loadAgentLibrary loadAgentLibrary} and
* {@link #loadAgentPath loadAgentPath} methods are used to load agents that
* are deployed either in a dynamic library or statically linked into the VM and
* make use of the <a href="{@docRoot}/../specs/jvmti.html">JVM Tools Interface</a>.
* </p>
*
* <p> In addition to loading agents a VirtualMachine provides read access to the
* {@link java.lang.System#getProperties() system properties} in the target VM.
* This can be useful in some environments where properties such as
* {@code java.home}, {@code os.name}, or {@code os.arch} are
* used to construct the path to agent that will be loaded into the target VM.
*
* <p> The following example demonstrates how VirtualMachine may be used:</p>
*
* <pre>
*
* // attach to target VM
* VirtualMachine vm = VirtualMachine.attach("2177");
*
* // start management agent
* Properties props = new Properties();
* props.put("com.sun.management.jmxremote.port", "5000");
* vm.startManagementAgent(props);
*
* // detach
* vm.detach();
*
* </pre>
*
* <p> In this example we attach to a Java virtual machine that is identified by
* the process identifier {@code 2177}. Then the JMX management agent is
* started in the target process using the supplied arguments. Finally, the
* client detaches from the target VM. </p>
*
* <p> A VirtualMachine is safe for use by multiple concurrent threads. </p>
*
* @since 1.6
*/
public abstract class VirtualMachine {
private AttachProvider provider;
private String id;
private volatile int hash; // 0 => not computed
/**
* Initializes a new instance of this class.
*
* @param provider
* The attach provider creating this class.
* @param id
* The abstract identifier that identifies the Java virtual machine.
*
* @throws NullPointerException
* If {@code provider} or {@code id} is {@code null}.
*/
protected VirtualMachine(AttachProvider provider, String id) {
if (provider == null) {
throw new NullPointerException("provider cannot be null");
}
if (id == null) {
throw new NullPointerException("id cannot be null");
}
this.provider = provider;
this.id = id;
}
/**
* Return a list of Java virtual machines.
*
* <p> This method returns a list of Java {@link
* com.sun.tools.attach.VirtualMachineDescriptor} elements.
* The list is an aggregation of the virtual machine
* descriptor lists obtained by invoking the {@link
* com.sun.tools.attach.spi.AttachProvider#listVirtualMachines
* listVirtualMachines} method of all installed
* {@link com.sun.tools.attach.spi.AttachProvider attach providers}.
* If there are no Java virtual machines known to any provider
* then an empty list is returned.
*
* @return The list of virtual machine descriptors.
*/
public static List<VirtualMachineDescriptor> list() {
ArrayList<VirtualMachineDescriptor> l =
new ArrayList<VirtualMachineDescriptor>();
List<AttachProvider> providers = AttachProvider.providers();
for (AttachProvider provider: providers) {
l.addAll(provider.listVirtualMachines());
}
return l;
}
/**
* Attaches to a Java virtual machine.
*
* <p> This method obtains the list of attach providers by invoking the
* {@link com.sun.tools.attach.spi.AttachProvider#providers()
* AttachProvider.providers()} method. It then iterates overs the list
* and invokes each provider's {@link
* com.sun.tools.attach.spi.AttachProvider#attachVirtualMachine(java.lang.String)
* attachVirtualMachine} method in turn. If a provider successfully
* attaches then the iteration terminates, and the VirtualMachine created
* by the provider that successfully attached is returned by this method.
* If the {@code attachVirtualMachine} method of all providers throws
* {@link com.sun.tools.attach.AttachNotSupportedException AttachNotSupportedException}
* then this method also throws {@code AttachNotSupportedException}.
* This means that {@code AttachNotSupportedException} is thrown when
* the identifier provided to this method is invalid, or the identifier
* corresponds to a Java virtual machine that does not exist, or none
* of the providers can attach to it. This exception is also thrown if
* {@link com.sun.tools.attach.spi.AttachProvider#providers()
* AttachProvider.providers()} returns an empty list. </p>
*
* @param id
* The abstract identifier that identifies the Java virtual machine.
*
* @return A VirtualMachine representing the target VM.
*
* @throws AttachNotSupportedException
* If the {@code attachVirtualmachine} method of all installed
* providers throws {@code AttachNotSupportedException}, or
* there aren't any providers installed.
*
* @throws IOException
* If an I/O error occurs
*
* @throws NullPointerException
* If {@code id} is {@code null}.
*/
public static VirtualMachine attach(String id)
throws AttachNotSupportedException, IOException
{
if (id == null) {
throw new NullPointerException("id cannot be null");
}
List<AttachProvider> providers = AttachProvider.providers();
if (providers.size() == 0) {
throw new AttachNotSupportedException("no providers installed");
}
AttachNotSupportedException lastExc = null;
for (AttachProvider provider: providers) {
try {
return provider.attachVirtualMachine(id);
} catch (AttachNotSupportedException x) {
lastExc = x;
}
}
throw lastExc;
}
/**
* Attaches to a Java virtual machine.
*
* <p> This method first invokes the {@link
* com.sun.tools.attach.VirtualMachineDescriptor#provider() provider()} method
* of the given virtual machine descriptor to obtain the attach provider. It
* then invokes the attach provider's {@link
* com.sun.tools.attach.spi.AttachProvider#attachVirtualMachine(VirtualMachineDescriptor)
* attachVirtualMachine} to attach to the target VM.
*
* @param vmd
* The virtual machine descriptor.
*
* @return A VirtualMachine representing the target VM.
*
* @throws AttachNotSupportedException
* If the attach provider's {@code attachVirtualmachine}
* throws {@code AttachNotSupportedException}.
*
* @throws IOException
* If an I/O error occurs
*
* @throws NullPointerException
* If {@code vmd} is {@code null}.
*/
public static VirtualMachine attach(VirtualMachineDescriptor vmd)
throws AttachNotSupportedException, IOException
{
return vmd.provider().attachVirtualMachine(vmd);
}
/**
* Detach from the virtual machine.
*
* <p> After detaching from the virtual machine, any further attempt to invoke
* operations on that virtual machine will cause an {@link java.io.IOException
* IOException} to be thrown. If an operation (such as {@link #loadAgent
* loadAgent} for example) is in progress when this method is invoked then
* the behaviour is implementation dependent. In other words, it is
* implementation specific if the operation completes or throws
* {@code IOException}.
*
* <p> If already detached from the virtual machine then invoking this
* method has no effect. </p>
*
* @throws IOException
* If an I/O error occurs
*/
public abstract void detach() throws IOException;
/**
* Returns the provider that created this virtual machine.
*
* @return The provider that created this virtual machine.
*/
public final AttachProvider provider() {
return provider;
}
/**
* Returns the identifier for this Java virtual machine.
*
* @return The identifier for this Java virtual machine.
*/
public final String id() {
return id;
}
/**
* Loads an agent library.
*
* <p> A <a href="{@docRoot}/../specs/jvmti.html">JVM TI</a>
* client is called an <i>agent</i>. It is developed in a native language.
* A JVM TI agent is deployed in a platform specific manner but it is typically the
* platform equivalent of a dynamic library. Alternatively, it may be statically linked into the VM.
* This method causes the given agent library to be loaded into the target
* VM (if not already loaded or if not statically linked into the VM).
* It then causes the target VM to invoke the {@code Agent_OnAttach} function
* or, for a statically linked agent named 'L', the {@code Agent_OnAttach_L} function
* as specified in the
* <a href="{@docRoot}/../specs/jvmti.html">JVM Tools Interface</a> specification.
* Note that the {@code Agent_OnAttach[_L]}
* function is invoked even if the agent library was loaded prior to invoking
* this method.
*
* <p> The agent library provided is the name of the agent library. It is interpreted
* in the target virtual machine in an implementation-dependent manner. Typically an
* implementation will expand the library name into an operating system specific file
* name. For example, on UNIX systems, the name {@code L} might be expanded to
* {@code libL.so}, and located using the search path specified by the
* {@code LD_LIBRARY_PATH} environment variable. If the agent named 'L' is
* statically linked into the VM then the VM must export a function named
* {@code Agent_OnAttach_L}.</p>
*
* <p> If the {@code Agent_OnAttach[_L]} function in the agent library returns
* an error then an {@link com.sun.tools.attach.AgentInitializationException} is
* thrown. The return value from the {@code Agent_OnAttach[_L]} can then be
* obtained by invoking the {@link
* com.sun.tools.attach.AgentInitializationException#returnValue() returnValue}
* method on the exception. </p>
*
* @param agentLibrary
* The name of the agent library.
*
* @param options
* The options to provide to the {@code Agent_OnAttach[_L]}
* function (can be {@code null}).
*
* @throws AgentLoadException
* If the agent library does not exist, the agent library is not
* statically linked with the VM, or the agent library cannot be
* loaded for another reason.
*
* @throws AgentInitializationException
* If the {@code Agent_OnAttach[_L]} function returns an error.
*
* @throws IOException
* If an I/O error occurs
*
* @throws NullPointerException
* If {@code agentLibrary} is {@code null}.
*
* @see com.sun.tools.attach.AgentInitializationException#returnValue()
*/
public abstract void loadAgentLibrary(String agentLibrary, String options)
throws AgentLoadException, AgentInitializationException, IOException;
/**
* Loads an agent library.
*
* <p> This convenience method works as if by invoking:
*
* <blockquote><code>
* {@link #loadAgentLibrary(String, String) loadAgentLibrary}(agentLibrary,&nbsp;null);
* </code></blockquote>
*
* @param agentLibrary
* The name of the agent library.
*
* @throws AgentLoadException
* If the agent library does not exist, the agent library is not
* statically linked with the VM, or the agent library cannot be
* loaded for another reason.
*
* @throws AgentInitializationException
* If the {@code Agent_OnAttach[_L]} function returns an error.
*
* @throws IOException
* If an I/O error occurs
*
* @throws NullPointerException
* If {@code agentLibrary} is {@code null}.
*/
public void loadAgentLibrary(String agentLibrary)
throws AgentLoadException, AgentInitializationException, IOException
{
loadAgentLibrary(agentLibrary, null);
}
/**
* Load a native agent library by full pathname.
*
* <p> A <a href="{@docRoot}/../specs/jvmti.html">JVM TI</a>
* client is called an <i>agent</i>. It is developed in a native language.
* A JVM TI agent is deployed in a platform specific manner but it is typically the
* platform equivalent of a dynamic library. Alternatively, the native
* library specified by the agentPath parameter may be statically
* linked with the VM. The parsing of the agentPath parameter into
* a statically linked library name is done in a platform
* specific manner in the VM. For example, in UNIX, an agentPath parameter
* of {@code /a/b/libL.so} would name a library 'L'.
*
* See the JVM TI Specification for more details.
*
* This method causes the given agent library to be loaded into the target
* VM (if not already loaded or if not statically linked into the VM).
* It then causes the target VM to invoke the {@code Agent_OnAttach}
* function or, for a statically linked agent named 'L', the
* {@code Agent_OnAttach_L} function as specified in the
* <a href="{@docRoot}/../specs/jvmti.html">JVM Tools Interface</a> specification.
* Note that the {@code Agent_OnAttach[_L]}
* function is invoked even if the agent library was loaded prior to invoking
* this method.
*
* <p> The agent library provided is the absolute path from which to load the
* agent library. Unlike {@link #loadAgentLibrary loadAgentLibrary}, the library name
* is not expanded in the target virtual machine. </p>
*
* <p> If the {@code Agent_OnAttach[_L]} function in the agent library returns
* an error then an {@link com.sun.tools.attach.AgentInitializationException} is
* thrown. The return value from the {@code Agent_OnAttach[_L]} can then be
* obtained by invoking the {@link
* com.sun.tools.attach.AgentInitializationException#returnValue() returnValue}
* method on the exception. </p>
*
* @param agentPath
* The full path of the agent library.
*
* @param options
* The options to provide to the {@code Agent_OnAttach[_L]}
* function (can be {@code null}).
*
* @throws AgentLoadException
* If the agent library does not exist, the agent library is not
* statically linked with the VM, or the agent library cannot be
* loaded for another reason.
*
* @throws AgentInitializationException
* If the {@code Agent_OnAttach[_L]} function returns an error.
*
* @throws IOException
* If an I/O error occurs
*
* @throws NullPointerException
* If {@code agentPath} is {@code null}.
*
* @see com.sun.tools.attach.AgentInitializationException#returnValue()
*/
public abstract void loadAgentPath(String agentPath, String options)
throws AgentLoadException, AgentInitializationException, IOException;
/**
* Load a native agent library by full pathname.
*
* <p> This convenience method works as if by invoking:
*
* <blockquote><code>
* {@link #loadAgentPath(String, String) loadAgentPath}(agentLibrary,&nbsp;null);
* </code></blockquote>
*
* @param agentPath
* The full path to the agent library.
*
* @throws AgentLoadException
* If the agent library does not exist, the agent library is not
* statically linked with the VM, or the agent library cannot be
* loaded for another reason.
*
* @throws AgentInitializationException
* If the {@code Agent_OnAttach[_L]} function returns an error.
*
* @throws IOException
* If an I/O error occurs
*
* @throws NullPointerException
* If {@code agentPath} is {@code null}.
*/
public void loadAgentPath(String agentPath)
throws AgentLoadException, AgentInitializationException, IOException
{
loadAgentPath(agentPath, null);
}
/**
* Loads an agent.
*
* <p> The agent provided to this method is a path name to a JAR file on the file
* system of the target virtual machine. This path is passed to the target virtual
* machine where it is interpreted. The target virtual machine attempts to start
* the agent as specified by the {@link java.lang.instrument} specification.
* That is, the specified JAR file is added to the system class path (of the target
* virtual machine), and the {@code agentmain} method of the agent class, specified
* by the {@code Agent-Class} attribute in the JAR manifest, is invoked. This
* method completes when the {@code agentmain} method completes.
*
* @param agent
* Path to the JAR file containing the agent.
*
* @param options
* The options to provide to the agent's {@code agentmain}
* method (can be {@code null}).
*
* @throws AgentLoadException
* If the agent does not exist, or cannot be started in the manner
* specified in the {@link java.lang.instrument} specification.
*
* @throws AgentInitializationException
* If the {@code agentmain} throws an exception
*
* @throws IOException
* If an I/O error occurs
*
* @throws NullPointerException
* If {@code agent} is {@code null}.
*/
public abstract void loadAgent(String agent, String options)
throws AgentLoadException, AgentInitializationException, IOException;
/**
* Loads an agent.
*
* <p> This convenience method works as if by invoking:
*
* <blockquote><code>
* {@link #loadAgent(String, String) loadAgent}(agent,&nbsp;null);
* </code></blockquote>
*
* @param agent
* Path to the JAR file containing the agent.
*
* @throws AgentLoadException
* If the agent does not exist, or cannot be started in the manner
* specified in the {@link java.lang.instrument} specification.
*
* @throws AgentInitializationException
* If the {@code agentmain} throws an exception
*
* @throws IOException
* If an I/O error occurs
*
* @throws NullPointerException
* If {@code agent} is {@code null}.
*/
public void loadAgent(String agent)
throws AgentLoadException, AgentInitializationException, IOException
{
loadAgent(agent, null);
}
/**
* Returns the current system properties in the target virtual machine.
*
* <p> This method returns the system properties in the target virtual
* machine. Properties whose key or value is not a {@code String} are
* omitted. The method is approximately equivalent to the invocation of the
* method {@link java.lang.System#getProperties System.getProperties}
* in the target virtual machine except that properties with a key or
* value that is not a {@code String} are not included.
*
* <p> This method is typically used to decide which agent to load into
* the target virtual machine with {@link #loadAgent loadAgent}, or
* {@link #loadAgentLibrary loadAgentLibrary}. For example, the
* {@code java.home} or {@code user.dir} properties might be
* use to create the path to the agent library or JAR file.
*
* @return The system properties
*
* @throws AttachOperationFailedException
* If the target virtual machine is unable to complete the
* attach operation. A more specific error message will be
* given by {@link AttachOperationFailedException#getMessage()}.
*
* @throws IOException
* If an I/O error occurs, a communication error for example,
* that cannot be identified as an error to indicate that the
* operation failed in the target VM.
*
* @see java.lang.System#getProperties
* @see #loadAgentLibrary
* @see #loadAgent
*/
public abstract Properties getSystemProperties() throws IOException;
/**
* Returns the current <i>agent properties</i> in the target virtual
* machine.
*
* <p> The target virtual machine can maintain a list of properties on
* behalf of agents. The manner in which this is done, the names of the
* properties, and the types of values that are allowed, is implementation
* specific. Agent properties are typically used to store communication
* end-points and other agent configuration details. For example, a debugger
* agent might create an agent property for its transport address.
*
* <p> This method returns the agent properties whose key and value is a
* {@code String}. Properties whose key or value is not a {@code String}
* are omitted. If there are no agent properties maintained in the target
* virtual machine then an empty property list is returned.
*
* @return The agent properties
*
* @throws AttachOperationFailedException
* If the target virtual machine is unable to complete the
* attach operation. A more specific error message will be
* given by {@link AttachOperationFailedException#getMessage()}.
*
* @throws IOException
* If an I/O error occurs, a communication error for example,
* that cannot be identified as an error to indicate that the
* operation failed in the target VM.
*/
public abstract Properties getAgentProperties() throws IOException;
/**
* Starts the JMX management agent in the target virtual machine.
*
* <p> The configuration properties are the same as those specified on
* the command line when starting the JMX management agent. In the same
* way as on the command line, you need to specify at least the
* {@code com.sun.management.jmxremote.port} property.
*
* <p> See the online documentation for
* {@extLink monitoring_and_management_using_jmx_technology
* Monitoring and Management Using JMX Technology} for further details.
*
* @param agentProperties
* A Properties object containing the configuration properties
* for the agent.
*
* @throws AttachOperationFailedException
* If the target virtual machine is unable to complete the
* attach operation. A more specific error message will be
* given by {@link AttachOperationFailedException#getMessage()}.
*
* @throws IOException
* If an I/O error occurs, a communication error for example,
* that cannot be identified as an error to indicate that the
* operation failed in the target VM.
*
* @throws IllegalArgumentException
* If keys or values in agentProperties are invalid.
*
* @throws NullPointerException
* If agentProperties is null.
*
* @since 1.8
*/
public abstract void startManagementAgent(Properties agentProperties) throws IOException;
/**
* Starts the local JMX management agent in the target virtual machine.
*
* <p> See the online documentation for
* {@extLink monitoring_and_management_using_jmx_technology
* Monitoring and Management Using JMX Technology} for further details.
*
* @return The String representation of the local connector's service address.
* The value can be parsed by the
* {@link javax.management.remote.JMXServiceURL#JMXServiceURL(String)}
* constructor.
*
* @throws AttachOperationFailedException
* If the target virtual machine is unable to complete the
* attach operation. A more specific error message will be
* given by {@link AttachOperationFailedException#getMessage()}.
*
* @throws IOException
* If an I/O error occurs, a communication error for example,
* that cannot be identified as an error to indicate that the
* operation failed in the target VM.
*
* @since 1.8
*/
public abstract String startLocalManagementAgent() throws IOException;
/**
* Returns a hash-code value for this VirtualMachine. The hash
* code is based upon the VirtualMachine's components, and satisfies
* the general contract of the {@link java.lang.Object#hashCode()
* Object.hashCode} method.
*
* @return A hash-code value for this virtual machine
*/
public int hashCode() {
if (hash != 0) {
return hash;
}
hash = provider.hashCode() * 127 + id.hashCode();
return hash;
}
/**
* Tests this VirtualMachine for equality with another object.
*
* <p> If the given object is not a VirtualMachine then this
* method returns {@code false}. For two VirtualMachines to
* be considered equal requires that they both reference the same
* provider, and their {@link VirtualMachineDescriptor#id() identifiers} are equal. </p>
*
* <p> This method satisfies the general contract of the {@link
* java.lang.Object#equals(Object) Object.equals} method. </p>
*
* @param ob The object to which this object is to be compared
*
* @return {@code true} if, and only if, the given object is
* a VirtualMachine that is equal to this
* VirtualMachine.
*/
public boolean equals(Object ob) {
if (ob == this)
return true;
if (!(ob instanceof VirtualMachine))
return false;
VirtualMachine other = (VirtualMachine)ob;
if (other.provider() != this.provider()) {
return false;
}
if (!other.id().equals(this.id())) {
return false;
}
return true;
}
/**
* Returns the string representation of the {@code VirtualMachine}.
*/
public String toString() {
return provider.toString() + ": " + id;
}
}

View file

@ -0,0 +1,203 @@
/*
* Copyright (c) 2005, 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.tools.attach;
import com.sun.tools.attach.spi.AttachProvider;
/**
* Describes a Java virtual machine.
*
* <p> A {@code VirtualMachineDescriptor} is a container class used to
* describe a Java virtual machine. It encapsulates an identifier that identifies
* a target virtual machine, and a reference to the {@link
* com.sun.tools.attach.spi.AttachProvider AttachProvider} that should be used
* when attempting to attach to the virtual machine. The identifier is
* implementation-dependent but is typically the process identifier (or pid)
* environments where each Java virtual machine runs in its own operating system
* process. </p>
*
* <p> A {@code VirtualMachineDescriptor} also has a {@link #displayName() displayName}.
* The display name is typically a human readable string that a tool might
* display to a user. For example, a tool that shows a list of Java
* virtual machines running on a system might use the display name rather
* than the identifier. A {@code VirtualMachineDescriptor} may be
* created without a <i>display name</i>. In that case the identifier is
* used as the <i>display name</i>.
*
* <p> {@code VirtualMachineDescriptor} instances are typically created by
* invoking the {@link com.sun.tools.attach.VirtualMachine#list VirtualMachine.list()}
* method. This returns the complete list of descriptors to describe the
* Java virtual machines known to all installed {@link
* com.sun.tools.attach.spi.AttachProvider attach providers}.
*
* @since 1.6
*/
public class VirtualMachineDescriptor {
private AttachProvider provider;
private String id;
private String displayName;
private volatile int hash; // 0 => not computed
/**
* Creates a virtual machine descriptor from the given components.
*
* @param provider The AttachProvider to attach to the Java virtual machine.
* @param id The virtual machine identifier.
* @param displayName The display name.
*
* @throws NullPointerException
* If any of the arguments are {@code null}
*/
public VirtualMachineDescriptor(AttachProvider provider, String id, String displayName) {
if (provider == null) {
throw new NullPointerException("provider cannot be null");
}
if (id == null) {
throw new NullPointerException("identifier cannot be null");
}
if (displayName == null) {
throw new NullPointerException("display name cannot be null");
}
this.provider = provider;
this.id = id;
this.displayName = displayName;
}
/**
* Creates a virtual machine descriptor from the given components.
*
* <p> This convenience constructor works as if by invoking the
* three-argument constructor as follows:
*
* <blockquote><code>
* new&nbsp;{@link #VirtualMachineDescriptor(AttachProvider, String, String)
* VirtualMachineDescriptor}(provider, &nbsp;id, &nbsp;id);
* </code></blockquote>
*
* <p> That is, it creates a virtual machine descriptor such that
* the <i>display name</i> is the same as the virtual machine
* identifier.
*
* @param provider The AttachProvider to attach to the Java virtual machine.
* @param id The virtual machine identifier.
*
* @throws NullPointerException
* If {@code provider} or {@code id} is {@code null}.
*/
public VirtualMachineDescriptor(AttachProvider provider, String id) {
this(provider, id, id);
}
/**
* Return the {@code AttachProvider} that this descriptor references.
*
* @return The {@code AttachProvider} that this descriptor references.
*/
public AttachProvider provider() {
return provider;
}
/**
* Return the identifier component of this descriptor.
*
* @return The identifier component of this descriptor.
*/
public String id() {
return id;
}
/**
* Return the <i>display name</i> component of this descriptor.
*
* @return The display name component of this descriptor.
*/
public String displayName() {
return displayName;
}
/**
* Returns a hash-code value for this VirtualMachineDescriptor. The hash
* code is based upon the descriptor's components, and satisfies
* the general contract of the {@link java.lang.Object#hashCode()
* Object.hashCode} method.
*
* @return A hash-code value for this descriptor.
*/
public int hashCode() {
if (hash != 0) {
return hash;
}
hash = provider.hashCode() * 127 + id.hashCode();
return hash;
}
/**
* Tests this VirtualMachineDescriptor for equality with another object.
*
* <p> If the given object is not a VirtualMachineDescriptor then this
* method returns {@code false}. For two VirtualMachineDescriptors to
* be considered equal requires that they both reference the same
* provider, and their {@link #id() identifiers} are equal. </p>
*
* <p> This method satisfies the general contract of the {@link
* java.lang.Object#equals(Object) Object.equals} method. </p>
*
* @param ob The object to which this object is to be compared
*
* @return {@code true} if, and only if, the given object is
* a VirtualMachineDescriptor that is equal to this
* VirtualMachineDescriptor.
*/
public boolean equals(Object ob) {
if (ob == this)
return true;
if (!(ob instanceof VirtualMachineDescriptor))
return false;
VirtualMachineDescriptor other = (VirtualMachineDescriptor)ob;
if (other.provider() != this.provider()) {
return false;
}
if (!other.id().equals(this.id())) {
return false;
}
return true;
}
/**
* Returns the string representation of the {@code VirtualMachineDescriptor}.
*/
public String toString() {
String s = provider.toString() + ": " + id;
if (displayName != id) {
s += " " + displayName;
}
return s;
}
}

View file

@ -0,0 +1,41 @@
/*
* Copyright (c) 2005, 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/**
* Provides the API to attach to a Java virtual machine.
* <p>
* A tool, written in the Java Language, uses this API to attach to a target
* virtual machine (VM) and load its tool agent into the target VM. For
* example, a management console might have a management agent which it uses
* to obtain management information from instrumented objects in a Java
* virtual machine. If the management console is required to manage
* an application that is running in a virtual machine that does not include
* the management agent, then this API can be used to attach to the target
* VM and load the agent.
*
* @since 1.6
*/
package com.sun.tools.attach;

View file

@ -0,0 +1,250 @@
/*
* Copyright (c) 2005, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.tools.attach.spi;
import java.io.IOException;
import java.util.Collections;
import java.util.Iterator;
import java.util.ArrayList;
import java.util.List;
import com.sun.tools.attach.VirtualMachine;
import com.sun.tools.attach.VirtualMachineDescriptor;
import com.sun.tools.attach.AttachNotSupportedException;
import java.util.ServiceLoader;
/**
* Attach provider class for attaching to a Java virtual machine.
*
* <p> An attach provider is a concrete subclass of this class that has a
* zero-argument constructor and implements the abstract methods specified
* below.
*
* <p> An attach provider implementation is typically tied to a Java virtual
* machine implementation, version, or even mode of operation. That is, a specific
* provider implementation will typically only be capable of attaching to
* a specific Java virtual machine implementation or version. For example, Oracle's
* JDK implementation ships with provider implementations that can only attach to
* Oracle's <i>HotSpot</i> virtual machine. In general, if an environment
* consists of Java virtual machines of different versions and from different
* vendors then there will be an attach provider implementation for each
* <i>family</i> of implementations or versions.
*
* <p> An attach provider is identified by its {@link #name <i>name</i>} and
* {@link #type <i>type</i>}. The <i>name</i> is typically, but not required to
* be, a name that corresponds to the VM vendor. The Oracle JDK implementation,
* for example, ships with attach providers that use the package name <i>"sun"</i>
* (for historical reasons). The
* <i>type</i> typically corresponds to the attach mechanism. For example, an
* implementation that uses the Doors inter-process communication mechanism
* might use the type <i>"doors"</i>. The purpose of the name and type is to
* identify providers in environments where there are multiple providers
* installed.
*
* <p> AttachProvider implementations are loaded and instantiated at the first
* invocation of the {@link #providers() providers} method. This method
* attempts to load all provider implementations that are installed on the
* platform.
*
* <p> All of the methods in this class are safe for use by multiple
* concurrent threads.
*
* @since 1.6
*/
public abstract class AttachProvider {
private static final Object lock = new Object();
private static List<AttachProvider> providers = null;
/**
* Initializes a new instance of this class.
*/
protected AttachProvider() {
}
/**
* Return this provider's name.
*
* @return The name of this provider
*/
public abstract String name();
/**
* Return this provider's type.
*
* @return The type of this provider
*/
public abstract String type();
/**
* Attaches to a Java virtual machine.
*
* <p> A Java virtual machine is identified by an abstract identifier. The
* nature of this identifier is platform dependent but in many cases it will be the
* string representation of the process identifier (or pid).
*
* <p> This method parses the identifier and maps the identifier to a Java
* virtual machine (in an implementation dependent manner). If the identifier
* cannot be parsed by the provider then an
* {@link com.sun.tools.attach.AttachNotSupportedException AttachNotSupportedException}
* is thrown. Once parsed this method attempts to attach to the Java virtual machine.
* If the provider detects that the identifier corresponds to a Java virtual machine
* that does not exist, or it corresponds to a Java virtual machine that does not support
* the attach mechanism implemented by this provider, or it detects that the
* Java virtual machine is a version to which this provider cannot attach, then
* an {@code AttachNotSupportedException} is thrown.
*
* @param id
* The abstract identifier that identifies the Java virtual machine.
*
* @return VirtualMachine representing the target virtual machine.
*
* @throws AttachNotSupportedException
* If the identifier cannot be parsed, or it corresponds to
* to a Java virtual machine that does not exist, or it
* corresponds to a Java virtual machine which this
* provider cannot attach.
*
* @throws IOException
* If some other I/O error occurs
*
* @throws NullPointerException
* If {@code id} is {@code null}
*/
public abstract VirtualMachine attachVirtualMachine(String id)
throws AttachNotSupportedException, IOException;
/**
* Attaches to a Java virtual machine.
*
* <p> A Java virtual machine can be described using a
* {@link com.sun.tools.attach.VirtualMachineDescriptor VirtualMachineDescriptor}.
* This method invokes the descriptor's
* {@link com.sun.tools.attach.VirtualMachineDescriptor#provider() provider()} method
* to check that it is equal to this provider. It then attempts to attach to the
* Java virtual machine.
*
* @param vmd
* The virtual machine descriptor
*
* @return VirtualMachine representing the target virtual machine.
*
* @throws AttachNotSupportedException
* If the descriptor's
* {@link com.sun.tools.attach.VirtualMachineDescriptor#provider() provider()}
* method returns a provider that is not this provider, or it does not
* correspond to a Java virtual machine to which this provider can attach.
*
* @throws IOException
* If some other I/O error occurs
*
* @throws NullPointerException
* If {@code vmd} is {@code null}
*/
public VirtualMachine attachVirtualMachine(VirtualMachineDescriptor vmd)
throws AttachNotSupportedException, IOException
{
if (vmd.provider() != this) {
throw new AttachNotSupportedException("provider mismatch");
}
return attachVirtualMachine(vmd.id());
}
/**
* Lists the Java virtual machines known to this provider.
*
* <p> This method returns a list of
* {@link com.sun.tools.attach.VirtualMachineDescriptor} elements. Each
* {@code VirtualMachineDescriptor} describes a Java virtual machine
* to which this provider can <i>potentially</i> attach. There isn't any
* guarantee that invoking
* {@link #attachVirtualMachine(VirtualMachineDescriptor) attachVirtualMachine}
* on each descriptor in the list will succeed.
*
* @return The list of virtual machine descriptors which describe the
* Java virtual machines known to this provider (may be empty).
*/
public abstract List<VirtualMachineDescriptor> listVirtualMachines();
/**
* Returns a list of the installed attach providers.
*
* <p> An AttachProvider is installed on the platform if:
*
* <ul>
* <li>It is installed in a JAR file that is visible to the defining
* class loader of the AttachProvider type (usually, but not required
* to be, the {@link java.lang.ClassLoader#getSystemClassLoader system
* class loader}).</li>
*
* <li>The JAR file contains a provider configuration named
* {@code com.sun.tools.attach.spi.AttachProvider} in the resource directory
* {@code META-INF/services}.</li>
*
* <li>The provider configuration file lists the full-qualified class
* name of the AttachProvider implementation.</li>
* </ul>
*
* <p> The format of the provider configuration file is one fully-qualified
* class name per line. Space and tab characters surrounding each class name,
* as well as blank lines are ignored. The comment character is
* {@code '#'} ({@code 0x23}), and on each line all characters following
* the first comment character are ignored. The file must be encoded in
* UTF-8.
*
* <p> AttachProvider implementations are loaded and instantiated
* (using the zero-arg constructor) at the first invocation of this method.
* The list returned by the first invocation of this method is the list
* of providers. Subsequent invocations of this method return a list of the same
* providers. The list is unmodifiable.
*
* @return A list of the installed attach providers.
*/
public static List<AttachProvider> providers() {
synchronized (lock) {
if (providers == null) {
providers = new ArrayList<AttachProvider>();
ServiceLoader<AttachProvider> providerLoader =
ServiceLoader.load(AttachProvider.class,
AttachProvider.class.getClassLoader());
Iterator<AttachProvider> i = providerLoader.iterator();
while (i.hasNext()) {
try {
providers.add(i.next());
} catch (Throwable t) {
// Log errors and exceptions since we cannot return them
t.printStackTrace();
}
}
}
return Collections.unmodifiableList(providers);
}
}
}

View file

@ -0,0 +1,34 @@
/*
* Copyright (c) 2005, 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/**
* Only developers who are defining new attach providers should need to make
* direct use of this package.
*
* @since 1.6
*/
package com.sun.tools.attach.spi;

View file

@ -0,0 +1,47 @@
/*
* Copyright (c) 2014, 2017, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/**
* Defines the attach API.
*
* @uses com.sun.tools.attach.spi.AttachProvider
*
* @moduleGraph
* @since 9
*/
module jdk.attach {
requires jdk.internal.jvmstat;
exports com.sun.tools.attach;
exports com.sun.tools.attach.spi;
exports sun.tools.attach to
jdk.jcmd;
uses com.sun.tools.attach.spi.AttachProvider;
provides com.sun.tools.attach.spi.AttachProvider with
sun.tools.attach.AttachProviderImpl;
}

View file

@ -0,0 +1,153 @@
/*
* Copyright (c) 2005, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package sun.tools.attach;
import com.sun.tools.attach.VirtualMachineDescriptor;
import com.sun.tools.attach.AttachNotSupportedException;
import com.sun.tools.attach.spi.AttachProvider;
import java.util.List;
import java.util.ArrayList;
import java.util.Set;
import sun.jvmstat.monitor.HostIdentifier;
import sun.jvmstat.monitor.MonitoredHost;
import sun.jvmstat.monitor.MonitoredVm;
import sun.jvmstat.monitor.MonitoredVmUtil;
import sun.jvmstat.monitor.VmIdentifier;
/*
* Platform specific provider implementations extend this
*/
public abstract class HotSpotAttachProvider extends AttachProvider {
public HotSpotAttachProvider() {
}
/*
* This listVirtualMachines implementation is based on jvmstat. Can override
* this in platform implementations when there is a more efficient mechanism
* available.
*/
public List<VirtualMachineDescriptor> listVirtualMachines() {
ArrayList<VirtualMachineDescriptor> result =
new ArrayList<VirtualMachineDescriptor>();
MonitoredHost host;
Set<Integer> vms;
try {
host = MonitoredHost.getMonitoredHost(new HostIdentifier((String)null));
vms = host.activeVms();
} catch (Throwable t) {
if (t instanceof ExceptionInInitializerError) {
t = t.getCause();
}
if (t instanceof SecurityException) {
return result;
}
throw new InternalError(t); // shouldn't happen
}
for (Integer vmid: vms) {
String pid = vmid.toString();
String name = pid; // default to pid if name not available
boolean isAttachable = false;
MonitoredVm mvm = null;
try {
mvm = host.getMonitoredVm(new VmIdentifier(pid));
try {
isAttachable = MonitoredVmUtil.isAttachable(mvm);
// use the command line as the display name
name = MonitoredVmUtil.commandLine(mvm);
} catch (Exception e) {
}
if (isAttachable) {
result.add(new HotSpotVirtualMachineDescriptor(this, pid, name));
}
} catch (Throwable t) {
// ignore
} finally {
if (mvm != null) {
mvm.detach();
}
}
}
return result;
}
/**
* Test if a VM is attachable. If it's not attachable,
* an AttachNotSupportedException will be thrown. For example,
* 1.4.2 or 5.0 VM are not attachable. There are cases that
* we can't determine if a VM is attachable or not and this method
* will just return.
*
* This method uses the jvmstat counter to determine if a VM
* is attachable. If the target VM does not have a jvmstat
* share memory buffer, this method returns.
*
* @exception AttachNotSupportedException if it's not attachable
*/
void testAttachable(String id) throws AttachNotSupportedException {
MonitoredVm mvm = null;
try {
VmIdentifier vmid = new VmIdentifier(id);
MonitoredHost host = MonitoredHost.getMonitoredHost(vmid);
mvm = host.getMonitoredVm(vmid);
if (MonitoredVmUtil.isAttachable(mvm)) {
// it's attachable; so return false
return;
}
} catch (Throwable t) {
// we do not know what this id is
return;
} finally {
if (mvm != null) {
mvm.detach();
}
}
// we're sure it's not attachable; throw exception
throw new AttachNotSupportedException(
"The VM does not support the attach mechanism");
}
/**
* A virtual machine descriptor to describe a HotSpot virtual machine.
*/
static class HotSpotVirtualMachineDescriptor extends VirtualMachineDescriptor {
HotSpotVirtualMachineDescriptor(AttachProvider provider,
String id,
String displayName) {
super(provider, id, displayName);
}
public boolean isAttachable() {
return true;
}
}
}

View file

@ -0,0 +1,644 @@
/*
* Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package sun.tools.attach;
import com.sun.tools.attach.AttachOperationFailedException;
import com.sun.tools.attach.AttachNotSupportedException;
import com.sun.tools.attach.VirtualMachine;
import com.sun.tools.attach.AgentLoadException;
import com.sun.tools.attach.AgentInitializationException;
import com.sun.tools.attach.spi.AttachProvider;
import jdk.internal.misc.VM;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.security.PrivilegedAction;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.stream.Collectors;
import java.nio.charset.StandardCharsets;
/*
* The HotSpot implementation of com.sun.tools.attach.VirtualMachine.
*/
public abstract class HotSpotVirtualMachine extends VirtualMachine {
private static final long CURRENT_PID = pid();
@SuppressWarnings("removal")
private static long pid() {
return ProcessHandle.current().pid();
}
private static final boolean ALLOW_ATTACH_SELF;
private static final boolean ALLOW_STREAMING_OUTPUT;
static {
String s = VM.getSavedProperty("jdk.attach.allowAttachSelf");
ALLOW_ATTACH_SELF = "".equals(s) || Boolean.parseBoolean(s);
String s2 = VM.getSavedProperty("jdk.attach.allowStreamingOutput");
ALLOW_STREAMING_OUTPUT = !("false".equals(s2));
}
private final boolean selfAttach;
HotSpotVirtualMachine(AttachProvider provider, String id)
throws AttachNotSupportedException, IOException
{
super(provider, id);
int pid;
try {
pid = Integer.parseInt(id);
} catch (NumberFormatException e) {
throw new AttachNotSupportedException("Invalid process identifier: " + id);
}
selfAttach = pid == 0 || pid == CURRENT_PID;
// The tool should be a different VM to the target. This check will
// eventually be enforced by the target VM.
if (!ALLOW_ATTACH_SELF && selfAttach) {
throw new IOException("Can not attach to current VM");
}
}
/*
* Load agent library
* If isAbsolute is true then the agent library is the absolute path
* to the library and thus will not be expanded in the target VM.
* if isAbsolute is false then the agent library is just a library
* name and it will be expended in the target VM.
*/
private void loadAgentLibrary(String agentLibrary, boolean isAbsolute, String options)
throws AgentLoadException, AgentInitializationException, IOException
{
if (agentLibrary == null) {
throw new NullPointerException("agentLibrary cannot be null");
}
String msgPrefix = "return code: ";
String errorMsg = "Failed to load agent library";
try {
InputStream in = execute("load",
agentLibrary,
isAbsolute ? "true" : "false",
options);
String result = readMessage(in);
if (result.isEmpty()) {
throw new AgentLoadException("Target VM did not respond");
} else if (result.startsWith(msgPrefix)) {
int retCode = Integer.parseInt(result.substring(msgPrefix.length()));
if (retCode != 0) {
throw new AgentInitializationException("Agent_OnAttach failed", retCode);
}
} else {
if (!result.isEmpty()) {
errorMsg += ": " + result;
}
throw new AgentLoadException(errorMsg);
}
} catch (AttachOperationFailedException ex) {
// execute() throws AttachOperationFailedException if attach agent reported error.
// Convert it to AgentLoadException.
throw new AgentLoadException(errorMsg + ": " + ex.getMessage());
}
}
/*
* Load agent library - library name will be expanded in target VM
*/
public void loadAgentLibrary(String agentLibrary, String options)
throws AgentLoadException, AgentInitializationException, IOException
{
loadAgentLibrary(agentLibrary, false, options);
}
/*
* Load agent - absolute path of library provided to target VM
*/
public void loadAgentPath(String agentLibrary, String options)
throws AgentLoadException, AgentInitializationException, IOException
{
loadAgentLibrary(agentLibrary, true, options);
}
/*
* Load JPLIS agent which will load the agent JAR file and invoke
* the agentmain method.
*/
public void loadAgent(String agent, String options)
throws AgentLoadException, AgentInitializationException, IOException
{
if (agent == null) {
throw new NullPointerException("agent cannot be null");
}
String args = agent;
if (options != null) {
args = args + "=" + options;
}
try {
loadAgentLibrary("instrument", args);
} catch (AgentInitializationException x) {
/*
* Translate interesting errors into the right exception and
* message (FIXME: create a better interface to the instrument
* implementation so this isn't necessary)
*/
int rc = x.returnValue();
switch (rc) {
case JNI_ENOMEM:
throw new AgentLoadException("Insuffient memory");
case ATTACH_ERROR_BADJAR:
throw new AgentLoadException(
"Agent JAR not found or no Agent-Class attribute");
case ATTACH_ERROR_NOTONCP:
throw new AgentLoadException(
"Unable to add JAR file to system class path");
case ATTACH_ERROR_STARTFAIL:
throw new AgentInitializationException(
"Agent JAR loaded but agent failed to initialize");
default :
throw new AgentLoadException("" +
"Failed to load agent - unknown reason: " + rc);
}
}
}
/*
* The possible errors returned by JPLIS's agentmain
*/
private static final int JNI_ENOMEM = -4;
private static final int ATTACH_ERROR_BADJAR = 100;
private static final int ATTACH_ERROR_NOTONCP = 101;
private static final int ATTACH_ERROR_STARTFAIL = 102;
// known error
private static final int ATTACH_ERROR_BADVERSION = 101;
/*
* Send "properties" command to target VM
*/
public Properties getSystemProperties() throws IOException {
InputStream in = null;
Properties props = new Properties();
try {
in = executeCommand("properties");
props.load(in);
} finally {
if (in != null) in.close();
}
return props;
}
public Properties getAgentProperties() throws IOException {
InputStream in = null;
Properties props = new Properties();
try {
in = executeCommand("agentProperties");
props.load(in);
} finally {
if (in != null) in.close();
}
return props;
}
private static final String MANAGEMENT_PREFIX = "com.sun.management.";
private static boolean checkedKeyName(Object key) {
if (!(key instanceof String)) {
throw new IllegalArgumentException("Invalid option (not a String): "+key);
}
if (!((String)key).startsWith(MANAGEMENT_PREFIX)) {
throw new IllegalArgumentException("Invalid option: "+key);
}
return true;
}
private static String stripKeyName(Object key) {
return ((String)key).substring(MANAGEMENT_PREFIX.length());
}
@Override
public void startManagementAgent(Properties agentProperties) throws IOException {
if (agentProperties == null) {
throw new NullPointerException("agentProperties cannot be null");
}
// Convert the arguments into arguments suitable for the Diagnostic Command:
// "ManagementAgent.start jmxremote.port=5555 jmxremote.authenticate=false"
String args = agentProperties.entrySet().stream()
.filter(entry -> checkedKeyName(entry.getKey()))
.map(entry -> stripKeyName(entry.getKey()) + "=" + escape(entry.getValue()))
.collect(Collectors.joining(" "));
executeJCmd("ManagementAgent.start " + args).close();
}
private String escape(Object arg) {
String value = arg.toString();
if (value.contains(" ")) {
return "'" + value + "'";
}
return value;
}
@Override
public String startLocalManagementAgent() throws IOException {
executeJCmd("ManagementAgent.start_local").close();
String prop = MANAGEMENT_PREFIX + "jmxremote.localConnectorAddress";
return getAgentProperties().getProperty(prop);
}
// --- HotSpot specific methods ---
// same as SIGQUIT
public void localDataDump() throws IOException {
executeCommand("datadump").close();
}
// Remote ctrl-break. The output of the ctrl-break actions can
// be read from the input stream.
public InputStream remoteDataDump(Object ... args) throws IOException {
return executeCommand("threaddump", args);
}
// Remote heap dump. The output (error message) can be read from the
// returned input stream.
public InputStream dumpHeap(Object ... args) throws IOException {
return executeCommand("dumpheap", args);
}
// Heap histogram (heap inspection in HotSpot)
public InputStream heapHisto(Object ... args) throws IOException {
return executeCommand("inspectheap", args);
}
// set JVM command line flag
public InputStream setFlag(String name, String value) throws IOException {
return executeCommand("setflag", name, value);
}
// print command line flag
public InputStream printFlag(String name) throws IOException {
return executeCommand("printflag", name);
}
public InputStream executeJCmd(String command) throws IOException {
return executeCommand("jcmd", command);
}
// -- Supporting methods
/*
* Execute the given command in the target VM - specific platform
* implementation must implement this.
*/
abstract InputStream execute(String cmd, Object ... args)
throws AgentLoadException, IOException;
/*
* Convenience method for simple commands
*/
public InputStream executeCommand(String cmd, Object ... args) throws IOException {
try {
return execute(cmd, args);
} catch (AgentLoadException x) {
throw new InternalError("Should not get here", x);
}
}
// Attach API version support
protected static final int VERSION_1 = 1;
protected static final int VERSION_2 = 2;
// Attach operation properties.
protected static class OperationProperties {
public final static String STREAMING = "streaming";
private int ver;
private Map<String, String> options = new HashMap<>();
OperationProperties(int ver) {
this.ver = ver;
}
int version() {
return ver;
}
void setOption(String name, String value) {
options.put(name, value);
}
String options() {
return options.entrySet().stream()
.map(e -> e.getKey() + "=" + e.getValue())
.collect(Collectors.joining(","));
}
}
/*
* Detects Attach API properties supported by target VM.
*/
protected OperationProperties getDefaultProps() throws IOException {
try {
InputStream reply = execute("getversion", "options");
String message = readMessage(reply);
reply.close();
// Reply is "<ver> option1,option2...".
int delimPos = message.indexOf(' ');
String ver = delimPos < 0 ? message : message.substring(0, delimPos);
int supportedVersion = Integer.parseUnsignedInt(ver);
// VERSION_2 supports options.
if (supportedVersion == VERSION_2) {
OperationProperties result = new OperationProperties(supportedVersion);
// Parse known options, ignore unknown.
String options = delimPos < 0 ? "" : message.substring(delimPos + 1);
String[] parts = options.split(",");
for (String s: parts) {
if (OperationProperties.STREAMING.equals(s)) {
result.setOption(OperationProperties.STREAMING,
(isStreamingEnabled() ? "1" : "0"));
}
}
return result;
}
} catch (AttachOperationFailedException | AgentLoadException ex) {
// the command is not supported, the VM supports VERSION_1 only
} catch (NumberFormatException nfe) {
// bad version number - fallback to VERSION_1
}
return new OperationProperties(VERSION_1);
}
/*
* For testing purposes Attach API v2 may be disabled.
*/
protected boolean isAPIv2Enabled() {
// if "jdk.attach.compat" property is set, only v1 is enabled.
return !Boolean.getBoolean("jdk.attach.compat");
}
/*
* Streaming output.
*/
protected boolean isStreamingEnabled() {
// Disable streaming for self-attach.
if (selfAttach) {
return false;
}
return ALLOW_STREAMING_OUTPUT;
}
/*
* Utility method to read an 'int' from the input stream. Ideally
* we should be using java.util.Scanner here but this implementation
* guarantees not to read ahead.
*/
int readInt(InputStream in) throws IOException {
StringBuilder sb = new StringBuilder();
// read to \n or EOF
int n;
byte buf[] = new byte[1];
do {
n = in.read(buf, 0, 1);
if (n > 0) {
char c = (char)buf[0];
if (c == '\n') {
break; // EOL found
} else {
sb.append(c);
}
}
} while (n > 0);
if (sb.length() == 0) {
throw new IOException("Premature EOF");
}
int value;
try {
value = Integer.parseInt(sb.toString());
} catch (NumberFormatException x) {
throw new IOException("Non-numeric value found - int expected");
}
return value;
}
/*
* Utility method to read data into a String.
*/
String readMessage(InputStream in) throws IOException {
String s;
StringBuilder message = new StringBuilder();
BufferedReader br = new BufferedReader(new InputStreamReader(in));
while ((s = br.readLine()) != null) {
if (message.length() > 0) {
message.append(' ');
}
message.append(s);
}
return message.toString();
}
/*
* Utility method to process the completion status after command execution.
* If we get IOE during previous command execution, delay throwing it until
* completion status has been read.
*/
void processCompletionStatus(IOException ioe, String cmd, InputStream sis) throws AgentLoadException, IOException {
// Read the command completion status
int completionStatus;
try {
completionStatus = readInt(sis);
} catch (IOException x) {
sis.close();
if (ioe != null) {
throw ioe;
} else {
throw x;
}
}
if (completionStatus != 0) {
// read from the stream and use that as the error message
String message = readMessage(sis);
sis.close();
// In the event of a protocol mismatch then the target VM
// returns a known error so that we can throw a reasonable
// error.
if (completionStatus == ATTACH_ERROR_BADVERSION) {
throw new IOException("Protocol mismatch with target VM");
}
if (message.isEmpty()) {
message = "Command failed in target VM";
}
throw new AttachOperationFailedException(message);
}
}
/*
* Helper writer interface to send commands to the target VM.
*/
public static interface AttachOutputStream {
abstract void write(byte[] buffer, int offset, int length) throws IOException;
}
private int dataSize(Object obj) {
return (obj == null ? 0 : obj.toString().getBytes(StandardCharsets.UTF_8).length) + 1;
}
/*
* Writes object (usually String or Integer) to the attach writer.
*/
private void writeString(AttachOutputStream writer, Object obj) throws IOException {
if (obj != null) {
String s = obj.toString();
if (s.length() > 0) {
byte[] b = s.getBytes(StandardCharsets.UTF_8);
writer.write(b, 0, b.length);
}
}
byte b[] = new byte[1];
b[0] = 0;
writer.write(b, 0, 1);
}
protected void writeCommand(AttachOutputStream writer, OperationProperties props,
String cmd, Object ... args) throws IOException {
writeString(writer, props.version());
if (props.version() == VERSION_2) {
// add options to the command name (if specified)
String options = props.options();
if (!options.isEmpty()) {
cmd += " " + options;
}
// for v2 write size of the data
int size = dataSize(cmd);
for (Object arg: args) {
size += dataSize(arg);
}
writeString(writer, size);
}
writeString(writer, cmd);
// v1 commands always write 3 arguments
int argNumber = props.version() == VERSION_1 ? 3 : args.length;
for (int i = 0; i < argNumber; i++) {
writeString(writer, i < args.length ? args[i] : null);
}
}
/*
* InputStream for the socket connection to get target VM
*/
abstract static class SocketInputStream extends InputStream {
private long fd;
public SocketInputStream(long fd) {
this.fd = fd;
}
protected abstract int read(long fd, byte[] bs, int off, int len) throws IOException;
protected abstract void close(long fd) throws IOException;
public synchronized int read() throws IOException {
byte b[] = new byte[1];
int n = this.read(b, 0, 1);
if (n == 1) {
return b[0] & 0xff;
} else {
return -1;
}
}
public synchronized int read(byte[] bs, int off, int len) throws IOException {
if ((off < 0) || (off > bs.length) || (len < 0) ||
((off + len) > bs.length) || ((off + len) < 0)) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return 0;
}
return read(fd, bs, off, len);
}
public synchronized void close() throws IOException {
if (fd != -1) {
long toClose = fd;
fd = -1;
close(toClose);
}
}
}
// -- attach timeout support
private static long defaultAttachTimeout = 10000;
private volatile long attachTimeout;
/*
* Return attach timeout based on the value of the sun.tools.attach.attachTimeout
* property, or the default timeout if the property is not set to a positive
* value.
*/
long attachTimeout() {
if (attachTimeout == 0) {
synchronized(this) {
if (attachTimeout == 0) {
try {
String s =
System.getProperty("sun.tools.attach.attachTimeout");
attachTimeout = Long.parseLong(s);
} catch (NumberFormatException ne) {
}
if (attachTimeout <= 0) {
attachTimeout = defaultAttachTimeout;
}
}
}
}
return attachTimeout;
}
protected static void checkNulls(Object... args) {
for (Object arg : args) {
if (arg instanceof String s) {
if (s.indexOf(0) >= 0) {
throw new IllegalArgumentException("illegal null character in command");
}
}
}
}
}

View file

@ -0,0 +1,167 @@
/*
* Copyright (c) 2005, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package sun.tools.attach;
import com.sun.tools.attach.VirtualMachine;
import com.sun.tools.attach.VirtualMachineDescriptor;
import com.sun.tools.attach.AttachNotSupportedException;
import java.util.ArrayList;
import java.util.List;
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
@SuppressWarnings("restricted")
public class AttachProviderImpl extends HotSpotAttachProvider {
public AttachProviderImpl() {
}
public String name() {
return "sun";
}
public String type() {
return "windows";
}
public VirtualMachine attachVirtualMachine(String vmid)
throws AttachNotSupportedException, IOException
{
// AttachNotSupportedException will be thrown if the target VM can be determined
// to be not attachable.
testAttachable(vmid);
return new VirtualMachineImpl(this, vmid);
}
public List<VirtualMachineDescriptor> listVirtualMachines() {
// If the temporary file system is secure then we use the default
// implementation, otherwise we create a list of Windows processes.
if (isTempPathSecure()) {
return super.listVirtualMachines();
} else {
return listJavaProcesses();
}
}
/**
* Returns true if the temporary file system supports security
*/
private static boolean isTempPathSecure() {
if (!wasTempPathChecked) {
synchronized (AttachProviderImpl.class) {
if (!wasTempPathChecked) {
// get the value of TMP/TEMP, ignoring UNC, and paths that
// aren't absolute
String temp = tempPath();
if ((temp != null) && (temp.length() >= 3) &&
(temp.charAt(1) == ':') && (temp.charAt(2) == '\\'))
{
// check if the volume supports security
long flags = volumeFlags(temp.substring(0, 3));
isTempPathSecure = ((flags & FS_PERSISTENT_ACLS) != 0);
}
wasTempPathChecked = true;
}
}
}
return isTempPathSecure;
}
// flag to indicate persistent ACLs are supported
private static final long FS_PERSISTENT_ACLS = 0x8L;
// indicates if we've checked the temporary file system
private static volatile boolean wasTempPathChecked;
// indicates if the temporary file system is secure (only valid when
// wasTempPathChecked is true)
private static boolean isTempPathSecure;
// returns the value of TMP/TEMP
private static native String tempPath();
// returns the flags for the given volume
private static native long volumeFlags(String volume);
/**
* Returns a list of virtual machine descriptors derived from an enumeration
* of the process list.
*/
private List<VirtualMachineDescriptor> listJavaProcesses() {
ArrayList<VirtualMachineDescriptor> list =
new ArrayList<VirtualMachineDescriptor>();
// Use localhost in the display name
String host = "localhost";
try {
host = InetAddress.getLocalHost().getHostName();
} catch (UnknownHostException uhe) {
// ignore
}
// Enumerate all processes.
// For those processes that have loaded a library named "jvm.dll"
// then we attempt to attach. If we succeed then we have a 6.0+ VM.
int processes[] = new int[1024];
int count = enumProcesses(processes, processes.length);
for (int i=0; i<count; i++) {
if (isLibraryLoadedByProcess("jvm.dll", processes[i])) {
String pid = Integer.toString(processes[i]);
try {
new VirtualMachineImpl(this, pid).detach();
// FIXME - for now we don't have an appropriate display
// name so we use pid@hostname
String name = pid + "@" + host;
list.add(new HotSpotVirtualMachineDescriptor(this, pid, name));
} catch (AttachNotSupportedException x) {
} catch (IOException ioe) {
}
}
}
return list;
}
// enumerates processes using psapi's EnumProcesses
private static native int enumProcesses(int[] processes, int max);
// indicates if a library of a given name has been loaded by a process
private static native boolean isLibraryLoadedByProcess(String library,
int processId);
// native functions in this library
static {
System.loadLibrary("attach");
}
}

View file

@ -0,0 +1,205 @@
/*
* Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package sun.tools.attach;
import com.sun.tools.attach.AgentLoadException;
import com.sun.tools.attach.AttachNotSupportedException;
import com.sun.tools.attach.AttachOperationFailedException;
import com.sun.tools.attach.spi.AttachProvider;
import java.io.IOException;
import java.io.InputStream;
import java.util.Random;
/*
* Windows implementation of HotSpotVirtualMachine
*/
@SuppressWarnings("restricted")
public class VirtualMachineImpl extends HotSpotVirtualMachine {
// the enqueue code stub (copied into each target VM)
private static byte[] stub;
private volatile long hProcess; // handle to the process
private OperationProperties props = new OperationProperties(VERSION_1); // updated in ctor
VirtualMachineImpl(AttachProvider provider, String id)
throws AttachNotSupportedException, IOException
{
super(provider, id);
int pid = Integer.parseInt(id);
hProcess = openProcess(pid);
try {
if (isAPIv2Enabled()) {
props = getDefaultProps();
} else {
// The target VM might be a pre-6.0 VM so we enqueue a "null" command
// which minimally tests that the enqueue function exists in the target
// VM.
enqueue(hProcess, stub, VERSION_1, null, null);
}
} catch (IOException x) {
throw new AttachNotSupportedException(x.getMessage());
}
}
public void detach() throws IOException {
synchronized (this) {
if (hProcess != -1) {
closeProcess(hProcess);
hProcess = -1;
}
}
}
InputStream execute(String cmd, Object ... args)
throws AgentLoadException, IOException
{
checkNulls(args);
// create a pipe using a random name
Random rnd = new Random();
int r = rnd.nextInt();
String pipeprefix = "\\\\.\\pipe\\javatool";
String pipename = pipeprefix + r;
long hPipe;
try {
hPipe = createPipe(props.version(), pipename);
} catch (IOException ce) {
// Retry with another random pipe name.
r = rnd.nextInt();
pipename = pipeprefix + r;
hPipe = createPipe(props.version(), pipename);
}
// check if we are detached - in theory it's possible that detach is invoked
// after this check but before we enqueue the command.
if (hProcess == -1) {
closePipe(hPipe);
throw new IOException("Detached from target VM");
}
// If writeCommand, below, throws IOException, we need to process it further.
IOException write_ioe = null;
try {
// enqueue the command to the process.
if (props.version() == VERSION_1) {
enqueue(hProcess, stub, props.version(), cmd, pipename, args);
} else {
// for v2 operations request contains only pipe name.
enqueue(hProcess, stub, props.version(), null, pipename);
}
// wait for the target VM to connect to the pipe.
connectPipe(hPipe);
if (props.version() == VERSION_2) {
PipeOutputStream writer = new PipeOutputStream(hPipe);
try {
writeCommand(writer, props, cmd, args);
} catch (IOException x) {
write_ioe = x;
}
}
} catch (IOException ioe) {
closePipe(hPipe);
throw ioe;
}
// create an input stream for the pipe
SocketInputStreamImpl in = new SocketInputStreamImpl(hPipe);
// Process the command completion status - this closes the stream
// and thus the pipe if an exception is to be thrown.
processCompletionStatus(write_ioe, cmd, in);
// return the input stream
return in;
}
private static class PipeOutputStream implements AttachOutputStream {
private long hPipe;
public PipeOutputStream(long hPipe) {
this.hPipe = hPipe;
}
@Override
public void write(byte[] buffer, int offset, int length) throws IOException {
VirtualMachineImpl.writePipe(hPipe, buffer, offset, length);
}
}
// An InputStream based on a pipe to the target VM
private static class SocketInputStreamImpl extends SocketInputStream {
public SocketInputStreamImpl(long fd) {
super(fd);
}
@Override
protected int read(long fd, byte[] bs, int off, int len) throws IOException {
return VirtualMachineImpl.readPipe(fd, bs, off, len);
}
@Override
protected void close(long fd) throws IOException {
VirtualMachineImpl.closePipe(fd);
}
}
//-- native methods
static native void init();
static native byte[] generateStub();
static native long openProcess(int pid) throws IOException;
static native void closeProcess(long hProcess) throws IOException;
static native long createPipe(int ver, String name) throws IOException;
static native void closePipe(long hPipe) throws IOException;
static native void connectPipe(long hPipe) throws IOException;
static native int readPipe(long hPipe, byte buf[], int off, int buflen) throws IOException;
static native void writePipe(long hPipe, byte buf[], int off, int buflen) throws IOException;
static native void enqueue(long hProcess, byte[] stub, int ver,
String cmd, String pipename, Object ... args) throws IOException;
static {
System.loadLibrary("attach");
init(); // native initialization
stub = generateStub(); // generate stub to copy into target process
}
}

View file

@ -0,0 +1,185 @@
/*
* Copyright (c) 2005, 2011, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
#include <windows.h>
#include <stdlib.h>
#include <string.h>
#include <Psapi.h>
#include "jni.h"
#include "jni_util.h"
#include "sun_tools_attach_AttachProviderImpl.h"
/*
* Class: sun_tools_attach_AttachProviderImpl
* Method: tempPath
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL
Java_sun_tools_attach_AttachProviderImpl_tempPath(JNIEnv *env, jclass cls)
{
char buf[256];
DWORD bufLen, actualLen;
jstring result = NULL;
bufLen = sizeof(buf) / sizeof(char);
actualLen = GetTempPath(bufLen, buf);
if (actualLen > 0) {
char* bufP = buf;
if (actualLen > bufLen) {
actualLen += sizeof(char);
bufP = (char*)malloc(actualLen * sizeof(char));
actualLen = GetTempPath(actualLen, bufP);
}
if (actualLen > 0) {
result = JNU_NewStringPlatform(env, bufP);
}
if (bufP != buf) {
free((void*)bufP);
}
}
return result;
}
/*
* Class: sun_tools_attach_AttachProviderImpl
* Method: volumeFlags
* Signature: ()J
*/
JNIEXPORT jlong JNICALL
Java_sun_tools_attach_AttachProviderImpl_volumeFlags(JNIEnv *env, jclass cls, jstring str)
{
jboolean isCopy;
const char* volume;
DWORD result = 0;
volume = JNU_GetStringPlatformChars(env, str, &isCopy);
if (volume != NULL) {
DWORD componentLen, flags;
BOOL res = GetVolumeInformation(volume,
NULL,
0,
NULL,
&componentLen,
&flags,
NULL,
0);
if (res != 0) {
result = flags;
}
if (isCopy) {
JNU_ReleaseStringPlatformChars(env, str, volume);
}
}
return result;
}
/*
* Class: sun_tools_attach_AttachProviderImpl
* Method: enumProcesses
* Signature: ([JI)I
*/
JNIEXPORT jint JNICALL
Java_sun_tools_attach_AttachProviderImpl_enumProcesses(JNIEnv *env, jclass cls,
jintArray arr, jint max)
{
DWORD size, bytesReturned;
DWORD* ptr;
jint result = 0;
size = max * sizeof(DWORD);
ptr = (DWORD*)malloc(size);
if (ptr != NULL) {
BOOL res = EnumProcesses(ptr, size, &bytesReturned);
if (res != 0) {
result = (jint)(bytesReturned / sizeof(DWORD));
(*env)->SetIntArrayRegion(env, arr, 0, (jsize)result, (jint*)ptr);
}
free((void*)ptr);
}
return result;
}
/*
* Class: sun_tools_attach_AttachProviderImpl
* Method: isLibraryLoadedByProcess
* Signature: (I[Ljava/lang/String;)Z
*/
JNIEXPORT jboolean JNICALL
Java_sun_tools_attach_AttachProviderImpl_isLibraryLoadedByProcess(JNIEnv *env, jclass cls,
jstring str, jint processId)
{
HANDLE hProcess;
jboolean isCopy;
const char* lib;
DWORD size, bytesReturned;
HMODULE* ptr;
jboolean result = JNI_FALSE;
hProcess = OpenProcess(PROCESS_QUERY_INFORMATION |
PROCESS_VM_READ,
FALSE, (DWORD)processId);
if (hProcess == NULL) {
return JNI_FALSE;
}
lib = JNU_GetStringPlatformChars(env, str, &isCopy);
if (lib == NULL) {
CloseHandle(hProcess);
return JNI_FALSE;
}
/*
* Enumerate the modules that the process has opened and see if we have a
* match.
*/
size = 1024 * sizeof(HMODULE);
ptr = (HMODULE*)malloc(size);
if (ptr != NULL) {
BOOL res = EnumProcessModules(hProcess, ptr, size, &bytesReturned);
if (res != 0) {
int count = bytesReturned / sizeof(HMODULE);
int i = 0;
while (i < count) {
char base[256];
BOOL res = GetModuleBaseName(hProcess, ptr[i], base, sizeof(base));
if (res != 0) {
if (strcmp(base, lib) == 0) {
result = JNI_TRUE;
break;
}
}
i++;
}
}
free((void*)ptr);
}
if (isCopy) {
JNU_ReleaseStringPlatformChars(env, str, lib);
}
CloseHandle(hProcess);
return result;
}

View file

@ -0,0 +1,691 @@
/*
* Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
#include "jni_util.h"
#include <windows.h>
#include <Sddl.h>
#include <string.h>
#include "sun_tools_attach_VirtualMachineImpl.h"
/* kernel32 */
typedef HINSTANCE (WINAPI* GetModuleHandleFunc) (LPCTSTR);
typedef FARPROC (WINAPI* GetProcAddressFunc)(HMODULE, LPCSTR);
/* only on Windows 64-bit or 32-bit application running under WOW64 */
typedef BOOL (WINAPI *IsWow64ProcessFunc) (HANDLE, PBOOL);
static GetModuleHandleFunc _GetModuleHandle;
static GetProcAddressFunc _GetProcAddress;
static IsWow64ProcessFunc _IsWow64Process;
/* psapi */
typedef BOOL (WINAPI *EnumProcessModulesFunc) (HANDLE, HMODULE *, DWORD, LPDWORD );
typedef DWORD (WINAPI *GetModuleFileNameExFunc) ( HANDLE, HMODULE, LPTSTR, DWORD );
/* exported functions in target VM */
typedef jint (WINAPI* EnqueueOperationFunc)
(const char* cmd, const char* arg1, const char* arg2, const char* arg3, const char* pipename);
typedef jint (WINAPI* EnqueueOperationFunc_v2)
(const char* pipename);
/* OpenProcess with SE_DEBUG_NAME privilege */
static HANDLE
doPrivilegedOpenProcess(DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwProcessId);
/* Converts jstring to C string, returns JNI_FALSE if the string has been truncated. */
static jboolean jstring_to_cstring(JNIEnv* env, jstring jstr, char* cstr, size_t cstr_buf_size);
/*
* Data copied to target process
*/
#define MAX_LIBNAME_LENGTH 16
#define MAX_FUNC_LENGTH 32
#define MAX_CMD_LENGTH 16
#define MAX_ARG_LENGTH 1024
#define MAX_ARGS 3
#define MAX_PIPE_NAME_LENGTH 256
typedef struct {
jint version;
GetModuleHandleFunc _GetModuleHandle;
GetProcAddressFunc _GetProcAddress;
char jvmLib[MAX_LIBNAME_LENGTH]; /* "jvm.dll" */
char func[MAX_FUNC_LENGTH];
char func_v2[MAX_FUNC_LENGTH];
char cmd[MAX_CMD_LENGTH + 1]; /* "load", "dump", ... */
char arg[MAX_ARGS][MAX_ARG_LENGTH + 1]; /* arguments to command */
char pipename[MAX_PIPE_NAME_LENGTH + 1];
} DataBlock;
/*
* Return codes from enqueue function executed in target VM
*/
#define ERR_OPEN_JVM_FAIL 200
#define ERR_GET_ENQUEUE_FUNC_FAIL 201
/*
* Declare library specific JNI_Onload entry if static build
*/
DEF_STATIC_JNI_OnLoad
/*
* Code copied to target process
*/
#pragma check_stack (off)
/* Switch off all runtime checks (checks caused by /RTC<x>). They cause the
* generated code to contain relative jumps to check functions which make
* the code position dependent. */
#pragma runtime_checks ("scu", off)
DWORD WINAPI jvm_attach_thread_func(DataBlock *pData)
{
HINSTANCE h;
h = pData->_GetModuleHandle(pData->jvmLib);
if (h == NULL) {
return ERR_OPEN_JVM_FAIL;
}
if (pData->version == 1) {
EnqueueOperationFunc addr = (EnqueueOperationFunc)(pData->_GetProcAddress(h, pData->func));
if (addr == NULL) {
return ERR_GET_ENQUEUE_FUNC_FAIL;
}
/* "null" command - does nothing in the target VM */
if (pData->cmd[0] == '\0') {
return 0;
} else {
return (*addr)(pData->cmd, pData->arg[0], pData->arg[1], pData->arg[2], pData->pipename);
}
} else if (pData->version == 2) {
EnqueueOperationFunc_v2 addr = (EnqueueOperationFunc_v2)(pData->_GetProcAddress(h, pData->func_v2));
if (addr == NULL) {
return ERR_GET_ENQUEUE_FUNC_FAIL;
}
return (*addr)(pData->pipename);
} else {
return ERR_GET_ENQUEUE_FUNC_FAIL;
}
}
/* This function marks the end of jvm_attach_thread_func. */
void jvm_attach_thread_func_end (void) {
}
#pragma check_stack
#pragma runtime_checks ("scu", restore)
/*
* Class: sun_tools_attach_VirtualMachineImpl
* Method: init
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_sun_tools_attach_VirtualMachineImpl_init
(JNIEnv *env, jclass cls)
{
// All following APIs exist on Windows XP with SP2/Windows Server 2008
_GetModuleHandle = (GetModuleHandleFunc)GetModuleHandle;
_GetProcAddress = (GetProcAddressFunc)GetProcAddress;
_IsWow64Process = (IsWow64ProcessFunc)IsWow64Process;
}
/*
* Class: sun_tools_attach_VirtualMachineImpl
* Method: generateStub
* Signature: ()[B
*/
JNIEXPORT jbyteArray JNICALL Java_sun_tools_attach_VirtualMachineImpl_generateStub
(JNIEnv *env, jclass cls)
{
/*
* We should replace this with a real stub generator at some point
*/
DWORD len;
jbyteArray array;
len = (DWORD)((LPBYTE) jvm_attach_thread_func_end - (LPBYTE) jvm_attach_thread_func);
array= (*env)->NewByteArray(env, (jsize)len);
if (array != NULL) {
(*env)->SetByteArrayRegion(env, array, 0, (jint)len, (jbyte*)&jvm_attach_thread_func);
}
return array;
}
/*
* Class: sun_tools_attach_VirtualMachineImpl
* Method: openProcess
* Signature: (I)J
*/
JNIEXPORT jlong JNICALL Java_sun_tools_attach_VirtualMachineImpl_openProcess
(JNIEnv *env, jclass cls, jint pid)
{
HANDLE hProcess = NULL;
if (pid == (jint) GetCurrentProcessId()) {
/* process is attaching to itself; get a pseudo handle instead */
hProcess = GetCurrentProcess();
/* duplicate the pseudo handle so it can be used in more contexts */
if (DuplicateHandle(hProcess, hProcess, hProcess, &hProcess,
PROCESS_ALL_ACCESS, FALSE, 0) == 0) {
/*
* Could not duplicate the handle which isn't a good sign,
* but we'll try again with OpenProcess() below.
*/
hProcess = NULL;
}
}
if (hProcess == NULL) {
/*
* Attempt to open process. If it fails then we try to enable the
* SE_DEBUG_NAME privilege and retry.
*/
hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, (DWORD)pid);
if (hProcess == NULL && GetLastError() == ERROR_ACCESS_DENIED) {
hProcess = doPrivilegedOpenProcess(PROCESS_ALL_ACCESS, FALSE,
(DWORD)pid);
}
if (hProcess == NULL) {
if (GetLastError() == ERROR_INVALID_PARAMETER) {
JNU_ThrowIOException(env, "no such process");
} else {
char err_mesg[255];
/* include the last error in the default detail message */
snprintf(err_mesg, sizeof(err_mesg), "OpenProcess(pid=%d) failed; LastError=0x%x",
(int)pid, (int)GetLastError());
JNU_ThrowIOExceptionWithLastError(env, err_mesg);
}
return (jlong)0;
}
}
/*
* On Windows we need to handle 64-bit tools trying to attach to 32-bit
* processes, which is currently not supported by this implementation.
*/
if (_IsWow64Process != NULL) {
BOOL isTarget32bit;
(*_IsWow64Process)(hProcess, &isTarget32bit);
if (isTarget32bit) {
CloseHandle(hProcess);
JNU_ThrowByName(env, "com/sun/tools/attach/AttachNotSupportedException",
"Unable to attach to 32-bit process running under WOW64");
}
}
return (jlong)hProcess;
}
/*
* Class: sun_tools_attach_VirtualMachineImpl
* Method: closeProcess
* Signature: (J)V
*/
JNIEXPORT void JNICALL Java_sun_tools_attach_VirtualMachineImpl_closeProcess
(JNIEnv *env, jclass cls, jlong hProcess)
{
CloseHandle((HANDLE)hProcess);
}
/*
* Class: sun_tools_attach_VirtualMachineImpl
* Method: createPipe
* Signature: (Ljava/lang/String;)J
*/
JNIEXPORT jlong JNICALL Java_sun_tools_attach_VirtualMachineImpl_createPipe
(JNIEnv *env, jclass cls, jint ver, jstring pipename)
{
HANDLE hPipe;
char name[MAX_PIPE_NAME_LENGTH];
SECURITY_ATTRIBUTES sa;
LPSECURITY_ATTRIBUTES lpSA = NULL;
// Custom Security Descriptor is required here to "get" Medium Integrity Level.
// In order to allow Medium Integrity Level clients to open
// and use a NamedPipe created by an High Integrity Level process.
TCHAR *szSD = TEXT("D:") // Discretionary ACL
TEXT("(A;OICI;GRGW;;;WD)") // Allow read/write to Everybody
TEXT("(A;OICI;GA;;;SY)") // Allow full control to System
TEXT("(A;OICI;GA;;;BA)"); // Allow full control to Administrators
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.bInheritHandle = FALSE;
sa.lpSecurityDescriptor = NULL;
if (ConvertStringSecurityDescriptorToSecurityDescriptor
(szSD, SDDL_REVISION_1, &(sa.lpSecurityDescriptor), NULL)) {
lpSA = &sa;
}
jstring_to_cstring(env, pipename, name, MAX_PIPE_NAME_LENGTH);
hPipe = CreateNamedPipe(
name, // pipe name
(ver == 1 ? PIPE_ACCESS_INBOUND : PIPE_ACCESS_DUPLEX) | // read or read-write access
FILE_FLAG_FIRST_PIPE_INSTANCE,
PIPE_TYPE_BYTE | // byte mode
PIPE_READMODE_BYTE |
PIPE_WAIT | // blocking mode
PIPE_REJECT_REMOTE_CLIENTS,
1, // max. instances
128, // output buffer size
8192, // input buffer size
NMPWAIT_USE_DEFAULT_WAIT, // client time-out
lpSA); // security attributes
LocalFree(sa.lpSecurityDescriptor);
if (hPipe == INVALID_HANDLE_VALUE) {
JNU_ThrowIOExceptionWithLastError(env, "CreateNamedPipe failed");
}
return (jlong)hPipe;
}
/*
* Class: sun_tools_attach_VirtualMachineImpl
* Method: closePipe
* Signature: (J)V
*/
JNIEXPORT void JNICALL Java_sun_tools_attach_VirtualMachineImpl_closePipe
(JNIEnv *env, jclass cls, jlong hPipe)
{
CloseHandle((HANDLE)hPipe);
}
/*
* Class: sun_tools_attach_VirtualMachineImpl
* Method: connectPipe
* Signature: (J)V
*/
JNIEXPORT void JNICALL Java_sun_tools_attach_VirtualMachineImpl_connectPipe
(JNIEnv *env, jclass cls, jlong hPipe)
{
BOOL fConnected;
fConnected = ConnectNamedPipe((HANDLE)hPipe, NULL) ?
TRUE : (GetLastError() == ERROR_PIPE_CONNECTED);
if (!fConnected) {
JNU_ThrowIOExceptionWithLastError(env, "ConnectNamedPipe failed");
}
}
/*
* Class: sun_tools_attach_VirtualMachineImpl
* Method: readPipe
* Signature: (J[BII)I
*/
JNIEXPORT jint JNICALL Java_sun_tools_attach_VirtualMachineImpl_readPipe
(JNIEnv *env, jclass cls, jlong hPipe, jbyteArray ba, jint off, jint baLen)
{
unsigned char buf[128];
DWORD len, nread, remaining;
BOOL fSuccess;
len = sizeof(buf);
remaining = (DWORD)(baLen - off);
if (len > remaining) {
len = remaining;
}
fSuccess = ReadFile(
(HANDLE)hPipe, // handle to pipe
buf, // buffer to receive data
len, // size of buffer
&nread, // number of bytes read
NULL); // not overlapped I/O
if (!fSuccess) {
if (GetLastError() == ERROR_BROKEN_PIPE) {
return (jint)-1;
} else {
JNU_ThrowIOExceptionWithLastError(env, "ReadFile");
}
} else {
if (nread == 0) {
return (jint)-1; // EOF
} else {
(*env)->SetByteArrayRegion(env, ba, off, (jint)nread, (jbyte *)(buf));
}
}
return (jint)nread;
}
/*
* Class: sun_tools_attach_VirtualMachineImpl
* Method: writePipe
* Signature: (J[BII)V
*/
JNIEXPORT void JNICALL Java_sun_tools_attach_VirtualMachineImpl_writePipe
(JNIEnv *env, jclass cls, jlong hPipe, jbyteArray buffer, jint offset, jint length)
{
jsize remaining = length;
do {
jbyte buf[128];
jsize len = sizeof(buf);
DWORD written;
if (len > remaining) {
len = remaining;
}
(*env)->GetByteArrayRegion(env, buffer, offset, len, buf);
BOOL fSuccess = WriteFile((HANDLE)hPipe, buf, len, &written, NULL);
if (!fSuccess) {
JNU_ThrowIOExceptionWithLastError(env, "WriteFile");
return;
}
offset += written;
remaining -= written;
} while (remaining > 0);
}
/*
* Class: sun_tools_attach_VirtualMachineImpl
* Method: enqueue
* Signature: (JZLjava/lang/String;[Ljava/lang/Object;)V
*/
JNIEXPORT void JNICALL Java_sun_tools_attach_VirtualMachineImpl_enqueue
(JNIEnv *env, jclass cls, jlong handle, jbyteArray stub, jint ver,
jstring cmd, jstring pipename, jobjectArray args)
{
DataBlock data;
DataBlock* pData;
DWORD* pCode;
DWORD stubLen;
HANDLE hProcess, hThread;
jint argsLen, i;
jbyte* stubCode;
jboolean isCopy;
/*
* Setup data to copy to target process
*/
memset(&data, 0, sizeof(data));
data.version = ver;
data._GetModuleHandle = _GetModuleHandle;
data._GetProcAddress = _GetProcAddress;
strcpy(data.jvmLib, "jvm");
strcpy(data.func, "JVM_EnqueueOperation");
strcpy(data.func_v2, "JVM_EnqueueOperation_v2");
/*
* Command and arguments
*/
if (!jstring_to_cstring(env, cmd, data.cmd, sizeof(data.cmd))) {
JNU_ThrowByName(env, "com/sun/tools/attach/AttachOperationFailedException",
"command is too long");
return;
}
argsLen = (*env)->GetArrayLength(env, args);
if (argsLen > 0) {
if (argsLen > MAX_ARGS) {
JNU_ThrowInternalError(env, "Too many arguments");
return;
}
for (i=0; i<argsLen; i++) {
jobject obj = (*env)->GetObjectArrayElement(env, args, i);
if (obj == NULL) {
data.arg[i][0] = '\0';
} else {
if (!jstring_to_cstring(env, obj, data.arg[i], sizeof(data.arg[i]))) {
JNU_ThrowByName(env, "com/sun/tools/attach/AttachOperationFailedException",
"argument is too long");
return;
}
}
if ((*env)->ExceptionCheck(env)) return;
}
}
for (i = argsLen; i < MAX_ARGS; i++) {
data.arg[i][0] = '\0';
}
/* pipe name */
if (!jstring_to_cstring(env, pipename, data.pipename, sizeof(data.pipename))) {
JNU_ThrowByName(env, "com/sun/tools/attach/AttachOperationFailedException",
"pipe name is too long");
return;
}
/*
* Allocate memory in target process for data and code stub
* (assumed aligned and matches architecture of target process)
*/
hProcess = (HANDLE)handle;
pData = (DataBlock*) VirtualAllocEx( hProcess, 0, sizeof(DataBlock), MEM_COMMIT, PAGE_READWRITE );
if (pData == NULL) {
JNU_ThrowIOExceptionWithLastError(env, "VirtualAllocEx failed");
return;
}
WriteProcessMemory( hProcess, (LPVOID)pData, (LPCVOID)&data, (SIZE_T)sizeof(DataBlock), NULL );
stubLen = (DWORD)(*env)->GetArrayLength(env, stub);
stubCode = (*env)->GetByteArrayElements(env, stub, &isCopy);
if ((*env)->ExceptionCheck(env)) return;
pCode = (PDWORD) VirtualAllocEx( hProcess, 0, stubLen, MEM_COMMIT, PAGE_EXECUTE_READWRITE );
if (pCode == NULL) {
JNU_ThrowIOExceptionWithLastError(env, "VirtualAllocEx failed");
VirtualFreeEx(hProcess, pData, 0, MEM_RELEASE);
(*env)->ReleaseByteArrayElements(env, stub, stubCode, JNI_ABORT);
return;
}
WriteProcessMemory( hProcess, (LPVOID)pCode, (LPCVOID)stubCode, (SIZE_T)stubLen, NULL );
(*env)->ReleaseByteArrayElements(env, stub, stubCode, JNI_ABORT);
/*
* Create thread in target process to execute code
*/
hThread = CreateRemoteThread( hProcess,
NULL,
0,
(LPTHREAD_START_ROUTINE) pCode,
pData,
0,
NULL );
if (hThread != NULL) {
if (WaitForSingleObject(hThread, INFINITE) != WAIT_OBJECT_0) {
JNU_ThrowIOExceptionWithLastError(env, "WaitForSingleObject failed");
} else {
DWORD exitCode;
GetExitCodeThread(hThread, &exitCode);
if (exitCode) {
switch (exitCode) {
case ERR_OPEN_JVM_FAIL :
JNU_ThrowIOException(env,
"jvm.dll not loaded by target process");
break;
case ERR_GET_ENQUEUE_FUNC_FAIL :
JNU_ThrowIOException(env,
"Unable to enqueue operation: the target VM does not support attach mechanism");
break;
default : {
char errmsg[128];
snprintf(errmsg, sizeof(errmsg), "Remote thread failed for unknown reason (%d)", exitCode);
JNU_ThrowInternalError(env, errmsg);
}
}
}
}
CloseHandle(hThread);
} else {
if (GetLastError() == ERROR_NOT_ENOUGH_MEMORY) {
//
// This error will occur when attaching to a process belonging to
// another terminal session. See "Remarks":
// http://msdn.microsoft.com/en-us/library/ms682437%28VS.85%29.aspx
//
JNU_ThrowIOException(env,
"Insufficient memory or insufficient privileges to attach");
} else {
JNU_ThrowIOExceptionWithLastError(env, "CreateRemoteThread failed");
}
}
VirtualFreeEx(hProcess, pCode, 0, MEM_RELEASE);
VirtualFreeEx(hProcess, pData, 0, MEM_RELEASE);
}
/*
* Attempts to enable the SE_DEBUG_NAME privilege and open the given process.
*/
static HANDLE
doPrivilegedOpenProcess(DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwProcessId) {
HANDLE hToken;
HANDLE hProcess = NULL;
LUID luid;
TOKEN_PRIVILEGES tp, tpPrevious;
DWORD retLength, error;
/*
* Get the access token
*/
if (!OpenThreadToken(GetCurrentThread(),
TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY,
FALSE,
&hToken)) {
if (GetLastError() != ERROR_NO_TOKEN) {
return (HANDLE)NULL;
}
/*
* No access token for the thread so impersonate the security context
* of the process.
*/
if (!ImpersonateSelf(SecurityImpersonation)) {
return (HANDLE)NULL;
}
if (!OpenThreadToken(GetCurrentThread(),
TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY,
FALSE,
&hToken)) {
return (HANDLE)NULL;
}
}
/*
* Get LUID for the privilege
*/
if(!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luid)) {
error = GetLastError();
CloseHandle(hToken);
SetLastError(error);
return (HANDLE)NULL;
}
/*
* Enable the privilege
*/
ZeroMemory(&tp, sizeof(tp));
tp.PrivilegeCount = 1;
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
tp.Privileges[0].Luid = luid;
error = 0;
if (AdjustTokenPrivileges(hToken,
FALSE,
&tp,
sizeof(TOKEN_PRIVILEGES),
&tpPrevious,
&retLength)) {
/*
* If we enabled the privilege then attempt to open the
* process.
*/
if (GetLastError() == ERROR_SUCCESS) {
hProcess = OpenProcess(dwDesiredAccess, bInheritHandle, dwProcessId);
if (hProcess == NULL) {
error = GetLastError();
}
} else {
error = ERROR_ACCESS_DENIED;
}
/*
* Revert to the previous privileges
*/
AdjustTokenPrivileges(hToken,
FALSE,
&tpPrevious,
retLength,
NULL,
NULL);
} else {
error = GetLastError();
}
/*
* Close token and restore error
*/
CloseHandle(hToken);
SetLastError(error);
return hProcess;
}
/* Converts jstring to C string, returns JNI_FALSE if the string has been truncated. */
static jboolean jstring_to_cstring(JNIEnv* env, jstring jstr, char* cstr, size_t cstr_buf_size) {
jboolean isCopy;
const char* str;
jboolean result = JNI_TRUE;
if (jstr == NULL) {
cstr[0] = '\0';
} else {
str = JNU_GetStringPlatformChars(env, jstr, &isCopy);
if ((*env)->ExceptionCheck(env)) {
return result;
}
if (strlen(str) >= cstr_buf_size) {
result = JNI_FALSE;
}
strncpy(cstr, str, cstr_buf_size);
cstr[cstr_buf_size - 1] = '\0';
if (isCopy) {
JNU_ReleaseStringPlatformChars(env, jstr, str);
}
}
return result;
}