summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Jiakai Zhang <jiakaiz@google.com> 2023-07-31 11:01:45 +0100
committer Jiakai Zhang <jiakaiz@google.com> 2023-08-02 15:18:55 +0000
commit0187b188b3f992362dfba829dcba11205e774608 (patch)
treea27df4cc2adfed7a4cc65e62ba8d893a69a400da
parent0e09c653ca085043e910e799358bd96ae16a0879 (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.h241
-rw-r--r--libartbase/base/common_art_test.h5
-rw-r--r--libartbase/base/file_utils.cc32
-rw-r--r--libartbase/base/file_utils.h13
-rw-r--r--oatdump/oatdump.cc414
-rw-r--r--oatdump/oatdump_app_test.cc142
-rw-r--r--oatdump/oatdump_test.h19
-rw-r--r--runtime/oat_file_assistant.cc5
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;
}