blob: d26dceb267a87bb879297a0c16e03cbe9ea5c9d6 [file] [log] [blame]
#include "compartment.h"
#include <cheri.hh>
#include <debug.hh>
#include <priv/riscv.h>
#include <simulator.h>
using DebugErrorHandler = ConditionalDebug<
#ifdef NDEBUG
false
#else
true
#endif
,
"Error handler">;
/**
* A error handler that reports unexpected traps and calls the scheduler to
* request that the simulator exit with exit code 1 (FAILURE).
*
* This is useful because the default behaviour in the absence of an error
* handler is to unwind the trusted stack and exit the thread when the top of
* the stack is reached. If all threads exit then the scheduler will exit the
* simulation with SUCCESS, which might mask errors.
*
* We attempt to detect the trap that occurs when a thread returns from its
* entry point function and do not halt the simulation as this is considered
* normal.
*
* Unwinds caused by errors in called compartments are also detected and are not
* treated as errors -- instead we resume execution at the call point with a
* return value of -1 as though this handler is not present.
*
* On non-simulation platforms this error handler reverts to the default force
* unwind behaviour.
*
* If NDEBUG is not defined then errors and thread exits will be logged.
*
* To use this simply include this file in your compartment's c / cc file.
*/
extern "C" ErrorRecoveryBehaviour
compartment_error_handler(ErrorState *frame, size_t mcause, size_t mtval)
{
if (mcause == priv::MCAUSE_CHERI)
{
auto [exceptionCode, registerNumber] =
CHERI::extract_cheri_mtval(mtval);
// The thread entry point is called with a NULL return address so the
// cret at the end of the entry point function will trap if it is
// reached. We don't want to treat this as an error but thankfully we
// detect it quite specifically by checking for all of:
// 1) CHERI cause is tag violation
// 2) faulting register is CRA
// 3) value of CRA is NULL
// 4) we've reached the top of the thread's stack
CHERI::Capability stackCapability{
frame->get_register_value<CHERI::RegisterNumber::CSP>()};
CHERI::Capability returnCapability{
frame->get_register_value<CHERI::RegisterNumber::CRA>()};
// The top of the stack is 16 bytes above the stack pointer on entry,
// to provide space for unwind lists and so on.
if (registerNumber == CHERI::RegisterNumber::CRA &&
returnCapability.address() == 0 &&
exceptionCode == CHERI::CauseCode::TagViolation &&
(stackCapability.top() - 16) == stackCapability.address())
{
// looks like thread exit -- just log it then ForceUnwind
DebugErrorHandler::log(
"Thread exit CSP={}, PCC={}", stackCapability, frame->pcc);
}
else
{
// An unexpected error -- log it and end the simulation
// with error. Note: handle CZR differently as
// `get_register_value` will return a nullptr which we
// cannot dereference.
DebugErrorHandler::log(
"{} error at {} (return address: {}), with capability register "
"{}: {}",
exceptionCode,
frame->pcc,
frame->get_register_value<CHERI::RegisterNumber::CRA>(),
registerNumber,
registerNumber == CHERI::RegisterNumber::CZR
? nullptr
: *frame->get_register_value(registerNumber));
simulation_exit(1);
}
}
else
{
// other error (e.g. __builtin_trap causes ReservedInstruciton)
// log and end simulation with error.
DebugErrorHandler::log("Unhandled error {} at {}", mcause, frame->pcc);
simulation_exit(1);
}
return ErrorRecoveryBehaviour::ForceUnwind;
}