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