Merge "Add shikhapanwar@ to OWNERS"
diff --git a/.prebuilt_info/prebuilt_info_pvmfw_pvmfw_img.asciipb b/.prebuilt_info/prebuilt_info_pvmfw_pvmfw_img.asciipb
index eee2b20..8b45e67 100644
--- a/.prebuilt_info/prebuilt_info_pvmfw_pvmfw_img.asciipb
+++ b/.prebuilt_info/prebuilt_info_pvmfw_pvmfw_img.asciipb
@@ -1,6 +1,6 @@
 drops {
   android_build_drop {
-    build_id: "8351176"
+    build_id: "8412944"
     target: "u-boot_pvmfw"
     source_file: "pvmfw.img"
   }
diff --git a/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java b/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java
index 64658a9..b332543 100644
--- a/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java
+++ b/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java
@@ -49,6 +49,7 @@
 import org.junit.After;
 import org.junit.AssumptionViolatedException;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TestName;
@@ -62,6 +63,7 @@
 
 @RootPermissionTest
 @RunWith(DeviceJUnit4ClassRunner.class)
+@Ignore("TODO(b/229823049): Make this work")
 public final class AuthFsHostTest extends VirtualizationTestCaseBase {
 
     /** Test directory on Android where data are located */
diff --git a/compos/composd/aidl/android/system/composd/ICompilationTaskCallback.aidl b/compos/composd/aidl/android/system/composd/ICompilationTaskCallback.aidl
index b334d8b..569bba5 100644
--- a/compos/composd/aidl/android/system/composd/ICompilationTaskCallback.aidl
+++ b/compos/composd/aidl/android/system/composd/ICompilationTaskCallback.aidl
@@ -20,6 +20,13 @@
  * requested compilation task completes.
  */
 oneway interface ICompilationTaskCallback {
+    enum FailureReason {
+        /** We failed to successfully start the VM and run compilation in it. */
+        CompilationFailed,
+        /** We ran compilation in the VM, but it reported a problem. */
+        UnexpectedCompilationResult,
+    }
+
     /**
      * Called if a compilation task has ended successfully, generating all the required artifacts.
      */
@@ -28,5 +35,5 @@
     /**
      * Called if a compilation task has ended unsuccessfully.
      */
-    void onFailure();
+    void onFailure(FailureReason reason, String message);
 }
diff --git a/compos/composd/src/odrefresh_task.rs b/compos/composd/src/odrefresh_task.rs
index 9dec1c1..e06e5fe 100644
--- a/compos/composd/src/odrefresh_task.rs
+++ b/compos/composd/src/odrefresh_task.rs
@@ -19,7 +19,8 @@
 use crate::fd_server_helper::FdServerConfig;
 use crate::instance_starter::CompOsInstance;
 use android_system_composd::aidl::android::system::composd::{
-    ICompilationTask::ICompilationTask, ICompilationTaskCallback::ICompilationTaskCallback,
+    ICompilationTask::ICompilationTask,
+    ICompilationTaskCallback::{FailureReason::FailureReason, ICompilationTaskCallback},
 };
 use android_system_composd::binder::{Interface, Result as BinderResult, Strong};
 use anyhow::{Context, Result};
@@ -99,12 +100,15 @@
                         task.callback.onSuccess()
                     }
                     Ok(exit_code) => {
-                        error!("Unexpected odrefresh result: {:?}", exit_code);
-                        task.callback.onFailure()
+                        let message = format!("Unexpected odrefresh result: {:?}", exit_code);
+                        error!("{}", message);
+                        task.callback
+                            .onFailure(FailureReason::UnexpectedCompilationResult, &message)
                     }
                     Err(e) => {
-                        error!("Running odrefresh failed: {:?}", e);
-                        task.callback.onFailure()
+                        let message = format!("Running odrefresh failed: {:?}", e);
+                        error!("{}", message);
+                        task.callback.onFailure(FailureReason::CompilationFailed, &message)
                     }
                 };
                 if let Err(e) = result {
diff --git a/compos/composd_cmd/composd_cmd.rs b/compos/composd_cmd/composd_cmd.rs
index 9f535d5..6afd711 100644
--- a/compos/composd_cmd/composd_cmd.rs
+++ b/compos/composd_cmd/composd_cmd.rs
@@ -19,7 +19,9 @@
 use android_system_composd::{
     aidl::android::system::composd::{
         ICompilationTask::ICompilationTask,
-        ICompilationTaskCallback::{BnCompilationTaskCallback, ICompilationTaskCallback},
+        ICompilationTaskCallback::{
+            BnCompilationTaskCallback, FailureReason::FailureReason, ICompilationTaskCallback,
+        },
         IIsolatedCompilationService::ApexSource::ApexSource,
         IIsolatedCompilationService::IIsolatedCompilationService,
     },
@@ -68,10 +70,10 @@
     completed: Condvar,
 }
 
-#[derive(Copy, Clone)]
 enum Outcome {
     Succeeded,
-    Failed,
+    Failed(FailureReason, String),
+    TaskDied,
 }
 
 impl Interface for Callback {}
@@ -82,8 +84,8 @@
         Ok(())
     }
 
-    fn onFailure(&self) -> BinderResult<()> {
-        self.0.set_outcome(Outcome::Failed);
+    fn onFailure(&self, reason: FailureReason, message: &str) -> BinderResult<()> {
+        self.0.set_outcome(Outcome::Failed(reason, message.to_owned()));
         Ok(())
     }
 }
@@ -97,14 +99,14 @@
     }
 
     fn wait(&self, duration: Duration) -> Result<Outcome> {
-        let (outcome, result) = self
+        let (mut outcome, result) = self
             .completed
             .wait_timeout_while(self.mutex.lock().unwrap(), duration, |outcome| outcome.is_none())
             .unwrap();
         if result.timed_out() {
             bail!("Timed out waiting for compilation")
         }
-        Ok(outcome.unwrap())
+        Ok(outcome.take().unwrap())
     }
 }
 
@@ -138,7 +140,7 @@
     let state_clone = state.clone();
     let mut death_recipient = DeathRecipient::new(move || {
         eprintln!("CompilationTask died");
-        state_clone.set_outcome(Outcome::Failed);
+        state_clone.set_outcome(Outcome::TaskDied);
     });
     // Note that dropping death_recipient cancels this, so we can't use a temporary here.
     task.as_binder().link_to_death(&mut death_recipient)?;
@@ -147,7 +149,10 @@
 
     match state.wait(timeouts()?.odrefresh_max_execution_time) {
         Ok(Outcome::Succeeded) => Ok(()),
-        Ok(Outcome::Failed) => bail!("Compilation failed"),
+        Ok(Outcome::TaskDied) => bail!("Compilation task died"),
+        Ok(Outcome::Failed(reason, message)) => {
+            bail!("Compilation failed: {:?}: {}", reason, message)
+        }
         Err(e) => {
             if let Err(e) = task.cancel() {
                 eprintln!("Failed to cancel compilation: {:?}", e);
diff --git a/compos/service/java/com/android/server/compos/IsolatedCompilationJobService.java b/compos/service/java/com/android/server/compos/IsolatedCompilationJobService.java
index 75f5334..cf852e3 100644
--- a/compos/service/java/com/android/server/compos/IsolatedCompilationJobService.java
+++ b/compos/service/java/com/android/server/compos/IsolatedCompilationJobService.java
@@ -31,6 +31,9 @@
 import android.system.composd.IIsolatedCompilationService;
 import android.util.Log;
 
+import com.android.server.compos.IsolatedCompilationMetrics.CompilationResult;
+
+import java.util.NoSuchElementException;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicReference;
 
@@ -41,26 +44,10 @@
  */
 public class IsolatedCompilationJobService extends JobService {
     private static final String TAG = IsolatedCompilationJobService.class.getName();
-    private static final int DAILY_JOB_ID = 5132250;
     private static final int STAGED_APEX_JOB_ID = 5132251;
 
     private final AtomicReference<CompilationJob> mCurrentJob = new AtomicReference<>();
 
-    static void scheduleDailyJob(JobScheduler scheduler) {
-        // TODO(b/205296305) Remove this
-        ComponentName serviceName =
-                new ComponentName("android", IsolatedCompilationJobService.class.getName());
-
-        int result = scheduler.schedule(new JobInfo.Builder(DAILY_JOB_ID, serviceName)
-                .setRequiresDeviceIdle(true)
-                .setRequiresCharging(true)
-                .setPeriodic(TimeUnit.DAYS.toMillis(1))
-                .build());
-        if (result != JobScheduler.RESULT_SUCCESS) {
-            Log.e(TAG, "Failed to schedule daily job");
-        }
-    }
-
     static void scheduleStagedApexJob(JobScheduler scheduler) {
         ComponentName serviceName =
                 new ComponentName("android", IsolatedCompilationJobService.class.getName());
@@ -73,7 +60,12 @@
                 .setRequiresCharging(true)
                 .setRequiresStorageNotLow(true)
                 .build());
-        if (result != JobScheduler.RESULT_SUCCESS) {
+        if (result == JobScheduler.RESULT_SUCCESS) {
+            IsolatedCompilationMetrics.onCompilationScheduled(
+                    IsolatedCompilationMetrics.SCHEDULING_SUCCESS);
+        } else {
+            IsolatedCompilationMetrics.onCompilationScheduled(
+                    IsolatedCompilationMetrics.SCHEDULING_FAILURE);
             Log.e(TAG, "Failed to schedule staged APEX job");
         }
     }
@@ -84,9 +76,7 @@
 
     @Override
     public boolean onStartJob(JobParameters params) {
-        int jobId = params.getJobId();
-
-        Log.i(TAG, "Starting job " + jobId);
+        Log.i(TAG, "Starting job");
 
         // This function (and onStopJob) are only ever called on the main thread, so we don't have
         // to worry about two starts at once, or start and stop happening at once. But onCompletion
@@ -99,8 +89,10 @@
             return false;  // Already finished
         }
 
+        IsolatedCompilationMetrics metrics = new IsolatedCompilationMetrics();
+
         CompilationJob newJob = new CompilationJob(IsolatedCompilationJobService.this::onCompletion,
-                params);
+                params, metrics);
         mCurrentJob.set(newJob);
 
         // This can take some time - we need to start up a VM - so we do it on a separate
@@ -110,9 +102,10 @@
             @Override
             public void run() {
                 try {
-                    newJob.start(jobId);
+                    newJob.start();
                 } catch (RuntimeException e) {
                     Log.e(TAG, "Starting CompilationJob failed", e);
+                    metrics.onCompilationEnded(IsolatedCompilationMetrics.RESULT_FAILED_TO_START);
                     mCurrentJob.set(null);
                     newJob.stop(); // Just in case it managed to start before failure
                     jobFinished(params, /*wantReschedule=*/ false);
@@ -153,18 +146,20 @@
 
     static class CompilationJob extends ICompilationTaskCallback.Stub
             implements IBinder.DeathRecipient {
+        private final IsolatedCompilationMetrics mMetrics;
         private final AtomicReference<ICompilationTask> mTask = new AtomicReference<>();
         private final CompilationCallback mCallback;
         private final JobParameters mParams;
         private volatile boolean mStopRequested = false;
-        private volatile boolean mCanceled = false;
 
-        CompilationJob(CompilationCallback callback, JobParameters params) {
+        CompilationJob(CompilationCallback callback, JobParameters params,
+                IsolatedCompilationMetrics metrics) {
             mCallback = requireNonNull(callback);
             mParams = params;
+            mMetrics = requireNonNull(metrics);
         }
 
-        void start(int jobId) {
+        void start() {
             IBinder binder = ServiceManager.waitForService("android.system.composd");
             IIsolatedCompilationService composd =
                     IIsolatedCompilationService.Stub.asInterface(binder);
@@ -174,13 +169,8 @@
             }
 
             try {
-                ICompilationTask composTask;
-                if (jobId == DAILY_JOB_ID) {
-                    composTask = composd.startTestCompile(
-                            IIsolatedCompilationService.ApexSource.NoStaged, this);
-                } else {
-                    composTask = composd.startStagedApexCompile(this);
-                }
+                ICompilationTask composTask = composd.startStagedApexCompile(this);
+                mMetrics.onCompilationStarted();
                 mTask.set(composTask);
                 composTask.asBinder().linkToDeath(this, 0);
             } catch (RemoteException e) {
@@ -201,38 +191,67 @@
 
         private void cancelTask() {
             ICompilationTask task = mTask.getAndSet(null);
-            if (task != null) {
-                mCanceled = true;
-                Log.i(TAG, "Cancelling task");
-                try {
-                    task.cancel();
-                } catch (RuntimeException | RemoteException e) {
-                    // If canceling failed we'll assume it means that the task has already failed;
-                    // there's nothing else we can do anyway.
-                    Log.w(TAG, "Failed to cancel CompilationTask", e);
-                }
+            if (task == null) {
+                return;
+            }
+
+            Log.i(TAG, "Cancelling task");
+            try {
+                task.cancel();
+            } catch (RuntimeException | RemoteException e) {
+                // If canceling failed we'll assume it means that the task has already failed;
+                // there's nothing else we can do anyway.
+                Log.w(TAG, "Failed to cancel CompilationTask", e);
+            }
+
+            mMetrics.onCompilationEnded(IsolatedCompilationMetrics.RESULT_JOB_CANCELED);
+            try {
+                task.asBinder().unlinkToDeath(this, 0);
+            } catch (NoSuchElementException e) {
+                // Harmless
             }
         }
 
         @Override
         public void binderDied() {
-            onFailure();
+            onCompletion(false, IsolatedCompilationMetrics.RESULT_COMPOSD_DIED);
         }
 
         @Override
         public void onSuccess() {
-            onCompletion(true);
+            onCompletion(true, IsolatedCompilationMetrics.RESULT_SUCCESS);
         }
 
         @Override
-        public void onFailure() {
-            onCompletion(false);
+        public void onFailure(byte reason, String message) {
+            int result;
+            switch (reason) {
+                case ICompilationTaskCallback.FailureReason.CompilationFailed:
+                    result = IsolatedCompilationMetrics.RESULT_COMPILATION_FAILED;
+                    break;
+
+                case ICompilationTaskCallback.FailureReason.UnexpectedCompilationResult:
+                    result = IsolatedCompilationMetrics.RESULT_UNEXPECTED_COMPILATION_RESULT;
+                    break;
+
+                default:
+                    result = IsolatedCompilationMetrics.RESULT_UNKNOWN_FAILURE;
+                    break;
+            }
+            Log.w(TAG, "Compilation failed: " + message);
+            onCompletion(false, result);
         }
 
-        private void onCompletion(boolean succeeded) {
-            mTask.set(null);
-            if (!mCanceled) {
+        private void onCompletion(boolean succeeded, @CompilationResult int result) {
+            ICompilationTask task = mTask.getAndSet(null);
+            if (task != null) {
+                mMetrics.onCompilationEnded(result);
                 mCallback.onCompletion(mParams, succeeded);
+                try {
+                    task.asBinder().unlinkToDeath(this, 0);
+                } catch (NoSuchElementException e) {
+                    // Harmless
+                }
             }
         }
     }
diff --git a/compos/service/java/com/android/server/compos/IsolatedCompilationMetrics.java b/compos/service/java/com/android/server/compos/IsolatedCompilationMetrics.java
new file mode 100644
index 0000000..0ed2305
--- /dev/null
+++ b/compos/service/java/com/android/server/compos/IsolatedCompilationMetrics.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2022 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.server.compos;
+
+import android.annotation.IntDef;
+import android.os.SystemClock;
+import android.util.Log;
+
+import com.android.internal.art.ArtStatsLog;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * A class that handles reporting metrics relating to Isolated Compilation to statsd.
+ *
+ * @hide
+ */
+class IsolatedCompilationMetrics {
+    private static final String TAG = IsolatedCompilationMetrics.class.getName();
+
+    // TODO(b/218525257): Move the definition of these enums to atoms.proto
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({RESULT_UNKNOWN, RESULT_SUCCESS, RESULT_UNKNOWN_FAILURE, RESULT_FAILED_TO_START,
+            RESULT_JOB_CANCELED, RESULT_COMPILATION_FAILED, RESULT_UNEXPECTED_COMPILATION_RESULT,
+            RESULT_COMPOSD_DIED})
+    public @interface CompilationResult {}
+
+    // Keep this in sync with Result enum in IsolatedCompilationEnded in
+    // frameworks/proto_logging/stats/atoms.proto
+    public static final int RESULT_UNKNOWN =
+            ArtStatsLog.ISOLATED_COMPILATION_ENDED__COMPILATION_RESULT__RESULT_UNKNOWN;
+    public static final int RESULT_SUCCESS =
+            ArtStatsLog.ISOLATED_COMPILATION_ENDED__COMPILATION_RESULT__RESULT_SUCCESS;
+    public static final int RESULT_UNKNOWN_FAILURE =
+            ArtStatsLog.ISOLATED_COMPILATION_ENDED__COMPILATION_RESULT__RESULT_UNKNOWN_FAILURE;
+    public static final int RESULT_FAILED_TO_START =
+            ArtStatsLog.ISOLATED_COMPILATION_ENDED__COMPILATION_RESULT__RESULT_FAILED_TO_START;
+    public static final int RESULT_JOB_CANCELED =
+            ArtStatsLog.ISOLATED_COMPILATION_ENDED__COMPILATION_RESULT__RESULT_JOB_CANCELED;
+    public static final int RESULT_COMPILATION_FAILED = ArtStatsLog
+            .ISOLATED_COMPILATION_ENDED__COMPILATION_RESULT__RESULT_COMPILATION_FAILED;
+    public static final int RESULT_UNEXPECTED_COMPILATION_RESULT = ArtStatsLog
+            .ISOLATED_COMPILATION_ENDED__COMPILATION_RESULT__RESULT_UNEXPECTED_COMPILATION_RESULT;
+    public static final int RESULT_COMPOSD_DIED =
+            ArtStatsLog.ISOLATED_COMPILATION_ENDED__COMPILATION_RESULT__RESULT_COMPOSD_DIED;
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({SCHEDULING_RESULT_UNKNOWN, SCHEDULING_SUCCESS, SCHEDULING_FAILURE})
+    public @interface ScheduleJobResult {}
+
+    // Keep this in sync with Result enum in IsolatedCompilationScheduled in
+    // frameworks/proto_logging/stats/atoms.proto
+
+    public static final int SCHEDULING_RESULT_UNKNOWN = ArtStatsLog
+            .ISOLATED_COMPILATION_SCHEDULED__SCHEDULING_RESULT__SCHEDULING_RESULT_UNKNOWN;
+    public static final int SCHEDULING_FAILURE =
+            ArtStatsLog.ISOLATED_COMPILATION_SCHEDULED__SCHEDULING_RESULT__SCHEDULING_FAILURE;
+    public static final int SCHEDULING_SUCCESS =
+            ArtStatsLog.ISOLATED_COMPILATION_SCHEDULED__SCHEDULING_RESULT__SCHEDULING_SUCCESS;
+
+    private long mCompilationStartTimeMs = 0;
+
+    public static void onCompilationScheduled(@ScheduleJobResult int result) {
+        // TODO(b/218525257): write to ArtStatsLog instead of logcat
+        ArtStatsLog.write(ArtStatsLog.ISOLATED_COMPILATION_SCHEDULED, result);
+        Log.i(TAG, "ISOLATED_COMPILATION_SCHEDULED: " + result);
+    }
+
+    public void onCompilationStarted() {
+        mCompilationStartTimeMs = SystemClock.elapsedRealtime();
+    }
+
+    public void onCompilationEnded(@CompilationResult int result) {
+        long compilationTime = mCompilationStartTimeMs == 0 ? -1
+                : SystemClock.elapsedRealtime() - mCompilationStartTimeMs;
+        mCompilationStartTimeMs = 0;
+
+        // TODO(b/218525257): write to ArtStatsLog instead of logcat
+        ArtStatsLog.write(ArtStatsLog.ISOLATED_COMPILATION_ENDED, compilationTime, result);
+        Log.i(TAG, "ISOLATED_COMPILATION_ENDED: " + result + ", " + compilationTime);
+    }
+}
diff --git a/compos/service/java/com/android/server/compos/IsolatedCompilationService.java b/compos/service/java/com/android/server/compos/IsolatedCompilationService.java
index 11e3743..b2fcbe0 100644
--- a/compos/service/java/com/android/server/compos/IsolatedCompilationService.java
+++ b/compos/service/java/com/android/server/compos/IsolatedCompilationService.java
@@ -67,7 +67,6 @@
             return;
         }
 
-        IsolatedCompilationJobService.scheduleDailyJob(scheduler);
         StagedApexObserver.registerForStagedApexUpdates(scheduler);
     }
 
diff --git a/microdroid/Android.bp b/microdroid/Android.bp
index b7d844f..a2ae144 100644
--- a/microdroid/Android.bp
+++ b/microdroid/Android.bp
@@ -119,6 +119,10 @@
     dirs: microdroid_rootdirs,
     symlinks: microdroid_symlinks,
     file_contexts: ":microdroid_file_contexts.gen",
+    // For deterministic output, use fake_timestamp, hard-coded uuid
+    fake_timestamp: "1611569676",
+    // python -c "import uuid; print(uuid.uuid5(uuid.NAMESPACE_URL, 'www.android.com/avf/microdroid/system'))"
+    uuid: "5fe079c6-f01a-52be-87d3-d415231a72ad",
 }
 
 prebuilt_etc {
@@ -189,6 +193,10 @@
     avb_private_key: ":microdroid_sign_key",
     avb_algorithm: "SHA256_RSA4096",
     file_contexts: ":microdroid_vendor_file_contexts.gen",
+    // For deterministic output, use fake_timestamp, hard-coded uuid
+    fake_timestamp: "1611569676",
+    // python -c "import uuid; print(uuid.uuid5(uuid.NAMESPACE_URL, 'www.android.com/avf/microdroid/vendor'))"
+    uuid: "156d40d7-8d8e-5c99-8913-ec82de549a70",
 }
 
 logical_partition {
@@ -382,6 +390,9 @@
     filename: "microdroid_bootconfig.full_debuggable",
 }
 
+// python -c "import hashlib; print(hashlib.sha256(b'bootconfig').hexdigest())"
+bootconfig_salt = "e158851fbebb402e1f18ea9372ea2f76b4dea23eceb5c4b92e5b27ade8537f5b"
+
 // TODO(jiyong): make a new module type that does the avb signing
 genrule {
     name: "microdroid_bootconfig_normal_gen",
@@ -394,6 +405,7 @@
     cmd: "cp $(location bootconfig.normal) $(out) && " +
         "$(location avbtool) add_hash_footer " +
         "--algorithm SHA256_RSA4096 " +
+        "--salt " + bootconfig_salt + " " +
         "--partition_name bootconfig " +
         "--key $(location :microdroid_sign_key) " +
         "--partition_size $$(( " + avb_hash_footer_kb + " * 1024 + ( $$(stat --format=%s $(out)) + 4096 - 1 ) / 4096 * 4096 )) " +
@@ -411,6 +423,7 @@
     cmd: "cp $(location bootconfig.app_debuggable) $(out) && " +
         "$(location avbtool) add_hash_footer " +
         "--algorithm SHA256_RSA4096 " +
+        "--salt " + bootconfig_salt + " " +
         "--partition_name bootconfig " +
         "--key $(location :microdroid_sign_key) " +
         "--partition_size $$(( " + avb_hash_footer_kb + " * 1024 + ( $$(stat --format=%s $(out)) + 4096 - 1 ) / 4096 * 4096 )) " +
@@ -428,6 +441,7 @@
     cmd: "cp $(location bootconfig.full_debuggable) $(out) && " +
         "$(location avbtool) add_hash_footer " +
         "--algorithm SHA256_RSA4096 " +
+        "--salt " + bootconfig_salt + " " +
         "--partition_name bootconfig " +
         "--key $(location :microdroid_sign_key) " +
         "--partition_size $$(( " + avb_hash_footer_kb + " * 1024 + ( $$(stat --format=%s $(out)) + 4096 - 1 ) / 4096 * 4096 )) " +
@@ -455,6 +469,9 @@
     filename: "microdroid_bootloader",
 }
 
+// python -c "import hashlib; print(hashlib.sha256(b'bootloader').hexdigest())"
+bootloader_salt = "3b4a12881d11f33cff968a24d7c53723a8232cde9a8d91e29fdbd6a95ae6adf0"
+
 genrule {
     name: "microdroid_bootloader_gen",
     tools: ["avbtool"],
@@ -473,6 +490,7 @@
         "if [ $$(stat --format=%s $(out)) -gt 4096 ]; then " +
         "$(location avbtool) add_hash_footer " +
         "--algorithm SHA256_RSA4096 " +
+        "--salt " + bootloader_salt + " " +
         "--partition_name bootloader " +
         "--key $(location :microdroid_sign_key) " +
         "--partition_size $$(( " + avb_hash_footer_kb + " * 1024 + ( $$(stat --format=%s $(out)) + 4096 - 1 ) / 4096 * 4096 )) " +
@@ -526,6 +544,9 @@
     filename: "uboot_env.img",
 }
 
+// python -c "import hashlib; print(hashlib.sha256(b'uboot_env').hexdigest())"
+uboot_env_salt = "cbf2d76827ece5ca8d176a40c94ac6355edcf6511b4b887364a8c0e05850df10"
+
 genrule {
     name: "microdroid_uboot_env_gen",
     tools: [
@@ -540,6 +561,7 @@
     cmd: "$(location mkenvimage_slim) -output_path $(out) -input_path $(location uboot-env.txt) && " +
         "$(location avbtool) add_hash_footer " +
         "--algorithm SHA256_RSA4096 " +
+        "--salt " + uboot_env_salt + " " +
         "--partition_name uboot_env " +
         "--key $(location :microdroid_sign_key) " +
         "--partition_size $$(( " + avb_hash_footer_kb + " * 1024 + ( $$(stat --format=%s $(out)) + 4096 - 1 ) / 4096 * 4096 )) " +
diff --git a/pvmfw/Android.bp b/pvmfw/Android.bp
index fbdd8d7..3f6cca4 100644
--- a/pvmfw/Android.bp
+++ b/pvmfw/Android.bp
@@ -28,6 +28,7 @@
     name: "pvmfw",
     srcs: [
         "entry.S",
+        "exceptions.S",
         "idmap.S",
     ],
     static_libs: [
diff --git a/pvmfw/entry.S b/pvmfw/entry.S
index e5c6045..f0021be 100644
--- a/pvmfw/entry.S
+++ b/pvmfw/entry.S
@@ -145,6 +145,10 @@
 	adr_l x30, boot_stack_end
 	mov sp, x30
 
+	/* Set up exception vector. */
+	adr x30, vector_table_el1
+	msr vbar_el1, x30
+
 	/* Call into Rust code. */
 	bl main
 
diff --git a/pvmfw/exceptions.S b/pvmfw/exceptions.S
new file mode 100644
index 0000000..86ef83c
--- /dev/null
+++ b/pvmfw/exceptions.S
@@ -0,0 +1,178 @@
+/*
+ * Copyright 2022 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
+ *
+ *     https://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.
+ */
+
+/**
+ * Saves the volatile registers onto the stack. This currently takes 14
+ * instructions, so it can be used in exception handlers with 18 instructions
+ * left.
+ *
+ * On return, x0 and x1 are initialised to elr_el2 and spsr_el2 respectively,
+ * which can be used as the first and second arguments of a subsequent call.
+ */
+.macro save_volatile_to_stack
+	/* Reserve stack space and save registers x0-x18, x29 & x30. */
+	stp x0, x1, [sp, #-(8 * 24)]!
+	stp x2, x3, [sp, #8 * 2]
+	stp x4, x5, [sp, #8 * 4]
+	stp x6, x7, [sp, #8 * 6]
+	stp x8, x9, [sp, #8 * 8]
+	stp x10, x11, [sp, #8 * 10]
+	stp x12, x13, [sp, #8 * 12]
+	stp x14, x15, [sp, #8 * 14]
+	stp x16, x17, [sp, #8 * 16]
+	str x18, [sp, #8 * 18]
+	stp x29, x30, [sp, #8 * 20]
+
+	/*
+	 * Save elr_el1 & spsr_el1. This such that we can take nested exception
+	 * and still be able to unwind.
+	 */
+	mrs x0, elr_el1
+	mrs x1, spsr_el1
+	stp x0, x1, [sp, #8 * 22]
+.endm
+
+/**
+ * Restores the volatile registers from the stack. This currently takes 14
+ * instructions, so it can be used in exception handlers while still leaving 18
+ * instructions left; if paired with save_volatile_to_stack, there are 4
+ * instructions to spare.
+ */
+.macro restore_volatile_from_stack
+	/* Restore registers x2-x18, x29 & x30. */
+	ldp x2, x3, [sp, #8 * 2]
+	ldp x4, x5, [sp, #8 * 4]
+	ldp x6, x7, [sp, #8 * 6]
+	ldp x8, x9, [sp, #8 * 8]
+	ldp x10, x11, [sp, #8 * 10]
+	ldp x12, x13, [sp, #8 * 12]
+	ldp x14, x15, [sp, #8 * 14]
+	ldp x16, x17, [sp, #8 * 16]
+	ldr x18, [sp, #8 * 18]
+	ldp x29, x30, [sp, #8 * 20]
+
+	/* Restore registers elr_el1 & spsr_el1, using x0 & x1 as scratch. */
+	ldp x0, x1, [sp, #8 * 22]
+	msr elr_el1, x0
+	msr spsr_el1, x1
+
+	/* Restore x0 & x1, and release stack space. */
+	ldp x0, x1, [sp], #8 * 24
+.endm
+
+/**
+ * This is a generic handler for exceptions taken at the current EL while using
+ * SP0. It behaves similarly to the SPx case by first switching to SPx, doing
+ * the work, then switching back to SP0 before returning.
+ *
+ * Switching to SPx and calling the Rust handler takes 16 instructions. To
+ * restore and return we need an additional 16 instructions, so we can implement
+ * the whole handler within the allotted 32 instructions.
+ */
+.macro current_exception_sp0 handler:req
+	msr spsel, #1
+	save_volatile_to_stack
+	bl \handler
+	restore_volatile_from_stack
+	msr spsel, #0
+	eret
+.endm
+
+/**
+ * This is a generic handler for exceptions taken at the current EL while using
+ * SPx. It saves volatile registers, calls the Rust handler, restores volatile
+ * registers, then returns.
+ *
+ * This also works for exceptions taken from EL0, if we don't care about
+ * non-volatile registers.
+ *
+ * Saving state and jumping to the Rust handler takes 15 instructions, and
+ * restoring and returning also takes 15 instructions, so we can fit the whole
+ * handler in 30 instructions, under the limit of 32.
+ */
+.macro current_exception_spx handler:req
+	save_volatile_to_stack
+	bl \handler
+	restore_volatile_from_stack
+	eret
+.endm
+
+.section .text.vector_table_el1, "ax"
+.global vector_table_el1
+.balign 0x800
+vector_table_el1:
+sync_cur_sp0:
+	current_exception_sp0 sync_exception_current
+
+.balign 0x80
+irq_cur_sp0:
+	current_exception_sp0 irq_current
+
+.balign 0x80
+fiq_cur_sp0:
+	current_exception_sp0 fiq_current
+
+.balign 0x80
+serr_cur_sp0:
+	current_exception_sp0 serr_current
+
+.balign 0x80
+sync_cur_spx:
+	current_exception_spx sync_exception_current
+
+.balign 0x80
+irq_cur_spx:
+	current_exception_spx irq_current
+
+.balign 0x80
+fiq_cur_spx:
+	current_exception_spx fiq_current
+
+.balign 0x80
+serr_cur_spx:
+	current_exception_spx serr_current
+
+.balign 0x80
+sync_lower_64:
+	current_exception_spx sync_lower
+
+.balign 0x80
+irq_lower_64:
+	current_exception_spx irq_lower
+
+.balign 0x80
+fiq_lower_64:
+	current_exception_spx fiq_lower
+
+.balign 0x80
+serr_lower_64:
+	current_exception_spx serr_lower
+
+.balign 0x80
+sync_lower_32:
+	current_exception_spx sync_lower
+
+.balign 0x80
+irq_lower_32:
+	current_exception_spx irq_lower
+
+.balign 0x80
+fiq_lower_32:
+	current_exception_spx fiq_lower
+
+.balign 0x80
+serr_lower_32:
+	current_exception_spx serr_lower
diff --git a/pvmfw/pvmfw.img b/pvmfw/pvmfw.img
index 7d8386b..307f6c6 100644
--- a/pvmfw/pvmfw.img
+++ b/pvmfw/pvmfw.img
Binary files differ
diff --git a/pvmfw/src/exceptions.rs b/pvmfw/src/exceptions.rs
new file mode 100644
index 0000000..2bdcf9c
--- /dev/null
+++ b/pvmfw/src/exceptions.rs
@@ -0,0 +1,81 @@
+// Copyright 2022, 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.
+
+//! Exception handlers.
+
+use crate::console::emergency_write_str;
+use crate::eprintln;
+use crate::psci::system_reset;
+use core::arch::asm;
+
+#[no_mangle]
+extern "C" fn sync_exception_current() {
+    emergency_write_str("sync_exception_current\n");
+    print_esr();
+    system_reset();
+}
+
+#[no_mangle]
+extern "C" fn irq_current() {
+    emergency_write_str("irq_current\n");
+    system_reset();
+}
+
+#[no_mangle]
+extern "C" fn fiq_current() {
+    emergency_write_str("fiq_current\n");
+    system_reset();
+}
+
+#[no_mangle]
+extern "C" fn serr_current() {
+    emergency_write_str("serr_current\n");
+    print_esr();
+    system_reset();
+}
+
+#[no_mangle]
+extern "C" fn sync_lower() {
+    emergency_write_str("sync_lower\n");
+    print_esr();
+    system_reset();
+}
+
+#[no_mangle]
+extern "C" fn irq_lower() {
+    emergency_write_str("irq_lower\n");
+    system_reset();
+}
+
+#[no_mangle]
+extern "C" fn fiq_lower() {
+    emergency_write_str("fiq_lower\n");
+    system_reset();
+}
+
+#[no_mangle]
+extern "C" fn serr_lower() {
+    emergency_write_str("serr_lower\n");
+    print_esr();
+    system_reset();
+}
+
+#[inline]
+fn print_esr() {
+    let mut esr: u64;
+    unsafe {
+        asm!("mrs {esr}, esr_el1", esr = out(reg) esr);
+    }
+    eprintln!("esr={:#08x}", esr);
+}
diff --git a/pvmfw/src/main.rs b/pvmfw/src/main.rs
index 4ab14b7..d38b1e3 100644
--- a/pvmfw/src/main.rs
+++ b/pvmfw/src/main.rs
@@ -18,6 +18,7 @@
 #![no_std]
 
 mod console;
+mod exceptions;
 mod psci;
 mod uart;