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/perfetto_sql/intrinsics/table_functions/experimental_annotated_stack.h"
18 
19 #include <cinttypes>
20 #include <cstddef>
21 #include <cstdint>
22 #include <memory>
23 #include <optional>
24 #include <string>
25 #include <utility>
26 #include <vector>
27 
28 #include "perfetto/base/logging.h"
29 #include "perfetto/base/status.h"
30 #include "perfetto/ext/base/status_or.h"
31 #include "perfetto/ext/base/string_view.h"
32 #include "perfetto/trace_processor/basic_types.h"
33 #include "src/trace_processor/containers/null_term_string_view.h"
34 #include "src/trace_processor/containers/string_pool.h"
35 #include "src/trace_processor/db/column_storage.h"
36 #include "src/trace_processor/db/table.h"
37 #include "src/trace_processor/perfetto_sql/intrinsics/table_functions/tables_py.h"
38 #include "src/trace_processor/storage/trace_storage.h"
39 #include "src/trace_processor/tables/profiler_tables_py.h"
40 #include "src/trace_processor/types/trace_processor_context.h"
41 
42 namespace perfetto::trace_processor {
43 namespace tables {
44 
45 ExperimentalAnnotatedCallstackTable::~ExperimentalAnnotatedCallstackTable() =
46     default;
47 
48 }  // namespace tables
49 
50 namespace {
51 
52 enum class MapType {
53   kArtInterp,
54   kArtJit,
55   kArtAot,
56   kNativeLibart,
57   kNativeOther,
58   kOther
59 };
60 
61 // Mapping examples:
62 //   /system/lib64/libc.so
63 //   /system/framework/framework.jar
64 //   /memfd:jit-cache (deleted)
65 //   /data/dalvik-cache/arm64/<snip>[email protected]
66 //   /data/app/<snip>/base.apk!libmonochrome_64.so
67 //   [vdso]
68 // TODO(rsavitski): consider moving this to a hidden column on
69 // stack_profile_mapping.
ClassifyMap(NullTermStringView map)70 MapType ClassifyMap(NullTermStringView map) {
71   if (map.empty())
72     return MapType::kOther;
73 
74   // Primary mapping where modern ART puts jitted code.
75   // The Zygote's JIT region is inherited by all descendant apps, so it can
76   // still appear in their callstacks.
77   if (map.StartsWith("/memfd:jit-cache") ||
78       map.StartsWith("/memfd:jit-zygote-cache")) {
79     return MapType::kArtJit;
80   }
81 
82   size_t last_slash_pos = map.rfind('/');
83   if (last_slash_pos != NullTermStringView::npos) {
84     base::StringView suffix = map.substr(last_slash_pos);
85     if (suffix.StartsWith("/libart.so") || suffix.StartsWith("/libartd.so"))
86       return MapType::kNativeLibart;
87   }
88   size_t extension_pos = map.rfind('.');
89   if (extension_pos != NullTermStringView::npos) {
90     base::StringView suffix = map.substr(extension_pos);
91     if (suffix.StartsWith(".so"))
92       return MapType::kNativeOther;
93     // unqualified dex
94     if (suffix.StartsWith(".dex"))
95       return MapType::kArtInterp;
96     // dex with verification speedup info, produced by dex2oat
97     if (suffix.StartsWith(".vdex"))
98       return MapType::kArtInterp;
99     // possibly uncompressed dex in a jar archive
100     if (suffix.StartsWith(".jar"))
101       return MapType::kArtInterp;
102     // android package (zip file), this can contain uncompressed dexes or
103     // native libraries that are mmap'd directly into the process. We rely on
104     // libunwindstack's MapInfo::GetFullName, which suffixes the mapping with
105     // "!lib.so" if it knows that the referenced piece of the archive is an
106     // uncompressed ELF file. So an unadorned ".apk" is assumed to be a dex
107     // file.
108     if (suffix.StartsWith(".apk"))
109       return MapType::kArtInterp;
110     // ahead of time compiled ELFs
111     if (suffix.StartsWith(".oat"))
112       return MapType::kArtAot;
113     // older/alternative name for .oat
114     if (suffix.StartsWith(".odex"))
115       return MapType::kArtAot;
116   }
117   return MapType::kOther;
118 }
119 
120 }  // namespace
121 
TableName()122 std::string ExperimentalAnnotatedStack::TableName() {
123   return tables::ExperimentalAnnotatedCallstackTable::Name();
124 }
125 
CreateSchema()126 Table::Schema ExperimentalAnnotatedStack::CreateSchema() {
127   return tables::ExperimentalAnnotatedCallstackTable::ComputeStaticSchema();
128 }
129 
130 // TODO(carlscab): Replace annotation logic with
131 // src/trace_processor/util/annotated_callsites.h
ComputeTable(const std::vector<SqlValue> & arguments)132 base::StatusOr<std::unique_ptr<Table>> ExperimentalAnnotatedStack::ComputeTable(
133     const std::vector<SqlValue>& arguments) {
134   PERFETTO_CHECK(arguments.size() == 1);
135 
136   using CallsiteTable = tables::StackProfileCallsiteTable;
137 
138   const auto& cs_table = context_->storage->stack_profile_callsite_table();
139   const auto& f_table = context_->storage->stack_profile_frame_table();
140   const auto& m_table = context_->storage->stack_profile_mapping_table();
141 
142   if (arguments[0].type != SqlValue::Type::kLong) {
143     return base::ErrStatus("invalid input callsite id");
144   }
145 
146   CallsiteId start_id(static_cast<uint32_t>(arguments[0].AsLong()));
147   auto opt_start_ref = cs_table.FindById(start_id);
148   if (!opt_start_ref) {
149     return base::ErrStatus("callsite with id %" PRIu32 " not found",
150                            start_id.value);
151   }
152 
153   // Iteratively walk the parent_id chain to construct the list of callstack
154   // entries, each pointing at a frame.
155   std::vector<CallsiteTable::RowNumber> cs_rows;
156   cs_rows.push_back(opt_start_ref->ToRowNumber());
157   std::optional<CallsiteId> maybe_parent_id = opt_start_ref->parent_id();
158   while (maybe_parent_id) {
159     auto parent_ref = *cs_table.FindById(*maybe_parent_id);
160     cs_rows.push_back(parent_ref.ToRowNumber());
161     maybe_parent_id = parent_ref.parent_id();
162   }
163 
164   // Walk the callsites root-to-leaf, annotating:
165   // * managed frames with their execution state (interpreted/jit/aot)
166   // * common ART frames, which are usually not relevant to
167   //   visualisation/inspection
168   //
169   // This is not a per-frame decision, because we do not want to filter out ART
170   // frames immediately after a JNI transition (such frames are often relevant).
171   //
172   // As a consequence of the logic being based on a root-to-leaf walk, a given
173   // callsite will always have the same annotation, as the parent path is always
174   // the same, and children callsites do not affect their parents' annotations.
175   StringId art_jni_trampoline =
176       context_->storage->InternString("art_jni_trampoline");
177 
178   StringId common_frame = context_->storage->InternString("common-frame");
179   StringId common_frame_interp =
180       context_->storage->InternString("common-frame-interp");
181   StringId art_interp = context_->storage->InternString("interp");
182   StringId art_jit = context_->storage->InternString("jit");
183   StringId art_aot = context_->storage->InternString("aot");
184 
185   // Annotation FSM states:
186   // * kInitial: default, native-only callstacks never leave this state.
187   // * kEraseLibart: we've seen a managed frame, and will now "erase" (i.e. tag
188   //                 as a common-frame) frames belonging to the ART runtime.
189   // * kKeepNext: we've seen a special JNI trampoline for managed->native
190   //              transition, keep the immediate child (even if it is in ART),
191   //              and then go back to kEraseLibart.
192   // Regardless of the state, managed frames get annotated with their execution
193   // mode, based on the mapping.
194   enum class State { kInitial, kEraseLibart, kKeepNext };
195   State annotation_state = State::kInitial;
196 
197   std::vector<StringPool::Id> annotations_reversed;
198   for (auto it = cs_rows.rbegin(); it != cs_rows.rend(); ++it) {
199     auto cs_ref = it->ToRowReference(cs_table);
200     auto frame_ref = *f_table.FindById(cs_ref.frame_id());
201     auto map_ref = *m_table.FindById(frame_ref.mapping());
202 
203     // Keep immediate callee of a JNI trampoline, but keep tagging all
204     // successive libart frames as common.
205     if (annotation_state == State::kKeepNext) {
206       annotations_reversed.push_back(kNullStringId);
207       annotation_state = State::kEraseLibart;
208       continue;
209     }
210 
211     // Special-case "art_jni_trampoline" frames, keeping their immediate callee
212     // even if it is in libart, as it could be a native implementation of a
213     // managed method. Example for "java.lang.reflect.Method.Invoke":
214     //   art_jni_trampoline
215     //   art::Method_invoke(_JNIEnv*, _jobject*, _jobject*, _jobjectArray*)
216     //
217     // Simpleperf also relies on this frame name, so it should be fairly stable.
218     // TODO(rsavitski): consider detecting standard JNI upcall entrypoints -
219     // _JNIEnv::Call*. These are sometimes inlined into other DSOs, so erasing
220     // only the libart frames does not clean up all of the JNI-related frames.
221     StringId fname_id = frame_ref.name();
222     if (fname_id == art_jni_trampoline) {
223       annotations_reversed.push_back(common_frame);
224       annotation_state = State::kKeepNext;
225       continue;
226     }
227 
228     NullTermStringView map_view = context_->storage->GetString(map_ref.name());
229     MapType map_type = ClassifyMap(map_view);
230 
231     // Annotate managed frames.
232     if (map_type == MapType::kArtInterp ||  //
233         map_type == MapType::kArtJit ||     //
234         map_type == MapType::kArtAot) {
235       if (map_type == MapType::kArtInterp)
236         annotations_reversed.push_back(art_interp);
237       else if (map_type == MapType::kArtJit)
238         annotations_reversed.push_back(art_jit);
239       else if (map_type == MapType::kArtAot)
240         annotations_reversed.push_back(art_aot);
241 
242       // Now know to be in a managed callstack - erase subsequent ART frames.
243       if (annotation_state == State::kInitial)
244         annotation_state = State::kEraseLibart;
245       continue;
246     }
247 
248     // Mixed callstack, tag libart frames as uninteresting (common-frame).
249     // Special case a subset of interpreter implementation frames as
250     // "common-frame-interp" using frame name prefixes. Those functions are
251     // actually executed, whereas the managed "interp" frames are synthesised as
252     // their caller by the unwinding library (based on the dex_pc virtual
253     // register restored using the libart's DWARF info). The heuristic covers
254     // the "nterp" and "switch" interpreter implementations.
255     //
256     // Example:
257     //  <towards root>
258     //  android.view.WindowLayout.computeFrames [interp]
259     //  nterp_op_iget_object_slow_path [common-frame-interp]
260     //
261     // This annotation is helpful when trying to answer "what mode was the
262     // process in?" based on the leaf frame of the callstack. As we want to
263     // classify such cases as interpreted, even though the leaf frame is
264     // libart.so.
265     //
266     // For "switch" interpreter, we match any frame starting with
267     // "art::interpreter::" according to itanium mangling.
268     if (annotation_state == State::kEraseLibart &&
269         map_type == MapType::kNativeLibart) {
270       NullTermStringView fname = context_->storage->GetString(fname_id);
271       if (fname.StartsWith("nterp_") || fname.StartsWith("Nterp") ||
272           fname.StartsWith("ExecuteNterp") ||
273           fname.StartsWith("ExecuteSwitchImpl") ||
274           fname.StartsWith("_ZN3art11interpreter")) {
275         annotations_reversed.push_back(common_frame_interp);
276         continue;
277       }
278       annotations_reversed.push_back(common_frame);
279       continue;
280     }
281 
282     // default - no special annotation
283     annotations_reversed.push_back(kNullStringId);
284   }
285 
286   // Build the dynamic table.
287   PERFETTO_DCHECK(cs_rows.size() == annotations_reversed.size());
288   ColumnStorage<StringPool::Id> annotation_vals;
289   for (auto it = annotations_reversed.rbegin();
290        it != annotations_reversed.rend(); ++it) {
291     annotation_vals.Append(*it);
292   }
293 
294   // Hidden column - always the input, i.e. the callsite leaf.
295   ColumnStorage<uint32_t> start_id_vals;
296   for (uint32_t i = 0; i < cs_rows.size(); i++)
297     start_id_vals.Append(start_id.value);
298 
299   return std::unique_ptr<Table>(
300       tables::ExperimentalAnnotatedCallstackTable::SelectAndExtendParent(
301           cs_table, std::move(cs_rows), std::move(annotation_vals),
302           std::move(start_id_vals)));
303 }
304 
EstimateRowCount()305 uint32_t ExperimentalAnnotatedStack::EstimateRowCount() {
306   return 1;
307 }
308 
309 }  // namespace perfetto::trace_processor
310