diff options
author | 2023-07-31 11:01:45 +0100 | |
---|---|---|
committer | 2023-08-02 15:18:55 +0000 | |
commit | 0187b188b3f992362dfba829dcba11205e774608 (patch) | |
tree | a27df4cc2adfed7a4cc65e62ba8d893a69a400da | |
parent | 0e09c653ca085043e910e799358bd96ae16a0879 (diff) |
Overhaul oatdump options.
Changes:
- `--app-image` (which dumps the app image) no longer requires
`--image`. oatdump infers the boot image location when possible.
- `--app-image` now only dumps the app image, and doesn't fall through
to dumping the boot image anymore.
- `--app-oat` is now deprecated and becomes an alias of `--oat-file`.
- When `--oat-file` (which dumps the oat file) is combined with
`--app-image`, it now correctly takes `--dex-file` as an additional
flag to specify the dex filename. Before this change, oatdump ignored
`--dex-file` when `--app-image` was specified, and it only dumped the
oat header.
- Using `--oat-file` alone now brings up a runtime whenever possible.
This allows dumping more information including BSS mappings for BCP
dex files. Before this change, oat files were dumped with runtime
only if `--boot-image` was specified.
- When the dex code is missing, oatdump now always dumps the oat file
without runtime. Before this change, it tried to dump with runtime and
failed.
- `--boot-image` now accepts a non-existing path, to start the runtime
in imageless mode. This allows dumping with runtime for an oat file
that is generated without a boot image.
- oatdump now checks the BCP checksums before dumping with runtime.
Before this change, it crashed when there was a BCP mismatch.
- When `--image` (which dumps the boot image) is unexpectedly combined
with `--boot-image`, oatdump now ignores `--boot-image`. Before this
change, the behavior was controlled by `--image` but the location was
taken from `--boot-image`.
- The order of precedence of the options is clarified in the code, but
is unchanged for backward compatibility.
Bug: 293335130
Test: Manually tested the examples in the help text added by this CL.
Test: m test-art-host-gtest-art_oatdump_tests
Test: atest art_standalone_oatdump_tests
Change-Id: I3e1dcf403f6fb459bcd91b47e5f4513237813215
-rw-r--r-- | cmdline/cmdline.h | 241 | ||||
-rw-r--r-- | libartbase/base/common_art_test.h | 5 | ||||
-rw-r--r-- | libartbase/base/file_utils.cc | 32 | ||||
-rw-r--r-- | libartbase/base/file_utils.h | 13 | ||||
-rw-r--r-- | oatdump/oatdump.cc | 414 | ||||
-rw-r--r-- | oatdump/oatdump_app_test.cc | 142 | ||||
-rw-r--r-- | oatdump/oatdump_test.h | 19 | ||||
-rw-r--r-- | runtime/oat_file_assistant.cc | 5 |
8 files changed, 613 insertions, 258 deletions
diff --git a/cmdline/cmdline.h b/cmdline/cmdline.h index 2108a7a172..3aac785609 100644 --- a/cmdline/cmdline.h +++ b/cmdline/cmdline.h @@ -22,16 +22,20 @@ #include <fstream> #include <iostream> +#include <memory> #include <string> #include <string_view> +#include <vector> #include "android-base/stringprintf.h" - +#include "android-base/strings.h" #include "base/file_utils.h" #include "base/logging.h" #include "base/mutex.h" #include "base/string_view_cpp20.h" +#include "base/utils.h" #include "noop_compiler_callbacks.h" +#include "oat_file_assistant_context.h" #include "runtime.h" #if !defined(NDEBUG) @@ -42,55 +46,9 @@ namespace art { -// TODO: Move to <runtime/utils.h> and remove all copies of this function. -static bool LocationToFilename(const std::string& location, InstructionSet isa, - std::string* filename) { - bool has_system = false; - bool has_cache = false; - // image_location = /system/framework/boot.art - // system_image_filename = /system/framework/<image_isa>/boot.art - std::string system_filename(GetSystemImageFilename(location.c_str(), isa)); - if (OS::FileExists(system_filename.c_str())) { - has_system = true; - } - - bool have_android_data = false; - bool dalvik_cache_exists = false; - bool is_global_cache = false; - std::string dalvik_cache; - GetDalvikCache(GetInstructionSetString(isa), false, &dalvik_cache, - &have_android_data, &dalvik_cache_exists, &is_global_cache); - - std::string cache_filename; - if (have_android_data && dalvik_cache_exists) { - // Always set output location even if it does not exist, - // so that the caller knows where to create the image. - // - // image_location = /system/framework/boot.art - // *image_filename = /data/dalvik-cache/<image_isa>/boot.art - std::string error_msg; - if (GetDalvikCacheFilename(location.c_str(), dalvik_cache.c_str(), - &cache_filename, &error_msg)) { - has_cache = true; - } - } - if (has_system) { - *filename = system_filename; - return true; - } else if (has_cache) { - *filename = cache_filename; - return true; - } else { - *filename = system_filename; - return false; - } -} - -static Runtime* StartRuntime(const char* boot_image_location, +static Runtime* StartRuntime(const std::vector<std::string>& boot_image_locations, InstructionSet instruction_set, const std::vector<const char*>& runtime_args) { - CHECK(boot_image_location != nullptr); - RuntimeOptions options; // We are more like a compiler than a run-time. We don't want to execute code. @@ -101,9 +59,12 @@ static Runtime* StartRuntime(const char* boot_image_location, // Boot image location. { - std::string boot_image_option; - boot_image_option += "-Ximage:"; - boot_image_option += boot_image_location; + std::string boot_image_option = "-Ximage:"; + if (!boot_image_locations.empty()) { + boot_image_option += android::base::Join(boot_image_locations, ':'); + } else { + boot_image_option += GetJitZygoteBootImageLocation(); + } options.push_back(std::make_pair(boot_image_option, nullptr)); } @@ -155,7 +116,7 @@ struct CmdlineArgs { const char* const raw_option = argv[i]; const std::string_view option(raw_option); if (StartsWith(option, "--boot-image=")) { - boot_image_location_ = raw_option + strlen("--boot-image="); + Split(raw_option + strlen("--boot-image="), ':', &boot_image_locations_); } else if (StartsWith(option, "--instruction-set=")) { const char* const instruction_set_str = raw_option + strlen("--instruction-set="); instruction_set_ = GetInstructionSetFromString(instruction_set_str); @@ -197,6 +158,11 @@ struct CmdlineArgs { } } + if (instruction_set_ == InstructionSet::kNone) { + LOG(WARNING) << "No instruction set given, assuming " << GetInstructionSetString(kRuntimeISA); + instruction_set_ = kRuntimeISA; + } + DBG_LOG << "will call parse checks"; { @@ -241,8 +207,14 @@ struct CmdlineArgs { return usage; } - // Specified by --boot-image. - const char* boot_image_location_ = nullptr; + // Specified by --runtime-arg -Xbootclasspath or default. + std::vector<std::string> boot_class_path_; + // Specified by --runtime-arg -Xbootclasspath-locations or default. + std::vector<std::string> boot_class_path_locations_; + // True if `boot_class_path_` is the default one. + bool is_default_boot_class_path_ = false; + // Specified by --boot-image or inferred. + std::vector<std::string> boot_image_locations_; // Specified by --instruction-set. InstructionSet instruction_set_ = InstructionSet::kNone; // Runtime arguments specified by --runtime-arg. @@ -254,70 +226,62 @@ struct CmdlineArgs { virtual ~CmdlineArgs() {} + // Checks for --boot-image location. bool ParseCheckBootImage(std::string* error_msg) { - if (boot_image_location_ == nullptr) { - *error_msg = "--boot-image must be specified"; - return false; + if (boot_image_locations_.empty()) { + LOG(WARNING) << "--boot-image not specified. Starting runtime in imageless mode"; + return true; } - if (instruction_set_ == InstructionSet::kNone) { - LOG(WARNING) << "No instruction set given, assuming " << GetInstructionSetString(kRuntimeISA); - instruction_set_ = kRuntimeISA; + + const std::string& boot_image_location = boot_image_locations_[0]; + size_t file_name_idx = boot_image_location.rfind('/'); + if (file_name_idx == std::string::npos) { // Prevent a InsertIsaDirectory check failure. + *error_msg = "Boot image location must have a / in it"; + return false; } - DBG_LOG << "boot image location: " << boot_image_location_; + // Don't let image locations with the 'arch' in it through, since it's not a location. + // This prevents a common error "Could not create an image space..." when initing the Runtime. + if (file_name_idx > 0) { + size_t ancestor_dirs_idx = boot_image_location.rfind('/', file_name_idx - 1); - // Checks for --boot-image location. - { - std::string boot_image_location = boot_image_location_; - size_t separator_pos = boot_image_location.find(':'); - if (separator_pos != std::string::npos) { - boot_image_location = boot_image_location.substr(/*pos*/ 0u, /*size*/ separator_pos); - } - size_t file_name_idx = boot_image_location.rfind('/'); - if (file_name_idx == std::string::npos) { // Prevent a InsertIsaDirectory check failure. - *error_msg = "Boot image location must have a / in it"; - return false; + std::string parent_dir_name; + if (ancestor_dirs_idx != std::string::npos) { + parent_dir_name = boot_image_location.substr(/*pos=*/ancestor_dirs_idx + 1, + /*n=*/file_name_idx - ancestor_dirs_idx - 1); + } else { + parent_dir_name = boot_image_location.substr(/*pos=*/0, + /*n=*/file_name_idx); } - // Don't let image locations with the 'arch' in it through, since it's not a location. - // This prevents a common error "Could not create an image space..." when initing the Runtime. - if (file_name_idx != std::string::npos) { - std::string no_file_name = boot_image_location.substr(0, file_name_idx); - size_t ancestor_dirs_idx = no_file_name.rfind('/'); - - std::string parent_dir_name; - if (ancestor_dirs_idx != std::string::npos) { - parent_dir_name = no_file_name.substr(ancestor_dirs_idx + 1); - } else { - parent_dir_name = no_file_name; - } - - DBG_LOG << "boot_image_location parent_dir_name was " << parent_dir_name; + DBG_LOG << "boot_image_location parent_dir_name was " << parent_dir_name; - if (GetInstructionSetFromString(parent_dir_name.c_str()) != InstructionSet::kNone) { + if (GetInstructionSetFromString(parent_dir_name.c_str()) != InstructionSet::kNone) { *error_msg = "Do not specify the architecture as part of the boot image location"; return false; - } } - - // Check that the boot image location points to a valid file name. - std::string file_name; - if (!LocationToFilename(boot_image_location, instruction_set_, &file_name)) { - *error_msg = android::base::StringPrintf( - "No corresponding file for location '%s' (filename '%s') exists", - boot_image_location.c_str(), - file_name.c_str()); - return false; - } - - DBG_LOG << "boot_image_filename does exist: " << file_name; } return true; } - void PrintUsage() { - fprintf(stderr, "%s", GetUsage().c_str()); + void PrintUsage() { fprintf(stderr, "%s", GetUsage().c_str()); } + + std::unique_ptr<OatFileAssistantContext> GetOatFileAssistantContext(std::string* error_msg) { + if (boot_class_path_.empty()) { + *error_msg = "Boot classpath is empty"; + return nullptr; + } + + CHECK(!boot_class_path_locations_.empty()); + + return std::make_unique<OatFileAssistantContext>( + std::make_unique<OatFileAssistantContext::RuntimeOptions>( + OatFileAssistantContext::RuntimeOptions{ + .image_locations = boot_image_locations_, + .boot_class_path = boot_class_path_, + .boot_class_path_locations = boot_class_path_locations_, + })); } protected: @@ -327,7 +291,76 @@ struct CmdlineArgs { return kParseUnknownArgument; } - virtual ParseStatus ParseChecks([[maybe_unused]] std::string* error_msg) { return kParseOk; } + virtual ParseStatus ParseChecks([[maybe_unused]] std::string* error_msg) { + ParseBootclasspath(); + if (boot_image_locations_.empty()) { + InferBootImage(); + } + return kParseOk; + } + + private: + void ParseBootclasspath() { + std::optional<std::string_view> bcp_str = std::nullopt; + std::optional<std::string_view> bcp_location_str = std::nullopt; + for (const char* arg : runtime_args_) { + if (StartsWith(arg, "-Xbootclasspath:")) { + bcp_str = arg + strlen("-Xbootclasspath:"); + } + if (StartsWith(arg, "-Xbootclasspath-locations:")) { + bcp_location_str = arg + strlen("-Xbootclasspath-locations:"); + } + } + + if (bcp_str.has_value() && bcp_location_str.has_value()) { + Split(*bcp_str, ':', &boot_class_path_); + Split(*bcp_location_str, ':', &boot_class_path_locations_); + } else if (bcp_str.has_value()) { + Split(*bcp_str, ':', &boot_class_path_); + boot_class_path_locations_ = boot_class_path_; + } else { + // Try the default. + const char* env_value = getenv("BOOTCLASSPATH"); + if (env_value != nullptr && strlen(env_value) > 0) { + Split(env_value, ':', &boot_class_path_); + boot_class_path_locations_ = boot_class_path_; + is_default_boot_class_path_ = true; + } + } + } + + // Infers the boot image on a best-effort basis. + // The inference logic aligns with installd/artd + dex2oat. + void InferBootImage() { + // The boot image inference only makes sense on device. + if (!kIsTargetAndroid) { + return; + } + + // The inferred boot image can only be used with the default bootclasspath. + if (boot_class_path_.empty() || !is_default_boot_class_path_) { + return; + } + + std::string error_msg; + std::string boot_image = GetBootImageLocationForDefaultBcpRespectingSysProps(&error_msg); + if (boot_image.empty()) { + LOG(WARNING) << "Failed to infer boot image: " << error_msg; + return; + } + + LOG(INFO) << "Inferred boot image: " << boot_image; + Split(boot_image, ':', &boot_image_locations_); + + // Verify the inferred boot image. + std::unique_ptr<OatFileAssistantContext> ofa_context = GetOatFileAssistantContext(&error_msg); + CHECK_NE(ofa_context, nullptr); + size_t verified_boot_image_count = ofa_context->GetBootImageInfoList(instruction_set_).size(); + if (verified_boot_image_count != boot_image_locations_.size()) { + LOG(WARNING) << "Failed to verify inferred boot image"; + boot_image_locations_.resize(verified_boot_image_count); + } + } }; template <typename Args = CmdlineArgs> @@ -412,7 +445,7 @@ struct CmdlineMain { Runtime* CreateRuntime(CmdlineArgs* args) { CHECK(args != nullptr); - return StartRuntime(args->boot_image_location_, args->instruction_set_, args_->runtime_args_); + return StartRuntime(args->boot_image_locations_, args->instruction_set_, args_->runtime_args_); } }; } // namespace art diff --git a/libartbase/base/common_art_test.h b/libartbase/base/common_art_test.h index 1e39716417..2a28011387 100644 --- a/libartbase/base/common_art_test.h +++ b/libartbase/base/common_art_test.h @@ -312,6 +312,11 @@ std::vector<pid_t> GetPidByName(const std::string& process_name); GTEST_SKIP() << "WARNING: TEST DISABLED FOR NON-STATIC HOST BUILDS"; \ } +#define TEST_DISABLED_FOR_DEBUG_BUILD() \ + if (kIsDebugBuild) { \ + GTEST_SKIP() << "WARNING: TEST DISABLED FOR DEBUG BUILD"; \ + } + #define TEST_DISABLED_FOR_MEMORY_TOOL() \ if (kRunningOnMemoryTool) { \ GTEST_SKIP() << "WARNING: TEST DISABLED FOR MEMORY TOOL"; \ diff --git a/libartbase/base/file_utils.cc b/libartbase/base/file_utils.cc index b3f42ee0e4..cb2aee2979 100644 --- a/libartbase/base/file_utils.cc +++ b/libartbase/base/file_utils.cc @@ -19,6 +19,7 @@ #include <inttypes.h> #include <sys/stat.h> #include <sys/types.h> + #ifndef _WIN32 #include <sys/wait.h> #endif @@ -44,6 +45,7 @@ #include "android-base/file.h" #include "android-base/logging.h" +#include "android-base/properties.h" #include "android-base/stringprintf.h" #include "android-base/strings.h" #include "base/bit_utils.h" @@ -70,6 +72,8 @@ namespace art { +using android::base::GetBoolProperty; +using android::base::GetProperty; using android::base::StringPrintf; static constexpr const char* kClassesDex = "classes.dex"; @@ -518,6 +522,34 @@ std::string GetJitZygoteBootImageLocation() { return "/nonx/boot.art!/apex/com.android.art/etc/boot-image.prof!/system/etc/boot-image.prof"; } +std::string GetBootImageLocationForDefaultBcp(bool no_boot_image, + std::string user_defined_boot_image, + bool deny_art_apex_data_files, + std::string* error_msg) { + if (no_boot_image) { + return GetJitZygoteBootImageLocation(); + } + if (!user_defined_boot_image.empty()) { + return user_defined_boot_image; + } + std::string android_root = GetAndroidRootSafe(error_msg); + if (!error_msg->empty()) { + return ""; + } + return GetDefaultBootImageLocationSafe(android_root, deny_art_apex_data_files, error_msg); +} + +std::string GetBootImageLocationForDefaultBcpRespectingSysProps(std::string* error_msg) { + bool no_boot_image = + GetBoolProperty("persist.device_config.runtime_native_boot.profilebootclasspath", + GetBoolProperty("dalvik.vm.profilebootclasspath", /*default_value=*/false)); + std::string user_defined_boot_image = GetProperty("dalvik.vm.boot-image", /*default_value=*/""); + bool deny_art_apex_data_files = + !GetBoolProperty("odsign.verification.success", /*default_value=*/false); + return GetBootImageLocationForDefaultBcp( + no_boot_image, user_defined_boot_image, deny_art_apex_data_files, error_msg); +} + static /*constinit*/ std::string_view dalvik_cache_sub_dir = "dalvik-cache"; void OverrideDalvikCacheSubDirectory(std::string sub_dir) { diff --git a/libartbase/base/file_utils.h b/libartbase/base/file_utils.h index faec95eabf..4ec348d9ec 100644 --- a/libartbase/base/file_utils.h +++ b/libartbase/base/file_utils.h @@ -96,6 +96,19 @@ std::string GetDefaultBootImageLocation(const std::string& android_root, // Returns the boot image location that forces the runtime to run in JIT Zygote mode. std::string GetJitZygoteBootImageLocation(); +// A helper function to pick the most appropriate boot image based on the given options. +// The boot image location can only be used with the default bootclasspath (the value of the +// BOOTCLASSPATH environment variable). +std::string GetBootImageLocationForDefaultBcp(bool no_boot_image, + std::string user_defined_boot_image, + bool deny_art_apex_data_files, + std::string* error_msg); + +// A helper function to pick the most appropriate boot image based on system properties. +// The boot image location can only be used with the default bootclasspath (the value of the +// BOOTCLASSPATH environment variable). +std::string GetBootImageLocationForDefaultBcpRespectingSysProps(std::string* error_msg); + // Allows the name to be used for the dalvik cache directory (normally "dalvik-cache") to be // overridden with a new value. void OverrideDalvikCacheSubDirectory(std::string sub_dir); diff --git a/oatdump/oatdump.cc b/oatdump/oatdump.cc index 9dd704aa06..06a3ac8137 100644 --- a/oatdump/oatdump.cc +++ b/oatdump/oatdump.cc @@ -16,35 +16,44 @@ #include <stdio.h> #include <stdlib.h> +#include <sys/stat.h> +#include <algorithm> +#include <cstdlib> #include <fstream> #include <iomanip> #include <iostream> #include <map> +#include <optional> #include <set> #include <string> #include <unordered_map> #include <unordered_set> +#include <utility> #include <vector> #include "android-base/logging.h" #include "android-base/parseint.h" #include "android-base/stringprintf.h" #include "android-base/strings.h" - +#include "arch/instruction_set.h" #include "arch/instruction_set_features.h" #include "art_field-inl.h" #include "art_method-inl.h" +#include "base/array_ref.h" #include "base/bit_utils_iterator.h" +#include "base/file_utils.h" #include "base/indenter.h" #include "base/os.h" #include "base/safe_map.h" #include "base/stats-inl.h" #include "base/stl_util.h" +#include "base/string_view_cpp20.h" #include "base/unix_file/fd_file.h" #include "class_linker-inl.h" #include "class_linker.h" #include "class_root-inl.h" +#include "cmdline.h" #include "debug/debug_info.h" #include "debug/elf_debug_writer.h" #include "debug/method_debug_info.h" @@ -74,6 +83,8 @@ #include "mirror/object_array-inl.h" #include "oat.h" #include "oat_file-inl.h" +#include "oat_file_assistant.h" +#include "oat_file_assistant_context.h" #include "oat_file_manager.h" #include "scoped_thread_state_change-inl.h" #include "stack.h" @@ -87,9 +98,6 @@ #include "verifier/verifier_deps.h" #include "well_known_classes.h" -#include <sys/stat.h> -#include "cmdline.h" - namespace art { using android::base::StringPrintf; @@ -344,22 +352,24 @@ class OatDumperOptions { bool dump_header_only, const char* export_dex_location, const char* app_image, - const char* app_oat, + const char* oat_filename, + const char* dex_filename, uint32_t addr2instr) - : dump_vmap_(dump_vmap), - dump_code_info_stack_maps_(dump_code_info_stack_maps), - disassemble_code_(disassemble_code), - absolute_addresses_(absolute_addresses), - class_filter_(class_filter), - method_filter_(method_filter), - list_classes_(list_classes), - list_methods_(list_methods), - dump_header_only_(dump_header_only), - export_dex_location_(export_dex_location), - app_image_(app_image), - app_oat_(app_oat), - addr2instr_(addr2instr), - class_loader_(nullptr) {} + : dump_vmap_(dump_vmap), + dump_code_info_stack_maps_(dump_code_info_stack_maps), + disassemble_code_(disassemble_code), + absolute_addresses_(absolute_addresses), + class_filter_(class_filter), + method_filter_(method_filter), + list_classes_(list_classes), + list_methods_(list_methods), + dump_header_only_(dump_header_only), + export_dex_location_(export_dex_location), + app_image_(app_image), + oat_filename_(oat_filename != nullptr ? std::make_optional(oat_filename) : std::nullopt), + dex_filename_(dex_filename != nullptr ? std::make_optional(dex_filename) : std::nullopt), + addr2instr_(addr2instr), + class_loader_(nullptr) {} const bool dump_vmap_; const bool dump_code_info_stack_maps_; @@ -372,7 +382,8 @@ class OatDumperOptions { const bool dump_header_only_; const char* const export_dex_location_; const char* const app_image_; - const char* const app_oat_; + const std::optional<std::string> oat_filename_; + const std::optional<std::string> dex_filename_; uint32_t addr2instr_; Handle<mirror::ClassLoader>* class_loader_; }; @@ -550,7 +561,7 @@ class OatDumper { CHECK_LE(oat_file_.bcp_bss_info_.size(), bcp_dex_files.size()); for (size_t i = 0; i < oat_file_.bcp_bss_info_.size(); i++) { const DexFile* const dex_file = bcp_dex_files[i]; - os << "Dumping entries for BCP DexFile: " << dex_file->GetLocation() << "\n"; + os << "Entries for BCP DexFile: " << dex_file->GetLocation() << "\n"; DumpBssMappings(os, dex_file, oat_file_.bcp_bss_info_[i].method_bss_mapping, @@ -562,7 +573,7 @@ class OatDumper { } else { // We don't have a runtime, just dump the offsets for (size_t i = 0; i < oat_file_.bcp_bss_info_.size(); i++) { - os << "We don't have a runtime, just dump the offsets for BCP Dexfile " << i << "\n"; + os << "Offsets for BCP DexFile at index " << i << "\n"; DumpBssOffsets(os, "ArtMethod", oat_file_.bcp_bss_info_[i].method_bss_mapping); DumpBssOffsets(os, "Class", oat_file_.bcp_bss_info_[i].type_bss_mapping); DumpBssOffsets(os, "Public Class", oat_file_.bcp_bss_info_[i].public_type_bss_mapping); @@ -2447,6 +2458,27 @@ class ImageDumper { DISALLOW_COPY_AND_ASSIGN(ImageDumper); }; +static std::unique_ptr<OatFile> OpenOat(const std::string& oat_filename, + const std::optional<std::string>& dex_filename, + std::string* error_msg) { + if (!dex_filename.has_value()) { + LOG(WARNING) << "No dex filename provided, " + << "oatdump might fail if the oat file does not contain the dex code."; + } + ArrayRef<const std::string> dex_filenames = + dex_filename.has_value() ? ArrayRef<const std::string>(&dex_filename.value(), /*size=*/1) : + ArrayRef<const std::string>(); + return std::unique_ptr<OatFile>(OatFile::Open(/*zip_fd=*/-1, + oat_filename, + oat_filename, + /*executable=*/false, + /*low_4gb=*/false, + dex_filenames, + /*dex_fds=*/ArrayRef<const int>(), + /*reservation=*/nullptr, + error_msg)); +} + static int DumpImage(gc::space::ImageSpace* image_space, OatDumperOptions* options, std::ostream* os) REQUIRES_SHARED(Locks::mutator_lock_) { @@ -2469,7 +2501,7 @@ static int DumpImages(Runtime* runtime, OatDumperOptions* options, std::ostream* ScopedObjectAccess soa(Thread::Current()); if (options->app_image_ != nullptr) { - if (options->app_oat_ == nullptr) { + if (!options->oat_filename_.has_value()) { LOG(ERROR) << "Can not dump app image without app oat file"; return EXIT_FAILURE; } @@ -2477,14 +2509,11 @@ static int DumpImages(Runtime* runtime, OatDumperOptions* options, std::ostream* // We need to map the oat file in the low 4gb or else the fixup wont be able to fit oat file // pointers into 32 bit pointer sized ArtMethods. std::string error_msg; - std::unique_ptr<OatFile> oat_file(OatFile::Open(/*zip_fd=*/ -1, - options->app_oat_, - options->app_oat_, - /*executable=*/ false, - /*low_4gb=*/ true, - &error_msg)); + std::unique_ptr<OatFile> oat_file = + OpenOat(*options->oat_filename_, options->dex_filename_, &error_msg); if (oat_file == nullptr) { - LOG(ERROR) << "Failed to open oat file " << options->app_oat_ << " with error " << error_msg; + LOG(ERROR) << "Failed to open oat file " << *options->oat_filename_ << " with error " + << error_msg; return EXIT_FAILURE; } std::unique_ptr<gc::space::ImageSpace> space( @@ -2502,11 +2531,7 @@ static int DumpImages(Runtime* runtime, OatDumperOptions* options, std::ostream* return EXIT_FAILURE; } // Dump the actual image. - int result = DumpImage(space.get(), options, os); - if (result != EXIT_SUCCESS) { - return result; - } - // Fall through to dump the boot images. + return DumpImage(space.get(), options, os); } gc::Heap* heap = runtime->GetHeap(); @@ -2593,30 +2618,12 @@ static int DumpOatWithoutRuntime(OatFile* oat_file, OatDumperOptions* options, s return (success) ? EXIT_SUCCESS : EXIT_FAILURE; } -static int DumpOat(Runtime* runtime, - const char* oat_filename, - const char* dex_filename, - OatDumperOptions* options, - std::ostream* os) { - if (dex_filename == nullptr) { - LOG(WARNING) << "No dex filename provided, " - << "oatdump might fail if the oat file does not contain the dex code."; - } - std::string dex_filename_str((dex_filename != nullptr) ? dex_filename : ""); - ArrayRef<const std::string> dex_filenames(&dex_filename_str, - /*size=*/ (dex_filename != nullptr) ? 1u : 0u); +static int DumpOat(Runtime* runtime, OatDumperOptions* options, std::ostream* os) { std::string error_msg; - std::unique_ptr<OatFile> oat_file(OatFile::Open(/*zip_fd=*/ -1, - oat_filename, - oat_filename, - /*executable=*/ false, - /*low_4gb=*/ false, - dex_filenames, - /*dex_fds=*/ ArrayRef<const int>(), - /*reservation=*/ nullptr, - &error_msg)); + std::unique_ptr<OatFile> oat_file = + OpenOat(*options->oat_filename_, options->dex_filename_, &error_msg); if (oat_file == nullptr) { - LOG(ERROR) << "Failed to open oat file from '" << oat_filename << "': " << error_msg; + LOG(ERROR) << "Failed to open oat file from '" << *options->oat_filename_ << "': " << error_msg; return EXIT_FAILURE; } @@ -2631,19 +2638,11 @@ static int SymbolizeOat(const char* oat_filename, const char* dex_filename, std::string& output_name, bool no_bits) { - std::string dex_filename_str((dex_filename != nullptr) ? dex_filename : ""); - ArrayRef<const std::string> dex_filenames(&dex_filename_str, - /*size=*/ (dex_filename != nullptr) ? 1u : 0u); std::string error_msg; - std::unique_ptr<OatFile> oat_file(OatFile::Open(/*zip_fd=*/ -1, - oat_filename, - oat_filename, - /*executable=*/ false, - /*low_4gb=*/ false, - dex_filenames, - /*dex_fds=*/ ArrayRef<const int>(), - /*reservation=*/ nullptr, - &error_msg)); + std::unique_ptr<OatFile> oat_file = + OpenOat(oat_filename, + dex_filename != nullptr ? std::make_optional(dex_filename) : std::nullopt, + &error_msg); if (oat_file == nullptr) { LOG(ERROR) << "Failed to open oat file from '" << oat_filename << "': " << error_msg; return EXIT_FAILURE; @@ -2682,19 +2681,11 @@ class IMTDumper { std::vector<const DexFile*> class_path; if (oat_filename != nullptr) { - std::string dex_filename_str((dex_filename != nullptr) ? dex_filename : ""); - ArrayRef<const std::string> dex_filenames(&dex_filename_str, - /*size=*/ (dex_filename != nullptr) ? 1u : 0u); std::string error_msg; - std::unique_ptr<OatFile> oat_file(OatFile::Open(/*zip_fd=*/ -1, - oat_filename, - oat_filename, - /*executable=*/ false, - /*low_4gb=*/false, - dex_filenames, - /*dex_fds=*/ArrayRef<const int>(), - /*reservation=*/ nullptr, - &error_msg)); + std::unique_ptr<OatFile> oat_file = + OpenOat(oat_filename, + dex_filename != nullptr ? std::make_optional(dex_filename) : std::nullopt, + &error_msg); if (oat_file == nullptr) { LOG(ERROR) << "Failed to open oat file from '" << oat_filename << "': " << error_msg; return false; @@ -3114,6 +3105,13 @@ class IMTDumper { } }; +enum class OatDumpMode { + kSymbolize, + kDumpImt, + kDumpImage, + kDumpOat, +}; + struct OatdumpArgs : public CmdlineArgs { protected: using Base = CmdlineArgs; @@ -3180,9 +3178,15 @@ struct OatdumpArgs : public CmdlineArgs { } ParseStatus ParseChecks(std::string* error_msg) override { - // Infer boot image location from the image location if possible. - if (boot_image_location_ == nullptr) { - boot_image_location_ = image_location_; + if (image_location_ != nullptr) { + if (!boot_image_locations_.empty()) { + std::cerr << "Warning: Invalid combination of --boot-image and --image\n"; + std::cerr << "Use --image alone to dump boot image(s)\n"; + std::cerr << "Ignoring --boot-image\n"; + std::cerr << "\n"; + boot_image_locations_.clear(); + } + Split(image_location_, ':', &boot_image_locations_); } // Perform the parent checks. @@ -3192,11 +3196,41 @@ struct OatdumpArgs : public CmdlineArgs { } // Perform our own checks. - if (image_location_ == nullptr && oat_filename_ == nullptr) { - *error_msg = "Either --image or --oat-file must be specified"; + if (image_location_ == nullptr && app_image_ == nullptr && oat_filename_ == nullptr) { + *error_msg = "Either --image, --app-image, --oat-file, or --symbolize must be specified"; + return kParseError; + } + + if (app_image_ != nullptr && image_location_ != nullptr) { + std::cerr << "Warning: Combining --app-image with --image is no longer supported\n"; + std::cerr << "Use --app-image alone to dump an app image, and optionally pass --boot-image " + "to specify the boot image that the app image is based on\n"; + std::cerr << "Use --image alone to dump boot image(s)\n"; + std::cerr << "Ignoring --image\n"; + std::cerr << "\n"; + image_location_ = nullptr; + } + + if (image_location_ != nullptr && oat_filename_ != nullptr) { + *error_msg = + "--image and --oat-file must not be specified together\n" + "Use --image alone to dump both boot image(s) and their oat file(s)\n" + "Use --oat-file alone to dump an oat file"; return kParseError; - } else if (image_location_ != nullptr && oat_filename_ != nullptr) { - *error_msg = "Either --image or --oat-file must be specified but not both"; + } + + if (app_oat_ != nullptr) { + std::cerr << "Warning: --app-oat is deprecated. Use --oat-file instead\n"; + std::cerr << "\n"; + oat_filename_ = app_oat_; + } + + if (boot_image_locations_.empty() && app_image_ != nullptr) { + // At this point, boot image inference is impossible or has failed, and the user has been + // warned about the failure. + // When dumping an app image, we need at least one valid boot image, so we have to stop. + // When dumping other things, we can continue to start the runtime in imageless mode. + *error_msg = "--boot-image must be specified"; return kParseError; } @@ -3206,25 +3240,43 @@ struct OatdumpArgs : public CmdlineArgs { std::string GetUsage() const override { std::string usage; - usage += - "Usage: oatdump [options] ...\n" - " Example: oatdump --image=$ANDROID_PRODUCT_OUT/system/framework/boot.art\n" - " Example: adb shell oatdump --image=/system/framework/boot.art\n" - "\n" - // Either oat-file or image is required. - " --oat-file=<file.oat>: specifies an input oat filename.\n" - " Example: --oat-file=/system/framework/arm64/boot.oat\n" - "\n" - " --image=<file.art>: specifies an input image location.\n" - " Example: --image=/system/framework/boot.art\n" - "\n" - " --app-image=<file.art>: specifies an input app image. Must also have a specified\n" - " boot image (with --image) and app oat file (with --app-oat).\n" - " Example: --app-image=app.art\n" - "\n" - " --app-oat=<file.odex>: specifies an input app oat.\n" - " Example: --app-oat=app.odex\n" - "\n"; + usage += R"( +Usage: oatdump [options] ... + +Examples: +- Dump a primary boot image with its oat file. + oatdump --image=/system/framework/boot.art + +- Dump a primary boot image and extension(s) with their oat files. + oatdump --image=/system/framework/boot.art:/system/framework/boot-framework-adservices.art + +- Dump an app image with its oat file. + oatdump --app-image=app.art --oat-file=app.odex [--dex-file=app.apk] [--boot-image=boot.art] + +- Dump an app oat file. + oatdump --oat-file=app.odex [--dex-file=app.apk] [--boot-image=boot.art] + +- Dump IMT collisions. (See --dump-imt for details.) + oatdump --oat-file=app.odex --dump-imt=imt.txt [--dex-file=app.apk] [--boot-image=boot.art] + [--dump-imt-stats] + +- Symbolize an oat file. (See --symbolize for details.) + oatdump --symbolize=app.odex [--dex-file=app.apk] [--only-keep-debug] + +Options: + --oat-file=<file.oat>: dumps an oat file with the given filename. + Example: --oat-file=/system/framework/arm64/boot.oat + + --image=<file.art>: dumps boot image(s) specified at the given location. + Example: --image=/system/framework/boot.art + + --app-image=<file.art>: dumps an app image with the given filename. + Must also have a specified app oat file (with --oat-file). + Example: --app-image=app.art + + --app-oat=<file.odex>: deprecated. Use --oat-file instead. + +)"; usage += Base::GetUsage(); @@ -3252,7 +3304,7 @@ struct OatdumpArgs : public CmdlineArgs { " --symbolize=<file.oat>: output a copy of file.oat with elf symbols included.\n" " Example: --symbolize=/system/framework/boot.oat\n" "\n" - " --only-keep-debug<file.oat>: Modifies the behaviour of --symbolize so that\n" + " --only-keep-debug: modifies the behaviour of --symbolize so that\n" " .rodata and .text sections are omitted in the output file to save space.\n" " Example: --symbolize=/system/framework/boot.oat --only-keep-debug\n" "\n" @@ -3276,7 +3328,8 @@ struct OatdumpArgs : public CmdlineArgs { " of a complete method name (separated by a whitespace).\n" " Example: --dump-imt=imt.txt\n" "\n" - " --dump-imt-stats: output IMT statistics for the given boot image\n" + " --dump-imt-stats: modifies the behavior of --dump-imt to also output IMT statistics\n" + " for the boot image.\n" " Example: --dump-imt-stats" "\n"; @@ -3284,6 +3337,21 @@ struct OatdumpArgs : public CmdlineArgs { } public: + OatDumpMode GetMode() { + // Keep the order of precedence for backward compatibility. + if (symbolize_) { + return OatDumpMode::kSymbolize; + } + if (!imt_dump_.empty()) { + return OatDumpMode::kDumpImt; + } + if (image_location_ != nullptr || app_image_ != nullptr) { + return OatDumpMode::kDumpImage; + } + CHECK_NE(oat_filename_, nullptr); + return OatDumpMode::kDumpOat; + } + const char* oat_filename_ = nullptr; const char* dex_filename_ = nullptr; const char* class_filter_ = ""; @@ -3310,57 +3378,74 @@ struct OatdumpMain : public CmdlineMain<OatdumpArgs> { bool NeedsRuntime() override { CHECK(args_ != nullptr); - // If we are only doing the oat file, disable absolute_addresses. Keep them for image dumping. - bool absolute_addresses = (args_->oat_filename_ == nullptr); - - oat_dumper_options_.reset(new OatDumperOptions( - args_->dump_vmap_, - args_->dump_code_info_stack_maps_, - args_->disassemble_code_, - absolute_addresses, - args_->class_filter_, - args_->method_filter_, - args_->list_classes_, - args_->list_methods_, - args_->dump_header_only_, - args_->export_dex_location_, - args_->app_image_, - args_->app_oat_, - args_->addr2instr_)); - - return (args_->boot_image_location_ != nullptr || - args_->image_location_ != nullptr || - !args_->imt_dump_.empty()) && - !args_->symbolize_; + OatDumpMode mode = args_->GetMode(); + + // Only enable absolute_addresses for image dumping. + bool absolute_addresses = mode == OatDumpMode::kDumpImage; + + oat_dumper_options_.reset(new OatDumperOptions(args_->dump_vmap_, + args_->dump_code_info_stack_maps_, + args_->disassemble_code_, + absolute_addresses, + args_->class_filter_, + args_->method_filter_, + args_->list_classes_, + args_->list_methods_, + args_->dump_header_only_, + args_->export_dex_location_, + args_->app_image_, + args_->oat_filename_, + args_->dex_filename_, + args_->addr2instr_)); + + switch (mode) { + case OatDumpMode::kDumpImt: + case OatDumpMode::kDumpImage: + return true; + case OatDumpMode::kSymbolize: + return false; + case OatDumpMode::kDumpOat: + std::string error_msg; + if (CanDumpWithRuntime(&error_msg)) { + LOG(INFO) << "Dumping oat file with runtime"; + return true; + } else { + LOG(INFO) << ART_FORMAT("Cannot dump oat file with runtime: {}. Dumping without runtime", + error_msg); + return false; + } + } } bool ExecuteWithoutRuntime() override { CHECK(args_ != nullptr); - CHECK(args_->oat_filename_ != nullptr); + + OatDumpMode mode = args_->GetMode(); + CHECK(mode == OatDumpMode::kSymbolize || mode == OatDumpMode::kDumpOat); MemMap::Init(); - if (args_->symbolize_) { + if (mode == OatDumpMode::kSymbolize) { // ELF has special kind of section called SHT_NOBITS which allows us to create // sections which exist but their data is omitted from the ELF file to save space. // This is what "strip --only-keep-debug" does when it creates separate ELF file // with only debug data. We use it in similar way to exclude .rodata and .text. bool no_bits = args_->only_keep_debug_; - return SymbolizeOat(args_->oat_filename_, args_->dex_filename_, args_->output_name_, no_bits) - == EXIT_SUCCESS; - } else { - return DumpOat(nullptr, - args_->oat_filename_, - args_->dex_filename_, - oat_dumper_options_.get(), - args_->os_) == EXIT_SUCCESS; + return SymbolizeOat( + args_->oat_filename_, args_->dex_filename_, args_->output_name_, no_bits) == + EXIT_SUCCESS; } + + return DumpOat(nullptr, oat_dumper_options_.get(), args_->os_) == EXIT_SUCCESS; } bool ExecuteWithRuntime(Runtime* runtime) override { CHECK(args_ != nullptr); + OatDumpMode mode = args_->GetMode(); + CHECK(mode == OatDumpMode::kDumpImt || mode == OatDumpMode::kDumpImage || + mode == OatDumpMode::kDumpOat); - if (!args_->imt_dump_.empty() || args_->imt_stat_dump_) { + if (mode == OatDumpMode::kDumpImt) { return IMTDumper::Dump(runtime, args_->imt_dump_, args_->imt_stat_dump_, @@ -3368,17 +3453,50 @@ struct OatdumpMain : public CmdlineMain<OatdumpArgs> { args_->dex_filename_); } - if (args_->oat_filename_ != nullptr) { - return DumpOat(runtime, - args_->oat_filename_, - args_->dex_filename_, - oat_dumper_options_.get(), - args_->os_) == EXIT_SUCCESS; + if (mode == OatDumpMode::kDumpOat) { + return DumpOat(runtime, oat_dumper_options_.get(), args_->os_) == EXIT_SUCCESS; } return DumpImages(runtime, oat_dumper_options_.get(), args_->os_) == EXIT_SUCCESS; } + bool CanDumpWithRuntime(std::string* error_msg) { + std::unique_ptr<OatFileAssistantContext> ofa_context = + args_->GetOatFileAssistantContext(error_msg); + if (ofa_context == nullptr) { + return false; + } + + std::unique_ptr<OatFile> oat_file = + OpenOat(*oat_dumper_options_->oat_filename_, oat_dumper_options_->dex_filename_, error_msg); + if (oat_file == nullptr) { + *error_msg = ART_FORMAT( + "Failed to open oat file from '{}': {}", *oat_dumper_options_->oat_filename_, *error_msg); + return false; + } + + const std::vector<const OatDexFile*>& dex_files = oat_file->GetOatDexFiles(); + if (dex_files.empty()) { + // Dump header only. Don't need a runtime. + *error_msg = "No dex code"; + return false; + } + + OatFileAssistant oat_file_assistant(dex_files[0]->GetLocation().c_str(), + args_->instruction_set_, + /*context=*/nullptr, + /*load_executable=*/false, + /*only_load_trusted_executable=*/false, + ofa_context.get()); + + if (!oat_file_assistant.ValidateBootClassPathChecksums(*oat_file)) { + *error_msg = "BCP checksum check failed"; + return false; + } + + return true; + } + std::unique_ptr<OatDumperOptions> oat_dumper_options_; }; diff --git a/oatdump/oatdump_app_test.cc b/oatdump/oatdump_app_test.cc index 14af024db1..03b43cef11 100644 --- a/oatdump/oatdump_app_test.cc +++ b/oatdump/oatdump_app_test.cc @@ -18,11 +18,78 @@ namespace art { -TEST_P(OatDumpTest, TestDumpOatWithBootImage) { +// Oat file compiled with a boot image. oatdump invoked with a boot image. +TEST_P(OatDumpTest, TestDumpOatWithRuntimeWithBootImage) { TEST_DISABLED_FOR_RISCV64(); ASSERT_TRUE(GenerateAppOdexFile(GetParam())); + ASSERT_TRUE(Exec(GetParam(), + kArgOatApp | kArgBootImage | kArgBcp | kArgIsa, + {}, + kExpectOat | kExpectCode | kExpectBssMappingsForBcp)); +} + +// Oat file compiled without a boot image. oatdump invoked without a boot image. +TEST_P(OatDumpTest, TestDumpOatWithRuntimeWithNoBootImage) { + TEST_DISABLED_FOR_RISCV64(); + TEST_DISABLED_FOR_DEBUG_BUILD(); // DCHECK failed. + ASSERT_TRUE(GenerateAppOdexFile(GetParam(), {"--boot-image=/nonx/boot.art"})); + ASSERT_TRUE(Exec(GetParam(), + kArgOatApp | kArgBcp | kArgIsa, + {"--boot-image=/nonx/boot.art"}, + kExpectOat | kExpectCode | kExpectBssMappingsForBcp)); +} + +// Dex code cannot be found in the vdex file, and no --dex-file is specified. Dump header only. +TEST_P(OatDumpTest, TestDumpOatTryWithRuntimeDexNotFound) { + TEST_DISABLED_FOR_RISCV64(); + ASSERT_TRUE( + GenerateAppOdexFile(GetParam(), {"--dex-location=/nonx/app.jar", "--copy-dex-files=false"})); + ASSERT_TRUE(Exec(GetParam(), kArgOatApp | kArgBootImage | kArgBcp | kArgIsa, {}, kExpectOat)); +} + +// Dex code cannot be found in the vdex file, but can be found in the specified dex file. +TEST_P(OatDumpTest, TestDumpOatWithRuntimeDexSpecified) { + TEST_DISABLED_FOR_RISCV64(); + ASSERT_TRUE( + GenerateAppOdexFile(GetParam(), {"--dex-location=/nonx/app.jar", "--copy-dex-files=false"})); + ASSERT_TRUE(Exec(GetParam(), + kArgOatApp | kArgDexApp | kArgBootImage | kArgBcp | kArgIsa, + {}, + kExpectOat | kExpectCode | kExpectBssMappingsForBcp)); +} + +// Oat file compiled with a boot image. oatdump invoked without a boot image. +TEST_P(OatDumpTest, TestDumpOatWithoutRuntimeBcpMismatch) { + TEST_DISABLED_FOR_RISCV64(); + ASSERT_TRUE(GenerateAppOdexFile(GetParam())); + ASSERT_TRUE(Exec(GetParam(), + kArgOatApp | kArgBcp | kArgIsa, + {"--boot-image=/nonx/boot.art"}, + kExpectOat | kExpectCode | kExpectBssOffsetsForBcp)); +} + +// Bootclasspath not specified. +TEST_P(OatDumpTest, TestDumpOatWithoutRuntimeNoBcp) { + TEST_DISABLED_FOR_RISCV64(); + ASSERT_TRUE(GenerateAppOdexFile(GetParam())); + ASSERT_TRUE(Exec(GetParam(), kArgOatApp, {}, kExpectOat | kExpectCode | kExpectBssOffsetsForBcp)); +} + +// Dex code cannot be found in the vdex file, and no --dex-file is specified. Dump header only. +TEST_P(OatDumpTest, TestDumpOatWithoutRuntimeDexNotFound) { + TEST_DISABLED_FOR_RISCV64(); + ASSERT_TRUE( + GenerateAppOdexFile(GetParam(), {"--dex-location=/nonx/app.jar", "--copy-dex-files=false"})); + ASSERT_TRUE(Exec(GetParam(), kArgOatApp, {}, kExpectOat)); +} + +// Dex code cannot be found in the vdex file, but can be found in the specified dex file. +TEST_P(OatDumpTest, TestDumpOatWithoutRuntimeDexSpecified) { + TEST_DISABLED_FOR_RISCV64(); + ASSERT_TRUE( + GenerateAppOdexFile(GetParam(), {"--dex-location=/nonx/app.jar", "--copy-dex-files=false"})); ASSERT_TRUE(Exec( - GetParam(), kArgOatApp | kArgBootImage | kArgBcp | kArgIsa, {}, kExpectOat | kExpectCode)); + GetParam(), kArgOatApp | kArgDexApp, {}, kExpectOat | kExpectCode | kExpectBssOffsetsForBcp)); } TEST_P(OatDumpTest, TestDumpAppImageWithBootImage) { @@ -31,9 +98,21 @@ TEST_P(OatDumpTest, TestDumpAppImageWithBootImage) { const std::string app_image_arg = "--app-image-file=" + GetAppImageName(); ASSERT_TRUE(GenerateAppOdexFile(GetParam(), {app_image_arg})); ASSERT_TRUE(Exec(GetParam(), + kArgAppImage | kArgOatApp | kArgBootImage | kArgBcp | kArgIsa, + {}, + kExpectImage | kExpectOat | kExpectCode | kExpectBssMappingsForBcp)); +} + +// Deprecated usage, but checked for compatibility. +TEST_P(OatDumpTest, TestDumpAppImageWithBootImageLegacy) { + TEST_DISABLED_FOR_RISCV64(); + TEST_DISABLED_WITHOUT_BAKER_READ_BARRIERS(); // GC bug, b/126305867 + const std::string app_image_arg = "--app-image-file=" + GetAppImageName(); + ASSERT_TRUE(GenerateAppOdexFile(GetParam(), {app_image_arg})); + ASSERT_TRUE(Exec(GetParam(), kArgAppImage | kArgImage | kArgBcp | kArgIsa, {"--app-oat=" + GetAppOdexName()}, - kExpectImage | kExpectOat | kExpectCode)); + kExpectImage | kExpectOat | kExpectCode | kExpectBssMappingsForBcp)); } TEST_P(OatDumpTest, TestDumpAppImageInvalidPath) { @@ -42,10 +121,63 @@ TEST_P(OatDumpTest, TestDumpAppImageInvalidPath) { const std::string app_image_arg = "--app-image-file=" + GetAppImageName(); ASSERT_TRUE(GenerateAppOdexFile(GetParam(), {app_image_arg})); ASSERT_TRUE(Exec(GetParam(), - kArgImage | kArgBcp | kArgIsa, - {"--app-image=missing_app_image.art", "--app-oat=" + GetAppOdexName()}, + kArgOatApp | kArgBootImage | kArgBcp | kArgIsa, + {"--app-image=missing_app_image.art"}, /*expects=*/0, /*expect_failure=*/true)); } +// The runtime can start, but the boot image check should fail. +TEST_P(OatDumpTest, TestDumpAppImageWithWrongBootImage) { + TEST_DISABLED_FOR_RISCV64(); + TEST_DISABLED_WITHOUT_BAKER_READ_BARRIERS(); // GC bug, b/126305867 + const std::string app_image_arg = "--app-image-file=" + GetAppImageName(); + ASSERT_TRUE(GenerateAppOdexFile(GetParam(), {app_image_arg})); + ASSERT_TRUE(Exec(GetParam(), + kArgAppImage | kArgOatApp | kArgBcp | kArgIsa, + {"--boot-image=/nonx/boot.art"}, + /*expects=*/0, + /*expect_failure=*/true)); +} + +// Not possible. +TEST_P(OatDumpTest, TestDumpAppImageWithoutRuntime) { + TEST_DISABLED_FOR_RISCV64(); + TEST_DISABLED_WITHOUT_BAKER_READ_BARRIERS(); // GC bug, b/126305867 + const std::string app_image_arg = "--app-image-file=" + GetAppImageName(); + ASSERT_TRUE(GenerateAppOdexFile(GetParam(), {app_image_arg})); + ASSERT_TRUE(Exec(GetParam(), + kArgAppImage | kArgOatApp, + {}, + /*expects=*/0, + /*expect_failure=*/true)); +} + +// Dex code cannot be found in the vdex file, and no --dex-file is specified. Cannot dump app image. +TEST_P(OatDumpTest, TestDumpAppImageDexNotFound) { + TEST_DISABLED_FOR_RISCV64(); + TEST_DISABLED_WITHOUT_BAKER_READ_BARRIERS(); // GC bug, b/126305867 + const std::string app_image_arg = "--app-image-file=" + GetAppImageName(); + ASSERT_TRUE(GenerateAppOdexFile( + GetParam(), {app_image_arg, "--dex-location=/nonx/app.jar", "--copy-dex-files=false"})); + ASSERT_TRUE(Exec(GetParam(), + kArgAppImage | kArgOatApp | kArgBootImage | kArgBcp | kArgIsa, + {}, + /*expects=*/0, + /*expect_failure=*/true)); +} + +// Dex code cannot be found in the vdex file, but can be found in the specified dex file. +TEST_P(OatDumpTest, TestDumpAppImageDexSpecified) { + TEST_DISABLED_FOR_RISCV64(); + TEST_DISABLED_WITHOUT_BAKER_READ_BARRIERS(); // GC bug, b/126305867 + const std::string app_image_arg = "--app-image-file=" + GetAppImageName(); + ASSERT_TRUE(GenerateAppOdexFile( + GetParam(), {app_image_arg, "--dex-location=/nonx/app.jar", "--copy-dex-files=false"})); + ASSERT_TRUE(Exec(GetParam(), + kArgAppImage | kArgOatApp | kArgDexApp | kArgBootImage | kArgBcp | kArgIsa, + {}, + kExpectImage | kExpectOat | kExpectCode | kExpectBssMappingsForBcp)); +} + } // namespace art diff --git a/oatdump/oatdump_test.h b/oatdump/oatdump_test.h index ba0245a30b..2b37bef94e 100644 --- a/oatdump/oatdump_test.h +++ b/oatdump/oatdump_test.h @@ -20,12 +20,14 @@ #include <sys/types.h> #include <unistd.h> +#include <memory> #include <sstream> #include <string> #include <type_traits> #include <vector> #include "arch/instruction_set.h" +#include "base/common_art_test.h" #include "base/file_utils.h" #include "base/os.h" #include "common_runtime_test.h" @@ -49,9 +51,13 @@ class OatDumpTest : public CommonRuntimeTest, public testing::WithParamInterface if (GetParam() == Flavor::kStatic) { TEST_DISABLED_FOR_NON_STATIC_HOST_BUILDS(); } + + // Prevent boot image inference to ensure consistent test behavior. + unset_bootclasspath_ = std::make_unique<ScopedUnsetEnvironmentVariable>("BOOTCLASSPATH"); } virtual void TearDown() { + unset_bootclasspath_.reset(); ClearDirectory(tmp_dir_.c_str(), /*recursive*/ false); ASSERT_EQ(rmdir(tmp_dir_.c_str()), 0); CommonRuntimeTest::TearDown(); @@ -97,6 +103,7 @@ class OatDumpTest : public CommonRuntimeTest, public testing::WithParamInterface kArgDexBcp = 1 << 3, // --dex-file=<bcp-dex-file> kArgOatApp = 1 << 4, // --oat-file=<app-oat-file> kArgSymbolize = 1 << 5, // --symbolize=<bcp-oat-file> + kArgDexApp = 1 << 6, // --dex-file=<app-dex-file> // Runtime args. kArgBcp = 1 << 16, // --runtime-arg -Xbootclasspath:<bcp> @@ -108,6 +115,8 @@ class OatDumpTest : public CommonRuntimeTest, public testing::WithParamInterface kExpectImage = 1 << 0, kExpectOat = 1 << 1, kExpectCode = 1 << 2, + kExpectBssMappingsForBcp = 1 << 3, + kExpectBssOffsetsForBcp = 1 << 4, }; static std::string GetAppBaseName() { @@ -189,6 +198,12 @@ class OatDumpTest : public CommonRuntimeTest, public testing::WithParamInterface expected_prefixes.push_back("CODE:"); expected_prefixes.push_back("StackMap"); } + if ((expects & kExpectBssMappingsForBcp) != 0) { + expected_prefixes.push_back("Entries for BCP DexFile"); + } + if ((expects & kExpectBssOffsetsForBcp) != 0) { + expected_prefixes.push_back("Offsets for BCP DexFile"); + } std::vector<std::string> exec_argv = {file_path}; if ((args & kArgSymbolize) != 0) { @@ -223,6 +238,9 @@ class OatDumpTest : public CommonRuntimeTest, public testing::WithParamInterface if ((args & kArgOatApp) != 0) { exec_argv.push_back("--oat-file=" + GetAppOdexName()); } + if ((args & kArgDexApp) != 0) { + exec_argv.push_back("--dex-file=" + GetTestDexFileName(GetAppBaseName().c_str())); + } exec_argv.insert(exec_argv.end(), extra_args.begin(), extra_args.end()); std::vector<bool> found(expected_prefixes.size(), false); @@ -369,6 +387,7 @@ class OatDumpTest : public CommonRuntimeTest, public testing::WithParamInterface private: std::string core_art_location_; std::string core_oat_location_; + std::unique_ptr<ScopedUnsetEnvironmentVariable> unset_bootclasspath_; }; } // namespace art diff --git a/runtime/oat_file_assistant.cc b/runtime/oat_file_assistant.cc index 9848a78cbd..47a3b341bc 100644 --- a/runtime/oat_file_assistant.cc +++ b/runtime/oat_file_assistant.cc @@ -32,6 +32,7 @@ #include "base/array_ref.h" #include "base/compiler_filter.h" #include "base/file_utils.h" +#include "base/globals.h" #include "base/logging.h" // For VLOG. #include "base/macros.h" #include "base/os.h" @@ -200,7 +201,9 @@ OatFileAssistant::OatFileAssistant(const char* dex_location, vdex_for_oat_.Reset(vdex_file_name, UseFdToReadFiles(), zip_fd, vdex_fd, oat_fd); std::string dm_file_name = GetDmFilename(dex_location); dm_for_oat_.Reset(dm_file_name, UseFdToReadFiles(), zip_fd, vdex_fd, oat_fd); - } else { + } else if (kIsTargetAndroid) { + // No need to warn on host. We are probably in oatdump, where we only need OatFileAssistant to + // validate BCP checksums. LOG(WARNING) << "Failed to determine oat file name for dex location " << dex_location_ << ": " << error_msg; } |