• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include "src/trace_processor/util/annotated_callsites.h"
18 
19 #include <cstddef>
20 #include <optional>
21 #include <utility>
22 
23 #include "perfetto/ext/base/string_view.h"
24 #include "src/trace_processor/containers/null_term_string_view.h"
25 #include "src/trace_processor/storage/trace_storage.h"
26 #include "src/trace_processor/tables/profiler_tables_py.h"
27 #include "src/trace_processor/types/trace_processor_context.h"
28 
29 namespace perfetto::trace_processor {
30 
AnnotatedCallsites(const TraceProcessorContext * context)31 AnnotatedCallsites::AnnotatedCallsites(const TraceProcessorContext* context)
32     : context_(*context),
33       // String to identify trampoline frames. If the string does not exist in
34       // TraceProcessor's StringPool (nullopt) then there will be no trampoline
35       // frames in the trace so there is no point in adding it to the pool to do
36       // all comparisons, instead we initialize the member to std::nullopt and
37       // the string comparisons will all fail.
38       art_jni_trampoline_(
39           context->storage->string_pool().GetId("art_jni_trampoline")) {}
40 
GetState(std::optional<CallsiteId> id)41 AnnotatedCallsites::State AnnotatedCallsites::GetState(
42     std::optional<CallsiteId> id) {
43   if (!id) {
44     return State::kInitial;
45   }
46   auto it = states_.find(*id);
47   if (it != states_.end()) {
48     return it->second;
49   }
50 
51   State state =
52       Get(*context_.storage->stack_profile_callsite_table().FindById(*id))
53           .first;
54   states_.emplace(*id, state);
55   return state;
56 }
57 
58 std::pair<AnnotatedCallsites::State, CallsiteAnnotation>
Get(const tables::StackProfileCallsiteTable::ConstRowReference & callsite)59 AnnotatedCallsites::Get(
60     const tables::StackProfileCallsiteTable::ConstRowReference& callsite) {
61   State state = GetState(callsite.parent_id());
62 
63   // Keep immediate callee of a JNI trampoline, but keep tagging all
64   // successive libart frames as common.
65   if (state == State::kKeepNext) {
66     return {State::kEraseLibart, CallsiteAnnotation::kNone};
67   }
68 
69   // Special-case "art_jni_trampoline" frames, keeping their immediate callee
70   // even if it is in libart, as it could be a native implementation of a
71   // managed method. Example for "java.lang.reflect.Method.Invoke":
72   //   art_jni_trampoline
73   //   art::Method_invoke(_JNIEnv*, _jobject*, _jobject*, _jobjectArray*)
74   //
75   // Simpleperf also relies on this frame name, so it should be fairly stable.
76   // TODO(rsavitski): consider detecting standard JNI upcall entrypoints -
77   // _JNIEnv::Call*. These are sometimes inlined into other DSOs, so erasing
78   // only the libart frames does not clean up all of the JNI-related frames.
79   auto frame = *context_.storage->stack_profile_frame_table().FindById(
80       callsite.frame_id());
81   // art_jni_trampoline_ could be std::nullopt if the string does not exist in
82   // the StringPool, but that also means no frame will ever have that name.
83   if (art_jni_trampoline_.has_value() &&
84       frame.name() == art_jni_trampoline_.value()) {
85     return {State::kKeepNext, CallsiteAnnotation::kCommonFrame};
86   }
87 
88   MapType map_type = GetMapType(frame.mapping());
89 
90   // Annotate managed frames.
91   if (map_type == MapType::kArtInterp ||  //
92       map_type == MapType::kArtJit ||     //
93       map_type == MapType::kArtAot) {
94     // Now know to be in a managed callstack - erase subsequent ART frames.
95     if (state == State::kInitial) {
96       state = State::kEraseLibart;
97     }
98 
99     if (map_type == MapType::kArtInterp)
100       return {state, CallsiteAnnotation::kArtInterpreted};
101     if (map_type == MapType::kArtJit)
102       return {state, CallsiteAnnotation::kArtJit};
103     if (map_type == MapType::kArtAot)
104       return {state, CallsiteAnnotation::kArtAot};
105   }
106 
107   // Mixed callstack, tag libart frames as uninteresting (common-frame).
108   // Special case a subset of interpreter implementation frames as
109   // "common-frame-interp" using frame name prefixes. Those functions are
110   // actually executed, whereas the managed "interp" frames are synthesised as
111   // their caller by the unwinding library (based on the dex_pc virtual
112   // register restored using the libart's DWARF info). The heuristic covers
113   // the "nterp" and "switch" interpreter implementations.
114   //
115   // Example:
116   //  <towards root>
117   //  android.view.WindowLayout.computeFrames [interp]
118   //  nterp_op_iget_object_slow_path [common-frame-interp]
119   //
120   // This annotation is helpful when trying to answer "what mode was the
121   // process in?" based on the leaf frame of the callstack. As we want to
122   // classify such cases as interpreted, even though the leaf frame is
123   // libart.so.
124   //
125   // For "switch" interpreter, we match any frame starting with
126   // "art::interpreter::" according to itanium mangling.
127   if (state == State::kEraseLibart && map_type == MapType::kNativeLibart) {
128     NullTermStringView fname = context_.storage->GetString(frame.name());
129     if (fname.StartsWith("nterp_") || fname.StartsWith("Nterp") ||
130         fname.StartsWith("ExecuteNterp") ||
131         fname.StartsWith("ExecuteSwitchImpl") ||
132         fname.StartsWith("_ZN3art11interpreter")) {
133       return {state, CallsiteAnnotation::kCommonFrameInterp};
134     }
135     return {state, CallsiteAnnotation::kCommonFrame};
136   }
137 
138   return {state, CallsiteAnnotation::kNone};
139 }
140 
GetMapType(MappingId id)141 AnnotatedCallsites::MapType AnnotatedCallsites::GetMapType(MappingId id) {
142   auto it = map_types_.find(id);
143   if (it != map_types_.end()) {
144     return it->second;
145   }
146 
147   return map_types_
148       .emplace(id, ClassifyMap(context_.storage->GetString(
149                        context_.storage->stack_profile_mapping_table()
150                            .FindById(id)
151                            ->name())))
152       .first->second;
153 }
154 
ClassifyMap(NullTermStringView map)155 AnnotatedCallsites::MapType AnnotatedCallsites::ClassifyMap(
156     NullTermStringView map) {
157   if (map.empty())
158     return MapType::kOther;
159 
160   // Primary mapping where modern ART puts jitted code.
161   // The Zygote's JIT region is inherited by all descendant apps, so it can
162   // still appear in their callstacks.
163   if (map.StartsWith("/memfd:jit-cache") ||
164       map.StartsWith("/memfd:jit-zygote-cache")) {
165     return MapType::kArtJit;
166   }
167 
168   size_t last_slash_pos = map.rfind('/');
169   if (last_slash_pos != NullTermStringView::npos) {
170     base::StringView suffix = map.substr(last_slash_pos);
171     if (suffix.StartsWith("/libart.so") || suffix.StartsWith("/libartd.so"))
172       return MapType::kNativeLibart;
173   }
174 
175   size_t extension_pos = map.rfind('.');
176   if (extension_pos != NullTermStringView::npos) {
177     base::StringView suffix = map.substr(extension_pos);
178     if (suffix.StartsWith(".so"))
179       return MapType::kNativeOther;
180     // unqualified dex
181     if (suffix.StartsWith(".dex"))
182       return MapType::kArtInterp;
183     // dex with verification speedup info, produced by dex2oat
184     if (suffix.StartsWith(".vdex"))
185       return MapType::kArtInterp;
186     // possibly uncompressed dex in a jar archive
187     if (suffix.StartsWith(".jar"))
188       return MapType::kArtInterp;
189     // android package (zip file), this can contain uncompressed dexes or
190     // native libraries that are mmap'd directly into the process. We rely on
191     // libunwindstack's MapInfo::GetFullName, which suffixes the mapping with
192     // "!lib.so" if it knows that the referenced piece of the archive is an
193     // uncompressed ELF file. So an unadorned ".apk" is assumed to be a dex
194     // file.
195     if (suffix.StartsWith(".apk"))
196       return MapType::kArtInterp;
197     // ahead of time compiled ELFs
198     if (suffix.StartsWith(".oat"))
199       return MapType::kArtAot;
200     // older/alternative name for .oat
201     if (suffix.StartsWith(".odex"))
202       return MapType::kArtAot;
203   }
204   return MapType::kOther;
205 }
206 
207 }  // namespace perfetto::trace_processor
208