2 * Copyright jattach authors
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 size_t n = snprintf(buf, bufsize, "ATTACH_DIAGNOSTICS:%s", argc > 1 ? argv[1] : "help");
52 for (i = 2; i < argc && n < bufsize; i++) {
53 n += snprintf(buf + n, bufsize - n, ",%s", argv[i]);
56 } else if (strcmp(cmd, "threaddump") == 0) {
57 snprintf(buf, bufsize, "ATTACH_DIAGNOSTICS:Thread.print,%s", argc > 1 ? argv[1] : "");
59 } else if (strcmp(cmd, "dumpheap") == 0) {
60 snprintf(buf, bufsize, "ATTACH_DIAGNOSTICS:Dump.heap,%s", argc > 1 ? argv[1] : "");
62 } else if (strcmp(cmd, "inspectheap") == 0) {
63 snprintf(buf, bufsize, "ATTACH_DIAGNOSTICS:GC.class_histogram,%s", argc > 1 ? argv[1] : "");
65 } else if (strcmp(cmd, "datadump") == 0) {
66 snprintf(buf, bufsize, "ATTACH_DIAGNOSTICS:Dump.java,%s", argc > 1 ? argv[1] : "");
68 } else if (strcmp(cmd, "properties") == 0) {
69 strcpy(buf, "ATTACH_GETSYSTEMPROPERTIES");
71 } else if (strcmp(cmd, "agentProperties") == 0) {
72 strcpy(buf, "ATTACH_GETAGENTPROPERTIES");
75 snprintf(buf, bufsize, "%s", cmd);
81 // Unescape a string and print it on stdout
82 static void print_unescaped(char* str) {
83 char* p = strchr(str, '\n');
88 while ((p = strchr(str, '\\')) != NULL) {
107 fwrite(str, 1, p - str + 1, stdout);
111 fwrite(str, 1, strlen(str), stdout);
115 // Send command with arguments to socket
116 static int write_command(int fd, const char* cmd) {
117 size_t len = strlen(cmd) + 1;
120 ssize_t bytes = write(fd, cmd + off, len - off);
129 // Mirror response from remote JVM to stdout
130 static int read_response(int fd, const char* cmd, int print_output) {
132 char* buf = malloc(size);
135 while (buf != NULL) {
136 ssize_t bytes = read(fd, buf + off, size - off);
138 fprintf(stderr, "Unexpected EOF reading response\n");
140 } else if (bytes < 0) {
141 perror("Error reading response");
146 if (buf[off - 1] == 0) {
151 buf = realloc(buf, size *= 2);
156 fprintf(stderr, "Failed to allocate memory for response\n");
162 if (strncmp(cmd, "ATTACH_LOADAGENT", 16) == 0) {
163 if (strncmp(buf, "ATTACH_ACK", 10) != 0) {
164 // AgentOnLoad error code comes right after AgentInitializationException
165 result = strncmp(buf, "ATTACH_ERR AgentInitializationException", 39) == 0 ? atoi(buf + 39) : -1;
167 } else if (strncmp(cmd, "ATTACH_DIAGNOSTICS:", 19) == 0 && print_output) {
168 char* p = strstr(buf, "openj9_diagnostics.string_result=");
170 // The result of a diagnostic command is encoded in Java Properties format
171 print_unescaped(p + 33);
179 fwrite(buf, 1, off, stdout);
186 static void detach(int fd) {
187 if (write_command(fd, "ATTACH_DETACHED") != 0) {
194 bytes = read(fd, buf, sizeof(buf));
195 } while (bytes > 0 && buf[bytes - 1] != 0);
198 static void close_with_errno(int fd) {
199 int saved_errno = errno;
204 static int acquire_lock(const char* subdir, const char* filename) {
206 snprintf(path, sizeof(path), "%s/.com_ibm_tools_attach/%s/%s", tmp_path, subdir, filename);
208 int lock_fd = open(path, O_WRONLY | O_CREAT, 0666);
213 if (flock(lock_fd, LOCK_EX) < 0) {
214 close_with_errno(lock_fd);
221 static void release_lock(int lock_fd) {
222 flock(lock_fd, LOCK_UN);
226 static int create_attach_socket(int* port) {
227 // Try IPv6 socket first, then fall back to IPv4
228 int s = socket(AF_INET6, SOCK_STREAM, 0);
230 struct sockaddr_in6 addr = {0};
231 addr.sin6_family = AF_INET6;
232 socklen_t addrlen = sizeof(addr);
233 if (bind(s, (struct sockaddr*)&addr, addrlen) == 0 && listen(s, 0) == 0
234 && getsockname(s, (struct sockaddr*)&addr, &addrlen) == 0) {
235 *port = ntohs(addr.sin6_port);
238 } else if ((s = socket(AF_INET, SOCK_STREAM, 0)) != -1) {
239 struct sockaddr_in addr = {0};
240 addr.sin_family = AF_INET;
241 socklen_t addrlen = sizeof(addr);
242 if (bind(s, (struct sockaddr*)&addr, addrlen) == 0 && listen(s, 0) == 0
243 && getsockname(s, (struct sockaddr*)&addr, &addrlen) == 0) {
244 *port = ntohs(addr.sin_port);
253 static void close_attach_socket(int s, int pid) {
255 snprintf(path, sizeof(path), "%s/.com_ibm_tools_attach/%d/replyInfo", tmp_path, pid);
261 static unsigned long long random_key() {
262 unsigned long long key = time(NULL) * 0xc6a4a7935bd1e995ULL;
264 int fd = open("/dev/urandom", O_RDONLY);
266 ssize_t r = read(fd, &key, sizeof(key));
274 static int write_reply_info(int pid, int port, unsigned long long key) {
276 snprintf(path, sizeof(path), "%s/.com_ibm_tools_attach/%d/replyInfo", tmp_path, pid);
278 int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0600);
283 int chars = snprintf(path, sizeof(path), "%016llx\n%d\n", key, port);
284 ssize_t r = write(fd, path, chars);
291 static int notify_semaphore(int value, int notif_count) {
293 snprintf(path, sizeof(path), "%s/.com_ibm_tools_attach/_notifier", tmp_path);
295 key_t sem_key = ftok(path, 0xa1);
296 int sem = semget(sem_key, 1, IPC_CREAT | 0666);
301 struct sembuf op = {0, value, value < 0 ? IPC_NOWAIT : 0};
302 while (notif_count-- > 0) {
309 static int accept_client(int s, unsigned long long key) {
310 struct timeval tv = {5, 0};
311 setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
313 int client = accept(s, NULL, NULL);
315 perror("JVM did not respond");
321 while (off < sizeof(buf)) {
322 ssize_t bytes = recv(client, buf + off, sizeof(buf) - off, 0);
324 fprintf(stderr, "The JVM connection was prematurely closed\n");
332 snprintf(expected, sizeof(expected), "ATTACH_CONNECTED %016llx ", key);
333 if (memcmp(buf, expected, sizeof(expected) - 1) != 0) {
334 fprintf(stderr, "Unexpected JVM response\n");
339 // Reset the timeout, as the command execution may take arbitrary long time
340 struct timeval tv0 = {0, 0};
341 setsockopt(client, SOL_SOCKET, SO_RCVTIMEO, &tv0, sizeof(tv0));
346 static int lock_notification_files() {
350 snprintf(path, sizeof(path), "%s/.com_ibm_tools_attach", tmp_path);
352 DIR* dir = opendir(path);
354 struct dirent* entry;
355 while ((entry = readdir(dir)) != NULL && count < MAX_NOTIF_FILES) {
356 if (entry->d_name[0] >= '1' && entry->d_name[0] <= '9' &&
357 (entry->d_type == DT_DIR || entry->d_type == DT_UNKNOWN)) {
358 notif_lock[count++] = acquire_lock(entry->d_name, "attachNotificationSync");
367 static void unlock_notification_files(int count) {
369 for (i = 0; i < count; i++) {
370 if (notif_lock[i] >= 0) {
371 release_lock(notif_lock[i]);
376 int is_openj9_process(int pid) {
378 snprintf(path, sizeof(path), "%s/.com_ibm_tools_attach/%d/attachInfo", tmp_path, pid);
381 return stat(path, &stats) == 0;
384 int jattach_openj9(int pid, int nspid, int argc, char** argv, int print_output) {
385 int attach_lock = acquire_lock("", "_attachlock");
386 if (attach_lock < 0) {
387 perror("Could not acquire attach lock");
393 int s = create_attach_socket(&port);
395 perror("Failed to listen to attach socket");
399 unsigned long long key = random_key();
400 if (write_reply_info(nspid, port, key) != 0) {
401 perror("Could not write replyInfo");
405 notif_count = lock_notification_files();
406 if (notify_semaphore(1, notif_count) != 0) {
407 perror("Could not notify semaphore");
411 int fd = accept_client(s, key);
413 // The error message has been already printed
417 close_attach_socket(s, nspid);
418 unlock_notification_files(notif_count);
419 notify_semaphore(-1, notif_count);
420 release_lock(attach_lock);
423 printf("Connected to remote JVM\n");
427 translate_command(cmd, sizeof(cmd), argc, argv);
429 if (write_command(fd, cmd) != 0) {
430 perror("Error writing to socket");
435 int result = read_response(fd, cmd, print_output);
445 close_attach_socket(s, nspid);
447 if (notif_count > 0) {
448 unlock_notification_files(notif_count);
449 notify_semaphore(-1, notif_count);
451 release_lock(attach_lock);