1 // Copyright (C) 2018 The Android Open Source Project
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 //
15
16 #include <android-base/logging.h>
17
18 #include <atomic>
19 #include <fstream>
20 #include <iostream>
21 #include <istream>
22 #include <iomanip>
23 #include <jni.h>
24 #include <jvmti.h>
25 #include <limits>
26 #include <map>
27 #include <memory>
28 #include <mutex>
29 #include <string>
30 #include <sstream>
31 #include <vector>
32
33 namespace tifast {
34
35 namespace {
36
37 // Special art ti-version number. We will use this as a fallback if we cannot get a regular JVMTI
38 // env.
39 static constexpr jint kArtTiVersion = JVMTI_VERSION_1_2 | 0x40000000;
40
41 // jthread is a typedef of jobject so we use this to allow the templates to distinguish them.
42 struct jthreadContainer { jthread thread; };
43 // jlocation is a typedef of jlong so use this to distinguish the less common jlong.
44 struct jlongContainer { jlong val; };
45
DeleteLocalRef(JNIEnv * env,jobject obj)46 static void DeleteLocalRef(JNIEnv* env, jobject obj) {
47 if (obj != nullptr && env != nullptr) {
48 env->DeleteLocalRef(obj);
49 }
50 }
51
52 class ScopedThreadInfo {
53 public:
ScopedThreadInfo(jvmtiEnv * jvmtienv,JNIEnv * env,jthread thread)54 ScopedThreadInfo(jvmtiEnv* jvmtienv, JNIEnv* env, jthread thread)
55 : jvmtienv_(jvmtienv), env_(env), free_name_(false) {
56 if (thread == nullptr) {
57 info_.name = const_cast<char*>("<NULLPTR>");
58 } else if (jvmtienv->GetThreadInfo(thread, &info_) != JVMTI_ERROR_NONE) {
59 info_.name = const_cast<char*>("<UNKNOWN THREAD>");
60 } else {
61 free_name_ = true;
62 }
63 }
64
~ScopedThreadInfo()65 ~ScopedThreadInfo() {
66 if (free_name_) {
67 jvmtienv_->Deallocate(reinterpret_cast<unsigned char*>(info_.name));
68 }
69 DeleteLocalRef(env_, info_.thread_group);
70 DeleteLocalRef(env_, info_.context_class_loader);
71 }
72
GetName() const73 const char* GetName() const {
74 return info_.name;
75 }
76
77 private:
78 jvmtiEnv* jvmtienv_;
79 JNIEnv* env_;
80 bool free_name_;
81 jvmtiThreadInfo info_{};
82 };
83
84 class ScopedClassInfo {
85 public:
ScopedClassInfo(jvmtiEnv * jvmtienv,jclass c)86 ScopedClassInfo(jvmtiEnv* jvmtienv, jclass c) : jvmtienv_(jvmtienv), class_(c) {}
87
~ScopedClassInfo()88 ~ScopedClassInfo() {
89 if (class_ != nullptr) {
90 jvmtienv_->Deallocate(reinterpret_cast<unsigned char*>(name_));
91 jvmtienv_->Deallocate(reinterpret_cast<unsigned char*>(generic_));
92 jvmtienv_->Deallocate(reinterpret_cast<unsigned char*>(file_));
93 jvmtienv_->Deallocate(reinterpret_cast<unsigned char*>(debug_ext_));
94 }
95 }
96
Init(bool get_generic=true)97 bool Init(bool get_generic = true) {
98 if (class_ == nullptr) {
99 name_ = const_cast<char*>("<NONE>");
100 generic_ = const_cast<char*>("<NONE>");
101 return true;
102 } else {
103 jvmtiError ret1 = jvmtienv_->GetSourceFileName(class_, &file_);
104 jvmtiError ret2 = jvmtienv_->GetSourceDebugExtension(class_, &debug_ext_);
105 char** gen_ptr = &generic_;
106 if (!get_generic) {
107 generic_ = nullptr;
108 gen_ptr = nullptr;
109 }
110 return jvmtienv_->GetClassSignature(class_, &name_, gen_ptr) == JVMTI_ERROR_NONE &&
111 ret1 != JVMTI_ERROR_MUST_POSSESS_CAPABILITY &&
112 ret1 != JVMTI_ERROR_INVALID_CLASS &&
113 ret2 != JVMTI_ERROR_MUST_POSSESS_CAPABILITY &&
114 ret2 != JVMTI_ERROR_INVALID_CLASS;
115 }
116 }
117
GetClass() const118 jclass GetClass() const {
119 return class_;
120 }
121
GetName() const122 const char* GetName() const {
123 return name_;
124 }
125
GetGeneric() const126 const char* GetGeneric() const {
127 return generic_;
128 }
129
GetSourceDebugExtension() const130 const char* GetSourceDebugExtension() const {
131 if (debug_ext_ == nullptr) {
132 return "<UNKNOWN_SOURCE_DEBUG_EXTENSION>";
133 } else {
134 return debug_ext_;
135 }
136 }
GetSourceFileName() const137 const char* GetSourceFileName() const {
138 if (file_ == nullptr) {
139 return "<UNKNOWN_FILE>";
140 } else {
141 return file_;
142 }
143 }
144
145 private:
146 jvmtiEnv* jvmtienv_;
147 jclass class_;
148 char* name_ = nullptr;
149 char* generic_ = nullptr;
150 char* file_ = nullptr;
151 char* debug_ext_ = nullptr;
152
153 friend std::ostream& operator<<(std::ostream &os, ScopedClassInfo const& m);
154 };
155
156 class ScopedMethodInfo {
157 public:
ScopedMethodInfo(jvmtiEnv * jvmtienv,JNIEnv * env,jmethodID m)158 ScopedMethodInfo(jvmtiEnv* jvmtienv, JNIEnv* env, jmethodID m)
159 : jvmtienv_(jvmtienv), env_(env), method_(m) {}
160
~ScopedMethodInfo()161 ~ScopedMethodInfo() {
162 DeleteLocalRef(env_, declaring_class_);
163 jvmtienv_->Deallocate(reinterpret_cast<unsigned char*>(name_));
164 jvmtienv_->Deallocate(reinterpret_cast<unsigned char*>(signature_));
165 jvmtienv_->Deallocate(reinterpret_cast<unsigned char*>(generic_));
166 }
167
Init(bool get_generic=true)168 bool Init(bool get_generic = true) {
169 if (jvmtienv_->GetMethodDeclaringClass(method_, &declaring_class_) != JVMTI_ERROR_NONE) {
170 return false;
171 }
172 class_info_.reset(new ScopedClassInfo(jvmtienv_, declaring_class_));
173 jint nlines;
174 jvmtiLineNumberEntry* lines;
175 jvmtiError err = jvmtienv_->GetLineNumberTable(method_, &nlines, &lines);
176 if (err == JVMTI_ERROR_NONE) {
177 if (nlines > 0) {
178 first_line_ = lines[0].line_number;
179 }
180 jvmtienv_->Deallocate(reinterpret_cast<unsigned char*>(lines));
181 } else if (err != JVMTI_ERROR_ABSENT_INFORMATION &&
182 err != JVMTI_ERROR_NATIVE_METHOD) {
183 return false;
184 }
185 return class_info_->Init(get_generic) &&
186 (jvmtienv_->GetMethodName(method_, &name_, &signature_, &generic_) == JVMTI_ERROR_NONE);
187 }
188
GetDeclaringClassInfo() const189 const ScopedClassInfo& GetDeclaringClassInfo() const {
190 return *class_info_;
191 }
192
GetDeclaringClass() const193 jclass GetDeclaringClass() const {
194 return declaring_class_;
195 }
196
GetName() const197 const char* GetName() const {
198 return name_;
199 }
200
GetSignature() const201 const char* GetSignature() const {
202 return signature_;
203 }
204
GetGeneric() const205 const char* GetGeneric() const {
206 return generic_;
207 }
208
GetFirstLine() const209 jint GetFirstLine() const {
210 return first_line_;
211 }
212
213 private:
214 jvmtiEnv* jvmtienv_;
215 JNIEnv* env_;
216 jmethodID method_;
217 jclass declaring_class_ = nullptr;
218 std::unique_ptr<ScopedClassInfo> class_info_;
219 char* name_ = nullptr;
220 char* signature_ = nullptr;
221 char* generic_ = nullptr;
222 jint first_line_ = -1;
223 };
224
operator <<(std::ostream & os,ScopedClassInfo const & c)225 std::ostream& operator<<(std::ostream &os, ScopedClassInfo const& c) {
226 const char* generic = c.GetGeneric();
227 if (generic != nullptr) {
228 return os << c.GetName() << "<" << generic << ">" << " file: " << c.GetSourceFileName();
229 } else {
230 return os << c.GetName() << " file: " << c.GetSourceFileName();
231 }
232 }
233
234 class LockedStream {
235 public:
LockedStream(const std::string & filepath)236 explicit LockedStream(const std::string& filepath) {
237 stream_.open(filepath, std::ofstream::out);
238 if (!stream_.is_open()) {
239 LOG(ERROR) << "====== JVMTI FAILED TO OPEN LOG FILE";
240 }
241 }
~LockedStream()242 ~LockedStream() {
243 stream_.close();
244 }
Write(const std::string & str)245 void Write(const std::string& str) {
246 stream_ << str;
247 stream_.flush();
248 }
249 private:
250 std::ofstream stream_;
251 };
252
253 static LockedStream* stream = nullptr;
254
255 class UniqueStringTable {
256 public:
257 UniqueStringTable() = default;
258 ~UniqueStringTable() = default;
Intern(const std::string & header,const std::string & key)259 std::string Intern(const std::string& header, const std::string& key) {
260 if (map_.find(key) == map_.end()) {
261 map_[key] = next_index_;
262 // Emit definition line. E.g., =123,string
263 stream->Write(header + std::to_string(next_index_) + "," + key + "\n");
264 ++next_index_;
265 }
266 return std::to_string(map_[key]);
267 }
268 private:
269 int32_t next_index_;
270 std::map<std::string, int32_t> map_;
271 };
272
273 static UniqueStringTable* string_table = nullptr;
274
275 // Formatter for the thread, type, and size of an allocation.
formatAllocation(jvmtiEnv * jvmti,JNIEnv * jni,jthreadContainer thr,jclass klass,jlongContainer size)276 static std::string formatAllocation(jvmtiEnv* jvmti,
277 JNIEnv* jni,
278 jthreadContainer thr,
279 jclass klass,
280 jlongContainer size) {
281 ScopedThreadInfo sti(jvmti, jni, thr.thread);
282 std::ostringstream allocation;
283 allocation << "jthread[" << sti.GetName() << "]";
284 ScopedClassInfo sci(jvmti, klass);
285 if (sci.Init(/*get_generic=*/false)) {
286 allocation << ", jclass[" << sci << "]";
287 } else {
288 allocation << ", jclass[TYPE UNKNOWN]";
289 }
290 allocation << ", size[" << size.val << ", hex: 0x" << std::hex << size.val << "]";
291 return string_table->Intern("+", allocation.str());
292 }
293
294 // Formatter for a method entry on a call stack.
formatMethod(jvmtiEnv * jvmti,JNIEnv * jni,jmethodID method_id)295 static std::string formatMethod(jvmtiEnv* jvmti, JNIEnv* jni, jmethodID method_id) {
296 ScopedMethodInfo smi(jvmti, jni, method_id);
297 std::string method;
298 if (smi.Init(/*get_generic=*/false)) {
299 method = std::string(smi.GetDeclaringClassInfo().GetName()) +
300 "::" + smi.GetName() + smi.GetSignature();
301 } else {
302 method = "ERROR";
303 }
304 return string_table->Intern("+", method);
305 }
306
307 static int sampling_rate;
308 static int stack_depth_limit;
309
logVMObjectAlloc(jvmtiEnv * jvmti,JNIEnv * jni,jthread thread,jobject obj ATTRIBUTE_UNUSED,jclass klass,jlong size)310 static void JNICALL logVMObjectAlloc(jvmtiEnv* jvmti,
311 JNIEnv* jni,
312 jthread thread,
313 jobject obj ATTRIBUTE_UNUSED,
314 jclass klass,
315 jlong size) {
316 // Sample only once out of sampling_rate tries, and prevent recursive allocation tracking,
317 static thread_local int sample_countdown = sampling_rate;
318 --sample_countdown;
319 if (sample_countdown != 0) {
320 return;
321 }
322
323 // Guard accesses to string table and emission.
324 static std::mutex mutex;
325 std::lock_guard<std::mutex> lg(mutex);
326
327 std::string record =
328 formatAllocation(jvmti,
329 jni,
330 jthreadContainer{.thread = thread},
331 klass,
332 jlongContainer{.val = size});
333
334 std::unique_ptr<jvmtiFrameInfo[]> stack_frames(new jvmtiFrameInfo[stack_depth_limit]);
335 jint stack_depth;
336 jvmtiError err = jvmti->GetStackTrace(thread,
337 0,
338 stack_depth_limit,
339 stack_frames.get(),
340 &stack_depth);
341 if (err == JVMTI_ERROR_NONE) {
342 // Emit stack frames in order from deepest in the stack to most recent.
343 // This simplifies post-collection processing.
344 for (int i = stack_depth - 1; i >= 0; --i) {
345 record += ";" + formatMethod(jvmti, jni, stack_frames[i].method);
346 }
347 }
348 stream->Write(string_table->Intern("=", record) + "\n");
349
350 sample_countdown = sampling_rate;
351 }
352
353 static jvmtiEventCallbacks kLogCallbacks {
354 .VMObjectAlloc = logVMObjectAlloc,
355 };
356
SetupJvmtiEnv(JavaVM * vm,jvmtiEnv ** jvmti)357 static jint SetupJvmtiEnv(JavaVM* vm, jvmtiEnv** jvmti) {
358 jint res = vm->GetEnv(reinterpret_cast<void**>(jvmti), JVMTI_VERSION_1_1);
359 if (res != JNI_OK || *jvmti == nullptr) {
360 LOG(ERROR) << "Unable to access JVMTI, error code " << res;
361 return vm->GetEnv(reinterpret_cast<void**>(jvmti), kArtTiVersion);
362 }
363 return res;
364 }
365
366 } // namespace
367
SetupCapabilities(jvmtiEnv * jvmti)368 static jvmtiError SetupCapabilities(jvmtiEnv* jvmti) {
369 jvmtiCapabilities caps{};
370 caps.can_generate_vm_object_alloc_events = 1;
371 caps.can_get_line_numbers = 1;
372 caps.can_get_source_file_name = 1;
373 caps.can_get_source_debug_extension = 1;
374 return jvmti->AddCapabilities(&caps);
375 }
376
ProcessOptions(std::string options)377 static bool ProcessOptions(std::string options) {
378 std::string output_file_path;
379 if (options.empty()) {
380 static constexpr int kDefaultSamplingRate = 10;
381 static constexpr int kDefaultStackDepthLimit = 50;
382 static constexpr const char* kDefaultOutputFilePath = "/data/local/tmp/logstream.txt";
383
384 sampling_rate = kDefaultSamplingRate;
385 stack_depth_limit = kDefaultStackDepthLimit;
386 output_file_path = kDefaultOutputFilePath;
387 } else {
388 // options string should contain "sampling_rate,stack_depth_limit,output_file_path".
389 size_t comma_pos = options.find(',');
390 if (comma_pos == std::string::npos) {
391 return false;
392 }
393 sampling_rate = std::stoi(options.substr(0, comma_pos));
394 options = options.substr(comma_pos + 1);
395 comma_pos = options.find(',');
396 if (comma_pos == std::string::npos) {
397 return false;
398 }
399 stack_depth_limit = std::stoi(options.substr(0, comma_pos));
400 output_file_path = options.substr(comma_pos + 1);
401 }
402 LOG(INFO) << "Starting allocation tracing: sampling_rate=" << sampling_rate
403 << ", stack_depth_limit=" << stack_depth_limit
404 << ", output_file_path=" << output_file_path;
405 stream = new LockedStream(output_file_path);
406
407 return true;
408 }
409
AgentStart(JavaVM * vm,char * options,void * reserved ATTRIBUTE_UNUSED)410 static jint AgentStart(JavaVM* vm,
411 char* options,
412 void* reserved ATTRIBUTE_UNUSED) {
413 // Handle the sampling rate, depth limit, and output path, if set.
414 if (!ProcessOptions(options)) {
415 return JNI_ERR;
416 }
417
418 // Create the environment.
419 jvmtiEnv* jvmti = nullptr;
420 if (SetupJvmtiEnv(vm, &jvmti) != JNI_OK) {
421 LOG(ERROR) << "Could not get JVMTI env or ArtTiEnv!";
422 return JNI_ERR;
423 }
424
425 jvmtiError error = SetupCapabilities(jvmti);
426 if (error != JVMTI_ERROR_NONE) {
427 LOG(ERROR) << "Unable to set caps";
428 return JNI_ERR;
429 }
430
431 // Add callbacks and notification.
432 error = jvmti->SetEventCallbacks(&kLogCallbacks, static_cast<jint>(sizeof(kLogCallbacks)));
433 if (error != JVMTI_ERROR_NONE) {
434 LOG(ERROR) << "Unable to set event callbacks.";
435 return JNI_ERR;
436 }
437 error = jvmti->SetEventNotificationMode(JVMTI_ENABLE,
438 JVMTI_EVENT_VM_OBJECT_ALLOC,
439 nullptr /* all threads */);
440 if (error != JVMTI_ERROR_NONE) {
441 LOG(ERROR) << "Unable to enable event " << JVMTI_EVENT_VM_OBJECT_ALLOC;
442 return JNI_ERR;
443 }
444
445 string_table = new UniqueStringTable();
446
447 return JNI_OK;
448 }
449
450 // Late attachment (e.g. 'am attach-agent').
Agent_OnAttach(JavaVM * vm,char * options,void * reserved)451 extern "C" JNIEXPORT jint JNICALL Agent_OnAttach(JavaVM *vm, char* options, void* reserved) {
452 return AgentStart(vm, options, reserved);
453 }
454
455 // Early attachment
Agent_OnLoad(JavaVM * jvm,char * options,void * reserved)456 extern "C" JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM* jvm, char* options, void* reserved) {
457 return AgentStart(jvm, options, reserved);
458 }
459
460 } // namespace tifast
461
462