Simpleperf: add test for reporting callgraph of shared libraries in apk file.

Also add comment for how to generate each perf.data in testdata/.
Add --log <leve> argument in unit test to help debugging.

Bug: 26962895
Change-Id: Iaa5a81cd8da5174d5b5b4e7847811e2432bf0db8
diff --git a/simpleperf/cmd_report_test.cpp b/simpleperf/cmd_report_test.cpp
index bf1ff46..a4bd6f3 100644
--- a/simpleperf/cmd_report_test.cpp
+++ b/simpleperf/cmd_report_test.cpp
@@ -26,6 +26,7 @@
 #include "command.h"
 #include "event_selection_set.h"
 #include "get_test_data.h"
+#include "perf_regs.h"
 #include "read_apk.h"
 #include "test_util.h"
 
@@ -259,6 +260,13 @@
             hit_set.end());
 }
 
+TEST_F(ReportCommandTest, report_symbols_of_nativelib_in_apk) {
+  Report(NATIVELIB_IN_APK_PERF_DATA);
+  ASSERT_TRUE(success);
+  ASSERT_NE(content.find(GetUrlInApk(APK_FILE, NATIVELIB_IN_APK)), std::string::npos);
+  ASSERT_NE(content.find("Func2"), std::string::npos);
+}
+
 #if defined(__linux__)
 
 static std::unique_ptr<Command> RecordCmd() {
@@ -277,11 +285,18 @@
   }
 }
 
+TEST_F(ReportCommandTest, report_dwarf_callgraph_of_nativelib_in_apk) {
+  // NATIVELIB_IN_APK_PERF_DATA is recorded on arm64, so can only report callgraph on arm64.
+  if (GetBuildArch() == ARCH_ARM64) {
+    Report(NATIVELIB_IN_APK_PERF_DATA, {"-g"});
+    ASSERT_NE(content.find(GetUrlInApk(APK_FILE, NATIVELIB_IN_APK)), std::string::npos);
+    ASSERT_NE(content.find("Func2"), std::string::npos);
+    ASSERT_NE(content.find("Func1"), std::string::npos);
+    ASSERT_NE(content.find("GlobalFunc"), std::string::npos);
+  } else {
+    GTEST_LOG_(INFO) << "This test does nothing as it is only run on arm64 devices";
+  }
+}
+
 #endif
 
-TEST_F(ReportCommandTest, report_symbols_of_nativelib_in_apk) {
-  Report(NATIVELIB_IN_APK_PERF_DATA);
-  ASSERT_TRUE(success);
-  ASSERT_NE(content.find(GetUrlInApk(APK_FILE, NATIVELIB_IN_APK)), std::string::npos);
-  ASSERT_NE(content.find("GlobalFunc"), std::string::npos);
-}
diff --git a/simpleperf/get_test_data.h b/simpleperf/get_test_data.h
index e485e7b..c380a7d 100644
--- a/simpleperf/get_test_data.h
+++ b/simpleperf/get_test_data.h
@@ -26,23 +26,45 @@
 
 bool IsRoot();
 
-static const std::string PERF_DATA = "perf.data";
-static const std::string CALLGRAPH_FP_PERF_DATA = "perf_g_fp.data";
-static const std::string BRANCH_PERF_DATA = "perf_b.data";
-static const std::string PERF_DATA_WITH_MINI_DEBUG_INFO = "perf_with_mini_debug_info.data";
-
+// The source code of elf and elf_with_mini_debug_info is testdata/elf_file_source.cpp.
 static const std::string ELF_FILE = "elf";
 static const std::string ELF_FILE_WITH_MINI_DEBUG_INFO = "elf_with_mini_debug_info";
-
-static const std::string APK_FILE = "data/app/com.example.hellojni-1/base.apk";
-static const std::string NATIVELIB_IN_APK = "lib/arm64-v8a/libhello-jni.so";
-static const std::string NATIVELIB_IN_APK_PERF_DATA = "has_embedded_native_libs_apk_perf.data";
-
-constexpr size_t NATIVELIB_OFFSET_IN_APK = 0x8000;
-constexpr size_t NATIVELIB_SIZE_IN_APK = 0x15d8;
+// perf.data is generated by sampling on three processes running different
+// executables: elf, t1, t2 (all generated by elf_file_source.cpp, but with different
+// executable name).
+static const std::string PERF_DATA = "perf.data";
+// perf_g_fp.data is generated by sampling on one process running elf using --call-graph fp option.
+static const std::string CALLGRAPH_FP_PERF_DATA = "perf_g_fp.data";
+// perf_b.data is generated by sampling on one process running elf using -b option.
+static const std::string BRANCH_PERF_DATA = "perf_b.data";
+// perf_with_mini_debug_info.data is generated by sampling on one process running
+// elf_with_mini_debug_info.
+static const std::string PERF_DATA_WITH_MINI_DEBUG_INFO = "perf_with_mini_debug_info.data";
 
 static BuildId elf_file_build_id("0b12a384a9f4a3f3659b7171ca615dbec3a81f71");
 
-static BuildId native_lib_build_id("b46f51cb9c4b71fb08a2fdbefc2c187894f14008");
+
+// To generate apk supporting execution on shared libraries in apk:
+// 1. Add android:extractNativeLibs=false in AndroidManifest.xml.
+// 2. Use `zip -0` to store native libraries in apk without compression.
+// 3. Use `zipalign -p 4096` to make native libraries in apk start at page boundaries.
+//
+// The logical in libhello-jni.so is as below:
+//  volatile int GlobalVar;
+//
+//  while (true) {
+//    GlobalFunc() -> Func1() -> Func2()
+//  }
+// And most time is spent in Func2().
+static const std::string APK_FILE = "data/app/com.example.hellojni-1/base.apk";
+static const std::string NATIVELIB_IN_APK = "lib/arm64-v8a/libhello-jni.so";
+// has_embedded_native_libs_apk_perf.data is generated by sampling on one process running
+// APK_FILE using -g --no-unwind option.
+static const std::string NATIVELIB_IN_APK_PERF_DATA = "has_embedded_native_libs_apk_perf.data";
+// The offset and size info are extracted from the generated apk file to run read_apk tests.
+constexpr size_t NATIVELIB_OFFSET_IN_APK = 0x639000;
+constexpr size_t NATIVELIB_SIZE_IN_APK = 0x1678;
+
+static BuildId native_lib_build_id("8ed5755a7fdc07586ca228b8ee21621bce2c7a97");
 
 #endif  // SIMPLE_PERF_GET_TEST_DATA_H_
diff --git a/simpleperf/gtest_main.cpp b/simpleperf/gtest_main.cpp
index 099bd81..1d0a5c9 100644
--- a/simpleperf/gtest_main.cpp
+++ b/simpleperf/gtest_main.cpp
@@ -59,6 +59,7 @@
     LOG(ERROR) << "failed to start iterating zip entries";
     return false;
   }
+  std::unique_ptr<void, decltype(&EndIteration)> guard(cookie, EndIteration);
   ZipEntry entry;
   ZipString name;
   while (Next(cookie, &entry, &name) == 0) {
@@ -87,7 +88,6 @@
       return false;
     }
   }
-  EndIteration(cookie);
   return true;
 }
 #endif  // defined(__ANDROID__)
@@ -95,13 +95,26 @@
 int main(int argc, char** argv) {
   InitLogging(argv, android::base::StderrLogger);
   testing::InitGoogleTest(&argc, argv);
+  android::base::LogSeverity log_severity = android::base::WARNING;
 
   for (int i = 1; i < argc; ++i) {
     if (strcmp(argv[i], "-t") == 0 && i + 1 < argc) {
       testdata_dir = argv[i + 1];
       i++;
+    } else if (strcmp(argv[i], "--log") == 0) {
+      if (i + 1 < argc) {
+        ++i;
+        if (!GetLogSeverity(argv[i], &log_severity)) {
+          LOG(ERROR) << "Unknown log severity: " << argv[i];
+          return 1;
+        }
+      } else {
+        LOG(ERROR) << "Missing argument for --log option.\n";
+        return 1;
+      }
     }
   }
+  android::base::ScopedLogSeverity severity(log_severity);
 
 #if defined(__ANDROID__)
   std::unique_ptr<TemporaryDir> tmp_dir;
diff --git a/simpleperf/main.cpp b/simpleperf/main.cpp
index 75313a5..0a73c2d 100644
--- a/simpleperf/main.cpp
+++ b/simpleperf/main.cpp
@@ -15,21 +15,14 @@
  */
 
 #include <string.h>
-#include <map>
+
 #include <string>
 #include <vector>
 
 #include <android-base/logging.h>
 
 #include "command.h"
-
-static std::map<std::string, android::base::LogSeverity> log_severity_map = {
-    {"verbose", android::base::VERBOSE},
-    {"debug", android::base::DEBUG},
-    {"warning", android::base::WARNING},
-    {"error", android::base::ERROR},
-    {"fatal", android::base::FATAL},
-};
+#include "utils.h"
 
 int main(int argc, char** argv) {
   InitLogging(argv, android::base::StderrLogger);
@@ -42,10 +35,7 @@
     } else if (strcmp(argv[i], "--log") == 0) {
       if (i + 1 < argc) {
         ++i;
-        auto it = log_severity_map.find(argv[i]);
-        if (it != log_severity_map.end()) {
-          log_severity = it->second;
-        } else {
+        if (!GetLogSeverity(argv[i], &log_severity)) {
           LOG(ERROR) << "Unknown log severity: " << argv[i];
           return 1;
         }
diff --git a/simpleperf/read_apk.cpp b/simpleperf/read_apk.cpp
index 18c76c2..af72cdf 100644
--- a/simpleperf/read_apk.cpp
+++ b/simpleperf/read_apk.cpp
@@ -24,10 +24,11 @@
 #include <sys/types.h>
 #include <unistd.h>
 
+#include <memory>
+
 #include <android-base/file.h>
 #include <android-base/logging.h>
 #include <ziparchive/zip_archive.h>
-
 #include "read_elf.h"
 #include "utils.h"
 
@@ -52,6 +53,7 @@
   if (!IsValidApkPath(apk_path)) {
     return nullptr;
   }
+
   FileHelper fhelper = FileHelper::OpenReadOnly(apk_path);
   if (!fhelper) {
     return nullptr;
diff --git a/simpleperf/read_apk_test.cpp b/simpleperf/read_apk_test.cpp
index e983a25..d7b30c5 100644
--- a/simpleperf/read_apk_test.cpp
+++ b/simpleperf/read_apk_test.cpp
@@ -31,9 +31,12 @@
   ApkInspector inspector;
   ASSERT_TRUE(inspector.FindElfInApkByOffset("/dev/null", 0) == nullptr);
   ASSERT_TRUE(inspector.FindElfInApkByOffset(GetTestData(APK_FILE), 0) == nullptr);
-  EmbeddedElf* ee = inspector.FindElfInApkByOffset(GetTestData(APK_FILE), 0x9000);
+  // Test if we can read the EmbeddedElf using an offset inside its [offset, offset+size] range
+  // in the apk file.
+  EmbeddedElf* ee = inspector.FindElfInApkByOffset(GetTestData(APK_FILE),
+                                                   NATIVELIB_OFFSET_IN_APK + NATIVELIB_SIZE_IN_APK / 2);
   ASSERT_TRUE(ee != nullptr);
-  ASSERT_EQ(ee->entry_name(), NATIVELIB_IN_APK);
+  ASSERT_EQ(NATIVELIB_IN_APK, ee->entry_name());
   ASSERT_EQ(NATIVELIB_OFFSET_IN_APK, ee->entry_offset());
   ASSERT_EQ(NATIVELIB_SIZE_IN_APK, ee->entry_size());
 }
diff --git a/simpleperf/testdata/data/app/com.example.hellojni-1/base.apk b/simpleperf/testdata/data/app/com.example.hellojni-1/base.apk
index c757e9e..95ea93a 100644
--- a/simpleperf/testdata/data/app/com.example.hellojni-1/base.apk
+++ b/simpleperf/testdata/data/app/com.example.hellojni-1/base.apk
Binary files differ
diff --git a/simpleperf/testdata/has_embedded_native_libs_apk_perf.data b/simpleperf/testdata/has_embedded_native_libs_apk_perf.data
index f85c9d3..fafbbbc 100644
--- a/simpleperf/testdata/has_embedded_native_libs_apk_perf.data
+++ b/simpleperf/testdata/has_embedded_native_libs_apk_perf.data
Binary files differ
diff --git a/simpleperf/utils.cpp b/simpleperf/utils.cpp
index f2418a6..dd09fbd 100644
--- a/simpleperf/utils.cpp
+++ b/simpleperf/utils.cpp
@@ -25,6 +25,7 @@
 #include <unistd.h>
 
 #include <algorithm>
+#include <map>
 #include <string>
 
 #include <android-base/file.h>
@@ -230,3 +231,19 @@
   *decompressed_data = std::move(dst);
   return true;
 }
+
+bool GetLogSeverity(const std::string& name, android::base::LogSeverity* severity) {
+  static std::map<std::string, android::base::LogSeverity> log_severity_map = {
+      {"verbose", android::base::VERBOSE},
+      {"debug", android::base::DEBUG},
+      {"warning", android::base::WARNING},
+      {"error", android::base::ERROR},
+      {"fatal", android::base::FATAL},
+  };
+  auto it = log_severity_map.find(name);
+  if (it != log_severity_map.end()) {
+    *severity = it->second;
+    return true;
+  }
+  return false;
+}
diff --git a/simpleperf/utils.h b/simpleperf/utils.h
index c5c4366..0424501 100644
--- a/simpleperf/utils.h
+++ b/simpleperf/utils.h
@@ -22,6 +22,7 @@
 #include <string>
 #include <vector>
 
+#include <android-base/logging.h>
 #include <android-base/macros.h>
 #include <ziparchive/zip_archive.h>
 
@@ -82,7 +83,6 @@
   DISALLOW_COPY_AND_ASSIGN(FileHelper);
 };
 
-
 class ArchiveHelper {
  public:
   ArchiveHelper(int fd, const std::string& debug_filename);
@@ -121,4 +121,6 @@
 
 bool XzDecompress(const std::string& compressed_data, std::string* decompressed_data);
 
+bool GetLogSeverity(const std::string& name, android::base::LogSeverity* severity);
+
 #endif  // SIMPLE_PERF_UTILS_H_