Skip to content

Commit c4ca702

Browse files
committed
Add read_panic_message kipc
Currently, there is no way to programmatically access the panic message of a task which has faulted due to a Rust panic fron within the Hubris userspace. This branch adds a new `read_panic_message` kipc that copies the contents of a panicked task's panic message buffer into the caller. If the requested task has not panicked, this kipc returns an error indicating this. This is intended by use by supervisor implementations or other tasks which wish to report panic messages from userspace. I've also added a test case that exercises this functionality. Fixes #2311
1 parent c75dfa8 commit c4ca702

File tree

7 files changed

+203
-3
lines changed

7 files changed

+203
-3
lines changed

doc/kipc.adoc

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,47 @@ while let Some(fault) = kipc::find_faulted_task(next_task) {
402402
}
403403
----
404404

405+
=== `read_panic_message` (10)
406+
407+
If the task with the requested index is in the faulted state due to a panic,
408+
reads the contents of its panic message into the response buffer.
409+
410+
==== Request
411+
412+
[source,rust]
413+
----
414+
struct ReadPanicMessageRequest {
415+
task_index: u32,
416+
}
417+
----
418+
419+
==== Preconditions
420+
421+
`task_index` must be a valid task index for this system.
422+
423+
==== Response
424+
425+
[source,rust]
426+
----
427+
Result<&str, abi::ReadPanicMessageError>
428+
----
429+
430+
==== Notes
431+
432+
If the requested task is not currently in the faulted state due to a panic,
433+
this KIPC returns the `abi::ReadPanicMessageError::TaskNotPanicked` response code.
434+
435+
If the requested task has panicked, but the buffer containing its panic
436+
message is invalid, this KIPC returns the
437+
`abi::ReadPanicMessageError::BadPanicMessage` response code. If the target task
438+
uses the panic handler provided by the `userlib` crate, this should not be
439+
possible. However, it may occur if a task calls the `sys_panic` syscall
440+
directly with invalid arguments.
441+
442+
The panic message is truncated to the length of the response buffer. Since
443+
Hubris only stores the first 128 bytes of panic messages, a 128-byte response
444+
buffer will never result in further truncation.
445+
405446
== Receiving from the kernel
406447

407448
The kernel never sends messages to tasks. It's simply not equipped to do so.

sys/abi/src/lib.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -512,6 +512,7 @@ pub enum Kipcnum {
512512
ReadTaskDumpRegion = 7,
513513
SoftwareIrq = 8,
514514
FindFaultedTask = 9,
515+
ReadPanicMessage = 10,
515516
}
516517

517518
impl core::convert::TryFrom<u16> for Kipcnum {
@@ -528,6 +529,7 @@ impl core::convert::TryFrom<u16> for Kipcnum {
528529
7 => Ok(Self::ReadTaskDumpRegion),
529530
8 => Ok(Self::SoftwareIrq),
530531
9 => Ok(Self::FindFaultedTask),
532+
10 => Ok(Self::ReadPanicMessage),
531533
_ => Err(()),
532534
}
533535
}
@@ -582,3 +584,29 @@ bitflags::bitflags! {
582584
const CLEAR_PENDING = 1 << 1;
583585
}
584586
}
587+
588+
/// Errors returned by [`Kipcnum::ReadPanicMessage`].
589+
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
590+
#[repr(u32)]
591+
pub enum ReadPanicMessageError {
592+
/// The task in question has not panicked.
593+
TaskNotPanicked = 1,
594+
/// The task has panicked, but its panic message buffer is invalid, so the
595+
/// kernel has not let us have it.
596+
BadPanicMessage = 2,
597+
}
598+
599+
/// We're using an explicit `TryFrom` impl for `ReadPanicMessageError` instead of
600+
/// `FromPrimitive` because the kernel doesn't currently depend on `num-traits`
601+
/// and this seems okay.
602+
impl core::convert::TryFrom<u32> for ReadPanicMessageError {
603+
type Error = ();
604+
605+
fn try_from(x: u32) -> Result<Self, Self::Error> {
606+
match x {
607+
1 => Ok(Self::TaskNotPanicked),
608+
2 => Ok(Self::BadPanicMessage),
609+
_ => Err(()),
610+
}
611+
}
612+
}

sys/kern/src/kipc.rs

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use unwrap_lite::UnwrapLite;
1111
use crate::arch;
1212
use crate::err::UserError;
1313
use crate::task::{current_id, ArchState, NextTask, Task};
14-
use crate::umem::USlice;
14+
use crate::umem::{safe_copy, USlice};
1515

1616
/// Message dispatcher.
1717
pub fn handle_kernel_message(
@@ -43,6 +43,9 @@ pub fn handle_kernel_message(
4343
Ok(Kipcnum::FindFaultedTask) => {
4444
find_faulted_task(tasks, caller, args.message?, args.response?)
4545
}
46+
Ok(Kipcnum::ReadPanicMessage) => {
47+
read_panic_message(tasks, caller, args.message?, args.response?)
48+
}
4649

4750
_ => {
4851
// Task has sent an unknown message to the kernel. That's bad.
@@ -507,3 +510,61 @@ fn find_faulted_task(
507510
.set_send_response_and_length(0, response_len);
508511
Ok(NextTask::Same)
509512
}
513+
514+
fn read_panic_message(
515+
tasks: &mut [Task],
516+
caller: usize,
517+
message: USlice<u8>,
518+
response: USlice<u8>,
519+
) -> Result<NextTask, UserError> {
520+
let index: u32 = deserialize_message(&tasks[caller], message)?;
521+
let index = index as usize;
522+
if index >= tasks.len() {
523+
return Err(UserError::Unrecoverable(FaultInfo::SyscallUsage(
524+
UsageError::TaskOutOfRange,
525+
)));
526+
}
527+
528+
if let TaskState::Faulted {
529+
fault: FaultInfo::Panic,
530+
..
531+
} = tasks[index].state()
532+
{
533+
// Okay, good
534+
} else {
535+
return Err(UserError::Recoverable(
536+
abi::ReadPanicMessageError::TaskNotPanicked as u32,
537+
NextTask::Same,
538+
));
539+
}
540+
541+
let Ok(message) = tasks[index].save().as_panic_args().message else {
542+
// XXX(eliza): should we have a whole error code for this, or just
543+
// return length 0?
544+
return Err(UserError::Recoverable(
545+
abi::ReadPanicMessageError::BadPanicMessage as u32,
546+
NextTask::Same,
547+
));
548+
};
549+
550+
match safe_copy(tasks, index, message, caller, response) {
551+
Ok(len) => {
552+
tasks[caller]
553+
.save_mut()
554+
.set_send_response_and_length(0, len);
555+
556+
Ok(NextTask::Same)
557+
}
558+
Err(crate::err::InteractFault {
559+
dst: Some(fault), ..
560+
}) => Err(UserError::Unrecoverable(fault)),
561+
Err(_) => {
562+
// Source region was bad, but it's not the caller's fault; give them
563+
// a recoverable error.
564+
Err(UserError::Recoverable(
565+
abi::ReadPanicMessageError::BadPanicMessage as u32,
566+
NextTask::Same,
567+
))
568+
}
569+
}
570+
}

sys/userlib/src/kipc.rs

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
1717
use core::num::NonZeroUsize;
1818

19-
use abi::{Kipcnum, TaskId};
19+
use abi::{Kipcnum, ReadPanicMessageError, TaskId};
2020
use zerocopy::IntoBytes;
2121

2222
use crate::{sys_send, UnwrapLite};
@@ -162,3 +162,37 @@ pub fn software_irq(task: usize, mask: u32) {
162162
&[],
163163
);
164164
}
165+
166+
/// Reads a task's panic message into the provided `buf`, if the task is
167+
/// panicked.
168+
///
169+
/// # Returns
170+
///
171+
/// - [`Ok`]`(&[u8])` if the task is panicked. The returned slice is borrowed
172+
/// from `buf`, and contains the task's panic message as a sequence of
173+
/// UTF-8 bytes. Note that the slice may be empty, if the task has panicked
174+
/// but was compiled without panic messages enabled.
175+
/// - [`Err`]`(`[`ReadPanicMessageError::TaskNotPanicked`]`)` if the task is
176+
/// not currently faulted due to a panic.
177+
/// - [`Err`]`(`[`ReadPanicMessageError::BadPanicMessage`]`)` if the task has
178+
/// panicked but the panic message buffer is invalid to read from.
179+
pub fn read_panic_message(
180+
task: usize,
181+
buf: &mut [u8; 128],
182+
) -> Result<&[u8], ReadPanicMessageError> {
183+
let task = task as u32;
184+
let (rc, len) = sys_send(
185+
TaskId::KERNEL,
186+
Kipcnum::ReadPanicMessage as u16,
187+
&task.as_bytes(),
188+
&mut buf[..],
189+
&[],
190+
);
191+
192+
if rc == 0 {
193+
Ok(&buf[..len])
194+
} else {
195+
// If the kernel sent us an unknown response code....i dunno, guess i'll die?
196+
Err(ReadPanicMessageError::try_from(rc).unwrap_lite())
197+
}
198+
}

test/test-api/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,3 +85,6 @@ impl TryFrom<u32> for TestResult {
8585
}
8686
}
8787
}
88+
89+
/// Panic message used when asking the test assistant to panic.
90+
pub const ASSIST_PANIC: &str = "wow this blew up, here's my soundcloud";

test/test-assist/src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ fn badread(arg: u32) {
2424
}
2525

2626
fn panic(_arg: u32) {
27-
panic!("wow this blew up, here's my soundcloud");
27+
panic!("{}", test_api::ASSIST_PANIC);
2828
}
2929

3030
#[inline(never)]

test/test-suite/src/main.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ test_cases! {
134134
test_irq_status,
135135
#[cfg(feature = "fru-id-eeprom")]
136136
at24csw080::test_at24csw080,
137+
test_read_panic_message,
137138
}
138139

139140
/// Tests that we can send a message to our assistant, and that the assistant
@@ -1553,6 +1554,38 @@ fn test_irq_status() {
15531554
assert_eq!(status, expected_status);
15541555
}
15551556

1557+
/// Tests that when a task panics, its panic message can be read via the `read_panic_message` kipc.
1558+
fn test_read_panic_message() {
1559+
set_autorestart(false);
1560+
1561+
let mut buf = [0u8; 128];
1562+
1563+
match kipc::read_panic_message(ASSIST.get_task_index().into(), &mut buf) {
1564+
Err(userlib::ReadPanicMessageError::TaskNotPanicked) => {}
1565+
x => panic!("expected `Err(TaskNotPanicked)`, got: {x:?}"),
1566+
}
1567+
1568+
// Ask the assistant to panic.
1569+
let assist = assist_task_id();
1570+
let mut response = 0u32;
1571+
let (_, _) = userlib::sys_send(
1572+
assist,
1573+
AssistOp::Panic as u16,
1574+
&0u32.to_le_bytes(),
1575+
response.as_mut_bytes(),
1576+
&[],
1577+
);
1578+
1579+
let msg =
1580+
kipc::read_panic_message(ASSIST.get_task_index().into(), &mut buf)
1581+
.unwrap();
1582+
// it should look kinda like a panic message (but since the line number may
1583+
// change, don't make assertions about the entire contents of the string...
1584+
assert!(core::str::from_utf8(&msg)
1585+
.expect("string shouldn't be corrupted")
1586+
.starts_with("panicked at"));
1587+
}
1588+
15561589
/// Asks the test runner (running as supervisor) to please trigger a software
15571590
/// interrupt for `notifications::TEST_IRQ`, thank you.
15581591
#[track_caller]

0 commit comments

Comments
 (0)