Android-6.0之PMS解析上篇

本文轉載於:http://www.iloveandroid.net/2016/06/20/Android_PackageManagerService-2/


前面介紹了pm命令如何使用以及PMS運行時的一些規則和行爲,現在就可以盡情享受PMS的代碼了。

PMS的入口點

PMS是由SystemServer啓動的。

1
Android6.0/frameworks/base/services/java/com/android/server/SystemServer.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private void startBootstrapServices(){

 // Wait for installd to finish starting up so that it has a chance to
        // create critical directories such as /data/user with the appropriate
        // permissions.  We need this to complete before we initialize other services.

Installer installer = mSystemServiceManager.startService(Installer.class);
......................................

 // Start the package manager.
        Slog.i(TAG, "Package Manager");
        mPackageManagerService = PackageManagerService.main(mSystemContext, installer,
                mFactoryTestMode != FactoryTest.FACTORY_TEST_OFF, mOnlyCore);
        mFirstBoot = mPackageManagerService.isFirstBoot();
        mPackageManager = mSystemContext.getPackageManager();
......................................
}

這樣就找到了分析PMS的入口點:

1
Android6.0/frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java的main方法

main函數中的第二個參數installer負責和native層中的installd守護進程進行socket通信,這裏在啓動Installer 的時候會有installd創建一些關鍵目錄,後續會詳細介紹installd守護進程。

第四個參數mOnlyCore用於判斷是否僅僅掃描系統的目錄,只有在與data分區加解密時纔會設置爲true,其他情況一般都爲false。

main函數也很簡單如下所示:

1
2
3
4
5
6
7
public static PackageManagerService main(Context context, Installer installer,
          boolean factoryTest, boolean onlyCore) {
      PackageManagerService m = new PackageManagerService(context, installer,
              factoryTest, onlyCore);
      ServiceManager.addService("package", m);
      return m;
  }

這裏主要做了兩件事:

  1. 創建PackageManagerService對象,也就是PMS的實體

  2. 將PMS向SMS中註冊,即加入SMS中,方便後續其他進程或者app通過SMS獲得PMS服務

PMS的構造函數中,做了大量的工作,總結起來就是掃描Android系統中的apk,並且建立相應的數據結構去管理Package的信息,四大組件的信息,權限信息等內容。

PMS構造函數分析之Settings

源碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
mLazyDexOpt = "eng".equals(SystemProperties.get("ro.build.type"));
mMetrics = new DisplayMetrics();
mSettings = new Settings(mPackages);
mSettings.addSharedUserLPw("android.uid.system", Process.SYSTEM_UID,
        ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
mSettings.addSharedUserLPw("android.uid.phone", RADIO_UID,
        ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
mSettings.addSharedUserLPw("android.uid.log", LOG_UID,
        ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
mSettings.addSharedUserLPw("android.uid.nfc", NFC_UID,
        ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
mSettings.addSharedUserLPw("android.uid.bluetooth", BLUETOOTH_UID,
        ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
mSettings.addSharedUserLPw("android.uid.shell", SHELL_UID,
        ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);

首先判斷build類型,build有三種:user,userdebug,eng。當爲eng時,意味着不需要進行odex優化。

接着創建一個DisplayMetrics對象,用於保存屏幕像素參數。

然後就是本次分析的重點Settings了.

創建一個Settings對象。

源碼路徑:

1
Android6.0/frameworks/base/services/core/java/com/android/server/pm/Settings.java

其構造方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Settings(Object lock) {
    this(Environment.getDataDirectory(), lock);
}

Settings(File dataDir, Object lock) {
    mLock = lock;

    mRuntimePermissionsPersistence = new RuntimePermissionPersistence(mLock);

    mSystemDir = new File(dataDir, "system");
    mSystemDir.mkdirs();
    FileUtils.setPermissions(mSystemDir.toString(),
            FileUtils.S_IRWXU|FileUtils.S_IRWXG
            |FileUtils.S_IROTH|FileUtils.S_IXOTH,
            -1, -1);
    mSettingsFilename = new File(mSystemDir, "packages.xml");
    mBackupSettingsFilename = new File(mSystemDir, "packages-backup.xml");
    mPackageListFilename = new File(mSystemDir, "packages.list");
    FileUtils.setPermissions(mPackageListFilename, 0640, SYSTEM_UID, PACKAGE_INFO_GID);

    // Deprecated: Needed for migration
    mStoppedPackagesFilename = new File(mSystemDir, "packages-stopped.xml");
    mBackupStoppedPackagesFilename = new File(mSystemDir, "packages-stopped-backup.xml");
}

dataDir爲

1
/data

所以mSystemDir爲

1
/data/system

該構造函數中主要創建了前面文章中介紹的關於PMS的若干配置文件的File對象,並且設置了權限等。

mSettings.addSharedUserLPw

addSharedUserLPw方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
SharedUserSetting addSharedUserLPw(String name, int uid, int pkgFlags, int pkgPrivateFlags) {
        SharedUserSetting s = mSharedUsers.get(name);
        if (s != null) {
            if (s.userId == uid) {
                return s;
            }
            PackageManagerService.reportSettingsProblem(Log.ERROR,
                    "Adding duplicate shared user, keeping first: " + name);
            return null;
        }
        s = new SharedUserSetting(name, pkgFlags, pkgPrivateFlags);
        s.userId = uid;
        if (addUserIdLPw(uid, s, name)) {
            mSharedUsers.put(name, s);
            return s;
        }
        return null;
    }

該方法主要是創建共享UID的相關信息。

這裏先以name爲key在mSharedUsers中查看找是否已經有名爲name的共享UID了。如果有的話,判斷此UID和傳入的uid是否相等,不相等的就報錯。這意味着,不能對已有的共享UID信息,綁定新的uid。

如果沒有的話,創建一個新的SharedUserSetting,並將其加入mSharedUsers中去。

pkgFlags爲ApplicationInfo.FLAG_SYSTEM表明該app是system app.

pkgPrivateFlags爲ApplicationInfo.PRIVATE_FLAG_PRIVILEGED表明具備system權限。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
private boolean addUserIdLPw(int uid, Object obj, Object name) {
    if (uid > Process.LAST_APPLICATION_UID) {
        return false;
    }

    if (uid >= Process.FIRST_APPLICATION_UID) {
        int N = mUserIds.size();
        final int index = uid - Process.FIRST_APPLICATION_UID;
        while (index >= N) {
            mUserIds.add(null);
            N++;
        }
        if (mUserIds.get(index) != null) {
            PackageManagerService.reportSettingsProblem(Log.ERROR,
                    "Adding duplicate user id: " + uid
                    + " name=" + name);
            return false;
        }
        mUserIds.set(index, obj);
    } else {
        if (mOtherUserIds.get(uid) != null) {
            PackageManagerService.reportSettingsProblem(Log.ERROR,
                    "Adding duplicate shared id: " + uid
                            + " name=" + name);
            return false;
        }
        mOtherUserIds.put(uid, obj);
    }
    return true;
}

主要針對普通uid和system uid進行不同處理。

每個普通的app安裝的時候,都會被分配一個uid,這和Android系統一些系統服務和具備高權限的app的uid是不在一個區間的。

普通的app的uid加入mUsrIds,其他的加入mOtherUserIds.

PMS構造方法中創建了android.uid.system,android.uid.phone,android.uid.log,android.uid.nfc,android.uid.bluetooth,android.uid.shell這些共享uid。主要給以下場景使用提供便利:

在一些系統app的AndroidManifest.xml中會有下面的信息:

1
android:sharedUserId="android.uis.system"

或者android.uid.phone等,如果不提前創建好,就沒辦法擁有這些共享uid所具備的權限了。

創建dexopt優化器對象

源碼:

1
2
3
4
5
6
mInstaller = installer;
mPackageDexOptimizer = new PackageDexOptimizer(this);
mMoveCallbacks = new MoveCallbacks(FgThread.get().getLooper());

mOnPermissionChangeListeners = new OnPermissionChangeListeners(
        FgThread.get().getLooper());

mInstaller是傳入的與installd守護進程通信的installer。

創建PackageDexOptimizer對象,該類主要用來執行ART中的patchoat命令,用來對oat文件的偏移值進行隨機化。該類是Android M 中才有的。

創建監聽權限更顯的監聽者。因爲Android M中允許動態修改App權限。

創建和初始化SystemConfig

源碼:

1
2
3
4
SystemConfig systemConfig = SystemConfig.getInstance();
mGlobalGids = systemConfig.getGlobalGids();
mSystemPermissions = systemConfig.getSystemPermissions();
mAvailableFeatures = systemConfig.getAvailableFeatures();

SystemConfig會讀取

1
/system/etc/permissions

文件夾中的相關文件。

看看其構造方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
SystemConfig() {
        // Read configuration from system
        readPermissions(Environment.buildPath(
                Environment.getRootDirectory(), "etc", "sysconfig"), false);
        // Read configuration from the old permissions dir
        readPermissions(Environment.buildPath(
                Environment.getRootDirectory(), "etc", "permissions"), false);
        // Only read features from OEM config
        readPermissions(Environment.buildPath(
                Environment.getOemDirectory(), "etc", "sysconfig"), true);
        readPermissions(Environment.buildPath(
                Environment.getOemDirectory(), "etc", "permissions"), true);
    }

其中rootDirectory是“/system”.oemDirectory是”/oem”.也就是會嘗試依次讀取

1
/system/etc/sysconfig
/system/etc/permissions
/oem/etc/sysconfig
/oem/etc/permissions

這四個目錄中的permission文件。

permission文件的讀取是通過readPermissions函數完成的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
void readPermissions(File libraryDir, boolean onlyFeatures) {
     // Read permissions from given directory.
     if (!libraryDir.exists() || !libraryDir.isDirectory()) {
         if (!onlyFeatures) {
             Slog.w(TAG, "No directory " + libraryDir + ", skipping");
         }
         return;//如果文件夾不存在,或者其不是一個文件夾,退出
     }
     if (!libraryDir.canRead()) {
         Slog.w(TAG, "Directory " + libraryDir + " cannot be read");
         return;//如果不可讀,則退出
     }

     // Iterate over the files in the directory and scan .xml files
     File platformFile = null;
     for (File f : libraryDir.listFiles()) {
         // We'll read platform.xml last
         if (f.getPath().endsWith("etc/permissions/platform.xml")) {
             platformFile = f;
             continue;//先不處理platform.xml,最後會單獨處理
         }

         if (!f.getPath().endsWith(".xml")) {
             Slog.i(TAG, "Non-xml file " + f + " in " + libraryDir + " directory, ignoring");
             continue;//只處理xml文件
         }
         if (!f.canRead()) {
             Slog.w(TAG, "Permissions library file " + f + " cannot be read");
             continue;//如果xml文件不可讀,跳過
         }

         readPermissionsFromXml(f, onlyFeatures);//解析xml文件
     }

     // Read platform permissions last so it will take precedence
     if (platformFile != null) {
        //最後單獨處理platform.xml文件
         readPermissionsFromXml(platformFile, onlyFeatures);
     }

readPermissions方法的作用就是讀取指定目錄下的xml文件,然後調用readPermissionsFromXml方法來解析xml文件。

這些xml文件中指明瞭當前設備的硬件特性:

1
2
3
<permissions>
    <feature name="android.hardware.nfc" />
</permissions>

解析後,存儲在ArrayMap類型的變量mAvailableFeatures中。

還可能指明瞭一些運行行時除了加載framework中的庫之外,還要加載的一些java庫:

1
2
3
4
<permissions>
 <library name="com.qualcomm.qcrilhook"
          file="/system/framework/qcrilhook.jar"/>
</permissions>

這些library字段中的name屬性值,將會存放到PMS的成員變量mSharedLibrsries中。

其中platform中,指明瞭當前設備中定義的相關權限:

1
2
3
4
5
6
7
8
9
..................

<permission name="android.permission.BLUETOOTH_ADMIN" >
       <group gid="net_bt_admin" />
</permission>

..................
<assign-permission name="android.permission.MODIFY_AUDIO_SETTINGS" uid="media" />
..................

其中permission表示name中的字符串表示的權限賦予group標籤中的屬性gid中的用戶組。解析的時候,創建一個PermissionEntry,它是SystemConfig的一個內部類

1
2
3
4
5
6
7
8
9
10
public static final class PermissionEntry {
    public final String name;
    public int[] gids;
    public boolean perUser;

    PermissionEntry(String name, boolean perUser) {
        this.name = name;
        this.perUser = perUser;
    }
}

SystemConfig中有一個ArrayMap的mPermissions變量,創建的PermissionEntry會存入該變量中。而group中的uid則會保存在SystemConfig中的mGlobalGids整型數組中。

assign-permission表示把屬性name中的字符串表示的權限賦予屬性uid中的用戶。uid和name則存入SystemConfig中的SparseArray<arrayset> 類型的mSystemPermissions變量中。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章