use core::ffi::c_void;
use nix::{errno::Errno, sys::ptrace, unistd::Pid};
use serde_derive::Deserialize;
use std::{collections::HashMap, fmt};
use crate::{diag::Result, hwaccess::Registers};
#[derive(Debug, Deserialize, PartialEq)]
pub enum Type {
Int,
Ptr,
Str,
Uint,
}
#[derive(Deserialize)]
pub struct Arg {
name: String,
arg_type: Type,
}
impl Arg {
#[must_use]
pub fn name(&self) -> &String {
&self.name
}
#[must_use]
pub fn arg_type(&self) -> &Type {
&self.arg_type
}
}
#[derive(Deserialize)]
pub struct Entry {
name: String,
ret_type: Type,
args: Option<Vec<Arg>>,
}
impl Entry {
#[must_use]
pub fn name(&self) -> &String {
&self.name
}
#[must_use]
pub fn ret_type(&self) -> &Type {
&self.ret_type
}
#[must_use]
pub fn args(&self) -> &Option<Vec<Arg>> {
&self.args
}
}
impl Default for Entry {
fn default() -> Self {
Self {
name: "unknown".to_string(),
ret_type: Type::Int,
args: None,
}
}
}
pub struct Entries {
map: HashMap<u64, Entry>,
default: Entry,
}
impl Entries {
pub fn new() -> Result<Self> {
let json = include_str!("data/syscall.json");
let map = serde_json::from_str(json)?;
Ok(Self {
map,
default: Entry::default(),
})
}
#[must_use]
pub fn get(&self, id: u64) -> &Entry {
self.map.get(&id).unwrap_or(&self.default)
}
}
pub struct Repr<'a> {
name: &'a str,
args: String,
ret_val: String,
}
fn trace_str(addr: u64, pid: Pid) -> Result<String> {
let mut ret = String::new();
let mut offset = 0;
loop {
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
let c = char::from(ptrace::read(pid, (addr + offset) as *mut c_void)? as u8);
if c == '\0' {
break;
}
if c == '\n' {
ret.push('\\');
ret.push('n');
offset += 1;
continue;
}
ret.push(c);
offset += 1;
}
Ok(format!("\"{ret}\""))
}
fn parse_value(type_repr: &Type, val: u64, pid: Pid) -> Result<String> {
match type_repr {
#[allow(clippy::cast_possible_wrap)]
Type::Int => Ok(format!("{}", val as i64)),
Type::Ptr => {
let ptr_str = if val == 0x0 {
"NULL".to_string()
} else {
format!("{val:#x}")
};
Ok(ptr_str)
}
Type::Str => Ok(if val == 0x0 {
"?".to_string()
} else {
trace_str(val, pid)?
}),
Type::Uint => Ok(format!("{val}")),
}
}
impl<'a> Repr<'a> {
pub fn build(pid: Pid, infos: &'a Entries) -> Result<Self> {
let regs = Registers::read(pid)?;
let info = infos.get(regs.orig_rax());
let name = info.name();
let reg_vals = regs.function_params();
let args = if let Some(args) = info.args() {
args.iter()
.enumerate()
.map(|(i, arg)| {
parse_value(arg.arg_type(), reg_vals[i], pid)
.map(|val| format!("{}={}", arg.name(), val))
})
.collect::<Result<Vec<_>>>()
.map(|v| v.join(", "))
} else {
Ok(String::new())
}?;
#[allow(clippy::cast_possible_wrap)]
let ret_val = if regs.rax() as i64 == -(Errno::ENOSYS as i64) {
"?".to_string()
} else {
parse_value(info.ret_type(), regs.rax(), pid)?
};
Ok(Self {
name,
args,
ret_val,
})
}
#[must_use]
pub fn is_exit(&self) -> bool {
self.name.contains("exit")
}
}
impl fmt::Display for Repr<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}({}) = {}", self.name, self.args, self.ret_val)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_entry_default() {
let entry = Entry::default();
assert_eq!(entry.name(), "unknown");
assert_eq!(entry.ret_type(), &Type::Int);
assert!(entry.args().is_none());
}
#[test]
fn test_entries_new() {
let entries = Entries::new();
assert!(entries.is_ok());
}
#[test]
fn test_entries_get() {
let entries = Entries::new().expect("Failed to create Entries");
let entry = entries.get(1);
assert_eq!(entry.name(), "write");
}
#[test]
fn test_arg_methods() {
let arg = Arg {
name: "arg1".to_string(),
arg_type: Type::Int,
};
assert_eq!(arg.name(), "arg1");
assert_eq!(arg.arg_type(), &Type::Int);
}
#[test]
fn test_entry_methods() {
let entry = Entry {
name: "test".to_string(),
ret_type: Type::Uint,
args: Some(vec![Arg {
name: "arg1".to_string(),
arg_type: Type::Str,
}]),
};
assert_eq!(entry.name(), "test");
assert_eq!(entry.ret_type(), &Type::Uint);
assert!(entry.args().is_some());
}
#[test]
fn test_parse_value() {
let pid = Pid::from_raw(1);
assert_eq!(
parse_value(&Type::Int, 42, pid).expect("Failed to parse Int value"),
"42"
);
assert_eq!(
parse_value(&Type::Uint, 42, pid).expect("Failed to parse Uint value"),
"42"
);
assert_eq!(
parse_value(&Type::Ptr, 0, pid).expect("Failed to parse Ptr value"),
"NULL"
);
assert_eq!(
parse_value(&Type::Ptr, 42, pid).expect("Failed to parse Ptr value"),
"0x2a"
);
assert_eq!(
parse_value(&Type::Str, 0, pid).expect("Failed to parse Str value"),
"?"
);
}
#[test]
fn test_repr_is_exit() {
let repr = Repr {
name: "exit",
args: String::new(),
ret_val: String::new(),
};
assert!(repr.is_exit());
let repr = Repr {
name: "open",
args: String::new(),
ret_val: String::new(),
};
assert!(!repr.is_exit());
}
#[test]
fn test_repr_display() {
let repr = Repr {
name: "open",
args: "arg1=42".to_string(),
ret_val: "0".to_string(),
};
assert_eq!(format!("{}", repr), "open(arg1=42) = 0");
}
#[test]
fn test_entries_get_unknown() {
let entries = Entries::new().expect("Failed to create Entries");
let entry = entries.get(999_999);
assert_eq!(entry.name(), "unknown");
}
}