1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167
// Copyright 2021-2023 Protocol Labs
// SPDX-License-Identifier: Apache-2.0, MIT
use std::fmt::Display;
use fvm_shared::address::Address;
use fvm_shared::error::{ErrorNumber, ExitCode};
use fvm_shared::{ActorID, MethodNum};
use crate::kernel::SyscallError;
/// A call backtrace records the actors an error was propagated through, from
/// the moment it was emitted. The original error is the _cause_. Backtraces are
/// useful for identifying the root cause of an error.
#[derive(Debug, Default, Clone)]
pub struct Backtrace {
/// The actors through which this error was propagated from bottom (source) to top.
pub frames: Vec<Frame>,
/// The last syscall error before the first actor in `frames` aborted.
pub cause: Option<Cause>,
}
impl Display for Backtrace {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for (i, frame) in self.frames.iter().rev().enumerate() {
writeln!(f, "{:02}: {}", i, frame)?;
}
if let Some(cause) = &self.cause {
writeln!(f, "--> caused by: {}", cause)?;
}
Ok(())
}
}
impl Backtrace {
/// Returns true if the backtrace is completely empty.
pub fn is_empty(&self) -> bool {
self.frames.is_empty() && self.cause.is_none()
}
/// Clear the backtrace.
pub fn clear(&mut self) {
self.cause = None;
self.frames.clear();
}
/// Begins a new backtrace. If there is an existing backtrace, this will clear it.
///
/// Note: Backtraces are populated _backwards_. That is, a frame is inserted
/// every time an actor returns. That's why `begin()` resets any currently
/// accumulated state, as once an error occurs, we want to track its
/// propagation all the way up.
pub fn begin(&mut self, cause: Cause) {
self.cause = Some(cause);
self.frames.clear();
}
/// Sets the cause of a backtrace.
///
/// This is useful to stamp a backtrace with its cause after the frames
/// have been collected, such as when we ultimately handle a fatal error at
/// the top of its propagation chain.
pub fn set_cause(&mut self, cause: Cause) {
self.cause = Some(cause);
}
/// Push a "frame" (actor exit) onto the backtrace.
///
/// This should be called every time an actor exits with an error.
pub fn push_frame(&mut self, frame: Frame) {
self.frames.push(frame)
}
}
/// A "frame" in a call backtrace.
#[derive(Clone, Debug)]
pub struct Frame {
/// The actor that exited with this code.
pub source: ActorID,
/// The method that was invoked.
pub method: MethodNum,
/// The exit code.
pub code: ExitCode,
/// The abort message.
pub message: String,
}
impl Display for Frame {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{} (method {}) -- {} ({})",
Address::new_id(self.source),
self.method,
&self.message,
self.code,
)
}
}
/// The ultimate "cause" of a failed message.
#[derive(Clone, Debug)]
pub enum Cause {
/// The original cause was a syscall error.
Syscall {
/// The syscall "module".
module: &'static str,
/// The syscall function name.
function: &'static str,
/// The exact syscall error.
error: ErrorNumber,
/// The informational syscall message.
message: String,
},
/// The original cause was a fatal error.
Fatal {
/// The alternate-formatted message from the anyhow error.
error_msg: String,
/// The backtrace, captured if the relevant
/// [environment variables](https://doc.rust-lang.org/std/backtrace/index.html#environment-variables) are enabled.
backtrace: String,
},
}
impl Cause {
/// Records a failing syscall as the cause of a backtrace.
pub fn from_syscall(module: &'static str, function: &'static str, err: SyscallError) -> Self {
Self::Syscall {
module,
function,
error: err.1,
message: err.0,
}
}
/// Records a fatal error as the cause of a backtrace.
pub fn from_fatal(err: anyhow::Error) -> Self {
Self::Fatal {
error_msg: format!("{:#}", err),
backtrace: err.backtrace().to_string(),
}
}
}
impl Display for Cause {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Cause::Syscall {
module,
function,
error,
message,
} => {
write!(
f,
"{}::{} -- {} ({}: {})",
module, function, &message, *error as u32, error,
)
}
Cause::Fatal {
error_msg,
backtrace,
} => {
write!(f, "[FATAL] Error: {}, Backtrace:\n{}", error_msg, backtrace)
}
}
}
}