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.
- 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
|
+
}
|