shadow_rs/host/syscall/
formatter.rs

1use std::any::TypeId;
2use std::fmt::Display;
3use std::marker::PhantomData;
4
5use shadow_shim_helper_rs::emulated_time::EmulatedTime;
6use shadow_shim_helper_rs::syscall_types::SyscallReg;
7use shadow_shim_helper_rs::util::time::TimeParts;
8
9use crate::core::worker::Worker;
10use crate::host::memory_manager::MemoryManager;
11use crate::host::process::Process;
12use crate::host::syscall::types::{SyscallError, SyscallResult};
13use crate::host::thread::ThreadId;
14
15#[derive(Copy, Clone, Debug, PartialEq, Eq)]
16pub enum FmtOptions {
17    Standard,
18    Deterministic,
19}
20
21// this type is required until we no longer need to access the format options from C
22#[derive(Copy, Clone, Debug, PartialEq, Eq)]
23#[repr(C)]
24pub enum StraceFmtMode {
25    Off,
26    Standard,
27    Deterministic,
28}
29
30impl From<StraceFmtMode> for Option<FmtOptions> {
31    fn from(x: StraceFmtMode) -> Self {
32        match x {
33            StraceFmtMode::Off => None,
34            StraceFmtMode::Standard => Some(FmtOptions::Standard),
35            StraceFmtMode::Deterministic => Some(FmtOptions::Deterministic),
36        }
37    }
38}
39
40impl From<Option<FmtOptions>> for StraceFmtMode {
41    fn from(x: Option<FmtOptions>) -> Self {
42        match x {
43            None => StraceFmtMode::Off,
44            Some(FmtOptions::Standard) => StraceFmtMode::Standard,
45            Some(FmtOptions::Deterministic) => StraceFmtMode::Deterministic,
46        }
47    }
48}
49
50pub trait SyscallDisplay {
51    fn fmt(
52        &self,
53        f: &mut std::fmt::Formatter<'_>,
54        options: FmtOptions,
55        mem: &MemoryManager,
56    ) -> std::fmt::Result;
57}
58
59/// A syscall argument or return value. It implements [`Display`], and only reads memory and
60/// converts types when being formatted.
61pub struct SyscallVal<'a, T> {
62    pub reg: SyscallReg,
63    pub args: [SyscallReg; 6],
64    options: FmtOptions,
65    mem: &'a MemoryManager,
66    _phantom: PhantomData<T>,
67}
68
69impl<'a, T> SyscallVal<'a, T> {
70    pub fn new(
71        reg: SyscallReg,
72        args: [SyscallReg; 6],
73        options: FmtOptions,
74        mem: &'a MemoryManager,
75    ) -> Self {
76        Self {
77            reg,
78            args,
79            options,
80            mem,
81            _phantom: PhantomData,
82        }
83    }
84
85    /// Cast a syscall argument or return value to another type.
86    pub fn cast<V>(&self) -> SyscallVal<'a, V> {
87        SyscallVal {
88            reg: self.reg,
89            args: self.args,
90            options: self.options,
91            mem: self.mem,
92            _phantom: PhantomData,
93        }
94    }
95}
96
97impl<'a, T> Display for SyscallVal<'a, T>
98where
99    SyscallVal<'a, T>: SyscallDisplay,
100{
101    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
102        SyscallDisplay::fmt(self, f, self.options, self.mem)
103    }
104}
105
106/// A marker type for indicating there are no types left in the syscall arguments.
107#[derive(Default)]
108pub struct NoArg {}
109
110impl SyscallDisplay for SyscallVal<'_, NoArg> {
111    fn fmt(
112        &self,
113        _f: &mut std::fmt::Formatter<'_>,
114        _options: FmtOptions,
115        _mem: &MemoryManager,
116    ) -> std::fmt::Result {
117        panic!("We shouldn't ever try to format this.");
118    }
119}
120
121/// A formatting wrapper for six syscall arguments.
122pub struct SyscallArgsFmt<'a, A = NoArg, B = NoArg, C = NoArg, D = NoArg, E = NoArg, F = NoArg> {
123    a: SyscallVal<'a, A>,
124    b: SyscallVal<'a, B>,
125    c: SyscallVal<'a, C>,
126    d: SyscallVal<'a, D>,
127    e: SyscallVal<'a, E>,
128    f: SyscallVal<'a, F>,
129}
130
131impl<'a, A, B, C, D, E, F> SyscallArgsFmt<'a, A, B, C, D, E, F>
132where
133    SyscallVal<'a, A>: Display,
134    SyscallVal<'a, B>: Display,
135    SyscallVal<'a, C>: Display,
136    SyscallVal<'a, D>: Display,
137    SyscallVal<'a, E>: Display,
138    SyscallVal<'a, F>: Display,
139{
140    pub fn new(args: [SyscallReg; 6], options: FmtOptions, mem: &'a MemoryManager) -> Self {
141        Self {
142            a: SyscallVal::new(args[0], args, options, mem),
143            b: SyscallVal::new(args[1], args, options, mem),
144            c: SyscallVal::new(args[2], args, options, mem),
145            d: SyscallVal::new(args[3], args, options, mem),
146            e: SyscallVal::new(args[4], args, options, mem),
147            f: SyscallVal::new(args[5], args, options, mem),
148        }
149    }
150}
151
152impl<'a, A, B, C, D, E, F> Display for SyscallArgsFmt<'a, A, B, C, D, E, F>
153where
154    SyscallVal<'a, A>: Display,
155    SyscallVal<'a, B>: Display,
156    SyscallVal<'a, C>: Display,
157    SyscallVal<'a, D>: Display,
158    SyscallVal<'a, E>: Display,
159    SyscallVal<'a, F>: Display,
160    A: 'static,
161    B: 'static,
162    C: 'static,
163    D: 'static,
164    E: 'static,
165    F: 'static,
166{
167    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
168        let args: [&dyn Display; 6] = [&self.a, &self.b, &self.c, &self.d, &self.e, &self.f];
169
170        let types: [TypeId; 6] = [
171            TypeId::of::<A>(),
172            TypeId::of::<B>(),
173            TypeId::of::<C>(),
174            TypeId::of::<D>(),
175            TypeId::of::<E>(),
176            TypeId::of::<F>(),
177        ];
178
179        let mut first = true;
180        for (arg, arg_type) in args.iter().zip(types) {
181            if arg_type == TypeId::of::<NoArg>() {
182                // the user didn't override this generic type, so it and any following types/args
183                // should not be shown
184                break;
185            }
186
187            if first {
188                write!(f, "{arg}")?;
189                first = false;
190            } else {
191                write!(f, ", {arg}")?;
192            }
193        }
194
195        Ok(())
196    }
197}
198
199/// A formatting wrapper for the syscall result.
200pub struct SyscallResultFmt<'a, RV>
201where
202    SyscallVal<'a, RV>: Display,
203    RV: std::fmt::Debug,
204{
205    rv: &'a SyscallResult,
206    args: [SyscallReg; 6],
207    options: FmtOptions,
208    mem: &'a MemoryManager,
209    _phantom: PhantomData<RV>,
210}
211
212impl<'a, RV> SyscallResultFmt<'a, RV>
213where
214    SyscallVal<'a, RV>: Display,
215    RV: std::fmt::Debug,
216{
217    pub fn new(
218        rv: &'a SyscallResult,
219        args: [SyscallReg; 6],
220        options: FmtOptions,
221        mem: &'a MemoryManager,
222    ) -> Self {
223        Self {
224            rv,
225            args,
226            options,
227            mem,
228            _phantom: PhantomData,
229        }
230    }
231}
232
233impl<'a, RV> Display for SyscallResultFmt<'a, RV>
234where
235    SyscallVal<'a, RV>: Display,
236    RV: std::fmt::Debug,
237{
238    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
239        match self.rv {
240            SyscallResult::Ok(x) => {
241                let rv = SyscallVal::<'_, RV>::new(*x, self.args, self.options, self.mem);
242                write!(f, "{rv}")
243            }
244            SyscallResult::Err(SyscallError::Failed(failed)) => {
245                let errno = failed.errno;
246                let rv = SyscallReg::from(errno.to_negated_i64());
247                let rv = SyscallVal::<'_, RV>::new(rv, self.args, self.options, self.mem);
248                write!(f, "{rv} ({errno})")
249            }
250            SyscallResult::Err(SyscallError::Native) => {
251                write!(f, "<native>")
252            }
253            SyscallResult::Err(SyscallError::Blocked(_)) => {
254                write!(f, "<blocked>")
255            }
256        }
257    }
258}
259
260/// Format and write the syscall.
261pub fn write_syscall(
262    mut writer: impl std::io::Write,
263    sim_time: &EmulatedTime,
264    tid: ThreadId,
265    name: impl Display,
266    args: impl Display,
267    rv: impl Display,
268) -> std::io::Result<()> {
269    let sim_time = sim_time.duration_since(&EmulatedTime::SIMULATION_START);
270    let sim_time = TimeParts::from_nanos(sim_time.as_nanos());
271    let sim_time = sim_time.fmt_hr_min_sec_nano();
272
273    writeln!(writer, "{sim_time} [tid {tid}] {name}({args}) = {rv}")
274}
275
276/// For logging unknown syscalls.
277pub fn log_syscall_simple(
278    proc: &Process,
279    logging_mode: Option<FmtOptions>,
280    tid: ThreadId,
281    syscall_name: &str,
282    args_str: &str,
283    result: &SyscallResult,
284) -> std::io::Result<()> {
285    let Some(logging_mode) = logging_mode else {
286        // logging was disabled
287        return Ok(());
288    };
289
290    let args = [SyscallReg::from(0i64); 6];
291    let mem = proc.memory_borrow();
292    let rv = SyscallResultFmt::<libc::c_long>::new(result, args, logging_mode, &mem);
293
294    proc.with_strace_file(|file| {
295        let time = Worker::current_time();
296
297        if let Some(time) = time {
298            write_syscall(file, &time, tid, syscall_name, args_str, rv)
299        } else {
300            log::warn!("Could not log syscall {syscall_name} with time {time:?}");
301            Ok(())
302        }
303    })
304    .unwrap_or(Ok(()))?;
305
306    Ok(())
307}
308
309#[cfg(test)]
310mod test {
311    use std::process::Command;
312
313    use linux_api::posix_types::Pid;
314    use shadow_shim_helper_rs::syscall_types::SyscallArgs;
315
316    use super::*;
317
318    #[test]
319    // can't call foreign function: gnu_get_libc_version
320    #[cfg_attr(miri, ignore)]
321    fn test_no_args() {
322        let args = SyscallArgs {
323            number: 100,
324            args: [0u32.into(); 6],
325        };
326
327        // 10 seconds should be long enough to keep the process alive while the following code runs
328        let mut proc = Command::new("sleep").arg(10.to_string()).spawn().unwrap();
329        let pid = Pid::from_raw(proc.id().try_into().unwrap()).unwrap();
330
331        let mem = unsafe { MemoryManager::new(pid) };
332
333        // make sure that we can construct a `SyscallArgsFmt` with no generic types
334        let _syscall_args = <SyscallArgsFmt>::new(args.args, FmtOptions::Standard, &mem);
335
336        proc.kill().unwrap();
337        proc.wait().unwrap();
338    }
339}