0%

方法监控instrumentation

之前研究jvmti时,知道jvmti时可以监控MethodEntry和MethodExit;今天在又发现了一个项目https://github.com/WaxMoon/ApoloPlugin,这个项目给了我们另一个思路,使用Debug的method trace

Debug.startMethodTracing

  • android.os.Debug

    1
    2
    3
    public 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
    14
    static 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
    114
    void 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
    57
    void 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;
    }
    }
    }