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