2 * Copyright 2021 Andrei Pangin
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
23 #include <sys/socket.h>
25 #include <netinet/in.h>
34 #define MAX_NOTIF_FILES 256
35 static int notif_lock[MAX_NOTIF_FILES];
38 // Translate HotSpot command to OpenJ9 equivalent
39 static void translate_command(char* buf, size_t bufsize, int argc, char** argv) {
40 const char* cmd = argv[0];
42 if (strcmp(cmd, "load") == 0 && argc >= 2) {
43 if (argc > 2 && strcmp(argv[2], "true") == 0) {
44 snprintf(buf, bufsize, "ATTACH_LOADAGENTPATH(%s,%s)", argv[1], argc > 3 ? argv[3] : "");
46 snprintf(buf, bufsize, "ATTACH_LOADAGENT(%s,%s)", argv[1], argc > 3 ? argv[3] : "");
49 } else if (strcmp(cmd, "jcmd") == 0) {
50 snprintf(buf, bufsize, "ATTACH_DIAGNOSTICS:%s,%s", argc > 1 ? argv[1] : "help", argc > 2 ? argv[2] : "");
52 } else if (strcmp(cmd, "threaddump") == 0) {
53 snprintf(buf, bufsize, "ATTACH_DIAGNOSTICS:Thread.print,%s", argc > 1 ? argv[1] : "");
55 } else if (strcmp(cmd, "dumpheap") == 0) {
56 snprintf(buf, bufsize, "ATTACH_DIAGNOSTICS:Dump.heap,%s", argc > 1 ? argv[1] : "");
58 } else if (strcmp(cmd, "inspectheap") == 0) {
59 snprintf(buf, bufsize, "ATTACH_DIAGNOSTICS:GC.class_histogram,%s", argc > 1 ? argv[1] : "");
61 } else if (strcmp(cmd, "datadump") == 0) {
62 snprintf(buf, bufsize, "ATTACH_DIAGNOSTICS:Dump.java,%s", argc > 1 ? argv[1] : "");
64 } else if (strcmp(cmd, "properties") == 0) {
65 strcpy(buf, "ATTACH_GETSYSTEMPROPERTIES");
67 } else if (strcmp(cmd, "agentProperties") == 0) {
68 strcpy(buf, "ATTACH_GETAGENTPROPERTIES");
71 snprintf(buf, bufsize, "%s", cmd);
77 // Unescape a string and print it on stdout
78 static void print_unescaped(char* str) {
79 char* p = strchr(str, '\n');
84 while ((p = strchr(str, '\\')) != NULL) {
103 fwrite(str, 1, p - str + 1, stdout);
107 fwrite(str, 1, strlen(str), stdout);
111 // Send command with arguments to socket
112 static int write_command(int fd, const char* cmd) {
113 size_t len = strlen(cmd) + 1;
116 ssize_t bytes = write(fd, cmd + off, len - off);
125 // Mirror response from remote JVM to stdout
126 static int read_response(int fd, const char* cmd) {
128 char* buf = malloc(size);
131 while (buf != NULL) {
132 ssize_t bytes = read(fd, buf + off, size - off);
134 fprintf(stderr, "Unexpected EOF reading response\n");
136 } else if (bytes < 0) {
137 perror("Error reading response");
142 if (buf[off - 1] == 0) {
147 buf = realloc(buf, size *= 2);
152 fprintf(stderr, "Failed to allocate memory for response\n");
158 if (strncmp(cmd, "ATTACH_LOADAGENT", 16) == 0) {
159 if (strncmp(buf, "ATTACH_ACK", 10) != 0) {
160 // AgentOnLoad error code comes right after AgentInitializationException
161 result = strncmp(buf, "ATTACH_ERR AgentInitializationException", 39) == 0 ? atoi(buf + 39) : -1;
163 } else if (strncmp(cmd, "ATTACH_DIAGNOSTICS:", 19) == 0) {
164 char* p = strstr(buf, "openj9_diagnostics.string_result=");
166 // The result of a diagnostic command is encoded in Java Properties format
167 print_unescaped(p + 33);
174 fwrite(buf, 1, off, stdout);
180 static void detach(int fd) {
181 if (write_command(fd, "ATTACH_DETACHED") != 0) {
188 bytes = read(fd, buf, sizeof(buf));
189 } while (bytes > 0 && buf[bytes - 1] != 0);
192 static void close_with_errno(int fd) {
193 int saved_errno = errno;
198 static int acquire_lock(const char* subdir, const char* filename) {
200 snprintf(path, sizeof(path), "%s/.com_ibm_tools_attach/%s/%s", tmp_path, subdir, filename);
202 int lock_fd = open(path, O_WRONLY | O_CREAT, 0666);
207 if (flock(lock_fd, LOCK_EX) < 0) {
208 close_with_errno(lock_fd);
215 static void release_lock(int lock_fd) {
216 flock(lock_fd, LOCK_UN);
220 static int create_attach_socket(int* port) {
221 // Try IPv6 socket first, then fall back to IPv4
222 int s = socket(AF_INET6, SOCK_STREAM, 0);
224 struct sockaddr_in6 addr = {AF_INET6, 0};
225 socklen_t addrlen = sizeof(addr);
226 if (bind(s, (struct sockaddr*)&addr, addrlen) == 0 && listen(s, 0) == 0
227 && getsockname(s, (struct sockaddr*)&addr, &addrlen) == 0) {
228 *port = ntohs(addr.sin6_port);
231 } else if ((s = socket(AF_INET, SOCK_STREAM, 0)) != -1) {
232 struct sockaddr_in addr = {AF_INET, 0};
233 socklen_t addrlen = sizeof(addr);
234 if (bind(s, (struct sockaddr*)&addr, addrlen) == 0 && listen(s, 0) == 0
235 && getsockname(s, (struct sockaddr*)&addr, &addrlen) == 0) {
236 *port = ntohs(addr.sin_port);
245 static void close_attach_socket(int s, int pid) {
247 snprintf(path, sizeof(path), "%s/.com_ibm_tools_attach/%d/replyInfo", tmp_path, pid);
253 static unsigned long long random_key() {
254 unsigned long long key = time(NULL) * 0xc6a4a7935bd1e995ULL;
256 int fd = open("/dev/urandom", O_RDONLY);
258 ssize_t r = read(fd, &key, sizeof(key));
266 static int write_reply_info(int pid, int port, unsigned long long key) {
268 snprintf(path, sizeof(path), "%s/.com_ibm_tools_attach/%d/replyInfo", tmp_path, pid);
270 int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0600);
275 int chars = snprintf(path, sizeof(path), "%016llx\n%d\n", key, port);
276 ssize_t r = write(fd, path, chars);
283 static int notify_semaphore(int value, int notif_count) {
285 snprintf(path, sizeof(path), "%s/.com_ibm_tools_attach/_notifier", tmp_path);
287 key_t sem_key = ftok(path, 0xa1);
288 int sem = semget(sem_key, 1, IPC_CREAT | 0666);
293 struct sembuf op = {0, value, value < 0 ? IPC_NOWAIT : 0};
294 while (notif_count-- > 0) {
301 static int accept_client(int s, unsigned long long key) {
302 struct timeval tv = {5, 0};
303 setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
305 int client = accept(s, NULL, NULL);
307 perror("JVM did not respond");
313 while (off < sizeof(buf)) {
314 ssize_t bytes = recv(client, buf + off, sizeof(buf) - off, 0);
316 fprintf(stderr, "The JVM connection was prematurely closed\n");
324 snprintf(expected, sizeof(expected), "ATTACH_CONNECTED %016llx ", key);
325 if (memcmp(buf, expected, sizeof(expected) - 1) != 0) {
326 fprintf(stderr, "Unexpected JVM response\n");
331 // Reset the timeout, as the command execution may take arbitrary long time
332 struct timeval tv0 = {0, 0};
333 setsockopt(client, SOL_SOCKET, SO_RCVTIMEO, &tv0, sizeof(tv0));
338 static int lock_notification_files() {
342 snprintf(path, sizeof(path), "%s/.com_ibm_tools_attach", tmp_path);
344 DIR* dir = opendir(path);
346 struct dirent* entry;
347 while ((entry = readdir(dir)) != NULL && count < MAX_NOTIF_FILES) {
348 if (entry->d_name[0] >= '1' && entry->d_name[0] <= '9' &&
349 (entry->d_type == DT_DIR || entry->d_type == DT_UNKNOWN)) {
350 notif_lock[count++] = acquire_lock(entry->d_name, "attachNotificationSync");
359 static void unlock_notification_files(int count) {
361 for (i = 0; i < count; i++) {
362 if (notif_lock[i] >= 0) {
363 release_lock(notif_lock[i]);
368 int is_openj9_process(int pid) {
370 snprintf(path, sizeof(path), "%s/.com_ibm_tools_attach/%d/attachInfo", tmp_path, pid);
373 return stat(path, &stats) == 0;
376 int jattach_openj9(int pid, int nspid, int argc, char** argv) {
377 int attach_lock = acquire_lock("", "_attachlock");
378 if (attach_lock < 0) {
379 perror("Could not acquire attach lock");
385 int s = create_attach_socket(&port);
387 perror("Failed to listen to attach socket");
391 unsigned long long key = random_key();
392 if (write_reply_info(nspid, port, key) != 0) {
393 perror("Could not write replyInfo");
397 notif_count = lock_notification_files();
398 if (notify_semaphore(1, notif_count) != 0) {
399 perror("Could not notify semaphore");
403 int fd = accept_client(s, key);
405 // The error message has been already printed
409 close_attach_socket(s, nspid);
410 unlock_notification_files(notif_count);
411 notify_semaphore(-1, notif_count);
412 release_lock(attach_lock);
414 printf("Connected to remote JVM\n");
417 translate_command(cmd, sizeof(cmd), argc, argv);
419 if (write_command(fd, cmd) != 0) {
420 perror("Error writing to socket");
425 int result = read_response(fd, cmd);
435 close_attach_socket(s, nspid);
437 if (notif_count > 0) {
438 unlock_notification_files(notif_count);
439 notify_semaphore(-1, notif_count);
441 release_lock(attach_lock);