art profile的问题分析
- 1. art profile生成差异的问题
- 2. cts测试内容的分析
- 3. 模拟验证
- 4. snapshot-profile的流程
- 5. prepareAppProfile的流程
- 6. art调试
- 7. profman解析执行
- 8. libprofile保存prof
- 9. 解析prof的demo
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.apkV/NativeDevice: pm install-write 1581118244 CtsDexMetadataSplitApp.dm
/data/local/tmp/CtsDexMetadataSplitApp.dmV/NativeDevice: pm install-write 1581118244
CtsDexMetadataSplitApp.dm.fsv_sig
/data/local/tmp/CtsDexMetadataSplitApp.dm.fsv_sigV/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

可以看到上面二进制文件是有差异的(左边是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()));
}

最低0.47元/天 解锁文章
9021





