diff options
| author | 2025-03-11 16:06:39 -0700 | |
|---|---|---|
| committer | 2025-03-11 16:06:39 -0700 | |
| commit | 9cd3adf41570af8b787e02dc7bef1eed035d8f40 (patch) | |
| tree | 9d83f9bf049efd2fb05903a7e6bf356deb3a6532 | |
| parent | 68440234b43e3e33f3cef439024d3bea6e62e39f (diff) | |
| parent | 199a768184b32b9d334d862356be8ad543dc8fde (diff) | |
Snap for 13197820 from 199a768184b32b9d334d862356be8ad543dc8fde to 25Q2-release
Change-Id: I87ba240391d7e2a635ee5fd8fb8dfe8d1778c529
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, ¤t_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(¤t_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(¤t_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, + ¤t_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; +} |