scx_utils/
user_exit_info.rs1use crate::bindings;
6use crate::compat;
7use anyhow::Result;
8use anyhow::bail;
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.$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
61 scx_utils::UserExitInfo::new(
62 &bpf_uei.kind as *const _,
63 exit_code_ptr,
64 bpf_uei.reason.as_ptr() as *const _,
65 bpf_uei.msg.as_ptr() as *const _,
66 bpf_dump,
67 )
68 }
69 }};
70}
71
72#[macro_export]
76macro_rules! uei_set_size {
77 ($skel: expr, $ops: ident, $uei:ident) => {{
78 scx_utils::paste! {
79 let len = match $skel.struct_ops.$ops().exit_dump_len {
80 0 => scx_utils::ScxConsts::ExitDumpDflLen as u32,
81 v => v,
82 };
83 $skel.maps.rodata_data.[<$uei _dump_len>] = len;
84 $skel.maps.[<data_ $uei _dump>].set_value_size(len).unwrap();
85
86 let mut ptr = scx_utils::UEI_DUMP_PTR_MUTEX.lock().unwrap();
87 *ptr = scx_utils::UeiDumpPtr { ptr:
88 $skel
89 .maps
90 .[<data_ $uei _dump>]
91 .initial_value()
92 .unwrap()
93 .as_ptr() as *const _,
94 };
95 }
96 }};
97}
98
99#[macro_export]
102macro_rules! uei_exited {
103 ($skel: expr, $uei:ident) => {{
104 let bpf_uei = $skel.maps.data_data.uei;
105 (unsafe { std::ptr::read_volatile(&bpf_uei.kind as *const _) } != 0)
106 }};
107}
108
109#[macro_export]
112macro_rules! uei_report {
113 ($skel: expr, $uei:ident) => {{
114 let uei = scx_utils::uei_read!($skel, $uei);
115 uei.report().and_then(|_| Ok(uei))
116 }};
117}
118
119#[derive(Debug, Default)]
121pub struct UserExitInfo {
122 kind: i32,
125 exit_code: i64,
126 reason: Option<String>,
127 msg: Option<String>,
128 dump: Option<String>,
129}
130
131impl UserExitInfo {
132 pub fn new(
138 kind_ptr: *const i32,
139 exit_code_ptr: *const i64,
140 reason_ptr: *const c_char,
141 msg_ptr: *const c_char,
142 dump_ptr: *const c_char,
143 ) -> Self {
144 let kind = unsafe { std::ptr::read_volatile(kind_ptr) };
145 let exit_code = if exit_code_ptr.is_null() {
146 0
147 } else {
148 unsafe { std::ptr::read_volatile(exit_code_ptr) }
149 };
150
151 let (reason, msg) = (
152 Some(
153 unsafe { CStr::from_ptr(reason_ptr) }
154 .to_str()
155 .expect("Failed to convert reason to string")
156 .to_string(),
157 )
158 .filter(|s| !s.is_empty()),
159 Some(
160 unsafe { CStr::from_ptr(msg_ptr) }
161 .to_str()
162 .expect("Failed to convert msg to string")
163 .to_string(),
164 )
165 .filter(|s| !s.is_empty()),
166 );
167
168 let dump = if dump_ptr.is_null() {
169 None
170 } else {
171 Some(
172 unsafe { CStr::from_ptr(dump_ptr) }
173 .to_str()
174 .expect("Failed to convert msg to string")
175 .to_string(),
176 )
177 .filter(|s| !s.is_empty())
178 };
179
180 Self {
181 kind,
182 exit_code,
183 reason,
184 msg,
185 dump,
186 }
187 }
188
189 pub fn report(&self) -> Result<()> {
193 if self.kind == 0 {
194 return Ok(());
195 }
196
197 if let Some(dump) = &self.dump {
198 eprintln!("\nDEBUG DUMP");
199 eprintln!(
200 "================================================================================\n"
201 );
202 eprintln!("{}", dump);
203 eprintln!(
204 "================================================================================\n"
205 );
206 }
207
208 let why = match (&self.reason, &self.msg) {
209 (Some(reason), None) => format!("EXIT: {}", reason),
210 (Some(reason), Some(msg)) => format!("EXIT: {} ({})", reason, msg),
211 _ => "<UNKNOWN>".into(),
212 };
213
214 if self.kind <= ScxExitKind::UnregKern as i32 {
215 eprintln!("{}", why);
216 Ok(())
217 } else {
218 bail!("{}", why)
219 }
220 }
221
222 pub fn exit_code(&self) -> Option<i64> {
226 if self.kind == ScxExitKind::UnregBPF as i32 || self.kind == ScxExitKind::UnregKern as i32 {
227 Some(self.exit_code)
228 } else {
229 None
230 }
231 }
232
233 pub fn should_restart(&self) -> bool {
235 match self.exit_code() {
236 Some(ecode) => (ecode & *SCX_ECODE_ACT_RESTART as i64) != 0,
237 _ => false,
238 }
239 }
240}