jruby-async-profiler 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +8 -0
- data/.gitmodules +3 -0
- data/Gemfile +4 -0
- data/README.md +35 -0
- data/Rakefile +2 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/ext/Rakefile +6 -0
- data/ext/async-profiler/.gitattributes +1 -0
- data/ext/async-profiler/.gitignore +6 -0
- data/ext/async-profiler/.travis.yml +11 -0
- data/ext/async-profiler/CHANGELOG.md +107 -0
- data/ext/async-profiler/JavaHome.class +0 -0
- data/ext/async-profiler/LICENSE +201 -0
- data/ext/async-profiler/Makefile +66 -0
- data/ext/async-profiler/README.md +487 -0
- data/ext/async-profiler/demo/SwingSet2.svg +2247 -0
- data/ext/async-profiler/docs/cddl1.txt +358 -0
- data/ext/async-profiler/profiler.sh +240 -0
- data/ext/async-profiler/src/allocTracer.cpp +155 -0
- data/ext/async-profiler/src/allocTracer.h +74 -0
- data/ext/async-profiler/src/arch.h +69 -0
- data/ext/async-profiler/src/arguments.cpp +265 -0
- data/ext/async-profiler/src/arguments.h +152 -0
- data/ext/async-profiler/src/codeCache.cpp +128 -0
- data/ext/async-profiler/src/codeCache.h +99 -0
- data/ext/async-profiler/src/engine.cpp +50 -0
- data/ext/async-profiler/src/engine.h +38 -0
- data/ext/async-profiler/src/flameGraph.cpp +770 -0
- data/ext/async-profiler/src/flameGraph.h +118 -0
- data/ext/async-profiler/src/flightRecorder.cpp +727 -0
- data/ext/async-profiler/src/flightRecorder.h +39 -0
- data/ext/async-profiler/src/frameName.cpp +189 -0
- data/ext/async-profiler/src/frameName.h +56 -0
- data/ext/async-profiler/src/itimer.cpp +49 -0
- data/ext/async-profiler/src/itimer.h +43 -0
- data/ext/async-profiler/src/jattach/jattach.c +437 -0
- data/ext/async-profiler/src/java/one/profiler/AsyncProfiler.java +160 -0
- data/ext/async-profiler/src/java/one/profiler/AsyncProfilerMXBean.java +43 -0
- data/ext/async-profiler/src/java/one/profiler/Counter.java +25 -0
- data/ext/async-profiler/src/java/one/profiler/Events.java +28 -0
- data/ext/async-profiler/src/javaApi.cpp +124 -0
- data/ext/async-profiler/src/lockTracer.cpp +161 -0
- data/ext/async-profiler/src/lockTracer.h +55 -0
- data/ext/async-profiler/src/mutex.cpp +33 -0
- data/ext/async-profiler/src/mutex.h +49 -0
- data/ext/async-profiler/src/os.h +45 -0
- data/ext/async-profiler/src/os_linux.cpp +129 -0
- data/ext/async-profiler/src/os_macos.cpp +115 -0
- data/ext/async-profiler/src/perfEvents.h +60 -0
- data/ext/async-profiler/src/perfEvents_linux.cpp +550 -0
- data/ext/async-profiler/src/perfEvents_macos.cpp +64 -0
- data/ext/async-profiler/src/profiler.cpp +952 -0
- data/ext/async-profiler/src/profiler.h +238 -0
- data/ext/async-profiler/src/spinLock.h +66 -0
- data/ext/async-profiler/src/stackFrame.h +57 -0
- data/ext/async-profiler/src/stackFrame_aarch64.cpp +75 -0
- data/ext/async-profiler/src/stackFrame_arm.cpp +58 -0
- data/ext/async-profiler/src/stackFrame_i386.cpp +82 -0
- data/ext/async-profiler/src/stackFrame_x64.cpp +113 -0
- data/ext/async-profiler/src/symbols.h +37 -0
- data/ext/async-profiler/src/symbols_linux.cpp +354 -0
- data/ext/async-profiler/src/symbols_macos.cpp +156 -0
- data/ext/async-profiler/src/vmEntry.cpp +173 -0
- data/ext/async-profiler/src/vmEntry.h +105 -0
- data/ext/async-profiler/src/vmStructs.cpp +104 -0
- data/ext/async-profiler/src/vmStructs.h +112 -0
- data/ext/async-profiler/src/wallClock.cpp +96 -0
- data/ext/async-profiler/src/wallClock.h +56 -0
- data/ext/async-profiler/test/AllocatingTarget.java +26 -0
- data/ext/async-profiler/test/LoadLibraryTest.java +21 -0
- data/ext/async-profiler/test/Target.java +31 -0
- data/ext/async-profiler/test/ThreadsTarget.java +35 -0
- data/ext/async-profiler/test/alloc-smoke-test.sh +36 -0
- data/ext/async-profiler/test/load-library-test.sh +35 -0
- data/ext/async-profiler/test/smoke-test.sh +37 -0
- data/ext/async-profiler/test/thread-smoke-test.sh +32 -0
- data/jruby-async-profiler.gemspec +32 -0
- data/lib/jruby/async/profiler.rb +10 -0
- data/lib/jruby/async/profiler/version.rb +7 -0
- 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
|
+
}
|