1 // Copyright (C) 2020 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 #include <fcntl.h>
18 #include <jni.h>
19 #include <jvmti.h>
20 
21 #include <atomic>
22 #include <cstring>
23 #include <iomanip>
24 #include <iostream>
25 #include <memory>
26 #include <sstream>
27 #include <string>
28 #include <unordered_map>
29 #include <utility>
30 #include <vector>
31 
32 #include "android-base/unique_fd.h"
33 #include "nativehelper/scoped_local_ref.h"
34 
35 namespace simple_profile {
36 
37 static constexpr jint kArtTiVersion = JVMTI_VERSION_1_2 | 0x40000000;
38 
39 #define CHECK_JVMTI(a) CHECK_EQ(JVMTI_ERROR_NONE, a)
40 
41 struct DataDefinition {
42   std::string_view class_name;
43   std::string_view method_name;
44   std::string_view method_descriptor;
45   uint64_t count;
46 };
47 
operator <<(std::ostream & os,const DataDefinition & dd)48 std::ostream& operator<<(std::ostream& os, const DataDefinition& dd) {
49   return os << "{\"class_name\":\"" << dd.class_name << "\",\"method_name\":\"" << dd.method_name
50             << "\",\"method_descriptor\":\"" << dd.method_descriptor << "\",\"count\":" << dd.count
51             << "}";
52 }
53 
54 class SimpleProfileData {
55  public:
SimpleProfileData(jvmtiEnv * env,std::string out_fd_name,int fd,bool dump_on_shutdown,bool dump_on_main_stop)56   SimpleProfileData(
57       jvmtiEnv* env, std::string out_fd_name, int fd, bool dump_on_shutdown, bool dump_on_main_stop)
58       : dump_id_(0),
59         out_fd_name_(std::move(out_fd_name)),
60         out_fd_(fd),
61         shutdown_(false),
62         dump_on_shutdown_(dump_on_shutdown || dump_on_main_stop),
63         dump_on_main_stop_(dump_on_main_stop) {
64     CHECK_JVMTI(env->CreateRawMonitor("simple_profile_mon", &mon_));
65     method_counts_.reserve(10000);
66   }
67 
68   void Dump(jvmtiEnv* jvmti);
69   void Enter(jvmtiEnv* jvmti, JNIEnv* env, jmethodID meth);
70 
71   void RunDumpLoop(jvmtiEnv* jvmti, JNIEnv* env);
72 
GetProfileData(jvmtiEnv * env)73   static SimpleProfileData* GetProfileData(jvmtiEnv* env) {
74     void* data;
75     CHECK_JVMTI(env->GetEnvironmentLocalStorage(&data));
76     return static_cast<SimpleProfileData*>(data);
77   }
78 
79   void FinishInitialization(jvmtiEnv* jvmti, JNIEnv* jni, jthread cur);
80   void Shutdown(jvmtiEnv* jvmti, JNIEnv* jni);
81 
82  private:
83   void DoDump(jvmtiEnv* jvmti, JNIEnv* jni, const std::unordered_map<jmethodID, uint64_t>& copy);
84 
85   jlong dump_id_;
86   jrawMonitorID mon_;
87   std::string out_fd_name_;
88   int out_fd_;
89   std::unordered_map<jmethodID, uint64_t> method_counts_;
90   bool shutdown_;
91   bool dump_on_shutdown_;
92   bool dump_on_main_stop_;
93 };
94 
95 struct ScopedJvmtiMonitor {
96  public:
ScopedJvmtiMonitorsimple_profile::ScopedJvmtiMonitor97   ScopedJvmtiMonitor(jvmtiEnv* env, jrawMonitorID mon) : jvmti_(env), mon_(mon) {
98     CHECK_JVMTI(jvmti_->RawMonitorEnter(mon_));
99   }
100 
~ScopedJvmtiMonitorsimple_profile::ScopedJvmtiMonitor101   ~ScopedJvmtiMonitor() {
102     CHECK_JVMTI(jvmti_->RawMonitorExit(mon_));
103   }
104 
Notifysimple_profile::ScopedJvmtiMonitor105   void Notify() {
106     CHECK_JVMTI(jvmti_->RawMonitorNotifyAll(mon_));
107   }
108 
Waitsimple_profile::ScopedJvmtiMonitor109   void Wait() {
110     CHECK_JVMTI(jvmti_->RawMonitorWait(mon_, 0));
111   }
112 
113  private:
114   jvmtiEnv* jvmti_;
115   jrawMonitorID mon_;
116 };
117 
Enter(jvmtiEnv * jvmti,JNIEnv * env,jmethodID meth)118 void SimpleProfileData::Enter(jvmtiEnv* jvmti, JNIEnv* env, jmethodID meth) {
119   ScopedJvmtiMonitor sjm(jvmti, mon_);
120   // Keep all classes from being unloaded to allow us to know we can get the method info later.
121   jclass tmp;
122   CHECK_JVMTI(jvmti->GetMethodDeclaringClass(meth, &tmp));
123   ScopedLocalRef<jclass> klass(env, tmp);
124   jlong tag;
125   CHECK_JVMTI(jvmti->GetTag(klass.get(), &tag));
126   if (tag == 0) {
127     CHECK_JVMTI(jvmti->SetTag(klass.get(), 1u));
128     env->NewGlobalRef(klass.get());
129   }
130   method_counts_.insert({ meth, 0u }).first->second++;
131 }
132 
Dump(jvmtiEnv * jvmti)133 void SimpleProfileData::Dump(jvmtiEnv* jvmti) {
134   ScopedJvmtiMonitor sjm(jvmti, mon_);
135   dump_id_++;
136   sjm.Notify();
137 }
138 
RunDumpLoop(jvmtiEnv * jvmti,JNIEnv * env)139 void SimpleProfileData::RunDumpLoop(jvmtiEnv* jvmti, JNIEnv* env) {
140   jlong current_id = 0;
141   do {
142     std::unordered_map<jmethodID, uint64_t> copy;
143     {
144       ScopedJvmtiMonitor sjm(jvmti, mon_);
145       while (!shutdown_ && current_id == dump_id_) {
146         sjm.Wait();
147       }
148       if (shutdown_) {
149         break;
150       }
151       current_id = dump_id_;
152       copy = method_counts_;
153     }
154     DoDump(jvmti, env, std::move(copy));
155   } while (true);
156 }
157 
Shutdown(jvmtiEnv * jvmti,JNIEnv * jni)158 void SimpleProfileData::Shutdown(jvmtiEnv* jvmti, JNIEnv* jni) {
159   std::unordered_map<jmethodID, uint64_t> copy;
160   {
161     ScopedJvmtiMonitor sjm(jvmti, mon_);
162     if (shutdown_) {
163       return;
164     }
165     shutdown_ = true;
166     copy = method_counts_;
167     sjm.Notify();
168   }
169   if (dump_on_shutdown_) {
170     DoDump(jvmti, jni, std::move(copy));
171   }
172 }
173 
FinishInitialization(jvmtiEnv * jvmti,JNIEnv * env,jthread cur)174 void SimpleProfileData::FinishInitialization(jvmtiEnv* jvmti, JNIEnv* env, jthread cur) {
175   // Finish up startup.
176   // Create a Thread object.
177   std::string name = std::string("profile dump Thread: ") + this->out_fd_name_;
178   ScopedLocalRef<jobject> thread_name(env, env->NewStringUTF(name.c_str()));
179   CHECK_NE(thread_name.get(), nullptr);
180 
181   ScopedLocalRef<jclass> thread_klass(env, env->FindClass("java/lang/Thread"));
182   CHECK_NE(thread_klass.get(), nullptr);
183   ScopedLocalRef<jobject> thread(env, env->AllocObject(thread_klass.get()));
184   CHECK_NE(thread.get(), nullptr);
185   jmethodID initID = env->GetMethodID(thread_klass.get(), "<init>", "(Ljava/lang/String;)V");
186   jmethodID setDaemonId = env->GetMethodID(thread_klass.get(), "setDaemon", "(Z)V");
187   CHECK_NE(initID, nullptr);
188   CHECK_NE(setDaemonId, nullptr);
189   env->CallNonvirtualVoidMethod(thread.get(), thread_klass.get(), initID, thread_name.get());
190   env->CallVoidMethod(thread.get(), setDaemonId, JNI_TRUE);
191   CHECK(!env->ExceptionCheck());
192 
193   CHECK_JVMTI(jvmti->RunAgentThread(
194       thread.get(),
195       [](jvmtiEnv* jvmti, JNIEnv* jni, [[maybe_unused]] void* unused_data) {
196         SimpleProfileData* data = SimpleProfileData::GetProfileData(jvmti);
197         data->RunDumpLoop(jvmti, jni);
198       },
199       nullptr,
200       JVMTI_THREAD_NORM_PRIORITY));
201 
202   CHECK_JVMTI(jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_METHOD_ENTRY, nullptr));
203   CHECK_JVMTI(
204       jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_DATA_DUMP_REQUEST, nullptr));
205   if (dump_on_main_stop_) {
206     CHECK_JVMTI(jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_THREAD_END, cur));
207   }
208   CHECK_JVMTI(jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_VM_DEATH, nullptr));
209 }
210 
211 class ScopedClassInfo {
212  public:
ScopedClassInfo(jvmtiEnv * jvmti_env,jclass c)213   ScopedClassInfo(jvmtiEnv* jvmti_env, jclass c)
214       : jvmti_env_(jvmti_env), class_(c), name_(nullptr), generic_(nullptr) {}
215 
~ScopedClassInfo()216   ~ScopedClassInfo() {
217     if (class_ != nullptr) {
218       jvmti_env_->Deallocate(reinterpret_cast<unsigned char*>(name_));
219       jvmti_env_->Deallocate(reinterpret_cast<unsigned char*>(generic_));
220     }
221   }
222 
Init()223   bool Init() {
224     if (class_ == nullptr) {
225       name_ = const_cast<char*>("<NONE>");
226       generic_ = const_cast<char*>("<NONE>");
227       return true;
228     } else {
229       return jvmti_env_->GetClassSignature(class_, &name_, &generic_) == JVMTI_ERROR_NONE;
230     }
231   }
232 
GetClass() const233   jclass GetClass() const {
234     return class_;
235   }
GetName() const236   const char* GetName() const {
237     return name_;
238   }
239   // Generic type parameters, whatever is in the <> for a class
GetGeneric() const240   const char* GetGeneric() const {
241     return generic_;
242   }
243 
244  private:
245   jvmtiEnv* jvmti_env_;
246   jclass class_;
247   char* name_;
248   char* generic_;
249 };
250 
251 class ScopedMethodInfo {
252  public:
ScopedMethodInfo(jvmtiEnv * jvmti_env,JNIEnv * env,jmethodID method)253   ScopedMethodInfo(jvmtiEnv* jvmti_env, JNIEnv* env, jmethodID method)
254       : jvmti_env_(jvmti_env),
255         env_(env),
256         method_(method),
257         declaring_class_(nullptr),
258         class_info_(nullptr),
259         name_(nullptr),
260         signature_(nullptr),
261         generic_(nullptr) {}
262 
~ScopedMethodInfo()263   ~ScopedMethodInfo() {
264     env_->DeleteLocalRef(declaring_class_);
265     jvmti_env_->Deallocate(reinterpret_cast<unsigned char*>(name_));
266     jvmti_env_->Deallocate(reinterpret_cast<unsigned char*>(signature_));
267     jvmti_env_->Deallocate(reinterpret_cast<unsigned char*>(generic_));
268   }
269 
Init()270   bool Init() {
271     if (jvmti_env_->GetMethodDeclaringClass(method_, &declaring_class_) != JVMTI_ERROR_NONE) {
272       LOG(INFO) << "No decl";
273       return false;
274     }
275     class_info_.reset(new ScopedClassInfo(jvmti_env_, declaring_class_));
276     return class_info_->Init() &&
277            (jvmti_env_->GetMethodName(method_, &name_, &signature_, &generic_) == JVMTI_ERROR_NONE);
278   }
279 
GetDeclaringClassInfo() const280   const ScopedClassInfo& GetDeclaringClassInfo() const {
281     return *class_info_;
282   }
283 
GetDeclaringClass() const284   jclass GetDeclaringClass() const {
285     return declaring_class_;
286   }
287 
GetName() const288   const char* GetName() const {
289     return name_;
290   }
291 
GetSignature() const292   const char* GetSignature() const {
293     return signature_;
294   }
295 
GetGeneric() const296   const char* GetGeneric() const {
297     return generic_;
298   }
299 
300  private:
301   jvmtiEnv* jvmti_env_;
302   JNIEnv* env_;
303   jmethodID method_;
304   jclass declaring_class_;
305   std::unique_ptr<ScopedClassInfo> class_info_;
306   char* name_;
307   char* signature_;
308   char* generic_;
309 
310   friend std::ostream& operator<<(std::ostream& os, ScopedMethodInfo const& method);
311 };
312 
operator <<(std::ostream & os,const ScopedMethodInfo * method)313 std::ostream& operator<<(std::ostream& os, const ScopedMethodInfo* method) {
314   return os << *method;
315 }
316 
operator <<(std::ostream & os,ScopedMethodInfo const & method)317 std::ostream& operator<<(std::ostream& os, ScopedMethodInfo const& method) {
318   return os << method.GetDeclaringClassInfo().GetName() << "->" << method.GetName()
319             << method.GetSignature();
320 }
321 
DoDump(jvmtiEnv * jvmti,JNIEnv * jni,const std::unordered_map<jmethodID,uint64_t> & copy)322 void SimpleProfileData::DoDump(jvmtiEnv* jvmti,
323                                JNIEnv* jni,
324                                const std::unordered_map<jmethodID, uint64_t>& copy) {
325   std::ostringstream oss;
326   oss << "[";
327   bool is_first = true;
328   for (auto [meth, cnt] : copy) {
329     ScopedMethodInfo smi(jvmti, jni, meth);
330     if (!smi.Init()) {
331       continue;
332     }
333     if (!is_first) {
334       oss << "," << std::endl;
335     }
336     is_first = false;
337     oss << DataDefinition {
338       .class_name = smi.GetDeclaringClassInfo().GetName(),
339       .method_name = smi.GetName(),
340       .method_descriptor = smi.GetSignature(),
341       .count = cnt,
342     };
343   }
344   oss << "]";
345   CHECK_GE(TEMP_FAILURE_RETRY(write(out_fd_, oss.str().c_str(), oss.str().size())), 0)
346       << strerror(errno) << out_fd_ << " " << out_fd_name_;
347   fsync(out_fd_);
348 }
349 
DataDumpCb(jvmtiEnv * jvmti_env)350 static void DataDumpCb(jvmtiEnv* jvmti_env) {
351   SimpleProfileData* data = SimpleProfileData::GetProfileData(jvmti_env);
352   data->Dump(jvmti_env);
353 }
354 
MethodEntryCB(jvmtiEnv * jvmti_env,JNIEnv * env,jthread thread,jmethodID method)355 static void MethodEntryCB(jvmtiEnv* jvmti_env,
356                           JNIEnv* env,
357                           [[maybe_unused]] jthread thread,
358                           jmethodID method) {
359   SimpleProfileData* data = SimpleProfileData::GetProfileData(jvmti_env);
360   data->Enter(jvmti_env, env, method);
361 }
362 
VMInitCB(jvmtiEnv * jvmti,JNIEnv * env,jthread thr)363 static void VMInitCB(jvmtiEnv* jvmti, JNIEnv* env, jthread thr) {
364   SimpleProfileData* data = SimpleProfileData::GetProfileData(jvmti);
365   data->FinishInitialization(jvmti, env, thr);
366 }
VMDeathCB(jvmtiEnv * jvmti,JNIEnv * env)367 static void VMDeathCB(jvmtiEnv* jvmti, JNIEnv* env) {
368   SimpleProfileData* data = SimpleProfileData::GetProfileData(jvmti);
369   data->Shutdown(jvmti, env);
370 }
371 
372 // Fills targets with the breakpoints to add.
373 // Lname/of/Klass;->methodName(Lsig/of/Method)Lreturn/Type;@location,<...>
ParseArgs(const std::string & start_options,std::string * fd_name,int * fd,bool * dump_on_shutdown,bool * dump_on_main_stop)374 static bool ParseArgs(const std::string& start_options,
375                       /*out*/ std::string* fd_name,
376                       /*out*/ int* fd,
377                       /*out*/ bool* dump_on_shutdown,
378                       /*out*/ bool* dump_on_main_stop) {
379   std::istringstream iss(start_options);
380   std::string item;
381   *dump_on_main_stop = false;
382   *dump_on_shutdown = false;
383   bool has_fd = false;
384   while (std::getline(iss, item, ',')) {
385     if (item == "dump_on_shutdown") {
386       *dump_on_shutdown = true;
387     } else if (item == "dump_on_main_stop") {
388       *dump_on_main_stop = true;
389     } else if (has_fd) {
390       LOG(ERROR) << "Too many args!";
391       return false;
392     } else {
393       has_fd = true;
394       *fd_name = item;
395       *fd = TEMP_FAILURE_RETRY(open(fd_name->c_str(), O_WRONLY | O_CLOEXEC | O_CREAT, 00666));
396       CHECK_GE(*fd, 0) << strerror(errno);
397     }
398   }
399   return has_fd;
400 }
401 
402 enum class StartType {
403   OnAttach,
404   OnLoad,
405 };
406 
SetupJvmtiEnv(JavaVM * vm,jvmtiEnv ** jvmti)407 static jint SetupJvmtiEnv(JavaVM* vm, jvmtiEnv** jvmti) {
408   jint res = 0;
409   res = vm->GetEnv(reinterpret_cast<void**>(jvmti), JVMTI_VERSION_1_1);
410 
411   if (res != JNI_OK || *jvmti == nullptr) {
412     LOG(ERROR) << "Unable to access JVMTI, error code " << res;
413     return vm->GetEnv(reinterpret_cast<void**>(jvmti), kArtTiVersion);
414   }
415   return res;
416 }
417 
AgentStart(StartType start,JavaVM * vm,const char * options,void * reserved)418 static jint AgentStart(StartType start,
419                        JavaVM* vm,
420                        const char* options,
421                        [[maybe_unused]] void* reserved) {
422   if (options == nullptr) {
423     options = "";
424   }
425   jvmtiEnv* jvmti = nullptr;
426   jvmtiError error = JVMTI_ERROR_NONE;
427   {
428     jint res = 0;
429     res = SetupJvmtiEnv(vm, &jvmti);
430 
431     if (res != JNI_OK || jvmti == nullptr) {
432       LOG(ERROR) << "Unable to access JVMTI, error code " << res;
433       return JNI_ERR;
434     }
435   }
436 
437   int fd;
438   std::string fd_name;
439   bool dump_on_shutdown;
440   bool dump_on_main_stop;
441   if (!ParseArgs(options,
442                  /*out*/ &fd_name,
443                  /*out*/ &fd,
444                  /*out*/ &dump_on_shutdown,
445                  /*out*/ &dump_on_main_stop)) {
446     LOG(ERROR) << "failed to get output file from " << options << "!";
447     return JNI_ERR;
448   }
449 
450   void* data_mem = nullptr;
451   error = jvmti->Allocate(sizeof(SimpleProfileData), reinterpret_cast<unsigned char**>(&data_mem));
452   if (error != JVMTI_ERROR_NONE) {
453     LOG(ERROR) << "Unable to alloc memory for breakpoint target data";
454     return JNI_ERR;
455   }
456 
457   SimpleProfileData* data =
458       new (data_mem) SimpleProfileData(jvmti, fd_name, fd, dump_on_shutdown, dump_on_main_stop);
459   error = jvmti->SetEnvironmentLocalStorage(data);
460   if (error != JVMTI_ERROR_NONE) {
461     LOG(ERROR) << "Unable to set local storage";
462     return JNI_ERR;
463   }
464 
465   jvmtiCapabilities caps {};
466   caps.can_generate_method_entry_events = JNI_TRUE;
467   caps.can_tag_objects = JNI_TRUE;
468   error = jvmti->AddCapabilities(&caps);
469   if (error != JVMTI_ERROR_NONE) {
470     LOG(ERROR) << "Unable to set caps";
471     return JNI_ERR;
472   }
473 
474   jvmtiEventCallbacks callbacks {};
475   callbacks.MethodEntry = &MethodEntryCB;
476   callbacks.VMInit = &VMInitCB;
477   callbacks.DataDumpRequest = &DataDumpCb;
478   callbacks.VMDeath = &VMDeathCB;
479   callbacks.ThreadEnd = [](jvmtiEnv* env, JNIEnv* jni, [[maybe_unused]] jthread thr) {
480     VMDeathCB(env, jni);
481   };
482 
483   error = jvmti->SetEventCallbacks(&callbacks, static_cast<jint>(sizeof(callbacks)));
484 
485   if (error != JVMTI_ERROR_NONE) {
486     LOG(ERROR) << "Unable to set event callbacks.";
487     return JNI_ERR;
488   }
489 
490   if (start == StartType::OnAttach) {
491     JNIEnv* env = nullptr;
492     jint res = 0;
493     res = vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_2);
494     if (res != JNI_OK || env == nullptr) {
495       LOG(ERROR) << "Unable to get jnienv";
496       return JNI_ERR;
497     }
498     jthread temp;
499     ScopedLocalRef<jthread> cur(env, nullptr);
500     CHECK_JVMTI(jvmti->GetCurrentThread(&temp));
501     cur.reset(temp);
502     VMInitCB(jvmti, env, cur.get());
503   } else {
504     error = jvmti->SetEventNotificationMode(
505         JVMTI_ENABLE, JVMTI_EVENT_VM_INIT, nullptr /* all threads */);
506     if (error != JVMTI_ERROR_NONE) {
507       LOG(ERROR) << "Unable to set event vminit";
508       return JNI_ERR;
509     }
510   }
511   return JNI_OK;
512 }
513 
514 // Late attachment (e.g. 'am attach-agent').
Agent_OnAttach(JavaVM * vm,char * options,void * reserved)515 extern "C" JNIEXPORT jint JNICALL Agent_OnAttach(JavaVM* vm, char* options, void* reserved) {
516   return AgentStart(StartType::OnAttach, vm, options, reserved);
517 }
518 
519 // Early attachment
Agent_OnLoad(JavaVM * jvm,char * options,void * reserved)520 extern "C" JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM* jvm, char* options, void* reserved) {
521   return AgentStart(StartType::OnLoad, jvm, options, reserved);
522 }
523 
524 }  // namespace simple_profile
525