From: Sven Hoexter Date: Tue, 16 Jan 2024 12:49:37 +0000 (+0100) Subject: releasing package jattach version 2.2-1 X-Git-Tag: debian/2.2-1^0 X-Git-Url: https://git.sven.stormbind.net/?p=sven%2Fjattach.git;a=commitdiff_plain;h=HEAD;hp=48d56245ea1be9174d88a2b91229582bf8602ed4 releasing package jattach version 2.2-1 --- diff --git a/Makefile b/Makefile index 4b04acf..078bca3 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -JATTACH_VERSION=2.0 +JATTACH_VERSION=2.2 ifneq ($(findstring Windows,$(OS)),) CL=cl.exe @@ -6,13 +6,14 @@ ifneq ($(findstring Windows,$(OS)),) JATTACH_EXE=jattach.exe JATTACH_DLL=jattach.dll else - CFLAGS ?= -O3 JATTACH_EXE=jattach UNAME_S:=$(shell uname -s) ifeq ($(UNAME_S),Darwin) + CFLAGS ?= -O3 -arch x86_64 -arch arm64 -mmacos-version-min=10.12 JATTACH_DLL=libjattach.dylib else + CFLAGS ?= -O3 JATTACH_DLL=libjattach.so endif @@ -38,7 +39,7 @@ build/jattach: src/posix/*.c src/posix/*.h $(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) -DJATTACH_VERSION=\"$(JATTACH_VERSION)\" -o $@ src/posix/*.c build/$(JATTACH_DLL): src/posix/*.c src/posix/*.h - $(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) -DJATTACH_VERSION=\"$(JATTACH_VERSION)\" -fPIC -shared -fvisibility=hidden -o $@ src/posix/*.c + $(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) -fPIC -shared -fvisibility=hidden -o $@ src/posix/*.c build/jattach.exe: src/windows/jattach.c $(CL) $(CFLAGS) /DJATTACH_VERSION=\"$(JATTACH_VERSION)\" /Fobuild/jattach.obj /Fe$@ $^ advapi32.lib /link /SUBSYSTEM:CONSOLE,5.02 diff --git a/README.md b/README.md index 711ffe6..9584204 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ ### JVM Dynamic Attach utility -The utility to send commands to remote JVM via Dynamic Attach mechanism. +The utility to send commands to a JVM process via Dynamic Attach mechanism. All-in-one **jmap + jstack + jcmd + jinfo** functionality in a single tiny program. No installed JDK required, works with just JRE. Supports Linux containers. @@ -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,23 +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 + +#### FreeBSD + +On FreeBSD, you can use the following command to install `jattach`: + + # pkg install jattach + diff --git a/debian/changelog b/debian/changelog index 146d119..c115385 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,4 +1,23 @@ -jattach (2.0-1) UNRELEASED; urgency=medium +jattach (2.2-1) unstable; urgency=medium + + * New upstream release. + * Update Standards-Version to 4.6.2 no changes required. + * Update debian/copyright, remove years for debian/* content. + + -- Sven Hoexter Tue, 16 Jan 2024 13:49:13 +0100 + +jattach (2.1-1) unstable; urgency=medium + + [ Sven Hoexter ] + * New upstream release. + * Update Standards-Version to 4.6.1 - no changes required. + * Drop additional LDFLAGS -Wl,--as-needed. + * Update dates to 2022 in debian/copyright. + * Set Rules-Requires-Root: no in debian/control. + + -- Sven Hoexter Mon, 25 Jul 2022 21:01:27 +0200 + +jattach (2.0-1) unstable; urgency=medium * New upstream release. + Adds OpenJ9 support. @@ -7,7 +26,7 @@ jattach (2.0-1) UNRELEASED; urgency=medium * Update Standards-Version to 4.5.1 - no changes required. * Raise debhelper-compat level from 12 to 13. - -- Sven Hoexter Mon, 16 Aug 2021 10:50:52 +0200 + -- Sven Hoexter Mon, 16 Aug 2021 20:06:29 +0200 jattach (1.5-2) unstable; urgency=medium diff --git a/debian/control b/debian/control index 3f232aa..0f168f5 100644 --- a/debian/control +++ b/debian/control @@ -3,10 +3,11 @@ Section: java Priority: optional Maintainer: Sven Hoexter Build-Depends: debhelper-compat (=13) -Standards-Version: 4.5.1 +Standards-Version: 4.6.2 Homepage: https://github.com/apangin/jattach Vcs-Browser: https://git.sven.stormbind.net/?p=sven/jattach.git Vcs-Git: https://git.sven.stormbind.net/jattach.git +Rules-Requires-Root: no Package: jattach Architecture: any diff --git a/debian/copyright b/debian/copyright index 43de908..6073fd7 100644 --- a/debian/copyright +++ b/debian/copyright @@ -3,11 +3,11 @@ Upstream-Name: jattach Source: https://github.com/apangin/jattach/releases Files: * -Copyright: 2016-2019 Andrei Pangin +Copyright: 2016-2024 Andrei Pangin, jattach authors License: Apache Files: debian/* -Copyright: 2019 Sven Hoexter +Copyright: Sven Hoexter License: Apache License: Apache diff --git a/debian/jattach.1 b/debian/jattach.1 index 22335f9..62c2195 100644 --- a/debian/jattach.1 +++ b/debian/jattach.1 @@ -1,10 +1,10 @@ .\" Hey, EMACS: -*- nroff -*- -.\" (C) Copyright 2019-2020 Sven Hoexter , +.\" (C) Copyright 2019-2024 Sven Hoexter , .\" .\" First parameter, NAME, should be all caps .\" Second parameter, SECTION, should be 1-8, maybe w/ subsection .\" other parameters are allowed: see man(7), man(1) -.TH JATTACH 1 "February 28 2020" +.TH JATTACH 1 "January 16 2024" .\" Please adjust this date whenever revising the manpage. .\" .\" Some roff macros, for reference: @@ -23,7 +23,7 @@ jattach \- dynamc attach utility for the jvm .B jattach .RI "pid [load|properties|agentProperties|datadump|threaddump|dumpheap|inspectheap|setflag|printflag|jcmd] [args]" .SH DESCRIPTION -jattach is an all in one jamp, jstack, jcmd, jinfo implementation as a tiny single C program. +jattach is an all in one jmap, jstack, jcmd, jinfo implementation as a tiny single C program. .SH OPTIONS .B load load agent library diff --git a/debian/rules b/debian/rules index b959739..ac1a236 100755 --- a/debian/rules +++ b/debian/rules @@ -1,7 +1,6 @@ #!/usr/bin/make -f #export DH_VERBOSE = 1 export DEB_BUILD_MAINT_OPTIONS = hardening=+all -export DEB_LDFLAGS_MAINT_APPEND = -Wl,--as-needed %: dh $@ diff --git a/jattach.spec b/jattach.spec index 529cf0e..fdac742 100644 --- a/jattach.spec +++ b/jattach.spec @@ -1,11 +1,11 @@ Name: jattach -Version: 2.0 +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,15 @@ 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 + * Wed Aug 11 2021 Vadim Tsesko - 2.0-1 - Attach to OpenJ9 VMs - Pass agent error codes diff --git a/src/posix/jattach.c b/src/posix/jattach.c index 804d13d..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,16 +60,17 @@ 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); } } +#ifdef JATTACH_VERSION + 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" @@ -84,5 +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 f34f4cc..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) { @@ -273,7 +281,8 @@ static int write_reply_info(int pid, int port, unsigned long long key) { } int chars = snprintf(path, sizeof(path), "%016llx\n%d\n", key, port); - write(fd, path, chars); + ssize_t r = write(fd, path, chars); + (void)r; close(fd); return 0; @@ -327,6 +336,10 @@ static int accept_client(int s, unsigned long long key) { return -1; } + // Reset the timeout, as the command execution may take arbitrary long time + struct timeval tv0 = {0, 0}; + setsockopt(client, SOL_SOCKET, SO_RCVTIMEO, &tv0, sizeof(tv0)); + return client; } @@ -368,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"); @@ -406,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); @@ -417,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 847a060..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; } @@ -129,15 +128,18 @@ int get_process_info(int pid, uid_t* uid, gid_t* gid, int* nspid) { int nspid_found = 0; while (getline(&line, &size, status_file) != -1) { - if (strncmp(line, "Uid:", 4) == 0) { + if (strncmp(line, "Uid:", 4) == 0 && strtok(line + 4, "\t ") != NULL) { // Get the effective UID, which is the second value in the line - *uid = (uid_t)atoi(strchr(line + 5, '\t')); - } else if (strncmp(line, "Gid:", 4) == 0) { + *uid = (uid_t)atoi(strtok(NULL, "\t ")); + } else if (strncmp(line, "Gid:", 4) == 0 && strtok(line + 4, "\t ") != NULL) { // Get the effective GID, which is the second value in the line - *gid = (gid_t)atoi(strchr(line + 5, '\t')); + *gid = (gid_t)atoi(strtok(NULL, "\t ")); } else if (strncmp(line, "NStgid:", 7) == 0) { // PID namespaces can be nested; the last one is the innermost one - *nspid = atoi(strrchr(line, '\t')); + char* s; + for (s = strtok(line + 7, "\t "); s != NULL; s = strtok(NULL, "\t ")) { + *nspid = atoi(s); + } nspid_found = 1; } } 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 b43e2f8..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. @@ -16,7 +16,8 @@ #include #include -#include +#include +#include typedef HMODULE (WINAPI *GetModuleHandle_t)(LPCTSTR lpModuleName); typedef FARPROC (WINAPI *GetProcAddress_t)(HMODULE hModule, LPCSTR lpProcName); @@ -84,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); @@ -202,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]; @@ -216,17 +226,54 @@ 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 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 + SECURITY_ATTRIBUTES sec = {sizeof(SECURITY_ATTRIBUTES), NULL, FALSE}; + ConvertStringSecurityDescriptorToSecurityDescriptor("D:(A;;GRGW;;;WD)", SDDL_REVISION_1, + &sec.lpSecurityDescriptor, NULL); + + char pipeName[MAX_PATH]; + 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 == INVALID_HANDLE_VALUE) { + print_error("Could not create pipe", GetLastError()); + LocalFree(sec.lpSecurityDescriptor); + return 1; + } + + LocalFree(sec.lpSecurityDescriptor); + + if (!inject_thread(pid, pipeName, argc, argv)) { + CloseHandle(hPipe); + return 1; + } + + int result = read_response(hPipe, print_output); + CloseHandle(hPipe); + + return result; +} + +#ifdef JATTACH_VERSION + 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" @@ -238,27 +285,12 @@ int main(int argc, char** argv) { } int pid = atoi(argv[1]); - - char pipeName[MAX_PATH]; - 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, NULL); - if (hPipe == NULL) { - print_error("Could not create pipe", GetLastError()); + if (pid <= 0) { + fprintf(stderr, "%s is not a valid process ID\n", argv[1]); return 1; } - if (!inject_thread(pid, pipeName, argc - 2, argv + 2)) { - CloseHandle(hPipe); - return 1; - } - - printf("Response code = "); - fflush(stdout); - - int result = read_response(hPipe); - printf("\n"); - CloseHandle(hPipe); - - return result; + return jattach(pid, argc - 2, argv + 2, 1); } + +#endif // JATTACH_VERSION