From f07cd5f1be036bbe0eb9f3f7c838421b45a0b4e8 Mon Sep 17 00:00:00 2001 From: Mika Vatanen Date: Sun, 11 Apr 2021 19:05:30 +0300 Subject: [PATCH] Output to file, JSON output draft --- Cargo.lock | 47 +++++++++++++++++++++- Cargo.toml | 4 +- README.md | 2 + src/call/fncntl.rs | 4 +- src/call/mman.rs | 4 +- src/call/mod.rs | 1 + src/call/sendfile.rs | 3 +- src/call/socket.rs | 6 ++- src/call/stat.rs | 5 ++- src/call/swap.rs | 5 ++- src/call/unistd.rs | 14 ++++--- src/call/utsname.rs | 5 ++- src/clap.yml | 5 +++ src/lib.rs | 13 ++++--- src/macros.rs | 10 ++--- src/main.rs | 67 +++++++++++++++++++++++--------- src/output.rs | 59 ++++++++++++++++++++++++++++ src/syscall/error.rs | 38 ++++++++++++++++-- src/syscall/x86_64.rs | 1 + src/trace/hstrace_impl.rs | 27 ++++++------- src/trace/output.rs | 2 +- src/trace/thread.rs | 7 +++- src/value/kind/memory_address.rs | 3 +- src/value/mod.rs | 17 +++++--- tests/test_c_binary.rs | 10 ++--- 25 files changed, 279 insertions(+), 80 deletions(-) create mode 100644 src/output.rs diff --git a/Cargo.lock b/Cargo.lock index ca532ca..f5b2e27 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -192,7 +192,7 @@ dependencies = [ [[package]] name = "hstrace" -version = "0.0.4" +version = "0.0.5" dependencies = [ "bindgen", "bitflags", @@ -208,6 +208,8 @@ dependencies = [ "nix 0.17.0", "num-derive", "num-traits", + "serde", + "serde_json", "serial_test", ] @@ -236,6 +238,12 @@ dependencies = [ "cfg-if 1.0.0", ] +[[package]] +name = "itoa" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" + [[package]] name = "lazy_static" version = "1.4.0" @@ -424,12 +432,49 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "ryu" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" + [[package]] name = "scopeguard" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "serde" +version = "1.0.125" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "558dc50e1a5a5fa7112ca2ce4effcb321b0300c0d4ccf0776a9f60cd89031171" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.125" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b093b7a2bb58203b5da3056c05b4ec1fed827dcfdb37347a8841695263b3d06d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79" +dependencies = [ + "itoa", + "ryu", + "serde", +] + [[package]] name = "serial_test" version = "0.5.1" diff --git a/Cargo.toml b/Cargo.toml index 0554a87..3758158 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "hstrace" -version = "0.0.4" +version = "0.0.5" authors = ["Mika Vatanen "] repository = "https://github.com/blaind/hstrace" documentation = "https://docs.rs/hstrace" @@ -38,6 +38,8 @@ bitflags = "1.2.1" crossbeam-utils = "0.8.3" ctrlc = "3.1.8" lazy_static = "1.4.0" +serde = { version = "1.0.125", features = ["derive"] } +serde_json = "1.0.64" [build-dependencies] bindgen = "0.58.1" diff --git a/README.md b/README.md index 212d1b0..a3e3868 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,8 @@ FLAGS: OPTIONS: -e Expression -m Run mode [default: strace] + -o Save output to a file instead of stderr. If suffix is `.json`, will be stored in JSON-format + (format subject to change) -p PID to trace -s Maximum length of printable strings [default: 32] diff --git a/src/call/fncntl.rs b/src/call/fncntl.rs index 6204986..206b928 100644 --- a/src/call/fncntl.rs +++ b/src/call/fncntl.rs @@ -1,4 +1,5 @@ use super::prelude::*; +use serde::Serialize; pub(crate) fn get_definitions(inp: &mut Definitions) { inp.add( @@ -16,7 +17,7 @@ pub(crate) fn get_definitions(inp: &mut Definitions) { ); } -#[derive(Debug, PartialEq, FromPtrace)] +#[derive(Debug, PartialEq, FromPtrace, Serialize)] #[hstrace(hmz("Open a file (dirfd: {}) {} with flags {:?}", self.dirfd, self.pathname, self.flags))] pub struct Openat { #[hstrace] @@ -31,6 +32,7 @@ pub struct Openat { } bitflags! { + #[derive(Serialize)] pub struct OpenatMode: isize { const O_ACCMODE = 0o0003; const O_RDONLY = 0o0; diff --git a/src/call/mman.rs b/src/call/mman.rs index fdb5f7c..9eeb17a 100644 --- a/src/call/mman.rs +++ b/src/call/mman.rs @@ -1,4 +1,5 @@ use super::prelude::*; +use serde::Serialize; pub(crate) fn get_definitions(inp: &mut Definitions) { inp.add( @@ -42,7 +43,7 @@ pub(crate) fn get_definitions(inp: &mut Definitions) { ); } -#[derive(Debug, PartialEq, FromPtrace)] +#[derive(Debug, PartialEq, FromPtrace, Serialize)] #[hstrace(hmz("Protect memory {:?} - {:?} (len {}) with flags {:?}", self.addr, MemoryAddress(self.addr.0 + self.len), @@ -61,6 +62,7 @@ pub struct Mprotect { } bitflags! { + #[derive(Serialize)] pub struct Prot: isize { const PROT_READ = 0x1; const PROT_WRITE = 0x2; diff --git a/src/call/mod.rs b/src/call/mod.rs index e3da5ec..714c5f1 100644 --- a/src/call/mod.rs +++ b/src/call/mod.rs @@ -9,6 +9,7 @@ //! Use this [GitHub issue 3](https://github.com/blaind/hstrace/issues/3) to request a new syscall implementation use num_traits::FromPrimitive; +use serde::Serialize; use std::fmt; use crate::syscall::{Definition, Direction}; diff --git a/src/call/sendfile.rs b/src/call/sendfile.rs index 3310446..984a945 100644 --- a/src/call/sendfile.rs +++ b/src/call/sendfile.rs @@ -1,4 +1,5 @@ use super::prelude::*; +use serde::Serialize; pub(crate) fn get_definitions(inp: &mut Definitions) { inp.add( @@ -15,7 +16,7 @@ pub(crate) fn get_definitions(inp: &mut Definitions) { } /// Syscall: Transfer data between file descriptors -#[derive(Debug, PartialEq, FromPtrace)] +#[derive(Debug, PartialEq, FromPtrace, Serialize)] #[hstrace(hmz("Transfer data to fd {:?} from fd {:?} offset {:?} len {:?}", self.out_fd, self.in_fd, self.offset, self.count))] pub struct Sendfile { /// FD where data is sent diff --git a/src/call/socket.rs b/src/call/socket.rs index bfbcd5d..c81c49b 100644 --- a/src/call/socket.rs +++ b/src/call/socket.rs @@ -1,4 +1,5 @@ use super::prelude::*; +use serde::Serialize; pub(crate) fn get_definitions(inp: &mut Definitions) { inp.add( @@ -17,7 +18,7 @@ pub(crate) fn get_definitions(inp: &mut Definitions) { } /// Syscall: Create an endpoint for communication -#[derive(Debug, PartialEq, FromPtrace)] +#[derive(Debug, PartialEq, FromPtrace, Serialize)] #[hstrace(hmz("Open {:?} domain socket with type {:?} and protocol {:?}", self.domain, self.socket_type, self.protocol))] pub struct Socket { #[hstrace] @@ -41,7 +42,7 @@ pub enum SockAddr { /// Argument: Communication domain / protocol family used #[allow(dead_code, non_camel_case_types)] -#[derive(Debug, Clone, FromPrimitive, PartialEq)] +#[derive(Debug, Clone, FromPrimitive, PartialEq, Serialize)] #[repr(isize)] pub enum AddressFamily { AF_UNSPEC = libc::AF_UNSPEC as isize, @@ -53,6 +54,7 @@ pub enum AddressFamily { bitflags! { /// Argument: Socket communication semantics + #[derive(Serialize)] pub struct SocketType: isize { const SOCK_STREAM = libc::SOCK_STREAM as isize; const SOCK_DGRAM = libc::SOCK_DGRAM as isize; diff --git a/src/call/stat.rs b/src/call/stat.rs index 392139c..677843c 100644 --- a/src/call/stat.rs +++ b/src/call/stat.rs @@ -1,5 +1,6 @@ use super::prelude::*; use crate::from_c::stat as sys_stat; +use serde::Serialize; pub(crate) fn get_definitions(inp: &mut Definitions) { inp.add( @@ -17,7 +18,7 @@ pub(crate) fn get_definitions(inp: &mut Definitions) { ); } -#[derive(Debug, PartialEq, FromPtrace)] +#[derive(Debug, PartialEq, FromPtrace, Serialize)] #[hstrace(hmz("Stat path {:?} returned {:?}", self.pathname, self.stat))] pub struct Stat { #[hstrace] @@ -27,7 +28,7 @@ pub struct Stat { pub stat: StatResult, } -#[derive(Debug, Clone, PartialEq, FromCStruct)] +#[derive(Debug, Clone, PartialEq, FromCStruct, Serialize)] #[hstrace(c_struct = sys_stat)] pub struct StatResult { // st_dev=makedev(0xfd, 0), diff --git a/src/call/swap.rs b/src/call/swap.rs index 415e497..ae46ec1 100644 --- a/src/call/swap.rs +++ b/src/call/swap.rs @@ -1,4 +1,5 @@ use super::prelude::*; +use serde::Serialize; pub(crate) fn get_definitions(inp: &mut Definitions) { inp.add( @@ -17,7 +18,7 @@ pub(crate) fn get_definitions(inp: &mut Definitions) { } /// Syscall: Start swapping to file/device -#[derive(Debug, PartialEq, FromPtrace)] +#[derive(Debug, PartialEq, FromPtrace, Serialize)] #[hstrace(hmz("Enable swap for path {:?} with flags {:?}", self.path, self.swapflags))] pub struct Swapon { #[hstrace] @@ -28,7 +29,7 @@ pub struct Swapon { } /// Syscall: Stop swap on file/device -#[derive(Debug, PartialEq, FromPtrace)] +#[derive(Debug, PartialEq, FromPtrace, Serialize)] #[hstrace(hmz("Disable swap path {}", self.path))] pub struct Swapoff { #[hstrace] diff --git a/src/call/unistd.rs b/src/call/unistd.rs index ed74c90..8a211f7 100644 --- a/src/call/unistd.rs +++ b/src/call/unistd.rs @@ -1,4 +1,5 @@ use super::prelude::*; +use serde::Serialize; pub(crate) fn get_definitions(inp: &mut Definitions) { inp.add( @@ -75,7 +76,7 @@ pub(crate) fn get_definitions(inp: &mut Definitions) { } /// Syscall: Read value of a symbolic link -#[derive(Debug, PartialEq, FromPtrace)] +#[derive(Debug, PartialEq, FromPtrace, Serialize)] #[hstrace(hmz("{:?} symlink points to {:?}", self.src, self.dst))] pub struct Readlink { /// Source file @@ -87,7 +88,7 @@ pub struct Readlink { pub dst: Option, } -#[derive(Debug, PartialEq, FromPtrace)] +#[derive(Debug, PartialEq, FromPtrace, Serialize)] #[hstrace(hmz("Check path {:?} permissions for mode {:?}", self.pathname, self.mode))] pub struct Access { #[hstrace] @@ -97,14 +98,14 @@ pub struct Access { pub mode: AccessMode, } -#[derive(Debug, PartialEq, FromPtrace)] +#[derive(Debug, PartialEq, FromPtrace, Serialize)] #[hstrace(hmz("Resolved current path to {:?}", self.pathname))] pub struct Getcwd { #[hstrace] pub pathname: Option, } -#[derive(Debug, PartialEq, FromPtrace)] +#[derive(Debug, PartialEq, FromPtrace, Serialize)] #[hstrace(hmz("Closed file descriptor {:?}", self.fd))] pub struct Close { /// File descriptor @@ -112,7 +113,7 @@ pub struct Close { pub fd: isize, } -#[derive(Debug, PartialEq, FromPtrace)] +#[derive(Debug, PartialEq, FromPtrace, Serialize)] #[hstrace(hmz("Request memory address expansion to {:?}", self.addr))] pub struct Brk { /// Expand to memory address @@ -120,7 +121,7 @@ pub struct Brk { pub addr: MemoryAddress, } -#[derive(Debug, PartialEq, FromPtrace)] +#[derive(Debug, PartialEq, FromPtrace, Serialize)] #[hstrace(hmz("Change working directory to {:?}", self.path))] pub struct Chdir { /// Working directory @@ -129,6 +130,7 @@ pub struct Chdir { } bitflags! { + #[derive(Serialize)] pub struct AccessMode: isize { const R_OK = 4; const W_OK = 2; diff --git a/src/call/utsname.rs b/src/call/utsname.rs index 4f48574..b67a4df 100644 --- a/src/call/utsname.rs +++ b/src/call/utsname.rs @@ -1,5 +1,6 @@ use super::prelude::*; use crate::from_c::utsname as sys_utsname; +use serde::Serialize; pub(crate) fn get_definitions(inp: &mut Definitions) { inp.add( @@ -10,14 +11,14 @@ pub(crate) fn get_definitions(inp: &mut Definitions) { ); } -#[derive(Debug, PartialEq, FromPtrace)] +#[derive(Debug, PartialEq, FromPtrace, Serialize)] #[hstrace(hmz("Detected uname to be {:?}", self.utsname))] pub struct Uname { #[hstrace(c_struct = sys_utsname)] pub utsname: Utsname, } -#[derive(Debug, Clone, PartialEq, FromCStruct)] +#[derive(Debug, Clone, PartialEq, FromCStruct, Serialize)] #[hstrace(c_struct = sys_utsname)] pub struct Utsname { #[hstrace(c_char)] diff --git a/src/clap.yml b/src/clap.yml index 8eb4fa1..4bced89 100644 --- a/src/clap.yml +++ b/src/clap.yml @@ -15,6 +15,11 @@ args: help: Run mode short: m default_value: strace + - output_file: + help: Save output to a file instead of stderr. If suffix is `.json`, will be stored in JSON-format (format subject to change) + value_name: file + short: o + takes_value: true - pid: help: PID to trace short: p diff --git a/src/lib.rs b/src/lib.rs index 354d8b1..3655bc7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,7 +7,7 @@ //! ``` //! use hstrace::prelude::*; //! -//! // initializes and strats tracing +//! // initializes and starts tracing //! let mut tracer = HStraceBuilder::new() //! .pid(1000).build(); //! tracer.start(); @@ -50,11 +50,13 @@ mod syscall; mod enums; mod from_c; +mod output; pub mod ptrace; mod trace; mod trace_grouper; mod traits; pub mod value; +pub use output::*; use crate::traits::hmz_format; pub use call::SyscallKind; @@ -62,11 +64,10 @@ pub(crate) use ptrace::Tracer; pub use syscall::*; pub use trace::*; -/// Result of the syscall invocation. If syscall returns `-1`, `errno` is resolved into specific `SyscallError` enum variant -pub type SyscallResult = Result<(), SyscallError>; +use serde::Serialize; /// Resolved system call -#[derive(Debug)] +#[derive(Serialize, Debug)] pub struct Syscall { /// Call that was made (enum variant). Resolved to correct one in 95%+ of cases. If not known, contains `Ident::Unknown` pub name: Ident, @@ -75,11 +76,11 @@ pub struct Syscall { pub kind: SyscallKind, /// Result of the syscall (success, or an error) - pub result: SyscallResult, + pub result: Result<(), SyscallError>, } impl Syscall { - pub(crate) fn new(name: Ident, kind: SyscallKind, result: SyscallResult) -> Self { + pub(crate) fn new(name: Ident, kind: SyscallKind, result: Result<(), SyscallError>) -> Self { Syscall { name, kind, result } } diff --git a/src/macros.rs b/src/macros.rs index e81a303..f95a9e2 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -31,7 +31,7 @@ macro_rules! define_calls { /// /// Currently only a subset of syscalls are resolved to these expanded structures /// Use this [GitHub issue 3](https://github.com/blaind/hstrace/issues/3) to request a new syscall implementation - #[derive(Debug, PartialEq)] + #[derive(Debug, PartialEq, Serialize)] #[allow(non_camel_case_types)] pub enum SyscallKind { $($kind($kind),)* @@ -48,13 +48,13 @@ macro_rules! define_calls { } impl From for Syscall { - fn from(sv: TraceOutput) -> Syscall { + fn from(mut sv: TraceOutput) -> Syscall { let name = FromPrimitive::from_usize(sv.nr).expect("primitive conversion"); - let result = match &sv.out { + let result = match sv.out.take() { Some(res) => match res { Ok(_) => Ok(()), // FIXME implement value - Err(e) => Err(e.clone()), + Err(e) => Err(e), } None => Ok(()) }; @@ -135,7 +135,7 @@ macro_rules! define_callnames { $($syscall:ident = $position:tt,)+ ) => { #[allow(dead_code)] - #[derive(FromPrimitive, Debug, PartialEq, Clone, Copy)] + #[derive(FromPrimitive, Debug, PartialEq, Clone, Copy, Serialize)] pub enum Ident { /// Unknown call, nr could not be parsed into an enum (missing implementation at hstrace) Unknown = -1, diff --git a/src/main.rs b/src/main.rs index 94cc9ee..8dd14b4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,21 +1,29 @@ use clap::App; use colored::Colorize; use env_logger; -use std::env; +use std::{env, path::PathBuf}; -use hstrace::{FilterMode, HStrace, TraceOptions, TraceType}; +use hstrace::{FilterMode, HStrace, Output, TraceOptions, TraceType}; fn main() { init_logger(); let mut options = parse_settings(); - let mut hstrace = initialize_hstrace(&mut options); + log::debug!("Parsed options: {:#?}", options); + + let is_json = match options.display_mode { + DisplayMode::Json => true, + _ => false, + }; + + let mut out = Output::new(&options.output_file, is_json); + let mut hstrace = initialize_hstrace(&mut options, &mut out); if let Err(e) = hstrace.start() { - println!("{}: {:?}", format!("Trace failed").red(), e); + out.write(format!("{}: {:?}", format!("Trace failed").red(), e)); return; }; - display_output(&options, hstrace); + display_output(&options, hstrace, &mut out); } fn init_logger() { @@ -30,13 +38,15 @@ fn register_quit_channel() -> crossbeam_channel::Receiver<()> { let (quit_sender, exit_receiver) = crossbeam_channel::bounded(1); ctrlc::set_handler(move || { - println!("Received ctrl-c, quitting..."); + eprintln!("Received ctrl-c, quitting..."); if let Err(e) = quit_sender.send(()) { log::debug!( "Could not send quit_sender msg, possibly receiving end died already: {:?}", e ); } + + // FIXME: should reset terminal here, e.g. after -o test.txt top ctrl-c will fail }) .unwrap(); @@ -66,7 +76,7 @@ fn parse_settings() -> ParsedOptions { } }; - let display_mode = match m.value_of("mode").unwrap() { + let mut display_mode = match m.value_of("mode").unwrap() { "human" => DisplayMode::Human, "devnull" => DisplayMode::DevNull, "strace" => DisplayMode::Strace, @@ -76,6 +86,14 @@ fn parse_settings() -> ParsedOptions { let mut filter_calls = Vec::new(); + let output_file = m.value_of("output_file").map(|f| PathBuf::from(f)); + if let Some(path) = &output_file { + if path.extension() == Some(std::ffi::OsStr::new("json")) { + log::debug!("Setting DisplayMode to Json, because file suffix is .json"); + display_mode = DisplayMode::Json; + } + } + // FIXME move to loop let _expr: Vec = m .values_of("expr") @@ -113,36 +131,41 @@ fn parse_settings() -> ParsedOptions { trace_type: Some(trace_type), display_mode, trace_options: Some(trace_options), + output_file, } } +#[derive(Debug)] enum DisplayMode { + Json, Human, DevNull, Strace, Grouped, } +#[derive(Debug)] struct ParsedOptions { display_mode: DisplayMode, trace_type: Option, trace_options: Option, + output_file: Option, } -fn initialize_hstrace(options: &mut ParsedOptions) -> HStrace { +fn initialize_hstrace(options: &mut ParsedOptions, out: &mut Output) -> HStrace { let trace_type = options.trace_type.take().unwrap(); match &trace_type { TraceType::Program(prog, args) => { - println!( + out.write(format!( "Tracing program {} with args {}", prog.cyan(), - format!("{:?}", args).cyan() - ); + format!("{:?}", args).cyan(), + )); } TraceType::Pid(pid) => { - println!("Tracing PID {}", format!("{}", pid).cyan()); + out.write(format!("Tracing PID {}", format!("{}", pid).cyan())); } }; @@ -155,13 +178,19 @@ fn initialize_hstrace(options: &mut ParsedOptions) -> HStrace { hstrace } -fn display_output(options: &ParsedOptions, mut hstrace: HStrace) { +fn display_output(options: &ParsedOptions, mut hstrace: HStrace, out: &mut Output) { let max_msg_count = 4_000_000_000_000_000; // FIXME match &options.display_mode { DisplayMode::Human => { for msg in hstrace.iter_as_syscall().take(max_msg_count) { - println!("{}", msg.fmt_human()); + out.write(format!("{}", msg.fmt_human())); + } + } + + DisplayMode::Json => { + for msg in hstrace.iter_as_syscall().take(max_msg_count) { + out.write_json(serde_json::to_string(&msg).unwrap()); } } @@ -173,23 +202,23 @@ fn display_output(options: &ParsedOptions, mut hstrace: HStrace) { DisplayMode::Strace => { for msg in hstrace.iter().take(max_msg_count) { - println!("{:?}", msg); + out.write(format!("{:?}", msg)); } } DisplayMode::Grouped => { for msg in hstrace.iter_grouped().take(max_msg_count) { if msg.calls.len() == 1 { - println!("{}", format!("{:?}", msg.calls[0])); + out.write(format!("{}", format!("{:?}", msg.calls[0]))); } else { - println!("{}", "File operations for file something"); + out.write(format!("{}", "File operations for file something")); for call in msg.calls { - println!(" {:?}", call); + out.write(format!(" {:?}", call)); } } } } } - hstrace.print_totals(); + hstrace.print_totals(out); } diff --git a/src/output.rs b/src/output.rs new file mode 100644 index 0000000..72b9eef --- /dev/null +++ b/src/output.rs @@ -0,0 +1,59 @@ +use std::{ + fs, + io::{self, Write}, + path::PathBuf, +}; + +pub struct Output { + inner: Box, + is_json: bool, + write_no: usize, +} + +impl Output { + pub fn new(output_file: &Option, is_json: bool) -> Self { + let mut inner: Box = match output_file { + Some(file) => { + let file = fs::File::create(file).unwrap(); + Box::new(file) + } + None => Box::new(io::stdout()), + }; + + if is_json { + write!(inner, "[\n").unwrap(); + } + + Self { + inner, + is_json, + write_no: 0, + } + } + + pub fn write(&mut self, message: String) { + if self.is_json { + return; + } + + write!(self.inner, "{}\n", message).unwrap(); + } + + pub fn write_json(&mut self, message: String) { + if self.write_no > 0 { + write!(self.inner, ",\n").unwrap(); + } + + write!(self.inner, "{}", message).unwrap(); + + self.write_no += 1; + } +} + +impl Drop for Output { + fn drop(&mut self) { + if self.is_json { + write!(self.inner, "]\n").unwrap(); + } + } +} diff --git a/src/syscall/error.rs b/src/syscall/error.rs index d81c79a..be1ae23 100644 --- a/src/syscall/error.rs +++ b/src/syscall/error.rs @@ -1,5 +1,37 @@ /// This is re-export of `nix::errno::Errno` -pub use nix::errno::Errno as SyscallError; +pub use nix::errno::Errno; + +use serde::Serialize; + +#[derive(Debug, PartialEq)] +pub struct SyscallError { + errno: Errno, +} + +impl Serialize for SyscallError { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_i32(self.errno as i32) + } +} + +impl SyscallError { + pub fn from_i32(ret: i32) -> Self { + Self { + errno: Errno::from_i32(ret), + } + } + + pub fn from_errno(errno: Errno) -> Self { + Self { errno } + } + + pub fn to_errno(&self) -> Errno { + self.errno + } +} #[cfg(test)] mod tests { @@ -8,9 +40,9 @@ mod tests { #[test] pub fn test_syscall_error() { let err: SyscallError = SyscallError::from_i32(32); - assert_eq!(err, SyscallError::EPIPE); + assert_eq!(err.to_errno(), Errno::EPIPE); let err: SyscallError = SyscallError::from_i32(32323232); - assert_eq!(err, SyscallError::UnknownErrno); + assert_eq!(err.to_errno(), Errno::UnknownErrno); } } diff --git a/src/syscall/x86_64.rs b/src/syscall/x86_64.rs index af749db..aa10447 100644 --- a/src/syscall/x86_64.rs +++ b/src/syscall/x86_64.rs @@ -1,3 +1,4 @@ +use serde::Serialize; use std::fmt; use std::slice::Iter; diff --git a/src/trace/hstrace_impl.rs b/src/trace/hstrace_impl.rs index 3d45442..f13e7ae 100644 --- a/src/trace/hstrace_impl.rs +++ b/src/trace/hstrace_impl.rs @@ -2,6 +2,7 @@ use colored::Colorize; use crossbeam_channel; use std::sync::mpsc; +use crate::Output; use crate::TraceError; use crate::{ ptrace::{SyscallPtrace, Tracer}, @@ -94,18 +95,18 @@ impl HStrace { .map(|message| Syscall::from(message)) } - pub fn print_totals(&self) { - println!( + pub fn print_totals(&self, out: &mut Output) { + out.write(format!( "{}", format!("-------------------------------------------------------------").blue() - ); - println!( + )); + out.write(format!( "{}: 0, {}: 2352kB", format!("Pids").magenta(), format!("Max memory usage").magenta() - ); + )); - println!( + out.write(format!( "Network: {}, {}", format!( "{} (main, {}/{})", @@ -119,22 +120,22 @@ impl HStrace { "52b".green(), "52b".green() ), - ); + )); - println!( + out.write(format!( "Files: {}, {}, (5 supressed)", format!("{} ({})", "/etc/passwd".cyan(), "RW".red(),), format!("{} ({})", "/usr/include/test.h".cyan(), "R".green(),), - ); + )); - println!( + out.write(format!( "{}: run with --file-all to view all files", format!("Info").magenta() - ); + )); - println!( + out.write(format!( "^ above information is not real data, but displays a possibility of adding a summary" - ); + )); } } diff --git a/src/trace/output.rs b/src/trace/output.rs index 2f16af7..c0aa578 100644 --- a/src/trace/output.rs +++ b/src/trace/output.rs @@ -113,7 +113,7 @@ impl fmt::Debug for TraceOutput { let out_var = match self.out.as_ref() { Some(o) => match o { Ok(o) => format!("{:?}", o).green(), - Err(e) => format!("-1 {:?} ({})", e, e.desc()).magenta(), + Err(e) => format!("-1 {:?} ({})", e, e.to_errno().desc()).magenta(), }, None => format!("?").yellow(), }; diff --git a/src/trace/thread.rs b/src/trace/thread.rs index 0e93a34..965bdb0 100644 --- a/src/trace/thread.rs +++ b/src/trace/thread.rs @@ -218,7 +218,7 @@ mod tests { use super::*; use crate::ptrace::MockPtrace; - use crate::SyscallError; + use crate::Errno; fn init() { let _ = env_logger::builder().is_test(true).try_init(); @@ -327,7 +327,10 @@ mod tests { ]) .unwrap(); - assert_eq!(output.out.unwrap().unwrap_err(), SyscallError::UnknownErrno); + assert_eq!( + output.out.unwrap().unwrap_err().to_errno(), + Errno::UnknownErrno + ); } #[test] diff --git a/src/value/kind/memory_address.rs b/src/value/kind/memory_address.rs index fdfeeeb..e466701 100644 --- a/src/value/kind/memory_address.rs +++ b/src/value/kind/memory_address.rs @@ -1,6 +1,7 @@ +use serde::Serialize; use std::fmt; -#[derive(Clone, PartialEq)] +#[derive(Clone, PartialEq, Serialize)] pub struct MemoryAddress(pub usize); impl fmt::Debug for MemoryAddress { diff --git a/src/value/mod.rs b/src/value/mod.rs index 16c6b39..6845e31 100644 --- a/src/value/mod.rs +++ b/src/value/mod.rs @@ -1,5 +1,6 @@ use std::convert::TryFrom; +use crate::Errno; use crate::{SyscallError, VarType}; mod transformer; @@ -55,10 +56,10 @@ pub(crate) fn map_output(out: &AV, is_error: u8, rval: i64) -> Result 0 { match i32::try_from( rval.checked_mul(-1) - .ok_or_else(|| SyscallError::UnknownErrno)?, + .ok_or_else(|| SyscallError::from_errno(Errno::UnknownErrno))?, ) { Ok(val) => Err(SyscallError::from_i32(val)), - Err(_) => return Err(SyscallError::UnknownErrno), + Err(_) => return Err(SyscallError::from_errno(Errno::UnknownErrno)), } } else { Ok(match out { @@ -79,16 +80,20 @@ mod tests { #[test] pub fn test_map_output_overflow() { assert_eq!( - map_output(&AV::Int(Direction::Out), 23, -9223372036854775808).unwrap_err(), - SyscallError::UnknownErrno + map_output(&AV::Int(Direction::Out), 23, -9223372036854775808) + .unwrap_err() + .to_errno(), + Errno::UnknownErrno ); } #[test] pub fn test_map_output() { assert_eq!( - map_output(&AV::Int(Direction::Out), 23, -1).unwrap_err(), - SyscallError::EPERM + map_output(&AV::Int(Direction::Out), 23, -1) + .unwrap_err() + .to_errno(), + Errno::EPERM ); match map_output(&AV::Int(Direction::Out), 0, 50).unwrap() { diff --git a/tests/test_c_binary.rs b/tests/test_c_binary.rs index 74a1e5b..30e5115 100644 --- a/tests/test_c_binary.rs +++ b/tests/test_c_binary.rs @@ -3,7 +3,7 @@ use std::env; use hstrace::value::kind::MemoryAddress; use hstrace::{call, Ident}; -use hstrace::{prelude::*, HStraceBuilder}; +use hstrace::{prelude::*, Errno, HStraceBuilder, SyscallError}; fn init() { let _ = env_logger::builder().is_test(true).try_init(); @@ -147,7 +147,7 @@ fn test_unistd() { src: "/tmp/link_src".into(), dst: None, }, - Err(SyscallError::ENOENT) + Err(SyscallError::from_errno(Errno::ENOENT)) ); test_syscall!(iterator, ExitGroup); @@ -169,7 +169,7 @@ fn test_fncntl() { pathname: "/tmp/hstrace.test".into(), flags: call::OpenatMode::O_WRONLY | call::OpenatMode::O_APPEND, }, - Err(SyscallError::ENOENT) + Err(SyscallError::from_errno(Errno::ENOENT)) ); test_syscall!(iterator, ExitGroup); @@ -190,7 +190,7 @@ fn test_swap() { path: "/tmp/ptrace/swap".into(), swapflags: 65536, //call::SwapFlag::SWAP_FLAG_DISCARD, }, - Err(SyscallError::EPERM) + Err(SyscallError::from_errno(Errno::EPERM)) ); test_syscall!( @@ -199,7 +199,7 @@ fn test_swap() { call::Swapoff { path: "/tmp/ptrace/swap".into(), }, - Err(SyscallError::EPERM) + Err(SyscallError::from_errno(Errno::EPERM)) ); test_syscall!(iterator, ExitGroup);