Hookメカニズム学習(四)-プラグインロードメカニズム
10129 ワード
weishu_ブログ
基本原理:システムはClassLoaderによって必要なActivityクラスをロードし、反射呼び出しコンストラクタによってActivityオブジェクトを作成します.
必要性:AndroidシステムはPathClassLoaderを使用してActivityなどのコンポーネントのロードを行う.apkがインストールされると、APKファイルのコードやリソースは、/data/app/package_nameなどの固定ディレクトリにシステムに格納されます.システムは、クラスのロードを行うときに、自動的にこのクラスを探しに行きます.しかし、システムはプラグインに存在するActivityコンポーネントの情報を知らないため、通常はプラグインのクラスをロードできません.LoakApk:LoadedApkオブジェクトは、メモリ内のAPKファイルの表示です.Apkファイルに関する情報、例えばApkファイルのコードやリソース、さらにはコード内のActivity、Serviceなどのコンポーネントの情報もこのオブジェクトから取得できます.
1.プラグインに対応するClassLoaderを構築してプラグインをロードする
基本原理:1まず反射呼び出しgetPackageInfoNoCheckによりLoadApkを生成し、このLoadApkに対応するClassLoaderのオブジェクトを作成し、ClassLoaderのパスをプラグインのパスに設定し、このLoadApkを早いActivity ThreadのmPackagesに保存する.これにより、Activityなどのプラグインコンポーネントを作成する際に、構築されたプラグインに対応するClassLoaderを使用してプラグインコンポーネントをロードします.2 getPackageInfoNoCheckには3つのパラメータが必要なので、まず各パラメータr.packageInfo:LoadApkを反射する必要があるので、プラグイン対応のClassLoaderを作成するには、まずプラグインLoadApkを作成します.
**原理:**LoadApkのキャッシュr.packageInfoはgetPackageInfoNoCheckメソッドで取得
getPackageInfoNoCheck簡単にgetPackageInfo()を呼び出しました
getPackageInfo:mPackagesによるLoadedApkキャッシュ
作り方:LoadApkはmPackagesでキャッシュするので、mPackagesを反射してプラグイン対応のLoadApkをmPackagesに保存することができます
ステップ1:反射Activity TheadのmPackagesを取得する
ステップ2:プラグイン対応のLoadApkを作成してmPackages 1に保存するには、publicメソッドの安定性と互換性がより良いため、getPackageInfoNoCheckではなくHook getPackageInfoNoCheckを採用します.2 getPackageInfoNoCheckは2つのパラメータを用意する必要があります:
1.1 PackageParserを構築する.Package:このクラスはPackageParserから解析したapkパケットの情報を表し、ディスク上のapkファイルのメモリ内のデータ構造表現である.したがって、このクラスを取得するには、apkファイル全体を解析する必要があります.PackageParserを使用します.parsePackage()で解析します.
1.2 int flags:パラメータは解析パケットで使用するflagであり、解析のすべての情報、すなわち0を直接選択する.1.3 PackageUserStateの構築:異なるユーザーのパッケージの情報を表します.Androidはマルチタスクマルチユーザーシステムであるため、異なるユーザーが同じパッケージで異なる状態になる可能性があります.ここではパケットの情報を取得するだけなので、デフォルトを直接使用すればいいです.
ステップ3:ClassLoader 1を置き換えgetPackageInfoNoCheckを呼び出してLoadedApkを取得する
2 LoadApkのClassLoaderを置き換え、Activity ThreadのmPackagesに追加します.
2.ホストClassloaderプラグインのパスを伝え、ホストClassloaderを使用してロードする
基本原理:1インストールされたApkはPathClassLoaderを使用してdata/packageディレクトリの下のクラスをロードし、PathClassLoaderはBaseDexClassLoaderに継承され、BaseDexClassLoaderはfindClass()スキームによってクラスをロードし、findClass()はpathListを呼び出す.findClass(). 2 DexPathList:DexElementsでBaseDexClassLoaderをロードする.findClass();
DexPathList.findClass
3プラグインの情報をdexElementsに保存する:
シナリオ1:ClassLoaderの利点を構築する:マルチClassLoaderメカニズム、各プラグインには対応するClassLoaderがあり、隔離性がよく、例えば2つの異なるプラグインが2つのライブラリの異なるバージョンを使用すると、衝突は発生しません.欠点:互換性が悪く、実現過程が複雑である.シナリオ2:パッチ・シナリオの利点:単純な欠点を実現する:単一ClassLoaderシナリオ、異なるプラグインはPathClassLoaderでロードされ、プラグイン間でプラグインとホスト間で使用されるクラス・ライブラリが衝突すると、タイプの衝突の結果が発生します.
一:Classloaderロードの基本原理
基本原理:システムはClassLoaderによって必要なActivityクラスをロードし、反射呼び出しコンストラクタによってActivityオブジェクトを作成します.
java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
StrictMode.incrementExpectedActivityCount(activity.getClass());
r.intent.setExtrasClassLoader(cl);
必要性:AndroidシステムはPathClassLoaderを使用してActivityなどのコンポーネントのロードを行う.apkがインストールされると、APKファイルのコードやリソースは、/data/app/package_nameなどの固定ディレクトリにシステムに格納されます.システムは、クラスのロードを行うときに、自動的にこのクラスを探しに行きます.しかし、システムはプラグインに存在するActivityコンポーネントの情報を知らないため、通常はプラグインのクラスをロードできません.LoakApk:LoadedApkオブジェクトは、メモリ内のAPKファイルの表示です.Apkファイルに関する情報、例えばApkファイルのコードやリソース、さらにはコード内のActivity、Serviceなどのコンポーネントの情報もこのオブジェクトから取得できます.
二:二つのロード方式
1.プラグインに対応するClassLoaderを構築してプラグインをロードする
基本原理:1まず反射呼び出しgetPackageInfoNoCheckによりLoadApkを生成し、このLoadApkに対応するClassLoaderのオブジェクトを作成し、ClassLoaderのパスをプラグインのパスに設定し、このLoadApkを早いActivity ThreadのmPackagesに保存する.これにより、Activityなどのプラグインコンポーネントを作成する際に、構築されたプラグインに対応するClassLoaderを使用してプラグインコンポーネントをロードします.2 getPackageInfoNoCheckには3つのパラメータが必要なので、まず各パラメータr.packageInfo:LoadApkを反射する必要があるので、プラグイン対応のClassLoaderを作成するには、まずプラグインLoadApkを作成します.
java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
StrictMode.incrementExpectedActivityCount(activity.getClass());
r.intent.setExtrasClassLoader(cl);
**原理:**LoadApkのキャッシュr.packageInfoはgetPackageInfoNoCheckメソッドで取得
final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
r.packageInfo = getPackageInfoNoCheck(
r.activityInfo.applicationInfo, r.compatInfo);
handleLaunchActivity(r, null);
getPackageInfoNoCheck簡単にgetPackageInfo()を呼び出しました
public final LoadedApk getPackageInfoNoCheck(ApplicationInfo ai,
CompatibilityInfo compatInfo) {
return getPackageInfo(ai, compatInfo, null, false, true, false);
}
getPackageInfo:mPackagesによるLoadedApkキャッシュ
private LoadedApk getPackageInfo(ApplicationInfo aInfo, CompatibilityInfo compatInfo,
ClassLoader baseLoader, boolean securityViolation, boolean includeCode,
boolean registerPackage) {
// userid
final boolean differentUser = (UserHandle.myUserId() != UserHandle.getUserId(aInfo.uid));
synchronized (mResourcesManager) {
//
WeakReference ref;
if (differentUser) {
// Caching not supported across users
ref = null;
} else if (includeCode) {
ref = mPackages.get(aInfo.packageName);
} else {
ref = mResourcePackages.get(aInfo.packageName);
}
LoadedApk packageInfo = ref != null ? ref.get() : null;
if (packageInfo == null || (packageInfo.mResources != null
&& !packageInfo.mResources.getAssets().isUpToDate())) {
// , new
packageInfo =
new LoadedApk(this, aInfo, compatInfo, baseLoader,
securityViolation, includeCode &&
(aInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0, registerPackage);
// 。。
return packageInfo;
}
}
作り方:LoadApkはmPackagesでキャッシュするので、mPackagesを反射してプラグイン対応のLoadApkをmPackagesに保存することができます
ステップ1:反射Activity TheadのmPackagesを取得する
Class> activityThreadClass = Class.forName("android.app.ActivityThread");
Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread");
currentActivityThreadMethod.setAccessible(true);
Object currentActivityThread = currentActivityThreadMethod.invoke(null);
// mPackages , dex
Field mPackagesField = activityThreadClass.getDeclaredField("mPackages");
mPackagesField.setAccessible(true);
Map mPackages = (Map) mPackagesField.get(currentActivityThread);
ステップ2:プラグイン対応のLoadApkを作成してmPackages 1に保存するには、publicメソッドの安定性と互換性がより良いため、getPackageInfoNoCheckではなくHook getPackageInfoNoCheckを採用します.2 getPackageInfoNoCheckは2つのパラメータを用意する必要があります:
ApplicationInfo aInfo, CompatibilityInfo compatInfo
第3歩:ApplicationInfo情報の準備:PackageParseを使用してAndroid manifestファイルのApplicationInfo情報を解析します.1 generateApplicationInfoによりApplicationを取得する.3つのパラメータを用意する必要がありますpublic static ApplicationInfo generateApplicationInfo(Package p, int flags,
PackageUserState state)
1.1 PackageParserを構築する.Package:このクラスはPackageParserから解析したapkパケットの情報を表し、ディスク上のapkファイルのメモリ内のデータ構造表現である.したがって、このクラスを取得するには、apkファイル全体を解析する必要があります.PackageParserを使用します.parsePackage()で解析します.
// , Package
// android.content.pm.PackageParser#parsePackage Package
// PackageParser
Object packageParser = packageParserClass.newInstance();
// PackageParser.parsePackage apk
Method parsePackageMethod = packageParserClass.getDeclaredMethod("parsePackage", File.class, int.class);
// android.content.pm.PackageParser.Package
Object packageObj = parsePackageMethod.invoke(packageParser, apkFile, 0);
1.2 int flags:パラメータは解析パケットで使用するflagであり、解析のすべての情報、すなわち0を直接選択する.1.3 PackageUserStateの構築:異なるユーザーのパッケージの情報を表します.Androidはマルチタスクマルチユーザーシステムであるため、異なるユーザーが同じパッケージで異なる状態になる可能性があります.ここではパケットの情報を取得するだけなので、デフォルトを直接使用すればいいです.
/ mDefaultPackageUserState
Object defaultPackageUserState = packageUserStateClass.newInstance();
// !!!!!!!!!!!!!!
ApplicationInfo applicationInfo = (ApplicationInfo) generateApplicationInfoMethod.invoke(packageParser,
packageObj, 0, defaultPackageUserState);
String apkPath = apkFile.getPath();
applicationInfo.sourceDir = apkPath;
applicationInfo.publicSourceDir = apkPath;
ステップ3:ClassLoader 1を置き換えgetPackageInfoNoCheckを呼び出してLoadedApkを取得する
// android.content.res.CompatibilityInfo
Class> compatibilityInfoClass = Class.forName("android.content.res.CompatibilityInfo");
Method getPackageInfoNoCheckMethod = activityThreadClass.getDeclaredMethod("getPackageInfoNoCheck", ApplicationInfo.class, compatibilityInfoClass);
Field defaultCompatibilityInfoField = compatibilityInfoClass.getDeclaredField("DEFAULT_COMPATIBILITY_INFO");
defaultCompatibilityInfoField.setAccessible(true);
Object defaultCompatibilityInfo = defaultCompatibilityInfoField.get(null);
ApplicationInfo applicationInfo = generateApplicationInfo(apkFile);
Object loadedApk = getPackageInfoNoCheckMethod.invoke(currentActivityThread, applicationInfo, defaultCompatibilityInfo);
2 LoadApkのClassLoaderを置き換え、Activity ThreadのmPackagesに追加します.
2.ホストClassloaderプラグインのパスを伝え、ホストClassloaderを使用してロードする
基本原理:1インストールされたApkはPathClassLoaderを使用してdata/packageディレクトリの下のクラスをロードし、PathClassLoaderはBaseDexClassLoaderに継承され、BaseDexClassLoaderはfindClass()スキームによってクラスをロードし、findClass()はpathListを呼び出す.findClass(). 2 DexPathList:DexElementsでBaseDexClassLoaderをロードする.findClass();
protected Class> findClass(String name) throws ClassNotFoundException {
List suppressedExceptions = new ArrayList();
Class c = pathList.findClass(name, suppressedExceptions);
if (c == null) {
ClassNotFoundException cnfe = new ClassNotFoundException("Didn't find class \"" + name + "\" on path: " + pathList);
for (Throwable t : suppressedExceptions) {
cnfe.addSuppressed(t);
}
throw cnfe;
}
return c;
}
DexPathList.findClass
public Class findClass(String name, List suppressed) {
for (Element element : dexElements) {
DexFile dex = element.dexFile;
if (dex != null) {
Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
if (clazz != null) {
return clazz;
}
}
}
if (dexElementsSuppressedExceptions != null) {
suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
}
return null;
}
3プラグインの情報をdexElementsに保存する:
public static void patchClassLoader(ClassLoader cl, File apkFile, File optDexFile)
throws IllegalAccessException, NoSuchMethodException, IOException, InvocationTargetException, InstantiationException, NoSuchFieldException {
// BaseDexClassLoader : pathList
Field pathListField = DexClassLoader.class.getSuperclass().getDeclaredField("pathList");
pathListField.setAccessible(true);
Object pathListObj = pathListField.get(cl);
// PathList: Element[] dexElements
Field dexElementArray = pathListObj.getClass().getDeclaredField("dexElements");
dexElementArray.setAccessible(true);
Object[] dexElements = (Object[]) dexElementArray.get(pathListObj);
// Element
Class> elementClass = dexElements.getClass().getComponentType();
// ,
Object[] newElements = (Object[]) Array.newInstance(elementClass, dexElements.length + 1);
// Element(File file, boolean isDirectory, File zip, DexFile dexFile)
Constructor> constructor = elementClass.getConstructor(File.class, boolean.class, File.class, DexFile.class);
Object o = constructor.newInstance(apkFile, false, apkFile, DexFile.loadDex(apkFile.getCanonicalPath(), optDexFile.getAbsolutePath(), 0));
Object[] toAddElementArray = new Object[] { o };
// elements
System.arraycopy(dexElements, 0, newElements, 0, dexElements.length);
// element
System.arraycopy(toAddElementArray, 0, newElements, dexElements.length, toAddElementArray.length);
//
dexElementArray.set(pathListObj, newElements);
}
2つのロードスキームの比較
シナリオ1:ClassLoaderの利点を構築する:マルチClassLoaderメカニズム、各プラグインには対応するClassLoaderがあり、隔離性がよく、例えば2つの異なるプラグインが2つのライブラリの異なるバージョンを使用すると、衝突は発生しません.欠点:互換性が悪く、実現過程が複雑である.シナリオ2:パッチ・シナリオの利点:単純な欠点を実現する:単一ClassLoaderシナリオ、異なるプラグインはPathClassLoaderでロードされ、プラグイン間でプラグインとホスト間で使用されるクラス・ライブラリが衝突すると、タイプの衝突の結果が発生します.