8339289: Enhance Attach API to support arbitrary length arguments - Windows
Reviewed-by: kevinw, sspitsyn
This commit is contained in:
parent
ff165f9f0c
commit
36d71735e3
@ -32,17 +32,24 @@
|
||||
#include <signal.h> // SIGBREAK
|
||||
#include <stdio.h>
|
||||
|
||||
// The AttachListener thread services a queue of operations. It blocks in the dequeue
|
||||
// function until an operation is enqueued. A client enqueues an operation by creating
|
||||
// The AttachListener thread services a queue of operation requests. It blocks in the dequeue
|
||||
// function until a request is enqueued. A client enqueues a request by creating
|
||||
// a thread in this process using the Win32 CreateRemoteThread function. That thread
|
||||
// executes a small stub generated by the client. The stub invokes the
|
||||
// JVM_EnqueueOperation function which checks the operation parameters and enqueues
|
||||
// the operation to the queue serviced by the attach listener. The thread created by
|
||||
// JVM_EnqueueOperation or JVM_EnqueueOperation_v2 function which checks the operation parameters
|
||||
// and enqueues the operation request to the queue. The thread created by
|
||||
// the client is a native thread and is restricted to a single page of stack. To keep
|
||||
// it simple operations are pre-allocated at initialization time. An enqueue thus
|
||||
// takes a preallocated operation, populates the operation parameters, adds it to
|
||||
// it simple operation requests are pre-allocated at initialization time. An enqueue thus
|
||||
// takes a preallocated request, populates the operation parameters, adds it to
|
||||
// queue and wakes up the attach listener.
|
||||
//
|
||||
// Differences between Attach API v1 and v2:
|
||||
// In v1 (jdk6+) client calls JVM_EnqueueOperation function and passes all operation parameters
|
||||
// as arguments of the function.
|
||||
// In v2 (jdk24+) client calls JVM_EnqueueOperation_v2 function and passes only pipe name.
|
||||
// Attach listeners connects to the pipe (in read/write mode) and reads all operation parameters
|
||||
// (the same way as other platform implementations read them using sockets).
|
||||
//
|
||||
// When an operation has completed the attach listener is required to send the
|
||||
// operation result and any result data to the client. In this implementation the
|
||||
// client is a pipe server. In the enqueue operation it provides the name of pipe
|
||||
@ -55,8 +62,154 @@
|
||||
// this wasn't worth worrying about.
|
||||
|
||||
|
||||
// forward reference
|
||||
class Win32AttachOperation;
|
||||
class PipeChannel : public AttachOperation::RequestReader, public AttachOperation::ReplyWriter {
|
||||
private:
|
||||
HANDLE _hPipe;
|
||||
public:
|
||||
PipeChannel() : _hPipe(INVALID_HANDLE_VALUE) {}
|
||||
~PipeChannel() {
|
||||
close();
|
||||
}
|
||||
|
||||
bool opened() const {
|
||||
return _hPipe != INVALID_HANDLE_VALUE;
|
||||
}
|
||||
|
||||
bool open(const char* pipe, bool write_only) {
|
||||
_hPipe = ::CreateFile(pipe,
|
||||
GENERIC_WRITE | (write_only ? 0 : GENERIC_READ),
|
||||
0, // no sharing
|
||||
nullptr, // default security attributes
|
||||
OPEN_EXISTING, // opens existing pipe
|
||||
0, // default attributes
|
||||
nullptr); // no template file
|
||||
if (_hPipe == INVALID_HANDLE_VALUE) {
|
||||
log_error(attach)("could not open (%d) pipe %s", GetLastError(), pipe);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void close() {
|
||||
if (opened()) {
|
||||
CloseHandle(_hPipe);
|
||||
_hPipe = INVALID_HANDLE_VALUE;
|
||||
}
|
||||
}
|
||||
|
||||
// RequestReader
|
||||
int read(void* buffer, int size) override {
|
||||
assert(opened(), "must be");
|
||||
DWORD nread;
|
||||
BOOL fSuccess = ReadFile(_hPipe,
|
||||
buffer,
|
||||
(DWORD)size,
|
||||
&nread,
|
||||
nullptr); // not overlapped
|
||||
return fSuccess ? (int)nread : -1;
|
||||
}
|
||||
|
||||
// ReplyWriter
|
||||
int write(const void* buffer, int size) override {
|
||||
assert(opened(), "must be");
|
||||
DWORD written;
|
||||
BOOL fSuccess = WriteFile(_hPipe,
|
||||
buffer,
|
||||
(DWORD)size,
|
||||
&written,
|
||||
nullptr); // not overlapped
|
||||
return fSuccess ? (int)written : -1;
|
||||
}
|
||||
|
||||
void flush() override {
|
||||
assert(opened(), "must be");
|
||||
FlushFileBuffers(_hPipe);
|
||||
}
|
||||
};
|
||||
|
||||
class Win32AttachOperation: public AttachOperation {
|
||||
public:
|
||||
enum {
|
||||
pipe_name_max = 256 // maximum pipe name
|
||||
};
|
||||
|
||||
private:
|
||||
PipeChannel _pipe;
|
||||
|
||||
public:
|
||||
// for v1 pipe must be write-only
|
||||
void open_pipe(const char* pipe_name, bool write_only) {
|
||||
_pipe.open(pipe_name, write_only);
|
||||
}
|
||||
|
||||
bool read_request() {
|
||||
return AttachOperation::read_request(&_pipe);
|
||||
}
|
||||
|
||||
public:
|
||||
void complete(jint result, bufferedStream* result_stream) override;
|
||||
};
|
||||
|
||||
|
||||
// Win32AttachOperationRequest is an element of AttachOperation request list.
|
||||
class Win32AttachOperationRequest {
|
||||
private:
|
||||
AttachAPIVersion _ver;
|
||||
char _name[AttachOperation::name_length_max + 1];
|
||||
char _arg[AttachOperation::arg_count_max][AttachOperation::arg_length_max + 1];
|
||||
char _pipe[Win32AttachOperation::pipe_name_max + 1];
|
||||
|
||||
Win32AttachOperationRequest* _next;
|
||||
|
||||
void set_value(char* dst, const char* str, size_t dst_size) {
|
||||
if (str != nullptr) {
|
||||
assert(strlen(str) < dst_size, "exceeds maximum length");
|
||||
strncpy(dst, str, dst_size - 1);
|
||||
dst[dst_size - 1] = '\0';
|
||||
} else {
|
||||
strcpy(dst, "");
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
void set(AttachAPIVersion ver, const char* pipename,
|
||||
const char* cmd = nullptr,
|
||||
const char* arg0 = nullptr,
|
||||
const char* arg1 = nullptr,
|
||||
const char* arg2 = nullptr) {
|
||||
_ver = ver;
|
||||
set_value(_name, cmd, sizeof(_name));
|
||||
set_value(_arg[0], arg0, sizeof(_arg[0]));
|
||||
set_value(_arg[1], arg1, sizeof(_arg[1]));
|
||||
set_value(_arg[2], arg2, sizeof(_arg[2]));
|
||||
set_value(_pipe, pipename, sizeof(_pipe));
|
||||
}
|
||||
AttachAPIVersion ver() const {
|
||||
return _ver;
|
||||
}
|
||||
const char* cmd() const {
|
||||
return _name;
|
||||
}
|
||||
const char* arg(int i) const {
|
||||
return (i >= 0 && i < AttachOperation::arg_count_max) ? _arg[i] : nullptr;
|
||||
}
|
||||
const char* pipe() const {
|
||||
return _pipe;
|
||||
}
|
||||
|
||||
Win32AttachOperationRequest* next() const {
|
||||
return _next;
|
||||
}
|
||||
void set_next(Win32AttachOperationRequest* next) {
|
||||
_next = next;
|
||||
}
|
||||
|
||||
// noarg constructor as operation is preallocated
|
||||
Win32AttachOperationRequest() {
|
||||
set(ATTACH_API_V1, "<nopipe>");
|
||||
set_next(nullptr);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
class Win32AttachListener: AllStatic {
|
||||
@ -69,18 +222,18 @@ class Win32AttachListener: AllStatic {
|
||||
static HANDLE _mutex;
|
||||
|
||||
// head of preallocated operations list
|
||||
static Win32AttachOperation* _avail;
|
||||
static Win32AttachOperationRequest* _avail;
|
||||
|
||||
// head and tail of enqueue operations list
|
||||
static Win32AttachOperation* _head;
|
||||
static Win32AttachOperation* _tail;
|
||||
static Win32AttachOperationRequest* _head;
|
||||
static Win32AttachOperationRequest* _tail;
|
||||
|
||||
|
||||
static Win32AttachOperation* head() { return _head; }
|
||||
static void set_head(Win32AttachOperation* head) { _head = head; }
|
||||
static Win32AttachOperationRequest* head() { return _head; }
|
||||
static void set_head(Win32AttachOperationRequest* head) { _head = head; }
|
||||
|
||||
static Win32AttachOperation* tail() { return _tail; }
|
||||
static void set_tail(Win32AttachOperation* tail) { _tail = tail; }
|
||||
static Win32AttachOperationRequest* tail() { return _tail; }
|
||||
static void set_tail(Win32AttachOperationRequest* tail) { _tail = tail; }
|
||||
|
||||
|
||||
// A semaphore is used for communication about enqueued operations.
|
||||
@ -101,11 +254,12 @@ class Win32AttachListener: AllStatic {
|
||||
static int init();
|
||||
static HANDLE mutex() { return _mutex; }
|
||||
|
||||
static Win32AttachOperation* available() { return _avail; }
|
||||
static void set_available(Win32AttachOperation* avail) { _avail = avail; }
|
||||
static Win32AttachOperationRequest* available() { return _avail; }
|
||||
static void set_available(Win32AttachOperationRequest* avail) { _avail = avail; }
|
||||
|
||||
// enqueue an operation to the end of the list
|
||||
static int enqueue(char* cmd, char* arg1, char* arg2, char* arg3, char* pipename);
|
||||
static int enqueue(AttachAPIVersion ver, const char* cmd,
|
||||
const char* arg1, const char* arg2, const char* arg3, const char* pipename);
|
||||
|
||||
// dequeue an operation from from head of the list
|
||||
static Win32AttachOperation* dequeue();
|
||||
@ -114,48 +268,9 @@ class Win32AttachListener: AllStatic {
|
||||
// statics
|
||||
HANDLE Win32AttachListener::_mutex;
|
||||
HANDLE Win32AttachListener::_enqueued_ops_semaphore;
|
||||
Win32AttachOperation* Win32AttachListener::_avail;
|
||||
Win32AttachOperation* Win32AttachListener::_head;
|
||||
Win32AttachOperation* Win32AttachListener::_tail;
|
||||
|
||||
|
||||
// Win32AttachOperation is an AttachOperation that additionally encapsulates the name
|
||||
// of a pipe which is used to send the operation reply/output to the client.
|
||||
// Win32AttachOperation can also be linked in a list.
|
||||
|
||||
class Win32AttachOperation: public AttachOperation {
|
||||
private:
|
||||
friend class Win32AttachListener;
|
||||
|
||||
enum {
|
||||
pipe_name_max = 256 // maximum pipe name
|
||||
};
|
||||
|
||||
char _pipe[pipe_name_max + 1];
|
||||
|
||||
const char* pipe() const { return _pipe; }
|
||||
void set_pipe(const char* pipe) {
|
||||
assert(strlen(pipe) <= pipe_name_max, "exceeds maximum length of pipe name");
|
||||
os::snprintf(_pipe, sizeof(_pipe), "%s", pipe);
|
||||
}
|
||||
|
||||
HANDLE open_pipe();
|
||||
static BOOL write_pipe(HANDLE hPipe, char* buf, int len);
|
||||
|
||||
Win32AttachOperation* _next;
|
||||
|
||||
Win32AttachOperation* next() const { return _next; }
|
||||
void set_next(Win32AttachOperation* next) { _next = next; }
|
||||
|
||||
// noarg constructor as operation is preallocated
|
||||
Win32AttachOperation() : AttachOperation("<noname>") {
|
||||
set_pipe("<nopipe>");
|
||||
set_next(nullptr);
|
||||
}
|
||||
|
||||
public:
|
||||
void complete(jint result, bufferedStream* result_stream);
|
||||
};
|
||||
Win32AttachOperationRequest* Win32AttachListener::_avail;
|
||||
Win32AttachOperationRequest* Win32AttachListener::_head;
|
||||
Win32AttachOperationRequest* Win32AttachListener::_tail;
|
||||
|
||||
|
||||
// Preallocate the maximum number of operations that can be enqueued.
|
||||
@ -171,18 +286,24 @@ int Win32AttachListener::init() {
|
||||
set_available(nullptr);
|
||||
|
||||
for (int i=0; i<max_enqueued_operations; i++) {
|
||||
Win32AttachOperation* op = new Win32AttachOperation();
|
||||
Win32AttachOperationRequest* op = new Win32AttachOperationRequest();
|
||||
op->set_next(available());
|
||||
set_available(op);
|
||||
}
|
||||
|
||||
AttachListener::set_supported_version(ATTACH_API_V2);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Enqueue an operation. This is called from a native thread that is not attached to VM.
|
||||
// Also we need to be careful not to execute anything that results in more than a 4k stack.
|
||||
//
|
||||
int Win32AttachListener::enqueue(char* cmd, char* arg0, char* arg1, char* arg2, char* pipename) {
|
||||
int Win32AttachListener::enqueue(AttachAPIVersion ver, const char* cmd,
|
||||
const char* arg0, const char* arg1, const char* arg2, const char* pipename) {
|
||||
|
||||
log_debug(attach)("AttachListener::enqueue, ver = %d, cmd = %s", (int)ver, cmd);
|
||||
|
||||
// wait up to 10 seconds for listener to be up and running
|
||||
int sleep_count = 0;
|
||||
while (!AttachListener::is_initialized()) {
|
||||
@ -210,7 +331,7 @@ int Win32AttachListener::enqueue(char* cmd, char* arg0, char* arg1, char* arg2,
|
||||
}
|
||||
|
||||
// try to get an operation from the available list
|
||||
Win32AttachOperation* op = available();
|
||||
Win32AttachOperationRequest* op = available();
|
||||
if (op != nullptr) {
|
||||
set_available(op->next());
|
||||
|
||||
@ -223,11 +344,7 @@ int Win32AttachListener::enqueue(char* cmd, char* arg0, char* arg1, char* arg2,
|
||||
}
|
||||
set_tail(op);
|
||||
|
||||
op->set_name(cmd);
|
||||
op->set_arg(0, arg0);
|
||||
op->set_arg(1, arg1);
|
||||
op->set_arg(2, arg2);
|
||||
op->set_pipe(pipename);
|
||||
op->set(ver, pipename, cmd, arg0, arg1, arg2);
|
||||
|
||||
// Increment number of enqueued operations.
|
||||
// Side effect: Semaphore will be signaled and will release
|
||||
@ -236,6 +353,7 @@ int Win32AttachListener::enqueue(char* cmd, char* arg0, char* arg1, char* arg2,
|
||||
::ReleaseSemaphore(enqueued_ops_semaphore(), 1, nullptr);
|
||||
guarantee(not_exceeding_semaphore_maximum_count, "invariant");
|
||||
}
|
||||
|
||||
::ReleaseMutex(mutex());
|
||||
|
||||
return (op != nullptr) ? 0 : ATTACH_ERROR_RESOURCE;
|
||||
@ -255,107 +373,63 @@ Win32AttachOperation* Win32AttachListener::dequeue() {
|
||||
guarantee(res != WAIT_FAILED, "WaitForSingleObject failed with error code: %lu", GetLastError());
|
||||
guarantee(res == WAIT_OBJECT_0, "WaitForSingleObject failed with return value: %lu", res);
|
||||
|
||||
Win32AttachOperation* op = nullptr;
|
||||
Win32AttachOperationRequest* request = head();
|
||||
if (request != nullptr) {
|
||||
log_debug(attach)("AttachListener::dequeue, got request, ver = %d, cmd = %s", request->ver(), request->cmd());
|
||||
|
||||
Win32AttachOperation* op = head();
|
||||
if (op != nullptr) {
|
||||
set_head(op->next());
|
||||
set_head(request->next());
|
||||
if (head() == nullptr) { // list is empty
|
||||
set_tail(nullptr);
|
||||
}
|
||||
|
||||
switch (request->ver()) {
|
||||
case ATTACH_API_V1:
|
||||
op = new Win32AttachOperation();
|
||||
op->set_name(request->cmd());
|
||||
for (int i = 0; i < AttachOperation::arg_count_max; i++) {
|
||||
op->append_arg(request->arg(i));
|
||||
}
|
||||
op->open_pipe(request->pipe(), true/*write-only*/);
|
||||
break;
|
||||
case ATTACH_API_V2:
|
||||
op = new Win32AttachOperation();
|
||||
op->open_pipe(request->pipe(), false/*write-only*/);
|
||||
if (!op->read_request()) {
|
||||
log_error(attach)("AttachListener::dequeue, reading request ERROR");
|
||||
delete op;
|
||||
op = nullptr;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
log_error(attach)("AttachListener::dequeue, unsupported version: %d", request->ver(), request->cmd());
|
||||
break;
|
||||
}
|
||||
}
|
||||
// put the operation back on the available list
|
||||
request->set_next(Win32AttachListener::available());
|
||||
Win32AttachListener::set_available(request);
|
||||
|
||||
::ReleaseMutex(mutex());
|
||||
|
||||
if (op != nullptr) {
|
||||
log_debug(attach)("AttachListener::dequeue, return op: %s", op->name());
|
||||
return op;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// open the pipe to the client
|
||||
HANDLE Win32AttachOperation::open_pipe() {
|
||||
HANDLE hPipe = ::CreateFile( pipe(), // pipe name
|
||||
GENERIC_WRITE, // write only
|
||||
0, // no sharing
|
||||
nullptr, // default security attributes
|
||||
OPEN_EXISTING, // opens existing pipe
|
||||
0, // default attributes
|
||||
nullptr); // no template file
|
||||
return hPipe;
|
||||
}
|
||||
|
||||
// write to the pipe
|
||||
BOOL Win32AttachOperation::write_pipe(HANDLE hPipe, char* buf, int len) {
|
||||
do {
|
||||
DWORD nwrote;
|
||||
|
||||
BOOL fSuccess = WriteFile( hPipe, // pipe handle
|
||||
(LPCVOID)buf, // message
|
||||
(DWORD)len, // message length
|
||||
&nwrote, // bytes written
|
||||
nullptr); // not overlapped
|
||||
if (!fSuccess) {
|
||||
return fSuccess;
|
||||
}
|
||||
buf += nwrote;
|
||||
len -= nwrote;
|
||||
} while (len > 0);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// Complete the operation:
|
||||
// - open the pipe to the client
|
||||
// - write the operation result (a jint)
|
||||
// - write the operation output (the result stream)
|
||||
//
|
||||
void Win32AttachOperation::complete(jint result, bufferedStream* result_stream) {
|
||||
JavaThread* thread = JavaThread::current();
|
||||
ThreadBlockInVM tbivm(thread);
|
||||
|
||||
HANDLE hPipe = open_pipe();
|
||||
int lastError = (int)::GetLastError();
|
||||
if (hPipe != INVALID_HANDLE_VALUE) {
|
||||
BOOL fSuccess;
|
||||
write_reply(&_pipe, result, result_stream);
|
||||
|
||||
char msg[32];
|
||||
os::snprintf(msg, sizeof(msg), "%d\n", result);
|
||||
msg[sizeof(msg) - 1] = '\0';
|
||||
|
||||
fSuccess = write_pipe(hPipe, msg, (int)strlen(msg));
|
||||
if (fSuccess) {
|
||||
fSuccess = write_pipe(hPipe, (char*)result_stream->base(), (int)(result_stream->size()));
|
||||
}
|
||||
lastError = (int)::GetLastError();
|
||||
|
||||
// Need to flush buffers
|
||||
FlushFileBuffers(hPipe);
|
||||
CloseHandle(hPipe);
|
||||
|
||||
if (fSuccess) {
|
||||
log_debug(attach)("wrote result of attach operation %s to pipe %s", name(), pipe());
|
||||
} else {
|
||||
log_error(attach)("failure (%d) writing result of operation %s to pipe %s", lastError, name(), pipe());
|
||||
}
|
||||
} else {
|
||||
log_error(attach)("could not open (%d) pipe %s to send result of operation %s", lastError, pipe(), name());
|
||||
}
|
||||
|
||||
DWORD res = ::WaitForSingleObject(Win32AttachListener::mutex(), INFINITE);
|
||||
assert(res != WAIT_FAILED, "WaitForSingleObject failed with error code: %lu", GetLastError());
|
||||
assert(res == WAIT_OBJECT_0, "WaitForSingleObject failed with return value: %lu", res);
|
||||
|
||||
if (res == WAIT_OBJECT_0) {
|
||||
|
||||
// put the operation back on the available list
|
||||
set_next(Win32AttachListener::available());
|
||||
Win32AttachListener::set_available(this);
|
||||
|
||||
::ReleaseMutex(Win32AttachListener::mutex());
|
||||
}
|
||||
delete this;
|
||||
}
|
||||
|
||||
|
||||
// AttachOperation functions
|
||||
// AttachListener functions
|
||||
|
||||
AttachOperation* AttachListener::dequeue() {
|
||||
JavaThread* thread = JavaThread::current();
|
||||
@ -404,8 +478,12 @@ void AttachListener::pd_detachall() {
|
||||
// Native thread started by remote client executes this.
|
||||
extern "C" {
|
||||
JNIEXPORT jint JNICALL
|
||||
JVM_EnqueueOperation(char* cmd, char* arg0, char* arg1, char* arg2, char* pipename) {
|
||||
return (jint)Win32AttachListener::enqueue(cmd, arg0, arg1, arg2, pipename);
|
||||
}
|
||||
JVM_EnqueueOperation(char* cmd, char* arg0, char* arg1, char* arg2, char* pipename) {
|
||||
return (jint)Win32AttachListener::enqueue(ATTACH_API_V1, cmd, arg0, arg1, arg2, pipename);
|
||||
}
|
||||
|
||||
JNIEXPORT jint JNICALL
|
||||
JVM_EnqueueOperation_v2(char* pipename) {
|
||||
return (jint)Win32AttachListener::enqueue(ATTACH_API_V2, "", "", "", "", pipename);
|
||||
}
|
||||
} // extern
|
||||
|
@ -50,6 +50,38 @@
|
||||
|
||||
volatile AttachListenerState AttachListener::_state = AL_NOT_INITIALIZED;
|
||||
|
||||
AttachAPIVersion AttachListener::_supported_version = ATTACH_API_V1;
|
||||
|
||||
static bool get_bool_sys_prop(const char* name, bool default_value, TRAPS) {
|
||||
ResourceMark rm(THREAD);
|
||||
HandleMark hm(THREAD);
|
||||
|
||||
// setup the arguments to getProperty
|
||||
Handle key_str = java_lang_String::create_from_str(name, CHECK_(default_value));
|
||||
// return value
|
||||
JavaValue result(T_OBJECT);
|
||||
// public static String getProperty(String key, String def);
|
||||
JavaCalls::call_static(&result,
|
||||
vmClasses::System_klass(),
|
||||
vmSymbols::getProperty_name(),
|
||||
vmSymbols::string_string_signature(),
|
||||
key_str,
|
||||
CHECK_(default_value));
|
||||
oop value_oop = result.get_oop();
|
||||
if (value_oop != nullptr) {
|
||||
// convert Java String to utf8 string
|
||||
char* value = java_lang_String::as_utf8_string(value_oop);
|
||||
if (strcasecmp(value, "true") == 0) {
|
||||
return true;
|
||||
}
|
||||
if (strcasecmp(value, "false") == 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return default_value;
|
||||
}
|
||||
|
||||
|
||||
// Implementation of "properties" command.
|
||||
//
|
||||
// Invokes VMSupport.serializePropertiesToByteArray to serialize
|
||||
@ -351,6 +383,12 @@ static jint print_flag(AttachOperation* op, outputStream* out) {
|
||||
return JNI_OK;
|
||||
}
|
||||
|
||||
// Implementation of "getversion" command
|
||||
static jint get_version(AttachOperation* op, outputStream* out) {
|
||||
out->print("%d", (int)AttachListener::get_supported_version());
|
||||
return JNI_OK;
|
||||
}
|
||||
|
||||
// Table to map operation names to functions.
|
||||
|
||||
// names must be of length <= AttachOperation::name_length_max
|
||||
@ -365,6 +403,7 @@ static AttachOperationFunctionInfo funcs[] = {
|
||||
{ "setflag", set_flag },
|
||||
{ "printflag", print_flag },
|
||||
{ "jcmd", jcmd },
|
||||
{ "getversion", get_version },
|
||||
{ nullptr, nullptr }
|
||||
};
|
||||
|
||||
@ -472,3 +511,175 @@ void AttachListener::detachall() {
|
||||
// call the platform dependent clean-up
|
||||
pd_detachall();
|
||||
}
|
||||
|
||||
void AttachListener::set_supported_version(AttachAPIVersion version) {
|
||||
// _supported_version = version;
|
||||
const char* prop_name = "jdk.attach.compat";
|
||||
if (!get_bool_sys_prop(prop_name, false, JavaThread::current())) {
|
||||
_supported_version = version;
|
||||
}
|
||||
}
|
||||
|
||||
AttachAPIVersion AttachListener::get_supported_version() {
|
||||
return _supported_version;
|
||||
}
|
||||
|
||||
|
||||
int AttachOperation::RequestReader::read_uint() {
|
||||
const int MAX_VALUE = INT_MAX / 20;
|
||||
char ch;
|
||||
int value = 0;
|
||||
while (true) {
|
||||
int n = read(&ch, 1);
|
||||
if (n != 1) {
|
||||
// IO errors (n < 0) are logged by read().
|
||||
if (n == 0) { // EOF
|
||||
log_error(attach)("Failed to read int value: EOF");
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
if (ch == '\0') {
|
||||
return value;
|
||||
}
|
||||
if (ch < '0' || ch > '9') {
|
||||
log_error(attach)("Failed to read int value: unexpected symbol: %c", ch);
|
||||
return -1;
|
||||
}
|
||||
// Ensure there is no integer overflow.
|
||||
if (value >= MAX_VALUE) {
|
||||
log_error(attach)("Failed to read int value: too big");
|
||||
return -1;
|
||||
}
|
||||
value = value * 10 + (ch - '0');
|
||||
}
|
||||
}
|
||||
|
||||
// Reads operation name and arguments.
|
||||
// buffer_size: maximum data size;
|
||||
// min_str_count: minimum number of strings in the request (name + arguments);
|
||||
// min_read_size: minimum data size.
|
||||
bool AttachOperation::read_request_data(AttachOperation::RequestReader* reader,
|
||||
int buffer_size, int min_str_count, int min_read_size) {
|
||||
char* buffer = (char*)os::malloc(buffer_size, mtServiceability);
|
||||
int str_count = 0;
|
||||
int off = 0;
|
||||
int left = buffer_size;
|
||||
|
||||
// Read until all (expected) strings or expected bytes have been read, the buffer is full, or EOF.
|
||||
do {
|
||||
int n = reader->read(buffer + off, left);
|
||||
if (n < 0) {
|
||||
os::free(buffer);
|
||||
return false;
|
||||
}
|
||||
if (n == 0) { // EOF
|
||||
break;
|
||||
}
|
||||
if (min_str_count > 0) { // need to count arguments
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (buffer[off + i] == '\0') {
|
||||
str_count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
off += n;
|
||||
left -= n;
|
||||
} while (left > 0 && (off < min_read_size || str_count < min_str_count));
|
||||
|
||||
if (off < min_read_size || str_count < min_str_count) { // unexpected EOF
|
||||
log_error(attach)("Failed to read request: incomplete request");
|
||||
os::free(buffer);
|
||||
return false;
|
||||
}
|
||||
// Request must ends with '\0'.
|
||||
if (buffer[off - 1] != '\0') {
|
||||
log_error(attach)("Failed to read request: not terminated");
|
||||
os::free(buffer);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Parse request.
|
||||
// Command name is the 1st string.
|
||||
set_name(buffer);
|
||||
log_debug(attach)("read request: cmd = %s", buffer);
|
||||
|
||||
// Arguments.
|
||||
char* end = buffer + off;
|
||||
for (char* cur = strchr(buffer, '\0') + 1; cur < end; cur = strchr(cur, '\0') + 1) {
|
||||
log_debug(attach)("read request: arg = %s", cur);
|
||||
append_arg(cur);
|
||||
}
|
||||
|
||||
os::free(buffer);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AttachOperation::read_request(RequestReader* reader) {
|
||||
uint ver = reader->read_uint();
|
||||
int buffer_size = 0;
|
||||
// Read conditions:
|
||||
int min_str_count = 0; // expected number of strings in the request
|
||||
int min_read_size = 1; // expected size of the request data (by default 1 symbol for terminating '\0')
|
||||
switch (ver) {
|
||||
case ATTACH_API_V1: // <ver>0<cmd>0<arg>0<arg>0<arg>0
|
||||
// Always contain a command (up to name_length_max chars)
|
||||
// and arg_count_max(3) arguments (each up to arg_length_max chars).
|
||||
buffer_size = (name_length_max + 1) + arg_count_max * (arg_length_max + 1);
|
||||
min_str_count = 1 /*name*/ + arg_count_max;
|
||||
break;
|
||||
case ATTACH_API_V2: // <ver>0<size>0<cmd>0<arg>0<arg>0<arg>0
|
||||
if (AttachListener::get_supported_version() < 2) {
|
||||
log_error(attach)("Failed to read request: v2 is unsupported ot disabled");
|
||||
return false;
|
||||
}
|
||||
|
||||
// read size of the data
|
||||
buffer_size = reader->read_uint();
|
||||
if (buffer_size < 0) {
|
||||
return false;
|
||||
}
|
||||
log_debug(attach)("v2 request, data size = %d", buffer_size);
|
||||
|
||||
// Sanity check: max request size is 256K.
|
||||
if (buffer_size > 256 * 1024) {
|
||||
log_error(attach)("Failed to read request: too big");
|
||||
return false;
|
||||
}
|
||||
// Must contain exact 'buffer_size' bytes.
|
||||
min_read_size = buffer_size;
|
||||
break;
|
||||
default:
|
||||
log_error(attach)("Failed to read request: unknown version (%d)", ver);
|
||||
return false;
|
||||
}
|
||||
|
||||
return read_request_data(reader, buffer_size, min_str_count, min_read_size);
|
||||
}
|
||||
|
||||
bool AttachOperation::ReplyWriter::write_fully(const void* buffer, int size) {
|
||||
const char* buf = (const char*)buffer;
|
||||
do {
|
||||
int n = write(buf, size);
|
||||
if (n < 0) {
|
||||
return false;
|
||||
}
|
||||
buf += n;
|
||||
size -= n;
|
||||
} while (size > 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AttachOperation::write_reply(ReplyWriter* writer, jint result, bufferedStream* result_stream) {
|
||||
char msg[32];
|
||||
os::snprintf_checked(msg, sizeof(msg), "%d\n", result);
|
||||
if (!writer->write_fully(msg, (int)strlen(msg))) {
|
||||
return false;
|
||||
}
|
||||
if (!writer->write_fully(result_stream->base(), (int)result_stream->size())) {
|
||||
return false;
|
||||
}
|
||||
writer->flush();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2005, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* 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
|
||||
@ -32,6 +32,7 @@
|
||||
#include "utilities/debug.hpp"
|
||||
#include "utilities/exceptions.hpp"
|
||||
#include "utilities/globalDefinitions.hpp"
|
||||
#include "utilities/growableArray.hpp"
|
||||
#include "utilities/macros.hpp"
|
||||
#include "utilities/ostream.hpp"
|
||||
|
||||
@ -59,6 +60,20 @@ enum AttachListenerState {
|
||||
AL_INITIALIZED
|
||||
};
|
||||
|
||||
/*
|
||||
Version 1 (since jdk6): attach operations always have 3 (AttachOperation::arg_count_max)
|
||||
arguments, each up to 1024 (AttachOperation::arg_length_max) chars.
|
||||
Version 2 (since jdk24): attach operations may have any number of arguments of any length;
|
||||
for safety default implementation restricts attach operation request size by 256KB.
|
||||
To detect if target VM supports version 2, client sends "getversion" command.
|
||||
Old VM reports "Operation not recognized" error, newer VM reports version supported by the implementation.
|
||||
If the target VM does not support version 2, client uses version 1 to enqueue operations.
|
||||
*/
|
||||
enum AttachAPIVersion: int {
|
||||
ATTACH_API_V1 = 1,
|
||||
ATTACH_API_V2 = 2
|
||||
};
|
||||
|
||||
class AttachListenerThread : public JavaThread {
|
||||
private:
|
||||
static void thread_entry(JavaThread* thread, TRAPS);
|
||||
@ -93,7 +108,12 @@ class AttachListener: AllStatic {
|
||||
private:
|
||||
static volatile AttachListenerState _state;
|
||||
|
||||
static AttachAPIVersion _supported_version;
|
||||
|
||||
public:
|
||||
static void set_supported_version(AttachAPIVersion version);
|
||||
static AttachAPIVersion get_supported_version();
|
||||
|
||||
static void set_state(AttachListenerState new_state) {
|
||||
Atomic::store(&_state, new_state);
|
||||
}
|
||||
@ -136,8 +156,9 @@ class AttachListener: AllStatic {
|
||||
};
|
||||
|
||||
#if INCLUDE_SERVICES
|
||||
class AttachOperation: public CHeapObj<mtInternal> {
|
||||
public:
|
||||
class AttachOperation: public CHeapObj<mtServiceability> {
|
||||
public:
|
||||
// v1 constants
|
||||
enum {
|
||||
name_length_max = 16, // maximum length of name
|
||||
arg_length_max = 1024, // maximum length of argument
|
||||
@ -148,51 +169,100 @@ class AttachOperation: public CHeapObj<mtInternal> {
|
||||
// clients detach
|
||||
static char* detachall_operation_name() { return (char*)"detachall"; }
|
||||
|
||||
private:
|
||||
char _name[name_length_max+1];
|
||||
char _arg[arg_count_max][arg_length_max+1];
|
||||
private:
|
||||
char* _name;
|
||||
GrowableArrayCHeap<char*, mtServiceability> _args;
|
||||
|
||||
public:
|
||||
const char* name() const { return _name; }
|
||||
static char* copy_str(const char* value) {
|
||||
return value == nullptr ? nullptr : os::strdup(value, mtServiceability);
|
||||
}
|
||||
|
||||
public:
|
||||
const char* name() const { return _name; }
|
||||
|
||||
// set the operation name
|
||||
void set_name(const char* name) {
|
||||
assert(strlen(name) <= name_length_max, "exceeds maximum name length");
|
||||
size_t len = MIN2(strlen(name), (size_t)name_length_max);
|
||||
memcpy(_name, name, len);
|
||||
_name[len] = '\0';
|
||||
os::free(_name);
|
||||
_name = copy_str(name);
|
||||
}
|
||||
|
||||
int arg_count() const {
|
||||
return _args.length();
|
||||
}
|
||||
|
||||
// get an argument value
|
||||
const char* arg(int i) const {
|
||||
assert(i>=0 && i<arg_count_max, "invalid argument index");
|
||||
return _arg[i];
|
||||
// Historically clients expect empty string for absent or null arguments.
|
||||
if (i >= _args.length() || _args.at(i) == nullptr) {
|
||||
static char empty_str[] = "";
|
||||
return empty_str;
|
||||
}
|
||||
return _args.at(i);
|
||||
}
|
||||
|
||||
// appends an argument
|
||||
void append_arg(const char* arg) {
|
||||
_args.append(copy_str(arg));
|
||||
}
|
||||
|
||||
// set an argument value
|
||||
void set_arg(int i, char* arg) {
|
||||
assert(i>=0 && i<arg_count_max, "invalid argument index");
|
||||
if (arg == nullptr) {
|
||||
_arg[i][0] = '\0';
|
||||
} else {
|
||||
assert(strlen(arg) <= arg_length_max, "exceeds maximum argument length");
|
||||
size_t len = MIN2(strlen(arg), (size_t)arg_length_max);
|
||||
memcpy(_arg[i], arg, len);
|
||||
_arg[i][len] = '\0';
|
||||
void set_arg(int i, const char* arg) {
|
||||
_args.at_put_grow(i, copy_str(arg), nullptr);
|
||||
}
|
||||
|
||||
// create an v1 operation of a given name (for compatibility, deprecated)
|
||||
AttachOperation(const char* name) : _name(nullptr) {
|
||||
set_name(name);
|
||||
for (int i = 0; i < arg_count_max; i++) {
|
||||
set_arg(i, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
// create an operation of a given name
|
||||
AttachOperation(const char* name) {
|
||||
set_name(name);
|
||||
for (int i=0; i<arg_count_max; i++) {
|
||||
set_arg(i, nullptr);
|
||||
AttachOperation() : _name(nullptr) {
|
||||
}
|
||||
|
||||
virtual ~AttachOperation() {
|
||||
os::free(_name);
|
||||
for (GrowableArrayIterator<char*> it = _args.begin(); it != _args.end(); ++it) {
|
||||
os::free(*it);
|
||||
}
|
||||
}
|
||||
|
||||
// complete operation by sending result code and any result data to the client
|
||||
virtual void complete(jint result, bufferedStream* result_stream) = 0;
|
||||
|
||||
// Helper classes/methods for platform-specific implementations.
|
||||
class RequestReader {
|
||||
public:
|
||||
// Returns number of bytes read,
|
||||
// 0 on EOF, negative value on error.
|
||||
virtual int read(void* buffer, int size) = 0;
|
||||
|
||||
// Reads unsigned value, returns -1 on error.
|
||||
int read_uint();
|
||||
};
|
||||
|
||||
// Reads standard operation request (v1 or v2).
|
||||
bool read_request(RequestReader* reader);
|
||||
|
||||
class ReplyWriter {
|
||||
public:
|
||||
// Returns number of bytes written, negative value on error.
|
||||
virtual int write(const void* buffer, int size) = 0;
|
||||
|
||||
virtual void flush() {}
|
||||
|
||||
bool write_fully(const void* buffer, int size);
|
||||
};
|
||||
|
||||
// Writes standard operation reply (to be called from 'complete' method).
|
||||
bool write_reply(ReplyWriter* writer, jint result, bufferedStream* result_stream);
|
||||
|
||||
private:
|
||||
bool read_request_data(AttachOperation::RequestReader* reader, int buffer_size, int min_str_count, int min_read_size);
|
||||
|
||||
};
|
||||
|
||||
#endif // INCLUDE_SERVICES
|
||||
|
||||
#endif // SHARE_SERVICES_ATTACHLISTENER_HPP
|
||||
|
@ -42,6 +42,8 @@ import java.security.PrivilegedAction;
|
||||
import java.util.Properties;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/*
|
||||
* The HotSpot implementation of com.sun.tools.attach.VirtualMachine.
|
||||
*/
|
||||
@ -102,7 +104,7 @@ public abstract class HotSpotVirtualMachine extends VirtualMachine {
|
||||
agentLibrary,
|
||||
isAbsolute ? "true" : "false",
|
||||
options);
|
||||
String result = readErrorMessage(in);
|
||||
String result = readMessage(in);
|
||||
if (result.isEmpty()) {
|
||||
throw new AgentLoadException("Target VM did not respond");
|
||||
} else if (result.startsWith(msgPrefix)) {
|
||||
@ -327,6 +329,45 @@ public abstract class HotSpotVirtualMachine extends VirtualMachine {
|
||||
}
|
||||
}
|
||||
|
||||
// Attach API version support
|
||||
protected static final int VERSION_1 = 1;
|
||||
protected static final int VERSION_2 = 2;
|
||||
|
||||
/*
|
||||
* Detects Attach API version supported by target VM.
|
||||
*/
|
||||
protected int detectVersion() throws IOException {
|
||||
try {
|
||||
InputStream reply = execute("getversion");
|
||||
String message = readMessage(reply);
|
||||
reply.close();
|
||||
try {
|
||||
int supportedVersion = Integer.parseUnsignedInt(message);
|
||||
// we expect only VERSION_2
|
||||
if (supportedVersion == VERSION_2) {
|
||||
return VERSION_2;
|
||||
}
|
||||
} catch (NumberFormatException nfe) {
|
||||
// bad reply - fallback to VERSION_1
|
||||
}
|
||||
} catch (AttachOperationFailedException | AgentLoadException ex) {
|
||||
// the command is not supported, the VM supports VERSION_1 only
|
||||
}
|
||||
return 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.
|
||||
try {
|
||||
String value = System.getProperty("jdk.attach.compat");
|
||||
return !("true".equalsIgnoreCase(value));
|
||||
} catch (SecurityException se) {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Utility method to read an 'int' from the input stream. Ideally
|
||||
@ -367,7 +408,7 @@ public abstract class HotSpotVirtualMachine extends VirtualMachine {
|
||||
/*
|
||||
* Utility method to read data into a String.
|
||||
*/
|
||||
String readErrorMessage(InputStream in) throws IOException {
|
||||
String readMessage(InputStream in) throws IOException {
|
||||
String s;
|
||||
StringBuilder message = new StringBuilder();
|
||||
BufferedReader br = new BufferedReader(new InputStreamReader(in));
|
||||
@ -400,7 +441,7 @@ public abstract class HotSpotVirtualMachine extends VirtualMachine {
|
||||
}
|
||||
if (completionStatus != 0) {
|
||||
// read from the stream and use that as the error message
|
||||
String message = readErrorMessage(sis);
|
||||
String message = readMessage(sis);
|
||||
sis.close();
|
||||
|
||||
// In the event of a protocol mismatch then the target VM
|
||||
@ -417,6 +458,51 @@ public abstract class HotSpotVirtualMachine extends VirtualMachine {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 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, int ver, String cmd, Object ... args) throws IOException {
|
||||
writeString(writer, ver);
|
||||
if (ver == VERSION_2) {
|
||||
// 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 = ver == 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
|
||||
*/
|
||||
|
@ -26,6 +26,7 @@ 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;
|
||||
@ -42,6 +43,7 @@ public class VirtualMachineImpl extends HotSpotVirtualMachine {
|
||||
private static byte[] stub;
|
||||
|
||||
private volatile long hProcess; // handle to the process
|
||||
private int ver = VERSION_1; // updated in ctor depending on detectVersion result
|
||||
|
||||
VirtualMachineImpl(AttachProvider provider, String id)
|
||||
throws AttachNotSupportedException, IOException
|
||||
@ -51,11 +53,15 @@ public class VirtualMachineImpl extends HotSpotVirtualMachine {
|
||||
int pid = Integer.parseInt(id);
|
||||
hProcess = openProcess(pid);
|
||||
|
||||
// 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.
|
||||
try {
|
||||
enqueue(hProcess, stub, null, null);
|
||||
if (isAPIv2Enabled()) {
|
||||
ver = detectVersion();
|
||||
} 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());
|
||||
}
|
||||
@ -73,7 +79,6 @@ public class VirtualMachineImpl extends HotSpotVirtualMachine {
|
||||
InputStream execute(String cmd, Object ... args)
|
||||
throws AgentLoadException, IOException
|
||||
{
|
||||
assert args.length <= 3; // includes null
|
||||
checkNulls(args);
|
||||
|
||||
// create a pipe using a random name
|
||||
@ -83,12 +88,12 @@ public class VirtualMachineImpl extends HotSpotVirtualMachine {
|
||||
String pipename = pipeprefix + r;
|
||||
long hPipe;
|
||||
try {
|
||||
hPipe = createPipe(pipename);
|
||||
hPipe = createPipe(ver, pipename);
|
||||
} catch (IOException ce) {
|
||||
// Retry with another random pipe name.
|
||||
r = rnd.nextInt();
|
||||
pipename = pipeprefix + r;
|
||||
hPipe = createPipe(pipename);
|
||||
hPipe = createPipe(ver, pipename);
|
||||
}
|
||||
|
||||
// check if we are detached - in theory it's possible that detach is invoked
|
||||
@ -99,18 +104,34 @@ public class VirtualMachineImpl extends HotSpotVirtualMachine {
|
||||
}
|
||||
|
||||
try {
|
||||
// enqueue the command to the process
|
||||
enqueue(hProcess, stub, cmd, pipename, args);
|
||||
// enqueue the command to the process.
|
||||
if (ver == VERSION_1) {
|
||||
enqueue(hProcess, stub, ver, cmd, pipename, args);
|
||||
} else {
|
||||
// for v2 operations request contains only pipe name.
|
||||
enqueue(hProcess, stub, ver, null, pipename);
|
||||
}
|
||||
|
||||
// wait for command to complete - process will connect with the
|
||||
// completion status
|
||||
// wait for the target VM to connect to the pipe.
|
||||
connectPipe(hPipe);
|
||||
|
||||
IOException ioe = null;
|
||||
|
||||
if (ver == VERSION_2) {
|
||||
PipeOutputStream writer = new PipeOutputStream(hPipe);
|
||||
|
||||
try {
|
||||
writeCommand(writer, ver, cmd, args);
|
||||
} catch (IOException x) {
|
||||
ioe = x;
|
||||
}
|
||||
}
|
||||
|
||||
// create an input stream for the pipe
|
||||
SocketInputStreamImpl in = new SocketInputStreamImpl(hPipe);
|
||||
|
||||
// Process the command completion status
|
||||
processCompletionStatus(null, cmd, in);
|
||||
processCompletionStatus(ioe, cmd, in);
|
||||
|
||||
// return the input stream
|
||||
return in;
|
||||
@ -121,6 +142,17 @@ public class VirtualMachineImpl extends HotSpotVirtualMachine {
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
@ -149,7 +181,7 @@ public class VirtualMachineImpl extends HotSpotVirtualMachine {
|
||||
|
||||
static native void closeProcess(long hProcess) throws IOException;
|
||||
|
||||
static native long createPipe(String name) throws IOException;
|
||||
static native long createPipe(int ver, String name) throws IOException;
|
||||
|
||||
static native void closePipe(long hPipe) throws IOException;
|
||||
|
||||
@ -157,7 +189,9 @@ public class VirtualMachineImpl extends HotSpotVirtualMachine {
|
||||
|
||||
static native int readPipe(long hPipe, byte buf[], int off, int buflen) throws IOException;
|
||||
|
||||
static native void enqueue(long hProcess, byte[] stub,
|
||||
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 {
|
||||
|
@ -46,9 +46,11 @@ static IsWow64ProcessFunc _IsWow64Process;
|
||||
typedef BOOL (WINAPI *EnumProcessModulesFunc) (HANDLE, HMODULE *, DWORD, LPDWORD );
|
||||
typedef DWORD (WINAPI *GetModuleFileNameExFunc) ( HANDLE, HMODULE, LPTSTR, DWORD );
|
||||
|
||||
/* exported function in target VM */
|
||||
/* 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
|
||||
@ -70,11 +72,13 @@ static jboolean jstring_to_cstring(JNIEnv* env, jstring jstr, char* cstr, size_t
|
||||
#define MAX_PIPE_NAME_LENGTH 256
|
||||
|
||||
typedef struct {
|
||||
jint version;
|
||||
GetModuleHandleFunc _GetModuleHandle;
|
||||
GetProcAddressFunc _GetProcAddress;
|
||||
char jvmLib[MAX_LIBNAME_LENGTH]; /* "jvm.dll" */
|
||||
char func1[MAX_FUNC_LENGTH];
|
||||
char func2[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];
|
||||
@ -102,27 +106,36 @@ DEF_STATIC_JNI_OnLoad
|
||||
DWORD WINAPI jvm_attach_thread_func(DataBlock *pData)
|
||||
{
|
||||
HINSTANCE h;
|
||||
EnqueueOperationFunc addr;
|
||||
|
||||
h = pData->_GetModuleHandle(pData->jvmLib);
|
||||
if (h == NULL) {
|
||||
return ERR_OPEN_JVM_FAIL;
|
||||
}
|
||||
|
||||
addr = (EnqueueOperationFunc)(pData->_GetProcAddress(h, pData->func1));
|
||||
if (addr == NULL) {
|
||||
addr = (EnqueueOperationFunc)(pData->_GetProcAddress(h, pData->func2));
|
||||
}
|
||||
if (addr == NULL) {
|
||||
if (pData->version == 1) {
|
||||
EnqueueOperationFunc addr = (EnqueueOperationFunc)(pData->_GetProcAddress(h, pData->func1));
|
||||
if (addr == NULL) {
|
||||
addr = (EnqueueOperationFunc)(pData->_GetProcAddress(h, pData->func2));
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
/* "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);
|
||||
}
|
||||
}
|
||||
|
||||
/* This function marks the end of jvm_attach_thread_func. */
|
||||
@ -261,7 +274,7 @@ JNIEXPORT void JNICALL Java_sun_tools_attach_VirtualMachineImpl_closeProcess
|
||||
* Signature: (Ljava/lang/String;)J
|
||||
*/
|
||||
JNIEXPORT jlong JNICALL Java_sun_tools_attach_VirtualMachineImpl_createPipe
|
||||
(JNIEnv *env, jclass cls, jstring pipename)
|
||||
(JNIEnv *env, jclass cls, jint ver, jstring pipename)
|
||||
{
|
||||
HANDLE hPipe;
|
||||
char name[MAX_PIPE_NAME_LENGTH];
|
||||
@ -289,7 +302,8 @@ JNIEXPORT jlong JNICALL Java_sun_tools_attach_VirtualMachineImpl_createPipe
|
||||
|
||||
hPipe = CreateNamedPipe(
|
||||
name, // pipe name
|
||||
PIPE_ACCESS_INBOUND, // read access
|
||||
ver == 1 ? PIPE_ACCESS_INBOUND // read access
|
||||
: PIPE_ACCESS_DUPLEX, // read-write access
|
||||
PIPE_TYPE_BYTE | // byte mode
|
||||
PIPE_READMODE_BYTE |
|
||||
PIPE_WAIT, // blocking mode
|
||||
@ -377,14 +391,46 @@ JNIEXPORT jint JNICALL Java_sun_tools_attach_VirtualMachineImpl_readPipe
|
||||
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, jstring cmd,
|
||||
jstring pipename, jobjectArray args)
|
||||
(JNIEnv *env, jclass cls, jlong handle, jbyteArray stub, jint ver,
|
||||
jstring cmd, jstring pipename, jobjectArray args)
|
||||
{
|
||||
DataBlock data;
|
||||
DataBlock* pData;
|
||||
@ -399,12 +445,15 @@ JNIEXPORT void JNICALL Java_sun_tools_attach_VirtualMachineImpl_enqueue
|
||||
* 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.func1, "JVM_EnqueueOperation");
|
||||
strcpy(data.func2, "_JVM_EnqueueOperation@20");
|
||||
strcpy(data.func_v2, "JVM_EnqueueOperation_v2");
|
||||
|
||||
/*
|
||||
* Command and arguments
|
||||
|
@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Copyright (c) 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.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @summary Basic compatibility test for Attach API v2
|
||||
* @bug 8219896
|
||||
* @library /test/lib
|
||||
* @modules jdk.attach/sun.tools.attach
|
||||
*
|
||||
* @run main/othervm -Xlog:attach=trace CompatTest
|
||||
* @run main/othervm -Xlog:attach=trace -Djdk.attach.compat=true CompatTest
|
||||
*/
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.IOException;
|
||||
|
||||
import com.sun.tools.attach.VirtualMachine;
|
||||
import sun.tools.attach.HotSpotVirtualMachine;
|
||||
|
||||
import jdk.test.lib.apps.LingeredApp;
|
||||
|
||||
public class CompatTest {
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
// if the test (client part) in the "compat" mode
|
||||
boolean clientCompat = "true".equals(System.getProperty("jdk.attach.compat"));
|
||||
System.out.println("Client is in compat mode: " + clientCompat);
|
||||
LingeredApp app = null;
|
||||
try {
|
||||
app = LingeredApp.startApp("-Xlog:attach=trace");
|
||||
test(app, clientCompat);
|
||||
} finally {
|
||||
LingeredApp.stopApp(app);
|
||||
}
|
||||
|
||||
try {
|
||||
app = LingeredApp.startApp("-Xlog:attach=trace", "-Djdk.attach.compat=true");
|
||||
// target VM in "compat" mode, always expect failure
|
||||
test(app, true);
|
||||
} finally {
|
||||
LingeredApp.stopApp(app);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// The test uses HotSpotVirtualMachine.setFlag method with long flag value.
|
||||
// For attach API v1 an exception is expected to be thrown (argument cannot be longer than 1024 characters).
|
||||
private static String flagName = "HeapDumpPath";
|
||||
// long for v1
|
||||
private static String flagValue = "X" + "A".repeat(1024) + "X";
|
||||
|
||||
private static void test(LingeredApp app, boolean expectFailure) throws Exception {
|
||||
System.out.println("======== Start ========");
|
||||
|
||||
HotSpotVirtualMachine vm = (HotSpotVirtualMachine)VirtualMachine.attach(String.valueOf(app.getPid()));
|
||||
|
||||
BufferedReader replyReader = null;
|
||||
try {
|
||||
replyReader = new BufferedReader(new InputStreamReader(
|
||||
vm.setFlag(flagName, flagValue)));
|
||||
|
||||
if (expectFailure) {
|
||||
throw new RuntimeException("No expected exception is thrown");
|
||||
}
|
||||
|
||||
String line;
|
||||
while ((line = replyReader.readLine()) != null) {
|
||||
System.out.println("setFlag reply: " + line);
|
||||
}
|
||||
replyReader.close();
|
||||
|
||||
} catch (IOException ex) {
|
||||
System.out.println("OK: setFlag thrown expected exception:");
|
||||
ex.printStackTrace(System.out);
|
||||
} finally {
|
||||
vm.detach();
|
||||
}
|
||||
|
||||
System.out.println("======== End ========");
|
||||
System.out.println();
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user