jruby-async-profiler 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
+ }