1use core::ffi::c_void;
2use nix::{errno::Errno, sys::ptrace, unistd::Pid};
3use serde_derive::Deserialize;
4use std::{collections::HashMap, fmt};
5
6use crate::{diag::Result, hwaccess::Registers};
7
8#[derive(Debug, Deserialize, PartialEq)]
9pub enum Type {
15 Int,
17 Ptr,
19 Str,
21 Uint,
23}
24
25#[derive(Deserialize)]
26pub struct Arg {
32 name: String,
33 arg_type: Type,
34}
35
36impl Arg {
37 #[must_use]
38 pub fn name(&self) -> &String {
44 &self.name
45 }
46
47 #[must_use]
48 pub fn arg_type(&self) -> &Type {
54 &self.arg_type
55 }
56}
57
58#[derive(Deserialize)]
59pub struct Entry {
65 name: String,
66 ret_type: Type,
67 args: Option<Vec<Arg>>,
68}
69
70impl Entry {
71 #[must_use]
72 pub fn name(&self) -> &String {
78 &self.name
79 }
80
81 #[must_use]
82 pub fn ret_type(&self) -> &Type {
88 &self.ret_type
89 }
90
91 #[must_use]
92 pub fn args(&self) -> &Option<Vec<Arg>> {
98 &self.args
99 }
100}
101
102impl Default for Entry {
103 fn default() -> Self {
104 Self {
105 name: "unknown".to_string(),
106 ret_type: Type::Int,
107 args: None,
108 }
109 }
110}
111
112pub struct Entries {
118 map: HashMap<u64, Entry>,
119 default: Entry,
120}
121
122impl Entries {
123 pub fn new() -> Result<Self> {
136 let json = include_str!("data/syscall.json");
137 let map = serde_json::from_str(json)?;
138 Ok(Self {
139 map,
140 default: Entry::default(),
141 })
142 }
143
144 #[must_use]
145 pub fn get(&self, id: u64) -> &Entry {
156 self.map.get(&id).unwrap_or(&self.default)
157 }
158}
159
160pub struct Repr<'a> {
166 name: &'a str,
167 args: String,
168 ret_val: String,
169}
170
171fn trace_str(addr: u64, pid: Pid) -> Result<String> {
172 let mut ret = String::new();
173 let mut offset = 0;
174 loop {
175 #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
176 let c = char::from(ptrace::read(pid, (addr + offset) as *mut c_void)? as u8);
177 if c == '\0' {
178 break;
179 }
180 if c == '\n' {
181 ret.push('\\');
182 ret.push('n');
183 offset += 1;
184 continue;
185 }
186 ret.push(c);
187 offset += 1;
188 }
189
190 Ok(format!("\"{ret}\""))
191}
192
193fn parse_value(type_repr: &Type, val: u64, pid: Pid) -> Result<String> {
194 match type_repr {
195 #[allow(clippy::cast_possible_wrap)]
196 Type::Int => Ok(format!("{}", val as i64)),
197 Type::Ptr => {
198 let ptr_str = if val == 0x0 {
199 "NULL".to_string()
200 } else {
201 format!("{val:#x}")
202 };
203 Ok(ptr_str)
204 }
205 Type::Str => Ok(if val == 0x0 {
206 "?".to_string()
207 } else {
208 trace_str(val, pid)?
209 }),
210 Type::Uint => Ok(format!("{val}")),
211 }
212}
213
214impl<'a> Repr<'a> {
215 pub fn build(pid: Pid, infos: &'a Entries) -> Result<Self> {
230 let regs = Registers::read(pid)?;
231 let info = infos.get(regs.orig_rax());
232 let name = info.name();
233
234 let reg_vals = regs.function_params();
235 let args = if let Some(args) = info.args() {
236 args.iter()
237 .enumerate()
238 .map(|(i, arg)| {
239 parse_value(arg.arg_type(), reg_vals[i], pid)
240 .map(|val| format!("{}={}", arg.name(), val))
241 })
242 .collect::<Result<Vec<_>>>()
243 .map(|v| v.join(", "))
244 } else {
245 Ok(String::new())
246 }?;
247
248 #[allow(clippy::cast_possible_wrap)]
249 let ret_val = if regs.rax() as i64 == -(Errno::ENOSYS as i64) {
250 "?".to_string()
251 } else {
252 parse_value(info.ret_type(), regs.rax(), pid)?
253 };
254
255 Ok(Self {
256 name,
257 args,
258 ret_val,
259 })
260 }
261
262 #[must_use]
263 pub fn is_exit(&self) -> bool {
269 self.name.contains("exit")
270 }
271}
272
273impl fmt::Display for Repr<'_> {
274 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
275 write!(f, "{}({}) = {}", self.name, self.args, self.ret_val)
276 }
277}
278
279#[cfg(test)]
280mod tests {
281 use super::*;
282
283 #[test]
284 fn test_entry_default() {
285 let entry = Entry::default();
286 assert_eq!(entry.name(), "unknown");
287 assert_eq!(entry.ret_type(), &Type::Int);
288 assert!(entry.args().is_none());
289 }
290
291 #[test]
292 fn test_entries_new() {
293 let entries = Entries::new();
294 assert!(entries.is_ok());
295 }
296
297 #[test]
298 fn test_entries_get() {
299 let entries = Entries::new().expect("Failed to create Entries");
300 let entry = entries.get(1);
301 assert_eq!(entry.name(), "write");
302 }
303
304 #[test]
305 fn test_arg_methods() {
306 let arg = Arg {
307 name: "arg1".to_string(),
308 arg_type: Type::Int,
309 };
310 assert_eq!(arg.name(), "arg1");
311 assert_eq!(arg.arg_type(), &Type::Int);
312 }
313
314 #[test]
315 fn test_entry_methods() {
316 let entry = Entry {
317 name: "test".to_string(),
318 ret_type: Type::Uint,
319 args: Some(vec![Arg {
320 name: "arg1".to_string(),
321 arg_type: Type::Str,
322 }]),
323 };
324 assert_eq!(entry.name(), "test");
325 assert_eq!(entry.ret_type(), &Type::Uint);
326 assert!(entry.args().is_some());
327 }
328
329 #[test]
330 fn test_parse_value() {
331 let pid = Pid::from_raw(1);
332 assert_eq!(
333 parse_value(&Type::Int, 42, pid).expect("Failed to parse Int value"),
334 "42"
335 );
336 assert_eq!(
337 parse_value(&Type::Uint, 42, pid).expect("Failed to parse Uint value"),
338 "42"
339 );
340 assert_eq!(
341 parse_value(&Type::Ptr, 0, pid).expect("Failed to parse Ptr value"),
342 "NULL"
343 );
344 assert_eq!(
345 parse_value(&Type::Ptr, 42, pid).expect("Failed to parse Ptr value"),
346 "0x2a"
347 );
348 assert_eq!(
349 parse_value(&Type::Str, 0, pid).expect("Failed to parse Str value"),
350 "?"
351 );
352 }
353
354 #[test]
355 fn test_repr_is_exit() {
356 let repr = Repr {
357 name: "exit",
358 args: String::new(),
359 ret_val: String::new(),
360 };
361 assert!(repr.is_exit());
362
363 let repr = Repr {
364 name: "open",
365 args: String::new(),
366 ret_val: String::new(),
367 };
368 assert!(!repr.is_exit());
369 }
370
371 #[test]
372 fn test_repr_display() {
373 let repr = Repr {
374 name: "open",
375 args: "arg1=42".to_string(),
376 ret_val: "0".to_string(),
377 };
378 assert_eq!(format!("{}", repr), "open(arg1=42) = 0");
379 }
380
381 #[test]
382 fn test_entries_get_unknown() {
383 let entries = Entries::new().expect("Failed to create Entries");
384 let entry = entries.get(999_999);
385 assert_eq!(entry.name(), "unknown");
386 }
387}