Home | History | Annotate | Download | only in breakpoint-logger
      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 
     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:
     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 
     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 
     93   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:
    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 
    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 
    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 
    139   jclass GetClass() const {
    140     return class_;
    141   }
    142   const char* GetName() const {
    143     return name_;
    144   }
    145   // Generic type parameters, whatever is in the <> for a class
    146   const char* GetGeneric() const {
    147     return generic_;
    148   }
    149   const char* GetSourceDebugExtension() const {
    150     if (debug_ext_ == nullptr) {
    151       return "<UNKNOWN_SOURCE_DEBUG_EXTENSION>";
    152     } else {
    153       return debug_ext_;
    154     }
    155   }
    156   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:
    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 
    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 
    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 
    214   const ScopedClassInfo& GetDeclaringClassInfo() const {
    215     return *class_info_;
    216   }
    217 
    218   jclass GetDeclaringClass() const {
    219     return declaring_class_;
    220   }
    221 
    222   const char* GetName() const {
    223     return name_;
    224   }
    225 
    226   const char* GetSignature() const {
    227     return signature_;
    228   }
    229 
    230   const char* GetGeneric() const {
    231     return generic_;
    232   }
    233 
    234   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 
    252 std::ostream& operator<<(std::ostream& os, const ScopedMethodInfo* method) {
    253   return os << *method;
    254 }
    255 
    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 
    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 
    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 
    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 
    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,<...>
    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 
    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').
    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
    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