diff options
author | Yabin Cui <yabinc@google.com> | 2021-03-23 14:58:25 -0700 |
---|---|---|
committer | Yabin Cui <yabinc@google.com> | 2021-03-30 11:41:35 -0700 |
commit | 16f41ffde6535be1663d628cc8e25666b2a7af97 (patch) | |
tree | 7a5ed3730121bd9ace2b4ac558f17048c56a5744 /simpleperf | |
parent | 58740ffd118bad9e29ce3dbe35e688e3f98cb371 (diff) | |
download | extras-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.cpp | 47 | ||||
-rw-r--r-- | simpleperf/ETMDecoder.h | 11 | ||||
-rw-r--r-- | simpleperf/cmd_inject.cpp | 106 | ||||
-rw-r--r-- | simpleperf/cmd_inject_test.cpp | 24 | ||||
-rw-r--r-- | simpleperf/dso.cpp | 17 | ||||
-rw-r--r-- | simpleperf/dso.h | 4 | ||||
-rw-r--r-- | simpleperf/etm_branch_list.proto | 14 | ||||
-rw-r--r-- | simpleperf/read_elf.cpp | 1 | ||||
-rw-r--r-- | simpleperf/read_elf.h | 1 | ||||
-rw-r--r-- | simpleperf/read_elf_test.cpp | 1 | ||||
-rw-r--r-- | simpleperf/testdata/etm/perf_kernel.data | bin | 0 -> 117363 bytes | |||
-rw-r--r-- | simpleperf/testdata/etm/rq_stats.ko | bin | 0 -> 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 Binary files differnew file mode 100644 index 00000000..11f399f9 --- /dev/null +++ b/simpleperf/testdata/etm/perf_kernel.data diff --git a/simpleperf/testdata/etm/rq_stats.ko b/simpleperf/testdata/etm/rq_stats.ko Binary files differnew file mode 100644 index 00000000..c35dcd3f --- /dev/null +++ b/simpleperf/testdata/etm/rq_stats.ko |