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