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