Skip to content
Open
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
5ba535a
Add `read_panic_message` kipc
hawkw Dec 3, 2025
1313935
Update kipc.rs
hawkw Dec 3, 2025
07460ec
review feedback + tidiness
hawkw Dec 3, 2025
68a2fb7
CLIPPY DAMAGE
hawkw Dec 3, 2025
2ab7a43
Merge branch 'master' into eliza/read-panic-message
hawkw Dec 4, 2025
8a221c1
Merge branch 'master' into eliza/read-panic-message
hawkw Dec 18, 2025
425c221
add note on UTF-8 truncation
hawkw Dec 29, 2025
3748fda
Update kipc.adoc
hawkw Dec 30, 2025
09fe93f
Update kipc.adoc
hawkw Dec 30, 2025
e468ef7
Update main.rs
hawkw Dec 30, 2025
7b4a6bb
More of @cbiffle's docs suggestions
hawkw Dec 30, 2025
2d9b1d2
line wrapping, docs links
hawkw Jan 5, 2026
fb2af3f
improve docs/naming for invalid buffers
hawkw Jan 5, 2026
5193057
@cbiffle convinced me to return Utf8Chunks
hawkw Jan 5, 2026
1439da0
jefe: Add mechanism for notifying another task on task faults
hawkw Jan 5, 2026
9c08e49
ereportulator: explicit panic for testing
hawkw Jan 5, 2026
a4798c9
packrat: wire up task fault notification
hawkw Jan 5, 2026
384307a
packrat: draw most of the rest of the owl
hawkw Jan 5, 2026
e608474
packrat: finish ereports
hawkw Jan 5, 2026
596c8cd
you bastards, you blew it all up!
hawkw Jan 5, 2026
d5fde83
i am stupid and also dumb
hawkw Jan 5, 2026
d145efc
don't bind unused variable
hawkw Jan 5, 2026
f4e5713
properly(?) handle invalid utf8
hawkw Jan 5, 2026
bc6268d
pwease dead-code eliminate me 🥺🥺🥺
hawkw Jan 5, 2026
a9ef212
Merge branch 'master' into eliza/fault-ereport
hawkw Jan 6, 2026
d3ff2bb
embiggen oxcon g0 dongle
hawkw Jan 6, 2026
f7d6231
Revert "pwease dead-code eliminate me 🥺🥺🥺"
hawkw Jan 6, 2026
525fce9
timestamp is now dead code
hawkw Jan 6, 2026
b906b4e
more commentary
hawkw Jan 6, 2026
b405002
clippy tidiness
hawkw Jan 6, 2026
b31df43
Merge branch 'master' into eliza/fault-ereport
hawkw Jan 6, 2026
7980493
Merge branch 'master' into eliza/fault-ereport
hawkw Jan 6, 2026
3eeb290
don't skip faults when the task has been restarted
hawkw Jan 6, 2026
f031658
ensmallerate a couple imports
hawkw Jan 6, 2026
75538b7
clippy fix when ereports are turned off
hawkw Jan 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions app/cosmo/base.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ net = "jefe-state-change"
host_sp_comms = "jefe-state-change"
spd = "jefe-state-change"

[tasks.jefe.config.on-task-fault]
packrat = "task-faulted"

[tasks.jefe.config.allowed-callers]
set_state = ["cosmo_seq"]
set_reset_reason = ["sys"]
Expand Down Expand Up @@ -134,6 +137,7 @@ start = true
# task-slots is explicitly empty: packrat should not send IPCs!
task-slots = []
features = ["cosmo", "ereport"]
notifications = ["task-faulted"]

[tasks.rng_driver]
features = ["h753", "ereport"]
Expand Down
4 changes: 4 additions & 0 deletions app/gimlet/base.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ net = "jefe-state-change"
host_sp_comms = "jefe-state-change"
spd = "jefe-state-change"

[tasks.jefe.config.on-task-fault]
packrat = "task-faulted"

[tasks.jefe.config.allowed-callers]
set_state = ["gimlet_seq"]
set_reset_reason = ["sys"]
Expand Down Expand Up @@ -128,6 +131,7 @@ start = true
# task-slots is explicitly empty: packrat should not send IPCs!
task-slots = []
features = ["gimlet", "ereport"]
notifications = ["task-faulted"]

[tasks.thermal]
name = "task-thermal"
Expand Down
4 changes: 4 additions & 0 deletions app/gimletlet/app.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ request_reset = ["hiffy", "control_plane_agent", "udprpc"]
[tasks.jefe.config.on-state-change]
host_sp_comms = "jefe-state-change"

[tasks.jefe.config.on-task-fault]
packrat = "task-faulted"

[tasks.sys]
# Enable EXTI in the sys task so that we can notify sprot when the RoT
# raises an IRQ.
Expand Down Expand Up @@ -48,6 +51,7 @@ start = true
task-slots = []
stacksize = 1040
features = ["ereport"]
notifications = ["task-faulted"]

[tasks.control_plane_agent]
name = "task-control-plane-agent"
Expand Down
4 changes: 4 additions & 0 deletions app/grapefruit/app-dev.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ interrupts = {"uart8.irq" = "usart-irq"}
[tasks.packrat]
stacksize = 1040
features = ["ereport"]
notifications = ["task-faulted"]

[tasks.jefe.config.on-task-fault]
packrat = "task-faulted"

[tasks.snitch]
name = "task-snitch"
Expand Down
4 changes: 4 additions & 0 deletions app/psc/base.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ extern-regions = ["sram1", "sram2", "sram3", "sram4"]
[tasks.jefe.config.on-state-change]
net = "jefe-state-change"

[tasks.jefe.config.on-task-fault]
packrat = "task-faulted"

[tasks.jefe.config.allowed-callers]
set_reset_reason = ["sys"]
request_reset = ["hiffy", "control_plane_agent"]
Expand Down Expand Up @@ -124,6 +127,7 @@ start = true
# task-slots is explicitly empty: packrat should not send IPCs!
task-slots = []
features = ["ereport"]
notifications = ["task-faulted"]

[tasks.sequencer]
name = "drv-psc-seq-server"
Expand Down
4 changes: 4 additions & 0 deletions app/sidecar/base.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ extern-regions = ["sram1", "sram2", "sram3", "sram4"]
set_reset_reason = ["sys"]
request_reset = ["hiffy", "control_plane_agent"]

[tasks.jefe.config.on-task-fault]
packrat = "task-faulted"

[tasks.sys]
name = "drv-stm32xx-sys"
features = ["h753", "exti", "no-panic"]
Expand Down Expand Up @@ -263,6 +266,7 @@ start = true
# task-slots is explicitly empty: packrat should not send IPCs!
task-slots = []
features = ["ereport"]
notifications = ["task-faulted"]

[tasks.sequencer]
name = "drv-sidecar-seq-server"
Expand Down
51 changes: 50 additions & 1 deletion doc/kipc.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,7 @@ This interface is primarily intended for testing interrupt handling, and can
only be called by a task running as the supervisor. When this KIPC is called,
an actual machine interrupt is triggered for the relevant hardware interrupt
source. The kernel then responds to the interrupt using its normal
interrupt-handling infrastructure, which dispatches a notification to the
interrupt-handling infrastructure, which dispatches a notification to the
subscribed task.

This KIPC is *not* intended for use as a general-purpose inter-task signalling
Expand Down Expand Up @@ -402,6 +402,55 @@ while let Some(fault) = kipc::find_faulted_task(next_task) {
}
----

=== `read_panic_message` (10)

If the task with the requested index is in the faulted state due to a panic,
reads the contents of its panic message into the response buffer.

==== Request

[source,rust]
----
struct ReadPanicMessageRequest {
task_index: u32,
}
----

==== Preconditions

`task_index` must be a valid task index for this system.

==== Response

[source,rust]
----
Result<&[u8], abi::ReadPanicMessageError>
----

==== Notes

If the requested task is not currently in the faulted state due to a panic,
this KIPC returns the `abi::ReadPanicMessageError::TaskNotPanicked` response
code.

If the requested task has panicked, but the buffer containing its panic
message is invalid, this KIPC returns the
`abi::ReadPanicMessageError::BadPanicMessage` response code. If the target task
uses the panic handler provided by the `userlib` crate, this should not be
possible. However, it may occur if a task calls the `sys_panic` syscall
directly with invalid arguments, such as a slice pointing outside the task's memory.

The panic message is truncated to the length of the response buffer. Since
Hubris only stores the first 128 bytes of panic messages, a 128-byte response
buffer will normally not result in further truncation.

The panic message is normally UTF-8. However, because of this byte-based
truncation, and because the panic message is coming from a failing task in the
first place, be careful not to *assume* that the panic message is valid UTF-8.
See the
link:++https://doc.rust-lang.org/stable/std/primitive.slice.html#method.utf8_chunks++[`<[u8]>::utf8_chunks`]
function for one possible method of handling the panic message safely.

== Receiving from the kernel

The kernel never sends messages to tasks. It's simply not equipped to do so.
Expand Down
7 changes: 7 additions & 0 deletions idl/ereportulator.idol
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,12 @@ Interface(
),
idempotent: true,
),
"panicme": (
doc: "Make the ereportulator panic",
args: {
},
reply: Simple("()"),
idempotent: true,
),
}
)
34 changes: 34 additions & 0 deletions sys/abi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -512,6 +512,7 @@ pub enum Kipcnum {
ReadTaskDumpRegion = 7,
SoftwareIrq = 8,
FindFaultedTask = 9,
ReadPanicMessage = 10,
}

impl core::convert::TryFrom<u16> for Kipcnum {
Expand All @@ -528,6 +529,7 @@ impl core::convert::TryFrom<u16> for Kipcnum {
7 => Ok(Self::ReadTaskDumpRegion),
8 => Ok(Self::SoftwareIrq),
9 => Ok(Self::FindFaultedTask),
10 => Ok(Self::ReadPanicMessage),
_ => Err(()),
}
}
Expand Down Expand Up @@ -582,3 +584,35 @@ bitflags::bitflags! {
const CLEAR_PENDING = 1 << 1;
}
}

/// Errors returned by [`Kipcnum::ReadPanicMessage`].
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[repr(u32)]
pub enum ReadPanicMessageError {
/// The task in question has not panicked.
TaskNotPanicked = 1,
/// The task has panicked, but its panic message buffer is invalid, so the
/// kernel has not let us have it.
///
/// In practice, this is quite unlikely, and would require the task to have
/// panicked with a panic message slice of a length that exceeds the end of
/// the address space. Panicking via the Hubris userlib will never do this.
/// But, since the panicked task could be any arbitrary binary...anything is
/// possible.
BadPanicBuffer = 2,
}

/// We're using an explicit `TryFrom` impl for `ReadPanicMessageError` instead of
/// `FromPrimitive` because the kernel doesn't currently depend on `num-traits`
/// and this seems okay.
impl core::convert::TryFrom<u32> for ReadPanicMessageError {
type Error = ();

fn try_from(x: u32) -> Result<Self, Self::Error> {
match x {
1 => Ok(Self::TaskNotPanicked),
2 => Ok(Self::BadPanicBuffer),
_ => Err(()),
}
}
}
83 changes: 76 additions & 7 deletions sys/kern/src/kipc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use unwrap_lite::UnwrapLite;
use crate::arch;
use crate::err::UserError;
use crate::task::{current_id, ArchState, NextTask, Task};
use crate::umem::USlice;
use crate::umem::{safe_copy, USlice};

/// Message dispatcher.
pub fn handle_kernel_message(
Expand Down Expand Up @@ -43,6 +43,9 @@ pub fn handle_kernel_message(
Ok(Kipcnum::FindFaultedTask) => {
find_faulted_task(tasks, caller, args.message?, args.response?)
}
Ok(Kipcnum::ReadPanicMessage) => {
read_panic_message(tasks, caller, args.message?, args.response?)
}

_ => {
// Task has sent an unknown message to the kernel. That's bad.
Expand Down Expand Up @@ -479,12 +482,6 @@ fn find_faulted_task(
message: USlice<u8>,
response: USlice<u8>,
) -> Result<NextTask, UserError> {
if caller != 0 {
return Err(UserError::Unrecoverable(FaultInfo::SyscallUsage(
UsageError::NotSupervisor,
)));
}

let index = deserialize_message::<u32>(&tasks[caller], message)? as usize;

// Note: we explicitly permit index == tasks.len(), which causes us to wrap
Expand All @@ -507,3 +504,75 @@ fn find_faulted_task(
.set_send_response_and_length(0, response_len);
Ok(NextTask::Same)
}

fn read_panic_message(
tasks: &mut [Task],
caller: usize,
message: USlice<u8>,
response: USlice<u8>,
) -> Result<NextTask, UserError> {
let index: u32 = deserialize_message(&tasks[caller], message)?;
let index = index as usize;
let Some(task) = tasks.get(index) else {
return Err(UserError::Unrecoverable(FaultInfo::SyscallUsage(
UsageError::TaskOutOfRange,
)));
};

// Make sure the task is actually panicked.
let TaskState::Faulted {
fault: FaultInfo::Panic,
..
} = task.state()
else {
return Err(UserError::Recoverable(
abi::ReadPanicMessageError::TaskNotPanicked as u32,
NextTask::Same,
));
};

let Ok(message) = task.save().as_panic_args().message else {
// There's really only one reason that `as_panic_args().message` would
// be an error. Because it's just a `USlice<u8>`, it can't be
// misaligned, so the only possible invalid slice here is one whose
// length exceeds the size of the address space, so that `base + len`
// would overflow.
//
// But, we shouldn't fault the *caller* over that; they didn't do it!
return Err(UserError::Recoverable(
abi::ReadPanicMessageError::BadPanicBuffer as u32,
NextTask::Same,
));
};

// Note that if the panic was recorded by `userlib`'s panic handler, it will
// never exceed 128 bytes in length, and if the caller requested this kipc
// using the `userlib::ipc::read_panic_message()` wrapper, then the caller's
// buffer will always be exactly 128 bytes long. However, we can't rely on
// that here, as either task *could* be an arbitrary binary that wasn't
// compiled with the Hubris userlib, so we need to be safe regardless.
match safe_copy(tasks, index, message, caller, response) {
Ok(len) => {
// Ladies and gentlemen...we got him!
tasks[caller]
.save_mut()
.set_send_response_and_length(0, len);

Ok(NextTask::Same)
}
Err(crate::err::InteractFault {
dst: Some(fault), ..
}) => {
// If the caller's buffer was invalid, they take a fault.
Err(UserError::Unrecoverable(fault))
}
Err(_) => {
// Source region was bad, but it's not the caller's fault; give them
// a recoverable error.
Err(UserError::Recoverable(
abi::ReadPanicMessageError::BadPanicBuffer as u32,
NextTask::Same,
))
}
}
}
1 change: 1 addition & 0 deletions sys/num-tasks/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {

if build_util::has_feature("task-enum") {
writeln!(task_file, "#[allow(non_camel_case_types)]").unwrap();
writeln!(task_file, "#[derive(Copy, Clone)]").unwrap();
writeln!(task_file, "pub enum Task {{").unwrap();
for line in task_enum {
writeln!(task_file, "{line}").unwrap();
Expand Down
Loading
Loading