OSDN Git Service

Dump build_id feature in `simpleperf record`.
authorYabin Cui <yabinc@google.com>
Wed, 6 May 2015 02:58:07 +0000 (19:58 -0700)
committerYabin Cui <yabinc@google.com>
Thu, 14 May 2015 02:21:34 +0000 (19:21 -0700)
Bug: 19483574
Change-Id: Ie2acd8a157bca9ad3c01a2e4b37e139aba89670f
(cherry picked from commit 8f6225147c5b6cb2159a7f5cb0dab952ee0759df)

16 files changed:
simpleperf/Android.mk
simpleperf/build_id.h [new file with mode: 0644]
simpleperf/cmd_dumprecord.cpp
simpleperf/cmd_record.cpp
simpleperf/cmd_record_test.cpp
simpleperf/environment.cpp
simpleperf/environment.h
simpleperf/event_type.cpp
simpleperf/read_elf.cpp [new file with mode: 0644]
simpleperf/read_elf.h [new file with mode: 0644]
simpleperf/record.cpp
simpleperf/record.h
simpleperf/record_equal_test.h
simpleperf/record_file.cpp
simpleperf/record_file.h
simpleperf/record_file_test.cpp

index 2635da0..f37c4b0 100644 (file)
@@ -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 (file)
index 0000000..5a4b12c
--- /dev/null
@@ -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_
index 4ee9394..57eec1f 100644 (file)
@@ -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()
index e27b6e4..98a0cd5 100644 (file)
  * 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()
index be01139..f0a8878 100644 (file)
@@ -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);
+}
index a2de935..0270b24 100644 (file)
@@ -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);
+}
index c411067..f81005c 100644 (file)
@@ -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);
 
index 15e3cf1..ee0e161 100644 (file)
@@ -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 (file)
index 0000000..1873b30
--- /dev/null
@@ -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 (file)
index 0000000..bc65fea
--- /dev/null
@@ -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_
index 3e34d52..46910b9 100644 (file)
@@ -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;
+}
index 4d62784..83f60db 100644 (file)
@@ -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_
index 45b0752..03768dc 100644 (file)
@@ -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));
   }
 }
index 784dc4e..54a4dda 100644 (file)
@@ -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;
+}
index cc213d5..694486c 100644 (file)
 #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:
index df138de..fffaa2a 100644 (file)
@@ -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());
 }