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 ¤t_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