diff options
author | Yabin Cui <yabinc@google.com> | 2015-05-13 23:33:54 +0000 |
---|---|---|
committer | Gerrit Code Review <noreply-gerritcodereview@google.com> | 2015-05-13 23:35:38 +0000 |
commit | c49491f2f0cc3e5d3e4c57bd755226c3b93490a0 (patch) | |
tree | 71ab094fc6fc165652ae5e5b85e3969fc8e18f49 | |
parent | f044a21af13472bed9d74b96829a0e5597af0a7a (diff) | |
parent | 8f6225147c5b6cb2159a7f5cb0dab952ee0759df (diff) | |
download | extras-c49491f2f0cc3e5d3e4c57bd755226c3b93490a0.tar.gz |
Merge "Dump build_id feature in `simpleperf record`."
-rw-r--r-- | simpleperf/Android.mk | 24 | ||||
-rw-r--r-- | simpleperf/build_id.h | 26 | ||||
-rw-r--r-- | simpleperf/cmd_dumprecord.cpp | 25 | ||||
-rw-r--r-- | simpleperf/cmd_record.cpp | 63 | ||||
-rw-r--r-- | simpleperf/cmd_record_test.cpp | 12 | ||||
-rw-r--r-- | simpleperf/environment.cpp | 10 | ||||
-rw-r--r-- | simpleperf/environment.h | 6 | ||||
-rw-r--r-- | simpleperf/event_type.cpp | 4 | ||||
-rw-r--r-- | simpleperf/read_elf.cpp | 116 | ||||
-rw-r--r-- | simpleperf/read_elf.h | 26 | ||||
-rw-r--r-- | simpleperf/record.cpp | 46 | ||||
-rw-r--r-- | simpleperf/record.h | 31 | ||||
-rw-r--r-- | simpleperf/record_equal_test.h | 9 | ||||
-rw-r--r-- | simpleperf/record_file.cpp | 175 | ||||
-rw-r--r-- | simpleperf/record_file.h | 19 | ||||
-rw-r--r-- | simpleperf/record_file_test.cpp | 22 |
16 files changed, 597 insertions, 17 deletions
diff --git a/simpleperf/Android.mk b/simpleperf/Android.mk index 2635da0a..f37c4b0b 100644 --- a/simpleperf/Android.mk +++ b/simpleperf/Android.mk @@ -18,10 +18,11 @@ LOCAL_PATH := $(call my-dir) simpleperf_common_cppflags := -std=c++11 -Wall -Wextra -Werror -Wunused -simpleperf_common_static_libraries := \ +simpleperf_common_shared_libraries := \ libbase \ - libcutils \ - liblog \ + libLLVM \ + +LLVM_ROOT_PATH := external/llvm libsimpleperf_src_files := \ cmd_dumprecord.cpp \ @@ -35,6 +36,7 @@ libsimpleperf_src_files := \ event_fd.cpp \ event_selection_set.cpp \ event_type.cpp \ + read_elf.cpp \ record.cpp \ record_file.cpp \ utils.cpp \ @@ -44,11 +46,13 @@ include $(CLEAR_VARS) LOCAL_CLANG := true LOCAL_CPPFLAGS := $(simpleperf_common_cppflags) LOCAL_SRC_FILES := $(libsimpleperf_src_files) -LOCAL_STATIC_LIBRARIES := $(simpleperf_common_static_libraries) +LOCAL_SHARED_LIBRARIES := $(simpleperf_common_shared_libraries) LOCAL_MODULE := libsimpleperf LOCAL_MODULE_TAGS := debug LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES) LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk +include $(LLVM_ROOT_PATH)/llvm.mk +include $(LLVM_DEVICE_BUILD_MK) include $(BUILD_STATIC_LIBRARY) ifeq ($(HOST_OS),linux) @@ -56,11 +60,13 @@ include $(CLEAR_VARS) LOCAL_CLANG := true LOCAL_CPPFLAGS := $(simpleperf_common_cppflags) LOCAL_SRC_FILES := $(libsimpleperf_src_files) -LOCAL_STATIC_LIBRARIES := $(simpleperf_common_static_libraries) +LOCAL_SHARED_LIBRARIES := $(simpleperf_common_shared_libraries) LOCAL_LDLIBS := -lrt LOCAL_MODULE := libsimpleperf LOCAL_MODULE_TAGS := optional LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk +include $(LLVM_ROOT_PATH)/llvm.mk +include $(LLVM_HOST_BUILD_MK) include $(BUILD_HOST_STATIC_LIBRARY) endif @@ -69,7 +75,7 @@ LOCAL_CLANG := true LOCAL_CPPFLAGS := $(simpleperf_common_cppflags) LOCAL_SRC_FILES := main.cpp LOCAL_WHOLE_STATIC_LIBRARIES := libsimpleperf -LOCAL_STATIC_LIBRARIES := $(simpleperf_common_static_libraries) +LOCAL_SHARED_LIBRARIES := $(simpleperf_common_shared_libraries) LOCAL_MODULE := simpleperf LOCAL_MODULE_TAGS := debug LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES) @@ -82,7 +88,7 @@ LOCAL_CLANG := true LOCAL_CPPFLAGS := $(simpleperf_common_cppflags) LOCAL_SRC_FILES := main.cpp LOCAL_WHOLE_STATIC_LIBRARIES := libsimpleperf -LOCAL_STATIC_LIBRARIES := $(simpleperf_common_static_libraries) +LOCAL_SHARED_LIBRARIES := $(simpleperf_common_shared_libraries) LOCAL_LDLIBS := -lrt LOCAL_MODULE := simpleperf LOCAL_MODULE_TAGS := optional @@ -107,7 +113,7 @@ LOCAL_CLANG := true LOCAL_CPPFLAGS := $(simpleperf_common_cppflags) LOCAL_SRC_FILES := $(simpleperf_unit_test_src_files) LOCAL_WHOLE_STATIC_LIBRARIES := libsimpleperf -LOCAL_STATIC_LIBRARIES := $(simpleperf_common_static_libraries) +LOCAL_SHARED_LIBRARIES := $(simpleperf_common_shared_libraries) LOCAL_MODULE := simpleperf_unit_test LOCAL_MODULE_TAGS := optional LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk @@ -119,7 +125,7 @@ LOCAL_CLANG := true LOCAL_CPPFLAGS := $(simpleperf_common_cppflags) LOCAL_SRC_FILES := $(simpleperf_unit_test_src_files) LOCAL_WHOLE_STATIC_LIBRARIES := libsimpleperf -LOCAL_STATIC_LIBRARIES := $(simpleperf_common_static_libraries) +LOCAL_SHARED_LIBRARIES := $(simpleperf_common_shared_libraries) LOCAL_MODULE := simpleperf_unit_test LOCAL_MODULE_TAGS := optional LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk diff --git a/simpleperf/build_id.h b/simpleperf/build_id.h new file mode 100644 index 00000000..5a4b12cb --- /dev/null +++ b/simpleperf/build_id.h @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2015 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. + */ + +#ifndef SIMPLE_PERF_BUILD_ID_H_ +#define SIMPLE_PERF_BUILD_ID_H_ + +#include <array> + +static constexpr int BUILD_ID_SIZE = 20; + +typedef std::array<unsigned char, BUILD_ID_SIZE> BuildId; + +#endif // SIMPLE_PERF_BUILD_ID_H_ diff --git a/simpleperf/cmd_dumprecord.cpp b/simpleperf/cmd_dumprecord.cpp index 4ee93942..57eec1f6 100644 --- a/simpleperf/cmd_dumprecord.cpp +++ b/simpleperf/cmd_dumprecord.cpp @@ -42,6 +42,7 @@ class DumpRecordCommandImpl { void DumpFileHeader(); void DumpAttrSection(); void DumpDataSection(); + void DumpFeatureSection(); std::string record_filename_; std::unique_ptr<RecordFileReader> record_file_reader_; @@ -60,6 +61,7 @@ bool DumpRecordCommandImpl::Run(const std::vector<std::string>& args) { DumpFileHeader(); DumpAttrSection(); DumpDataSection(); + DumpFeatureSection(); return true; } @@ -162,6 +164,29 @@ void DumpRecordCommandImpl::DumpDataSection() { } } +void DumpRecordCommandImpl::DumpFeatureSection() { + std::vector<SectionDesc> sections = record_file_reader_->FeatureSectionDescriptors(); + CHECK_EQ(sections.size(), features_.size()); + for (size_t i = 0; i < features_.size(); ++i) { + int feature = features_[i]; + SectionDesc& section = sections[i]; + printf("feature section for %s: offset %" PRId64 ", size %" PRId64 "\n", + GetFeatureName(feature).c_str(), section.offset, section.size); + if (feature == FEAT_BUILD_ID) { + const char* p = record_file_reader_->DataAtOffset(section.offset); + const char* end = p + section.size; + while (p < end) { + const perf_event_header* header = reinterpret_cast<const perf_event_header*>(p); + CHECK_LE(p + header->size, end); + CHECK_EQ(PERF_RECORD_BUILD_ID, header->type); + BuildIdRecord record(header); + record.Dump(1); + p += header->size; + } + } + } +} + class DumpRecordCommand : public Command { public: DumpRecordCommand() diff --git a/simpleperf/cmd_record.cpp b/simpleperf/cmd_record.cpp index e27b6e4b..98a0cd55 100644 --- a/simpleperf/cmd_record.cpp +++ b/simpleperf/cmd_record.cpp @@ -14,17 +14,20 @@ * limitations under the License. */ +#include <libgen.h> #include <poll.h> #include <signal.h> #include <string> #include <vector> #include <base/logging.h> +#include <base/strings.h> #include "command.h" #include "environment.h" #include "event_selection_set.h" #include "event_type.h" +#include "read_elf.h" #include "record.h" #include "record_file.h" #include "utils.h" @@ -60,6 +63,8 @@ class RecordCommandImpl { bool WriteData(const char* data, size_t size); bool DumpKernelAndModuleMmaps(); bool DumpThreadCommAndMmaps(); + bool DumpAdditionalFeatures(); + bool DumpBuildIdFeature(); bool use_sample_freq_; // Use sample_freq_ when true, otherwise using sample_period_. uint64_t sample_freq_; // Sample 'sample_freq_' times per second. @@ -157,7 +162,10 @@ bool RecordCommandImpl::Run(const std::vector<std::string>& args) { poll(&pollfds[0], pollfds.size(), -1); } - // 6. Close record file. + // 6. Dump additional features, and close record file. + if (!DumpAdditionalFeatures()) { + return false; + } if (!record_file_writer_->Close()) { return false; } @@ -302,6 +310,59 @@ bool RecordCommandImpl::DumpThreadCommAndMmaps() { return true; } +bool RecordCommandImpl::DumpAdditionalFeatures() { + if (!record_file_writer_->WriteFeatureHeader(1)) { + return false; + } + return DumpBuildIdFeature(); +} + +bool RecordCommandImpl::DumpBuildIdFeature() { + std::vector<std::string> hit_kernel_modules; + std::vector<std::string> hit_user_files; + if (!record_file_writer_->GetHitModules(&hit_kernel_modules, &hit_user_files)) { + return false; + } + std::vector<BuildIdRecord> build_id_records; + BuildId build_id; + // Add build_ids for kernel/modules. + for (auto& filename : hit_kernel_modules) { + if (filename == DEFAULT_KERNEL_MMAP_NAME) { + if (!GetKernelBuildId(&build_id)) { + 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)); + } else { + std::string module_name = basename(&filename[0]); + if (android::base::EndsWith(module_name, ".ko")) { + module_name = module_name.substr(0, module_name.size() - 3); + } + if (!GetModuleBuildId(module_name, &build_id)) { + LOG(DEBUG) << "can't read build_id for module " << module_name; + continue; + } + build_id_records.push_back(CreateBuildIdRecord(true, UINT_MAX, build_id, filename)); + } + } + // Add build_ids for user elf files. + for (auto& filename : hit_user_files) { + if (filename == DEFAULT_EXECNAME_FOR_THREAD_MMAP) { + continue; + } + if (!GetBuildIdFromElfFile(filename, &build_id)) { + LOG(DEBUG) << "can't read build_id from file " << filename; + continue; + } + build_id_records.push_back(CreateBuildIdRecord(false, UINT_MAX, build_id, filename)); + } + if (!record_file_writer_->WriteBuildIdFeature(build_id_records)) { + return false; + } + return true; +} + class RecordCommand : public Command { public: RecordCommand() diff --git a/simpleperf/cmd_record_test.cpp b/simpleperf/cmd_record_test.cpp index be011391..f0a8878b 100644 --- a/simpleperf/cmd_record_test.cpp +++ b/simpleperf/cmd_record_test.cpp @@ -21,6 +21,8 @@ #include "record.h" #include "record_file.h" +using namespace PerfFileFormat; + class RecordCommandTest : public ::testing::Test { protected: virtual void SetUp() { @@ -74,3 +76,13 @@ TEST_F(RecordCommandTest, dump_kernel_mmap) { } ASSERT_TRUE(have_kernel_mmap); } + +TEST_F(RecordCommandTest, dump_build_id_feature) { + ASSERT_TRUE(record_cmd->Run({"record", "sleep", "1"})); + std::unique_ptr<RecordFileReader> reader = RecordFileReader::CreateInstance("perf.data"); + ASSERT_TRUE(reader != nullptr); + const FileHeader* file_header = reader->FileHeader(); + ASSERT_TRUE(file_header != nullptr); + ASSERT_TRUE(file_header->features[FEAT_BUILD_ID / 8] & (1 << (FEAT_BUILD_ID % 8))); + ASSERT_GT(reader->FeatureSectionDescriptors().size(), 0u); +} diff --git a/simpleperf/environment.cpp b/simpleperf/environment.cpp index a2de935a..0270b247 100644 --- a/simpleperf/environment.cpp +++ b/simpleperf/environment.cpp @@ -27,6 +27,7 @@ #include <base/strings.h> #include <base/stringprintf.h> +#include "read_elf.h" #include "utils.h" std::vector<int> GetOnlineCpus() { @@ -346,3 +347,12 @@ bool GetThreadMmapsInProcess(pid_t pid, std::vector<ThreadMmap>* thread_mmaps) { } return true; } + +bool GetKernelBuildId(BuildId* build_id) { + return GetBuildIdFromNoteFile("/sys/kernel/notes", build_id); +} + +bool GetModuleBuildId(const std::string& module_name, BuildId* build_id) { + std::string notefile = "/sys/module/" + module_name + "/notes/.note.gnu.build-id"; + return GetBuildIdFromNoteFile(notefile, build_id); +} diff --git a/simpleperf/environment.h b/simpleperf/environment.h index c4110677..f81005ce 100644 --- a/simpleperf/environment.h +++ b/simpleperf/environment.h @@ -20,6 +20,7 @@ #include <functional> #include <string> #include <vector> +#include "build_id.h" std::vector<int> GetOnlineCpus(); @@ -61,6 +62,11 @@ struct ThreadMmap { bool GetThreadMmapsInProcess(pid_t pid, std::vector<ThreadMmap>* thread_mmaps); +static const char* DEFAULT_KERNEL_FILENAME_FOR_BUILD_ID = "[kernel.kallsyms]"; + +bool GetKernelBuildId(BuildId* build_id); +bool GetModuleBuildId(const std::string& module_name, BuildId* build_id); + // Expose the following functions for unit tests. std::vector<int> GetOnlineCpusFromString(const std::string& s); diff --git a/simpleperf/event_type.cpp b/simpleperf/event_type.cpp index 15e3cf17..ee0e161f 100644 --- a/simpleperf/event_type.cpp +++ b/simpleperf/event_type.cpp @@ -61,8 +61,8 @@ const EventType* EventTypeFactory::FindEventTypeByName(const std::string& name, return nullptr; } if (!result->IsSupportedByKernel()) { - (report_unsupported_type ? LOG(ERROR) : LOG(DEBUG)) << "Event type '" << result->name - << "' is not supported by the kernel"; + (report_unsupported_type ? PLOG(ERROR) : PLOG(DEBUG)) << "Event type '" << result->name + << "' is not supported by the kernel"; return nullptr; } return result; diff --git a/simpleperf/read_elf.cpp b/simpleperf/read_elf.cpp new file mode 100644 index 00000000..1873b308 --- /dev/null +++ b/simpleperf/read_elf.cpp @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2015 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 "read_elf.h" + +#include <stdio.h> +#include <string.h> +#include <algorithm> +#include <base/file.h> +#include <base/logging.h> + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-parameter" + +#include <llvm/ADT/StringRef.h> +#include <llvm/Object/Binary.h> +#include <llvm/Object/ELFObjectFile.h> +#include <llvm/Object/ObjectFile.h> + +#pragma clang diagnostic pop + +#include <elf.h> + +#include "utils.h" + +static bool GetBuildIdFromNoteSection(const char* section, size_t section_size, BuildId* build_id) { + const char* p = section; + const char* end = p + section_size; + while (p < end) { + CHECK_LE(p + 12, end); + size_t namesz = *reinterpret_cast<const uint32_t*>(p); + p += 4; + size_t descsz = *reinterpret_cast<const uint32_t*>(p); + p += 4; + uint32_t type = *reinterpret_cast<const uint32_t*>(p); + p += 4; + namesz = ALIGN(namesz, 4); + descsz = ALIGN(descsz, 4); + CHECK_LE(p + namesz + descsz, end); + if ((type == NT_GNU_BUILD_ID) && (strcmp(p, ELF_NOTE_GNU) == 0)) { + std::fill(build_id->begin(), build_id->end(), 0); + memcpy(build_id->data(), p + namesz, std::min(build_id->size(), descsz)); + return true; + } + p += namesz + descsz; + } + return false; +} + +bool GetBuildIdFromNoteFile(const std::string& filename, BuildId* build_id) { + std::string content; + if (!android::base::ReadFileToString(filename, &content)) { + LOG(DEBUG) << "can't read note file " << filename; + return false; + } + if (GetBuildIdFromNoteSection(content.c_str(), content.size(), build_id) == false) { + LOG(DEBUG) << "can't read build_id from note file " << filename; + return false; + } + return true; +} + +template <class ELFT> +bool GetBuildIdFromELFFile(const llvm::object::ELFFile<ELFT>* elf, BuildId* build_id) { + for (auto section_iterator = elf->begin_sections(); section_iterator != elf->end_sections(); + ++section_iterator) { + if (section_iterator->sh_type == SHT_NOTE) { + auto contents = elf->getSectionContents(&*section_iterator); + if (contents.getError()) { + LOG(DEBUG) << "read note section error"; + continue; + } + if (GetBuildIdFromNoteSection(reinterpret_cast<const char*>(contents->data()), + contents->size(), build_id)) { + return true; + } + } + } + return false; +} + +bool GetBuildIdFromElfFile(const std::string& filename, BuildId* build_id) { + auto owning_binary = llvm::object::createBinary(llvm::StringRef(filename)); + if (owning_binary.getError()) { + PLOG(DEBUG) << "can't open file " << filename; + return false; + } + bool result = false; + llvm::object::Binary* binary = owning_binary.get().getBinary(); + if (auto obj = llvm::dyn_cast<llvm::object::ObjectFile>(binary)) { + if (auto elf = llvm::dyn_cast<llvm::object::ELF32LEObjectFile>(obj)) { + result = GetBuildIdFromELFFile(elf->getELFFile(), build_id); + } else if (auto elf = llvm::dyn_cast<llvm::object::ELF64LEObjectFile>(obj)) { + result = GetBuildIdFromELFFile(elf->getELFFile(), build_id); + } else { + PLOG(DEBUG) << "unknown elf format in file " << filename; + } + } + if (!result) { + PLOG(DEBUG) << "can't read build_id from file " << filename; + } + return result; +} diff --git a/simpleperf/read_elf.h b/simpleperf/read_elf.h new file mode 100644 index 00000000..bc65fea0 --- /dev/null +++ b/simpleperf/read_elf.h @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2015 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. + */ + +#ifndef SIMPLE_PERF_READ_ELF_H_ +#define SIMPLE_PERF_READ_ELF_H_ + +#include <string> +#include "build_id.h" + +bool GetBuildIdFromNoteFile(const std::string& filename, BuildId* build_id); +bool GetBuildIdFromElfFile(const std::string& filename, BuildId* build_id); + +#endif // SIMPLE_PERF_READ_ELF_H_ diff --git a/simpleperf/record.cpp b/simpleperf/record.cpp index 3e34d525..46910b92 100644 --- a/simpleperf/record.cpp +++ b/simpleperf/record.cpp @@ -303,6 +303,39 @@ void SampleRecord::DumpData(size_t indent) 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; + MoveFromBinaryFormat(pid, p); + std::copy_n(p, build_id.size(), build_id.begin()); + p += ALIGN(build_id.size(), 8); + filename = p; + p += ALIGN(filename.size() + 1, 64); + CHECK_EQ(p, end); +} + +void BuildIdRecord::DumpData(size_t indent) const { + PrintIndented(indent, "pid %u\n", pid); + PrintIndented(indent, "build_id 0x"); + for (auto& c : build_id) { + printf("%02x", c); + } + printf("\n"); + PrintIndented(indent, "filename %s\n", filename.c_str()); +} + +std::vector<char> BuildIdRecord::BinaryFormat() const { + std::vector<char> buf(header.size); + char* p = buf.data(); + MoveToBinaryFormat(header, p); + MoveToBinaryFormat(pid, p); + memcpy(p, build_id.data(), build_id.size()); + p += ALIGN(build_id.size(), 8); + strcpy(p, filename.c_str()); + p += ALIGN(filename.size() + 1, 64); + return buf; +} + std::unique_ptr<const Record> ReadRecordFromBuffer(const perf_event_attr& attr, const perf_event_header* pheader) { switch (pheader->type) { @@ -350,3 +383,16 @@ CommRecord CreateCommRecord(const perf_event_attr& attr, uint32_t pid, uint32_t ALIGN(record.comm.size() + 1, 8) + 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.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); + return record; +} diff --git a/simpleperf/record.h b/simpleperf/record.h index 4d62784c..83f60db9 100644 --- a/simpleperf/record.h +++ b/simpleperf/record.h @@ -20,6 +20,7 @@ #include <string> #include <vector> +#include "build_id.h" #include "perf_event.h" struct KernelMmap; @@ -129,8 +130,10 @@ struct MmapRecord : public Record { } MmapRecord(const perf_event_attr& attr, const perf_event_header* pheader); - void DumpData(size_t indent) const override; std::vector<char> BinaryFormat() const; + + protected: + void DumpData(size_t indent) const override; }; struct CommRecord : public Record { @@ -143,8 +146,10 @@ struct CommRecord : public Record { } CommRecord(const perf_event_attr& attr, const perf_event_header* pheader); - void DumpData(size_t indent) const override; std::vector<char> BinaryFormat() const; + + protected: + void DumpData(size_t indent) const override; }; struct ExitRecord : public Record { @@ -155,6 +160,8 @@ struct ExitRecord : public Record { } data; ExitRecord(const perf_event_attr& attr, const perf_event_header* pheader); + + protected: void DumpData(size_t indent) const override; }; @@ -171,6 +178,24 @@ struct SampleRecord : public Record { PerfSamplePeriodType period_data; // Valid if PERF_SAMPLE_PERIOD. SampleRecord(const perf_event_attr& attr, const perf_event_header* pheader); + + protected: + void DumpData(size_t indent) const override; +}; + +// BuildIdRecord is defined in user-space, stored in BuildId feature section in record file. +struct BuildIdRecord : public Record { + uint32_t pid; + BuildId build_id; + std::string filename; + + BuildIdRecord() { + } + + BuildIdRecord(const perf_event_header* pheader); + std::vector<char> BinaryFormat() const; + + protected: void DumpData(size_t indent) const override; }; @@ -181,4 +206,6 @@ MmapRecord CreateMmapRecord(const perf_event_attr& attr, bool in_kernel, uint32_ const std::string& filename); CommRecord CreateCommRecord(const perf_event_attr& attr, uint32_t pid, uint32_t tid, const std::string& comm); +BuildIdRecord CreateBuildIdRecord(bool in_kernel, pid_t pid, const BuildId& build_id, + const std::string& filename); #endif // SIMPLE_PERF_RECORD_H_ diff --git a/simpleperf/record_equal_test.h b/simpleperf/record_equal_test.h index 45b0752c..03768dc5 100644 --- a/simpleperf/record_equal_test.h +++ b/simpleperf/record_equal_test.h @@ -24,6 +24,12 @@ static void CheckCommRecordDataEqual(const CommRecord& r1, const CommRecord& r2) ASSERT_EQ(r1.comm, r2.comm); } +static void CheckBuildIdRecordDataEqual(const BuildIdRecord& r1, const BuildIdRecord& r2) { + ASSERT_EQ(r1.pid, r2.pid); + ASSERT_EQ(r1.build_id, r2.build_id); + ASSERT_EQ(r1.filename, r2.filename); +} + static void CheckRecordEqual(const Record& r1, const Record& r2) { ASSERT_EQ(0, memcmp(&r1.header, &r2.header, sizeof(r1.header))); ASSERT_EQ(0, memcmp(&r1.sample_id, &r2.sample_id, sizeof(r1.sample_id))); @@ -31,5 +37,8 @@ static void CheckRecordEqual(const Record& r1, const Record& r2) { CheckMmapRecordDataEqual(static_cast<const MmapRecord&>(r1), static_cast<const MmapRecord&>(r2)); } else if (r1.header.type == PERF_RECORD_COMM) { CheckCommRecordDataEqual(static_cast<const CommRecord&>(r1), static_cast<const CommRecord&>(r2)); + } else if (r1.header.type == PERF_RECORD_BUILD_ID) { + CheckBuildIdRecordDataEqual(static_cast<const BuildIdRecord&>(r1), + static_cast<const BuildIdRecord&>(r2)); } } diff --git a/simpleperf/record_file.cpp b/simpleperf/record_file.cpp index 784dc4e1..54a4ddaa 100644 --- a/simpleperf/record_file.cpp +++ b/simpleperf/record_file.cpp @@ -21,6 +21,7 @@ #include <sys/mman.h> #include <unistd.h> #include <set> +#include <vector> #include <base/logging.h> @@ -48,7 +49,14 @@ std::unique_ptr<RecordFileWriter> RecordFileWriter::CreateInstance( } RecordFileWriter::RecordFileWriter(const std::string& filename, FILE* fp) - : filename_(filename), record_fp_(fp), data_section_offset_(0), data_section_size_(0) { + : filename_(filename), + record_fp_(fp), + attr_section_offset_(0), + attr_section_size_(0), + data_section_offset_(0), + data_section_size_(0), + feature_count_(0), + current_feature_index_(0) { } RecordFileWriter::~RecordFileWriter() { @@ -121,6 +129,152 @@ bool RecordFileWriter::Write(const void* buf, size_t len) { return true; } +void RecordFileWriter::GetHitModulesInBuffer(const char* p, const char* end, + std::vector<std::string>* hit_kernel_modules, + std::vector<std::string>* hit_user_files) { + std::vector<std::unique_ptr<const Record>> kernel_mmaps; + std::vector<std::unique_ptr<const Record>> user_mmaps; + std::set<std::string> hit_kernel_set; + std::set<std::string> hit_user_set; + + while (p < end) { + auto header = reinterpret_cast<const perf_event_header*>(p); + CHECK_LE(p + header->size, end); + p += header->size; + std::unique_ptr<const Record> record = ReadRecordFromBuffer(event_attr_, header); + CHECK(record != nullptr); + if (record->header.type == PERF_RECORD_MMAP) { + if (record->header.misc & PERF_RECORD_MISC_KERNEL) { + kernel_mmaps.push_back(std::move(record)); + } else { + user_mmaps.push_back(std::move(record)); + } + } else if (record->header.type == PERF_RECORD_SAMPLE) { + auto& r = *static_cast<const SampleRecord*>(record.get()); + if (!(r.sample_type & PERF_SAMPLE_IP) || !(r.sample_type & PERF_SAMPLE_TID)) { + continue; + } + uint32_t pid = r.tid_data.pid; + uint64_t ip = r.ip_data.ip; + if (r.header.misc & PERF_RECORD_MISC_KERNEL) { + // Loop from back to front, because new MmapRecords are inserted at the end of the mmaps, + // and we want to match the newest one. + for (auto it = kernel_mmaps.rbegin(); it != kernel_mmaps.rend(); ++it) { + auto& m_record = *reinterpret_cast<const MmapRecord*>(it->get()); + if (ip >= m_record.data.addr && ip < m_record.data.addr + m_record.data.len) { + hit_kernel_set.insert(m_record.filename); + break; + } + } + } else { + for (auto it = user_mmaps.rbegin(); it != user_mmaps.rend(); ++it) { + auto& m_record = *reinterpret_cast<const MmapRecord*>(it->get()); + if (pid == m_record.data.pid && ip >= m_record.data.addr && + ip < m_record.data.addr + m_record.data.len) { + hit_user_set.insert(m_record.filename); + break; + } + } + } + } + } + hit_kernel_modules->clear(); + hit_kernel_modules->insert(hit_kernel_modules->begin(), hit_kernel_set.begin(), + hit_kernel_set.end()); + hit_user_files->clear(); + hit_user_files->insert(hit_user_files->begin(), hit_user_set.begin(), hit_user_set.end()); +} + +bool RecordFileWriter::GetHitModules(std::vector<std::string>* hit_kernel_modules, + std::vector<std::string>* hit_user_files) { + if (fflush(record_fp_) != 0) { + PLOG(ERROR) << "fflush() failed"; + return false; + } + if (fseek(record_fp_, 0, SEEK_END) == -1) { + PLOG(ERROR) << "fseek() failed"; + return false; + } + long file_size = ftell(record_fp_); + if (file_size == -1) { + PLOG(ERROR) << "ftell() failed"; + return false; + } + size_t mmap_len = file_size; + void* mmap_addr = mmap(nullptr, mmap_len, PROT_READ, MAP_SHARED, fileno(record_fp_), 0); + if (mmap_addr == MAP_FAILED) { + PLOG(ERROR) << "mmap() failed"; + return false; + } + const char* data_section_p = reinterpret_cast<const char*>(mmap_addr) + data_section_offset_; + const char* data_section_end = data_section_p + data_section_size_; + GetHitModulesInBuffer(data_section_p, data_section_end, hit_kernel_modules, hit_user_files); + + if (munmap(mmap_addr, mmap_len) == -1) { + PLOG(ERROR) << "munmap() failed"; + return false; + } + return true; +} + +bool RecordFileWriter::WriteFeatureHeader(size_t feature_count) { + feature_count_ = feature_count; + current_feature_index_ = 0; + uint64_t feature_header_size = feature_count * sizeof(SectionDesc); + + // Reserve enough space in the record file for the feature header. + std::vector<unsigned char> zero_data(feature_header_size); + if (fseek(record_fp_, data_section_offset_ + data_section_size_, SEEK_SET) == -1) { + PLOG(ERROR) << "fseek() failed"; + return false; + } + return Write(zero_data.data(), zero_data.size()); +} + +bool RecordFileWriter::WriteBuildIdFeature(const std::vector<BuildIdRecord>& build_id_records) { + if (current_feature_index_ >= feature_count_) { + return false; + } + // Always write features at the end of the file. + if (fseek(record_fp_, 0, SEEK_END) == -1) { + PLOG(ERROR) << "fseek() failed"; + return false; + } + long section_start = ftell(record_fp_); + if (section_start == -1) { + PLOG(ERROR) << "ftell() failed"; + return false; + } + for (auto& record : build_id_records) { + std::vector<char> data = record.BinaryFormat(); + if (!Write(data.data(), data.size())) { + return false; + } + } + long section_end = ftell(record_fp_); + if (section_end == -1) { + return false; + } + + // Write feature section descriptor for build_id feature. + SectionDesc desc; + desc.offset = section_start; + desc.size = section_end - section_start; + uint64_t feature_offset = data_section_offset_ + data_section_size_; + if (fseek(record_fp_, feature_offset + current_feature_index_ * sizeof(SectionDesc), SEEK_SET) == + -1) { + PLOG(ERROR) << "fseek() failed"; + return false; + } + if (fwrite(&desc, sizeof(SectionDesc), 1, record_fp_) != 1) { + PLOG(ERROR) << "fwrite() failed"; + return false; + } + ++current_feature_index_; + features_.push_back(FEAT_BUILD_ID); + return true; +} + bool RecordFileWriter::WriteFileHeader() { FileHeader header; memset(&header, 0, sizeof(header)); @@ -261,3 +415,22 @@ std::vector<std::unique_ptr<const Record>> RecordFileReader::DataSection() { } return result; } + +std::vector<SectionDesc> RecordFileReader::FeatureSectionDescriptors() { + std::vector<SectionDesc> result; + const struct FileHeader* header = FileHeader(); + size_t feature_count = 0; + for (size_t i = 0; i < sizeof(header->features); ++i) { + for (size_t j = 0; j < 8; ++j) { + if (header->features[i] & (1 << j)) { + ++feature_count; + } + } + } + uint64_t feature_section_offset = header->data.offset + header->data.size; + const SectionDesc* p = reinterpret_cast<const SectionDesc*>(mmap_addr_ + feature_section_offset); + for (size_t i = 0; i < feature_count; ++i) { + result.push_back(*p++); + } + return result; +} diff --git a/simpleperf/record_file.h b/simpleperf/record_file.h index cc213d57..694486c0 100644 --- a/simpleperf/record_file.h +++ b/simpleperf/record_file.h @@ -25,10 +25,10 @@ #include <base/macros.h> #include "perf_event.h" +#include "record.h" #include "record_file_format.h" class EventFd; -struct Record; // RecordFileWriter writes to a perf record file, like perf.data. class RecordFileWriter { @@ -45,6 +45,14 @@ class RecordFileWriter { return WriteData(data.data(), data.size()); } + // Use MmapRecords and SampleRecords in record file to conclude which modules/files were executing + // at sample times. + bool GetHitModules(std::vector<std::string>* hit_kernel_modules, + std::vector<std::string>* hit_user_files); + + bool WriteFeatureHeader(size_t feature_count); + bool WriteBuildIdFeature(const std::vector<BuildIdRecord>& build_id_records); + // Normally, Close() should be called after writing. But if something // wrong happens and we need to finish in advance, the destructor // will take care of calling Close(). @@ -54,6 +62,9 @@ class RecordFileWriter { RecordFileWriter(const std::string& filename, FILE* fp); bool WriteAttrSection(const perf_event_attr& event_attr, const std::vector<std::unique_ptr<EventFd>>& event_fds); + void GetHitModulesInBuffer(const char* p, const char* end, + std::vector<std::string>* hit_kernel_modules, + std::vector<std::string>* hit_user_files); bool WriteFileHeader(); bool Write(const void* buf, size_t len); @@ -67,6 +78,8 @@ class RecordFileWriter { uint64_t data_section_size_; std::vector<int> features_; + int feature_count_; + int current_feature_index_; DISALLOW_COPY_AND_ASSIGN(RecordFileWriter); }; @@ -82,6 +95,10 @@ class RecordFileReader { std::vector<const PerfFileFormat::FileAttr*> AttrSection(); std::vector<uint64_t> IdsForAttr(const PerfFileFormat::FileAttr* attr); std::vector<std::unique_ptr<const Record>> DataSection(); + std::vector<PerfFileFormat::SectionDesc> FeatureSectionDescriptors(); + const char* DataAtOffset(uint64_t offset) { + return mmap_addr_ + offset; + } bool Close(); private: diff --git a/simpleperf/record_file_test.cpp b/simpleperf/record_file_test.cpp index df138def..fffaa2a9 100644 --- a/simpleperf/record_file_test.cpp +++ b/simpleperf/record_file_test.cpp @@ -33,6 +33,7 @@ class RecordFileTest : public ::testing::Test { virtual void SetUp() { filename = "temporary.record_file"; const EventType* event_type = EventTypeFactory::FindEventTypeByName("cpu-cycles"); + ASSERT_TRUE(event_type != nullptr); event_attr = CreateDefaultPerfEventAttr(*event_type); std::unique_ptr<EventFd> event_fd = EventFd::OpenEventFileForProcess(event_attr, getpid()); ASSERT_TRUE(event_fd != nullptr); @@ -50,10 +51,19 @@ TEST_F(RecordFileTest, smoke) { RecordFileWriter::CreateInstance(filename, event_attr, event_fds); ASSERT_TRUE(writer != nullptr); - // Write Data section. + // Write data section. MmapRecord mmap_record = CreateMmapRecord(event_attr, true, 1, 1, 0x1000, 0x2000, 0x3000, "mmap_record_example"); ASSERT_TRUE(writer->WriteData(mmap_record.BinaryFormat())); + + // Write feature section. + ASSERT_TRUE(writer->WriteFeatureHeader(1)); + BuildId build_id; + for (size_t i = 0; i < build_id.size(); ++i) { + build_id[i] = i; + } + BuildIdRecord build_id_record = CreateBuildIdRecord(false, getpid(), build_id, "init"); + ASSERT_TRUE(writer->WriteBuildIdFeature({build_id_record})); ASSERT_TRUE(writer->Close()); // Read from a record file. @@ -73,5 +83,15 @@ TEST_F(RecordFileTest, smoke) { ASSERT_EQ(mmap_record.header.type, records[0]->header.type); CheckRecordEqual(mmap_record, *records[0]); + // Read and check feature section. + ASSERT_TRUE(file_header->features[FEAT_BUILD_ID / 8] & (1 << (FEAT_BUILD_ID % 8))); + std::vector<SectionDesc> sections = reader->FeatureSectionDescriptors(); + ASSERT_EQ(1u, sections.size()); + const perf_event_header* header = + reinterpret_cast<const perf_event_header*>(reader->DataAtOffset(sections[0].offset)); + ASSERT_TRUE(header != nullptr); + ASSERT_EQ(sections[0].size, header->size); + CheckRecordEqual(build_id_record, BuildIdRecord(header)); + ASSERT_TRUE(reader->Close()); } |