Skip to content

Commit aa0efaa

Browse files
committed
add support for propagating backtraces on fatal errors.
1 parent 61c032e commit aa0efaa

File tree

5 files changed

+57
-28
lines changed

5 files changed

+57
-28
lines changed

fvm/src/call_manager/backtrace.rs

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,15 @@ impl Backtrace {
5353
self.frames.clear();
5454
}
5555

56+
/// Sets the cause of a backtrace.
57+
///
58+
/// This is useful to stamp a backtrace with its cause after the frames
59+
/// have been collected, such as when we ultimately handle a fatal error at
60+
/// the top of its propagation chain.
61+
pub fn set_cause(&mut self, cause: Cause) {
62+
self.cause = Some(cause);
63+
}
64+
5665
/// Push a "frame" (actor exit) onto the backtrace.
5766
///
5867
/// This should be called every time an actor exits.
@@ -89,7 +98,8 @@ impl Display for Frame {
8998
}
9099
}
91100

92-
struct SyscallCause {
101+
#[derive(Clone, Debug)]
102+
pub struct SyscallCause {
93103
/// The syscall "module".
94104
pub module: &'static str,
95105
/// The syscall function name.
@@ -100,13 +110,24 @@ struct SyscallCause {
100110
pub message: String,
101111
}
102112

113+
#[derive(Clone, Debug)]
114+
pub struct FatalCause {
115+
/// The error message from the error.
116+
pub error_msg: String,
117+
/// The original cause that initiated the error chain.
118+
pub root_cause: String,
119+
/// The backtrace, captured if the relevant
120+
/// [environment variables](https://doc.rust-lang.org/std/backtrace/index.html#environment-variables) are enabled.
121+
pub backtrace: String,
122+
}
123+
103124
/// The ultimate "cause" of a failed message.
104125
#[derive(Clone, Debug)]
105126
pub enum Cause {
106127
/// The original cause was a syscall error.
107128
Syscall(SyscallCause),
108129
/// The original cause was a fatal error.
109-
Fatal(String),
130+
Fatal(FatalCause),
110131
}
111132

112133
impl Cause {
@@ -122,7 +143,11 @@ impl Cause {
122143

123144
/// Records a fatal error as the cause of a backtrace.
124145
pub fn from_fatal(err: anyhow::Error) -> Self {
125-
Self::Fatal(err.into())
146+
Self::Fatal(FatalCause {
147+
error_msg: err.to_string(),
148+
root_cause: err.root_cause().to_string(),
149+
backtrace: err.backtrace().to_string(),
150+
})
126151
}
127152
}
128153

@@ -137,7 +162,11 @@ impl Display for Cause {
137162
)
138163
}
139164
Cause::Fatal(msg) => {
140-
write!(f, "[FATAL] {}", msg)
165+
write!(
166+
f,
167+
"[FATAL] Root cause: {}, Stacktrace: {}",
168+
msg.root_cause, msg.backtrace
169+
)
141170
}
142171
}
143172
}

fvm/src/call_manager/default.rs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -392,13 +392,8 @@ where
392392
let ret = match result {
393393
Ok(value) => Ok(InvocationResult::Return(value)),
394394
Err(abort) => {
395-
let cause = match abort {
396-
Abort::Exit(_, _) => {}
397-
Abort::OutOfGas => {}
398-
Abort::Fatal(_) => {}
399-
};
400395
if let Some(err) = last_error {
401-
cm.backtrace.set_cause(err);
396+
cm.backtrace.begin(err);
402397
}
403398

404399
let (code, message, res) = match abort {

fvm/src/executor/default.rs

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -137,17 +137,22 @@ where
137137
gas_used,
138138
}
139139
}
140-
Err(ExecutionError::Fatal(e)) => {
141-
// Annotate the error with the message context.
142-
let _err = e.context(format!(
143-
"[from={}, to={}, seq={}, m={}, h={}] fatal error",
144-
msg.from,
145-
msg.to,
146-
msg.sequence,
147-
msg.method_num,
148-
self.context().epoch
149-
));
150-
// TODO backtrace
140+
Err(ExecutionError::Fatal(err)) => {
141+
// // Annotate the error with the message context.
142+
// let err = {
143+
// let backtrace = String::from("foo"); //e.backtrace().to_string();
144+
// // e.context(format!(
145+
// // "[from={}, to={}, seq={}, m={}, h={}] fatal error; backtrace: {}",
146+
// // msg.from,
147+
// // msg.to,
148+
// // msg.sequence,
149+
// // msg.method_num,
150+
// // self.context().epoch,
151+
// // backtrace,
152+
// // ))
153+
// };
154+
backtrace.set_cause(backtrace::Cause::from_fatal(err));
155+
// Produce a receipt that consumes the full gas amount.
151156
Receipt {
152157
exit_code: ExitCode::SYS_ASSERTION_FAILED,
153158
return_data: Default::default(),

fvm/src/syscalls/bind.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ macro_rules! impl_bind_syscalls {
141141
Ok(Err(err)) => {
142142
let code = err.1;
143143
log::trace!("syscall {}::{}: fail ({})", module, name, code as u32);
144-
data.last_error = Some(backtrace::Cause::new(module, name, err));
144+
data.last_error = Some(backtrace::Cause::from_syscall(module, name, err));
145145
Ok(code as u32)
146146
},
147147
Err(e) => Err(e.into()),
@@ -163,7 +163,7 @@ macro_rules! impl_bind_syscalls {
163163
if (ret as u64) > (memory.len() as u64)
164164
|| memory.len() - (ret as usize) < mem::size_of::<Ret::Value>() {
165165
let code = ErrorNumber::IllegalArgument;
166-
data.last_error = Some(backtrace::Cause::new(module, name, SyscallError(format!("no space for return value"), code)));
166+
data.last_error = Some(backtrace::Cause::from_syscall(module, name, SyscallError(format!("no space for return value"), code)));
167167
return Ok(code as u32);
168168
}
169169

@@ -178,7 +178,7 @@ macro_rules! impl_bind_syscalls {
178178
Ok(Err(err)) => {
179179
let code = err.1;
180180
log::trace!("syscall {}::{}: fail ({})", module, name, code as u32);
181-
data.last_error = Some(backtrace::Cause::new(module, name, err));
181+
data.last_error = Some(backtrace::Cause::from_syscall(module, name, err));
182182
Ok(code as u32)
183183
},
184184
Err(e) => Err(e.into()),

testing/integration/tests/lib.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,8 @@ fn out_of_stack() {
206206

207207
#[test]
208208
fn backtraces() {
209+
// Note: this test **does not actually assert anything**, but it's useful to
210+
// let us peep into FVM backtrace generation under different scenarios.
209211
const WAT_ABORT: &str = r#"
210212
(module
211213
;; ipld::open
@@ -300,8 +302,7 @@ fn backtraces() {
300302
.execute_message(message, ApplyKind::Explicit, 100)
301303
.unwrap();
302304

303-
println!("abort backtrace:");
304-
dbg!(res.failure_info);
305+
println!("abort backtrace: {}", res.failure_info.unwrap());
305306

306307
// Send message
307308
let message = Message {
@@ -320,8 +321,7 @@ fn backtraces() {
320321
.execute_message(message, ApplyKind::Explicit, 100)
321322
.unwrap();
322323

323-
println!("fatal backtrace:");
324-
dbg!(res.failure_info);
324+
println!("fatal backtrace: {}", res.failure_info.unwrap());
325325
}
326326

327327
#[derive(Default)]

0 commit comments

Comments
 (0)