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 "-i <record_file> Record file to dump. Default is perf.data.\n"
203 "--symdir <dir> Look for binaries in a directory recursively.\n"
204 // clang-format on
205 ) {}
206
207 bool Run(const std::vector<std::string>& args);
208
209 private:
210 bool ParseOptions(const std::vector<std::string>& args);
211 void DumpFileHeader();
212 void DumpAttrSection();
213 bool DumpDataSection();
214 bool ProcessRecord(Record* r);
215 void ProcessSampleRecord(const SampleRecord& r);
216 void ProcessCallChainRecord(const CallChainRecord& r);
217 SymbolInfo GetSymbolInfo(uint32_t pid, uint32_t tid, uint64_t ip,
218 std::optional<bool> in_kernel = std::nullopt);
219 bool ProcessTracingData(const TracingDataRecord& r);
220 bool DumpAuxData(const AuxRecord& aux);
221 bool DumpFeatureSection();
222
223 // options
224 std::string record_filename_ = "perf.data";
225 ETMDumpOption etm_dump_option_;
226
227 std::unique_ptr<RecordFileReader> record_file_reader_;
228 std::unique_ptr<ETMDecoder> etm_decoder_;
229 std::unique_ptr<ETMThreadTree> etm_thread_tree_;
230 ThreadTree thread_tree_;
231
232 std::vector<EventInfo> events_;
233 };
234
Run(const std::vector<std::string> & args)235 bool DumpRecordCommand::Run(const std::vector<std::string>& args) {
236 if (!ParseOptions(args)) {
237 return false;
238 }
239 record_file_reader_ = RecordFileReader::CreateInstance(record_filename_);
240 if (record_file_reader_ == nullptr) {
241 return false;
242 }
243 DumpFileHeader();
244 DumpAttrSection();
245 if (!DumpDataSection()) {
246 return false;
247 }
248 return DumpFeatureSection();
249 }
250
ParseOptions(const std::vector<std::string> & args)251 bool DumpRecordCommand::ParseOptions(const std::vector<std::string>& args) {
252 const OptionFormatMap option_formats = {
253 {"--dump-etm", {OptionValueType::STRING, OptionType::SINGLE}},
254 {"-i", {OptionValueType::STRING, OptionType::SINGLE}},
255 {"--symdir", {OptionValueType::STRING, OptionType::MULTIPLE}},
256 };
257 OptionValueMap options;
258 std::vector<std::pair<OptionName, OptionValue>> ordered_options;
259 std::vector<std::string> non_option_args;
260 if (!PreprocessOptions(args, option_formats, &options, &ordered_options, &non_option_args)) {
261 return false;
262 }
263 if (auto value = options.PullValue("--dump-etm"); value) {
264 if (!ParseEtmDumpOption(*value->str_value, &etm_dump_option_)) {
265 return false;
266 }
267 }
268 options.PullStringValue("-i", &record_filename_);
269 for (const OptionValue& value : options.PullValues("--symdir")) {
270 if (!Dso::AddSymbolDir(*value.str_value)) {
271 return false;
272 }
273 }
274 CHECK(options.values.empty());
275 if (non_option_args.size() > 1) {
276 LOG(ERROR) << "too many record files";
277 return false;
278 }
279 if (non_option_args.size() == 1) {
280 record_filename_ = non_option_args[0];
281 }
282 return true;
283 }
284
GetFeatureNameOrUnknown(int feature)285 static const std::string GetFeatureNameOrUnknown(int feature) {
286 std::string name = GetFeatureName(feature);
287 return name.empty() ? android::base::StringPrintf("unknown_feature(%d)", feature) : name;
288 }
289
DumpFileHeader()290 void DumpRecordCommand::DumpFileHeader() {
291 const FileHeader& header = record_file_reader_->FileHeader();
292 printf("magic: ");
293 for (size_t i = 0; i < 8; ++i) {
294 printf("%c", header.magic[i]);
295 }
296 printf("\n");
297 printf("header_size: %" PRId64 "\n", header.header_size);
298 if (header.header_size != sizeof(header)) {
299 PLOG(WARNING) << "record file header size " << header.header_size
300 << "doesn't match expected header size " << sizeof(header);
301 }
302 printf("attr_size: %" PRId64 "\n", header.attr_size);
303 printf("attrs[file section]: offset %" PRId64 ", size %" PRId64 "\n", header.attrs.offset,
304 header.attrs.size);
305 printf("data[file section]: offset %" PRId64 ", size %" PRId64 "\n", header.data.offset,
306 header.data.size);
307 printf("event_types[file section]: offset %" PRId64 ", size %" PRId64 "\n",
308 header.event_types.offset, header.event_types.size);
309
310 std::vector<int> features;
311 for (size_t i = 0; i < FEAT_MAX_NUM; ++i) {
312 size_t j = i / 8;
313 size_t k = i % 8;
314 if ((header.features[j] & (1 << k)) != 0) {
315 features.push_back(i);
316 }
317 }
318 for (auto& feature : features) {
319 printf("feature: %s\n", GetFeatureNameOrUnknown(feature).c_str());
320 }
321 }
322
DumpAttrSection()323 void DumpRecordCommand::DumpAttrSection() {
324 const EventAttrIds& attrs = record_file_reader_->AttrSection();
325 for (size_t i = 0; i < attrs.size(); ++i) {
326 const auto& attr = attrs[i];
327 printf("attr %zu:\n", i + 1);
328 DumpPerfEventAttr(attr.attr, 1);
329 if (!attr.ids.empty()) {
330 printf(" ids:");
331 for (const auto& id : attr.ids) {
332 printf(" %" PRId64, id);
333 }
334 printf("\n");
335 }
336 }
337 }
338
DumpDataSection()339 bool DumpRecordCommand::DumpDataSection() {
340 thread_tree_.ShowIpForUnknownSymbol();
341 if (!record_file_reader_->LoadBuildIdAndFileFeatures(thread_tree_)) {
342 return false;
343 }
344
345 auto record_callback = [&](std::unique_ptr<Record> r) { return ProcessRecord(r.get()); };
346 return record_file_reader_->ReadDataSection(record_callback);
347 }
348
ProcessRecord(Record * r)349 bool DumpRecordCommand::ProcessRecord(Record* r) {
350 r->Dump();
351 thread_tree_.Update(*r);
352
353 bool res = true;
354 switch (r->type()) {
355 case PERF_RECORD_SAMPLE:
356 ProcessSampleRecord(*static_cast<SampleRecord*>(r));
357 break;
358 case SIMPLE_PERF_RECORD_CALLCHAIN:
359 ProcessCallChainRecord(*static_cast<CallChainRecord*>(r));
360 break;
361 case PERF_RECORD_AUXTRACE_INFO: {
362 etm_thread_tree_.reset(new ETMThreadTreeForDumpCmd(thread_tree_));
363 etm_decoder_ = ETMDecoder::Create(*static_cast<AuxTraceInfoRecord*>(r), *etm_thread_tree_);
364 if (etm_decoder_) {
365 etm_decoder_->EnableDump(etm_dump_option_);
366 } else {
367 res = false;
368 }
369 break;
370 }
371 case PERF_RECORD_AUX: {
372 res = DumpAuxData(*static_cast<AuxRecord*>(r));
373 break;
374 }
375 case PERF_RECORD_TRACING_DATA:
376 case SIMPLE_PERF_RECORD_TRACING_DATA: {
377 res = ProcessTracingData(*static_cast<TracingDataRecord*>(r));
378 break;
379 }
380 }
381 return res;
382 }
383
ProcessSampleRecord(const SampleRecord & sr)384 void DumpRecordCommand::ProcessSampleRecord(const SampleRecord& sr) {
385 bool in_kernel = sr.InKernel();
386 if (sr.sample_type & PERF_SAMPLE_CALLCHAIN) {
387 PrintIndented(1, "callchain:\n");
388 for (size_t i = 0; i < sr.callchain_data.ip_nr; ++i) {
389 if (sr.callchain_data.ips[i] >= PERF_CONTEXT_MAX) {
390 if (sr.callchain_data.ips[i] == PERF_CONTEXT_USER) {
391 in_kernel = false;
392 }
393 continue;
394 }
395 SymbolInfo s =
396 GetSymbolInfo(sr.tid_data.pid, sr.tid_data.tid, sr.callchain_data.ips[i], in_kernel);
397 PrintIndented(2, "%s (%s[+%" PRIx64 "])\n", s.symbol->DemangledName(), s.dso->Path().c_str(),
398 s.vaddr_in_file);
399 }
400 }
401 if (sr.sample_type & PERF_SAMPLE_BRANCH_STACK) {
402 PrintIndented(1, "branch_stack:\n");
403 for (size_t i = 0; i < sr.branch_stack_data.stack_nr; ++i) {
404 uint64_t from_ip = sr.branch_stack_data.stack[i].from;
405 uint64_t to_ip = sr.branch_stack_data.stack[i].to;
406 SymbolInfo from_symbol = GetSymbolInfo(sr.tid_data.pid, sr.tid_data.tid, from_ip);
407 SymbolInfo to_symbol = GetSymbolInfo(sr.tid_data.pid, sr.tid_data.tid, to_ip);
408 PrintIndented(2, "%s (%s[+%" PRIx64 "]) -> %s (%s[+%" PRIx64 "])\n",
409 from_symbol.symbol->DemangledName(), from_symbol.dso->Path().c_str(),
410 from_symbol.vaddr_in_file, to_symbol.symbol->DemangledName(),
411 to_symbol.dso->Path().c_str(), to_symbol.vaddr_in_file);
412 }
413 }
414 // Dump tracepoint fields.
415 if (!events_.empty()) {
416 size_t attr_index = record_file_reader_->GetAttrIndexOfRecord(&sr);
417 auto& event = events_[attr_index];
418 if (event.tp_data_size > 0 && sr.raw_data.size >= event.tp_data_size) {
419 PrintIndented(1, "tracepoint fields:\n");
420 for (size_t i = 0; i < event.tp_fields.size(); i++) {
421 auto& field = event.tp_fields[i];
422 std::string s = event.extract_field_functions[i](field, sr.raw_data);
423 PrintIndented(2, "%s: %s\n", field.name.c_str(), s.c_str());
424 }
425 }
426 }
427 }
428
ProcessCallChainRecord(const CallChainRecord & cr)429 void DumpRecordCommand::ProcessCallChainRecord(const CallChainRecord& cr) {
430 PrintIndented(1, "callchain:\n");
431 for (size_t i = 0; i < cr.ip_nr; ++i) {
432 SymbolInfo s = GetSymbolInfo(cr.pid, cr.tid, cr.ips[i], false);
433 PrintIndented(2, "%s (%s[+%" PRIx64 "])\n", s.symbol->DemangledName(), s.dso->Path().c_str(),
434 s.vaddr_in_file);
435 }
436 }
437
GetSymbolInfo(uint32_t pid,uint32_t tid,uint64_t ip,std::optional<bool> in_kernel)438 SymbolInfo DumpRecordCommand::GetSymbolInfo(uint32_t pid, uint32_t tid, uint64_t ip,
439 std::optional<bool> in_kernel) {
440 ThreadEntry* thread = thread_tree_.FindThreadOrNew(pid, tid);
441 const MapEntry* map;
442 if (in_kernel.has_value()) {
443 map = thread_tree_.FindMap(thread, ip, in_kernel.value());
444 } else {
445 map = thread_tree_.FindMap(thread, ip);
446 }
447 SymbolInfo info;
448 info.symbol = thread_tree_.FindSymbol(map, ip, &info.vaddr_in_file, &info.dso);
449 return info;
450 }
451
DumpAuxData(const AuxRecord & aux)452 bool DumpRecordCommand::DumpAuxData(const AuxRecord& aux) {
453 if (aux.data->aux_size > SIZE_MAX) {
454 LOG(ERROR) << "invalid aux size";
455 return false;
456 }
457 size_t size = aux.data->aux_size;
458 if (size > 0) {
459 std::vector<uint8_t> data;
460 bool error = false;
461 if (!record_file_reader_->ReadAuxData(aux.Cpu(), aux.data->aux_offset, size, data, error)) {
462 return !error;
463 }
464 if (!etm_decoder_) {
465 LOG(ERROR) << "ETMDecoder isn't created";
466 return false;
467 }
468 return etm_decoder_->ProcessData(data.data(), size, !aux.Unformatted(), aux.Cpu());
469 }
470 return true;
471 }
472
ProcessTracingData(const TracingDataRecord & r)473 bool DumpRecordCommand::ProcessTracingData(const TracingDataRecord& r) {
474 auto tracing = Tracing::Create(std::vector<char>(r.data, r.data + r.data_size));
475 if (!tracing) {
476 return false;
477 }
478 const EventAttrIds& attrs = record_file_reader_->AttrSection();
479 events_.resize(attrs.size());
480 for (size_t i = 0; i < attrs.size(); i++) {
481 auto& attr = attrs[i].attr;
482 auto& event = events_[i];
483
484 if (attr.type != PERF_TYPE_TRACEPOINT) {
485 continue;
486 }
487 std::optional<TracingFormat> format = tracing->GetTracingFormatHavingId(attr.config);
488 if (!format.has_value()) {
489 LOG(ERROR) << "failed to get tracing format";
490 return false;
491 }
492 event.tp_fields = format.value().fields;
493 // Decide dump function for each field.
494 for (size_t j = 0; j < event.tp_fields.size(); j++) {
495 auto& field = event.tp_fields[j];
496 event.extract_field_functions.push_back(GetExtractFieldFunction(field));
497 event.tp_data_size += field.elem_count * field.elem_size;
498 }
499 }
500 return true;
501 }
502
DumpFeatureSection()503 bool DumpRecordCommand::DumpFeatureSection() {
504 std::map<int, SectionDesc> section_map = record_file_reader_->FeatureSectionDescriptors();
505 for (const auto& pair : section_map) {
506 int feature = pair.first;
507 const auto& section = pair.second;
508 printf("feature section for %s: offset %" PRId64 ", size %" PRId64 "\n",
509 GetFeatureNameOrUnknown(feature).c_str(), section.offset, section.size);
510 if (feature == FEAT_BUILD_ID) {
511 std::vector<BuildIdRecord> records = record_file_reader_->ReadBuildIdFeature();
512 for (auto& r : records) {
513 r.Dump(1);
514 }
515 } else if (feature == FEAT_OSRELEASE) {
516 std::string s = record_file_reader_->ReadFeatureString(feature);
517 PrintIndented(1, "osrelease: %s\n", s.c_str());
518 } else if (feature == FEAT_ARCH) {
519 std::string s = record_file_reader_->ReadFeatureString(feature);
520 PrintIndented(1, "arch: %s\n", s.c_str());
521 } else if (feature == FEAT_CMDLINE) {
522 std::vector<std::string> cmdline = record_file_reader_->ReadCmdlineFeature();
523 PrintIndented(1, "cmdline: %s\n", android::base::Join(cmdline, ' ').c_str());
524 } else if (feature == FEAT_FILE || feature == FEAT_FILE2) {
525 FileFeature file;
526 uint64_t read_pos = 0;
527 bool error = false;
528 PrintIndented(1, "file:\n");
529 while (record_file_reader_->ReadFileFeature(read_pos, file, error)) {
530 PrintIndented(2, "file_path %s\n", file.path.c_str());
531 PrintIndented(2, "file_type %s\n", DsoTypeToString(file.type));
532 PrintIndented(2, "min_vaddr 0x%" PRIx64 "\n", file.min_vaddr);
533 PrintIndented(2, "file_offset_of_min_vaddr 0x%" PRIx64 "\n", file.file_offset_of_min_vaddr);
534 PrintIndented(2, "symbols:\n");
535 for (const auto& symbol : file.symbols) {
536 PrintIndented(3, "%s [0x%" PRIx64 "-0x%" PRIx64 "]\n", symbol.DemangledName(),
537 symbol.addr, symbol.addr + symbol.len);
538 }
539 if (file.type == DSO_DEX_FILE) {
540 PrintIndented(2, "dex_file_offsets:\n");
541 for (uint64_t offset : file.dex_file_offsets) {
542 PrintIndented(3, "0x%" PRIx64 "\n", offset);
543 }
544 }
545 }
546 if (error) {
547 return false;
548 }
549 } else if (feature == FEAT_META_INFO) {
550 PrintIndented(1, "meta_info:\n");
551 for (auto& pair : record_file_reader_->GetMetaInfoFeature()) {
552 PrintIndented(2, "%s = %s\n", pair.first.c_str(), pair.second.c_str());
553 }
554 } else if (feature == FEAT_AUXTRACE) {
555 PrintIndented(1, "file_offsets_of_auxtrace_records:\n");
556 for (auto offset : record_file_reader_->ReadAuxTraceFeature()) {
557 PrintIndented(2, "%" PRIu64 "\n", offset);
558 }
559 } else if (feature == FEAT_DEBUG_UNWIND) {
560 PrintIndented(1, "debug_unwind:\n");
561 if (auto opt_debug_unwind = record_file_reader_->ReadDebugUnwindFeature(); opt_debug_unwind) {
562 for (const DebugUnwindFile& file : opt_debug_unwind.value()) {
563 PrintIndented(2, "path: %s\n", file.path.c_str());
564 PrintIndented(2, "size: %" PRIu64 "\n", file.size);
565 }
566 }
567 } else if (feature == FEAT_ETM_BRANCH_LIST) {
568 std::string data;
569 if (!record_file_reader_->ReadFeatureSection(FEAT_ETM_BRANCH_LIST, &data)) {
570 return false;
571 }
572 ETMBinaryMap binary_map;
573 if (!StringToETMBinaryMap(data, binary_map)) {
574 return false;
575 }
576 PrintIndented(1, "etm_branch_list:\n");
577 for (const auto& [key, binary] : binary_map) {
578 PrintIndented(2, "path: %s\n", key.path.c_str());
579 PrintIndented(2, "build_id: %s\n", key.build_id.ToString().c_str());
580 PrintIndented(2, "binary_type: %s\n", DsoTypeToString(binary.dso_type));
581 if (binary.dso_type == DSO_KERNEL) {
582 PrintIndented(2, "kernel_start_addr: 0x%" PRIx64 "\n", key.kernel_start_addr);
583 }
584 for (const auto& [addr, branches] : binary.GetOrderedBranchMap()) {
585 PrintIndented(3, "addr: 0x%" PRIx64 "\n", addr);
586 for (const auto& [branch, count] : branches) {
587 std::string s = "0b";
588 for (auto it = branch.rbegin(); it != branch.rend(); ++it) {
589 s.push_back(*it ? '1' : '0');
590 }
591 PrintIndented(3, "branch: %s\n", s.c_str());
592 PrintIndented(3, "count: %" PRIu64 "\n", count);
593 }
594 }
595 }
596 }
597 }
598 return true;
599 }
600
601 } // namespace
602
RegisterDumpRecordCommand()603 void RegisterDumpRecordCommand() {
604 RegisterCommand("dump", [] { return std::unique_ptr<Command>(new DumpRecordCommand); });
605 }
606
607 } // namespace simpleperf
608