close

作者論壇賬號:白雲點綴的藍

安卓9.0是採用的art虛擬機加載dex文件,與dvm虛擬機不一樣,但是在java層的加載dex文件的方法還是在BaseDexClassLoader類裡面我們看一下BaseDexClassLoader.java這個類如下是這個類的構造方法

63 public BaseDexClassLoader(String dexPath, File optimizedDirectory,64 String librarySearchPath, ClassLoader parent) {65 this(dexPath, optimizedDirectory, librarySearchPath, parent, false);66 }6768 /**69 * @hide70 */71 public BaseDexClassLoader(String dexPath, File optimizedDirectory,72 String librarySearchPath, ClassLoader parent, boolean isTrusted) {73 super(parent);74 this.pathList = new DexPathList(this, dexPath, librarySearchPath, null, isTrusted);7576 if (reporter != null) {77 reportClassLoaderChain();78 }79 }80123 public BaseDexClassLoader(ByteBuffer[] dexFiles, ClassLoader parent) {124 // TODO We should support giving this a library search path maybe.125 super(parent);126 this.pathList = new DexPathList(this, dexFiles);127 }

我們可以傳入dex文件的路徑把dex文件加載進內存,然後調用如下方法,獲取dex裡面的類,

我們可以傳入想要獲取的類的名字,可以看到,這個方法裡面又調用了pathList對象裡面的findClass方法如下方法判斷是否有這個類,沒有就拋出一個ClassNotFoundException的異常有的話就返回獲取到的類

129 @Override130 protected Class<?> findClass(String name) throws ClassNotFoundException {131 List<Throwable> suppressedExceptions = new ArrayList<Throwable>();132 Class c = pathList.findClass(name, suppressedExceptions);133 if (c == null) {134 ClassNotFoundException cnfe = new ClassNotFoundException(135 "Didn't find class \"" + name + "\" on path: " + pathList);136 for (Throwable t : suppressedExceptions) {137 cnfe.addSuppressed(t);138 }139 throw cnfe;140 }141 return c;142 }143

獲取到類之後,我們就可以通過反射來間接調用裡面的方法,獲取裡面的變量等。

調用案例

package org.entity;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;public class Test2 { public static void main(String[] args) { try { /* 這裡省略加載dex文件,獲取class對象 */ //clzz為獲取到的class對象 Object classObj=clzz.newInstance(); //newInstance創建實例將class類轉換為對象 //調用getMethods方法獲取該類的所有方法 Method[] methods = clzz.getMethods(); //遍歷方法 for(Method m:methods){ if(m.getName().equals("xxxxxxx")){//找到xxxxxxx這個方法 try { //invoke方法第一個參數是要調用的類 //第二個是要傳入的參數 m.invoke(classObj, "xxxxxxxxxx"); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } } } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } }}

我們回到如下構造方法中

71 public BaseDexClassLoader(String dexPath, File optimizedDirectory,72 String librarySearchPath, ClassLoader parent, boolean isTrusted) {73 super(parent);74 this.pathList = new DexPathList(this, dexPath, librarySearchPath, null, isTrusted);7576 if (reporter != null) {77 reportClassLoaderChain();78 }79 }

可以看到這個構造方法創建了DexPathList對象,傳入了我們的dexpath路徑,

我們看一下這個方法的構造方法

這個方法判斷dexpath也就是dex文件路徑是否為空。然後判斷優化後的文件存放路徑是否為空,判斷這個存放文件夾是否存在

130 public DexPathList(ClassLoader definingContext, String dexPath,131 String librarySearchPath, File optimizedDirectory) {132 this(definingContext, dexPath, librarySearchPath, optimizedDirectory, false);133 }134135 DexPathList(ClassLoader definingContext, String dexPath,136 String librarySearchPath, File optimizedDirectory, boolean isTrusted) {137 if (definingContext == null) {138 throw new NullPointerException("definingContext == null");139 }140141 if (dexPath == null) {142 throw new NullPointerException("dexPath == null");143 }144145 if (optimizedDirectory != null) {146 if (!optimizedDirectory.exists()) {147 throw new IllegalArgumentException(148 "optimizedDirectory doesn't exist: "149 + optimizedDirectory);150 }151152 if (!(optimizedDirectory.canRead()153 && optimizedDirectory.canWrite())) {154 throw new IllegalArgumentException(155 "optimizedDirectory not readable/writable: "156 + optimizedDirectory);157 }158 }159160 this.definingContext = definingContext;161162 ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();163 // save dexPath for BaseDexClassLoader164 this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,165 suppressedExceptions, definingContext, isTrusted);166167 // Native libraries may exist in both the system and168 // application library paths, and we use this search order:169 //170 // 1. This class loader's library path for application libraries (librarySearchPath):171 // 1.1. Native library directories172 // 1.2. Path to libraries in apk-files173 // 2. The VM's library path from the system property for system libraries174 // also known as java.library.path175 //176 // This order was reversed prior to Gingerbread; see http://b/2933456.177 this.nativeLibraryDirectories = splitPaths(librarySearchPath, false);178 this.systemNativeLibraryDirectories =179 splitPaths(System.getProperty("java.library.path"), true);180 List<File> allNativeLibraryDirectories = new ArrayList<>(nativeLibraryDirectories);181 allNativeLibraryDirectories.addAll(systemNativeLibraryDirectories);182183 this.nativeLibraryPathElements = makePathElements(allNativeLibraryDirectories);184185 if (suppressedExceptions.size() > 0) {186 this.dexElementsSuppressedExceptions =187 suppressedExceptions.toArray(new IOException[suppressedExceptions.size()]);188 } else {189 dexElementsSuppressedExceptions = null;190 }191 }

我們看一下makeDexElements這個方法,因為傳入了dexpath路徑,optimizedDirectory優化文件存放路徑等,

這個方法先是遍歷了file文件集合,然後判斷這個file對象是否是文件夾或者文件,然後調用endsWith方法判斷文件名結尾是否為.dex如下是DEX_SUFFIX變量的定義private static final String DEX_SUFFIX = ".dex";

315 /**316 * Makes an array of dex/resource path elements, one per element of317 * the given array.318 */319 private static Element[] makeDexElements(List<File> files, File optimizedDirectory,320 List<IOException> suppressedExceptions, ClassLoader loader) {321 return makeDexElements(files, optimizedDirectory, suppressedExceptions, loader, false);322 }323324325 private static Element[] makeDexElements(List<File> files, File optimizedDirectory,326 List<IOException> suppressedExceptions, ClassLoader loader, boolean isTrusted) {327 Element[] elements = new Element[files.size()];328 int elementsPos = 0;329 /*330 * Open all files and load the (direct or contained) dex files up front.331 */332 for (File file : files) {333 if (file.isDirectory()) {334 // We support directories for looking up resources. Looking up resources in335 // directories is useful for running libcore tests.336 elements[elementsPos++] = new Element(file);337 } else if (file.isFile()) {338 String name = file.getName();339340 DexFile dex = null;341 if (name.endsWith(DEX_SUFFIX)) {342 // Raw dex file (not inside a zip/jar).343 try {344 dex = loadDexFile(file, optimizedDirectory, loader, elements);345 if (dex != null) {346 elements[elementsPos++] = new Element(dex, null);347 }348 } catch (IOException suppressed) {349 System.logE("Unable to load dex file: " + file, suppressed);350 suppressedExceptions.add(suppressed);351 }352 } else {353 try {354 dex = loadDexFile(file, optimizedDirectory, loader, elements);355 } catch (IOException suppressed) {356 /*357 * IOException might get thrown "legitimately" by the DexFile constructor if358 * the zip file turns out to be resource-only (that is, no classes.dex file359 * in it).360 * Let dex == null and hang on to the exception to add to the tea-leaves for361 * when findClass returns null.362 */363 suppressedExceptions.add(suppressed);364 }365366 if (dex == null) {367 elements[elementsPos++] = new Element(file);368 } else {369 elements[elementsPos++] = new Element(dex, file);370 }371 }372 if (dex != null && isTrusted) {373 dex.setTrusted();374 }375 } else {376 System.logW("ClassLoader referenced unknown path: " + file);377 }378 }379 if (elementsPos != elements.length) {380 elements = Arrays.copyOf(elements, elementsPos);381 }382 return elements;383 }

可以看到下面的判斷分支都調用了loadDexFile方法,傳入了BaseDexClassLoader構造方法的那幾個參數

if (name.endsWith(DEX_SUFFIX)) {342 // Raw dex file (not inside a zip/jar).343 try {344 dex = loadDexFile(file, optimizedDirectory, loader, elements);345 if (dex != null) {346 elements[elementsPos++] = new Element(dex, null);347 }348 } catch (IOException suppressed) {349 System.logE("Unable to load dex file: " + file, suppressed);350 suppressedExceptions.add(suppressed);351 }352 } else {353 try {354 dex = loadDexFile(file, optimizedDirectory, loader, elements);355 } catch (IOException suppressed) {356 /*357 * IOException might get thrown "legitimately" by the DexFile constructor if358 * the zip file turns out to be resource-only (that is, no classes.dex file359 * in it).360 * Let dex == null and hang on to the exception to add to the tea-leaves for361 * when findClass returns null.362 */363 suppressedExceptions.add(suppressed);364 }365366 if (dex == null) {367 elements[elementsPos++] = new Element(file);368 } else {369 elements[elementsPos++] = new Element(dex, file);370 }371 }

我們看一下loadDexFile方法

可以看到有兩個判斷分支,一個是創建了一個DexFile對象,一個是調用了DexFile對象里的,loadDex方法主要區別就是這個優化dex文件的存放路徑optimizedDirectory

390 private static DexFile loadDexFile(File file, File optimizedDirectory, ClassLoader loader,391 Element[] elements)392 throws IOException {393 if (optimizedDirectory == null) {394 return new DexFile(file, loader, elements);395 } else {396 String optimizedPath = optimizedPathFor(file, optimizedDirectory);397 return DexFile.loadDex(file.getPath(), optimizedPath, 0, loader, elements);398 }399 }

我們看一下DexFile類的構造方法,還有loadDex方法

如下是DexFile類的構造方法

52 /**53 * Opens a DEX file from a given File object.54 *55 * @deprecated Applications should use one of the standard classloaders such56 * as {@link dalvik.system.PathClassLoader} instead. <b>This API will be removed57 * in a future Android release</b>.58 */59 @Deprecated60 public DexFile(File file) throws IOException {61 this(file.getPath());62 }63 /*64 * Private version with class loader argument.65 *66 * @param file67 * the File object referencing the actual DEX file68 * @param loader69 * the class loader object creating the DEX file object70 * @param elements71 * the temporary dex path list elements from DexPathList.makeElements72 */73 DexFile(File file, ClassLoader loader, DexPathList.Element[] elements)74 throws IOException {75 this(file.getPath(), loader, elements);76 }7778 /**79 * Opens a DEX file from a given filename.80 *81 * @deprecated Applications should use one of the standard classloaders such82 * as {@link dalvik.system.PathClassLoader} instead. <b>This API will be removed83 * in a future Android release</b>.84 */85 @Deprecated86 public DexFile(String fileName) throws IOException {87 this(fileName, null, null);88 }8990 /*91 * Private version with class loader argument.92 *93 * @param fileName94 * the filename of the DEX file95 * @param loader96 * the class loader creating the DEX file object97 * @param elements98 * the temporary dex path list elements from DexPathList.makeElements99 */100 DexFile(String fileName, ClassLoader loader, DexPathList.Element[] elements) throws IOException {101 mCookie = openDexFile(fileName, null, 0, loader, elements);102 mInternalCookie = mCookie;103 mFileName = fileName;104 //System.out.println("DEX FILE cookie is " + mCookie + " fileName=" + fileName);105 }106107 DexFile(ByteBuffer buf) throws IOException {108 mCookie = openInMemoryDexFile(buf);109 mInternalCookie = mCookie;110 mFileName = null;111 }112113 /**114 * Opens a DEX file from a given filename, using a specified file115 * to hold the optimized data.116 *117 * @param sourceName118 * Jar or APK file with "classes.dex".119 * @param outputName120 * File that will hold the optimized form of the DEX data.121 * @param flags122 * Enable optional features.123 * @param loader124 * The class loader creating the DEX file object.125 * @param elements126 * The temporary dex path list elements from DexPathList.makeElements127 */128 private DexFile(String sourceName, String outputName, int flags, ClassLoader loader,129 DexPathList.Element[] elements) throws IOException {130 if (outputName != null) {131 try {132 String parent = new File(outputName).getParent();133 if (Libcore.os.getuid() != Libcore.os.stat(parent).st_uid) {134 throw new IllegalArgumentException("Optimized data directory " + parent135 + " is not owned by the current user. Shared storage cannot protect"136 + " your application from code injection attacks.");137 }138 } catch (ErrnoException ignored) {139 // assume we'll fail with a more contextual error later140 }141 }142143 mCookie = openDexFile(sourceName, outputName, flags, loader, elements);144 mInternalCookie = mCookie;145 mFileName = sourceName;146 //System.out.println("DEX FILE cookie is " + mCookie + " sourceName=" + sourceName + " outputName=" + outputName);147 }

因為傳入的是三個參數的構造方法,我們只需要看如下構造方法

100 DexFile(String fileName, ClassLoader loader, DexPathList.Element[] elements) throws IOException {101 mCookie = openDexFile(fileName, null, 0, loader, elements);102 mInternalCookie = mCookie;103 mFileName = fileName;104 //System.out.println("DEX FILE cookie is " + mCookie + " fileName=" + fileName);105 }106

可以看到調用了openDexFile方法

347 /*348 * Open a DEX file. The value returned is a magic VM cookie. On349 * failure, an IOException is thrown.350 */351 private static Object openDexFile(String sourceName, String outputName, int flags,352 ClassLoader loader, DexPathList.Element[] elements) throws IOException {353 // Use absolute paths to enable the use of relative paths when testing on host.354 return openDexFileNative(new File(sourceName).getAbsolutePath(),355 (outputName == null)356 ? null357 : new File(outputName).getAbsolutePath(),358 flags,359 loader,360 elements);361 }

我們再看一下loadDex方法

這個方法傳入了五個參數,

return DexFile.loadDex(file.getPath(), optimizedPath, 0, loader, elements);

如下是loadDex方法的實現

149 /**150 * Open a DEX file, specifying the file in which the optimized DEX151 * data should be written. If the optimized form exists and appears152 * to be current, it will be used; if not, the VM will attempt to153 * regenerate it.154 *155 * @deprecated Applications should use one of the standard classloaders such156 * as {@link dalvik.system.PathClassLoader} instead. <b>This API will be removed157 * in a future Android release</b>.158 */159 @Deprecated160 static public DexFile loadDex(String sourcePathName, String outputPathName,161 int flags) throws IOException {162163 /*164 * TODO: we may want to cache previously-opened DexFile objects.165 * The cache would be synchronized with close(). This would help166 * us avoid mapping the same DEX more than once when an app167 * decided to open it multiple times. In practice this may not168 * be a real issue.169 */170 return loadDex(sourcePathName, outputPathName, flags, null, null);171 }172173 /*174 * Private version of loadDex that also takes a class loader.175 *176 * @param sourcePathName177 * Jar or APK file with "classes.dex". (May expand this to include178 * "raw DEX" in the future.)179 * @param outputPathName180 * File that will hold the optimized form of the DEX data.181 * @param flags182 * Enable optional features. (Currently none defined.)183 * @param loader184 * Class loader that is aloading the DEX file.185 * @param elements186 * The temporary dex path list elements from DexPathList.makeElements187 * @return188 * A new or previously-opened DexFile.189 * @throws IOException190 * If unable to open the source or output file.191 */192 static DexFile loadDex(String sourcePathName, String outputPathName,193 int flags, ClassLoader loader, DexPathList.Element[] elements) throws IOException {194195 /*196 * TODO: we may want to cache previously-opened DexFile objects.197 * The cache would be synchronized with close(). This would help198 * us avoid mapping the same DEX more than once when an app199 * decided to open it multiple times. In practice this may not200 * be a real issue.201 */202 return new DexFile(sourcePathName, outputPathName, flags, loader, elements);203 }

我們可以看到這個方法最後還是調用了DexFile對象裡面的構造方法,而且是五個參數的。

113 /**114 * Opens a DEX file from a given filename, using a specified file115 * to hold the optimized data.116 *117 * @param sourceName118 * Jar or APK file with "classes.dex".119 * @param outputName120 * File that will hold the optimized form of the DEX data.121 * @param flags122 * Enable optional features.123 * @param loader124 * The class loader creating the DEX file object.125 * @param elements126 * The temporary dex path list elements from DexPathList.makeElements127 */128 private DexFile(String sourceName, String outputName, int flags, ClassLoader loader,129 DexPathList.Element[] elements) throws IOException {130 if (outputName != null) {131 try {132 String parent = new File(outputName).getParent();133 if (Libcore.os.getuid() != Libcore.os.stat(parent).st_uid) {134 throw new IllegalArgumentException("Optimized data directory " + parent135 + " is not owned by the current user. Shared storage cannot protect"136 + " your application from code injection attacks.");137 }138 } catch (ErrnoException ignored) {139 // assume we'll fail with a more contextual error later140 }141 }142143 mCookie = openDexFile(sourceName, outputName, flags, loader, elements);144 mInternalCookie = mCookie;145 mFileName = sourceName;146 //System.out.println("DEX FILE cookie is " + mCookie + " sourceName=" + sourceName + " outputName=" + outputName);147 }

最後面還是調用了openDexFile方法

351 private static Object openDexFile(String sourceName, String outputName, int flags,352 ClassLoader loader, DexPathList.Element[] elements) throws IOException {353 // Use absolute paths to enable the use of relative paths when testing on host.354 return openDexFileNative(new File(sourceName).getAbsolutePath(),355 (outputName == null)356 ? null357 : new File(outputName).getAbsolutePath(),358 flags,359 loader,360 elements);361 }

我們跟蹤一下openDexFileNative方法,這個方法是native修飾的,所以方法實現在c/c++層

我們可以看到這個方法是動態註冊的

841static JNINativeMethod gMethods[] = {842 NATIVE_METHOD(DexFile, closeDexFile, "(Ljava/lang/Object;)Z"),843 NATIVE_METHOD(DexFile,844 defineClassNative,845 "(Ljava/lang/String;"846 "Ljava/lang/ClassLoader;"847 "Ljava/lang/Object;"848 "Ldalvik/system/DexFile;"849 ")Ljava/lang/Class;"),850 NATIVE_METHOD(DexFile, getClassNameList, "(Ljava/lang/Object;)[Ljava/lang/String;"),851 NATIVE_METHOD(DexFile, isDexOptNeeded, "(Ljava/lang/String;)Z"),852 NATIVE_METHOD(DexFile, getDexOptNeeded,853 "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ZZ)I"),854 NATIVE_METHOD(DexFile, openDexFileNative,855 "(Ljava/lang/String;"856 "Ljava/lang/String;"857 "I"858 "Ljava/lang/ClassLoader;"859 "[Ldalvik/system/DexPathList$Element;"860 ")Ljava/lang/Object;"),861 NATIVE_METHOD(DexFile, createCookieWithDirectBuffer,862 "(Ljava/nio/ByteBuffer;II)Ljava/lang/Object;"),863 NATIVE_METHOD(DexFile, createCookieWithArray, "([BII)Ljava/lang/Object;"),864 NATIVE_METHOD(DexFile, isValidCompilerFilter, "(Ljava/lang/String;)Z"),865 NATIVE_METHOD(DexFile, isProfileGuidedCompilerFilter, "(Ljava/lang/String;)Z"),866 NATIVE_METHOD(DexFile,867 getNonProfileGuidedCompilerFilter,868 "(Ljava/lang/String;)Ljava/lang/String;"),869 NATIVE_METHOD(DexFile,870 getSafeModeCompilerFilter,871 "(Ljava/lang/String;)Ljava/lang/String;"),872 NATIVE_METHOD(DexFile, isBackedByOatFile, "(Ljava/lang/Object;)Z"),873 NATIVE_METHOD(DexFile, getDexFileStatus,874 "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;"),875 NATIVE_METHOD(DexFile, getDexFileOutputPaths,876 "(Ljava/lang/String;Ljava/lang/String;)[Ljava/lang/String;"),877 NATIVE_METHOD(DexFile, getStaticSizeOfDexFile, "(Ljava/lang/Object;)J"),878 NATIVE_METHOD(DexFile, getDexFileOptimizationStatus,879 "(Ljava/lang/String;Ljava/lang/String;)[Ljava/lang/String;"),880 NATIVE_METHOD(DexFile, setTrusted, "(Ljava/lang/Object;)V")881};

如下是這個函數的實現

266// TODO(calin): clean up the unused parameters (here and in libcore).267static jobject DexFile_openDexFileNative(JNIEnv* env,268 jclass,269 jstring javaSourceName,270 jstring javaOutputName ATTRIBUTE_UNUSED,271 jint flags ATTRIBUTE_UNUSED,272 jobject class_loader,273 jobjectArray dex_elements) {274 ScopedUtfChars sourceName(env, javaSourceName);275 if (sourceName.c_str() == nullptr) {276 return 0;277 }278279 Runtime* const runtime = Runtime::Current();280 ClassLinker* linker = runtime->GetClassLinker();281 std::vector<std::unique_ptr<const DexFile>> dex_files;282 std::vector<std::string> error_msgs;283 const OatFile* oat_file = nullptr;284285 dex_files = runtime->GetOatFileManager().OpenDexFilesFromOat(sourceName.c_str(),286 class_loader,287 dex_elements,288 /*out*/ &oat_file,289 /*out*/ &error_msgs);290291 if (!dex_files.empty()) {292 jlongArray array = ConvertDexFilesToJavaArray(env, oat_file, dex_files);293 if (array == nullptr) {294 ScopedObjectAccess soa(env);295 for (auto& dex_file : dex_files) {296 if (linker->IsDexFileRegistered(soa.Self(), *dex_file)) {297 dex_file.release();298 }299 }300 }301 return array;302 } else {303 ScopedObjectAccess soa(env);304 CHECK(!error_msgs.empty());305 // The most important message is at the end. So set up nesting by going forward, which will306 // wrap the existing exception as a cause for the following one.307 auto it = error_msgs.begin();308 auto itEnd = error_msgs.end();309 for ( ; it != itEnd; ++it) {310 ThrowWrappedIOException("%s", it->c_str());311 }312313 return nullptr;314 }315}316

我們看到java層傳入的參數傳入了OpenDexFilesFromOat這個函數,

這個函數判斷了dex_location dex的文件路徑 是否為空,判斷classloader 類加載器是否為空

394std::vector<std::unique_ptr<const DexFile>> OatFileManager::OpenDexFilesFromOat(395 const char* dex_location,396 jobject class_loader,397 jobjectArray dex_elements,398 const OatFile** out_oat_file,399 std::vector<std::string>* error_msgs) {400 ScopedTrace trace(__FUNCTION__);401 CHECK(dex_location != nullptr);402 CHECK(error_msgs != nullptr);403404 // Verify we aren't holding the mutator lock, which could starve GC if we405 // have to generate or relocate an oat file.406 Thread* const self = Thread::Current();407 Locks::mutator_lock_->AssertNotHeld(self);408 Runtime* const runtime = Runtime::Current();409410 std::unique_ptr<ClassLoaderContext> context;411 // If the class_loader is null there's not much we can do. This happens if a dex files is loaded412 // directly with DexFile APIs instead of using class loaders.413 if (class_loader == nullptr) {414 LOG(WARNING) << "Opening an oat file without a class loader. "415 << "Are you using the deprecated DexFile APIs?";416 context = nullptr;417 } else {418 context = ClassLoaderContext::CreateContextForClassLoader(class_loader, dex_elements);419 }420421 OatFileAssistant oat_file_assistant(dex_location,422 kRuntimeISA,423 !runtime->IsAotCompiler(),424 only_use_system_oat_files_);425426 // Lock the target oat location to avoid races generating and loading the427 // oat file.428 std::string error_msg;429 if (!oat_file_assistant.Lock(/*out*/&error_msg)) {430 // Don't worry too much if this fails. If it does fail, it's unlikely we431 // can generate an oat file anyway.432 VLOG(class_linker) << "OatFileAssistant::Lock: " << error_msg;433 }434435 const OatFile* source_oat_file = nullptr;436437 if (!oat_file_assistant.IsUpToDate()) {438 // Update the oat file on disk if we can, based on the --compiler-filter439 // option derived from the current runtime options.440 // This may fail, but that's okay. Best effort is all that matters here.441 // TODO(calin): b/64530081 b/66984396. Pass a null context to verify and compile442 // secondary dex files in isolation (and avoid to extract/verify the main apk443 // if it's in the class path). Note this trades correctness for performance444 // since the resulting slow down is unacceptable in some cases until b/64530081445 // is fixed.446 // We still pass the class loader context when the classpath string of the runtime447 // is not empty, which is the situation when ART is invoked standalone.448 ClassLoaderContext* actual_context = Runtime::Current()->GetClassPathString().empty()449 ? nullptr450 : context.get();451 switch (oat_file_assistant.MakeUpToDate(/*profile_changed*/ false,452 actual_context,453 /*out*/ &error_msg)) {454 case OatFileAssistant::kUpdateFailed:455 LOG(WARNING) << error_msg;456 break;457458 case OatFileAssistant::kUpdateNotAttempted:459 // Avoid spamming the logs if we decided not to attempt making the oat460 // file up to date.461 VLOG(oat) << error_msg;462 break;463464 case OatFileAssistant::kUpdateSucceeded:465 // Nothing to do.466 break;467 }468 }470 // Get the oat file on disk.471 std::unique_ptr<const OatFile> oat_file(oat_file_assistant.GetBestOatFile().release());472 VLOG(oat) << "OatFileAssistant(" << dex_location << ").GetBestOatFile()="473 << reinterpret_cast<uintptr_t>(oat_file.get())474 << " (executable=" << (oat_file != nullptr ? oat_file->IsExecutable() : false) << ")";475476 if ((class_loader != nullptr || dex_elements != nullptr) && oat_file != nullptr) {477 // Prevent oat files from being loaded if no class_loader or dex_elements are provided.478 // This can happen when the deprecated DexFile.<init>(String) is called directly, and it479 // could load oat files without checking the classpath, which would be incorrect.480 // Take the file only if it has no collisions, or we must take it because of preopting.481 bool accept_oat_file =482 !HasCollisions(oat_file.get(), context.get(), /*out*/ &error_msg);483 if (!accept_oat_file) {484 // Failed the collision check. Print warning.485 if (Runtime::Current()->IsDexFileFallbackEnabled()) {486 if (!oat_file_assistant.HasOriginalDexFiles()) {487 // We need to fallback but don't have original dex files. We have to488 // fallback to opening the existing oat file. This is potentially489 // unsafe so we warn about it.490 accept_oat_file = true;491492 LOG(WARNING) << "Dex location " << dex_location << " does not seem to include dex file. "493 << "Allow oat file use. This is potentially dangerous.";494 } else {495 // We have to fallback and found original dex files - extract them from an APK.496 // Also warn about this operation because it's potentially wasteful.497 LOG(WARNING) << "Found duplicate classes, falling back to extracting from APK : "498 << dex_location;499 LOG(WARNING) << "NOTE: This wastes RAM and hurts startup performance.";500 }501 } else {502 // TODO: We should remove this. The fact that we're here implies -Xno-dex-file-fallback503 // was set, which means that we should never fallback. If we don't have original dex504 // files, we should just fail resolution as the flag intended.505 if (!oat_file_assistant.HasOriginalDexFiles()) {506 accept_oat_file = true;507 }508509 LOG(WARNING) << "Found duplicate classes, dex-file-fallback disabled, will be failing to "510 " load classes for " << dex_location;511 }512513 LOG(WARNING) << error_msg;514 }515516 if (accept_oat_file) {517 VLOG(class_linker) << "Registering " << oat_file->GetLocation();518 source_oat_file = RegisterOatFile(std::move(oat_file));519 *out_oat_file = source_oat_file;520 }521 }522523 std::vector<std::unique_ptr<const DexFile>> dex_files;524525 // Load the dex files from the oat file.526 if (source_oat_file != nullptr) {527 bool added_image_space = false;528 if (source_oat_file->IsExecutable()) {529 // We need to throw away the image space if we are debuggable but the oat-file source of the530 // image is not otherwise we might get classes with inlined methods or other such things.531 std::unique_ptr<gc::space::ImageSpace> image_space;532 if (kEnableAppImage && (!runtime->IsJavaDebuggable() || source_oat_file->IsDebuggable())) {533 image_space = oat_file_assistant.OpenImageSpace(source_oat_file);534 } else {535 image_space = nullptr;536 }537 if (image_space != nullptr) {538 ScopedObjectAccess soa(self);539 StackHandleScope<1> hs(self);540 Handle<mirror::ClassLoader> h_loader(541 hs.NewHandle(soa.Decode<mirror::ClassLoader>(class_loader)));542 // Can not load app image without class loader.543 if (h_loader != nullptr) {544 std::string temp_error_msg;545 // Add image space has a race condition since other threads could be reading from the546 // spaces array.547 {548 ScopedThreadSuspension sts(self, kSuspended);549 gc::ScopedGCCriticalSection gcs(self,550 gc::kGcCauseAddRemoveAppImageSpace,551 gc::kCollectorTypeAddRemoveAppImageSpace);552 ScopedSuspendAll ssa("Add image space");553 runtime->GetHeap()->AddSpace(image_space.get());554 }555 {556 ScopedTrace trace2(StringPrintf("Adding image space for location %s", dex_location));557 added_image_space = runtime->GetClassLinker()->AddImageSpace(image_space.get(),558 h_loader,559 dex_elements,560 dex_location,561 /*out*/&dex_files,562 /*out*/&temp_error_msg);563 }564 if (added_image_space) {565 // Successfully added image space to heap, release the map so that it does not get566 // freed.567 image_space.release();568569 // Register for tracking.570 for (const auto& dex_file : dex_files) {571 dex::tracking::RegisterDexFile(dex_file.get());572 }573 } else {574 LOG(INFO) << "Failed to add image file " << temp_error_msg;575 dex_files.clear();576 {577 ScopedThreadSuspension sts(self, kSuspended);578 gc::ScopedGCCriticalSection gcs(self,579 gc::kGcCauseAddRemoveAppImageSpace,580 gc::kCollectorTypeAddRemoveAppImageSpace);581 ScopedSuspendAll ssa("Remove image space");582 runtime->GetHeap()->RemoveSpace(image_space.get());583 }584 // Non-fatal, don't update error_msg.585 }586 }587 }588 }589 if (!added_image_space) {590 DCHECK(dex_files.empty());591 dex_files = oat_file_assistant.LoadDexFiles(*source_oat_file, dex_location);592593 // Register for tracking.594 for (const auto& dex_file : dex_files) {595 dex::tracking::RegisterDexFile(dex_file.get());596 }597 }598 if (dex_files.empty()) {599 error_msgs->push_back("Failed to open dex files from " + source_oat_file->GetLocation());600 } else {601 // Opened dex files from an oat file, madvise them to their loaded state.602 for (const std::unique_ptr<const DexFile>& dex_file : dex_files) {603 OatDexFile::MadviseDexFile(*dex_file, MadviseState::kMadviseStateAtLoad);604 }605 }606 }607608 // Fall back to running out of the original dex file if we couldn't load any609 // dex_files from the oat file.610 if (dex_files.empty()) {611 if (oat_file_assistant.HasOriginalDexFiles()) {612 if (Runtime::Current()->IsDexFileFallbackEnabled()) {613 static constexpr bool kVerifyChecksum = true;614 const ArtDexFileLoader dex_file_loader;615 if (!dex_file_loader.Open(dex_location,616 dex_location,617 Runtime::Current()->IsVerificationEnabled(),618 kVerifyChecksum,619 /*out*/ &error_msg,620 &dex_files)) {621 LOG(WARNING) << error_msg;622 error_msgs->push_back("Failed to open dex files from " + std::string(dex_location)623 + " because: " + error_msg);624 }625 } else {626 error_msgs->push_back("Fallback mode disabled, skipping dex files.");627 }628 } else {629 error_msgs->push_back("No original dex files found for dex location "630 + std::string(dex_location));631 }632 }633634 return dex_files;635}

我們看一下如下函數,可以看到創建了一個OatFileAssistant 對象,調用了oat_file_assistant構造方法。

傳入了java層的相關參數,我們跟進去看一下這個函數,這個構造方法傳入了四個參數

421 OatFileAssistant oat_file_assistant(dex_location,422 kRuntimeISA,423 !runtime->IsAotCompiler(),424 only_use_system_oat_files_);

可以看到這個構造函數又繼續調用了OatFileAssistant函數

75OatFileAssistant::OatFileAssistant(const char* dex_location,76 const InstructionSet isa,77 bool load_executable,78 bool only_load_system_executable)79 : OatFileAssistant(dex_location,80 isa,81 load_executable,82 only_load_system_executable,83 -1 /* vdex_fd */,84 -1 /* oat_fd */,85 -1 /* zip_fd */) {}86

如下是OatFileAssistant的重載函數,有七個參數,函數開頭還是在判斷dex_location 是否為空

88OatFileAssistant::OatFileAssistant(const char* dex_location,89 const InstructionSet isa,90 bool load_executable,91 bool only_load_system_executable,92 int vdex_fd,93 int oat_fd,94 int zip_fd)95 : isa_(isa),96 load_executable_(load_executable),97 only_load_system_executable_(only_load_system_executable),98 odex_(this, /*is_oat_location*/ false),99 oat_(this, /*is_oat_location*/ true),100 zip_fd_(zip_fd) {101 CHECK(dex_location != nullptr) << "OatFileAssistant: null dex location";102103 if (zip_fd < 0) {104 CHECK_LE(oat_fd, 0) << "zip_fd must be provided with valid oat_fd. zip_fd=" << zip_fd105 << " oat_fd=" << oat_fd;106 CHECK_LE(vdex_fd, 0) << "zip_fd must be provided with valid vdex_fd. zip_fd=" << zip_fd107 << " vdex_fd=" << vdex_fd;;108 }109110 dex_location_.assign(dex_location);111112 if (load_executable_ && isa != kRuntimeISA) {113 LOG(WARNING) << "OatFileAssistant: Load executable specified, "114 << "but isa is not kRuntimeISA. Will not attempt to load executable.";115 load_executable_ = false;116 }117118 // Get the odex filename.119 std::string error_msg;120 std::string odex_file_name;121 if (DexLocationToOdexFilename(dex_location_, isa_, &odex_file_name, &error_msg)) {122 odex_.Reset(odex_file_name, UseFdToReadFiles(), zip_fd, vdex_fd, oat_fd);123 } else {124 LOG(WARNING) << "Failed to determine odex file name: " << error_msg;125 }126127 if (!UseFdToReadFiles()) {128 // Get the oat filename.129 std::string oat_file_name;130 if (DexLocationToOatFilename(dex_location_, isa_, &oat_file_name, &error_msg)) {131 oat_.Reset(oat_file_name, false /* use_fd */);132 } else {133 LOG(WARNING) << "Failed to determine oat file name for dex location "134 << dex_location_ << ": " << error_msg;135 }136 }137138 // Check if the dex directory is writable.139 // This will be needed in most uses of OatFileAssistant and so it's OK to140 // compute it eagerly. (the only use which will not make use of it is141 // OatFileAssistant::GetStatusDump())142 size_t pos = dex_location_.rfind('/');143 if (pos == std::string::npos) {144 LOG(WARNING) << "Failed to determine dex file parent directory: " << dex_location_;145 } else if (!UseFdToReadFiles()) {146 // We cannot test for parent access when using file descriptors. That's ok147 // because in this case we will always pick the odex file anyway.148 std::string parent = dex_location_.substr(0, pos);149 if (access(parent.c_str(), W_OK) == 0) {150 dex_parent_writable_ = true;151 } else {152 VLOG(oat) << "Dex parent of " << dex_location_ << " is not writable: " << strerror(errno);153 }154 }155}

我們看一下如下函數DexLocationToOdexFilename,相關參數都傳入了當前函數

862bool OatFileAssistant::DexLocationToOatFilename(const std::string& location,863 InstructionSet isa,864 std::string* oat_filename,865 std::string* error_msg) {866 CHECK(oat_filename != nullptr);867 CHECK(error_msg != nullptr);868869 std::string cache_dir = GetDalvikCache(GetInstructionSetString(isa));870 if (cache_dir.empty()) {871 *error_msg = "Dalvik cache directory does not exist";872 return false;873 }874875 // TODO: The oat file assistant should be the definitive place for876 // determining the oat file name from the dex location, not877 // GetDalvikCacheFilename.878 return GetDalvikCacheFilename(location.c_str(), cache_dir.c_str(), oat_filename, error_msg);879}

我們繼續跟蹤GetDalvikCacheFilename函數

我們看到這個函數返回值是bool類型的,獲取DalvikCacheFilename,獲取Dalvik緩存文件名。通過指針形式給內存空間賦值。

269bool GetDalvikCacheFilename(const char* location, const char* cache_location,270 std::string* filename, std::string* error_msg) {271 if (location[0] != '/') {272 *error_msg = StringPrintf("Expected path in location to be absolute: %s", location);273 return false;274 }275 std::string cache_file(&location[1]); // skip leading slash276 if (!android::base::EndsWith(location, ".dex") &&277 !android::base::EndsWith(location, ".art") &&278 !android::base::EndsWith(location, ".oat")) {279 cache_file += "/";280 cache_file += DexFileLoader::kClassesDex;281 }282 std::replace(cache_file.begin(), cache_file.end(), '/', '@');283 *filename = StringPrintf("%s/%s", cache_location, cache_file.c_str());284 return true;285}

我們回到前面

可以看到這裡定義了一個容器,裡面用來存放Dex文件對象的相關信息

複製代碼 隱藏代碼std::vector<std::unique_ptr<const DexFile>> dex_files;

在DexFile類裡面我們可以看到dex文件結構相關信息,比如checksum,stringids,header_,fieldids,method_ids_等

96DexFile::DexFile(const uint8_t* base,97 size_t size,98 const uint8_t* data_begin,99 size_t data_size,100 const std::string& location,101 uint32_t location_checksum,102 const OatDexFile* oat_dex_file,103 std::unique_ptr<DexFileContainer> container,104 bool is_compact_dex)105 : begin_(base),106 size_(size),107 data_begin_(data_begin),108 data_size_(data_size),109 location_(location),110 location_checksum_(location_checksum),111 header_(reinterpret_cast<const Header*>(base)),112 string_ids_(reinterpret_cast<const StringId*>(base + header_->string_ids_off_)),113 type_ids_(reinterpret_cast<const TypeId*>(base + header_->type_ids_off_)),114 field_ids_(reinterpret_cast<const FieldId*>(base + header_->field_ids_off_)),115 method_ids_(reinterpret_cast<const MethodId*>(base + header_->method_ids_off_)),116 proto_ids_(reinterpret_cast<const ProtoId*>(base + header_->proto_ids_off_)),117 class_defs_(reinterpret_cast<const ClassDef*>(base + header_->class_defs_off_)),118 method_handles_(nullptr),119 num_method_handles_(0),120 call_site_ids_(nullptr),121 num_call_site_ids_(0),122 oat_dex_file_(oat_dex_file),123 container_(std::move(container)),124 is_compact_dex_(is_compact_dex),125 is_platform_dex_(false) {126 CHECK(begin_ != nullptr) << GetLocation();127 CHECK_GT(size_, 0U) << GetLocation();128 // Check base (=header) alignment.129 // Must be 4-byte aligned to avoid undefined behavior when accessing130 // any of the sections via a pointer.131 CHECK_ALIGNED(begin_, alignof(Header));132133 InitializeSectionsFromMapList();134}135
84 struct Header {85 uint8_t magic_[8] = {};86 uint32_t checksum_ = 0; // See also location_checksum_87 uint8_t signature_[kSha1DigestSize] = {};88 uint32_t file_size_ = 0; // size of entire file89 uint32_t header_size_ = 0; // offset to start of next section90 uint32_t endian_tag_ = 0;91 uint32_t link_size_ = 0; // unused92 uint32_t link_off_ = 0; // unused93 uint32_t map_off_ = 0; // unused94 uint32_t string_ids_size_ = 0; // number of StringIds95 uint32_t string_ids_off_ = 0; // file offset of StringIds array96 uint32_t type_ids_size_ = 0; // number of TypeIds, we don't support more than 6553597 uint32_t type_ids_off_ = 0; // file offset of TypeIds array98 uint32_t proto_ids_size_ = 0; // number of ProtoIds, we don't support more than 6553599 uint32_t proto_ids_off_ = 0; // file offset of ProtoIds array100 uint32_t field_ids_size_ = 0; // number of FieldIds101 uint32_t field_ids_off_ = 0; // file offset of FieldIds array102 uint32_t method_ids_size_ = 0; // number of MethodIds103 uint32_t method_ids_off_ = 0; // file offset of MethodIds array104 uint32_t class_defs_size_ = 0; // number of ClassDefs105 uint32_t class_defs_off_ = 0; // file offset of ClassDef array106 uint32_t data_size_ = 0; // size of data section107 uint32_t data_off_ = 0; // file offset of data section108109 // Decode the dex magic version110 uint32_t GetVersion() const;111 };

在DexFile類裡面還要檢查魔數和版本的函數

計算Checksum的函數等。

150bool DexFile::CheckMagicAndVersion(std::string* error_msg) const {151 if (!IsMagicValid()) {152 std::ostringstream oss;153 oss << "Unrecognized magic number in " << GetLocation() << ":"154 << " " << header_->magic_[0]155 << " " << header_->magic_[1]156 << " " << header_->magic_[2]157 << " " << header_->magic_[3];158 *error_msg = oss.str();159 return false;160 }161 if (!IsVersionValid()) {162 std::ostringstream oss;163 oss << "Unrecognized version number in " << GetLocation() << ":"164 << " " << header_->magic_[4]165 << " " << header_->magic_[5]166 << " " << header_->magic_[6]167 << " " << header_->magic_[7];168 *error_msg = oss.str();169 return false;170 }171 return true;172}63uint32_t DexFile::CalculateChecksum() const {64 return CalculateChecksum(Begin(), Size());65}6667uint32_t DexFile::CalculateChecksum(const uint8_t* begin, size_t size) {68 const uint32_t non_sum_bytes = OFFSETOF_MEMBER(DexFile::Header, signature_);69 return ChecksumMemoryRange(begin + non_sum_bytes, size - non_sum_bytes);70}7172uint32_t DexFile::ChecksumMemoryRange(const uint8_t* begin, size_t size) {73 return adler32(adler32(0L, Z_NULL, 0), begin, size);74}7576int DexFile::GetPermissions() const {77 CHECK(container_.get() != nullptr);78 return container_->GetPermissions();79}8081bool DexFile::IsReadOnly() const {82 CHECK(container_.get() != nullptr);83 return container_->IsReadOnly();84}8586bool DexFile::EnableWrite() const {87 CHECK(container_.get() != nullptr);88 return container_->EnableWrite();89}9091bool DexFile::DisableWrite() const {92 CHECK(container_.get() != nullptr);93 return container_->DisableWrite();94}

我們回到前面,可以看到如下語句塊

可以看到調用了LoadDexFiles函數,返回值就是dex_files

589 if (!added_image_space) {590 DCHECK(dex_files.empty());591 dex_files = oat_file_assistant.LoadDexFiles(*source_oat_file, dex_location);592593 // Register for tracking.594 for (const auto& dex_file : dex_files) {595 dex::tracking::RegisterDexFile(dex_file.get());596 }597 }

我們看一下LoadDexFiles這個函數

341std::vector<std::unique_ptr<const DexFile>> OatFileAssistant::LoadDexFiles(342 const OatFile &oat_file, const char *dex_location) {343 std::vector<std::unique_ptr<const DexFile>> dex_files;344 if (LoadDexFiles(oat_file, dex_location, &dex_files)) {345 return dex_files;346 } else {347 return std::vector<std::unique_ptr<const DexFile>>();348 }349}350351bool OatFileAssistant::LoadDexFiles(352 const OatFile &oat_file,353 const std::string& dex_location,354 std::vector<std::unique_ptr<const DexFile>>* out_dex_files) {355 // Load the main dex file.356 std::string error_msg;357 const OatFile::OatDexFile* oat_dex_file = oat_file.GetOatDexFile(358 dex_location.c_str(), nullptr, &error_msg);359 if (oat_dex_file == nullptr) {360 LOG(WARNING) << error_msg;361 return false;362 }363364 std::unique_ptr<const DexFile> dex_file = oat_dex_file->OpenDexFile(&error_msg);365 if (dex_file.get() == nullptr) {366 LOG(WARNING) << "Failed to open dex file from oat dex file: " << error_msg;367 return false;368 }369 out_dex_files->push_back(std::move(dex_file));370371 // Load the rest of the multidex entries372 for (size_t i = 1;; i++) {373 std::string multidex_dex_location = DexFileLoader::GetMultiDexLocation(i, dex_location.c_str());374 oat_dex_file = oat_file.GetOatDexFile(multidex_dex_location.c_str(), nullptr);375 if (oat_dex_file == nullptr) {376 // There are no more multidex entries to load.377 break;378 }379380 dex_file = oat_dex_file->OpenDexFile(&error_msg);381 if (dex_file.get() == nullptr) {382 LOG(WARNING) << "Failed to open dex file from oat dex file: " << error_msg;383 return false;384 }385 out_dex_files->push_back(std::move(dex_file));386 }387 return true;388}

我們可以看到關鍵函數,這個函數返回值就是dex_file

複製代碼 隱藏代碼364std::unique_ptr<const DexFile> dex_file = oat_dex_file->OpenDexFile(&error_msg);

LoadDexFiles函數返回值為dex_file,LoadDexFiles函數又調用了OpenDexFile函數,如下是OpenDexFile函數的定義可以看到直接就返回了DexFile對象的指針,這個DexFile對象裡面有我們dex文件的所有信息。我們可以選擇在這個時機點脫殼,hookOpenDexFile函數的返回值即可

如下是關於OpenDexFile的兩個函數

113const DexFile* OpenDexFile(const OatFile::OatDexFile* oat_dex_file, std::string* error_msg) {114 DCHECK(oat_dex_file != nullptr);115 auto it = opened_dex_files.find(oat_dex_file);116 if (it != opened_dex_files.end()) {117 return it->second.get();118 }119 const DexFile* ret = oat_dex_file->OpenDexFile(error_msg).release();120 opened_dex_files.emplace(oat_dex_file, std::unique_ptr<const DexFile>(ret));121 return ret;122}1737std::unique_ptr<const DexFile> OatFile::OatDexFile::OpenDexFile(std::string* error_msg) const {1738 ScopedTrace trace(__PRETTY_FUNCTION__);1739 static constexpr bool kVerify = false;1740 static constexpr bool kVerifyChecksum = false;1741 const ArtDexFileLoader dex_file_loader;1742 return dex_file_loader.Open(dex_file_pointer_,1743 FileSize(),1744 dex_file_location_,1745 dex_file_location_checksum_,1746 this,1747 kVerify,1748 kVerifyChecksum,1749 error_msg);1750}

我們可以看到,這個OpenDexFile函數傳入了dex_filepointer,FileSize,

因此也可以hook OpenDexFile裡面的 open函數的參數,進行內存dump,獲得dex文件,

如下是open函數的實現第一個參數就是指針base,第二個參數是size

219std::unique_ptr<const DexFile> DexFileLoader::Open(const uint8_t* base,220 size_t size,221 const std::string& location,222 uint32_t location_checksum,223 const OatDexFile* oat_dex_file,224 bool verify,225 bool verify_checksum,226 std::string* error_msg) const {227 return OpenCommon(base,228 size,229 /*data_base*/ nullptr,230 /*data_size*/ 0,231 location,232 location_checksum,233 oat_dex_file,234 verify,235 verify_checksum,236 error_msg,237 /*container*/ nullptr,238 /*verify_result*/ nullptr);239}

總結:

java層通過調用BaseDexClassLoader加載器,傳入了要加載的dex文件相關路徑,然後調用了loadDexFile方法,隨後調用了native層的方法openDexFileNative,進行相關的參數判斷,最後調用了OpenDexFile函數,把dex的文件相關信息加載進入了內存,返回值為DexFile對象指針,該對象裡面含有dex文件的相關結構信息等。我們可以通過hook這個OpenDexFile函數,獲取返回值,得到dex文件。也可以通過hook OpenDexFile裡面 open函數的參數,進行內存dump獲取文件。

--官方論壇

www.52pojie.cn

--推薦給朋友

公眾微信號:吾愛破解論壇

或搜微信號:pojie_52

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 鑽石舞台 的頭像
    鑽石舞台

    鑽石舞台

    鑽石舞台 發表在 痞客邦 留言(0) 人氣()