hw: send exception signals and support single-stepping

Fixes #4975
This commit is contained in:
Christian Prochaska 2023-08-08 07:20:32 +02:00 committed by Christian Helmuth
parent f3b03fa01b
commit 8b7f959451
19 changed files with 266 additions and 31 deletions

View File

@ -14,6 +14,9 @@
#ifndef _CORE__KERNEL__CORE_INTERFACE_H_ #ifndef _CORE__KERNEL__CORE_INTERFACE_H_
#define _CORE__KERNEL__CORE_INTERFACE_H_ #define _CORE__KERNEL__CORE_INTERFACE_H_
/* base includes */
#include <cpu/cpu_state.h>
/* base-internal includes */ /* base-internal includes */
#include <base/internal/native_utcb.h> #include <base/internal/native_utcb.h>
@ -31,6 +34,7 @@ namespace Kernel {
class Vm; class Vm;
class User_irq; class User_irq;
using Native_utcb = Genode::Native_utcb; using Native_utcb = Genode::Native_utcb;
using Cpu_state = Genode::Cpu_state;
template <typename T> class Core_object_identity; template <typename T> class Core_object_identity;
/** /**
@ -58,6 +62,10 @@ namespace Kernel {
constexpr Call_arg call_id_new_obj() { return 121; } constexpr Call_arg call_id_new_obj() { return 121; }
constexpr Call_arg call_id_delete_obj() { return 122; } constexpr Call_arg call_id_delete_obj() { return 122; }
constexpr Call_arg call_id_new_core_thread() { return 123; } constexpr Call_arg call_id_new_core_thread() { return 123; }
constexpr Call_arg call_id_get_cpu_state() { return 124; }
constexpr Call_arg call_id_set_cpu_state() { return 125; }
constexpr Call_arg call_id_exception_state() { return 126; }
constexpr Call_arg call_id_single_step() { return 127; }
/** /**
* Invalidate TLB entries for the `pd` in region `addr`, `sz` * Invalidate TLB entries for the `pd` in region `addr`, `sz`
@ -159,6 +167,42 @@ namespace Kernel {
{ {
call(call_id_ack_irq(), (Call_arg) &irq); call(call_id_ack_irq(), (Call_arg) &irq);
} }
/**
* Get CPU state
*
* \param thread pointer to thread kernel object
* \param thread_state pointer to result CPU state object
*/
inline void get_cpu_state(Thread & thread, Cpu_state & cpu_state)
{
call(call_id_get_cpu_state(), (Call_arg)&thread, (Call_arg)&cpu_state);
}
/**
* Set CPU state
*
* \param thread pointer to thread kernel object
* \param thread_state pointer to CPU state object
*/
inline void set_cpu_state(Thread & thread, Cpu_state & cpu_state)
{
call(call_id_set_cpu_state(), (Call_arg)&thread, (Call_arg)&cpu_state);
}
/**
* Enable/disable single-stepping
*
* \param thread pointer to thread kernel object
* \param on enable or disable
*/
inline void single_step(Thread & thread, bool & on)
{
call(call_id_single_step(), (Call_arg)&thread, (Call_arg)&on);
}
} }
#endif /* _CORE__KERNEL__CORE_INTERFACE_H_ */ #endif /* _CORE__KERNEL__CORE_INTERFACE_H_ */

View File

@ -403,6 +403,7 @@ bool Thread::_restart()
{ {
assert(_state == ACTIVE || _state == AWAITS_RESTART); assert(_state == ACTIVE || _state == AWAITS_RESTART);
if (_state != AWAITS_RESTART) { return false; } if (_state != AWAITS_RESTART) { return false; }
_exception_state = NO_EXCEPTION;
_become_active(); _become_active();
return true; return true;
} }
@ -793,6 +794,34 @@ void Kernel::Thread::_call_invalidate_tlb()
} }
void Thread::_call_get_cpu_state() {
Thread &thread = *(Thread*)user_arg_1();
Cpu_state &cpu_state = *(Cpu_state*)user_arg_2();
cpu_state = *thread.regs;
}
void Thread::_call_set_cpu_state() {
Thread &thread = *(Thread*)user_arg_1();
Cpu_state &cpu_state = *(Cpu_state*)user_arg_2();
static_cast<Cpu_state&>(*thread.regs) = cpu_state;
}
void Thread::_call_exception_state() {
Thread &thread = *(Thread*)user_arg_1();
Exception_state &exception_state = *(Exception_state*)user_arg_2();
exception_state = thread.exception_state();
}
void Thread::_call_single_step() {
Thread &thread = *(Thread*)user_arg_1();
bool on = *(bool*)user_arg_2();
Cpu::single_step(*thread.regs, on);
}
void Thread::_call() void Thread::_call()
{ {
try { try {
@ -871,6 +900,10 @@ void Thread::_call()
case call_id_new_obj(): _call_new_obj(); return; case call_id_new_obj(): _call_new_obj(); return;
case call_id_delete_obj(): _call_delete_obj(); return; case call_id_delete_obj(): _call_delete_obj(); return;
case call_id_suspend(): _call_suspend(); return; case call_id_suspend(): _call_suspend(); return;
case call_id_get_cpu_state(): _call_get_cpu_state(); return;
case call_id_set_cpu_state(): _call_set_cpu_state(); return;
case call_id_exception_state(): _call_exception_state(); return;
case call_id_single_step(): _call_single_step(); return;
default: default:
Genode::raw(*this, ": unknown kernel call"); Genode::raw(*this, ": unknown kernel call");
_die(); _die();
@ -883,6 +916,7 @@ void Thread::_call()
void Thread::_mmu_exception() void Thread::_mmu_exception()
{ {
_become_inactive(AWAITS_RESTART); _become_inactive(AWAITS_RESTART);
_exception_state = MMU_FAULT;
Cpu::mmu_fault(*regs, _fault); Cpu::mmu_fault(*regs, _fault);
_fault.ip = regs->ip; _fault.ip = regs->ip;
@ -901,6 +935,25 @@ void Thread::_mmu_exception()
} }
void Thread::_exception()
{
_become_inactive(AWAITS_RESTART);
_exception_state = EXCEPTION;
if (_type != USER) {
Genode::raw(*this, " raised an exception, which should never happen");
_die();
}
if (_pager && _pager->can_submit(1)) {
_pager->submit(1);
} else {
Genode::raw(*this, " could not send signal to pager on exception");
_die();
}
}
Thread::Thread(Board::Address_space_id_allocator &addr_space_id_alloc, Thread::Thread(Board::Address_space_id_allocator &addr_space_id_alloc,
Irq::Pool &user_irq_pool, Irq::Pool &user_irq_pool,
Cpu_pool &cpu_pool, Cpu_pool &cpu_pool,

View File

@ -59,6 +59,8 @@ class Kernel::Thread : private Kernel::Object, public Cpu_job, private Timeout
enum Type { USER, CORE, IDLE }; enum Type { USER, CORE, IDLE };
enum Exception_state { NO_EXCEPTION, MMU_FAULT, EXCEPTION };
private: private:
/* /*
@ -181,6 +183,7 @@ class Kernel::Thread : private Kernel::Object, public Cpu_job, private Timeout
bool _paused { false }; bool _paused { false };
bool _cancel_next_await_signal { false }; bool _cancel_next_await_signal { false };
Type const _type; Type const _type;
Exception_state _exception_state { NO_EXCEPTION };
Genode::Constructible<Tlb_invalidation> _tlb_invalidation {}; Genode::Constructible<Tlb_invalidation> _tlb_invalidation {};
Genode::Constructible<Destroy> _destroy {}; Genode::Constructible<Destroy> _destroy {};
@ -233,6 +236,11 @@ class Kernel::Thread : private Kernel::Object, public Cpu_job, private Timeout
*/ */
void _mmu_exception(); void _mmu_exception();
/**
* Handle a non-mmu exception
*/
void _exception();
/** /**
* Handle kernel-call request of the thread * Handle kernel-call request of the thread
*/ */
@ -294,6 +302,10 @@ class Kernel::Thread : private Kernel::Object, public Cpu_job, private Timeout
void _call_timeout_max_us(); void _call_timeout_max_us();
void _call_time(); void _call_time();
void _call_suspend(); void _call_suspend();
void _call_get_cpu_state();
void _call_set_cpu_state();
void _call_exception_state();
void _call_single_step();
template <typename T, typename... ARGS> template <typename T, typename... ARGS>
void _call_new(ARGS &&... args) void _call_new(ARGS &&... args)
@ -468,6 +480,7 @@ class Kernel::Thread : private Kernel::Object, public Cpu_job, private Timeout
Thread_fault fault() const { return _fault; } Thread_fault fault() const { return _fault; }
Genode::Native_utcb *utcb() { return _utcb; } Genode::Native_utcb *utcb() { return _utcb; }
Type type() const { return _type; } Type type() const { return _type; }
Exception_state exception_state() const { return _exception_state; }
Pd &pd() const Pd &pd() const
{ {

View File

@ -61,9 +61,6 @@ void Pager_object::start_paging(Kernel_object<Kernel::Signal_receiver> & receive
} }
void Pager_object::exception_handler(Signal_context_capability) { }
void Pager_object::unresolved_page_fault_occurred() { } void Pager_object::unresolved_page_fault_occurred() { }

View File

@ -105,6 +105,12 @@ class Core::Pager_object : private Object_pool<Pager_object>::Entry,
Cpu_session_capability _cpu_session_cap; Cpu_session_capability _cpu_session_cap;
Thread_capability _thread_cap; Thread_capability _thread_cap;
/**
* User-level signal handler registered for this pager object via
* 'Cpu_session::exception_handler()'.
*/
Signal_context_capability _exception_sigh { };
public: public:
/** /**
@ -128,9 +134,24 @@ class Core::Pager_object : private Object_pool<Pager_object>::Entry,
void wake_up(); void wake_up();
/** /**
* Unnecessary as base-hw doesn't use exception handlers * Assign user-level exception handler for the pager object
*/ */
void exception_handler(Signal_context_capability); void exception_handler(Signal_context_capability sigh)
{
_exception_sigh = sigh;
}
/**
* Notify exception handler about the occurrence of an exception
*/
bool submit_exception_signal()
{
if (!_exception_sigh.valid()) return false;
Signal_transmitter transmitter(_exception_sigh);
transmitter.submit();
return true;
}
/** /**
* Install information that is necessary to handle page faults * Install information that is necessary to handle page faults

View File

@ -210,15 +210,31 @@ Core::Pager_object &Platform_thread::pager()
Thread_state Platform_thread::state() Thread_state Platform_thread::state()
{ {
Thread_state bstate(*_kobj->regs); Cpu_state cpu_state;
return Thread_state(bstate); Kernel::get_cpu_state(*_kobj, cpu_state);
Thread_state state(cpu_state);
using Exception_state = Kernel::Thread::Exception_state;
switch (exception_state()) {
case Exception_state::NO_EXCEPTION:
break;
case Exception_state::MMU_FAULT:
state.unresolved_page_fault = true;
break;
case Exception_state::EXCEPTION:
state.exception = true;
break;
}
return state;
} }
void Platform_thread::state(Thread_state thread_state) void Platform_thread::state(Thread_state thread_state)
{ {
Cpu_state * cstate = static_cast<Cpu_state *>(&*_kobj->regs); Cpu_state cpu_state(thread_state);
*cstate = static_cast<Cpu_state>(thread_state); Kernel::set_cpu_state(*_kobj, cpu_state);
} }

View File

@ -124,6 +124,22 @@ class Core::Platform_thread : Noncopyable
*/ */
~Platform_thread(); ~Platform_thread();
/**
* Return information about current exception state
*
* This syscall wrapper is located here and not in
* 'core_interface.h' because the 'Thread::Exception_state'
* type is not known there.
*/
Kernel::Thread::Exception_state exception_state()
{
Kernel::Thread::Exception_state exception_state;
using namespace Kernel;
call(call_id_exception_state(), (Call_arg)&*_kobj,
(Call_arg)&exception_state);
return exception_state;
}
/** /**
* Return information about current fault * Return information about current fault
*/ */
@ -158,12 +174,19 @@ class Core::Platform_thread : Noncopyable
/** /**
* Enable/disable single stepping * Enable/disable single stepping
*/ */
void single_step(bool) { } void single_step(bool on) { Kernel::single_step(*_kobj, on); }
/** /**
* Resume this thread * Resume this thread
*/ */
void resume() { Kernel::resume_thread(*_kobj); } void resume()
{
if (exception_state() !=
Kernel::Thread::Exception_state::NO_EXCEPTION)
restart();
Kernel::resume_thread(*_kobj);
}
/** /**
* Set CPU quota of the thread to 'quota' * Set CPU quota of the thread to 'quota'

View File

@ -43,6 +43,16 @@ void Pager_entrypoint::entry()
continue; continue;
} }
if (pt->exception_state() ==
Kernel::Thread::Exception_state::EXCEPTION) {
if (!po->submit_exception_signal())
warning("unresolvable exception: "
"pd='", pt->pd()->label(), "', "
"thread='", pt->label(), "', "
"ip=", Hex(pt->state().ip));
continue;
}
_fault = pt->fault_info(); _fault = pt->fault_info();
/* try to resolve fault directly via local region managers */ /* try to resolve fault directly via local region managers */

View File

@ -120,6 +120,8 @@ struct Core::Arm_cpu : public Hw::Arm_cpu
* Return kernel name of the executing CPU * Return kernel name of the executing CPU
*/ */
static unsigned executing_id() { return 0; } static unsigned executing_id() { return 0; }
static void single_step(Context &, bool) { };
}; };
#endif /* _CORE__SPEC__ARM__CPU_SUPPORT_H_ */ #endif /* _CORE__SPEC__ARM__CPU_SUPPORT_H_ */

View File

@ -75,6 +75,7 @@ struct Core::Cpu : Hw::Arm_64_cpu
struct alignas(16) Context : Cpu_state struct alignas(16) Context : Cpu_state
{ {
uint64_t pstate { }; uint64_t pstate { };
uint64_t mdscr_el1 { };
uint64_t exception_type { RESET }; uint64_t exception_type { RESET };
Fpu_state fpu_state { }; Fpu_state fpu_state { };
@ -104,6 +105,12 @@ struct Core::Cpu : Hw::Arm_64_cpu
static void mmu_fault(Context &, Kernel::Thread_fault &); static void mmu_fault(Context &, Kernel::Thread_fault &);
static void single_step(Context &regs, bool on)
{
Cpu::Spsr::Ss::set(regs.pstate, on ? 1 : 0);
Cpu::Mdscr::Ss::set(regs.mdscr_el1, on ? 1 : 0);
};
/** /**
* Return kernel name of the executing CPU * Return kernel name of the executing CPU
*/ */

View File

@ -35,10 +35,12 @@
mrs x1, sp_el0 mrs x1, sp_el0
mrs x2, elr_el1 mrs x2, elr_el1
mrs x3, spsr_el1 mrs x3, spsr_el1
adr x4, . mrs x4, mdscr_el1
and x4, x4, #0xf80 adr x5, .
and x5, x5, #0xf80
stp x1, x2, [x0], #16 stp x1, x2, [x0], #16
stp x3, x4, [x0], #16 stp xzr, x3, [x0], #16 /* ec will be updated later if needed */
stp x4, x5, [x0], #16
b _kernel_entry b _kernel_entry
.balign 128 .balign 128
.endr .endr
@ -91,11 +93,12 @@ _kernel_entry:
mov sp, x1 /* reset stack */ mov sp, x1 /* reset stack */
str x0, [sp, #-16] /* store cpu state pointer */ str x0, [sp, #-16] /* store cpu state pointer */
add x1, x0, #8*31 add x1, x0, #8*31
ldp x2, x3, [x1], #16 /* load sp, ip */ ldp x2, x3, [x1], #16+8 /* load sp, ip, skip ec */
ldr x4, [x1], #16 /* load pstate */ ldp x4, x5, [x1], #16+8 /* load pstate, mdscr_el1, skip exception_type */
msr sp_el0, x2 msr sp_el0, x2
msr elr_el1, x3 msr elr_el1, x3
msr spsr_el1, x4 msr spsr_el1, x4
msr mdscr_el1, x5
ldp q0, q1, [x1], #32 ldp q0, q1, [x1], #32
ldp q2, q3, [x1], #32 ldp q2, q3, [x1], #32
ldp q4, q5, [x1], #32 ldp q4, q5, [x1], #32

View File

@ -53,6 +53,11 @@ void Thread::exception(Cpu & cpu)
case Cpu::Esr::Ec::DATA_ABORT_LOW_LEVEL: case Cpu::Esr::Ec::DATA_ABORT_LOW_LEVEL:
_mmu_exception(); _mmu_exception();
return; return;
case Cpu::Esr::Ec::SOFTWARE_STEP_LOW_LEVEL: [[fallthrough]];
case Cpu::Esr::Ec::BRK:
regs->ec = Cpu::Esr::Ec::get(esr);
_exception();
return;
default: default:
Genode::raw("Unknown cpu exception EC=", Cpu::Esr::Ec::get(esr), Genode::raw("Unknown cpu exception EC=", Cpu::Esr::Ec::get(esr),
" ISS=", Cpu::Esr::Iss::get(esr), " ISS=", Cpu::Esr::Iss::get(esr),

View File

@ -101,6 +101,8 @@ class Core::Cpu : public Hw::Riscv_cpu
void switch_to(Mmu_context & context); void switch_to(Mmu_context & context);
static void mmu_fault(Context & c, Kernel::Thread_fault & f); static void mmu_fault(Context & c, Kernel::Thread_fault & f);
static void single_step(Context &, bool) { };
static unsigned executing_id() { return 0; } static unsigned executing_id() { return 0; }
static void clear_memory_region(addr_t const addr, static void clear_memory_region(addr_t const addr,

View File

@ -158,3 +158,12 @@ void Cpu::clear_memory_region(addr_t const addr, size_t const size, bool)
memset((void*)addr, 0, size); memset((void*)addr, 0, size);
} }
} }
void Cpu::single_step(Context &regs, bool on)
{
if (on)
regs.eflags |= Context::Eflags::EFLAGS_TF;
else
regs.eflags &= ~Context::Eflags::EFLAGS_TF;
}

View File

@ -101,6 +101,7 @@ class Core::Cpu : public Hw::X86_64_cpu
struct alignas(16) Context : Cpu_state, Kernel_stack, Fpu_context struct alignas(16) Context : Cpu_state, Kernel_stack, Fpu_context
{ {
enum Eflags { enum Eflags {
EFLAGS_TF = 1 << 8,
EFLAGS_IF_SET = 1 << 9, EFLAGS_IF_SET = 1 << 9,
EFLAGS_IOPL_3 = 3 << 12, EFLAGS_IOPL_3 = 3 << 12,
}; };
@ -134,6 +135,8 @@ class Core::Cpu : public Hw::X86_64_cpu
static void mmu_fault(Context & regs, Kernel::Thread_fault & fault); static void mmu_fault(Context & regs, Kernel::Thread_fault & fault);
static void single_step(Context &regs, bool on);
/** /**
* Invalidate the whole TLB * Invalidate the whole TLB
*/ */

View File

@ -147,9 +147,19 @@
.align 8 .align 8
__idt: __idt:
/* first 128 entries */ /* first 3 entries */
.set isr_addr, ISR .set isr_addr, ISR
.rept 0x80 .rept 3
_idt_entry isr_addr IDT_FLAGS_PRIVILEGED
.set isr_addr, isr_addr + ISR_ENTRY_SIZE
.endr
/* int3 */
_idt_entry isr_addr IDT_FLAGS_UNPRIVILEGED
.set isr_addr, isr_addr + ISR_ENTRY_SIZE
/* entries 4-127 */
.rept 124
_idt_entry isr_addr IDT_FLAGS_PRIVILEGED _idt_entry isr_addr IDT_FLAGS_PRIVILEGED
.set isr_addr, isr_addr + ISR_ENTRY_SIZE .set isr_addr, isr_addr + ISR_ENTRY_SIZE
.endr .endr

View File

@ -30,9 +30,12 @@ void Thread::exception(Cpu & cpu)
_mmu_exception(); _mmu_exception();
return; return;
case Cpu_state::DIVIDE_ERROR:
case Cpu_state::DEBUG:
case Cpu_state::BREAKPOINT:
case Cpu_state::UNDEFINED_INSTRUCTION: case Cpu_state::UNDEFINED_INSTRUCTION:
Genode::raw(*this, ": undefined instruction at ip=", (void*)regs->ip); case Cpu_state::GENERAL_PROTECTION:
_die(); _exception();
return; return;
case Cpu_state::SUPERVISOR_CALL: case Cpu_state::SUPERVISOR_CALL:

View File

@ -84,6 +84,8 @@ struct Hw::Arm_64_cpu
INST_ABORT_SAME_LEVEL = 0b100001, INST_ABORT_SAME_LEVEL = 0b100001,
DATA_ABORT_LOW_LEVEL = 0b100100, DATA_ABORT_LOW_LEVEL = 0b100100,
DATA_ABORT_SAME_LEVEL = 0b100101, DATA_ABORT_SAME_LEVEL = 0b100101,
SOFTWARE_STEP_LOW_LEVEL = 0b110010,
BRK = 0b111100,
}; };
}; };
@ -144,6 +146,11 @@ struct Hw::Arm_64_cpu
SYSTEM_REGISTER(64, Mair_el1, mair_el1); SYSTEM_REGISTER(64, Mair_el1, mair_el1);
SYSTEM_REGISTER(64, Mair_el2, mair_el2); SYSTEM_REGISTER(64, Mair_el2, mair_el2);
struct Mdscr : Genode::Register<64>
{
struct Ss : Bitfield<0, 1> {};
};
SYSTEM_REGISTER(64, Mpidr, mpidr_el1, SYSTEM_REGISTER(64, Mpidr, mpidr_el1,
struct Aff0 : Bitfield<0, 8> {}; struct Aff0 : Bitfield<0, 8> {};
struct Aff1 : Bitfield<8, 8> {}; struct Aff1 : Bitfield<8, 8> {};
@ -185,6 +192,7 @@ struct Hw::Arm_64_cpu
struct I : Bitfield<7, 1> {}; struct I : Bitfield<7, 1> {};
struct A : Bitfield<8, 1> {}; struct A : Bitfield<8, 1> {};
struct D : Bitfield<9, 1> {}; struct D : Bitfield<9, 1> {};
struct Ss : Bitfield<21, 1> {};
}; };
SYSTEM_REGISTER(64, Spsr_el2, spsr_el2); SYSTEM_REGISTER(64, Spsr_el2, spsr_el2);

View File

@ -22,9 +22,15 @@ namespace Genode { struct Cpu_state; }
struct Genode::Cpu_state struct Genode::Cpu_state
{ {
enum Cpu_exception {
SOFTWARE_STEP = 0x32,
BREAKPOINT = 0x3c,
};
addr_t r[31] { 0 }; /* general purpose register 0...30 */ addr_t r[31] { 0 }; /* general purpose register 0...30 */
addr_t sp { 0 }; /* stack pointer */ addr_t sp { 0 }; /* stack pointer */
addr_t ip { 0 }; /* instruction pointer */ addr_t ip { 0 }; /* instruction pointer */
addr_t ec { 0 }; /* exception class */
}; };
#endif /* _INCLUDE__SPEC__ARM_64__CPU__CPU_STATE_H_ */ #endif /* _INCLUDE__SPEC__ARM_64__CPU__CPU_STATE_H_ */