/* * Copyright (C) 2018 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 #include #include #include "JITDebugReader.h" #include "OfflineUnwinder.h" #include "command.h" #include "environment.h" #include "perf_regs.h" #include "record_file.h" #include "report_utils.h" #include "thread_tree.h" #include "utils.h" namespace simpleperf { namespace { struct MemStat { std::string vm_peak; std::string vm_size; std::string vm_hwm; std::string vm_rss; std::string ToString() const { return android::base::StringPrintf("VmPeak:%s;VmSize:%s;VmHWM:%s;VmRSS:%s", vm_peak.c_str(), vm_size.c_str(), vm_hwm.c_str(), vm_rss.c_str()); } }; static bool GetMemStat(MemStat* stat) { std::string s; if (!android::base::ReadFileToString(android::base::StringPrintf("/proc/%d/status", getpid()), &s)) { PLOG(ERROR) << "Failed to read process status"; return false; } std::vector lines = android::base::Split(s, "\n"); for (auto& line : lines) { if (android::base::StartsWith(line, "VmPeak:")) { stat->vm_peak = android::base::Trim(line.substr(strlen("VmPeak:"))); } else if (android::base::StartsWith(line, "VmSize:")) { stat->vm_size = android::base::Trim(line.substr(strlen("VmSize:"))); } else if (android::base::StartsWith(line, "VmHWM:")) { stat->vm_hwm = android::base::Trim(line.substr(strlen("VmHWM:"))); } else if (android::base::StartsWith(line, "VmRSS:")) { stat->vm_rss = android::base::Trim(line.substr(strlen("VmRSS:"))); } } return true; } struct UnwindingStat { // For testing unwinding performance uint64_t unwinding_sample_count = 0u; uint64_t total_unwinding_time_in_ns = 0u; uint64_t max_unwinding_time_in_ns = 0u; // For memory consumption MemStat mem_before_unwinding; MemStat mem_after_unwinding; void AddUnwindingResult(const UnwindingResult& result) { unwinding_sample_count++; total_unwinding_time_in_ns += result.used_time; max_unwinding_time_in_ns = std::max(max_unwinding_time_in_ns, result.used_time); } void Dump(FILE* fp) { if (unwinding_sample_count == 0) { return; } fprintf(fp, "unwinding_sample_count: %" PRIu64 "\n", unwinding_sample_count); fprintf(fp, "average_unwinding_time: %.3f us\n", total_unwinding_time_in_ns / 1e3 / unwinding_sample_count); fprintf(fp, "max_unwinding_time: %.3f us\n", max_unwinding_time_in_ns / 1e3); if (!mem_before_unwinding.vm_peak.empty()) { fprintf(fp, "memory_change_VmPeak: %s -> %s\n", mem_before_unwinding.vm_peak.c_str(), mem_after_unwinding.vm_peak.c_str()); fprintf(fp, "memory_change_VmSize: %s -> %s\n", mem_before_unwinding.vm_size.c_str(), mem_after_unwinding.vm_size.c_str()); fprintf(fp, "memory_change_VmHwM: %s -> %s\n", mem_before_unwinding.vm_hwm.c_str(), mem_after_unwinding.vm_hwm.c_str()); fprintf(fp, "memory_change_VmRSS: %s -> %s\n", mem_before_unwinding.vm_rss.c_str(), mem_after_unwinding.vm_rss.c_str()); } } }; class RecordFileProcessor { public: RecordFileProcessor(const std::string& output_filename, bool output_binary_mode) : output_filename_(output_filename), output_binary_mode_(output_binary_mode), unwinder_(OfflineUnwinder::Create(true)), callchain_report_builder_(thread_tree_) {} virtual ~RecordFileProcessor() { if (out_fp_ != nullptr && out_fp_ != stdout) { fclose(out_fp_); } } bool ProcessFile(const std::string& input_filename) { // 1. Check input file. record_filename_ = input_filename; reader_ = RecordFileReader::CreateInstance(record_filename_); if (!reader_) { return false; } std::string record_cmd = android::base::Join(reader_->ReadCmdlineFeature(), " "); if (record_cmd.find("-g") == std::string::npos && record_cmd.find("--call-graph dwarf") == std::string::npos) { LOG(ERROR) << "file isn't recorded with dwarf call graph: " << record_filename_; return false; } if (!CheckRecordCmd(record_cmd)) { return false; } // 2. Load feature sections. if (!reader_->LoadBuildIdAndFileFeatures(thread_tree_)) { return false; } ScopedCurrentArch scoped_arch( GetArchType(reader_->ReadFeatureString(PerfFileFormat::FEAT_ARCH))); unwinder_->LoadMetaInfo(reader_->GetMetaInfoFeature()); if (reader_->HasFeature(PerfFileFormat::FEAT_DEBUG_UNWIND) && reader_->HasFeature(PerfFileFormat::FEAT_DEBUG_UNWIND_FILE)) { auto debug_unwind_feature = reader_->ReadDebugUnwindFeature(); if (!debug_unwind_feature.has_value()) { return false; } uint64_t offset = reader_->FeatureSectionDescriptors().at(PerfFileFormat::FEAT_DEBUG_UNWIND_FILE).offset; for (DebugUnwindFile& file : debug_unwind_feature.value()) { auto& loc = debug_unwind_files_[file.path]; loc.offset = offset; loc.size = file.size; offset += file.size; } } callchain_report_builder_.SetRemoveArtFrame(false); callchain_report_builder_.SetConvertJITFrame(false); // 3. Open output file. if (output_filename_.empty()) { out_fp_ = stdout; } else { out_fp_ = fopen(output_filename_.c_str(), output_binary_mode_ ? "web+" : "we+"); if (out_fp_ == nullptr) { PLOG(ERROR) << "failed to write to " << output_filename_; return false; } } // 4. Process records. return Process(); } protected: struct DebugUnwindFileLocation { uint64_t offset; uint64_t size; }; virtual bool CheckRecordCmd(const std::string& record_cmd) = 0; virtual bool Process() = 0; std::string record_filename_; std::unique_ptr reader_; std::string output_filename_; bool output_binary_mode_; FILE* out_fp_ = nullptr; ThreadTree thread_tree_; std::unique_ptr unwinder_; // Files stored in DEBUG_UNWIND_FILE feature section in the recording file. // Map from file path to offset in the recording file. std::unordered_map debug_unwind_files_; CallChainReportBuilder callchain_report_builder_; }; static void DumpUnwindingResult(const UnwindingResult& result, FILE* fp) { fprintf(fp, "unwinding_used_time: %.3f us\n", result.used_time / 1e3); fprintf(fp, "unwinding_error_code: %" PRIu64 "\n", result.error_code); fprintf(fp, "unwinding_error_addr: 0x%" PRIx64 "\n", result.error_addr); fprintf(fp, "stack_start: 0x%" PRIx64 "\n", result.stack_start); fprintf(fp, "stack_end: 0x%" PRIx64 "\n", result.stack_end); } class SampleUnwinder : public RecordFileProcessor { public: SampleUnwinder(const std::string& output_filename, const std::unordered_set& sample_times, bool skip_sample_print) : RecordFileProcessor(output_filename, false), sample_times_(sample_times), skip_sample_print_(skip_sample_print) {} protected: bool CheckRecordCmd(const std::string& record_cmd) override { if (record_cmd.find("--no-unwind") == std::string::npos && record_cmd.find("--keep-failed-unwinding-debug-info") == std::string::npos) { LOG(ERROR) << "file isn't record with --no-unwind or --keep-failed-unwinding-debug-info: " << record_filename_; return false; } return true; } bool Process() override { if (!GetMemStat(&stat_.mem_before_unwinding)) { return false; } if (!reader_->ReadDataSection( [&](std::unique_ptr r) { return ProcessRecord(std::move(r)); })) { return false; } if (!GetMemStat(&stat_.mem_after_unwinding)) { return false; } stat_.Dump(out_fp_); return true; } bool ProcessRecord(std::unique_ptr r) { UpdateRecord(r.get()); thread_tree_.Update(*r); if (r->type() == SIMPLE_PERF_RECORD_UNWINDING_RESULT) { last_unwinding_result_.reset(static_cast(r.release())); } else if (r->type() == PERF_RECORD_SAMPLE) { if (sample_times_.empty() || sample_times_.count(r->Timestamp())) { auto& sr = *static_cast(r.get()); const PerfSampleStackUserType* stack = &sr.stack_user_data; const PerfSampleRegsUserType* regs = &sr.regs_user_data; if (last_unwinding_result_ && last_unwinding_result_->Timestamp() == sr.Timestamp()) { stack = &last_unwinding_result_->stack_user_data; regs = &last_unwinding_result_->regs_user_data; } if (stack->size > 0 || regs->reg_mask > 0) { if (!UnwindRecord(sr, *regs, *stack)) { return false; } } } last_unwinding_result_.reset(); } return true; } void UpdateRecord(Record* record) { if (record->type() == PERF_RECORD_MMAP) { UpdateMmapRecordForEmbeddedFiles(*static_cast(record)); } else if (record->type() == PERF_RECORD_MMAP2) { UpdateMmapRecordForEmbeddedFiles(*static_cast(record)); } } template void UpdateMmapRecordForEmbeddedFiles(MmapRecordType& record) { // Modify mmap records to point to files stored in DEBUG_UNWIND_FILE feature section. std::string filename = record.filename; if (auto it = debug_unwind_files_.find(filename); it != debug_unwind_files_.end()) { auto data = *record.data; uint64_t old_pgoff = data.pgoff; if (JITDebugReader::IsPathInJITSymFile(filename)) { data.pgoff = it->second.offset; } else { data.pgoff += it->second.offset; } debug_unwind_dsos_[data.pgoff] = std::make_pair(thread_tree_.FindUserDsoOrNew(filename), old_pgoff); record.SetDataAndFilename(data, record_filename_); } } bool UnwindRecord(const SampleRecord& r, const PerfSampleRegsUserType& regs, const PerfSampleStackUserType& stack) { ThreadEntry* thread = thread_tree_.FindThreadOrNew(r.tid_data.pid, r.tid_data.tid); RegSet reg_set(regs.abi, regs.reg_mask, regs.regs); std::vector ips; std::vector sps; if (!unwinder_->UnwindCallChain(*thread, reg_set, stack.data, stack.size, &ips, &sps)) { return false; } stat_.AddUnwindingResult(unwinder_->GetUnwindingResult()); if (!skip_sample_print_) { // Print unwinding result. fprintf(out_fp_, "sample_time: %" PRIu64 "\n", r.Timestamp()); DumpUnwindingResult(unwinder_->GetUnwindingResult(), out_fp_); std::vector entries = callchain_report_builder_.Build(thread, ips, 0); for (size_t i = 0; i < entries.size(); i++) { size_t id = i + 1; auto& entry = entries[i]; fprintf(out_fp_, "ip_%zu: 0x%" PRIx64 "\n", id, entry.ip); fprintf(out_fp_, "sp_%zu: 0x%" PRIx64 "\n", id, sps[i]); Dso* dso = entry.map->dso; uint64_t pgoff = entry.map->pgoff; if (dso->Path() == record_filename_) { auto it = debug_unwind_dsos_.find(entry.map->pgoff); CHECK(it != debug_unwind_dsos_.end()); const auto& p = it->second; dso = p.first; pgoff = p.second; if (!JITDebugReader::IsPathInJITSymFile(dso->Path())) { entry.vaddr_in_file = dso->IpToVaddrInFile(entry.ip, entry.map->start_addr, pgoff); } entry.symbol = dso->FindSymbol(entry.vaddr_in_file); } fprintf(out_fp_, "map_%zu: [0x%" PRIx64 "-0x%" PRIx64 "], pgoff 0x%" PRIx64 "\n", id, entry.map->start_addr, entry.map->get_end_addr(), pgoff); fprintf(out_fp_, "dso_%zu: %s\n", id, dso->Path().c_str()); fprintf(out_fp_, "vaddr_in_file_%zu: 0x%" PRIx64 "\n", id, entry.vaddr_in_file); fprintf(out_fp_, "symbol_%zu: %s\n", id, entry.symbol->DemangledName()); } fprintf(out_fp_, "\n"); } return true; } private: const std::unordered_set sample_times_; bool skip_sample_print_; // Map from offset in recording file to the corresponding debug_unwind_file. std::unordered_map> debug_unwind_dsos_; UnwindingStat stat_; std::unique_ptr last_unwinding_result_; }; class TestFileGenerator : public RecordFileProcessor { public: TestFileGenerator(const std::string& output_filename, const std::unordered_set& sample_times, const std::unordered_set& kept_binaries) : RecordFileProcessor(output_filename, true), sample_times_(sample_times), kept_binaries_(kept_binaries) {} protected: bool CheckRecordCmd(const std::string&) override { return true; } bool Process() override { writer_.reset(new RecordFileWriter(output_filename_, out_fp_, false)); if (!writer_ || !writer_->WriteAttrSection(reader_->AttrSection())) { return false; } if (!reader_->ReadDataSection( [&](std::unique_ptr r) { return ProcessRecord(std::move(r)); })) { return false; } return WriteFeatureSections(); } bool ProcessRecord(std::unique_ptr r) { thread_tree_.Update(*r); bool keep_record = false; if (r->type() == SIMPLE_PERF_RECORD_UNWINDING_RESULT) { keep_record = (sample_times_.count(r->Timestamp()) > 0); } else if (r->type() == PERF_RECORD_SAMPLE) { keep_record = (sample_times_.count(r->Timestamp()) > 0); if (keep_record) { // Dump maps needed to unwind this sample. if (!WriteMapsForSample(*static_cast(r.get()))) { return false; } } } if (keep_record) { return writer_->WriteRecord(*r); } return true; } bool WriteMapsForSample(const SampleRecord& r) { ThreadEntry* thread = thread_tree_.FindThread(r.tid_data.tid); if (thread != nullptr && thread->maps) { const EventAttrIds& attrs = reader_->AttrSection(); const perf_event_attr& attr = attrs[0].attr; uint64_t event_id = attrs[0].ids[0]; for (const auto& p : thread->maps->maps) { const MapEntry* map = p.second; Mmap2Record map_record(attr, false, r.tid_data.pid, r.tid_data.tid, map->start_addr, map->len, map->pgoff, map->flags, map->dso->Path(), event_id, r.Timestamp()); if (!writer_->WriteRecord(map_record)) { return false; } } } return true; } bool WriteFeatureSections() { if (!writer_->BeginWriteFeatures(reader_->FeatureSectionDescriptors().size())) { return false; } std::unordered_set feature_types_to_copy = { PerfFileFormat::FEAT_ARCH, PerfFileFormat::FEAT_CMDLINE, PerfFileFormat::FEAT_META_INFO}; const size_t BUFFER_SIZE = 64 * kKilobyte; std::string buffer(BUFFER_SIZE, '\0'); for (const auto& p : reader_->FeatureSectionDescriptors()) { auto feat_type = p.first; if (feat_type == PerfFileFormat::FEAT_DEBUG_UNWIND) { DebugUnwindFeature feature; buffer.resize(BUFFER_SIZE); for (const auto& file_p : debug_unwind_files_) { if (kept_binaries_.count(file_p.first)) { feature.resize(feature.size() + 1); feature.back().path = file_p.first; feature.back().size = file_p.second.size; if (!CopyDebugUnwindFile(file_p.second, buffer)) { return false; } } } if (!writer_->WriteDebugUnwindFeature(feature)) { return false; } } else if (feat_type == PerfFileFormat::FEAT_FILE || feat_type == PerfFileFormat::FEAT_FILE2) { uint64_t read_pos = 0; FileFeature file_feature; bool error = false; while (reader_->ReadFileFeature(read_pos, file_feature, error)) { if (kept_binaries_.count(file_feature.path) && !writer_->WriteFileFeature(file_feature)) { return false; } } if (error) { return false; } } else if (feat_type == PerfFileFormat::FEAT_BUILD_ID) { std::vector build_ids = reader_->ReadBuildIdFeature(); std::vector write_build_ids; for (auto& build_id : build_ids) { if (kept_binaries_.count(build_id.filename)) { write_build_ids.emplace_back(std::move(build_id)); } } if (!writer_->WriteBuildIdFeature(write_build_ids)) { return false; } } else if (feature_types_to_copy.count(feat_type)) { if (!reader_->ReadFeatureSection(feat_type, &buffer) || !writer_->WriteFeature(feat_type, buffer.data(), buffer.size())) { return false; } } } return writer_->EndWriteFeatures() && writer_->Close(); } bool CopyDebugUnwindFile(const DebugUnwindFileLocation& loc, std::string& buffer) { uint64_t offset = loc.offset; uint64_t left_size = loc.size; while (left_size > 0) { size_t nread = std::min(left_size, buffer.size()); if (!reader_->ReadAtOffset(offset, buffer.data(), nread) || !writer_->WriteFeature(PerfFileFormat::FEAT_DEBUG_UNWIND_FILE, buffer.data(), nread)) { return false; } offset += nread; left_size -= nread; } return true; } private: const std::unordered_set sample_times_; const std::unordered_set kept_binaries_; std::unique_ptr writer_; }; class ReportGenerator : public RecordFileProcessor { public: ReportGenerator(const std::string& output_filename) : RecordFileProcessor(output_filename, false) {} protected: bool CheckRecordCmd(const std::string& record_cmd) override { if (record_cmd.find("--keep-failed-unwinding-debug-info") == std::string::npos && record_cmd.find("--keep-failed-unwinding-result") == std::string::npos) { LOG(ERROR) << "file isn't record with --keep-failed-unwinding-debug-info or " << "--keep-failed-unwinding-result: " << record_filename_; return false; } return true; } bool Process() override { if (!reader_->ReadDataSection( [&](std::unique_ptr r) { return ProcessRecord(std::move(r)); })) { return false; } return true; } private: bool ProcessRecord(std::unique_ptr r) { thread_tree_.Update(*r); if (r->type() == SIMPLE_PERF_RECORD_UNWINDING_RESULT) { last_unwinding_result_.reset(static_cast(r.release())); } else if (r->type() == PERF_RECORD_SAMPLE) { if (last_unwinding_result_) { ReportUnwindingResult(*static_cast(r.get()), *last_unwinding_result_); last_unwinding_result_.reset(); } } return true; } void ReportUnwindingResult(const SampleRecord& sr, const UnwindingResultRecord& unwinding_r) { ThreadEntry* thread = thread_tree_.FindThreadOrNew(sr.tid_data.pid, sr.tid_data.tid); size_t kernel_ip_count; std::vector ips = sr.GetCallChain(&kernel_ip_count); if (kernel_ip_count != 0) { ips.erase(ips.begin(), ips.begin() + kernel_ip_count); } fprintf(out_fp_, "sample_time: %" PRIu64 "\n", sr.Timestamp()); DumpUnwindingResult(unwinding_r.unwinding_result, out_fp_); // Print callchain. std::vector entries = callchain_report_builder_.Build(thread, ips, 0); for (size_t i = 0; i < entries.size(); i++) { size_t id = i + 1; const auto& entry = entries[i]; fprintf(out_fp_, "ip_%zu: 0x%" PRIx64 "\n", id, entry.ip); if (i < unwinding_r.callchain.length) { fprintf(out_fp_, "unwinding_ip_%zu: 0x%" PRIx64 "\n", id, unwinding_r.callchain.ips[i]); fprintf(out_fp_, "unwinding_sp_%zu: 0x%" PRIx64 "\n", id, unwinding_r.callchain.sps[i]); } fprintf(out_fp_, "map_%zu: [0x%" PRIx64 "-0x%" PRIx64 "], pgoff 0x%" PRIx64 "\n", id, entry.map->start_addr, entry.map->get_end_addr(), entry.map->pgoff); fprintf(out_fp_, "dso_%zu: %s\n", id, entry.map->dso->Path().c_str()); fprintf(out_fp_, "vaddr_in_file_%zu: 0x%" PRIx64 "\n", id, entry.vaddr_in_file); fprintf(out_fp_, "symbol_%zu: %s\n", id, entry.symbol->DemangledName()); } // Print regs. uint64_t stack_addr = 0; if (unwinding_r.regs_user_data.reg_nr > 0) { auto& reg_data = unwinding_r.regs_user_data; RegSet regs(reg_data.abi, reg_data.reg_mask, reg_data.regs); uint64_t value; if (regs.GetSpRegValue(&value)) { stack_addr = value; for (size_t i = 0; i < 64; i++) { if (regs.GetRegValue(i, &value)) { fprintf(out_fp_, "reg_%s: 0x%" PRIx64 "\n", GetRegName(i, regs.arch).c_str(), value); } } } } // Print stack. if (unwinding_r.stack_user_data.size > 0) { auto& stack = unwinding_r.stack_user_data; const char* p = stack.data; const char* end = stack.data + stack.size; uint64_t value; while (p + 8 <= end) { fprintf(out_fp_, "stack_%" PRIx64 ":", stack_addr); for (size_t i = 0; i < 4 && p + 8 <= end; ++i) { MoveFromBinaryFormat(value, p); fprintf(out_fp_, " %016" PRIx64, value); } fprintf(out_fp_, "\n"); stack_addr += 32; } fprintf(out_fp_, "\n"); } } std::unique_ptr last_unwinding_result_; }; class DebugUnwindCommand : public Command { public: DebugUnwindCommand() : Command( "debug-unwind", "Debug/test offline unwinding.", // clang-format off "Usage: simpleperf debug-unwind [options]\n" "--generate-report Generate a failed unwinding report.\n" "--generate-test-file Generate a test file with only one sample.\n" "-i Input recording file. Default is perf.data.\n" "-o Output file. Default is stdout.\n" "--keep-binaries-in-test-file binary1,binary2... Keep binaries in test file.\n" "--sample-time time1,time2... Only process samples recorded at selected times.\n" "--symfs Look for files with symbols relative to this directory.\n" "--unwind-sample Unwind samples.\n" "--skip-sample-print Skip printing unwound samples.\n" "\n" "Examples:\n" "1. Unwind a sample.\n" "$ simpleperf debug-unwind -i perf.data --unwind-sample --sample-time 626970493946976\n" " perf.data should be generated with \"--no-unwind\" or \"--keep-failed-unwinding-debug-info\".\n" "2. Generate a test file.\n" "$ simpleperf debug-unwind -i perf.data --generate-test-file -o test.data --sample-time \\\n" " 626970493946976 --keep-binaries-in-test-file perf.data_jit_app_cache:255984-259968\n" "3. Generate a failed unwinding report.\n" "$ simpleperf debug-unwind -i perf.data --generate-report -o report.txt\n" " perf.data should be generated with \"--keep-failed-unwinding-debug-info\" or \\\n" " \"--keep-failed-unwinding-result\".\n" "\n" // clang-format on ) {} bool Run(const std::vector& args); private: bool ParseOptions(const std::vector& args); std::string input_filename_ = "perf.data"; std::string output_filename_; bool unwind_sample_ = false; bool skip_sample_print_ = false; bool generate_report_ = false; bool generate_test_file_; std::unordered_set kept_binaries_in_test_file_; std::unordered_set sample_times_; }; bool DebugUnwindCommand::Run(const std::vector& args) { // 1. Parse options. if (!ParseOptions(args)) { return false; } // 2. Distribute sub commands. if (unwind_sample_) { SampleUnwinder sample_unwinder(output_filename_, sample_times_, skip_sample_print_); return sample_unwinder.ProcessFile(input_filename_); } if (generate_test_file_) { TestFileGenerator test_file_generator(output_filename_, sample_times_, kept_binaries_in_test_file_); return test_file_generator.ProcessFile(input_filename_); } if (generate_report_) { ReportGenerator report_generator(output_filename_); return report_generator.ProcessFile(input_filename_); } return true; } bool DebugUnwindCommand::ParseOptions(const std::vector& args) { const OptionFormatMap option_formats = { {"--generate-report", {OptionValueType::NONE, OptionType::SINGLE}}, {"--generate-test-file", {OptionValueType::NONE, OptionType::SINGLE}}, {"-i", {OptionValueType::STRING, OptionType::SINGLE}}, {"--keep-binaries-in-test-file", {OptionValueType::STRING, OptionType::MULTIPLE}}, {"-o", {OptionValueType::STRING, OptionType::SINGLE}}, {"--sample-time", {OptionValueType::STRING, OptionType::MULTIPLE}}, {"--skip-sample-print", {OptionValueType::NONE, OptionType::SINGLE}}, {"--symfs", {OptionValueType::STRING, OptionType::MULTIPLE}}, {"--unwind-sample", {OptionValueType::NONE, OptionType::SINGLE}}, }; OptionValueMap options; std::vector> ordered_options; if (!PreprocessOptions(args, option_formats, &options, &ordered_options)) { return false; } generate_report_ = options.PullBoolValue("--generate-report"); generate_test_file_ = options.PullBoolValue("--generate-test-file"); options.PullStringValue("-i", &input_filename_); for (auto& value : options.PullValues("--keep-binaries-in-test-file")) { std::vector binaries = android::base::Split(*value.str_value, ","); kept_binaries_in_test_file_.insert(binaries.begin(), binaries.end()); } skip_sample_print_ = options.PullBoolValue("--skip-sample-print"); options.PullStringValue("-o", &output_filename_); for (auto& value : options.PullValues("--sample-time")) { auto times = ParseUintVector(*value.str_value); if (!times) { return false; } sample_times_.insert(times.value().begin(), times.value().end()); } if (auto value = options.PullValue("--symfs"); value) { if (!Dso::SetSymFsDir(*value->str_value)) { return false; } } unwind_sample_ = options.PullBoolValue("--unwind-sample"); CHECK(options.values.empty()); if (generate_test_file_) { if (output_filename_.empty()) { LOG(ERROR) << "no output path for generated test file"; return false; } if (sample_times_.empty()) { LOG(ERROR) << "no samples are selected via --sample-time"; return false; } } return true; } } // namespace void RegisterDebugUnwindCommand() { RegisterCommand("debug-unwind", [] { return std::unique_ptr(new DebugUnwindCommand()); }); } } // namespace simpleperf