using ::android::base::Result; using ::android::base::StringPrintf; using ::android::base::testing::HasValue; using ::android::base::testing::Ok; using ::testing::AssertionFailure; using ::testing::AssertionResult; using ::testing::AssertionSuccess; using ::testing::Ne; using ::testing::Not;
class Dex2oatTest : public Dex2oatEnvironmentTest { public: enumclass Status { kFailCompile, kFailOpenOat, kSuccess };
// Unless otherwise stated, use a small amount of threads, so that potential aborts are // shorter. This can be overridden with extra_args.
args.push_back("-j4");
int status = OR_RETURN(Dex2Oat(args, &output_)); if (oat_file != nullptr) { int fc_errno = oat_file->FlushClose(); if (fc_errno != 0) { return Errorf( "Could not flush and close oat file {}: {}", odex_location, strerror(-fc_errno));
}
} return status;
}
// Verify the odex file was generated as expected.
std::string error_msg;
std::unique_ptr<OatFile> odex_file(OatFile::Open(/*zip_fd=*/-1,
odex_location,
odex_location, /*executable=*/false, /*low_4gb=*/false,
dex_location,
&error_msg));
if (expect_status == Status::kFailOpenOat) { return (odex_file == nullptr) ?
AssertionSuccess() :
AssertionFailure() << "Unexpectedly was able to open odex file";
}
if (odex_file == nullptr) { return AssertionFailure() << "Could not open odex file: " << error_msg;
}
CheckFilter(filter, odex_file->GetCompilerFilter());
check_oat(*(odex_file.get()));
} else { if (success) { return AssertionFailure() << "Succeeded to compile odex: " << output_;
}
// Check the input compiler filter against the generated oat file's filter. May be overridden // in subclasses when equality is not expected. virtualvoid CheckFilter(CompilerFilter::Filter expected, CompilerFilter::Filter actual) {
EXPECT_EQ(expected, actual);
}
std::string output_ = "";
};
// This test class provides an easy way to validate an expected filter which is different // then the one pass to generate the odex file (compared to adding yet another argument // to what's already huge test methods). class Dex2oatWithExpectedFilterTest : public Dex2oatTest { protected: void CheckFilter([[maybe_unused]] CompilerFilter::Filter expected,
CompilerFilter::Filter actual) override {
EXPECT_EQ(expected_filter_, actual);
}
virtualvoid CheckTargetResult([[maybe_unused]] bool expect_use) { // TODO: Ignore for now, as we won't capture any output (it goes to the logcat). We may do // something for variants with file descriptor where we can control the lifetime of // the swap file and thus take a look at it.
}
virtualvoid CheckHostResult(bool expect_use) { if (!kIsTargetBuild) { if (expect_use) {
EXPECT_NE(output_.find("Large app, accepted running with swap."), std::string::npos)
<< output_;
} else {
EXPECT_EQ(output_.find("Large app, accepted running with swap."), std::string::npos)
<< output_;
}
}
}
// Check whether the dex2oat run was really successful. virtualvoid CheckValidity() { if (kIsTargetBuild) {
CheckTargetValidity();
} else {
CheckHostValidity();
}
}
virtualvoid CheckTargetValidity() { // TODO: Ignore for now, as we won't capture any output (it goes to the logcat). We may do // something for variants with file descriptor where we can control the lifetime of // the swap file and thus take a look at it.
}
// On the host, we can get the dex2oat output. Here, look for "dex2oat took." virtualvoid CheckHostValidity() {
EXPECT_NE(output_.find("dex2oat took"), std::string::npos) << output_;
}
};
class Dex2oatSwapUseTest : public Dex2oatSwapTest { protected: void CheckHostResult(bool expect_use) override { if (!kIsTargetBuild) { if (expect_use) {
EXPECT_NE(output_.find("Large app, accepted running with swap."), std::string::npos)
<< output_;
} else {
EXPECT_EQ(output_.find("Large app, accepted running with swap."), std::string::npos)
<< output_;
}
}
}
std::string GetTestDexFileName() override { // Use Statics as it has a handful of functions. return CommonRuntimeTest::GetTestDexFileName("Statics");
}
TEST_F(Dex2oatSwapUseTest, CheckSwapUsage) { // Native memory usage isn't correctly tracked when running under ASan.
TEST_DISABLED_FOR_MEMORY_TOOL();
// The `native_alloc_2_ >= native_alloc_1_` assertion below may not // hold true on some x86 or x86_64 systems; disable this test while we // investigate (b/29259363).
TEST_DISABLED_FOR_X86();
TEST_DISABLED_FOR_X86_64();
void CheckResult(const std::string& dex_location, const std::string& odex_location, const std::string& app_image_file,
CompilerFilter::Filter expected_filter, bool expect_large, bool expect_downgrade) { if (expect_downgrade) {
EXPECT_TRUE(expect_large);
} // Host/target independent checks.
std::string error_msg;
std::unique_ptr<OatFile> odex_file(OatFile::Open(/*zip_fd=*/-1,
odex_location,
odex_location, /*executable=*/false, /*low_4gb=*/false,
dex_location,
&error_msg));
ASSERT_TRUE(odex_file.get() != nullptr) << error_msg;
EXPECT_GT(app_image_file.length(), 0u);
std::unique_ptr<File> file(OS::OpenFileForReading(app_image_file.c_str())); if (expect_large) { // Note: we cannot check the following // EXPECT_FALSE(CompilerFilter::IsAotCompilationEnabled(odex_file->GetCompilerFilter())); // The reason is that the filter override currently happens when the dex files are // loaded in dex2oat, which is after the oat file has been started. Thus, the header // store cannot be changed, and the original filter is set in stone.
void CheckTargetResult([[maybe_unused]] bool expect_downgrade) { // TODO: Ignore for now. May do something for fd things.
}
void CheckHostResult(bool expect_downgrade) { if (!kIsTargetBuild) { if (expect_downgrade) {
EXPECT_NE(output_.find("Very large app, downgrading to"), std::string::npos) << output_;
} else {
EXPECT_EQ(output_.find("Very large app, downgrading to"), std::string::npos) << output_;
}
}
}
// Check whether the dex2oat run was really successful. void CheckValidity() { if (kIsTargetBuild) {
CheckTargetValidity();
} else {
CheckHostValidity();
}
}
void CheckTargetValidity() { // TODO: Ignore for now.
}
// On the host, we can get the dex2oat output. Here, look for "dex2oat took." void CheckHostValidity() {
EXPECT_NE(output_.find("dex2oat took"), std::string::npos) << output_;
}
};
// Regressin test for b/35665292.
TEST_F(Dex2oatVeryLargeTest, SpeedProfileNoProfile) { // Test that dex2oat doesn't crash with speed-profile but no input profile.
RunTest(CompilerFilter::kSpeedProfile, CompilerFilter::kVerify, false, false);
}
class Dex2oatLayoutTest : public Dex2oatTest { protected: void CheckFilter([[maybe_unused]] CompilerFilter::Filter input,
[[maybe_unused]] CompilerFilter::Filter result) override { // Ignore, we'll do our own checks.
}
// Emits a profile with a single dex file with the given location and classes ranging // from `class_offset` to `class_offset + num_classes`. void GenerateProfile(const std::string& test_profile, const std::string& dex_location,
size_t num_classes,
size_t class_offset = 0) { constchar* location = dex_location.c_str();
std::string error_msg;
std::vector<std::unique_ptr<const DexFile>> dex_files;
ArtDexFileLoader dex_file_loader(location);
ASSERT_TRUE(dex_file_loader.Open( /*verify=*/true, /*verify_checksum=*/true, &error_msg, &dex_files));
EXPECT_EQ(dex_files.size(), 1U);
std::unique_ptr<const DexFile>& dex_file = dex_files[0];
uint32_t image_file_empty_profile = 0; if (app_image) {
CompileProfileOdex(dex_location,
odex_location,
app_image_file, /*use_fd=*/false, /*num_profile_classes=*/0);
CheckValidity(); // Don't check the result since CheckResult relies on the class being in the profile.
image_file_empty_profile = GetImageObjectSectionSize(app_image_file);
EXPECT_GT(image_file_empty_profile, 0u);
CheckCompilerFilter(dex_location, odex_location, CompilerFilter::Filter::kVerify);
}
if (app_image) { // Test that the profile made a difference by adding more classes. const uint32_t image_file_small_profile = GetImageObjectSectionSize(app_image_file);
ASSERT_LT(image_file_empty_profile, image_file_small_profile);
}
}
if (!app_image_file_name.empty()) { // Go peek at the image header to make sure it was large enough to contain the class.
std::unique_ptr<File> file(OS::OpenFileForReading(app_image_file_name.c_str()));
ImageHeader image_header; bool success = file->ReadFully(&image_header, sizeof(image_header));
ASSERT_TRUE(success);
ASSERT_TRUE(image_header.IsValid());
EXPECT_GT(image_header.GetObjectsSection().Size(), 0u);
}
}
// Check whether the dex2oat run was really successful. void CheckValidity() { if (kIsTargetBuild) {
CheckTargetValidity();
} else {
CheckHostValidity();
}
}
void CheckTargetValidity() { // TODO: Ignore for now.
}
// On the host, we can get the dex2oat output. Here, look for "dex2oat took." void CheckHostValidity() {
EXPECT_NE(output_.find("dex2oat took"), std::string::npos) << output_;
}
};
// The image file generated with multiple profiles should be larger than any image file generated // with each profile.
ASSERT_GT(image_file_size_multiple_profiles, image_file_size_profile1);
ASSERT_GT(image_file_size_multiple_profiles, image_file_size_profile2);
}
// Create two profiles whose dex locations are the same but checksums are different.
Copy(GetDexSrc1(), dex_location); const std::string profile_old = GetScratchDir() + "/profile_old.prof";
GenerateProfile(profile_old, dex_location, /*num_classes=*/1, /*class_offset=*/0);
// This should produce a normal image because only `profile_new` is used and it has the right // checksum.
CompileProfileOdex(dex_location,
odex_location,
app_image_file, /*use_fd=*/false,
{profile_new, profile_old});
uint32_t image_size_right_checksum = GetImageObjectSectionSize(app_image_file);
// This should produce an empty image because only `profile_old` is used and it has the wrong // checksum. Note that dex2oat does not abort compilation when the profile verification fails // (b/62602192, b/65260586).
CompileProfileOdex(dex_location,
odex_location,
app_image_file, /*use_fd=*/false,
{profile_old, profile_new});
uint32_t image_size_wrong_checksum = GetImageObjectSectionSize(app_image_file);
// Create an empty image using an empty profile for reference.
CompileProfileOdex(dex_location,
odex_location,
app_image_file, /*use_fd=*/false,
{profile_empty});
uint32_t image_size_empty = GetImageObjectSectionSize(app_image_file);
TEST_F(Dex2oatWatchdogTest, TestWatchdogOK) { // Check with default.
RunTest(/*expect_status=*/Status::kSuccess);
// Check with ten minutes.
RunTest(/*expect_status=*/Status::kSuccess, {"--watchdog-timeout=600000"});
}
TEST_F(Dex2oatWatchdogTest, TestWatchdogTrigger) { // This test is frequently interrupted by signal_dumper on host (x86); // disable it while we investigate (b/121352534).
TEST_DISABLED_FOR_X86();
// The watchdog is independent of dex2oat and will not delete intermediates. It is possible // that the compilation succeeds and the file is completely written by the time the watchdog // kills dex2oat (but the dex2oat threads must have been scheduled pretty badly).
test_accepts_odex_file_on_failure = true;
// Check with ten milliseconds.
RunTest(/*expect_status=*/Status::kFailCompile, {"--watchdog-timeout=10"});
}
TEST_F(Dex2oatTest, MissingBootImageTest) {
std::string out_dir = GetScratchDir(); const std::string base_oat_name = out_dir + "/base.oat"; // The compilation should succeed even without the boot image.
ASSERT_TRUE(GenerateOdexForTest(
{GetTestDexFileName("MainUncompressedAligned")},
base_oat_name,
CompilerFilter::Filter::kVerify, // Note: Extra options go last and the second `--boot-image` option overrides the first.
{"--boot-image=/nonx/boot.art"}));
}
TEST_F(Dex2oatTest, EmptyUncompressedDexTest) {
std::string out_dir = GetScratchDir(); const std::string base_oat_name = out_dir + "/base.oat"; // Expect to fail with code 1 and not SIGSEGV or SIGABRT.
EXPECT_THAT(GenerateOdexForTestWithStatus({GetTestDexFileName("MainEmptyUncompressed")},
base_oat_name,
CompilerFilter::Filter::kVerify, /*extra_args*/ {}, /*use_fd*/ false),
HasValue(1));
}
TEST_F(Dex2oatTest, EmptyUncompressedAlignedDexTest) {
std::string out_dir = GetScratchDir(); const std::string base_oat_name = out_dir + "/base.oat"; // Expect to fail with code 1 and not SIGSEGV or SIGABRT.
EXPECT_THAT(GenerateOdexForTestWithStatus({GetTestDexFileName("MainEmptyUncompressedAligned")},
base_oat_name,
CompilerFilter::Filter::kVerify, /*extra_args*/ {}, /*use_fd*/ false),
HasValue(1));
}
// Test file doesn't matter.
Copy(GetDexSrc1(), dex_location);
ASSERT_TRUE(GenerateOdexForTest(dex_location,
odex_location,
CompilerFilter::kVerify,
{"--runtime-arg", "-Xuse-stderr-logger"}, /*expect_status=*/Status::kSuccess)); // Look for some random part of dex2oat logging. With the stderr logger this should be captured, // even on device.
EXPECT_NE(std::string::npos, output_.find("dex2oat took"));
}
// Check that the vdex doesn't have dex code.
std::unique_ptr<VdexFile> vdex(VdexFile::Open(vdex_location, /*low_4gb=*/false,
&error_msg));
ASSERT_TRUE(vdex != nullptr);
EXPECT_FALSE(vdex->HasDexSection()) << output_;
}
TEST_F(Dex2oatTest, AppImageResolveStrings) { using Hotness = ProfileCompilationInfo::MethodHotness; // Create a profile with the startup method marked.
ScratchFile profile_file;
ScratchFile temp_dex; const std::string& dex_location = temp_dex.GetFilename();
std::vector<uint16_t> methods;
std::vector<dex::TypeIndex> classes;
{
MutateDexFile(
temp_dex.GetFile(), GetTestDexFileName("StringLiterals"), [&](DexFile* dex) { bool mutated_successfully = false; // Change the dex instructions to make an opcode that spans past the end of the code item. for (ClassAccessor accessor : dex->GetClasses()) { if (accessor.GetDescriptorView() == "LStringLiterals$StartupClass;") {
classes.push_back(accessor.GetClassIdx());
} for (const ClassAccessor::Method& method : accessor.GetMethods()) {
std::string method_name(dex->GetMethodName(dex->GetMethodId(method.GetIndex())));
CodeItemInstructionAccessor instructions = method.GetInstructions(); if (method_name == "startUpMethod2") { // Make an instruction that runs past the end of the code item and verify that it // doesn't cause dex2oat to crash.
ASSERT_TRUE(instructions.begin() != instructions.end());
DexInstructionIterator last_instruction = instructions.begin(); for (auto dex_it = instructions.begin(); dex_it != instructions.end(); ++dex_it) {
last_instruction = dex_it;
}
ASSERT_EQ(last_instruction->SizeInCodeUnits(), 1u); // Set the opcode to something that will go past the end of the code item. const_cast<Instruction&>(last_instruction.Inst())
.SetOpcode(Instruction::CONST_STRING_JUMBO);
mutated_successfully = true;
methods.push_back(method.GetIndex());
mutated_successfully = true;
} elseif (method_name == "startUpMethod") {
methods.push_back(method.GetIndex());
}
}
}
CHECK(mutated_successfully)
<< "Failed to find candidate code item with only one code unit in last instruction.";
});
}
std::unique_ptr<const DexFile> dex_file(OpenDexFile(temp_dex.GetFilename().c_str()));
{
ASSERT_GT(classes.size(), 0u);
ASSERT_GT(methods.size(), 0u); // Here, we build the profile from the method lists.
ProfileCompilationInfo info;
info.AddClassesForDex(dex_file.get(), classes.begin(), classes.end());
info.AddMethodsForDex(Hotness::kFlagStartup, dex_file.get(), methods.begin(), methods.end()); // Save the profile since we want to use it with dex2oat to produce an oat file.
ASSERT_TRUE(info.Save(profile_file.GetFd()));
} const std::string out_dir = GetScratchDir(); const std::string odex_location = out_dir + "/base.odex"; const std::string app_image_location = out_dir + "/base.art";
ASSERT_TRUE(GenerateOdexForTest(dex_location,
odex_location,
CompilerFilter::Filter::kSpeedProfile,
{"--app-image-file=" + app_image_location, "--resolve-startup-const-strings=true", "--profile-file=" + profile_file.GetFilename()}, /*expect_status=*/Status::kSuccess, /*use_fd=*/false, /*use_zip_fd=*/false,
[](const OatFile&) {})); // Open our generated oat file.
std::string error_msg;
std::unique_ptr<OatFile> odex_file(OatFile::Open(/*zip_fd=*/-1,
odex_location,
odex_location, /*executable=*/false, /*low_4gb=*/false,
&error_msg));
ASSERT_TRUE(odex_file != nullptr); // Check the strings in the app image intern table only contain the "startup" strigs.
{
std::unique_ptr<gc::space::ImageSpace> space = gc::space::ImageSpace::CreateFromAppImage(
app_image_location.c_str(), odex_file.get(), &error_msg);
ASSERT_TRUE(space != nullptr) << error_msg;
ScopedObjectAccess soa(Thread::Current());
std::set<std::string> seen;
InternTable intern_table;
intern_table.AddImageStringsToTable(
space.get(), [&](InternTable::UnorderedSet& interns) REQUIRES_SHARED(Locks::mutator_lock_) { for (const GcRoot<mirror::String>& str : interns) {
seen.insert(str.Read()->ToModifiedUtf8());
}
}); // Normal methods
EXPECT_TRUE(seen.find("Loading ") != seen.end());
EXPECT_TRUE(seen.find("Starting up") != seen.end());
EXPECT_TRUE(seen.find("abcd.apk") != seen.end());
EXPECT_TRUE(seen.find("Unexpected error") == seen.end());
EXPECT_TRUE(seen.find("Shutting down!") == seen.end()); // Classes initializers
EXPECT_TRUE(seen.find("Startup init") != seen.end());
EXPECT_TRUE(seen.find("Other class init") == seen.end());
// Verify what strings are marked as boot image.
std::set<std::string> boot_image_strings;
std::set<std::string> app_image_strings;
// Check that we can open the oat file as executable.
{
std::string error_msg;
std::unique_ptr<OatFile> odex_file(OatFile::Open(/*zip_fd=*/-1,
base_oat_name,
base_oat_name, /*executable=*/true, /*low_4gb=*/false,
dex->GetLocation(),
&error_msg));
ASSERT_TRUE(odex_file != nullptr) << error_msg;
}
// Rewrite the oat file with wrong version and bogus contents.
{
std::unique_ptr<File> file(OS::OpenFileReadWrite(base_oat_name.c_str()));
ASSERT_TRUE(file != nullptr); // Retrieve the offset and size of the embedded oat file.
size_t oatdata_offset;
size_t oatdata_size;
{
std::string error_msg;
std::unique_ptr<ElfFile> elf_file(ElfFile::Open(file.get(), /*low_4gb=*/false,
&error_msg));
ASSERT_TRUE(elf_file != nullptr) << error_msg;
ASSERT_TRUE(elf_file->Load(/*executable=*/false, /*low_4gb=*/false, /*reservation=*/nullptr,
&error_msg))
<< error_msg; const uint8_t* base_address = elf_file->GetBaseAddress(); const uint8_t* oatdata = elf_file->FindDynamicSymbolAddress("oatdata");
ASSERT_TRUE(oatdata != nullptr);
ASSERT_TRUE(oatdata > base_address); // Note: We're assuming here that the virtual address offset is the same // as file offset. This is currently true for all oat files we generate.
oatdata_offset = static_cast<size_t>(oatdata - base_address); const uint8_t* oatlastword = elf_file->FindDynamicSymbolAddress("oatlastword");
ASSERT_TRUE(oatlastword != nullptr);
ASSERT_TRUE(oatlastword > oatdata);
oatdata_size = oatlastword - oatdata;
}
// Check that we have the right `oatdata_offset`.
int64_t length = file->GetLength();
ASSERT_GE(length, static_cast<ssize_t>(oatdata_offset + sizeof(OatHeader)));
alignas(OatHeader) uint8_t header_data[sizeof(OatHeader)];
ASSERT_TRUE(file->PreadFully(header_data, sizeof(header_data), oatdata_offset)); const OatHeader& header = reinterpret_cast<const OatHeader&>(header_data);
ASSERT_TRUE(header.IsValid()) << header.GetValidationErrorMessage();
// Overwrite all oat data from version onwards with bytes with value 4. // (0x04040404 is not a valid version, we're using three decimal digits and '\0'.) // // We previously tried to find the value for key "debuggable" (bug 179221298) // in the key-value store before checking the oat header. This test tries to // ensure that such early processing of the key-value store shall crash. // Reading 0x04040404 as the size of the key-value store yields a bit over // 64MiB which should hopefully include some unmapped memory beyond the end // of the loaded oat file. Overwriting the whole embedded oat file ensures // that we do not match the key within the oat file but we could still // accidentally match it in the additional sections of the elf file, so this // approach could fail to catch similar issues. At the time of writing, this // test crashed when run without the fix on 64-bit host (but not 32-bit). static constexpr size_t kVersionOffset = sizeof(OatHeader::kOatMagic);
static_assert(kVersionOffset < sizeof(OatHeader));
std::vector<uint8_t> data(oatdata_size - kVersionOffset, 4u);
ASSERT_TRUE(file->PwriteFully(data.data(), data.size(), oatdata_offset + kVersionOffset));
UNUSED(oatdata_size);
CHECK_EQ(file->FlushClose(), 0) << "Could not flush and close oat file";
}
// Check that we reject the oat file without crashing.
{
std::string error_msg;
std::unique_ptr<OatFile> odex_file(OatFile::Open(/*zip_fd=*/-1,
base_oat_name,
base_oat_name, /*executable=*/true, /*low_4gb=*/false,
dex->GetLocation(),
&error_msg));
ASSERT_FALSE(odex_file != nullptr);
}
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.