starnix_core/execution/
crash_reporter.rs1use crate::signals::SignalInfo;
6use crate::task::CurrentTask;
7use crash_throttling::{CrashThrottler, PendingCrashReport};
8use fidl_fuchsia_feedback::{
9 Annotation, CrashReport, CrashReporterProxy, MAX_ANNOTATION_VALUE_LENGTH,
10 MAX_CRASH_SIGNATURE_LENGTH, NativeCrashReport, SpecificCrashReport,
11};
12use fuchsia_inspect::Node;
13use starnix_logging::{
14 CATEGORY_STARNIX, CoreDumpInfo, CoreDumpList, TraceScope, log_error, log_info, log_warn,
15 trace_instant,
16};
17
18pub struct CrashReporter {
19 core_dumps: CoreDumpList,
21
22 throttler: CrashThrottler,
24
25 proxy: Option<CrashReporterProxy>,
27}
28
29impl CrashReporter {
30 pub fn new(
31 inspect_node: &Node,
32 proxy: Option<CrashReporterProxy>,
33 crash_loop_age_out: zx::MonotonicDuration,
34 enable_throttling: bool,
35 ) -> Self {
36 Self {
37 core_dumps: CoreDumpList::new(inspect_node.create_child("coredumps")),
38 throttler: CrashThrottler::new(inspect_node, crash_loop_age_out, enable_throttling),
39 proxy,
40 }
41 }
42
43 pub fn begin_crash_report(&self, current_task: &CurrentTask) -> Option<PendingCrashReport> {
46 let argv = current_task
47 .read_argv(MAX_ANNOTATION_VALUE_LENGTH as usize)
48 .unwrap_or_else(|_| vec!["<unknown>".into()])
49 .into_iter()
50 .map(|a| a.to_string())
51 .collect::<Vec<_>>();
52 let argv0 = argv.get(0).map(AsRef::as_ref).unwrap_or_else(|| "<unknown>");
53
54 let argv0 = argv0.rsplit_once("/").unwrap_or(("", &argv0)).1.to_string();
56
57 self.throttler.should_report(argv, argv0, zx::MonotonicInstant::get())
58 }
59
60 pub fn handle_core_dump(
62 &self,
63 current_task: &CurrentTask,
64 signal_info: &SignalInfo,
65 pending_crash_report: PendingCrashReport,
66 ) {
67 trace_instant!(CATEGORY_STARNIX, "RecordCoreDump", TraceScope::Process);
68
69 let argv = pending_crash_report.argv;
70 let argv0 = pending_crash_report.argv0;
71 let process_koid = current_task
72 .thread_group()
73 .process
74 .koid()
75 .expect("handles for processes with crashing threads are still valid");
76 let thread_koid = current_task
77 .live()
78 .thread
79 .read()
80 .as_ref()
81 .expect("coredumps occur in tasks with associated threads")
82 .koid()
83 .expect("handles for crashing threads are still valid");
84 let linux_pid = current_task.thread_group().leader as i64;
85 let thread_name = current_task.command().to_string();
86
87 let uptime = zx::MonotonicInstant::get() - current_task.thread_group().start_time;
89
90 let dump_info = CoreDumpInfo {
91 process_koid,
92 thread_koid,
93 linux_pid,
94 uptime: uptime.into_nanos(),
95 argv: argv.clone(),
96 thread_name: thread_name.clone(),
97 signal: signal_info.signal.to_string(),
98 };
99 self.core_dumps.record_core_dump(dump_info);
100
101 let mut argv_joined = argv.join(" ");
102 truncate_with_ellipsis(&mut argv_joined, MAX_ANNOTATION_VALUE_LENGTH as usize);
103
104 let mut env_joined = current_task
105 .read_env(MAX_ANNOTATION_VALUE_LENGTH as usize)
106 .unwrap_or_else(|_| vec![])
107 .into_iter()
108 .map(|a| a.to_string())
109 .collect::<Vec<_>>()
110 .join(" ");
111 truncate_with_ellipsis(&mut env_joined, MAX_ANNOTATION_VALUE_LENGTH as usize);
112
113 let signal_str = signal_info.signal.to_string();
114
115 let max_signature_prefix_len = MAX_CRASH_SIGNATURE_LENGTH as usize - (signal_str.len() + 1);
117 let mut crash_signature = argv0.clone();
118 truncate_with_ellipsis(&mut crash_signature, max_signature_prefix_len);
119 crash_signature.push(' ');
120 crash_signature.push_str(&signal_str);
121
122 let crash_report = CrashReport {
123 crash_signature: Some(crash_signature),
124 program_name: Some(argv0.clone()),
125 program_uptime: Some(uptime.into_nanos()),
126 specific_report: Some(SpecificCrashReport::Native(NativeCrashReport {
127 process_koid: Some(process_koid.raw_koid()),
128 process_name: Some(argv0),
129 thread_koid: Some(thread_koid.raw_koid()),
130 thread_name: Some(thread_name),
131 ..Default::default()
132 })),
133 annotations: Some(vec![
134 Annotation { key: "linux.pid".to_string(), value: linux_pid.to_string() },
138 Annotation { key: "linux.argv".to_string(), value: argv_joined },
139 Annotation { key: "linux.env".to_string(), value: env_joined },
140 Annotation { key: "linux.signal".to_string(), value: signal_str },
141 ]),
142 is_fatal: Some(true),
143 weight: Some(pending_crash_report.weight),
144 ..Default::default()
145 };
146
147 if let Some(reporter) = &self.proxy {
148 let reporter = reporter.clone();
149 current_task.kernel().kthreads.spawn_future(
151 move || async move {
152 match reporter.file_report(crash_report).await {
153 Ok(Ok(_)) => (),
154 Ok(Err(filing_error)) => {
155 log_error!(filing_error:?; "Couldn't file crash report.");
156 }
157 Err(fidl_error) => log_warn!(
158 fidl_error:?;
159 "Couldn't file crash report due to error on underlying channel."
160 ),
161 };
162 },
163 "crash-filing",
164 );
165 } else {
166 log_info!(crash_report:?; "no crash reporter available for crash");
167 }
168 }
169}
170
171fn truncate_with_ellipsis(s: &mut String, max_len: usize) {
172 if s.len() <= max_len {
173 return;
174 }
175
176 let max_content_len = max_len - 3;
178
179 let mut new_len = 0;
182 let mut iter = s.char_indices();
183 while let Some((offset, _)) = iter.next() {
184 if offset > max_content_len {
185 break;
186 }
187 new_len = offset;
188 }
189
190 s.truncate(new_len);
191 s.push_str("...");
192}
193
194#[cfg(test)]
195mod tests {
196 use super::*;
197
198 #[test]
199 fn truncate_noop_on_max_length_string() {
200 let mut s = String::from("1234567890");
201 let before = s.clone();
202 truncate_with_ellipsis(&mut s, 10);
203 assert_eq!(s, before);
204 }
205
206 #[test]
207 fn truncate_adds_ellipsis() {
208 let mut s = String::from("1234567890");
209 truncate_with_ellipsis(&mut s, 9);
210 assert_eq!(s.len(), 9);
211 assert_eq!(s, "123456...", "truncate must add ellipsis and still fit under max len");
212 }
213
214 #[test]
215 fn truncate_is_sensible_in_middle_of_multibyte_chars() {
216 let mut s = String::from("æææææææææ");
217 truncate_with_ellipsis(&mut s, 8);
221 assert_eq!(s.len(), 7, "may end up shorter than provided max length w/ multi-byte chars");
222 assert_eq!(s, "ææ...", "truncate must remove whole characters and add ellipsis");
223 }
224}