/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "oat_file.h"
#include <dlfcn.h>
#include <algorithm>
#include <cstring>
#include <memory>
#include <string>
#include <string_view>
#include "android-base/scopeguard.h"
#include "base/file_utils.h"
#include "base/os.h"
#include "common_runtime_test.h"
#include "dexopt_test.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "scoped_thread_state_change-inl.h"
#include "vdex_file.h"
namespace art HIDDEN {
using ::testing::HasSubstr;
using std::string_view_literals::
operator""sv;
// Returns the offset of the first dex file in the vdex file.
static void GetFirstDexFileOffset(
const std::string& vdex_filename,
/*out*/ size_t* offset) {
std::string error_msg;
std::unique_ptr<VdexFile> vdex_file =
VdexFile::Open(vdex_filename,
/*low_4gb=*/false, &error_msg);
ASSERT_NE(vdex_file, nullptr) << error_msg;
const uint8_t* ptr = vdex_file->GetNextDexFileData(
/*cursor=*/nullptr, /*dex_file_index=*/0);
ASSERT_NE(ptr, nullptr) <<
"No dex code in vdex";
ASSERT_GE(ptr, vdex_file->Begin());
ASSERT_LT(ptr, vdex_file->End());
*offset = ptr - vdex_file->Begin();
}
class OatFileTest :
public DexoptTest {};
TEST_F(OatFileTest, LoadOat) {
std::string dex_location = GetScratchDir() +
"/LoadOat.jar";
Copy(GetDexSrc1(), dex_location);
GenerateOatForTest(dex_location.c_str(), CompilerFilter::kSpeed);
std::string oat_location;
std::string error_msg;
ASSERT_TRUE(OatFileAssistant::DexLocationToOatFilename(
dex_location, kRuntimeISA, &oat_location, &error_msg))
<< error_msg;
std::unique_ptr<OatFile> odex_file(OatFile::Open(
/*zip_fd=*/-1,
oat_location,
oat_location,
/*executable=*/false,
/*low_4gb=*/false,
dex_location,
&error_msg));
ASSERT_TRUE(odex_file.get() != nullptr);
}
TEST_F(OatFileTest, ChangingMultiDexUncompressed) {
std::string dex_location = GetScratchDir() +
"/MultiDexUncompressedAligned.jar";
Copy(GetTestDexFileName(
"MultiDexUncompressedAligned"), dex_location);
GenerateOatForTest(dex_location.c_str(), CompilerFilter::kVerify);
std::string oat_location;
std::string error_msg;
ASSERT_TRUE(OatFileAssistant::DexLocationToOatFilename(
dex_location, kRuntimeISA, &oat_location, &error_msg))
<< error_msg;
// Ensure we can load that file. Just a precondition.
{
std::unique_ptr<OatFile> odex_file(OatFile::Open(
/*zip_fd=*/-1,
oat_location,
oat_location,
/*executable=*/false,
/*low_4gb=*/false,
dex_location,
&error_msg));
ASSERT_TRUE(odex_file != nullptr);
ASSERT_EQ(
2u, odex_file->GetOatDexFiles().size());
}
// Now replace the source.
Copy(GetTestDexFileName(
"MainUncompressedAligned"), dex_location);
// And try to load again.
std::unique_ptr<OatFile> odex_file(OatFile::Open(
/*zip_fd=*/-1,
oat_location,
oat_location,
/*executable=*/false,
/*low_4gb=*/false,
dex_location,
&error_msg));
EXPECT_TRUE(odex_file == nullptr);
EXPECT_NE(std::string::npos, error_msg.find(
"expected 2 uncompressed dex files, but found 1"))
<< error_msg;
}
TEST_F(OatFileTest, DlOpenLoad) {
std::string dex_location = GetScratchDir() +
"/LoadOat.jar";
Copy(GetDexSrc1(), dex_location);
GenerateOatForTest(dex_location.c_str(), CompilerFilter::kSpeed);
std::string oat_location;
std::string error_msg;
ASSERT_TRUE(OatFileAssistant::DexLocationToOatFilename(
dex_location, kRuntimeISA, &oat_location, &error_msg))
<< error_msg;
// Clear previous errors if any.
dlerror();
error_msg.clear();
std::unique_ptr<OatFile> odex_file(OatFile::Open(
/*zip_fd=*/-1,
oat_location,
oat_location,
/*executable=*/true,
/*low_4gb=*/false,
dex_location,
&error_msg));
ASSERT_NE(odex_file.get(), nullptr) << error_msg;
#ifdef __GLIBC__
if (!error_msg.empty()) {
// If a valid oat file was returned but there was an error message, then dlopen failed
// but the backup ART ELF loader successfully loaded the oat file.
// There are a few expected reasons for this:
// - a bug in glibc that prevents loading dynamic shared objects with a read-only dynamic
// section https://sourceware.org/bugzilla/show_bug.cgi?id=28340.
// - glibc >= 2.41 fails to dlopen shared objects that don't have GNU_STACK segment,
// see https://lists.gnu.org/archive/html/info-gnu/2025-01/msg00014.html
ASSERT_TRUE(
error_msg ==
"DlOpen does not support read-only .dynamic section." ||
error_msg ==
"Failed to dlopen '" + oat_location +
"': " + oat_location +
": cannot enable executable stack as shared object requires: Invalid argument")
<< error_msg;
GTEST_SKIP() << error_msg;
}
#else
// If a valid oat file was returned with no error message, then dlopen was successful.
ASSERT_TRUE(error_msg.empty()) << error_msg;
#endif
const char *dlerror_msg = dlerror();
ASSERT_EQ(dlerror_msg, nullptr) << dlerror_msg;
// Ensure that the oat file is loaded with dlopen by requesting information about it
// using dladdr.
Dl_info info;
ASSERT_NE(dladdr(odex_file->Begin(), &info),
0);
EXPECT_STREQ(info.dli_fname, oat_location.c_str())
<<
"dli_fname: " << info.dli_fname
<<
", location: " << oat_location;
EXPECT_STREQ(info.dli_sname,
"oatdata") << info.dli_sname;
}
TEST_F(OatFileTest, RejectsCdex) {
std::string dex_location = GetScratchDir() +
"/LoadOat.jar";
std::string odex_location = GetScratchDir() +
"/LoadOat.odex";
std::string vdex_location = GetVdexFilename(odex_location);
Copy(GetDexSrc1(), dex_location);
ASSERT_NO_FATAL_FAILURE(GenerateOdexForTest(dex_location, odex_location, CompilerF
ilter::kSpeed));
// Patch the generated vdex file to simulate that it contains cdex.
{
size_t dex_offset;
ASSERT_NO_FATAL_FAILURE(GetFirstDexFileOffset(vdex_location, &dex_offset));
std::unique_ptr<File> vdex_file(OS::OpenFileReadWrite(vdex_location.c_str()));
ASSERT_NE(vdex_file, nullptr) << strerror(errno);
auto cleanup = android::base::make_scope_guard([&] { (void)vdex_file->FlushClose(); });
constexpr std::string_view kCdexMagic = "cdex001\0"sv;
ASSERT_LE(dex_offset + kCdexMagic.size(), vdex_file->GetLength()) << "Dex file too short";
bool success = vdex_file->PwriteFully(kCdexMagic.data(), kCdexMagic.size(), dex_offset);
ASSERT_TRUE(success) << strerror(errno);
cleanup.Disable();
ASSERT_EQ(vdex_file->FlushClose(), 0);
}
// Create `OatFile` from the vdex file together with the oat file. This should fail.
{
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));
EXPECT_EQ(odex_file, nullptr) << "Cdex accepted unexpectedly";
EXPECT_THAT(error_msg, HasSubstr("invalid dex file magic"));
}
// Create `OatFile` from the vdex file alone. This should fail too.
{
std::string error_msg;
std::unique_ptr<VdexFile> vdex_file =
VdexFile::Open(vdex_location, /*low_4gb=*/false, &error_msg);
ASSERT_NE(vdex_file, nullptr);
std::unique_ptr<OatFile> odex_file(OatFile::OpenFromVdex(/*zip_fd=*/-1,
std::move(vdex_file),
vdex_location,
/*context=*/nullptr,
kReasonVdex,
&error_msg));
EXPECT_EQ(odex_file, nullptr) << "Cdex accepted unexpectedly";
EXPECT_THAT(error_msg, HasSubstr("found dex file with invalid dex file version"));
}
}
} // namespace art