X-Git-Url: https://git.sven.stormbind.net/?p=sven%2Fjattach.git;a=blobdiff_plain;f=src%2Fposix%2Fjattach_openj9.c;fp=src%2Fposix%2Fjattach_openj9.c;h=f34f4cc79447eec298f021df132235d4f2a444ba;hp=0000000000000000000000000000000000000000;hb=54ba80f2c210c8003b4ab5c7112ab64063ee8212;hpb=00866600824dca327d930613f79c2382fc222765 diff --git a/src/posix/jattach_openj9.c b/src/posix/jattach_openj9.c new file mode 100644 index 0000000..f34f4cc --- /dev/null +++ b/src/posix/jattach_openj9.c @@ -0,0 +1,439 @@ +/* + * Copyright 2021 Andrei Pangin + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "psutil.h" + + +#define MAX_NOTIF_FILES 256 +static int notif_lock[MAX_NOTIF_FILES]; + + +// Translate HotSpot command to OpenJ9 equivalent +static void translate_command(char* buf, size_t bufsize, int argc, char** argv) { + const char* cmd = argv[0]; + + if (strcmp(cmd, "load") == 0 && argc >= 2) { + if (argc > 2 && strcmp(argv[2], "true") == 0) { + snprintf(buf, bufsize, "ATTACH_LOADAGENTPATH(%s,%s)", argv[1], argc > 3 ? argv[3] : ""); + } else { + snprintf(buf, bufsize, "ATTACH_LOADAGENT(%s,%s)", argv[1], argc > 3 ? argv[3] : ""); + } + + } else if (strcmp(cmd, "jcmd") == 0) { + snprintf(buf, bufsize, "ATTACH_DIAGNOSTICS:%s,%s", argc > 1 ? argv[1] : "help", argc > 2 ? argv[2] : ""); + + } else if (strcmp(cmd, "threaddump") == 0) { + snprintf(buf, bufsize, "ATTACH_DIAGNOSTICS:Thread.print,%s", argc > 1 ? argv[1] : ""); + + } else if (strcmp(cmd, "dumpheap") == 0) { + snprintf(buf, bufsize, "ATTACH_DIAGNOSTICS:Dump.heap,%s", argc > 1 ? argv[1] : ""); + + } else if (strcmp(cmd, "inspectheap") == 0) { + snprintf(buf, bufsize, "ATTACH_DIAGNOSTICS:GC.class_histogram,%s", argc > 1 ? argv[1] : ""); + + } else if (strcmp(cmd, "datadump") == 0) { + snprintf(buf, bufsize, "ATTACH_DIAGNOSTICS:Dump.java,%s", argc > 1 ? argv[1] : ""); + + } else if (strcmp(cmd, "properties") == 0) { + strcpy(buf, "ATTACH_GETSYSTEMPROPERTIES"); + + } else if (strcmp(cmd, "agentProperties") == 0) { + strcpy(buf, "ATTACH_GETAGENTPROPERTIES"); + + } else { + snprintf(buf, bufsize, "%s", cmd); + } + + buf[bufsize - 1] = 0; +} + +// Unescape a string and print it on stdout +static void print_unescaped(char* str) { + char* p = strchr(str, '\n'); + if (p != NULL) { + *p = 0; + } + + while ((p = strchr(str, '\\')) != NULL) { + switch (p[1]) { + case 0: + break; + case 'f': + *p = '\f'; + break; + case 'n': + *p = '\n'; + break; + case 'r': + *p = '\r'; + break; + case 't': + *p = '\t'; + break; + default: + *p = p[1]; + } + fwrite(str, 1, p - str + 1, stdout); + str = p + 2; + } + + fwrite(str, 1, strlen(str), stdout); + printf("\n"); +} + +// Send command with arguments to socket +static int write_command(int fd, const char* cmd) { + size_t len = strlen(cmd) + 1; + size_t off = 0; + while (off < len) { + ssize_t bytes = write(fd, cmd + off, len - off); + if (bytes <= 0) { + return -1; + } + off += bytes; + } + return 0; +} + +// Mirror response from remote JVM to stdout +static int read_response(int fd, const char* cmd) { + size_t size = 8192; + char* buf = malloc(size); + + size_t off = 0; + while (buf != NULL) { + ssize_t bytes = read(fd, buf + off, size - off); + if (bytes == 0) { + fprintf(stderr, "Unexpected EOF reading response\n"); + return 1; + } else if (bytes < 0) { + perror("Error reading response"); + return 1; + } + + off += bytes; + if (buf[off - 1] == 0) { + break; + } + + if (off >= size) { + buf = realloc(buf, size *= 2); + } + } + + if (buf == NULL) { + fprintf(stderr, "Failed to allocate memory for response\n"); + return 1; + } + + int result = 0; + + if (strncmp(cmd, "ATTACH_LOADAGENT", 16) == 0) { + if (strncmp(buf, "ATTACH_ACK", 10) != 0) { + // AgentOnLoad error code comes right after AgentInitializationException + result = strncmp(buf, "ATTACH_ERR AgentInitializationException", 39) == 0 ? atoi(buf + 39) : -1; + } + } else if (strncmp(cmd, "ATTACH_DIAGNOSTICS:", 19) == 0) { + char* p = strstr(buf, "openj9_diagnostics.string_result="); + if (p != NULL) { + // The result of a diagnostic command is encoded in Java Properties format + print_unescaped(p + 33); + free(buf); + return result; + } + } + + buf[off - 1] = '\n'; + fwrite(buf, 1, off, stdout); + + free(buf); + return result; +} + +static void detach(int fd) { + if (write_command(fd, "ATTACH_DETACHED") != 0) { + return; + } + + char buf[256]; + ssize_t bytes; + do { + bytes = read(fd, buf, sizeof(buf)); + } while (bytes > 0 && buf[bytes - 1] != 0); +} + +static void close_with_errno(int fd) { + int saved_errno = errno; + close(fd); + errno = saved_errno; +} + +static int acquire_lock(const char* subdir, const char* filename) { + char path[MAX_PATH]; + snprintf(path, sizeof(path), "%s/.com_ibm_tools_attach/%s/%s", tmp_path, subdir, filename); + + int lock_fd = open(path, O_WRONLY | O_CREAT, 0666); + if (lock_fd < 0) { + return -1; + } + + if (flock(lock_fd, LOCK_EX) < 0) { + close_with_errno(lock_fd); + return -1; + } + + return lock_fd; +} + +static void release_lock(int lock_fd) { + flock(lock_fd, LOCK_UN); + close(lock_fd); +} + +static int create_attach_socket(int* port) { + // Try IPv6 socket first, then fall back to IPv4 + int s = socket(AF_INET6, SOCK_STREAM, 0); + if (s != -1) { + struct sockaddr_in6 addr = {AF_INET6, 0}; + socklen_t addrlen = sizeof(addr); + if (bind(s, (struct sockaddr*)&addr, addrlen) == 0 && listen(s, 0) == 0 + && getsockname(s, (struct sockaddr*)&addr, &addrlen) == 0) { + *port = ntohs(addr.sin6_port); + return s; + } + } else if ((s = socket(AF_INET, SOCK_STREAM, 0)) != -1) { + struct sockaddr_in addr = {AF_INET, 0}; + socklen_t addrlen = sizeof(addr); + if (bind(s, (struct sockaddr*)&addr, addrlen) == 0 && listen(s, 0) == 0 + && getsockname(s, (struct sockaddr*)&addr, &addrlen) == 0) { + *port = ntohs(addr.sin_port); + return s; + } + } + + close_with_errno(s); + return -1; +} + +static void close_attach_socket(int s, int pid) { + char path[MAX_PATH]; + snprintf(path, sizeof(path), "%s/.com_ibm_tools_attach/%d/replyInfo", tmp_path, pid); + unlink(path); + + close(s); +} + +static unsigned long long random_key() { + unsigned long long key = time(NULL) * 0xc6a4a7935bd1e995ULL; + + int fd = open("/dev/urandom", O_RDONLY); + if (fd >= 0) { + ssize_t r = read(fd, &key, sizeof(key)); + (void)r; + close(fd); + } + + return key; +} + +static int write_reply_info(int pid, int port, unsigned long long key) { + char path[MAX_PATH]; + snprintf(path, sizeof(path), "%s/.com_ibm_tools_attach/%d/replyInfo", tmp_path, pid); + + int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0600); + if (fd < 0) { + return -1; + } + + int chars = snprintf(path, sizeof(path), "%016llx\n%d\n", key, port); + write(fd, path, chars); + close(fd); + + return 0; +} + +static int notify_semaphore(int value, int notif_count) { + char path[MAX_PATH]; + snprintf(path, sizeof(path), "%s/.com_ibm_tools_attach/_notifier", tmp_path); + + key_t sem_key = ftok(path, 0xa1); + int sem = semget(sem_key, 1, IPC_CREAT | 0666); + if (sem < 0) { + return -1; + } + + struct sembuf op = {0, value, value < 0 ? IPC_NOWAIT : 0}; + while (notif_count-- > 0) { + semop(sem, &op, 1); + } + + return 0; +} + +static int accept_client(int s, unsigned long long key) { + struct timeval tv = {5, 0}; + setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); + + int client = accept(s, NULL, NULL); + if (client < 0) { + perror("JVM did not respond"); + return -1; + } + + char buf[35]; + size_t off = 0; + while (off < sizeof(buf)) { + ssize_t bytes = recv(client, buf + off, sizeof(buf) - off, 0); + if (bytes <= 0) { + fprintf(stderr, "The JVM connection was prematurely closed\n"); + close(client); + return -1; + } + off += bytes; + } + + char expected[35]; + snprintf(expected, sizeof(expected), "ATTACH_CONNECTED %016llx ", key); + if (memcmp(buf, expected, sizeof(expected) - 1) != 0) { + fprintf(stderr, "Unexpected JVM response\n"); + close(client); + return -1; + } + + return client; +} + +static int lock_notification_files() { + int count = 0; + + char path[MAX_PATH]; + snprintf(path, sizeof(path), "%s/.com_ibm_tools_attach", tmp_path); + + DIR* dir = opendir(path); + if (dir != NULL) { + struct dirent* entry; + while ((entry = readdir(dir)) != NULL && count < MAX_NOTIF_FILES) { + if (entry->d_name[0] >= '1' && entry->d_name[0] <= '9' && + (entry->d_type == DT_DIR || entry->d_type == DT_UNKNOWN)) { + notif_lock[count++] = acquire_lock(entry->d_name, "attachNotificationSync"); + } + } + closedir(dir); + } + + return count; +} + +static void unlock_notification_files(int count) { + int i; + for (i = 0; i < count; i++) { + if (notif_lock[i] >= 0) { + release_lock(notif_lock[i]); + } + } +} + +int is_openj9_process(int pid) { + char path[MAX_PATH]; + snprintf(path, sizeof(path), "%s/.com_ibm_tools_attach/%d/attachInfo", tmp_path, pid); + + struct stat stats; + return stat(path, &stats) == 0; +} + +int jattach_openj9(int pid, int nspid, int argc, char** argv) { + int attach_lock = acquire_lock("", "_attachlock"); + if (attach_lock < 0) { + perror("Could not acquire attach lock"); + return 1; + } + + int notif_count = 0; + int port; + int s = create_attach_socket(&port); + if (s < 0) { + perror("Failed to listen to attach socket"); + goto error; + } + + unsigned long long key = random_key(); + if (write_reply_info(nspid, port, key) != 0) { + perror("Could not write replyInfo"); + goto error; + } + + notif_count = lock_notification_files(); + if (notify_semaphore(1, notif_count) != 0) { + perror("Could not notify semaphore"); + goto error; + } + + int fd = accept_client(s, key); + if (fd < 0) { + // The error message has been already printed + goto error; + } + + close_attach_socket(s, nspid); + unlock_notification_files(notif_count); + notify_semaphore(-1, notif_count); + release_lock(attach_lock); + + printf("Connected to remote JVM\n"); + + char cmd[8192]; + translate_command(cmd, sizeof(cmd), argc, argv); + + if (write_command(fd, cmd) != 0) { + perror("Error writing to socket"); + close(fd); + return 1; + } + + int result = read_response(fd, cmd); + if (result != 1) { + detach(fd); + } + close(fd); + + return result; + +error: + if (s >= 0) { + close_attach_socket(s, nspid); + } + if (notif_count > 0) { + unlock_notification_files(notif_count); + notify_semaphore(-1, notif_count); + } + release_lock(attach_lock); + + return 1; +}