Inject an Unknown Fault instead of aborting VM on unknown exception

This allows the VM to decide how to handle the fault, rather than being
forcefully aborted.  It also allows us to simplify some of the secondary tests,
whereby the test can trap and observe the exception rather than abort, which
reduces the number of secondary VM services tests need to spawn.

Bug: 140916188
Change-Id: Ib2e98a6507aa15c5d805089cd28851b66c6a6379
diff --git a/src/arch/aarch64/hypervisor/handler.c b/src/arch/aarch64/hypervisor/handler.c
index a2d0437..21c6d94 100644
--- a/src/arch/aarch64/hypervisor/handler.c
+++ b/src/arch/aarch64/hypervisor/handler.c
@@ -439,6 +439,94 @@
 	return NULL;
 }
 
+/*
+ * Exception vector offsets.
+ * See Arm Architecture Reference Manual Armv8-A, D1.10.2.
+ */
+
+/**
+ * Offset for synchronous exceptions at current EL with SPx.
+ */
+#define OFFSET_CURRENT_SPX UINT64_C(0x200)
+
+/**
+ * Offset for synchronous exceptions at lower EL using AArch64.
+ */
+#define OFFSET_LOWER_EL_64 UINT64_C(0x400)
+
+/**
+ * Offset for synchronous exceptions at lower EL using AArch32.
+ */
+#define OFFSET_LOWER_EL_32 UINT64_C(0x600)
+
+/**
+ * Returns the address for the exception handler at EL1.
+ */
+static uintreg_t get_el1_exception_handler_addr(const struct vcpu *vcpu)
+{
+	uintreg_t base_addr = read_msr(vbar_el1);
+	uintreg_t pe_mode = vcpu->regs.spsr & PSR_PE_MODE_MASK;
+	bool is_arch32 = vcpu->regs.spsr & PSR_ARCH_MODE_32;
+
+	if (pe_mode == PSR_PE_MODE_EL0T) {
+		if (is_arch32) {
+			base_addr += OFFSET_LOWER_EL_32;
+		} else {
+			base_addr += OFFSET_LOWER_EL_64;
+		}
+	} else {
+		CHECK(!is_arch32);
+		base_addr += OFFSET_CURRENT_SPX;
+	}
+
+	return base_addr;
+}
+
+/**
+ * Injects an exception with an unknown reason (EC=0x0) to the EL1.
+ * See Arm Architecture Reference Manual Armv8-A, page D13-2924.
+ *
+ * NOTE: This function assumes that the lazy registers haven't been saved, and
+ * writes to the lazy registers of the CPU directly instead of the vCPU.
+ */
+static struct vcpu *inject_el1_unknown_exception(struct vcpu *vcpu,
+						 uintreg_t esr_el2)
+{
+	uintreg_t esr_el1_value = GET_ESR_IL(esr_el2);
+	uintreg_t handler_address = get_el1_exception_handler_addr(vcpu);
+	char *direction_str;
+
+	/* Update the CPU state to inject the exception. */
+	write_msr(esr_el1, esr_el1_value);
+	write_msr(elr_el1, vcpu->regs.pc);
+	write_msr(spsr_el1, vcpu->regs.spsr);
+
+	/*
+	 * Mask (disable) interrupts and run in EL1h mode.
+	 * EL1h mode is used because by default, taking an exception selects the
+	 * stack pointer for the target Exception level. The software can change
+	 * that later in the handler if needed.
+	 * See Arm Architecture Reference Manual Armv8-A, page D13-2924
+	 */
+	vcpu->regs.spsr = PSR_D | PSR_A | PSR_I | PSR_F | PSR_PE_MODE_EL1H;
+
+	/* Transfer control to the exception hander. */
+	vcpu->regs.pc = handler_address;
+
+	direction_str = ISS_IS_READ(esr_el2) ? "read" : "write";
+	dlog("Trapped access to system register %s: op0=%d, op1=%d, crn=%d, "
+	     "crm=%d, op2=%d, rt=%d.\n",
+	     direction_str, GET_ISS_OP0(esr_el2), GET_ISS_OP1(esr_el2),
+	     GET_ISS_CRN(esr_el2), GET_ISS_CRM(esr_el2), GET_ISS_OP2(esr_el2),
+	     GET_ISS_RT(esr_el2));
+
+	dlog("Injecting Unknown Reason exception into VM%d.\n", vcpu->vm->id);
+	dlog("Exception handler address 0x%x\n", handler_address);
+
+	/* Schedule the same VM to continue running. */
+	return NULL;
+}
+
 struct vcpu *hvc_handler(struct vcpu *vcpu)
 {
 	struct spci_value args = {
@@ -635,96 +723,11 @@
 		break;
 	}
 
-	/* The exception wasn't handled so abort the VM. */
-	return api_abort(vcpu);
-}
-
-/*
- * Exception vector offsets.
- * See Arm Architecture Reference Manual Armv8-A, D1.10.2.
- */
-
-/**
- * Offset for synchronous exceptions at current EL with SPx.
- */
-#define OFFSET_CURRENT_SPX UINT64_C(0x200)
-
-/**
- * Offset for synchronous exceptions at lower EL using AArch64.
- */
-#define OFFSET_LOWER_EL_64 UINT64_C(0x400)
-
-/**
- * Offset for synchronous exceptions at lower EL using AArch32.
- */
-#define OFFSET_LOWER_EL_32 UINT64_C(0x600)
-
-/**
- * Returns the address for the exception handler at EL1.
- */
-static uintreg_t get_el1_exception_handler_addr(const struct vcpu *vcpu)
-{
-	uintreg_t base_addr = read_msr(vbar_el1);
-	uintreg_t pe_mode = vcpu->regs.spsr & PSR_PE_MODE_MASK;
-	bool is_arch32 = vcpu->regs.spsr & PSR_ARCH_MODE_32;
-
-	if (pe_mode == PSR_PE_MODE_EL0T) {
-		if (is_arch32) {
-			base_addr += OFFSET_LOWER_EL_32;
-		} else {
-			base_addr += OFFSET_LOWER_EL_64;
-		}
-	} else {
-		CHECK(!is_arch32);
-		base_addr += OFFSET_CURRENT_SPX;
-	}
-
-	return base_addr;
-}
-
-/**
- * Injects an exception with an unknown reason (EC=0x0) to the EL1.
- * See Arm Architecture Reference Manual Armv8-A, page D13-2924.
- *
- * NOTE: This function assumes that the lazy registers haven't been saved, and
- * writes to the lazy registers of the CPU directly instead of the vCPU.
- */
-static struct vcpu *inject_el1_unknown_exception(struct vcpu *vcpu,
-						 uintreg_t esr_el2)
-{
-	uintreg_t esr_el1_value = GET_ESR_IL(esr_el2);
-	uintreg_t handler_address = get_el1_exception_handler_addr(vcpu);
-	char *direction_str;
-
-	/* Update the CPU state to inject the exception. */
-	write_msr(esr_el1, esr_el1_value);
-	write_msr(elr_el1, vcpu->regs.pc);
-	write_msr(spsr_el1, vcpu->regs.spsr);
-
 	/*
-	 * Mask (disable) interrupts and run in EL1h mode.
-	 * EL1h mode is used because by default, taking an exception selects the
-	 * stack pointer for the target Exception level. The software can change
-	 * that later in the handler if needed.
-	 * See Arm Architecture Reference Manual Armv8-A, page D13-2924
+	 * The exception wasn't handled. Inject to the VM to give it chance to
+	 * handle as an unknown exception.
 	 */
-	vcpu->regs.spsr = PSR_D | PSR_A | PSR_I | PSR_F | PSR_PE_MODE_EL1H;
-
-	/* Transfer control to the exception hander. */
-	vcpu->regs.pc = handler_address;
-
-	direction_str = ISS_IS_READ(esr_el2) ? "read" : "write";
-	dlog("Trapped access to system register %s: op0=%d, op1=%d, crn=%d, "
-	     "crm=%d, op2=%d, rt=%d.\n",
-	     direction_str, GET_ISS_OP0(esr_el2), GET_ISS_OP1(esr_el2),
-	     GET_ISS_CRN(esr_el2), GET_ISS_CRM(esr_el2), GET_ISS_OP2(esr_el2),
-	     GET_ISS_RT(esr_el2));
-
-	dlog("Injecting Unknown Reason exception into VM%d.\n", vcpu->vm->id);
-	dlog("Exception handler address 0x%x\n", handler_address);
-
-	/* Schedule the same VM to continue running. */
-	return NULL;
+	return inject_el1_unknown_exception(vcpu, esr);
 }
 
 /**
diff --git a/test/inc/test/vmapi/exception_handler.h b/test/inc/test/vmapi/exception_handler.h
new file mode 100644
index 0000000..1703af6
--- /dev/null
+++ b/test/inc/test/vmapi/exception_handler.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2019 The Hafnium Authors.
+ *
+ * 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.
+ */
+
+#pragma once
+
+#include "vmapi/hf/spci.h"
+
+bool exception_handler_skip_instruction(void);
+
+bool exception_handler_yield(void);
+
+int exception_handler_get_num(void);
+
+void exception_handler_reset(void);
+
+void exception_handler_send_num_exceptions(void);
+
+int exception_handler_receive_num_exceptions(
+	const struct spci_value *send_res,
+	const struct spci_memory_region *recv_buf);
diff --git a/test/vmapi/BUILD.gn b/test/vmapi/BUILD.gn
index 7cbb8cd..c981e76 100644
--- a/test/vmapi/BUILD.gn
+++ b/test/vmapi/BUILD.gn
@@ -20,6 +20,7 @@
 
   deps = [
     "arch/${plat_arch}:arch",
+    "common:common",
     "primary_only:primary_only_test",
     "primary_with_secondaries:primary_with_secondaries_test",
   ]
diff --git a/test/vmapi/arch/aarch64/gicv3/gicv3.c b/test/vmapi/arch/aarch64/gicv3/gicv3.c
index 8127592..13c4565 100644
--- a/test/vmapi/arch/aarch64/gicv3/gicv3.c
+++ b/test/vmapi/arch/aarch64/gicv3/gicv3.c
@@ -28,7 +28,6 @@
 
 #include "../msr.h"
 #include "test/hftest.h"
-#include "test/vmapi/spci.h"
 
 alignas(PAGE_SIZE) uint8_t send_page[PAGE_SIZE];
 alignas(PAGE_SIZE) uint8_t recv_page[PAGE_SIZE];
@@ -83,35 +82,19 @@
 }
 
 /*
- * Check that an attempt by a secondary VM to read a GICv3 system register is
+ * Check that an attempt by a secondary VM to access a GICv3 system register is
  * trapped.
  */
-TEST(system, icc_ctlr_read_trapped_secondary)
+TEST(system, icc_ctlr_access_trapped_secondary)
 {
 	struct spci_value run_res;
 
 	EXPECT_EQ(spci_rxtx_map(send_page_addr, recv_page_addr).func,
 		  SPCI_SUCCESS_32);
-	SERVICE_SELECT(SERVICE_VM1, "read_systemreg_ctlr", send_buffer);
+	SERVICE_SELECT(SERVICE_VM1, "access_systemreg_ctlr", send_buffer);
 
 	run_res = spci_run(SERVICE_VM1, 0);
-	EXPECT_SPCI_ERROR(run_res, SPCI_ABORTED);
-}
-
-/*
- * Check that an attempt by a secondary VM to write a GICv3 system register is
- * trapped.
- */
-TEST(system, icc_ctlr_write_trapped_secondary)
-{
-	struct spci_value run_res;
-
-	EXPECT_EQ(spci_rxtx_map(send_page_addr, recv_page_addr).func,
-		  SPCI_SUCCESS_32);
-	SERVICE_SELECT(SERVICE_VM1, "write_systemreg_ctlr", send_buffer);
-
-	run_res = spci_run(SERVICE_VM1, 0);
-	EXPECT_SPCI_ERROR(run_res, SPCI_ABORTED);
+	EXPECT_EQ(run_res.func, SPCI_YIELD_32);
 }
 
 /*
@@ -127,7 +110,5 @@
 	SERVICE_SELECT(SERVICE_VM1, "write_systemreg_sre", send_buffer);
 
 	run_res = spci_run(SERVICE_VM1, 0);
-	EXPECT_TRUE((run_res.func == SPCI_ERROR_32 &&
-		     run_res.arg2 == SPCI_ABORTED) ||
-		    run_res.func == SPCI_YIELD_32);
+	EXPECT_EQ(run_res.func, SPCI_YIELD_32);
 }
diff --git a/test/vmapi/arch/aarch64/gicv3/services/BUILD.gn b/test/vmapi/arch/aarch64/gicv3/services/BUILD.gn
index 81aacdd..6c2f019 100644
--- a/test/vmapi/arch/aarch64/gicv3/services/BUILD.gn
+++ b/test/vmapi/arch/aarch64/gicv3/services/BUILD.gn
@@ -65,6 +65,11 @@
     ":common",
     "//src/arch/aarch64:arch",
   ]
+
+  include_dirs = [
+    "//test/vmapi/common",
+    "//inc/vmapi/hf",
+  ]
 }
 
 # Group services together into VMs.
@@ -77,5 +82,6 @@
     ":systemreg",
     ":timer",
     "//test/hftest:hftest_secondary_vm",
+    "//test/vmapi/common:common",
   ]
 }
diff --git a/test/vmapi/arch/aarch64/gicv3/services/systemreg.c b/test/vmapi/arch/aarch64/gicv3/services/systemreg.c
index 8ada16e..78ddc0f 100644
--- a/test/vmapi/arch/aarch64/gicv3/services/systemreg.c
+++ b/test/vmapi/arch/aarch64/gicv3/services/systemreg.c
@@ -15,6 +15,7 @@
  */
 
 #include "hf/arch/vm/events.h"
+#include "hf/arch/vm/interrupts.h"
 #include "hf/arch/vm/interrupts_gicv3.h"
 #include "hf/arch/vm/timer.h"
 
@@ -25,32 +26,32 @@
 
 #include "common.h"
 #include "test/hftest.h"
+#include "test/vmapi/exception_handler.h"
 
 /*
  * Secondary VM that tries to access GICv3 system registers.
  */
 
-TEST_SERVICE(read_systemreg_ctlr)
+TEST_SERVICE(access_systemreg_ctlr)
 {
-	/* Reading ICC_CTLR_EL1 should trap and abort the VM. */
-	dlog("ICC_CTLR_EL1=%#x\n", read_msr(ICC_CTLR_EL1));
-	FAIL("Reading ICC_CTLR_EL1 didn't trap.");
-}
+	exception_setup(NULL, exception_handler_skip_instruction);
 
-TEST_SERVICE(write_systemreg_ctlr)
-{
-	/* Writing ICC_CTLR_EL1 should trap and abort the VM. */
+	/* Reading ICC_CTLR_EL1 should trap the VM. */
+	read_msr(ICC_CTLR_EL1);
+
+	/* Writing ICC_CTLR_EL1 should trap the VM. */
 	write_msr(ICC_CTLR_EL1, 0);
-	FAIL("Writing ICC_CTLR_EL1 didn't trap.");
+
+	EXPECT_EQ(exception_handler_get_num(), 2);
+
+	/* Yield after catching the exceptions. */
+	spci_yield();
 }
 
 TEST_SERVICE(write_systemreg_sre)
 {
 	ASSERT_EQ(read_msr(ICC_SRE_EL1), 0x7);
-	/*
-	 * Writing ICC_SRE_EL1 should either trap and abort the VM or be
-	 * ignored.
-	 */
+	/* Writing ICC_SRE_EL1 should trap the VM or be ignored. */
 	write_msr(ICC_SRE_EL1, 0x0);
 	ASSERT_EQ(read_msr(ICC_SRE_EL1), 0x7);
 	write_msr(ICC_SRE_EL1, 0xffffffff);
diff --git a/test/vmapi/common/BUILD.gn b/test/vmapi/common/BUILD.gn
index 426d10e..48be642 100644
--- a/test/vmapi/common/BUILD.gn
+++ b/test/vmapi/common/BUILD.gn
@@ -1,4 +1,4 @@
-# Copyright 2018 The Hafnium Authors.
+# Copyright 2019 The Hafnium Authors.
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@
   testonly = true
   public_configs = [ "//test/hftest:hftest_config" ]
   sources = [
+    "exception_handler.c",
     "spci.c",
   ]
 }
diff --git a/test/vmapi/common/exception_handler.c b/test/vmapi/common/exception_handler.c
new file mode 100644
index 0000000..87e61ec
--- /dev/null
+++ b/test/vmapi/common/exception_handler.c
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2019 The Hafnium Authors.
+ *
+ * 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.
+ */
+
+#include "hf/dlog.h"
+
+#include "vmapi/hf/call.h"
+
+#include "../msr.h"
+#include "test/hftest.h"
+
+/**
+ * Tracks the number of times the exception handler has been invoked.
+ */
+static int exception_handler_num_exceptions = 0;
+
+/**
+ * Sends the number of exceptions handled to the Primary VM.
+ */
+void exception_handler_send_num_exceptions(void)
+{
+	void *send_buf = SERVICE_SEND_BUFFER();
+
+	dlog("Sending num_exceptions %d to primary VM\n",
+	     exception_handler_num_exceptions);
+	memcpy_s(send_buf, SPCI_MSG_PAYLOAD_MAX,
+		 (const void *)&exception_handler_num_exceptions,
+		 sizeof(exception_handler_num_exceptions));
+	EXPECT_EQ(spci_msg_send(hf_vm_get_id(), HF_PRIMARY_VM_ID,
+				sizeof(exception_handler_num_exceptions), 0)
+			  .func,
+		  SPCI_SUCCESS_32);
+}
+
+/**
+ * Receives the number of exceptions handled.
+ */
+int exception_handler_receive_num_exceptions(
+	const struct spci_value *send_res,
+	const struct spci_memory_region *recv_buf)
+{
+	int num_exceptions = *((const int *)recv_buf);
+
+	EXPECT_EQ(send_res->func, SPCI_MSG_SEND_32);
+	EXPECT_EQ(spci_msg_send_size(*send_res), sizeof(num_exceptions));
+	EXPECT_EQ(spci_rx_release().func, SPCI_SUCCESS_32);
+	return num_exceptions;
+}
+
+/**
+ * EL1 exception handler to use in unit test VMs.
+ * Skips the instruction that triggered the exception.
+ */
+bool exception_handler_skip_instruction(void)
+{
+	dlog("%s function is triggered!\n", __func__);
+	++exception_handler_num_exceptions;
+
+	/* Skip instruction that triggered the exception. */
+	uint64_t next_pc = read_msr(elr_el1);
+	next_pc += 4UL;
+	write_msr(elr_el1, next_pc);
+
+	/* Indicate that elr_el1 should not be restored. */
+	return true;
+}
+
+/**
+ * EL1 exception handler to use in unit test VMs.
+ * Yields control back to the hypervisor and sends the number of exceptions.
+ */
+bool exception_handler_yield(void)
+{
+	dlog("%s function is triggered!\n", __func__);
+	++exception_handler_num_exceptions;
+
+	exception_handler_send_num_exceptions();
+
+	/* Indicate that elr_el1 should not be restored. */
+	return true;
+}
+
+/**
+ * Returns the number of times the instruction handler was invoked.
+ */
+int exception_handler_get_num(void)
+{
+	return exception_handler_num_exceptions;
+}
+
+/**
+ * Resets the number of exceptions counter;
+ */
+void exception_handler_reset(void)
+{
+	exception_handler_num_exceptions = 0;
+}
diff --git a/test/vmapi/primary_with_secondaries/BUILD.gn b/test/vmapi/primary_with_secondaries/BUILD.gn
index 8554d4b..ee3ab92 100644
--- a/test/vmapi/primary_with_secondaries/BUILD.gn
+++ b/test/vmapi/primary_with_secondaries/BUILD.gn
@@ -27,7 +27,6 @@
   ]
 
   sources = [
-    "abort.c",
     "boot.c",
     "debug_el1.c",
     "floating_point.c",
@@ -40,6 +39,7 @@
     "smp.c",
     "spci.c",
     "sysregs.c",
+    "unmapped.c",
   ]
 
   deps = [
diff --git a/test/vmapi/primary_with_secondaries/abort.c b/test/vmapi/primary_with_secondaries/abort.c
deleted file mode 100644
index 07eea62..0000000
--- a/test/vmapi/primary_with_secondaries/abort.c
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Copyright 2019 The Hafnium Authors.
- *
- * 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.
- */
-
-#include "vmapi/hf/call.h"
-
-#include "primary_with_secondary.h"
-#include "test/hftest.h"
-#include "test/vmapi/spci.h"
-
-/**
- * Accessing unmapped memory aborts the VM.
- */
-TEST(abort, data_abort)
-{
-	struct spci_value run_res;
-	struct mailbox_buffers mb = set_up_mailbox();
-
-	SERVICE_SELECT(SERVICE_VM1, "data_abort", mb.send);
-
-	run_res = spci_run(SERVICE_VM1, 0);
-	EXPECT_SPCI_ERROR(run_res, SPCI_ABORTED);
-}
-
-/**
- * Accessing partially unmapped memory aborts the VM.
- */
-TEST(abort, straddling_data_abort)
-{
-	struct spci_value run_res;
-	struct mailbox_buffers mb = set_up_mailbox();
-
-	SERVICE_SELECT(SERVICE_VM1, "straddling_data_abort", mb.send);
-
-	/*
-	 * First we get a message about the memory being donated to us, then we
-	 * get the abort.
-	 */
-	run_res = spci_run(SERVICE_VM1, 0);
-	EXPECT_EQ(run_res.func, SPCI_MSG_SEND_32);
-	run_res = spci_run(SERVICE_VM1, 0);
-	EXPECT_SPCI_ERROR(run_res, SPCI_ABORTED);
-}
-
-/**
- * Executing unmapped memory aborts the VM.
- */
-TEST(abort, instruction_abort)
-{
-	struct spci_value run_res;
-	struct mailbox_buffers mb = set_up_mailbox();
-
-	SERVICE_SELECT(SERVICE_VM1, "instruction_abort", mb.send);
-
-	run_res = spci_run(SERVICE_VM1, 0);
-	EXPECT_SPCI_ERROR(run_res, SPCI_ABORTED);
-}
-
-/**
- * Executing partially unmapped memory aborts the VM.
- */
-TEST(abort, straddling_instruction_abort)
-{
-	struct spci_value run_res;
-	struct mailbox_buffers mb = set_up_mailbox();
-
-	SERVICE_SELECT(SERVICE_VM1, "straddling_instruction_abort", mb.send);
-
-	/*
-	 * First we get a message about the memory being donated to us, then we
-	 * get the abort.
-	 */
-	run_res = spci_run(SERVICE_VM1, 0);
-	EXPECT_EQ(run_res.func, SPCI_MSG_SEND_32);
-	run_res = spci_run(SERVICE_VM1, 0);
-	EXPECT_SPCI_ERROR(run_res, SPCI_ABORTED);
-}
diff --git a/test/vmapi/primary_with_secondaries/boot.c b/test/vmapi/primary_with_secondaries/boot.c
index ef97715..7236b34 100644
--- a/test/vmapi/primary_with_secondaries/boot.c
+++ b/test/vmapi/primary_with_secondaries/boot.c
@@ -20,6 +20,7 @@
 
 #include "primary_with_secondary.h"
 #include "test/hftest.h"
+#include "test/vmapi/exception_handler.h"
 #include "test/vmapi/spci.h"
 
 /**
@@ -37,7 +38,7 @@
 }
 
 /**
- * Accessing memory outside the given range aborts the VM.
+ * Accessing memory outside the given range traps the VM and yields.
  */
 TEST(boot, beyond_memory_size)
 {
@@ -47,11 +48,12 @@
 	SERVICE_SELECT(SERVICE_VM1, "boot_memory_overrun", mb.send);
 
 	run_res = spci_run(SERVICE_VM1, 0);
-	EXPECT_SPCI_ERROR(run_res, SPCI_ABORTED);
+	EXPECT_EQ(exception_handler_receive_num_exceptions(&run_res, mb.recv),
+		  1);
 }
 
 /**
- * Accessing memory before the start of the image aborts the VM.
+ * Accessing memory before the start of the image traps the VM and yields.
  */
 TEST(boot, memory_before_image)
 {
@@ -61,5 +63,6 @@
 	SERVICE_SELECT(SERVICE_VM1, "boot_memory_underrun", mb.send);
 
 	run_res = spci_run(SERVICE_VM1, 0);
-	EXPECT_SPCI_ERROR(run_res, SPCI_ABORTED);
+	EXPECT_EQ(exception_handler_receive_num_exceptions(&run_res, mb.recv),
+		  1);
 }
diff --git a/test/vmapi/primary_with_secondaries/debug_el1.c b/test/vmapi/primary_with_secondaries/debug_el1.c
index 2dd5312..d7d3df1 100644
--- a/test/vmapi/primary_with_secondaries/debug_el1.c
+++ b/test/vmapi/primary_with_secondaries/debug_el1.c
@@ -25,65 +25,15 @@
  */
 #define CUSTOM_QEMU_BUILD() 0
 
-/*
- * TODO(b/132422368): Devise a way to test exhaustively read/write behavior to
- * all debug registers that does not involve a separate service per register.
- * This needs proper trap support as a starting point.
- */
-
-TEST(debug_el1, secondary_mdccint_el1)
+TEST(debug_el1, secondary_basic)
 {
 	struct spci_value run_res;
 	struct mailbox_buffers mb = set_up_mailbox();
 
-	SERVICE_SELECT(SERVICE_VM1, "debug_el1_secondary_mdccint_el1", mb.send);
+	SERVICE_SELECT(SERVICE_VM1, "debug_el1_secondary_basic", mb.send);
 
 	run_res = spci_run(SERVICE_VM1, 0);
-	EXPECT_SPCI_ERROR(run_res, SPCI_ABORTED);
-}
-
-TEST(debug_el1, secondary_dbgbcr0_el1)
-{
-	struct spci_value run_res;
-	struct mailbox_buffers mb = set_up_mailbox();
-
-	SERVICE_SELECT(SERVICE_VM1, "debug_el1_secondary_dbgbcr0_el1", mb.send);
-
-	run_res = spci_run(SERVICE_VM1, 0);
-	EXPECT_SPCI_ERROR(run_res, SPCI_ABORTED);
-}
-
-TEST(debug_el1, secondary_dbgbvr0_el1)
-{
-	struct spci_value run_res;
-	struct mailbox_buffers mb = set_up_mailbox();
-
-	SERVICE_SELECT(SERVICE_VM1, "debug_el1_secondary_dbgbvr0_el1", mb.send);
-
-	run_res = spci_run(SERVICE_VM1, 0);
-	EXPECT_SPCI_ERROR(run_res, SPCI_ABORTED);
-}
-
-TEST(debug_el1, secondary_dbgwcr0_el1)
-{
-	struct spci_value run_res;
-	struct mailbox_buffers mb = set_up_mailbox();
-
-	SERVICE_SELECT(SERVICE_VM1, "debug_el1_secondary_dbgwcr0_el1", mb.send);
-
-	run_res = spci_run(SERVICE_VM1, 0);
-	EXPECT_SPCI_ERROR(run_res, SPCI_ABORTED);
-}
-
-TEST(debug_el1, secondary_dbgwvr0_el1)
-{
-	struct spci_value run_res;
-	struct mailbox_buffers mb = set_up_mailbox();
-
-	SERVICE_SELECT(SERVICE_VM1, "debug_el1_secondary_dbgwvr0_el1", mb.send);
-
-	run_res = spci_run(SERVICE_VM1, 0);
-	EXPECT_SPCI_ERROR(run_res, SPCI_ABORTED);
+	EXPECT_EQ(run_res.func, SPCI_YIELD_32);
 }
 
 /**
diff --git a/test/vmapi/primary_with_secondaries/memory_sharing.c b/test/vmapi/primary_with_secondaries/memory_sharing.c
index efe0275..bcb0777 100644
--- a/test/vmapi/primary_with_secondaries/memory_sharing.c
+++ b/test/vmapi/primary_with_secondaries/memory_sharing.c
@@ -23,6 +23,7 @@
 
 #include "primary_with_secondary.h"
 #include "test/hftest.h"
+#include "test/vmapi/exception_handler.h"
 #include "test/vmapi/spci.h"
 
 alignas(PAGE_SIZE) static uint8_t pages[4 * PAGE_SIZE];
@@ -223,6 +224,7 @@
 
 	run_res = spci_run(SERVICE_VM1, 0);
 	EXPECT_EQ(run_res.func, SPCI_MSG_SEND_32);
+	spci_rx_release();
 
 	for (int i = 0; i < PAGE_SIZE; ++i) {
 		uint8_t value = i + 1;
@@ -264,10 +266,11 @@
 	for (int i = 0; i < PAGE_SIZE; ++i) {
 		ASSERT_EQ(ptr[i], 'c');
 	}
+	spci_rx_release();
 
-	/* Observe the service faulting when accessing the memory. */
 	run_res = spci_run(SERVICE_VM1, 0);
-	EXPECT_SPCI_ERROR(run_res, SPCI_ABORTED);
+	EXPECT_EQ(exception_handler_receive_num_exceptions(&run_res, mb.recv),
+		  1);
 }
 
 /**
@@ -324,15 +327,16 @@
 
 	/* Let the memory be returned. */
 	EXPECT_EQ(run_res.func, SPCI_MSG_SEND_32);
+	spci_rx_release();
 
 	/* Ensure that the secondary VM accessed the region. */
 	for (int i = 0; i < PAGE_SIZE; ++i) {
 		ASSERT_EQ(ptr[i], 'c');
 	}
 
-	/* Observe the service faulting when accessing the memory. */
 	run_res = spci_run(SERVICE_VM1, 0);
-	EXPECT_SPCI_ERROR(run_res, SPCI_ABORTED);
+	EXPECT_EQ(exception_handler_receive_num_exceptions(&run_res, mb.recv),
+		  1);
 }
 
 /**
@@ -405,10 +409,11 @@
 	for (int i = 0; i < PAGE_SIZE; ++i) {
 		ASSERT_EQ(ptr[i], 'c');
 	}
+	spci_rx_release();
 
-	/* Observe the service faulting when accessing the memory. */
 	run_res = spci_run(SERVICE_VM1, 0);
-	EXPECT_SPCI_ERROR(run_res, SPCI_ABORTED);
+	EXPECT_EQ(exception_handler_receive_num_exceptions(&run_res, mb.recv),
+		  1);
 }
 
 /**
@@ -444,10 +449,11 @@
 	for (int i = 0; i < PAGE_SIZE; ++i) {
 		ASSERT_EQ(ptr[i], 'd');
 	}
+	spci_rx_release();
 
-	/* Observe the service faulting when accessing the memory. */
 	run_res = spci_run(SERVICE_VM1, 0);
-	EXPECT_SPCI_ERROR(run_res, SPCI_ABORTED);
+	EXPECT_EQ(exception_handler_receive_num_exceptions(&run_res, mb.recv),
+		  1);
 }
 
 /**
@@ -522,6 +528,7 @@
 	/* Let the memory be returned. */
 	run_res = spci_run(SERVICE_VM1, 0);
 	EXPECT_EQ(run_res.func, SPCI_MSG_SEND_32);
+	spci_rx_release();
 
 	/* Share the memory with a different VM after it has been returned. */
 	msg_size = spci_memory_region_init(
@@ -533,9 +540,9 @@
 			  .func,
 		  SPCI_SUCCESS_32);
 
-	/* Observe the service faulting when accessing the memory. */
 	run_res = spci_run(SERVICE_VM1, 0);
-	EXPECT_SPCI_ERROR(run_res, SPCI_ABORTED);
+	EXPECT_EQ(exception_handler_receive_num_exceptions(&run_res, mb.recv),
+		  1);
 }
 
 /**
@@ -564,10 +571,11 @@
 	for (int i = 0; i < PAGE_SIZE; ++i) {
 		ASSERT_EQ(ptr[i], 0);
 	}
+	spci_rx_release();
 
-	/* Observe the service fault when it tries to access it. */
 	run_res = spci_run(SERVICE_VM1, 0);
-	EXPECT_SPCI_ERROR(run_res, SPCI_ABORTED);
+	EXPECT_EQ(exception_handler_receive_num_exceptions(&run_res, mb.recv),
+		  1);
 }
 
 /**
@@ -596,10 +604,11 @@
 	for (int i = 0; i < PAGE_SIZE; ++i) {
 		ASSERT_EQ(ptr[i], 0);
 	}
+	spci_rx_release();
 
-	/* Observe the service fault when it tries to access it. */
 	run_res = spci_run(SERVICE_VM1, 0);
-	EXPECT_SPCI_ERROR(run_res, SPCI_ABORTED);
+	EXPECT_EQ(exception_handler_receive_num_exceptions(&run_res, mb.recv),
+		  1);
 }
 
 /**
@@ -613,7 +622,6 @@
 	uint32_t msg_size;
 
 	SERVICE_SELECT(SERVICE_VM1, "spci_donate_check_upper_bound", mb.send);
-	SERVICE_SELECT(SERVICE_VM2, "spci_donate_check_upper_bound", mb.send);
 
 	/* Initialise the memory before giving it. */
 	memset_s(ptr, sizeof(pages), 'b', 4 * PAGE_SIZE);
@@ -639,9 +647,9 @@
 			  .func,
 		  SPCI_SUCCESS_32);
 
-	/* Observe the service faulting when accessing out of bounds. */
 	run_res = spci_run(SERVICE_VM1, 0);
-	EXPECT_SPCI_ERROR(run_res, SPCI_ABORTED);
+	EXPECT_EQ(exception_handler_receive_num_exceptions(&run_res, mb.recv),
+		  1);
 
 	/* Use different memory regions for verifying the second constituent. */
 	constituents[0].address = (uint64_t)pages + PAGE_SIZE * 1;
@@ -653,19 +661,18 @@
 	 */
 	pages[PAGE_SIZE] = 1;
 
-	/* Use the secondary VM for this test as the first is now aborted. */
 	msg_size = spci_memory_region_init(
-		mb.send, SERVICE_VM2, constituents, ARRAY_SIZE(constituents), 0,
+		mb.send, SERVICE_VM1, constituents, ARRAY_SIZE(constituents), 0,
 		0, SPCI_MEMORY_RW_X, SPCI_MEMORY_NORMAL_MEM,
 		SPCI_MEMORY_CACHE_WRITE_BACK, SPCI_MEMORY_OUTER_SHAREABLE);
-	EXPECT_EQ(spci_msg_send(HF_PRIMARY_VM_ID, SERVICE_VM2, msg_size,
+	EXPECT_EQ(spci_msg_send(HF_PRIMARY_VM_ID, SERVICE_VM1, msg_size,
 				SPCI_MSG_SEND_LEGACY_MEMORY_DONATE)
 			  .func,
 		  SPCI_SUCCESS_32);
 
-	/* Observe the service faulting when accessing out of bounds. */
-	run_res = spci_run(SERVICE_VM2, 0);
-	EXPECT_SPCI_ERROR(run_res, SPCI_ABORTED);
+	run_res = spci_run(SERVICE_VM1, 0);
+	EXPECT_EQ(exception_handler_receive_num_exceptions(&run_res, mb.recv),
+		  1);
 }
 
 /**
@@ -679,7 +686,6 @@
 	uint32_t msg_size;
 
 	SERVICE_SELECT(SERVICE_VM1, "spci_donate_check_lower_bound", mb.send);
-	SERVICE_SELECT(SERVICE_VM2, "spci_donate_check_lower_bound", mb.send);
 
 	/* Initialise the memory before donating it. */
 	memset_s(ptr, sizeof(pages), 'b', 4 * PAGE_SIZE);
@@ -705,9 +711,9 @@
 			  .func,
 		  SPCI_SUCCESS_32);
 
-	/* Observe the service faulting when accessing out of bounds. */
 	run_res = spci_run(SERVICE_VM1, 0);
-	EXPECT_SPCI_ERROR(run_res, SPCI_ABORTED);
+	EXPECT_EQ(exception_handler_receive_num_exceptions(&run_res, mb.recv),
+		  1);
 
 	/* Use different memory regions for verifying the second constituent. */
 	constituents[0].address = (uint64_t)pages + PAGE_SIZE * 1;
@@ -719,19 +725,22 @@
 	 */
 	pages[PAGE_SIZE] = 1;
 
-	/* Use the secondary VM for this test as the first is now aborted. */
 	msg_size = spci_memory_region_init(
-		mb.send, SERVICE_VM2, constituents, ARRAY_SIZE(constituents), 0,
+		mb.send, SERVICE_VM1, constituents, ARRAY_SIZE(constituents), 0,
 		0, SPCI_MEMORY_RW_X, SPCI_MEMORY_NORMAL_MEM,
 		SPCI_MEMORY_CACHE_WRITE_BACK, SPCI_MEMORY_OUTER_SHAREABLE);
-	EXPECT_EQ(spci_msg_send(HF_PRIMARY_VM_ID, SERVICE_VM2, msg_size,
+	EXPECT_EQ(spci_msg_send(HF_PRIMARY_VM_ID, SERVICE_VM1, msg_size,
 				SPCI_MSG_SEND_LEGACY_MEMORY_DONATE)
 			  .func,
 		  SPCI_SUCCESS_32);
 
-	/* Observe the service faulting when accessing out of bounds. */
-	run_res = spci_run(SERVICE_VM2, 0);
-	EXPECT_SPCI_ERROR(run_res, SPCI_ABORTED);
+	run_res = spci_run(SERVICE_VM1, 0);
+	/*
+	 * NOTE: This generates two exceptions, one for the page fault, and one
+	 * for accessing a region past the lower bound.
+	 */
+	EXPECT_EQ(exception_handler_receive_num_exceptions(&run_res, mb.recv),
+		  2);
 }
 
 /**
@@ -768,6 +777,7 @@
 
 	/* Let the memory be returned. */
 	EXPECT_EQ(run_res.func, SPCI_MSG_SEND_32);
+	spci_rx_release();
 
 	/* Share the memory with another VM. */
 	msg_size = spci_memory_region_init(
@@ -779,9 +789,9 @@
 			  .func,
 		  SPCI_SUCCESS_32);
 
-	/* Observe the original service faulting when accessing the memory. */
 	run_res = spci_run(SERVICE_VM1, 0);
-	EXPECT_SPCI_ERROR(run_res, SPCI_ABORTED);
+	EXPECT_EQ(exception_handler_receive_num_exceptions(&run_res, mb.recv),
+		  1);
 }
 
 /**
@@ -822,14 +832,16 @@
 	/* Let the memory be sent from VM1 to VM2. */
 	run_res = spci_run(SERVICE_VM1, 0);
 	EXPECT_EQ(run_res.func, SPCI_MSG_SEND_32);
+	spci_rx_release();
 
 	/* Receive memory in VM2. */
 	run_res = spci_run(SERVICE_VM2, 0);
 	EXPECT_EQ(run_res.func, SPCI_YIELD_32);
 
-	/* Try to access memory in VM1 and fail. */
+	/* Try to access memory in VM1. */
 	run_res = spci_run(SERVICE_VM1, 0);
-	EXPECT_SPCI_ERROR(run_res, SPCI_ABORTED);
+	EXPECT_EQ(exception_handler_receive_num_exceptions(&run_res, mb.recv),
+		  1);
 
 	/* Ensure that memory in VM2 remains the same. */
 	run_res = spci_run(SERVICE_VM2, 0);
@@ -1167,6 +1179,7 @@
 	/* Let service write to and return memory. */
 	run_res = spci_run(SERVICE_VM1, 0);
 	EXPECT_EQ(run_res.func, SPCI_MSG_SEND_32);
+	spci_rx_release();
 
 	/* Re-initialise the memory before giving it. */
 	memset_s(ptr, sizeof(pages), 'b', PAGE_SIZE);
@@ -1185,9 +1198,9 @@
 	run_res = spci_run(SERVICE_VM1, 0);
 	EXPECT_EQ(run_res.func, SPCI_YIELD_32);
 
-	/* Observe the service faulting when writing to the memory. */
 	run_res = spci_run(SERVICE_VM1, 0);
-	EXPECT_SPCI_ERROR(run_res, SPCI_ABORTED);
+	EXPECT_EQ(exception_handler_receive_num_exceptions(&run_res, mb.recv),
+		  1);
 }
 
 /**
@@ -1233,6 +1246,7 @@
 	/* Let service write to and return memory. */
 	run_res = spci_run(SERVICE_VM1, 0);
 	EXPECT_EQ(run_res.func, SPCI_MSG_SEND_32);
+	spci_rx_release();
 
 	/* Re-initialise the memory before giving it. */
 	memset_s(ptr, sizeof(pages), 'b', PAGE_SIZE);
@@ -1257,9 +1271,9 @@
 		ptr[i]++;
 	}
 
-	/* Observe the service faulting when writing to the memory. */
 	run_res = spci_run(SERVICE_VM1, 0);
-	EXPECT_SPCI_ERROR(run_res, SPCI_ABORTED);
+	EXPECT_EQ(exception_handler_receive_num_exceptions(&run_res, mb.recv),
+		  1);
 }
 
 /**
@@ -1304,6 +1318,7 @@
 	/* Let service write to and return memory. */
 	run_res = spci_run(SERVICE_VM1, 0);
 	EXPECT_EQ(run_res.func, SPCI_MSG_SEND_32);
+	spci_rx_release();
 
 	/* Re-initialise the memory before giving it. */
 	memset_s(ptr, sizeof(pages), 'b', PAGE_SIZE);
@@ -1328,9 +1343,9 @@
 		ptr[i]++;
 	}
 
-	/* Observe the service faulting when writing to the memory. */
 	run_res = spci_run(SERVICE_VM1, 0);
-	EXPECT_SPCI_ERROR(run_res, SPCI_ABORTED);
+	EXPECT_EQ(exception_handler_receive_num_exceptions(&run_res, mb.recv),
+		  1);
 }
 
 /**
@@ -1368,6 +1383,7 @@
 	/* Attempt to execute from memory. */
 	run_res = spci_run(SERVICE_VM1, 0);
 	EXPECT_EQ(run_res.func, SPCI_MSG_SEND_32);
+	spci_rx_release();
 
 	msg_size = spci_memory_region_init(
 		mb.send, SERVICE_VM1, constituents, ARRAY_SIZE(constituents), 0,
@@ -1378,9 +1394,9 @@
 			  .func,
 		  SPCI_SUCCESS_32);
 
-	/* Try and fail to execute from the memory region. */
 	run_res = spci_run(SERVICE_VM1, 0);
-	EXPECT_SPCI_ERROR(run_res, SPCI_ABORTED);
+	EXPECT_EQ(exception_handler_receive_num_exceptions(&run_res, mb.recv),
+		  1);
 }
 
 /**
@@ -1418,6 +1434,7 @@
 	/* Attempt to execute from memory. */
 	run_res = spci_run(SERVICE_VM1, 0);
 	EXPECT_EQ(run_res.func, SPCI_MSG_SEND_32);
+	spci_rx_release();
 
 	msg_size = spci_memory_region_init(
 		mb.send, SERVICE_VM1, constituents, ARRAY_SIZE(constituents), 0,
@@ -1428,9 +1445,9 @@
 			  .func,
 		  SPCI_SUCCESS_32);
 
-	/* Try and fail to execute from the memory region. */
 	run_res = spci_run(SERVICE_VM1, 0);
-	EXPECT_SPCI_ERROR(run_res, SPCI_ABORTED);
+	EXPECT_EQ(exception_handler_receive_num_exceptions(&run_res, mb.recv),
+		  1);
 }
 
 /**
@@ -1771,9 +1788,9 @@
 			  .func,
 		  SPCI_SUCCESS_32);
 
-	/* Observe the service faulting when accessing out of bounds. */
 	run_res = spci_run(SERVICE_VM1, 0);
-	EXPECT_SPCI_ERROR(run_res, SPCI_ABORTED);
+	EXPECT_EQ(exception_handler_receive_num_exceptions(&run_res, mb.recv),
+		  1);
 
 	/* Use different memory regions for verifying the second constituent. */
 	constituents[0].address = (uint64_t)pages + PAGE_SIZE * 1;
@@ -1795,9 +1812,9 @@
 			  .func,
 		  SPCI_SUCCESS_32);
 
-	/* Observe the service faulting when accessing out of bounds. */
 	run_res = spci_run(SERVICE_VM2, 0);
-	EXPECT_SPCI_ERROR(run_res, SPCI_ABORTED);
+	EXPECT_EQ(exception_handler_receive_num_exceptions(&run_res, mb.recv),
+		  1);
 }
 
 /**
@@ -1837,9 +1854,9 @@
 			  .func,
 		  SPCI_SUCCESS_32);
 
-	/* Observe the service faulting when accessing out of bounds. */
 	run_res = spci_run(SERVICE_VM1, 0);
-	EXPECT_SPCI_ERROR(run_res, SPCI_ABORTED);
+	EXPECT_EQ(exception_handler_receive_num_exceptions(&run_res, mb.recv),
+		  1);
 
 	/* Use different memory regions for verifying the second constituent. */
 	constituents[0].address = (uint64_t)pages + PAGE_SIZE * 1;
@@ -1861,7 +1878,7 @@
 			  .func,
 		  SPCI_SUCCESS_32);
 
-	/* Observe the service faulting when accessing out of bounds. */
 	run_res = spci_run(SERVICE_VM2, 0);
-	EXPECT_SPCI_ERROR(run_res, SPCI_ABORTED);
+	EXPECT_EQ(exception_handler_receive_num_exceptions(&run_res, mb.recv),
+		  1);
 }
diff --git a/test/vmapi/primary_with_secondaries/perfmon.c b/test/vmapi/primary_with_secondaries/perfmon.c
index 4edc81b..ad43de9 100644
--- a/test/vmapi/primary_with_secondaries/perfmon.c
+++ b/test/vmapi/primary_with_secondaries/perfmon.c
@@ -21,45 +21,15 @@
 #include "sysregs.h"
 #include "test/vmapi/spci.h"
 
-/*
- * TODO(b/132394973): Devise a way to test exhaustively read/write behavior to
- * all debug registers that does not involve a separate service per register,
- * because creating a new test/VM for every instance becomes too slow.
- * This needs proper trap support as a starting point.
- */
-
-TEST(perfmon, secondary_pmccfiltr_el0)
+TEST(perfmon, secondary_basic)
 {
 	struct spci_value run_res;
 	struct mailbox_buffers mb = set_up_mailbox();
 
-	SERVICE_SELECT(SERVICE_VM1, "perfmon_secondary_pmccfiltr_el0", mb.send);
+	SERVICE_SELECT(SERVICE_VM1, "perfmon_secondary_basic", mb.send);
 
 	run_res = spci_run(SERVICE_VM1, 0);
-	EXPECT_SPCI_ERROR(run_res, SPCI_ABORTED);
-}
-
-TEST(perfmon, secondary_pmcr_el0)
-{
-	struct spci_value run_res;
-	struct mailbox_buffers mb = set_up_mailbox();
-
-	SERVICE_SELECT(SERVICE_VM1, "perfmon_secondary_pmcr_el0", mb.send);
-
-	run_res = spci_run(SERVICE_VM1, 0);
-	EXPECT_SPCI_ERROR(run_res, SPCI_ABORTED);
-}
-
-TEST(perfmon, secondary_pmintenset_el1)
-{
-	struct spci_value run_res;
-	struct mailbox_buffers mb = set_up_mailbox();
-
-	SERVICE_SELECT(SERVICE_VM1, "perfmon_secondary_pmintenset_el1",
-		       mb.send);
-
-	run_res = spci_run(SERVICE_VM1, 0);
-	EXPECT_SPCI_ERROR(run_res, SPCI_ABORTED);
+	EXPECT_EQ(run_res.func, SPCI_YIELD_32);
 }
 
 /**
diff --git a/test/vmapi/primary_with_secondaries/services/BUILD.gn b/test/vmapi/primary_with_secondaries/services/BUILD.gn
index 728692f..71235d1 100644
--- a/test/vmapi/primary_with_secondaries/services/BUILD.gn
+++ b/test/vmapi/primary_with_secondaries/services/BUILD.gn
@@ -103,13 +103,17 @@
   ]
 }
 
-# Services related to aborting VMs.
-source_set("abort") {
+# Services related to VMs that access unmapped memory.
+source_set("unmapped") {
   testonly = true
   public_configs = [ "//test/hftest:hftest_config" ]
 
   sources = [
-    "abort.c",
+    "unmapped.c",
+  ]
+
+  deps = [
+    "//test/vmapi/common:common",
   ]
 }
 
@@ -121,6 +125,10 @@
   sources = [
     "boot.c",
   ]
+
+  deps = [
+    "//test/vmapi/common:common",
+  ]
 }
 
 # Service that can be interrupted.
@@ -218,7 +226,6 @@
   testonly = true
 
   deps = [
-    ":abort",
     ":boot",
     ":check_state",
     ":debug_el1",
@@ -231,6 +238,7 @@
     ":receive_block",
     ":relay",
     ":spci_check",
+    ":unmapped",
     ":wfi",
     "//test/hftest:hftest_secondary_vm",
   ]
@@ -240,6 +248,7 @@
   testonly = true
 
   deps = [
+    ":interruptible",
     ":memory",
     ":relay",
     "//test/hftest:hftest_secondary_vm",
diff --git a/test/vmapi/primary_with_secondaries/services/boot.c b/test/vmapi/primary_with_secondaries/services/boot.c
index 3ee35a6..a7b9b66 100644
--- a/test/vmapi/primary_with_secondaries/services/boot.c
+++ b/test/vmapi/primary_with_secondaries/services/boot.c
@@ -14,12 +14,15 @@
  * limitations under the License.
  */
 
+#include "hf/arch/vm/interrupts.h"
+
 #include "hf/mm.h"
 #include "hf/std.h"
 
 #include "vmapi/hf/call.h"
 
 #include "test/hftest.h"
+#include "test/vmapi/exception_handler.h"
 
 /*
  * This must match the size specified for services1 in
@@ -52,9 +55,10 @@
 
 TEST_SERVICE(boot_memory_underrun)
 {
+	exception_setup(NULL, exception_handler_yield);
 	/*
 	 * Try to read memory below the start of the image. This should result
-	 * in the VM being aborted.
+	 * in the VM trapping and yielding.
 	 */
 	dlog("Read memory below limit: %d\n", text_begin[-1]);
 	FAIL("Managed to read memory below limit");
@@ -62,9 +66,10 @@
 
 TEST_SERVICE(boot_memory_overrun)
 {
+	exception_setup(NULL, exception_handler_yield);
 	/*
 	 * Try to read memory above the limit defined by memory_size. This
-	 * should result in the VM being aborted.
+	 * should result in the VM trapping and yielding.
 	 */
 	dlog("Read memory above limit: %d\n",
 	     text_begin[SERVICE_MEMORY_SIZE()]);
diff --git a/test/vmapi/primary_with_secondaries/services/debug_el1.c b/test/vmapi/primary_with_secondaries/services/debug_el1.c
index c53e6bd..1e09f76 100644
--- a/test/vmapi/primary_with_secondaries/services/debug_el1.c
+++ b/test/vmapi/primary_with_secondaries/services/debug_el1.c
@@ -14,41 +14,24 @@
  * limitations under the License.
  */
 
+#include "hf/arch/vm/interrupts.h"
+
 #include "hf/dlog.h"
 
 #include "../sysregs.h"
+#include "test/vmapi/exception_handler.h"
 
-TEST_SERVICE(debug_el1_secondary_mdccint_el1)
+TEST_SERVICE(debug_el1_secondary_basic)
 {
+	exception_setup(NULL, exception_handler_skip_instruction);
+
 	EXPECT_GT(hf_vm_get_id(), HF_PRIMARY_VM_ID);
 	TRY_READ(MDCCINT_EL1);
-	FAIL("Reading debug EL1 register in secondary VM didn't trap.");
-}
-
-TEST_SERVICE(debug_el1_secondary_dbgbcr0_el1)
-{
-	EXPECT_GT(hf_vm_get_id(), HF_PRIMARY_VM_ID);
 	TRY_READ(DBGBCR0_EL1);
-	FAIL("Reading debug EL1 register in secondary VM didn't trap.");
-}
-
-TEST_SERVICE(debug_el1_secondary_dbgbvr0_el1)
-{
-	EXPECT_GT(hf_vm_get_id(), HF_PRIMARY_VM_ID);
 	TRY_READ(DBGBVR0_EL1);
-	FAIL("Reading debug EL1 register in secondary VM didn't trap.");
-}
-
-TEST_SERVICE(debug_el1_secondary_dbgwcr0_el1)
-{
-	EXPECT_GT(hf_vm_get_id(), HF_PRIMARY_VM_ID);
 	TRY_READ(DBGWCR0_EL1);
-	FAIL("Reading debug EL1 register in secondary VM didn't trap.");
-}
-
-TEST_SERVICE(debug_el1_secondary_dbgwvr0_el1)
-{
-	EXPECT_GT(hf_vm_get_id(), HF_PRIMARY_VM_ID);
 	TRY_READ(DBGWVR0_EL1);
-	FAIL("Reading debug EL1 register in secondary VM didn't trap.");
+
+	EXPECT_EQ(exception_handler_get_num(), 5);
+	spci_yield();
 }
diff --git a/test/vmapi/primary_with_secondaries/services/memory.c b/test/vmapi/primary_with_secondaries/services/memory.c
index 58015e2..71a6c68 100644
--- a/test/vmapi/primary_with_secondaries/services/memory.c
+++ b/test/vmapi/primary_with_secondaries/services/memory.c
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+#include "hf/arch/vm/interrupts.h"
+
 #include "hf/mm.h"
 #include "hf/std.h"
 
@@ -21,6 +23,7 @@
 
 #include "primary_with_secondary.h"
 #include "test/hftest.h"
+#include "test/vmapi/exception_handler.h"
 #include "test/vmapi/spci.h"
 
 alignas(PAGE_SIZE) static uint8_t page[PAGE_SIZE];
@@ -82,8 +85,12 @@
 			  .func,
 		  SPCI_SUCCESS_32);
 
+	exception_setup(NULL, exception_handler_yield);
+
 	/* Try using the memory that isn't valid unless it's been returned. */
 	page[16] = 123;
+
+	FAIL("Exception not generated by invalid access.");
 }
 
 TEST_SERVICE(lend_memory_and_fault)
@@ -104,12 +111,18 @@
 			  .func,
 		  SPCI_SUCCESS_32);
 
+	exception_setup(NULL, exception_handler_yield);
+
 	/* Try using the memory that isn't valid unless it's been returned. */
 	page[633] = 180;
+
+	FAIL("Exception not generated by invalid access.");
 }
 
 TEST_SERVICE(spci_memory_return)
 {
+	exception_setup(NULL, exception_handler_yield);
+
 	/* Loop, giving memory back to the sender. */
 	for (;;) {
 		struct spci_value ret = spci_msg_wait();
@@ -163,56 +176,79 @@
 		 * memory has been shared back again.
 		 */
 		ptr[0] = 123;
+
+		FAIL("Exception not generated by invalid access.");
 	}
 }
 
 TEST_SERVICE(spci_donate_check_upper_bound)
 {
-	struct spci_value ret = spci_msg_wait();
-	uint8_t *ptr;
-	uint8_t index;
-	void *recv_buf = SERVICE_RECV_BUFFER();
-	struct spci_memory_region *memory_region;
-	struct spci_memory_region_constituent *constituents;
+	exception_setup(NULL, exception_handler_yield);
 
-	EXPECT_EQ(ret.func, SPCI_MSG_SEND_32);
-	EXPECT_EQ(spci_msg_send_attributes(ret),
-		  SPCI_MSG_SEND_LEGACY_MEMORY_DONATE);
-	memory_region = (struct spci_memory_region *)recv_buf;
-	constituents = spci_memory_region_get_constituents(memory_region);
+	for (;;) {
+		struct spci_value ret = spci_msg_wait();
+		uint8_t *ptr;
+		uint8_t index;
+		void *recv_buf = SERVICE_RECV_BUFFER();
+		struct spci_memory_region *memory_region;
+		struct spci_memory_region_constituent *constituents;
 
-	/* Choose which constituent we want to test. */
-	index = *(uint8_t *)constituents[0].address;
-	ptr = (uint8_t *)constituents[index].address;
+		exception_handler_reset();
 
-	spci_rx_release();
+		EXPECT_EQ(ret.func, SPCI_MSG_SEND_32);
+		EXPECT_EQ(spci_msg_send_attributes(ret),
+			  SPCI_MSG_SEND_LEGACY_MEMORY_DONATE);
 
-	/* Check that one cannot access out of bounds after donated region. */
-	ptr[PAGE_SIZE]++;
+		memory_region = (struct spci_memory_region *)recv_buf;
+		constituents =
+			spci_memory_region_get_constituents(memory_region);
+
+		/* Choose which constituent we want to test. */
+		index = *(uint8_t *)constituents[0].address;
+		ptr = (uint8_t *)constituents[index].address;
+		spci_rx_release();
+
+		/*
+		 * Check that one cannot access out of bounds after donated
+		 * region. This should trigger the exception handler.
+		 */
+		ptr[PAGE_SIZE]++;
+	}
 }
 
 TEST_SERVICE(spci_donate_check_lower_bound)
 {
-	struct spci_value ret = spci_msg_wait();
-	uint8_t *ptr;
-	uint8_t index;
-	void *recv_buf = SERVICE_RECV_BUFFER();
-	struct spci_memory_region *memory_region =
-		(struct spci_memory_region *)recv_buf;
-	struct spci_memory_region_constituent *constituents =
-		spci_memory_region_get_constituents(memory_region);
+	exception_setup(NULL, exception_handler_yield);
 
-	EXPECT_EQ(ret.func, SPCI_MSG_SEND_32);
-	EXPECT_EQ(spci_msg_send_attributes(ret),
-		  SPCI_MSG_SEND_LEGACY_MEMORY_DONATE);
+	for (;;) {
+		struct spci_value ret = spci_msg_wait();
+		uint8_t *ptr;
+		uint8_t index;
+		void *recv_buf = SERVICE_RECV_BUFFER();
+		struct spci_memory_region *memory_region;
+		struct spci_memory_region_constituent *constituents;
 
-	/* Choose which constituent we want to test. */
-	index = *(uint8_t *)constituents[0].address;
-	ptr = (uint8_t *)constituents[index].address;
-	spci_rx_release();
+		exception_handler_reset();
 
-	/* Check that one cannot access out of bounds before donated region. */
-	ptr[-1]++;
+		EXPECT_EQ(ret.func, SPCI_MSG_SEND_32);
+		EXPECT_EQ(spci_msg_send_attributes(ret),
+			  SPCI_MSG_SEND_LEGACY_MEMORY_DONATE);
+
+		memory_region = (struct spci_memory_region *)recv_buf;
+		constituents =
+			spci_memory_region_get_constituents(memory_region);
+
+		/* Choose which constituent we want to test. */
+		index = *(uint8_t *)constituents[0].address;
+		ptr = (uint8_t *)constituents[index].address;
+		spci_rx_release();
+
+		/*
+		 * Check that one cannot access out of bounds after donated
+		 * region. This should trigger the exception handler.
+		 */
+		ptr[-1]++;
+	}
 }
 
 /**
@@ -234,6 +270,8 @@
 	EXPECT_EQ(spci_msg_send_attributes(ret),
 		  SPCI_MSG_SEND_LEGACY_MEMORY_DONATE);
 
+	exception_setup(NULL, exception_handler_yield);
+
 	ptr = (uint8_t *)constituents[0].address;
 
 	/* Donate memory to next VM. */
@@ -250,8 +288,8 @@
 
 	/* Ensure that we are unable to modify memory any more. */
 	ptr[0] = 'c';
-	EXPECT_EQ(ptr[0], 'c');
-	spci_yield();
+
+	FAIL("Exception not generated by invalid access.");
 }
 
 /**
@@ -373,6 +411,8 @@
 
 TEST_SERVICE(spci_memory_lend_relinquish)
 {
+	exception_setup(NULL, exception_handler_yield);
+
 	/* Loop, giving memory back to the sender. */
 	for (;;) {
 		struct spci_value ret = spci_msg_wait();
@@ -559,6 +599,8 @@
  */
 TEST_SERVICE(spci_memory_lend_relinquish_X)
 {
+	exception_setup(NULL, exception_handler_yield);
+
 	for (;;) {
 		struct spci_value ret = spci_msg_wait();
 		uint64_t *ptr;
@@ -605,6 +647,8 @@
  */
 TEST_SERVICE(spci_memory_lend_relinquish_RW)
 {
+	exception_setup(NULL, exception_handler_yield);
+
 	for (;;) {
 		struct spci_value ret = spci_msg_wait();
 		uint8_t *ptr;
@@ -682,6 +726,8 @@
 	EXPECT_EQ(spci_msg_send_attributes(ret),
 		  SPCI_MSG_SEND_LEGACY_MEMORY_LEND);
 
+	exception_setup(NULL, exception_handler_yield);
+
 	/* Choose which constituent we want to test. */
 	index = *(uint8_t *)constituents[0].address;
 	ptr = (uint8_t *)constituents[index].address;
@@ -689,6 +735,8 @@
 
 	/* Check that one cannot access after lent region. */
 	ASSERT_EQ(ptr[PAGE_SIZE], 0);
+
+	FAIL("Exception not generated by invalid access.");
 }
 
 /**
@@ -706,6 +754,8 @@
 	struct spci_memory_region_constituent *constituents =
 		spci_memory_region_get_constituents(memory_region);
 
+	exception_setup(NULL, exception_handler_yield);
+
 	EXPECT_EQ(ret.func, SPCI_MSG_SEND_32);
 	EXPECT_EQ(spci_msg_send_attributes(ret),
 		  SPCI_MSG_SEND_LEGACY_MEMORY_LEND);
@@ -717,7 +767,8 @@
 
 	/* Check that one cannot access after lent region. */
 	ptr[-1]++;
-	spci_yield();
+
+	FAIL("Exception not generated by invalid access.");
 }
 
 TEST_SERVICE(spci_memory_lend_twice)
diff --git a/test/vmapi/primary_with_secondaries/services/perfmon.c b/test/vmapi/primary_with_secondaries/services/perfmon.c
index 413391a..1e19ffb 100644
--- a/test/vmapi/primary_with_secondaries/services/perfmon.c
+++ b/test/vmapi/primary_with_secondaries/services/perfmon.c
@@ -14,27 +14,22 @@
  * limitations under the License.
  */
 
+#include "hf/arch/vm/interrupts.h"
+
 #include "hf/dlog.h"
 
 #include "../sysregs.h"
+#include "test/vmapi/exception_handler.h"
 
-TEST_SERVICE(perfmon_secondary_pmccfiltr_el0)
+TEST_SERVICE(perfmon_secondary_basic)
 {
+	exception_setup(NULL, exception_handler_skip_instruction);
+
 	EXPECT_GT(hf_vm_get_id(), HF_PRIMARY_VM_ID);
 	TRY_READ(PMCCFILTR_EL0);
-	FAIL("Accessing perfmon register in secondary VM didn't trap.");
-}
-
-TEST_SERVICE(perfmon_secondary_pmcr_el0)
-{
-	EXPECT_GT(hf_vm_get_id(), HF_PRIMARY_VM_ID);
 	TRY_READ(PMCR_EL0);
-	FAIL("Accessing perfmon register in secondary VM didn't trap.");
-}
-
-TEST_SERVICE(perfmon_secondary_pmintenset_el1)
-{
-	EXPECT_GT(hf_vm_get_id(), HF_PRIMARY_VM_ID);
 	write_msr(PMINTENSET_EL1, 0xf);
-	FAIL("Accessing perfmon register in secondary VM didn't trap.");
+
+	EXPECT_EQ(exception_handler_get_num(), 3);
+	spci_yield();
 }
diff --git a/test/vmapi/primary_with_secondaries/services/abort.c b/test/vmapi/primary_with_secondaries/services/unmapped.c
similarity index 78%
rename from test/vmapi/primary_with_secondaries/services/abort.c
rename to test/vmapi/primary_with_secondaries/services/unmapped.c
index 660fa6d..b23ffb2 100644
--- a/test/vmapi/primary_with_secondaries/services/abort.c
+++ b/test/vmapi/primary_with_secondaries/services/unmapped.c
@@ -14,23 +14,29 @@
  * limitations under the License.
  */
 
+#include "hf/arch/vm/interrupts.h"
+
 #include "hf/mm.h"
 #include "hf/std.h"
 
 #include "vmapi/hf/call.h"
 
+#include "../sysregs.h"
 #include "test/hftest.h"
+#include "test/vmapi/exception_handler.h"
 
 alignas(PAGE_SIZE) static uint8_t pages[2 * PAGE_SIZE];
 
-TEST_SERVICE(data_abort)
+TEST_SERVICE(data_unmapped)
 {
 	/* Not using NULL so static analysis doesn't complain. */
 	int *p = (int *)1;
+	exception_setup(NULL, exception_handler_yield);
 	*p = 12;
+	FAIL("Exception not generated by invalid access.");
 }
 
-TEST_SERVICE(straddling_data_abort)
+TEST_SERVICE(straddling_data_unmapped)
 {
 	void *send_buf = SERVICE_SEND_BUFFER();
 	/* Give some memory to the primary VM so that it's unmapped. */
@@ -42,22 +48,27 @@
 		ARRAY_SIZE(constituents), 0, 0, SPCI_MEMORY_RW_X,
 		SPCI_MEMORY_NORMAL_MEM, SPCI_MEMORY_CACHE_WRITE_BACK,
 		SPCI_MEMORY_OUTER_SHAREABLE);
+	exception_setup(NULL, exception_handler_yield);
+
 	EXPECT_EQ(spci_msg_send(hf_vm_get_id(), HF_PRIMARY_VM_ID, msg_size,
 				SPCI_MSG_SEND_LEGACY_MEMORY_DONATE)
 			  .func,
 		  SPCI_SUCCESS_32);
 
 	*(volatile uint64_t *)(&pages[PAGE_SIZE - 6]);
+	FAIL("Exception not generated by invalid access.");
 }
 
-TEST_SERVICE(instruction_abort)
+TEST_SERVICE(instruction_unmapped)
 {
 	/* Not using NULL so static analysis doesn't complain. */
 	int (*f)(void) = (int (*)(void))4;
+	exception_setup(NULL, exception_handler_yield);
 	f();
+	FAIL("Exception not generated by invalid access.");
 }
 
-TEST_SERVICE(straddling_instruction_abort)
+TEST_SERVICE(straddling_instruction_unmapped)
 {
 	void *send_buf = SERVICE_SEND_BUFFER();
 
@@ -76,6 +87,9 @@
 		ARRAY_SIZE(constituents), 0, 0, SPCI_MEMORY_RW_X,
 		SPCI_MEMORY_NORMAL_MEM, SPCI_MEMORY_CACHE_WRITE_BACK,
 		SPCI_MEMORY_OUTER_SHAREABLE);
+
+	exception_setup(NULL, exception_handler_yield);
+
 	EXPECT_EQ(spci_msg_send(hf_vm_get_id(), HF_PRIMARY_VM_ID, msg_size,
 				SPCI_MSG_SEND_LEGACY_MEMORY_DONATE)
 			  .func,
@@ -83,4 +97,5 @@
 
 	/* Branch to instruction whose 2 bytes are now in an unmapped page. */
 	f();
+	FAIL("Exception not generated by invalid access.");
 }
diff --git a/test/vmapi/primary_with_secondaries/sysregs.c b/test/vmapi/primary_with_secondaries/sysregs.c
index f15a692..42f0636 100644
--- a/test/vmapi/primary_with_secondaries/sysregs.c
+++ b/test/vmapi/primary_with_secondaries/sysregs.c
@@ -19,30 +19,12 @@
 #include "hf/arch/vm/interrupts.h"
 
 #include "primary_with_secondary.h"
+#include "test/vmapi/exception_handler.h"
 #include "test/vmapi/spci.h"
 
-/**
- * Tracks the number of times the exception handler has been invoked.
- */
-static int num_exceptions = 0;
-
-static bool handle_exception(void)
-{
-	dlog("%s function is triggered!\n", __func__);
-	++num_exceptions;
-
-	/* Skip instruction that triggered the exception. */
-	uint64_t next_pc = read_msr(elr_el1);
-	next_pc += 4UL;
-	write_msr(elr_el1, next_pc);
-
-	/* Indicate that elr_el1 should not be restored. */
-	return true;
-}
-
 SET_UP(sysregs)
 {
-	exception_setup(NULL, handle_exception);
+	exception_setup(NULL, exception_handler_skip_instruction);
 }
 
 /**
@@ -50,10 +32,8 @@
  */
 TEST(sysregs, lor_exception)
 {
-	num_exceptions = 0;
-
 	EXPECT_EQ(hf_vm_get_id(), HF_PRIMARY_VM_ID);
 	TRY_READ(MSR_LORC_EL1);
 
-	EXPECT_EQ(num_exceptions, 1);
+	EXPECT_EQ(exception_handler_get_num(), 1);
 }
diff --git a/test/vmapi/primary_with_secondaries/unmapped.c b/test/vmapi/primary_with_secondaries/unmapped.c
new file mode 100644
index 0000000..39682f2
--- /dev/null
+++ b/test/vmapi/primary_with_secondaries/unmapped.c
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2019 The Hafnium Authors.
+ *
+ * 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.
+ */
+
+#include "vmapi/hf/call.h"
+
+#include "primary_with_secondary.h"
+#include "test/hftest.h"
+#include "test/vmapi/exception_handler.h"
+#include "test/vmapi/spci.h"
+
+/**
+ * Accessing unmapped memory traps the VM.
+ */
+TEST(unmapped, data_unmapped)
+{
+	struct spci_value run_res;
+	struct mailbox_buffers mb = set_up_mailbox();
+
+	SERVICE_SELECT(SERVICE_VM1, "data_unmapped", mb.send);
+
+	run_res = spci_run(SERVICE_VM1, 0);
+	EXPECT_EQ(exception_handler_receive_num_exceptions(&run_res, mb.recv),
+		  1);
+}
+
+/**
+ * Accessing partially unmapped memory traps the VM.
+ */
+TEST(unmapped, straddling_data_unmapped)
+{
+	struct spci_value run_res;
+	struct mailbox_buffers mb = set_up_mailbox();
+
+	SERVICE_SELECT(SERVICE_VM1, "straddling_data_unmapped", mb.send);
+
+	/*
+	 * First we get a message about the memory being donated to us, then we
+	 * get the trap.
+	 */
+	run_res = spci_run(SERVICE_VM1, 0);
+	EXPECT_EQ(run_res.func, SPCI_MSG_SEND_32);
+	spci_rx_release();
+
+	run_res = spci_run(SERVICE_VM1, 0);
+	EXPECT_EQ(exception_handler_receive_num_exceptions(&run_res, mb.recv),
+		  1);
+}
+
+/**
+ * Executing unmapped memory traps the VM.
+ */
+TEST(unmapped, instruction_unmapped)
+{
+	struct spci_value run_res;
+	struct mailbox_buffers mb = set_up_mailbox();
+
+	SERVICE_SELECT(SERVICE_VM1, "instruction_unmapped", mb.send);
+
+	run_res = spci_run(SERVICE_VM1, 0);
+	EXPECT_EQ(exception_handler_receive_num_exceptions(&run_res, mb.recv),
+		  1);
+}
+
+/**
+ * Executing partially unmapped memory traps the VM.
+ */
+TEST(unmapped, straddling_instruction_unmapped)
+{
+	struct spci_value run_res;
+	struct mailbox_buffers mb = set_up_mailbox();
+
+	SERVICE_SELECT(SERVICE_VM1, "straddling_instruction_unmapped", mb.send);
+
+	/*
+	 * First we get a message about the memory being donated to us, then we
+	 * get the trap.
+	 */
+	run_res = spci_run(SERVICE_VM1, 0);
+	EXPECT_EQ(run_res.func, SPCI_MSG_SEND_32);
+	spci_rx_release();
+
+	run_res = spci_run(SERVICE_VM1, 0);
+	EXPECT_EQ(exception_handler_receive_num_exceptions(&run_res, mb.recv),
+		  1);
+}