From 443b80898c49b466100dffc328ea8905887af2a1 Mon Sep 17 00:00:00 2001 From: Sven Hoexter Date: Tue, 16 Jan 2024 13:25:21 +0100 Subject: [PATCH] New upstream version 2.2 --- Makefile | 2 +- README.md | 23 +++++++-------- jattach.spec | 9 ++++-- src/posix/jattach.c | 19 ++++++------ src/posix/jattach_hotspot.c | 58 +++++++++++++++++++++++++------------ src/posix/jattach_openj9.c | 32 +++++++++++++------- src/posix/psutil.c | 7 ++--- src/posix/psutil.h | 10 ++++++- src/windows/jattach.c | 41 ++++++++++++++++---------- 9 files changed, 126 insertions(+), 75 deletions(-) diff --git a/Makefile b/Makefile index 7ff43df..078bca3 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -JATTACH_VERSION=2.1 +JATTACH_VERSION=2.2 ifneq ($(findstring Windows,$(OS)),) CL=cl.exe diff --git a/README.md b/README.md index 3699451..9584204 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ https://docs.oracle.com/javase/8/docs/jdk/api/attach/spec/ ### Download -Binaries are available on the [Releases](https://github.com/apangin/jattach/releases) page. +Binaries are available on the [Releases](https://github.com/jattach/jattach/releases) page. On some platforms, you can also [install](#installation) jattach with a package manager. @@ -46,31 +46,30 @@ which takes .jar path and its arguments as a single options string. #### List available jcmd commands - $ jattach jcmd "help -all" + $ jattach jcmd help -all ### Installation -#### FreeBSD +#### Debian, Ubuntu -On FreeBSD, you can use the following command to install `jattach` package: +On Debian and Ubuntu, you can install `jattach` from the official repository: - $ pkg install jattach + # apt install jattach #### Alpine Linux -On Alpine Linux, you can use the following command to install `jattach` package from the edge/community repository: +On Alpine Linux, you can install `jattach` package from the edge/community repository: - $ apk add --no-cache jattach --repository http://dl-cdn.alpinelinux.org/alpine/edge/community/ + # apk add --no-cache jattach --repository http://dl-cdn.alpinelinux.org/alpine/edge/community/ #### Archlinux [jattach](https://aur.archlinux.org/packages/jattach/) package can be installed from [AUR](https://wiki.archlinux.org/index.php/Arch_User_Repository) using one of [AUR helpers](https://wiki.archlinux.org/index.php/AUR_helpers), e.g., `yay`: - $ yay -S jattach + # yay -S jattach -#### Debian Linux +#### FreeBSD -On Debian Linux, you can use the following command to install `jattach` from the [official repository](https://packages.debian.org/search?keywords=jattach): +On FreeBSD, you can use the following command to install `jattach`: - $ apt install jattach + # pkg install jattach -Packages are provided for **bullseye** (stable), **bookworm** (testing) and **sid** (unstable). diff --git a/jattach.spec b/jattach.spec index 452ab91..fdac742 100644 --- a/jattach.spec +++ b/jattach.spec @@ -1,11 +1,11 @@ Name: jattach -Version: 2.1 +Version: 2.2 Release: 1 Summary: JVM Dynamic Attach utility Group: Development/Tools License: ASL 2.0 -URL: https://github.com/apangin/jattach +URL: https://github.com/jattach/jattach Vendor: Andrei Pangin Packager: Vadim Tsesko @@ -35,6 +35,11 @@ install -p -m 555 %{_sourcedir}/bin/jattach ${BIN} /usr/bin/jattach %changelog +* Wed Jan 10 2024 Andrei Pangin - 2.2-1 +- Automatically concatenate jcmd arguments +- Fixed attach to OpenJ9 on macOS +- Fixed container support on Linux 3.x + * Mon Jul 25 2022 Vadim Tsesko - 2.1-1 - Handle both tabs and spaces when parsing /proc/pid/status - Socket timeout while reading response from OpenJ9 VM diff --git a/src/posix/jattach.c b/src/posix/jattach.c index 53d9dfb..a8a851e 100644 --- a/src/posix/jattach.c +++ b/src/posix/jattach.c @@ -1,5 +1,5 @@ /* - * Copyright 2021 Andrei Pangin + * Copyright jattach authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,12 +22,14 @@ extern int is_openj9_process(int pid); -extern int jattach_openj9(int pid, int nspid, int argc, char** argv); -extern int jattach_hotspot(int pid, int nspid, int argc, char** argv); +extern int jattach_openj9(int pid, int nspid, int argc, char** argv, int print_output); +extern int jattach_hotspot(int pid, int nspid, int argc, char** argv, int print_output); + +int mnt_changed = 0; __attribute__((visibility("default"))) -int jattach(int pid, int argc, char** argv) { +int jattach(int pid, int argc, char** argv, int print_output) { uid_t my_uid = geteuid(); gid_t my_gid = getegid(); uid_t target_uid = my_uid; @@ -42,7 +44,7 @@ int jattach(int pid, int argc, char** argv) { // Network and IPC namespaces are essential for OpenJ9 connection. enter_ns(pid, "net"); enter_ns(pid, "ipc"); - int mnt_changed = enter_ns(pid, "mnt"); + mnt_changed = enter_ns(pid, "mnt"); // In HotSpot, dynamic attach is allowed only for the clients with the same euid/egid. // If we are running under root, switch to the required euid/egid automatically. @@ -58,9 +60,9 @@ int jattach(int pid, int argc, char** argv) { signal(SIGPIPE, SIG_IGN); if (is_openj9_process(nspid)) { - return jattach_openj9(pid, nspid, argc, argv); + return jattach_openj9(pid, nspid, argc, argv, print_output); } else { - return jattach_hotspot(pid, nspid, argc, argv); + return jattach_hotspot(pid, nspid, argc, argv, print_output); } } @@ -69,7 +71,6 @@ int jattach(int pid, int argc, char** argv) { int main(int argc, char** argv) { if (argc < 3) { printf("jattach " JATTACH_VERSION " built on " __DATE__ "\n" - "Copyright 2021 Andrei Pangin\n" "\n" "Usage: jattach [args ...]\n" "\n" @@ -86,7 +87,7 @@ int main(int argc, char** argv) { return 1; } - return jattach(pid, argc - 2, argv + 2); + return jattach(pid, argc - 2, argv + 2, 1); } #endif // JATTACH_VERSION diff --git a/src/posix/jattach_hotspot.c b/src/posix/jattach_hotspot.c index e23e460..68d8805 100644 --- a/src/posix/jattach_hotspot.c +++ b/src/posix/jattach_hotspot.c @@ -1,5 +1,5 @@ /* - * Copyright 2021 Andrei Pangin + * Copyright jattach authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,6 +27,8 @@ #include "psutil.h" +extern int mnt_changed; + // Check if remote JVM has already opened socket for Dynamic Attach static int check_socket(int pid) { char path[MAX_PATH]; @@ -46,7 +48,7 @@ static uid_t get_file_owner(const char* path) { // HotSpot will start Attach listener in response to SIGQUIT if it sees .attach_pid file static int start_attach_mechanism(int pid, int nspid) { char path[MAX_PATH]; - snprintf(path, sizeof(path), "/proc/%d/cwd/.attach_pid%d", nspid, nspid); + snprintf(path, sizeof(path), "/proc/%d/cwd/.attach_pid%d", mnt_changed > 0 ? nspid : pid, nspid); int fd = creat(path, 0660); if (fd == -1 || (close(fd) == 0 && get_file_owner(path) != geteuid())) { @@ -102,23 +104,37 @@ static int connect_socket(int pid) { // Send command with arguments to socket static int write_command(int fd, int argc, char** argv) { + char buf[8192]; + const char* const limit = buf + sizeof(buf); + + // jcmd has 2 arguments maximum; merge excessive arguments into one + int cmd_args = argc >= 2 && strcmp(argv[0], "jcmd") == 0 ? 2 : argc >= 4 ? 4 : argc; + // Protocol version - if (write(fd, "1", 2) <= 0) { - return -1; - } + char* p = stpncpy(buf, "1", sizeof(buf)) + 1; int i; - for (i = 0; i < 4; i++) { - const char* arg = i < argc ? argv[i] : ""; - if (write(fd, arg, strlen(arg) + 1) <= 0) { + for (i = 0; i < argc && p < limit; i++) { + if (i >= cmd_args) p[-1] = ' '; + p = stpncpy(p, argv[i], limit - p) + 1; + } + for (i = cmd_args; i < 4 && p < limit; i++) { + *p++ = 0; + } + + const char* q = p < limit ? p : limit; + for (p = buf; p < q; ) { + ssize_t bytes = write(fd, p, q - p); + if (bytes <= 0) { return -1; } + p += (size_t)bytes; } return 0; } // Mirror response from remote JVM to stdout -static int read_response(int fd, int argc, char** argv) { +static int read_response(int fd, int argc, char** argv, int print_output) { char buf[8192]; ssize_t bytes = read(fd, buf, sizeof(buf) - 1); if (bytes == 0) { @@ -146,18 +162,20 @@ static int read_response(int fd, int argc, char** argv) { result = atoi(strncmp(buf + 2, "return code: ", 13) == 0 ? buf + 15 : buf + 2); } - // Mirror JVM response to stdout - printf("JVM response code = "); - do { - fwrite(buf, 1, bytes, stdout); - bytes = read(fd, buf, sizeof(buf)); - } while (bytes > 0); - printf("\n"); + if (print_output) { + // Mirror JVM response to stdout + printf("JVM response code = "); + do { + fwrite(buf, 1, bytes, stdout); + bytes = read(fd, buf, sizeof(buf)); + } while (bytes > 0); + printf("\n"); + } return result; } -int jattach_hotspot(int pid, int nspid, int argc, char** argv) { +int jattach_hotspot(int pid, int nspid, int argc, char** argv, int print_output) { if (check_socket(nspid) != 0 && start_attach_mechanism(pid, nspid) != 0) { perror("Could not start attach mechanism"); return 1; @@ -169,7 +187,9 @@ int jattach_hotspot(int pid, int nspid, int argc, char** argv) { return 1; } - printf("Connected to remote JVM\n"); + if (print_output) { + printf("Connected to remote JVM\n"); + } if (write_command(fd, argc, argv) != 0) { perror("Error writing to socket"); @@ -177,7 +197,7 @@ int jattach_hotspot(int pid, int nspid, int argc, char** argv) { return 1; } - int result = read_response(fd, argc, argv); + int result = read_response(fd, argc, argv, print_output); close(fd); return result; diff --git a/src/posix/jattach_openj9.c b/src/posix/jattach_openj9.c index deab4c6..90683c5 100644 --- a/src/posix/jattach_openj9.c +++ b/src/posix/jattach_openj9.c @@ -1,5 +1,5 @@ /* - * Copyright 2021 Andrei Pangin + * Copyright jattach authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -47,7 +47,11 @@ static void translate_command(char* buf, size_t bufsize, int argc, char** argv) } } else if (strcmp(cmd, "jcmd") == 0) { - snprintf(buf, bufsize, "ATTACH_DIAGNOSTICS:%s,%s", argc > 1 ? argv[1] : "help", argc > 2 ? argv[2] : ""); + size_t n = snprintf(buf, bufsize, "ATTACH_DIAGNOSTICS:%s", argc > 1 ? argv[1] : "help"); + int i; + for (i = 2; i < argc && n < bufsize; i++) { + n += snprintf(buf + n, bufsize - n, ",%s", argv[i]); + } } else if (strcmp(cmd, "threaddump") == 0) { snprintf(buf, bufsize, "ATTACH_DIAGNOSTICS:Thread.print,%s", argc > 1 ? argv[1] : ""); @@ -123,7 +127,7 @@ static int write_command(int fd, const char* cmd) { } // Mirror response from remote JVM to stdout -static int read_response(int fd, const char* cmd) { +static int read_response(int fd, const char* cmd, int print_output) { size_t size = 8192; char* buf = malloc(size); @@ -160,7 +164,7 @@ static int read_response(int fd, const char* cmd) { // 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) { + } else if (strncmp(cmd, "ATTACH_DIAGNOSTICS:", 19) == 0 && print_output) { char* p = strstr(buf, "openj9_diagnostics.string_result="); if (p != NULL) { // The result of a diagnostic command is encoded in Java Properties format @@ -170,8 +174,10 @@ static int read_response(int fd, const char* cmd) { } } - buf[off - 1] = '\n'; - fwrite(buf, 1, off, stdout); + if (print_output) { + buf[off - 1] = '\n'; + fwrite(buf, 1, off, stdout); + } free(buf); return result; @@ -221,7 +227,8 @@ 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}; + struct sockaddr_in6 addr = {0}; + addr.sin6_family = AF_INET6; socklen_t addrlen = sizeof(addr); if (bind(s, (struct sockaddr*)&addr, addrlen) == 0 && listen(s, 0) == 0 && getsockname(s, (struct sockaddr*)&addr, &addrlen) == 0) { @@ -229,7 +236,8 @@ static int create_attach_socket(int* port) { return s; } } else if ((s = socket(AF_INET, SOCK_STREAM, 0)) != -1) { - struct sockaddr_in addr = {AF_INET, 0}; + struct sockaddr_in addr = {0}; + addr.sin_family = AF_INET; socklen_t addrlen = sizeof(addr); if (bind(s, (struct sockaddr*)&addr, addrlen) == 0 && listen(s, 0) == 0 && getsockname(s, (struct sockaddr*)&addr, &addrlen) == 0) { @@ -373,7 +381,7 @@ int is_openj9_process(int pid) { return stat(path, &stats) == 0; } -int jattach_openj9(int pid, int nspid, int argc, char** argv) { +int jattach_openj9(int pid, int nspid, int argc, char** argv, int print_output) { int attach_lock = acquire_lock("", "_attachlock"); if (attach_lock < 0) { perror("Could not acquire attach lock"); @@ -411,7 +419,9 @@ int jattach_openj9(int pid, int nspid, int argc, char** argv) { notify_semaphore(-1, notif_count); release_lock(attach_lock); - printf("Connected to remote JVM\n"); + if (print_output) { + printf("Connected to remote JVM\n"); + } char cmd[8192]; translate_command(cmd, sizeof(cmd), argc, argv); @@ -422,7 +432,7 @@ int jattach_openj9(int pid, int nspid, int argc, char** argv) { return 1; } - int result = read_response(fd, cmd); + int result = read_response(fd, cmd, print_output); if (result != 1) { detach(fd); } diff --git a/src/posix/psutil.c b/src/posix/psutil.c index 9beea88..d0287f5 100644 --- a/src/posix/psutil.c +++ b/src/posix/psutil.c @@ -1,5 +1,5 @@ /* - * Copyright 2021 Andrei Pangin + * Copyright jattach authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -93,15 +93,14 @@ static int alt_lookup_nspid(int pid) { // Check if /proc//sched points back to snprintf(path, sizeof(path), "/proc/%d/root/proc/%s/sched", pid, entry->d_name); if (sched_get_host_pid(path) == pid) { - closedir(dir); - return atoi(entry->d_name); + pid = atoi(entry->d_name); + break; } } } closedir(dir); } - // Could not find container pid; return host pid as the last resort return pid; } diff --git a/src/posix/psutil.h b/src/posix/psutil.h index fa1c416..5dc3040 100644 --- a/src/posix/psutil.h +++ b/src/posix/psutil.h @@ -1,5 +1,5 @@ /* - * Copyright 2021 Andrei Pangin + * Copyright jattach authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +20,10 @@ #include +#ifdef __cplusplus +extern "C" { +#endif + #define MAX_PATH 1024 extern char tmp_path[]; @@ -43,4 +47,8 @@ int get_process_info(int pid, uid_t* uid, gid_t* gid, int* nspid); // -1, if the attempt failed. int enter_ns(int pid, const char* type); +#ifdef __cplusplus +} +#endif + #endif // _PSUTIL_H diff --git a/src/windows/jattach.c b/src/windows/jattach.c index 1318a94..f25f1d6 100644 --- a/src/windows/jattach.c +++ b/src/windows/jattach.c @@ -1,5 +1,5 @@ /* - * Copyright 2016 Andrei Pangin + * Copyright jattach authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -85,10 +85,19 @@ static LPVOID allocate_data(HANDLE hProcess, char* pipeName, int argc, char** ar strcpy(data.strJvm, "jvm"); strcpy(data.strEnqueue, "_JVM_EnqueueOperation"); strcpy(data.pipeName, pipeName); + data.args[0][0] = data.args[1][0] = data.args[2][0] = data.args[3][0] = 0; + // jcmd has 2 arguments maximum; merge excessive arguments into one + int cmd_args = argc >= 2 && strcmp(argv[0], "jcmd") == 0 ? 2 : argc >= 4 ? 4 : argc; + + size_t n = 0; int i; - for (i = 0; i < 4; i++) { - strcpy(data.args[i], i < argc ? argv[i] : ""); + for (i = 0; i < argc; i++) { + if (i < cmd_args) { + n = snprintf(data.args[i], sizeof(data.args[i]), "%s", argv[i]); + } else if (n < sizeof(data.args[cmd_args - 1])) { + n += snprintf(data.args[cmd_args - 1] + n, sizeof(data.args[cmd_args - 1]) - n, " %s", argv[i]); + } } LPVOID remoteData = VirtualAllocEx(hProcess, NULL, sizeof(CallData), MEM_COMMIT, PAGE_READWRITE); @@ -203,7 +212,7 @@ static int inject_thread(int pid, char* pipeName, int argc, char** argv) { } // JVM response is read from the pipe and mirrored to stdout -static int read_response(HANDLE hPipe) { +static int read_response(HANDLE hPipe, int print_output) { ConnectNamedPipe(hPipe, NULL); char buf[8192]; @@ -217,14 +226,19 @@ static int read_response(HANDLE hPipe) { buf[bytesRead] = 0; int result = atoi(buf); - do { - fwrite(buf, 1, bytesRead, stdout); - } while (ReadFile(hPipe, buf, sizeof(buf), &bytesRead, NULL)); + if (print_output) { + // Mirror JVM response to stdout + printf("JVM response code = "); + do { + fwrite(buf, 1, bytesRead, stdout); + } while (ReadFile(hPipe, buf, sizeof(buf), &bytesRead, NULL)); + printf("\n"); + } return result; } -int jattach(int pid, int argc, char** argv) { +int jattach(int pid, int argc, char** argv, int print_output) { // When attaching as an Administrator, make sure the target process can connect to our pipe, // i.e. allow read-write access to everyone. For the complete format description, see // https://docs.microsoft.com/en-us/windows/win32/secauthz/security-descriptor-string-format @@ -236,7 +250,7 @@ int jattach(int pid, int argc, char** argv) { sprintf(pipeName, "\\\\.\\pipe\\javatool%d", GetTickCount()); HANDLE hPipe = CreateNamedPipe(pipeName, PIPE_ACCESS_INBOUND, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, 1, 4096, 8192, NMPWAIT_USE_DEFAULT_WAIT, &sec); - if (hPipe == NULL) { + if (hPipe == INVALID_HANDLE_VALUE) { print_error("Could not create pipe", GetLastError()); LocalFree(sec.lpSecurityDescriptor); return 1; @@ -249,11 +263,7 @@ int jattach(int pid, int argc, char** argv) { return 1; } - printf("Response code = "); - fflush(stdout); - - int result = read_response(hPipe); - printf("\n"); + int result = read_response(hPipe, print_output); CloseHandle(hPipe); return result; @@ -264,7 +274,6 @@ int jattach(int pid, int argc, char** argv) { int main(int argc, char** argv) { if (argc < 3) { printf("jattach " JATTACH_VERSION " built on " __DATE__ "\n" - "Copyright 2021 Andrei Pangin\n" "\n" "Usage: jattach [args ...]\n" "\n" @@ -281,7 +290,7 @@ int main(int argc, char** argv) { return 1; } - return jattach(pid, argc - 2, argv + 2); + return jattach(pid, argc - 2, argv + 2, 1); } #endif // JATTACH_VERSION -- 2.39.2