Skip to content

Commit

Permalink
Add ExitStatusExt::task_retcode for Fuchsia platform
Browse files Browse the repository at this point in the history
The method task_retcode() method improves the ergonomics of
determining if a Fuchsia process was killed by the kernel and
for what reason.
  • Loading branch information
c6c7 committed Jul 15, 2024
1 parent deb8ebb commit 900e3c6
Show file tree
Hide file tree
Showing 5 changed files with 93 additions and 19 deletions.
2 changes: 2 additions & 0 deletions library/std/src/os/fuchsia/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,6 @@
#![stable(feature = "raw_ext", since = "1.1.0")]

pub mod fs;
#[unstable(feature = "fuchsia_exit_status", issue = "none")]
pub mod process;
pub mod raw;
52 changes: 52 additions & 0 deletions library/std/src/os/fuchsia/process.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
//! Fuchsia-specific extensions to primitives in the [`std::process`] module.
//!
//! [`std::process`]: crate::process

use crate::process;
use crate::sealed::Sealed;

#[unstable(feature = "fuchsia_exit_status", issue = "none")]
pub type zx_status_t = i32;
#[unstable(feature = "fuchsia_exit_status", issue = "none")]
pub const ZX_TASK_RETCODE_SYSCALL_KILL: zx_status_t = -1024;
#[unstable(feature = "fuchsia_exit_status", issue = "none")]
pub const ZX_TASK_RETCODE_OOM_KILL: zx_status_t = -1025;
#[unstable(feature = "fuchsia_exit_status", issue = "none")]
pub const ZX_TASK_RETCODE_POLICY_KILL: zx_status_t = -1026;
#[unstable(feature = "fuchsia_exit_status", issue = "none")]
pub const ZX_TASK_RETCODE_VDSO_KILL: zx_status_t = -1027;
/// On Zircon (the Fuchsia kernel), an abort from userspace calls the
/// LLVM implementation of __builtin_trap(), e.g., ud2 on x86, which
/// raises a kernel exception. If a userspace process does not
/// otherwise arrange exception handling, the kernel kills the process
/// with this return code.
#[unstable(feature = "fuchsia_exit_status", issue = "none")]
pub const ZX_TASK_RETCODE_EXCEPTION_KILL: zx_status_t = -1028;
#[unstable(feature = "fuchsia_exit_status", issue = "none")]
pub const ZX_TASK_RETCODE_CRITICAL_PROCESS_KILL: zx_status_t = -1029;

#[unstable(feature = "fuchsia_exit_status", issue = "none")]
pub trait ExitStatusExt: Sealed {
/// If the task was killed, returns the `ZX_TASK_RETCODE_*`.
#[must_use]
fn task_retcode(&self) -> Option<i32>;
}

#[unstable(feature = "fuchsia_exit_status", issue = "none")]
impl ExitStatusExt for process::ExitStatus {
fn task_retcode(&self) -> Option<i32> {
self.code().and_then(|code| {
if code == ZX_TASK_RETCODE_SYSCALL_KILL
|| code == ZX_TASK_RETCODE_OOM_KILL
|| code == ZX_TASK_RETCODE_POLICY_KILL
|| code == ZX_TASK_RETCODE_VDSO_KILL
|| code == ZX_TASK_RETCODE_EXCEPTION_KILL
|| code == ZX_TASK_RETCODE_CRITICAL_PROCESS_KILL
{
Some(code)
} else {
None
}
})
}
}
1 change: 1 addition & 0 deletions library/test/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#![feature(process_exitcode_internals)]
#![feature(panic_can_unwind)]
#![feature(test)]
#![cfg_attr(target_os = "fuchsia", feature(fuchsia_exit_status))]
#![allow(internal_features)]

// Public reexports
Expand Down
41 changes: 23 additions & 18 deletions library/test/src/test_result.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
use std::any::Any;
use std::process::ExitStatus;

#[cfg(target_os = "fuchsia")]
use std::os::fuchsia::process::{ExitStatusExt as _, ZX_TASK_RETCODE_EXCEPTION_KILL};
#[cfg(unix)]
use std::os::unix::process::ExitStatusExt;
use std::os::unix::process::ExitStatusExt as _;

use super::bench::BenchSamples;
use super::options::ShouldPanic;
Expand All @@ -21,14 +23,6 @@ pub const TR_OK: i32 = 50;
#[cfg(windows)]
const STATUS_FAIL_FAST_EXCEPTION: i32 = 0xC0000409u32 as i32;

// On Zircon (the Fuchsia kernel), an abort from userspace calls the
// LLVM implementation of __builtin_trap(), e.g., ud2 on x86, which
// raises a kernel exception. If a userspace process does not
// otherwise arrange exception handling, the kernel kills the process
// with this return code.
#[cfg(target_os = "fuchsia")]
const ZX_TASK_RETCODE_EXCEPTION_KILL: i32 = -1028;

#[derive(Debug, Clone, PartialEq)]
pub enum TestResult {
TrOk,
Expand Down Expand Up @@ -101,10 +95,28 @@ pub fn get_result_from_exit_code(
time_opts: &Option<time::TestTimeOptions>,
exec_time: &Option<time::TestExecTime>,
) -> TestResult {
let result = match status.code() {
// Upon a panic, a Fuchsia process will trigger a kernel exception
// that, if uncaught, will cause the kernel to kill the process with
// ZX_TASK_RETCODE_EXCEPTION_KILL. Though unlikely, the same code could be
// returned for other unhandled exceptions too. Even in those cases the test
// should still fail and the printed stacktrace from the kernel should
// sufficienly compensate for omitting this return code from test output.
#[cfg(target_os = "fuchsia")]
let result = match status.task_retcode() {
Some(ZX_TASK_RETCODE_EXCEPTION_KILL) => Some(TestResult::TrFailed),
_ => None,
};
#[cfg(not(target_os = "fuchsia"))]
let result: Option<TestResult> = None;

let result = result.unwrap_or_else(|| match status.code() {
Some(TR_OK) => TestResult::TrOk,
#[cfg(windows)]
Some(STATUS_FAIL_FAST_EXCEPTION) => TestResult::TrFailed,
#[cfg(any(windows, unix))]
Some(code) => TestResult::TrFailedMsg(format!("got unexpected return code {code}")),
#[cfg(not(any(windows, unix)))]
Some(_) => TestResult::TrFailed,
#[cfg(unix)]
None => match status.signal() {
Some(libc::SIGABRT) => TestResult::TrFailed,
Expand All @@ -113,16 +125,9 @@ pub fn get_result_from_exit_code(
}
None => unreachable!("status.code() returned None but status.signal() was None"),
},
// Upon an abort, Fuchsia returns the status code ZX_TASK_RETCODE_EXCEPTION_KILL.
#[cfg(target_os = "fuchsia")]
Some(ZX_TASK_RETCODE_EXCEPTION_KILL) => TestResult::TrFailed,
#[cfg(not(unix))]
None => TestResult::TrFailedMsg(format!("unknown return code")),
#[cfg(any(windows, unix))]
Some(code) => TestResult::TrFailedMsg(format!("got unexpected return code {code}")),
#[cfg(not(any(windows, unix)))]
Some(_) => TestResult::TrFailed,
};
});

// If test is already failed (or allowed to fail), do not change the result.
if result != TestResult::TrOk {
Expand Down
16 changes: 15 additions & 1 deletion tests/ui/process/signal-exit-status.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,34 @@
//@ ignore-wasm32 no processes
//@ ignore-sgx no processes
//@ ignore-windows
//@ ignore-fuchsia code returned as ZX_TASK_RETCODE_EXCEPTION_KILL, FIXME (#58590)

#![feature(core_intrinsics)]
#![cfg_attr(target_os = "fuchsia", feature(fuchsia_exit_status))]

use std::env;
use std::process::Command;

#[cfg(target_os = "fuchsia")]
use std::os::fuchsia::process::{ExitStatusExt, ZX_TASK_RETCODE_EXCEPTION_KILL};

pub fn main() {
let args: Vec<String> = env::args().collect();
if args.len() >= 2 && args[1] == "signal" {
// Raise an aborting signal without UB
core::intrinsics::abort();
} else {
// Spawn a child process that will raise an aborting signal
let status = Command::new(&args[0]).arg("signal").status().unwrap();

#[cfg(not(target_os = "fuchsia"))]
assert!(status.code().is_none());

// Upon abort(), a Fuchsia process will trigger a kernel exception
// that, if uncaught, will cause the kernel to kill the process with
// ZX_TASK_RETCODE_EXCEPTION_KILL. The same code could be
// returned for a different unhandled exception, but the simplicity of
// the program under test makes such an exception unlikely.
#[cfg(target_os = "fuchsia")]
assert_eq!(Some(ZX_TASK_RETCODE_EXCEPTION_KILL), status.task_retcode());
}
}

0 comments on commit 900e3c6

Please sign in to comment.