xref: /aosp_15_r20/system/extras/simpleperf/cmd_dumprecord.cpp (revision 288bf5226967eb3dac5cce6c939ccc2a7f2b4fe5)
1 /*
2  * Copyright (C) 2015 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 <inttypes.h>
18 #include <stdint.h>
19 
20 #include <map>
21 #include <string>
22 #include <type_traits>
23 #include <vector>
24 
25 #include <android-base/logging.h>
26 #include <android-base/stringprintf.h>
27 #include <android-base/strings.h>
28 
29 #include "BranchListFile.h"
30 #include "ETMDecoder.h"
31 #include "command.h"
32 #include "dso.h"
33 #include "event_attr.h"
34 #include "event_type.h"
35 #include "perf_regs.h"
36 #include "record.h"
37 #include "record_file.h"
38 #include "tracing.h"
39 #include "utils.h"
40 
41 namespace simpleperf {
42 namespace {
43 
44 using namespace PerfFileFormat;
45 
46 struct SymbolInfo {
47   Dso* dso;
48   const Symbol* symbol;
49   uint64_t vaddr_in_file;
50 };
51 
52 using ExtractFieldFn = std::function<std::string(const TracingField&, const PerfSampleRawType&)>;
53 
54 struct EventInfo {
55   size_t tp_data_size = 0;
56   std::vector<TracingField> tp_fields;
57   std::vector<ExtractFieldFn> extract_field_functions;
58 };
59 
ExtractStringField(const TracingField & field,const PerfSampleRawType & data)60 std::string ExtractStringField(const TracingField& field, const PerfSampleRawType& data) {
61   std::string s;
62   // data points to a char [field.elem_count] array. It is not guaranteed to be ended
63   // with '\0'. So need to copy from data like strncpy.
64   size_t max_len = std::min(data.size - field.offset, field.elem_count);
65   const char* p = data.data + field.offset;
66   for (size_t i = 0; i < max_len && *p != '\0'; i++) {
67     s.push_back(*p++);
68   }
69   return s;
70 }
71 
ExtractDynamicStringField(const TracingField & field,const PerfSampleRawType & data)72 std::string ExtractDynamicStringField(const TracingField& field, const PerfSampleRawType& data) {
73   std::string s;
74   const char* p = data.data + field.offset;
75   if (field.elem_size != 4 || field.offset + field.elem_size > data.size) {
76     return s;
77   }
78   uint32_t location;
79   MoveFromBinaryFormat(location, p);
80   // Parse location: (max_len << 16) | off.
81   uint32_t offset = location & 0xffff;
82   uint32_t max_len = location >> 16;
83   if (offset + max_len <= data.size) {
84     p = data.data + offset;
85     for (size_t i = 0; i < max_len && *p != '\0'; i++) {
86       s.push_back(*p++);
87     }
88   }
89   return s;
90 }
91 
92 template <typename T, typename UT = typename std::make_unsigned<T>::type>
ExtractIntFieldFromPointer(const TracingField & field,const char * p)93 std::string ExtractIntFieldFromPointer(const TracingField& field, const char* p) {
94   static_assert(std::is_signed<T>::value);
95   T value;
96   MoveFromBinaryFormat(value, p);
97 
98   if (field.is_signed) {
99     return android::base::StringPrintf("%" PRId64, static_cast<int64_t>(value));
100   }
101   return android::base::StringPrintf("0x%" PRIx64, static_cast<uint64_t>(static_cast<UT>(value)));
102 }
103 
104 template <typename T>
ExtractIntField(const TracingField & field,const PerfSampleRawType & data)105 std::string ExtractIntField(const TracingField& field, const PerfSampleRawType& data) {
106   if (field.offset + sizeof(T) > data.size) {
107     return "";
108   }
109   return ExtractIntFieldFromPointer<T>(field, data.data + field.offset);
110 }
111 
112 template <typename T>
ExtractIntArrayField(const TracingField & field,const PerfSampleRawType & data)113 std::string ExtractIntArrayField(const TracingField& field, const PerfSampleRawType& data) {
114   if (field.offset + field.elem_size * field.elem_count > data.size) {
115     return "";
116   }
117   std::string s;
118   const char* p = data.data + field.offset;
119   for (size_t i = 0; i < field.elem_count; i++) {
120     if (i != 0) {
121       s.push_back(' ');
122     }
123     ExtractIntFieldFromPointer<T>(field, p);
124     p += field.elem_size;
125   }
126   return s;
127 }
128 
ExtractUnknownField(const TracingField & field,const PerfSampleRawType & data)129 std::string ExtractUnknownField(const TracingField& field, const PerfSampleRawType& data) {
130   size_t total = field.elem_size * field.elem_count;
131   if (field.offset + total > data.size) {
132     return "";
133   }
134   uint32_t value;
135   std::string s;
136   const char* p = data.data + field.offset;
137   for (size_t i = 0; i + sizeof(value) <= total; i += sizeof(value)) {
138     if (i != 0) {
139       s.push_back(' ');
140     }
141     MoveFromBinaryFormat(value, p);
142     s += android::base::StringPrintf("0x%08x", value);
143   }
144   return s;
145 }
146 
GetExtractFieldFunction(const TracingField & field)147 ExtractFieldFn GetExtractFieldFunction(const TracingField& field) {
148   if (field.is_dynamic) {
149     return ExtractDynamicStringField;
150   }
151   if (field.elem_count > 1 && field.elem_size == 1) {
152     // Probably the field is a string.
153     // Don't use field.is_signed, which has different values on x86 and arm.
154     return ExtractStringField;
155   }
156   if (field.elem_count == 1) {
157     switch (field.elem_size) {
158       case 1:
159         return ExtractIntField<int8_t>;
160       case 2:
161         return ExtractIntField<int16_t>;
162       case 4:
163         return ExtractIntField<int32_t>;
164       case 8:
165         return ExtractIntField<int64_t>;
166     }
167   } else {
168     switch (field.elem_size) {
169       case 1:
170         return ExtractIntArrayField<int8_t>;
171       case 2:
172         return ExtractIntArrayField<int16_t>;
173       case 4:
174         return ExtractIntArrayField<int32_t>;
175       case 8:
176         return ExtractIntArrayField<int64_t>;
177     }
178   }
179   return ExtractUnknownField;
180 }
181 
182 class ETMThreadTreeForDumpCmd : public ETMThreadTree {
183  public:
ETMThreadTreeForDumpCmd(ThreadTree & thread_tree)184   ETMThreadTreeForDumpCmd(ThreadTree& thread_tree) : thread_tree_(thread_tree) {}
185 
DisableThreadExitRecords()186   void DisableThreadExitRecords() override { thread_tree_.DisableThreadExitRecords(); }
FindThread(int tid)187   const ThreadEntry* FindThread(int tid) override { return thread_tree_.FindThread(tid); }
GetKernelMaps()188   const MapSet& GetKernelMaps() override { return thread_tree_.GetKernelMaps(); }
189 
190  private:
191   ThreadTree& thread_tree_;
192 };
193 
194 class DumpRecordCommand : public Command {
195  public:
DumpRecordCommand()196   DumpRecordCommand()
197       : Command("dump", "dump perf record file",
198                 // clang-format off
199 "Usage: simpleperf dumprecord [options] [perf_record_file]\n"
200 "    Dump different parts of a perf record file. Default file is perf.data.\n"
201 "--dump-etm type1,type2,...   Dump etm data. A type is one of raw, packet and element.\n"
202 "--dump-feature feature1,feature2,...  Only dump selected feature sections.\n"
203 "-i <record_file>             Record file to dump. Default is perf.data.\n"
204 "--symdir <dir>               Look for binaries in a directory recursively.\n"
205                 // clang-format on
206         ) {}
207 
208   bool Run(const std::vector<std::string>& args);
209 
210  private:
211   bool ParseOptions(const std::vector<std::string>& args);
212   void DumpFileHeader();
213   void DumpAttrSection();
214   bool DumpDataSection();
215   bool ProcessRecord(Record* r);
216   void ProcessSampleRecord(const SampleRecord& r);
217   void ProcessCallChainRecord(const CallChainRecord& r);
218   SymbolInfo GetSymbolInfo(uint32_t pid, uint32_t tid, uint64_t ip,
219                            std::optional<bool> in_kernel = std::nullopt);
220   bool ProcessTracingData(const TracingDataRecord& r);
221   bool DumpAuxData(const AuxRecord& aux);
222   bool DumpFeatureSection();
223 
224   // options
225   std::string record_filename_ = "perf.data";
226   ETMDumpOption etm_dump_option_;
227   std::vector<std::string> dump_features_;
228 
229   std::unique_ptr<RecordFileReader> record_file_reader_;
230   std::unique_ptr<ETMDecoder> etm_decoder_;
231   std::unique_ptr<ETMThreadTree> etm_thread_tree_;
232   ThreadTree thread_tree_;
233 
234   std::vector<EventInfo> events_;
235 };
236 
Run(const std::vector<std::string> & args)237 bool DumpRecordCommand::Run(const std::vector<std::string>& args) {
238   if (!ParseOptions(args)) {
239     return false;
240   }
241   record_file_reader_ = RecordFileReader::CreateInstance(record_filename_);
242   if (record_file_reader_ == nullptr) {
243     return false;
244   }
245 
246   if (!dump_features_.empty()) {
247     return DumpFeatureSection();
248   }
249 
250   DumpFileHeader();
251   DumpAttrSection();
252   if (!DumpDataSection()) {
253     return false;
254   }
255   return DumpFeatureSection();
256 }
257 
ParseOptions(const std::vector<std::string> & args)258 bool DumpRecordCommand::ParseOptions(const std::vector<std::string>& args) {
259   const OptionFormatMap option_formats = {
260       {"--dump-etm", {OptionValueType::STRING, OptionType::SINGLE}},
261       {"--dump-feature", {OptionValueType::STRING, OptionType::MULTIPLE}},
262       {"-i", {OptionValueType::STRING, OptionType::SINGLE}},
263       {"--symdir", {OptionValueType::STRING, OptionType::MULTIPLE}},
264   };
265   OptionValueMap options;
266   std::vector<std::pair<OptionName, OptionValue>> ordered_options;
267   std::vector<std::string> non_option_args;
268   if (!PreprocessOptions(args, option_formats, &options, &ordered_options, &non_option_args)) {
269     return false;
270   }
271   if (auto value = options.PullValue("--dump-etm"); value) {
272     if (!ParseEtmDumpOption(value->str_value, &etm_dump_option_)) {
273       return false;
274     }
275   }
276   dump_features_ = options.PullStringValues("--dump-feature");
277   options.PullStringValue("-i", &record_filename_);
278   for (const OptionValue& value : options.PullValues("--symdir")) {
279     if (!Dso::AddSymbolDir(value.str_value)) {
280       return false;
281     }
282   }
283   CHECK(options.values.empty());
284   if (non_option_args.size() > 1) {
285     LOG(ERROR) << "too many record files";
286     return false;
287   }
288   if (non_option_args.size() == 1) {
289     record_filename_ = non_option_args[0];
290   }
291   return true;
292 }
293 
GetFeatureNameOrUnknown(int feature)294 static const std::string GetFeatureNameOrUnknown(int feature) {
295   std::string name = GetFeatureName(feature);
296   return name.empty() ? android::base::StringPrintf("unknown_feature(%d)", feature) : name;
297 }
298 
DumpFileHeader()299 void DumpRecordCommand::DumpFileHeader() {
300   const FileHeader& header = record_file_reader_->FileHeader();
301   printf("magic: ");
302   for (size_t i = 0; i < 8; ++i) {
303     printf("%c", header.magic[i]);
304   }
305   printf("\n");
306   printf("header_size: %" PRId64 "\n", header.header_size);
307   if (header.header_size != sizeof(header)) {
308     PLOG(WARNING) << "record file header size " << header.header_size
309                   << "doesn't match expected header size " << sizeof(header);
310   }
311   printf("attr_size: %" PRId64 "\n", header.attr_size);
312   printf("attrs[file section]: offset %" PRId64 ", size %" PRId64 "\n", header.attrs.offset,
313          header.attrs.size);
314   printf("data[file section]: offset %" PRId64 ", size %" PRId64 "\n", header.data.offset,
315          header.data.size);
316   printf("event_types[file section]: offset %" PRId64 ", size %" PRId64 "\n",
317          header.event_types.offset, header.event_types.size);
318 
319   std::vector<int> features;
320   for (size_t i = 0; i < FEAT_MAX_NUM; ++i) {
321     size_t j = i / 8;
322     size_t k = i % 8;
323     if ((header.features[j] & (1 << k)) != 0) {
324       features.push_back(i);
325     }
326   }
327   for (auto& feature : features) {
328     printf("feature: %s\n", GetFeatureNameOrUnknown(feature).c_str());
329   }
330 }
331 
DumpAttrSection()332 void DumpRecordCommand::DumpAttrSection() {
333   const EventAttrIds& attrs = record_file_reader_->AttrSection();
334   for (size_t i = 0; i < attrs.size(); ++i) {
335     const auto& attr = attrs[i];
336     printf("attr %zu:\n", i + 1);
337     DumpPerfEventAttr(attr.attr, 1);
338     if (!attr.ids.empty()) {
339       printf("  ids:");
340       for (const auto& id : attr.ids) {
341         printf(" %" PRId64, id);
342       }
343       printf("\n");
344     }
345   }
346 }
347 
DumpDataSection()348 bool DumpRecordCommand::DumpDataSection() {
349   thread_tree_.ShowIpForUnknownSymbol();
350   if (!record_file_reader_->LoadBuildIdAndFileFeatures(thread_tree_)) {
351     return false;
352   }
353 
354   auto record_callback = [&](std::unique_ptr<Record> r) { return ProcessRecord(r.get()); };
355   return record_file_reader_->ReadDataSection(record_callback);
356 }
357 
ProcessRecord(Record * r)358 bool DumpRecordCommand::ProcessRecord(Record* r) {
359   r->Dump();
360   thread_tree_.Update(*r);
361 
362   bool res = true;
363   switch (r->type()) {
364     case PERF_RECORD_SAMPLE:
365       ProcessSampleRecord(*static_cast<SampleRecord*>(r));
366       break;
367     case SIMPLE_PERF_RECORD_CALLCHAIN:
368       ProcessCallChainRecord(*static_cast<CallChainRecord*>(r));
369       break;
370     case PERF_RECORD_AUXTRACE_INFO: {
371       etm_thread_tree_.reset(new ETMThreadTreeForDumpCmd(thread_tree_));
372       etm_decoder_ = ETMDecoder::Create(*static_cast<AuxTraceInfoRecord*>(r), *etm_thread_tree_);
373       if (etm_decoder_) {
374         etm_decoder_->EnableDump(etm_dump_option_);
375       } else {
376         res = false;
377       }
378       break;
379     }
380     case PERF_RECORD_AUX: {
381       res = DumpAuxData(*static_cast<AuxRecord*>(r));
382       break;
383     }
384     case PERF_RECORD_TRACING_DATA:
385     case SIMPLE_PERF_RECORD_TRACING_DATA: {
386       res = ProcessTracingData(*static_cast<TracingDataRecord*>(r));
387       break;
388     }
389   }
390   return res;
391 }
392 
ProcessSampleRecord(const SampleRecord & sr)393 void DumpRecordCommand::ProcessSampleRecord(const SampleRecord& sr) {
394   bool in_kernel = sr.InKernel();
395   if (sr.sample_type & PERF_SAMPLE_CALLCHAIN) {
396     PrintIndented(1, "callchain:\n");
397     for (size_t i = 0; i < sr.callchain_data.ip_nr; ++i) {
398       if (sr.callchain_data.ips[i] >= PERF_CONTEXT_MAX) {
399         if (sr.callchain_data.ips[i] == PERF_CONTEXT_USER) {
400           in_kernel = false;
401         }
402         continue;
403       }
404       SymbolInfo s =
405           GetSymbolInfo(sr.tid_data.pid, sr.tid_data.tid, sr.callchain_data.ips[i], in_kernel);
406       PrintIndented(2, "%s (%s[+%" PRIx64 "])\n", s.symbol->DemangledName(), s.dso->Path().c_str(),
407                     s.vaddr_in_file);
408     }
409   }
410   if (sr.sample_type & PERF_SAMPLE_BRANCH_STACK) {
411     PrintIndented(1, "branch_stack:\n");
412     for (size_t i = 0; i < sr.branch_stack_data.stack_nr; ++i) {
413       uint64_t from_ip = sr.branch_stack_data.stack[i].from;
414       uint64_t to_ip = sr.branch_stack_data.stack[i].to;
415       SymbolInfo from_symbol = GetSymbolInfo(sr.tid_data.pid, sr.tid_data.tid, from_ip);
416       SymbolInfo to_symbol = GetSymbolInfo(sr.tid_data.pid, sr.tid_data.tid, to_ip);
417       PrintIndented(2, "%s (%s[+%" PRIx64 "]) -> %s (%s[+%" PRIx64 "])\n",
418                     from_symbol.symbol->DemangledName(), from_symbol.dso->Path().c_str(),
419                     from_symbol.vaddr_in_file, to_symbol.symbol->DemangledName(),
420                     to_symbol.dso->Path().c_str(), to_symbol.vaddr_in_file);
421     }
422   }
423   // Dump tracepoint fields.
424   if (!events_.empty()) {
425     size_t attr_index = record_file_reader_->GetAttrIndexOfRecord(&sr);
426     auto& event = events_[attr_index];
427     if (event.tp_data_size > 0 && sr.raw_data.size >= event.tp_data_size) {
428       PrintIndented(1, "tracepoint fields:\n");
429       for (size_t i = 0; i < event.tp_fields.size(); i++) {
430         auto& field = event.tp_fields[i];
431         std::string s = event.extract_field_functions[i](field, sr.raw_data);
432         PrintIndented(2, "%s: %s\n", field.name.c_str(), s.c_str());
433       }
434     }
435   }
436 }
437 
ProcessCallChainRecord(const CallChainRecord & cr)438 void DumpRecordCommand::ProcessCallChainRecord(const CallChainRecord& cr) {
439   PrintIndented(1, "callchain:\n");
440   for (size_t i = 0; i < cr.ip_nr; ++i) {
441     SymbolInfo s = GetSymbolInfo(cr.pid, cr.tid, cr.ips[i], false);
442     PrintIndented(2, "%s (%s[+%" PRIx64 "])\n", s.symbol->DemangledName(), s.dso->Path().c_str(),
443                   s.vaddr_in_file);
444   }
445 }
446 
GetSymbolInfo(uint32_t pid,uint32_t tid,uint64_t ip,std::optional<bool> in_kernel)447 SymbolInfo DumpRecordCommand::GetSymbolInfo(uint32_t pid, uint32_t tid, uint64_t ip,
448                                             std::optional<bool> in_kernel) {
449   ThreadEntry* thread = thread_tree_.FindThreadOrNew(pid, tid);
450   const MapEntry* map;
451   if (in_kernel.has_value()) {
452     map = thread_tree_.FindMap(thread, ip, in_kernel.value());
453   } else {
454     map = thread_tree_.FindMap(thread, ip);
455   }
456   SymbolInfo info;
457   info.symbol = thread_tree_.FindSymbol(map, ip, &info.vaddr_in_file, &info.dso);
458   return info;
459 }
460 
DumpAuxData(const AuxRecord & aux)461 bool DumpRecordCommand::DumpAuxData(const AuxRecord& aux) {
462   if (aux.data->aux_size > SIZE_MAX) {
463     LOG(ERROR) << "invalid aux size";
464     return false;
465   }
466   size_t size = aux.data->aux_size;
467   if (size > 0) {
468     std::vector<uint8_t> data;
469     bool error = false;
470     if (!record_file_reader_->ReadAuxData(aux.Cpu(), aux.data->aux_offset, size, data, error)) {
471       return !error;
472     }
473     if (!etm_decoder_) {
474       LOG(ERROR) << "ETMDecoder isn't created";
475       return false;
476     }
477     return etm_decoder_->ProcessData(data.data(), size, !aux.Unformatted(), aux.Cpu());
478   }
479   return true;
480 }
481 
ProcessTracingData(const TracingDataRecord & r)482 bool DumpRecordCommand::ProcessTracingData(const TracingDataRecord& r) {
483   auto tracing = Tracing::Create(std::vector<char>(r.data, r.data + r.data_size));
484   if (!tracing) {
485     return false;
486   }
487   const EventAttrIds& attrs = record_file_reader_->AttrSection();
488   events_.resize(attrs.size());
489   for (size_t i = 0; i < attrs.size(); i++) {
490     auto& attr = attrs[i].attr;
491     auto& event = events_[i];
492 
493     if (attr.type != PERF_TYPE_TRACEPOINT) {
494       continue;
495     }
496     std::optional<TracingFormat> format = tracing->GetTracingFormatHavingId(attr.config);
497     if (!format.has_value()) {
498       LOG(ERROR) << "failed to get tracing format";
499       return false;
500     }
501     event.tp_fields = format.value().fields;
502     // Decide dump function for each field.
503     for (size_t j = 0; j < event.tp_fields.size(); j++) {
504       auto& field = event.tp_fields[j];
505       event.extract_field_functions.push_back(GetExtractFieldFunction(field));
506       event.tp_data_size += field.elem_count * field.elem_size;
507     }
508   }
509   return true;
510 }
511 
DumpFeatureSection()512 bool DumpRecordCommand::DumpFeatureSection() {
513   std::map<int, SectionDesc> section_map = record_file_reader_->FeatureSectionDescriptors();
514   for (const auto& pair : section_map) {
515     int feature = pair.first;
516     const auto& section = pair.second;
517     std::string feature_name = GetFeatureNameOrUnknown(feature);
518     if (!dump_features_.empty() &&
519         std::count(dump_features_.begin(), dump_features_.end(), feature_name) == 0) {
520       continue;
521     }
522     printf("feature section for %s: offset %" PRId64 ", size %" PRId64 "\n", feature_name.c_str(),
523            section.offset, section.size);
524     if (feature == FEAT_BUILD_ID) {
525       std::vector<BuildIdRecord> records = record_file_reader_->ReadBuildIdFeature();
526       for (auto& r : records) {
527         r.Dump(1);
528       }
529     } else if (feature == FEAT_OSRELEASE) {
530       std::string s = record_file_reader_->ReadFeatureString(feature);
531       PrintIndented(1, "osrelease: %s\n", s.c_str());
532     } else if (feature == FEAT_ARCH) {
533       std::string s = record_file_reader_->ReadFeatureString(feature);
534       PrintIndented(1, "arch: %s\n", s.c_str());
535     } else if (feature == FEAT_CMDLINE) {
536       std::vector<std::string> cmdline = record_file_reader_->ReadCmdlineFeature();
537       PrintIndented(1, "cmdline: %s\n", android::base::Join(cmdline, ' ').c_str());
538     } else if (feature == FEAT_FILE || feature == FEAT_FILE2) {
539       FileFeature file;
540       uint64_t read_pos = 0;
541       bool error = false;
542       PrintIndented(1, "file:\n");
543       while (record_file_reader_->ReadFileFeature(read_pos, file, error)) {
544         PrintIndented(2, "file_path %s\n", file.path.c_str());
545         PrintIndented(2, "file_type %s\n", DsoTypeToString(file.type));
546         PrintIndented(2, "min_vaddr 0x%" PRIx64 "\n", file.min_vaddr);
547         PrintIndented(2, "file_offset_of_min_vaddr 0x%" PRIx64 "\n", file.file_offset_of_min_vaddr);
548         PrintIndented(2, "symbols:\n");
549         for (const auto& symbol : file.symbols) {
550           PrintIndented(3, "%s [0x%" PRIx64 "-0x%" PRIx64 "]\n", symbol.DemangledName(),
551                         symbol.addr, symbol.addr + symbol.len);
552         }
553         if (file.type == DSO_DEX_FILE) {
554           PrintIndented(2, "dex_file_offsets:\n");
555           for (uint64_t offset : file.dex_file_offsets) {
556             PrintIndented(3, "0x%" PRIx64 "\n", offset);
557           }
558         }
559       }
560       if (error) {
561         return false;
562       }
563     } else if (feature == FEAT_META_INFO) {
564       PrintIndented(1, "meta_info:\n");
565       for (auto& pair : record_file_reader_->GetMetaInfoFeature()) {
566         PrintIndented(2, "%s = %s\n", pair.first.c_str(), pair.second.c_str());
567       }
568     } else if (feature == FEAT_AUXTRACE) {
569       PrintIndented(1, "file_offsets_of_auxtrace_records:\n");
570       for (auto offset : record_file_reader_->ReadAuxTraceFeature()) {
571         PrintIndented(2, "%" PRIu64 "\n", offset);
572       }
573     } else if (feature == FEAT_DEBUG_UNWIND) {
574       PrintIndented(1, "debug_unwind:\n");
575       if (auto opt_debug_unwind = record_file_reader_->ReadDebugUnwindFeature(); opt_debug_unwind) {
576         for (const DebugUnwindFile& file : opt_debug_unwind.value()) {
577           PrintIndented(2, "path: %s\n", file.path.c_str());
578           PrintIndented(2, "size: %" PRIu64 "\n", file.size);
579         }
580       }
581     } else if (feature == FEAT_ETM_BRANCH_LIST) {
582       std::string data;
583       if (!record_file_reader_->ReadFeatureSection(FEAT_ETM_BRANCH_LIST, &data)) {
584         return false;
585       }
586       ETMBinaryMap binary_map;
587       if (!StringToETMBinaryMap(data, binary_map)) {
588         return false;
589       }
590       PrintIndented(1, "etm_branch_list:\n");
591       for (const auto& [key, binary] : binary_map) {
592         PrintIndented(2, "path: %s\n", key.path.c_str());
593         PrintIndented(2, "build_id: %s\n", key.build_id.ToString().c_str());
594         PrintIndented(2, "binary_type: %s\n", DsoTypeToString(binary.dso_type));
595         if (binary.dso_type == DSO_KERNEL) {
596           PrintIndented(2, "kernel_start_addr: 0x%" PRIx64 "\n", key.kernel_start_addr);
597         }
598         for (const auto& [addr, branches] : binary.GetOrderedBranchMap()) {
599           PrintIndented(3, "addr: 0x%" PRIx64 "\n", addr);
600           for (const auto& [branch, count] : branches) {
601             std::string s = "0b";
602             for (auto it = branch.rbegin(); it != branch.rend(); ++it) {
603               s.push_back(*it ? '1' : '0');
604             }
605             PrintIndented(3, "branch: %s\n", s.c_str());
606             PrintIndented(3, "count: %" PRIu64 "\n", count);
607           }
608         }
609       }
610     } else if (feature == FEAT_INIT_MAP) {
611       PrintIndented(1, "init_map:\n");
612       auto callback = [&](std::unique_ptr<Record> r) { return ProcessRecord(r.get()); };
613       if (!record_file_reader_->ReadInitMapFeature(callback)) {
614         return false;
615       }
616     }
617   }
618   return true;
619 }
620 
621 }  // namespace
622 
RegisterDumpRecordCommand()623 void RegisterDumpRecordCommand() {
624   RegisterCommand("dump", [] { return std::unique_ptr<Command>(new DumpRecordCommand); });
625 }
626 
627 }  // namespace simpleperf
628