Merge "simpleperf: create one map for each jit symbol."
diff --git a/cppreopts/cppreopts.sh b/cppreopts/cppreopts.sh
index 3416e67..d409db8 100755
--- a/cppreopts/cppreopts.sh
+++ b/cppreopts/cppreopts.sh
@@ -53,7 +53,7 @@
   # NOTE: this implementation will break in any path with spaces to favor
   # background copy tasks
   for file in $(find ${mountpoint} -type f -name "*.odex" -o -type f -name "*.vdex" -o -type f -name "*.art"); do
-    real_name=${file/${mountpoint}/\/system}
+    real_name=${file/${mountpoint}/}
     dest_name=$(preopt2cachename ${real_name})
     if ! test $? -eq 0 ; then
       log -p i -t cppreopts "Unable to figure out destination for ${file}"
diff --git a/libfscrypt/fscrypt.cpp b/libfscrypt/fscrypt.cpp
index d0950df..66a4320 100644
--- a/libfscrypt/fscrypt.cpp
+++ b/libfscrypt/fscrypt.cpp
@@ -33,7 +33,33 @@
 
 #include <array>
 
-#define FS_KEY_DESCRIPTOR_SIZE_HEX (2 * FS_KEY_DESCRIPTOR_SIZE + 1)
+// TODO: switch to <linux/fscrypt.h> once it's in Bionic
+#ifndef FSCRYPT_POLICY_V1
+
+// Careful: due to an API quirk this is actually 0, not 1.  We use 1 everywhere
+// else, so make sure to only use this constant in the ioctl itself.
+#define FSCRYPT_POLICY_V1 0
+#define FSCRYPT_KEY_DESCRIPTOR_SIZE 8
+struct fscrypt_policy_v1 {
+    __u8 version;
+    __u8 contents_encryption_mode;
+    __u8 filenames_encryption_mode;
+    __u8 flags;
+    __u8 master_key_descriptor[FSCRYPT_KEY_DESCRIPTOR_SIZE];
+};
+
+#define FSCRYPT_POLICY_V2 2
+#define FSCRYPT_KEY_IDENTIFIER_SIZE 16
+struct fscrypt_policy_v2 {
+    __u8 version;
+    __u8 contents_encryption_mode;
+    __u8 filenames_encryption_mode;
+    __u8 flags;
+    __u8 __reserved[4];
+    __u8 master_key_identifier[FSCRYPT_KEY_IDENTIFIER_SIZE];
+};
+
+#endif /* FSCRYPT_POLICY_V1 */
 
 /* modes not supported by upstream kernel, so not in <linux/fs.h> */
 #define FS_ENCRYPTION_MODE_AES_256_HEH      126
@@ -41,6 +67,8 @@
 
 #define HEX_LOOKUP "0123456789abcdef"
 
+#define MAX_KEY_REF_SIZE_HEX (2 * FSCRYPT_KEY_IDENTIFIER_SIZE + 1)
+
 bool fscrypt_is_native() {
     char value[PROPERTY_VALUE_MAX];
     property_get("ro.crypto.type", value, "none");
@@ -68,40 +96,52 @@
     }
 }
 
-static void policy_to_hex(const char* policy, char* hex) {
-    for (size_t i = 0, j = 0; i < FS_KEY_DESCRIPTOR_SIZE; i++) {
-        hex[j++] = HEX_LOOKUP[(policy[i] & 0xF0) >> 4];
-        hex[j++] = HEX_LOOKUP[policy[i] & 0x0F];
+static void keyrefstring(const char* key_raw_ref, size_t key_raw_ref_length, char* hex) {
+    size_t j = 0;
+    for (size_t i = 0; i < key_raw_ref_length; i++) {
+        hex[j++] = HEX_LOOKUP[(key_raw_ref[i] & 0xF0) >> 4];
+        hex[j++] = HEX_LOOKUP[key_raw_ref[i] & 0x0F];
     }
-    hex[FS_KEY_DESCRIPTOR_SIZE_HEX - 1] = '\0';
+    hex[j] = '\0';
 }
 
-static uint8_t fscrypt_get_policy_flags(int filenames_encryption_mode) {
-    if (filenames_encryption_mode == FS_ENCRYPTION_MODE_AES_256_CTS) {
-        // Use legacy padding with our original filenames encryption mode.
-        return FS_POLICY_FLAGS_PAD_4;
-    } else if (filenames_encryption_mode == FS_ENCRYPTION_MODE_ADIANTUM) {
-        // Use DIRECT_KEY for Adiantum, since it's much more efficient but just
-        // as secure since Android doesn't reuse the same master key for
-        // multiple encryption modes
-        return (FS_POLICY_FLAGS_PAD_16 | FS_POLICY_FLAG_DIRECT_KEY);
+static uint8_t fscrypt_get_policy_flags(int filenames_encryption_mode, int policy_version) {
+    uint8_t flags = 0;
+
+    // In the original setting of v1 policies and AES-256-CTS we used 4-byte
+    // padding of filenames, so we have to retain that for compatibility.
+    //
+    // For everything else, use 16-byte padding.  This is more secure (it helps
+    // hide the length of filenames), and it makes the inputs evenly divisible
+    // into cipher blocks which is more efficient for encryption and decryption.
+    if (policy_version == 1 && filenames_encryption_mode == FS_ENCRYPTION_MODE_AES_256_CTS) {
+        flags |= FS_POLICY_FLAGS_PAD_4;
+    } else {
+        flags |= FS_POLICY_FLAGS_PAD_16;
     }
-    // With a new mode we can use the better padding flag without breaking existing devices: pad
-    // filenames with zeroes to the next 16-byte boundary.  This is more secure (helps hide the
-    // length of filenames) and makes the inputs evenly divisible into blocks which is more
-    // efficient for encryption and decryption.
-    return FS_POLICY_FLAGS_PAD_16;
+
+    // Use DIRECT_KEY for Adiantum, since it's much more efficient but just as
+    // secure since Android doesn't reuse the same master key for multiple
+    // encryption modes.
+    if (filenames_encryption_mode == FS_ENCRYPTION_MODE_ADIANTUM) {
+        flags |= FS_POLICY_FLAG_DIRECT_KEY;
+    }
+
+    return flags;
 }
 
 static bool fscrypt_is_encrypted(int fd) {
-    fscrypt_policy fp;
-    return ioctl(fd, FS_IOC_GET_ENCRYPTION_POLICY, &fp) == 0;
+    fscrypt_policy_v1 policy;
+
+    // success => encrypted with v1 policy
+    // EINVAL => encrypted with v2 policy
+    // ENODATA => not encrypted
+    return ioctl(fd, FS_IOC_GET_ENCRYPTION_POLICY, &policy) == 0 || errno == EINVAL;
 }
 
-int fscrypt_policy_ensure(const char *directory, const char *policy,
-                          size_t policy_length,
-                          const char *contents_encryption_mode,
-                          const char *filenames_encryption_mode) {
+int fscrypt_policy_ensure(const char* directory, const char* key_raw_ref, size_t key_raw_ref_length,
+                          const char* contents_encryption_mode,
+                          const char* filenames_encryption_mode, int policy_version) {
     int contents_mode = 0;
     int filenames_mode = 0;
 
@@ -130,19 +170,44 @@
         return -1;
     }
 
-    if (policy_length != FS_KEY_DESCRIPTOR_SIZE) {
-        LOG(ERROR) << "Policy wrong length: " << policy_length;
-        return -1;
-    }
-    char policy_hex[FS_KEY_DESCRIPTOR_SIZE_HEX];
-    policy_to_hex(policy, policy_hex);
+    union {
+        fscrypt_policy_v1 v1;
+        fscrypt_policy_v2 v2;
+    } policy;
+    memset(&policy, 0, sizeof(policy));
 
-    fscrypt_policy fp;
-    fp.version = 0;
-    fp.contents_encryption_mode = contents_mode;
-    fp.filenames_encryption_mode = filenames_mode;
-    fp.flags = fscrypt_get_policy_flags(filenames_mode);
-    memcpy(fp.master_key_descriptor, policy, FS_KEY_DESCRIPTOR_SIZE);
+    switch (policy_version) {
+        case 1:
+            if (key_raw_ref_length != FSCRYPT_KEY_DESCRIPTOR_SIZE) {
+                LOG(ERROR) << "Invalid key ref length for v1 policy: " << key_raw_ref_length;
+                return -1;
+            }
+            // Careful: FSCRYPT_POLICY_V1 is actually 0 in the API, so make sure
+            // to use it here instead of a literal 1.
+            policy.v1.version = FSCRYPT_POLICY_V1;
+            policy.v1.contents_encryption_mode = contents_mode;
+            policy.v1.filenames_encryption_mode = filenames_mode;
+            policy.v1.flags = fscrypt_get_policy_flags(filenames_mode, policy_version);
+            memcpy(policy.v1.master_key_descriptor, key_raw_ref, FSCRYPT_KEY_DESCRIPTOR_SIZE);
+            break;
+        case 2:
+            if (key_raw_ref_length != FSCRYPT_KEY_IDENTIFIER_SIZE) {
+                LOG(ERROR) << "Invalid key ref length for v2 policy: " << key_raw_ref_length;
+                return -1;
+            }
+            policy.v2.version = FSCRYPT_POLICY_V2;
+            policy.v2.contents_encryption_mode = contents_mode;
+            policy.v2.filenames_encryption_mode = filenames_mode;
+            policy.v2.flags = fscrypt_get_policy_flags(filenames_mode, policy_version);
+            memcpy(policy.v2.master_key_identifier, key_raw_ref, FSCRYPT_KEY_IDENTIFIER_SIZE);
+            break;
+        default:
+            LOG(ERROR) << "Invalid encryption policy version: " << policy_version;
+            return -1;
+    }
+
+    char ref[MAX_KEY_REF_SIZE_HEX];
+    keyrefstring(key_raw_ref, key_raw_ref_length, ref);
 
     android::base::unique_fd fd(open(directory, O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC));
     if (fd == -1) {
@@ -155,7 +220,7 @@
     // FS_IOC_SET_ENCRYPTION_POLICY will set the policy if the directory is
     // unencrypted; otherwise it will verify that the existing policy matches.
     // Setting the policy will fail if the directory is already nonempty.
-    if (ioctl(fd, FS_IOC_SET_ENCRYPTION_POLICY, &fp) != 0) {
+    if (ioctl(fd, FS_IOC_SET_ENCRYPTION_POLICY, &policy) != 0) {
         std::string reason;
         switch (errno) {
             case EEXIST:
@@ -165,7 +230,7 @@
                 reason = strerror(errno);
                 break;
         }
-        LOG(ERROR) << "Failed to set encryption policy of " << directory << " to " << policy_hex
+        LOG(ERROR) << "Failed to set encryption policy of " << directory << " to " << ref
                    << " modes " << contents_mode << "/" << filenames_mode << ": " << reason;
         if (errno == ENOTEMPTY) {
             log_ls(directory);
@@ -174,10 +239,10 @@
     }
 
     if (already_encrypted) {
-        LOG(INFO) << "Verified that " << directory << " has the encryption policy " << policy_hex
+        LOG(INFO) << "Verified that " << directory << " has the encryption policy " << ref
                   << " modes " << contents_mode << "/" << filenames_mode;
     } else {
-        LOG(INFO) << "Encryption policy of " << directory << " set to " << policy_hex << " modes "
+        LOG(INFO) << "Encryption policy of " << directory << " set to " << ref << " modes "
                   << contents_mode << "/" << filenames_mode;
     }
     return 0;
diff --git a/libfscrypt/include/fscrypt/fscrypt.h b/libfscrypt/include/fscrypt/fscrypt.h
index ff82d47..13358bb 100644
--- a/libfscrypt/include/fscrypt/fscrypt.h
+++ b/libfscrypt/include/fscrypt/fscrypt.h
@@ -25,10 +25,9 @@
 
 bool fscrypt_is_native();
 
-int fscrypt_policy_ensure(const char *directory, const char *policy,
-                          size_t policy_length,
-                          const char *contents_encryption_mode,
-                          const char *filenames_encryption_mode);
+int fscrypt_policy_ensure(const char* directory, const char* key_raw_ref, size_t key_raw_ref_length,
+                          const char* contents_encryption_mode,
+                          const char* filenames_encryption_mode, int policy_version);
 
 static const char* fscrypt_unencrypted_folder = "/unencrypted";
 static const char* fscrypt_key_ref = "/unencrypted/ref";
diff --git a/simpleperf/report_lib_interface.cpp b/simpleperf/report_lib_interface.cpp
index d685ddf..70a274f 100644
--- a/simpleperf/report_lib_interface.cpp
+++ b/simpleperf/report_lib_interface.cpp
@@ -108,6 +108,7 @@
 bool SetKallsymsFile(ReportLib* report_lib, const char* kallsyms_file) EXPORT;
 void ShowIpForUnknownSymbol(ReportLib* report_lib) EXPORT;
 void ShowArtFrames(ReportLib* report_lib, bool show) EXPORT;
+void MergeJavaMethods(ReportLib* report_lib, bool merge) EXPORT;
 
 Sample* GetNextSample(ReportLib* report_lib) EXPORT;
 Event* GetEventOfCurrentSample(ReportLib* report_lib) EXPORT;
@@ -154,6 +155,7 @@
 
   void ShowIpForUnknownSymbol() { thread_tree_.ShowIpForUnknownSymbol(); }
   void ShowArtFrames(bool show) { show_art_frames_ = show; }
+  void MergeJavaMethods(bool merge) { merge_java_methods_ = merge; }
 
   Sample* GetNextSample();
   Event* GetEventOfCurrentSample() { return &current_event_; }
@@ -192,6 +194,9 @@
   FeatureSection feature_section_;
   std::vector<char> feature_section_data_;
   bool show_art_frames_;
+  bool merge_java_methods_ = true;
+  // Map from a java method name to it's dex file, start_addr and len.
+  std::unordered_map<std::string, std::tuple<Dso*, uint64_t, uint64_t>> java_methods_;
   std::unique_ptr<Tracing> tracing_;
 };
 
@@ -227,6 +232,15 @@
     if (auto it = meta_info.find("trace_offcpu"); it != meta_info.end()) {
       trace_offcpu_ = it->second == "true";
     }
+    if (merge_java_methods_) {
+      for (Dso* dso : thread_tree_.GetAllDsos()) {
+        if (dso->type() == DSO_DEX_FILE) {
+          for (auto& symbol : dso->GetSymbols()) {
+            java_methods_[symbol.Name()] = std::make_tuple(dso, symbol.addr, symbol.len);
+          }
+        }
+      }
+    }
   }
   return true;
 }
@@ -328,6 +342,23 @@
     entry.symbol.symbol_addr = symbol->addr;
     entry.symbol.symbol_len = symbol->len;
     entry.symbol.mapping = AddMapping(*map);
+
+    if (merge_java_methods_ && map->dso->type() == DSO_ELF_FILE && map->dso->IsForJavaMethod()) {
+      // This is a jitted java method, merge it with the interpreted java method having the same
+      // name if possible. Otherwise, merge it with other jitted java methods having the same name
+      // by assigning a common dso_name.
+      if (auto it = java_methods_.find(entry.symbol.symbol_name); it != java_methods_.end()) {
+        entry.symbol.dso_name = std::get<0>(it->second)->Path().c_str();
+        entry.symbol.symbol_addr = std::get<1>(it->second);
+        entry.symbol.symbol_len = std::get<2>(it->second);
+        // Not enough info to map an offset in a jitted method to an offset in a dex file. So just
+        // use the symbol_addr.
+        entry.symbol.vaddr_in_file = entry.symbol.symbol_addr;
+      } else {
+        entry.symbol.dso_name = "[JIT cache]";
+      }
+    }
+
     callchain_entries_.push_back(entry);
   }
   current_sample_.ip = callchain_entries_[0].ip;
@@ -460,6 +491,10 @@
   return report_lib->ShowArtFrames(show);
 }
 
+void MergeJavaMethods(ReportLib* report_lib, bool merge) {
+  return report_lib->MergeJavaMethods(merge);
+}
+
 bool SetKallsymsFile(ReportLib* report_lib, const char* kallsyms_file) {
   return report_lib->SetKallsymsFile(kallsyms_file);
 }
diff --git a/simpleperf/scripts/simpleperf_report_lib.py b/simpleperf/scripts/simpleperf_report_lib.py
index ab45b67..3e677be 100644
--- a/simpleperf/scripts/simpleperf_report_lib.py
+++ b/simpleperf/scripts/simpleperf_report_lib.py
@@ -240,6 +240,7 @@
         self._SetKallsymsFileFunc = self._lib.SetKallsymsFile
         self._ShowIpForUnknownSymbolFunc = self._lib.ShowIpForUnknownSymbol
         self._ShowArtFramesFunc = self._lib.ShowArtFrames
+        self._MergeJavaMethodsFunc = self._lib.MergeJavaMethods
         self._GetNextSampleFunc = self._lib.GetNextSample
         self._GetNextSampleFunc.restype = ct.POINTER(SampleStruct)
         self._GetEventOfCurrentSampleFunc = self._lib.GetEventOfCurrentSample
@@ -294,6 +295,17 @@
         """ Show frames of internal methods of the Java interpreter. """
         self._ShowArtFramesFunc(self.getInstance(), show)
 
+    def MergeJavaMethods(self, merge=True):
+        """ This option merges jitted java methods with the same name but in different jit
+            symfiles. If possible, it also merges jitted methods with interpreted methods,
+            by mapping jitted methods to their corresponding dex files.
+            Side effects:
+              It only works at method level, not instruction level.
+              It makes symbol.vaddr_in_file and symbol.mapping not accurate for jitted methods.
+            Java methods are merged by default.
+        """
+        self._MergeJavaMethodsFunc(self.getInstance(), merge)
+
     def SetKallsymsFile(self, kallsym_file):
         """ Set the file path to a copy of the /proc/kallsyms file (for off device decoding) """
         cond = self._SetKallsymsFileFunc(self.getInstance(), _char_pt(kallsym_file))
diff --git a/simpleperf/scripts/test.py b/simpleperf/scripts/test.py
index e5b5957..2adee0e 100755
--- a/simpleperf/scripts/test.py
+++ b/simpleperf/scripts/test.py
@@ -990,6 +990,31 @@
         report_lib.ShowArtFrames(True)
         self.assertTrue(has_art_frame(report_lib))
 
+    def test_merge_java_methods(self):
+        def parse_dso_names(report_lib):
+            dso_names = set()
+            report_lib.SetRecordFile(os.path.join('testdata', 'perf_with_interpreter_frames.data'))
+            while report_lib.GetNextSample():
+                dso_names.add(report_lib.GetSymbolOfCurrentSample().dso_name)
+                callchain = report_lib.GetCallChainOfCurrentSample()
+                for i in range(callchain.nr):
+                    dso_names.add(callchain.entries[i].symbol.dso_name)
+            report_lib.Close()
+            has_jit_symfiles = any('TemporaryFile-' in name for name in dso_names)
+            has_jit_cache = '[JIT cache]' in dso_names
+            return has_jit_symfiles, has_jit_cache
+
+        report_lib = ReportLib()
+        self.assertEqual(parse_dso_names(report_lib), (False, True))
+
+        report_lib = ReportLib()
+        report_lib.MergeJavaMethods(True)
+        self.assertEqual(parse_dso_names(report_lib), (False, True))
+
+        report_lib = ReportLib()
+        report_lib.MergeJavaMethods(False)
+        self.assertEqual(parse_dso_names(report_lib), (True, False))
+
     def test_tracing_data(self):
         self.report_lib.SetRecordFile(os.path.join('testdata', 'perf_with_tracepoint_event.data'))
         has_tracing_data = False