diff options
author | 2017-02-14 17:52:23 -0800 | |
---|---|---|
committer | 2017-02-14 18:33:42 -0800 | |
commit | 286839e40d302563befa0f43b071d8a19d744004 (patch) | |
tree | b4bd68341b6f844af11c582ed1be4cd7d0d28d5d | |
parent | dfabcb1ca015fe76f395e19997048bf8eca6eafb (diff) |
Add functionality to overwrite the /etc/preloaded-classes file.
* Requires root, disable-verity, reboot, root before use.
Test: manual through UI and sequencing
Change-Id: I68965334776e130b8220a5814b2525109cf96800
3 files changed, 144 insertions, 38 deletions
diff --git a/tools/preload2/src/com/android/preload/DeviceUtils.java b/tools/preload2/src/com/android/preload/DeviceUtils.java index 803a7f195a59..18cab7bee12d 100644 --- a/tools/preload2/src/com/android/preload/DeviceUtils.java +++ b/tools/preload2/src/com/android/preload/DeviceUtils.java @@ -16,13 +16,18 @@ package com.android.preload; +import com.android.ddmlib.AdbCommandRejectedException; import com.android.ddmlib.AndroidDebugBridge; import com.android.ddmlib.AndroidDebugBridge.IDeviceChangeListener; import com.android.preload.classdataretrieval.hprof.Hprof; import com.android.ddmlib.DdmPreferences; import com.android.ddmlib.IDevice; import com.android.ddmlib.IShellOutputReceiver; +import com.android.ddmlib.SyncException; +import com.android.ddmlib.TimeoutException; +import java.io.File; +import java.io.IOException; import java.util.Date; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; @@ -32,6 +37,18 @@ import java.util.concurrent.TimeUnit; */ public class DeviceUtils { + // Locations + private static final String PRELOADED_CLASSES_FILE = "/etc/preloaded-classes"; + // Shell commands + private static final String CREATE_EMPTY_PRELOADED_CMD = "touch " + PRELOADED_CLASSES_FILE; + private static final String DELETE_CACHE_CMD = "rm /data/dalvik-cache/*/*boot.art"; + private static final String DELETE_PRELOADED_CMD = "rm " + PRELOADED_CLASSES_FILE; + private static final String READ_PRELOADED_CMD = "cat " + PRELOADED_CLASSES_FILE; + private static final String START_SHELL_CMD = "start"; + private static final String STOP_SHELL_CMD = "stop"; + private static final String REMOUNT_SYSTEM_CMD = "mount -o rw,remount /system"; + private static final String UNSET_BOOTCOMPLETE_CMD = "setprop dev.bootcomplete \"0\""; + public static void init(int debugPort) { DdmPreferences.setSelectedDebugPort(debugPort); @@ -119,43 +136,56 @@ public class DeviceUtils { return !ret.contains("No such file or directory"); } - /** - * Remove files involved in a standard build that interfere with collecting data. This will - * remove /etc/preloaded-classes, which determines which classes are allocated already in the - * boot image. It also deletes any compiled boot image on the device. Then it restarts the - * device. - * - * This is a potentially long-running operation, as the boot after the deletion may take a while. - * The method will abort after the given timeout. - */ - public static boolean removePreloaded(IDevice device, long preloadedWaitTimeInSeconds) { - String oldContent = - DeviceUtils.doShellReturnString(device, "cat /etc/preloaded-classes", 1, TimeUnit.SECONDS); - if (oldContent.trim().equals("")) { - System.out.println("Preloaded-classes already empty."); - return true; - } - - // Stop the system server etc. - doShell(device, "stop", 100, TimeUnit.MILLISECONDS); - - // Remount /system, delete /etc/preloaded-classes. It would be nice to use "adb remount," - // but AndroidDebugBridge doesn't expose it. - doShell(device, "mount -o remount,rw /system", 500, TimeUnit.MILLISECONDS); - doShell(device, "rm /etc/preloaded-classes", 100, TimeUnit.MILLISECONDS); - // We do need an empty file. - doShell(device, "touch /etc/preloaded-classes", 100, TimeUnit.MILLISECONDS); - - // Delete the files in the dalvik cache. - doShell(device, "rm /data/dalvik-cache/*/*boot.art", 500, TimeUnit.MILLISECONDS); - - // We'll try to use dev.bootcomplete to know when the system server is back up. But stop - // doesn't reset it, so do it manually. - doShell(device, "setprop dev.bootcomplete \"0\"", 500, TimeUnit.MILLISECONDS); + /** + * Write over the preloaded-classes file with an empty or existing file and regenerate the boot + * image as necessary. + * + * @param device + * @param pcFile + * @param bootTimeout + * @throws AdbCommandRejectedException + * @throws IOException + * @throws TimeoutException + * @throws SyncException + * @return true if successfully overwritten, false otherwise + */ + public static boolean overwritePreloaded(IDevice device, File pcFile, long bootTimeout) + throws AdbCommandRejectedException, IOException, TimeoutException, SyncException { + boolean writeEmpty = (pcFile == null); + if (writeEmpty) { + // Check if the preloaded-classes file is already empty. + String oldContent = + doShellReturnString(device, READ_PRELOADED_CMD, 1, TimeUnit.SECONDS); + if (oldContent.trim().equals("")) { + System.out.println("Preloaded-classes already empty."); + return true; + } + } - // Start the system server. - doShell(device, "start", 100, TimeUnit.MILLISECONDS); + // Stop the system server etc. + doShell(device, STOP_SHELL_CMD, 1, TimeUnit.SECONDS); + // Remount the read-only system partition + doShell(device, REMOUNT_SYSTEM_CMD, 1, TimeUnit.SECONDS); + // Delete the preloaded-classes file + doShell(device, DELETE_PRELOADED_CMD, 1, TimeUnit.SECONDS); + // Delete the dalvik cache files + doShell(device, DELETE_CACHE_CMD, 1, TimeUnit.SECONDS); + if (writeEmpty) { + // Write an empty preloaded-classes file + doShell(device, CREATE_EMPTY_PRELOADED_CMD, 500, TimeUnit.MILLISECONDS); + } else { + // Push the new preloaded-classes file + device.pushFile(pcFile.getAbsolutePath(), PRELOADED_CLASSES_FILE); + } + // Manually reset the boot complete flag + doShell(device, UNSET_BOOTCOMPLETE_CMD, 1, TimeUnit.SECONDS); + // Restart system server on the device + doShell(device, START_SHELL_CMD, 1, TimeUnit.SECONDS); + // Wait for the boot complete flag and return the outcome. + return waitForBootComplete(device, bootTimeout); + } + private static boolean waitForBootComplete(IDevice device, long timeout) { // Do a loop checking each second whether bootcomplete. Wait for at most the given // threshold. Date startDate = new Date(); @@ -178,7 +208,7 @@ public class DeviceUtils { Date endDate = new Date(); long seconds = TimeUnit.SECONDS.convert(endDate.getTime() - startDate.getTime(), TimeUnit.MILLISECONDS); - if (seconds > preloadedWaitTimeInSeconds) { + if (seconds > timeout) { return false; } } diff --git a/tools/preload2/src/com/android/preload/Main.java b/tools/preload2/src/com/android/preload/Main.java index c42a19b58811..2265e9557c4b 100644 --- a/tools/preload2/src/com/android/preload/Main.java +++ b/tools/preload2/src/com/android/preload/Main.java @@ -29,6 +29,7 @@ import com.android.preload.actions.RunMonkeyAction; import com.android.preload.actions.ScanAllPackagesAction; import com.android.preload.actions.ScanPackageAction; import com.android.preload.actions.ShowDataAction; +import com.android.preload.actions.WritePreloadedClassesAction; import com.android.preload.classdataretrieval.ClassDataRetriever; import com.android.preload.classdataretrieval.hprof.Hprof; import com.android.preload.classdataretrieval.jdwp.JDWPClassDataRetriever; @@ -96,6 +97,7 @@ public class Main { public final static String COMPUTE_FILE_CMD = "comp"; public final static String EXPORT_CMD = "export"; public final static String IMPORT_CMD = "import"; + public final static String WRITE_CMD = "write"; /** * @param args @@ -132,6 +134,7 @@ public class Main { null)); actions.add(new ComputeThresholdXAction("Compute(X)", dataTableModel, CLASS_PRELOAD_BLACKLIST)); + actions.add(new WritePreloadedClassesAction(clientUtils, null, dataTableModel)); actions.add(new ShowDataAction(dataTableModel)); actions.add(new ImportAction(dataTableModel)); actions.add(new ExportAction(dataTableModel)); @@ -200,6 +203,11 @@ public class Main { ui.input(it.next()); ui.confirmYes(); ui.output(new File(it.next())); + // Operation: Write preloaded classes from a specific file + } else if (WRITE_CMD.equals(op)) { + System.out.println("Writing preloaded classes."); + ui.action(WritePreloadedClassesAction.class); + ui.input(new File(it.next())); } } } catch (NoSuchElementException e) { @@ -305,8 +313,16 @@ public class Main { Main.getUI().showMessageDialog("The device will reboot. This will potentially take a " + "long time. Please be patient."); - if (!DeviceUtils.removePreloaded(device, 15 * 60) /* 15m timeout */) { - Main.getUI().showMessageDialog("Removing preloaded-classes failed unexpectedly!"); + boolean success = false; + try { + success = DeviceUtils.overwritePreloaded(device, null, 15 * 60); + } catch (Exception e) { + System.err.println(e); + } finally { + if (!success) { + Main.getUI().showMessageDialog( + "Removing preloaded-classes failed unexpectedly!"); + } } } } diff --git a/tools/preload2/src/com/android/preload/actions/WritePreloadedClassesAction.java b/tools/preload2/src/com/android/preload/actions/WritePreloadedClassesAction.java new file mode 100644 index 000000000000..9b97f1168df9 --- /dev/null +++ b/tools/preload2/src/com/android/preload/actions/WritePreloadedClassesAction.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.preload.actions; + +import com.android.ddmlib.Client; +import com.android.ddmlib.IDevice; +import com.android.preload.ClientUtils; +import com.android.preload.DeviceUtils; +import com.android.preload.DumpData; +import com.android.preload.DumpTableModel; +import com.android.preload.Main; + +import java.awt.event.ActionEvent; +import java.io.File; +import java.util.Date; +import java.util.Map; + +public class WritePreloadedClassesAction extends AbstractThreadedDeviceSpecificAction { + private File preloadedClassFile; + + public WritePreloadedClassesAction(ClientUtils utils, IDevice device, DumpTableModel dataTableModel) { + super("Write preloaded classes action", device); + } + + @Override + public void actionPerformed(ActionEvent e) { + File[] files = Main.getUI().showOpenDialog(true); + if (files != null && files.length > 0) { + preloadedClassFile = files[0]; + super.actionPerformed(e); + } + } + + @Override + public void run() { + Main.getUI().showWaitDialog(); + try { + // Write the new file with a 5-minute timeout + DeviceUtils.overwritePreloaded(device, preloadedClassFile, 5 * 60); + } catch (Exception e) { + System.err.println(e); + } finally { + Main.getUI().hideWaitDialog(); + } + } +} |