• 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::__anone671e40a0111::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::__anone671e40a0111::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::__anone671e40a0111::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,bool skip_sample_print)218   SampleUnwinder(const std::string& output_filename,
219                  const std::unordered_set<uint64_t>& sample_times, bool skip_sample_print)
220       : RecordFileProcessor(output_filename, false),
221         sample_times_(sample_times),
222         skip_sample_print_(skip_sample_print) {}
223 
224  protected:
CheckRecordCmd(const std::string & record_cmd)225   bool CheckRecordCmd(const std::string& record_cmd) override {
226     if (record_cmd.find("--no-unwind") == std::string::npos &&
227         record_cmd.find("--keep-failed-unwinding-debug-info") == std::string::npos) {
228       LOG(ERROR) << "file isn't record with --no-unwind or --keep-failed-unwinding-debug-info: "
229                  << record_filename_;
230       return false;
231     }
232     return true;
233   }
234 
Process()235   bool Process() override {
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     UpdateRecord(r.get());
252     thread_tree_.Update(*r);
253     if (r->type() == SIMPLE_PERF_RECORD_UNWINDING_RESULT) {
254       last_unwinding_result_.reset(static_cast<UnwindingResultRecord*>(r.release()));
255     } else if (r->type() == PERF_RECORD_SAMPLE) {
256       if (sample_times_.empty() || sample_times_.count(r->Timestamp())) {
257         auto& sr = *static_cast<SampleRecord*>(r.get());
258         const PerfSampleStackUserType* stack = &sr.stack_user_data;
259         const PerfSampleRegsUserType* regs = &sr.regs_user_data;
260         if (last_unwinding_result_ && last_unwinding_result_->Timestamp() == sr.Timestamp()) {
261           stack = &last_unwinding_result_->stack_user_data;
262           regs = &last_unwinding_result_->regs_user_data;
263         }
264         if (stack->size > 0 || regs->reg_mask > 0) {
265           if (!UnwindRecord(sr, *regs, *stack)) {
266             return false;
267           }
268         }
269       }
270       last_unwinding_result_.reset();
271     }
272     return true;
273   }
274 
UpdateRecord(Record * record)275   void UpdateRecord(Record* record) {
276     if (record->type() == PERF_RECORD_MMAP) {
277       UpdateMmapRecordForEmbeddedFiles(*static_cast<MmapRecord*>(record));
278     } else if (record->type() == PERF_RECORD_MMAP2) {
279       UpdateMmapRecordForEmbeddedFiles(*static_cast<Mmap2Record*>(record));
280     }
281   }
282 
283   template <typename MmapRecordType>
UpdateMmapRecordForEmbeddedFiles(MmapRecordType & record)284   void UpdateMmapRecordForEmbeddedFiles(MmapRecordType& record) {
285     // Modify mmap records to point to files stored in DEBUG_UNWIND_FILE feature section.
286     std::string filename = record.filename;
287     if (auto it = debug_unwind_files_.find(filename); it != debug_unwind_files_.end()) {
288       auto data = *record.data;
289       uint64_t old_pgoff = data.pgoff;
290       if (JITDebugReader::IsPathInJITSymFile(filename)) {
291         data.pgoff = it->second.offset;
292       } else {
293         data.pgoff += it->second.offset;
294       }
295       debug_unwind_dsos_[data.pgoff] =
296           std::make_pair(thread_tree_.FindUserDsoOrNew(filename), old_pgoff);
297       record.SetDataAndFilename(data, record_filename_);
298     }
299   }
300 
UnwindRecord(const SampleRecord & r,const PerfSampleRegsUserType & regs,const PerfSampleStackUserType & stack)301   bool UnwindRecord(const SampleRecord& r, const PerfSampleRegsUserType& regs,
302                     const PerfSampleStackUserType& stack) {
303     ThreadEntry* thread = thread_tree_.FindThreadOrNew(r.tid_data.pid, r.tid_data.tid);
304 
305     RegSet reg_set(regs.abi, regs.reg_mask, regs.regs);
306     std::vector<uint64_t> ips;
307     std::vector<uint64_t> sps;
308     if (!unwinder_->UnwindCallChain(*thread, reg_set, stack.data, stack.size, &ips, &sps)) {
309       return false;
310     }
311     stat_.AddUnwindingResult(unwinder_->GetUnwindingResult());
312 
313     if (!skip_sample_print_) {
314       // Print unwinding result.
315       fprintf(out_fp_, "sample_time: %" PRIu64 "\n", r.Timestamp());
316       DumpUnwindingResult(unwinder_->GetUnwindingResult(), out_fp_);
317       std::vector<CallChainReportEntry> entries = callchain_report_builder_.Build(thread, ips, 0);
318       for (size_t i = 0; i < entries.size(); i++) {
319         size_t id = i + 1;
320         auto& entry = entries[i];
321         fprintf(out_fp_, "ip_%zu: 0x%" PRIx64 "\n", id, entry.ip);
322         fprintf(out_fp_, "sp_%zu: 0x%" PRIx64 "\n", id, sps[i]);
323 
324         Dso* dso = entry.map->dso;
325         uint64_t pgoff = entry.map->pgoff;
326         if (dso->Path() == record_filename_) {
327           auto it = debug_unwind_dsos_.find(entry.map->pgoff);
328           CHECK(it != debug_unwind_dsos_.end());
329           const auto& p = it->second;
330           dso = p.first;
331           pgoff = p.second;
332           if (!JITDebugReader::IsPathInJITSymFile(dso->Path())) {
333             entry.vaddr_in_file = dso->IpToVaddrInFile(entry.ip, entry.map->start_addr, pgoff);
334           }
335           entry.symbol = dso->FindSymbol(entry.vaddr_in_file);
336         }
337         fprintf(out_fp_, "map_%zu: [0x%" PRIx64 "-0x%" PRIx64 "], pgoff 0x%" PRIx64 "\n", id,
338                 entry.map->start_addr, entry.map->get_end_addr(), pgoff);
339         fprintf(out_fp_, "dso_%zu: %s\n", id, dso->Path().c_str());
340         fprintf(out_fp_, "vaddr_in_file_%zu: 0x%" PRIx64 "\n", id, entry.vaddr_in_file);
341         fprintf(out_fp_, "symbol_%zu: %s\n", id, entry.symbol->DemangledName());
342       }
343       fprintf(out_fp_, "\n");
344     }
345     return true;
346   }
347 
348  private:
349   const std::unordered_set<uint64_t> sample_times_;
350   bool skip_sample_print_;
351   // Map from offset in recording file to the corresponding debug_unwind_file.
352   std::unordered_map<uint64_t, std::pair<Dso*, uint64_t>> debug_unwind_dsos_;
353   UnwindingStat stat_;
354   std::unique_ptr<UnwindingResultRecord> last_unwinding_result_;
355 };
356 
357 class TestFileGenerator : public RecordFileProcessor {
358  public:
TestFileGenerator(const std::string & output_filename,const std::unordered_set<uint64_t> & sample_times,const std::unordered_set<std::string> & kept_binaries)359   TestFileGenerator(const std::string& output_filename,
360                     const std::unordered_set<uint64_t>& sample_times,
361                     const std::unordered_set<std::string>& kept_binaries)
362       : RecordFileProcessor(output_filename, true),
363         sample_times_(sample_times),
364         kept_binaries_(kept_binaries) {}
365 
366  protected:
CheckRecordCmd(const std::string &)367   bool CheckRecordCmd(const std::string&) override { return true; }
368 
Process()369   bool Process() override {
370     writer_.reset(new RecordFileWriter(output_filename_, out_fp_, false));
371     if (!writer_ || !writer_->WriteAttrSection(reader_->AttrSection())) {
372       return false;
373     }
374     if (!reader_->ReadDataSection(
375             [&](std::unique_ptr<Record> r) { return ProcessRecord(std::move(r)); })) {
376       return false;
377     }
378     return WriteFeatureSections();
379   }
380 
ProcessRecord(std::unique_ptr<Record> r)381   bool ProcessRecord(std::unique_ptr<Record> r) {
382     thread_tree_.Update(*r);
383     bool keep_record = false;
384     if (r->type() == SIMPLE_PERF_RECORD_UNWINDING_RESULT) {
385       keep_record = (sample_times_.count(r->Timestamp()) > 0);
386     } else if (r->type() == PERF_RECORD_SAMPLE) {
387       keep_record = (sample_times_.count(r->Timestamp()) > 0);
388       if (keep_record) {
389         // Dump maps needed to unwind this sample.
390         if (!WriteMapsForSample(*static_cast<SampleRecord*>(r.get()))) {
391           return false;
392         }
393       }
394     }
395     if (keep_record) {
396       return writer_->WriteRecord(*r);
397     }
398     return true;
399   }
400 
WriteMapsForSample(const SampleRecord & r)401   bool WriteMapsForSample(const SampleRecord& r) {
402     ThreadEntry* thread = thread_tree_.FindThread(r.tid_data.tid);
403     if (thread != nullptr && thread->maps) {
404       auto attr = reader_->AttrSection()[0].attr;
405       auto event_id = reader_->AttrSection()[0].ids[0];
406 
407       for (const auto& p : thread->maps->maps) {
408         const MapEntry* map = p.second;
409         Mmap2Record map_record(*attr, false, r.tid_data.pid, r.tid_data.tid, map->start_addr,
410                                map->len, map->pgoff, map->flags, map->dso->Path(), event_id,
411                                r.Timestamp());
412         if (!writer_->WriteRecord(map_record)) {
413           return false;
414         }
415       }
416     }
417     return true;
418   }
419 
WriteFeatureSections()420   bool WriteFeatureSections() {
421     if (!writer_->BeginWriteFeatures(reader_->FeatureSectionDescriptors().size())) {
422       return false;
423     }
424     std::unordered_set<int> feature_types_to_copy = {
425         PerfFileFormat::FEAT_ARCH, PerfFileFormat::FEAT_CMDLINE, PerfFileFormat::FEAT_META_INFO};
426     const size_t BUFFER_SIZE = 64 * 1024;
427     std::string buffer(BUFFER_SIZE, '\0');
428     for (const auto& p : reader_->FeatureSectionDescriptors()) {
429       auto feat_type = p.first;
430       if (feat_type == PerfFileFormat::FEAT_DEBUG_UNWIND) {
431         DebugUnwindFeature feature;
432         buffer.resize(BUFFER_SIZE);
433         for (const auto& file_p : debug_unwind_files_) {
434           if (kept_binaries_.count(file_p.first)) {
435             feature.resize(feature.size() + 1);
436             feature.back().path = file_p.first;
437             feature.back().size = file_p.second.size;
438             if (!CopyDebugUnwindFile(file_p.second, buffer)) {
439               return false;
440             }
441           }
442         }
443         if (!writer_->WriteDebugUnwindFeature(feature)) {
444           return false;
445         }
446       } else if (feat_type == PerfFileFormat::FEAT_FILE ||
447                  feat_type == PerfFileFormat::FEAT_FILE2) {
448         size_t read_pos = 0;
449         FileFeature file_feature;
450         while (reader_->ReadFileFeature(read_pos, &file_feature)) {
451           if (kept_binaries_.count(file_feature.path) && !writer_->WriteFileFeature(file_feature)) {
452             return false;
453           }
454         }
455       } else if (feat_type == PerfFileFormat::FEAT_BUILD_ID) {
456         std::vector<BuildIdRecord> build_ids = reader_->ReadBuildIdFeature();
457         std::vector<BuildIdRecord> write_build_ids;
458         for (auto& build_id : build_ids) {
459           if (kept_binaries_.count(build_id.filename)) {
460             write_build_ids.emplace_back(std::move(build_id));
461           }
462         }
463         if (!writer_->WriteBuildIdFeature(write_build_ids)) {
464           return false;
465         }
466       } else if (feature_types_to_copy.count(feat_type)) {
467         if (!reader_->ReadFeatureSection(feat_type, &buffer) ||
468             !writer_->WriteFeature(feat_type, buffer.data(), buffer.size())) {
469           return false;
470         }
471       }
472     }
473     return writer_->EndWriteFeatures() && writer_->Close();
474   }
475 
CopyDebugUnwindFile(const DebugUnwindFileLocation & loc,std::string & buffer)476   bool CopyDebugUnwindFile(const DebugUnwindFileLocation& loc, std::string& buffer) {
477     uint64_t offset = loc.offset;
478     uint64_t left_size = loc.size;
479     while (left_size > 0) {
480       size_t nread = std::min<size_t>(left_size, buffer.size());
481       if (!reader_->ReadAtOffset(offset, buffer.data(), nread) ||
482           !writer_->WriteFeature(PerfFileFormat::FEAT_DEBUG_UNWIND_FILE, buffer.data(), nread)) {
483         return false;
484       }
485       offset += nread;
486       left_size -= nread;
487     }
488     return true;
489   }
490 
491  private:
492   const std::unordered_set<uint64_t> sample_times_;
493   const std::unordered_set<std::string> kept_binaries_;
494   std::unique_ptr<RecordFileWriter> writer_;
495 };
496 
497 class ReportGenerator : public RecordFileProcessor {
498  public:
ReportGenerator(const std::string & output_filename)499   ReportGenerator(const std::string& output_filename)
500       : RecordFileProcessor(output_filename, false) {}
501 
502  protected:
CheckRecordCmd(const std::string & record_cmd)503   bool CheckRecordCmd(const std::string& record_cmd) override {
504     if (record_cmd.find("--keep-failed-unwinding-debug-info") == std::string::npos &&
505         record_cmd.find("--keep-failed-unwinding-result") == std::string::npos) {
506       LOG(ERROR) << "file isn't record with --keep-failed-unwinding-debug-info or "
507                  << "--keep-failed-unwinding-result: " << record_filename_;
508       return false;
509     }
510     return true;
511   }
512 
Process()513   bool Process() override {
514     if (!reader_->ReadDataSection(
515             [&](std::unique_ptr<Record> r) { return ProcessRecord(std::move(r)); })) {
516       return false;
517     }
518     return true;
519   }
520 
521  private:
ProcessRecord(std::unique_ptr<Record> r)522   bool ProcessRecord(std::unique_ptr<Record> r) {
523     thread_tree_.Update(*r);
524     if (r->type() == SIMPLE_PERF_RECORD_UNWINDING_RESULT) {
525       last_unwinding_result_.reset(static_cast<UnwindingResultRecord*>(r.release()));
526     } else if (r->type() == PERF_RECORD_SAMPLE) {
527       if (last_unwinding_result_) {
528         ReportUnwindingResult(*static_cast<SampleRecord*>(r.get()), *last_unwinding_result_);
529         last_unwinding_result_.reset();
530       }
531     }
532     return true;
533   }
534 
ReportUnwindingResult(const SampleRecord & sr,const UnwindingResultRecord & unwinding_r)535   void ReportUnwindingResult(const SampleRecord& sr, const UnwindingResultRecord& unwinding_r) {
536     ThreadEntry* thread = thread_tree_.FindThreadOrNew(sr.tid_data.pid, sr.tid_data.tid);
537     size_t kernel_ip_count;
538     std::vector<uint64_t> ips = sr.GetCallChain(&kernel_ip_count);
539     if (kernel_ip_count != 0) {
540       ips.erase(ips.begin(), ips.begin() + kernel_ip_count);
541     }
542 
543     fprintf(out_fp_, "sample_time: %" PRIu64 "\n", sr.Timestamp());
544     DumpUnwindingResult(unwinding_r.unwinding_result, out_fp_);
545     // Print callchain.
546     std::vector<CallChainReportEntry> entries = callchain_report_builder_.Build(thread, ips, 0);
547     for (size_t i = 0; i < entries.size(); i++) {
548       size_t id = i + 1;
549       const auto& entry = entries[i];
550       fprintf(out_fp_, "ip_%zu: 0x%" PRIx64 "\n", id, entry.ip);
551       if (i < unwinding_r.callchain.length) {
552         fprintf(out_fp_, "unwinding_ip_%zu: 0x%" PRIx64 "\n", id, unwinding_r.callchain.ips[i]);
553         fprintf(out_fp_, "unwinding_sp_%zu: 0x%" PRIx64 "\n", id, unwinding_r.callchain.sps[i]);
554       }
555       fprintf(out_fp_, "map_%zu: [0x%" PRIx64 "-0x%" PRIx64 "], pgoff 0x%" PRIx64 "\n", id,
556               entry.map->start_addr, entry.map->get_end_addr(), entry.map->pgoff);
557       fprintf(out_fp_, "dso_%zu: %s\n", id, entry.map->dso->Path().c_str());
558       fprintf(out_fp_, "vaddr_in_file_%zu: 0x%" PRIx64 "\n", id, entry.vaddr_in_file);
559       fprintf(out_fp_, "symbol_%zu: %s\n", id, entry.symbol->DemangledName());
560     }
561     // Print regs.
562     uint64_t stack_addr = 0;
563     if (unwinding_r.regs_user_data.reg_nr > 0) {
564       auto& reg_data = unwinding_r.regs_user_data;
565       RegSet regs(reg_data.abi, reg_data.reg_mask, reg_data.regs);
566       uint64_t value;
567       if (regs.GetSpRegValue(&value)) {
568         stack_addr = value;
569         for (size_t i = 0; i < 64; i++) {
570           if (regs.GetRegValue(i, &value)) {
571             fprintf(out_fp_, "reg_%s: 0x%" PRIx64 "\n", GetRegName(i, regs.arch).c_str(), value);
572           }
573         }
574       }
575     }
576     // Print stack.
577     if (unwinding_r.stack_user_data.size > 0) {
578       auto& stack = unwinding_r.stack_user_data;
579       const char* p = stack.data;
580       const char* end = stack.data + stack.size;
581       uint64_t value;
582       while (p + 8 <= end) {
583         fprintf(out_fp_, "stack_%" PRIx64 ":", stack_addr);
584         for (size_t i = 0; i < 4 && p + 8 <= end; ++i) {
585           MoveFromBinaryFormat(value, p);
586           fprintf(out_fp_, " %016" PRIx64, value);
587         }
588         fprintf(out_fp_, "\n");
589         stack_addr += 32;
590       }
591       fprintf(out_fp_, "\n");
592     }
593   }
594 
595   std::unique_ptr<UnwindingResultRecord> last_unwinding_result_;
596 };
597 
598 class DebugUnwindCommand : public Command {
599  public:
DebugUnwindCommand()600   DebugUnwindCommand()
601       : Command(
602             "debug-unwind", "Debug/test offline unwinding.",
603             // clang-format off
604 "Usage: simpleperf debug-unwind [options]\n"
605 "--generate-report         Generate a failed unwinding report.\n"
606 "--generate-test-file      Generate a test file with only one sample.\n"
607 "-i <file>                 Input recording file. Default is perf.data.\n"
608 "-o <file>                 Output file. Default is stdout.\n"
609 "--keep-binaries-in-test-file  binary1,binary2...   Keep binaries in test file.\n"
610 "--sample-time time1,time2...      Only process samples recorded at selected times.\n"
611 "--symfs <dir>                     Look for files with symbols relative to this directory.\n"
612 "--unwind-sample                   Unwind samples.\n"
613 "--skip-sample-print               Skip printing unwound samples.\n"
614 "\n"
615 "Examples:\n"
616 "1. Unwind a sample.\n"
617 "$ simpleperf debug-unwind -i perf.data --unwind-sample --sample-time 626970493946976\n"
618 "  perf.data should be generated with \"--no-unwind\" or \"--keep-failed-unwinding-debug-info\".\n"
619 "2. Generate a test file.\n"
620 "$ simpleperf debug-unwind -i perf.data --generate-test-file -o test.data --sample-time \\\n"
621 "     626970493946976 --keep-binaries-in-test-file perf.data_jit_app_cache:255984-259968\n"
622 "3. Generate a failed unwinding report.\n"
623 "$ simpleperf debug-unwind -i perf.data --generate-report -o report.txt\n"
624 "  perf.data should be generated with \"--keep-failed-unwinding-debug-info\" or \\\n"
625 "  \"--keep-failed-unwinding-result\".\n"
626 "\n"
627             // clang-format on
628         ) {}
629 
630   bool Run(const std::vector<std::string>& args);
631 
632  private:
633   bool ParseOptions(const std::vector<std::string>& args);
634 
635   std::string input_filename_ = "perf.data";
636   std::string output_filename_;
637   bool unwind_sample_ = false;
638   bool skip_sample_print_ = false;
639   bool generate_report_ = false;
640   bool generate_test_file_;
641   std::unordered_set<std::string> kept_binaries_in_test_file_;
642   std::unordered_set<uint64_t> sample_times_;
643 };
644 
Run(const std::vector<std::string> & args)645 bool DebugUnwindCommand::Run(const std::vector<std::string>& args) {
646   // 1. Parse options.
647   if (!ParseOptions(args)) {
648     return false;
649   }
650 
651   // 2. Distribute sub commands.
652   if (unwind_sample_) {
653     SampleUnwinder sample_unwinder(output_filename_, sample_times_, skip_sample_print_);
654     return sample_unwinder.ProcessFile(input_filename_);
655   }
656   if (generate_test_file_) {
657     TestFileGenerator test_file_generator(output_filename_, sample_times_,
658                                           kept_binaries_in_test_file_);
659     return test_file_generator.ProcessFile(input_filename_);
660   }
661   if (generate_report_) {
662     ReportGenerator report_generator(output_filename_);
663     return report_generator.ProcessFile(input_filename_);
664   }
665   return true;
666 }
667 
ParseOptions(const std::vector<std::string> & args)668 bool DebugUnwindCommand::ParseOptions(const std::vector<std::string>& args) {
669   const OptionFormatMap option_formats = {
670       {"--generate-report", {OptionValueType::NONE, OptionType::SINGLE}},
671       {"--generate-test-file", {OptionValueType::NONE, OptionType::SINGLE}},
672       {"-i", {OptionValueType::STRING, OptionType::SINGLE}},
673       {"--keep-binaries-in-test-file", {OptionValueType::STRING, OptionType::MULTIPLE}},
674       {"-o", {OptionValueType::STRING, OptionType::SINGLE}},
675       {"--sample-time", {OptionValueType::STRING, OptionType::MULTIPLE}},
676       {"--skip-sample-print", {OptionValueType::NONE, OptionType::SINGLE}},
677       {"--symfs", {OptionValueType::STRING, OptionType::MULTIPLE}},
678       {"--unwind-sample", {OptionValueType::NONE, OptionType::SINGLE}},
679   };
680   OptionValueMap options;
681   std::vector<std::pair<OptionName, OptionValue>> ordered_options;
682   if (!PreprocessOptions(args, option_formats, &options, &ordered_options)) {
683     return false;
684   }
685   generate_report_ = options.PullBoolValue("--generate-report");
686   generate_test_file_ = options.PullBoolValue("--generate-test-file");
687   options.PullStringValue("-i", &input_filename_);
688   for (auto& value : options.PullValues("--keep-binaries-in-test-file")) {
689     std::vector<std::string> binaries = android::base::Split(*value.str_value, ",");
690     kept_binaries_in_test_file_.insert(binaries.begin(), binaries.end());
691   }
692   skip_sample_print_ = options.PullBoolValue("--skip-sample-print");
693   options.PullStringValue("-o", &output_filename_);
694   for (auto& value : options.PullValues("--sample-time")) {
695     auto times = ParseUintVector<uint64_t>(*value.str_value);
696     if (!times) {
697       return false;
698     }
699     sample_times_.insert(times.value().begin(), times.value().end());
700   }
701   if (auto value = options.PullValue("--symfs"); value) {
702     if (!Dso::SetSymFsDir(*value->str_value)) {
703       return false;
704     }
705   }
706   unwind_sample_ = options.PullBoolValue("--unwind-sample");
707   CHECK(options.values.empty());
708 
709   if (generate_test_file_) {
710     if (output_filename_.empty()) {
711       LOG(ERROR) << "no output path for generated test file";
712       return false;
713     }
714     if (sample_times_.empty()) {
715       LOG(ERROR) << "no samples are selected via --sample-time";
716       return false;
717     }
718   }
719 
720   return true;
721 }
722 
723 }  // namespace
724 
RegisterDebugUnwindCommand()725 void RegisterDebugUnwindCommand() {
726   RegisterCommand("debug-unwind",
727                   [] { return std::unique_ptr<Command>(new DebugUnwindCommand()); });
728 }
729 
730 }  // namespace simpleperf
731