Skip to main content

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