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