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