mirror of
https://github.com/openjdk/jdk.git
synced 2026-03-14 09:53:18 +00:00
8352392: AIX: implement attach API v2 and streaming output
Reviewed-by: mdoerr, jkern, amenkov
This commit is contained in:
parent
1c2a5533f4
commit
41d4a0d7bd
@ -73,16 +73,7 @@ class AixAttachListener: AllStatic {
|
||||
|
||||
static bool _atexit_registered;
|
||||
|
||||
// reads a request from the given connected socket
|
||||
static AixAttachOperation* read_request(int s);
|
||||
|
||||
public:
|
||||
enum {
|
||||
ATTACH_PROTOCOL_VER = 1 // protocol version
|
||||
};
|
||||
enum {
|
||||
ATTACH_ERROR_BADVERSION = 101 // error codes
|
||||
};
|
||||
|
||||
static void set_path(char* path) {
|
||||
if (path == nullptr) {
|
||||
@ -107,25 +98,65 @@ class AixAttachListener: AllStatic {
|
||||
static void set_shutdown(bool shutdown) { _shutdown = shutdown; }
|
||||
static bool is_shutdown() { return _shutdown; }
|
||||
|
||||
// write the given buffer to a socket
|
||||
static int write_fully(int s, char* buf, size_t len);
|
||||
|
||||
static AixAttachOperation* dequeue();
|
||||
};
|
||||
|
||||
class SocketChannel : public AttachOperation::RequestReader, public AttachOperation::ReplyWriter {
|
||||
private:
|
||||
int _socket;
|
||||
public:
|
||||
SocketChannel(int socket) : _socket(socket) {}
|
||||
~SocketChannel() {
|
||||
close();
|
||||
}
|
||||
|
||||
bool opened() const {
|
||||
return _socket != -1;
|
||||
}
|
||||
|
||||
void close() {
|
||||
if (opened()) {
|
||||
// SHUT_RDWR is not available
|
||||
::shutdown(_socket, 2);
|
||||
::close(_socket);
|
||||
_socket = -1;
|
||||
}
|
||||
}
|
||||
|
||||
// RequestReader
|
||||
int read(void* buffer, int size) override {
|
||||
ssize_t n;
|
||||
RESTARTABLE(::read(_socket, buffer, (size_t)size), n);
|
||||
return checked_cast<int>(n);
|
||||
}
|
||||
|
||||
// ReplyWriter
|
||||
int write(const void* buffer, int size) override {
|
||||
ssize_t n;
|
||||
RESTARTABLE(::write(_socket, buffer, size), n);
|
||||
return checked_cast<int>(n);
|
||||
}
|
||||
|
||||
void flush() override {
|
||||
}
|
||||
};
|
||||
|
||||
class AixAttachOperation: public AttachOperation {
|
||||
private:
|
||||
// the connection to the client
|
||||
int _socket;
|
||||
SocketChannel _socket_channel;
|
||||
|
||||
public:
|
||||
void complete(jint res, bufferedStream* st);
|
||||
AixAttachOperation(int socket) : AttachOperation(), _socket_channel(socket) {}
|
||||
|
||||
void set_socket(int s) { _socket = s; }
|
||||
int socket() const { return _socket; }
|
||||
void complete(jint res, bufferedStream* st) override;
|
||||
|
||||
AixAttachOperation(char* name) : AttachOperation(name) {
|
||||
set_socket(-1);
|
||||
ReplyWriter* get_reply_writer() override {
|
||||
return &_socket_channel;
|
||||
}
|
||||
|
||||
bool read_request() {
|
||||
return _socket_channel.read_request(this, &_socket_channel);
|
||||
}
|
||||
};
|
||||
|
||||
@ -137,34 +168,6 @@ bool AixAttachListener::_atexit_registered = false;
|
||||
// Shutdown marker to prevent accept blocking during clean-up
|
||||
volatile bool AixAttachListener::_shutdown = false;
|
||||
|
||||
// Supporting class to help split a buffer into individual components
|
||||
class ArgumentIterator : public StackObj {
|
||||
private:
|
||||
char* _pos;
|
||||
char* _end;
|
||||
public:
|
||||
ArgumentIterator(char* arg_buffer, size_t arg_size) {
|
||||
_pos = arg_buffer;
|
||||
_end = _pos + arg_size - 1;
|
||||
}
|
||||
char* next() {
|
||||
if (*_pos == '\0') {
|
||||
// advance the iterator if possible (null arguments)
|
||||
if (_pos < _end) {
|
||||
_pos += 1;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
char* res = _pos;
|
||||
char* next_pos = strchr(_pos, '\0');
|
||||
if (next_pos < _end) {
|
||||
next_pos++;
|
||||
}
|
||||
_pos = next_pos;
|
||||
return res;
|
||||
}
|
||||
};
|
||||
|
||||
// On AIX if sockets block until all data has been transmitted
|
||||
// successfully in some communication domains a socket "close" may
|
||||
// never complete. We have to take care that after the socket shutdown
|
||||
@ -258,106 +261,6 @@ int AixAttachListener::init() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Given a socket that is connected to a peer we read the request and
|
||||
// create an AttachOperation. As the socket is blocking there is potential
|
||||
// for a denial-of-service if the peer does not response. However this happens
|
||||
// after the peer credentials have been checked and in the worst case it just
|
||||
// means that the attach listener thread is blocked.
|
||||
//
|
||||
AixAttachOperation* AixAttachListener::read_request(int s) {
|
||||
char ver_str[8];
|
||||
os::snprintf_checked(ver_str, sizeof(ver_str), "%d", ATTACH_PROTOCOL_VER);
|
||||
|
||||
// The request is a sequence of strings so we first figure out the
|
||||
// expected count and the maximum possible length of the request.
|
||||
// The request is:
|
||||
// <ver>0<cmd>0<arg>0<arg>0<arg>0
|
||||
// where <ver> is the protocol version (1), <cmd> is the command
|
||||
// name ("load", "datadump", ...), and <arg> is an argument
|
||||
int expected_str_count = 2 + AttachOperation::arg_count_max;
|
||||
const size_t max_len = (sizeof(ver_str) + 1) + (AttachOperation::name_length_max + 1) +
|
||||
AttachOperation::arg_count_max*(AttachOperation::arg_length_max + 1);
|
||||
|
||||
char buf[max_len];
|
||||
int str_count = 0;
|
||||
|
||||
// Read until all (expected) strings have been read, the buffer is
|
||||
// full, or EOF.
|
||||
|
||||
size_t off = 0;
|
||||
size_t left = max_len;
|
||||
|
||||
do {
|
||||
ssize_t n;
|
||||
// Don't block on interrupts because this will
|
||||
// hang in the clean-up when shutting down.
|
||||
n = read(s, buf+off, left);
|
||||
assert(n <= checked_cast<ssize_t>(left), "buffer was too small, impossible!");
|
||||
buf[max_len - 1] = '\0';
|
||||
if (n == -1) {
|
||||
return nullptr; // reset by peer or other error
|
||||
}
|
||||
if (n == 0) {
|
||||
break;
|
||||
}
|
||||
for (int i=0; i<n; i++) {
|
||||
if (buf[off+i] == 0) {
|
||||
// EOS found
|
||||
str_count++;
|
||||
|
||||
// The first string is <ver> so check it now to
|
||||
// check for protocol mismatch
|
||||
if (str_count == 1) {
|
||||
if ((strlen(buf) != strlen(ver_str)) ||
|
||||
(atoi(buf) != ATTACH_PROTOCOL_VER)) {
|
||||
char msg[32];
|
||||
os::snprintf_checked(msg, sizeof(msg), "%d\n", ATTACH_ERROR_BADVERSION);
|
||||
write_fully(s, msg, strlen(msg));
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
off += n;
|
||||
left -= n;
|
||||
} while (left > 0 && str_count < expected_str_count);
|
||||
|
||||
if (str_count != expected_str_count) {
|
||||
return nullptr; // incomplete request
|
||||
}
|
||||
|
||||
// parse request
|
||||
|
||||
ArgumentIterator args(buf, (max_len)-left);
|
||||
|
||||
// version already checked
|
||||
char* v = args.next();
|
||||
|
||||
char* name = args.next();
|
||||
if (name == nullptr || strlen(name) > AttachOperation::name_length_max) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
AixAttachOperation* op = new AixAttachOperation(name);
|
||||
|
||||
for (int i=0; i<AttachOperation::arg_count_max; i++) {
|
||||
char* arg = args.next();
|
||||
if (arg == nullptr) {
|
||||
op->set_arg(i, nullptr);
|
||||
} else {
|
||||
if (strlen(arg) > AttachOperation::arg_length_max) {
|
||||
delete op;
|
||||
return nullptr;
|
||||
}
|
||||
op->set_arg(i, arg);
|
||||
}
|
||||
}
|
||||
|
||||
op->set_socket(s);
|
||||
return op;
|
||||
}
|
||||
|
||||
|
||||
// Dequeue an operation
|
||||
//
|
||||
// In the Aix implementation there is only a single operation and clients
|
||||
@ -402,9 +305,9 @@ AixAttachOperation* AixAttachListener::dequeue() {
|
||||
}
|
||||
|
||||
// peer credential look okay so we read the request
|
||||
AixAttachOperation* op = read_request(s);
|
||||
if (op == nullptr) {
|
||||
::close(s);
|
||||
AixAttachOperation* op = new AixAttachOperation(s);
|
||||
if (!op->read_request()) {
|
||||
delete op;
|
||||
continue;
|
||||
} else {
|
||||
return op;
|
||||
@ -412,21 +315,6 @@ AixAttachOperation* AixAttachListener::dequeue() {
|
||||
}
|
||||
}
|
||||
|
||||
// write the given buffer to the socket
|
||||
int AixAttachListener::write_fully(int s, char* buf, size_t len) {
|
||||
do {
|
||||
ssize_t n = ::write(s, buf, len);
|
||||
if (n == -1) {
|
||||
if (errno != EINTR) return -1;
|
||||
} else {
|
||||
buf += n;
|
||||
len -= n;
|
||||
}
|
||||
}
|
||||
while (len > 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Complete an operation by sending the operation result and any result
|
||||
// output to the client. At this time the socket is in blocking mode so
|
||||
// potentially we can block if there is a lot of data and the client is
|
||||
@ -436,24 +324,6 @@ int AixAttachListener::write_fully(int s, char* buf, size_t len) {
|
||||
// socket could be made non-blocking and a timeout could be used.
|
||||
|
||||
void AixAttachOperation::complete(jint result, bufferedStream* st) {
|
||||
JavaThread* thread = JavaThread::current();
|
||||
ThreadBlockInVM tbivm(thread);
|
||||
|
||||
// write operation result
|
||||
char msg[32];
|
||||
os::snprintf_checked(msg, sizeof(msg), "%d\n", result);
|
||||
int rc = AixAttachListener::write_fully(this->socket(), msg, strlen(msg));
|
||||
|
||||
// write any result data
|
||||
if (rc == 0) {
|
||||
// Shutdown the socket in the cleanup function to enable more than
|
||||
// one agent attach in a sequence (see comments to listener_cleanup()).
|
||||
AixAttachListener::write_fully(this->socket(), (char*) st->base(), st->size());
|
||||
}
|
||||
|
||||
// done
|
||||
::close(this->socket());
|
||||
|
||||
delete this;
|
||||
}
|
||||
|
||||
@ -493,6 +363,7 @@ void AttachListener::vm_start() {
|
||||
}
|
||||
|
||||
int AttachListener::pd_init() {
|
||||
AttachListener::set_supported_version(ATTACH_API_V2);
|
||||
JavaThread* thread = JavaThread::current();
|
||||
ThreadBlockInVM tbivm(thread);
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2008, 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* 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.
|
||||
*
|
||||
@ -47,6 +47,7 @@ public class VirtualMachineImpl extends HotSpotVirtualMachine {
|
||||
// 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
|
||||
@ -110,11 +111,18 @@ public class VirtualMachineImpl extends HotSpotVirtualMachine {
|
||||
// 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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -129,14 +137,10 @@ public class VirtualMachineImpl extends HotSpotVirtualMachine {
|
||||
}
|
||||
}
|
||||
|
||||
// protocol version
|
||||
private static final String PROTOCOL_VERSION = "1";
|
||||
|
||||
/**
|
||||
* Execute the given command in the target VM.
|
||||
*/
|
||||
InputStream execute(String cmd, Object ... args) throws AgentLoadException, IOException {
|
||||
assert args.length <= 3; // includes null
|
||||
checkNulls(args);
|
||||
|
||||
// did we detach?
|
||||
@ -162,16 +166,8 @@ public class VirtualMachineImpl extends HotSpotVirtualMachine {
|
||||
// connected - write request
|
||||
// <ver> <cmd> <args...>
|
||||
try {
|
||||
writeString(s, PROTOCOL_VERSION);
|
||||
writeString(s, cmd);
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
if (i < args.length && args[i] != null) {
|
||||
writeString(s, (String)args[i]);
|
||||
} else {
|
||||
writeString(s, "");
|
||||
}
|
||||
}
|
||||
SocketOutputStream writer = new SocketOutputStream(s);
|
||||
writeCommand(writer, props, cmd, args);
|
||||
} catch (IOException x) {
|
||||
ioe = x;
|
||||
}
|
||||
@ -187,6 +183,17 @@ public class VirtualMachineImpl extends HotSpotVirtualMachine {
|
||||
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
|
||||
*/
|
||||
@ -223,21 +230,6 @@ public class VirtualMachineImpl extends HotSpotVirtualMachine {
|
||||
return f;
|
||||
}
|
||||
|
||||
/*
|
||||
* Write/sends the given to the target VM. String is transmitted in
|
||||
* UTF-8 encoding.
|
||||
*/
|
||||
private void writeString(int fd, String s) throws IOException {
|
||||
if (s.length() > 0) {
|
||||
byte[] b = s.getBytes(UTF_8);
|
||||
VirtualMachineImpl.write(fd, b, 0, b.length);
|
||||
}
|
||||
byte b[] = new byte[1];
|
||||
b[0] = 0;
|
||||
write(fd, b, 0, 1);
|
||||
}
|
||||
|
||||
|
||||
//-- native methods
|
||||
|
||||
static native void sendQuitTo(int pid) throws IOException;
|
||||
|
||||
@ -128,8 +128,6 @@ containers/docker/TestJcmdWithSideCar.java 8341518 linux-x64
|
||||
|
||||
# :hotspot_serviceability
|
||||
|
||||
serviceability/attach/AttachAPIv2/StreamingOutputTest.java 8352392 aix-ppc64
|
||||
|
||||
serviceability/sa/sadebugd/DebugdConnectTest.java 8239062,8270326 macosx-x64,macosx-aarch64
|
||||
serviceability/sa/TestRevPtrsForInvokeDynamic.java 8241235 generic-all
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user