1 // Copyright (C) 2017 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 <atomic>
18 #include <iostream>
19 #include <iomanip>
20 #include <jni.h>
21 #include <jvmti.h>
22 #include <memory>
23 #include <string>
24 #include <vector>
25 
26 namespace breakpoint_logger {
27 
28 struct SingleBreakpointTarget {
29   std::string class_name;
30   std::string method_name;
31   std::string method_sig;
32   jlocation location;
33 };
34 
35 struct BreakpointTargets {
36   std::vector<SingleBreakpointTarget> bps;
37 };
38 
VMInitCB(jvmtiEnv * jvmti,JNIEnv * env,jthread thr ATTRIBUTE_UNUSED)39 static void VMInitCB(jvmtiEnv* jvmti, JNIEnv* env, jthread thr ATTRIBUTE_UNUSED) {
40   BreakpointTargets* all_targets = nullptr;
41   jvmtiError err = jvmti->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&all_targets));
42   if (err != JVMTI_ERROR_NONE || all_targets == nullptr) {
43     env->FatalError("unable to get breakpoint targets");
44   }
45   for (const SingleBreakpointTarget& target : all_targets->bps) {
46     jclass k = env->FindClass(target.class_name.c_str());
47     if (env->ExceptionCheck()) {
48       env->ExceptionDescribe();
49       env->FatalError("Could not find class!");
50       return;
51     }
52     jmethodID m = env->GetMethodID(k, target.method_name.c_str(), target.method_sig.c_str());
53     if (env->ExceptionCheck()) {
54       env->ExceptionClear();
55       m = env->GetStaticMethodID(k, target.method_name.c_str(), target.method_sig.c_str());
56       if (env->ExceptionCheck()) {
57         env->ExceptionDescribe();
58         env->FatalError("Could not find method!");
59         return;
60       }
61     }
62     err = jvmti->SetBreakpoint(m, target.location);
63     if (err != JVMTI_ERROR_NONE) {
64       env->FatalError("unable to set breakpoint");
65       return;
66     }
67     env->DeleteLocalRef(k);
68   }
69 }
70 
71 class ScopedThreadInfo {
72  public:
ScopedThreadInfo(jvmtiEnv * jvmti_env,JNIEnv * env,jthread thread)73   ScopedThreadInfo(jvmtiEnv* jvmti_env, JNIEnv* env, jthread thread)
74       : jvmti_env_(jvmti_env), env_(env), free_name_(false) {
75     memset(&info_, 0, sizeof(info_));
76     if (thread == nullptr) {
77       info_.name = const_cast<char*>("<NULLPTR>");
78     } else if (jvmti_env->GetThreadInfo(thread, &info_) != JVMTI_ERROR_NONE) {
79       info_.name = const_cast<char*>("<UNKNOWN THREAD>");
80     } else {
81       free_name_ = true;
82     }
83   }
84 
~ScopedThreadInfo()85   ~ScopedThreadInfo() {
86     if (free_name_) {
87       jvmti_env_->Deallocate(reinterpret_cast<unsigned char*>(info_.name));
88     }
89     env_->DeleteLocalRef(info_.thread_group);
90     env_->DeleteLocalRef(info_.context_class_loader);
91   }
92 
GetName() const93   const char* GetName() const {
94     return info_.name;
95   }
96 
97  private:
98   jvmtiEnv* jvmti_env_;
99   JNIEnv* env_;
100   bool free_name_;
101   jvmtiThreadInfo info_;
102 };
103 
104 class ScopedClassInfo {
105  public:
ScopedClassInfo(jvmtiEnv * jvmti_env,jclass c)106   ScopedClassInfo(jvmtiEnv* jvmti_env, jclass c)
107       : jvmti_env_(jvmti_env),
108         class_(c),
109         name_(nullptr),
110         generic_(nullptr),
111         file_(nullptr),
112         debug_ext_(nullptr) {}
113 
~ScopedClassInfo()114   ~ScopedClassInfo() {
115     if (class_ != nullptr) {
116       jvmti_env_->Deallocate(reinterpret_cast<unsigned char*>(name_));
117       jvmti_env_->Deallocate(reinterpret_cast<unsigned char*>(generic_));
118       jvmti_env_->Deallocate(reinterpret_cast<unsigned char*>(file_));
119       jvmti_env_->Deallocate(reinterpret_cast<unsigned char*>(debug_ext_));
120     }
121   }
122 
Init()123   bool Init() {
124     if (class_ == nullptr) {
125       name_ = const_cast<char*>("<NONE>");
126       generic_ = const_cast<char*>("<NONE>");
127       return true;
128     } else {
129       jvmtiError ret1 = jvmti_env_->GetSourceFileName(class_, &file_);
130       jvmtiError ret2 = jvmti_env_->GetSourceDebugExtension(class_, &debug_ext_);
131       return jvmti_env_->GetClassSignature(class_, &name_, &generic_) == JVMTI_ERROR_NONE &&
132           ret1 != JVMTI_ERROR_MUST_POSSESS_CAPABILITY &&
133           ret1 != JVMTI_ERROR_INVALID_CLASS &&
134           ret2 != JVMTI_ERROR_MUST_POSSESS_CAPABILITY &&
135           ret2 != JVMTI_ERROR_INVALID_CLASS;
136     }
137   }
138 
GetClass() const139   jclass GetClass() const {
140     return class_;
141   }
GetName() const142   const char* GetName() const {
143     return name_;
144   }
145   // Generic type parameters, whatever is in the <> for a class
GetGeneric() const146   const char* GetGeneric() const {
147     return generic_;
148   }
GetSourceDebugExtension() const149   const char* GetSourceDebugExtension() const {
150     if (debug_ext_ == nullptr) {
151       return "<UNKNOWN_SOURCE_DEBUG_EXTENSION>";
152     } else {
153       return debug_ext_;
154     }
155   }
GetSourceFileName() const156   const char* GetSourceFileName() const {
157     if (file_ == nullptr) {
158       return "<UNKNOWN_FILE>";
159     } else {
160       return file_;
161     }
162   }
163 
164  private:
165   jvmtiEnv* jvmti_env_;
166   jclass class_;
167   char* name_;
168   char* generic_;
169   char* file_;
170   char* debug_ext_;
171 };
172 
173 class ScopedMethodInfo {
174  public:
ScopedMethodInfo(jvmtiEnv * jvmti_env,JNIEnv * env,jmethodID method)175   ScopedMethodInfo(jvmtiEnv* jvmti_env, JNIEnv* env, jmethodID method)
176       : jvmti_env_(jvmti_env),
177         env_(env),
178         method_(method),
179         declaring_class_(nullptr),
180         class_info_(nullptr),
181         name_(nullptr),
182         signature_(nullptr),
183         generic_(nullptr),
184         first_line_(-1) {}
185 
~ScopedMethodInfo()186   ~ScopedMethodInfo() {
187     env_->DeleteLocalRef(declaring_class_);
188     jvmti_env_->Deallocate(reinterpret_cast<unsigned char*>(name_));
189     jvmti_env_->Deallocate(reinterpret_cast<unsigned char*>(signature_));
190     jvmti_env_->Deallocate(reinterpret_cast<unsigned char*>(generic_));
191   }
192 
Init()193   bool Init() {
194     if (jvmti_env_->GetMethodDeclaringClass(method_, &declaring_class_) != JVMTI_ERROR_NONE) {
195       return false;
196     }
197     class_info_.reset(new ScopedClassInfo(jvmti_env_, declaring_class_));
198     jint nlines;
199     jvmtiLineNumberEntry* lines;
200     jvmtiError err = jvmti_env_->GetLineNumberTable(method_, &nlines, &lines);
201     if (err == JVMTI_ERROR_NONE) {
202       if (nlines > 0) {
203         first_line_ = lines[0].line_number;
204       }
205       jvmti_env_->Deallocate(reinterpret_cast<unsigned char*>(lines));
206     } else if (err != JVMTI_ERROR_ABSENT_INFORMATION &&
207                err != JVMTI_ERROR_NATIVE_METHOD) {
208       return false;
209     }
210     return class_info_->Init() &&
211         (jvmti_env_->GetMethodName(method_, &name_, &signature_, &generic_) == JVMTI_ERROR_NONE);
212   }
213 
GetDeclaringClassInfo() const214   const ScopedClassInfo& GetDeclaringClassInfo() const {
215     return *class_info_;
216   }
217 
GetDeclaringClass() const218   jclass GetDeclaringClass() const {
219     return declaring_class_;
220   }
221 
GetName() const222   const char* GetName() const {
223     return name_;
224   }
225 
GetSignature() const226   const char* GetSignature() const {
227     return signature_;
228   }
229 
GetGeneric() const230   const char* GetGeneric() const {
231     return generic_;
232   }
233 
GetFirstLine() const234   jint GetFirstLine() const {
235     return first_line_;
236   }
237 
238  private:
239   jvmtiEnv* jvmti_env_;
240   JNIEnv* env_;
241   jmethodID method_;
242   jclass declaring_class_;
243   std::unique_ptr<ScopedClassInfo> class_info_;
244   char* name_;
245   char* signature_;
246   char* generic_;
247   jint first_line_;
248 
249   friend std::ostream& operator<<(std::ostream& os, ScopedMethodInfo const& method);
250 };
251 
operator <<(std::ostream & os,const ScopedMethodInfo * method)252 std::ostream& operator<<(std::ostream& os, const ScopedMethodInfo* method) {
253   return os << *method;
254 }
255 
operator <<(std::ostream & os,ScopedMethodInfo const & method)256 std::ostream& operator<<(std::ostream& os, ScopedMethodInfo const& method) {
257   return os << method.GetDeclaringClassInfo().GetName() << "->" << method.GetName()
258             << method.GetSignature() << " (source: "
259             << method.GetDeclaringClassInfo().GetSourceFileName() << ":" << method.GetFirstLine()
260             << ")";
261 }
262 
BreakpointCB(jvmtiEnv * jvmti_env,JNIEnv * env,jthread thread,jmethodID method,jlocation location)263 static void BreakpointCB(jvmtiEnv* jvmti_env,
264                          JNIEnv* env,
265                          jthread thread,
266                          jmethodID method,
267                          jlocation location) {
268   ScopedThreadInfo info(jvmti_env, env, thread);
269   ScopedMethodInfo method_info(jvmti_env, env, method);
270   if (!method_info.Init()) {
271     LOG(ERROR) << "Unable to get method info!";
272     return;
273   }
274   LOG(WARNING) << "Breakpoint at location: 0x" << std::setw(8) << std::setfill('0') << std::hex
275             << location << " in method " << method_info << " thread: " << info.GetName();
276 }
277 
SubstrOf(const std::string & s,size_t start,size_t end)278 static std::string SubstrOf(const std::string& s, size_t start, size_t end) {
279   if (end == std::string::npos) {
280     end = s.size();
281   }
282   if (end == start) {
283     return "";
284   }
285   CHECK_GT(end, start) << "cannot get substr of " << s;
286   return s.substr(start, end - start);
287 }
288 
ParseSingleBreakpoint(const std::string & bp,SingleBreakpointTarget * target)289 static bool ParseSingleBreakpoint(const std::string& bp, /*out*/SingleBreakpointTarget* target) {
290   std::string option = bp;
291   if (option.empty() || option[0] != 'L' || option.find(';') == std::string::npos) {
292     LOG(ERROR) << option << " doesn't look like it has a class name";
293     return false;
294   }
295   target->class_name = SubstrOf(option, 1, option.find(';'));
296 
297   option = SubstrOf(option, option.find(';') + 1, std::string::npos);
298   if (option.size() < 2 || option[0] != '-' || option[1] != '>') {
299     LOG(ERROR) << bp << " doesn't seem to indicate a method, expected ->";
300     return false;
301   }
302   option = SubstrOf(option, 2, std::string::npos);
303   size_t sig_start = option.find('(');
304   size_t loc_start = option.find('@');
305   if (option.empty() || sig_start == std::string::npos) {
306     LOG(ERROR) << bp << " doesn't seem to have a method sig!";
307     return false;
308   } else if (loc_start == std::string::npos ||
309              loc_start < sig_start ||
310              loc_start + 1 >= option.size()) {
311     LOG(ERROR) << bp << " doesn't seem to have a valid location!";
312     return false;
313   }
314   target->method_name = SubstrOf(option, 0, sig_start);
315   target->method_sig = SubstrOf(option, sig_start, loc_start);
316   target->location = std::stol(SubstrOf(option, loc_start + 1, std::string::npos));
317   return true;
318 }
319 
RemoveLastOption(const std::string & op)320 static std::string RemoveLastOption(const std::string& op) {
321   if (op.find(',') == std::string::npos) {
322     return "";
323   } else {
324     return SubstrOf(op, op.find(',') + 1, std::string::npos);
325   }
326 }
327 
328 // Fills targets with the breakpoints to add.
329 // Lname/of/Klass;->methodName(Lsig/of/Method)Lreturn/Type;@location,<...>
ParseArgs(const std::string & start_options,BreakpointTargets * targets)330 static bool ParseArgs(const std::string& start_options,
331                       /*out*/BreakpointTargets* targets) {
332   for (std::string options = start_options;
333        !options.empty();
334        options = RemoveLastOption(options)) {
335     SingleBreakpointTarget target;
336     std::string next = SubstrOf(options, 0, options.find(','));
337     if (!ParseSingleBreakpoint(next, /*out*/ &target)) {
338       LOG(ERROR) << "Unable to parse breakpoint from " << next;
339       return false;
340     }
341     targets->bps.push_back(target);
342   }
343   return true;
344 }
345 
346 enum class StartType {
347   OnAttach, OnLoad,
348 };
349 
AgentStart(StartType start,JavaVM * vm,char * options,void * reserved ATTRIBUTE_UNUSED)350 static jint AgentStart(StartType start,
351                        JavaVM* vm,
352                        char* options,
353                        void* reserved ATTRIBUTE_UNUSED) {
354   jvmtiEnv* jvmti = nullptr;
355   jvmtiError error = JVMTI_ERROR_NONE;
356   {
357     jint res = 0;
358     res = vm->GetEnv(reinterpret_cast<void**>(&jvmti), JVMTI_VERSION_1_1);
359 
360     if (res != JNI_OK || jvmti == nullptr) {
361       LOG(ERROR) << "Unable to access JVMTI, error code " << res;
362       return JNI_ERR;
363     }
364   }
365 
366   void* bp_target_mem = nullptr;
367   error = jvmti->Allocate(sizeof(BreakpointTargets),
368                           reinterpret_cast<unsigned char**>(&bp_target_mem));
369   if (error != JVMTI_ERROR_NONE) {
370     LOG(ERROR) << "Unable to alloc memory for breakpoint target data";
371     return JNI_ERR;
372   }
373 
374   BreakpointTargets* data = new(bp_target_mem) BreakpointTargets;
375   error = jvmti->SetEnvironmentLocalStorage(data);
376   if (error != JVMTI_ERROR_NONE) {
377     LOG(ERROR) << "Unable to set local storage";
378     return JNI_ERR;
379   }
380 
381   if (!ParseArgs(options, /*out*/data)) {
382     LOG(ERROR) << "failed to parse breakpoint list!";
383     return JNI_ERR;
384   }
385 
386   jvmtiCapabilities caps{};
387   caps.can_generate_breakpoint_events = JNI_TRUE;
388   caps.can_get_line_numbers           = JNI_TRUE;
389   caps.can_get_source_file_name       = JNI_TRUE;
390   caps.can_get_source_debug_extension = JNI_TRUE;
391   error = jvmti->AddCapabilities(&caps);
392   if (error != JVMTI_ERROR_NONE) {
393     LOG(ERROR) << "Unable to set caps";
394     return JNI_ERR;
395   }
396 
397   jvmtiEventCallbacks callbacks{};
398   callbacks.Breakpoint = &BreakpointCB;
399   callbacks.VMInit = &VMInitCB;
400 
401   error = jvmti->SetEventCallbacks(&callbacks, static_cast<jint>(sizeof(callbacks)));
402 
403   if (error != JVMTI_ERROR_NONE) {
404     LOG(ERROR) << "Unable to set event callbacks.";
405     return JNI_ERR;
406   }
407 
408   error = jvmti->SetEventNotificationMode(JVMTI_ENABLE,
409                                           JVMTI_EVENT_BREAKPOINT,
410                                           nullptr /* all threads */);
411   if (error != JVMTI_ERROR_NONE) {
412     LOG(ERROR) << "Unable to enable breakpoint event";
413     return JNI_ERR;
414   }
415   if (start == StartType::OnAttach) {
416     JNIEnv* env = nullptr;
417     jint res = 0;
418     res = vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_2);
419     if (res != JNI_OK || env == nullptr) {
420       LOG(ERROR) << "Unable to get jnienv";
421       return JNI_ERR;
422     }
423     VMInitCB(jvmti, env, nullptr);
424   } else {
425     error = jvmti->SetEventNotificationMode(JVMTI_ENABLE,
426                                             JVMTI_EVENT_VM_INIT,
427                                             nullptr /* all threads */);
428     if (error != JVMTI_ERROR_NONE) {
429       LOG(ERROR) << "Unable to set event vminit";
430       return JNI_ERR;
431     }
432   }
433   return JNI_OK;
434 }
435 
436 // Late attachment (e.g. 'am attach-agent').
Agent_OnAttach(JavaVM * vm,char * options,void * reserved)437 extern "C" JNIEXPORT jint JNICALL Agent_OnAttach(JavaVM *vm, char* options, void* reserved) {
438   return AgentStart(StartType::OnAttach, vm, options, reserved);
439 }
440 
441 // Early attachment
Agent_OnLoad(JavaVM * jvm,char * options,void * reserved)442 extern "C" JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM* jvm, char* options, void* reserved) {
443   return AgentStart(StartType::OnLoad, jvm, options, reserved);
444 }
445 
446 }  // namespace breakpoint_logger
447 
448