dvm,art模式下的dex文件加载流程
dex加载是学习android的重中之重,刚看完几篇参考博客,对应android源码,收益匪浅,用一篇博客总结一下自己学到的东西。
1.dvm模式下的dex加载
这方面的资料大概已经很多了,小弟水平有限,暂时引用两篇大佬的文章
然后,应该就懂了为什么之前的加固方式的脱壳关键函数在dvmDexFileOpenPartial(...),mmap(...);
2.art模式下的dex加载
这里我们拿dex动态加载举例,动态加载dex我们直接通过DexClassLoder实现
public DexClassLoader(String dexPath, String optimizedDirectory,
String libraryPath, ClassLoader parent) {
super(dexPath, new File(optimizedDirectory), libraryPath, parent);
}
dexpath我们需要加载的dex文件的路径;optimizedDirectory是我们将dex进行优化后存储的路径
public BaseDexClassLoader(String dexPath, File optimizedDirectory,
String libraryPath, ClassLoader parent) {
super(parent);
this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
}
public DexPathList(ClassLoader definingContext, String dexPath,
String libraryPath, File optimizedDirectory) {
if (definingContext == null) {
throw new NullPointerException("definingContext == null");
}
if (dexPath == null) {
throw new NullPointerException("dexPath == null");
}
if (optimizedDirectory != null) {
if (!optimizedDirectory.exists()) {
throw new IllegalArgumentException(
"optimizedDirectory doesn't exist: "
+ optimizedDirectory);
}
if (!(optimizedDirectory.canRead()
&& optimizedDirectory.canWrite())) {
throw new IllegalArgumentException(
"optimizedDirectory not readable/writable: "
+ optimizedDirectory);
}
}
this.definingContext = definingContext;
ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
suppressedExceptions);
if (suppressedExceptions.size() > 0) {
this.dexElementsSuppressedExceptions =
suppressedExceptions.toArray(new IOException[suppressedExceptions.size()]);
} else {
dexElementsSuppressedExceptions = null;
}
this.nativeLibraryDirectories = splitLibraryPath(libraryPath);
}
//贴上全部代码方便之后查看
这里就先后校验我们dexpath是否为空,以及optimizedDirectory是否合法以及可读,这里makeDexElements(…)函数是我们要关注的地方。
private static Element[] makeDexElements(ArrayList<File> files, File optimizedDirectory,
ArrayList<IOException> suppressedExceptions) {
ArrayList<Element> elements = new ArrayList<Element>();
/*
* Open all files and load the (direct or contained) dex files
* up front.
*/
for (File file : files) {
File zip = null;
DexFile dex = null;
String name = file.getName();
if (file.isDirectory()) {
// We support directories for looking up resources.
// This is only useful for running libcore tests.
elements.add(new Element(file, true, null, null));
} else if (file.isFile()){
if (name.endsWith(DEX_SUFFIX)) {
// Raw dex file (not inside a zip/jar).
try {
dex = loadDexFile(file, optimizedDirectory);
} catch (IOException ex) {
System.logE("Unable to load dex file: " + file, ex);
}
} else {
zip = file;
try {
dex = loadDexFile(file, optimizedDirectory);
} catch (IOException suppressed) {
/*
* IOException might get thrown "legitimately" by the DexFile constructor if
* the zip file turns out to be resource-only (that is, no classes.dex file
* in it).
* Let dex == null and hang on to the exception to add to the tea-leaves for
* when findClass returns null.
*/
suppressedExceptions.add(suppressed);
}
}
} else {
System.logW("ClassLoader referenced unknown path: " + file);
}
if ((zip != null) || (dex != null)) {
elements.add(new Element(file, false, zip, dex));
}
}
return elements.toArray(new Element[elements.size()]);
}
很明显这里重要的函数有两个loadDexFile( … ),elements.add( … );后一个方法类似存了一个源文件(.dex,.zip,.apk…)与优化后dex存了类似键值对的数据结构。
我们跟进 loadDexFile( … )
private static DexFile loadDexFile(File file, File optimizedDirectory)
throws IOException {
if (optimizedDirectory == null) {
return new DexFile(file);
} else {
String optimizedPath = optimizedPathFor(file, optimizedDirectory);
return DexFile.loadDex(file.getPath(), optimizedPath, 0);
}
}
跟进 DexFile.loadDex( … )
static public DexFile loadDex(String sourcePathName, String outputPathName,
int flags) throws IOException {
/*
* TODO: we may want to cache previously-opened DexFile objects.
* The cache would be synchronized with close(). This would help
* us avoid mapping the same DEX more than once when an app
* decided to open it multiple times. In practice this may not
* be a real issue.
*/
return new DexFile(sourcePathName, outputPathName, flags);
}
private DexFile(String sourceName, String outputName, int flags) throws IOException {
if (outputName != null) {
try {
String parent = new File(outputName).getParent();
if (Libcore.os.getuid() != Libcore.os.stat(parent).st_uid) {
throw new IllegalArgumentException("Optimized data directory " + parent
+ " is not owned by the current user. Shared storage cannot protect"
+ " your application from code injection attacks.");
}
} catch (ErrnoException ignored) {
// assume we'll fail with a more contextual error later
}
}
mCookie = openDexFile(sourceName, outputName, flags);
mFileName = sourceName;
guard.open("close");
//System.out.println("DEX FILE cookie is " + mCookie + " sourceName=" + sourceName + " outputName=" + outputName);
}
private static long openDexFile(String sourceName, String outputName, int flags) throws IOException {
// Use absolute paths to enable the use of relative paths when testing on host.
return openDexFileNative(new File(sourceName).getAbsolutePath(),
(outputName == null) ? null : new File(outputName).getAbsolutePath(),
flags);
}
到这里,我们调用了native层的openDexFileNative( … ), flags值为0。
openDexFileNative( .. )在 art/runtime/native/dalvik_system_DexFile.cc 里
小弟能力有限,大概也只能看懂大概流程,之后随着对android系统,以及art的理解,会进一步分析。
static jlong DexFile_openDexFileNative(JNIEnv* env, jclass, jstring javaSourceName, jstring javaOutputName, jint) {
ScopedUtfChars sourceName(env, javaSourceName);
if (sourceName.c_str() == NULL) {
return 0;
}
NullableScopedUtfChars outputName(env, javaOutputName);
if (env->ExceptionCheck()) {
return 0;
}
ClassLinker* linker = Runtime::Current()->GetClassLinker();
std::unique_ptr<std::vector<const DexFile*>> dex_files(new std::vector<const DexFile*>());
std::vector<std::string> error_msgs;
bool success = linker->OpenDexFilesFromOat(sourceName.c_str(), outputName.c_str(), &error_msgs,
dex_files.get());
if (success || !dex_files->empty()) {
// In the case of non-success, we have not found or could not generate the oat file.
// But we may still have found a dex file that we can use.
return static_cast<jlong>(reinterpret_cast<uintptr_t>(dex_files.release()));
} else {
// The vector should be empty after a failed loading attempt.
DCHECK_EQ(0U, dex_files->size());
ScopedObjectAccess soa(env);
CHECK(!error_msgs.empty());
// The most important message is at the end. So set up nesting by going forward, which will
// wrap the existing exception as a cause for the following one.
auto it = error_msgs.begin();
auto itEnd = error_msgs.end();
for ( ; it != itEnd; ++it) {
ThrowWrappedIOException("%s", it->c_str());
}
return 0;
}
}
在函数的一开始,进行了简单的检查,然后获取了ClassLinker对象,我们知道art虚拟机后会把dex文件进行优化成oat文件,这不同与dvm的odex,oat文件实质类似我们的so文件,所以这里的ClassLinker不难理解,大概就是我们将dex优化后的oat进行加载的工具。这里的OpenDexFilesFromOat( … )就是这一步的重点了。这个函数在我们的class_linker.cc中。这个函数过长我们只摘取一个大概流程。
bool ClassLinker::OpenDexFilesFromOat(const char* dex_location, const char* oat_location,
std::vector<std::string>* error_msgs,
std::vector<const DexFile*>* dex_files) {
...
if (!DexFile::GetChecksum(dex_location, dex_location_checksum_pointer, &checksum_error_msg)) {
// This happens for pre-opted files since the corresponding dex files are no longer on disk.
dex_location_checksum_pointer = nullptr;
have_checksum = false;
}
//这一段中根据函数名我们大致确定拿到我们dex文件的checksum放到dex_location_checksum_pointer指针中。
bool needs_registering = false;
const OatFile::OatDexFile* oat_dex_file = FindOpenedOatDexFile(oat_location, dex_location,
dex_location_checksum_pointer);
//为了方便我们之后的继续深入,暂定这里打下 标记 1。然后在标记1下,分析这个关键函数 *** goto: label 1
// 2) If we do not have an open one, maybe there's one on disk already.
// In case the oat file is not open, we play a locking game here so
// that if two different processes race to load and register or generate
// (or worse, one tries to open a partial generated file) we will be okay.
// This is actually common with apps that use DexClassLoader to work
// around the dex method reference limit and that have a background
// service running in a separate process.
/*
有时候看看这种注释也很好,能大概给个方向,意思是如果在内存中不存在,可能会在磁盘里存在,接下来大概就是会在磁盘中查找了吧
*/
...
open_oat_file.reset(FindOatFileInOatLocationForDexFile(dex_location, dex_location_checksum,
oat_location, &error_msg));
//检查在磁盘是否已经存在
if (open_oat_file.get() == nullptr) {
std::string compound_msg = StringPrintf("Failed to find dex file '%s' in oat location '%s': %s",
dex_location, oat_location, error_msg.c_str());
VLOG(class_linker) << compound_msg;
error_msgs->push_back(compound_msg);
}
} else {
// TODO: What to lock here?
bool obsolete_file_cleanup_failed;
open_oat_file.reset(FindOatFileContainingDexFileFromDexLocation(dex_location,
dex_location_checksum_pointer,
kRuntimeISA, error_msgs,
&obsolete_file_cleanup_failed));
// There's no point in going forward and eventually try to regenerate the
// file if we couldn't remove the obsolete one. Mostly likely we will fail
// with the same error when trying to write the new file.
// TODO: should we maybe do this only when we get permission issues? (i.e. EACCESS).
if (obsolete_file_cleanup_failed) {
return false;
}
}
needs_registering = true;
}
// 3) If we have an oat file, check all contained multidex files for our dex_location.
// Note: LoadMultiDexFilesFromOatFile will check for nullptr in the first argument.
bool success = LoadMultiDexFilesFromOatFile(open_oat_file.get(), dex_location,
dex_location_checksum_pointer,
false, error_msgs, dex_files);
if (success) {
const OatFile* oat_file = open_oat_file.release(); // Avoid deleting it.
if (needs_registering) {
// We opened the oat file, so we must register it.
RegisterOatFile(oat_file);
}
// If the file isn't executable we failed patchoat but did manage to get the dex files.
return oat_file->IsExecutable();
} else {
if (needs_registering) {
// We opened it, delete it.
open_oat_file.reset();
} else {
open_oat_file.release(); // Do not delete open oat files.
}
}
/*
如果已经存在,LoadMultiDexFilesFromOatFile(...)后如果成功,则通过RegisterOatFile(...)加载到内存中,即oat_files_;
*/
//LoadMultiDexFilesFromOatFile goto: label 2
...
//如果不存在,则重新创建
if (Runtime::Current()->IsDex2OatEnabled() && has_flock && scoped_flock.HasFile()) {
// Create the oat file.
open_oat_file.reset(CreateOatFileForDexLocation(dex_location, scoped_flock.GetFile()->Fd(),
oat_location, error_msgs));
}
//goto: label3 CreateOatFileForDexLocation(...)
...
/*
之后的代码就很简单了,类似先前,先LoadMultiDexFilesFromOatFile()来加载其他dex,如果成功则RegisterOatFile()注册到oat_files_中
*/
success = LoadMultiDexFilesFromOatFile(open_oat_file.get(), dex_location,
dex_location_checksum_pointer,
true, error_msgs, dex_files);
if (success) {
RegisterOatFile(open_oat_file.release());
return true;
} else {
return false;
}
label 1 FindOpenedOatDexFile( … )
const OatFile::OatDexFile* ClassLinker::FindOpenedOatDexFile(const char* oat_location,
const char* dex_location,
const uint32_t* dex_location_checksum) {
ReaderMutexLock mu(Thread::Current(), dex_lock_);
for (const OatFile* oat_file : oat_files_) {
DCHECK(oat_file != nullptr);
if (oat_location != nullptr) {
if (oat_file->GetLocation() != oat_location) {
continue;
}
}
const OatFile::OatDexFile* oat_dex_file = oat_file->GetOatDexFile(dex_location,
dex_location_checksum,
false);
if (oat_dex_file != nullptr) {
return oat_dex_file;
}
}
return nullptr;
}
从这里开始大概就有点吃力,作为一个初学者,对自己的要求就是理解就好,这里大概放一下网上资料的简单总结,剩下的疑问留待以后解决。
这里会遍历 oat_files_;每次dex文件生成oat文件后,会将dex文件的路径,和对应的OatDexFile以键值对的形式保存在OatDexFile结构体中,这里就是会根据我们传入的dex_location当key在内存查看对应的oat_dex_file 已经被加载过,有则返回,这里我们算作第一次进行则返回的是空指针。
label 2 LoadMultiDexFilesFromOatFile(…)
static bool LoadMultiDexFilesFromOatFile(const OatFile* oat_file,
const char* dex_location,
const uint32_t* dex_location_checksum,
bool generated,
std::vector<std::string>* error_msgs,
std::vector<const DexFile*>* dex_files) {
if (oat_file == nullptr) {
return false;
}
size_t old_size = dex_files->size(); // To rollback on error.
bool success = true;
for (size_t i = 0; success; ++i) {
std::string next_name_str = DexFile::GetMultiDexClassesDexName(i, dex_location);
const char* next_name = next_name_str.c_str();
uint32_t next_location_checksum;
uint32_t* next_location_checksum_pointer = &next_location_checksum;
std::string error_msg;
if ((i == 0) && (strcmp(next_name, dex_location) == 0)) {
// When i=0 the multidex name should be the same as the location name. We already have the
// checksum it so we don't need to recompute it.
if (dex_location_checksum == nullptr) {
next_location_checksum_pointer = nullptr;
} else {
next_location_checksum = *dex_location_checksum;
}
} else if (!DexFile::GetChecksum(next_name, next_location_checksum_pointer, &error_msg)) {
DCHECK_EQ(false, i == 0 && generated);
next_location_checksum_pointer = nullptr;
}
const OatFile::OatDexFile* oat_dex_file = oat_file->GetOatDexFile(next_name, nullptr, false);
if (oat_dex_file == nullptr) {
if (i == 0 && generated) {
std::string error_msg;
error_msg = StringPrintf("\nFailed to find dex file '%s' (checksum 0x%x) in generated out "
" file'%s'", dex_location, next_location_checksum,
oat_file->GetLocation().c_str());
error_msgs->push_back(error_msg);
}
break; // Not found, done.
}
// Checksum test. Test must succeed when generated.
success = !generated;
if (next_location_checksum_pointer != nullptr) {
success = next_location_checksum == oat_dex_file->GetDexFileLocationChecksum();
}
if (success) {
const DexFile* dex_file = oat_dex_file->OpenDexFile(&error_msg);
if (dex_file == nullptr) {
success = false;
error_msgs->push_back(error_msg);
} else {
dex_files->push_back(dex_file);
}
}
// When we generated the file, we expect success, or something is terribly wrong.
CHECK_EQ(false, generated && !success)
<< "dex_location=" << next_name << " oat_location=" << oat_file->GetLocation().c_str()
<< std::hex << " dex_location_checksum=" << next_location_checksum
<< " OatDexFile::GetLocationChecksum()=" << oat_dex_file->GetDexFileLocationChecksum();
}
if (dex_files->size() == old_size) {
success = false; // We did not even find classes.dex
}
if (success) {
return true;
} else {
// Free all the dex files we have loaded.
auto it = dex_files->begin() + old_size;
auto it_end = dex_files->end();
for (; it != it_end; it++) {
delete *it;
}
dex_files->erase(dex_files->begin() + old_size, it_end);
return false;
}
}
LoadMultiDexFilesFromOatFile()是将多个dex文件(例如classes1.dex,classes2.dex)映射成DexFile对象,并添加到一个vector数据结构dex_files中,之后调用RegisterOatFile()函数将内存中的oat对象注册到oat_files_中,然后整个流程就跑完了。
label 3: CreateOatFileForDexLocation(…)
const OatFile* ClassLinker::CreateOatFileForDexLocation(const char* dex_location,
int fd, const char* oat_location,
std::vector<std::string>* error_msgs) {
// Generate the output oat file for the dex file
VLOG(class_linker) << "Generating oat file " << oat_location << " for " << dex_location;
std::string error_msg;
if (!GenerateOatFile(dex_location, fd, oat_location, &error_msg)) {
//这里的GenerateOatFile(...)是重点,goto: label 4
CHECK(!error_msg.empty());
error_msgs->push_back(error_msg);
return nullptr;
}
std::unique_ptr<OatFile> oat_file(OatFile::Open(oat_location, oat_location, nullptr, nullptr,
!Runtime::Current()->IsCompiler(),
&error_msg));
if (oat_file.get() == nullptr) {
std::string compound_msg = StringPrintf("\nFailed to open generated oat file '%s': %s",
oat_location, error_msg.c_str());
error_msgs->push_back(compound_msg);
return nullptr;
}
return oat_file.release();
}
label 4: GenerateOatFile(…)
bool ClassLinker::GenerateOatFile(const char* dex_filename,
int oat_fd,
const char* oat_cache_filename,
std::string* error_msg) {
Locks::mutator_lock_->AssertNotHeld(Thread::Current()); // Avoid starving GC.
std::string dex2oat(Runtime::Current()->GetCompilerExecutable());
//直接执行dex2oat工具来生成oat文件
gc::Heap* heap = Runtime::Current()->GetHeap();
std::string boot_image_option("--boot-image=");
if (heap->GetImageSpace() == nullptr) {
// TODO If we get a dex2dex compiler working we could maybe use that, OTOH since we are likely
// out of space anyway it might not matter.
*error_msg = StringPrintf("Cannot create oat file for '%s' because we are running "
"without an image.", dex_filename);
return false;
}
boot_image_option += heap->GetImageSpace()->GetImageLocation();
std::string dex_file_option("--dex-file=");
dex_file_option += dex_filename;
std::string oat_fd_option("--oat-fd=");
StringAppendF(&oat_fd_option, "%d", oat_fd);
std::string oat_location_option("--oat-location=");
oat_location_option += oat_cache_filename;
std::vector<std::string> argv;
argv.push_back(dex2oat);
argv.push_back("--runtime-arg");
argv.push_back("-classpath");
argv.push_back("--runtime-arg");
argv.push_back(Runtime::Current()->GetClassPathString());
Runtime::Current()->AddCurrentRuntimeFeaturesAsDex2OatArguments(&argv);
if (!Runtime::Current()->IsVerificationEnabled()) {
argv.push_back("--compiler-filter=verify-none");
}
if (Runtime::Current()->MustRelocateIfPossible()) {
argv.push_back("--runtime-arg");
argv.push_back("-Xrelocate");
} else {
argv.push_back("--runtime-arg");
argv.push_back("-Xnorelocate");
}
if (!kIsTargetBuild) {
argv.push_back("--host");
}
argv.push_back(boot_image_option);
argv.push_back(dex_file_option);
argv.push_back(oat_fd_option);
argv.push_back(oat_location_option);
const std::vector<std::string>& compiler_options = Runtime::Current()->GetCompilerOptions();
for (size_t i = 0; i < compiler_options.size(); ++i) {
argv.push_back(compiler_options[i].c_str());
}
if (!Exec(argv, error_msg)) {
// Manually delete the file. Ensures there is no garbage left over if the process unexpectedly
// died. Ignore unlink failure, propagate the original error.
TEMP_FAILURE_RETRY(unlink(oat_cache_filename));
return false;
}
return true;
}
直接执行 dex2oat工具生成oat文件
总结
首先系统会先从内存中查找是否有已经优化并加载好的oat,如果没有,则再从磁盘查找是否有dex对应的oat文件,并判断oat文件是否和dex匹配,如果没有对应的oat文件,则使用dex2oat工具生成oat文件,文件保存在DexClassLoader构造函数中指定的参数目录,如果没指定,就保存在类似/data/dalvik-cache的系统目录中,如果oat文件创建成功,就加载到内存,最后DexClassLoader构造完毕。