summaryrefslogtreecommitdiff
path: root/simpleperf/ETMBranchListFile.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'simpleperf/ETMBranchListFile.cpp')
-rw-r--r--simpleperf/ETMBranchListFile.cpp385
1 files changed, 385 insertions, 0 deletions
diff --git a/simpleperf/ETMBranchListFile.cpp b/simpleperf/ETMBranchListFile.cpp
new file mode 100644
index 00000000..c92cf8cc
--- /dev/null
+++ b/simpleperf/ETMBranchListFile.cpp
@@ -0,0 +1,385 @@
+/*
+ * Copyright (C) 2023 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 "ETMBranchListFile.h"
+
+#include "ETMDecoder.h"
+#include "system/extras/simpleperf/etm_branch_list.pb.h"
+
+namespace simpleperf {
+
+static constexpr const char* ETM_BRANCH_LIST_PROTO_MAGIC = "simpleperf:EtmBranchList";
+
+std::string BranchToProtoString(const std::vector<bool>& branch) {
+ size_t bytes = (branch.size() + 7) / 8;
+ std::string res(bytes, '\0');
+ for (size_t i = 0; i < branch.size(); i++) {
+ if (branch[i]) {
+ res[i >> 3] |= 1 << (i & 7);
+ }
+ }
+ return res;
+}
+
+std::vector<bool> ProtoStringToBranch(const std::string& s, size_t bit_size) {
+ std::vector<bool> branch(bit_size, false);
+ for (size_t i = 0; i < bit_size; i++) {
+ if (s[i >> 3] & (1 << (i & 7))) {
+ branch[i] = true;
+ }
+ }
+ return branch;
+}
+
+static 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;
+ }
+}
+
+bool BranchListBinaryMapToString(const BranchListBinaryMap& binary_map, std::string& s) {
+ proto::ETMBranchList branch_list_proto;
+ branch_list_proto.set_magic(ETM_BRANCH_LIST_PROTO_MAGIC);
+ std::vector<char> branch_buf;
+ for (const auto& p : binary_map) {
+ const BinaryKey& key = p.first;
+ const BranchListBinaryInfo& binary = p.second;
+ auto binary_proto = branch_list_proto.add_binaries();
+
+ binary_proto->set_path(key.path);
+ if (!key.build_id.IsEmpty()) {
+ binary_proto->set_build_id(key.build_id.ToString().substr(2));
+ }
+ auto opt_binary_type = ToProtoBinaryType(binary.dso_type);
+ if (!opt_binary_type.has_value()) {
+ return false;
+ }
+ binary_proto->set_type(opt_binary_type.value());
+
+ for (const auto& addr_p : binary.branch_map) {
+ auto addr_proto = binary_proto->add_addrs();
+ addr_proto->set_addr(addr_p.first);
+
+ for (const auto& branch_p : addr_p.second) {
+ const std::vector<bool>& branch = branch_p.first;
+ auto branch_proto = addr_proto->add_branches();
+
+ branch_proto->set_branch(BranchToProtoString(branch));
+ branch_proto->set_branch_size(branch.size());
+ branch_proto->set_count(branch_p.second);
+ }
+ }
+
+ if (binary.dso_type == DSO_KERNEL) {
+ binary_proto->mutable_kernel_info()->set_kernel_start_addr(key.kernel_start_addr);
+ }
+ }
+ if (!branch_list_proto.SerializeToString(&s)) {
+ LOG(ERROR) << "failed to serialize branch list binary map";
+ return false;
+ }
+ return true;
+}
+
+static 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;
+ }
+}
+
+static UnorderedBranchMap BuildUnorderedBranchMap(const proto::ETMBranchList_Binary& binary_proto) {
+ UnorderedBranchMap 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()];
+ for (size_t j = 0; j < addr_proto.branches_size(); j++) {
+ const auto& branch_proto = addr_proto.branches(j);
+ std::vector<bool> branch =
+ ProtoStringToBranch(branch_proto.branch(), branch_proto.branch_size());
+ b_map[branch] = branch_proto.count();
+ }
+ }
+ return branch_map;
+}
+
+bool StringToBranchListBinaryMap(const std::string& s, BranchListBinaryMap& binary_map) {
+ proto::ETMBranchList branch_list_proto;
+ if (!branch_list_proto.ParseFromString(s)) {
+ PLOG(ERROR) << "failed to read ETMBranchList msg";
+ return false;
+ }
+ if (branch_list_proto.magic() != ETM_BRANCH_LIST_PROTO_MAGIC) {
+ PLOG(ERROR) << "not in format etm_branch_list.proto";
+ return false;
+ }
+
+ for (size_t i = 0; i < branch_list_proto.binaries_size(); i++) {
+ const auto& binary_proto = branch_list_proto.binaries(i);
+ BinaryKey key(binary_proto.path(), BuildId(binary_proto.build_id()));
+ if (binary_proto.has_kernel_info()) {
+ key.kernel_start_addr = binary_proto.kernel_info().kernel_start_addr();
+ }
+ BranchListBinaryInfo& binary = binary_map[key];
+ auto dso_type = ToDsoType(binary_proto.type());
+ if (!dso_type) {
+ LOG(ERROR) << "invalid binary type " << binary_proto.type();
+ return false;
+ }
+ binary.dso_type = dso_type.value();
+ binary.branch_map = BuildUnorderedBranchMap(binary_proto);
+ }
+ return true;
+}
+
+class ETMThreadTreeWhenRecording : public ETMThreadTree {
+ public:
+ ETMThreadTreeWhenRecording(bool dump_maps_from_proc)
+ : dump_maps_from_proc_(dump_maps_from_proc) {}
+
+ ThreadTree& GetThreadTree() { return thread_tree_; }
+ void ExcludePid(pid_t pid) { exclude_pid_ = pid; }
+
+ const ThreadEntry* FindThread(int tid) override {
+ const ThreadEntry* thread = thread_tree_.FindThread(tid);
+ if (thread == nullptr) {
+ if (dump_maps_from_proc_) {
+ thread = FindThreadFromProc(tid);
+ }
+ if (thread == nullptr) {
+ return nullptr;
+ }
+ }
+ if (exclude_pid_ && exclude_pid_ == thread->pid) {
+ return nullptr;
+ }
+
+ if (dump_maps_from_proc_) {
+ DumpMapsFromProc(thread->pid);
+ }
+ return thread;
+ }
+
+ void DisableThreadExitRecords() override { thread_tree_.DisableThreadExitRecords(); }
+ const MapSet& GetKernelMaps() override { return thread_tree_.GetKernelMaps(); }
+
+ private:
+ const ThreadEntry* FindThreadFromProc(int tid) {
+ std::string comm;
+ pid_t pid;
+ if (ReadThreadNameAndPid(tid, &comm, &pid)) {
+ thread_tree_.SetThreadName(pid, tid, comm);
+ return thread_tree_.FindThread(tid);
+ }
+ return nullptr;
+ }
+
+ void DumpMapsFromProc(int pid) {
+ if (dumped_processes_.count(pid) == 0) {
+ dumped_processes_.insert(pid);
+ std::vector<ThreadMmap> maps;
+ if (GetThreadMmapsInProcess(pid, &maps)) {
+ for (const auto& map : maps) {
+ thread_tree_.AddThreadMap(pid, pid, map.start_addr, map.len, map.pgoff, map.name);
+ }
+ }
+ }
+ }
+
+ ThreadTree thread_tree_;
+ bool dump_maps_from_proc_;
+ std::unordered_set<int> dumped_processes_;
+ std::optional<pid_t> exclude_pid_;
+};
+
+class ETMBranchListGeneratorImpl : public ETMBranchListGenerator {
+ public:
+ ETMBranchListGeneratorImpl(bool dump_maps_from_proc)
+ : thread_tree_(dump_maps_from_proc), binary_filter_(nullptr) {}
+
+ void SetExcludePid(pid_t pid) override { thread_tree_.ExcludePid(pid); }
+ void SetBinaryFilter(const RegEx* binary_name_regex) override {
+ binary_filter_.SetRegex(binary_name_regex);
+ }
+
+ bool ProcessRecord(const Record& r, bool& consumed) override;
+ BranchListBinaryMap GetBranchListBinaryMap() override;
+
+ private:
+ struct AuxRecordData {
+ uint64_t start;
+ uint64_t end;
+ bool formatted;
+ AuxRecordData(uint64_t start, uint64_t end, bool formatted)
+ : start(start), end(end), formatted(formatted) {}
+ };
+
+ struct PerCpuData {
+ std::vector<uint8_t> aux_data;
+ uint64_t data_offset = 0;
+ std::queue<AuxRecordData> aux_records;
+ };
+
+ bool ProcessAuxRecord(const AuxRecord& r);
+ bool ProcessAuxTraceRecord(const AuxTraceRecord& r);
+ void ProcessBranchList(const ETMBranchList& branch_list);
+
+ ETMThreadTreeWhenRecording thread_tree_;
+ uint64_t kernel_map_start_addr_ = 0;
+ BinaryFilter binary_filter_;
+ std::map<uint32_t, PerCpuData> cpu_map_;
+ std::unique_ptr<ETMDecoder> etm_decoder_;
+ std::unordered_map<Dso*, BranchListBinaryInfo> branch_list_binary_map_;
+};
+
+bool ETMBranchListGeneratorImpl::ProcessRecord(const Record& r, bool& consumed) {
+ consumed = true; // No need to store any records.
+ uint32_t type = r.type();
+ if (type == PERF_RECORD_AUXTRACE_INFO) {
+ etm_decoder_ = ETMDecoder::Create(*static_cast<const AuxTraceInfoRecord*>(&r), thread_tree_);
+ if (!etm_decoder_) {
+ return false;
+ }
+ etm_decoder_->RegisterCallback(
+ [this](const ETMBranchList& branch) { ProcessBranchList(branch); });
+ return true;
+ }
+ if (type == PERF_RECORD_AUX) {
+ return ProcessAuxRecord(*static_cast<const AuxRecord*>(&r));
+ }
+ if (type == PERF_RECORD_AUXTRACE) {
+ return ProcessAuxTraceRecord(*static_cast<const AuxTraceRecord*>(&r));
+ }
+ if (type == PERF_RECORD_MMAP && r.InKernel()) {
+ auto& mmap_r = *static_cast<const MmapRecord*>(&r);
+ if (android::base::StartsWith(mmap_r.filename, DEFAULT_KERNEL_MMAP_NAME)) {
+ kernel_map_start_addr_ = mmap_r.data->addr;
+ }
+ }
+ thread_tree_.GetThreadTree().Update(r);
+ return true;
+}
+
+bool ETMBranchListGeneratorImpl::ProcessAuxRecord(const AuxRecord& r) {
+ OverflowResult result = SafeAdd(r.data->aux_offset, r.data->aux_size);
+ if (result.overflow || r.data->aux_size > SIZE_MAX) {
+ LOG(ERROR) << "invalid aux record";
+ return false;
+ }
+ size_t size = r.data->aux_size;
+ uint64_t start = r.data->aux_offset;
+ uint64_t end = result.value;
+ PerCpuData& data = cpu_map_[r.Cpu()];
+ if (start >= data.data_offset && end <= data.data_offset + data.aux_data.size()) {
+ // The ETM data is available. Process it now.
+ uint8_t* p = data.aux_data.data() + (start - data.data_offset);
+ if (!etm_decoder_) {
+ LOG(ERROR) << "ETMDecoder isn't created";
+ return false;
+ }
+ return etm_decoder_->ProcessData(p, size, !r.Unformatted(), r.Cpu());
+ }
+ // The ETM data isn't available. Put the aux record into queue.
+ data.aux_records.emplace(start, end, !r.Unformatted());
+ return true;
+}
+
+bool ETMBranchListGeneratorImpl::ProcessAuxTraceRecord(const AuxTraceRecord& r) {
+ OverflowResult result = SafeAdd(r.data->offset, r.data->aux_size);
+ if (result.overflow || r.data->aux_size > SIZE_MAX) {
+ LOG(ERROR) << "invalid auxtrace record";
+ return false;
+ }
+ size_t size = r.data->aux_size;
+ uint64_t start = r.data->offset;
+ uint64_t end = result.value;
+ PerCpuData& data = cpu_map_[r.Cpu()];
+ data.data_offset = start;
+ CHECK(r.location.addr != nullptr);
+ data.aux_data.resize(size);
+ memcpy(data.aux_data.data(), r.location.addr, size);
+
+ // Process cached aux records.
+ while (!data.aux_records.empty() && data.aux_records.front().start < end) {
+ const AuxRecordData& aux = data.aux_records.front();
+ if (aux.start >= start && aux.end <= end) {
+ uint8_t* p = data.aux_data.data() + (aux.start - start);
+ if (!etm_decoder_) {
+ LOG(ERROR) << "ETMDecoder isn't created";
+ return false;
+ }
+ if (!etm_decoder_->ProcessData(p, aux.end - aux.start, aux.formatted, r.Cpu())) {
+ return false;
+ }
+ }
+ data.aux_records.pop();
+ }
+ return true;
+}
+
+void ETMBranchListGeneratorImpl::ProcessBranchList(const ETMBranchList& branch_list) {
+ if (!binary_filter_.Filter(branch_list.dso)) {
+ return;
+ }
+ auto& branch_map = branch_list_binary_map_[branch_list.dso].branch_map;
+ ++branch_map[branch_list.addr][branch_list.branch];
+}
+
+BranchListBinaryMap ETMBranchListGeneratorImpl::GetBranchListBinaryMap() {
+ BranchListBinaryMap binary_map;
+ for (auto& p : branch_list_binary_map_) {
+ Dso* dso = p.first;
+ BranchListBinaryInfo& binary = p.second;
+ binary.dso_type = dso->type();
+ BuildId build_id;
+ GetBuildId(*dso, build_id);
+ BinaryKey key(dso->Path(), build_id);
+ if (binary.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.";
+ continue;
+ }
+ key.kernel_start_addr = kernel_map_start_addr_;
+ }
+ binary_map[key] = std::move(binary);
+ }
+ return binary_map;
+}
+
+std::unique_ptr<ETMBranchListGenerator> ETMBranchListGenerator::Create(bool dump_maps_from_proc) {
+ return std::unique_ptr<ETMBranchListGenerator>(
+ new ETMBranchListGeneratorImpl(dump_maps_from_proc));
+}
+
+ETMBranchListGenerator::~ETMBranchListGenerator() {}
+
+} // namespace simpleperf