• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 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 <stdio.h>
18 
19 #include <algorithm>
20 #include <memory>
21 #include <string>
22 #include <unordered_map>
23 #include <unordered_set>
24 #include <vector>
25 
26 #include <android-base/file.h>
27 #include <android-base/logging.h>
28 #include <android-base/parseint.h>
29 #include <android-base/stringprintf.h>
30 #include <android-base/strings.h>
31 
32 #include "JITDebugReader.h"
33 #include "OfflineUnwinder.h"
34 #include "command.h"
35 #include "environment.h"
36 #include "perf_regs.h"
37 #include "record_file.h"
38 #include "report_utils.h"
39 #include "thread_tree.h"
40 #include "utils.h"
41 
42 namespace simpleperf {
43 namespace {
44 
45 struct MemStat {
46   std::string vm_peak;
47   std::string vm_size;
48   std::string vm_hwm;
49   std::string vm_rss;
50 
ToStringsimpleperf::__anonc541a1a50111::MemStat51   std::string ToString() const {
52     return android::base::StringPrintf("VmPeak:%s;VmSize:%s;VmHWM:%s;VmRSS:%s", vm_peak.c_str(),
53                                        vm_size.c_str(), vm_hwm.c_str(), vm_rss.c_str());
54   }
55 };
56 
GetMemStat(MemStat * stat)57 static bool GetMemStat(MemStat* stat) {
58   std::string s;
59   if (!android::base::ReadFileToString(android::base::StringPrintf("/proc/%d/status", getpid()),
60                                        &s)) {
61     PLOG(ERROR) << "Failed to read process status";
62     return false;
63   }
64   std::vector<std::string> lines = android::base::Split(s, "\n");
65   for (auto& line : lines) {
66     if (android::base::StartsWith(line, "VmPeak:")) {
67       stat->vm_peak = android::base::Trim(line.substr(strlen("VmPeak:")));
68     } else if (android::base::StartsWith(line, "VmSize:")) {
69       stat->vm_size = android::base::Trim(line.substr(strlen("VmSize:")));
70     } else if (android::base::StartsWith(line, "VmHWM:")) {
71       stat->vm_hwm = android::base::Trim(line.substr(strlen("VmHWM:")));
72     } else if (android::base::StartsWith(line, "VmRSS:")) {
73       stat->vm_rss = android::base::Trim(line.substr(strlen("VmRSS:")));
74     }
75   }
76   return true;
77 }
78 
79 struct UnwindingStat {
80   // For testing unwinding performance
81   uint64_t unwinding_sample_count = 0u;
82   uint64_t total_unwinding_time_in_ns = 0u;
83   uint64_t max_unwinding_time_in_ns = 0u;
84 
85   // For memory consumption
86   MemStat mem_before_unwinding;
87   MemStat mem_after_unwinding;
88 
AddUnwindingResultsimpleperf::__anonc541a1a50111::UnwindingStat89   void AddUnwindingResult(const UnwindingResult& result) {
90     unwinding_sample_count++;
91     total_unwinding_time_in_ns += result.used_time;
92     max_unwinding_time_in_ns = std::max(max_unwinding_time_in_ns, result.used_time);
93   }
94 
Dumpsimpleperf::__anonc541a1a50111::UnwindingStat95   void Dump(FILE* fp) {
96     if (unwinding_sample_count == 0) {
97       return;
98     }
99     fprintf(fp, "unwinding_sample_count: %" PRIu64 "\n", unwinding_sample_count);
100     fprintf(fp, "average_unwinding_time: %.3f us\n",
101             total_unwinding_time_in_ns / 1e3 / unwinding_sample_count);
102     fprintf(fp, "max_unwinding_time: %.3f us\n", max_unwinding_time_in_ns / 1e3);
103 
104     if (!mem_before_unwinding.vm_peak.empty()) {
105       fprintf(fp, "memory_change_VmPeak: %s -> %s\n", mem_before_unwinding.vm_peak.c_str(),
106               mem_after_unwinding.vm_peak.c_str());
107       fprintf(fp, "memory_change_VmSize: %s -> %s\n", mem_before_unwinding.vm_size.c_str(),
108               mem_after_unwinding.vm_size.c_str());
109       fprintf(fp, "memory_change_VmHwM: %s -> %s\n", mem_before_unwinding.vm_hwm.c_str(),
110               mem_after_unwinding.vm_hwm.c_str());
111       fprintf(fp, "memory_change_VmRSS: %s -> %s\n", mem_before_unwinding.vm_rss.c_str(),
112               mem_after_unwinding.vm_rss.c_str());
113     }
114   }
115 };
116 
117 class RecordFileProcessor {
118  public:
RecordFileProcessor(const std::string & output_filename,bool output_binary_mode)119   RecordFileProcessor(const std::string& output_filename, bool output_binary_mode)
120       : output_filename_(output_filename),
121         output_binary_mode_(output_binary_mode),
122         unwinder_(OfflineUnwinder::Create(true)),
123         callchain_report_builder_(thread_tree_) {}
124 
~RecordFileProcessor()125   virtual ~RecordFileProcessor() {
126     if (out_fp_ != nullptr && out_fp_ != stdout) {
127       fclose(out_fp_);
128     }
129   }
130 
ProcessFile(const std::string & input_filename)131   bool ProcessFile(const std::string& input_filename) {
132     // 1. Check input file.
133     record_filename_ = input_filename;
134     reader_ = RecordFileReader::CreateInstance(record_filename_);
135     if (!reader_) {
136       return false;
137     }
138     std::string record_cmd = android::base::Join(reader_->ReadCmdlineFeature(), " ");
139     if (record_cmd.find("-g") == std::string::npos &&
140         record_cmd.find("--call-graph dwarf") == std::string::npos) {
141       LOG(ERROR) << "file isn't recorded with dwarf call graph: " << record_filename_;
142       return false;
143     }
144     if (!CheckRecordCmd(record_cmd)) {
145       return false;
146     }
147 
148     // 2. Load feature sections.
149     reader_->LoadBuildIdAndFileFeatures(thread_tree_);
150     ScopedCurrentArch scoped_arch(
151         GetArchType(reader_->ReadFeatureString(PerfFileFormat::FEAT_ARCH)));
152     unwinder_->LoadMetaInfo(reader_->GetMetaInfoFeature());
153     if (reader_->HasFeature(PerfFileFormat::FEAT_DEBUG_UNWIND) &&
154         reader_->HasFeature(PerfFileFormat::FEAT_DEBUG_UNWIND_FILE)) {
155       auto debug_unwind_feature = reader_->ReadDebugUnwindFeature();
156       if (!debug_unwind_feature.has_value()) {
157         return false;
158       }
159       uint64_t offset =
160           reader_->FeatureSectionDescriptors().at(PerfFileFormat::FEAT_DEBUG_UNWIND_FILE).offset;
161       for (DebugUnwindFile& file : debug_unwind_feature.value()) {
162         auto& loc = debug_unwind_files_[file.path];
163         loc.offset = offset;
164         loc.size = file.size;
165         offset += file.size;
166       }
167     }
168     callchain_report_builder_.SetRemoveArtFrame(false);
169     callchain_report_builder_.SetConvertJITFrame(false);
170 
171     // 3. Open output file.
172     if (output_filename_.empty()) {
173       out_fp_ = stdout;
174     } else {
175       out_fp_ = fopen(output_filename_.c_str(), output_binary_mode_ ? "web+" : "we+");
176       if (out_fp_ == nullptr) {
177         PLOG(ERROR) << "failed to write to " << output_filename_;
178         return false;
179       }
180     }
181 
182     // 4. Process records.
183     return Process();
184   }
185 
186  protected:
187   struct DebugUnwindFileLocation {
188     uint64_t offset;
189     uint64_t size;
190   };
191 
192   virtual bool CheckRecordCmd(const std::string& record_cmd) = 0;
193   virtual bool Process() = 0;
194 
195   std::string record_filename_;
196   std::unique_ptr<RecordFileReader> reader_;
197   std::string output_filename_;
198   bool output_binary_mode_;
199   FILE* out_fp_ = nullptr;
200   ThreadTree thread_tree_;
201   std::unique_ptr<OfflineUnwinder> unwinder_;
202   // Files stored in DEBUG_UNWIND_FILE feature section in the recording file.
203   // Map from file path to offset in the recording file.
204   std::unordered_map<std::string, DebugUnwindFileLocation> debug_unwind_files_;
205   CallChainReportBuilder callchain_report_builder_;
206 };
207 
DumpUnwindingResult(const UnwindingResult & result,FILE * fp)208 static void DumpUnwindingResult(const UnwindingResult& result, FILE* fp) {
209   fprintf(fp, "unwinding_used_time: %.3f us\n", result.used_time / 1e3);
210   fprintf(fp, "unwinding_error_code: %" PRIu64 "\n", result.error_code);
211   fprintf(fp, "unwinding_error_addr: 0x%" PRIx64 "\n", result.error_addr);
212   fprintf(fp, "stack_start: 0x%" PRIx64 "\n", result.stack_start);
213   fprintf(fp, "stack_end: 0x%" PRIx64 "\n", result.stack_end);
214 }
215 
216 class SampleUnwinder : public RecordFileProcessor {
217  public:
SampleUnwinder(const std::string & output_filename,const std::unordered_set<uint64_t> & sample_times)218   SampleUnwinder(const std::string& output_filename,
219                  const std::unordered_set<uint64_t>& sample_times)
220       : RecordFileProcessor(output_filename, false), sample_times_(sample_times) {}
221 
222  protected:
CheckRecordCmd(const std::string & record_cmd)223   bool CheckRecordCmd(const std::string& record_cmd) override {
224     if (record_cmd.find("--no-unwind") == std::string::npos &&
225         record_cmd.find("--keep-failed-unwinding-debug-info") == std::string::npos) {
226       LOG(ERROR) << "file isn't record with --no-unwind or --keep-failed-unwinding-debug-info: "
227                  << record_filename_;
228       return false;
229     }
230     return true;
231   }
232 
Process()233   bool Process() override {
234     recording_file_dso_ = Dso::CreateDso(DSO_ELF_FILE, record_filename_);
235 
236     if (!GetMemStat(&stat_.mem_before_unwinding)) {
237       return false;
238     }
239     if (!reader_->ReadDataSection(
240             [&](std::unique_ptr<Record> r) { return ProcessRecord(std::move(r)); })) {
241       return false;
242     }
243     if (!GetMemStat(&stat_.mem_after_unwinding)) {
244       return false;
245     }
246     stat_.Dump(out_fp_);
247     return true;
248   }
249 
ProcessRecord(std::unique_ptr<Record> r)250   bool ProcessRecord(std::unique_ptr<Record> r) {
251     thread_tree_.Update(*r);
252     if (r->type() == SIMPLE_PERF_RECORD_UNWINDING_RESULT) {
253       last_unwinding_result_.reset(static_cast<UnwindingResultRecord*>(r.release()));
254     } else if (r->type() == PERF_RECORD_SAMPLE) {
255       if (sample_times_.empty() || sample_times_.count(r->Timestamp())) {
256         auto& sr = *static_cast<SampleRecord*>(r.get());
257         const PerfSampleStackUserType* stack = &sr.stack_user_data;
258         const PerfSampleRegsUserType* regs = &sr.regs_user_data;
259         if (last_unwinding_result_ && last_unwinding_result_->Timestamp() == sr.Timestamp()) {
260           stack = &last_unwinding_result_->stack_user_data;
261           regs = &last_unwinding_result_->regs_user_data;
262         }
263         if (stack->size > 0 || regs->reg_mask > 0) {
264           if (!UnwindRecord(sr, *regs, *stack)) {
265             return false;
266           }
267         }
268       }
269       last_unwinding_result_.reset();
270     }
271     return true;
272   }
273 
UnwindRecord(const SampleRecord & r,const PerfSampleRegsUserType & regs,const PerfSampleStackUserType & stack)274   bool UnwindRecord(const SampleRecord& r, const PerfSampleRegsUserType& regs,
275                     const PerfSampleStackUserType& stack) {
276     ThreadEntry* thread = thread_tree_.FindThreadOrNew(r.tid_data.pid, r.tid_data.tid);
277     ThreadEntry thread_with_new_maps = CreateThreadWithUpdatedMaps(*thread);
278 
279     RegSet reg_set(regs.abi, regs.reg_mask, regs.regs);
280     std::vector<uint64_t> ips;
281     std::vector<uint64_t> sps;
282     if (!unwinder_->UnwindCallChain(thread_with_new_maps, reg_set, stack.data, stack.size, &ips,
283                                     &sps)) {
284       return false;
285     }
286     stat_.AddUnwindingResult(unwinder_->GetUnwindingResult());
287 
288     // Print unwinding result.
289     fprintf(out_fp_, "sample_time: %" PRIu64 "\n", r.Timestamp());
290     DumpUnwindingResult(unwinder_->GetUnwindingResult(), out_fp_);
291     std::vector<CallChainReportEntry> entries = callchain_report_builder_.Build(thread, ips, 0);
292     for (size_t i = 0; i < entries.size(); i++) {
293       size_t id = i + 1;
294       const auto& entry = entries[i];
295       fprintf(out_fp_, "ip_%zu: 0x%" PRIx64 "\n", id, entry.ip);
296       fprintf(out_fp_, "sp_%zu: 0x%" PRIx64 "\n", id, sps[i]);
297       fprintf(out_fp_, "map_%zu: [0x%" PRIx64 "-0x%" PRIx64 "], pgoff 0x%" PRIx64 "\n", id,
298               entry.map->start_addr, entry.map->get_end_addr(), entry.map->pgoff);
299       fprintf(out_fp_, "dso_%zu: %s\n", id, entry.map->dso->Path().c_str());
300       fprintf(out_fp_, "vaddr_in_file_%zu: 0x%" PRIx64 "\n", id, entry.vaddr_in_file);
301       fprintf(out_fp_, "symbol_%zu: %s\n", id, entry.symbol->DemangledName());
302     }
303     fprintf(out_fp_, "\n");
304     return true;
305   }
306 
307   // To use files stored in DEBUG_UNWIND_FILE feature section, create maps mapping to them.
CreateThreadWithUpdatedMaps(const ThreadEntry & thread)308   ThreadEntry CreateThreadWithUpdatedMaps(const ThreadEntry& thread) {
309     ThreadEntry new_thread = thread;
310     new_thread.maps.reset(new MapSet);
311     new_thread.maps->version = thread.maps->version;
312     for (auto& p : thread.maps->maps) {
313       const MapEntry* old_map = p.second;
314       MapEntry* map = nullptr;
315       const std::string& path = old_map->dso->Path();
316       if (auto it = debug_unwind_files_.find(path); it != debug_unwind_files_.end()) {
317         map_storage_.emplace_back(new MapEntry);
318         map = map_storage_.back().get();
319         *map = *old_map;
320         map->dso = recording_file_dso_.get();
321         if (JITDebugReader::IsPathInJITSymFile(old_map->dso->Path())) {
322           map->pgoff = it->second.offset;
323         } else {
324           map->pgoff += it->second.offset;
325         }
326       } else {
327         map = const_cast<MapEntry*>(p.second);
328       }
329       new_thread.maps->maps[p.first] = map;
330     }
331     return new_thread;
332   }
333 
334  private:
335   const std::unordered_set<uint64_t> sample_times_;
336   std::unique_ptr<Dso> recording_file_dso_;
337   std::vector<std::unique_ptr<MapEntry>> map_storage_;
338   UnwindingStat stat_;
339   std::unique_ptr<UnwindingResultRecord> last_unwinding_result_;
340 };
341 
342 class TestFileGenerator : public RecordFileProcessor {
343  public:
TestFileGenerator(const std::string & output_filename,const std::unordered_set<uint64_t> & sample_times,const std::unordered_set<std::string> & kept_binaries)344   TestFileGenerator(const std::string& output_filename,
345                     const std::unordered_set<uint64_t>& sample_times,
346                     const std::unordered_set<std::string>& kept_binaries)
347       : RecordFileProcessor(output_filename, true),
348         sample_times_(sample_times),
349         kept_binaries_(kept_binaries) {}
350 
351  protected:
CheckRecordCmd(const std::string &)352   bool CheckRecordCmd(const std::string&) override { return true; }
353 
Process()354   bool Process() override {
355     writer_.reset(new RecordFileWriter(output_filename_, out_fp_, false));
356     if (!writer_ || !writer_->WriteAttrSection(reader_->AttrSection())) {
357       return false;
358     }
359     if (!reader_->ReadDataSection(
360             [&](std::unique_ptr<Record> r) { return ProcessRecord(std::move(r)); })) {
361       return false;
362     }
363     return WriteFeatureSections();
364   }
365 
ProcessRecord(std::unique_ptr<Record> r)366   bool ProcessRecord(std::unique_ptr<Record> r) {
367     thread_tree_.Update(*r);
368     bool keep_record = false;
369     if (r->type() == SIMPLE_PERF_RECORD_UNWINDING_RESULT) {
370       keep_record = (sample_times_.count(r->Timestamp()) > 0);
371     } else if (r->type() == PERF_RECORD_SAMPLE) {
372       keep_record = (sample_times_.count(r->Timestamp()) > 0);
373       if (keep_record) {
374         // Dump maps needed to unwind this sample.
375         if (!WriteMapsForSample(*static_cast<SampleRecord*>(r.get()))) {
376           return false;
377         }
378       }
379     }
380     if (keep_record) {
381       return writer_->WriteRecord(*r);
382     }
383     return true;
384   }
385 
WriteMapsForSample(const SampleRecord & r)386   bool WriteMapsForSample(const SampleRecord& r) {
387     ThreadEntry* thread = thread_tree_.FindThread(r.tid_data.tid);
388     if (thread != nullptr && thread->maps) {
389       auto attr = reader_->AttrSection()[0].attr;
390       auto event_id = reader_->AttrSection()[0].ids[0];
391 
392       size_t kernel_ip_count;
393       std::vector<uint64_t> ips = r.GetCallChain(&kernel_ip_count);
394       for (size_t i = kernel_ip_count; i < ips.size(); i++) {
395         const MapEntry* map = thread_tree_.FindMap(thread, ips[i], false);
396         if (!thread_tree_.IsUnknownDso(map->dso)) {
397           Mmap2Record map_record(*attr, false, r.tid_data.pid, r.tid_data.tid, map->start_addr,
398                                  map->len, map->pgoff, map->flags, map->dso->Path(), event_id,
399                                  r.Timestamp());
400           if (!writer_->WriteRecord(map_record)) {
401             return false;
402           }
403         }
404       }
405     }
406     return true;
407   }
408 
WriteFeatureSections()409   bool WriteFeatureSections() {
410     if (!writer_->BeginWriteFeatures(reader_->FeatureSectionDescriptors().size())) {
411       return false;
412     }
413     std::unordered_set<int> feature_types_to_copy = {
414         PerfFileFormat::FEAT_ARCH, PerfFileFormat::FEAT_CMDLINE, PerfFileFormat::FEAT_META_INFO};
415     const size_t BUFFER_SIZE = 64 * 1024;
416     std::string buffer(BUFFER_SIZE, '\0');
417     for (const auto& p : reader_->FeatureSectionDescriptors()) {
418       auto feat_type = p.first;
419       if (feat_type == PerfFileFormat::FEAT_DEBUG_UNWIND) {
420         DebugUnwindFeature feature;
421         buffer.resize(BUFFER_SIZE);
422         for (const auto& file_p : debug_unwind_files_) {
423           if (kept_binaries_.count(file_p.first)) {
424             feature.resize(feature.size() + 1);
425             feature.back().path = file_p.first;
426             feature.back().size = file_p.second.size;
427             if (!CopyDebugUnwindFile(file_p.second, buffer)) {
428               return false;
429             }
430           }
431         }
432         if (!writer_->WriteDebugUnwindFeature(feature)) {
433           return false;
434         }
435       } else if (feat_type == PerfFileFormat::FEAT_FILE) {
436         size_t read_pos = 0;
437         FileFeature file_feature;
438         while (reader_->ReadFileFeature(read_pos, &file_feature)) {
439           if (kept_binaries_.count(file_feature.path) && !writer_->WriteFileFeature(file_feature)) {
440             return false;
441           }
442         }
443       } else if (feat_type == PerfFileFormat::FEAT_BUILD_ID) {
444         std::vector<BuildIdRecord> build_ids = reader_->ReadBuildIdFeature();
445         std::vector<BuildIdRecord> write_build_ids;
446         for (auto& build_id : build_ids) {
447           if (kept_binaries_.count(build_id.filename)) {
448             write_build_ids.emplace_back(std::move(build_id));
449           }
450         }
451         if (!writer_->WriteBuildIdFeature(write_build_ids)) {
452           return false;
453         }
454       } else if (feature_types_to_copy.count(feat_type)) {
455         if (!reader_->ReadFeatureSection(feat_type, &buffer) ||
456             !writer_->WriteFeature(feat_type, buffer.data(), buffer.size())) {
457           return false;
458         }
459       }
460     }
461     return writer_->EndWriteFeatures() && writer_->Close();
462   }
463 
CopyDebugUnwindFile(const DebugUnwindFileLocation & loc,std::string & buffer)464   bool CopyDebugUnwindFile(const DebugUnwindFileLocation& loc, std::string& buffer) {
465     uint64_t offset = loc.offset;
466     uint64_t left_size = loc.size;
467     while (left_size > 0) {
468       size_t nread = std::min<size_t>(left_size, buffer.size());
469       if (!reader_->ReadAtOffset(offset, buffer.data(), nread) ||
470           !writer_->WriteFeature(PerfFileFormat::FEAT_DEBUG_UNWIND_FILE, buffer.data(), nread)) {
471         return false;
472       }
473       offset += nread;
474       left_size -= nread;
475     }
476     return true;
477   }
478 
479  private:
480   const std::unordered_set<uint64_t> sample_times_;
481   const std::unordered_set<std::string> kept_binaries_;
482   std::unique_ptr<RecordFileWriter> writer_;
483 };
484 
485 class ReportGenerator : public RecordFileProcessor {
486  public:
ReportGenerator(const std::string & output_filename)487   ReportGenerator(const std::string& output_filename)
488       : RecordFileProcessor(output_filename, false) {}
489 
490  protected:
CheckRecordCmd(const std::string & record_cmd)491   bool CheckRecordCmd(const std::string& record_cmd) override {
492     if (record_cmd.find("--keep-failed-unwinding-debug-info") == std::string::npos &&
493         record_cmd.find("--keep-failed-unwinding-result") == std::string::npos) {
494       LOG(ERROR) << "file isn't record with --keep-failed-unwinding-debug-info or "
495                  << "--keep-failed-unwinding-result: " << record_filename_;
496       return false;
497     }
498     return true;
499   }
500 
Process()501   bool Process() override {
502     if (!reader_->ReadDataSection(
503             [&](std::unique_ptr<Record> r) { return ProcessRecord(std::move(r)); })) {
504       return false;
505     }
506     return true;
507   }
508 
509  private:
ProcessRecord(std::unique_ptr<Record> r)510   bool ProcessRecord(std::unique_ptr<Record> r) {
511     thread_tree_.Update(*r);
512     if (r->type() == SIMPLE_PERF_RECORD_UNWINDING_RESULT) {
513       last_unwinding_result_.reset(static_cast<UnwindingResultRecord*>(r.release()));
514     } else if (r->type() == PERF_RECORD_SAMPLE) {
515       if (last_unwinding_result_) {
516         ReportUnwindingResult(*static_cast<SampleRecord*>(r.get()), *last_unwinding_result_);
517         last_unwinding_result_.reset();
518       }
519     }
520     return true;
521   }
522 
ReportUnwindingResult(const SampleRecord & sr,const UnwindingResultRecord & unwinding_r)523   void ReportUnwindingResult(const SampleRecord& sr, const UnwindingResultRecord& unwinding_r) {
524     ThreadEntry* thread = thread_tree_.FindThreadOrNew(sr.tid_data.pid, sr.tid_data.tid);
525     size_t kernel_ip_count;
526     std::vector<uint64_t> ips = sr.GetCallChain(&kernel_ip_count);
527     if (kernel_ip_count != 0) {
528       ips.erase(ips.begin(), ips.begin() + kernel_ip_count);
529     }
530 
531     fprintf(out_fp_, "sample_time: %" PRIu64 "\n", sr.Timestamp());
532     DumpUnwindingResult(unwinding_r.unwinding_result, out_fp_);
533     // Print callchain.
534     std::vector<CallChainReportEntry> entries = callchain_report_builder_.Build(thread, ips, 0);
535     for (size_t i = 0; i < entries.size(); i++) {
536       size_t id = i + 1;
537       const auto& entry = entries[i];
538       fprintf(out_fp_, "ip_%zu: 0x%" PRIx64 "\n", id, entry.ip);
539       if (i < unwinding_r.callchain.length) {
540         fprintf(out_fp_, "unwinding_ip_%zu: 0x%" PRIx64 "\n", id, unwinding_r.callchain.ips[i]);
541         fprintf(out_fp_, "unwinding_sp_%zu: 0x%" PRIx64 "\n", id, unwinding_r.callchain.sps[i]);
542       }
543       fprintf(out_fp_, "map_%zu: [0x%" PRIx64 "-0x%" PRIx64 "], pgoff 0x%" PRIx64 "\n", id,
544               entry.map->start_addr, entry.map->get_end_addr(), entry.map->pgoff);
545       fprintf(out_fp_, "dso_%zu: %s\n", id, entry.map->dso->Path().c_str());
546       fprintf(out_fp_, "vaddr_in_file_%zu: 0x%" PRIx64 "\n", id, entry.vaddr_in_file);
547       fprintf(out_fp_, "symbol_%zu: %s\n", id, entry.symbol->DemangledName());
548     }
549     // Print regs.
550     uint64_t stack_addr = 0;
551     if (unwinding_r.regs_user_data.reg_nr > 0) {
552       auto& reg_data = unwinding_r.regs_user_data;
553       RegSet regs(reg_data.abi, reg_data.reg_mask, reg_data.regs);
554       uint64_t value;
555       if (regs.GetSpRegValue(&value)) {
556         stack_addr = value;
557         for (size_t i = 0; i < 64; i++) {
558           if (regs.GetRegValue(i, &value)) {
559             fprintf(out_fp_, "reg_%s: 0x%" PRIx64 "\n", GetRegName(i, regs.arch).c_str(), value);
560           }
561         }
562       }
563     }
564     // Print stack.
565     if (unwinding_r.stack_user_data.size > 0) {
566       auto& stack = unwinding_r.stack_user_data;
567       const char* p = stack.data;
568       const char* end = stack.data + stack.size;
569       uint64_t value;
570       while (p + 8 <= end) {
571         fprintf(out_fp_, "stack_%" PRIx64 ":", stack_addr);
572         for (size_t i = 0; i < 4 && p + 8 <= end; ++i) {
573           MoveFromBinaryFormat(value, p);
574           fprintf(out_fp_, " %016" PRIx64, value);
575         }
576         fprintf(out_fp_, "\n");
577         stack_addr += 32;
578       }
579       fprintf(out_fp_, "\n");
580     }
581   }
582 
583   std::unique_ptr<UnwindingResultRecord> last_unwinding_result_;
584 };
585 
586 class DebugUnwindCommand : public Command {
587  public:
DebugUnwindCommand()588   DebugUnwindCommand()
589       : Command(
590             "debug-unwind", "Debug/test offline unwinding.",
591             // clang-format off
592 "Usage: simpleperf debug-unwind [options]\n"
593 "--generate-report         Generate a failed unwinding report.\n"
594 "--generate-test-file      Generate a test file with only one sample.\n"
595 "-i <file>                 Input recording file. Default is perf.data.\n"
596 "-o <file>                 Output file. Default is stdout.\n"
597 "--keep-binaries-in-test-file  binary1,binary2...   Keep binaries in test file.\n"
598 "--sample-time time1,time2...      Only process samples recorded at selected times.\n"
599 "--symfs <dir>                     Look for files with symbols relative to this directory.\n"
600 "--unwind-sample                   Unwind samples.\n"
601 "\n"
602 "Examples:\n"
603 "1. Unwind a sample.\n"
604 "$ simpleperf debug-unwind -i perf.data --unwind-sample --sample-time 626970493946976\n"
605 "  perf.data should be generated with \"--no-unwind\" or \"--keep-failed-unwinding-debug-info\".\n"
606 "2. Generate a test file.\n"
607 "$ simpleperf debug-unwind -i perf.data --generate-test-file -o test.data --sample-time \\\n"
608 "     626970493946976 --keep-binaries-in-test-file perf.data_jit_app_cache:255984-259968\n"
609 "3. Generate a failed unwinding report.\n"
610 "$ simpleperf debug-unwind -i perf.data --generate-report -o report.txt\n"
611 "  perf.data should be generated with \"--keep-failed-unwinding-debug-info\" or \\\n"
612 "  \"--keep-failed-unwinding-result\".\n"
613 "\n"
614             // clang-format on
615         ) {}
616 
617   bool Run(const std::vector<std::string>& args);
618 
619  private:
620   bool ParseOptions(const std::vector<std::string>& args);
621 
622   std::string input_filename_ = "perf.data";
623   std::string output_filename_;
624   bool unwind_sample_ = false;
625   bool generate_report_ = false;
626   bool generate_test_file_;
627   std::unordered_set<std::string> kept_binaries_in_test_file_;
628   std::unordered_set<uint64_t> sample_times_;
629 };
630 
Run(const std::vector<std::string> & args)631 bool DebugUnwindCommand::Run(const std::vector<std::string>& args) {
632   // 1. Parse options.
633   if (!ParseOptions(args)) {
634     return false;
635   }
636 
637   // 2. Distribute sub commands.
638   if (unwind_sample_) {
639     SampleUnwinder sample_unwinder(output_filename_, sample_times_);
640     return sample_unwinder.ProcessFile(input_filename_);
641   }
642   if (generate_test_file_) {
643     TestFileGenerator test_file_generator(output_filename_, sample_times_,
644                                           kept_binaries_in_test_file_);
645     return test_file_generator.ProcessFile(input_filename_);
646   }
647   if (generate_report_) {
648     ReportGenerator report_generator(output_filename_);
649     return report_generator.ProcessFile(input_filename_);
650   }
651   return true;
652 }
653 
ParseOptions(const std::vector<std::string> & args)654 bool DebugUnwindCommand::ParseOptions(const std::vector<std::string>& args) {
655   const OptionFormatMap option_formats = {
656       {"--generate-report", {OptionValueType::NONE, OptionType::SINGLE}},
657       {"--generate-test-file", {OptionValueType::NONE, OptionType::SINGLE}},
658       {"-i", {OptionValueType::STRING, OptionType::SINGLE}},
659       {"--keep-binaries-in-test-file", {OptionValueType::STRING, OptionType::MULTIPLE}},
660       {"-o", {OptionValueType::STRING, OptionType::SINGLE}},
661       {"--sample-time", {OptionValueType::STRING, OptionType::MULTIPLE}},
662       {"--symfs", {OptionValueType::STRING, OptionType::MULTIPLE}},
663       {"--unwind-sample", {OptionValueType::NONE, OptionType::SINGLE}},
664   };
665   OptionValueMap options;
666   std::vector<std::pair<OptionName, OptionValue>> ordered_options;
667   if (!PreprocessOptions(args, option_formats, &options, &ordered_options)) {
668     return false;
669   }
670   generate_report_ = options.PullBoolValue("--generate-report");
671   generate_test_file_ = options.PullBoolValue("--generate-test-file");
672   options.PullStringValue("-i", &input_filename_);
673   for (auto& value : options.PullValues("--keep-binaries-in-test-file")) {
674     std::vector<std::string> binaries = android::base::Split(*value.str_value, ",");
675     kept_binaries_in_test_file_.insert(binaries.begin(), binaries.end());
676   }
677   options.PullStringValue("-o", &output_filename_);
678   for (auto& value : options.PullValues("--sample-time")) {
679     auto times = ParseUintVector<uint64_t>(*value.str_value);
680     if (!times) {
681       return false;
682     }
683     sample_times_.insert(times.value().begin(), times.value().end());
684   }
685   if (auto value = options.PullValue("--symfs"); value) {
686     if (!Dso::SetSymFsDir(*value->str_value)) {
687       return false;
688     }
689   }
690   unwind_sample_ = options.PullBoolValue("--unwind-sample");
691   CHECK(options.values.empty());
692 
693   if (generate_test_file_) {
694     if (output_filename_.empty()) {
695       LOG(ERROR) << "no output path for generated test file";
696       return false;
697     }
698     if (sample_times_.empty()) {
699       LOG(ERROR) << "no samples are selected via --sample-time";
700       return false;
701     }
702   }
703 
704   return true;
705 }
706 
707 }  // namespace
708 
RegisterDebugUnwindCommand()709 void RegisterDebugUnwindCommand() {
710   RegisterCommand("debug-unwind",
711                   [] { return std::unique_ptr<Command>(new DebugUnwindCommand()); });
712 }
713 
714 }  // namespace simpleperf
715