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