Andorid DexClassLoader的创建过程解析(基于5.0)
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Andorid DexClassLoader的创建过程解析(基于5.0)相关的知识,希望对你有一定的参考价值。
做android插件框架时,经常会用到dex的动态加载,就要直接或间接的使用DexClassLoader,在new DexClassLoader的时候Android系统做了很多工作,下面我们详细分析一下:
public class DexClassLoader extends BaseDexClassLoader { public DexClassLoader(String dexPath, String optimizedDirectory, String libraryPath, ClassLoader parent) { super(dexPath, new File(optimizedDirectory), libraryPath, parent); } }
public class BaseDexClassLoader extends ClassLoader { private final DexPathList pathList; public BaseDexClassLoader(String dexPath, File optimizedDirectory, String libraryPath, ClassLoader parent) { super(parent); this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory); }
......
@Override protected URL findResource(String name) { return pathList.findResource(name); } @Override protected Enumeration<URL> findResources(String name) { return pathList.findResources(name); } @Override public String findLibrary(String name) { return pathList.findLibrary(name); }
......
}
看到关键步骤了,设置完parent的ClassLoader之后,创建了DexPathList对象pathList,可以看到,很多操作都是直接委托给pathList的,我们看下这个对象里面做了什么。
这里涉及到两个目录,optimizedDirectory和libraryPath,我们先从简单的入手,先看看DexPathList是如何处理libraryPath的。
final class DexPathList { private static final String DEX_SUFFIX = ".dex";/** List of native library directories. */ private final File[] nativeLibraryDirectories;public DexPathList(ClassLoader definingContext, String dexPath, String libraryPath, File optimizedDirectory) { ...... this.nativeLibraryDirectories = splitLibraryPath(libraryPath); } private static File[] splitLibraryPath(String path) { // Native libraries may exist in both the system and // application library paths, and we use this search order: // // 1. this class loader‘s library path for application libraries // 2. the VM‘s library path from the system property for system libraries // // This order was reversed prior to Gingerbread; see http://b/2933456. ArrayList<File> result = splitPaths(path, System.getProperty("java.library.path"), true); return result.toArray(new File[result.size()]); } private static ArrayList<File> splitPaths(String path1, String path2, boolean wantDirectories) { ArrayList<File> result = new ArrayList<File>(); splitAndAdd(path1, wantDirectories, result); splitAndAdd(path2, wantDirectories, result); return result; } private static void splitAndAdd(String searchPath, boolean directoriesOnly, ArrayList<File> resultList) { if (searchPath == null) { return; } for (String path : searchPath.split(":")) { try { StructStat sb = Libcore.os.stat(path); if (!directoriesOnly || S_ISDIR(sb.st_mode)) { resultList.add(new File(path)); } } catch (ErrnoException ignored) { } } } }
从上面代码可以看到,系统会将用户传进的目录和默认的系统lib目录(System.getProperty("java.library.path"))通过“:”分割后,存进一个File数组中。
这就是DexClassLoader对libraryPath的所有操作,所以可以看到,和optimizePath不一样,并没有对so文件解压。
接下来,我们看看DexClassLoader初始化时候对optimizePath做了什么:
还是从DexPathList入手
final class DexPathList { /** class definition context */ private final ClassLoader definingContext; private final Element[] dexElements; private final IOException[] dexElementsSuppressedExceptions; public DexPathList(ClassLoader definingContext, String dexPath, String libraryPath, File optimizedDirectory) { ...... //check optimized directory ...... this.definingContext = definingContext; ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>(); this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory, suppressedExceptions); //save Exceptions ...... this.nativeLibraryDirectories = splitLibraryPath(libraryPath); } private static Element[] makeDexElements(ArrayList<File> files, File optimizedDirectory, ArrayList<IOException> suppressedExceptions) { ...... 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) { suppressedExceptions.add(suppressed); } } ...... 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); } } }
除了一些目录检查,异常处理,从构造函数一步一步调用到了loadDexFile,从而进入到DexFile中,我们继续。
public final class DexFile { public DexFile(File file) throws IOException { this(file.getPath()); } public DexFile(String fileName) throws IOException { mCookie = openDexFile(fileName, null, 0); mFileName = fileName; guard.open("close"); //System.out.println("DEX FILE cookie is " + mCookie + " fileName=" + fileName); } 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); } static public DexFile loadDex(String sourcePathName, String outputPathName, int flags) throws IOException { return new DexFile(sourcePathName, outputPathName, flags); } 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); } }
可以看到,最终走到openDexFileNative这个native方法中,这个方法的native实现在art/runtime/native/dalvik_system_DexFile.cc中,看代码:
static jlong DexFile_openDexFileNative(JNIEnv* env, jclass, jstring javaSourceName, jstring javaOutputName, jint) { ...... bool success = linker->OpenDexFilesFromOat(sourceName.c_str(), outputName.c_str(), &error_msgs, dex_files.get()); ...... }
方法OpenDexFilesFromOat的实现在art/runtime/class_linker.cc中,这个方法太长,我先把全部内容贴上来,
1 bool ClassLinker::OpenDexFilesFromOat(const char* dex_location, const char* oat_location, 2 std::vector<std::string>* error_msgs, 3 std::vector<std::unique_ptr<const DexFile>>* dex_files) { 4 // 1) Check whether we have an open oat file. 5 // This requires a dex checksum, use the "primary" one. 6 uint32_t dex_location_checksum; 7 uint32_t* dex_location_checksum_pointer = &dex_location_checksum; 8 bool have_checksum = true; 9 std::string checksum_error_msg; 10 if (!DexFile::GetChecksum(dex_location, dex_location_checksum_pointer, &checksum_error_msg)) { 11 // This happens for pre-opted files since the corresponding dex files are no longer on disk. 12 dex_location_checksum_pointer = nullptr; 13 have_checksum = false; 14 } 15 16 bool needs_registering = false; 17 18 const OatFile::OatDexFile* oat_dex_file = FindOpenedOatDexFile(oat_location, dex_location, 19 dex_location_checksum_pointer); 20 std::unique_ptr<const OatFile> open_oat_file( 21 oat_dex_file != nullptr ? oat_dex_file->GetOatFile() : nullptr); 22 23 // 2) If we do not have an open one, maybe there‘s one on disk already. 24 25 // In case the oat file is not open, we play a locking game here so 26 // that if two different processes race to load and register or generate 27 // (or worse, one tries to open a partial generated file) we will be okay. 28 // This is actually common with apps that use DexClassLoader to work 29 // around the dex method reference limit and that have a background 30 // service running in a separate process. 31 ScopedFlock scoped_flock; 32 33 if (open_oat_file.get() == nullptr) { 34 if (oat_location != nullptr) { 35 // Can only do this if we have a checksum, else error. 36 if (!have_checksum) { 37 error_msgs->push_back(checksum_error_msg); 38 return false; 39 } 40 41 std::string error_msg; 42 43 // We are loading or creating one in the future. Time to set up the file lock. 44 if (!scoped_flock.Init(oat_location, &error_msg)) { 45 error_msgs->push_back(error_msg); 46 return false; 47 } 48 49 // TODO Caller specifically asks for this oat_location. We should honor it. Probably? 50 open_oat_file.reset(FindOatFileInOatLocationForDexFile(dex_location, dex_location_checksum, 51 oat_location, &error_msg)); 52 53 if (open_oat_file.get() == nullptr) { 54 std::string compound_msg = StringPrintf("Failed to find dex file ‘%s‘ in oat location ‘%s‘: %s", 55 dex_location, oat_location, error_msg.c_str()); 56 VLOG(class_linker) << compound_msg; 57 error_msgs->push_back(compound_msg); 58 } 59 } else { 60 // TODO: What to lock here? 61 bool obsolete_file_cleanup_failed; 62 open_oat_file.reset(FindOatFileContainingDexFileFromDexLocation(dex_location, 63 dex_location_checksum_pointer, 64 kRuntimeISA, error_msgs, 65 &obsolete_file_cleanup_failed)); 66 // There‘s no point in going forward and eventually try to regenerate the 67 // file if we couldn‘t remove the obsolete one. Mostly likely we will fail 68 // with the same error when trying to write the new file. 69 // TODO: should we maybe do this only when we get permission issues? (i.e. EACCESS). 70 if (obsolete_file_cleanup_failed) { 71 return false; 72 } 73 } 74 needs_registering = true; 75 } 76 77 // 3) If we have an oat file, check all contained multidex files for our dex_location. 78 // Note: LoadMultiDexFilesFromOatFile will check for nullptr in the first argument. 79 bool success = LoadMultiDexFilesFromOatFile(open_oat_file.get(), dex_location, 80 dex_location_checksum_pointer, 81 false, error_msgs, dex_files); 82 if (success) { 83 const OatFile* oat_file = open_oat_file.release(); // Avoid deleting it. 84 if (needs_registering) { 85 // We opened the oat file, so we must register it. 86 RegisterOatFile(oat_file); 87 } 88 // If the file isn‘t executable we failed patchoat but did manage to get the dex files. 89 return oat_file->IsExecutable(); 90 } else { 91 if (needs_registering) { 92 // We opened it, delete it. 93 open_oat_file.reset(); 94 } else { 95 open_oat_file.release(); // Do not delete open oat files. 96 } 97 } 98 99 // 4) If it‘s not the case (either no oat file or mismatches), regenerate and load. 100 101 // Need a checksum, fail else. 102 if (!have_checksum) { 103 error_msgs->push_back(checksum_error_msg); 104 return false; 105 } 106 107 // Look in cache location if no oat_location is given. 108 std::string cache_location; 109 if (oat_location == nullptr) { 110 // Use the dalvik cache. 111 const std::string dalvik_cache(GetDalvikCacheOrDie(GetInstructionSetString(kRuntimeISA))); 112 cache_location = GetDalvikCacheFilenameOrDie(dex_location, dalvik_cache.c_str()); 113 oat_location = cache_location.c_str(); 114 } 115 116 bool has_flock = true; 117 // Definitely need to lock now. 118 if (!scoped_flock.HasFile()) { 119 std::string error_msg; 120 if (!scoped_flock.Init(oat_location, &error_msg)) { 121 error_msgs->push_back(error_msg); 122 has_flock = false; 123 } 124 } 125 126 if (Runtime::Current()->IsDex2OatEnabled() && has_flock && scoped_flock.HasFile()) { 127 // Create the oat file. 128 open_oat_file.reset(CreateOatFileForDexLocation(dex_location, scoped_flock.GetFile()->Fd(), 129 oat_location, error_msgs)); 130 } 131 132 // Failed, bail. 133 if (open_oat_file.get() == nullptr) { 134 std::string error_msg; 135 // dex2oat was disabled or crashed. Add the dex file in the list of dex_files to make progress. 136 DexFile::Open(dex_location, dex_location, &error_msg, dex_files); 137 error_msgs->push_back(error_msg); 138 return false; 139 } 140 141 // Try to load again, but stronger checks. 142 success = LoadMultiDexFilesFromOatFile(open_oat_file.get(), dex_location, 143 dex_location_checksum_pointer, 144 true, error_msgs, dex_files); 145 if (success) { 146 RegisterOatFile(open_oat_file.release()); 147 return true; 148 } else { 149 return false; 150 } 151 }
我们按步骤分析,首先会先获取这个dex文件的Checksum值:
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; }
这个checksum的值存放在dex_location_checksum_pointer指向的地址,然后通过这个dex_location和checksum查找oat文件是否已经生成,并且加载到内存,
const OatFile::OatDexFile* oat_dex_file = FindOpenedOatDexFile(oat_location, dex_location, dex_location_checksum_pointer);
进入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的文件路径,查找内存里是否加载了同路径的oat文件,有的话执行oat_file->GetOatDexFile(),
1 const OatFile::OatDexFile* OatFile::GetOatDexFile(const char* dex_location, 2 const uint32_t* dex_location_checksum, 3 bool warn_if_not_found) const { 4 // NOTE: We assume here that the canonical location for a given dex_location never 5 // changes. If it does (i.e. some symlink used by the filename changes) we may return 6 // an incorrect OatDexFile. As long as we have a checksum to check, we shall return 7 // an identical file or fail; otherwise we may see some unpredictable failures. 8 9 // TODO: Additional analysis of usage patterns to see if this can be simplified 10 // without any performance loss, for example by not doing the first lock-free lookup. 11 12 const OatFile::OatDexFile* oat_dex_file = nullptr; 13 StringPiece key(dex_location); 14 // Try to find the key cheaply in the oat_dex_files_ map which holds dex locations 15 // directly mentioned in the oat file and doesn‘t require locking. 16 auto primary_it = oat_dex_files_.find(key); 17 if (primary_it != oat_dex_files_.end()) { 18 oat_dex_file = primary_it->second; 19 DCHECK(oat_dex_file != nullptr); 20 } else { 21 // This dex_location is not one of the dex locations directly mentioned in the 22 // oat file. The correct lookup is via the canonical location but first see in 23 // the secondary_oat_dex_files_ whether we‘ve looked up this location before. 24 MutexLock mu(Thread::Current(), secondary_lookup_lock_); 25 auto secondary_lb = secondary_oat_dex_files_.lower_bound(key); 26 if (secondary_lb != secondary_oat_dex_files_.end() && key == secondary_lb->first) { 27 oat_dex_file = secondary_lb->second; // May be nullptr. 28 } else { 29 // We haven‘t seen this dex_location before, we must check the canonical location. 30 std::string dex_canonical_location = DexFile::GetDexCanonicalLocation(dex_location); 31 if (dex_canonical_location != dex_location) { 32 StringPiece canonical_key(dex_canonical_location); 33 auto canonical_it = oat_dex_files_.find(canonical_key); 34 if (canonical_it != oat_dex_files_.end()) { 35 oat_dex_file = canonical_it->second; 36 } // else keep nullptr. 37 } // else keep nullptr. 38 39 // Copy the key to the string_cache_ and store the result in secondary map. 40 string_cache_.emplace_back(key.data(), key.length()); 41 StringPiece key_copy(string_cache_.back()); 42 secondary_oat_dex_files_.PutBefore(secondary_lb, key_copy, oat_dex_file); 43 } 44 } 45 if (oat_dex_file != nullptr && 46 (dex_location_checksum == nullptr || 47 oat_dex_file->GetDexFileLocationChecksum() == *dex_location_checksum)) { 48 return oat_dex_file; 49 } 50 51 if (warn_if_not_found) { 52 std::string dex_canonical_location = DexFile::GetDexCanonicalLocation(dex_location); 53 std::string checksum("<unspecified>"); 54 if (dex_location_checksum != NULL) { 55 checksum = StringPrintf("0x%08x", *dex_location_checksum); 56 } 57 LOG(WARNING) << "Failed to find OatDexFile for DexFile " << dex_location 58 << " ( canonical path " << dex_canonical_location << ")" 59 << " with checksum " << checksum << " in OatFile " << GetLocation(); 60 if (kIsDebugBuild) { 61 for (const OatDexFile* odf : oat_dex_files_storage_) { 62 LOG(WARNING) << "OatFile " << GetLocation() 63 << " contains OatDexFile " << odf->GetDexFileLocation() 64 << " (canonical path " << odf->GetCanonicalDexFileLocation() << ")" 65 << " with checksum 0x" << std::hex << odf->GetDexFileLocationChecksum(); 66 } 67 } 68 } 69 70 return NULL; 71 }
简单说一下GetOatDexFile函数做了什么,系统每次成功生成oat后,都会将dex文件的路径(注意这里是dex文件,不是oat文件)和对应的OatDexFile对象以key/value的形式保存到oat_dex_files_中,这里有两点注意,第一,缓存的key值是原dex或apk的路径,不是oat的;第二,之所以一个oat对象会用一个map做缓存,是因为会有multidex的情况存在。
首次运行,内存中没有已打开的oat对象,从磁盘查找,
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); }
FindOatFileInOatLocationForDexFile()函数会尝试打开目标oat文件,并对其checksum,oat_offset,patch_delta做校验,如果oat文件存在且内容正确,则
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(); }
LoadMultiDexFilesFromOatFile()是将多个dex文件(例如classes1.dex,classes2.dex)映射成DexFile对象,并添加到一个vector数据结构dex_files中,之后调用RegisterOatFile()函数将内存中的oat对象注册到oat_files_中,然后整个流程就跑完了。
如果oat文件不存在,或者文件不匹配,则重新创建dex的oat文件,并加载,
// Look in cache location if no oat_location is given. std::string cache_location; if (oat_location == nullptr) { // Use the dalvik cache. // GetDalvikCacheOrDie()函数在art/runtime/utils.cc中 // 这句意味着如果没有指定oat的存放目录,则使用类似/data/dalvik-cache目录 const std::string dalvik_cache(GetDalvikCacheOrDie(GetInstructionSetString(kRuntimeISA))); cache_location = GetDalvikCacheFilenameOrDie(dex_location, dalvik_cache.c_str()); oat_location = cache_location.c_str(); } // 对文件上锁 bool has_flock = true; // Definitely need to lock now. if (!scoped_flock.HasFile()) { std::string error_msg; if (!scoped_flock.Init(oat_location, &error_msg)) { error_msgs->push_back(error_msg); has_flock = false; } } if (Runtime::Current()->IsDex2OatEnabled() && has_flock && scoped_flock.HasFile()) { // Create the oat file. // 如果dex2oat工具可用,则生成oat文件 open_oat_file.reset(CreateOatFileForDexLocation(dex_location, scoped_flock.GetFile()->Fd(), oat_location, error_msgs)); }
由于代码比较简单,直接写在注释里了,接下来看下oat是怎么生成的,也就是CreateOatFileForDexLocation()函数做了什么,
const OatFile* ClassLinker::CreateOatFileForDexLocation(const char* dex_location, int fd, const char* oat_location, std::vector<std::string>* error_msgs) { std::string error_msg; if (!GenerateOatFile(dex_location, fd, oat_location, &error_msg)) { CHECK(!error_msg.empty()); error_msgs->push_back(error_msg); return nullptr; } ...... return oat_file.release(); }
真正生成oat是在GenerateOatFile()函数中,
1 bool ClassLinker::GenerateOatFile(const char* dex_filename, 2 int oat_fd, 3 const char* oat_cache_filename, 4 std::string* error_msg) { 5 Locks::mutator_lock_->AssertNotHeld(Thread::Current()); // Avoid starving GC.
以上是关于Andorid DexClassLoader的创建过程解析(基于5.0)的主要内容,如果未能解决你的问题,请参考以下文章
Android 逆向启动 DEX 字节码中的 Activity 组件 ( 在 PathClassLoader 和 BootClassLoader 之间插入 DexClassLoader )
Android 逆向ART 脱壳 ( DexClassLoader 脱壳 | DexClassLoader 构造函数 | 参考 Dalvik 的 DexClassLoader 类加载流程 )(代码片段
Android 逆向ART 脱壳 ( DexClassLoader 脱壳 | DexClassLoader 构造函数 | 参考 Dalvik 的 DexClassLoader 类加载流程 )(代码片段
Android 逆向ART 脱壳 ( DexClassLoader 脱壳 | ART 虚拟机下 DexClassLoader 类加载器脱壳点总结 )