Adds information about exceptions/interrupts to interactive prompt.
Adds capability to break on interrupt and/or exceptions and their returns.

PiperOrigin-RevId: 663036503
Change-Id: Ie3b89c2375808528280c24a8a81f4307945183da
diff --git a/cheriot/BUILD b/cheriot/BUILD
index 8380337..15fce63 100644
--- a/cheriot/BUILD
+++ b/cheriot/BUILD
@@ -543,6 +543,7 @@
         "@com_google_absl//absl/container:btree",
         "@com_google_absl//absl/container:flat_hash_set",
         "@com_google_absl//absl/functional:any_invocable",
+        "@com_google_absl//absl/log",
         "@com_google_absl//absl/status",
         "@com_google_absl//absl/status:statusor",
         "@com_google_absl//absl/strings",
@@ -550,6 +551,7 @@
         "@com_google_mpact-riscv//riscv:stoull_wrapper",
         "@com_google_mpact-sim//mpact/sim/generic:core",
         "@com_google_mpact-sim//mpact/sim/generic:core_debug_interface",
+        "@com_google_mpact-sim//mpact/sim/generic:counters",
         "@com_google_mpact-sim//mpact/sim/generic:debug_command_shell_interface",
         "@com_google_mpact-sim//mpact/sim/generic:instruction",
         "@com_google_mpact-sim//mpact/sim/generic:type_helpers",
diff --git a/cheriot/cheriot_state.cc b/cheriot/cheriot_state.cc
index 28edaec..e0c194d 100644
--- a/cheriot/cheriot_state.cc
+++ b/cheriot/cheriot_state.cc
@@ -241,7 +241,9 @@
                            util::AtomicMemoryOpInterface *atomic_memory)
     : generic::ArchState(id),
       tagged_memory_(memory),
-      atomic_tagged_memory_(atomic_memory) {
+      atomic_tagged_memory_(atomic_memory),
+      counter_interrupts_taken_("interrupts_taken", 0),
+      counter_interrupt_returns_("interrupt_returns", 0) {
   for (auto &[name, index] : std::vector<std::pair<std::string, unsigned>>{
            {"c0", 0b0'00000},   {"c1", 0b0'00001},   {"c2", 0b0'00010},
            {"c3", 0b0'00011},   {"c4", 0b0'00100},   {"c5", 0b0'00101},
@@ -258,6 +260,8 @@
            {"mepcc", 0b1'11111}}) {
     cap_index_map_.emplace(name, index);
   }
+  CHECK_OK(AddCounter(&counter_interrupts_taken_));
+  CHECK_OK(AddCounter(&counter_interrupt_returns_));
   // Create root capabilities and the special capability CSRs.
   executable_root_ = new CheriotRegister(this, "executable_root");
   executable_root_->ResetExecuteRoot();
@@ -604,8 +608,8 @@
 void CheriotState::Trap(bool is_interrupt, uint64_t trap_value,
                         uint64_t exception_code, uint64_t epc,
                         const Instruction *inst) {
-  // LOG(INFO) << "Trap: " << std::hex << is_interrupt << " " << trap_value << "
-  // " << exception_code << " " << epc; Call the handler.
+  // LOG(INFO) << "Trap: " << std::hex << is_interrupt << " " << trap_value
+  //  << " " << exception_code << " " << epc;  // Call the handler.
   if (on_trap_ != nullptr) {
     bool res = on_trap_(is_interrupt, trap_value, exception_code, epc, inst);
     // If the handler returns true, the trap has been handled. Just return.
@@ -651,6 +655,7 @@
   set_branch(true);
   // TODO(torerik): set next pc
   mstatus_->Submit();
+  counter_interrupts_taken_.Increment(1);
 }
 
 // CheckForInterrupt is called whenever any relevant bits in the interrupt
@@ -682,7 +687,6 @@
   Trap(/*is_interrupt*/ true, 0, *available_interrupt_code_, epc, nullptr);
   // Clear pending interrupt.
   is_interrupt_available_ = false;
-  ++interrupt_handler_depth_;
   available_interrupt_code_ = InterruptCode::kNone;
 }
 
diff --git a/cheriot/cheriot_state.h b/cheriot/cheriot_state.h
index c736f71..b24f5e3 100644
--- a/cheriot/cheriot_state.h
+++ b/cheriot/cheriot_state.h
@@ -30,6 +30,7 @@
 #include "absl/strings/str_cat.h"
 #include "absl/strings/string_view.h"
 #include "mpact/sim/generic/arch_state.h"
+#include "mpact/sim/generic/counters.h"
 #include "mpact/sim/generic/data_buffer.h"
 #include "mpact/sim/generic/instruction.h"
 #include "mpact/sim/generic/operand_interface.h"
@@ -60,6 +61,7 @@
 using ::mpact::sim::generic::DataBuffer;
 using ::mpact::sim::generic::Instruction;
 using ::mpact::sim::generic::ReferenceCount;
+using ::mpact::sim::generic::SimpleCounter;
 using ::mpact::sim::riscv::InterruptCode;
 using ::mpact::sim::riscv::IsaExtension;
 using ::mpact::sim::riscv::PrivilegeMode;
@@ -288,11 +290,23 @@
   // Indicates that the program has returned from handling an interrupt. This
   // decrements the interrupt handler depth and should be called by the
   // implementations of mret, sret, and uret.
-  void SignalReturnFromInterrupt() { --interrupt_handler_depth_; }
+  void SignalReturnFromInterrupt() { counter_interrupt_returns_.Increment(1); }
 
   // Returns the depth of the interrupt handler currently being executed, or
   // zero if no interrupt handler is being executed.
-  int InterruptHandlerDepth() const { return interrupt_handler_depth_; }
+  int InterruptHandlerDepth() const {
+    return counter_interrupts_taken_.GetValue() -
+           counter_interrupt_returns_.GetValue();
+  }
+  // Returns the interrupt counters. This allows code to be connected to the
+  // counters when the value changes.
+  SimpleCounter<int64_t> *counter_interrupts_taken() {
+    return &counter_interrupts_taken_;
+  }
+
+  SimpleCounter<int64_t> *counter_interrupt_returns() {
+    return &counter_interrupt_returns_;
+  }
 
   // Returns true if a capability register with the given base should be
   // revoked.
@@ -434,7 +448,8 @@
   CheriotVectorState *rv_vector_ = nullptr;
   // For interrupt handling.
   bool is_interrupt_available_ = false;
-  int interrupt_handler_depth_ = 0;
+  SimpleCounter<int64_t> counter_interrupts_taken_;
+  SimpleCounter<int64_t> counter_interrupt_returns_;
   InterruptCode available_interrupt_code_ = InterruptCode::kNone;
   // By default, execute in machine mode.
   PrivilegeMode privilege_mode_ = PrivilegeMode::kMachine;
diff --git a/cheriot/cheriot_top.cc b/cheriot/cheriot_top.cc
index 47002d3..14d1cbf 100644
--- a/cheriot/cheriot_top.cc
+++ b/cheriot/cheriot_top.cc
@@ -73,6 +73,7 @@
       counter_pc_("pc", 0),
       cap_reg_re_{
           R"((\w+)\.(top|base|length|tag|permissions|object_type|reserved))"} {
+  CHECK_OK(AddChildComponent(*state_));
   Initialize();
 }
 
diff --git a/cheriot/debug_command_shell.cc b/cheriot/debug_command_shell.cc
index 43537b4..f174d56 100644
--- a/cheriot/debug_command_shell.cc
+++ b/cheriot/debug_command_shell.cc
@@ -17,6 +17,7 @@
 #include <cstdint>
 #include <cstring>
 #include <fstream>
+#include <functional>
 #include <istream>
 #include <ostream>
 #include <string>
@@ -24,6 +25,7 @@
 #include <vector>
 
 #include "absl/functional/any_invocable.h"
+#include "absl/log/log.h"
 #include "absl/status/status.h"
 #include "absl/status/statusor.h"
 #include "absl/strings/numbers.h"
@@ -51,6 +53,59 @@
 using HaltReason = ::mpact::sim::generic::CoreDebugInterface::HaltReason;
 using ::mpact::sim::generic::operator*;  // NOLINT: used below (clang error).
 
+DebugCommandShell::InterruptListener::InterruptListener(CoreAccess *core_access)
+    : core_access_(core_access),
+      top_(static_cast<CheriotTop *>(core_access->debug_interface)),
+      taken_listener_(std::bind_front(&InterruptListener::SetTakenValue, this)),
+      return_listener_(
+          std::bind_front(&InterruptListener::SetReturnValue, this)) {
+  top_->state()->counter_interrupts_taken()->AddListener(&taken_listener_);
+  top_->state()->counter_interrupt_returns()->AddListener(&return_listener_);
+}
+
+void DebugCommandShell::InterruptListener::SetReturnValue(int64_t value) {
+  if (interrupt_info_list_.empty()) {
+    LOG(ERROR) << "Interrupt stack is empty";
+    return;
+  }
+  auto info = interrupt_info_list_.front();
+  interrupt_info_list_.pop_front();
+  // If breakpoints are enabled, then request a halt of the appropriate type.
+  if (info.is_interrupt && interrupts_enabled_)
+    top_->RequestHalt(kInterruptReturn, nullptr);
+  if (!info.is_interrupt && exceptions_enabled_)
+    top_->RequestHalt(kExceptionReturn, nullptr);
+}
+
+void DebugCommandShell::InterruptListener::SetTakenValue(int64_t value) {
+  InterruptInfo info;
+  bool ok = true;
+  // Read the values of the interrupt registers.
+  auto res = core_access_->debug_interface->ReadRegister("mcause");
+  ok &= res.ok();
+  if (ok) info.cause = res.value();
+
+  res = core_access_->debug_interface->ReadRegister("mtval");
+  ok &= res.ok();
+  if (ok) info.tval = res.value();
+
+  res = core_access_->debug_interface->ReadRegister("mepcc");
+  ok &= res.ok();
+  if (ok) info.epc = res.value();
+
+  if (!ok) {
+    LOG(ERROR) << "Failed to read interrupt registers";
+    return;
+  }
+  info.is_interrupt = (info.cause & 0x8000'0000u) != 0;
+  // If breakpoints are enabled, the request a halt of the appropriate type.
+  if (info.is_interrupt && interrupts_enabled_)
+    top_->RequestHalt(kInterruptTaken, nullptr);
+  if (!info.is_interrupt && exceptions_enabled_)
+    top_->RequestHalt(kExceptionTaken, nullptr);
+  interrupt_info_list_.push_front(info);
+}
+
 // The constructor initializes all the regular expressions and the help string.
 DebugCommandShell::DebugCommandShell()
     : quit_re_{R"(\s*quit\s*)"},
@@ -123,6 +178,9 @@
                                      according to FORMAT. Default format is x32.
   break [set] VALUE                - set breakpoint at address VALUE.
   break [set] SYMBOL               - set breakpoint at value of SYMBOL.
+                                     '$exception' and '$interrupt are special
+                                     symbols to break upon entry to and return
+                                     from exception/interrupt handlers.
   break set #<N>                   - reactivate breakpoint index N.
   break #<N>                       - reactivate breakpoint index N.
   break clear VALUE                - clear breakpoint at address VALUE.
@@ -175,8 +233,15 @@
   reg_vector_.push_back("mscratchc");
 }
 
+DebugCommandShell::~DebugCommandShell() {
+  for (auto *listener : interrupt_listeners_) {
+    delete listener;
+  }
+}
+
 void DebugCommandShell::AddCore(const CoreAccess &core_access) {
   core_access_.push_back(core_access);
+  interrupt_listeners_.push_back(new InterruptListener(&core_access_.back()));
   core_action_point_id_.push_back(0);
   core_action_point_info_.emplace_back();
 }
@@ -218,6 +283,19 @@
           case *HaltReason::kDataWatchPoint:
             absl::StrAppend(&prompt, "Stopped at data watchpoint\n");
             break;
+          case InterruptListener::kInterruptTaken:
+            absl::StrAppend(&prompt, "Stopped at taken interrupt\n");
+            break;
+          case InterruptListener::kInterruptReturn:
+            absl::StrAppend(&prompt,
+                            "Stopped at return from interrupt handler\n");
+            break;
+          case InterruptListener::kExceptionTaken:
+            absl::StrAppend(&prompt, "Stopped at exception\n");
+            break;
+          case InterruptListener::kExceptionReturn:
+            absl::StrAppend(&prompt, "Stopped at return exception handler\n");
+            break;
           case *HaltReason::kProgramDone:
             absl::StrAppend(&prompt, "Program done\n");
             break;
@@ -252,6 +330,16 @@
       }
       absl::StrAppend(&prompt, "\n");
     }
+    auto &info_list =
+        interrupt_listeners_[current_core_]->interrupt_info_list();
+    int count = 0;
+    for (auto iter = info_list.rbegin(); iter != info_list.rend(); ++iter) {
+      auto const &info = *iter;
+      absl::StrAppend(&prompt, "[", count++, "] ",
+                      info.is_interrupt ? "interrupt" : "exception", " ",
+                      info.is_interrupt ? GetInterruptDescription(info)
+                                        : GetExceptionDescription(info));
+    }
     absl::StrAppend(&prompt, "[", current_core_, "] > ");
     while (!command_streams_.empty()) {
       auto &current_is = *command_streams_.back();
@@ -455,16 +543,31 @@
 
     // break set VALUE | SYMBOL
     if (std::string str_value;
-        RE2::FullMatch(line_view, *set_break_re_, &str_value)) {
+        RE2::FullMatch(line_view, *set_break_re_, &str_value) ||
+        RE2::FullMatch(line_view, *set_break2_re_, &str_value)) {
       if (str_value == "$branch") {
         auto *cheriot_interface = reinterpret_cast<CheriotDebugInterface *>(
             core_access_[current_core_].debug_interface);
         cheriot_interface->SetBreakOnControlFlowChange(true);
         continue;
+      } else if (str_value == "$exception") {
+        if (interrupt_listeners_[current_core_]->AreExceptionsEnabled()) {
+          os << "Break on exceptions are already enabled\n";
+          continue;
+        }
+        interrupt_listeners_[current_core_]->SetEnableExceptions(true);
+        continue;
+      } else if (str_value == "$interrupt") {
+        if (interrupt_listeners_[current_core_]->AreInterruptsEnabled()) {
+          os << "Break on interrupts are already enabled\n";
+          continue;
+        }
+        interrupt_listeners_[current_core_]->SetEnableInterrupts(true);
+        continue;
       }
       auto result = GetValueFromString(current_core_, str_value, /*radix=*/0);
       if (!result.ok()) {
-        os << absl::StrCat("Error: '", str_value, "' ",
+        os << absl::StrCat("Error: ?????? '", str_value, "' ",
                            result.status().message())
            << std::endl;
         os.flush();
@@ -538,6 +641,8 @@
         if (!status.ok()) {
           os << absl::StrCat("Error: ", status.message(), "\n");
         }
+      } else {
+        os << absl::StrCat("No such active breakpoint: #", index, "\n");
       }
       continue;
     }
@@ -566,6 +671,20 @@
             core_access_[current_core_].debug_interface);
         cheriot_interface->SetBreakOnControlFlowChange(false);
         continue;
+      } else if (str_value == "$exception") {
+        if (!interrupt_listeners_[current_core_]->AreExceptionsEnabled()) {
+          os << "Break on exceptions are already disabled\n";
+          continue;
+        }
+        interrupt_listeners_[current_core_]->SetEnableExceptions(false);
+        continue;
+      } else if (str_value == "$interrupt") {
+        if (!interrupt_listeners_[current_core_]->AreInterruptsEnabled()) {
+          os << "Break on interrupts are already disabled\n";
+          continue;
+        }
+        interrupt_listeners_[current_core_]->SetEnableInterrupts(false);
+        continue;
       }
       auto result = GetValueFromString(current_core_, str_value, /*radix=*/0);
       if (!result.ok()) {
@@ -632,40 +751,6 @@
       continue;
     }
 
-    // break SYMBOL | VALUE
-    if (std::string str_value;
-        RE2::FullMatch(line_view, *set_break2_re_, &str_value)) {
-      if (str_value == "$branch") {
-        auto *cheriot_interface = reinterpret_cast<CheriotDebugInterface *>(
-            core_access_[current_core_].debug_interface);
-        cheriot_interface->SetBreakOnControlFlowChange(true);
-        continue;
-      }
-      auto result = GetValueFromString(current_core_, str_value, /*radix=*/0);
-      if (!result.ok()) {
-        os << absl::StrCat("Error: '", str_value, "' ",
-                           result.status().message())
-           << std::endl;
-        os.flush();
-        continue;
-      }
-      auto cmd_result =
-          core_access_[current_core_].debug_interface->SetSwBreakpoint(
-              result.value());
-      if (!cmd_result.ok()) {
-        os << "Error:  " << cmd_result.message() << std::endl;
-        os.flush();
-        continue;
-      }
-      core_access_[current_core_]
-          .breakpoint_map[core_access_[current_core_].breakpoint_index++] =
-          result.value();
-      os << absl::StrCat("Breakpoint set at 0x",
-                         absl::Hex(result.value(), absl::PadSpec::kZeroPad8))
-         << std::endl;
-      continue;
-    }
-
     // watch set SYMBOL | VALUE  <length> [r|w|rw]
     if (std::string str_value, length_value, rw_value; RE2::FullMatch(
             line_view, *set_watch_re_, &str_value, &length_value, &rw_value)) {
@@ -1457,11 +1542,9 @@
   // inserted and return.
   uint64_t bp_address = pcc + inst->size();
   inst->DecRef();
-  bool bp_set = false;
   // See if there is a bp on that address already, if so, don't try to set
   // another one.
   if (!core_access_[current_core_].debug_interface->HasBreakpoint(bp_address)) {
-    bp_set = true;
     auto bp_set_res =
         core_access_[current_core_].debug_interface->SetSwBreakpoint(
             bp_address);
@@ -1684,6 +1767,159 @@
   return absl::OkStatus();
 }
 
+std::string DebugCommandShell::GetInterruptDescription(
+    const InterruptInfo &info) {
+  std::string output;
+  if (!info.is_interrupt) return output;
+  switch (info.cause & 0x7fff'ffff) {
+    case 0:
+      absl::StrAppend(&output, "User software interrupt");
+      break;
+    case 1:
+      absl::StrAppend(&output, "Supervisor software interrupt");
+      break;
+    case 3:
+      absl::StrAppend(&output, "Machine software interrupt");
+      break;
+    case 4:
+      absl::StrAppend(&output, "User timer interrupt");
+      break;
+    case 5:
+      absl::StrAppend(&output, "Supervisor timer interrupt");
+      break;
+    case 7:
+      absl::StrAppend(&output, "Machine timer interrupt");
+      break;
+    case 8:
+      absl::StrAppend(&output, "User external interrupt");
+      break;
+    case 9:
+      absl::StrAppend(&output, "Supervisor external interrupt");
+      break;
+    case 11:
+      absl::StrAppend(&output, "Machine external interrupt");
+      break;
+    default:
+      absl::StrAppend(&output, "Error - Unknown interrupt");
+      break;
+  }
+  absl::StrAppend(&output, "\n");
+  return output;
+}
+
+std::string DebugCommandShell::GetExceptionDescription(
+    const InterruptInfo &info) {
+  std::string output;
+  if (info.is_interrupt) return output;
+  absl::StrAppend(&output, " Exception taken at ", absl::Hex(info.epc), ": ");
+  switch (info.cause) {
+    case 0:
+      absl::StrAppend(&output, "Instruction address misaligned: ");
+      absl::StrAppend(&output, ": ", absl::Hex(info.tval));
+      break;
+    case 1:
+      absl::StrAppend(&output, "Instruction access fault");
+      break;
+    case 2:
+      absl::StrAppend(&output, "Illegal instruction");
+      absl::StrAppend(&output, " opcode: ", absl::Hex(info.tval));
+      break;
+    case 3:
+      absl::StrAppend(&output, "Breakpoint instruction");
+      break;
+    case 4:
+      absl::StrAppend(&output, "Load address misaligned");
+      absl::StrAppend(&output, ": ", absl::Hex(info.tval));
+      break;
+    case 5:
+      absl::StrAppend(&output, "Load access fault");
+      break;
+    case 6:
+      absl::StrAppend(&output, "Store/AMO address misaligned");
+      absl::StrAppend(&output, ": ", absl::Hex(info.tval));
+      break;
+    case 7:
+      absl::StrAppend(&output, "Store/AMO access fault");
+      break;
+    case 8:
+      absl::StrAppend(&output, "Environment call from U-mode");
+      break;
+    case 9:
+      absl::StrAppend(&output, "Environment call from S-mode");
+      break;
+    case 11:
+      absl::StrAppend(&output, "Environment call from M-mode");
+      break;
+    case 12:
+      absl::StrAppend(&output, "Instruction page fault");
+      absl::StrAppend(&output, ": ", absl::Hex(info.tval));
+      break;
+    case 13:
+      absl::StrAppend(&output, "Load page fault");
+      absl::StrAppend(&output, ": ", absl::Hex(info.tval));
+      break;
+    case 15:
+      absl::StrAppend(&output, "Store/AMO page fault");
+      absl::StrAppend(&output, ": ", absl::Hex(info.tval));
+      break;
+    case 0x1c: {
+      absl::StrAppend(&output, "CHERI exception");
+      switch (info.tval & 0x1f) {
+        case 0:
+          absl::StrAppend(&output, ": none??");
+          break;
+        case 1:
+          absl::StrAppend(&output, ": bounds violation");
+          break;
+        case 2:
+          absl::StrAppend(&output, ": tag violation");
+          break;
+        case 3:
+          absl::StrAppend(&output, ": seal violation");
+          break;
+        case 0x11:
+          absl::StrAppend(&output, ": PERMIT_EXECUTION violation");
+          break;
+        case 0x12:
+          absl::StrAppend(&output, ": PERMIT_LOAD violation");
+          break;
+        case 0x13:
+          absl::StrAppend(&output, ": PERMIT_STORE violation");
+          break;
+        case 0x15:
+          absl::StrAppend(&output, ": PERMIT_STORE_CAPABILITY violation");
+          break;
+        case 0x18:
+          absl::StrAppend(&output,
+                          ": PERMIT_ACCESS_SYSTEM_REGISTERS violation");
+          break;
+        default:
+          absl::StrAppend(&output, ": unknown cause");
+          break;
+      }
+      int cap_indx = (info.tval >> 5) & 0x1f;
+      if (cap_indx < 16)
+        absl::StrAppend(&output, " c", cap_indx);
+      else if (cap_indx == 28)
+        absl::StrAppend(&output, " mtcc");
+      else if (cap_indx == 29)
+        absl::StrAppend(&output, " mtdc");
+      else if (cap_indx == 30)
+        absl::StrAppend(&output, " mscratchc");
+      else if (cap_indx == 31)
+        absl::StrAppend(&output, " mepcc");
+      else
+        absl::StrAppend(&output, " unknown capability");
+      break;
+    }
+    default:
+      absl::StrAppend(&output, "Error - Unknown trap");
+      break;
+  }
+  absl::StrAppend(&output, "\n");
+  return output;
+}
+
 }  // namespace cheriot
 }  // namespace sim
 }  // namespace mpact
diff --git a/cheriot/debug_command_shell.h b/cheriot/debug_command_shell.h
index 0a3b218..71fc7b0 100644
--- a/cheriot/debug_command_shell.h
+++ b/cheriot/debug_command_shell.h
@@ -19,11 +19,11 @@
 
 #include <cstdint>
 #include <deque>
-#include <fstream>
 #include <iostream>
 #include <istream>
 #include <ostream>
 #include <string>
+#include <utility>
 #include <vector>
 
 #include "absl/container/btree_map.h"
@@ -32,7 +32,11 @@
 #include "absl/status/status.h"
 #include "absl/status/statusor.h"
 #include "absl/strings/string_view.h"
+#include "cheriot/cheriot_top.h"
+#include "mpact/sim/generic/core_debug_interface.h"
+#include "mpact/sim/generic/counters_base.h"
 #include "mpact/sim/generic/debug_command_shell_interface.h"
+#include "mpact/sim/generic/type_helpers.h"
 #include "re2/re2.h"
 
 namespace mpact::sim::generic {
@@ -43,14 +47,17 @@
 namespace sim {
 namespace cheriot {
 
+using ::mpact::sim::generic::CounterValueSetInterface;
 using ::mpact::sim::generic::DebugCommandShellInterface;
+using HaltReason = ::mpact::sim::generic::CoreDebugInterface::HaltReason;
+using ::mpact::sim::generic::operator*;  // NOLINT: used below (clang error).
 
 // This class implements an interactive command shell for a set of cores
 // simulated by the MPact simulator using the CoreDebugInterface.
 class DebugCommandShell : public DebugCommandShellInterface {
  public:
-  // Default constructor is deleted.
   DebugCommandShell();
+  ~DebugCommandShell() override;
 
   // Add core access to the system. All cores must be added before calling Run.
   void AddCore(const CoreAccess &core_access) override;
@@ -81,6 +88,67 @@
     bool is_enabled;
   };
 
+  // Struct to track interrupt/trap information.
+  struct InterruptInfo {
+    bool is_interrupt;
+    uint32_t cause;
+    uint32_t tval;
+    uint32_t epc;
+  };
+
+  // The interrupt listener class is used to track interrupts/exceptions and
+  // returns from interrupts/exceptions, so that breakpoints can be set on these
+  // events.
+  class InterruptListener {
+   public:
+    // Convenience class to provide listeners to the counters.
+    class Listener : public CounterValueSetInterface<int64_t> {
+     public:
+      explicit Listener(absl::AnyInvocable<void(int64_t)> callback)
+          : callback_(std::move(callback)) {}
+
+     private:
+      void SetValue(const int64_t &value) override { callback_(value); }
+      absl::AnyInvocable<void(int64_t)> callback_;
+    };
+
+    using InterruptInfoList = std::deque<InterruptInfo>;
+    static constexpr uint32_t kInterruptTaken =
+        *HaltReason::kUserSpecifiedMin + 1;
+    static constexpr uint32_t kInterruptReturn =
+        *HaltReason::kUserSpecifiedMin + 2;
+    static constexpr uint32_t kExceptionTaken =
+        *HaltReason::kUserSpecifiedMin + 3;
+    static constexpr uint32_t kExceptionReturn =
+        *HaltReason::kUserSpecifiedMin + 4;
+
+    explicit InterruptListener(CoreAccess *core_access);
+    void SetEnableExceptions(bool value) { exceptions_enabled_ = value; }
+    void SetEnableInterrupts(bool value) { interrupts_enabled_ = value; }
+    bool AreExceptionsEnabled() const { return exceptions_enabled_; }
+    bool AreInterruptsEnabled() const { return interrupts_enabled_; }
+
+    const InterruptInfoList &interrupt_info_list() const {
+      return interrupt_info_list_;
+    }
+
+   private:
+    void SetReturnValue(int64_t value);
+    void SetTakenValue(int64_t value);
+
+    CoreAccess *core_access_;
+    CheriotTop *top_;
+    bool interrupts_enabled_ = false;
+    bool exceptions_enabled_ = false;
+    InterruptInfoList interrupt_info_list_;
+    Listener taken_listener_;
+    Listener return_listener_;
+  };
+
+  // Helper method to get the interrupt description.
+  std::string GetInterruptDescription(const InterruptInfo &info);
+  std::string GetExceptionDescription(const InterruptInfo &info);
+
   // Helper method for formatting single data buffer value.
   std::string FormatSingleDbValue(generic::DataBuffer *db,
                                   const std::string &format, int width,
@@ -194,6 +262,7 @@
   std::deque<std::string> previous_commands_;
   std::vector<absl::btree_map<int, ActionPointInfo>> core_action_point_info_;
   std::vector<int> core_action_point_id_;
+  std::vector<InterruptListener *> interrupt_listeners_;
 };
 
 }  // namespace cheriot
diff --git a/cheriot/riscv_cheriot_priv_instructions.cc b/cheriot/riscv_cheriot_priv_instructions.cc
index e729c5d..ee2b29b 100644
--- a/cheriot/riscv_cheriot_priv_instructions.cc
+++ b/cheriot/riscv_cheriot_priv_instructions.cc
@@ -36,8 +36,8 @@
   // Set mstatus:mpie to 1.
   mstatus->set_mpie(1);
   mstatus->set_mpp(*PrivilegeMode::kMachine);
-  state->SignalReturnFromInterrupt();
   mstatus->Submit();
+  state->SignalReturnFromInterrupt();
 }
 
 void RiscVPrivWfi(const Instruction *inst) {