Introduction to Loading Executable Files for Android Applications

Keywords: Android xml

There are three kinds of code files that can be loaded after the Android application starts, in the order of loading, as follows:

  1. jar specified by uses-library in Android manifest
  2. dex file of APK package root directory
  3. so file in APK package lib directory

Can you specify any jar you want to load in manifest? Of course not. This jar must have been configured in xml in the / etc/permissions / directory, such as:

//Intercept the relevant content in / etc/permissions/platform.xml file
<permissions>
    ...
    <library name="android.test.runner"
            file="/system/framework/android.test.runner.jar" />
    <library name="javax.obex"
            file="/system/framework/javax.obex.jar"/>
    ...
</permissions>

At system startup, PMS reads all configurations under / etc/permissions / and checks them when APK is installed. If the uses-library file of APK configuration does not exist in system configuration, the installation will be wrong.

After the successful installation of APK, we can get it through its Application Info:

ApplicationInfo aInfo;
...
aInfo.sourceDir //Execution file path
aInfo.publicSourceDir //resource file path
aInfo.nativeLibraryDir //so path
aInfo.dataDir //app data directory
aInfo.sharedLibraryFiles //Use-library configuration file

SorceDir and publicSourceDir are usually the same, so let's look at the construction code of Loaded Apk:

    public LoadedApk(ActivityThread activityThread, ApplicationInfo aInfo,
            CompatibilityInfo compatInfo,
            ActivityThread mainThread, ClassLoader baseLoader,
            boolean securityViolation, boolean includeCode) {
        mActivityThread = activityThread;
        mApplicationInfo = aInfo;
        mPackageName = aInfo.packageName;
        mAppDir = aInfo.sourceDir;
        final int myUid = Process.myUid();
        mResDir = aInfo.uid == myUid ? aInfo.sourceDir
                : aInfo.publicSourceDir;
        if (!UserHandle.isSameUser(aInfo.uid, myUid) && !Process.isIsolated()) {
            aInfo.dataDir = PackageManager.getDataDirForUser(UserHandle.getUserId(myUid),
                    mPackageName);
        }
        mSharedLibraries = aInfo.sharedLibraryFiles;
        mDataDir = aInfo.dataDir;
        mDataDirFile = mDataDir != null ? new File(mDataDir) : null;
        mLibDir = aInfo.nativeLibraryDir;
        ...
    }

When Loaded Apk is constructed, a directory of the code and resources contained in Application Info is obtained and saved, and then see the creation of PathClassLoader:

    public ClassLoader getClassLoader() {
        synchronized (this) {
            if (mClassLoader != null) {
                return mClassLoader;
            }

            if (mIncludeCode && !mPackageName.equals("android")) {
                String zip = mAppDir;
                String libraryPath = mLibDir;

                /*
                 * The following is a bit of a hack to inject
                 * instrumentation into the system: If the app
                 * being started matches one of the instrumentation names,
                 * then we combine both the "instrumentation" and
                 * "instrumented" app into the path, along with the
                 * concatenation of both apps' shared library lists.
                 */

                String instrumentationAppDir =
                        mActivityThread.mInstrumentationAppDir;
                String instrumentationAppLibraryDir =
                        mActivityThread.mInstrumentationAppLibraryDir;
                String instrumentationAppPackage =
                        mActivityThread.mInstrumentationAppPackage;
                String instrumentedAppDir =
                        mActivityThread.mInstrumentedAppDir;
                String instrumentedAppLibraryDir =
                        mActivityThread.mInstrumentedAppLibraryDir;
                String[] instrumentationLibs = null;

                if (mAppDir.equals(instrumentationAppDir)
                        || mAppDir.equals(instrumentedAppDir)) {
                    zip = instrumentationAppDir + ":" + instrumentedAppDir;
                    libraryPath = instrumentationAppLibraryDir + ":" + instrumentedAppLibraryDir;
                    if (! instrumentedAppDir.equals(instrumentationAppDir)) {
                        instrumentationLibs =
                            getLibrariesFor(instrumentationAppPackage);
                    }
                }

                if ((mSharedLibraries != null) ||
                        (instrumentationLibs != null)) {
                    zip =
                        combineLibs(mSharedLibraries, instrumentationLibs)
                        + ':' + zip;
                }

                /*
                 * With all the combination done (if necessary, actually
                 * create the class loader.
                 */

                if (ActivityThread.localLOGV)
                    Slog.v(ActivityThread.TAG, "Class path: " + zip + ", JNI path: " + libraryPath);

                // Temporarily disable logging of disk reads on the Looper thread
                // as this is early and necessary.
                StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();

                mClassLoader =
                    ApplicationLoaders.getDefault().getClassLoader(
                        zip, libraryPath, mBaseClassLoader);
                initializeJavaContextClassLoader();

                StrictMode.setThreadPolicy(oldPolicy);
            } else {
                if (mBaseClassLoader == null) {
                    mClassLoader = ClassLoader.getSystemClassLoader();
                } else {
                    mClassLoader = mBaseClassLoader;
                }
            }
            return mClassLoader;
        }
    }

Here we focus on the zip and libraryPath created by the getClassLoader function

String zip = mAppDir;
...
if ((mSharedLibraries != null) ||
                        (instrumentationLibs != null)) {
    zip = combineLibs(mSharedLibraries, instrumentationLibs) + ':' + zip;
}

From here, we can see that when creating PathClassLoader, the jar set by apk and uses-library is passed in as the dex path list to be loaded when the apk starts.

So far, the loading of dex and uses-library has been clear. What about so files?

Look at the code for System. load Library:

 public static void loadLibrary(String libName) {
        Runtime.getRuntime().loadLibrary(libName, VMStack.getCallingClassLoader());
}

VMStack.getCallingClassLoader() takes the current App's Path ClassLoader, and then looks at it

 void loadLibrary(String libraryName, ClassLoader loader) {
        if (loader != null) {
            String filename = loader.findLibrary(libraryName);
            if (filename == null) {
                throw new UnsatisfiedLinkError("Couldn't load " + libraryName +
                                               " from loader " + loader +
                                               ": findLibrary returned null");
            }
            String error = doLoad(filename, loader);
            if (error != null) {
                throw new UnsatisfiedLinkError(error);
            }
            return;
        }

        String filename = System.mapLibraryName(libraryName);
        List<String> candidates = new ArrayList<String>();
        String lastError = null;
        for (String directory : mLibPaths) {
            String candidate = directory + filename;
            candidates.add(candidate);

            if (IoUtils.canOpenReadOnly(candidate)) {
                String error = doLoad(candidate, loader);
                if (error == null) {
                    return; // We successfully loaded the library. Job done.
                }
                lastError = error;
            }
        }

        if (lastError != null) {
            throw new UnsatisfiedLinkError(lastError);
        }
        throw new UnsatisfiedLinkError("Library " + libraryName + " not found; tried " + candidates);
    }

Did you see it? It passed in the end.

String filename = loader.findLibrary(libraryName);

Call PathClassLoader's findLibrary to find so and return the file path to load so

summary

All the information of the executable files needed to start App has been fully stored in Application Info after the successful installation of App. When App starts, the corresponding Application Info can be obtained from PMS through Intent, and then the corresponding Loaded Apk and Path Class Loader can be generated based on it.

Posted by faswad on Tue, 27 Aug 2019 00:51:13 -0700