art profile生成差异的问题分析

1. art profile生成差异的问题

最近Android S遇到一个问题,art升级以后,cts测试失败了,报错日志

I/ModuleListener: [1/1]
com.android.compatibility.common.tradefed.testtype.JarHostTest
com.android.cts.dexmetadata.InstallDexMetadataHostTest#testProfileSnapshotAfterInstall
FAILURE: arrays first differed at element [22]; expected:<7> but
was:<119> at
org.junit.internal.ComparisonCriteria.arrayEquals(ComparisonCriteria.java:78)
at
org.junit.internal.ComparisonCriteria.arrayEquals(ComparisonCriteria.java:28)
at org.junit.Assert.internalArrayEquals(Assert.java:534) at
org.junit.Assert.assertArrayEquals(Assert.java:343) at
org.junit.Assert.assertArrayEquals(Assert.java:354) at
com.android.cts.dexmetadata.InstallDexMetadataHostTest.testProfileSnapshotAfterInstall(InstallDexMetadataHostTest.java:356)

大概意思是com.android.cts.dexmetadata.InstallDexMetadataHostTest.testProfileSnapshotAfterInstall(InstallDexMetadataHostTest.java:356)测试报错,里面元素 [22],期望值是7,但实际是119。

本身对art这块了解得不多,遇到这个问题,很多内容需要从头看起,这里就将分析过程记录下来

2. cts测试内容的分析

找到cts的代码目录

cts$ grep -rn “testProfileSnapshotAfterInstall” .

然后你可以找到具体的代码位置

//cts/hostsidetests/dexmetadata/host/src/com/android/cts/dexmetadata/InstallDexMetadataHostTest.java
    public void testProfileSnapshotAfterInstall() throws Exception {
   
   //报错的测试case
        assumeProfilesAreEnabled();

        // Determine which profile to use.
        boolean useProfileForS = ApiLevelUtil.isAtLeast(getDevice(), "S");//这个是true

        // Install the app.
        File dmBaseFile = useProfileForS ? mDmBaseFileForS : mDmBaseFile;//.apk的文件,实际的apk
        File dmBaseFsvSigFile = useProfileForS ? mDmBaseFsvSigFileForS : mDmBaseFsvSigFile;//.fsv_sig文件,Fs Verity的签名校验文件
        String dmName = mDmBaseFile.getName();  // APK name with ".apk" replaced by ".dm".就是".dm"文件,全程dex metadata
        new InstallMultiple()
                .addApk(mApkBaseFile).addDm(dmBaseFile, dmBaseFsvSigFile, dmName).run();//后面有说明,这里指的安装apk

        // Take a snapshot of the installed profile.
        String snapshotCmd = "cmd package snapshot-profile " + INSTALL_PACKAGE;
        String result = getDevice().executeShellCommand(snapshotCmd);//执行adb指令
        assertTrue(result.trim().isEmpty());

        // Extract the profile bytes from the dex metadata and from the profile snapshot.
        byte[] rawDeviceProfile = extractProfileSnapshotFromDevice();//取出生成的snapshot prof文件(/data/misc/profman/com.android.cts.dexmetadata.splitapp.prof)
        byte[] rawMetadataProfile = extractProfileFromDexMetadata(dmBaseFile);//取出dm文件里面的prof文件
        if (useProfileForS) {
   
   
            ProfileReaderV15 snapshotReader = new ProfileReaderV15(rawDeviceProfile);//生成ProfileReaderV15的解析器,解析生成的snapshot prof文件
            ProfileReaderV15 expectedReader = new ProfileReaderV15(rawMetadataProfile);//解析".dm"里面的prof文件

            assertArrayEquals(expectedReader.dexFilesData, snapshotReader.dexFilesData);//匹配dexFilesData
            assertArrayEquals(expectedReader.extraDescriptorsData,
                              snapshotReader.extraDescriptorsData);//匹配extraDescriptorsData
            assertArrayEquals(expectedReader.classesData, snapshotReader.classesData);//匹配classesData
            assertArrayEquals(expectedReader.methodsData, snapshotReader.methodsData);//匹配methodsData,这里也是这个问题出错的地方
         } else {
   
   
            byte[] snapshotProfileBytes = new ProfileReaderV10(rawDeviceProfile).data;
            byte[] expectedProfileBytes = new ProfileReaderV10(rawMetadataProfile).data;

            assertArrayEquals(expectedProfileBytes, snapshotProfileBytes);
         }
    }

这个问题出错是在methodsData的内容匹配出现差异。

由于cts调试需要的环境比较多,查看cts的代码,看一下如何本地模拟

//安装的apk的方法
//cts/hostsidetests/dexmetadata/host/src/com/android/cts/dexmetadata/BaseInstallMultiple.java
   private void run(boolean expectingSuccess) throws DeviceNotAvailableException {
   
   
        final ITestDevice device = mDevice;

        // Create an install session
        final StringBuilder cmd = new StringBuilder();
        cmd.append("pm install-create");
        for (String arg : mArgs) {
   
   
            cmd.append(' ').append(arg);//第一个指令是pm install-create -g
        }

        String result = device.executeShellCommand(cmd.toString());
        TestCase.assertTrue(result, result.startsWith("Success"));

        final int start = result.lastIndexOf("[");
        final int end = result.lastIndexOf("]");
        int sessionId = -1;
        try {
   
   
            if (start != -1 && end != -1 && start < end) {
   
   
                sessionId = Integer.parseInt(result.substring(start + 1, end));//获取pm install-create -g返回的session id
            }
        } catch (NumberFormatException e) {
   
   
            throw new IllegalStateException("Failed to parse install session: " + result);
        }
        if (sessionId == -1) {
   
   
            throw new IllegalStateException("Failed to create install session: " + result);
        }

        // Push our files into session. Ideally we'd use stdin streaming,
        // but ddmlib doesn't support it yet.
        assert(mFilesToInstall.size() == mInstallNames.size());
        int numPushedFiles = 0;
        boolean success = false;
        try {
   
   
            for (int i = 0, size = mFilesToInstall.size(); i != size; ++i) {
   
   
                File file = mFilesToInstall.get(i);
                String name = mInstallNames.get(i);
                final String remotePath = "/data/local/tmp/" + name;//安装的文件都在/data/local/tmp/里面
                if (!device.pushFile(file, remotePath)) {
   
   
                    throw new IllegalStateException("Failed to push " + file);
                }

                cmd.setLength(0);
                cmd.append("pm install-write");//分别将之前的mFilesToInstall,如'.apk', '.dm', '.fsv_sig'用pm install-write写入
                cmd.append(' ').append(sessionId);
                cmd.append(' ').append(name);
                cmd.append(' ').append(remotePath);

                result = device.executeShellCommand(cmd.toString());
                TestCase.assertTrue(result, result.startsWith("Success"));
            }

            // Everything staged; let's pull trigger
            cmd.setLength(0);
            cmd.append("pm install-commit");
            cmd.append(' ').append(sessionId);//最后用pm install-commit结束安装指令

            result = device.executeShellCommand(cmd.toString());
            //...
    }

结合CTS的报错日志,测试过程看起来就是将"/data/local/tmp/"里面的apk安装进入系统,并执行cmd package snapshot-profile生成snapshot prof

V/NativeDevice: pm install-create -g on 83cf23d4 returned Success:
created install session [1581118244]

V/NativeDevice: pm install-write 1581118244 CtsDexMetadataSplitApp.apk
/data/local/tmp/CtsDexMetadataSplitApp.apk

V/NativeDevice: pm install-write 1581118244 CtsDexMetadataSplitApp.dm
/data/local/tmp/CtsDexMetadataSplitApp.dm

V/NativeDevice: pm install-write 1581118244
CtsDexMetadataSplitApp.dm.fsv_sig
/data/local/tmp/CtsDexMetadataSplitApp.dm.fsv_sig

V/NativeDevice: pm install-commit 1581118244

V/NativeDevice: cmd package snapshot-profile
com.android.cts.dexmetadata.splitapp

3. 模拟验证

首先将cts测试过程中的"/data/local/tmp/"文件pull出来,并放入需要验证的手机里面

adb root; adb push CtsDexMetadataSplitApp.apk /data/local/tmp/ ; adb push CtsDexMetadataSplitApp.dm /data/local/tmp/ ; adb push CtsDexMetadataSplitApp.dm.fsv_sig /data/local/tmp/ ;

安装apk

adb shell pm install-create -g
=>Success: created install session [1581118244]

adb shell pm install-write 1581118244 CtsDexMetadataSplitApp.apk /data/local/tmp/CtsDexMetadataSplitApp.apk
adb shell pm install-write 1581118244 CtsDexMetadataSplitApp.dm /data/local/tmp/CtsDexMetadataSplitApp.dm
adb shell pm install-write 1581118244 CtsDexMetadataSplitApp.dm.fsv_sig /data/local/tmp/CtsDexMetadataSplitApp.dm.fsv_sig
adb shell pm install-commit 1581118244

生成snapshot prof文件

adb shell cmd package snapshot-profile com.android.cts.dexmetadata.splitapp

=>二进制对比profman的prof和dm里面的primary.prof
adb pull /data/misc/profman/com.android.cts.dexmetadata.splitapp.prof .
解压CtsDexMetadataSplitApp.dm里面的primary.prof

prof二进制对比
可以看到上面二进制文件是有差异的(左边是snapshot prof,右边是dm的prof)。

本地可以复现,调试起来就方便很多

4. snapshot-profile的流程

=> 从adb shell cmd package snapshot-profile com.android.cts.dexmetadata.splitappk开始调查

//frameworks/base/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
                  case "snapshot-profile":
                      return runSnapshotProfile();

        mInterface.getArtManager().snapshotRuntimeProfile(profileType, packageName,
                codePath, callback, callingPackage);



//Installer.java
    public boolean createProfileSnapshot(int appId, String packageName, String profileName,
            String classpath) throws InstallerException {
   
   
        if (!checkBeforeRemote()) return false;
        try {
   
   
            return mInstalld.createProfileSnapshot(appId, packageName, profileName, classpath);
        } catch (Exception e) {
   
   
            throw InstallerException.from(e);
        }
    }

//frameworks/native/cmds/installd/InstalldNativeService.cpp
  binder::Status InstalldNativeService::createProfileSnapshot(int32_t appId,
          const std::string& packageName, const std::string& profileName,
          const std::string& classpath, bool* _aidl_return) {
   
   
      ENFORCE_UID(AID_SYSTEM);
      CHECK_ARGUMENT_PACKAGE_NAME(packageName);
      std::lock_guard<std::recursive_mutex> lock(mLock);
  
      *_aidl_return = create_profile_snapshot(appId, packageName, profileName, classpath);
      return ok();
  }

//frameworks/native/cmds/installd/dexopt.cpp
  bool create_profile_snapshot(int32_t app_id, const std::string& package_name,
          const std::string& profile_name, const std::string& classpath) {
   
   
      if (app_id == -1) {
   
   
          return create_boot_image_profile_snapshot(package_name, profile_name, classpath);
      } else {
   
   
          return create_app_profile_snapshot(app_id, package_name, profile_name, classpath);
      }
  }
static bool create_app_profile_snapshot(int32_t app_id,
                                        const std::string& package_name,
                                        const std::string& profile_name,
                                        const std::string& classpath) {
   
   
                
    args.SetupMerge(profiles_fd,
            snapshot_fd,
            apk_fds,
            dex_locations,
            /* for_snapshot= */ true,
            /* for_boot_image= */ false);


    void SetupMerge(const std::vector<unique_fd>& profiles_fd,
                    const unique_fd& reference_profile_fd,
                    const std::vector<unique_fd>& apk_fds = std::vector<unique_fd>(),
                    const std::vector<std::string>& dex_locations = std::vector<std::string>(),
                    bool for_snapshot = false,
                    bool for_boot_image = false) {
   
   
        SetupArgs(profiles_fd,
                  reference_profile_fd,
                  apk_fds,
                  dex_locations,
                  /*copy_and_update=*/ false,
                  for_snapshot,
                  for_boot_image);
    }

  void SetupArgs(const std::vector<unique_fd>& profile_fds,
                  const unique_fd& reference_profile_fd,
                  const std::vector<unique_fd>& apk_fds,
                  const std::vector<std::string>& dex_locations,
                  bool copy_and_update,
                  bool for_snapshot,
                  bool for_boot_image) {
   
   

        // TODO(calin): Assume for now we run in the bg compile job (which is in
        // most of the invocation). With the current data flow, is not very easy or
        // clean to discover this in RunProfman (it will require quite a messy refactoring).
        const char* profman_bin = select_execution_binary(
            kProfmanPath, kProfmanDebugPath, /*background_job_compile=*/ true);

        if (copy_and_update) {
   
   
            CHECK_EQ(1u, profile_fds.size());
            CHECK_EQ(1u, apk_fds.size());
        }
        if (reference_profile_fd != -1) {
   
   
            AddArg("--reference-profile-file-fd=" + std::to_string(reference_profile_fd.get()));
        }

        for (const unique_fd& fd : profile_fds) {
   
   
            AddArg("--profile-file-fd=" + std::to_string(fd.get()));
        }

        for (const unique_fd& fd : apk_fds) {
   
   
            AddArg("--apk-fd=" + std::to_string(fd.get()));
        }

   
评论 6
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值