--- /dev/null
+ 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.
--- /dev/null
+JATTACH_VERSION=1.5
+
+ifneq ($(findstring Windows,$(OS)),)
+ CL=cl.exe
+ CFLAGS=/O2 /D_CRT_SECURE_NO_WARNINGS
+ JATTACH_EXE=jattach.exe
+else
+ UNAME_S:=$(shell uname -s)
+ ifneq ($(findstring FreeBSD,$(UNAME_S)),)
+ CC=cc
+ CFLAGS=-O2
+ JATTACH_EXE=jattach
+ else
+ ROOT_DIR:=$(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
+ RPM_ROOT=$(ROOT_DIR)/build/rpm
+ SOURCES=$(RPM_ROOT)/SOURCES
+ SPEC_FILE=jattach.spec
+ CC=gcc
+ CFLAGS=-O2
+ JATTACH_EXE=jattach
+ endif
+endif
+
+all: build build/$(JATTACH_EXE)
+
+build:
+ mkdir -p build
+
+build/jattach: src/jattach_posix.c
+ $(CC) $(CFLAGS) -DJATTACH_VERSION=\"$(JATTACH_VERSION)\" -o $@ $^
+
+build/jattach.exe: src/jattach_windows.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
--- /dev/null
+## 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
+
+### Examples
+#### Load JVMTI agent
+
+ $ jattach <pid> 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.
+
+#### List available jcmd commands
+
+ $ jattach <pid> 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/testing repository:
+
+ $ apk add --no-cache jattach --repository http://dl-cdn.alpinelinux.org/alpine/edge/testing/
+
+#### 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
--- /dev/null
+Name: jattach
+Version: 1.3
+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 <incubos@yandex.com>
+
+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 Nov 30 2016 Vadim Tsesko <incubos@yandex.com> - 0.1-1
+- Initial version
--- /dev/null
+/*
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/syscall.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <time.h>
+#include <unistd.h>
+
+#define MAX_PATH 1024
+#define TMP_PATH (MAX_PATH - 64)
+
+static char temp_path_storage[TMP_PATH] = {0};
+
+
+#ifdef __linux__
+
+const char* get_temp_path() {
+ return temp_path_storage;
+}
+
+int get_process_info(int pid, uid_t* uid, gid_t* gid, int* nspid) {
+ // A process may have its own root path (when running in chroot environment)
+ char path[64];
+ snprintf(path, sizeof(path), "/proc/%d/root", pid);
+
+ // Append /tmp to the resolved root symlink
+ ssize_t path_size = readlink(path, temp_path_storage, sizeof(temp_path_storage) - 10);
+ strcpy(temp_path_storage + (path_size > 1 ? path_size : 0), "/tmp");
+
+ // Parse /proc/pid/status to find process credentials
+ snprintf(path, sizeof(path), "/proc/%d/status", pid);
+ FILE* status_file = fopen(path, "r");
+ if (status_file == NULL) {
+ return 0;
+ }
+
+ char* line = NULL;
+ size_t size;
+
+ 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'));
+ }
+ }
+
+ free(line);
+ fclose(status_file);
+ return 1;
+}
+
+int enter_mount_ns(int pid) {
+#ifdef __NR_setns
+ char path[128];
+ snprintf(path, sizeof(path), "/proc/%d/ns/mnt", pid);
+
+ struct stat oldns_stat, newns_stat;
+ if (stat("/proc/self/ns/mnt", &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 0;
+ }
+
+ // Some ancient Linux distributions do not have setns() function
+ int result = syscall(__NR_setns, newns, 0);
+ close(newns);
+ return result < 0 ? 0 : 1;
+ }
+ }
+#endif // __NR_setns
+
+ return 1;
+}
+
+// The first line of /proc/pid/sched looks like
+// java (1234, #threads: 12)
+// where 1234 is the required host PID
+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.
+int alt_lookup_nspid(int pid) {
+ int namespace_differs = 0;
+ 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;
+ }
+ namespace_differs = 1;
+ }
+
+ // 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/<container-pid>/sched points back to <host-pid>
+ 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);
+ }
+
+ if (namespace_differs) {
+ printf("WARNING: couldn't find container pid of the target process\n");
+ }
+
+ return pid;
+}
+
+#elif defined(__APPLE__)
+
+#include <sys/sysctl.h>
+
+// macOS has a secure per-user temporary directory
+const char* get_temp_path() {
+ if (temp_path_storage[0] == 0) {
+ int path_size = confstr(_CS_DARWIN_USER_TEMP_DIR, temp_path_storage, sizeof(temp_path_storage));
+ if (path_size == 0 || path_size > sizeof(temp_path_storage)) {
+ strcpy(temp_path_storage, "/tmp");
+ }
+ }
+
+ return temp_path_storage;
+}
+
+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 0;
+ }
+
+ *uid = info.kp_eproc.e_ucred.cr_uid;
+ *gid = info.kp_eproc.e_ucred.cr_gid;
+ *nspid = pid;
+ return 1;
+}
+
+// This is a Linux-specific API; nothing to do on macOS and FreeBSD
+int enter_mount_ns(int pid) {
+ return 1;
+}
+
+// Not used on macOS and FreeBSD
+int alt_lookup_nspid(int pid) {
+ return pid;
+}
+
+#else // __FreeBSD__
+
+#include <sys/sysctl.h>
+#include <sys/user.h>
+
+const char* get_temp_path() {
+ return "/tmp";
+}
+
+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 0;
+ }
+
+ *uid = info.ki_uid;
+ *gid = info.ki_groups[0];
+ *nspid = pid;
+ return 1;
+}
+
+// This is a Linux-specific API; nothing to do on macOS and FreeBSD
+int enter_mount_ns(int pid) {
+ return 1;
+}
+
+// Not used on macOS and FreeBSD
+int alt_lookup_nspid(int pid) {
+ return pid;
+}
+
+#endif
+
+
+// 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", get_temp_path(), pid);
+
+ struct stat stats;
+ return stat(path, &stats) == 0 && S_ISSOCK(stats.st_mode);
+}
+
+// Check if a file is owned by current user
+static int check_file_owner(const char* path) {
+ struct stat stats;
+ if (stat(path, &stats) == 0 && stats.st_uid == geteuid()) {
+ return 1;
+ }
+
+ // 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);
+ return 0;
+}
+
+// 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 && !check_file_owner(path))) {
+ // Failed to create attach trigger in current directory. Retry in /tmp
+ snprintf(path, sizeof(path), "%s/.attach_pid%d", get_temp_path(), nspid);
+ fd = creat(path, 0660);
+ if (fd == -1) {
+ return 0;
+ }
+ 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
+ struct timespec ts = {0, 20000000};
+ int result;
+ do {
+ nanosleep(&ts, NULL);
+ result = check_socket(nspid);
+ } while (!result && (ts.tv_nsec += 20000000) < 300000000);
+
+ 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", get_temp_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 0;
+ }
+
+ int i;
+ for (i = 0; i < 4; i++) {
+ const char* arg = i < argc ? argv[i] : "";
+ if (write(fd, arg, strlen(arg) + 1) <= 0) {
+ return 0;
+ }
+ }
+ return 1;
+}
+
+// Mirror response from remote JVM to stdout
+static int read_response(int fd) {
+ char buf[8192];
+ ssize_t bytes = read(fd, buf, sizeof(buf) - 1);
+ 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);
+
+ do {
+ fwrite(buf, 1, bytes, stdout);
+ bytes = read(fd, buf, sizeof(buf));
+ } while (bytes > 0);
+
+ return result;
+}
+
+int main(int argc, char** argv) {
+ if (argc < 3) {
+ printf("jattach " JATTACH_VERSION " built on " __DATE__ "\n"
+ "Copyright 2018 Andrei Pangin\n"
+ "\n"
+ "Usage: jattach <pid> <cmd> [args ...]\n");
+ return 1;
+ }
+
+ int pid = atoi(argv[1]);
+ if (pid == 0) {
+ perror("Invalid pid provided");
+ return 1;
+ }
+
+ uid_t my_uid = geteuid();
+ gid_t my_gid = getegid();
+ uid_t target_uid = my_uid;
+ gid_t target_gid = my_gid;
+ int nspid = -1;
+ if (!get_process_info(pid, &target_uid, &target_gid, &nspid)) {
+ fprintf(stderr, "Process %d not found\n", pid);
+ return 1;
+ }
+
+ if (nspid < 0) {
+ nspid = alt_lookup_nspid(pid);
+ }
+
+ // Make sure our /tmp and target /tmp is the same
+ if (!enter_mount_ns(pid)) {
+ printf("WARNING: couldn't enter target process mnt namespace\n");
+ }
+
+ // 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;
+ }
+
+ // Make write() return EPIPE instead of silent process termination
+ signal(SIGPIPE, SIG_IGN);
+
+ if (!check_socket(nspid) && !start_attach_mechanism(pid, nspid)) {
+ 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 - 2, argv + 2)) {
+ perror("Error writing to socket");
+ close(fd);
+ return 1;
+ }
+
+ printf("Response code = ");
+ fflush(stdout);
+
+ int result = read_response(fd);
+ printf("\n");
+ close(fd);
+
+ return result;
+}
--- /dev/null
+/*
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <Windows.h>
+
+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
+DWORD WINAPI remote_thread_entry(LPVOID param) {
+ CallData* data = (CallData*)param;
+
+ HMODULE libJvm = data->GetModuleHandleA(data->strJvm);
+ if (libJvm != NULL) {
+ JVM_EnqueueOperation_t JVM_EnqueueOperation = (JVM_EnqueueOperation_t)data->GetProcAddress(libJvm, data->strEnqueue);
+ if (JVM_EnqueueOperation != NULL) {
+ return (DWORD)JVM_EnqueueOperation(data->args[0], data->args[1], data->args[2], data->args[3], data->pipeName);
+ }
+ }
+
+ return 0xffff;
+}
+
+#pragma check_stack
+
+
+// Allocate executable memory in remote process
+static LPTHREAD_START_ROUTINE allocate_code(HANDLE hProcess) {
+ SIZE_T codeSize = 1024;
+ 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;
+}
+
+// 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;
+ }
+
+ 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 2018 Andrei Pangin\n"
+ "\n"
+ "Usage: jattach <pid> <cmd> [args ...]\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;
+}