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