DexClassLoader加载流程分析

admin 2024年5月8日21:45:03评论5 views字数 15699阅读52分19秒阅读模式

一、前言

      在安卓app加固、插件化开发过程中,DexClassLoader用的比较多。像现在的各种类Xposed的框架在加载外部插件的时候,也使用了DexClassLoader来进行动态加载模块,然后反射调用插件配置的入口方法。

      在8.0系统以后,新增了一个InMemoryDexClassLoader。和DexClassLoader都是继承于BaseDexClassLoader。本章节只分析DexClassLoader,下一章分析InMemoryDexClassLoader。

二、加载流程分析

 1.DexClassLoader类分析

      DexClassLoader文件路径如下:

libcoredalviksrcmainjavadalviksystemDexClassLoader.java

      该类的构造函数如下:

public class DexClassLoader extends BaseDexClassLoader {    //构造函数,第一个参数dex文件路径    //第二个参数编译优化存放路径    //第三个参数so库的搜索路径    //第四个参数为父classLoader    public DexClassLoader(String dexPath, String optimizedDirectory,            String librarySearchPath, ClassLoader parent) {        super(dexPath, null, librarySearchPath, parent);    }}

     由以上构造函数分析可以知道调用了父类的构造函数。父类为BaseDexClassLoader。        

   2.BaseDexClassLoader类分析

       BaseDexClassLoader类文件路径如下:

libcoredalviksrcmainjavadalviksystemBaseDexClassLoader.java

      在改该类中通过构造函数追踪,追踪调用了的构造函数链如下:

//构造函数调用1public BaseDexClassLoader(String dexPath, File optimizedDirectory,            String librarySearchPath, ClassLoader parent) {        this(dexPath, librarySearchPath, parent, null, false);    } //构造函数调用2    public BaseDexClassLoader(String dexPath, File optimizedDirectory,            String librarySearchPath, ClassLoader parent, boolean isTrusted) {        this(dexPath, librarySearchPath, parent, null, isTrusted);    }//构造函数调用3    public BaseDexClassLoader(String dexPath,            String librarySearchPath, ClassLoader parent, ClassLoader[] libraries) {        this(dexPath, librarySearchPath, parent, libraries, false);    }    //构造函数调用4    public BaseDexClassLoader(String dexPath,            String librarySearchPath, ClassLoader parent, ClassLoader[] sharedLibraryLoaders,            boolean isTrusted) {        super(parent);        // Setup shared libraries before creating the path list. ART relies on the class loader        // hierarchy being finalized before loading dex files.        this.sharedLibraryLoaders = sharedLibraryLoaders == null                ? null                : Arrays.copyOf(sharedLibraryLoaders, sharedLibraryLoaders.length);        this.pathList = new DexPathList(this, dexPath, librarySearchPath, null, isTrusted);        if (reporter != null) {            reportClassLoaderChain();        }    }

    在以上构造函数4中,使用了DexPathList类传入dexPath构造对象。下面将分析DexPathList内部逻辑。

     3.DexPathList类流程分析

         DexPathList类文件路径:

libcoredalviksrcmainjavadalviksystemDexPathList.java

      该类中相关的构造函数如下:

public DexPathList(ClassLoader definingContext, String dexPath,            String librarySearchPath, File optimizedDirectory) {        this(definingContext, dexPath, librarySearchPath, optimizedDirectory, false);    }DexPathList(ClassLoader definingContext, String dexPath,            String librarySearchPath, File optimizedDirectory, boolean isTrusted) {        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>();        // save dexPath for BaseDexClassLoader        this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,                                           suppressedExceptions, definingContext, isTrusted);        // 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 (librarySearchPath):        //   1.1. Native library directories        //   1.2. Path to libraries in apk-files        //   2. The VM's library path from the system property for system libraries        //      also known as java.library.path        //        // This order was reversed prior to Gingerbread; see http://b/2933456.        this.nativeLibraryDirectories = splitPaths(librarySearchPath, false);        this.systemNativeLibraryDirectories =                splitPaths(System.getProperty("java.library.path"), true);        this.nativeLibraryPathElements = makePathElements(getAllNativeLibraryDirectories());        if (suppressedExceptions.size() > 0) {            this.dexElementsSuppressedExceptions =                suppressedExceptions.toArray(new IOException[suppressedExceptions.size()]);        } else {            dexElementsSuppressedExceptions = null;        }    }

    在以上构造函数中,关键部分为makeDexElements函数调用。该函数实现如下:

 private static Element[] makeDexElements(List<File> files, File optimizedDirectory,            List<IOException> suppressedExceptions, ClassLoader loader, boolean isTrusted) {      Element[] elements = new Element[files.size()];      int elementsPos = 0;      /*       * Open all files and load the (direct or contained) dex files up front.       */      for (File file : files) {          if (file.isDirectory()) {              // We support directories for looking up resources. Looking up resources in              // directories is useful for running libcore tests.              elements[elementsPos++] = new Element(file);          } else if (file.isFile()) {              String name = file.getName();              DexFile dex = null;              if (name.endsWith(DEX_SUFFIX)) {                  // Raw dex file (not inside a zip/jar).                  try {                      dex = loadDexFile(file, optimizedDirectory, loader, elements);                      if (dex != null) {                          elements[elementsPos++] = new Element(dex, null);                      }                  } catch (IOException suppressed) {                      System.logE("Unable to load dex file: " + file, suppressed);                      suppressedExceptions.add(suppressed);                  }              } else {                  try {                      dex = loadDexFile(file, optimizedDirectory, loader, elements);                  } 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);                  }                  if (dex == null) {                      elements[elementsPos++] = new Element(file);                  } else {                      elements[elementsPos++] = new Element(dex, file);                  }              }              if (dex != null && isTrusted) {                dex.setTrusted();              }          } else {              System.logW("ClassLoader referenced unknown path: " + file);          }      }      if (elementsPos != elements.length) {          elements = Arrays.copyOf(elements, elementsPos);      }      return elements;    }

     以上函数调用了loadDexFile。loadDexFile函数实现如下:

private static DexFile loadDexFile(File file, File optimizedDirectory, ClassLoader loader,                                       Element[] elements)            throws IOException {        if (optimizedDirectory == null) {            return new DexFile(file, loader, elements);        } else {            String optimizedPath = optimizedPathFor(file, optimizedDirectory);            return DexFile.loadDex(file.getPath(), optimizedPath, 0, loader, elements);        }    }

       该方法会根据传入的optimizedDirectory是否为null,进行不同的DexFile构造。在实际开发过程中,一般optimizedDirectory都不为null,所以我们追踪DexFile.loadDex调用情况。

   4.DexFile内部流程分析

       DexFile文件路径如下:

libcoredalviksrcmainjavadalviksystemDexFile.java

        该类中loadDex方法实现如下:

static DexFile loadDex(String sourcePathName, String outputPathName,        int flags, ClassLoader loader, DexPathList.Element[] elements) 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, loader, elements);    }

       最终还是调用了DexFile的构造函数,根据参数类型找到DexFile的构造函数如下:

private DexFile(String sourceName, String outputName, int flags, ClassLoader loader,            DexPathList.Element[] elements) 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, loader, elements);        mInternalCookie = mCookie;        mFileName = sourceName;        //System.out.println("DEX FILE cookie is " + mCookie + " sourceName=" + sourceName + " outputName=" + outputName);    }

        该构造函数里面调用了openDexFile方法,该方法的实现如下:

 private static Object openDexFile(String sourceName, String outputName, int flags,            ClassLoader loader, DexPathList.Element[] elements) 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,                                 loader,                                 elements);    }

   openDexFile方法内部调用了openDexFileNative方法,openDexFileNative实现如下:

 private static native Object openDexFileNative(String sourceName, String outputName, int flags,            ClassLoader loader, DexPathList.Element[] elements);

    openDexFileNative是一个native方法,具体实现在DexFile类的jni代码中。在源码中搜索定位找到对应的jni实现文件为dalvik_system_DexFile.cc。

 5.dalvik_system_DexFile.cc中内部流程分析

        dalvik_system_DexFile.cc文件路径如下:

artruntimenativedalvik_system_DexFile.cc

      该文件中找到java层native方法openDexFileNative对应的jni实现方法为DexFile_openDexFileNative方法。该方法实现如下:

static jobject DexFile_openDexFileNative(JNIEnv* env,                                         jclass,                                         jstring javaSourceName,                                         jstring javaOutputName ATTRIBUTE_UNUSED,                                         jint flags ATTRIBUTE_UNUSED,                                         jobject class_loader,                                         jobjectArray dex_elements) {  ScopedUtfChars sourceName(env, javaSourceName);  if (sourceName.c_str() == nullptr) {    return nullptr;  }  std::vector<std::string> error_msgs;  const OatFile* oat_file = nullptr;  std::vector<std::unique_ptr<const DexFile>> dex_files =      Runtime::Current()->GetOatFileManager().OpenDexFilesFromOat(sourceName.c_str(),                                                                  class_loader,                                                                  dex_elements,                                                                  /*out*/ &oat_file,                                                                  /*out*/ &error_msgs);  return CreateCookieFromOatFileManagerResult(env, dex_files, oat_file, error_msgs);}

        该方法中关键的调用为:

Runtime::Current()->GetOatFileManager().OpenDexFilesFromOat

     在源码中找到OpenDexFilesFromOat实现文件为oat_file_manager.cc。

   6.oat_file_manager.cc中内部流程分析

         oat_file_manager.cpp源文件路径如下:

artruntimeoat_file_manager.cpp

       该文件中OpenDexFilesFromOat实现如下:

std::vector<std::unique_ptr<const DexFile>> OatFileManager::OpenDexFilesFromOat(    const char* dex_location,    jobject class_loader,    jobjectArray dex_elements,    const OatFile** out_oat_file,    std::vector<std::string>* error_msgs) {  ScopedTrace trace(__FUNCTION__);  CHECK(dex_location != nullptr);  CHECK(error_msgs != nullptr);  // Verify we aren't holding the mutator lock, which could starve GC if we  // have to generate or relocate an oat file.  Thread* const self = Thread::Current();  Locks::mutator_lock_->AssertNotHeld(self);  Runtime* const runtime = Runtime::Current();  std::unique_ptr<ClassLoaderContext> context;  // If the class_loader is null there's not much we can do. This happens if a dex files is loaded  // directly with DexFile APIs instead of using class loaders.  if (class_loader == nullptr) {    LOG(WARNING) << "Opening an oat file without a class loader. "                 << "Are you using the deprecated DexFile APIs?";    context = nullptr;  } else {    context = ClassLoaderContext::CreateContextForClassLoader(class_loader, dex_elements);  }  OatFileAssistant oat_file_assistant(dex_location,                                      kRuntimeISA,                                      !runtime->IsAotCompiler(),                                      only_use_system_oat_files_);  // Get the oat file on disk.  std::unique_ptr<const OatFile> oat_file(oat_file_assistant.GetBestOatFile().release());  VLOG(oat) << "OatFileAssistant(" << dex_location << ").GetBestOatFile()="            << reinterpret_cast<uintptr_t>(oat_file.get())            << " (executable=" << (oat_file != nullptr ? oat_file->IsExecutable() : false) << ")";  const OatFile* source_oat_file = nullptr;  CheckCollisionResult check_collision_result = CheckCollisionResult::kPerformedHasCollisions;  std::string error_msg;  if ((class_loader != nullptr || dex_elements != nullptr) && oat_file != nullptr) {    // Prevent oat files from being loaded if no class_loader or dex_elements are provided.    // This can happen when the deprecated DexFile.<init>(String) is called directly, and it    // could load oat files without checking the classpath, which would be incorrect.    // Take the file only if it has no collisions, or we must take it because of preopting.    check_collision_result = CheckCollision(oat_file.get(), context.get(), /*out*/ &error_msg);    bool accept_oat_file = AcceptOatFile(check_collision_result);    if (!accept_oat_file) {      // Failed the collision check. Print warning.      if (runtime->IsDexFileFallbackEnabled()) {        if (!oat_file_assistant.HasOriginalDexFiles()) {          // We need to fallback but don't have original dex files. We have to          // fallback to opening the existing oat file. This is potentially          // unsafe so we warn about it.          accept_oat_file = true;          LOG(WARNING) << "Dex location " << dex_location << " does not seem to include dex file. "                       << "Allow oat file use. This is potentially dangerous.";        } else {          // We have to fallback and found original dex files - extract them from an APK.          // Also warn about this operation because it's potentially wasteful.          LOG(WARNING) << "Found duplicate classes, falling back to extracting from APK : "                       << dex_location;          LOG(WARNING) << "NOTE: This wastes RAM and hurts startup performance.";        }      } else {        // TODO: We should remove this. The fact that we're here implies -Xno-dex-file-fallback        // was set, which means that we should never fallback. If we don't have original dex        // files, we should just fail resolution as the flag intended.        if (!oat_file_assistant.HasOriginalDexFiles()) {          accept_oat_file = true;        }        LOG(WARNING) << "Found duplicate classes, dex-file-fallback disabled, will be failing to "                        " load classes for " << dex_location;      }      LOG(WARNING) << error_msg;    }    if (accept_oat_file) {      VLOG(class_linker) << "Registering " << oat_file->GetLocation();      source_oat_file = RegisterOatFile(std::move(oat_file));      *out_oat_file = source_oat_file;    }  }  std::vector<std::unique_ptr<const DexFile>> dex_files;  // Load the dex files from the oat file.  if (source_oat_file != nullptr) {    bool added_image_space = false;    if (source_oat_file->IsExecutable()) {      ScopedTrace app_image_timing("AppImage:Loading");      // We need to throw away the image space if we are debuggable but the oat-file source of the      // image is not otherwise we might get classes with inlined methods or other such things.      std::unique_ptr<gc::space::ImageSpace> image_space;      if (ShouldLoadAppImage(check_collision_result,                             source_oat_file,                             context.get(),                             &error_msg)) {        image_space = oat_file_assistant.OpenImageSpace(source_oat_file);      }      if (image_space != nullptr) {        ScopedObjectAccess soa(self);        StackHandleScope<1> hs(self);        Handle<mirror::ClassLoader> h_loader(            hs.NewHandle(soa.Decode<mirror::ClassLoader>(class_loader)));        // Can not load app image without class loader.        if (h_loader != nullptr) {          std::string temp_error_msg;          // Add image space has a race condition since other threads could be reading from the          // spaces array.          {            ScopedThreadSuspension sts(self, kSuspended);            gc::ScopedGCCriticalSection gcs(self,                                            gc::kGcCauseAddRemoveAppImageSpace,                                            gc::kCollectorTypeAddRemoveAppImageSpace);            ScopedSuspendAll ssa("Add image space");            runtime->GetHeap()->AddSpace(image_space.get());          }          {            ScopedTrace trace2(StringPrintf("Adding image space for location %s", dex_location));            added_image_space = runtime->GetClassLinker()->AddImageSpace(image_space.get(),                                                                         h_loader,                                                                         dex_elements,                                                                         dex_location,                                                                         /*out*/&dex_files,                                                                         /*out*/&temp_error_msg);          }          if (added_image_space) {            // Successfully added image space to heap, release the map so that it does not get            // freed.            image_space.release();  // NOLINT b/117926937            // Register for tracking.            for (const auto& dex_file : dex_files) {              dex::tracking::RegisterDexFile(dex_file.get());            }          } else {            LOG(INFO) << "Failed to add image file " << temp_error_msg;            dex_files.clear();            {              ScopedThreadSuspension sts(self, kSuspended);              gc::ScopedGCCriticalSection gcs(self,                                              gc::kGcCauseAddRemoveAppImageSpace,                                              gc::kCollectorTypeAddRemoveAppImageSpace);              ScopedSuspendAll ssa("Remove image space");              runtime->GetHeap()->RemoveSpace(image_space.get());            }            // Non-fatal, don't update error_msg.          }        }      }    }    if (!added_image_space) {      DCHECK(dex_files.empty());      dex_files = oat_file_assistant.LoadDexFiles(*source_oat_file, dex_location);      // Register for tracking.      for (const auto& dex_file : dex_files) {        dex::tracking::RegisterDexFile(dex_file.get());      }    }    if (dex_files.empty()) {      error_msgs->push_back("Failed to open dex files from " + source_oat_file->GetLocation());    } else {      // Opened dex files from an oat file, madvise them to their loaded state.       for (const std::unique_ptr<const DexFile>& dex_file : dex_files) {         OatDexFile::MadviseDexFile(*dex_file, MadviseState::kMadviseStateAtLoad);       }    }  }  // Fall back to running out of the original dex file if we couldn't load any  // dex_files from the oat file.  if (dex_files.empty()) {    if (oat_file_assistant.HasOriginalDexFiles()) {      if (Runtime::Current()->IsDexFileFallbackEnabled()) {        static constexpr bool kVerifyChecksum = true;        const ArtDexFileLoader dex_file_loader;        if (!dex_file_loader.Open(dex_location,                                  dex_location,                                  Runtime::Current()->IsVerificationEnabled(),                                  kVerifyChecksum,                                  /*out*/ &error_msg,                                  &dex_files)) {          LOG(WARNING) << error_msg;          error_msgs->push_back("Failed to open dex files from " + std::string(dex_location)                                + " because: " + error_msg);        }      } else {        error_msgs->push_back("Fallback mode disabled, skipping dex files.");      }    } else {      error_msgs->push_back("No original dex files found for dex location "          + std::string(dex_location));    }  }  if (Runtime::Current()->GetJit() != nullptr) {    ScopedObjectAccess soa(self);    Runtime::Current()->GetJit()->RegisterDexFiles(        dex_files, soa.Decode<mirror::ClassLoader>(class_loader));  }  return dex_files;}

     以上代码主要是将dex文件转oat。如果转oat失败,那就打开dex的方式来加载。

DexClassLoader加载流程分析

原文始发于微信公众号(哆啦安全):DexClassLoader加载流程分析

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年5月8日21:45:03
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   DexClassLoader加载流程分析https://cn-sec.com/archives/787011.html

发表评论

匿名网友 填写信息