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_
#define _CORE__KERNEL__CORE_INTERFACE_H_
/* base includes */
#include <cpu/cpu_state.h>
/* base-internal includes */
#include <base/internal/native_utcb.h>
@ -31,6 +34,7 @@ namespace Kernel {
class Vm;
class User_irq;
using Native_utcb = Genode::Native_utcb;
using Cpu_state = Genode::Cpu_state;
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_delete_obj() { return 122; }
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`
@ -159,6 +167,42 @@ namespace Kernel {
{
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_ */

View File

@ -403,6 +403,7 @@ bool Thread::_restart()
{
assert(_state == ACTIVE || _state == AWAITS_RESTART);
if (_state != AWAITS_RESTART) { return false; }
_exception_state = NO_EXCEPTION;
_become_active();
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()
{
try {
@ -871,6 +900,10 @@ void Thread::_call()
case call_id_new_obj(): _call_new_obj(); return;
case call_id_delete_obj(): _call_delete_obj(); 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:
Genode::raw(*this, ": unknown kernel call");
_die();
@ -883,6 +916,7 @@ void Thread::_call()
void Thread::_mmu_exception()
{
_become_inactive(AWAITS_RESTART);
_exception_state = MMU_FAULT;
Cpu::mmu_fault(*regs, _fault);
_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,
Irq::Pool &user_irq_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 Exception_state { NO_EXCEPTION, MMU_FAULT, EXCEPTION };
private:
/*
@ -181,6 +183,7 @@ class Kernel::Thread : private Kernel::Object, public Cpu_job, private Timeout
bool _paused { false };
bool _cancel_next_await_signal { false };
Type const _type;
Exception_state _exception_state { NO_EXCEPTION };
Genode::Constructible<Tlb_invalidation> _tlb_invalidation {};
Genode::Constructible<Destroy> _destroy {};
@ -233,6 +236,11 @@ class Kernel::Thread : private Kernel::Object, public Cpu_job, private Timeout
*/
void _mmu_exception();
/**
* Handle a non-mmu exception
*/
void _exception();
/**
* 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_time();
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>
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; }
Genode::Native_utcb *utcb() { return _utcb; }
Type type() const { return _type; }
Exception_state exception_state() const { return _exception_state; }
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() { }

View File

@ -105,6 +105,12 @@ class Core::Pager_object : private Object_pool<Pager_object>::Entry,
Cpu_session_capability _cpu_session_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:
/**
@ -128,9 +134,24 @@ class Core::Pager_object : private Object_pool<Pager_object>::Entry,
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

View File

@ -210,15 +210,31 @@ Core::Pager_object &Platform_thread::pager()
Thread_state Platform_thread::state()
{
Thread_state bstate(*_kobj->regs);
return Thread_state(bstate);
Cpu_state cpu_state;
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)
{
Cpu_state * cstate = static_cast<Cpu_state *>(&*_kobj->regs);
*cstate = static_cast<Cpu_state>(thread_state);
Cpu_state cpu_state(thread_state);
Kernel::set_cpu_state(*_kobj, cpu_state);
}

View File

@ -124,6 +124,22 @@ class Core::Platform_thread : Noncopyable
*/
~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
*/
@ -158,12 +174,19 @@ class Core::Platform_thread : Noncopyable
/**
* Enable/disable single stepping
*/
void single_step(bool) { }
void single_step(bool on) { Kernel::single_step(*_kobj, on); }
/**
* 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'

View File

@ -43,6 +43,16 @@ void Pager_entrypoint::entry()
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();
/* 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
*/
static unsigned executing_id() { return 0; }
static void single_step(Context &, bool) { };
};
#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
{
uint64_t pstate { };
uint64_t mdscr_el1 { };
uint64_t exception_type { RESET };
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 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
*/

View File

@ -35,10 +35,12 @@
mrs x1, sp_el0
mrs x2, elr_el1
mrs x3, spsr_el1
adr x4, .
and x4, x4, #0xf80
mrs x4, mdscr_el1
adr x5, .
and x5, x5, #0xf80
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
.balign 128
.endr
@ -91,11 +93,12 @@ _kernel_entry:
mov sp, x1 /* reset stack */
str x0, [sp, #-16] /* store cpu state pointer */
add x1, x0, #8*31
ldp x2, x3, [x1], #16 /* load sp, ip */
ldr x4, [x1], #16 /* load pstate */
ldp x2, x3, [x1], #16+8 /* load sp, ip, skip ec */
ldp x4, x5, [x1], #16+8 /* load pstate, mdscr_el1, skip exception_type */
msr sp_el0, x2
msr elr_el1, x3
msr spsr_el1, x4
msr mdscr_el1, x5
ldp q0, q1, [x1], #32
ldp q2, q3, [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:
_mmu_exception();
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:
Genode::raw("Unknown cpu exception EC=", Cpu::Esr::Ec::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);
static void mmu_fault(Context & c, Kernel::Thread_fault & f);
static void single_step(Context &, bool) { };
static unsigned executing_id() { return 0; }
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);
}
}
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
{
enum Eflags {
EFLAGS_TF = 1 << 8,
EFLAGS_IF_SET = 1 << 9,
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 single_step(Context &regs, bool on);
/**
* Invalidate the whole TLB
*/

View File

@ -147,9 +147,19 @@
.align 8
__idt:
/* first 128 entries */
/* first 3 entries */
.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
.set isr_addr, isr_addr + ISR_ENTRY_SIZE
.endr

View File

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

View File

@ -84,6 +84,8 @@ struct Hw::Arm_64_cpu
INST_ABORT_SAME_LEVEL = 0b100001,
DATA_ABORT_LOW_LEVEL = 0b100100,
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_el2, mair_el2);
struct Mdscr : Genode::Register<64>
{
struct Ss : Bitfield<0, 1> {};
};
SYSTEM_REGISTER(64, Mpidr, mpidr_el1,
struct Aff0 : Bitfield<0, 8> {};
struct Aff1 : Bitfield<8, 8> {};
@ -185,6 +192,7 @@ struct Hw::Arm_64_cpu
struct I : Bitfield<7, 1> {};
struct A : Bitfield<8, 1> {};
struct D : Bitfield<9, 1> {};
struct Ss : Bitfield<21, 1> {};
};
SYSTEM_REGISTER(64, Spsr_el2, spsr_el2);

View File

@ -22,9 +22,15 @@ namespace Genode { struct 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 sp { 0 }; /* stack pointer */
addr_t ip { 0 }; /* instruction pointer */
addr_t ec { 0 }; /* exception class */
};
#endif /* _INCLUDE__SPEC__ARM_64__CPU__CPU_STATE_H_ */