jruby-async-profiler 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +8 -0
  3. data/.gitmodules +3 -0
  4. data/Gemfile +4 -0
  5. data/README.md +35 -0
  6. data/Rakefile +2 -0
  7. data/bin/console +14 -0
  8. data/bin/setup +8 -0
  9. data/ext/Rakefile +6 -0
  10. data/ext/async-profiler/.gitattributes +1 -0
  11. data/ext/async-profiler/.gitignore +6 -0
  12. data/ext/async-profiler/.travis.yml +11 -0
  13. data/ext/async-profiler/CHANGELOG.md +107 -0
  14. data/ext/async-profiler/JavaHome.class +0 -0
  15. data/ext/async-profiler/LICENSE +201 -0
  16. data/ext/async-profiler/Makefile +66 -0
  17. data/ext/async-profiler/README.md +487 -0
  18. data/ext/async-profiler/demo/SwingSet2.svg +2247 -0
  19. data/ext/async-profiler/docs/cddl1.txt +358 -0
  20. data/ext/async-profiler/profiler.sh +240 -0
  21. data/ext/async-profiler/src/allocTracer.cpp +155 -0
  22. data/ext/async-profiler/src/allocTracer.h +74 -0
  23. data/ext/async-profiler/src/arch.h +69 -0
  24. data/ext/async-profiler/src/arguments.cpp +265 -0
  25. data/ext/async-profiler/src/arguments.h +152 -0
  26. data/ext/async-profiler/src/codeCache.cpp +128 -0
  27. data/ext/async-profiler/src/codeCache.h +99 -0
  28. data/ext/async-profiler/src/engine.cpp +50 -0
  29. data/ext/async-profiler/src/engine.h +38 -0
  30. data/ext/async-profiler/src/flameGraph.cpp +770 -0
  31. data/ext/async-profiler/src/flameGraph.h +118 -0
  32. data/ext/async-profiler/src/flightRecorder.cpp +727 -0
  33. data/ext/async-profiler/src/flightRecorder.h +39 -0
  34. data/ext/async-profiler/src/frameName.cpp +189 -0
  35. data/ext/async-profiler/src/frameName.h +56 -0
  36. data/ext/async-profiler/src/itimer.cpp +49 -0
  37. data/ext/async-profiler/src/itimer.h +43 -0
  38. data/ext/async-profiler/src/jattach/jattach.c +437 -0
  39. data/ext/async-profiler/src/java/one/profiler/AsyncProfiler.java +160 -0
  40. data/ext/async-profiler/src/java/one/profiler/AsyncProfilerMXBean.java +43 -0
  41. data/ext/async-profiler/src/java/one/profiler/Counter.java +25 -0
  42. data/ext/async-profiler/src/java/one/profiler/Events.java +28 -0
  43. data/ext/async-profiler/src/javaApi.cpp +124 -0
  44. data/ext/async-profiler/src/lockTracer.cpp +161 -0
  45. data/ext/async-profiler/src/lockTracer.h +55 -0
  46. data/ext/async-profiler/src/mutex.cpp +33 -0
  47. data/ext/async-profiler/src/mutex.h +49 -0
  48. data/ext/async-profiler/src/os.h +45 -0
  49. data/ext/async-profiler/src/os_linux.cpp +129 -0
  50. data/ext/async-profiler/src/os_macos.cpp +115 -0
  51. data/ext/async-profiler/src/perfEvents.h +60 -0
  52. data/ext/async-profiler/src/perfEvents_linux.cpp +550 -0
  53. data/ext/async-profiler/src/perfEvents_macos.cpp +64 -0
  54. data/ext/async-profiler/src/profiler.cpp +952 -0
  55. data/ext/async-profiler/src/profiler.h +238 -0
  56. data/ext/async-profiler/src/spinLock.h +66 -0
  57. data/ext/async-profiler/src/stackFrame.h +57 -0
  58. data/ext/async-profiler/src/stackFrame_aarch64.cpp +75 -0
  59. data/ext/async-profiler/src/stackFrame_arm.cpp +58 -0
  60. data/ext/async-profiler/src/stackFrame_i386.cpp +82 -0
  61. data/ext/async-profiler/src/stackFrame_x64.cpp +113 -0
  62. data/ext/async-profiler/src/symbols.h +37 -0
  63. data/ext/async-profiler/src/symbols_linux.cpp +354 -0
  64. data/ext/async-profiler/src/symbols_macos.cpp +156 -0
  65. data/ext/async-profiler/src/vmEntry.cpp +173 -0
  66. data/ext/async-profiler/src/vmEntry.h +105 -0
  67. data/ext/async-profiler/src/vmStructs.cpp +104 -0
  68. data/ext/async-profiler/src/vmStructs.h +112 -0
  69. data/ext/async-profiler/src/wallClock.cpp +96 -0
  70. data/ext/async-profiler/src/wallClock.h +56 -0
  71. data/ext/async-profiler/test/AllocatingTarget.java +26 -0
  72. data/ext/async-profiler/test/LoadLibraryTest.java +21 -0
  73. data/ext/async-profiler/test/Target.java +31 -0
  74. data/ext/async-profiler/test/ThreadsTarget.java +35 -0
  75. data/ext/async-profiler/test/alloc-smoke-test.sh +36 -0
  76. data/ext/async-profiler/test/load-library-test.sh +35 -0
  77. data/ext/async-profiler/test/smoke-test.sh +37 -0
  78. data/ext/async-profiler/test/thread-smoke-test.sh +32 -0
  79. data/jruby-async-profiler.gemspec +32 -0
  80. data/lib/jruby/async/profiler.rb +10 -0
  81. data/lib/jruby/async/profiler/version.rb +7 -0
  82. metadata +155 -0
@@ -0,0 +1,39 @@
1
+ /*
2
+ * Copyright 2018 Andrei Pangin
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ #ifndef _FLIGHTRECORDER_H
18
+ #define _FLIGHTRECORDER_H
19
+
20
+ #include "arguments.h"
21
+
22
+
23
+ class Recording;
24
+
25
+ class FlightRecorder {
26
+ private:
27
+ Recording* _rec;
28
+
29
+ public:
30
+ FlightRecorder() : _rec(NULL) {
31
+ }
32
+
33
+ Error start(const char* file);
34
+ void stop();
35
+
36
+ void recordExecutionSample(int lock_index, int tid, int call_trace_id);
37
+ };
38
+
39
+ #endif // _FLIGHTRECORDER_H
@@ -0,0 +1,189 @@
1
+ /*
2
+ * Copyright 2017 Andrei Pangin
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ #include <cxxabi.h>
18
+ #include <stdint.h>
19
+ #include <stdlib.h>
20
+ #include <string.h>
21
+ #include "frameName.h"
22
+ #include "arguments.h"
23
+ #include "vmStructs.h"
24
+
25
+
26
+ FrameName::FrameName(int style, Mutex& thread_names_lock, ThreadMap& thread_names) :
27
+ _cache(),
28
+ _style(style),
29
+ _thread_names_lock(thread_names_lock),
30
+ _thread_names(thread_names)
31
+ {
32
+ // Require printf to use standard C format regardless of system locale
33
+ _saved_locale = uselocale(newlocale(LC_NUMERIC_MASK, "C", (locale_t)0));
34
+ memset(_buf, 0, sizeof(_buf));
35
+ }
36
+
37
+ FrameName::~FrameName() {
38
+ freelocale(uselocale(_saved_locale));
39
+ }
40
+
41
+ char* FrameName::truncate(char* name, int max_length) {
42
+ if (strlen(name) > max_length && max_length >= 4) {
43
+ strcpy(name + max_length - 4, "...)");
44
+ }
45
+ return name;
46
+ }
47
+
48
+ const char* FrameName::cppDemangle(const char* name) {
49
+ if (name != NULL && name[0] == '_' && name[1] == 'Z') {
50
+ int status;
51
+ char* demangled = abi::__cxa_demangle(name, NULL, NULL, &status);
52
+ if (demangled != NULL) {
53
+ strncpy(_buf, demangled, sizeof(_buf) - 1);
54
+ free(demangled);
55
+ return _buf;
56
+ }
57
+ }
58
+ return name;
59
+ }
60
+
61
+ char* FrameName::javaMethodName(jmethodID method) {
62
+ jclass method_class;
63
+ char* class_name = NULL;
64
+ char* method_name = NULL;
65
+ char* method_sig = NULL;
66
+ char* result;
67
+
68
+ jvmtiEnv* jvmti = VM::jvmti();
69
+ jvmtiError err;
70
+
71
+ if ((err = jvmti->GetMethodName(method, &method_name, &method_sig, NULL)) == 0 &&
72
+ (err = jvmti->GetMethodDeclaringClass(method, &method_class)) == 0 &&
73
+ (err = jvmti->GetClassSignature(method_class, &class_name, NULL)) == 0) {
74
+ // Trim 'L' and ';' off the class descriptor like 'Ljava/lang/Object;'
75
+ result = javaClassName(class_name + 1, strlen(class_name) - 2, _style);
76
+ strcat(result, ".");
77
+ strcat(result, method_name);
78
+ if (_style & STYLE_SIGNATURES) strcat(result, truncate(method_sig, 255));
79
+ if (_style & STYLE_ANNOTATE) strcat(result, "_[j]");
80
+ } else {
81
+ snprintf(_buf, sizeof(_buf), "[jvmtiError %d]", err);
82
+ result = _buf;
83
+ }
84
+
85
+ jvmti->Deallocate((unsigned char*)class_name);
86
+ jvmti->Deallocate((unsigned char*)method_sig);
87
+ jvmti->Deallocate((unsigned char*)method_name);
88
+
89
+ return result;
90
+ }
91
+
92
+ char* FrameName::javaClassName(const char* symbol, int length, int style) {
93
+ char* result = _buf;
94
+
95
+ int array_dimension = 0;
96
+ while (*symbol == '[') {
97
+ array_dimension++;
98
+ symbol++;
99
+ }
100
+
101
+ if (array_dimension == 0) {
102
+ strncpy(result, symbol, length);
103
+ result[length] = 0;
104
+ } else {
105
+ switch (*symbol) {
106
+ case 'B': strcpy(result, "byte"); break;
107
+ case 'C': strcpy(result, "char"); break;
108
+ case 'I': strcpy(result, "int"); break;
109
+ case 'J': strcpy(result, "long"); break;
110
+ case 'S': strcpy(result, "short"); break;
111
+ case 'Z': strcpy(result, "boolean"); break;
112
+ case 'F': strcpy(result, "float"); break;
113
+ case 'D': strcpy(result, "double"); break;
114
+ default:
115
+ length -= array_dimension + 2;
116
+ strncpy(result, symbol + 1, length);
117
+ result[length] = 0;
118
+ }
119
+
120
+ do {
121
+ strcat(result, "[]");
122
+ } while (--array_dimension > 0);
123
+ }
124
+
125
+ if (style & STYLE_SIMPLE) {
126
+ for (char* s = result; *s; s++) {
127
+ if (*s == '/') result = s + 1;
128
+ }
129
+ }
130
+
131
+ if (style & STYLE_DOTTED) {
132
+ for (char* s = result; *s; s++) {
133
+ if (*s == '/') *s = '.';
134
+ }
135
+ }
136
+
137
+ return result;
138
+ }
139
+
140
+ const char* FrameName::name(ASGCT_CallFrame& frame) {
141
+ if (frame.method_id == NULL) {
142
+ return "[unknown]";
143
+ }
144
+
145
+ switch (frame.bci) {
146
+ case BCI_NATIVE_FRAME:
147
+ return cppDemangle((const char*)frame.method_id);
148
+
149
+ case BCI_SYMBOL: {
150
+ VMSymbol* symbol = (VMSymbol*)frame.method_id;
151
+ char* class_name = javaClassName(symbol->body(), symbol->length(), _style | STYLE_DOTTED);
152
+ return strcat(class_name, _style & STYLE_DOTTED ? "" : "_[i]");
153
+ }
154
+
155
+ case BCI_SYMBOL_OUTSIDE_TLAB: {
156
+ VMSymbol* symbol = (VMSymbol*)((uintptr_t)frame.method_id ^ 1);
157
+ char* class_name = javaClassName(symbol->body(), symbol->length(), _style | STYLE_DOTTED);
158
+ return strcat(class_name, _style & STYLE_DOTTED ? " (out)" : "_[k]");
159
+ }
160
+
161
+ case BCI_THREAD_ID: {
162
+ int tid = (int)(uintptr_t)frame.method_id;
163
+ MutexLocker ml(_thread_names_lock);
164
+ ThreadMap::iterator it = _thread_names.find(tid);
165
+ if (it != _thread_names.end()) {
166
+ snprintf(_buf, sizeof(_buf), "[%s tid=%d]", it->second.c_str(), tid);
167
+ } else {
168
+ snprintf(_buf, sizeof(_buf), "[tid=%d]", tid);
169
+ }
170
+ return _buf;
171
+ }
172
+
173
+ case BCI_ERROR: {
174
+ snprintf(_buf, sizeof(_buf), "[%s]", (const char*)frame.method_id);
175
+ return _buf;
176
+ }
177
+
178
+ default: {
179
+ JMethodCache::iterator it = _cache.lower_bound(frame.method_id);
180
+ if (it != _cache.end() && it->first == frame.method_id) {
181
+ return it->second.c_str();
182
+ }
183
+
184
+ const char* newName = javaMethodName(frame.method_id);
185
+ _cache.insert(it, JMethodCache::value_type(frame.method_id, newName));
186
+ return newName;
187
+ }
188
+ }
189
+ }
@@ -0,0 +1,56 @@
1
+ /*
2
+ * Copyright 2017 Andrei Pangin
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ #ifndef _FRAMENAME_H
18
+ #define _FRAMENAME_H
19
+
20
+ #include <jvmti.h>
21
+ #include <locale.h>
22
+ #include <map>
23
+ #include <string>
24
+ #include "mutex.h"
25
+ #include "vmEntry.h"
26
+
27
+ #ifdef __APPLE__
28
+ # include <xlocale.h>
29
+ #endif
30
+
31
+
32
+ typedef std::map<jmethodID, std::string> JMethodCache;
33
+ typedef std::map<int, std::string> ThreadMap;
34
+
35
+ class FrameName {
36
+ private:
37
+ JMethodCache _cache;
38
+ char _buf[800]; // must be large enough for class name + method name + method signature
39
+ int _style;
40
+ Mutex& _thread_names_lock;
41
+ ThreadMap& _thread_names;
42
+ locale_t _saved_locale;
43
+
44
+ char* truncate(char* name, int max_length);
45
+ const char* cppDemangle(const char* name);
46
+ char* javaMethodName(jmethodID method);
47
+ char* javaClassName(const char* symbol, int length, int style);
48
+
49
+ public:
50
+ FrameName(int style, Mutex& thread_names_lock, ThreadMap& thread_names);
51
+ ~FrameName();
52
+
53
+ const char* name(ASGCT_CallFrame& frame);
54
+ };
55
+
56
+ #endif // _FRAMENAME_H
@@ -0,0 +1,49 @@
1
+ /*
2
+ * Copyright 2018 Andrei Pangin
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ #include <sys/time.h>
18
+ #include "itimer.h"
19
+ #include "os.h"
20
+ #include "profiler.h"
21
+
22
+
23
+ long ITimer::_interval;
24
+
25
+
26
+ void ITimer::signalHandler(int signo, siginfo_t* siginfo, void* ucontext) {
27
+ Profiler::_instance.recordSample(ucontext, _interval, 0, NULL);
28
+ }
29
+
30
+ Error ITimer::start(Arguments& args) {
31
+ if (args._interval < 0) {
32
+ return Error("interval must be positive");
33
+ }
34
+ _interval = args._interval ? args._interval : DEFAULT_INTERVAL;
35
+
36
+ OS::installSignalHandler(SIGPROF, signalHandler);
37
+
38
+ long sec = _interval / 1000000000;
39
+ long usec = (_interval % 1000000000) / 1000;
40
+ struct itimerval tv = {{sec, usec}, {sec, usec}};
41
+ setitimer(ITIMER_PROF, &tv, NULL);
42
+
43
+ return Error::OK;
44
+ }
45
+
46
+ void ITimer::stop() {
47
+ struct itimerval tv = {{0, 0}, {0, 0}};
48
+ setitimer(ITIMER_PROF, &tv, NULL);
49
+ }
@@ -0,0 +1,43 @@
1
+ /*
2
+ * Copyright 2018 Andrei Pangin
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ #ifndef _ITIMER_H
18
+ #define _ITIMER_H
19
+
20
+ #include <signal.h>
21
+ #include "engine.h"
22
+
23
+
24
+ class ITimer : public Engine {
25
+ private:
26
+ static long _interval;
27
+
28
+ static void signalHandler(int signo, siginfo_t* siginfo, void* ucontext);
29
+
30
+ public:
31
+ const char* name() {
32
+ return "itimer";
33
+ }
34
+
35
+ const char* units() {
36
+ return "ns";
37
+ }
38
+
39
+ Error start(Arguments& args);
40
+ void stop();
41
+ };
42
+
43
+ #endif // _ITIMER_H
@@ -0,0 +1,437 @@
1
+ /*
2
+ * Copyright 2016 Andrei Pangin
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ #include <stdio.h>
18
+ #include <stdlib.h>
19
+ #include <string.h>
20
+ #include <sys/types.h>
21
+ #include <sys/stat.h>
22
+ #include <sys/socket.h>
23
+ #include <sys/un.h>
24
+ #include <sys/syscall.h>
25
+ #include <dirent.h>
26
+ #include <errno.h>
27
+ #include <fcntl.h>
28
+ #include <signal.h>
29
+ #include <time.h>
30
+ #include <unistd.h>
31
+
32
+ #define MAX_PATH 1024
33
+ #define TMP_PATH (MAX_PATH - 64)
34
+
35
+ static char tmp_path[TMP_PATH] = {0};
36
+
37
+
38
+ #ifdef __linux__
39
+
40
+ int get_process_info(int pid, uid_t* uid, gid_t* gid, int* nspid) {
41
+ // Parse /proc/pid/status to find process credentials
42
+ char path[64];
43
+ snprintf(path, sizeof(path), "/proc/%d/status", pid);
44
+ FILE* status_file = fopen(path, "r");
45
+ if (status_file == NULL) {
46
+ return 0;
47
+ }
48
+
49
+ char* line = NULL;
50
+ size_t size;
51
+
52
+ while (getline(&line, &size, status_file) != -1) {
53
+ if (strncmp(line, "Uid:", 4) == 0) {
54
+ // Get the effective UID, which is the second value in the line
55
+ *uid = (uid_t)atoi(strchr(line + 5, '\t'));
56
+ } else if (strncmp(line, "Gid:", 4) == 0) {
57
+ // Get the effective GID, which is the second value in the line
58
+ *gid = (gid_t)atoi(strchr(line + 5, '\t'));
59
+ } else if (strncmp(line, "NStgid:", 7) == 0) {
60
+ // PID namespaces can be nested; the last one is the innermost one
61
+ *nspid = atoi(strrchr(line, '\t'));
62
+ }
63
+ }
64
+
65
+ free(line);
66
+ fclose(status_file);
67
+ return 1;
68
+ }
69
+
70
+ int get_tmp_path(int pid) {
71
+ // A process may have its own root path (when running in chroot environment)
72
+ char path[64];
73
+ snprintf(path, sizeof(path), "/proc/%d/root", pid);
74
+
75
+ // Append /tmp to the resolved root symlink
76
+ ssize_t path_size = readlink(path, tmp_path, sizeof(tmp_path) - 10);
77
+ strcpy(tmp_path + (path_size > 1 ? path_size : 0), "/tmp");
78
+ return 1;
79
+ }
80
+
81
+ int enter_mount_ns(int pid) {
82
+ #ifdef __NR_setns
83
+ char path[128];
84
+ snprintf(path, sizeof(path), "/proc/%d/ns/mnt", pid);
85
+
86
+ struct stat oldns_stat, newns_stat;
87
+ if (stat("/proc/self/ns/mnt", &oldns_stat) == 0 && stat(path, &newns_stat) == 0) {
88
+ // Don't try to call setns() if we're in the same namespace already
89
+ if (oldns_stat.st_ino != newns_stat.st_ino) {
90
+ int newns = open(path, O_RDONLY);
91
+ if (newns < 0) {
92
+ return 0;
93
+ }
94
+
95
+ // Some ancient Linux distributions do not have setns() function
96
+ int result = syscall(__NR_setns, newns, 0);
97
+ close(newns);
98
+ return result < 0 ? 0 : 1;
99
+ }
100
+ }
101
+ #endif // __NR_setns
102
+
103
+ return 1;
104
+ }
105
+
106
+ // The first line of /proc/pid/sched looks like
107
+ // java (1234, #threads: 12)
108
+ // where 1234 is the required host PID
109
+ int sched_get_host_pid(const char* path) {
110
+ static char* line = NULL;
111
+ size_t size;
112
+ int result = -1;
113
+
114
+ FILE* sched_file = fopen(path, "r");
115
+ if (sched_file != NULL) {
116
+ if (getline(&line, &size, sched_file) != -1) {
117
+ char* c = strrchr(line, '(');
118
+ if (c != NULL) {
119
+ result = atoi(c + 1);
120
+ }
121
+ }
122
+ fclose(sched_file);
123
+ }
124
+
125
+ return result;
126
+ }
127
+
128
+ // Linux kernels < 4.1 do not export NStgid field in /proc/pid/status.
129
+ // Fortunately, /proc/pid/sched in a container exposes a host PID,
130
+ // so the idea is to scan all container PIDs to find which one matches the host PID.
131
+ int alt_lookup_nspid(int pid) {
132
+ int namespace_differs = 0;
133
+ char path[300];
134
+ snprintf(path, sizeof(path), "/proc/%d/ns/pid", pid);
135
+
136
+ // Don't bother looking for container PID if we are already in the same PID namespace
137
+ struct stat oldns_stat, newns_stat;
138
+ if (stat("/proc/self/ns/pid", &oldns_stat) == 0 && stat(path, &newns_stat) == 0) {
139
+ if (oldns_stat.st_ino == newns_stat.st_ino) {
140
+ return pid;
141
+ }
142
+ namespace_differs = 1;
143
+ }
144
+
145
+ // Otherwise browse all PIDs in the namespace of the target process
146
+ // trying to find which one corresponds to the host PID
147
+ snprintf(path, sizeof(path), "/proc/%d/root/proc", pid);
148
+ DIR* dir = opendir(path);
149
+ if (dir != NULL) {
150
+ struct dirent* entry;
151
+ while ((entry = readdir(dir)) != NULL) {
152
+ if (entry->d_name[0] >= '1' && entry->d_name[0] <= '9') {
153
+ // Check if /proc/<container-pid>/sched points back to <host-pid>
154
+ snprintf(path, sizeof(path), "/proc/%d/root/proc/%s/sched", pid, entry->d_name);
155
+ if (sched_get_host_pid(path) == pid) {
156
+ closedir(dir);
157
+ return atoi(entry->d_name);
158
+ }
159
+ }
160
+ }
161
+ closedir(dir);
162
+ }
163
+
164
+ if (namespace_differs) {
165
+ printf("WARNING: couldn't find container pid of the target process\n");
166
+ }
167
+
168
+ return pid;
169
+ }
170
+
171
+ #elif defined(__APPLE__)
172
+
173
+ #include <sys/sysctl.h>
174
+
175
+ int get_process_info(int pid, uid_t* uid, gid_t* gid, int* nspid) {
176
+ int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, pid};
177
+ struct kinfo_proc info;
178
+ size_t len = sizeof(info);
179
+
180
+ if (sysctl(mib, 4, &info, &len, NULL, 0) < 0 || len <= 0) {
181
+ return 0;
182
+ }
183
+
184
+ *uid = info.kp_eproc.e_ucred.cr_uid;
185
+ *gid = info.kp_eproc.e_ucred.cr_gid;
186
+ *nspid = pid;
187
+ return 1;
188
+ }
189
+
190
+ // macOS has a secure per-user temporary directory
191
+ int get_tmp_path(int pid) {
192
+ int path_size = confstr(_CS_DARWIN_USER_TEMP_DIR, tmp_path, sizeof(tmp_path));
193
+ return path_size > 0 && path_size <= sizeof(tmp_path);
194
+ }
195
+
196
+ // This is a Linux-specific API; nothing to do on macOS and FreeBSD
197
+ int enter_mount_ns(int pid) {
198
+ return 1;
199
+ }
200
+
201
+ // Not used on macOS and FreeBSD
202
+ int alt_lookup_nspid(int pid) {
203
+ return pid;
204
+ }
205
+
206
+ #else // __FreeBSD__
207
+
208
+ #include <sys/sysctl.h>
209
+ #include <sys/user.h>
210
+
211
+ int get_process_info(int pid, uid_t* uid, gid_t* gid, int* nspid) {
212
+ int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, pid};
213
+ struct kinfo_proc info;
214
+ size_t len = sizeof(info);
215
+
216
+ if (sysctl(mib, 4, &info, &len, NULL, 0) < 0 || len <= 0) {
217
+ return 0;
218
+ }
219
+
220
+ *uid = info.ki_uid;
221
+ *gid = info.ki_groups[0];
222
+ *nspid = pid;
223
+ return 1;
224
+ }
225
+
226
+ // Use default /tmp path on FreeBSD
227
+ int get_tmp_path(int pid) {
228
+ return 0;
229
+ }
230
+
231
+ // This is a Linux-specific API; nothing to do on macOS and FreeBSD
232
+ int enter_mount_ns(int pid) {
233
+ return 1;
234
+ }
235
+
236
+ // Not used on macOS and FreeBSD
237
+ int alt_lookup_nspid(int pid) {
238
+ return pid;
239
+ }
240
+
241
+ #endif
242
+
243
+
244
+ // Check if remote JVM has already opened socket for Dynamic Attach
245
+ static int check_socket(int pid) {
246
+ char path[MAX_PATH];
247
+ snprintf(path, sizeof(path), "%s/.java_pid%d", tmp_path, pid);
248
+
249
+ struct stat stats;
250
+ return stat(path, &stats) == 0 && S_ISSOCK(stats.st_mode);
251
+ }
252
+
253
+ // Check if a file is owned by current user
254
+ static int check_file_owner(const char* path) {
255
+ struct stat stats;
256
+ if (stat(path, &stats) == 0 && stats.st_uid == geteuid()) {
257
+ return 1;
258
+ }
259
+
260
+ // Some mounted filesystems may change the ownership of the file.
261
+ // JVM will not trust such file, so it's better to remove it and try a different path
262
+ unlink(path);
263
+ return 0;
264
+ }
265
+
266
+ // Force remote JVM to start Attach listener.
267
+ // HotSpot will start Attach listener in response to SIGQUIT if it sees .attach_pid file
268
+ static int start_attach_mechanism(int pid, int nspid) {
269
+ char path[MAX_PATH];
270
+ snprintf(path, sizeof(path), "/proc/%d/cwd/.attach_pid%d", nspid, nspid);
271
+
272
+ int fd = creat(path, 0660);
273
+ if (fd == -1 || (close(fd) == 0 && !check_file_owner(path))) {
274
+ // Failed to create attach trigger in current directory. Retry in /tmp
275
+ snprintf(path, sizeof(path), "%s/.attach_pid%d", tmp_path, nspid);
276
+ fd = creat(path, 0660);
277
+ if (fd == -1) {
278
+ return 0;
279
+ }
280
+ close(fd);
281
+ }
282
+
283
+ // We have to still use the host namespace pid here for the kill() call
284
+ kill(pid, SIGQUIT);
285
+
286
+ // Start with 20 ms sleep and increment delay each iteration
287
+ struct timespec ts = {0, 20000000};
288
+ int result;
289
+ do {
290
+ nanosleep(&ts, NULL);
291
+ result = check_socket(nspid);
292
+ } while (!result && (ts.tv_nsec += 20000000) < 300000000);
293
+
294
+ unlink(path);
295
+ return result;
296
+ }
297
+
298
+ // Connect to UNIX domain socket created by JVM for Dynamic Attach
299
+ static int connect_socket(int pid) {
300
+ int fd = socket(PF_UNIX, SOCK_STREAM, 0);
301
+ if (fd == -1) {
302
+ return -1;
303
+ }
304
+
305
+ struct sockaddr_un addr;
306
+ addr.sun_family = AF_UNIX;
307
+ int bytes = snprintf(addr.sun_path, sizeof(addr.sun_path), "%s/.java_pid%d", tmp_path, pid);
308
+ if (bytes >= sizeof(addr.sun_path)) {
309
+ addr.sun_path[sizeof(addr.sun_path) - 1] = 0;
310
+ }
311
+
312
+ if (connect(fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
313
+ close(fd);
314
+ return -1;
315
+ }
316
+ return fd;
317
+ }
318
+
319
+ // Send command with arguments to socket
320
+ static int write_command(int fd, int argc, char** argv) {
321
+ // Protocol version
322
+ if (write(fd, "1", 2) <= 0) {
323
+ return 0;
324
+ }
325
+
326
+ int i;
327
+ for (i = 0; i < 4; i++) {
328
+ const char* arg = i < argc ? argv[i] : "";
329
+ if (write(fd, arg, strlen(arg) + 1) <= 0) {
330
+ return 0;
331
+ }
332
+ }
333
+ return 1;
334
+ }
335
+
336
+ // Mirror response from remote JVM to stdout
337
+ static int read_response(int fd) {
338
+ char buf[8192];
339
+ ssize_t bytes = read(fd, buf, sizeof(buf) - 1);
340
+ if (bytes <= 0) {
341
+ perror("Error reading response");
342
+ return 1;
343
+ }
344
+
345
+ // First line of response is the command result code
346
+ buf[bytes] = 0;
347
+ int result = atoi(buf);
348
+
349
+ do {
350
+ fwrite(buf, 1, bytes, stdout);
351
+ bytes = read(fd, buf, sizeof(buf));
352
+ } while (bytes > 0);
353
+
354
+ return result;
355
+ }
356
+
357
+ int main(int argc, char** argv) {
358
+ if (argc < 3) {
359
+ printf("jattach " JATTACH_VERSION " built on " __DATE__ "\n"
360
+ "Copyright 2018 Andrei Pangin\n"
361
+ "\n"
362
+ "Usage: jattach <pid> <cmd> [args ...]\n");
363
+ return 1;
364
+ }
365
+
366
+ int pid = atoi(argv[1]);
367
+ if (pid == 0) {
368
+ perror("Invalid pid provided");
369
+ return 1;
370
+ }
371
+
372
+ uid_t my_uid = geteuid();
373
+ gid_t my_gid = getegid();
374
+ uid_t target_uid = my_uid;
375
+ gid_t target_gid = my_gid;
376
+ int nspid = -1;
377
+ if (!get_process_info(pid, &target_uid, &target_gid, &nspid)) {
378
+ fprintf(stderr, "Process %d not found\n", pid);
379
+ return 1;
380
+ }
381
+
382
+ if (nspid < 0) {
383
+ nspid = alt_lookup_nspid(pid);
384
+ }
385
+
386
+ // Get attach socket path of the target process (usually /tmp)
387
+ char* jattach_path = getenv("JATTACH_PATH");
388
+ if (jattach_path != NULL && strlen(jattach_path) < TMP_PATH) {
389
+ strcpy(tmp_path, jattach_path);
390
+ } else {
391
+ // Make sure our /tmp and target /tmp is the same
392
+ if (!get_tmp_path(pid)) {
393
+ strcpy(tmp_path, "/tmp");
394
+ }
395
+ if (!enter_mount_ns(pid)) {
396
+ printf("WARNING: couldn't enter target process mnt namespace\n");
397
+ }
398
+ }
399
+
400
+ // Dynamic attach is allowed only for the clients with the same euid/egid.
401
+ // If we are running under root, switch to the required euid/egid automatically.
402
+ if ((my_gid != target_gid && setegid(target_gid) != 0) ||
403
+ (my_uid != target_uid && seteuid(target_uid) != 0)) {
404
+ perror("Failed to change credentials to match the target process");
405
+ return 1;
406
+ }
407
+
408
+ // Make write() return EPIPE instead of silent process termination
409
+ signal(SIGPIPE, SIG_IGN);
410
+
411
+ if (!check_socket(nspid) && !start_attach_mechanism(pid, nspid)) {
412
+ perror("Could not start attach mechanism");
413
+ return 1;
414
+ }
415
+
416
+ int fd = connect_socket(nspid);
417
+ if (fd == -1) {
418
+ perror("Could not connect to socket");
419
+ return 1;
420
+ }
421
+
422
+ printf("Connected to remote JVM\n");
423
+ if (!write_command(fd, argc - 2, argv + 2)) {
424
+ perror("Error writing to socket");
425
+ close(fd);
426
+ return 1;
427
+ }
428
+
429
+ printf("Response code = ");
430
+ fflush(stdout);
431
+
432
+ int result = read_response(fd);
433
+ printf("\n");
434
+ close(fd);
435
+
436
+ return result;
437
+ }