lurk_cli/
syscall_info.rs

1use crate::arch::parse_args;
2use crate::style::StyleConfig;
3use libc::{c_ulonglong, user_regs_struct};
4use nix::unistd::Pid;
5use serde::ser::{SerializeMap, SerializeSeq};
6use serde::Serialize;
7use serde_json::Value;
8use std::borrow::Cow::{self, Borrowed, Owned};
9use std::fmt::{Debug, Display};
10use std::io;
11use std::io::Write;
12use std::time::Duration;
13use syscalls::Sysno;
14
15#[derive(Debug)]
16pub struct SyscallInfo {
17    pub typ: &'static str,
18    pub pid: Pid,
19    pub syscall: Sysno,
20    pub args: SyscallArgs,
21    pub result: RetCode,
22    pub duration: Duration,
23}
24
25impl SyscallInfo {
26    pub fn new(
27        pid: Pid,
28        syscall: Sysno,
29        ret_code: RetCode,
30        registers: user_regs_struct,
31        duration: Duration,
32    ) -> Self {
33        Self {
34            typ: "SYSCALL",
35            pid,
36            syscall,
37            args: parse_args(pid, syscall, registers),
38            result: ret_code,
39            duration,
40        }
41    }
42
43    pub fn write_syscall(
44        &self,
45        style: StyleConfig,
46        string_limit: Option<usize>,
47        show_syscall_num: bool,
48        show_duration: bool,
49        output: &mut dyn Write,
50    ) -> anyhow::Result<()> {
51        if style.use_colors {
52            write!(output, "[{}] ", style.pid.apply_to(&self.pid.to_string()))?;
53        } else {
54            write!(output, "[{}] ", &self.pid)?;
55        }
56        if show_syscall_num {
57            write!(output, "{:>3} ", self.syscall.id())?;
58        }
59        if style.use_colors {
60            let styled = style.syscall.apply_to(self.syscall.to_string());
61            write!(output, "{styled}(")
62        } else {
63            write!(output, "{}(", &self.syscall)
64        }?;
65        for (idx, arg) in self.args.0.iter().enumerate() {
66            if idx > 0 {
67                write!(output, ", ")?;
68            }
69            arg.write(output, string_limit)?;
70        }
71        write!(output, ") = ")?;
72        if self.syscall == Sysno::exit || self.syscall == Sysno::exit_group {
73            write!(output, "?")?;
74        } else {
75            if style.use_colors {
76                let style = style.from_ret_code(self.result);
77                // TODO: it would be great if we can force termcolor to write
78                //       the styling prefix and suffix into the formatter.
79                //       This would allow us to use the same code for both cases,
80                //       and avoid additional string alloc
81                write!(output, "{}", style.apply_to(self.result.to_string()))
82            } else {
83                write!(output, "{}", self.result)
84            }?;
85            if show_duration {
86                // TODO: add an option to control each syscall duration scaling, e.g. ms, us, ns
87                write!(output, " <{:.6}ns>", self.duration.as_nanos())?;
88            }
89        }
90        Ok(writeln!(output)?)
91    }
92}
93
94impl Serialize for SyscallInfo {
95    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
96        let mut map = serializer.serialize_map(Some(7))?;
97        map.serialize_entry("type", &self.typ)?;
98        map.serialize_entry("pid", &self.pid.as_raw())?;
99        map.serialize_entry("num", &self.syscall)?;
100        map.serialize_entry("syscall", &self.syscall.to_string())?;
101        map.serialize_entry("args", &self.args)?;
102        match self.result {
103            RetCode::Ok(value) => map.serialize_entry("success", &value)?,
104            RetCode::Err(value) => map.serialize_entry("error", &value)?,
105            RetCode::Address(value) => map.serialize_entry("result", &value)?,
106        }
107        map.serialize_entry("duration", &self.duration.as_secs_f64())?;
108        map.end()
109    }
110}
111
112#[derive(Debug)]
113pub struct SyscallArgs(pub Vec<SyscallArg>);
114
115impl Serialize for SyscallArgs {
116    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
117        let mut seq = serializer.serialize_seq(Some(self.0.len()))?;
118        for arg in &self.0 {
119            let value = match arg {
120                SyscallArg::Int(v) => serde_json::to_value(v).unwrap(),
121                SyscallArg::Str(v) => serde_json::to_value(v).unwrap(),
122                SyscallArg::Addr(v) => Value::String(format!("{v:#x}")),
123            };
124            seq.serialize_element(&value)?;
125        }
126        seq.end()
127    }
128}
129
130#[derive(Debug, Copy, Clone)]
131pub enum RetCode {
132    Ok(i32),
133    Err(i32),
134    Address(usize),
135}
136
137impl RetCode {
138    pub fn from_raw(ret_code: c_ulonglong) -> Self {
139        let ret_i32 = ret_code as isize;
140        // TODO: is this > or >= ?  Add a link to the docs.
141        if ret_i32.abs() > 0x8000 {
142            Self::Address(ret_code as usize)
143        } else if ret_i32 < 0 {
144            Self::Err(ret_i32 as i32)
145        } else {
146            Self::Ok(ret_i32 as i32)
147        }
148    }
149}
150
151impl Display for RetCode {
152    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
153        match self {
154            Self::Ok(v) | Self::Err(v) => Display::fmt(v, f),
155            Self::Address(v) => write!(f, "{v:#X}"),
156        }
157    }
158}
159
160#[derive(Debug, Serialize)]
161pub enum SyscallArg {
162    Int(i64),
163    Str(String),
164    Addr(usize),
165}
166
167impl SyscallArg {
168    pub fn write(&self, f: &mut dyn Write, string_limit: Option<usize>) -> io::Result<()> {
169        match self {
170            Self::Int(v) => write!(f, "{v}"),
171            Self::Str(v) => {
172                let value: Value = match string_limit {
173                    Some(width) => trim_str(v, width),
174                    None => Borrowed(v.as_ref()),
175                }
176                .into();
177                write!(f, "{value}")
178            }
179            Self::Addr(v) => write!(f, "{v:#X}"),
180        }
181    }
182}
183
184fn trim_str(string: &str, limit: usize) -> Cow<'_, str> {
185    match string.chars().as_str().get(..limit) {
186        None => Borrowed(string),
187        Some(s) => Owned(format!("{s}...")),
188    }
189}