Simpleperf: support symbol parsing in report command.

Also fix the storage of ProcessEntry.

Bug: 19483574
Change-Id: I2182a804f6ecbd28e7aa3c1a38a6f19b86f583c9
diff --git a/simpleperf/Android.mk b/simpleperf/Android.mk
index d2c5276..4642e2a 100644
--- a/simpleperf/Android.mk
+++ b/simpleperf/Android.mk
@@ -34,6 +34,7 @@
   cmd_report.cpp \
   cmd_stat.cpp \
   command.cpp \
+  dso.cpp \
   environment.cpp \
   event_attr.cpp \
   event_fd.cpp \
@@ -110,6 +111,7 @@
   cpu_offline_test.cpp \
   environment_test.cpp \
   gtest_main.cpp \
+  read_elf_test.cpp \
   record_file_test.cpp \
   record_test.cpp \
   sample_tree_test.cpp \
diff --git a/simpleperf/cmd_report.cpp b/simpleperf/cmd_report.cpp
index f2dc443..d6aca57 100644
--- a/simpleperf/cmd_report.cpp
+++ b/simpleperf/cmd_report.cpp
@@ -45,7 +45,7 @@
 };
 
 static int ComparePid(const SampleEntry& sample1, const SampleEntry& sample2) {
-  return sample1.process_entry->pid - sample2.process_entry->pid;
+  return sample1.process->pid - sample2.process->pid;
 }
 
 static std::string PrintHeaderPid() {
@@ -53,7 +53,7 @@
 }
 
 static std::string PrintPid(const SampleEntry& sample) {
-  return android::base::StringPrintf("%d", sample.process_entry->pid);
+  return android::base::StringPrintf("%d", sample.process->pid);
 }
 
 static ReportItem report_pid_item = {
@@ -63,7 +63,7 @@
 };
 
 static int CompareComm(const SampleEntry& sample1, const SampleEntry& sample2) {
-  return strcmp(sample1.process_entry->comm.c_str(), sample2.process_entry->comm.c_str());
+  return strcmp(sample1.process->comm.c_str(), sample2.process->comm.c_str());
 }
 
 static std::string PrintHeaderComm() {
@@ -71,7 +71,7 @@
 }
 
 static std::string PrintComm(const SampleEntry& sample) {
-  return sample.process_entry->comm;
+  return sample.process->comm;
 }
 
 static ReportItem report_comm_item = {
@@ -81,7 +81,7 @@
 };
 
 static int CompareDso(const SampleEntry& sample1, const SampleEntry& sample2) {
-  return strcmp(sample1.map_entry->filename.c_str(), sample2.map_entry->filename.c_str());
+  return strcmp(sample1.map->dso->path.c_str(), sample2.map->dso->path.c_str());
 }
 
 static std::string PrintHeaderDso() {
@@ -89,10 +89,8 @@
 }
 
 static std::string PrintDso(const SampleEntry& sample) {
-  std::string filename = sample.map_entry->filename;
-  if (filename == DEFAULT_KERNEL_MMAP_NAME) {
-    filename = "[kernel.kallsyms]";
-  } else if (filename == DEFAULT_EXECNAME_FOR_THREAD_MMAP) {
+  std::string filename = sample.map->dso->path;
+  if (filename == DEFAULT_EXECNAME_FOR_THREAD_MMAP) {
     filename = "[unknown]";
   }
   return filename;
@@ -104,8 +102,29 @@
     .print_function = PrintDso,
 };
 
+static int CompareSymbol(const SampleEntry& sample1, const SampleEntry& sample2) {
+  return strcmp(sample1.symbol->name.c_str(), sample2.symbol->name.c_str());
+}
+
+static std::string PrintHeaderSymbol() {
+  return "Symbol";
+}
+
+static std::string PrintSymbol(const SampleEntry& sample) {
+  return sample.symbol->name;
+}
+
+static ReportItem report_symbol_item = {
+    .compare_function = CompareSymbol,
+    .print_header_function = PrintHeaderSymbol,
+    .print_function = PrintSymbol,
+};
+
 static std::unordered_map<std::string, ReportItem*> report_item_map = {
-    {"comm", &report_comm_item}, {"pid", &report_pid_item}, {"dso", &report_dso_item},
+    {"comm", &report_comm_item},
+    {"pid", &report_pid_item},
+    {"dso", &report_dso_item},
+    {"symbol", &report_symbol_item},
 };
 
 class ReportCommand : public Command {
@@ -115,7 +134,7 @@
                 "Usage: simpleperf report [options]\n"
                 "    -i <file>     specify path of record file, default is perf.data\n"
                 "    --sort key1,key2,... Select the keys to sort and print the report.\n"
-                "                         Possible keys include pid, comm, dso.\n"
+                "                         Possible keys include pid, comm, dso, symbol.\n"
                 "                         Default keys are \"comm,pid,dso\"\n"),
         record_filename_("perf.data") {
   }
diff --git a/simpleperf/cmd_report_test.cpp b/simpleperf/cmd_report_test.cpp
index 34d156c..8281a15 100644
--- a/simpleperf/cmd_report_test.cpp
+++ b/simpleperf/cmd_report_test.cpp
@@ -47,5 +47,5 @@
 }
 
 TEST_F(ReportCommandTest, sort_option_all) {
-  ASSERT_TRUE(ReportCmd()->Run({"--sort", "comm,pid,dso"}));
+  ASSERT_TRUE(ReportCmd()->Run({"--sort", "comm,pid,dso,symbol"}));
 }
diff --git a/simpleperf/dso.cpp b/simpleperf/dso.cpp
new file mode 100644
index 0000000..e8cd27c
--- /dev/null
+++ b/simpleperf/dso.cpp
@@ -0,0 +1,109 @@
+/*
+ * 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 "dso.h"
+
+#include "environment.h"
+#include "read_elf.h"
+
+bool SymbolComparator::operator()(const std::unique_ptr<SymbolEntry>& symbol1,
+                                  const std::unique_ptr<SymbolEntry>& symbol2) {
+  return symbol1->addr < symbol2->addr;
+}
+
+const SymbolEntry* DsoEntry::FindSymbol(uint64_t offset_in_dso) {
+  std::unique_ptr<SymbolEntry> symbol(new SymbolEntry{
+      .name = "", .addr = offset_in_dso, .len = 0,
+  });
+
+  auto it = symbols.upper_bound(symbol);
+  if (it != symbols.begin()) {
+    --it;
+    if ((*it)->addr <= offset_in_dso && (*it)->addr + (*it)->len > offset_in_dso) {
+      return (*it).get();
+    }
+  }
+  return nullptr;
+}
+
+static bool IsKernelFunctionSymbol(const KernelSymbol& symbol) {
+  return (symbol.type == 'T' || symbol.type == 't' || symbol.type == 'W' || symbol.type == 'w');
+}
+
+static bool KernelSymbolCallback(const KernelSymbol& kernel_symbol, DsoEntry* dso) {
+  if (IsKernelFunctionSymbol(kernel_symbol)) {
+    SymbolEntry* symbol = new SymbolEntry{
+        .name = kernel_symbol.name, .addr = kernel_symbol.addr, .len = 0,
+    };
+    dso->symbols.insert(std::unique_ptr<SymbolEntry>(symbol));
+  }
+  return false;
+}
+
+std::unique_ptr<DsoEntry> DsoFactory::LoadKernel() {
+  std::unique_ptr<DsoEntry> dso(new DsoEntry);
+  dso->path = "[kernel.kallsyms]";
+
+  ProcessKernelSymbols("/proc/kallsyms",
+                       std::bind(&KernelSymbolCallback, std::placeholders::_1, dso.get()));
+  // Fix symbol.len.
+  auto prev_it = dso->symbols.end();
+  for (auto it = dso->symbols.begin(); it != dso->symbols.end(); ++it) {
+    if (prev_it != dso->symbols.end()) {
+      (*prev_it)->len = (*it)->addr - (*prev_it)->addr;
+    }
+    prev_it = it;
+  }
+  if (prev_it != dso->symbols.end()) {
+    (*prev_it)->len = ULLONG_MAX - (*prev_it)->addr;
+  }
+  return dso;
+}
+
+static void ParseSymbolCallback(const ElfFileSymbol& elf_symbol, DsoEntry* dso,
+                                bool (*filter)(const ElfFileSymbol&)) {
+  if (filter(elf_symbol)) {
+    SymbolEntry* symbol = new SymbolEntry{
+        .name = elf_symbol.name, .addr = elf_symbol.start_in_file, .len = elf_symbol.len,
+    };
+    dso->symbols.insert(std::unique_ptr<SymbolEntry>(symbol));
+  }
+}
+
+static bool SymbolFilterForKernelModule(const ElfFileSymbol& elf_symbol) {
+  // TODO: Parse symbol outside of .text section.
+  return (elf_symbol.is_func && elf_symbol.is_in_text_section);
+}
+
+std::unique_ptr<DsoEntry> DsoFactory::LoadKernelModule(const std::string& dso_path) {
+  std::unique_ptr<DsoEntry> dso(new DsoEntry);
+  dso->path = dso_path;
+  ParseSymbolsFromElfFile(dso_path, std::bind(ParseSymbolCallback, std::placeholders::_1, dso.get(),
+                                              SymbolFilterForKernelModule));
+  return dso;
+}
+
+static bool SymbolFilterForDso(const ElfFileSymbol& elf_symbol) {
+  return elf_symbol.is_func;
+}
+
+std::unique_ptr<DsoEntry> DsoFactory::LoadDso(const std::string& dso_path) {
+  std::unique_ptr<DsoEntry> dso(new DsoEntry);
+  dso->path = dso_path;
+  ParseSymbolsFromElfFile(dso_path, std::bind(ParseSymbolCallback, std::placeholders::_1, dso.get(),
+                                              SymbolFilterForDso));
+  return dso;
+}
diff --git a/simpleperf/dso.h b/simpleperf/dso.h
new file mode 100644
index 0000000..f596cd5
--- /dev/null
+++ b/simpleperf/dso.h
@@ -0,0 +1,49 @@
+/*
+ * 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_DSO_H_
+#define SIMPLE_PERF_DSO_H_
+
+#include <memory>
+#include <set>
+#include <string>
+
+struct SymbolEntry {
+  std::string name;
+  uint64_t addr;
+  uint64_t len;
+};
+
+struct SymbolComparator {
+  bool operator()(const std::unique_ptr<SymbolEntry>& symbol1,
+                  const std::unique_ptr<SymbolEntry>& symbol2);
+};
+
+struct DsoEntry {
+  std::string path;
+  std::set<std::unique_ptr<SymbolEntry>, SymbolComparator> symbols;
+
+  const SymbolEntry* FindSymbol(uint64_t offset_in_dso);
+};
+
+class DsoFactory {
+ public:
+  static std::unique_ptr<DsoEntry> LoadKernel();
+  static std::unique_ptr<DsoEntry> LoadKernelModule(const std::string& dso_path);
+  static std::unique_ptr<DsoEntry> LoadDso(const std::string& dso_path);
+};
+
+#endif  // SIMPLE_PERF_DSO_H_
diff --git a/simpleperf/environment.cpp b/simpleperf/environment.cpp
index 0270b24..f4027fb 100644
--- a/simpleperf/environment.cpp
+++ b/simpleperf/environment.cpp
@@ -182,7 +182,7 @@
     if (android::base::EndsWith(name, ".ko")) {
       std::string module_name = name.substr(0, name.size() - 3);
       std::replace(module_name.begin(), module_name.end(), '-', '_');
-      module_file_map->insert(std::make_pair(module_name, path + name));
+      module_file_map->insert(std::make_pair(module_name, path + "/" + name));
     }
   }
   for (auto& name : subdirs) {
diff --git a/simpleperf/read_elf.cpp b/simpleperf/read_elf.cpp
index 1873b30..6585252 100644
--- a/simpleperf/read_elf.cpp
+++ b/simpleperf/read_elf.cpp
@@ -114,3 +114,73 @@
   }
   return result;
 }
+template <class ELFT>
+bool ParseSymbolsFromELFFile(const llvm::object::ELFFile<ELFT>* elf,
+                             std::function<void(const ElfFileSymbol&)> callback) {
+  auto begin = elf->begin_symbols();
+  auto end = elf->end_symbols();
+  if (begin == end) {
+    begin = elf->begin_dynamic_symbols();
+    end = elf->end_dynamic_symbols();
+  }
+  for (; begin != end; ++begin) {
+    auto& elf_symbol = *begin;
+
+    ElfFileSymbol symbol;
+    memset(&symbol, '\0', sizeof(symbol));
+
+    auto shdr = elf->getSection(&elf_symbol);
+    if (shdr == nullptr) {
+      continue;
+    }
+    auto section_name = elf->getSectionName(shdr);
+    if (section_name.getError() || section_name.get().empty()) {
+      continue;
+    }
+
+    symbol.start_in_file = elf_symbol.st_value - shdr->sh_addr + shdr->sh_offset;
+    symbol.len = elf_symbol.st_size;
+    int type = elf_symbol.getType();
+    if (type & STT_FUNC) {
+      symbol.is_func = true;
+    }
+    if (section_name.get() == ".text") {
+      symbol.is_in_text_section = true;
+    }
+
+    auto symbol_name = elf->getSymbolName(begin);
+    if (symbol_name.getError()) {
+      continue;
+    }
+    symbol.name = symbol_name.get();
+    if (symbol.name.empty()) {
+      continue;
+    }
+    callback(symbol);
+  }
+  return true;
+}
+
+bool ParseSymbolsFromElfFile(const std::string& filename,
+                             std::function<void(const ElfFileSymbol&)> callback) {
+  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 = ParseSymbolsFromELFFile(elf->getELFFile(), callback);
+    } else if (auto elf = llvm::dyn_cast<llvm::object::ELF64LEObjectFile>(obj)) {
+      result = ParseSymbolsFromELFFile(elf->getELFFile(), callback);
+    } else {
+      PLOG(DEBUG) << "unknown elf format in file" << filename;
+    }
+  }
+  if (!result) {
+    PLOG(DEBUG) << "can't parse symbols from file " << filename;
+  }
+  return result;
+}
diff --git a/simpleperf/read_elf.h b/simpleperf/read_elf.h
index bc65fea..ee90c8a 100644
--- a/simpleperf/read_elf.h
+++ b/simpleperf/read_elf.h
@@ -17,10 +17,22 @@
 #ifndef SIMPLE_PERF_READ_ELF_H_
 #define SIMPLE_PERF_READ_ELF_H_
 
+#include <functional>
 #include <string>
 #include "build_id.h"
 
 bool GetBuildIdFromNoteFile(const std::string& filename, BuildId* build_id);
 bool GetBuildIdFromElfFile(const std::string& filename, BuildId* build_id);
 
+struct ElfFileSymbol {
+  uint64_t start_in_file;
+  uint64_t len;
+  bool is_func;
+  bool is_in_text_section;
+  std::string name;
+};
+
+bool ParseSymbolsFromElfFile(const std::string& filename,
+                             std::function<void(const ElfFileSymbol&)> callback);
+
 #endif  // SIMPLE_PERF_READ_ELF_H_
diff --git a/simpleperf/read_elf_test.cpp b/simpleperf/read_elf_test.cpp
new file mode 100644
index 0000000..bc9ef73
--- /dev/null
+++ b/simpleperf/read_elf_test.cpp
@@ -0,0 +1,38 @@
+/*
+ * 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 <gtest/gtest.h>
+
+static void ParseSymbol(const ElfFileSymbol& symbol, bool* result) {
+  if (symbol.is_func) {
+    *result = true;
+  }
+}
+
+TEST(read_elf, parse_symbols_from_elf_file) {
+  char elf_file[PATH_MAX];
+  ssize_t elf_file_len = readlink("/proc/self/exe", elf_file, sizeof(elf_file));
+  ASSERT_GT(elf_file_len, 0L);
+  ASSERT_LT(static_cast<size_t>(elf_file_len), sizeof(elf_file));
+  elf_file[elf_file_len] = '\0';
+
+  bool result = false;
+  ASSERT_TRUE(
+      ParseSymbolsFromElfFile(elf_file, std::bind(ParseSymbol, std::placeholders::_1, &result)));
+  ASSERT_TRUE(result);
+}
diff --git a/simpleperf/sample_tree.cpp b/simpleperf/sample_tree.cpp
index b980af4..5c359e8 100644
--- a/simpleperf/sample_tree.cpp
+++ b/simpleperf/sample_tree.cpp
@@ -18,6 +18,8 @@
 
 #include <base/logging.h>
 
+#include "environment.h"
+
 bool SampleTree::MapComparator::operator()(const MapEntry* map1, const MapEntry* map2) {
   if (map1->pid != map2->pid) {
     return map1->pid < map2->pid;
@@ -35,39 +37,58 @@
 }
 
 void SampleTree::AddProcess(int pid, const std::string& comm) {
-  ProcessEntry process = {
+  ProcessEntry* process = new ProcessEntry{
       .pid = pid, .comm = comm,
   };
-  process_tree_[pid] = process;
+  // TODO: Check the return value of below insertion operation when per thread comm is used.
+  process_tree_.insert(std::make_pair(pid, std::unique_ptr<ProcessEntry>(process)));
 }
 
 void SampleTree::AddKernelMap(uint64_t start_addr, uint64_t len, uint64_t pgoff, uint64_t time,
                               const std::string& filename) {
+  DsoEntry* dso = FindKernelDsoOrNew(filename);
   MapEntry* map = new MapEntry{
-      .pid = -1,
-      .start_addr = start_addr,
-      .len = len,
-      .pgoff = pgoff,
-      .time = time,
-      .filename = filename,
+      .pid = -1, .start_addr = start_addr, .len = len, .pgoff = pgoff, .time = time, .dso = dso,
   };
-  map_storage_.push_back(map);
-  kernel_map_tree_.insert(map);
+  map_storage_.push_back(std::unique_ptr<MapEntry>(map));
+  auto pair = kernel_map_tree_.insert(map);
+  CHECK(pair.second);
+}
+
+DsoEntry* SampleTree::FindKernelDsoOrNew(const std::string& filename) {
+  if (filename == DEFAULT_KERNEL_MMAP_NAME) {
+    if (kernel_dso_ == nullptr) {
+      kernel_dso_ = DsoFactory::LoadKernel();
+    }
+    return kernel_dso_.get();
+  }
+  auto it = module_dso_tree_.find(filename);
+  if (it == module_dso_tree_.end()) {
+    module_dso_tree_[filename] = DsoFactory::LoadKernelModule(filename);
+    it = module_dso_tree_.find(filename);
+  }
+  return it->second.get();
 }
 
 void SampleTree::AddUserMap(int pid, uint64_t start_addr, uint64_t len, uint64_t pgoff,
                             uint64_t time, const std::string& filename) {
+  DsoEntry* dso = FindUserDsoOrNew(filename);
   MapEntry* map = new MapEntry{
-      .pid = pid,
-      .start_addr = start_addr,
-      .len = len,
-      .pgoff = pgoff,
-      .time = time,
-      .filename = filename,
+      .pid = pid, .start_addr = start_addr, .len = len, .pgoff = pgoff, .time = time, .dso = dso,
   };
-  map_storage_.push_back(map);
+  map_storage_.push_back(std::unique_ptr<MapEntry>(map));
   RemoveOverlappedUserMap(map);
-  user_map_tree_.insert(map);
+  auto pair = user_map_tree_.insert(map);
+  CHECK(pair.second);
+}
+
+DsoEntry* SampleTree::FindUserDsoOrNew(const std::string& filename) {
+  auto it = user_dso_tree_.find(filename);
+  if (it == user_dso_tree_.end()) {
+    user_dso_tree_[filename] = DsoFactory::LoadDso(filename);
+    it = user_dso_tree_.find(filename);
+  }
+  return it->second.get();
 }
 
 void SampleTree::RemoveOverlappedUserMap(const MapEntry* map) {
@@ -90,13 +111,14 @@
 const ProcessEntry* SampleTree::FindProcessEntryOrNew(int pid) {
   auto it = process_tree_.find(pid);
   if (it == process_tree_.end()) {
-    ProcessEntry new_entry = {
+    ProcessEntry* process = new ProcessEntry{
         .pid = pid, .comm = "unknown",
     };
-    auto pair = process_tree_.insert(std::make_pair(pid, new_entry));
+    auto pair = process_tree_.insert(std::make_pair(pid, std::unique_ptr<ProcessEntry>(process)));
     it = pair.first;
+    CHECK(pair.second);
   }
-  return &it->second;
+  return it->second.get();
 }
 
 static bool IsIpInMap(int pid, uint64_t ip, const MapEntry* map) {
@@ -132,11 +154,12 @@
         .len = static_cast<uint64_t>(-1),
         .pgoff = 0,
         .time = 0,
-        .filename = "unknown",
+        .dso = &unknown_dso_,
     };
-    map_storage_.push_back(map);
+    map_storage_.push_back(std::unique_ptr<MapEntry>(map));
     auto pair = unknown_maps_.insert(std::make_pair(pid, map));
     it = pair.first;
+    CHECK(pair.second);
   }
   return it->second;
 }
@@ -144,6 +167,7 @@
 void SampleTree::AddSample(int pid, int tid, uint64_t ip, uint64_t time, uint64_t period) {
   const ProcessEntry* process_entry = FindProcessEntryOrNew(pid);
   const MapEntry* map_entry = FindMapEntryOrNew(pid, ip);
+  const SymbolEntry* symbol_entry = FindSymbolEntry(ip, map_entry);
 
   SampleEntry find_sample = {
       .tid = tid,
@@ -151,12 +175,14 @@
       .time = time,
       .period = period,
       .sample_count = 1,
-      .process_entry = process_entry,
-      .map_entry = map_entry,
+      .process = process_entry,
+      .map = map_entry,
+      .symbol = symbol_entry,
   };
   auto it = sample_tree_.find(find_sample);
   if (it == sample_tree_.end()) {
-    sample_tree_.insert(find_sample);
+    auto pair = sample_tree_.insert(find_sample);
+    CHECK(pair.second);
   } else {
     SampleEntry* sample_entry = const_cast<SampleEntry*>(&*it);
     sample_entry->period += period;
@@ -166,6 +192,20 @@
   total_period_ += period;
 }
 
+const SymbolEntry* SampleTree::FindSymbolEntry(uint64_t ip, const MapEntry* map_entry) {
+  uint64_t offset_in_file;
+  if (map_entry->dso == kernel_dso_.get()) {
+    offset_in_file = ip;
+  } else {
+    offset_in_file = ip - map_entry->start_addr + map_entry->pgoff;
+  }
+  const SymbolEntry* symbol = map_entry->dso->FindSymbol(offset_in_file);
+  if (symbol == nullptr) {
+    symbol = &unknown_symbol_;
+  }
+  return symbol;
+}
+
 void SampleTree::VisitAllSamples(std::function<void(const SampleEntry&)> callback) {
   if (sorted_sample_tree_.size() != sample_tree_.size()) {
     sorted_sample_tree_.clear();
diff --git a/simpleperf/sample_tree.h b/simpleperf/sample_tree.h
index 852f4da..6d18393 100644
--- a/simpleperf/sample_tree.h
+++ b/simpleperf/sample_tree.h
@@ -17,12 +17,15 @@
 #ifndef SIMPLE_PERF_SAMPLE_TREE_H_
 #define SIMPLE_PERF_SAMPLE_TREE_H_
 
+#include <limits.h>
 #include <functional>
 #include <set>
 #include <string>
 #include <unordered_map>
 #include <vector>
 
+#include "dso.h"
+
 struct ProcessEntry {
   int pid;
   std::string comm;
@@ -34,7 +37,7 @@
   uint64_t len;
   uint64_t pgoff;
   uint64_t time;  // Map creation time.
-  std::string filename;
+  DsoEntry* dso;
 };
 
 struct SampleEntry {
@@ -43,8 +46,9 @@
   uint64_t time;
   uint64_t period;
   uint64_t sample_count;
-  const ProcessEntry* process_entry;
-  const MapEntry* map_entry;
+  const ProcessEntry* process;
+  const MapEntry* map;
+  const SymbolEntry* symbol;
 };
 
 typedef std::function<int(const SampleEntry&, const SampleEntry&)> compare_sample_func_t;
@@ -58,12 +62,10 @@
         sorted_sample_tree_(sorted_sample_comparator_),
         total_samples_(0),
         total_period_(0) {
-  }
-
-  ~SampleTree() {
-    for (auto& map : map_storage_) {
-      delete map;
-    }
+    unknown_dso_.path = "unknown";
+    unknown_symbol_ = {
+        .name = "unknown", .addr = 0, .len = ULLONG_MAX,
+    };
   }
 
   void AddProcess(int pid, const std::string& comm);
@@ -87,6 +89,9 @@
   const ProcessEntry* FindProcessEntryOrNew(int pid);
   const MapEntry* FindMapEntryOrNew(int pid, uint64_t ip);
   const MapEntry* FindUnknownMapEntryOrNew(int pid);
+  DsoEntry* FindKernelDsoOrNew(const std::string& filename);
+  DsoEntry* FindUserDsoOrNew(const std::string& filename);
+  const SymbolEntry* FindSymbolEntry(uint64_t ip, const MapEntry* map_entry);
 
   struct MapComparator {
     bool operator()(const MapEntry* map1, const MapEntry* map2);
@@ -116,12 +121,18 @@
     compare_sample_func_t compare_function;
   };
 
-  std::unordered_map<int, ProcessEntry> process_tree_;
+  std::unordered_map<int, std::unique_ptr<ProcessEntry>> process_tree_;
 
   std::set<MapEntry*, MapComparator> kernel_map_tree_;
   std::set<MapEntry*, MapComparator> user_map_tree_;
   std::unordered_map<int, MapEntry*> unknown_maps_;
-  std::vector<MapEntry*> map_storage_;
+  std::vector<std::unique_ptr<MapEntry>> map_storage_;
+
+  std::unique_ptr<DsoEntry> kernel_dso_;
+  std::unordered_map<std::string, std::unique_ptr<DsoEntry>> module_dso_tree_;
+  std::unordered_map<std::string, std::unique_ptr<DsoEntry>> user_dso_tree_;
+  DsoEntry unknown_dso_;
+  SymbolEntry unknown_symbol_;
 
   SampleComparator sample_comparator_;
   std::set<SampleEntry, SampleComparator> sample_tree_;
diff --git a/simpleperf/sample_tree_test.cpp b/simpleperf/sample_tree_test.cpp
index 114fa03..166297d 100644
--- a/simpleperf/sample_tree_test.cpp
+++ b/simpleperf/sample_tree_test.cpp
@@ -29,12 +29,12 @@
 static void SampleMatchExpectation(const SampleEntry& sample, const ExpectedSampleInMap& expected,
                                    bool* has_error) {
   *has_error = true;
-  ASSERT_TRUE(sample.process_entry != nullptr);
-  ASSERT_EQ(expected.pid, sample.process_entry->pid);
+  ASSERT_TRUE(sample.process != nullptr);
+  ASSERT_EQ(expected.pid, sample.process->pid);
   ASSERT_EQ(expected.tid, sample.tid);
-  ASSERT_TRUE(sample.map_entry != nullptr);
-  ASSERT_EQ(expected.map_pid, sample.map_entry->pid);
-  ASSERT_EQ(expected.map_start_addr, sample.map_entry->start_addr);
+  ASSERT_TRUE(sample.map != nullptr);
+  ASSERT_EQ(expected.map_pid, sample.map->pid);
+  ASSERT_EQ(expected.map_start_addr, sample.map->start_addr);
   ASSERT_EQ(expected.sample_count, sample.sample_count);
   *has_error = false;
 }
@@ -49,17 +49,17 @@
 }
 
 static int CompareSampleFunction(const SampleEntry& sample1, const SampleEntry& sample2) {
-  if (sample1.process_entry->pid != sample2.process_entry->pid) {
-    return sample1.process_entry->pid - sample2.process_entry->pid;
+  if (sample1.process->pid != sample2.process->pid) {
+    return sample1.process->pid - sample2.process->pid;
   }
   if (sample1.tid != sample2.tid) {
     return sample1.tid - sample2.tid;
   }
-  if (sample1.map_entry->pid != sample2.map_entry->pid) {
-    return sample1.map_entry->pid - sample2.map_entry->pid;
+  if (sample1.map->pid != sample2.map->pid) {
+    return sample1.map->pid - sample2.map->pid;
   }
-  if (sample1.map_entry->start_addr != sample2.map_entry->start_addr) {
-    return sample1.map_entry->start_addr - sample2.map_entry->start_addr;
+  if (sample1.map->start_addr != sample2.map->start_addr) {
+    return sample1.map->start_addr - sample2.map->start_addr;
   }
   return 0;
 }