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::__private::ser::FlatMapSerializer;
8use serde_json::Value;
9use std::borrow::Cow::{self, Borrowed, Owned};
10use std::fmt::{Debug, Display};
11use std::io;
12use std::io::Write;
13use std::time::Duration;
14use syscalls::Sysno;
15
16#[derive(Debug)]
17pub struct SyscallInfo {
18    pub typ: &'static str,
19    pub pid: Pid,
20    pub syscall: Sysno,
21    pub args: SyscallArgs,
22    pub result: RetCode,
23    pub duration: Duration,
24}
25
26impl SyscallInfo {
27    pub fn new(
28        pid: Pid,
29        syscall: Sysno,
30        ret_code: RetCode,
31        registers: user_regs_struct,
32        duration: Duration,
33    ) -> Self {
34        Self {
35            typ: "SYSCALL",
36            pid,
37            syscall,
38            args: parse_args(pid, syscall, registers),
39            result: ret_code,
40            duration,
41        }
42    }
43
44    pub fn write_syscall(
45        &self,
46        style: StyleConfig,
47        string_limit: Option<usize>,
48        show_syscall_num: bool,
49        show_duration: bool,
50        output: &mut dyn Write,
51    ) -> anyhow::Result<()> {
52        if style.use_colors {
53            write!(output, "[{}] ", style.pid.apply_to(&self.pid.to_string()))?;
54        } else {
55            write!(output, "[{}] ", &self.pid)?;
56        }
57        if show_syscall_num {
58            write!(output, "{:>3} ", self.syscall.id())?;
59        }
60        if style.use_colors {
61            let styled = style.syscall.apply_to(self.syscall.to_string());
62            write!(output, "{styled}(")
63        } else {
64            write!(output, "{}(", &self.syscall)
65        }?;
66        for (idx, arg) in self.args.0.iter().enumerate() {
67            if idx > 0 {
68                write!(output, ", ")?;
69            }
70            arg.write(output, string_limit)?;
71        }
72        write!(output, ") = ")?;
73        if self.syscall == Sysno::exit || self.syscall == Sysno::exit_group {
74            write!(output, "?")?;
75        } else {
76            if style.use_colors {
77                let style = style.from_ret_code(self.result);
78                // TODO: it would be great if we can force termcolor to write
79                //       the styling prefix and suffix into the formatter.
80                //       This would allow us to use the same code for both cases,
81                //       and avoid additional string alloc
82                write!(output, "{}", style.apply_to(self.result.to_string()))
83            } else {
84                write!(output, "{}", self.result)
85            }?;
86            if show_duration {
87                // TODO: add an option to control each syscall duration scaling, e.g. ms, us, ns
88                write!(output, " <{:.6}ns>", self.duration.as_nanos())?;
89            }
90        }
91        Ok(writeln!(output)?)
92    }
93}
94
95impl Serialize for SyscallInfo {
96    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
97        let mut map = serializer.serialize_map(Some(7))?;
98        map.serialize_entry("type", &self.typ)?;
99        map.serialize_entry("pid", &self.pid.as_raw())?;
100        map.serialize_entry("num", &self.syscall)?;
101        map.serialize_entry("syscall", &self.syscall.to_string())?;
102        map.serialize_entry("args", &self.args)?;
103        Serialize::serialize(&self.result, FlatMapSerializer(&mut map))?;
104        map.serialize_entry("duration", &self.duration.as_secs_f64())?;
105        map.end()
106    }
107}
108
109#[derive(Debug)]
110pub struct SyscallArgs(pub Vec<SyscallArg>);
111
112impl Serialize for SyscallArgs {
113    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
114        let mut seq = serializer.serialize_seq(Some(self.0.len()))?;
115        for arg in &self.0 {
116            let value = match arg {
117                SyscallArg::Int(v) => serde_json::to_value(v).unwrap(),
118                SyscallArg::Str(v) => serde_json::to_value(v).unwrap(),
119                SyscallArg::Addr(v) => Value::String(format!("{v:#x}")),
120            };
121            seq.serialize_element(&value)?;
122        }
123        seq.end()
124    }
125}
126
127#[derive(Debug, Copy, Clone, Serialize)]
128pub enum RetCode {
129    #[serde(rename = "success")]
130    Ok(i32),
131    #[serde(rename = "error")]
132    Err(i32),
133    #[serde(rename = "result")]
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}