之前研究jvmti时,知道jvmti时可以监控MethodEntry和MethodExit;今天在又发现了一个项目https://github.com/WaxMoon/ApoloPlugin,这个项目给了我们另一个思路,使用Debug的method trace
Debug.startMethodTracing
android.os .Debug
1
2
3public static void startMethodTracing() {
VMDebug.startMethodTracing(fixTracePath(null), 0, 0, false, 0);
}art/runtime/native/dalvik_system_VMDebug.cc
1
2
3
4
5
6
7
8
9
10
11
12
13
14static void VMDebug_startMethodTracingFilename(JNIEnv* env, jclass, jstring javaTraceFilename,
jint bufferSize, jint flags,
jboolean samplingEnabled, jint intervalUs) {
ScopedUtfChars traceFilename(env, javaTraceFilename);
if (traceFilename.c_str() == nullptr) {
return;
}
Trace::Start(traceFilename.c_str(),
bufferSize,
flags,
Trace::TraceOutputMode::kFile,
samplingEnabled ? Trace::TraceMode::kSampling : Trace::TraceMode::kMethodTracing,
intervalUs);
}trace.cc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114void Trace::Start(const char* trace_filename,
size_t buffer_size,
int flags,
TraceOutputMode output_mode,
TraceMode trace_mode,
int interval_us) {
// 创建文件
std::unique_ptr<File> file(OS::CreateEmptyFileWriteOnly(trace_filename));
// 调用Start
Start(std::move(file), buffer_size, flags, output_mode, trace_mode, interval_us);
}
void Trace::Start(std::unique_ptr<File>&& trace_file_in,
size_t buffer_size,
int flags,
TraceOutputMode output_mode,
TraceMode trace_mode,
int interval_us) {
// We own trace_file now and are responsible for closing it. To account for error situations, use
// a specialized unique_ptr to ensure we close it on the way out (if it hasn't been passed to a
// Trace instance).
auto deleter = [](File* file) {
if (file != nullptr) {
file->MarkUnchecked(); // Don't deal with flushing requirements.
int result ATTRIBUTE_UNUSED = file->Close();
delete file;
}
};
std::unique_ptr<File, decltype(deleter)> trace_file(trace_file_in.release(), deleter);
Thread* self = Thread::Current();
{
MutexLock mu(self, *Locks::trace_lock_);
// 如果 the_trace_ 不为空就返回,防止多次进入
if (the_trace_ != nullptr) {
LOG(ERROR) << "Trace already in progress, ignoring this request";
return;
}
}
// trace_mode = Trace::TraceMode::kMethodTracing
if (trace_mode == TraceMode::kSampling && interval_us <= 0) {
LOG(ERROR) << "Invalid sampling interval: " << interval_us;
ScopedObjectAccess soa(self);
ThrowRuntimeException("Invalid sampling interval: %d", interval_us);
return;
}
Runtime* runtime = Runtime::Current();
// Enable count of allocs if specified in the flags.
bool enable_stats = false;
if (runtime->GetJit() != nullptr) {
// TODO b/110263880 It would be better if we didn't need to do this.
// Since we need to hold the method entrypoint across a suspend to ensure instrumentation
// hooks are called correctly we have to disable jit-gc to ensure that the entrypoint doesn't
// go away. Furthermore we need to leave this off permanently since one could get the same
// effect by causing this to be toggled on and off.
runtime->GetJit()->GetCodeCache()->SetGarbageCollectCode(false);
}
// Create Trace object.
{
// Required since EnableMethodTracing calls ConfigureStubs which visits class linker classes.
gc::ScopedGCCriticalSection gcs(self,
gc::kGcCauseInstrumentation,
gc::kCollectorTypeInstrumentation);
ScopedSuspendAll ssa(__FUNCTION__);
MutexLock mu(self, *Locks::trace_lock_);
// 首次 the_trace_ 肯定是为空
if (the_trace_ != nullptr) {
LOG(ERROR) << "Trace already in progress, ignoring this request";
} else {
enable_stats = (flags & kTraceCountAllocs) != 0;
the_trace_ = new Trace(trace_file.release(), buffer_size, flags, output_mode, trace_mode);
// trace_mode 为 kMethodTracing
if (trace_mode == TraceMode::kSampling) {
CHECK_PTHREAD_CALL(pthread_create, (&sampling_pthread_, nullptr, &RunSamplingThread,
reinterpret_cast<void*>(interval_us)),
"Sampling profiler thread");
the_trace_->interval_us_ = interval_us;
} else {
// 这里是关键 1.添加监听,很熟悉的样子,jvmti也是调用了此方法
runtime->GetInstrumentation()->AddListener(the_trace_,
instrumentation::Instrumentation::kMethodEntered |
instrumentation::Instrumentation::kMethodExited |
instrumentation::Instrumentation::kMethodUnwind);
// TODO: In full-PIC mode, we don't need to fully deopt.
// TODO: We can only use trampoline entrypoints if we are java-debuggable since in that case
// we know that inlining and other problematic optimizations are disabled. We might just
// want to use the trampolines anyway since it is faster. It makes the story with disabling
// jit-gc more complex though.
// 2. 设置MethodTracing功能开启
// static constexpr const char* kTracerInstrumentationKey = "Tracer";
// needs_interpreter 值理论上是可以通过hook runtime来修改的
runtime->GetInstrumentation()->EnableMethodTracing(
kTracerInstrumentationKey, /*needs_interpreter=*/!runtime->IsJavaDebuggable());
}
}
}
// Can't call this when holding the mutator lock.
if (enable_stats) {
runtime->SetStatsEnabled(true);
}
}到这里Method Trace功能就已经开启了,这里主要调用了Instrumentation的两个方法
1
2
3
4
void AddListener(InstrumentationListener* listener, uint32_t events)
void EnableMethodTracing(const char* key, bool needs_interpreter = kDeoptimizeForAccurateMethodEntryExitListeners)
Instrumentation
- AddListener 设置监听
- EnableMethodTracing 开启监听
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57void Instrumentation::UpdateStubs() {
// Look for the highest required instrumentation level.
InstrumentationLevel requested_level = InstrumentationLevel::kInstrumentNothing;
for (const auto& v : requested_instrumentation_levels_) {
requested_level = std::max(requested_level, v.second);
}
DCHECK(can_use_instrumentation_trampolines_ ||
requested_level != InstrumentationLevel::kInstrumentWithInstrumentationStubs)
<< "Use trampolines: " << can_use_instrumentation_trampolines_ << " level "
<< requested_level;
interpret_only_ = (requested_level == InstrumentationLevel::kInstrumentWithInterpreter) ||
forced_interpret_only_;
if (!RequiresInstrumentationInstallation(requested_level)) {
// We're already set.
return;
}
Thread* const self = Thread::Current();
Runtime* runtime = Runtime::Current();
Locks::mutator_lock_->AssertExclusiveHeld(self);
Locks::thread_list_lock_->AssertNotHeld(self);
if (requested_level > InstrumentationLevel::kInstrumentNothing) {
if (requested_level == InstrumentationLevel::kInstrumentWithInterpreter) {
interpreter_stubs_installed_ = true;
entry_exit_stubs_installed_ = true;
} else {
CHECK_EQ(requested_level, InstrumentationLevel::kInstrumentWithInstrumentationStubs);
entry_exit_stubs_installed_ = true;
interpreter_stubs_installed_ = false;
}
InstallStubsClassVisitor visitor(this);
runtime->GetClassLinker()->VisitClasses(&visitor);
instrumentation_stubs_installed_ = true;
MutexLock mu(self, *Locks::thread_list_lock_);
runtime->GetThreadList()->ForEach(InstrumentationInstallStack, this);
} else {
interpreter_stubs_installed_ = false;
entry_exit_stubs_installed_ = false;
InstallStubsClassVisitor visitor(this);
runtime->GetClassLinker()->VisitClasses(&visitor);
// Restore stack only if there is no method currently deoptimized.
bool empty;
{
ReaderMutexLock mu(self, *GetDeoptimizedMethodsLock());
empty = IsDeoptimizedMethodsEmpty(); // Avoid lock violation.
}
if (empty) {
MutexLock mu(self, *Locks::thread_list_lock_);
Runtime::Current()->GetThreadList()->ForEach(InstrumentationRestoreStack, this);
// Only do this after restoring, as walking the stack when restoring will see
// the instrumentation exit pc.
instrumentation_stubs_installed_ = false;
}
}
}