summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Android Build Coastguard Worker <android-build-coastguard-worker@google.com> 2025-03-11 16:06:39 -0700
committer Android Build Coastguard Worker <android-build-coastguard-worker@google.com> 2025-03-11 16:06:39 -0700
commit9cd3adf41570af8b787e02dc7bef1eed035d8f40 (patch)
tree9d83f9bf049efd2fb05903a7e6bf356deb3a6532
parent68440234b43e3e33f3cef439024d3bea6e62e39f (diff)
parent199a768184b32b9d334d862356be8ad543dc8fde (diff)
Snap for 13197820 from 199a768184b32b9d334d862356be8ad543dc8fde to 25Q2-release
Change-Id: I87ba240391d7e2a635ee5fd8fb8dfe8d1778c529
-rw-r--r--compiler/common_compiler_test.cc2
-rw-r--r--dex2oat/dex2oat.cc21
-rw-r--r--dex2oat/linker/image_test.h9
-rw-r--r--dex2oat/linker/oat_writer.cc49
-rw-r--r--dex2oat/linker/oat_writer.h27
-rw-r--r--dex2oat/linker/oat_writer_test.cc85
-rw-r--r--libartbase/base/memfd.cc23
-rw-r--r--libartbase/base/memfd.h4
-rw-r--r--libartservice/service/java/com/android/server/art/ArtManagerLocal.java29
-rw-r--r--libartservice/service/java/com/android/server/art/ArtShellCommand.java21
-rw-r--r--libartservice/service/java/com/android/server/art/DexUseManagerLocal.java14
-rw-r--r--libartservice/service/java/com/android/server/art/DumpHelper.java93
-rw-r--r--libartservice/service/javatests/com/android/server/art/DumpHelperTest.java139
-rw-r--r--oatdump/oatdump.cc5
-rw-r--r--odrefresh/odrefresh.cc10
-rw-r--r--odrefresh/odrefresh_test.cc30
-rw-r--r--openjdkjvmti/ti_search.cc2
-rw-r--r--runtime/gc/collector/mark_compact.cc33
-rw-r--r--runtime/gc/space/image_space.cc6
-rw-r--r--runtime/oat/oat.cc128
-rw-r--r--runtime/oat/oat.h63
-rw-r--r--test/1963-add-to-dex-classloader-in-memory/check_memfd_create.cc67
-rw-r--r--test/1963-add-to-dex-classloader-in-memory/src/Main.java6
-rw-r--r--test/Android.bp1
-rw-r--r--tools/trace_parser/Android.bp41
-rw-r--r--tools/trace_parser/long_running_method_trace_parser.cc252
26 files changed, 673 insertions, 487 deletions
diff --git a/compiler/common_compiler_test.cc b/compiler/common_compiler_test.cc
index e54c85f747..ef915e4152 100644
--- a/compiler/common_compiler_test.cc
+++ b/compiler/common_compiler_test.cc
@@ -63,7 +63,7 @@ class CommonCompilerTestImpl::CodeAndMetadata {
const uint32_t capacity = RoundUp(code_offset + code_size, page_size);
// Create a memfd handle with sufficient capacity.
- android::base::unique_fd mem_fd(art::memfd_create_compat("test code", /*flags=*/ 0));
+ android::base::unique_fd mem_fd(art::memfd_create("test code", /*flags=*/ 0));
CHECK_GE(mem_fd.get(), 0);
int err = ftruncate(mem_fd, capacity);
CHECK_EQ(err, 0);
diff --git a/dex2oat/dex2oat.cc b/dex2oat/dex2oat.cc
index 7564bf0b68..39b0d826b4 100644
--- a/dex2oat/dex2oat.cc
+++ b/dex2oat/dex2oat.cc
@@ -500,15 +500,6 @@ class ThreadLocalHashOverride {
Handle<mirror::Object> old_field_value_;
};
-class OatKeyValueStore : public SafeMap<std::string, std::string> {
- public:
- using SafeMap::Put;
-
- iterator Put(const std::string& k, bool v) {
- return SafeMap::Put(k, v ? OatHeader::kTrueValue : OatHeader::kFalseValue);
- }
-};
-
class Dex2Oat final {
public:
explicit Dex2Oat(TimingLogger* timings)
@@ -893,7 +884,7 @@ class Dex2Oat final {
}
// Fill some values into the key-value store for the oat header.
- key_value_store_.reset(new OatKeyValueStore());
+ key_value_store_.reset(new linker::OatKeyValueStore());
// Automatically force determinism for the boot image and boot image extensions in a host build.
if (!kIsTargetBuild && (IsBootImage() || IsBootImageExtension())) {
@@ -978,7 +969,8 @@ class Dex2Oat final {
}
oss << argv[i];
}
- key_value_store_->Put(OatHeader::kDex2OatCmdLineKey, oss.str());
+ key_value_store_->PutNonDeterministic(
+ OatHeader::kDex2OatCmdLineKey, oss.str(), /*allow_truncation=*/true);
}
key_value_store_->Put(OatHeader::kDebuggableKey, compiler_options_->debuggable_);
key_value_store_->Put(OatHeader::kNativeDebuggableKey,
@@ -1696,7 +1688,10 @@ class Dex2Oat final {
CompilerFilter::DependsOnImageChecksum(original_compiler_filter)) {
std::string versions =
apex_versions_argument_.empty() ? runtime->GetApexVersions() : apex_versions_argument_;
- key_value_store_->Put(OatHeader::kApexVersionsKey, versions);
+ if (!key_value_store_->PutNonDeterministic(OatHeader::kApexVersionsKey, versions)) {
+ LOG(ERROR) << "Cannot store apex versions string because it's too long";
+ return dex2oat::ReturnCode::kOther;
+ }
}
// Now that we have adjusted whether we generate an image, encode it in the
@@ -2915,7 +2910,7 @@ class Dex2Oat final {
std::unique_ptr<CompilerOptions> compiler_options_;
- std::unique_ptr<OatKeyValueStore> key_value_store_;
+ std::unique_ptr<linker::OatKeyValueStore> key_value_store_;
std::unique_ptr<VerificationResults> verification_results_;
diff --git a/dex2oat/linker/image_test.h b/dex2oat/linker/image_test.h
index 3706b685fa..349462e098 100644
--- a/dex2oat/linker/image_test.h
+++ b/dex2oat/linker/image_test.h
@@ -231,13 +231,12 @@ inline void ImageTest::DoCompile(ImageHeader::StorageMode storage_mode,
CompileAll(class_loader, class_path, &timings);
TimingLogger::ScopedTiming t("WriteElf", &timings);
- SafeMap<std::string, std::string> key_value_store;
+ OatKeyValueStore key_value_store;
key_value_store.Put(OatHeader::kBootClassPathKey,
android::base::Join(out_helper.dex_file_locations, ':'));
- key_value_store.Put(OatHeader::kApexVersionsKey, Runtime::Current()->GetApexVersions());
- key_value_store.Put(
- OatHeader::kConcurrentCopying,
- compiler_options_->EmitReadBarrier() ? OatHeader::kTrueValue : OatHeader::kFalseValue);
+ key_value_store.PutNonDeterministic(OatHeader::kApexVersionsKey,
+ Runtime::Current()->GetApexVersions());
+ key_value_store.Put(OatHeader::kConcurrentCopying, compiler_options_->EmitReadBarrier());
std::vector<std::unique_ptr<ElfWriter>> elf_writers;
std::vector<std::unique_ptr<OatWriter>> oat_writers;
diff --git a/dex2oat/linker/oat_writer.cc b/dex2oat/linker/oat_writer.cc
index 20787ddc8f..6cdfe7bb40 100644
--- a/dex2oat/linker/oat_writer.cc
+++ b/dex2oat/linker/oat_writer.cc
@@ -111,6 +111,33 @@ inline uint32_t CodeAlignmentSize(uint32_t header_offset, const CompiledMethod&
} // anonymous namespace
+bool OatKeyValueStore::PutNonDeterministic(const std::string& k,
+ const std::string& v,
+ bool allow_truncation) {
+ size_t length = OatHeader::GetNonDeterministicFieldLength(k);
+ DCHECK_GT(length, 0u);
+ if (v.length() <= length) {
+ map_.Put(k, v);
+ return true;
+ }
+ if (allow_truncation) {
+ LOG(WARNING) << "Key value store field " << k << "too long. Truncating";
+ map_.Put(k, v.substr(length));
+ return true;
+ }
+ return false;
+}
+
+void OatKeyValueStore::Put(const std::string& k, const std::string& v) {
+ DCHECK(OatHeader::IsDeterministicField(k));
+ map_.Put(k, v);
+}
+
+void OatKeyValueStore::Put(const std::string& k, bool v) {
+ DCHECK(OatHeader::IsDeterministicField(k));
+ map_.Put(k, v ? OatHeader::kTrueValue : OatHeader::kFalseValue);
+}
+
// .bss mapping offsets used for BCP DexFiles.
struct OatWriter::BssMappingInfo {
// Offsets set in PrepareLayout.
@@ -550,7 +577,7 @@ bool OatWriter::WriteAndOpenDexFiles(
bool OatWriter::StartRoData(const std::vector<const DexFile*>& dex_files,
OutputStream* oat_rodata,
- SafeMap<std::string, std::string>* key_value_store) {
+ OatKeyValueStore* key_value_store) {
CHECK(write_state_ == WriteState::kStartRoData);
// Record the ELF rodata section offset, i.e. the beginning of the OAT data.
@@ -1972,9 +1999,18 @@ bool OatWriter::VisitDexMethods(DexMethodVisitor* visitor) {
return true;
}
-size_t OatWriter::InitOatHeader(uint32_t num_dex_files,
- SafeMap<std::string, std::string>* key_value_store) {
+size_t OatWriter::InitOatHeader(uint32_t num_dex_files, OatKeyValueStore* key_value_store) {
TimingLogger::ScopedTiming split("InitOatHeader", timings_);
+
+ // `key_value_store` only exists in the first oat file in a multi-image boot image.
+ if (key_value_store != nullptr) {
+ // Add non-deterministic fields if they don't exist. These fields should always exist with fixed
+ // lengths.
+ for (auto [field, length] : OatHeader::kNonDeterministicFieldsAndLengths) {
+ key_value_store->map_.FindOrAdd(std::string(field));
+ }
+ }
+
// Check that oat version when runtime was compiled matches the oat version
// when dex2oat was compiled. We have seen cases where they got out of sync.
constexpr std::array<uint8_t, 4> dex2oat_oat_version = OatHeader::kOatVersion;
@@ -1982,7 +2018,7 @@ size_t OatWriter::InitOatHeader(uint32_t num_dex_files,
oat_header_ = OatHeader::Create(GetCompilerOptions().GetInstructionSet(),
GetCompilerOptions().GetInstructionSetFeatures(),
num_dex_files,
- key_value_store,
+ key_value_store != nullptr ? &key_value_store->map_ : nullptr,
oat_data_offset_);
size_oat_header_ += sizeof(OatHeader);
size_oat_header_key_value_store_ += oat_header_->GetHeaderSize() - sizeof(OatHeader);
@@ -2751,10 +2787,7 @@ bool OatWriter::WriteHeader(OutputStream* out) {
// Update checksum with header data.
DCHECK_EQ(oat_header_->GetChecksum(), 0u); // For checksum calculation.
- const uint8_t* header_begin = reinterpret_cast<const uint8_t*>(oat_header_);
- const uint8_t* header_end = oat_header_->GetKeyValueStore() + oat_header_->GetKeyValueStoreSize();
- uint32_t old_checksum = oat_checksum_;
- oat_checksum_ = adler32(old_checksum, header_begin, header_end - header_begin);
+ oat_header_->ComputeChecksum(&oat_checksum_);
oat_header_->SetChecksum(oat_checksum_);
const size_t file_offset = oat_data_offset_;
diff --git a/dex2oat/linker/oat_writer.h b/dex2oat/linker/oat_writer.h
index 5a775fa517..4f50b1a39c 100644
--- a/dex2oat/linker/oat_writer.h
+++ b/dex2oat/linker/oat_writer.h
@@ -70,6 +70,29 @@ enum class CopyOption {
kOnlyIfCompressed
};
+class OatKeyValueStore {
+ public:
+ // Puts a key value pair whose key is in `OatHeader::kNonDeterministicFieldsAndLengths`.
+ bool PutNonDeterministic(const std::string& k,
+ const std::string& v,
+ bool allow_truncation = false);
+
+ // Puts a key value pair whose key is in `OatHeader::kDeterministicFields`.
+ void Put(const std::string& k, const std::string& v);
+
+ // Puts a key value pair whose key is in `OatHeader::kDeterministicFields`.
+ void Put(const std::string& k, bool v);
+
+ // Makes sure calls with `const char*` falls into the overload for `std::string`, not the one for
+ // `bool`.
+ void Put(const std::string& k, const char* v) { Put(k, std::string(v)); }
+
+ private:
+ SafeMap<std::string, std::string> map_;
+
+ friend class OatWriter;
+};
+
// OatHeader variable length with count of D OatDexFiles
//
// TypeLookupTable[0] one descriptor to class def index hash table for each OatDexFile.
@@ -167,7 +190,7 @@ class OatWriter {
// Start writing .rodata, including supporting data structures for dex files.
bool StartRoData(const std::vector<const DexFile*>& dex_files,
OutputStream* oat_rodata,
- SafeMap<std::string, std::string>* key_value_store);
+ OatKeyValueStore* key_value_store);
// Initialize the writer with the given parameters.
void Initialize(const CompilerDriver* compiler_driver,
const VerificationResults* verification_results,
@@ -291,7 +314,7 @@ class OatWriter {
void WriteVerifierDeps(verifier::VerifierDeps* verifier_deps,
/*out*/std::vector<uint8_t>* buffer);
- size_t InitOatHeader(uint32_t num_dex_files, SafeMap<std::string, std::string>* key_value_store);
+ size_t InitOatHeader(uint32_t num_dex_files, OatKeyValueStore* key_value_store);
size_t InitClassOffsets(size_t offset);
size_t InitOatClasses(size_t offset);
size_t InitOatMaps(size_t offset);
diff --git a/dex2oat/linker/oat_writer_test.cc b/dex2oat/linker/oat_writer_test.cc
index f3b598fe77..538c1abc9a 100644
--- a/dex2oat/linker/oat_writer_test.cc
+++ b/dex2oat/linker/oat_writer_test.cc
@@ -14,8 +14,11 @@
* limitations under the License.
*/
-#include "android-base/stringprintf.h"
+#include "oat_writer.h"
+#include <cstdint>
+
+#include "android-base/stringprintf.h"
#include "arch/instruction_set_features.h"
#include "art_method-inl.h"
#include "base/file_utils.h"
@@ -35,6 +38,7 @@
#include "driver/compiler_driver.h"
#include "driver/compiler_options.h"
#include "entrypoints/quick/quick_entrypoints.h"
+#include "gtest/gtest.h"
#include "linker/elf_writer.h"
#include "linker/elf_writer_quick.h"
#include "linker/multi_oat_relative_patcher.h"
@@ -104,7 +108,7 @@ class OatTest : public CommonCompilerDriverTest {
bool WriteElf(File* vdex_file,
File* oat_file,
const std::vector<const DexFile*>& dex_files,
- SafeMap<std::string, std::string>& key_value_store,
+ OatKeyValueStore& key_value_store,
bool verify) {
TimingLogger timings("WriteElf", false, false);
ClearBootImageOption();
@@ -124,7 +128,7 @@ class OatTest : public CommonCompilerDriverTest {
bool WriteElf(File* vdex_file,
File* oat_file,
const std::vector<const char*>& dex_filenames,
- SafeMap<std::string, std::string>& key_value_store,
+ OatKeyValueStore& key_value_store,
bool verify,
CopyOption copy,
ProfileCompilationInfo* profile_compilation_info) {
@@ -143,7 +147,7 @@ class OatTest : public CommonCompilerDriverTest {
File* oat_file,
File&& dex_file_fd,
const char* location,
- SafeMap<std::string, std::string>& key_value_store,
+ OatKeyValueStore& key_value_store,
bool verify,
CopyOption copy,
ProfileCompilationInfo* profile_compilation_info = nullptr) {
@@ -159,7 +163,7 @@ class OatTest : public CommonCompilerDriverTest {
bool DoWriteElf(File* vdex_file,
File* oat_file,
OatWriter& oat_writer,
- SafeMap<std::string, std::string>& key_value_store,
+ OatKeyValueStore& key_value_store,
bool verify,
CopyOption copy) {
std::unique_ptr<ElfWriter> elf_writer = CreateElfWriterQuick(
@@ -426,7 +430,7 @@ TEST_F(OatTest, WriteRead) {
}
ScratchFile tmp_base, tmp_oat(tmp_base, ".oat"), tmp_vdex(tmp_base, ".vdex");
- SafeMap<std::string, std::string> key_value_store;
+ OatKeyValueStore key_value_store;
key_value_store.Put(OatHeader::kBootClassPathChecksumsKey, "testkey");
bool success = WriteElf(tmp_vdex.GetFile(),
tmp_oat.GetFile(),
@@ -494,6 +498,67 @@ TEST_F(OatTest, WriteRead) {
}
}
+TEST_F(OatTest, ChecksumDeterminism) {
+ ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
+ SetupCompiler(/*compiler_options=*/{});
+
+ if (kCompile) {
+ TimingLogger timings("OatTest::ChecksumDeterminism", /*precise=*/false, /*verbose=*/false);
+ CompileAll(/*class_loader=*/nullptr, class_linker->GetBootClassPath(), &timings);
+ }
+
+ auto write_elf_and_get_checksum = [&](OatKeyValueStore& key_value_store,
+ /*out*/ uint32_t* checksum) {
+ ScratchFile tmp_base, tmp_oat(tmp_base, ".oat"), tmp_vdex(tmp_base, ".vdex");
+
+ bool success = WriteElf(tmp_vdex.GetFile(),
+ tmp_oat.GetFile(),
+ class_linker->GetBootClassPath(),
+ key_value_store,
+ /*verify=*/false);
+ ASSERT_TRUE(success);
+
+ std::string error_msg;
+ std::unique_ptr<OatFile> oat_file(OatFile::Open(/*zip_fd=*/-1,
+ tmp_oat.GetFilename(),
+ tmp_oat.GetFilename(),
+ /*executable=*/false,
+ /*low_4gb=*/true,
+ &error_msg));
+ ASSERT_TRUE(oat_file.get() != nullptr) << error_msg;
+ const OatHeader& oat_header = oat_file->GetOatHeader();
+ ASSERT_TRUE(oat_header.IsValid());
+ *checksum = oat_header.GetChecksum();
+ };
+
+ uint32_t checksum_1, checksum_2, checksum_3;
+
+ {
+ OatKeyValueStore key_value_store;
+ key_value_store.Put(OatHeader::kBootClassPathChecksumsKey, "testkey");
+ ASSERT_NO_FATAL_FAILURE(write_elf_and_get_checksum(key_value_store, &checksum_1));
+ }
+
+ {
+ // Put non-deterministic fields. This should not affect the checksum.
+ OatKeyValueStore key_value_store;
+ key_value_store.Put(OatHeader::kBootClassPathChecksumsKey, "testkey");
+ key_value_store.PutNonDeterministic(OatHeader::kDex2OatCmdLineKey, "cmdline");
+ key_value_store.PutNonDeterministic(OatHeader::kApexVersionsKey, "apex-versions");
+ ASSERT_NO_FATAL_FAILURE(write_elf_and_get_checksum(key_value_store, &checksum_2));
+ EXPECT_EQ(checksum_1, checksum_2);
+ }
+
+ {
+ // Put deterministic fields. This should affect the checksum.
+ OatKeyValueStore key_value_store;
+ key_value_store.Put(OatHeader::kBootClassPathChecksumsKey, "testkey");
+ key_value_store.Put(OatHeader::kClassPathKey, "classpath");
+ ASSERT_NO_FATAL_FAILURE(write_elf_and_get_checksum(key_value_store, &checksum_3));
+ EXPECT_NE(checksum_1, checksum_3);
+ }
+}
+
TEST_F(OatTest, OatHeaderSizeCheck) {
// If this test is failing and you have to update these constants,
// it is time to update OatHeader::kOatVersion
@@ -548,7 +613,7 @@ TEST_F(OatTest, EmptyTextSection) {
CompileAll(class_loader, dex_files, &timings);
ScratchFile tmp_base, tmp_oat(tmp_base, ".oat"), tmp_vdex(tmp_base, ".vdex");
- SafeMap<std::string, std::string> key_value_store;
+ OatKeyValueStore key_value_store;
bool success = WriteElf(tmp_vdex.GetFile(),
tmp_oat.GetFile(),
dex_files,
@@ -617,7 +682,7 @@ void OatTest::TestDexFileInput(bool verify, bool low_4gb, bool use_profile) {
input_dexfiles.push_back(std::move(dex_file2_data));
scratch_files.push_back(&dex_file2);
- SafeMap<std::string, std::string> key_value_store;
+ OatKeyValueStore key_value_store;
{
// Test using the AddDexFileSource() interface with the dex files.
ScratchFile tmp_base, tmp_oat(tmp_base, ".oat"), tmp_vdex(tmp_base, ".vdex");
@@ -746,7 +811,7 @@ void OatTest::TestZipFileInput(bool verify, CopyOption copy) {
success = zip_builder.Finish();
ASSERT_TRUE(success) << strerror(errno);
- SafeMap<std::string, std::string> key_value_store;
+ OatKeyValueStore key_value_store;
{
// Test using the AddDexFileSource() interface with the zip file.
std::vector<const char*> input_filenames = { zip_file.GetFilename().c_str() };
@@ -865,7 +930,7 @@ void OatTest::TestZipFileInputWithEmptyDex() {
success = zip_builder.Finish();
ASSERT_TRUE(success) << strerror(errno);
- SafeMap<std::string, std::string> key_value_store;
+ OatKeyValueStore key_value_store;
std::vector<const char*> input_filenames = { zip_file.GetFilename().c_str() };
ScratchFile oat_file, vdex_file(oat_file, ".vdex");
std::unique_ptr<ProfileCompilationInfo> profile_compilation_info(new ProfileCompilationInfo());
diff --git a/libartbase/base/memfd.cc b/libartbase/base/memfd.cc
index 3b9872b295..4530f8199b 100644
--- a/libartbase/base/memfd.cc
+++ b/libartbase/base/memfd.cc
@@ -64,29 +64,6 @@ int memfd_create([[maybe_unused]] const char* name, [[maybe_unused]] unsigned in
#endif // __NR_memfd_create
-// This is a wrapper that will attempt to simulate memfd_create if normal running fails.
-int memfd_create_compat(const char* name, unsigned int flags) {
- int res = memfd_create(name, flags);
- if (res >= 0) {
- return res;
- }
-#if !defined(_WIN32)
- // Try to create an anonymous file with tmpfile that we can use instead.
- if (flags == 0) {
- FILE* file = tmpfile();
- if (file != nullptr) {
- // We want the normal 'dup' semantics since memfd_create without any flags isn't CLOEXEC.
- // Unfortunately on some android targets we will compiler error if we use dup directly and so
- // need to use fcntl.
- int nfd = fcntl(fileno(file), F_DUPFD, /*lowest allowed fd*/ 0);
- fclose(file);
- return nfd;
- }
- }
-#endif
- return res;
-}
-
#if defined(__BIONIC__)
static bool IsSealFutureWriteSupportedInternal() {
diff --git a/libartbase/base/memfd.h b/libartbase/base/memfd.h
index 3c27dcb9e3..b288f7bc37 100644
--- a/libartbase/base/memfd.h
+++ b/libartbase/base/memfd.h
@@ -69,10 +69,6 @@ namespace art {
// check for safety on older kernels (b/116769556)..
int memfd_create(const char* name, unsigned int flags);
-// Call memfd(2) if available on platform and return result. Try to give us an unlinked FD in some
-// other way if memfd fails or isn't supported.
-int memfd_create_compat(const char* name, unsigned int flags);
-
// Return whether the kernel supports sealing future writes of a memfd.
bool IsSealFutureWriteSupported();
diff --git a/libartservice/service/java/com/android/server/art/ArtManagerLocal.java b/libartservice/service/java/com/android/server/art/ArtManagerLocal.java
index d8a508eb4e..735da25fbb 100644
--- a/libartservice/service/java/com/android/server/art/ArtManagerLocal.java
+++ b/libartservice/service/java/com/android/server/art/ArtManagerLocal.java
@@ -1002,19 +1002,8 @@ public final class ArtManagerLocal {
@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
public void dump(
@NonNull PrintWriter pw, @NonNull PackageManagerLocal.FilteredSnapshot snapshot) {
- dump(pw, snapshot, false /* verifySdmSignatures */);
- }
-
- /**
- * Same as above, but allows to specify options.
- *
- * @hide
- */
- @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
- public void dump(@NonNull PrintWriter pw,
- @NonNull PackageManagerLocal.FilteredSnapshot snapshot, boolean verifySdmSignatures) {
try (var pin = mInjector.createArtdPin()) {
- new DumpHelper(this, verifySdmSignatures).dump(pw, snapshot);
+ new DumpHelper(this).dump(pw, snapshot);
}
}
@@ -1030,21 +1019,9 @@ public final class ArtManagerLocal {
@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
public void dumpPackage(@NonNull PrintWriter pw,
@NonNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull String packageName) {
- dumpPackage(pw, snapshot, packageName, false /* verifySdmSignatures */);
- }
-
- /**
- * Same as above, but allows to specify options.
- *
- * @hide
- */
- @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
- public void dumpPackage(@NonNull PrintWriter pw,
- @NonNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull String packageName,
- boolean verifySdmSignatures) {
try (var pin = mInjector.createArtdPin()) {
- new DumpHelper(this, verifySdmSignatures)
- .dumpPackage(pw, snapshot, Utils.getPackageStateOrThrow(snapshot, packageName));
+ new DumpHelper(this).dumpPackage(
+ pw, snapshot, Utils.getPackageStateOrThrow(snapshot, packageName));
}
}
diff --git a/libartservice/service/java/com/android/server/art/ArtShellCommand.java b/libartservice/service/java/com/android/server/art/ArtShellCommand.java
index 0bbb6213d2..f83f5cf083 100644
--- a/libartservice/service/java/com/android/server/art/ArtShellCommand.java
+++ b/libartservice/service/java/com/android/server/art/ArtShellCommand.java
@@ -171,23 +171,11 @@ public final class ArtShellCommand extends BasicShellCommandHandler {
return 0;
}
case "dump": {
- boolean verifySdmSignatures = false;
-
- String opt;
- while ((opt = getNextOption()) != null) {
- switch (opt) {
- case "--verify-sdm-signatures":
- verifySdmSignatures = true;
- break;
- }
- }
-
String packageName = getNextArg();
if (packageName != null) {
- mInjector.getArtManagerLocal().dumpPackage(
- pw, snapshot, packageName, verifySdmSignatures);
+ mInjector.getArtManagerLocal().dumpPackage(pw, snapshot, packageName);
} else {
- mInjector.getArtManagerLocal().dump(pw, snapshot, verifySdmSignatures);
+ mInjector.getArtManagerLocal().dump(pw, snapshot);
}
return 0;
}
@@ -1084,13 +1072,10 @@ public final class ArtShellCommand extends BasicShellCommandHandler {
pw.println(" Cleanup obsolete files, such as dexopt artifacts that are outdated or");
pw.println(" correspond to dex container files that no longer exist.");
pw.println();
- pw.println(" dump [--verify-sdm-signatures] [PACKAGE_NAME]");
+ pw.println(" dump [PACKAGE_NAME]");
pw.println(" Dump the dexopt state in text format to stdout.");
pw.println(" If PACKAGE_NAME is empty, the command is for all packages. Otherwise, it");
pw.println(" is for the given package.");
- pw.println(" Options:");
- pw.println(" --verify-sdm-signatures Also verify SDM file signatures and include");
- pw.println(" their statuses.");
pw.println();
pw.println(" dexopt-packages -r REASON");
pw.println(" Run batch dexopt for the given reason.");
diff --git a/libartservice/service/java/com/android/server/art/DexUseManagerLocal.java b/libartservice/service/java/com/android/server/art/DexUseManagerLocal.java
index 704dabe034..9c7f45de24 100644
--- a/libartservice/service/java/com/android/server/art/DexUseManagerLocal.java
+++ b/libartservice/service/java/com/android/server/art/DexUseManagerLocal.java
@@ -67,6 +67,8 @@ import java.io.OutputStream;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.nio.file.Files;
+import java.nio.file.LinkOption;
+import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.Collections;
@@ -116,10 +118,8 @@ public class DexUseManagerLocal {
// Impose a limit on the input accepted by notifyDexContainersLoaded per owning package.
/** @hide */
@VisibleForTesting public static final int MAX_PATH_LENGTH = 4096;
-
/** @hide */
@VisibleForTesting public static final int MAX_CLASS_LOADER_CONTEXT_LENGTH = 10000;
-
/** @hide */
private static final int MAX_SECONDARY_DEX_FILES_PER_OWNER = 500;
@@ -669,14 +669,18 @@ public class DexUseManagerLocal {
@NonNull String classLoaderContext, @NonNull String abiName, long lastUsedAtMs) {
DexLoader loader = DexLoader.create(loadingPackageName, isolatedProcess);
// This is to avoid a loading package from using up the SecondaryDexUse entries for another
- // package (up to the MAX_SECONDARY_DEX_FILES_PER_OWNER limit). We don't care about the
- // loading package messing up its own SecondaryDexUse entries.
+ // package (up to the MAX_SECONDARY_DEX_FILES_PER_OWNER limit).
// Note that we are using system_server's permission to check the existence. This is fine
// with the assumption that the file must be world readable to be used by other apps.
// We could use artd's permission to check the existence, and then there wouldn't be any
// permission issue, but that requires bringing up the artd service, which may be too
// expensive.
// TODO(jiakaiz): Check if the assumption is true.
+ // This doesn't apply to secondary dex files that aren't used by other apps, but we
+ // don't care about the loading package messing up its own SecondaryDexUse
+ // entries.
+ // Also note that the check doesn't follow symlinks because GMSCore creates symlinks to
+ // its secondary dex files, while system_server doesn't have the permission to follow them.
if (isLoaderOtherApp(loader, owningPackageName) && !mInjector.pathExists(dexPath)) {
AsLog.w("Not recording non-existent secondary dex file '" + dexPath + "'");
return;
@@ -1399,7 +1403,7 @@ public class DexUseManagerLocal {
}
public boolean pathExists(String path) {
- return new File(path).exists();
+ return Files.exists(Paths.get(path), LinkOption.NOFOLLOW_LINKS);
}
@NonNull
diff --git a/libartservice/service/java/com/android/server/art/DumpHelper.java b/libartservice/service/java/com/android/server/art/DumpHelper.java
index 77cc37f4b3..9c3bb3f087 100644
--- a/libartservice/service/java/com/android/server/art/DumpHelper.java
+++ b/libartservice/service/java/com/android/server/art/DumpHelper.java
@@ -20,12 +20,7 @@ import static com.android.server.art.DexUseManagerLocal.CheckedSecondaryDexInfo;
import static com.android.server.art.DexUseManagerLocal.DexLoader;
import static com.android.server.art.model.DexoptStatus.DexContainerFileDexoptStatus;
-import android.annotation.FlaggedApi;
import android.annotation.NonNull;
-import android.annotation.SuppressLint;
-import android.content.pm.PackageManager;
-import android.content.pm.SigningInfo;
-import android.content.pm.SigningInfoException;
import android.os.Build;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
@@ -39,7 +34,6 @@ import com.android.server.pm.pkg.PackageState;
import dalvik.system.VMRuntime;
-import java.io.File;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Comparator;
@@ -60,16 +54,14 @@ import java.util.stream.Collectors;
@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
public class DumpHelper {
@NonNull private final Injector mInjector;
- private final boolean mVerifySdmSignatures;
- public DumpHelper(@NonNull ArtManagerLocal artManagerLocal, boolean verifySdmSignatures) {
- this(new Injector(artManagerLocal), verifySdmSignatures);
+ public DumpHelper(@NonNull ArtManagerLocal artManagerLocal) {
+ this(new Injector(artManagerLocal));
}
@VisibleForTesting
- public DumpHelper(@NonNull Injector injector, boolean verifySdmSignatures) {
+ public DumpHelper(@NonNull Injector injector) {
mInjector = injector;
- mVerifySdmSignatures = verifySdmSignatures;
}
/** Handles {@link ArtManagerLocal#dump(PrintWriter, PackageManagerLocal.FilteredSnapshot)}. */
@@ -205,9 +197,6 @@ public class DumpHelper {
fileStatus.isPrimaryAbi() ? " [primary-abi]" : "");
ipw.increaseIndent();
ipw.printf("[location is %s]\n", fileStatus.getLocationDebugString());
- if (fileStatus.isPrimaryDex()) {
- dumpSdmStatus(ipw, fileStatus.getDexContainerFile(), isa);
- }
ipw.decreaseIndent();
}
}
@@ -228,69 +217,6 @@ public class DumpHelper {
}
}
- private void dumpSdmStatus(
- @NonNull IndentingPrintWriter ipw, @NonNull String dexPath, @NonNull String isa) {
- if (!android.content.pm.Flags.cloudCompilationPm()) {
- return;
- }
-
- String sdmPath = getSdmPath(dexPath, isa);
- String status = "";
- String signature = "skipped";
- if (mInjector.fileExists(sdmPath)) {
- // "Pending" means yet to be picked up by dexopt. For now, "pending" is the only status
- // because SDM files are not supported yet.
- status = "pending";
- // This operation is expensive, so hide it behind a flag.
- if (mVerifySdmSignatures) {
- signature = getSdmSignatureStatus(dexPath, sdmPath);
- }
- }
- if (!status.isEmpty()) {
- ipw.printf("sdm: [sdm-status=%s] [sdm-signature=%s]\n", status, signature);
- }
- }
-
- // The new API usage is safe because it's guarded by a flag. The "NewApi" lint is wrong because
- // it's meaningless (b/380891026). We have to work around the lint error because there is no
- // `isAtLeastB` to check yet.
- // TODO(jiakaiz): Remove this workaround, change @FlaggedApi to @RequiresApi here, and check
- // `isAtLeastB` at the call site after B SDK is finalized.
- @FlaggedApi(android.content.pm.Flags.FLAG_CLOUD_COMPILATION_PM)
- @SuppressLint("NewApi")
- @NonNull
- private String getSdmSignatureStatus(@NonNull String dexPath, @NonNull String sdmPath) {
- SigningInfo sdmSigningInfo;
- try {
- sdmSigningInfo =
- mInjector.getVerifiedSigningInfo(sdmPath, SigningInfo.VERSION_SIGNING_BLOCK_V3);
- } catch (SigningInfoException e) {
- AsLog.w("Failed to verify SDM signature", e);
- return "invalid-sdm-signature";
- }
-
- SigningInfo apkSigningInfo;
- try {
- apkSigningInfo =
- mInjector.getVerifiedSigningInfo(dexPath, SigningInfo.VERSION_SIGNING_BLOCK_V3);
- } catch (SigningInfoException e) {
- AsLog.w("Failed to verify SDM signature", e);
- return "invalid-apk-signature";
- }
-
- if (!sdmSigningInfo.signersMatchExactly(apkSigningInfo)) {
- return "mismatched-signers";
- }
-
- return "verified";
- }
-
- @NonNull
- private static String getSdmPath(@NonNull String dexPath, @NonNull String isa) {
- return Utils.replaceFileExtension(
- dexPath, "." + isa + ArtConstants.SECURE_DEX_METADATA_FILE_EXT);
- }
-
@NonNull
private String getLoaderState(
@NonNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull DexLoader loader) {
@@ -326,18 +252,5 @@ public class DumpHelper {
public DexUseManagerLocal getDexUseManager() {
return GlobalInjector.getInstance().getDexUseManager();
}
-
- public boolean fileExists(@NonNull String path) {
- return new File(path).exists();
- }
-
- // TODO(jiakaiz): See another comment about "NewApi" above.
- @FlaggedApi(android.content.pm.Flags.FLAG_CLOUD_COMPILATION_PM)
- @SuppressLint("NewApi")
- @NonNull
- public SigningInfo getVerifiedSigningInfo(
- @NonNull String path, int minAppSigningSchemeVersion) throws SigningInfoException {
- return PackageManager.getVerifiedSigningInfo(path, minAppSigningSchemeVersion);
- }
}
}
diff --git a/libartservice/service/javatests/com/android/server/art/DumpHelperTest.java b/libartservice/service/javatests/com/android/server/art/DumpHelperTest.java
index 9dbecaf49c..55224b93f7 100644
--- a/libartservice/service/javatests/com/android/server/art/DumpHelperTest.java
+++ b/libartservice/service/javatests/com/android/server/art/DumpHelperTest.java
@@ -24,19 +24,12 @@ import static com.android.server.art.model.DexoptStatus.DexContainerFileDexoptSt
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.argThat;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
import android.annotation.NonNull;
-import android.annotation.SuppressLint;
-import android.content.pm.SigningInfo;
-import android.content.pm.SigningInfoException;
import android.os.SystemProperties;
import androidx.test.filters.SmallTest;
@@ -77,8 +70,8 @@ public class DumpHelperTest {
@Mock private ArtManagerLocal mArtManagerLocal;
@Mock private DexUseManagerLocal mDexUseManagerLocal;
@Mock private PackageManagerLocal.FilteredSnapshot mSnapshot;
- @Mock private SigningInfo mSigningInfoA;
- @Mock private SigningInfo mSigningInfoB;
+
+ private DumpHelper mDumpHelper;
@Before
public void setUp() throws Exception {
@@ -106,10 +99,7 @@ public class DumpHelperTest {
setUpForBar();
setUpForSdk();
- lenient().when(mSigningInfoA.signersMatchExactly(mSigningInfoA)).thenReturn(true);
- lenient().when(mSigningInfoA.signersMatchExactly(mSigningInfoB)).thenReturn(false);
- lenient().when(mSigningInfoB.signersMatchExactly(mSigningInfoB)).thenReturn(true);
- lenient().when(mSigningInfoB.signersMatchExactly(mSigningInfoA)).thenReturn(false);
+ mDumpHelper = new DumpHelper(mInjector);
}
@Test
@@ -157,122 +147,10 @@ public class DumpHelperTest {
+ "Current GC: CollectorTypeCMC\n";
var stringWriter = new StringWriter();
- createDumpHelper(false /* verifySdmSignatures */)
- .dump(new PrintWriter(stringWriter), mSnapshot);
+ mDumpHelper.dump(new PrintWriter(stringWriter), mSnapshot);
assertThat(stringWriter.toString()).isEqualTo(expected);
}
- @Test
- public void testDumpSdmStatusNotFound() throws Exception {
- when(mInjector.fileExists(any())).thenReturn(false);
-
- var stringWriter = new StringWriter();
- createDumpHelper(true /* verifySdmSignatures */)
- .dumpPackage(
- new PrintWriter(stringWriter), mSnapshot, getPackageState(PKG_NAME_BAR));
- assertThat(stringWriter.toString()).doesNotContain("sdm:");
- }
-
- @Test
- public void testDumpSdmStatusInvalidSdmSignature() throws Exception {
- doReturn(false).when(mInjector).fileExists("/somewhere/app/bar/base.arm.sdm");
- doReturn(true).when(mInjector).fileExists("/somewhere/app/bar/base.arm64.sdm");
- when(mInjector.getVerifiedSigningInfo(eq("/somewhere/app/bar/base.arm64.sdm"), anyInt()))
- .thenThrow(SigningInfoException.class);
-
- var stringWriter = new StringWriter();
- createDumpHelper(true /* verifySdmSignatures */)
- .dumpPackage(
- new PrintWriter(stringWriter), mSnapshot, getPackageState(PKG_NAME_BAR));
- assertThat(stringWriter.toString())
- .contains("sdm: [sdm-status=pending] [sdm-signature=invalid-sdm-signature]");
- }
-
- @Test
- public void testDumpSdmStatusInvalidApkSignature() throws Exception {
- doReturn(false).when(mInjector).fileExists("/somewhere/app/bar/base.arm.sdm");
- doReturn(true).when(mInjector).fileExists("/somewhere/app/bar/base.arm64.sdm");
- doReturn(mSigningInfoA)
- .when(mInjector)
- .getVerifiedSigningInfo(eq("/somewhere/app/bar/base.arm64.sdm"), anyInt());
- doThrow(SigningInfoException.class)
- .when(mInjector)
- .getVerifiedSigningInfo(eq("/somewhere/app/bar/base.apk"), anyInt());
-
- var stringWriter = new StringWriter();
- createDumpHelper(true /* verifySdmSignatures */)
- .dumpPackage(
- new PrintWriter(stringWriter), mSnapshot, getPackageState(PKG_NAME_BAR));
- assertThat(stringWriter.toString())
- .contains("sdm: [sdm-status=pending] [sdm-signature=invalid-apk-signature]");
- }
-
- @Test
- public void testDumpSdmStatusSignersNotMatch() throws Exception {
- doReturn(false).when(mInjector).fileExists("/somewhere/app/bar/base.arm.sdm");
- doReturn(true).when(mInjector).fileExists("/somewhere/app/bar/base.arm64.sdm");
- doReturn(mSigningInfoA)
- .when(mInjector)
- .getVerifiedSigningInfo(eq("/somewhere/app/bar/base.arm64.sdm"), anyInt());
- doReturn(mSigningInfoB)
- .when(mInjector)
- .getVerifiedSigningInfo(eq("/somewhere/app/bar/base.apk"), anyInt());
-
- var stringWriter = new StringWriter();
- createDumpHelper(true /* verifySdmSignatures */)
- .dumpPackage(
- new PrintWriter(stringWriter), mSnapshot, getPackageState(PKG_NAME_BAR));
- assertThat(stringWriter.toString())
- .contains("sdm: [sdm-status=pending] [sdm-signature=mismatched-signers]");
- }
-
- @Test
- public void testDumpSdmStatusVerified() throws Exception {
- doReturn(false).when(mInjector).fileExists("/somewhere/app/bar/base.arm.sdm");
- doReturn(true).when(mInjector).fileExists("/somewhere/app/bar/base.arm64.sdm");
- doReturn(mSigningInfoA)
- .when(mInjector)
- .getVerifiedSigningInfo(eq("/somewhere/app/bar/base.arm64.sdm"), anyInt());
- doReturn(mSigningInfoA)
- .when(mInjector)
- .getVerifiedSigningInfo(eq("/somewhere/app/bar/base.apk"), anyInt());
-
- var stringWriter = new StringWriter();
- createDumpHelper(true /* verifySdmSignatures */)
- .dumpPackage(
- new PrintWriter(stringWriter), mSnapshot, getPackageState(PKG_NAME_BAR));
- assertThat(stringWriter.toString())
- .containsMatch(" \\Qpath: /somewhere/app/bar/base.apk\\E\n"
- + " arm:.*\n"
- + " .*\n"
- + " arm64:.*\n"
- + " .*\n"
- + " \\Qsdm: [sdm-status=pending] "
- + "[sdm-signature=verified]\\E\n");
- }
-
- @Test
- public void testDumpSdmStatusMultiArch() throws Exception {
- doReturn(true).when(mInjector).fileExists("/somewhere/app/bar/base.arm.sdm");
- doReturn(true).when(mInjector).fileExists("/somewhere/app/bar/base.arm64.sdm");
- doReturn(mSigningInfoA).when(mInjector).getVerifiedSigningInfo(any(), anyInt());
-
- var stringWriter = new StringWriter();
- createDumpHelper(true /* verifySdmSignatures */)
- .dumpPackage(
- new PrintWriter(stringWriter), mSnapshot, getPackageState(PKG_NAME_BAR));
- assertThat(stringWriter.toString())
- .containsMatch(" \\Qpath: /somewhere/app/bar/base.apk\\E\n"
- + " arm:.*\n"
- + " .*\n"
- + " \\Qsdm: [sdm-status=pending] "
- + "[sdm-signature=verified]\\E\n"
- + " arm64:.*\n"
- + " .*\n"
- + " \\Qsdm: [sdm-status=pending] "
- + "[sdm-signature=verified]\\E\n");
- }
-
private PackageState createPackageState(@NonNull String packageName, int appId, boolean isApex,
boolean hasPackage, @NonNull String primaryAbi, @NonNull String secondaryAbi) {
var pkgState = mock(PackageState.class);
@@ -440,13 +318,4 @@ public class DumpHelperTest {
PKG_NAME_SDK, "/somewhere/app/sdk/base.apk"))
.thenReturn(Set.of());
}
-
- @SuppressLint("DirectInvocationOnMock")
- private PackageState getPackageState(String packageName) {
- return mSnapshot.getPackageState(packageName);
- }
-
- private DumpHelper createDumpHelper(boolean verifySdmSignatures) {
- return new DumpHelper(mInjector, verifySdmSignatures);
- }
}
diff --git a/oatdump/oatdump.cc b/oatdump/oatdump.cc
index c567a5e730..f7320765a7 100644
--- a/oatdump/oatdump.cc
+++ b/oatdump/oatdump.cc
@@ -490,12 +490,11 @@ class OatDumper {
// Print the key-value store.
{
os << "KEY VALUE STORE:\n";
- size_t index = 0;
+ uint32_t offset = 0;
const char* key;
const char* value;
- while (oat_header.GetStoreKeyValuePairByIndex(index, &key, &value)) {
+ while (oat_header.GetNextStoreKeyValuePair(&offset, &key, &value)) {
os << key << " = " << value << "\n";
- index++;
}
os << "\n";
}
diff --git a/odrefresh/odrefresh.cc b/odrefresh/odrefresh.cc
index fc782bc697..92912b7bfa 100644
--- a/odrefresh/odrefresh.cc
+++ b/odrefresh/odrefresh.cc
@@ -1762,7 +1762,6 @@ WARN_UNUSED CompilationResult OnDeviceRefresh::RunDex2oat(
}
}
- args.Add("--oat-location=%s", artifacts.OatPath());
std::pair<std::string, const char*> location_kind_pairs[] = {
std::make_pair(artifacts.ImagePath(), artifacts.ImageKind()),
std::make_pair(artifacts.OatPath(), "oat"),
@@ -1910,9 +1909,16 @@ OnDeviceRefresh::RunDex2oatForBootClasspath(const std::string& staging_dir,
preloaded_classes_file,
strerror(errno)));
}
+ args.Add("--oat-location=%s", OdrArtifacts::ForBootImage(output_path).OatPath());
} else {
// Mainline extension.
args.Add("--compiler-filter=%s", kMainlineCompilerFilter);
+ // For boot image extensions, dex2oat takes the oat location of the primary boot image and
+ // expends it with the name of the first input dex file.
+ args.Add("--oat-location=%s",
+ OdrArtifacts::ForBootImage(
+ GetPrimaryBootImagePath(/*on_system=*/false, /*minimal=*/false, isa))
+ .OatPath());
}
const OdrSystemProperties& system_properties = config_.GetSystemProperties();
@@ -2080,6 +2086,8 @@ WARN_UNUSED CompilationResult OnDeviceRefresh::RunDex2oatForSystemServer(
args.Add("--class-loader-context-fds=%s", Join(fds, ':'));
}
+ args.Add("--oat-location=%s", OdrArtifacts::ForSystemServer(output_path).OatPath());
+
const OdrSystemProperties& system_properties = config_.GetSystemProperties();
args.AddRuntimeIfNonEmpty("-Xms%s", system_properties.GetOrEmpty("dalvik.vm.dex2oat-Xms"))
.AddRuntimeIfNonEmpty("-Xmx%s", system_properties.GetOrEmpty("dalvik.vm.dex2oat-Xmx"));
diff --git a/odrefresh/odrefresh_test.cc b/odrefresh/odrefresh_test.cc
index b19a4225b4..7f4e990ffe 100644
--- a/odrefresh/odrefresh_test.cc
+++ b/odrefresh/odrefresh_test.cc
@@ -48,12 +48,14 @@
namespace art {
namespace odrefresh {
+using ::android::base::Basename;
using ::android::base::Split;
using ::android::modules::sdklevel::IsAtLeastU;
using ::testing::_;
using ::testing::AllOf;
using ::testing::Contains;
using ::testing::ElementsAre;
+using ::testing::EndsWith;
using ::testing::Not;
using ::testing::ResultOf;
using ::testing::Return;
@@ -336,7 +338,7 @@ TEST_F(OdRefreshTest, BootImageMainlineExtension) {
FdOf(framework_jar_),
FdOf(conscrypt_jar_),
FdOf(framework_wifi_jar_)))),
- Contains(Flag("--oat-location=", dalvik_cache_dir_ + "/x86_64/boot-conscrypt.oat")),
+ Contains(Flag("--oat-location=", dalvik_cache_dir_ + "/x86_64/boot.oat")),
Not(Contains(Flag("--base=", _))),
Contains(Flag("--boot-image=", _)),
Contains(Flag("--cache-info-fd=", FdOf(cache_info_xml_))))))
@@ -435,11 +437,15 @@ TEST_F(OdRefreshTest, BootClasspathJarsFallback) {
}
TEST_F(OdRefreshTest, AllSystemServerJars) {
- EXPECT_CALL(*mock_exec_utils_,
- DoExecAndReturnCode(AllOf(Contains(Flag("--dex-file=", location_provider_jar_)),
- Contains("--class-loader-context=PCL[]"),
- Not(Contains(Flag("--class-loader-context-fds=", _))),
- Contains(Flag("--cache-info-fd=", FdOf(cache_info_xml_))))))
+ EXPECT_CALL(
+ *mock_exec_utils_,
+ DoExecAndReturnCode(AllOf(
+ Contains(Flag("--dex-file=", location_provider_jar_)),
+ Contains("--class-loader-context=PCL[]"),
+ Not(Contains(Flag("--class-loader-context-fds=", _))),
+ Contains(Flag("--cache-info-fd=", FdOf(cache_info_xml_))),
+ Contains(Flag("--oat-location=",
+ EndsWith("@" + Basename(location_provider_jar_) + "@classes.odex"))))))
.WillOnce(Return(0));
EXPECT_CALL(
*mock_exec_utils_,
@@ -447,7 +453,9 @@ TEST_F(OdRefreshTest, AllSystemServerJars) {
Contains(Flag("--dex-file=", services_jar_)),
Contains(Flag("--class-loader-context=", ART_FORMAT("PCL[{}]", location_provider_jar_))),
Contains(Flag("--class-loader-context-fds=", FdOf(location_provider_jar_))),
- Contains(Flag("--cache-info-fd=", FdOf(cache_info_xml_))))))
+ Contains(Flag("--cache-info-fd=", FdOf(cache_info_xml_))),
+ Contains(
+ Flag("--oat-location=", EndsWith("@" + Basename(services_jar_) + "@classes.odex"))))))
.WillOnce(Return(0));
EXPECT_CALL(
*mock_exec_utils_,
@@ -457,7 +465,9 @@ TEST_F(OdRefreshTest, AllSystemServerJars) {
ART_FORMAT("PCL[];PCL[{}:{}]", location_provider_jar_, services_jar_))),
Contains(ListFlag("--class-loader-context-fds=",
ElementsAre(FdOf(location_provider_jar_), FdOf(services_jar_)))),
- Contains(Flag("--cache-info-fd=", FdOf(cache_info_xml_))))))
+ Contains(Flag("--cache-info-fd=", FdOf(cache_info_xml_))),
+ Contains(Flag("--oat-location=",
+ EndsWith("@" + Basename(services_foo_jar_) + "@classes.odex"))))))
.WillOnce(Return(0));
EXPECT_CALL(
*mock_exec_utils_,
@@ -467,7 +477,9 @@ TEST_F(OdRefreshTest, AllSystemServerJars) {
ART_FORMAT("PCL[];PCL[{}:{}]", location_provider_jar_, services_jar_))),
Contains(ListFlag("--class-loader-context-fds=",
ElementsAre(FdOf(location_provider_jar_), FdOf(services_jar_)))),
- Contains(Flag("--cache-info-fd=", FdOf(cache_info_xml_))))))
+ Contains(Flag("--cache-info-fd=", FdOf(cache_info_xml_))),
+ Contains(Flag("--oat-location=",
+ EndsWith("@" + Basename(services_bar_jar_) + "@classes.odex"))))))
.WillOnce(Return(0));
EXPECT_EQ(
diff --git a/openjdkjvmti/ti_search.cc b/openjdkjvmti/ti_search.cc
index 30a889aaa5..e441010939 100644
--- a/openjdkjvmti/ti_search.cc
+++ b/openjdkjvmti/ti_search.cc
@@ -278,7 +278,7 @@ jvmtiError SearchUtil::AddToDexClassLoaderInMemory(jvmtiEnv* jvmti_env,
// lot of code as well.
// Create a memfd
- art::File file(art::memfd_create_compat("JVMTI InMemory Added dex file", 0), /*check-usage*/true);
+ art::File file(art::memfd_create("JVMTI InMemory Added dex file", 0), /*check-usage*/true);
if (file.Fd() < 0) {
char* reason = strerror(errno);
JVMTI_LOG(ERROR, jvmti_env) << "Unable to create memfd due to " << reason;
diff --git a/runtime/gc/collector/mark_compact.cc b/runtime/gc/collector/mark_compact.cc
index 57ec48a3de..95c33d0540 100644
--- a/runtime/gc/collector/mark_compact.cc
+++ b/runtime/gc/collector/mark_compact.cc
@@ -1247,12 +1247,14 @@ bool MarkCompact::PrepareForCompaction() {
[&first_obj](mirror::Object* obj) { first_obj = obj; });
}
if (first_obj != nullptr) {
+ mirror::Object* compacted_obj;
if (reinterpret_cast<uint8_t*>(first_obj) >= old_gen_end_) {
// post-compact address of the first live object in young-gen.
- first_obj = PostCompactOldObjAddr(first_obj);
- DCHECK_LT(reinterpret_cast<uint8_t*>(first_obj), post_compact_end_);
+ compacted_obj = PostCompactOldObjAddr(first_obj);
+ DCHECK_LT(reinterpret_cast<uint8_t*>(compacted_obj), post_compact_end_);
} else {
DCHECK(!young_gen_);
+ compacted_obj = first_obj;
}
// It's important to page-align mid-gen boundary. However, that means
// there could be an object overlapping that boundary. We will deal with
@@ -1260,7 +1262,32 @@ bool MarkCompact::PrepareForCompaction() {
// to ensure that we don't de-promote an object from old-gen back to
// young-gen. Otherwise, we may skip dirtying card for such an object if
// it contains native-roots to young-gen.
- mid_gen_end_ = AlignUp(reinterpret_cast<uint8_t*>(first_obj), gPageSize);
+ mid_gen_end_ = AlignUp(reinterpret_cast<uint8_t*>(compacted_obj), gPageSize);
+ // We need to ensure that for any object in old-gen, its class is also in
+ // there (for the same reason as mentioned above in the black-dense case).
+ // So adjust mid_gen_end_ accordingly, in the worst case all the way up
+ // to post_compact_end_.
+ auto iter = class_after_obj_map_.lower_bound(ObjReference::FromMirrorPtr(first_obj));
+ for (; iter != class_after_obj_map_.end(); iter++) {
+ // 'mid_gen_end_' is now post-compact, so need to compare with
+ // post-compact addresses.
+ compacted_obj =
+ PostCompactAddress(iter->second.AsMirrorPtr(), old_gen_end_, moving_space_end_);
+ // We cannot update the map with post-compact addresses yet as compaction-phase
+ // expects pre-compacted addresses. So we will update in FinishPhase().
+ if (reinterpret_cast<uint8_t*>(compacted_obj) < mid_gen_end_) {
+ mirror::Object* klass = iter->first.AsMirrorPtr();
+ DCHECK_LT(reinterpret_cast<uint8_t*>(klass), black_allocations_begin_);
+ klass = PostCompactAddress(klass, old_gen_end_, moving_space_end_);
+ // We only need to make sure that the class object doesn't move during
+ // compaction, which can be ensured by just making its first word be
+ // consumed in to the old-gen.
+ mid_gen_end_ =
+ std::max(mid_gen_end_, reinterpret_cast<uint8_t*>(klass) + kObjectAlignment);
+ mid_gen_end_ = AlignUp(mid_gen_end_, gPageSize);
+ }
+ }
+ CHECK_LE(mid_gen_end_, post_compact_end_);
} else {
// Young-gen is empty.
mid_gen_end_ = post_compact_end_;
diff --git a/runtime/gc/space/image_space.cc b/runtime/gc/space/image_space.cc
index 3d55c04828..97512bc79c 100644
--- a/runtime/gc/space/image_space.cc
+++ b/runtime/gc/space/image_space.cc
@@ -1967,9 +1967,9 @@ bool ImageSpace::BootImageLayout::CompileBootclasspathElements(
std::string art_filename = ExpandLocation(base_filename, bcp_index);
std::string vdex_filename = ImageHeader::GetVdexLocationFromImageLocation(art_filename);
std::string oat_filename = ImageHeader::GetOatLocationFromImageLocation(art_filename);
- android::base::unique_fd art_fd(memfd_create_compat(art_filename.c_str(), /*flags=*/ 0));
- android::base::unique_fd vdex_fd(memfd_create_compat(vdex_filename.c_str(), /*flags=*/ 0));
- android::base::unique_fd oat_fd(memfd_create_compat(oat_filename.c_str(), /*flags=*/ 0));
+ android::base::unique_fd art_fd(memfd_create(art_filename.c_str(), /*flags=*/ 0));
+ android::base::unique_fd vdex_fd(memfd_create(vdex_filename.c_str(), /*flags=*/ 0));
+ android::base::unique_fd oat_fd(memfd_create(oat_filename.c_str(), /*flags=*/ 0));
if (art_fd.get() == -1 || vdex_fd.get() == -1 || oat_fd.get() == -1) {
*error_msg = StringPrintf("Failed to create memfd handles for compiling bootclasspath for %s",
boot_class_path_locations_[bcp_index].c_str());
diff --git a/runtime/oat/oat.cc b/runtime/oat/oat.cc
index 9a5dd9dfdf..840825cea8 100644
--- a/runtime/oat/oat.cc
+++ b/runtime/oat/oat.cc
@@ -17,9 +17,10 @@
#include "oat.h"
#include <string.h>
+#include <zlib.h>
+#include "android-base/logging.h"
#include "android-base/stringprintf.h"
-
#include "arch/instruction_set.h"
#include "arch/instruction_set_features.h"
#include "base/bit_utils.h"
@@ -37,6 +38,13 @@ static size_t ComputeOatHeaderSize(const SafeMap<std::string, std::string>* vari
for ( ; it != end; ++it) {
estimate += it->first.length() + 1;
estimate += it->second.length() + 1;
+
+ size_t non_deterministic_field_length = OatHeader::GetNonDeterministicFieldLength(it->first);
+ if (non_deterministic_field_length > 0u) {
+ DCHECK_LE(it->second.length(), non_deterministic_field_length);
+ size_t padding = non_deterministic_field_length - it->second.length();
+ estimate += padding;
+ }
}
}
return sizeof(OatHeader) + estimate;
@@ -367,67 +375,79 @@ const uint8_t* OatHeader::GetKeyValueStore() const {
const char* OatHeader::GetStoreValueByKey(const char* key) const {
std::string_view key_view(key);
- const char* ptr = reinterpret_cast<const char*>(&key_value_store_);
- const char* end = ptr + key_value_store_size_;
-
- while (ptr < end) {
- // Scan for a closing zero.
- const char* str_end = reinterpret_cast<const char*>(memchr(ptr, 0, end - ptr));
- if (UNLIKELY(str_end == nullptr)) {
- LOG(WARNING) << "OatHeader: Unterminated key in key value store.";
- return nullptr;
- }
- const char* value_start = str_end + 1;
- const char* value_end =
- reinterpret_cast<const char*>(memchr(value_start, 0, end - value_start));
- if (UNLIKELY(value_end == nullptr)) {
- LOG(WARNING) << "OatHeader: Unterminated value in key value store.";
- return nullptr;
- }
- if (key_view == std::string_view(ptr, str_end - ptr)) {
+
+ uint32_t offset = 0;
+ const char* current_key;
+ const char* value;
+ while (GetNextStoreKeyValuePair(&offset, &current_key, &value)) {
+ if (key_view == current_key) {
// Same as key.
- return value_start;
+ return value;
}
- // Different from key. Advance over the value.
- ptr = value_end + 1;
}
+
// Not found.
return nullptr;
}
-bool OatHeader::GetStoreKeyValuePairByIndex(size_t index,
- const char** key,
- const char** value) const {
- const char* ptr = reinterpret_cast<const char*>(&key_value_store_);
- const char* end = ptr + key_value_store_size_;
- size_t counter = index;
+bool OatHeader::GetNextStoreKeyValuePair(/*inout*/ uint32_t* offset,
+ /*out*/ const char** key,
+ /*out*/ const char** value) const {
+ if (*offset >= key_value_store_size_) {
+ return false;
+ }
- while (ptr < end) {
- // Scan for a closing zero.
- const char* str_end = reinterpret_cast<const char*>(memchr(ptr, 0, end - ptr));
- if (UNLIKELY(str_end == nullptr)) {
- LOG(WARNING) << "OatHeader: Unterminated key in key value store.";
- return false;
- }
- const char* value_start = str_end + 1;
- const char* value_end =
- reinterpret_cast<const char*>(memchr(value_start, 0, end - value_start));
- if (UNLIKELY(value_end == nullptr)) {
- LOG(WARNING) << "OatHeader: Unterminated value in key value store.";
+ const char* start = reinterpret_cast<const char*>(&key_value_store_);
+ const char* ptr = start + *offset;
+ const char* end = start + key_value_store_size_;
+
+ // Scan for a closing zero.
+ const char* str_end = reinterpret_cast<const char*>(memchr(ptr, 0, end - ptr));
+ if (UNLIKELY(str_end == nullptr)) {
+ LOG(WARNING) << "OatHeader: Unterminated key in key value store.";
+ return false;
+ }
+ const char* value_start = str_end + 1;
+ const char* value_end = reinterpret_cast<const char*>(memchr(value_start, 0, end - value_start));
+ if (UNLIKELY(value_end == nullptr)) {
+ LOG(WARNING) << "OatHeader: Unterminated value in key value store.";
+ return false;
+ }
+
+ *key = ptr;
+ *value = value_start;
+
+ // Advance over the value.
+ size_t key_len = str_end - ptr;
+ size_t value_len = value_end - value_start;
+ size_t non_deterministic_field_length = GetNonDeterministicFieldLength(*key);
+ if (non_deterministic_field_length > 0u) {
+ if (UNLIKELY(value_len > non_deterministic_field_length)) {
+ LOG(WARNING) << "OatHeader: Non-deterministic field too long in key value store.";
return false;
}
- if (counter == 0) {
- *key = ptr;
- *value = value_start;
- return true;
- } else {
- --counter;
+ *offset += key_len + 1 + non_deterministic_field_length + 1;
+ } else {
+ *offset += key_len + 1 + value_len + 1;
+ }
+
+ return true;
+}
+
+void OatHeader::ComputeChecksum(/*inout*/ uint32_t* checksum) const {
+ *checksum = adler32(*checksum, reinterpret_cast<const uint8_t*>(this), sizeof(OatHeader));
+
+ uint32_t last_offset = 0;
+ uint32_t offset = 0;
+ const char* key;
+ const char* value;
+ while (GetNextStoreKeyValuePair(&offset, &key, &value)) {
+ if (IsDeterministicField(key)) {
+ // Update the checksum.
+ *checksum = adler32(*checksum, GetKeyValueStore() + last_offset, offset - last_offset);
}
- // Advance over the value.
- ptr = value_end + 1;
+ last_offset = offset;
}
- // Not found.
- return false;
}
size_t OatHeader::GetHeaderSize() const {
@@ -478,6 +498,14 @@ void OatHeader::Flatten(const SafeMap<std::string, std::string>* key_value_store
data_ptr += it->first.length() + 1;
strlcpy(data_ptr, it->second.c_str(), it->second.length() + 1);
data_ptr += it->second.length() + 1;
+
+ size_t non_deterministic_field_length = GetNonDeterministicFieldLength(it->first);
+ if (non_deterministic_field_length > 0u) {
+ DCHECK_LE(it->second.length(), non_deterministic_field_length);
+ size_t padding = non_deterministic_field_length - it->second.length();
+ memset(data_ptr, 0, padding);
+ data_ptr += padding;
+ }
}
}
key_value_store_size_ = data_ptr - reinterpret_cast<char*>(&key_value_store_);
diff --git a/runtime/oat/oat.h b/runtime/oat/oat.h
index 53f9497d1f..0061c90da9 100644
--- a/runtime/oat/oat.h
+++ b/runtime/oat/oat.h
@@ -18,6 +18,9 @@
#define ART_RUNTIME_OAT_OAT_H_
#include <array>
+#include <cstddef>
+#include <string_view>
+#include <utility>
#include <vector>
#include "base/compiler_filter.h"
@@ -44,8 +47,8 @@ std::ostream& operator<<(std::ostream& stream, StubType stub_type);
class EXPORT PACKED(4) OatHeader {
public:
static constexpr std::array<uint8_t, 4> kOatMagic { { 'o', 'a', 't', '\n' } };
- // Last oat version changed reason: Restore to 16 KB ELF alignment.
- static constexpr std::array<uint8_t, 4> kOatVersion{{'2', '5', '8', '\0'}};
+ // Last oat version changed reason: Ensure oat checksum determinism across hosts and devices.
+ static constexpr std::array<uint8_t, 4> kOatVersion{{'2', '5', '9', '\0'}};
static constexpr const char* kDex2OatCmdLineKey = "dex2oat-cmdline";
static constexpr const char* kDebuggableKey = "debuggable";
@@ -59,6 +62,34 @@ class EXPORT PACKED(4) OatHeader {
static constexpr const char* kCompilationReasonKey = "compilation-reason";
static constexpr const char* kRequiresImage = "requires-image";
+ // Fields listed here are key value store fields that are deterministic across hosts and devices,
+ // meaning they should have exactly the same value when the oat file is generated on different
+ // hosts and devices for the same app / boot image and for the same device model with the same
+ // compiler options. If you are adding a new field that doesn't hold this property, put it in
+ // `kNonDeterministicFieldsAndLengths` and assign a length limit.
+ //
+ // When writing the oat header, the non-deterministic fields are padded to their length limits and
+ // excluded from the oat checksum computation. This makes the oat checksum deterministic across
+ // hosts and devices, which is important for Cloud Compilation, where we generate an oat file on a
+ // host and use it on a device.
+ static constexpr std::array<std::string_view, 9> kDeterministicFields{
+ kDebuggableKey,
+ kNativeDebuggableKey,
+ kCompilerFilter,
+ kClassPathKey,
+ kBootClassPathKey,
+ kBootClassPathChecksumsKey,
+ kConcurrentCopying,
+ kCompilationReasonKey,
+ kRequiresImage,
+ };
+
+ static constexpr std::array<std::pair<std::string_view, size_t>, 2>
+ kNonDeterministicFieldsAndLengths{
+ std::make_pair(kDex2OatCmdLineKey, 2048),
+ std::make_pair(kApexVersionsKey, 1024),
+ };
+
static constexpr const char kTrueValue[] = "true";
static constexpr const char kFalseValue[] = "false";
@@ -70,6 +101,24 @@ class EXPORT PACKED(4) OatHeader {
uint32_t base_oat_offset = 0u);
static void Delete(OatHeader* header);
+ static constexpr bool IsDeterministicField(std::string_view key) {
+ for (std::string_view field : kDeterministicFields) {
+ if (field == key) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ static constexpr size_t GetNonDeterministicFieldLength(std::string_view key) {
+ for (auto [field, length] : kNonDeterministicFieldsAndLengths) {
+ if (field == key) {
+ return length;
+ }
+ }
+ return 0;
+ }
+
bool IsValid() const;
std::string GetValidationErrorMessage() const;
static void CheckOatVersion(std::array<uint8_t, 4> version);
@@ -116,7 +165,13 @@ class EXPORT PACKED(4) OatHeader {
uint32_t GetKeyValueStoreSize() const;
const uint8_t* GetKeyValueStore() const;
const char* GetStoreValueByKey(const char* key) const;
- bool GetStoreKeyValuePairByIndex(size_t index, const char** key, const char** value) const;
+
+ // Returns the next key-value pair, at the given offset. On success, updates `offset`.
+ // The expected use case is to start the iteration with an offset initialized to zero and
+ // repeatedly call this function with the same offset pointer, until the function returns false.
+ bool GetNextStoreKeyValuePair(/*inout*/ uint32_t* offset,
+ /*out*/ const char** key,
+ /*out*/ const char** value) const;
size_t GetHeaderSize() const;
bool IsDebuggable() const;
@@ -127,6 +182,8 @@ class EXPORT PACKED(4) OatHeader {
const uint8_t* GetOatAddress(StubType type) const;
+ void ComputeChecksum(/*inout*/ uint32_t* checksum) const;
+
private:
bool KeyHasValue(const char* key, const char* value, size_t value_size) const;
diff --git a/test/1963-add-to-dex-classloader-in-memory/check_memfd_create.cc b/test/1963-add-to-dex-classloader-in-memory/check_memfd_create.cc
deleted file mode 100644
index 70a64d71ee..0000000000
--- a/test/1963-add-to-dex-classloader-in-memory/check_memfd_create.cc
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2019 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 <string>
-#include <iostream>
-#include <sstream>
-
-#include "jvmti.h"
-
-#include "base/logging.h"
-#include "base/globals.h"
-#include "base/memfd.h"
-
-#ifdef __linux__
-#include <sys/utsname.h>
-#endif
-
-namespace art {
-namespace Test1963AddToDexClassLoaderInMemory {
-
-extern "C" JNIEXPORT jboolean JNICALL Java_Main_hasWorkingMemfdCreate(JNIEnv*, jclass) {
- // We should always have a working version if we're on normal buildbots.
- if (!art::kIsTargetBuild) {
- return true;
- }
-#ifdef __linux__
- struct utsname name;
- if (uname(&name) >= 0) {
- std::istringstream version(name.release);
- std::string major_str;
- std::string minor_str;
- std::getline(version, major_str, '.');
- std::getline(version, minor_str, '.');
- int major = std::stoi(major_str);
- int minor = std::stoi(minor_str);
- if (major >= 4 || (major == 3 && minor >= 17)) {
- // memfd_create syscall was added in 3.17
- return true;
- }
- }
-#endif
- int res = memfd_create_compat("TEST THAT MEMFD CREATE WORKS", 0);
- if (res < 0) {
- PLOG(ERROR) << "Unable to call memfd_create_compat successfully!";
- return false;
- } else {
- close(res);
- return true;
- }
-}
-
-} // namespace Test1963AddToDexClassLoaderInMemory
-} // namespace art
diff --git a/test/1963-add-to-dex-classloader-in-memory/src/Main.java b/test/1963-add-to-dex-classloader-in-memory/src/Main.java
index 1825e4faab..9c4cf57739 100644
--- a/test/1963-add-to-dex-classloader-in-memory/src/Main.java
+++ b/test/1963-add-to-dex-classloader-in-memory/src/Main.java
@@ -17,16 +17,10 @@
public class Main {
public static void main(String[] args) throws Exception {
try {
- if (!hasWorkingMemfdCreate()) {
- System.out.println("---NO memfd_create---");
- }
art.Test1963.run();
} catch (Throwable t) {
System.out.println(t);
t.printStackTrace(System.out);
- return;
}
}
-
- public static native boolean hasWorkingMemfdCreate();
}
diff --git a/test/Android.bp b/test/Android.bp
index 63840648ca..d3084fe7d2 100644
--- a/test/Android.bp
+++ b/test/Android.bp
@@ -725,7 +725,6 @@ art_cc_defaults {
"1959-redefine-object-instrument/fake_redef_object.cc",
"1960-obsolete-jit-multithread-native/native_say_hi.cc",
"1964-add-to-dex-classloader-file/add_to_loader.cc",
- "1963-add-to-dex-classloader-in-memory/check_memfd_create.cc",
"2012-structural-redefinition-failures-jni-id/set-jni-id-used.cc",
"2031-zygote-compiled-frame-deopt/native-wait.cc",
"2038-hiddenapi-jvmti-ext/hiddenapi_ext.cc",
diff --git a/tools/trace_parser/Android.bp b/tools/trace_parser/Android.bp
new file mode 100644
index 0000000000..f28af64c7f
--- /dev/null
+++ b/tools/trace_parser/Android.bp
@@ -0,0 +1,41 @@
+//
+// Copyright (C) 2025 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.
+//
+
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "art_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["art_license"],
+}
+
+art_cc_binary {
+ name: "long_running_method_trace_parser",
+ defaults: ["art_debug_defaults"],
+ host_supported: true,
+ device_supported: false,
+ srcs: [
+ "long_running_method_trace_parser.cc",
+ ],
+ static_libs: [
+ "libbase",
+ "libartbase",
+ "liblog",
+ "libz",
+ "libziparchive",
+ ],
+}
diff --git a/tools/trace_parser/long_running_method_trace_parser.cc b/tools/trace_parser/long_running_method_trace_parser.cc
new file mode 100644
index 0000000000..9645a445f8
--- /dev/null
+++ b/tools/trace_parser/long_running_method_trace_parser.cc
@@ -0,0 +1,252 @@
+/*
+ * Copyright (C) 2025 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 <stdio.h>
+
+#include <map>
+#include <memory>
+
+#include "base/leb128.h"
+#include "base/os.h"
+#include "base/unix_file/fd_file.h"
+
+namespace art {
+
+// These constants are defined in the ART sources in the following files:
+//
+// - art/runtime/trace.h
+// - art/runtime/trace_profile.cc
+static const int kThreadInfoHeaderV2 = 0;
+static const int kMethodInfoHeaderV2 = 1;
+static const int kEntryHeaderV2 = 2;
+static const int kMethodEntry = 0;
+static const int kMethodExit = 1;
+static const int kAlwaysOnMethodInfoHeaderSize = 11;
+static const int kAlwaysOnTraceHeaderSize = 12;
+
+uint64_t ReadNumber(int num_bytes, uint8_t* header) {
+ uint64_t number = 0;
+ for (int i = 0; i < num_bytes; i++) {
+ uint64_t c = header[i];
+ number += c << (i * 8);
+ }
+ return number;
+}
+
+bool ProcessMethodInfo(std::unique_ptr<File>& file, std::map<uint64_t, std::string>& name_map) {
+ // The first byte that specified the type of the packet is already read in
+ // ParseLongRunningMethodTrace.
+ uint8_t header[kAlwaysOnMethodInfoHeaderSize - 1];
+ if (!file->ReadFully(&header, sizeof(header))) {
+ printf("Couldn't read header\n");
+ return false;
+ }
+ uint64_t id = ReadNumber(8, header);
+ int length = ReadNumber(2, header + 8);
+
+ std::unique_ptr<char[]> name(new char[length]);
+ if (!file->ReadFully(name.get(), length)) {
+ return false;
+ }
+ std::string str(name.get(), length);
+ std::replace(str.begin(), str.end(), '\t', ' ');
+ if (str[str.length() - 1] == '\n') {
+ str.erase(str.length() - 1);
+ }
+ name_map.emplace(id, str);
+ return true;
+}
+
+void PrintTraceEntry(const std::string& method_name,
+ int event_type,
+ int* current_depth,
+ size_t timestamp) {
+ std::string entry;
+ for (int i = 0; i < *current_depth; i++) {
+ entry.push_back('.');
+ }
+ if (event_type == kMethodEntry) {
+ *current_depth += 1;
+ entry.append(".>> ");
+ } else if (event_type == kMethodExit) {
+ *current_depth -= 1;
+ entry.append("<< ");
+ } else {
+ entry.append("?? ");
+ }
+ entry.append(" ");
+ entry.append(method_name);
+ entry.append(" ");
+ entry.append(std::to_string(timestamp));
+ entry.append("\n");
+ printf("%s", entry.c_str());
+}
+
+bool SkipTraceEntries(std::unique_ptr<File>& file) {
+ // The first byte that specified the type of the packet is already read in
+ // ParseLongRunningMethodTrace.
+ uint8_t header[kAlwaysOnTraceHeaderSize - 1];
+ if (!file->ReadFully(header, sizeof(header))) {
+ return false;
+ }
+
+ // Read thread id
+ ReadNumber(4, header);
+ int offset = 4;
+ // Read number of records
+ ReadNumber(3, header + offset);
+ offset += 3;
+ int total_size = ReadNumber(4, header + offset);
+ std::unique_ptr<uint8_t[]> buffer(new uint8_t[total_size]);
+ if (!file->ReadFully(buffer.get(), total_size)) {
+ return false;
+ }
+ return true;
+}
+
+bool ProcessLongRunningMethodTraceEntries(std::unique_ptr<File>& file,
+ std::map<int64_t, int>& current_depth_map,
+ std::map<uint64_t, std::string>& method_map) {
+ // The first byte that specified the type of the packet is already read in
+ // ParseLongRunningMethodTrace.
+ uint8_t header[kAlwaysOnTraceHeaderSize - 1];
+ if (!file->ReadFully(header, sizeof(header))) {
+ return false;
+ }
+
+ uint32_t thread_id = ReadNumber(4, header);
+ int offset = 4;
+ int num_records = ReadNumber(3, header + offset);
+ offset += 3;
+ int total_size = ReadNumber(4, header + offset);
+ if (total_size == 0) {
+ return true;
+ }
+ std::unique_ptr<uint8_t[]> buffer(new uint8_t[total_size]);
+ if (!file->ReadFully(buffer.get(), total_size)) {
+ return false;
+ }
+
+ printf("Thread: %d\n", thread_id);
+ int current_depth = 0;
+ if (current_depth_map.find(thread_id) != current_depth_map.end()) {
+ // Get the current call stack depth. If it is the first method we are seeing on this thread
+ // then this map wouldn't have an entry, and we start with the depth of 0.
+ current_depth = current_depth_map[thread_id];
+ }
+
+ const uint8_t* current_buffer_ptr = buffer.get();
+ const uint8_t* end_ptr = buffer.get() + total_size;
+ uint64_t prev_method_id = 0;
+ int64_t prev_timestamp_and_action = 0;
+ for (int i = 0; i < num_records; i++) {
+ // Read timestamp and action
+ int64_t ts_diff = 0;
+ if (!DecodeSignedLeb128Checked(&current_buffer_ptr, end_ptr, &ts_diff)) {
+ LOG(FATAL) << "Reading past the buffer when decoding timestamp";
+ }
+ int64_t timestamp_and_action = prev_timestamp_and_action + ts_diff;
+ prev_timestamp_and_action = timestamp_and_action;
+ bool is_method_exit = timestamp_and_action & 0x1;
+
+ uint64_t method_id;
+ std::string method_name;
+ if (!is_method_exit) {
+ int64_t method_diff = 0;
+ if (!DecodeSignedLeb128Checked(&current_buffer_ptr, end_ptr, &method_diff)) {
+ LOG(FATAL) << "Reading past the buffer when decoding method id";
+ }
+ method_id = prev_method_id + method_diff;
+ prev_method_id = method_id;
+ if (method_map.find(method_id) == method_map.end()) {
+ LOG(FATAL) << "No entry for method " << std::hex << method_id;
+ }
+ method_name = method_map[method_id];
+ }
+
+ PrintTraceEntry(method_name,
+ is_method_exit? kMethodExit: kMethodEntry,
+ &current_depth,
+ timestamp_and_action & ~0x1);
+ }
+ current_depth_map[thread_id] = current_depth;
+ return true;
+}
+
+void ParseLongRunningMethodTrace(char* file_name) {
+ std::unique_ptr<File> file(OS::OpenFileForReading(file_name));
+ if (file == nullptr) {
+ printf("Couldn't open file\n");
+ return;
+ }
+
+ // Map to maintain information about threads and methods
+ std::map<uint64_t, std::string> method_map;
+
+ // Map to Maintain the current depth of the method in the call stack. Used to
+ // correctly indent when printing the trace events.
+ std::map<int64_t, int> current_depth_map;
+
+ // First parse metadata. To keep the implementation of dumping the data
+ // simple, we don't ensure that the information about methods is dumped before the
+ // methods. This is also good if the ANR report got truncated. We will then
+ // have information about how long the methods took and we can infer some of
+ // the method names from the stack trace.
+ while (true) {
+ uint8_t entry_header;
+ if (!file->ReadFully(&entry_header, sizeof(entry_header))) {
+ break;
+ }
+ if (entry_header == kEntryHeaderV2) {
+ if (!SkipTraceEntries(file)) {
+ break;
+ }
+ } else {
+ DCHECK_EQ(entry_header, kMethodInfoHeaderV2);
+ if (!ProcessMethodInfo(file, method_map)) {
+ break;
+ }
+ }
+ }
+
+ // Reset file
+ file->ResetOffset();
+
+ while (true) {
+ uint8_t entry_header;
+ if (!file->ReadFully(&entry_header, sizeof(entry_header))) {
+ break;
+ }
+ if (entry_header != kEntryHeaderV2) {
+ break;
+ }
+ if (!ProcessLongRunningMethodTraceEntries(file, current_depth_map, method_map)) {
+ break;
+ }
+ }
+}
+
+} // namespace art
+
+int main(int argc, char **argv) {
+ if (argc < 1) {
+ printf("Usage trace <filename>");
+ return -1;
+ }
+
+ art::ParseLongRunningMethodTrace(argv[1]);
+ return 0;
+}