├── .gitignore ├── .travis.yml ├── Cargo.toml ├── README.md ├── gtrace-derive ├── Cargo.toml └── src │ └── lib.rs └── src ├── arch.rs ├── bin └── gtrace.rs ├── decode.rs ├── lib.rs └── syscall.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | rust: 3 | - stable 4 | - beta 5 | - nightly 6 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "gtrace" 3 | version = "0.1.0" 4 | authors = ["Geoffrey Thomas "] 5 | 6 | [dependencies] 7 | nix = { git = "https://github.com/nix-rust/nix" } 8 | libc = "0.2" 9 | serde = "0.9" 10 | serde_derive = "0.9" 11 | serde_json = "0.9" 12 | gtrace-derive = { path = "gtrace-derive" } 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | gtrace 2 | === 3 | 4 | gtrace is a library for implementing strace-like behavior, and a 5 | reference client that works like strace. 6 | 7 | Thanks 8 | === 9 | 10 | The following sources were very helpful in learning how to do this: 11 | 12 | * Nelson Elhage's [Write yourself an strace in 70 lines of code](https://blog.nelhage.com/2010/08/write-yourself-an-strace-in-70-lines-of-code/) 13 | * Joe Kain's [Loading and ptrac'ing a process in Rust](http://system.joekain.com/2015/07/15/rust-load-and-ptrace.html) and related blog posts 14 | * the [ptrace(2) man page](http://man7.org/linux/man-pages/man2/ptrace.2.html), in particular the extended description [written by Denys Vlasenko](https://git.kernel.org/pub/scm/docs/man-pages/man-pages.git/commit/?id=4d12a715f2780abaecb4001e50be3ac6e915cbba) 15 | -------------------------------------------------------------------------------- /gtrace-derive/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "gtrace-derive" 3 | version = "0.1.0" 4 | authors = ["Geoffrey Thomas "] 5 | 6 | [dependencies] 7 | syn = "0.11.9" 8 | quote = "0.3.15" 9 | 10 | [lib] 11 | proc-macro = true 12 | -------------------------------------------------------------------------------- /gtrace-derive/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate proc_macro; 2 | extern crate syn; 3 | #[macro_use] 4 | extern crate quote; 5 | 6 | use proc_macro::TokenStream; 7 | 8 | #[proc_macro_derive(Syscall, attributes(syscall))] 9 | pub fn derive_syscall(input: TokenStream) -> TokenStream { 10 | let ast = syn::parse_derive_input(&input.to_string()).unwrap(); 11 | let gen = match ast.body { 12 | syn::Body::Struct(_) => panic!("Syscall should be an enum"), 13 | syn::Body::Enum(variants) => handle(&ast.ident, variants) 14 | }; 15 | gen.parse().unwrap() 16 | } 17 | 18 | fn handle(name: &syn::Ident, variants: Vec) -> quote::Tokens { 19 | let mut arms = vec![]; 20 | for var in variants { 21 | let ident = &var.ident; 22 | let fields = match var.data { 23 | syn::VariantData::Struct(ref v) => v, 24 | _ => panic!("Syscall should use only struct variants"), 25 | }; 26 | let fieldnames: Vec<_> = fields.iter().map(|f| &f.ident).collect(); 27 | let fieldnames2 = fieldnames.clone(); 28 | let mut writefmt = format!("{}(", ident).to_lowercase(); 29 | let mut len = fields.len(); 30 | if is_unknown(&var) { 31 | writefmt = "syscall_{}(".to_owned(); 32 | len -= 1; 33 | } 34 | writefmt.push_str(&vec!["{}"; len].join(", ")); 35 | writefmt.push_str(")"); 36 | let writefmtlit = syn::Lit::Str(writefmt, syn::StrStyle::Cooked); 37 | arms.push(quote! { 38 | &#name::#ident {#(ref #fieldnames),*} => write!(formatter, #writefmtlit, #(#fieldnames2),*) 39 | }); 40 | } 41 | quote! { 42 | impl ::std::fmt::Display for #name { 43 | fn fmt(&self, formatter: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { 44 | match self { 45 | #(#arms,)* 46 | } 47 | } 48 | } 49 | } 50 | } 51 | 52 | fn is_unknown(var: &syn::Variant) -> bool { 53 | for attr in &var.attrs { 54 | if let syn::MetaItem::List(ref name, ref items) = attr.value { 55 | if name == "syscall" { 56 | for subattr in items { 57 | if let &syn::NestedMetaItem::MetaItem(syn::MetaItem::Word(ref ident)) = subattr { 58 | if ident == "unknown" { 59 | return true; 60 | } 61 | } 62 | } 63 | } 64 | } 65 | } 66 | return false; 67 | } 68 | -------------------------------------------------------------------------------- /src/arch.rs: -------------------------------------------------------------------------------- 1 | pub mod x86_64 { 2 | pub const R15: usize = 0; 3 | pub const R14: usize = 8; 4 | pub const R13: usize = 16; 5 | pub const R12: usize = 24; 6 | pub const RBP: usize = 32; 7 | pub const RBX: usize = 40; 8 | pub const R11: usize = 48; 9 | pub const R10: usize = 56; 10 | pub const R9: usize = 64; 11 | pub const R8: usize = 72; 12 | pub const RAX: usize = 80; 13 | pub const RCX: usize = 88; 14 | pub const RDX: usize = 96; 15 | pub const RSI: usize = 104; 16 | pub const RDI: usize = 112; 17 | pub const ORIG_RAX: usize = 120; 18 | pub const RIP: usize = 128; 19 | pub const CS: usize = 136; 20 | pub const EFLAGS: usize = 144; 21 | pub const RSP: usize = 152; 22 | pub const SS: usize = 160; 23 | pub const FS_BASE: usize = 168; 24 | pub const GS_BASE: usize = 176; 25 | pub const DS: usize = 184; 26 | pub const ES: usize = 192; 27 | pub const FS: usize = 200; 28 | pub const GS: usize = 208; 29 | 30 | pub const PAGE_SIZE: usize = 4096; 31 | } 32 | -------------------------------------------------------------------------------- /src/bin/gtrace.rs: -------------------------------------------------------------------------------- 1 | extern crate libc; 2 | extern crate gtrace; 3 | extern crate nix; 4 | 5 | use std::os::unix::process::CommandExt; 6 | use std::process::Command; 7 | use nix::sys::wait::waitpid; 8 | 9 | use gtrace::TraceEvent; 10 | 11 | fn main() { 12 | let mut args = std::env::args_os().skip(1); 13 | let mut cmd = Command::new(args.next().expect("usage: gtrace cmd [args...]")); 14 | for arg in args { 15 | cmd.arg(arg); 16 | } 17 | cmd.before_exec(gtrace::traceme); 18 | let mut child = cmd.spawn().expect("child process failed"); 19 | let pid = nix::unistd::Pid::from_raw(child.id() as libc::pid_t); 20 | let mut tracee = gtrace::Tracee::new(pid); 21 | loop { 22 | match tracee.step(waitpid(pid, None).unwrap()) { 23 | TraceEvent::SysEnter => print!("{}", gtrace::decode::decode(&mut tracee).unwrap()), 24 | TraceEvent::SysExit => println!(" = {}", tracee.get_return().unwrap()), 25 | TraceEvent::Signal(sig) => println!("** signal {:?}", sig), 26 | TraceEvent::Exit(_) => { break; } 27 | } 28 | tracee.run().unwrap(); 29 | } 30 | //child.wait().unwrap(); 31 | } 32 | -------------------------------------------------------------------------------- /src/decode.rs: -------------------------------------------------------------------------------- 1 | use libc::PATH_MAX; 2 | use nix::Result; 3 | 4 | use Tracee; 5 | use syscall::{Buffer, Syscall}; 6 | 7 | fn parse_read(tracee: &mut Tracee) -> Result { 8 | let fd = tracee.get_arg(0)?; 9 | let buf = Buffer { addr: tracee.get_arg(1)?, 10 | data: None }; 11 | let count = tracee.get_arg(2)?; 12 | Ok(Syscall::Read { fd: fd, buf: buf, count: count }) 13 | } 14 | 15 | fn parse_write(tracee: &mut Tracee) -> Result { 16 | let fd = tracee.get_arg(0)?; 17 | let addr = tracee.get_arg(1)?; 18 | let count = tracee.get_arg(2)?; 19 | let data = tracee.copy_from(addr as usize, count as usize)?; 20 | let buf = Buffer { addr: addr, 21 | data: Some(data) }; 22 | Ok(Syscall::Write { fd: fd, buf: buf, count: count }) 23 | } 24 | 25 | fn parse_open(tracee: &mut Tracee) -> Result { 26 | let addr = tracee.get_arg(0)?; 27 | let data = tracee.strncpy_from(addr as usize, PATH_MAX as usize)?.0; 28 | let pathname = Buffer { addr: addr, 29 | data: Some(data) }; 30 | let flags = tracee.get_arg(1)?; 31 | let mode = if flags & (::libc::O_CREAT as u64) != 0 { tracee.get_arg(2)? } else { 0 }; 32 | Ok(Syscall::Open { pathname: pathname, flags: flags, mode: mode }) 33 | } 34 | 35 | fn parse_close(tracee: &mut Tracee) -> Result { 36 | Ok(Syscall::Close { fd: tracee.get_arg(0)? }) 37 | } 38 | 39 | fn parse_stat(tracee: &mut Tracee) -> Result { 40 | let addr = tracee.get_arg(0)?; 41 | let data = tracee.strncpy_from(addr as usize, PATH_MAX as usize)?.0; 42 | let pathname = Buffer { addr : addr, 43 | data: Some(data) }; 44 | let buf = tracee.get_arg(1)?; 45 | Ok(Syscall::Stat { pathname: pathname, buf: buf }) 46 | } 47 | 48 | fn parse_fstat(tracee: &mut Tracee) -> Result { 49 | let fd = tracee.get_arg(0)?; 50 | let buf = tracee.get_arg(1)?; 51 | Ok(Syscall::Fstat { fd: fd, buf: buf }) 52 | } 53 | 54 | fn parse_lstat(tracee: &mut Tracee) -> Result { 55 | let addr = tracee.get_arg(0)?; 56 | let data = tracee.strncpy_from(addr as usize, PATH_MAX as usize)?.0; 57 | let pathname = Buffer { addr : addr, 58 | data: Some(data) }; 59 | let buf = tracee.get_arg(1)?; 60 | Ok(Syscall::Lstat { pathname: pathname, buf: buf }) 61 | } 62 | 63 | fn parse_unknown(tracee: &mut Tracee, nr: u64) -> Result { 64 | Ok(Syscall::Unknown { 65 | nr: nr, 66 | a: tracee.get_arg(0)?, 67 | b: tracee.get_arg(1)?, 68 | c: tracee.get_arg(2)?, 69 | d: tracee.get_arg(3)?, 70 | e: tracee.get_arg(4)?, 71 | f: tracee.get_arg(5)?, 72 | }) 73 | } 74 | 75 | // TODO following need to move to arch 76 | 77 | /// Decode a syscall on entry. 78 | pub fn decode(tracee: &mut Tracee) -> Result { 79 | match tracee.get_syscall()? { 80 | 0 => parse_read(tracee), 81 | 1 => parse_write(tracee), 82 | 2 => parse_open(tracee), 83 | 3 => parse_close(tracee), 84 | 4 => parse_stat(tracee), 85 | 5 => parse_fstat(tracee), 86 | 6 => parse_lstat(tracee), 87 | /* 88 | 8 => parse_lseek(tracee), 89 | 9 => parse_mmap(tracee), 90 | 10 => parse_mprotect(tracee), 91 | */ 92 | other => parse_unknown(tracee, other) 93 | } 94 | } 95 | 96 | /// Fill in the remainder of a syscall on exit. 97 | pub fn fixup(syscall: Syscall) -> Syscall { 98 | syscall 99 | } 100 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate serde; 2 | 3 | #[macro_use] 4 | extern crate serde_derive; 5 | 6 | #[macro_use] 7 | extern crate gtrace_derive; 8 | 9 | extern crate libc; 10 | extern crate nix; 11 | 12 | use nix::unistd::Pid; 13 | use nix::sys::wait::WaitStatus; 14 | use nix::sys::ptrace; 15 | use nix::Result; 16 | 17 | pub mod arch; 18 | pub mod decode; 19 | pub mod syscall; 20 | 21 | pub fn traceme() -> std::io::Result<()> { 22 | match ptrace::traceme() { 23 | Ok(()) => Ok(()), 24 | Err(::nix::Error::Sys(errno)) => Err(std::io::Error::from_raw_os_error(errno as i32)), 25 | Err(e) => Err(std::io::Error::new(std::io::ErrorKind::Other, e)) 26 | } 27 | } 28 | 29 | pub enum TraceEvent { 30 | SysEnter, 31 | SysExit, 32 | Signal(u8), 33 | Exit(i32), 34 | } 35 | 36 | enum State { 37 | Userspace, 38 | Kernelspace, 39 | } 40 | 41 | pub struct Tracee { 42 | pid: Pid, 43 | state: State, 44 | } 45 | 46 | impl Tracee { 47 | pub fn new(pid: Pid) -> Tracee { 48 | ptrace::setoptions(pid, ptrace::PTRACE_O_TRACESYSGOOD | 49 | ptrace::PTRACE_O_TRACEEXEC).unwrap(); 50 | Tracee { 51 | pid: pid, 52 | state: State::Userspace, 53 | } 54 | } 55 | 56 | pub fn step(&mut self, status: WaitStatus) -> TraceEvent { 57 | match status { 58 | WaitStatus::PtraceSyscall(_) => { 59 | match self.state { 60 | State::Userspace => { 61 | self.state = State::Kernelspace; 62 | TraceEvent::SysEnter 63 | }, 64 | State::Kernelspace => { 65 | self.state = State::Userspace; 66 | TraceEvent::SysExit 67 | }, 68 | } 69 | } 70 | WaitStatus::Exited(_, status) => TraceEvent::Exit(status), 71 | WaitStatus::Stopped(_, sig) => TraceEvent::Signal(sig as u8), 72 | _ => panic!("unexpected status {:?}", status) 73 | } 74 | } 75 | 76 | pub fn run(&mut self) -> Result<()> { 77 | ptrace::syscall(self.pid) 78 | } 79 | 80 | pub fn get_syscall(&mut self) -> Result { 81 | unsafe { ptrace::ptrace(ptrace::Request::PTRACE_PEEKUSER, self.pid, 82 | arch::x86_64::ORIG_RAX as *mut _, 0 as *mut _).map(|x| x as u64) } 83 | } 84 | 85 | pub fn get_arg(&mut self, reg: u8) -> Result { 86 | let offset = match reg { 87 | 0 => arch::x86_64::RDI, 88 | 1 => arch::x86_64::RSI, 89 | 2 => arch::x86_64::RDX, 90 | 3 => arch::x86_64::R10, 91 | 4 => arch::x86_64::R8, 92 | 5 => arch::x86_64::R9, 93 | _ => panic!("there aren't that many registers") 94 | }; 95 | unsafe { ptrace::ptrace(ptrace::Request::PTRACE_PEEKUSER, self.pid, 96 | offset as *mut _, 0 as *mut _).map(|x| x as u64) } 97 | } 98 | 99 | pub fn get_return(&mut self) -> Result { 100 | unsafe { ptrace::ptrace(ptrace::Request::PTRACE_PEEKUSER, self.pid, 101 | arch::x86_64::RAX as *mut _, 0 as *mut _) } 102 | } 103 | 104 | /// Read len bytes from addr. May return fewer than len bytes if 105 | /// part of the range is unmapped, or an error if addr itself is 106 | /// unmapped. 107 | pub fn copy_from(&mut self, addr: usize, len: usize) -> Result> { 108 | use nix::sys::uio::*; 109 | 110 | let mut res = Vec::with_capacity(len); 111 | unsafe { 112 | res.set_len(len); 113 | let target: Vec<_> = PageIter::new(addr, len, arch::x86_64::PAGE_SIZE) 114 | .map(|(a, l)| RemoteIoVec { base: a, len: l }) 115 | .collect(); 116 | let n = { 117 | try!(process_vm_readv(self.pid, 118 | &mut [IoVec::from_mut_slice(&mut res)], 119 | &target[..])) 120 | }; 121 | res.set_len(n); 122 | } 123 | Ok(res) 124 | } 125 | 126 | /// Read a NUL-terminated C string of up to len bytes from addr. 127 | /// Returns an error if none of the string could be read, 128 | /// Ok(vec, false) if some of the string could be read but we ran into an unmapped page, or 129 | /// Ok(vec, true) if all of the string could be read, up to either len or 130 | /// the terminating NUL. As with strncpy, if there is no terminating NUL in 131 | /// the first len bytes, no terminating NUL will be in the output. 132 | pub fn strncpy_from(&mut self, addr: usize, len: usize) -> Result<(Vec, bool)> { 133 | use nix::sys::uio::*; 134 | use nix::Error::Sys; 135 | use nix::Errno::EFAULT; 136 | 137 | let mut remote_pages = PageIter::new(addr, len, arch::x86_64::PAGE_SIZE); 138 | 139 | let mut res = Vec::with_capacity(len); 140 | 141 | // Read the first chunk, returning an error if it didn't work 142 | match remote_pages.next() { 143 | None => { return Ok((Vec::new(), true)); } 144 | Some((chunkaddr, chunklen)) => unsafe { 145 | res.set_len(chunklen); 146 | let n = { 147 | try!(process_vm_readv(self.pid, 148 | &mut [IoVec::from_mut_slice(&mut res)], 149 | &[RemoteIoVec { base: chunkaddr, len: chunklen }])) 150 | }; 151 | res.set_len(n); 152 | } 153 | } 154 | 155 | let mut oldlen = 0; 156 | 157 | loop { 158 | // Try to find a terminating NUL 159 | if let Some(nul) = res[oldlen..].iter().position(|&b| b == 0) { 160 | res.truncate(oldlen + nul + 1); 161 | return Ok((res, true)); 162 | } 163 | oldlen = res.len(); 164 | 165 | // If not, read the next page, but don't error on EFAULT, 166 | // just report the partial read. Report all other errors as 167 | // errors. 168 | match remote_pages.next() { 169 | None => { return Ok((res, true)); } 170 | Some((chunkaddr, chunklen)) => unsafe { 171 | res.set_len(oldlen + chunklen); 172 | match { process_vm_readv(self.pid, 173 | &mut [IoVec::from_mut_slice(&mut res[oldlen..])], 174 | &[RemoteIoVec { base: chunkaddr, len: chunklen }]) } { 175 | Ok(n) => { res.set_len(n); } 176 | Err(Sys(EFAULT)) => { return Ok((res, false)); } 177 | Err(e) => { return Err(e); } 178 | } 179 | } 180 | } 181 | } 182 | } 183 | } 184 | 185 | struct PageIter { 186 | addr: usize, 187 | max_addr: usize, 188 | page_size: usize, 189 | } 190 | 191 | impl PageIter { 192 | fn new(addr: usize, len: usize, page_size: usize) -> PageIter { 193 | PageIter { addr: addr, max_addr: addr + len, page_size: page_size } 194 | } 195 | } 196 | 197 | impl Iterator for PageIter { 198 | type Item = (usize, usize); 199 | 200 | fn next(&mut self) -> Option<(usize, usize)> { 201 | if self.addr == self.max_addr { 202 | return None; 203 | } 204 | let mut len = self.page_size - (self.addr % self.page_size); 205 | if self.addr + len > self.max_addr { 206 | len = self.max_addr - self.addr; 207 | } 208 | let ret = Some((self.addr, len)); 209 | self.addr += len; 210 | ret 211 | } 212 | } 213 | 214 | #[test] 215 | fn test_page_iter() { 216 | fn check(addr: usize, len: usize, v: Vec<(usize, usize)>) { 217 | let iov: Vec<(usize, usize)> = PageIter::new(addr, len, 10).collect(); 218 | assert_eq!(iov, v); 219 | } 220 | 221 | check(3, 5, vec![(3, 5)]); 222 | check(3, 21, vec![(3, 7), (10, 10), (20, 4)]); 223 | check(10, 10, vec![(10, 10)]); 224 | check(10, 11, vec![(10, 10), (20, 1)]); 225 | check(10, 0, vec![]); 226 | } 227 | -------------------------------------------------------------------------------- /src/syscall.rs: -------------------------------------------------------------------------------- 1 | #[derive(Serialize, Deserialize)] 2 | pub struct Buffer { 3 | pub addr: u64, 4 | pub data: Option>, 5 | } 6 | 7 | impl ::std::fmt::Display for Buffer { 8 | fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { 9 | if let Some(ref data) = self.data { 10 | write!(f, "{:?}", String::from_utf8_lossy(data)) 11 | } else { 12 | write!(f, "{:x}", self.addr) 13 | } 14 | } 15 | } 16 | 17 | #[derive(Serialize, Deserialize, Syscall)] 18 | pub enum Syscall { 19 | Read { fd: u64, buf: Buffer, count: u64 }, 20 | Write { fd: u64, buf: Buffer, count: u64 }, 21 | Open { pathname: Buffer, flags: u64, mode: u64 }, // XXX Option 22 | Close { fd: u64 }, 23 | Stat { pathname: Buffer, buf: u64 }, 24 | Fstat { fd: u64, buf: u64 }, 25 | Lstat { pathname: Buffer, buf: u64 }, 26 | #[syscall(unknown)] 27 | Unknown { nr: u64, a: u64, b: u64, c: u64, d: u64, e: u64, f: u64 } 28 | } 29 | 30 | #[derive(Serialize, Deserialize)] 31 | pub struct SyscallRecord { 32 | // time 33 | pub pid: u64, 34 | pub call: Syscall, 35 | pub result: u64, 36 | // XXX decode errno 37 | } 38 | --------------------------------------------------------------------------------