|  | /* | 
|  | * Copyright (C) 2021 The Android Open Source Project | 
|  | * | 
|  | * Licensed under the Apache License, Version 2.0 (the "License"); | 
|  | * you may not use this file except in compliance with the License. | 
|  | * You may obtain a copy of the License at | 
|  | * | 
|  | *      http://www.apache.org/licenses/LICENSE-2.0 | 
|  | * | 
|  | * Unless required by applicable law or agreed to in writing, software | 
|  | * distributed under the License is distributed on an "AS IS" BASIS, | 
|  | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
|  | * See the License for the specific language governing permissions and | 
|  | * limitations under the License. | 
|  | */ | 
|  |  | 
|  | #include "src/trace_processor/util/annotated_callsites.h" | 
|  |  | 
|  | #include <iostream> | 
|  | #include <optional> | 
|  |  | 
|  | #include "src/trace_processor/tables/profiler_tables_py.h" | 
|  | #include "src/trace_processor/types/trace_processor_context.h" | 
|  |  | 
|  | namespace perfetto { | 
|  | namespace trace_processor { | 
|  |  | 
|  | AnnotatedCallsites::AnnotatedCallsites(const TraceProcessorContext* context) | 
|  | : context_(*context), | 
|  | // String to identify trampoline frames. If the string does not exist in | 
|  | // TraceProcessor's StringPool (nullopt) then there will be no trampoline | 
|  | // frames in the trace so there is no point in adding it to the pool to do | 
|  | // all comparisons, instead we initialize the member to std::nullopt and | 
|  | // the string comparisons will all fail. | 
|  | art_jni_trampoline_( | 
|  | context->storage->string_pool().GetId("art_jni_trampoline")) {} | 
|  |  | 
|  | AnnotatedCallsites::State AnnotatedCallsites::GetState( | 
|  | std::optional<CallsiteId> id) { | 
|  | if (!id) { | 
|  | return State::kInitial; | 
|  | } | 
|  | auto it = states_.find(*id); | 
|  | if (it != states_.end()) { | 
|  | return it->second; | 
|  | } | 
|  |  | 
|  | State state = | 
|  | Get(*context_.storage->stack_profile_callsite_table().FindById(*id)) | 
|  | .first; | 
|  | states_.emplace(*id, state); | 
|  | return state; | 
|  | } | 
|  |  | 
|  | std::pair<AnnotatedCallsites::State, CallsiteAnnotation> | 
|  | AnnotatedCallsites::Get( | 
|  | const tables::StackProfileCallsiteTable::ConstRowReference& callsite) { | 
|  | State state = GetState(callsite.parent_id()); | 
|  |  | 
|  | // Keep immediate callee of a JNI trampoline, but keep tagging all | 
|  | // successive libart frames as common. | 
|  | if (state == State::kKeepNext) { | 
|  | return {State::kEraseLibart, CallsiteAnnotation::kNone}; | 
|  | } | 
|  |  | 
|  | // Special-case "art_jni_trampoline" frames, keeping their immediate callee | 
|  | // even if it is in libart, as it could be a native implementation of a | 
|  | // managed method. Example for "java.lang.reflect.Method.Invoke": | 
|  | //   art_jni_trampoline | 
|  | //   art::Method_invoke(_JNIEnv*, _jobject*, _jobject*, _jobjectArray*) | 
|  | // | 
|  | // Simpleperf also relies on this frame name, so it should be fairly stable. | 
|  | // TODO(rsavitski): consider detecting standard JNI upcall entrypoints - | 
|  | // _JNIEnv::Call*. These are sometimes inlined into other DSOs, so erasing | 
|  | // only the libart frames does not clean up all of the JNI-related frames. | 
|  | auto frame = *context_.storage->stack_profile_frame_table().FindById( | 
|  | callsite.frame_id()); | 
|  | // art_jni_trampoline_ could be std::nullopt if the string does not exist in | 
|  | // the StringPool, but that also means no frame will ever have that name. | 
|  | if (art_jni_trampoline_.has_value() && | 
|  | frame.name() == art_jni_trampoline_.value()) { | 
|  | return {State::kKeepNext, CallsiteAnnotation::kCommonFrame}; | 
|  | } | 
|  |  | 
|  | MapType map_type = GetMapType(frame.mapping()); | 
|  |  | 
|  | // Annotate managed frames. | 
|  | if (map_type == MapType::kArtInterp ||  // | 
|  | map_type == MapType::kArtJit ||     // | 
|  | map_type == MapType::kArtAot) { | 
|  | // Now know to be in a managed callstack - erase subsequent ART frames. | 
|  | if (state == State::kInitial) { | 
|  | state = State::kEraseLibart; | 
|  | } | 
|  |  | 
|  | if (map_type == MapType::kArtInterp) | 
|  | return {state, CallsiteAnnotation::kArtInterpreted}; | 
|  | if (map_type == MapType::kArtJit) | 
|  | return {state, CallsiteAnnotation::kArtJit}; | 
|  | if (map_type == MapType::kArtAot) | 
|  | return {state, CallsiteAnnotation::kArtAot}; | 
|  | } | 
|  |  | 
|  | // Mixed callstack, tag libart frames as uninteresting (common-frame). | 
|  | // Special case a subset of interpreter implementation frames as | 
|  | // "common-frame-interp" using frame name prefixes. Those functions are | 
|  | // actually executed, whereas the managed "interp" frames are synthesised as | 
|  | // their caller by the unwinding library (based on the dex_pc virtual | 
|  | // register restored using the libart's DWARF info). The heuristic covers | 
|  | // the "nterp" and "switch" interpreter implementations. | 
|  | // | 
|  | // Example: | 
|  | //  <towards root> | 
|  | //  android.view.WindowLayout.computeFrames [interp] | 
|  | //  nterp_op_iget_object_slow_path [common-frame-interp] | 
|  | // | 
|  | // This annotation is helpful when trying to answer "what mode was the | 
|  | // process in?" based on the leaf frame of the callstack. As we want to | 
|  | // classify such cases as interpreted, even though the leaf frame is | 
|  | // libart.so. | 
|  | // | 
|  | // For "switch" interpreter, we match any frame starting with | 
|  | // "art::interpreter::" according to itanium mangling. | 
|  | if (state == State::kEraseLibart && map_type == MapType::kNativeLibart) { | 
|  | NullTermStringView fname = context_.storage->GetString(frame.name()); | 
|  | if (fname.StartsWith("nterp_") || fname.StartsWith("Nterp") || | 
|  | fname.StartsWith("ExecuteNterp") || | 
|  | fname.StartsWith("ExecuteSwitchImpl") || | 
|  | fname.StartsWith("_ZN3art11interpreter")) { | 
|  | return {state, CallsiteAnnotation::kCommonFrameInterp}; | 
|  | } | 
|  | return {state, CallsiteAnnotation::kCommonFrame}; | 
|  | } | 
|  |  | 
|  | return {state, CallsiteAnnotation::kNone}; | 
|  | } | 
|  |  | 
|  | AnnotatedCallsites::MapType AnnotatedCallsites::GetMapType(MappingId id) { | 
|  | auto it = map_types_.find(id); | 
|  | if (it != map_types_.end()) { | 
|  | return it->second; | 
|  | } | 
|  |  | 
|  | return map_types_ | 
|  | .emplace(id, ClassifyMap(context_.storage->GetString( | 
|  | context_.storage->stack_profile_mapping_table() | 
|  | .FindById(id) | 
|  | ->name()))) | 
|  | .first->second; | 
|  | } | 
|  |  | 
|  | AnnotatedCallsites::MapType AnnotatedCallsites::ClassifyMap( | 
|  | NullTermStringView map) { | 
|  | if (map.empty()) | 
|  | return MapType::kOther; | 
|  |  | 
|  | // Primary mapping where modern ART puts jitted code. | 
|  | // The Zygote's JIT region is inherited by all descendant apps, so it can | 
|  | // still appear in their callstacks. | 
|  | if (map.StartsWith("/memfd:jit-cache") || | 
|  | map.StartsWith("/memfd:jit-zygote-cache")) { | 
|  | return MapType::kArtJit; | 
|  | } | 
|  |  | 
|  | size_t last_slash_pos = map.rfind('/'); | 
|  | if (last_slash_pos != NullTermStringView::npos) { | 
|  | base::StringView suffix = map.substr(last_slash_pos); | 
|  | if (suffix.StartsWith("/libart.so") || suffix.StartsWith("/libartd.so")) | 
|  | return MapType::kNativeLibart; | 
|  | } | 
|  |  | 
|  | size_t extension_pos = map.rfind('.'); | 
|  | if (extension_pos != NullTermStringView::npos) { | 
|  | base::StringView suffix = map.substr(extension_pos); | 
|  | if (suffix.StartsWith(".so")) | 
|  | return MapType::kNativeOther; | 
|  | // unqualified dex | 
|  | if (suffix.StartsWith(".dex")) | 
|  | return MapType::kArtInterp; | 
|  | // dex with verification speedup info, produced by dex2oat | 
|  | if (suffix.StartsWith(".vdex")) | 
|  | return MapType::kArtInterp; | 
|  | // possibly uncompressed dex in a jar archive | 
|  | if (suffix.StartsWith(".jar")) | 
|  | return MapType::kArtInterp; | 
|  | // android package (zip file), this can contain uncompressed dexes or | 
|  | // native libraries that are mmap'd directly into the process. We rely on | 
|  | // libunwindstack's MapInfo::GetFullName, which suffixes the mapping with | 
|  | // "!lib.so" if it knows that the referenced piece of the archive is an | 
|  | // uncompressed ELF file. So an unadorned ".apk" is assumed to be a dex | 
|  | // file. | 
|  | if (suffix.StartsWith(".apk")) | 
|  | return MapType::kArtInterp; | 
|  | // ahead of time compiled ELFs | 
|  | if (suffix.StartsWith(".oat")) | 
|  | return MapType::kArtAot; | 
|  | // older/alternative name for .oat | 
|  | if (suffix.StartsWith(".odex")) | 
|  | return MapType::kArtAot; | 
|  | } | 
|  | return MapType::kOther; | 
|  | } | 
|  |  | 
|  | }  // namespace trace_processor | 
|  | }  // namespace perfetto |