simpleperf: show dso[+vaddr_in_file] for unknown symbols.

It gives more information than just unknown symbols.
Add --no-show-ip option to disable this additional detail.

Bug: 29772268
Change-Id: Ie8067f95b5fdc65806044e229ee12095367d115a
Test: run simpleperf_unit_test.
diff --git a/simpleperf/cmd_report.cpp b/simpleperf/cmd_report.cpp
index 464c57f..cbdd14b 100644
--- a/simpleperf/cmd_report.cpp
+++ b/simpleperf/cmd_report.cpp
@@ -280,6 +280,7 @@
 "-i <file>  Specify path of record file, default is perf.data.\n"
 "-n         Print the sample count for each item.\n"
 "--no-demangle         Don't demangle symbol names.\n"
+"--no-show-ip          Don't show vaddr in file for unknown symbols.\n"
 "-o report_file_name   Set report file name, default is stdout.\n"
 "--pids pid1,pid2,...  Report only for selected pids.\n"
 "--sort key1,key2,...  Select keys used to sort and print the report. The\n"
@@ -379,6 +380,7 @@
 
 bool ReportCommand::ParseOptions(const std::vector<std::string>& args) {
   bool demangle = true;
+  bool show_ip_for_unknown_symbol = true;
   std::string symfs_dir;
   std::string vmlinux;
   bool print_sample_count = false;
@@ -428,6 +430,8 @@
 
     } else if (args[i] == "--no-demangle") {
       demangle = false;
+    } else if (args[i] == "--no-show-ip") {
+      show_ip_for_unknown_symbol = false;
     } else if (args[i] == "-o") {
       if (!NextArgumentOrError(args, &i)) {
         return false;
@@ -488,6 +492,10 @@
     Dso::SetVmlinux(vmlinux);
   }
 
+  if (show_ip_for_unknown_symbol) {
+    thread_tree_.ShowIpForUnknownSymbol();
+  }
+
   SampleDisplayer<SampleEntry, SampleTree> displayer;
   SampleComparator<SampleEntry> comparator;
 
diff --git a/simpleperf/cmd_report_test.cpp b/simpleperf/cmd_report_test.cpp
index 1281137..d6231bf 100644
--- a/simpleperf/cmd_report_test.cpp
+++ b/simpleperf/cmd_report_test.cpp
@@ -340,6 +340,15 @@
       testing::ExitedWithCode(0), "build id.*mismatch");
 }
 
+TEST_F(ReportCommandTest, no_show_ip_option) {
+  Report(PERF_DATA);
+  ASSERT_TRUE(success);
+  ASSERT_EQ(content.find("unknown"), std::string::npos);
+  Report(PERF_DATA, {"--no-show-ip"});
+  ASSERT_TRUE(success);
+  ASSERT_NE(content.find("unknown"), std::string::npos);
+}
+
 #if defined(__linux__)
 
 static std::unique_ptr<Command> RecordCmd() {
diff --git a/simpleperf/dso.cpp b/simpleperf/dso.cpp
index c10779a..f31d4c4 100644
--- a/simpleperf/dso.cpp
+++ b/simpleperf/dso.cpp
@@ -154,6 +154,12 @@
       debug_file_path_ = path_in_symfs;
     }
   }
+  size_t pos = path.find_last_of("/\\");
+  if (pos != std::string::npos) {
+    file_name_ = path.substr(pos + 1);
+  } else {
+    file_name_ = path;
+  }
   dso_count_++;
 }
 
diff --git a/simpleperf/dso.h b/simpleperf/dso.h
index a352570..c381e6d 100644
--- a/simpleperf/dso.h
+++ b/simpleperf/dso.h
@@ -86,6 +86,8 @@
   const std::string& Path() const { return path_; }
   // Return the path containing symbol table and debug information.
   const std::string& GetDebugFilePath() const { return debug_file_path_; }
+  // Return the file name without directory info.
+  const std::string& FileName() const { return file_name_; }
 
   bool HasDumped() const { return has_dumped_; }
 
@@ -122,6 +124,8 @@
   // path of the shared library having symbol table and debug information
   // It is the same as path_, or has the same build id as path_.
   std::string debug_file_path_;
+  // File name of the shared library, got by removing directories in path_.
+  std::string file_name_;
   uint64_t min_vaddr_;
   std::set<Symbol, SymbolComparator> symbols_;
   bool is_loaded_;
diff --git a/simpleperf/thread_tree.cpp b/simpleperf/thread_tree.cpp
index f3291e5..fafefa0 100644
--- a/simpleperf/thread_tree.cpp
+++ b/simpleperf/thread_tree.cpp
@@ -16,9 +16,12 @@
 
 #include "thread_tree.h"
 
+#include <inttypes.h>
+
 #include <limits>
 
 #include <android-base/logging.h>
+#include <android-base/stringprintf.h>
 
 #include "environment.h"
 #include "perf_event.h"
@@ -207,21 +210,31 @@
 const Symbol* ThreadTree::FindSymbol(const MapEntry* map, uint64_t ip,
                                      uint64_t* pvaddr_in_file) {
   uint64_t vaddr_in_file;
-  if (map->dso == kernel_dso_.get()) {
+  Dso* dso = map->dso;
+  if (dso == kernel_dso_.get()) {
     vaddr_in_file = ip;
   } else {
     vaddr_in_file = ip - map->start_addr + map->dso->MinVirtualAddress();
   }
-  const Symbol* symbol = map->dso->FindSymbol(vaddr_in_file);
-  if (symbol == nullptr && map->in_kernel && map->dso != kernel_dso_.get()) {
+  const Symbol* symbol = dso->FindSymbol(vaddr_in_file);
+  if (symbol == nullptr && map->in_kernel && dso != kernel_dso_.get()) {
     // It is in a kernel module, but we can't find the kernel module file, or
     // the kernel module file contains no symbol. Try finding the symbol in
     // /proc/kallsyms.
     vaddr_in_file = ip;
-    symbol = kernel_dso_->FindSymbol(vaddr_in_file);
+    dso = kernel_dso_.get();
+    symbol = dso->FindSymbol(vaddr_in_file);
   }
   if (symbol == nullptr) {
-    symbol = &unknown_symbol_;
+    if (show_ip_for_unknown_symbol_) {
+      std::string name = android::base::StringPrintf(
+          "%s[+%" PRIx64 "]", dso->FileName().c_str(), vaddr_in_file);
+      dso->InsertSymbol(Symbol(name, vaddr_in_file, 1));
+      symbol = dso->FindSymbol(vaddr_in_file);
+      CHECK(symbol != nullptr);
+    } else {
+      symbol = &unknown_symbol_;
+    }
   }
   if (pvaddr_in_file != nullptr) {
     *pvaddr_in_file = vaddr_in_file;
diff --git a/simpleperf/thread_tree.h b/simpleperf/thread_tree.h
index ae73cc7..a8d3fa0 100644
--- a/simpleperf/thread_tree.h
+++ b/simpleperf/thread_tree.h
@@ -68,7 +68,8 @@
 class ThreadTree {
  public:
   ThreadTree()
-      : unknown_symbol_("unknown", 0,
+      : show_ip_for_unknown_symbol_(false),
+        unknown_symbol_("unknown", 0,
                         std::numeric_limits<unsigned long long>::max()) {
     unknown_dso_ = Dso::CreateDso(DSO_ELF_FILE, "unknown");
     unknown_map_ = MapEntry(0, std::numeric_limits<unsigned long long>::max(),
@@ -92,6 +93,7 @@
   const Symbol* FindKernelSymbol(uint64_t ip);
   const Symbol* UnknownSymbol() const { return &unknown_symbol_; }
 
+  void ShowIpForUnknownSymbol() { show_ip_for_unknown_symbol_ = true; }
   // Clear thread and map information, but keep loaded dso information. It saves
   // the time to reload dso information.
   void ClearThreadAndMap();
@@ -117,6 +119,7 @@
   std::unordered_map<std::string, std::unique_ptr<Dso>> module_dso_tree_;
   std::unordered_map<std::string, std::unique_ptr<Dso>> user_dso_tree_;
   std::unique_ptr<Dso> unknown_dso_;
+  bool show_ip_for_unknown_symbol_;
   Symbol unknown_symbol_;
   std::unordered_map<uint64_t, Dso*> dso_id_to_dso_map_;
 };