From: Yabin Cui Date: Wed, 25 May 2016 21:08:05 +0000 (-0700) Subject: simpleperf: record kernel symbols in perf.data. X-Git-Tag: android-x86-8.1-r1~68^2~140^2^2~19^2~90^2 X-Git-Url: http://git.osdn.net/view?a=commitdiff_plain;h=b42129797f17f990ea5a88c6bdc7446000cc361c;p=android-x86%2Fsystem-extras.git simpleperf: record kernel symbols in perf.data. To better support kernel profiling, record kernel symbols in perf.data when necessary. An option --no-dump-kernel-symbols is added in record command to always avoid recording kernel symbols. The way to handle all zero /proc/modules and /proc/kallsyms is improved. Add Better support in finding symbols for kernel modules. Bug: 27403614 Change-Id: I470151c54f8a45ad1c101c1b94490e33d7fd7485 --- diff --git a/simpleperf/Android.mk b/simpleperf/Android.mk index 842991e3..ebd0aa93 100644 --- a/simpleperf/Android.mk +++ b/simpleperf/Android.mk @@ -192,6 +192,7 @@ simpleperf_unit_test_src_files := \ read_elf_test.cpp \ record_test.cpp \ sample_tree_test.cpp \ + utils_test.cpp \ simpleperf_unit_test_src_files_linux := \ cmd_dumprecord_test.cpp \ diff --git a/simpleperf/cmd_record.cpp b/simpleperf/cmd_record.cpp index f3218d4e..83f60e73 100644 --- a/simpleperf/cmd_record.cpp +++ b/simpleperf/cmd_record.cpp @@ -25,6 +25,7 @@ #include #include +#include #include #include "command.h" @@ -53,65 +54,66 @@ static std::unordered_map branch_sampling_type_map = { }; static volatile bool signaled; -static void signal_handler(int) { - signaled = true; -} +static void signal_handler(int) { signaled = true; } class RecordCommand : public Command { public: RecordCommand() : Command( "record", "record sampling info in perf.data", - "Usage: simpleperf record [options] [command [command-args]]\n" - " Gather sampling information when running [command].\n" - " -a System-wide collection.\n" - " -b Enable take branch stack sampling. Same as '-j any'\n" - " -c count Set event sample period.\n" - " --call-graph fp | dwarf[,]\n" - " Enable call graph recording. Use frame pointer or dwarf as the\n" - " method to parse call graph in stack. Default is dwarf,8192.\n" - " --cpu cpu_item1,cpu_item2,...\n" - " Collect samples only on the selected cpus. cpu_item can be cpu\n" - " number like 1, or cpu range like 0-3.\n" - " -e event1[:modifier1],event2[:modifier2],...\n" - " Select the event list to sample. Use `simpleperf list` to find\n" - " all possible event names. Modifiers can be added to define\n" - " how the event should be monitored. Possible modifiers are:\n" - " u - monitor user space events only\n" - " k - monitor kernel space events only\n" - " -f freq Set event sample frequency.\n" - " -F freq Same as '-f freq'.\n" - " -g Same as '--call-graph dwarf'.\n" - " -j branch_filter1,branch_filter2,...\n" - " Enable taken branch stack sampling. Each sample\n" - " captures a series of consecutive taken branches.\n" - " The following filters are defined:\n" - " any: any type of branch\n" - " any_call: any function call or system call\n" - " any_ret: any function return or system call return\n" - " ind_call: any indirect branch\n" - " u: only when the branch target is at the user level\n" - " k: only when the branch target is in the kernel\n" - " This option requires at least one branch type among any,\n" - " any_call, any_ret, ind_call.\n" - " -m mmap_pages\n" - " Set the size of the buffer used to receiving sample data from\n" - " the kernel. It should be a power of 2. The default value is 16.\n" - " --no-inherit\n" - " Don't record created child threads/processes.\n" - " --no-unwind If `--call-graph dwarf` option is used, then the user's stack will\n" - " be unwound by default. Use this option to disable the unwinding of\n" - " the user's stack.\n" - " -o record_file_name Set record file name, default is perf.data.\n" - " -p pid1,pid2,...\n" - " Record events on existing processes. Mutually exclusive with -a.\n" - " --post-unwind\n" - " If `--call-graph dwarf` option is used, then the user's stack will\n" - " be unwound while recording by default. But it may lose records as\n" - " stacking unwinding can be time consuming. Use this option to unwind\n" - " the user's stack after recording.\n" - " -t tid1,tid2,...\n" - " Record events on existing threads. Mutually exclusive with -a.\n"), + // clang-format off +"Usage: simpleperf record [options] [command [command-args]]\n" +" Gather sampling information when running [command].\n" +"-a System-wide collection.\n" +"-b Enable take branch stack sampling. Same as '-j any'\n" +"-c count Set event sample period.\n" +"--call-graph fp | dwarf[,]\n" +" Enable call graph recording. Use frame pointer or dwarf debug\n" +" frame as the method to parse call graph in stack.\n" +" Default is dwarf,8192.\n" +"--cpu cpu_item1,cpu_item2,...\n" +" Collect samples only on the selected cpus. cpu_item can be cpu\n" +" number like 1, or cpu range like 0-3.\n" +"-e event1[:modifier1],event2[:modifier2],...\n" +" Select the event list to sample. Use `simpleperf list` to find\n" +" all possible event names. Modifiers can be added to define how\n" +" the event should be monitored.\n" +" Possible modifiers are:\n" +" u - monitor user space events only\n" +" k - monitor kernel space events only\n" +"-f freq Set event sample frequency.\n" +"-F freq Same as '-f freq'.\n" +"-g Same as '--call-graph dwarf'.\n" +"-j branch_filter1,branch_filter2,...\n" +" Enable taken branch stack sampling. Each sample captures a series\n" +" of consecutive taken branches.\n" +" The following filters are defined:\n" +" any: any type of branch\n" +" any_call: any function call or system call\n" +" any_ret: any function return or system call return\n" +" ind_call: any indirect branch\n" +" u: only when the branch target is at the user level\n" +" k: only when the branch target is in the kernel\n" +" This option requires at least one branch type among any, any_call,\n" +" any_ret, ind_call.\n" +"-m mmap_pages Set the size of the buffer used to receiving sample data from\n" +" the kernel. It should be a power of 2. The default value is 16.\n" +"--no-dump-kernel-symbols Don't dump kernel symbols in perf.data. By default\n" +" kernel symbols will be dumped when needed.\n" +"--no-inherit Don't record created child threads/processes.\n" +"--no-unwind If `--call-graph dwarf` option is used, then the user's stack\n" +" will be unwound by default. Use this option to disable the\n" +" unwinding of the user's stack.\n" +"-o record_file_name Set record file name, default is perf.data.\n" +"-p pid1,pid2,... Record events on existing processes. Mutually exclusive\n" +" with -a.\n" +"--post-unwind If `--call-graph dwarf` option is used, then the user's stack\n" +" will be unwound while recording by default. But it may lose\n" +" records as stacking unwinding can be time consuming. Use this\n" +" option to unwind the user's stack after recording.\n" +"-t tid1,tid2,... Record events on existing threads. Mutually exclusive with -a.\n" + // clang-format on + ), use_sample_freq_(true), sample_freq_(4000), system_wide_collection_(false), @@ -122,6 +124,7 @@ class RecordCommand : public Command { unwind_dwarf_callchain_(true), post_unwind_(false), child_inherit_(true), + can_dump_kernel_symbols_(true), perf_mmap_pages_(16), record_filename_("perf.data"), sample_record_count_(0) { @@ -133,14 +136,18 @@ class RecordCommand : public Command { bool Run(const std::vector& args); private: - bool ParseOptions(const std::vector& args, std::vector* non_option_args); + bool ParseOptions(const std::vector& args, + std::vector* non_option_args); bool AddMeasuredEventType(const std::string& event_type_name); bool SetEventSelection(); bool CreateAndInitRecordFile(); - std::unique_ptr CreateRecordFile(const std::string& filename); + std::unique_ptr CreateRecordFile( + const std::string& filename); + bool DumpKernelSymbol(); bool DumpKernelAndModuleMmaps(const perf_event_attr* attr, uint64_t event_id); bool DumpThreadCommAndMmaps(const perf_event_attr* attr, uint64_t event_id, - bool all_threads, const std::vector& selected_threads); + bool all_threads, + const std::vector& selected_threads); bool ProcessRecord(Record* record); void UpdateRecordForEmbeddedElfPath(Record* record); void UnwindRecord(Record* record); @@ -148,9 +155,10 @@ class RecordCommand : public Command { bool DumpAdditionalFeatures(const std::vector& args); bool DumpBuildIdFeature(); void CollectHitFileInfo(Record* record); - std::pair TestForEmbeddedElf(Dso *dso, uint64_t pgoff); + std::pair TestForEmbeddedElf(Dso* dso, uint64_t pgoff); - bool use_sample_freq_; // Use sample_freq_ when true, otherwise using sample_period_. + // Use sample_freq_ when true, otherwise using sample_period_. + bool use_sample_freq_; uint64_t sample_freq_; // Sample 'sample_freq_' times per second. uint64_t sample_period_; // Sample once when 'sample_period_' events occur. @@ -162,6 +170,7 @@ class RecordCommand : public Command { bool unwind_dwarf_callchain_; bool post_unwind_; bool child_inherit_; + bool can_dump_kernel_symbols_; std::vector monitored_threads_; std::vector cpus_; std::vector measured_event_types_; @@ -209,19 +218,21 @@ bool RecordCommand::Run(const std::vector& args) { monitored_threads_.push_back(workload->GetPid()); event_selection_set_.SetEnableOnExec(true); } else { - LOG(ERROR) << "No threads to monitor. Try `simpleperf help record` for help\n"; + LOG(ERROR) + << "No threads to monitor. Try `simpleperf help record` for help\n"; return false; } } - // 3. Open perf_event_files, create memory mapped buffers for perf_event_files, add prepare poll - // for perf_event_files. + // 3. Open perf_event_files, create memory mapped buffers for + // perf_event_files, add prepare poll for perf_event_files. if (system_wide_collection_) { if (!event_selection_set_.OpenEventFilesForCpus(cpus_)) { return false; } } else { - if (!event_selection_set_.OpenEventFilesForThreadsOnCpus(monitored_threads_, cpus_)) { + if (!event_selection_set_.OpenEventFilesForThreadsOnCpus(monitored_threads_, + cpus_)) { return false; } } @@ -236,11 +247,13 @@ bool RecordCommand::Run(const std::vector& args) { return false; } - // 5. Write records in mmap buffers of perf_event_files to output file while workload is running. + // 5. Write records in mmap buffers of perf_event_files to output file while + // workload is running. if (workload != nullptr && !workload->Start()) { return false; } - auto callback = std::bind(&RecordCommand::ProcessRecord, this, std::placeholders::_1); + auto callback = + std::bind(&RecordCommand::ProcessRecord, this, std::placeholders::_1); event_selection_set_.PrepareToReadMmapEventData(callback); while (true) { if (!event_selection_set_.ReadMmapEventData()) { @@ -275,7 +288,7 @@ bool RecordCommand::ParseOptions(const std::vector& args, std::vector* non_option_args) { std::set tid_set; size_t i; - for (i = 0; i < args.size() && args[i].size() > 0 && args[i][0] == '-'; ++i) { + for (i = 0; i < args.size() && !args[i].empty() && args[i][0] == '-'; ++i) { if (args[i] == "-a") { system_wide_collection_ = true; } else if (args[i] == "-b") { @@ -306,17 +319,20 @@ bool RecordCommand::ParseOptions(const std::vector& args, char* endptr; uint64_t size = strtoull(strs[1].c_str(), &endptr, 0); if (*endptr != '\0' || size > UINT_MAX) { - LOG(ERROR) << "invalid dump stack size in --call-graph option: " << strs[1]; + LOG(ERROR) << "invalid dump stack size in --call-graph option: " + << strs[1]; return false; } if ((size & 7) != 0) { - LOG(ERROR) << "dump stack size " << size << " is not 8-byte aligned."; + LOG(ERROR) << "dump stack size " << size + << " is not 8-byte aligned."; return false; } dump_stack_size_in_dwarf_sampling_ = static_cast(size); } } else { - LOG(ERROR) << "unexpected argument for --call-graph option: " << args[i]; + LOG(ERROR) << "unexpected argument for --call-graph option: " + << args[i]; return false; } } else if (args[i] == "--cpu") { @@ -352,7 +368,8 @@ bool RecordCommand::ParseOptions(const std::vector& args, if (!NextArgumentOrError(args, &i)) { return false; } - std::vector branch_sampling_types = android::base::Split(args[i], ","); + std::vector branch_sampling_types = + android::base::Split(args[i], ","); for (auto& type : branch_sampling_types) { auto it = branch_sampling_type_map.find(type); if (it == branch_sampling_type_map.end()) { @@ -372,6 +389,8 @@ bool RecordCommand::ParseOptions(const std::vector& args, return false; } perf_mmap_pages_ = pages; + } else if (args[i] == "--no-dump-kernel-symbols") { + can_dump_kernel_symbols_ = false; } else if (args[i] == "--no-inherit") { child_inherit_ = false; } else if (args[i] == "--no-unwind") { @@ -405,14 +424,16 @@ bool RecordCommand::ParseOptions(const std::vector& args, if (!dwarf_callchain_sampling_) { if (!unwind_dwarf_callchain_) { - LOG(ERROR) << "--no-unwind is only used with `--call-graph dwarf` option."; + LOG(ERROR) + << "--no-unwind is only used with `--call-graph dwarf` option."; return false; } unwind_dwarf_callchain_ = false; } if (post_unwind_) { if (!dwarf_callchain_sampling_) { - LOG(ERROR) << "--post-unwind is only used with `--call-graph dwarf` option."; + LOG(ERROR) + << "--post-unwind is only used with `--call-graph dwarf` option."; return false; } if (!unwind_dwarf_callchain_) { @@ -421,10 +442,11 @@ bool RecordCommand::ParseOptions(const std::vector& args, } } - monitored_threads_.insert(monitored_threads_.end(), tid_set.begin(), tid_set.end()); + monitored_threads_.insert(monitored_threads_.end(), tid_set.begin(), + tid_set.end()); if (system_wide_collection_ && !monitored_threads_.empty()) { - LOG(ERROR) - << "Record system wide and existing processes/threads can't be used at the same time."; + LOG(ERROR) << "Record system wide and existing processes/threads can't be " + "used at the same time."; return false; } @@ -438,7 +460,8 @@ bool RecordCommand::ParseOptions(const std::vector& args, } bool RecordCommand::AddMeasuredEventType(const std::string& event_type_name) { - std::unique_ptr event_type_modifier = ParseEventType(event_type_name); + std::unique_ptr event_type_modifier = + ParseEventType(event_type_name); if (event_type_modifier == nullptr) { return false; } @@ -464,7 +487,8 @@ bool RecordCommand::SetEventSelection() { if (fp_callchain_sampling_) { event_selection_set_.EnableFpCallChainSampling(); } else if (dwarf_callchain_sampling_) { - if (!event_selection_set_.EnableDwarfCallChainSampling(dump_stack_size_in_dwarf_sampling_)) { + if (!event_selection_set_.EnableDwarfCallChainSampling( + dump_stack_size_in_dwarf_sampling_)) { return false; } } @@ -478,21 +502,28 @@ bool RecordCommand::CreateAndInitRecordFile() { return false; } // Use first perf_event_attr and first event id to dump mmap and comm records. - const perf_event_attr* attr = event_selection_set_.FindEventAttrByType(measured_event_types_[0]); + const perf_event_attr* attr = + event_selection_set_.FindEventAttrByType(measured_event_types_[0]); const std::vector>* fds = event_selection_set_.FindEventFdsByType(measured_event_types_[0]); uint64_t event_id = (*fds)[0]->Id(); + if (!DumpKernelSymbol()) { + return false; + } if (!DumpKernelAndModuleMmaps(attr, event_id)) { return false; } - if (!DumpThreadCommAndMmaps(attr, event_id, system_wide_collection_, monitored_threads_)) { + if (!DumpThreadCommAndMmaps(attr, event_id, system_wide_collection_, + monitored_threads_)) { return false; } return true; } -std::unique_ptr RecordCommand::CreateRecordFile(const std::string& filename) { - std::unique_ptr writer = RecordFileWriter::CreateInstance(filename); +std::unique_ptr RecordCommand::CreateRecordFile( + const std::string& filename) { + std::unique_ptr writer = + RecordFileWriter::CreateInstance(filename); if (writer == nullptr) { return nullptr; } @@ -516,19 +547,49 @@ std::unique_ptr RecordCommand::CreateRecordFile(const std::str return writer; } -bool RecordCommand::DumpKernelAndModuleMmaps(const perf_event_attr* attr, uint64_t event_id) { +bool RecordCommand::DumpKernelSymbol() { + if (can_dump_kernel_symbols_) { + std::string kallsyms; + bool need_kernel_symbol = false; + for (const auto& type : measured_event_types_) { + if (!type.exclude_kernel) { + need_kernel_symbol = true; + break; + } + } + if (need_kernel_symbol) { + if (!android::base::ReadFileToString("/proc/kallsyms", &kallsyms)) { + PLOG(ERROR) << "failed to read /proc/kallsyms"; + return false; + } + } + std::vector records = + CreateKernelSymbolRecords(std::move(kallsyms)); + for (auto& r : records) { + if (!ProcessRecord(&r)) { + return false; + } + } + } + return true; +} + +bool RecordCommand::DumpKernelAndModuleMmaps(const perf_event_attr* attr, + uint64_t event_id) { KernelMmap kernel_mmap; std::vector module_mmaps; GetKernelAndModuleMmaps(&kernel_mmap, &module_mmaps); - MmapRecord mmap_record = CreateMmapRecord(*attr, true, UINT_MAX, 0, kernel_mmap.start_addr, - kernel_mmap.len, 0, kernel_mmap.filepath, event_id); + MmapRecord mmap_record = + CreateMmapRecord(*attr, true, UINT_MAX, 0, kernel_mmap.start_addr, + kernel_mmap.len, 0, kernel_mmap.filepath, event_id); if (!ProcessRecord(&mmap_record)) { return false; } for (auto& module_mmap : module_mmaps) { - MmapRecord mmap_record = CreateMmapRecord(*attr, true, UINT_MAX, 0, module_mmap.start_addr, - module_mmap.len, 0, module_mmap.filepath, event_id); + MmapRecord mmap_record = + CreateMmapRecord(*attr, true, UINT_MAX, 0, module_mmap.start_addr, + module_mmap.len, 0, module_mmap.filepath, event_id); if (!ProcessRecord(&mmap_record)) { return false; } @@ -536,9 +597,9 @@ bool RecordCommand::DumpKernelAndModuleMmaps(const perf_event_attr* attr, uint64 return true; } -bool RecordCommand::DumpThreadCommAndMmaps(const perf_event_attr* attr, uint64_t event_id, - bool all_threads, - const std::vector& selected_threads) { +bool RecordCommand::DumpThreadCommAndMmaps( + const perf_event_attr* attr, uint64_t event_id, bool all_threads, + const std::vector& selected_threads) { std::vector thread_comms; if (!GetThreadComms(&thread_comms)) { return false; @@ -560,10 +621,12 @@ bool RecordCommand::DumpThreadCommAndMmaps(const perf_event_attr* attr, uint64_t if (thread.pid != thread.tid) { continue; } - if (!all_threads && dump_processes.find(thread.pid) == dump_processes.end()) { + if (!all_threads && + dump_processes.find(thread.pid) == dump_processes.end()) { continue; } - CommRecord record = CreateCommRecord(*attr, thread.pid, thread.tid, thread.comm, event_id); + CommRecord record = + CreateCommRecord(*attr, thread.pid, thread.tid, thread.comm, event_id); if (!ProcessRecord(&record)) { return false; } @@ -576,9 +639,9 @@ bool RecordCommand::DumpThreadCommAndMmaps(const perf_event_attr* attr, uint64_t if (thread_mmap.executable == 0) { continue; // No need to dump non-executable mmap info. } - MmapRecord record = - CreateMmapRecord(*attr, false, thread.pid, thread.tid, thread_mmap.start_addr, - thread_mmap.len, thread_mmap.pgoff, thread_mmap.name, event_id); + MmapRecord record = CreateMmapRecord( + *attr, false, thread.pid, thread.tid, thread_mmap.start_addr, + thread_mmap.len, thread_mmap.pgoff, thread_mmap.name, event_id); if (!ProcessRecord(&record)) { return false; } @@ -593,13 +656,13 @@ bool RecordCommand::DumpThreadCommAndMmaps(const perf_event_attr* attr, uint64_t if (!all_threads && dump_threads.find(thread.tid) == dump_threads.end()) { continue; } - ForkRecord fork_record = CreateForkRecord(*attr, thread.pid, thread.tid, thread.pid, - thread.pid, event_id); + ForkRecord fork_record = CreateForkRecord(*attr, thread.pid, thread.tid, + thread.pid, thread.pid, event_id); if (!ProcessRecord(&fork_record)) { return false; } - CommRecord comm_record = CreateCommRecord(*attr, thread.pid, thread.tid, thread.comm, - event_id); + CommRecord comm_record = + CreateCommRecord(*attr, thread.pid, thread.tid, thread.comm, event_id); if (!ProcessRecord(&comm_record)) { return false; } @@ -621,11 +684,10 @@ bool RecordCommand::ProcessRecord(Record* record) { return result; } -template +template void UpdateMmapRecordForEmbeddedElfPath(RecordType* record) { RecordType& r = *record; - bool in_kernel = ((r.header.misc & PERF_RECORD_MISC_CPUMODE_MASK) == PERF_RECORD_MISC_KERNEL); - if (!in_kernel && r.data.pgoff != 0) { + if (!r.InKernel() && r.data.pgoff != 0) { // For the case of a shared library "foobar.so" embedded // inside an APK, we rewrite the original MMAP from // ["path.apk" offset=X] to ["path.apk!/foobar.so" offset=W] @@ -636,7 +698,8 @@ void UpdateMmapRecordForEmbeddedElfPath(RecordType* record) { // is not present on the host. The new offset W is // calculated to be with respect to the start of foobar.so, // not to the start of path.apk. - EmbeddedElf* ee = ApkInspector::FindElfInApkByOffset(r.filename, r.data.pgoff); + EmbeddedElf* ee = + ApkInspector::FindElfInApkByOffset(r.filename, r.data.pgoff); if (ee != nullptr) { // Compute new offset relative to start of elf in APK. r.data.pgoff -= ee->entry_offset(); @@ -657,20 +720,25 @@ void RecordCommand::UpdateRecordForEmbeddedElfPath(Record* record) { void RecordCommand::UnwindRecord(Record* record) { if (record->type() == PERF_RECORD_SAMPLE) { SampleRecord& r = *static_cast(record); - if ((r.sample_type & PERF_SAMPLE_CALLCHAIN) && (r.sample_type & PERF_SAMPLE_REGS_USER) && - (r.regs_user_data.reg_mask != 0) && (r.sample_type & PERF_SAMPLE_STACK_USER) && + if ((r.sample_type & PERF_SAMPLE_CALLCHAIN) && + (r.sample_type & PERF_SAMPLE_REGS_USER) && + (r.regs_user_data.reg_mask != 0) && + (r.sample_type & PERF_SAMPLE_STACK_USER) && (!r.stack_user_data.data.empty())) { - ThreadEntry* thread = thread_tree_.FindThreadOrNew(r.tid_data.pid, r.tid_data.tid); - RegSet regs = CreateRegSet(r.regs_user_data.reg_mask, r.regs_user_data.regs); + ThreadEntry* thread = + thread_tree_.FindThreadOrNew(r.tid_data.pid, r.tid_data.tid); + RegSet regs = + CreateRegSet(r.regs_user_data.reg_mask, r.regs_user_data.regs); std::vector& stack = r.stack_user_data.data; ArchType arch = GetArchForAbi(GetBuildArch(), r.regs_user_data.abi); - // Normally do strict arch check when unwinding stack. But allow unwinding 32-bit processes - // on 64-bit devices for system wide profiling. + // Normally do strict arch check when unwinding stack. But allow unwinding + // 32-bit processes on 64-bit devices for system wide profiling. bool strict_arch_check = !system_wide_collection_; - std::vector unwind_ips = UnwindCallChain(arch, *thread, regs, stack, - strict_arch_check); + std::vector unwind_ips = + UnwindCallChain(arch, *thread, regs, stack, strict_arch_check); r.callchain_data.ips.push_back(PERF_CONTEXT_USER); - r.callchain_data.ips.insert(r.callchain_data.ips.end(), unwind_ips.begin(), unwind_ips.end()); + r.callchain_data.ips.insert(r.callchain_data.ips.end(), + unwind_ips.begin(), unwind_ips.end()); r.regs_user_data.abi = 0; r.regs_user_data.reg_mask = 0; r.regs_user_data.regs.clear(); @@ -683,7 +751,8 @@ void RecordCommand::UnwindRecord(Record* record) { bool RecordCommand::PostUnwind(const std::vector& args) { thread_tree_.Clear(); - std::unique_ptr reader = RecordFileReader::CreateInstance(record_filename_); + std::unique_ptr reader = + RecordFileReader::CreateInstance(record_filename_); if (reader == nullptr) { return false; } @@ -714,13 +783,15 @@ bool RecordCommand::PostUnwind(const std::vector& args) { return false; } if (rename(tmp_filename.c_str(), record_filename_.c_str()) != 0) { - PLOG(ERROR) << "failed to rename " << tmp_filename << " to " << record_filename_; + PLOG(ERROR) << "failed to rename " << tmp_filename << " to " + << record_filename_; return false; } return true; } -bool RecordCommand::DumpAdditionalFeatures(const std::vector& args) { +bool RecordCommand::DumpAdditionalFeatures( + const std::vector& args) { size_t feature_count = (branch_sampling_ != 0 ? 5 : 4); if (!record_file_writer_->WriteFeatureHeader(feature_count)) { return false; @@ -733,10 +804,12 @@ bool RecordCommand::DumpAdditionalFeatures(const std::vector& args) PLOG(ERROR) << "uname() failed"; return false; } - if (!record_file_writer_->WriteFeatureString(PerfFileFormat::FEAT_OSRELEASE, uname_buf.release)) { + if (!record_file_writer_->WriteFeatureString(PerfFileFormat::FEAT_OSRELEASE, + uname_buf.release)) { return false; } - if (!record_file_writer_->WriteFeatureString(PerfFileFormat::FEAT_ARCH, uname_buf.machine)) { + if (!record_file_writer_->WriteFeatureString(PerfFileFormat::FEAT_ARCH, + uname_buf.machine)) { return false; } @@ -749,7 +822,8 @@ bool RecordCommand::DumpAdditionalFeatures(const std::vector& args) if (!record_file_writer_->WriteCmdlineFeature(cmdline)) { return false; } - if (branch_sampling_ != 0 && !record_file_writer_->WriteBranchStackFeature()) { + if (branch_sampling_ != 0 && + !record_file_writer_->WriteBranchStackFeature()) { return false; } return true; @@ -765,8 +839,8 @@ bool RecordCommand::DumpBuildIdFeature() { LOG(DEBUG) << "can't read build_id for kernel"; continue; } - build_id_records.push_back( - CreateBuildIdRecord(true, UINT_MAX, build_id, DEFAULT_KERNEL_FILENAME_FOR_BUILD_ID)); + build_id_records.push_back(CreateBuildIdRecord( + true, UINT_MAX, build_id, DEFAULT_KERNEL_FILENAME_FOR_BUILD_ID)); } else { std::string path = filename; std::string module_name = basename(&path[0]); @@ -777,7 +851,8 @@ bool RecordCommand::DumpBuildIdFeature() { LOG(DEBUG) << "can't read build_id for module " << module_name; continue; } - build_id_records.push_back(CreateBuildIdRecord(true, UINT_MAX, build_id, filename)); + build_id_records.push_back( + CreateBuildIdRecord(true, UINT_MAX, build_id, filename)); } } // Add build_ids for user elf files. @@ -787,7 +862,8 @@ bool RecordCommand::DumpBuildIdFeature() { } auto tuple = SplitUrlInApk(filename); if (std::get<0>(tuple)) { - if (!GetBuildIdFromApkFile(std::get<1>(tuple), std::get<2>(tuple), &build_id)) { + if (!GetBuildIdFromApkFile(std::get<1>(tuple), std::get<2>(tuple), + &build_id)) { LOG(DEBUG) << "can't read build_id from file " << filename; continue; } @@ -797,7 +873,8 @@ bool RecordCommand::DumpBuildIdFeature() { continue; } } - build_id_records.push_back(CreateBuildIdRecord(false, UINT_MAX, build_id, filename)); + build_id_records.push_back( + CreateBuildIdRecord(false, UINT_MAX, build_id, filename)); } if (!record_file_writer_->WriteBuildIdFeature(build_id_records)) { return false; @@ -808,8 +885,9 @@ bool RecordCommand::DumpBuildIdFeature() { void RecordCommand::CollectHitFileInfo(Record* record) { if (record->type() == PERF_RECORD_SAMPLE) { auto r = *static_cast(record); - bool in_kernel = ((r.header.misc & PERF_RECORD_MISC_CPUMODE_MASK) == PERF_RECORD_MISC_KERNEL); - const ThreadEntry* thread = thread_tree_.FindThreadOrNew(r.tid_data.pid, r.tid_data.tid); + bool in_kernel = r.InKernel(); + const ThreadEntry* thread = + thread_tree_.FindThreadOrNew(r.tid_data.pid, r.tid_data.tid); const MapEntry* map = thread_tree_.FindMap(thread, r.ip_data.ip, in_kernel); if (in_kernel) { hit_kernel_modules_.insert(map->dso->Path()); @@ -820,5 +898,6 @@ void RecordCommand::CollectHitFileInfo(Record* record) { } void RegisterRecordCommand() { - RegisterCommand("record", [] { return std::unique_ptr(new RecordCommand()); }); + RegisterCommand("record", + [] { return std::unique_ptr(new RecordCommand()); }); } diff --git a/simpleperf/cmd_record_test.cpp b/simpleperf/cmd_record_test.cpp index 41a675b0..678d9856 100644 --- a/simpleperf/cmd_record_test.cpp +++ b/simpleperf/cmd_record_test.cpp @@ -35,7 +35,8 @@ static std::unique_ptr RecordCmd() { return CreateCommandInstance("record"); } -static bool RunRecordCmd(std::vector v, const char* output_file = nullptr) { +static bool RunRecordCmd(std::vector v, + const char* output_file = nullptr) { std::unique_ptr tmpfile; std::string out_file; if (output_file != nullptr) { @@ -48,9 +49,7 @@ static bool RunRecordCmd(std::vector v, const char* output_file = n return RecordCmd()->Run(v); } -TEST(record_cmd, no_options) { - ASSERT_TRUE(RunRecordCmd({})); -} +TEST(record_cmd, no_options) { ASSERT_TRUE(RunRecordCmd({})); } TEST(record_cmd, system_wide_option) { TEST_IN_ROOT(ASSERT_TRUE(RunRecordCmd({"-a"}))); @@ -77,14 +76,16 @@ TEST(record_cmd, output_file_option) { TEST(record_cmd, dump_kernel_mmap) { TemporaryFile tmpfile; ASSERT_TRUE(RunRecordCmd({}, tmpfile.path)); - std::unique_ptr reader = RecordFileReader::CreateInstance(tmpfile.path); + std::unique_ptr reader = + RecordFileReader::CreateInstance(tmpfile.path); ASSERT_TRUE(reader != nullptr); std::vector> records = reader->DataSection(); ASSERT_GT(records.size(), 0U); bool have_kernel_mmap = false; for (auto& record : records) { - if (record->header.type == PERF_RECORD_MMAP) { - const MmapRecord* mmap_record = static_cast(record.get()); + if (record->type() == PERF_RECORD_MMAP) { + const MmapRecord* mmap_record = + static_cast(record.get()); if (mmap_record->filename == DEFAULT_KERNEL_MMAP_NAME) { have_kernel_mmap = true; break; @@ -97,10 +98,12 @@ TEST(record_cmd, dump_kernel_mmap) { TEST(record_cmd, dump_build_id_feature) { TemporaryFile tmpfile; ASSERT_TRUE(RunRecordCmd({}, tmpfile.path)); - std::unique_ptr reader = RecordFileReader::CreateInstance(tmpfile.path); + std::unique_ptr reader = + RecordFileReader::CreateInstance(tmpfile.path); ASSERT_TRUE(reader != nullptr); const FileHeader& file_header = reader->FileHeader(); - ASSERT_TRUE(file_header.features[FEAT_BUILD_ID / 8] & (1 << (FEAT_BUILD_ID % 8))); + ASSERT_TRUE(file_header.features[FEAT_BUILD_ID / 8] & + (1 << (FEAT_BUILD_ID % 8))); ASSERT_GT(reader->FeatureSectionDescriptors().size(), 0u); } @@ -116,8 +119,8 @@ TEST(record_cmd, branch_sampling) { ASSERT_TRUE(RunRecordCmd({"-j", "any,u"})); ASSERT_FALSE(RunRecordCmd({"-j", "u"})); } else { - GTEST_LOG_(INFO) - << "This test does nothing as branch stack sampling is not supported on this device."; + GTEST_LOG_(INFO) << "This test does nothing as branch stack sampling is " + "not supported on this device."; } } @@ -139,8 +142,8 @@ TEST(record_cmd, dwarf_callchain_sampling) { ASSERT_TRUE(RunRecordCmd({"--call-graph", "dwarf,16384"})); ASSERT_TRUE(RunRecordCmd({"-g"})); } else { - GTEST_LOG_(INFO) - << "This test does nothing as dwarf callchain sampling is not supported on this device."; + GTEST_LOG_(INFO) << "This test does nothing as dwarf callchain sampling is " + "not supported on this device."; } } @@ -148,8 +151,8 @@ TEST(record_cmd, system_wide_dwarf_callchain_sampling) { if (IsDwarfCallChainSamplingSupported()) { TEST_IN_ROOT(RunRecordCmd({"-a", "--call-graph", "dwarf"})); } else { - GTEST_LOG_(INFO) - << "This test does nothing as dwarf callchain sampling is not supported on this device."; + GTEST_LOG_(INFO) << "This test does nothing as dwarf callchain sampling is " + "not supported on this device."; } } @@ -157,8 +160,8 @@ TEST(record_cmd, no_unwind_option) { if (IsDwarfCallChainSamplingSupported()) { ASSERT_TRUE(RunRecordCmd({"--call-graph", "dwarf", "--no-unwind"})); } else { - GTEST_LOG_(INFO) - << "This test does nothing as dwarf callchain sampling is not supported on this device."; + GTEST_LOG_(INFO) << "This test does nothing as dwarf callchain sampling is " + "not supported on this device."; } ASSERT_FALSE(RunRecordCmd({"--no-unwind"})); } @@ -167,8 +170,8 @@ TEST(record_cmd, post_unwind_option) { if (IsDwarfCallChainSamplingSupported()) { ASSERT_TRUE(RunRecordCmd({"--call-graph", "dwarf", "--post-unwind"})); } else { - GTEST_LOG_(INFO) - << "This test does nothing as dwarf callchain sampling is not supported on this device."; + GTEST_LOG_(INFO) << "This test does nothing as dwarf callchain sampling is " + "not supported on this device."; } ASSERT_FALSE(RunRecordCmd({"--post-unwind"})); ASSERT_FALSE( @@ -178,8 +181,8 @@ TEST(record_cmd, post_unwind_option) { TEST(record_cmd, existing_processes) { std::vector> workloads; CreateProcesses(2, &workloads); - std::string pid_list = - android::base::StringPrintf("%d,%d", workloads[0]->GetPid(), workloads[1]->GetPid()); + std::string pid_list = android::base::StringPrintf( + "%d,%d", workloads[0]->GetPid(), workloads[1]->GetPid()); TemporaryFile tmpfile; ASSERT_TRUE(RecordCmd()->Run({"-p", pid_list, "-o", tmpfile.path})); } @@ -188,15 +191,13 @@ TEST(record_cmd, existing_threads) { std::vector> workloads; CreateProcesses(2, &workloads); // Process id can also be used as thread id in linux. - std::string tid_list = - android::base::StringPrintf("%d,%d", workloads[0]->GetPid(), workloads[1]->GetPid()); + std::string tid_list = android::base::StringPrintf( + "%d,%d", workloads[0]->GetPid(), workloads[1]->GetPid()); TemporaryFile tmpfile; ASSERT_TRUE(RecordCmd()->Run({"-t", tid_list, "-o", tmpfile.path})); } -TEST(record_cmd, no_monitored_threads) { - ASSERT_FALSE(RecordCmd()->Run({""})); -} +TEST(record_cmd, no_monitored_threads) { ASSERT_FALSE(RecordCmd()->Run({""})); } TEST(record_cmd, more_than_one_event_types) { ASSERT_TRUE(RunRecordCmd({"-e", "cpu-cycles,cpu-clock"})); @@ -213,3 +214,35 @@ TEST(record_cmd, mmap_page_option) { ASSERT_FALSE(RunRecordCmd({"-m", "0"})); ASSERT_FALSE(RunRecordCmd({"-m", "7"})); } + +void CheckKernelSymbol(const std::string& path, bool need_kallsyms, + bool* success) { + *success = false; + std::unique_ptr reader = + RecordFileReader::CreateInstance(path); + ASSERT_TRUE(reader != nullptr); + std::vector> records = reader->DataSection(); + bool has_kernel_symbol_records = false; + for (const auto& record : records) { + if (record->type() == SIMPLE_PERF_RECORD_KERNEL_SYMBOL) { + has_kernel_symbol_records = true; + auto& r = *static_cast(record.get()); + if (!r.end_of_symbols) { + ASSERT_FALSE(r.kallsyms.empty()); + } + } + } + ASSERT_EQ(need_kallsyms, has_kernel_symbol_records); + *success = true; +} + +TEST(record_cmd, kernel_symbol) { + TemporaryFile tmpfile; + ASSERT_TRUE(RunRecordCmd({}, tmpfile.path)); + bool success; + CheckKernelSymbol(tmpfile.path, true, &success); + ASSERT_TRUE(success); + ASSERT_TRUE(RunRecordCmd({"--no-dump-kernel-symbols"}, tmpfile.path)); + CheckKernelSymbol(tmpfile.path, false, &success); + ASSERT_TRUE(success); +} diff --git a/simpleperf/cmd_report.cpp b/simpleperf/cmd_report.cpp index e2cd39c0..2ab58278 100644 --- a/simpleperf/cmd_report.cpp +++ b/simpleperf/cmd_report.cpp @@ -583,7 +583,7 @@ bool ReportCommand::ReadSampleTreeFromRecordFile() { bool ReportCommand::ProcessRecord(std::unique_ptr record) { BuildThreadTree(*record, &thread_tree_); - if (record->header.type == PERF_RECORD_SAMPLE) { + if (record->type() == PERF_RECORD_SAMPLE) { sample_tree_builder_->ProcessSampleRecord( *static_cast(record.get())); } @@ -616,6 +616,7 @@ void ReportCommand::PrintReportContext(FILE* report_fp) { if (!record_cmdline_.empty()) { fprintf(report_fp, "Cmdline: %s\n", record_cmdline_.c_str()); } + fprintf(report_fp, "Arch: %s\n", GetArchString(record_file_arch_).c_str()); for (const auto& attr : event_attrs_) { const EventType* event_type = FindEventTypeByConfig(attr.type, attr.config); std::string name; diff --git a/simpleperf/cmd_report_test.cpp b/simpleperf/cmd_report_test.cpp index 4c2b4978..befa1111 100644 --- a/simpleperf/cmd_report_test.cpp +++ b/simpleperf/cmd_report_test.cpp @@ -274,6 +274,12 @@ TEST_F(ReportCommandTest, report_more_than_one_event_types) { ASSERT_NE(content.find("cpu-clock"), std::string::npos); } +TEST_F(ReportCommandTest, report_kernel_symbol) { + Report(PERF_DATA_WITH_KERNEL_SYMBOL); + ASSERT_TRUE(success); + ASSERT_NE(content.find("perf_event_comm_output"), std::string::npos); +} + #if defined(__linux__) static std::unique_ptr RecordCmd() { diff --git a/simpleperf/dso.cpp b/simpleperf/dso.cpp index e34d6bc9..0a29b91c 100644 --- a/simpleperf/dso.cpp +++ b/simpleperf/dso.cpp @@ -23,6 +23,7 @@ #include #include +#include #include #include "environment.h" @@ -54,6 +55,7 @@ const char* Symbol::DemangledName() const { bool Dso::demangle_ = true; std::string Dso::symfs_dir_; std::string Dso::vmlinux_; +std::string Dso::kallsyms_; std::unordered_map Dso::build_id_map_; size_t Dso::dso_count_; @@ -142,7 +144,13 @@ Dso::Dso(DsoType type, const std::string& path) Dso::~Dso() { if (--dso_count_ == 0) { + // Clean up global variables when no longer used. symbol_name_allocator.Clear(); + demangle_ = true; + symfs_dir_.clear(); + vmlinux_.clear(); + kallsyms_.clear(); + build_id_map_.clear(); } } @@ -238,6 +246,22 @@ bool Dso::LoadKernel() { if (!vmlinux_.empty()) { ParseSymbolsFromElfFile(vmlinux_, build_id, std::bind(VmlinuxSymbolCallback, std::placeholders::_1, this)); + } else if (!kallsyms_.empty()) { + ProcessKernelSymbols(kallsyms_, + std::bind(&KernelSymbolCallback, std::placeholders::_1, this)); + bool all_zero = true; + for (const auto& symbol : symbols_) { + if (symbol.addr != 0) { + all_zero = false; + break; + } + } + if (all_zero) { + LOG(WARNING) << "Symbol addresses in /proc/kallsyms on device are all zero. " + "`echo 0 >/proc/sys/kernel/kptr_restrict` or use root privilege if possible."; + symbols_.clear(); + return false; + } } else { if (!build_id.IsEmpty()) { BuildId real_build_id; @@ -250,18 +274,23 @@ bool Dso::LoadKernel() { } } - ProcessKernelSymbols("/proc/kallsyms", + std::string kallsyms; + if (!android::base::ReadFileToString("/proc/kallsyms", &kallsyms)) { + LOG(DEBUG) << "failed to read /proc/kallsyms"; + return false; + } + ProcessKernelSymbols(kallsyms, std::bind(&KernelSymbolCallback, std::placeholders::_1, this)); - bool allZero = true; - for (auto& symbol : symbols_) { + bool all_zero = true; + for (const auto& symbol : symbols_) { if (symbol.addr != 0) { - allZero = false; + all_zero = false; break; } } - if (allZero) { - LOG(WARNING) << "Symbol addresses in /proc/kallsyms are all zero. Check " - "/proc/sys/kernel/kptr_restrict if possible."; + if (all_zero) { + LOG(WARNING) << "Symbol addresses in /proc/kallsyms are all zero. " + "`echo 0 >/proc/sys/kernel/kptr_restrict` or use root privilege if possible."; symbols_.clear(); return false; } diff --git a/simpleperf/dso.h b/simpleperf/dso.h index 9697319b..e11ea38a 100644 --- a/simpleperf/dso.h +++ b/simpleperf/dso.h @@ -55,6 +55,11 @@ struct Dso { static std::string Demangle(const std::string& name); static bool SetSymFsDir(const std::string& symfs_dir); static void SetVmlinux(const std::string& vmlinux); + static void SetKallsyms(std::string kallsyms) { + if (!kallsyms.empty()) { + kallsyms_ = std::move(kallsyms); + } + } static void SetBuildIds(const std::vector>& build_ids); static std::unique_ptr CreateDso(DsoType dso_type, const std::string& dso_path = ""); @@ -85,6 +90,7 @@ struct Dso { static bool demangle_; static std::string symfs_dir_; static std::string vmlinux_; + static std::string kallsyms_; static std::unordered_map build_id_map_; static size_t dso_count_; diff --git a/simpleperf/environment.cpp b/simpleperf/environment.cpp index 53119ad7..578c1b16 100644 --- a/simpleperf/environment.cpp +++ b/simpleperf/environment.cpp @@ -106,41 +106,6 @@ std::vector GetCpusFromString(const std::string& s) { return std::vector(cpu_set.begin(), cpu_set.end()); } -bool ProcessKernelSymbols(const std::string& symbol_file, - std::function callback) { - FILE* fp = fopen(symbol_file.c_str(), "re"); - if (fp == nullptr) { - PLOG(ERROR) << "failed to open file " << symbol_file; - return false; - } - LineReader reader(fp); - char* line; - while ((line = reader.ReadLine()) != nullptr) { - // Parse line like: ffffffffa005c4e4 d __warned.41698 [libsas] - char name[reader.MaxLineSize()]; - char module[reader.MaxLineSize()]; - strcpy(module, ""); - - KernelSymbol symbol; - if (sscanf(line, "%" PRIx64 " %c %s%s", &symbol.addr, &symbol.type, name, module) < 3) { - continue; - } - symbol.name = name; - size_t module_len = strlen(module); - if (module_len > 2 && module[0] == '[' && module[module_len - 1] == ']') { - module[module_len - 1] = '\0'; - symbol.module = &module[1]; - } else { - symbol.module = nullptr; - } - - if (callback(symbol)) { - return true; - } - } - return false; -} - static std::vector GetLoadedModules() { std::vector result; FILE* fp = fopen("/proc/modules", "re"); @@ -162,6 +127,16 @@ static std::vector GetLoadedModules() { result.push_back(map); } } + bool all_zero = true; + for (const auto& map : result) { + if (map.start_addr != 0) { + all_zero = false; + } + } + if (all_zero) { + LOG(DEBUG) << "addresses in /proc/modules are all zero, so ignore kernel modules"; + return std::vector(); + } return result; } diff --git a/simpleperf/environment.h b/simpleperf/environment.h index c405af8f..cb44ebe3 100644 --- a/simpleperf/environment.h +++ b/simpleperf/environment.h @@ -69,15 +69,4 @@ bool GetValidThreadsFromThreadString(const std::string& tid_str, std::set bool GetExecPath(std::string* exec_path); -// Expose the following functions for unit tests. -struct KernelSymbol { - uint64_t addr; - char type; - const char* name; - const char* module; // If nullptr, the symbol is not in a kernel module. -}; - -bool ProcessKernelSymbols(const std::string& symbol_file, - std::function callback); - #endif // SIMPLE_PERF_ENVIRONMENT_H_ diff --git a/simpleperf/environment_test.cpp b/simpleperf/environment_test.cpp index 6bca7b8d..9b4cbab3 100644 --- a/simpleperf/environment_test.cpp +++ b/simpleperf/environment_test.cpp @@ -16,10 +16,6 @@ #include -#include -#include -#include - #include "environment.h" TEST(environment, GetCpusFromString) { @@ -28,47 +24,3 @@ TEST(environment, GetCpusFromString) { ASSERT_EQ(GetCpusFromString("0,2-3"), std::vector({0, 2, 3})); ASSERT_EQ(GetCpusFromString("1,0-3,3,4"), std::vector({0, 1, 2, 3, 4})); } - -static bool ModulesMatch(const char* p, const char* q) { - if (p == nullptr && q == nullptr) { - return true; - } - if (p != nullptr && q != nullptr) { - return strcmp(p, q) == 0; - } - return false; -} - -static bool KernelSymbolsMatch(const KernelSymbol& sym1, const KernelSymbol& sym2) { - return sym1.addr == sym2.addr && - sym1.type == sym2.type && - strcmp(sym1.name, sym2.name) == 0 && - ModulesMatch(sym1.module, sym2.module); -} - -TEST(environment, ProcessKernelSymbols) { - std::string data = - "ffffffffa005c4e4 d __warned.41698 [libsas]\n" - "aaaaaaaaaaaaaaaa T _text\n" - "cccccccccccccccc c ccccc\n"; - TemporaryFile tempfile; - ASSERT_TRUE(android::base::WriteStringToFile(data, tempfile.path)); - KernelSymbol expected_symbol; - expected_symbol.addr = 0xffffffffa005c4e4ULL; - expected_symbol.type = 'd'; - expected_symbol.name = "__warned.41698"; - expected_symbol.module = "libsas"; - ASSERT_TRUE(ProcessKernelSymbols( - tempfile.path, std::bind(&KernelSymbolsMatch, std::placeholders::_1, expected_symbol))); - - expected_symbol.addr = 0xaaaaaaaaaaaaaaaaULL; - expected_symbol.type = 'T'; - expected_symbol.name = "_text"; - expected_symbol.module = nullptr; - ASSERT_TRUE(ProcessKernelSymbols( - tempfile.path, std::bind(&KernelSymbolsMatch, std::placeholders::_1, expected_symbol))); - - expected_symbol.name = "non_existent_symbol"; - ASSERT_FALSE(ProcessKernelSymbols( - tempfile.path, std::bind(&KernelSymbolsMatch, std::placeholders::_1, expected_symbol))); -} diff --git a/simpleperf/get_test_data.h b/simpleperf/get_test_data.h index 4c21d39e..fa0e113b 100644 --- a/simpleperf/get_test_data.h +++ b/simpleperf/get_test_data.h @@ -68,4 +68,7 @@ static BuildId native_lib_build_id("8ed5755a7fdc07586ca228b8ee21621bce2c7a97"); // perf_with_two_event_types.data is generated by sampling using -e cpu-cycles,cpu-clock option. static const std::string PERF_DATA_WITH_TWO_EVENT_TYPES = "perf_with_two_event_types.data"; +// perf_with_kernel_symbol.data is generated by `sudo simpleperf record ls -l`. +static const std::string PERF_DATA_WITH_KERNEL_SYMBOL = "perf_with_kernel_symbol.data"; + #endif // SIMPLE_PERF_GET_TEST_DATA_H_ diff --git a/simpleperf/nonlinux_support/nonlinux_support.cpp b/simpleperf/nonlinux_support/nonlinux_support.cpp index 37833ed1..24af5ce5 100644 --- a/simpleperf/nonlinux_support/nonlinux_support.cpp +++ b/simpleperf/nonlinux_support/nonlinux_support.cpp @@ -25,10 +25,6 @@ std::vector UnwindCallChain(ArchType, const ThreadEntry&, const RegSet return std::vector(); } -bool ProcessKernelSymbols(const std::string&, std::function) { - return false; -} - bool GetKernelBuildId(BuildId*) { return false; } diff --git a/simpleperf/record.cpp b/simpleperf/record.cpp index 02162753..701b295c 100644 --- a/simpleperf/record.cpp +++ b/simpleperf/record.cpp @@ -34,7 +34,7 @@ static std::string RecordTypeToString(int record_type) { {PERF_RECORD_THROTTLE, "throttle"}, {PERF_RECORD_UNTHROTTLE, "unthrottle"}, {PERF_RECORD_FORK, "fork"}, {PERF_RECORD_READ, "read"}, {PERF_RECORD_SAMPLE, "sample"}, {PERF_RECORD_BUILD_ID, "build_id"}, - {PERF_RECORD_MMAP2, "mmap2"}, + {PERF_RECORD_MMAP2, "mmap2"}, {SIMPLE_PERF_RECORD_KERNEL_SYMBOL, "kernel_symbol"}, }; auto it = record_type_names.find(record_type); @@ -96,7 +96,9 @@ void SampleId::ReadFromBinaryFormat(const perf_event_attr& attr, const char* p, if (sample_type & PERF_SAMPLE_CPU) { MoveFromBinaryFormat(cpu_data, p); } - // TODO: Add parsing of PERF_SAMPLE_IDENTIFIER. + if (sample_type & PERF_SAMPLE_IDENTIFIER) { + MoveFromBinaryFormat(id_data, p); + } } CHECK_LE(p, end); if (p < end) { @@ -132,7 +134,7 @@ void SampleId::Dump(size_t indent) const { if (sample_type & PERF_SAMPLE_TIME) { PrintIndented(indent, "sample_id: time %" PRId64 "\n", time_data.time); } - if (sample_type & PERF_SAMPLE_ID) { + if (sample_type & (PERF_SAMPLE_ID | PERF_SAMPLE_IDENTIFIER)) { PrintIndented(indent, "sample_id: id %" PRId64 "\n", id_data.id); } if (sample_type & PERF_SAMPLE_STREAM_ID) { @@ -162,21 +164,16 @@ size_t SampleId::Size() const { if (sample_type & PERF_SAMPLE_CPU) { size += sizeof(PerfSampleCpuType); } + if (sample_type & PERF_SAMPLE_IDENTIFIER) { + size += sizeof(PerfSampleIdType); + } } return size; } -Record::Record() { - memset(&header, 0, sizeof(header)); -} - -Record::Record(const perf_event_header* pheader) { - header = *pheader; -} - void Record::Dump(size_t indent) const { PrintIndented(indent, "record %s: type %u, misc %u, size %u\n", - RecordTypeToString(header.type).c_str(), header.type, header.misc, header.size); + RecordTypeToString(type()).c_str(), type(), misc(), size()); DumpData(indent + 1); sample_id.Dump(indent + 1); } @@ -188,7 +185,7 @@ uint64_t Record::Timestamp() const { MmapRecord::MmapRecord(const perf_event_attr& attr, const perf_event_header* pheader) : Record(pheader) { const char* p = reinterpret_cast(pheader + 1); - const char* end = reinterpret_cast(pheader) + pheader->size; + const char* end = reinterpret_cast(pheader) + size(); MoveFromBinaryFormat(data, p); filename = p; p += ALIGN(filename.size() + 1, 8); @@ -197,7 +194,7 @@ MmapRecord::MmapRecord(const perf_event_attr& attr, const perf_event_header* phe } std::vector MmapRecord::BinaryFormat() const { - std::vector buf(header.size); + std::vector buf(size()); char* p = buf.data(); MoveToBinaryFormat(header, p); MoveToBinaryFormat(data, p); @@ -208,7 +205,7 @@ std::vector MmapRecord::BinaryFormat() const { } void MmapRecord::AdjustSizeBasedOnData() { - header.size = sizeof(header) + sizeof(data) + ALIGN(filename.size() + 1, 8) + sample_id.Size(); + SetSize(header_size() + sizeof(data) + ALIGN(filename.size() + 1, 8) + sample_id.Size()); } void MmapRecord::DumpData(size_t indent) const { @@ -220,7 +217,7 @@ void MmapRecord::DumpData(size_t indent) const { Mmap2Record::Mmap2Record(const perf_event_attr& attr, const perf_event_header* pheader) : Record(pheader) { const char* p = reinterpret_cast(pheader + 1); - const char* end = reinterpret_cast(pheader) + pheader->size; + const char* end = reinterpret_cast(pheader) + size(); MoveFromBinaryFormat(data, p); filename = p; p += ALIGN(filename.size() + 1, 8); @@ -229,7 +226,7 @@ Mmap2Record::Mmap2Record(const perf_event_attr& attr, const perf_event_header* p } std::vector Mmap2Record::BinaryFormat() const { - std::vector buf(header.size); + std::vector buf(size()); char* p = buf.data(); MoveToBinaryFormat(header, p); MoveToBinaryFormat(data, p); @@ -240,7 +237,7 @@ std::vector Mmap2Record::BinaryFormat() const { } void Mmap2Record::AdjustSizeBasedOnData() { - header.size = sizeof(header) + sizeof(data) + ALIGN(filename.size() + 1, 8) + sample_id.Size(); + SetSize(header_size() + sizeof(data) + ALIGN(filename.size() + 1, 8) + sample_id.Size()); } void Mmap2Record::DumpData(size_t indent) const { @@ -256,7 +253,7 @@ void Mmap2Record::DumpData(size_t indent) const { CommRecord::CommRecord(const perf_event_attr& attr, const perf_event_header* pheader) : Record(pheader) { const char* p = reinterpret_cast(pheader + 1); - const char* end = reinterpret_cast(pheader) + pheader->size; + const char* end = reinterpret_cast(pheader) + size(); MoveFromBinaryFormat(data, p); comm = p; p += ALIGN(strlen(p) + 1, 8); @@ -265,7 +262,7 @@ CommRecord::CommRecord(const perf_event_attr& attr, const perf_event_header* phe } std::vector CommRecord::BinaryFormat() const { - std::vector buf(header.size); + std::vector buf(size()); char* p = buf.data(); MoveToBinaryFormat(header, p); MoveToBinaryFormat(data, p); @@ -282,14 +279,14 @@ void CommRecord::DumpData(size_t indent) const { ExitOrForkRecord::ExitOrForkRecord(const perf_event_attr& attr, const perf_event_header* pheader) : Record(pheader) { const char* p = reinterpret_cast(pheader + 1); - const char* end = reinterpret_cast(pheader) + pheader->size; + const char* end = reinterpret_cast(pheader) + size(); MoveFromBinaryFormat(data, p); CHECK_LE(p, end); sample_id.ReadFromBinaryFormat(attr, p, end); } std::vector ExitOrForkRecord::BinaryFormat() const { - std::vector buf(header.size); + std::vector buf(size()); char* p = buf.data(); MoveToBinaryFormat(header, p); MoveToBinaryFormat(data, p); @@ -305,9 +302,12 @@ void ExitOrForkRecord::DumpData(size_t indent) const { SampleRecord::SampleRecord(const perf_event_attr& attr, const perf_event_header* pheader) : Record(pheader) { const char* p = reinterpret_cast(pheader + 1); - const char* end = reinterpret_cast(pheader) + pheader->size; + const char* end = reinterpret_cast(pheader) + size(); sample_type = attr.sample_type; + if (sample_type & PERF_SAMPLE_IDENTIFIER) { + MoveFromBinaryFormat(id_data, p); + } if (sample_type & PERF_SAMPLE_IP) { MoveFromBinaryFormat(ip_data, p); } @@ -385,9 +385,12 @@ SampleRecord::SampleRecord(const perf_event_attr& attr, const perf_event_header* } std::vector SampleRecord::BinaryFormat() const { - std::vector buf(header.size); + std::vector buf(size()); char* p = buf.data(); MoveToBinaryFormat(header, p); + if (sample_type & PERF_SAMPLE_IDENTIFIER) { + MoveToBinaryFormat(id_data, p); + } if (sample_type & PERF_SAMPLE_IP) { MoveToBinaryFormat(ip_data, p); } @@ -450,9 +453,9 @@ std::vector SampleRecord::BinaryFormat() const { void SampleRecord::AdjustSizeBasedOnData() { size_t size = BinaryFormat().size(); - LOG(DEBUG) << "Record (type " << RecordTypeToString(header.type) << ") size is changed from " - << header.size << " to " << size; - header.size = size; + LOG(DEBUG) << "Record (type " << RecordTypeToString(type()) + << ") size is changed from " << this->size() << " to " << size; + SetSize(size); } void SampleRecord::DumpData(size_t indent) const { @@ -469,7 +472,7 @@ void SampleRecord::DumpData(size_t indent) const { if (sample_type & PERF_SAMPLE_ADDR) { PrintIndented(indent, "addr %p\n", reinterpret_cast(addr_data.addr)); } - if (sample_type & PERF_SAMPLE_ID) { + if (sample_type & (PERF_SAMPLE_ID | PERF_SAMPLE_IDENTIFIER)) { PrintIndented(indent, "id %" PRId64 "\n", id_data.id); } if (sample_type & PERF_SAMPLE_STREAM_ID) { @@ -534,7 +537,7 @@ uint64_t SampleRecord::Timestamp() const { BuildIdRecord::BuildIdRecord(const perf_event_header* pheader) : Record(pheader) { const char* p = reinterpret_cast(pheader + 1); - const char* end = reinterpret_cast(pheader) + pheader->size; + const char* end = reinterpret_cast(pheader) + size(); MoveFromBinaryFormat(pid, p); build_id = BuildId(p, BUILD_ID_SIZE); p += ALIGN(build_id.Size(), 8); @@ -544,7 +547,7 @@ BuildIdRecord::BuildIdRecord(const perf_event_header* pheader) : Record(pheader) } std::vector BuildIdRecord::BinaryFormat() const { - std::vector buf(header.size); + std::vector buf(size()); char* p = buf.data(); MoveToBinaryFormat(header, p); MoveToBinaryFormat(pid, p); @@ -560,14 +563,47 @@ void BuildIdRecord::DumpData(size_t indent) const { PrintIndented(indent, "filename %s\n", filename.c_str()); } +KernelSymbolRecord::KernelSymbolRecord(const perf_event_header* pheader) : Record(pheader) { + const char* p = reinterpret_cast(pheader + 1); + const char* end = reinterpret_cast(pheader) + size(); + uint32_t end_flag; + MoveFromBinaryFormat(end_flag, p); + end_of_symbols = (end_flag == 1); + uint32_t size; + MoveFromBinaryFormat(size, p); + kallsyms.resize(size); + if (size != 0u) { + memcpy(&kallsyms[0], p, size); + } + p += ALIGN(size, 8); + CHECK_EQ(p, end); +} + +std::vector KernelSymbolRecord::BinaryFormat() const { + std::vector buf(size()); + char* p = buf.data(); + MoveToBinaryFormat(header, p); + uint32_t end_flag = (end_of_symbols ? 1 : 0); + MoveToBinaryFormat(end_flag, p); + uint32_t size = static_cast(kallsyms.size()); + MoveToBinaryFormat(size, p); + memcpy(p, kallsyms.data(), size); + return buf; +} + +void KernelSymbolRecord::DumpData(size_t indent) const { + PrintIndented(indent, "end_of_symbols: %s\n", end_of_symbols ? "true" : "false"); + PrintIndented(indent, "kallsyms: %s\n", kallsyms.c_str()); +} + UnknownRecord::UnknownRecord(const perf_event_header* pheader) : Record(pheader) { const char* p = reinterpret_cast(pheader + 1); - const char* end = reinterpret_cast(pheader) + pheader->size; + const char* end = reinterpret_cast(pheader) + size(); data.insert(data.end(), p, end); } std::vector UnknownRecord::BinaryFormat() const { - std::vector buf(header.size); + std::vector buf(size()); char* p = buf.data(); MoveToBinaryFormat(header, p); MoveToBinaryFormat(data.data(), data.size(), p); @@ -592,6 +628,8 @@ std::unique_ptr ReadRecordFromBuffer(const perf_event_attr& attr, return std::unique_ptr(new ForkRecord(attr, pheader)); case PERF_RECORD_SAMPLE: return std::unique_ptr(new SampleRecord(attr, pheader)); + case SIMPLE_PERF_RECORD_KERNEL_SYMBOL: + return std::unique_ptr(new KernelSymbolRecord(pheader)); default: return std::unique_ptr(new UnknownRecord(pheader)); } @@ -604,10 +642,11 @@ std::vector> ReadRecordsFromBuffer(const perf_event_attr const char* end = buf + buf_size; while (p < end) { const perf_event_header* header = reinterpret_cast(p); - CHECK_LE(p + header->size, end); - CHECK_NE(0u, header->size); + uint32_t size = header->size; + CHECK_LE(p + size, end); + CHECK_NE(0u, size); result.push_back(ReadRecordFromBuffer(attr, header)); - p += header->size; + p += size; } return result; } @@ -616,8 +655,7 @@ MmapRecord CreateMmapRecord(const perf_event_attr& attr, bool in_kernel, uint32_ uint64_t addr, uint64_t len, uint64_t pgoff, const std::string& filename, uint64_t event_id) { MmapRecord record; - record.header.type = PERF_RECORD_MMAP; - record.header.misc = (in_kernel ? PERF_RECORD_MISC_KERNEL : PERF_RECORD_MISC_USER); + record.SetTypeAndMisc(PERF_RECORD_MMAP, in_kernel ? PERF_RECORD_MISC_KERNEL : PERF_RECORD_MISC_USER); record.data.pid = pid; record.data.tid = tid; record.data.addr = addr; @@ -625,56 +663,72 @@ MmapRecord CreateMmapRecord(const perf_event_attr& attr, bool in_kernel, uint32_ record.data.pgoff = pgoff; record.filename = filename; size_t sample_id_size = record.sample_id.CreateContent(attr, event_id); - record.header.size = sizeof(record.header) + sizeof(record.data) + - ALIGN(record.filename.size() + 1, 8) + sample_id_size; + record.SetSize(record.header_size() + sizeof(record.data) + + ALIGN(record.filename.size() + 1, 8) + sample_id_size); return record; } CommRecord CreateCommRecord(const perf_event_attr& attr, uint32_t pid, uint32_t tid, const std::string& comm, uint64_t event_id) { CommRecord record; - record.header.type = PERF_RECORD_COMM; - record.header.misc = 0; + record.SetTypeAndMisc(PERF_RECORD_COMM, 0); record.data.pid = pid; record.data.tid = tid; record.comm = comm; size_t sample_id_size = record.sample_id.CreateContent(attr, event_id); - record.header.size = sizeof(record.header) + sizeof(record.data) + - ALIGN(record.comm.size() + 1, 8) + sample_id_size; + record.SetSize(record.header_size() + sizeof(record.data) + + ALIGN(record.comm.size() + 1, 8) + sample_id_size); return record; } ForkRecord CreateForkRecord(const perf_event_attr& attr, uint32_t pid, uint32_t tid, uint32_t ppid, uint32_t ptid, uint64_t event_id) { ForkRecord record; - record.header.type = PERF_RECORD_FORK; - record.header.misc = 0; + record.SetTypeAndMisc(PERF_RECORD_FORK, 0); record.data.pid = pid; record.data.ppid = ppid; record.data.tid = tid; record.data.ptid = ptid; record.data.time = 0; size_t sample_id_size = record.sample_id.CreateContent(attr, event_id); - record.header.size = sizeof(record.header) + sizeof(record.data) + sample_id_size; + record.SetSize(record.header_size() + sizeof(record.data) + sample_id_size); return record; } BuildIdRecord CreateBuildIdRecord(bool in_kernel, pid_t pid, const BuildId& build_id, const std::string& filename) { BuildIdRecord record; - record.header.type = PERF_RECORD_BUILD_ID; - record.header.misc = (in_kernel ? PERF_RECORD_MISC_KERNEL : PERF_RECORD_MISC_USER); + record.SetTypeAndMisc(PERF_RECORD_BUILD_ID, in_kernel ? PERF_RECORD_MISC_KERNEL : PERF_RECORD_MISC_USER); record.pid = pid; record.build_id = build_id; record.filename = filename; - record.header.size = sizeof(record.header) + sizeof(record.pid) + - ALIGN(record.build_id.Size(), 8) + ALIGN(filename.size() + 1, 64); + record.SetSize(record.header_size() + sizeof(record.pid) + + ALIGN(record.build_id.Size(), 8) + ALIGN(filename.size() + 1, 64)); return record; } +std::vector CreateKernelSymbolRecords(const std::string& kallsyms) { + std::vector result; + size_t left_bytes = kallsyms.size(); + const size_t max_bytes_per_record = 64000; + const char* p = kallsyms.c_str(); + while (left_bytes > 0) { + size_t used_bytes = std::min(left_bytes, max_bytes_per_record); + KernelSymbolRecord r; + r.SetTypeAndMisc(SIMPLE_PERF_RECORD_KERNEL_SYMBOL, 0); + r.end_of_symbols = (used_bytes == left_bytes); + r.kallsyms.assign(p, used_bytes); + r.SetSize(r.header_size() + 8 + ALIGN(r.kallsyms.size(), 8)); + result.push_back(r); + p += used_bytes; + left_bytes -= used_bytes; + } + return result; +} + bool RecordCache::RecordWithSeq::IsHappensBefore(const RecordWithSeq& other) const { - bool is_sample = (record->header.type == PERF_RECORD_SAMPLE); - bool is_other_sample = (other.record->header.type == PERF_RECORD_SAMPLE); + bool is_sample = (record->type() == PERF_RECORD_SAMPLE); + bool is_other_sample = (other.record->type() == PERF_RECORD_SAMPLE); uint64_t time = record->Timestamp(); uint64_t other_time = other.record->Timestamp(); // The record with smaller time happens first. diff --git a/simpleperf/record.h b/simpleperf/record.h index 85dcbc70..55512b8a 100644 --- a/simpleperf/record.h +++ b/simpleperf/record.h @@ -25,6 +25,8 @@ #include #include +#include + #include "build_id.h" #include "perf_event.h" @@ -34,11 +36,15 @@ struct ThreadComm; struct ThreadMmap; enum user_record_type { + PERF_RECORD_USER_DEFINED_TYPE_START = 64, PERF_RECORD_ATTR = 64, PERF_RECORD_EVENT_TYPE, PERF_RECORD_TRACING_DATA, PERF_RECORD_BUILD_ID, PERF_RECORD_FINISHED_ROUND, + + SIMPLE_PERF_RECORD_TYPE_START = 32768, + SIMPLE_PERF_RECORD_KERNEL_SYMBOL, }; struct PerfSampleIpType { @@ -139,24 +145,46 @@ struct Record { perf_event_header header; SampleId sample_id; - Record(); - Record(const perf_event_header* pheader); + Record() { + memset(&header, 0, sizeof(header)); + } + Record(const perf_event_header* pheader) { + header = *pheader; + } virtual ~Record() { } + uint32_t type() const { + return header.type; + } + + uint16_t misc() const { + return header.misc; + } + size_t size() const { return header.size; } - uint32_t type() const { - return header.type; + static uint32_t header_size() { + return sizeof(perf_event_header); } bool InKernel() const { return (header.misc & PERF_RECORD_MISC_CPUMODE_MASK) == PERF_RECORD_MISC_KERNEL; } + void SetTypeAndMisc(uint32_t type, uint16_t misc) { + header.type = type; + header.misc = misc; + } + + void SetSize(uint32_t size) { + CHECK_LT(size, 1u << 16); + header.size = size; + } + void Dump(size_t indent = 0) const; virtual std::vector BinaryFormat() const = 0; virtual uint64_t Timestamp() const; @@ -299,6 +327,20 @@ struct BuildIdRecord : public Record { void DumpData(size_t indent) const override; }; +struct KernelSymbolRecord : public Record { + bool end_of_symbols; + std::string kallsyms; + + KernelSymbolRecord() { + } + + KernelSymbolRecord(const perf_event_header* pheader); + std::vector BinaryFormat() const override; + + protected: + void DumpData(size_t indent) const override; +}; + // UnknownRecord is used for unknown record types, it makes sure all unknown records // are not changed when modifying perf.data. struct UnknownRecord : public Record { @@ -371,5 +413,6 @@ ForkRecord CreateForkRecord(const perf_event_attr& attr, uint32_t pid, uint32_t uint32_t ptid, uint64_t event_id); BuildIdRecord CreateBuildIdRecord(bool in_kernel, pid_t pid, const BuildId& build_id, const std::string& filename); +std::vector CreateKernelSymbolRecords(const std::string& kallsyms); #endif // SIMPLE_PERF_RECORD_H_ diff --git a/simpleperf/record_equal_test.h b/simpleperf/record_equal_test.h index 03768dc5..a15fad20 100644 --- a/simpleperf/record_equal_test.h +++ b/simpleperf/record_equal_test.h @@ -31,13 +31,15 @@ static void CheckBuildIdRecordDataEqual(const BuildIdRecord& r1, const BuildIdRe } static void CheckRecordEqual(const Record& r1, const Record& r2) { - ASSERT_EQ(0, memcmp(&r1.header, &r2.header, sizeof(r1.header))); + ASSERT_EQ(r1.type(), r2.type()); + ASSERT_EQ(r1.misc(), r2.misc()); + ASSERT_EQ(r1.size(), r2.size()); ASSERT_EQ(0, memcmp(&r1.sample_id, &r2.sample_id, sizeof(r1.sample_id))); - if (r1.header.type == PERF_RECORD_MMAP) { + if (r1.type() == PERF_RECORD_MMAP) { CheckMmapRecordDataEqual(static_cast(r1), static_cast(r2)); - } else if (r1.header.type == PERF_RECORD_COMM) { + } else if (r1.type() == PERF_RECORD_COMM) { CheckCommRecordDataEqual(static_cast(r1), static_cast(r2)); - } else if (r1.header.type == PERF_RECORD_BUILD_ID) { + } else if (r1.type() == PERF_RECORD_BUILD_ID) { CheckBuildIdRecordDataEqual(static_cast(r1), static_cast(r2)); } diff --git a/simpleperf/record_file_reader.cpp b/simpleperf/record_file_reader.cpp index 1165494c..9b97936f 100644 --- a/simpleperf/record_file_reader.cpp +++ b/simpleperf/record_file_reader.cpp @@ -213,29 +213,40 @@ std::unique_ptr RecordFileReader::ReadRecord() { PLOG(ERROR) << "failed to read file " << filename_; return nullptr; } - perf_event_header* header = reinterpret_cast(&buf[0]); - if (buf.size() < header->size) { - buf.resize(header->size); - header = reinterpret_cast(&buf[0]); - } - if (fread(&buf[sizeof(perf_event_header)], buf.size() - sizeof(perf_event_header), 1, record_fp_) != 1) { - PLOG(ERROR) << "failed to read file " << filename_; - return nullptr; + perf_event_header* pheader = reinterpret_cast(&buf[0]); + if (buf.size() < pheader->size) { + buf.resize(pheader->size); + pheader = reinterpret_cast(&buf[0]); + } + if (buf.size() > sizeof(perf_event_header)) { + if (fread(&buf[sizeof(perf_event_header)], pheader->size - sizeof(perf_event_header), 1, record_fp_) != 1) { + PLOG(ERROR) << "failed to read file " << filename_; + return nullptr; + } } const perf_event_attr* attr = &file_attrs_[0].attr; - if (file_attrs_.size() > 1) { + if (file_attrs_.size() > 1 && pheader->type < PERF_RECORD_USER_DEFINED_TYPE_START) { + bool has_event_id = false; uint64_t event_id; - if (header->type == PERF_RECORD_SAMPLE) { - event_id = *reinterpret_cast(&buf[event_id_pos_in_sample_records_]); + if (pheader->type == PERF_RECORD_SAMPLE) { + if (pheader->size > event_id_pos_in_sample_records_ + sizeof(uint64_t)) { + has_event_id = true; + event_id = *reinterpret_cast(&buf[event_id_pos_in_sample_records_]); + } } else { - event_id = *reinterpret_cast( - &buf[header->size - event_id_reverse_pos_in_non_sample_records_]); + if (pheader->size > event_id_reverse_pos_in_non_sample_records_) { + has_event_id = true; + event_id = *reinterpret_cast(&buf[pheader->size - event_id_reverse_pos_in_non_sample_records_]); + } + } + if (has_event_id) { + auto it = event_id_to_attr_map_.find(event_id); + if (it != event_id_to_attr_map_.end()) { + attr = it->second; + } } - auto it = event_id_to_attr_map_.find(event_id); - CHECK(it != event_id_to_attr_map_.end()); - attr = it->second; } - return ReadRecordFromBuffer(*attr, header); + return ReadRecordFromBuffer(*attr, pheader); } bool RecordFileReader::ReadFeatureSection(int feature, std::vector* data) { @@ -246,6 +257,9 @@ bool RecordFileReader::ReadFeatureSection(int feature, std::vector* data) } SectionDesc section = it->second; data->resize(section.size); + if (section.size == 0) { + return true; + } if (fseek(record_fp_, section.offset, SEEK_SET) != 0) { PLOG(ERROR) << "failed to fseek()"; return false; @@ -291,9 +305,9 @@ std::vector RecordFileReader::ReadBuildIdFeature() { CHECK_LE(p + header->size, end); BuildIdRecord record(header); // Set type explicitly as the perf.data produced by perf doesn't set it. - record.header.type = PERF_RECORD_BUILD_ID; + record.SetTypeAndMisc(PERF_RECORD_BUILD_ID, record.misc()); result.push_back(record); - p += header->size; + p += record.size(); } return result; } diff --git a/simpleperf/testdata/perf_with_kernel_symbol.data b/simpleperf/testdata/perf_with_kernel_symbol.data new file mode 100644 index 00000000..30b14c9f Binary files /dev/null and b/simpleperf/testdata/perf_with_kernel_symbol.data differ diff --git a/simpleperf/thread_tree.cpp b/simpleperf/thread_tree.cpp index d9714042..70ed739e 100644 --- a/simpleperf/thread_tree.cpp +++ b/simpleperf/thread_tree.cpp @@ -87,7 +87,7 @@ void ThreadTree::AddKernelMap(uint64_t start_addr, uint64_t len, uint64_t pgoff, return; } Dso* dso = FindKernelDsoOrNew(filename); - MapEntry* map = AllocateMap(MapEntry(start_addr, len, pgoff, time, dso)); + MapEntry* map = AllocateMap(MapEntry(start_addr, len, pgoff, time, dso, true)); FixOverlappedMap(&kernel_map_tree_, map); auto pair = kernel_map_tree_.insert(map); CHECK(pair.second); @@ -95,9 +95,6 @@ void ThreadTree::AddKernelMap(uint64_t start_addr, uint64_t len, uint64_t pgoff, Dso* ThreadTree::FindKernelDsoOrNew(const std::string& filename) { if (filename == DEFAULT_KERNEL_MMAP_NAME) { - if (kernel_dso_ == nullptr) { - kernel_dso_ = Dso::CreateDso(DSO_KERNEL); - } return kernel_dso_.get(); } auto it = module_dso_tree_.find(filename); @@ -112,7 +109,7 @@ void ThreadTree::AddThreadMap(int pid, int tid, uint64_t start_addr, uint64_t le uint64_t time, const std::string& filename) { ThreadEntry* thread = FindThreadOrNew(pid, tid); Dso* dso = FindUserDsoOrNew(filename); - MapEntry* map = AllocateMap(MapEntry(start_addr, len, pgoff, time, dso)); + MapEntry* map = AllocateMap(MapEntry(start_addr, len, pgoff, time, dso, false)); FixOverlappedMap(&thread->maps, map); auto pair = thread->maps.insert(map); CHECK(pair.second); @@ -145,13 +142,13 @@ void ThreadTree::FixOverlappedMap(std::set* map_set, c MapEntry* old = *it; if (old->start_addr < map->start_addr) { MapEntry* before = AllocateMap(MapEntry(old->start_addr, map->start_addr - old->start_addr, - old->pgoff, old->time, old->dso)); + old->pgoff, old->time, old->dso, old->in_kernel)); map_set->insert(before); } if (old->get_end_addr() > map->get_end_addr()) { MapEntry* after = AllocateMap( MapEntry(map->get_end_addr(), old->get_end_addr() - map->get_end_addr(), - map->get_end_addr() - old->start_addr + old->pgoff, old->time, old->dso)); + map->get_end_addr() - old->start_addr + old->pgoff, old->time, old->dso, old->in_kernel)); map_set->insert(after); } @@ -167,7 +164,7 @@ static bool IsAddrInMap(uint64_t addr, const MapEntry* map) { static MapEntry* FindMapByAddr(const std::set& maps, uint64_t addr) { // Construct a map_entry which is strictly after the searched map_entry, based on MapComparator. MapEntry find_map(addr, std::numeric_limits::max(), 0, - std::numeric_limits::max(), nullptr); + std::numeric_limits::max(), nullptr, false); auto it = maps.upper_bound(&find_map); if (it != maps.begin() && IsAddrInMap(addr, *--it)) { return *it; @@ -202,6 +199,13 @@ const Symbol* ThreadTree::FindSymbol(const MapEntry* map, uint64_t ip) { vaddr_in_file = ip - map->start_addr + map->dso->MinVirtualAddress(); } const Symbol* symbol = map->dso->FindSymbol(vaddr_in_file); + if (symbol == nullptr && map->in_kernel && map->dso != kernel_dso_.get()) { + // It is in a kernel module, but we can't find the kernel module file, or + // the kernel module file contains no symbol. Try finding the symbol in + // /proc/kallsyms. + vaddr_in_file = ip; + symbol = kernel_dso_->FindSymbol(vaddr_in_file); + } if (symbol == nullptr) { symbol = &unknown_symbol_; } @@ -221,18 +225,18 @@ void ThreadTree::Clear() { } // namespace simpleperf void BuildThreadTree(const Record& record, ThreadTree* thread_tree) { - if (record.header.type == PERF_RECORD_MMAP) { + if (record.type() == PERF_RECORD_MMAP) { const MmapRecord& r = *static_cast(&record); - if ((r.header.misc & PERF_RECORD_MISC_CPUMODE_MASK) == PERF_RECORD_MISC_KERNEL) { + if (r.InKernel()) { thread_tree->AddKernelMap(r.data.addr, r.data.len, r.data.pgoff, r.sample_id.time_data.time, r.filename); } else { thread_tree->AddThreadMap(r.data.pid, r.data.tid, r.data.addr, r.data.len, r.data.pgoff, r.sample_id.time_data.time, r.filename); } - } else if (record.header.type == PERF_RECORD_MMAP2) { + } else if (record.type() == PERF_RECORD_MMAP2) { const Mmap2Record& r = *static_cast(&record); - if ((r.header.misc & PERF_RECORD_MISC_CPUMODE_MASK) == PERF_RECORD_MISC_KERNEL) { + if (r.InKernel()) { thread_tree->AddKernelMap(r.data.addr, r.data.len, r.data.pgoff, r.sample_id.time_data.time, r.filename); } else { @@ -241,11 +245,19 @@ void BuildThreadTree(const Record& record, ThreadTree* thread_tree) { thread_tree->AddThreadMap(r.data.pid, r.data.tid, r.data.addr, r.data.len, r.data.pgoff, r.sample_id.time_data.time, filename); } - } else if (record.header.type == PERF_RECORD_COMM) { + } else if (record.type() == PERF_RECORD_COMM) { const CommRecord& r = *static_cast(&record); thread_tree->AddThread(r.data.pid, r.data.tid, r.comm); - } else if (record.header.type == PERF_RECORD_FORK) { + } else if (record.type() == PERF_RECORD_FORK) { const ForkRecord& r = *static_cast(&record); thread_tree->ForkThread(r.data.pid, r.data.tid, r.data.ppid, r.data.ptid); + } else if (record.type() == SIMPLE_PERF_RECORD_KERNEL_SYMBOL) { + const auto& r = *static_cast(&record); + static std::string kallsyms; + kallsyms += r.kallsyms; + if (r.end_of_symbols) { + Dso::SetKallsyms(std::move(kallsyms)); + kallsyms.clear(); + } } } diff --git a/simpleperf/thread_tree.h b/simpleperf/thread_tree.h index 6dc7dd7e..5d7d0c19 100644 --- a/simpleperf/thread_tree.h +++ b/simpleperf/thread_tree.h @@ -33,9 +33,10 @@ struct MapEntry { uint64_t pgoff; uint64_t time; // Map creation time. Dso* dso; + bool in_kernel; - MapEntry(uint64_t start_addr, uint64_t len, uint64_t pgoff, uint64_t time, Dso* dso) - : start_addr(start_addr), len(len), pgoff(pgoff), time(time), dso(dso) { + MapEntry(uint64_t start_addr, uint64_t len, uint64_t pgoff, uint64_t time, Dso* dso, bool in_kernel) + : start_addr(start_addr), len(len), pgoff(pgoff), time(time), dso(dso), in_kernel(in_kernel) { } MapEntry() { } @@ -61,7 +62,8 @@ class ThreadTree { ThreadTree() : unknown_symbol_("unknown", 0, std::numeric_limits::max()) { unknown_dso_ = Dso::CreateDso(DSO_ELF_FILE, "unknown"); unknown_map_ = - MapEntry(0, std::numeric_limits::max(), 0, 0, unknown_dso_.get()); + MapEntry(0, std::numeric_limits::max(), 0, 0, unknown_dso_.get(), false); + kernel_dso_ = Dso::CreateDso(DSO_KERNEL); } void AddThread(int pid, int tid, const std::string& comm); diff --git a/simpleperf/utils.cpp b/simpleperf/utils.cpp index 2eaac42c..0068b8e5 100644 --- a/simpleperf/utils.cpp +++ b/simpleperf/utils.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -259,3 +260,44 @@ bool IsRoot() { } return is_root == 1; } + +bool ProcessKernelSymbols(std::string& symbol_data, + std::function callback) { + char* p = &symbol_data[0]; + char* data_end = p + symbol_data.size(); + while (p < data_end) { + char* line_end = strchr(p, '\n'); + if (line_end != nullptr) { + *line_end = '\0'; + } + size_t line_size = (line_end != nullptr) ? (line_end - p) : (data_end - p); + // Parse line like: ffffffffa005c4e4 d __warned.41698 [libsas] + char name[line_size]; + char module[line_size]; + strcpy(module, ""); + + KernelSymbol symbol; + int ret = sscanf(p, "%" PRIx64 " %c %s%s", &symbol.addr, &symbol.type, name, module); + if (line_end != nullptr) { + *line_end = '\n'; + p = line_end + 1; + } else { + p = data_end; + } + if (ret >= 3) { + symbol.name = name; + size_t module_len = strlen(module); + if (module_len > 2 && module[0] == '[' && module[module_len - 1] == ']') { + module[module_len - 1] = '\0'; + symbol.module = &module[1]; + } else { + symbol.module = nullptr; + } + + if (callback(symbol)) { + return true; + } + } + } + return false; +} diff --git a/simpleperf/utils.h b/simpleperf/utils.h index 1f02e178..fc47485d 100644 --- a/simpleperf/utils.h +++ b/simpleperf/utils.h @@ -125,4 +125,14 @@ bool GetLogSeverity(const std::string& name, android::base::LogSeverity* severit bool IsRoot(); +struct KernelSymbol { + uint64_t addr; + char type; + const char* name; + const char* module; // If nullptr, the symbol is not in a kernel module. +}; + +bool ProcessKernelSymbols(std::string& symbol_data, + std::function callback); + #endif // SIMPLE_PERF_UTILS_H_ diff --git a/simpleperf/utils_test.cpp b/simpleperf/utils_test.cpp new file mode 100644 index 00000000..23c669e4 --- /dev/null +++ b/simpleperf/utils_test.cpp @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "utils.h" + +static bool ModulesMatch(const char* p, const char* q) { + if (p == nullptr && q == nullptr) { + return true; + } + if (p != nullptr && q != nullptr) { + return strcmp(p, q) == 0; + } + return false; +} + +static bool KernelSymbolsMatch(const KernelSymbol& sym1, + const KernelSymbol& sym2) { + return sym1.addr == sym2.addr && sym1.type == sym2.type && + strcmp(sym1.name, sym2.name) == 0 && + ModulesMatch(sym1.module, sym2.module); +} + +TEST(environment, ProcessKernelSymbols) { + std::string data = + "ffffffffa005c4e4 d __warned.41698 [libsas]\n" + "aaaaaaaaaaaaaaaa T _text\n" + "cccccccccccccccc c ccccc\n"; + KernelSymbol expected_symbol; + expected_symbol.addr = 0xffffffffa005c4e4ULL; + expected_symbol.type = 'd'; + expected_symbol.name = "__warned.41698"; + expected_symbol.module = "libsas"; + ASSERT_TRUE(ProcessKernelSymbols( + data, + std::bind(&KernelSymbolsMatch, std::placeholders::_1, expected_symbol))); + + expected_symbol.addr = 0xaaaaaaaaaaaaaaaaULL; + expected_symbol.type = 'T'; + expected_symbol.name = "_text"; + expected_symbol.module = nullptr; + ASSERT_TRUE(ProcessKernelSymbols( + data, + std::bind(&KernelSymbolsMatch, std::placeholders::_1, expected_symbol))); + + expected_symbol.name = "non_existent_symbol"; + ASSERT_FALSE(ProcessKernelSymbols( + data, + std::bind(&KernelSymbolsMatch, std::placeholders::_1, expected_symbol))); +}