summaryrefslogtreecommitdiff
path: root/simpleperf
diff options
context:
space:
mode:
authorYabin Cui <yabinc@google.com>2016-05-25 14:08:05 -0700
committerYabin Cui <yabinc@google.com>2016-05-31 14:16:14 -0700
commitb42129797f17f990ea5a88c6bdc7446000cc361c (patch)
treecbc62824b5cd6bbaa0e012b21529d240fa0a868c /simpleperf
parentc8642b2dcfd60f12a0cdbd1f9361967d264d2a3a (diff)
downloadextras-b42129797f17f990ea5a88c6bdc7446000cc361c.tar.gz
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
Diffstat (limited to 'simpleperf')
-rw-r--r--simpleperf/Android.mk1
-rw-r--r--simpleperf/cmd_record.cpp327
-rw-r--r--simpleperf/cmd_record_test.cpp85
-rw-r--r--simpleperf/cmd_report.cpp3
-rw-r--r--simpleperf/cmd_report_test.cpp6
-rw-r--r--simpleperf/dso.cpp43
-rw-r--r--simpleperf/dso.h6
-rw-r--r--simpleperf/environment.cpp45
-rw-r--r--simpleperf/environment.h11
-rw-r--r--simpleperf/environment_test.cpp48
-rw-r--r--simpleperf/get_test_data.h3
-rw-r--r--simpleperf/nonlinux_support/nonlinux_support.cpp4
-rw-r--r--simpleperf/record.cpp158
-rw-r--r--simpleperf/record.h51
-rw-r--r--simpleperf/record_equal_test.h10
-rw-r--r--simpleperf/record_file_reader.cpp52
-rw-r--r--simpleperf/testdata/perf_with_kernel_symbol.databin0 -> 5325508 bytes
-rw-r--r--simpleperf/thread_tree.cpp40
-rw-r--r--simpleperf/thread_tree.h8
-rw-r--r--simpleperf/utils.cpp42
-rw-r--r--simpleperf/utils.h10
-rw-r--r--simpleperf/utils_test.cpp64
22 files changed, 665 insertions, 352 deletions
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 <vector>
#include <android-base/logging.h>
+#include <android-base/file.h>
#include <android-base/strings.h>
#include "command.h"
@@ -53,65 +54,66 @@ static std::unordered_map<std::string, uint64_t> 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[,<dump_stack_size>]\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[,<dump_stack_size>]\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<std::string>& args);
private:
- bool ParseOptions(const std::vector<std::string>& args, std::vector<std::string>* non_option_args);
+ bool ParseOptions(const std::vector<std::string>& args,
+ std::vector<std::string>* non_option_args);
bool AddMeasuredEventType(const std::string& event_type_name);
bool SetEventSelection();
bool CreateAndInitRecordFile();
- std::unique_ptr<RecordFileWriter> CreateRecordFile(const std::string& filename);
+ std::unique_ptr<RecordFileWriter> 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<pid_t>& selected_threads);
+ bool all_threads,
+ const std::vector<pid_t>& 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<std::string>& args);
bool DumpBuildIdFeature();
void CollectHitFileInfo(Record* record);
- std::pair<std::string, uint64_t> TestForEmbeddedElf(Dso *dso, uint64_t pgoff);
+ std::pair<std::string, uint64_t> 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<pid_t> monitored_threads_;
std::vector<int> cpus_;
std::vector<EventTypeAndModifier> measured_event_types_;
@@ -209,19 +218,21 @@ bool RecordCommand::Run(const std::vector<std::string>& 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<std::string>& 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<std::string>& args,
std::vector<std::string>* non_option_args) {
std::set<pid_t> 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<std::string>& 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<uint32_t>(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<std::string>& args,
if (!NextArgumentOrError(args, &i)) {
return false;
}
- std::vector<std::string> branch_sampling_types = android::base::Split(args[i], ",");
+ std::vector<std::string> 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<std::string>& 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<std::string>& 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<std::string>& 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<std::string>& args,
}
bool RecordCommand::AddMeasuredEventType(const std::string& event_type_name) {
- std::unique_ptr<EventTypeAndModifier> event_type_modifier = ParseEventType(event_type_name);
+ std::unique_ptr<EventTypeAndModifier> 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<std::unique_ptr<EventFd>>* 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<RecordFileWriter> RecordCommand::CreateRecordFile(const std::string& filename) {
- std::unique_ptr<RecordFileWriter> writer = RecordFileWriter::CreateInstance(filename);
+std::unique_ptr<RecordFileWriter> RecordCommand::CreateRecordFile(
+ const std::string& filename) {
+ std::unique_ptr<RecordFileWriter> writer =
+ RecordFileWriter::CreateInstance(filename);
if (writer == nullptr) {
return nullptr;
}
@@ -516,19 +547,49 @@ std::unique_ptr<RecordFileWriter> 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<KernelSymbolRecord> 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<KernelMmap> 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<pid_t>& selected_threads) {
+bool RecordCommand::DumpThreadCommAndMmaps(
+ const perf_event_attr* attr, uint64_t event_id, bool all_threads,
+ const std::vector<pid_t>& selected_threads) {
std::vector<ThreadComm> 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<class RecordType>
+template <class RecordType>
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<SampleRecord*>(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<char>& 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<uint64_t> unwind_ips = UnwindCallChain(arch, *thread, regs, stack,
- strict_arch_check);
+ std::vector<uint64_t> 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<std::string>& args) {
thread_tree_.Clear();
- std::unique_ptr<RecordFileReader> reader = RecordFileReader::CreateInstance(record_filename_);
+ std::unique_ptr<RecordFileReader> reader =
+ RecordFileReader::CreateInstance(record_filename_);
if (reader == nullptr) {
return false;
}
@@ -714,13 +783,15 @@ bool RecordCommand::PostUnwind(const std::vector<std::string>& 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<std::string>& args) {
+bool RecordCommand::DumpAdditionalFeatures(
+ const std::vector<std::string>& 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<std::string>& 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<std::string>& 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<SampleRecord*>(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<Command>(new RecordCommand()); });
+ RegisterCommand("record",
+ [] { return std::unique_ptr<Command>(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<Command> RecordCmd() {
return CreateCommandInstance("record");
}
-static bool RunRecordCmd(std::vector<std::string> v, const char* output_file = nullptr) {
+static bool RunRecordCmd(std::vector<std::string> v,
+ const char* output_file = nullptr) {
std::unique_ptr<TemporaryFile> tmpfile;
std::string out_file;
if (output_file != nullptr) {
@@ -48,9 +49,7 @@ static bool RunRecordCmd(std::vector<std::string> 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<RecordFileReader> reader = RecordFileReader::CreateInstance(tmpfile.path);
+ std::unique_ptr<RecordFileReader> reader =
+ RecordFileReader::CreateInstance(tmpfile.path);
ASSERT_TRUE(reader != nullptr);
std::vector<std::unique_ptr<Record>> 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<const MmapRecord*>(record.get());
+ if (record->type() == PERF_RECORD_MMAP) {
+ const MmapRecord* mmap_record =
+ static_cast<const MmapRecord*>(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<RecordFileReader> reader = RecordFileReader::CreateInstance(tmpfile.path);
+ std::unique_ptr<RecordFileReader> 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<std::unique_ptr<Workload>> 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<std::unique_ptr<Workload>> 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<RecordFileReader> reader =
+ RecordFileReader::CreateInstance(path);
+ ASSERT_TRUE(reader != nullptr);
+ std::vector<std::unique_ptr<Record>> 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<KernelSymbolRecord*>(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> record) {
BuildThreadTree(*record, &thread_tree_);
- if (record->header.type == PERF_RECORD_SAMPLE) {
+ if (record->type() == PERF_RECORD_SAMPLE) {
sample_tree_builder_->ProcessSampleRecord(
*static_cast<const SampleRecord*>(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<Command> 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 <limits>
#include <vector>
+#include <android-base/file.h>
#include <android-base/logging.h>
#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<std::string, BuildId> 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<std::pair<std::string, BuildId>>& build_ids);
static std::unique_ptr<Dso> 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<std::string, BuildId> 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<int> GetCpusFromString(const std::string& s) {
return std::vector<int>(cpu_set.begin(), cpu_set.end());
}
-bool ProcessKernelSymbols(const std::string& symbol_file,
- std::function<bool(const KernelSymbol&)> 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<KernelMmap> GetLoadedModules() {
std::vector<KernelMmap> result;
FILE* fp = fopen("/proc/modules", "re");
@@ -162,6 +127,16 @@ static std::vector<KernelMmap> 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<KernelMmap>();
+ }
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<pid_t>
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<bool(const KernelSymbol&)> 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 <gtest/gtest.h>
-#include <functional>
-#include <android-base/file.h>
-#include <android-base/test_utils.h>
-
#include "environment.h"
TEST(environment, GetCpusFromString) {
@@ -28,47 +24,3 @@ TEST(environment, GetCpusFromString) {
ASSERT_EQ(GetCpusFromString("0,2-3"), std::vector<int>({0, 2, 3}));
ASSERT_EQ(GetCpusFromString("1,0-3,3,4"), std::vector<int>({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<uint64_t> UnwindCallChain(ArchType, const ThreadEntry&, const RegSet
return std::vector<uint64_t>();
}
-bool ProcessKernelSymbols(const std::string&, std::function<bool(const KernelSymbol&)>) {
- 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<const char*>(pheader + 1);
- const char* end = reinterpret_cast<const char*>(pheader) + pheader->size;
+ const char* end = reinterpret_cast<const char*>(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<char> MmapRecord::BinaryFormat() const {
- std::vector<char> buf(header.size);
+ std::vector<char> buf(size());
char* p = buf.data();
MoveToBinaryFormat(header, p);
MoveToBinaryFormat(data, p);
@@ -208,7 +205,7 @@ std::vector<char> 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<const char*>(pheader + 1);
- const char* end = reinterpret_cast<const char*>(pheader) + pheader->size;
+ const char* end = reinterpret_cast<const char*>(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<char> Mmap2Record::BinaryFormat() const {
- std::vector<char> buf(header.size);
+ std::vector<char> buf(size());
char* p = buf.data();
MoveToBinaryFormat(header, p);
MoveToBinaryFormat(data, p);
@@ -240,7 +237,7 @@ std::vector<char> 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<const char*>(pheader + 1);
- const char* end = reinterpret_cast<const char*>(pheader) + pheader->size;
+ const char* end = reinterpret_cast<const char*>(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<char> CommRecord::BinaryFormat() const {
- std::vector<char> buf(header.size);
+ std::vector<char> 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<const char*>(pheader + 1);
- const char* end = reinterpret_cast<const char*>(pheader) + pheader->size;
+ const char* end = reinterpret_cast<const char*>(pheader) + size();
MoveFromBinaryFormat(data, p);
CHECK_LE(p, end);
sample_id.ReadFromBinaryFormat(attr, p, end);
}
std::vector<char> ExitOrForkRecord::BinaryFormat() const {
- std::vector<char> buf(header.size);
+ std::vector<char> 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<const char*>(pheader + 1);
- const char* end = reinterpret_cast<const char*>(pheader) + pheader->size;
+ const char* end = reinterpret_cast<const char*>(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<char> SampleRecord::BinaryFormat() const {
- std::vector<char> buf(header.size);
+ std::vector<char> 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<char> 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<void*>(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<const char*>(pheader + 1);
- const char* end = reinterpret_cast<const char*>(pheader) + pheader->size;
+ const char* end = reinterpret_cast<const char*>(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<char> BuildIdRecord::BinaryFormat() const {
- std::vector<char> buf(header.size);
+ std::vector<char> 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<const char*>(pheader + 1);
+ const char* end = reinterpret_cast<const char*>(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<char> KernelSymbolRecord::BinaryFormat() const {
+ std::vector<char> 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<uint32_t>(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<const char*>(pheader + 1);
- const char* end = reinterpret_cast<const char*>(pheader) + pheader->size;
+ const char* end = reinterpret_cast<const char*>(pheader) + size();
data.insert(data.end(), p, end);
}
std::vector<char> UnknownRecord::BinaryFormat() const {
- std::vector<char> buf(header.size);
+ std::vector<char> buf(size());
char* p = buf.data();
MoveToBinaryFormat(header, p);
MoveToBinaryFormat(data.data(), data.size(), p);
@@ -592,6 +628,8 @@ std::unique_ptr<Record> ReadRecordFromBuffer(const perf_event_attr& attr,
return std::unique_ptr<Record>(new ForkRecord(attr, pheader));
case PERF_RECORD_SAMPLE:
return std::unique_ptr<Record>(new SampleRecord(attr, pheader));
+ case SIMPLE_PERF_RECORD_KERNEL_SYMBOL:
+ return std::unique_ptr<Record>(new KernelSymbolRecord(pheader));
default:
return std::unique_ptr<Record>(new UnknownRecord(pheader));
}
@@ -604,10 +642,11 @@ std::vector<std::unique_ptr<Record>> ReadRecordsFromBuffer(const perf_event_attr
const char* end = buf + buf_size;
while (p < end) {
const perf_event_header* header = reinterpret_cast<const perf_event_header*>(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<KernelSymbolRecord> CreateKernelSymbolRecords(const std::string& kallsyms) {
+ std::vector<KernelSymbolRecord> 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 <string>
#include <vector>
+#include <android-base/logging.h>
+
#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<char> 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<char> 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<KernelSymbolRecord> 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<const MmapRecord&>(r1), static_cast<const MmapRecord&>(r2));
- } else if (r1.header.type == PERF_RECORD_COMM) {
+ } else if (r1.type() == PERF_RECORD_COMM) {
CheckCommRecordDataEqual(static_cast<const CommRecord&>(r1), static_cast<const CommRecord&>(r2));
- } else if (r1.header.type == PERF_RECORD_BUILD_ID) {
+ } else if (r1.type() == PERF_RECORD_BUILD_ID) {
CheckBuildIdRecordDataEqual(static_cast<const BuildIdRecord&>(r1),
static_cast<const BuildIdRecord&>(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<Record> RecordFileReader::ReadRecord() {
PLOG(ERROR) << "failed to read file " << filename_;
return nullptr;
}
- perf_event_header* header = reinterpret_cast<perf_event_header*>(&buf[0]);
- if (buf.size() < header->size) {
- buf.resize(header->size);
- header = reinterpret_cast<perf_event_header*>(&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<perf_event_header*>(&buf[0]);
+ if (buf.size() < pheader->size) {
+ buf.resize(pheader->size);
+ pheader = reinterpret_cast<perf_event_header*>(&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<uint64_t*>(&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<uint64_t*>(&buf[event_id_pos_in_sample_records_]);
+ }
} else {
- event_id = *reinterpret_cast<uint64_t*>(
- &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<uint64_t*>(&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<char>* data) {
@@ -246,6 +257,9 @@ bool RecordFileReader::ReadFeatureSection(int feature, std::vector<char>* 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<BuildIdRecord> 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
--- /dev/null
+++ b/simpleperf/testdata/perf_with_kernel_symbol.data
Binary files 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<MapEntry*, MapComparator>* 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<MapEntry*, MapComparator>& 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<uint64_t>::max(), 0,
- std::numeric_limits<uint64_t>::max(), nullptr);
+ std::numeric_limits<uint64_t>::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<const MmapRecord*>(&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<const Mmap2Record*>(&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<const CommRecord*>(&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<const ForkRecord*>(&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<const KernelSymbolRecord*>(&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<unsigned long long>::max()) {
unknown_dso_ = Dso::CreateDso(DSO_ELF_FILE, "unknown");
unknown_map_ =
- MapEntry(0, std::numeric_limits<unsigned long long>::max(), 0, 0, unknown_dso_.get());
+ MapEntry(0, std::numeric_limits<unsigned long long>::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 <dirent.h>
#include <errno.h>
#include <fcntl.h>
+#include <inttypes.h>
#include <stdarg.h>
#include <stdio.h>
#include <sys/stat.h>
@@ -259,3 +260,44 @@ bool IsRoot() {
}
return is_root == 1;
}
+
+bool ProcessKernelSymbols(std::string& symbol_data,
+ std::function<bool(const KernelSymbol&)> 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<bool(const KernelSymbol&)> 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 <gtest/gtest.h>
+
+#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)));
+}