/* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #include #include #include #include "BranchListFile.h" #include "ETMDecoder.h" #include "command.h" #include "dso.h" #include "event_attr.h" #include "event_type.h" #include "perf_regs.h" #include "record.h" #include "record_file.h" #include "tracing.h" #include "utils.h" namespace simpleperf { namespace { using namespace PerfFileFormat; struct SymbolInfo { Dso* dso; const Symbol* symbol; uint64_t vaddr_in_file; }; using ExtractFieldFn = std::function; struct EventInfo { size_t tp_data_size = 0; std::vector tp_fields; std::vector extract_field_functions; }; std::string ExtractStringField(const TracingField& field, const PerfSampleRawType& data) { std::string s; // data points to a char [field.elem_count] array. It is not guaranteed to be ended // with '\0'. So need to copy from data like strncpy. size_t max_len = std::min(data.size - field.offset, field.elem_count); const char* p = data.data + field.offset; for (size_t i = 0; i < max_len && *p != '\0'; i++) { s.push_back(*p++); } return s; } std::string ExtractDynamicStringField(const TracingField& field, const PerfSampleRawType& data) { std::string s; const char* p = data.data + field.offset; if (field.elem_size != 4 || field.offset + field.elem_size > data.size) { return s; } uint32_t location; MoveFromBinaryFormat(location, p); // Parse location: (max_len << 16) | off. uint32_t offset = location & 0xffff; uint32_t max_len = location >> 16; if (offset + max_len <= data.size) { p = data.data + offset; for (size_t i = 0; i < max_len && *p != '\0'; i++) { s.push_back(*p++); } } return s; } template ::type> std::string ExtractIntFieldFromPointer(const TracingField& field, const char* p) { static_assert(std::is_signed::value); T value; MoveFromBinaryFormat(value, p); if (field.is_signed) { return android::base::StringPrintf("%" PRId64, static_cast(value)); } return android::base::StringPrintf("0x%" PRIx64, static_cast(static_cast(value))); } template std::string ExtractIntField(const TracingField& field, const PerfSampleRawType& data) { if (field.offset + sizeof(T) > data.size) { return ""; } return ExtractIntFieldFromPointer(field, data.data + field.offset); } template std::string ExtractIntArrayField(const TracingField& field, const PerfSampleRawType& data) { if (field.offset + field.elem_size * field.elem_count > data.size) { return ""; } std::string s; const char* p = data.data + field.offset; for (size_t i = 0; i < field.elem_count; i++) { if (i != 0) { s.push_back(' '); } ExtractIntFieldFromPointer(field, p); p += field.elem_size; } return s; } std::string ExtractUnknownField(const TracingField& field, const PerfSampleRawType& data) { size_t total = field.elem_size * field.elem_count; if (field.offset + total > data.size) { return ""; } uint32_t value; std::string s; const char* p = data.data + field.offset; for (size_t i = 0; i + sizeof(value) <= total; i += sizeof(value)) { if (i != 0) { s.push_back(' '); } MoveFromBinaryFormat(value, p); s += android::base::StringPrintf("0x%08x", value); } return s; } ExtractFieldFn GetExtractFieldFunction(const TracingField& field) { if (field.is_dynamic) { return ExtractDynamicStringField; } if (field.elem_count > 1 && field.elem_size == 1) { // Probably the field is a string. // Don't use field.is_signed, which has different values on x86 and arm. return ExtractStringField; } if (field.elem_count == 1) { switch (field.elem_size) { case 1: return ExtractIntField; case 2: return ExtractIntField; case 4: return ExtractIntField; case 8: return ExtractIntField; } } else { switch (field.elem_size) { case 1: return ExtractIntArrayField; case 2: return ExtractIntArrayField; case 4: return ExtractIntArrayField; case 8: return ExtractIntArrayField; } } return ExtractUnknownField; } class ETMThreadTreeForDumpCmd : public ETMThreadTree { public: ETMThreadTreeForDumpCmd(ThreadTree& thread_tree) : thread_tree_(thread_tree) {} void DisableThreadExitRecords() override { thread_tree_.DisableThreadExitRecords(); } const ThreadEntry* FindThread(int tid) override { return thread_tree_.FindThread(tid); } const MapSet& GetKernelMaps() override { return thread_tree_.GetKernelMaps(); } private: ThreadTree& thread_tree_; }; class DumpRecordCommand : public Command { public: DumpRecordCommand() : Command("dump", "dump perf record file", // clang-format off "Usage: simpleperf dumprecord [options] [perf_record_file]\n" " Dump different parts of a perf record file. Default file is perf.data.\n" "--dump-etm type1,type2,... Dump etm data. A type is one of raw, packet and element.\n" "-i Record file to dump. Default is perf.data.\n" "--symdir Look for binaries in a directory recursively.\n" // clang-format on ) {} bool Run(const std::vector& args); private: bool ParseOptions(const std::vector& args); void DumpFileHeader(); void DumpAttrSection(); bool DumpDataSection(); bool ProcessRecord(Record* r); void ProcessSampleRecord(const SampleRecord& r); void ProcessCallChainRecord(const CallChainRecord& r); SymbolInfo GetSymbolInfo(uint32_t pid, uint32_t tid, uint64_t ip, std::optional in_kernel = std::nullopt); bool ProcessTracingData(const TracingDataRecord& r); bool DumpAuxData(const AuxRecord& aux); bool DumpFeatureSection(); // options std::string record_filename_ = "perf.data"; ETMDumpOption etm_dump_option_; std::unique_ptr record_file_reader_; std::unique_ptr etm_decoder_; std::unique_ptr etm_thread_tree_; ThreadTree thread_tree_; std::vector events_; }; bool DumpRecordCommand::Run(const std::vector& args) { if (!ParseOptions(args)) { return false; } record_file_reader_ = RecordFileReader::CreateInstance(record_filename_); if (record_file_reader_ == nullptr) { return false; } DumpFileHeader(); DumpAttrSection(); if (!DumpDataSection()) { return false; } return DumpFeatureSection(); } bool DumpRecordCommand::ParseOptions(const std::vector& args) { const OptionFormatMap option_formats = { {"--dump-etm", {OptionValueType::STRING, OptionType::SINGLE}}, {"-i", {OptionValueType::STRING, OptionType::SINGLE}}, {"--symdir", {OptionValueType::STRING, OptionType::MULTIPLE}}, }; OptionValueMap options; std::vector> ordered_options; std::vector non_option_args; if (!PreprocessOptions(args, option_formats, &options, &ordered_options, &non_option_args)) { return false; } if (auto value = options.PullValue("--dump-etm"); value) { if (!ParseEtmDumpOption(*value->str_value, &etm_dump_option_)) { return false; } } options.PullStringValue("-i", &record_filename_); for (const OptionValue& value : options.PullValues("--symdir")) { if (!Dso::AddSymbolDir(*value.str_value)) { return false; } } CHECK(options.values.empty()); if (non_option_args.size() > 1) { LOG(ERROR) << "too many record files"; return false; } if (non_option_args.size() == 1) { record_filename_ = non_option_args[0]; } return true; } static const std::string GetFeatureNameOrUnknown(int feature) { std::string name = GetFeatureName(feature); return name.empty() ? android::base::StringPrintf("unknown_feature(%d)", feature) : name; } void DumpRecordCommand::DumpFileHeader() { const FileHeader& header = record_file_reader_->FileHeader(); printf("magic: "); for (size_t i = 0; i < 8; ++i) { printf("%c", header.magic[i]); } printf("\n"); printf("header_size: %" PRId64 "\n", header.header_size); if (header.header_size != sizeof(header)) { PLOG(WARNING) << "record file header size " << header.header_size << "doesn't match expected header size " << sizeof(header); } printf("attr_size: %" PRId64 "\n", header.attr_size); printf("attrs[file section]: offset %" PRId64 ", size %" PRId64 "\n", header.attrs.offset, header.attrs.size); printf("data[file section]: offset %" PRId64 ", size %" PRId64 "\n", header.data.offset, header.data.size); printf("event_types[file section]: offset %" PRId64 ", size %" PRId64 "\n", header.event_types.offset, header.event_types.size); std::vector features; for (size_t i = 0; i < FEAT_MAX_NUM; ++i) { size_t j = i / 8; size_t k = i % 8; if ((header.features[j] & (1 << k)) != 0) { features.push_back(i); } } for (auto& feature : features) { printf("feature: %s\n", GetFeatureNameOrUnknown(feature).c_str()); } } void DumpRecordCommand::DumpAttrSection() { const EventAttrIds& attrs = record_file_reader_->AttrSection(); for (size_t i = 0; i < attrs.size(); ++i) { const auto& attr = attrs[i]; printf("attr %zu:\n", i + 1); DumpPerfEventAttr(attr.attr, 1); if (!attr.ids.empty()) { printf(" ids:"); for (const auto& id : attr.ids) { printf(" %" PRId64, id); } printf("\n"); } } } bool DumpRecordCommand::DumpDataSection() { thread_tree_.ShowIpForUnknownSymbol(); if (!record_file_reader_->LoadBuildIdAndFileFeatures(thread_tree_)) { return false; } auto record_callback = [&](std::unique_ptr r) { return ProcessRecord(r.get()); }; return record_file_reader_->ReadDataSection(record_callback); } bool DumpRecordCommand::ProcessRecord(Record* r) { r->Dump(); thread_tree_.Update(*r); bool res = true; switch (r->type()) { case PERF_RECORD_SAMPLE: ProcessSampleRecord(*static_cast(r)); break; case SIMPLE_PERF_RECORD_CALLCHAIN: ProcessCallChainRecord(*static_cast(r)); break; case PERF_RECORD_AUXTRACE_INFO: { etm_thread_tree_.reset(new ETMThreadTreeForDumpCmd(thread_tree_)); etm_decoder_ = ETMDecoder::Create(*static_cast(r), *etm_thread_tree_); if (etm_decoder_) { etm_decoder_->EnableDump(etm_dump_option_); } else { res = false; } break; } case PERF_RECORD_AUX: { res = DumpAuxData(*static_cast(r)); break; } case PERF_RECORD_TRACING_DATA: case SIMPLE_PERF_RECORD_TRACING_DATA: { res = ProcessTracingData(*static_cast(r)); break; } } return res; } void DumpRecordCommand::ProcessSampleRecord(const SampleRecord& sr) { bool in_kernel = sr.InKernel(); if (sr.sample_type & PERF_SAMPLE_CALLCHAIN) { PrintIndented(1, "callchain:\n"); for (size_t i = 0; i < sr.callchain_data.ip_nr; ++i) { if (sr.callchain_data.ips[i] >= PERF_CONTEXT_MAX) { if (sr.callchain_data.ips[i] == PERF_CONTEXT_USER) { in_kernel = false; } continue; } SymbolInfo s = GetSymbolInfo(sr.tid_data.pid, sr.tid_data.tid, sr.callchain_data.ips[i], in_kernel); PrintIndented(2, "%s (%s[+%" PRIx64 "])\n", s.symbol->DemangledName(), s.dso->Path().c_str(), s.vaddr_in_file); } } if (sr.sample_type & PERF_SAMPLE_BRANCH_STACK) { PrintIndented(1, "branch_stack:\n"); for (size_t i = 0; i < sr.branch_stack_data.stack_nr; ++i) { uint64_t from_ip = sr.branch_stack_data.stack[i].from; uint64_t to_ip = sr.branch_stack_data.stack[i].to; SymbolInfo from_symbol = GetSymbolInfo(sr.tid_data.pid, sr.tid_data.tid, from_ip); SymbolInfo to_symbol = GetSymbolInfo(sr.tid_data.pid, sr.tid_data.tid, to_ip); PrintIndented(2, "%s (%s[+%" PRIx64 "]) -> %s (%s[+%" PRIx64 "])\n", from_symbol.symbol->DemangledName(), from_symbol.dso->Path().c_str(), from_symbol.vaddr_in_file, to_symbol.symbol->DemangledName(), to_symbol.dso->Path().c_str(), to_symbol.vaddr_in_file); } } // Dump tracepoint fields. if (!events_.empty()) { size_t attr_index = record_file_reader_->GetAttrIndexOfRecord(&sr); auto& event = events_[attr_index]; if (event.tp_data_size > 0 && sr.raw_data.size >= event.tp_data_size) { PrintIndented(1, "tracepoint fields:\n"); for (size_t i = 0; i < event.tp_fields.size(); i++) { auto& field = event.tp_fields[i]; std::string s = event.extract_field_functions[i](field, sr.raw_data); PrintIndented(2, "%s: %s\n", field.name.c_str(), s.c_str()); } } } } void DumpRecordCommand::ProcessCallChainRecord(const CallChainRecord& cr) { PrintIndented(1, "callchain:\n"); for (size_t i = 0; i < cr.ip_nr; ++i) { SymbolInfo s = GetSymbolInfo(cr.pid, cr.tid, cr.ips[i], false); PrintIndented(2, "%s (%s[+%" PRIx64 "])\n", s.symbol->DemangledName(), s.dso->Path().c_str(), s.vaddr_in_file); } } SymbolInfo DumpRecordCommand::GetSymbolInfo(uint32_t pid, uint32_t tid, uint64_t ip, std::optional in_kernel) { ThreadEntry* thread = thread_tree_.FindThreadOrNew(pid, tid); const MapEntry* map; if (in_kernel.has_value()) { map = thread_tree_.FindMap(thread, ip, in_kernel.value()); } else { map = thread_tree_.FindMap(thread, ip); } SymbolInfo info; info.symbol = thread_tree_.FindSymbol(map, ip, &info.vaddr_in_file, &info.dso); return info; } bool DumpRecordCommand::DumpAuxData(const AuxRecord& aux) { if (aux.data->aux_size > SIZE_MAX) { LOG(ERROR) << "invalid aux size"; return false; } size_t size = aux.data->aux_size; if (size > 0) { std::vector data; bool error = false; if (!record_file_reader_->ReadAuxData(aux.Cpu(), aux.data->aux_offset, size, data, error)) { return !error; } if (!etm_decoder_) { LOG(ERROR) << "ETMDecoder isn't created"; return false; } return etm_decoder_->ProcessData(data.data(), size, !aux.Unformatted(), aux.Cpu()); } return true; } bool DumpRecordCommand::ProcessTracingData(const TracingDataRecord& r) { auto tracing = Tracing::Create(std::vector(r.data, r.data + r.data_size)); if (!tracing) { return false; } const EventAttrIds& attrs = record_file_reader_->AttrSection(); events_.resize(attrs.size()); for (size_t i = 0; i < attrs.size(); i++) { auto& attr = attrs[i].attr; auto& event = events_[i]; if (attr.type != PERF_TYPE_TRACEPOINT) { continue; } std::optional format = tracing->GetTracingFormatHavingId(attr.config); if (!format.has_value()) { LOG(ERROR) << "failed to get tracing format"; return false; } event.tp_fields = format.value().fields; // Decide dump function for each field. for (size_t j = 0; j < event.tp_fields.size(); j++) { auto& field = event.tp_fields[j]; event.extract_field_functions.push_back(GetExtractFieldFunction(field)); event.tp_data_size += field.elem_count * field.elem_size; } } return true; } bool DumpRecordCommand::DumpFeatureSection() { std::map section_map = record_file_reader_->FeatureSectionDescriptors(); for (const auto& pair : section_map) { int feature = pair.first; const auto& section = pair.second; printf("feature section for %s: offset %" PRId64 ", size %" PRId64 "\n", GetFeatureNameOrUnknown(feature).c_str(), section.offset, section.size); if (feature == FEAT_BUILD_ID) { std::vector records = record_file_reader_->ReadBuildIdFeature(); for (auto& r : records) { r.Dump(1); } } else if (feature == FEAT_OSRELEASE) { std::string s = record_file_reader_->ReadFeatureString(feature); PrintIndented(1, "osrelease: %s\n", s.c_str()); } else if (feature == FEAT_ARCH) { std::string s = record_file_reader_->ReadFeatureString(feature); PrintIndented(1, "arch: %s\n", s.c_str()); } else if (feature == FEAT_CMDLINE) { std::vector cmdline = record_file_reader_->ReadCmdlineFeature(); PrintIndented(1, "cmdline: %s\n", android::base::Join(cmdline, ' ').c_str()); } else if (feature == FEAT_FILE || feature == FEAT_FILE2) { FileFeature file; uint64_t read_pos = 0; bool error = false; PrintIndented(1, "file:\n"); while (record_file_reader_->ReadFileFeature(read_pos, file, error)) { PrintIndented(2, "file_path %s\n", file.path.c_str()); PrintIndented(2, "file_type %s\n", DsoTypeToString(file.type)); PrintIndented(2, "min_vaddr 0x%" PRIx64 "\n", file.min_vaddr); PrintIndented(2, "file_offset_of_min_vaddr 0x%" PRIx64 "\n", file.file_offset_of_min_vaddr); PrintIndented(2, "symbols:\n"); for (const auto& symbol : file.symbols) { PrintIndented(3, "%s [0x%" PRIx64 "-0x%" PRIx64 "]\n", symbol.DemangledName(), symbol.addr, symbol.addr + symbol.len); } if (file.type == DSO_DEX_FILE) { PrintIndented(2, "dex_file_offsets:\n"); for (uint64_t offset : file.dex_file_offsets) { PrintIndented(3, "0x%" PRIx64 "\n", offset); } } } if (error) { return false; } } else if (feature == FEAT_META_INFO) { PrintIndented(1, "meta_info:\n"); for (auto& pair : record_file_reader_->GetMetaInfoFeature()) { PrintIndented(2, "%s = %s\n", pair.first.c_str(), pair.second.c_str()); } } else if (feature == FEAT_AUXTRACE) { PrintIndented(1, "file_offsets_of_auxtrace_records:\n"); for (auto offset : record_file_reader_->ReadAuxTraceFeature()) { PrintIndented(2, "%" PRIu64 "\n", offset); } } else if (feature == FEAT_DEBUG_UNWIND) { PrintIndented(1, "debug_unwind:\n"); if (auto opt_debug_unwind = record_file_reader_->ReadDebugUnwindFeature(); opt_debug_unwind) { for (const DebugUnwindFile& file : opt_debug_unwind.value()) { PrintIndented(2, "path: %s\n", file.path.c_str()); PrintIndented(2, "size: %" PRIu64 "\n", file.size); } } } else if (feature == FEAT_ETM_BRANCH_LIST) { std::string data; if (!record_file_reader_->ReadFeatureSection(FEAT_ETM_BRANCH_LIST, &data)) { return false; } ETMBinaryMap binary_map; if (!StringToETMBinaryMap(data, binary_map)) { return false; } PrintIndented(1, "etm_branch_list:\n"); for (const auto& [key, binary] : binary_map) { PrintIndented(2, "path: %s\n", key.path.c_str()); PrintIndented(2, "build_id: %s\n", key.build_id.ToString().c_str()); PrintIndented(2, "binary_type: %s\n", DsoTypeToString(binary.dso_type)); if (binary.dso_type == DSO_KERNEL) { PrintIndented(2, "kernel_start_addr: 0x%" PRIx64 "\n", key.kernel_start_addr); } for (const auto& [addr, branches] : binary.GetOrderedBranchMap()) { PrintIndented(3, "addr: 0x%" PRIx64 "\n", addr); for (const auto& [branch, count] : branches) { std::string s = "0b"; for (auto it = branch.rbegin(); it != branch.rend(); ++it) { s.push_back(*it ? '1' : '0'); } PrintIndented(3, "branch: %s\n", s.c_str()); PrintIndented(3, "count: %" PRIu64 "\n", count); } } } } } return true; } } // namespace void RegisterDumpRecordCommand() { RegisterCommand("dump", [] { return std::unique_ptr(new DumpRecordCommand); }); } } // namespace simpleperf