diff options
| -rw-r--r-- | .gitignore | 1 | ||||
| -rw-r--r-- | Cargo.lock | 336 | ||||
| -rw-r--r-- | Cargo.toml | 27 | ||||
| -rw-r--r-- | README.md | 63 | ||||
| -rw-r--r-- | src/main.rs | 396 | 
5 files changed, 823 insertions, 0 deletions
| diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..1a26dff --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,336 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clap" +version = "3.2.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71655c45cb9845d3270c9d6df84ebe72b4dad3c2ba3f7023ad47c144e4e473a5" +dependencies = [ + "atty", + "bitflags", + "clap_derive", + "clap_lex", + "indexmap", + "once_cell", + "strsim", + "termcolor", + "textwrap", +] + +[[package]] +name = "clap_derive" +version = "3.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea0c8bce528c4be4da13ea6fead8965e95b6073585a2f05204bd8f4119f82a65" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" +dependencies = [ + "os_str_bytes", +] + +[[package]] +name = "either" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "heck" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "indexmap" +version = "1.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "libc" +version = "0.2.139" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" + +[[package]] +name = "memoffset" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" +dependencies = [ + "autocfg", +] + +[[package]] +name = "nix" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46a58d1d356c6597d08cde02c2f09d785b09e28711837b1ed667dc652c08a694" +dependencies = [ + "bitflags", + "cfg-if", + "libc", + "memoffset", + "pin-utils", + "static_assertions", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" + +[[package]] +name = "os_str_bytes" +version = "6.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57a8eca9f9c4ffde41714334dee777596264c7825420f521abc92b5b5deb63a5" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "syn" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "termcolor" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "textwrap" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" + +[[package]] +name = "unicode-ident" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "yaxpeax-arch" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1ba5c2f163fa2f866c36750c6c931566c6d93231ae9410083b0738953b609d5" +dependencies = [ + "num-traits", +] + +[[package]] +name = "yaxpeax-eval" +version = "0.0.1" +dependencies = [ + "clap", + "hex", + "itertools", + "libc", + "nix", + "num-traits", + "yaxpeax-arch", + "yaxpeax-x86", +] + +[[package]] +name = "yaxpeax-x86" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "934a0186cc9f96af563264382d03946c95d8393e8e03f18cbbadd2efa8830b53" +dependencies = [ + "cfg-if", + "num-traits", + "yaxpeax-arch", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..98079c9 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "yaxpeax-eval" +version = "0.0.1" +authors = ["iximeow <me@iximeow.net>"] +license = "0BSD" +edition = "2021" +keywords = ["disassembly", "disassembler"] +repository = "https://git.iximeow.net/yaxpeax-eval/about/" +description = "batch eval tool for machine code" +readme = "README.md" + +[[bin]] +name = "yaxeval" +path = "src/main.rs" + +[dependencies] +nix = { version = "0.26.1", features = ["mman", "process", "ptrace"] } +clap = { version = "3", features = ["derive"] } +hex = "0.4.0" +num-traits = "0.2.10" +itertools = "0.10.1" +libc = "0.2.139" + +# common interfaces for all yaxpeax decoders +yaxpeax-arch = { version = "0.2.4" , default-features = false, features = ["std"] } + +yaxpeax-x86 = { version = "1.1.5", default-features = false, features = ["fmt", "std"] } diff --git a/README.md b/README.md new file mode 100644 index 0000000..51a750e --- /dev/null +++ b/README.md @@ -0,0 +1,63 @@ +## yaxpeax-eval + +[](https://crates.io/crates/yaxpeax-eval) + +`yaxpeax-eval` is the repo providing `yaxeval`, a tool to execute machine code with preconditions and report state at exit. + +currently, `yaxeval` works by spawning a thread and executing the provided machine code on the local physical processor. there is some boring glue for architecture-dependent state setting and reporting. this means that `yaxeval` supports, or is close to supporting, whatever physical processor you would run it on. + +i am interested in using qemu-user as an alternate execution backend for cross-platform emulation. `yaxeval` should be able to use qemu-user just the same for setup and reporting by using qemu's gdbserver. + +## usage + +if you just want to build and use it, `cargo install yaxpeax-eval` should get you started. otherwise, clone this repo and a `cargo build` will work as well. `yaxeval <x86 machine code>` is a good starting point: + +``` +yaxpeax-eval> ./target/release/yaxeval b878563412 +loaded code... +  00007f774b497000: mov eax, 0x12345678 +  00007f774b497005: 🏁 (int 0x3) +running... +  rax:   0000000000000000 +   to -> 0000000012345678 +  rip:   00007f774b497000 +   to -> 00007f774b497006 +``` + +initial register state is generally zeroes, with exception of `rip`, which by default points to whatever address an unrestricted `mmap` could find. + +inital register values, including `rip`, can be specified explicitly: + +``` +yaxpeax-eval> ./target/release/yaxeval --regs rax=4,rcx=5,rip=0x123456789a,eflags=0x246 03c133c9 +loaded code... +  000000123456789a: add eax, ecx +  000000123456789c: xor ecx, ecx +  000000123456789e: 🏁 (int 0x3) +running... +  rax:   0000000000000004 +   to -> 0000000000000009 +  rcx:   0000000000000005 +   to -> 0000000000000000 +  rip:   000000123456789a +   to -> 000000123456789f +``` + +and if the provided code disastrously crashes, `yaxeval` will try to say a bit about what occurred: + +``` +yaxpeax-eval> ./target/release/yaxeval --regs rax=4,rcx=5,rip=0x123456789a,eflags=0x246 0000 +loaded code... +  000000123456789a: add byte [rax], al +  000000123456789c: 🏁 (int 0x3) +running... +  eflags:        00000246 +   to ->         00010246 +sigsegv at unexpected address: 000000123456789a +``` + +## aspirations + +* accept some config to map memory regions other than the implicitly-initialized code region +* machine-friendly input/output formats +* mode to single-step through provided code? diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..f7ce4ed --- /dev/null +++ b/src/main.rs @@ -0,0 +1,396 @@ +use clap::Parser; +use libc::pid_t; +use nix::sys::mman::{MapFlags, ProtFlags}; +use nix::sys::ptrace::Request::*; +use nix::unistd::ForkResult; +use std::ffi::c_void; +use std::fs; +use std::num::NonZeroUsize; +use std::path::PathBuf; +use std::sync::atomic::{AtomicBool, Ordering}; +use yaxpeax_arch::LengthedInstruction; + +#[derive(Parser)] +#[clap(about, version, author)] +struct Args { +    /// file of bytes to execute +    #[clap(short, long, parse(from_os_str), conflicts_with = "code")] +    file: Option<PathBuf>, + +    /// hex bytes to execute. for example, try `33c0` +    #[clap(required_unless_present = "file")] +    code: Option<String>, + +    /// initial register state. registers not specified here may be initialized to random values. +    #[clap(short, long)] +    regs: Option<String>, +} + +fn parse_number(v: &str) -> Result<u64, String> { +    let res = if v.starts_with("0x") { +        u64::from_str_radix(&v[2..], 16) +    } else if v.starts_with("0b") { +        u64::from_str_radix(&v[2..], 2) +    } else if v.starts_with("0o") { +        u64::from_str_radix(&v[2..], 8) +    } else { +        u64::from_str_radix(v, 10) +    }; + +    res.map_err(|e| format!("{}", e)) +} + +fn main() { +    let args = Args::parse(); + +    let buf: Vec<u8> = match args.code { +        Some(code) => match hex::decode(code) { +            Ok(buf) => buf, +            Err(e) => { +                eprintln!("invalid input, {}. expected a sequence of bytes as hex", e); +                return; +            } +        }, +        None => { +            let name = args.file.unwrap(); +            match fs::read(&name) { +                Ok(v) => v, +                Err(e) => { +                    eprintln!("error reading {}: {}", name.display(), e); +                    return; +                } +            } +        } +    }; + +    let initial_rip: Option<u64> = args.regs.as_ref() +        .and_then(|regs| regs.split(",").find(|x| x.starts_with("rip="))) +        .map(|assign| { +            match assign.split("=").collect::<Vec<_>>().as_slice() { +                ["rip", value] => parse_number(value), +                [_other, _value] => Err(format!("found `rip=` but then string didn't start with `rip=`?")), +                other => Err(format!("string was not a simple reg=value format: {:?}", other)) +            } +        }) +        .transpose() +        .unwrap(); + +    // implement `initial_rip`, if present, as follows: +    // `initial_rip / 4096` is implemented by an mmap base address, +    // `initial_rip % 4096` is implemented by offsetting in that mmap ("before" initial_rip is +    // implicitly all 0) +    let required_len = initial_rip.map(|x| x as usize & 0xfff).unwrap_or(0) + buf.len() + 1; +    let rounded_len = (required_len + 0xfff) & !0xfff; + +    let sync: *const AtomicBool = { +        let shared_ptr = unsafe { +            nix::sys::mman::mmap( +                None, +                NonZeroUsize::new(4096).unwrap(), +                ProtFlags::PROT_EXEC | ProtFlags::PROT_WRITE | ProtFlags::PROT_READ, +                MapFlags::MAP_SHARED | MapFlags::MAP_ANON, +                0, +                0, +            ).unwrap() +        }; +        let ptr = shared_ptr as *mut AtomicBool; +        unsafe { std::ptr::write(ptr, AtomicBool::new(false)) }; +        ptr as *const AtomicBool +    }; + +    let mut map_flags = MapFlags::MAP_SHARED | MapFlags::MAP_ANON; +    if initial_rip.is_some() { +        map_flags |= MapFlags::MAP_FIXED; +    } + +    let code = unsafe { +        nix::sys::mman::mmap( +            initial_rip.map(|x| NonZeroUsize::new(x as usize - (x as usize % 0x1000)).unwrap()), +            NonZeroUsize::new(rounded_len).unwrap(), +            ProtFlags::PROT_EXEC | ProtFlags::PROT_WRITE | ProtFlags::PROT_READ, +            map_flags, +            0, +            0, +        ).unwrap() +    }; + +    let initial_rip = (code as u64) + initial_rip.map(|x| x & 0xfff).unwrap_or(0); + +    let slice: &mut [u8] = unsafe { +        std::slice::from_raw_parts_mut( +            initial_rip as *mut u8, +            required_len +        ) +    }; +    (&mut slice[..buf.len()]).copy_from_slice(buf.as_slice()); +    slice[buf.len()] = 0xcc; + +    println!("loaded code..."); +    let mut offset = 0; +    while offset < buf.len() + 1 { +        match yaxpeax_x86::long_mode::InstDecoder::default().decode_slice(&slice[offset..]) { +            Ok(inst) => { +                if offset < buf.len() { +                    println!("  {:016x}: {}", offset + initial_rip as usize, inst); +                } else { +                    println!("  {:016x}: 🏁 ({})", offset + initial_rip as usize, inst); +                } +                offset += inst.len().to_const() as usize +            } +            Err(e) => { +                println!("  {:016x}: {}", offset + initial_rip as usize, e); +                println!("    (offset {:x})", offset); +                break; +            } +        } +    } +    println!("running..."); + +    let target = match unsafe { nix::unistd::fork().unwrap() } { +        ForkResult::Parent { child } => PtraceEvalTarget::attach(child.as_raw(), sync), +        ForkResult::Child => unsafe { setup(sync) }, +    }; + +    unsafe { +        target.clear_regs(); +        if let Some(regs) = args.regs.as_ref() { +            target.apply_regs(regs); +        } +        target.set_rip(initial_rip); + +        let regs = target.get_regs(); +        let status = target.run(); +    //    println!("status: {}", status); + +        let exit_regs = target.get_regs(); + +        print_diff(®s, &exit_regs); + +        if status & 0xff == 0x7f { +            let signal = status >> 8; +            if signal == libc::SIGTRAP { +                if exit_regs.rip == (initial_rip + buf.len() as u64 + 1) { +                    // code completed normally +                } else { +                    println!("sigtrap at atypical address: {:016x}", exit_regs.rip); +                    std::process::exit(1); +                } +            } else if signal == libc::SIGSEGV { +                println!("sigsegv at unexpected address: {:016x}", exit_regs.rip); +                std::process::exit(1); +            } else { +                println!("signal {} at unexpected address: {:016x}", signal, exit_regs.rip); +            } +        } else if status <= 255 { +            println!("exited with signal: {}", status & 0xff); +            std::process::exit(1); +        } else { +            println!("unknown stop status? {}", status); +            std::process::exit(1); +        } +    } +} + +fn print_diff(from: &libc::user_regs_struct, to: &libc::user_regs_struct) { +    if from.rax != to.rax { +        println!("  rax:   {:016x}", from.rax); +        println!("   to -> {:016x}", to.rax); +    } +    if from.rcx != to.rcx { +        println!("  rcx:   {:016x}", from.rcx); +        println!("   to -> {:016x}", to.rcx); +    } +    if from.rdx != to.rdx { +        println!("  rdx:   {:016x}", from.rdx); +        println!("   to -> {:016x}", to.rdx); +    } +    if from.rbx != to.rbx { +        println!("  rbx:   {:016x}", from.rbx); +        println!("   to -> {:016x}", to.rbx); +    } +    if from.rbp != to.rbp { +        println!("  rbp:   {:016x}", from.rbp); +        println!("   to -> {:016x}", to.rbp); +    } +    if from.rsp != to.rsp { +        println!("  rsp:   {:016x}", from.rsp); +        println!("   to -> {:016x}", to.rsp); +    } +    if from.rsi != to.rsi { +        println!("  rsi:   {:016x}", from.rsi); +        println!("   to -> {:016x}", to.rsi); +    } +    if from.rdi != to.rdi { +        println!("  rdi:   {:016x}", from.rdi); +        println!("   to -> {:016x}", to.rdi); +    } +    if from.r8 != to.r8 { +        println!("  r8:    {:016x}", from.r8); +        println!("   to -> {:016x}", to.r8); +    } +    if from.r9 != to.r9 { +        println!("  r9:    {:016x}", from.r9); +        println!("   to -> {:016x}", to.r9); +    } +    if from.r10 != to.r10 { +        println!("  r10:   {:016x}", from.r10); +        println!("   to -> {:016x}", to.r10); +    } +    if from.r11 != to.r11 { +        println!("  r11:   {:016x}", from.r11); +        println!("   to -> {:016x}", to.r11); +    } +    if from.r12 != to.r12 { +        println!("  r12:   {:016x}", from.r12); +        println!("   to -> {:016x}", to.r12); +    } +    if from.r13 != to.r13 { +        println!("  r13:   {:016x}", from.r13); +        println!("   to -> {:016x}", to.r13); +    } +    if from.r14 != to.r14 { +        println!("  r14:   {:016x}", from.r14); +        println!("   to -> {:016x}", to.r14); +    } +    if from.r15 != to.r15 { +        println!("  r15:   {:016x}", from.r15); +        println!("   to -> {:016x}", to.r15); +    } +    if from.rip != to.rip { +        println!("  rip:   {:016x}", from.rip); +        println!("   to -> {:016x}", to.rip); +    } +    if from.eflags != to.eflags { +        println!("  eflags:        {:08x}", from.eflags); +        println!("   to ->         {:08x}", to.eflags); +    } +} + +unsafe fn setup(sync: *const AtomicBool) -> ! { +    assert_eq!(libc::prctl(libc::PR_SET_PDEATHSIG, libc::SIGKILL), 0); +    sync.as_ref().unwrap().store(true, Ordering::SeqCst); +    loop {} +} + +struct PtraceEvalTarget { +    pid: pid_t, +} + +impl PtraceEvalTarget { +    fn attach(pid: pid_t, sync: *const AtomicBool) -> Self { +        // wait until the child process signals it's ready to be attached to. this solves a small +        // race where if we attach and die before the child sets `prctl(PR_SET_PDEATHSIG)`, the +        // child process can become an orphan. the default config leaves the child process in an +        // infinite loop in such a case. +        let syncref = unsafe { sync.as_ref().unwrap() }; +        while !syncref.load(Ordering::SeqCst) { +            std::thread::sleep(std::time::Duration::from_millis(10)); +        } +        if unsafe { libc::ptrace(PTRACE_ATTACH as u32, pid) } != 0 { +            panic!("ptrace: {}", nix::errno::errno()); +        } +        if unsafe { libc::waitpid(pid, std::ptr::null_mut(), 0) } == -1 { +            panic!("waitpid: {}", nix::errno::errno()); +        } + +        Self { pid } +    } + +    unsafe fn apply_regs(&self, regs: &str) { +        let mut state = self.get_regs(); + +        let parts = regs.split(","); +        for part in parts { +            let kv = part.split("=").collect::<Vec<_>>(); +            match kv.as_slice() { +                [reg, value] => { +                    let value: u64 = parse_number(value).unwrap(); +                    match *reg { +                        "rax" => { state.rax = value }, +                        "rcx" => { state.rcx = value }, +                        "rdx" => { state.rdx = value }, +                        "rbx" => { state.rbx = value }, +                        "rbp" => { state.rbp = value }, +                        "rsp" => { state.rsp = value }, +                        "rsi" => { state.rsi = value }, +                        "rdi" => { state.rdi = value }, +                        "r8" => { state.r8 = value }, +                        "r9" => { state.r9 = value }, +                        "r10" => { state.r10 = value }, +                        "r11" => { state.r11 = value }, +                        "r12" => { state.r12 = value }, +                        "r13" => { state.r13 = value }, +                        "r14" => { state.r14 = value }, +                        "r15" => { state.r15 = value }, +                        "eflags" => { state.eflags = value }, +                        "rip" => { /* handled elsewhere */ }, +                        other => { panic!("unknown register {}", other) } +                    } +                }, +                other => { +                    eprintln!("register assignment was not of the form `regname=value`? {:?}", other); +                } +            } +        } + +        self.set_regs(&mut state); +    } + +    unsafe fn clear_regs(&self) { +        let mut regs = self.get_regs(); +        regs.rax = 0; +        regs.rcx = 0; +        regs.rdx = 0; +        regs.rbx = 0; +        regs.rbp = 0; +        regs.rsp = 0; +        regs.rsi = 0; +        regs.rdi = 0; +        regs.r8 = 0; +        regs.r9 = 0; +        regs.r10 = 0; +        regs.r11 = 0; +        regs.r12 = 0; +        regs.r13 = 0; +        regs.r14 = 0; +        regs.r15 = 0; +        regs.eflags = 0; +        self.set_regs(&mut regs); +    } + +    unsafe fn get_regs(&self) -> libc::user_regs_struct { +        let mut regs: libc::user_regs_struct = +            std::mem::transmute([0u8; std::mem::size_of::<libc::user_regs_struct>()]); +        if libc::ptrace(PTRACE_GETREGS as u32, self.pid, std::ptr::null_mut::<*const c_void>(), &mut regs as *mut libc::user_regs_struct) != 0 { +            panic!("ptrace(getregs): {}", nix::errno::errno()); +        } + +        regs +    } + +    unsafe fn set_regs(&self, regs: &mut libc::user_regs_struct) { +        if libc::ptrace(PTRACE_SETREGS as u32, self.pid, std::ptr::null_mut::<*const c_void>(), regs as *mut libc::user_regs_struct) != 0 { +            panic!("ptrace(setregs): {}", nix::errno::errno()); +        } +    } + +    unsafe fn set_rip(&self, rip: u64) { +        let mut regs = self.get_regs(); +        regs.rip = rip; +        if libc::ptrace(PTRACE_SETREGS as u32, self.pid, std::ptr::null_mut::<*const c_void>(), &mut regs as *mut libc::user_regs_struct) != 0 { +            panic!("ptrace(setregs): {}", nix::errno::errno()); +        } +    } + +    unsafe fn run(&self) -> i32 { +        if libc::ptrace(PTRACE_CONT as u32, self.pid, 0, 0) != 0 { +            panic!("ptrace(cont): {}", nix::errno::errno()); +        } + +        let mut status = 0; +        if libc::waitpid(self.pid, &mut status as *mut i32, 0) == -1 { +            panic!("waitpid: {}", nix::errno::errno()); +        } +        status +    } +} | 
