pax_global_header00006660000000000000000000000064141046115510014510gustar00rootroot0000000000000052 comment=6de69a90ed2b5dc7d824ebf13addce47a52e789a jattach-2.0/000077500000000000000000000000001410461155100127675ustar00rootroot00000000000000jattach-2.0/.gitignore000066400000000000000000000000111410461155100147470ustar00rootroot00000000000000/build/ jattach-2.0/LICENSE000066400000000000000000000261351410461155100140030ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright {yyyy} {name of copyright owner} 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. jattach-2.0/Makefile000066400000000000000000000034421410461155100144320ustar00rootroot00000000000000JATTACH_VERSION=2.0 ifneq ($(findstring Windows,$(OS)),) CL=cl.exe CFLAGS=/O2 /D_CRT_SECURE_NO_WARNINGS JATTACH_EXE=jattach.exe JATTACH_DLL=jattach.dll else CFLAGS ?= -O3 JATTACH_EXE=jattach UNAME_S:=$(shell uname -s) ifeq ($(UNAME_S),Darwin) JATTACH_DLL=libjattach.dylib else JATTACH_DLL=libjattach.so endif ifeq ($(UNAME_S),Linux) ROOT_DIR:=$(shell dirname $(realpath $(lastword $(MAKEFILE_LIST)))) RPM_ROOT=$(ROOT_DIR)/build/rpm SOURCES=$(RPM_ROOT)/SOURCES SPEC_FILE=jattach.spec endif endif .PHONY: all dll clean rpm-dirs rpm all: build build/$(JATTACH_EXE) dll: build build/$(JATTACH_DLL) build: mkdir -p build 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 build/jattach.exe: src/windows/jattach.c $(CL) $(CFLAGS) /DJATTACH_VERSION=\"$(JATTACH_VERSION)\" /Fobuild/jattach.obj /Fe$@ $^ advapi32.lib /link /SUBSYSTEM:CONSOLE,5.02 clean: rm -rf build $(RPM_ROOT): mkdir -p $(RPM_ROOT) rpm-dirs: $(RPM_ROOT) mkdir -p $(RPM_ROOT)/SPECS mkdir -p $(SOURCES)/bin mkdir -p $(RPM_ROOT)/BUILD mkdir -p $(RPM_ROOT)/SRPMS mkdir -p $(RPM_ROOT)/RPMS mkdir -p $(RPM_ROOT)/tmp rpm: rpm-dirs build build/$(JATTACH_EXE) cp $(SPEC_FILE) $(RPM_ROOT)/ cp build/jattach $(SOURCES)/bin/ rpmbuild -bb \ --define '_topdir $(RPM_ROOT)' \ --define '_tmppath $(RPM_ROOT)/tmp' \ --clean \ --rmsource \ --rmspec \ --buildroot $(RPM_ROOT)/tmp/build-root \ $(RPM_ROOT)/jattach.spec jattach-2.0/README.md000066400000000000000000000045601410461155100142530ustar00rootroot00000000000000## jattach ### JVM Dynamic Attach utility The utility to send commands to remote JVM 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. This is the lightweight native version of HotSpot Attach API https://docs.oracle.com/javase/8/docs/jdk/api/attach/spec/ [Supported commands](http://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/812ed44725b8/src/share/vm/services/attachListener.cpp#l388): - **load** : load agent library - **properties** : print system properties - **agentProperties** : print agent properties - **datadump** : show heap and thread summary - **threaddump** : dump all stack traces (like jstack) - **dumpheap** : dump heap (like jmap) - **inspectheap** : heap histogram (like jmap -histo) - **setflag** : modify manageable VM flag - **printflag** : print VM flag - **jcmd** : execute jcmd command ### Download Binaries are available on the [Releases](https://github.com/apangin/jattach/releases) page. On some platforms, you can also [install](#installation) jattach with a package manager. ### Examples #### Load native agent $ jattach load <.so-path> { true | false } [ options ] Where `true` means that the path is absolute, `false` -- the path is relative. `options` are passed to the agent. #### Load Java agent Java agents are loaded by the special built-in native agent named `instrument`, which takes .jar path and its arguments as a single options string. $ jattach load instrument false "javaagent.jar=arguments" #### List available jcmd commands $ jattach jcmd "help -all" ### Installation #### FreeBSD On FreeBSD, you can use the following command to install `jattach` package: $ pkg install jattach #### Alpine Linux On Alpine Linux, you can use the following command to install `jattach` package from the edge/community repository: $ 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 jattach-2.0/jattach.spec000066400000000000000000000021151410461155100152600ustar00rootroot00000000000000Name: jattach Version: 2.0 Release: 1 Summary: JVM Dynamic Attach utility Group: Development/Tools License: ASL 2.0 URL: https://github.com/apangin/jattach Vendor: Andrei Pangin Packager: Vadim Tsesko BuildRequires: gcc BuildRequires: make %description The utility to send commands to remote JVM 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. This is the lightweight native version of HotSpot Attach API: https://docs.oracle.com/javase/8/docs/jdk/api/attach/spec/ %build # Do nothing %install BIN=%{buildroot}/usr/bin mkdir -p ${BIN} install -p -m 555 %{_sourcedir}/bin/jattach ${BIN} %files /usr/bin/jattach %changelog * Wed Aug 11 2021 Vadim Tsesko - 2.0-1 - Attach to OpenJ9 VMs - Pass agent error codes - Improved container support * Wed Jan 09 2018 Vadim Tsesko - 1.5-1 - Improved attach to containerized JVMs - chroot support * Wed Nov 30 2016 Vadim Tsesko - 0.1-1 - Initial version jattach-2.0/src/000077500000000000000000000000001410461155100135565ustar00rootroot00000000000000jattach-2.0/src/posix/000077500000000000000000000000001410461155100147205ustar00rootroot00000000000000jattach-2.0/src/posix/jattach.c000066400000000000000000000055101410461155100165030ustar00rootroot00000000000000/* * 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 "psutil.h" 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); __attribute__((visibility("default"))) int jattach(int pid, int argc, char** argv) { uid_t my_uid = geteuid(); gid_t my_gid = getegid(); uid_t target_uid = my_uid; gid_t target_gid = my_gid; int nspid; if (get_process_info(pid, &target_uid, &target_gid, &nspid) < 0) { fprintf(stderr, "Process %d not found\n", pid); return 1; } // Container support: switch to the target namespaces. // Network and IPC namespaces are essential for OpenJ9 connection. enter_ns(pid, "net"); enter_ns(pid, "ipc"); int 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. if ((my_gid != target_gid && setegid(target_gid) != 0) || (my_uid != target_uid && seteuid(target_uid) != 0)) { perror("Failed to change credentials to match the target process"); return 1; } get_tmp_path(mnt_changed > 0 ? nspid : pid); // Make write() return EPIPE instead of abnormal process termination signal(SIGPIPE, SIG_IGN); if (is_openj9_process(nspid)) { return jattach_openj9(pid, nspid, argc, argv); } else { return jattach_hotspot(pid, nspid, argc, 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" "Commands:\n" " load threaddump dumpheap setflag properties\n" " jcmd inspectheap datadump printflag agentProperties\n" ); return 1; } int pid = atoi(argv[1]); if (pid <= 0) { fprintf(stderr, "%s is not a valid process ID\n", argv[1]); return 1; } return jattach(pid, argc - 2, argv + 2); } jattach-2.0/src/posix/jattach_hotspot.c000066400000000000000000000124541410461155100202700ustar00rootroot00000000000000/* * 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 "psutil.h" // Check if remote JVM has already opened socket for Dynamic Attach static int check_socket(int pid) { char path[MAX_PATH]; snprintf(path, sizeof(path), "%s/.java_pid%d", tmp_path, pid); struct stat stats; return stat(path, &stats) == 0 && S_ISSOCK(stats.st_mode) ? 0 : -1; } // Check if a file is owned by current user static uid_t get_file_owner(const char* path) { struct stat stats; return stat(path, &stats) == 0 ? stats.st_uid : (uid_t)-1; } // Force remote JVM to start Attach listener. // 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); int fd = creat(path, 0660); if (fd == -1 || (close(fd) == 0 && get_file_owner(path) != geteuid())) { // Some mounted filesystems may change the ownership of the file. // JVM will not trust such file, so it's better to remove it and try a different path unlink(path); // Failed to create attach trigger in current directory. Retry in /tmp snprintf(path, sizeof(path), "%s/.attach_pid%d", tmp_path, nspid); fd = creat(path, 0660); if (fd == -1) { return -1; } close(fd); } // We have to still use the host namespace pid here for the kill() call kill(pid, SIGQUIT); // Start with 20 ms sleep and increment delay each iteration. Total timeout is 6000 ms struct timespec ts = {0, 20000000}; int result; do { nanosleep(&ts, NULL); result = check_socket(nspid); } while (result != 0 && (ts.tv_nsec += 20000000) < 500000000); unlink(path); return result; } // Connect to UNIX domain socket created by JVM for Dynamic Attach static int connect_socket(int pid) { int fd = socket(PF_UNIX, SOCK_STREAM, 0); if (fd == -1) { return -1; } struct sockaddr_un addr; addr.sun_family = AF_UNIX; int bytes = snprintf(addr.sun_path, sizeof(addr.sun_path), "%s/.java_pid%d", tmp_path, pid); if (bytes >= sizeof(addr.sun_path)) { addr.sun_path[sizeof(addr.sun_path) - 1] = 0; } if (connect(fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) { close(fd); return -1; } return fd; } // Send command with arguments to socket static int write_command(int fd, int argc, char** argv) { // Protocol version if (write(fd, "1", 2) <= 0) { return -1; } int i; for (i = 0; i < 4; i++) { const char* arg = i < argc ? argv[i] : ""; if (write(fd, arg, strlen(arg) + 1) <= 0) { return -1; } } return 0; } // Mirror response from remote JVM to stdout static int read_response(int fd, int argc, char** argv) { char buf[8192]; ssize_t bytes = read(fd, buf, sizeof(buf) - 1); if (bytes == 0) { fprintf(stderr, "Unexpected EOF reading response\n"); return 1; } else if (bytes < 0) { perror("Error reading response"); return 1; } // First line of response is the command result code buf[bytes] = 0; int result = atoi(buf); // Special treatment of 'load' command if (result == 0 && argc > 0 && strcmp(argv[0], "load") == 0) { size_t total = bytes; while (total < sizeof(buf) - 1 && (bytes = read(fd, buf + total, sizeof(buf) - 1 - total)) > 0) { total += (size_t)bytes; } bytes = total; // The second line is the result of 'load' command; since JDK 9 it starts from "return code: " buf[bytes] = 0; 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"); return result; } int jattach_hotspot(int pid, int nspid, int argc, char** argv) { if (check_socket(nspid) != 0 && start_attach_mechanism(pid, nspid) != 0) { perror("Could not start attach mechanism"); return 1; } int fd = connect_socket(nspid); if (fd == -1) { perror("Could not connect to socket"); return 1; } printf("Connected to remote JVM\n"); if (write_command(fd, argc, argv) != 0) { perror("Error writing to socket"); close(fd); return 1; } int result = read_response(fd, argc, argv); close(fd); return result; } jattach-2.0/src/posix/jattach_openj9.c000066400000000000000000000270741410461155100200000ustar00rootroot00000000000000/* * 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; } jattach-2.0/src/posix/psutil.c000066400000000000000000000160051410461155100164060ustar00rootroot00000000000000/* * 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 "psutil.h" // Less than MAX_PATH to leave some space for appending char tmp_path[MAX_PATH - 100]; // Called just once to fill in tmp_path buffer void get_tmp_path(int pid) { // Try user-provided alternative path first const char* jattach_path = getenv("JATTACH_PATH"); if (jattach_path != NULL && strlen(jattach_path) < sizeof(tmp_path)) { strcpy(tmp_path, jattach_path); return; } if (get_tmp_path_r(pid, tmp_path, sizeof(tmp_path)) != 0) { strcpy(tmp_path, "/tmp"); } } #ifdef __linux__ // The first line of /proc/pid/sched looks like // java (1234, #threads: 12) // where 1234 is the host PID (before Linux 4.1) static int sched_get_host_pid(const char* path) { static char* line = NULL; size_t size; int result = -1; FILE* sched_file = fopen(path, "r"); if (sched_file != NULL) { if (getline(&line, &size, sched_file) != -1) { char* c = strrchr(line, '('); if (c != NULL) { result = atoi(c + 1); } } fclose(sched_file); } return result; } // Linux kernels < 4.1 do not export NStgid field in /proc/pid/status. // Fortunately, /proc/pid/sched in a container exposes a host PID, // so the idea is to scan all container PIDs to find which one matches the host PID. static int alt_lookup_nspid(int pid) { char path[300]; snprintf(path, sizeof(path), "/proc/%d/ns/pid", pid); // Don't bother looking for container PID if we are already in the same PID namespace struct stat oldns_stat, newns_stat; if (stat("/proc/self/ns/pid", &oldns_stat) == 0 && stat(path, &newns_stat) == 0) { if (oldns_stat.st_ino == newns_stat.st_ino) { return pid; } } // Otherwise browse all PIDs in the namespace of the target process // trying to find which one corresponds to the host PID snprintf(path, sizeof(path), "/proc/%d/root/proc", pid); DIR* dir = opendir(path); if (dir != NULL) { struct dirent* entry; while ((entry = readdir(dir)) != NULL) { if (entry->d_name[0] >= '1' && entry->d_name[0] <= '9') { // 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); } } } closedir(dir); } // Could not find container pid; return host pid as the last resort return pid; } int get_tmp_path_r(int pid, char* buf, size_t bufsize) { if (snprintf(buf, bufsize, "/proc/%d/root/tmp", pid) >= bufsize) { return -1; } // Check if the remote /tmp can be accessed via /proc/[pid]/root struct stat stats; return stat(buf, &stats); } int get_process_info(int pid, uid_t* uid, gid_t* gid, int* nspid) { // Parse /proc/pid/status to find process credentials char path[64]; snprintf(path, sizeof(path), "/proc/%d/status", pid); FILE* status_file = fopen(path, "r"); if (status_file == NULL) { return -1; } char* line = NULL; size_t size; int nspid_found = 0; while (getline(&line, &size, status_file) != -1) { if (strncmp(line, "Uid:", 4) == 0) { // 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) { // Get the effective GID, which is the second value in the line *gid = (gid_t)atoi(strchr(line + 5, '\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')); nspid_found = 1; } } free(line); fclose(status_file); if (!nspid_found) { *nspid = alt_lookup_nspid(pid); } return 0; } int enter_ns(int pid, const char* type) { #ifdef __NR_setns char path[64], selfpath[64]; snprintf(path, sizeof(path), "/proc/%d/ns/%s", pid, type); snprintf(selfpath, sizeof(selfpath), "/proc/self/ns/%s", type); struct stat oldns_stat, newns_stat; if (stat(selfpath, &oldns_stat) == 0 && stat(path, &newns_stat) == 0) { // Don't try to call setns() if we're in the same namespace already if (oldns_stat.st_ino != newns_stat.st_ino) { int newns = open(path, O_RDONLY); if (newns < 0) { return -1; } // Some ancient Linux distributions do not have setns() function int result = syscall(__NR_setns, newns, 0); close(newns); return result < 0 ? -1 : 1; } } #endif // __NR_setns return 0; } #elif defined(__APPLE__) #include // macOS has a secure per-user temporary directory int get_tmp_path_r(int pid, char* buf, size_t bufsize) { size_t path_size = confstr(_CS_DARWIN_USER_TEMP_DIR, buf, bufsize); return path_size > 0 && path_size <= sizeof(tmp_path) ? 0 : -1; } int get_process_info(int pid, uid_t* uid, gid_t* gid, int* nspid) { int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, pid}; struct kinfo_proc info; size_t len = sizeof(info); if (sysctl(mib, 4, &info, &len, NULL, 0) < 0 || len <= 0) { return -1; } *uid = info.kp_eproc.e_ucred.cr_uid; *gid = info.kp_eproc.e_ucred.cr_gid; *nspid = pid; return 0; } // This is a Linux-specific API; nothing to do on macOS and FreeBSD int enter_ns(int pid, const char* type) { return 0; } #else // __FreeBSD__ #include #include // Use default /tmp path on FreeBSD int get_tmp_path_r(int pid, char* buf, size_t bufsize) { return -1; } int get_process_info(int pid, uid_t* uid, gid_t* gid, int* nspid) { int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, pid}; struct kinfo_proc info; size_t len = sizeof(info); if (sysctl(mib, 4, &info, &len, NULL, 0) < 0 || len <= 0) { return -1; } *uid = info.ki_uid; *gid = info.ki_groups[0]; *nspid = pid; return 0; } // This is a Linux-specific API; nothing to do on macOS and FreeBSD int enter_ns(int pid, const char* type) { return 0; } #endif jattach-2.0/src/posix/psutil.h000066400000000000000000000031421410461155100164110ustar00rootroot00000000000000/* * 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. */ #ifndef _PSUTIL_H #define _PSUTIL_H #include #define MAX_PATH 1024 extern char tmp_path[]; // Gets /tmp path of the specified process, as it can be accessed from the host. // The obtained path is stored in the global tmp_path buffer. void get_tmp_path(int pid); // The reentrant version of get_tmp_path. // Stores the process-specific temporary path into the provided buffer. // Returns 0 on success, -1 on failure. int get_tmp_path_r(int pid, char* buf, size_t bufsize); // Gets the owner uid/gid of the target process, and also its pid inside the container. // Returns 0 on success, -1 on failure. int get_process_info(int pid, uid_t* uid, gid_t* gid, int* nspid); // Tries to enter the namespace of the target process. // type of the namespace can be "mnt", "net", "pid", etc. // Returns 1, if the namespace has been successfully changed, // 0, if the target process is in the same namespace as the host, // -1, if the attempt failed. int enter_ns(int pid, const char* type); #endif // _PSUTIL_H jattach-2.0/src/windows/000077500000000000000000000000001410461155100152505ustar00rootroot00000000000000jattach-2.0/src/windows/jattach.c000066400000000000000000000205631410461155100170400ustar00rootroot00000000000000/* * Copyright 2016 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 typedef HMODULE (WINAPI *GetModuleHandle_t)(LPCTSTR lpModuleName); typedef FARPROC (WINAPI *GetProcAddress_t)(HMODULE hModule, LPCSTR lpProcName); typedef int (__stdcall *JVM_EnqueueOperation_t)(char* cmd, char* arg0, char* arg1, char* arg2, char* pipename); typedef struct { GetModuleHandle_t GetModuleHandleA; GetProcAddress_t GetProcAddress; char strJvm[32]; char strEnqueue[32]; char pipeName[MAX_PATH]; char args[4][MAX_PATH]; } CallData; #pragma check_stack(off) // This code is executed in remote JVM process; be careful with memory it accesses static DWORD WINAPI remote_thread_entry(LPVOID param) { CallData* data = (CallData*)param; HMODULE libJvm = data->GetModuleHandleA(data->strJvm); if (libJvm == NULL) { return 1001; } JVM_EnqueueOperation_t JVM_EnqueueOperation = (JVM_EnqueueOperation_t)data->GetProcAddress(libJvm, data->strEnqueue + 1); if (JVM_EnqueueOperation == NULL) { // Try alternative name: _JVM_EnqueueOperation@20 data->strEnqueue[21] = '@'; data->strEnqueue[22] = '2'; data->strEnqueue[23] = '0'; data->strEnqueue[24] = 0; JVM_EnqueueOperation = (JVM_EnqueueOperation_t)data->GetProcAddress(libJvm, data->strEnqueue); if (JVM_EnqueueOperation == NULL) { return 1002; } } return (DWORD)JVM_EnqueueOperation(data->args[0], data->args[1], data->args[2], data->args[3], data->pipeName); } static VOID WINAPI remote_thread_entry_end() { } #pragma check_stack // Allocate executable memory in remote process static LPTHREAD_START_ROUTINE allocate_code(HANDLE hProcess) { SIZE_T codeSize = (SIZE_T)remote_thread_entry_end - (SIZE_T)remote_thread_entry; LPVOID code = VirtualAllocEx(hProcess, NULL, codeSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE); if (code != NULL) { WriteProcessMemory(hProcess, code, remote_thread_entry, codeSize, NULL); } return (LPTHREAD_START_ROUTINE)code; } // Allocate memory for CallData in remote process static LPVOID allocate_data(HANDLE hProcess, char* pipeName, int argc, char** argv) { CallData data; data.GetModuleHandleA = GetModuleHandleA; data.GetProcAddress = GetProcAddress; strcpy(data.strJvm, "jvm"); strcpy(data.strEnqueue, "_JVM_EnqueueOperation"); strcpy(data.pipeName, pipeName); int i; for (i = 0; i < 4; i++) { strcpy(data.args[i], i < argc ? argv[i] : ""); } LPVOID remoteData = VirtualAllocEx(hProcess, NULL, sizeof(CallData), MEM_COMMIT, PAGE_READWRITE); if (remoteData != NULL) { WriteProcessMemory(hProcess, remoteData, &data, sizeof(data), NULL); } return remoteData; } static void print_error(const char* msg, DWORD code) { printf("%s (error code = %d)\n", msg, code); } // If the process is owned by another user, request SeDebugPrivilege to open it. // Debug privileges are typically granted to Administrators. static int enable_debug_privileges() { HANDLE hToken; if (!OpenThreadToken(GetCurrentThread(), TOKEN_ADJUST_PRIVILEGES, FALSE, &hToken)) { if (!ImpersonateSelf(SecurityImpersonation) || !OpenThreadToken(GetCurrentThread(), TOKEN_ADJUST_PRIVILEGES, FALSE, &hToken)) { return 0; } } LUID luid; if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luid)) { return 0; } TOKEN_PRIVILEGES tp; tp.PrivilegeCount = 1; tp.Privileges[0].Luid = luid; tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; BOOL success = AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULL, NULL); CloseHandle(hToken); return success ? 1 : 0; } // Fail if attaching 64-bit jattach to 32-bit JVM or vice versa static int check_bitness(HANDLE hProcess) { #ifdef _WIN64 BOOL targetWow64 = FALSE; if (IsWow64Process(hProcess, &targetWow64) && targetWow64) { printf("Cannot attach 64-bit process to 32-bit JVM\n"); return 0; } #else BOOL thisWow64 = FALSE; BOOL targetWow64 = FALSE; if (IsWow64Process(GetCurrentProcess(), &thisWow64) && IsWow64Process(hProcess, &targetWow64)) { if (thisWow64 != targetWow64) { printf("Cannot attach 32-bit process to 64-bit JVM\n"); return 0; } } #endif return 1; } // The idea of Dynamic Attach on Windows is to inject a thread into remote JVM // that calls JVM_EnqueueOperation() function exported by HotSpot DLL static int inject_thread(int pid, char* pipeName, int argc, char** argv) { HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, (DWORD)pid); if (hProcess == NULL && GetLastError() == ERROR_ACCESS_DENIED) { if (!enable_debug_privileges()) { print_error("Not enough privileges", GetLastError()); return 0; } hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, (DWORD)pid); } if (hProcess == NULL) { print_error("Could not open process", GetLastError()); return 0; } if (!check_bitness(hProcess)) { CloseHandle(hProcess); return 0; } LPTHREAD_START_ROUTINE code = allocate_code(hProcess); LPVOID data = code != NULL ? allocate_data(hProcess, pipeName, argc, argv) : NULL; if (data == NULL) { print_error("Could not allocate memory in target process", GetLastError()); CloseHandle(hProcess); return 0; } int success = 1; HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, code, data, 0, NULL); if (hThread == NULL) { print_error("Could not create remote thread", GetLastError()); success = 0; } else { printf("Connected to remote process\n"); WaitForSingleObject(hThread, INFINITE); DWORD exitCode; GetExitCodeThread(hThread, &exitCode); if (exitCode != 0) { print_error("Attach is not supported by the target process", exitCode); success = 0; } CloseHandle(hThread); } VirtualFreeEx(hProcess, code, 0, MEM_RELEASE); VirtualFreeEx(hProcess, data, 0, MEM_RELEASE); CloseHandle(hProcess); return success; } // JVM response is read from the pipe and mirrored to stdout static int read_response(HANDLE hPipe) { ConnectNamedPipe(hPipe, NULL); char buf[8192]; DWORD bytesRead; if (!ReadFile(hPipe, buf, sizeof(buf) - 1, &bytesRead, NULL)) { print_error("Error reading response", GetLastError()); return 1; } // First line of response is the command result code buf[bytesRead] = 0; int result = atoi(buf); do { fwrite(buf, 1, bytesRead, stdout); } while (ReadFile(hPipe, buf, sizeof(buf), &bytesRead, NULL)); return result; } 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" "Commands:\n" " load threaddump dumpheap setflag properties\n" " jcmd inspectheap datadump printflag agentProperties\n" ); return 1; } 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()); 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; }