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