summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--Cargo.lock336
-rw-r--r--Cargo.toml27
-rw-r--r--README.md63
-rw-r--r--src/main.rs396
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
+
+[![crate](https://img.shields.io/crates/v/yaxpeax-eval.svg?logo=rust)](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(&regs, &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
+ }
+}