scx_utils/
user_exit_info.rs1use crate::bindings;
6use crate::compat;
7use anyhow::bail;
8use anyhow::Result;
9use std::ffi::CStr;
10use std::os::raw::c_char;
11use std::sync::Mutex;
12
13pub struct UeiDumpPtr {
14 pub ptr: *const c_char,
15}
16unsafe impl Send for UeiDumpPtr {}
17
18pub static UEI_DUMP_PTR_MUTEX: Mutex<UeiDumpPtr> = Mutex::new(UeiDumpPtr {
19 ptr: std::ptr::null(),
20});
21
22lazy_static::lazy_static! {
23 pub static ref SCX_ECODE_RSN_HOTPLUG: u64 =
24 compat::read_enum("scx_exit_code", "SCX_ECODE_RSN_HOTPLUG").unwrap_or(0);
25}
26
27lazy_static::lazy_static! {
28 pub static ref SCX_ECODE_ACT_RESTART: u64 =
29 compat::read_enum("scx_exit_code", "SCX_ECODE_ACT_RESTART").unwrap_or(0);
30}
31
32pub enum ScxExitKind {
33 None = bindings::scx_exit_kind_SCX_EXIT_NONE as isize,
34 Done = bindings::scx_exit_kind_SCX_EXIT_DONE as isize,
35 Unreg = bindings::scx_exit_kind_SCX_EXIT_UNREG as isize,
36 UnregBPF = bindings::scx_exit_kind_SCX_EXIT_UNREG_BPF as isize,
37 UnregKern = bindings::scx_exit_kind_SCX_EXIT_UNREG_KERN as isize,
38 SysRq = bindings::scx_exit_kind_SCX_EXIT_SYSRQ as isize,
39 Error = bindings::scx_exit_kind_SCX_EXIT_ERROR as isize,
40 ErrorBPF = bindings::scx_exit_kind_SCX_EXIT_ERROR_BPF as isize,
41 ErrorStall = bindings::scx_exit_kind_SCX_EXIT_ERROR_STALL as isize,
42}
43
44pub enum ScxConsts {
45 ExitDumpDflLen = bindings::scx_consts_SCX_EXIT_DUMP_DFL_LEN as isize,
46}
47
48#[macro_export]
51macro_rules! uei_read {
52 ($skel: expr, $uei:ident) => {{
53 scx_utils::paste! {
54 let bpf_uei = $skel.maps.data_data.as_ref().unwrap().$uei;
55 let bpf_dump = scx_utils::UEI_DUMP_PTR_MUTEX.lock().unwrap().ptr;
56 let exit_code_ptr = match scx_utils::compat::struct_has_field("scx_exit_info", "exit_code") {
57 Ok(true) => &bpf_uei.exit_code as *const _,
58 _ => std::ptr::null(),
59 };
60 let exit_cpu_ptr = match scx_utils::compat::struct_has_field("scx_exit_info", "exit_cpu") {
61 Ok(true) => &bpf_uei.exit_cpu as *const _,
62 _ => std::ptr::null(),
63 };
64
65 scx_utils::UserExitInfo::new(
66 &bpf_uei.kind as *const _,
67 exit_code_ptr,
68 exit_cpu_ptr,
69 bpf_uei.reason.as_ptr() as *const _,
70 bpf_uei.msg.as_ptr() as *const _,
71 bpf_dump,
72 )
73 }
74 }};
75}
76
77#[macro_export]
81macro_rules! uei_set_size {
82 ($skel: expr, $ops: ident, $uei:ident) => {{
83 scx_utils::paste! {
84 let len = match $skel.struct_ops.$ops().exit_dump_len {
85 0 => scx_utils::ScxConsts::ExitDumpDflLen as u32,
86 v => v,
87 };
88 $skel.maps.rodata_data.as_mut().unwrap().[<$uei _dump_len>] = len;
89 $skel.maps.[<data_ $uei _dump>].set_value_size(len).unwrap();
90
91 let mut ptr = scx_utils::UEI_DUMP_PTR_MUTEX.lock().unwrap();
92 *ptr = scx_utils::UeiDumpPtr { ptr:
93 $skel
94 .maps
95 .[<data_ $uei _dump>]
96 .initial_value()
97 .unwrap()
98 .as_ptr() as *const _,
99 };
100 }
101 }};
102}
103
104#[macro_export]
107macro_rules! uei_exited {
108 ($skel: expr, $uei:ident) => {{
109 let bpf_uei = $skel.maps.data_data.as_ref().unwrap().uei;
110 (unsafe { std::ptr::read_volatile(&bpf_uei.kind as *const _) } != 0)
111 }};
112}
113
114#[macro_export]
117macro_rules! uei_report {
118 ($skel: expr, $uei:ident) => {{
119 let uei = scx_utils::uei_read!($skel, $uei);
120 uei.report().and_then(|_| Ok(uei))
121 }};
122}
123
124#[derive(Debug)]
126pub struct UserExitInfo {
127 kind: i32,
130 exit_code: i64,
131 exit_cpu: i32,
133 reason: Option<String>,
134 msg: Option<String>,
135 dump: Option<String>,
136}
137
138impl Default for UserExitInfo {
139 fn default() -> Self {
140 Self {
141 kind: 0,
142 exit_code: 0,
143 exit_cpu: -1,
144 reason: None,
145 msg: None,
146 dump: None,
147 }
148 }
149}
150
151impl UserExitInfo {
152 pub fn new(
158 kind_ptr: *const i32,
159 exit_code_ptr: *const i64,
160 exit_cpu_ptr: *const i32,
161 reason_ptr: *const c_char,
162 msg_ptr: *const c_char,
163 dump_ptr: *const c_char,
164 ) -> Self {
165 let kind = unsafe { std::ptr::read_volatile(kind_ptr) };
166 let exit_code = if exit_code_ptr.is_null() {
167 0
168 } else {
169 unsafe { std::ptr::read_volatile(exit_code_ptr) }
170 };
171 let exit_cpu = if exit_cpu_ptr.is_null() {
176 -1
177 } else {
178 unsafe { std::ptr::read_volatile(exit_cpu_ptr) }
179 };
180
181 let (reason, msg) = (
182 Some(
183 unsafe { CStr::from_ptr(reason_ptr) }
184 .to_str()
185 .expect("Failed to convert reason to string")
186 .to_string(),
187 )
188 .filter(|s| !s.is_empty()),
189 Some(
190 unsafe { CStr::from_ptr(msg_ptr) }
191 .to_str()
192 .expect("Failed to convert msg to string")
193 .to_string(),
194 )
195 .filter(|s| !s.is_empty()),
196 );
197
198 let dump = if dump_ptr.is_null() {
199 None
200 } else {
201 Some(
202 unsafe { CStr::from_ptr(dump_ptr) }
203 .to_str()
204 .expect("Failed to convert msg to string")
205 .to_string(),
206 )
207 .filter(|s| !s.is_empty())
208 };
209
210 Self {
211 kind,
212 exit_code,
213 exit_cpu,
214 reason,
215 msg,
216 dump,
217 }
218 }
219
220 pub fn report(&self) -> Result<()> {
224 if self.kind == 0 {
225 return Ok(());
226 }
227
228 if let Some(dump) = &self.dump {
229 eprintln!("\nDEBUG DUMP");
230 eprintln!(
231 "================================================================================\n"
232 );
233 eprintln!("{dump}");
234 eprintln!(
235 "================================================================================\n"
236 );
237 }
238
239 let cpu = match self.exit_cpu {
240 v if v >= 0 => format!(" on CPU {v}"),
241 _ => "".into(),
242 };
243 let why = match (&self.reason, &self.msg) {
244 (Some(reason), None) => format!("EXIT: {reason}{cpu}"),
245 (Some(reason), Some(msg)) => format!("EXIT: {reason} ({msg}){cpu}"),
246 _ => format!("<UNKNOWN>{cpu}"),
247 };
248
249 if self.kind <= ScxExitKind::UnregKern as i32 {
250 eprintln!("{why}");
251 Ok(())
252 } else {
253 bail!("{why}")
254 }
255 }
256
257 pub fn exit_code(&self) -> Option<i64> {
261 (self.kind == ScxExitKind::UnregBPF as i32 || self.kind == ScxExitKind::UnregKern as i32)
262 .then_some(self.exit_code)
263 }
264
265 pub fn exit_cpu(&self) -> Option<i32> {
268 (self.exit_cpu >= 0).then_some(self.exit_cpu)
269 }
270
271 pub fn should_restart(&self) -> bool {
273 match self.exit_code() {
274 Some(ecode) => (ecode & *SCX_ECODE_ACT_RESTART as i64) != 0,
275 _ => false,
276 }
277 }
278}