summaryrefslogtreecommitdiff
path: root/simpleperf
diff options
context:
space:
mode:
authorYabin Cui <yabinc@google.com>2021-03-23 14:58:25 -0700
committerYabin Cui <yabinc@google.com>2021-03-30 11:41:35 -0700
commit16f41ffde6535be1663d628cc8e25666b2a7af97 (patch)
tree7a5ed3730121bd9ace2b4ac558f17048c56a5744 /simpleperf
parent58740ffd118bad9e29ce3dbe35e688e3f98cb371 (diff)
downloadextras-16f41ffde6535be1663d628cc8e25666b2a7af97.tar.gz
simpleperf: support kernel etm data in inject cmd.
Add extra info in etm_branch_list.proto to decode kernel etm data. Bug: 183135316 Test: run simpleperf_unit_test Change-Id: I165d28c4e0f3a26c09741238336949bcff3902ca
Diffstat (limited to 'simpleperf')
-rw-r--r--simpleperf/ETMDecoder.cpp47
-rw-r--r--simpleperf/ETMDecoder.h11
-rw-r--r--simpleperf/cmd_inject.cpp106
-rw-r--r--simpleperf/cmd_inject_test.cpp24
-rw-r--r--simpleperf/dso.cpp17
-rw-r--r--simpleperf/dso.h4
-rw-r--r--simpleperf/etm_branch_list.proto14
-rw-r--r--simpleperf/read_elf.cpp1
-rw-r--r--simpleperf/read_elf.h1
-rw-r--r--simpleperf/read_elf_test.cpp1
-rw-r--r--simpleperf/testdata/etm/perf_kernel.databin0 -> 117363 bytes
-rw-r--r--simpleperf/testdata/etm/rq_stats.kobin0 -> 13176 bytes
12 files changed, 190 insertions, 36 deletions
diff --git a/simpleperf/ETMDecoder.cpp b/simpleperf/ETMDecoder.cpp
index b077de51..546f2988 100644
--- a/simpleperf/ETMDecoder.cpp
+++ b/simpleperf/ETMDecoder.cpp
@@ -16,6 +16,9 @@
#include "ETMDecoder.h"
+#include <sstream>
+
+#include <android-base/expected.h>
#include <android-base/logging.h>
#include <android-base/strings.h>
#include <llvm/Support/MemoryBuffer.h>
@@ -782,21 +785,38 @@ std::unique_ptr<ETMDecoder> ETMDecoder::Create(const AuxTraceInfoRecord& auxtrac
// Use OpenCSD instruction decoder to convert branches to instruction addresses.
class BranchDecoder {
public:
- bool Init(Dso* dso) {
+ android::base::expected<void, std::string> Init(Dso* dso) {
ElfStatus status;
elf_ = ElfFile::Open(dso->GetDebugFilePath(), &status);
if (!elf_) {
- return false;
+ std::stringstream ss;
+ ss << status;
+ return android::base::unexpected(ss.str());
+ }
+ if (dso->type() == DSO_KERNEL_MODULE) {
+ // Kernel module doesn't have program header. So create a fake one mapping to .text section.
+ for (const auto& section : elf_->GetSectionHeader()) {
+ if (section.name == ".text") {
+ segments_.resize(1);
+ segments_[0].is_executable = true;
+ segments_[0].is_load = true;
+ segments_[0].file_offset = section.file_offset;
+ segments_[0].file_size = section.size;
+ segments_[0].vaddr = section.vaddr;
+ break;
+ }
+ }
+ } else {
+ segments_ = elf_->GetProgramHeader();
+ auto it = std::remove_if(segments_.begin(), segments_.end(),
+ [](const ElfSegment& s) { return !s.is_executable; });
+ segments_.resize(it - segments_.begin());
}
- segments_ = elf_->GetProgramHeader();
- auto it = std::remove_if(segments_.begin(), segments_.end(),
- [](const ElfSegment& s) { return !s.is_executable; });
- segments_.resize(it - segments_.begin());
if (segments_.empty()) {
- return false;
+ return android::base::unexpected("no segments");
}
buffer_ = elf_->GetMemoryBuffer();
- return true;
+ return {};
}
void SetAddr(uint64_t addr, bool is_thumb) {
@@ -844,15 +864,14 @@ class BranchDecoder {
InstructionDecoder instruction_decoder_;
};
-bool ConvertBranchMapToInstrRanges(
- Dso* dso, const std::map<uint64_t, std::map<std::vector<bool>, uint64_t>>& branch_map,
- const ETMDecoder::InstrRangeCallbackFn& callback) {
+android::base::expected<void, std::string> ConvertBranchMapToInstrRanges(
+ Dso* dso, const BranchMap& branch_map, const ETMDecoder::InstrRangeCallbackFn& callback) {
ETMInstrRange instr_range;
instr_range.dso = dso;
BranchDecoder decoder;
- if (!decoder.Init(dso)) {
- return false;
+ if (auto result = decoder.Init(dso); !result.ok()) {
+ return result;
}
for (const auto& addr_p : branch_map) {
@@ -891,7 +910,7 @@ bool ConvertBranchMapToInstrRanges(
}
}
}
- return true;
+ return {};
}
} // namespace simpleperf \ No newline at end of file
diff --git a/simpleperf/ETMDecoder.h b/simpleperf/ETMDecoder.h
index eeb7aded..f8cec366 100644
--- a/simpleperf/ETMDecoder.h
+++ b/simpleperf/ETMDecoder.h
@@ -20,6 +20,8 @@
#include <memory>
#include <string>
+#include <android-base/expected.h>
+
#include "record.h"
#include "thread_tree.h"
@@ -76,11 +78,12 @@ class ETMDecoder {
virtual bool FinishData() = 0;
};
-// branch_map: map from addrs to a map of (branch_list, count).
+// Map from addrs to a map of (branch_list, count).
// Use maps instead of unordered_maps. Because it helps locality by decoding instructions for sorted
// addresses.
-bool ConvertBranchMapToInstrRanges(
- Dso* dso, const std::map<uint64_t, std::map<std::vector<bool>, uint64_t>>& branch_map,
- const ETMDecoder::InstrRangeCallbackFn& callback);
+using BranchMap = std::map<uint64_t, std::map<std::vector<bool>, uint64_t>>;
+
+android::base::expected<void, std::string> ConvertBranchMapToInstrRanges(
+ Dso* dso, const BranchMap& branch_map, const ETMDecoder::InstrRangeCallbackFn& callback);
} // namespace simpleperf \ No newline at end of file
diff --git a/simpleperf/cmd_inject.cpp b/simpleperf/cmd_inject.cpp
index 28abee08..36332c95 100644
--- a/simpleperf/cmd_inject.cpp
+++ b/simpleperf/cmd_inject.cpp
@@ -151,7 +151,7 @@ class InjectCommand : public Command {
}
// 4. Write output file.
- if (!PostProcess()) {
+ if (!WriteOutput()) {
return false;
}
output_fp_.reset(nullptr);
@@ -268,6 +268,11 @@ class InjectCommand : public Command {
}
return etm_decoder_->ProcessData(aux_data_buffer_.data(), aux_size);
}
+ } else if (r->type() == PERF_RECORD_MMAP && r->InKernel()) {
+ auto& mmap_r = *static_cast<MmapRecord*>(r);
+ if (android::base::StartsWith(mmap_r.filename, DEFAULT_KERNEL_MMAP_NAME)) {
+ kernel_map_start_addr_ = mmap_r.data->addr;
+ }
}
return true;
}
@@ -340,7 +345,12 @@ class InjectCommand : public Command {
for (size_t i = 0; i < branch_list_proto.binaries_size(); i++) {
const auto& binary_proto = branch_list_proto.binaries(i);
BuildId build_id(binary_proto.build_id());
- std::unique_ptr<Dso> dso = Dso::CreateElfDsoWithBuildId(binary_proto.path(), build_id);
+ std::optional<DsoType> dso_type = ToDsoType(binary_proto.type());
+ if (!dso_type.has_value()) {
+ return false;
+ }
+ std::unique_ptr<Dso> dso =
+ Dso::CreateDsoWithBuildId(dso_type.value(), binary_proto.path(), build_id);
if (!dso || !FilterDso(dso.get()) || !check_build_id(dso.get(), build_id)) {
continue;
}
@@ -348,16 +358,21 @@ class InjectCommand : public Command {
Dso* dso_p = dso.get();
branch_list_dso_v_.emplace_back(dso.release());
auto branch_map = BuildBranchMap(binary_proto);
- if (!ConvertBranchMapToInstrRanges(dso_p, branch_map, callback)) {
- LOG(WARNING) << "failed to build instr ranges for binary " << dso_p->Path();
+
+ if (dso_p->type() == DSO_KERNEL) {
+ ModifyBranchMapForKernel(branch_list_proto, dso_p, branch_map);
+ }
+
+ if (auto result = ConvertBranchMapToInstrRanges(dso_p, branch_map, callback); !result.ok()) {
+ LOG(WARNING) << "failed to build instr ranges for binary " << dso_p->Path() << ": "
+ << result.error();
}
}
return true;
}
- std::map<uint64_t, std::map<std::vector<bool>, uint64_t>> BuildBranchMap(
- const proto::ETMBranchList_Binary& binary_proto) {
- std::map<uint64_t, std::map<std::vector<bool>, uint64_t>> branch_map;
+ BranchMap BuildBranchMap(const proto::ETMBranchList_Binary& binary_proto) {
+ BranchMap branch_map;
for (size_t i = 0; i < binary_proto.addrs_size(); i++) {
const auto& addr_proto = binary_proto.addrs(i);
auto& b_map = branch_map[addr_proto.addr()];
@@ -371,16 +386,31 @@ class InjectCommand : public Command {
return branch_map;
}
- bool PostProcess() {
+ void ModifyBranchMapForKernel(const proto::ETMBranchList& branch_list_proto, Dso* dso,
+ BranchMap& branch_map) {
+ uint64_t kernel_map_start_addr = branch_list_proto.kernel_start_addr();
+ if (kernel_map_start_addr == 0) {
+ return;
+ }
+ // Addresses are still kernel ip addrs in memory. Need to convert them to vaddrs in vmlinux.
+ BranchMap new_branch_map;
+ for (auto& p : branch_map) {
+ uint64_t vaddr_in_file = dso->IpToVaddrInFile(p.first, kernel_map_start_addr, 0);
+ new_branch_map[vaddr_in_file] = std::move(p.second);
+ }
+ branch_map = std::move(new_branch_map);
+ }
+
+ bool WriteOutput() {
if (output_format_ == OutputFormat::AutoFDO) {
- PostProcessInstrRange();
+ GenerateInstrRange();
return true;
}
CHECK(output_format_ == OutputFormat::BranchList);
- return PostProcessBranchList();
+ return GenerateBranchList();
}
- void PostProcessInstrRange() {
+ void GenerateInstrRange() {
// autofdo_binary_map is used to store instruction ranges, which can have a large amount. And it
// has a larger access time (instruction ranges * executed time). So it's better to use
// unorder_maps to speed up access time. But we also want a stable output here, to compare
@@ -452,7 +482,7 @@ class InjectCommand : public Command {
return 0;
}
- bool PostProcessBranchList() {
+ bool GenerateBranchList() {
// Don't produce empty output file.
if (branch_list_binary_map_.empty()) {
LOG(INFO) << "Skip empty output file.";
@@ -474,6 +504,11 @@ class InjectCommand : public Command {
if (!build_id.IsEmpty()) {
binary_proto->set_build_id(build_id.ToString().substr(2));
}
+ auto opt_binary_type = ToProtoBinaryType(dso->type());
+ if (!opt_binary_type.has_value()) {
+ return false;
+ }
+ binary_proto->set_type(opt_binary_type.value());
for (const auto& addr_p : addr_map) {
auto addr_proto = binary_proto->add_addrs();
@@ -488,6 +523,24 @@ class InjectCommand : public Command {
branch_proto->set_count(branch_p.second);
}
}
+
+ if (dso->type() == DSO_KERNEL) {
+ if (kernel_map_start_addr_ == 0) {
+ LOG(WARNING) << "Can't convert kernel ip addresses without kernel start addr. So remove "
+ "branches for the kernel.";
+ branch_list_proto.mutable_binaries()->RemoveLast();
+ continue;
+ }
+ if (dso->GetDebugFilePath() == dso->Path()) {
+ // vmlinux isn't available. We still use kernel ip addr. Put kernel start addr in proto
+ // for address conversion later.
+ branch_list_proto.set_kernel_start_addr(kernel_map_start_addr_);
+ } else {
+ // vmlinux is available. We have converted kernel ip addr to vaddr in vmlinux. So no need
+ // to put kernel start addr in proto.
+ branch_list_proto.set_kernel_start_addr(0);
+ }
+ }
}
if (!branch_list_proto.SerializeToFileDescriptor(fileno(output_fp_.get()))) {
PLOG(ERROR) << "failed to write to output file";
@@ -496,6 +549,34 @@ class InjectCommand : public Command {
return true;
}
+ std::optional<proto::ETMBranchList_Binary::BinaryType> ToProtoBinaryType(DsoType dso_type) {
+ switch (dso_type) {
+ case DSO_ELF_FILE:
+ return proto::ETMBranchList_Binary::ELF_FILE;
+ case DSO_KERNEL:
+ return proto::ETMBranchList_Binary::KERNEL;
+ case DSO_KERNEL_MODULE:
+ return proto::ETMBranchList_Binary::KERNEL_MODULE;
+ default:
+ LOG(ERROR) << "unexpected dso type " << dso_type;
+ return std::nullopt;
+ }
+ }
+
+ std::optional<DsoType> ToDsoType(proto::ETMBranchList_Binary::BinaryType binary_type) {
+ switch (binary_type) {
+ case proto::ETMBranchList_Binary::ELF_FILE:
+ return DSO_ELF_FILE;
+ case proto::ETMBranchList_Binary::KERNEL:
+ return DSO_KERNEL;
+ case proto::ETMBranchList_Binary::KERNEL_MODULE:
+ return DSO_KERNEL_MODULE;
+ default:
+ LOG(ERROR) << "unexpected binary type " << binary_type;
+ return std::nullopt;
+ }
+ }
+
std::regex binary_name_regex_{""}; // Default to match everything.
bool exclude_perf_ = false;
std::string input_filename_ = "perf.data";
@@ -513,6 +594,7 @@ class InjectCommand : public Command {
// Store results for BranchList.
std::unordered_map<Dso*, BranchListBinaryInfo> branch_list_binary_map_;
std::vector<std::unique_ptr<Dso>> branch_list_dso_v_;
+ uint64_t kernel_map_start_addr_ = 0;
};
} // namespace
diff --git a/simpleperf/cmd_inject_test.cpp b/simpleperf/cmd_inject_test.cpp
index 0741c3b6..8668174c 100644
--- a/simpleperf/cmd_inject_test.cpp
+++ b/simpleperf/cmd_inject_test.cpp
@@ -40,6 +40,7 @@ static bool RunInjectCmd(std::vector<std::string>&& args) {
static bool RunInjectCmd(std::vector<std::string>&& args, std::string* output) {
TemporaryFile tmpfile;
+ close(tmpfile.release());
args.insert(args.end(), {"-o", tmpfile.path});
if (!RunInjectCmd(std::move(args))) {
return false;
@@ -94,6 +95,7 @@ TEST(cmd_inject, exclude_perf_option) {
TEST(cmd_inject, output_option) {
TemporaryFile tmpfile;
+ close(tmpfile.release());
ASSERT_TRUE(RunInjectCmd({"--output", "autofdo", "-o", tmpfile.path}));
ASSERT_TRUE(RunInjectCmd({"--output", "branch-list", "-o", tmpfile.path}));
std::string autofdo_data;
@@ -124,3 +126,25 @@ TEST(cmd_inject, skip_empty_output_file) {
ASSERT_FALSE(IsRegularFile(tmpfile.path));
tmpfile.DoNotRemove();
}
+
+TEST(cmd_inject, inject_kernel_data) {
+ const std::string recording_file =
+ GetTestData(std::string("etm") + OS_PATH_SEPARATOR + "perf_kernel.data");
+
+ // Inject directly to autofdo format.
+ TemporaryFile tmpfile;
+ close(tmpfile.release());
+ ASSERT_TRUE(RunInjectCmd({"-i", recording_file, "-o", tmpfile.path}));
+ std::string autofdo_output;
+ ASSERT_TRUE(android::base::ReadFileToString(tmpfile.path, &autofdo_output));
+ ASSERT_NE(autofdo_output.find("rq_stats.ko"), std::string::npos);
+
+ // Inject through etm branch list.
+ TemporaryFile tmpfile2;
+ close(tmpfile2.release());
+ ASSERT_TRUE(RunInjectCmd({"-i", recording_file, "-o", tmpfile.path, "--output", "branch-list"}));
+ ASSERT_TRUE(RunInjectCmd({"-i", tmpfile.path, "-o", tmpfile2.path}));
+ std::string output;
+ ASSERT_TRUE(android::base::ReadFileToString(tmpfile2.path, &output));
+ ASSERT_EQ(output, autofdo_output);
+}
diff --git a/simpleperf/dso.cpp b/simpleperf/dso.cpp
index 77a8b15f..672b24a3 100644
--- a/simpleperf/dso.cpp
+++ b/simpleperf/dso.cpp
@@ -910,9 +910,20 @@ std::unique_ptr<Dso> Dso::CreateDso(DsoType dso_type, const std::string& dso_pat
return nullptr;
}
-std::unique_ptr<Dso> Dso::CreateElfDsoWithBuildId(const std::string& dso_path, BuildId& build_id) {
- return std::unique_ptr<Dso>(
- new ElfDso(dso_path, debug_elf_file_finder_.FindDebugFile(dso_path, false, build_id)));
+std::unique_ptr<Dso> Dso::CreateDsoWithBuildId(DsoType dso_type, const std::string& dso_path,
+ BuildId& build_id) {
+ std::string debug_path = debug_elf_file_finder_.FindDebugFile(dso_path, false, build_id);
+ switch (dso_type) {
+ case DSO_ELF_FILE:
+ return std::unique_ptr<Dso>(new ElfDso(dso_path, debug_path));
+ case DSO_KERNEL:
+ return std::unique_ptr<Dso>(new KernelDso(dso_path, debug_path));
+ case DSO_KERNEL_MODULE:
+ return std::unique_ptr<Dso>(new KernelModuleDso(dso_path, debug_path, 0, 0, nullptr));
+ default:
+ LOG(FATAL) << "Unexpected dso_type " << static_cast<int>(dso_type);
+ }
+ return nullptr;
}
std::unique_ptr<Dso> Dso::CreateKernelModuleDso(const std::string& dso_path, uint64_t memory_start,
diff --git a/simpleperf/dso.h b/simpleperf/dso.h
index 15a640dd..b2e99df2 100644
--- a/simpleperf/dso.h
+++ b/simpleperf/dso.h
@@ -130,8 +130,8 @@ class Dso {
static std::unique_ptr<Dso> CreateDso(DsoType dso_type, const std::string& dso_path,
bool force_64bit = false);
- static std::unique_ptr<Dso> CreateElfDsoWithBuildId(const std::string& dso_path,
- BuildId& build_id);
+ static std::unique_ptr<Dso> CreateDsoWithBuildId(DsoType dso_type, const std::string& dso_path,
+ BuildId& build_id);
static std::unique_ptr<Dso> CreateKernelModuleDso(const std::string& dso_path,
uint64_t memory_start, uint64_t memory_end,
Dso* kernel_dso);
diff --git a/simpleperf/etm_branch_list.proto b/simpleperf/etm_branch_list.proto
index 8c0cfa25..396f7525 100644
--- a/simpleperf/etm_branch_list.proto
+++ b/simpleperf/etm_branch_list.proto
@@ -40,10 +40,22 @@ message ETMBranchList {
}
repeated Address addrs = 3;
+
+ enum BinaryType {
+ ELF_FILE = 0;
+ KERNEL = 1;
+ KERNEL_MODULE = 2;
+ }
+ BinaryType type = 4;
}
// Used to identify format in generated proto files.
// Should always be "simpleperf:EtmBranchList".
string magic = 1;
repeated Binary binaries = 2;
-} \ No newline at end of file
+
+ // kernel_start_addr is used to convert kernel ip address to vaddr in vmlinux.
+ // If it is zero, the Address in KERNEL binary has been converted to vaddr.
+ // Otherwise, the Address in KERNEL binary is still ip address, and need to be converted later.
+ uint64 kernel_start_addr = 3;
+}
diff --git a/simpleperf/read_elf.cpp b/simpleperf/read_elf.cpp
index 2322db61..db3d242a 100644
--- a/simpleperf/read_elf.cpp
+++ b/simpleperf/read_elf.cpp
@@ -350,6 +350,7 @@ class ElfFileImpl<llvm::object::ELFObjectFile<ELFT>> : public ElfFile {
}
sections[i].vaddr = shdr.sh_addr;
sections[i].file_offset = shdr.sh_offset;
+ sections[i].size = shdr.sh_size;
}
return sections;
}
diff --git a/simpleperf/read_elf.h b/simpleperf/read_elf.h
index bf5cb8b2..3ade27fa 100644
--- a/simpleperf/read_elf.h
+++ b/simpleperf/read_elf.h
@@ -73,6 +73,7 @@ struct ElfSection {
std::string name;
uint64_t vaddr = 0;
uint64_t file_offset = 0;
+ uint64_t size = 0;
};
class ElfFile {
diff --git a/simpleperf/read_elf_test.cpp b/simpleperf/read_elf_test.cpp
index 80b1a413..e2a2cd88 100644
--- a/simpleperf/read_elf_test.cpp
+++ b/simpleperf/read_elf_test.cpp
@@ -255,4 +255,5 @@ TEST(read_elf, GetSectionHeader) {
ASSERT_EQ(sections[13].name, ".text");
ASSERT_EQ(sections[13].vaddr, 0x400400);
ASSERT_EQ(sections[13].file_offset, 0x400);
+ ASSERT_EQ(sections[13].size, 0x1b2);
}
diff --git a/simpleperf/testdata/etm/perf_kernel.data b/simpleperf/testdata/etm/perf_kernel.data
new file mode 100644
index 00000000..11f399f9
--- /dev/null
+++ b/simpleperf/testdata/etm/perf_kernel.data
Binary files differ
diff --git a/simpleperf/testdata/etm/rq_stats.ko b/simpleperf/testdata/etm/rq_stats.ko
new file mode 100644
index 00000000..c35dcd3f
--- /dev/null
+++ b/simpleperf/testdata/etm/rq_stats.ko
Binary files differ