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