├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md └── src └── main.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "procout" 3 | version = "0.0.0" 4 | authors = ["itchyny "] 5 | description = "Peek write(2) of another process" 6 | repository = "https://github.com/itchyny/procout" 7 | readme = "README.md" 8 | license = "MIT" 9 | keywords = [] 10 | categories = ["Command line utilities"] 11 | 12 | [dependencies] 13 | libc = "0.2.27" 14 | nix = "0.9.0" 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 itchyny 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # procout 2 | _process output_ 3 | 4 | ### procout peeks write(2) of another process 5 | This is my hobby project to learn how ptrace(2) works and how can I use it from Rust. 6 | Supports Linux on x86-64. 7 | 8 | ```sh 9 | $ sudo procout [pid] 10 | ``` 11 | 12 | ![procout example](https://user-images.githubusercontent.com/375258/28751937-007fd6ec-754f-11e7-9806-30c296832874.gif) 13 | 14 | ## Author 15 | itchyny (https://github.com/itchyny) 16 | 17 | ## License 18 | This software is released under the MIT License, see LICENSE. 19 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate libc; 2 | extern crate nix; 3 | use nix::sys::ptrace::{ptrace, ptrace_setoptions}; 4 | use nix::sys::wait::waitpid; 5 | use nix::sys::wait::WaitStatus; 6 | 7 | use std::env; 8 | use std::io::Write; 9 | use std::io; 10 | use std::mem; 11 | use std::ptr; 12 | 13 | const CMD_NAME: &'static str = "procout"; 14 | 15 | fn main() { 16 | std::process::exit(match run() { 17 | Ok(_) => 0, 18 | Err(err) => { 19 | eprintln!("{}: {}", CMD_NAME, err); 20 | 1 21 | } 22 | }); 23 | } 24 | 25 | fn run() -> Result<(), String> { 26 | match env::args().nth(1).and_then(|x| x.parse().ok()) { 27 | Some(pid) => procout(nix::unistd::Pid::from_raw(pid)), 28 | None => Err(String::from("specify pid")), 29 | } 30 | } 31 | 32 | fn procout(pid: nix::unistd::Pid) -> Result<(), String> { 33 | ptrace(ptrace::PTRACE_ATTACH, pid, ptr::null_mut(), ptr::null_mut()).map_err(|e| format!("failed to ptrace attach {} ({})", pid, e))?; 34 | ptrace_setoptions(pid, ptrace::PTRACE_O_TRACESYSGOOD).map_err(|e| format!("failed to ptrace setoptions {} ({})", pid, e))?; 35 | let mut regs: libc::user_regs_struct = unsafe { mem::zeroed() }; 36 | let regs_ptr: *mut libc::user_regs_struct = &mut regs; 37 | let mut is_enter_stop: bool = false; 38 | let mut prev_orig_rax: u64 = 0; 39 | loop { 40 | match waitpid(pid, None) { 41 | Err(_) | Ok(WaitStatus::Exited(_, _)) => break, 42 | Ok(WaitStatus::PtraceSyscall(_)) => { 43 | ptrace(ptrace::PTRACE_GETREGS, pid, ptr::null_mut(), regs_ptr as *mut libc::c_void) 44 | .map_err(|e| format!("failed to ptrace getregs {} ({})", pid, e))?; 45 | is_enter_stop = if prev_orig_rax == regs.orig_rax { !is_enter_stop } else { true }; 46 | prev_orig_rax = regs.orig_rax; 47 | if regs.orig_rax == libc::SYS_write as u64 && is_enter_stop { 48 | output(peek_bytes(pid, regs.rsi, regs.rdx), regs.rdi)?; 49 | } 50 | } 51 | _ => {} 52 | } 53 | ptrace(ptrace::PTRACE_SYSCALL, pid, ptr::null_mut(), ptr::null_mut()).map_err(|e| format!("failed to ptrace syscall {} ({})", pid, e))?; 54 | } 55 | Ok(()) 56 | } 57 | 58 | fn peek_bytes(pid: nix::unistd::Pid, addr: u64, size: u64) -> Vec { 59 | let mut vec = (0..(size + 7) / 8) 60 | .filter_map(|i| { 61 | ptrace( 62 | ptrace::PTRACE_PEEKDATA, 63 | pid, 64 | (addr + 8 * i) as *mut libc::c_void, 65 | ptr::null_mut(), 66 | ).map(|l| unsafe { mem::transmute(l) }) 67 | .ok() 68 | }) 69 | .collect::>() 70 | .concat(); 71 | vec.truncate(size as usize); 72 | vec 73 | } 74 | 75 | fn output(bs: Vec, fd: u64) -> Result<(), String> { 76 | match fd { 77 | 1 => { 78 | io::stdout().write_all(bs.as_slice()).map_err(|_| "failed to write to stdout")?; 79 | io::stdout().flush().map_err(|_| "failed to flush stdout")?; 80 | } 81 | 2 => { 82 | io::stderr().write_all(bs.as_slice()).map_err(|_| "failed to write to stderr")?; 83 | io::stderr().flush().map_err(|_| "failed to flush stderr")?; 84 | } 85 | _ => { 86 | // sometimes want to print for debug? 87 | } 88 | } 89 | Ok(()) 90 | } 91 | --------------------------------------------------------------------------------