pete-0.13.0/.cargo_vcs_info.json0000644000000001360000000000100120520ustar { "git": { "sha1": "378d9fdc7d8ef0b4e85681829631ff47bf4ca4b6" }, "path_in_vcs": "" }pete-0.13.0/Cargo.lock0000644000000406410000000000100100320ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "ansi_term" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" dependencies = [ "winapi", ] [[package]] name = "anyhow" version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" [[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.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" version = "2.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" [[package]] name = "cfg-if" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" [[package]] name = "cfg_aliases" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "clap" version = "2.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" dependencies = [ "ansi_term", "atty", "bitflags 1.3.2", "strsim", "textwrap", "unicode-width", "vec_map", ] [[package]] name = "equivalent" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "hashbrown" version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" [[package]] name = "heck" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" dependencies = [ "unicode-segmentation", ] [[package]] name = "hermit-abi" version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" dependencies = [ "libc", ] [[package]] name = "indexmap" version = "2.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" dependencies = [ "equivalent", "hashbrown", ] [[package]] name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" version = "0.2.176" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174" [[package]] name = "log" version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" [[package]] name = "memchr" version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] name = "memoffset" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" dependencies = [ "autocfg", ] [[package]] name = "nix" version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" dependencies = [ "bitflags 2.9.4", "cfg-if", "cfg_aliases", "libc", ] [[package]] name = "ntest" version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb183f0a1da7a937f672e5ee7b7edb727bf52b8a52d531374ba8ebb9345c0330" dependencies = [ "ntest_test_cases", "ntest_timeout", ] [[package]] name = "ntest_test_cases" version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16d0d3f2a488592e5368ebbe996e7f1d44aa13156efad201f5b4d84e150eaa93" dependencies = [ "proc-macro2", "quote", "syn 1.0.109", ] [[package]] name = "ntest_timeout" version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcc7c92f190c97f79b4a332f5e81dcf68c8420af2045c936c9be0bc9de6f63b5" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", "syn 1.0.109", ] [[package]] name = "nu-ansi-term" version = "0.50.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4a28e057d01f97e61255210fcff094d74ed0466038633e95017f5beb68e4399" dependencies = [ "windows-sys", ] [[package]] name = "once_cell" version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "pete" version = "0.13.0" dependencies = [ "anyhow", "lazy_static", "libc", "memoffset", "nix", "ntest", "structopt", "thiserror", "tracing", "tracing-subscriber", ] [[package]] name = "pin-project-lite" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] name = "proc-macro-crate" version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" dependencies = [ "toml_edit", ] [[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 1.0.109", "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.101" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] [[package]] name = "serde_core" version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "sharded-slab" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" dependencies = [ "lazy_static", ] [[package]] name = "smallvec" version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "strsim" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" [[package]] name = "structopt" version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c6b5c64445ba8094a6ab0c3cd2ad323e07171012d9c98b0b15651daf1787a10" dependencies = [ "clap", "lazy_static", "structopt-derive", ] [[package]] name = "structopt-derive" version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0" dependencies = [ "heck", "proc-macro-error", "proc-macro2", "quote", "syn 1.0.109", ] [[package]] name = "syn" version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "syn" version = "2.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "textwrap" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" dependencies = [ "unicode-width", ] [[package]] name = "thiserror" version = "2.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" version = "2.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "thread_local" version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" dependencies = [ "cfg-if", ] [[package]] name = "toml_datetime" version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32f1085dec27c2b6632b04c80b3bb1b4300d6495d1e129693bdda7d91e72eec1" dependencies = [ "serde_core", ] [[package]] name = "toml_edit" version = "0.23.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3effe7c0e86fdff4f69cdd2ccc1b96f933e24811c5441d44904e8683e27184b" dependencies = [ "indexmap", "toml_datetime", "toml_parser", "winnow", ] [[package]] name = "toml_parser" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cf893c33be71572e0e9aa6dd15e6677937abd686b066eac3f8cd3531688a627" dependencies = [ "winnow", ] [[package]] name = "tracing" version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "pin-project-lite", "tracing-attributes", "tracing-core", ] [[package]] name = "tracing-attributes" version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "tracing-core" version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" dependencies = [ "once_cell", "valuable", ] [[package]] name = "tracing-log" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" dependencies = [ "log", "once_cell", "tracing-core", ] [[package]] name = "tracing-subscriber" version = "0.3.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5" dependencies = [ "nu-ansi-term", "sharded-slab", "smallvec", "thread_local", "tracing-core", "tracing-log", ] [[package]] name = "unicode-ident" version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" [[package]] name = "unicode-segmentation" version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" [[package]] name = "unicode-width" version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" [[package]] name = "valuable" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" [[package]] name = "vec_map" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" [[package]] name = "version_check" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[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-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ "windows-targets", ] [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_gnullvm", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" dependencies = [ "memchr", ] pete-0.13.0/Cargo.toml0000644000000026330000000000100100540ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2018" rust-version = "1.64" name = "pete" version = "0.13.0" authors = ["Joe Ranweiler "] build = false include = ["src/"] autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "A friendly wrapper around ptrace(2)" readme = "README.md" license = "ISC" repository = "https://github.com/ranweiler/pete" [lib] name = "pete" path = "src/lib.rs" [dependencies.libc] version = "0.2.99" [dependencies.nix] version = "0.30.1" features = [ "process", "ptrace", ] [dependencies.thiserror] version = "2.0.16" [dependencies.tracing] version = "0.1.41" [dev-dependencies.anyhow] version = "1.0.31" [dev-dependencies.lazy_static] version = "1.4.0" [dev-dependencies.ntest] version = "0.9.0" [dev-dependencies.structopt] version = "0.3.21" [dev-dependencies.tracing-subscriber] version = "0.3.19" [target.'cfg(target_arch = "x86_64")'.dependencies.memoffset] version = "0.9.1" pete-0.13.0/Cargo.toml.orig000064400000000000000000000011601046102023000135270ustar 00000000000000[package] name = "pete" version = "0.13.0" rust-version = "1.64" edition = "2018" license = "ISC" readme = "README.md" authors = ["Joe Ranweiler "] repository = "https://github.com/ranweiler/pete" description = "A friendly wrapper around ptrace(2)" include = [ "src/", ] [dependencies] libc = "0.2.99" nix = { version = "0.30.1", features = ["process", "ptrace"] } thiserror = "2.0.16" tracing = "0.1.41" [target.'cfg(target_arch = "x86_64")'.dependencies] memoffset = "0.9.1" [dev-dependencies] anyhow = "1.0.31" lazy_static = "1.4.0" ntest = "0.9.0" structopt = "0.3.21" tracing-subscriber = "0.3.19" pete-0.13.0/README.md000064400000000000000000000027351046102023000121300ustar 00000000000000# Pete A friendly wrapper around the Linux `ptrace(2)` syscall. ## Requirements The current minimum supported OS and compiler versions are: - Linux 4.8 - rustc 1.64 Due to test library requirements, the oldest _tested_ Rust version is 1.76. Continuous testing is only run for `x86_64-unknown-linux-gnu`. Support for earlier Linux versions is possible, but low priority. Eventually, we would like to support any platform that provides `ptrace(2)`. ## Summary The `ptrace(2)` interface entails interpreting a series of `wait(2)` statuses. The context used to interpret a status includes the attach options set on each tracee, previously-seen stops, recent ptrace requests, and in some cases, extra event data that must be queried using additional ptrace calls. Pete is meant to instead permit reasoning directly about ptrace-stops, as described in the manual. We hide the lowest-level contextual bookkeeping required to disambiguate ptrace-stops. Whenever we can, we avoid extraneous ptrace calls, deferring to downstream tracers implemented on top of the library. For example, Pete can distinguish a syscall-enter-stop and syscall-exit-stop, but does not _automatically_ query register state to identify the specific syscall. ## License Pete is licensed under the [ISC License](./LICENSE). ## Contributing Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in `pete` by you, shall be licensed as ISC, without any additional terms or conditions. pete-0.13.0/src/aarch64.rs000064400000000000000000000031151046102023000132270ustar 00000000000000/// Defined in [`include/uapi/linux/elf.h`](https://android.googlesource.com/kernel/common/+/refs/heads/android-mainline/include/uapi/linux/elf.h#421). const NT_ARM_HW_BREAK: i32 = 0x402; const NT_ARM_HW_WATCH: i32 = 0x403; pub type DebugRegisters = user_hwdebug_state; /// Defined in [`arch/arm64/include/uapi/asm/ptrace.h`](https://android.googlesource.com/kernel/common/+/refs/heads/android-mainline/arch/arm64/include/uapi/asm/ptrace.h#88). #[cfg(target_arch = "aarch64")] #[repr(C)] #[derive(Clone, Copy, Debug)] pub struct user_pt_regs { pub regs: [u64; 31], pub sp: u64, pub pc: u64, pub pstate: u64 } /// Nested, untagged struct declaration in `user_hwdebug_state`. #[cfg(target_arch = "aarch64")] #[repr(C)] #[derive(Clone, Copy, Debug)] pub struct user_hwdebug_state_reg { pub addr: u64, pub ctrl: u32, pad: u32, } #[cfg(target_arch = "aarch64")] impl user_hwdebug_state_reg { pub fn new() -> Self { Self { addr: 0, ctrl: 0, pad: 0, } } } #[cfg(target_arch = "aarch64")] #[repr(i32)] #[derive(Copy, Clone)] pub enum DebugRegisterType { Break = NT_ARM_HW_BREAK, Watch = NT_ARM_HW_WATCH, } #[cfg(target_arch = "aarch64")] #[repr(C)] #[derive(Clone, Copy, Debug)] pub struct user_hwdebug_state { pub dbg_info: u32, pad: u32, pub dbg_regs: [user_hwdebug_state_reg; 4], } #[cfg(target_arch = "aarch64")] impl user_hwdebug_state { pub fn new() -> Self { Self { dbg_info: 0, pad: 0, dbg_regs: [user_hwdebug_state_reg::new(); 4], } } } pete-0.13.0/src/error.rs000064400000000000000000000041001046102023000131230ustar 00000000000000//! Custom types for tracing errors. use std::io; use crate::ptracer::Pid; /// Alias for `Result`. pub type Result = std::result::Result; /// A tracing error. /// /// Tracees are controlled by the tracer, but still impacted by their environment. In /// particular, they may die unexpectedly while in ptrace-stop. Some errors can be due to /// this, and will not necessarily be followed by a `wait()` status reporting the tracee's /// death. This should be observed as an error with a `source` of /// `nix::Error::Sys(Errno::ESRCH)`. /// /// For this reason, tracing programs may need to handle an `ESRCH` error on any /// `ptrace()` operation. #[derive(thiserror::Error, Debug)] pub enum Error { #[error("Could not attach to tracee = {pid}")] Attach { pid: Pid, source: nix::Error, }, #[error("Tracee died while in ptrace-stop")] TraceeDied { pid: Pid, source: nix::Error }, #[error("Input/output error")] IO(#[from] io::Error), #[error("OS error")] OS(#[from] nix::Error), #[error("Internal error: {0}. Please open an issue at https://github.com/ranweiler/pete/issues")] Internal(String), } impl Error { pub fn tracee_died(&self) -> bool { if let Error::TraceeDied { .. } = self { true } else { false } } } macro_rules! internal_error { ($ctx: expr) => {{ return Err($crate::error::Error::Internal($ctx.into())); }} } pub(crate) trait ResultExt { /// Maps an `ESRCH` error result to `Error::TraceeDied`. /// /// Should only be called on results of `ptrace()` operations on valid tracees known /// to be in ptrace-stop. fn died_if_esrch(self, pid: Pid) -> Result; } impl ResultExt for std::result::Result { fn died_if_esrch(self, pid: Pid) -> Result { use nix::errno::Errno; self.map_err(|err| { if err == Errno::ESRCH { Error::TraceeDied { pid, source: err } } else { err.into() } }) } } pete-0.13.0/src/lib.rs000064400000000000000000000023351046102023000125500ustar 00000000000000//! A friendly wrapper around the Linux `ptrace(2)` system call. //! //! The `ptrace(2)` interface entails interpreting a series of `wait(2)` statuses. The context used //! to interpret a status includes the attach options set on each tracee, previously-seen stops, //! recent `ptrace` requests, and in some cases, extra event data that must be queried using //! additional `ptrace` calls. //! //! Pete is meant to instead permit reasoning directly about ptrace-stops, as described in the //! manual. We hide the lowest-level contextual bookkeeping required to disambiguate //! [ptrace-stops](ptracer::Stop). Whenever we can, we avoid extraneous ptrace calls, deferring to //! downstream tracers implemented on top of the library. For example, Pete can distinguish a //! syscall-enter-stop and syscall-exit-stop, but does not _automatically_ query register state to //! identify the specific syscall. #[macro_use] pub mod error; pub mod ptracer; #[cfg(target_arch = "aarch64")] pub mod aarch64; #[cfg(target_arch = "x86_64")] pub mod x86; #[doc(inline)] pub use error::Error; #[doc(inline)] pub use ptracer::{Pid, Ptracer, Restart, Siginfo, Signal, Stop, Tracee}; #[cfg(target_arch = "x86_64")] #[doc(inline)] pub use ptracer::Registers; pete-0.13.0/src/ptracer.rs000064400000000000000000000770631046102023000134540ustar 00000000000000//! Types for attaching to processes, managing tracees, and interpreting tracing events. use std::collections::BTreeMap; use std::fs; use std::io; use std::marker::PhantomData; use std::os::unix::process::CommandExt; use std::process::{Child, Command}; use std::time::Duration; use nix::{ errno::Errno, sys::{ ptrace, wait::{self, WaitPidFlag, WaitStatus}, }, }; use tracing::{debug, info, trace}; use crate::error::{Error, Result, ResultExt}; #[cfg(target_arch = "aarch64")] use crate::aarch64; #[cfg(target_arch = "x86_64")] use crate::x86; #[cfg(target_arch = "x86_64")] use x86::DebugRegister; #[cfg(target_arch = "aarch64")] pub type DebugRegisters = aarch64::user_hwdebug_state; pub use nix::unistd::Pid; pub use nix::sys::ptrace::Options; /// POSIX signal. pub use nix::sys::signal::Signal; /// Register state of a tracee. #[cfg(target_arch = "aarch64")] pub type Registers = aarch64::user_pt_regs; /// Register state of a tracee. #[cfg(target_arch = "x86_64")] pub type Registers = libc::user_regs_struct; /// Extra signal info, such as its cause. pub type Siginfo = libc::siginfo_t; /// Linux constant defined in `include/uapi/linux/elf.h`. #[cfg(target_arch = "aarch64")] const NT_PRSTATUS: i32 = 0x1; /// A _ptrace-stop_, a tracee state in which it is stopped and ready to accept ptrace /// commands. /// /// These ptrace-stops may carry data obtained via additional (internal) ptrace requests /// to `PTRACE_GETEVENTMSG`. Requests to `PTRACE_GETSIGINFO` may be made to disambiguate /// stops. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum Stop { Attach, // signal-delivery-stop SignalDelivery { signal: Signal }, // group-stop Group { signal: Signal }, // syscall-stops SyscallEnter, SyscallExit, // ptrace-event-stops Clone { new: Pid }, Fork { new: Pid }, Exec { old: Pid }, Exiting { exit_code: i32 }, Signaling { signal: Signal, core_dumped: bool, }, Vfork { new: Pid }, VforkDone { new: Pid }, Seccomp { data: u16 }, } /// Restart requests, which resume stopped tracees. /// /// The restart mode determines the possible subsequent stops of the restarted tracee. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum Restart { Step, Continue, Syscall, } /// Tracee task in ptrace-stop, with an optional pending signal. /// /// **Warning:** the underlying tracee is not guaranteed to exist, and /// operations on it may fail between calls to [`Ptracer::wait()`]. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct Tracee { pub pid: Pid, pub pending: Option, pub stop: Stop, #[doc(hidden)] _not_send: PhantomData<*const ()>, } impl Tracee { pub fn new(pid: Pid, pending: impl Into>, stop: Stop) -> Self { let pending = pending.into(); let _not_send = PhantomData; Self { pid, pending, stop, _not_send } } /// Set a signal to deliver to the stopped process upon restart. pub fn inject(&mut self, pending: Signal) { self.pending = Some(pending); } /// Remove any signal scheduled for delivery to `pid` upon restart. pub fn suppress(&mut self) { self.pending = None; } /// Set custom tracing options on the tracee. pub fn set_options(&mut self, options: Options) -> Result<()> { Ok(ptrace::setoptions(self.pid, options).died_if_esrch(self.pid)?) } #[cfg(target_arch = "x86_64")] pub fn registers(&self) -> Result { Ok(ptrace::getregs(self.pid).died_if_esrch(self.pid)?) } #[cfg(target_arch = "aarch64")] pub fn registers(&self) -> Result { let mut data = std::mem::MaybeUninit::uninit(); let mut rv = libc::iovec { iov_base: &mut data as *mut _ as *mut libc::c_void, iov_len: std::mem::size_of::(), }; let res = unsafe { libc::ptrace(libc::PTRACE_GETREGSET, self.pid, NT_PRSTATUS, &mut rv as *mut _ as *mut libc::c_void) }; Errno::result(res)?; Ok( unsafe { data.assume_init() } ) } #[cfg(target_arch = "x86_64")] pub fn set_registers(&mut self, regs: Registers) -> Result<()> { Ok(ptrace::setregs(self.pid, regs).died_if_esrch(self.pid)?) } #[cfg(target_arch = "aarch64")] pub fn set_registers(&mut self, regs: Registers) -> Result<()> { let mut rv = libc::iovec { iov_base: ®s as *const _ as *const libc::c_void as *mut libc::c_void, iov_len: std::mem::size_of::(), }; let res = unsafe { libc::ptrace(libc::PTRACE_SETREGSET, self.pid, NT_PRSTATUS, &mut rv as *mut _ as *mut libc::c_void) }; Errno::result(res)?; Ok(()) } pub fn read_memory(&mut self, addr: u64, len: usize) -> Result> { let mut data = Vec::with_capacity(len); data.resize(len, 0); let len_read = self.read_memory_mut(addr, &mut data)?; data.truncate(len_read); Ok(data) } pub fn read_memory_mut(&self, addr: u64, data: &mut [u8]) -> Result { use std::os::unix::fs::FileExt; let mem = self.memory()?; let len = mem.read_at(data, addr)?; Ok(len) } pub fn write_memory(&mut self, addr: u64, data: &[u8]) -> Result { use std::os::unix::fs::FileExt; let mem = fs::OpenOptions::new() .read(true) .write(true) .open(self.proc_mem_path())?; let len = mem.write_at(data, addr)?; Ok(len) } fn proc_mem_path(&self) -> String { let tid = self.pid.as_raw() as u32; format!("/proc/{}/mem", tid) } pub fn memory(&self) -> Result { Ok(fs::File::open(self.proc_mem_path())?) } pub fn siginfo(&self) -> Result> { let info = if let Stop::SignalDelivery { .. } = self.stop { Some(ptrace::getsiginfo(self.pid).died_if_esrch(self.pid)?) } else { None }; Ok(info) } #[cfg(target_arch = "x86_64")] pub fn debug_register(&self, dr: DebugRegister) -> Result { let index = 8 * u64::from(dr); if let Some(off) = DebugRegister::user_offset().checked_add(index) { self.peek_user(off) } else { internal_error!("unreachable overflow") } } #[cfg(target_arch = "aarch64")] pub fn debug_registers(&self, regtype: aarch64::DebugRegisterType) -> Result { let mut data = std::mem::MaybeUninit::uninit(); let mut rv = libc::iovec { iov_base: &mut data as *mut _ as *mut libc::c_void, iov_len: std::mem::size_of::(), }; let res = unsafe { libc::ptrace(libc::PTRACE_GETREGSET, self.pid, regtype, &mut rv as *mut _ as *mut libc::c_void) }; Errno::result(res)?; Ok(unsafe { data.assume_init() }) } #[cfg(target_arch = "x86_64")] pub fn set_debug_register(&self, dr: DebugRegister, data: u64) -> Result<()> { let index = 8 * u64::from(dr); if let Some(off) = DebugRegister::user_offset().checked_add(index) { self.poke_user(off, data) } else { internal_error!("unreachable overflow") } } #[cfg(target_arch = "aarch64")] pub fn set_debug_registers(&self, regtype: aarch64::DebugRegisterType, mut state: DebugRegisters) -> Result<()> { let mut rv = libc::iovec { iov_base: &mut state as *mut _ as *mut libc::c_void, iov_len: std::mem::size_of::(), }; let res = unsafe { libc::ptrace(libc::PTRACE_SETREGSET, self.pid, regtype, &mut rv as *mut _ as *mut libc::c_void) }; Errno::result(res)?; Ok(()) } #[cfg(target_arch = "x86_64")] fn peek_user(&self, off: u64) -> Result { // SAFETY: `off` does not require validation, because it is not actually used as a // pointer offset by the kernel. // // See: https://github.com/torvalds/linux/blob/v4.9/arch/x86/kernel/ptrace.c#L774-L791 let data = unsafe { libc::ptrace( libc::PTRACE_PEEKUSER, self.pid, off, 0, ) }; Ok(data as u64) } #[cfg(target_arch = "x86_64")] fn poke_user(&self, off: u64, data: u64) -> Result<()> { // SAFETY: `off` does not require validation, because it is not actually used as a // pointer offset by the kernel. // // See: https://github.com/torvalds/linux/blob/v4.9/arch/x86/kernel/ptrace.c#L774-L791 unsafe { libc::ptrace( libc::PTRACE_POKEUSER, self.pid, off, data, ) }; Ok(()) } } #[derive(Clone, Copy, Debug, Eq, PartialEq)] enum State { // Traced, no special expectation for next stop. Traced, // Newly-attached, expecting a SIGSTOP. Attaching, // Self-attached, via `spawn()` with a pre-exec `TRACEME` request. Spawned, // After a syscall-exit-stop or seccomp-stop. Syscalling, // Stopped mid-exit (but not yet reaped) and pending detach. Exiting, } /// Tracer for a Linux process. /// /// By default, [spawning](Ptracer::spawn()) a child tracee will follow calls to `fork()`, /// `clone()`, and `exec()`, tracing any child tasks (both threads and processes). /// /// This can be configured for any stopped tracee via [`Tracee::set_options()`]. #[derive(Clone, Debug, Eq, PartialEq)] pub struct Ptracer { /// Ptrace options that will be applied to tracees, by default. options: Options, /// Time to sleep for before polling tracees for new events. poll_delay: Duration, /// Known tracees, and their state. tracees: BTreeMap, } const DEFAULT_POLL_DELAY: Duration = Duration::from_micros(1); impl Ptracer { pub fn new() -> Self { let options = Options::all(); let poll_delay = DEFAULT_POLL_DELAY; let tracees = BTreeMap::new(); Self { options, poll_delay, tracees } } /// Return the default ptrace options applied to newly-spawned tracees. pub fn default_options(&self) -> Options { self.options } /// Set the default ptrace options applied to newly-spawned tracees. pub fn set_default_options(&mut self, options: Options) { self.options = options; } /// Return the initial tracee poll delay. pub fn poll_delay(&self) -> Duration { self.poll_delay } /// Set the initial tracee poll delay. pub fn set_poll_delay(&mut self, poll_delay: Duration) { self.poll_delay = poll_delay; } /// Resume the stopped tracee, delivering any pending signal. pub fn restart(&mut self, tracee: Tracee, restart: Restart) -> Result<()> { let Tracee { pid, pending, .. } = tracee; if let Some(State::Exiting) = self.tracee_state(tracee.pid) { // Override restart request with a detach. let r = ptrace::detach(tracee.pid, tracee.pending); r.died_if_esrch(tracee.pid)?; // We must not wait on this PID again, or we will reap it and break `std::process::Child`. self.remove_tracee(tracee.pid); return Ok(()); } let r = match restart { Restart::Step => ptrace::step(pid, pending), Restart::Continue => ptrace::cont(pid, pending), Restart::Syscall => ptrace::syscall(pid, pending), }; r.died_if_esrch(pid)?; Ok(()) } /// Spawn `cmd` for tracing. /// /// The command will be configured to request `PTRACE_TRACEME` after `fork()` and /// pre-`exec()`. The caller will use this to avoid races and missed events. pub fn spawn(&mut self, mut cmd: Command) -> Result { // On fork, request `PTRACE_TRACEME`. unsafe { cmd.pre_exec(|| ptrace::traceme().map_err(|err| io::Error::from_raw_os_error(err as i32))) }; let child = cmd.spawn()?; // Register the tracee as having been spawned with a pre-exec `TRACEME` request. // This lets us interpret the `SIGTRAP` that will be issued for `execve()`, set // the desired trace options, &c. let pid = Pid::from_raw(child.id() as i32); self.set_tracee_state(pid, State::Spawned); Ok(child) } /// Attach to a running tracee. This will deliver a `SIGSTOP`. /// /// **Warning:** the tracee may not be considered stopped until it has been seen to /// stop via `wait()`. pub fn attach(&mut self, pid: Pid) -> Result<()> { let r = ptrace::attach(pid); let r = r.map_err(|source| Error::Attach { pid, source }); self.mark_tracee(pid); r } // Poll tracees for a `wait(2)` status change. fn poll_tracees(&self) -> Result> { let flag = WaitPidFlag::__WALL | WaitPidFlag::WNOHANG; for tracee in self.tracees.keys().copied() { let pid = Pid::from_raw(tracee); match wait::waitpid(pid, Some(flag)) { Ok(WaitStatus::StillAlive) => { // Alive, no state change. Check remaining tracees. continue; }, Ok(status) => { // One of our tracees changed state. return Ok(Some(status)); }, Err(errno) if errno == Errno::ECHILD => { // No more children to wait on: we're done. return Ok(None) }, Err(err) => { // Something else went wrong. return Err(err.into()) }, }; } // No tracee changed state. Ok(None) } /// Wait for some running tracee process to stop. /// /// If there are no tracees to wait on, returns `None`. pub fn wait(&mut self) -> Result> { use Signal::*; let mut poll_delay = self.poll_delay; // Wait on known tracees with exponential backoff. let status = loop { if self.tracees.is_empty() { debug!("no tracees to wait on"); return Ok(None); } if let Some(status) = self.poll_tracees()? { // A tracee changed state; examine its `wait(2)` status. break status; } else { trace!(tracees = self.tracees.len(), ?poll_delay, "no tracee updates, backing off"); std::thread::sleep(poll_delay); // Back off before next attempt. poll_delay *= 2; } }; let tracee = match status { WaitStatus::Exited(_pid, _exit_code) => { internal_error!("consumed wait status for exited tracee"); }, WaitStatus::Signaled(_pid, _sig, _is_core_dump) => { internal_error!("consumed wait status for signaled tracee"); }, WaitStatus::Stopped(pid, SIGTRAP) => { let state = self.tracee_state_mut(pid); if let Some(state @ State::Spawned) = state { // A `SIGTRAP` for a tracee in the `Spawned` state means it has returned from a // successful `execve()` after requesting `PTRACE_TRACEME`. From the manual: // // If the `PTRACE_O_TRACEEXEC` option is not in effect, all successful calls // to `execve(2)` by the traced process will cause it to be sent a `SIGTRAP` // signal, giving the parent a chance to gain control before the new program // begins execution. // // `PTRACE_O_TRACEEXEC` is not set by default, so it is not set when the child // requests the attach. We will thus see its exec as a `SIGTRAP`, no matter what // is set in `self.options`. let stop = Stop::SyscallExit; let mut tracee = Tracee::new(pid, None, stop); // Update the tracee state so subsequent traps are interpreted correctly. *state = State::Traced; // Set global tracing options on this root tracee. Auto-attached tracees from // fork, clone, and exec will inherit them. tracee.set_options(self.options)?; tracee } else { let stop = Stop::SignalDelivery { signal: SIGTRAP }; Tracee::new(pid, None, stop) } }, WaitStatus::Stopped(pid, signal) => { if signal == SIGSTOP { if let Some(state) = self.tracee_state_mut(pid) { if *state == State::Attaching { *state = State::Traced; let stop = Stop::Attach; let tracee = Tracee::new(pid, None, stop); return Ok(Some(tracee)); } } else { // We may see an attach-stop out-of-order, before the ptrace-event-stop // which would otherwise have us mark it as `Attaching`. Since `Attaching` // only exists to let us know that the next stop (i.e. this stop) is an // attach-stop, we can directly initialize this tracee as `Traced`. self.set_tracee_state(pid, State::Traced); let stop = Stop::Attach; let tracee = Tracee::new(pid, None, stop); return Ok(Some(tracee)); } } let stop = if is_group_stop(pid, signal)? { Stop::Group { signal } } else { Stop::SignalDelivery { signal } }; Tracee::new(pid, signal, stop) }, WaitStatus::PtraceEvent(pid, signal, code) => { match code { libc::PTRACE_EVENT_FORK => { let evt_data = ptrace::getevent(pid).died_if_esrch(pid)?; let new = Pid::from_raw(evt_data as u32 as i32); // When we return, `new` will start as a tracee, but will be delivered a // `SIGSTOP`. Mark it so we can recognize the `SIGSTOP` as an attach-stop. self.mark_tracee(new); let stop = Stop::Fork { new }; Tracee::new(pid, signal, stop) }, libc::PTRACE_EVENT_CLONE => { let evt_data = ptrace::getevent(pid).died_if_esrch(pid)?; let new = Pid::from_raw(evt_data as u32 as i32); // When we return, `new` will start as a tracee, but will be delivered a // `SIGSTOP`. Mark it so we can recognize the `SIGSTOP` as an attach-stop. self.mark_tracee(new); let stop = Stop::Clone { new }; Tracee::new(pid, signal, stop) }, libc::PTRACE_EVENT_EXEC => { // We are in one of two cases. The exec has either occurred on the main // thread of the thread group, or not. In either case, the new tid of the // execing thread will be equal to the tgid. In the off-main case, this is // a change, and the old state for the tid == tgid will be invalid. // The current `pid` is now equal to the tgid of `old`. let evt_data = ptrace::getevent(pid).died_if_esrch(pid)?; let old = Pid::from_raw(evt_data as u32 as i32); if old != pid { // We exec'd off-thread, and previous tid state is now invalid. self.remove_tracee(old); } // We know we are in a syscall. Make sure we can correctly label the next // syscall-stop as an exit-stop. // // Important: if we trace all the syscall-stops, we will report the syscall- // enter-stop as occurring on `old`, but its matching syscall-exit-stop as // occurring on `pid`. This is correct, but might look odd. self.set_tracee_state(pid, State::Syscalling); let stop = Stop::Exec { old }; Tracee::new(pid, signal, stop) }, libc::PTRACE_EVENT_EXIT => { // In this context, `PTRACE_GETEVENTMSG` returns the pending wait status // as an `unsigned long`. We are only interested in the low 16-bit word. let status = ptrace::getevent(pid).died_if_esrch(pid)? as u16; // Mark the tracee as exiting so we can detach on next restart. self.set_tracee_state(pid, State::Exiting); let stop = match ExitType::parse(status)? { ExitType::Exit(exit_code) => Stop::Exiting { exit_code }, ExitType::Signaled(signal, core_dumped) => Stop::Signaling { signal, core_dumped }, }; Tracee::new(pid, signal, stop) }, libc::PTRACE_EVENT_VFORK => { let evt_data = ptrace::getevent(pid).died_if_esrch(pid)?; let new = Pid::from_raw(evt_data as u32 as i32); self.mark_tracee(new); let stop = Stop::Vfork { new }; Tracee::new(pid, signal, stop) }, libc::PTRACE_EVENT_VFORK_DONE => { let evt_data = ptrace::getevent(pid).died_if_esrch(pid)?; let new = Pid::from_raw(evt_data as u32 as i32); let stop = Stop::VforkDone {new }; Tracee::new(pid, signal, stop) }, libc::PTRACE_EVENT_SECCOMP => { // `SECCOMP_RET_DATA`, which is the low 16 bits of an int. let data = ptrace::getevent(pid).died_if_esrch(pid)? as u16; let stop = Stop::Seccomp { data }; if let Some(state) = self.tracee_state_mut(pid) { *state = State::Syscalling; } else { internal_error!("seccomp ptrace-event-stop for non-tracee"); } Tracee::new(pid, signal, stop) }, libc::PTRACE_EVENT_STOP => { // Unreachable by us, since we do not expose `PTRACE_SEIZE` &c. internal_error!("unreachable ptrace-event-stop") }, _ => { // All kernel-delivered `event` values are matched above. internal_error!("unexpected ptrace-event-stop code") }, } }, // A signal-delivery-stop never happens between syscall-enter-stop and syscall-exit-stop. // It will always happen _after_ syscall-exit-stop, and not necessarily immediately. We // may observe ptrace-event-stops in-between -enter and -exit. // // From the manual: // // No matter which method caused the syscall-entry-stop, if the tracer restarts // the tracee with PTRACE_SYSCALL, the tracee enters syscall-exit-stop // when the system call is finished, or if it is interrupted by a sig‐ // nal. (That is, signal-delivery-stop never happens between syscall- // enter-stop and syscall-exit-stop; it happens after syscall-exit- // stop.). If the tracee is continued using any other method (including // PTRACE_SYSEMU), no syscall-exit-stop occurs. // // [...] // // Syscall-enter-stop and syscall-exit-stop are indistinguishable from // each other by the tracer. The tracer needs to keep track of the // sequence of ptrace-stops in order to not misinterpret syscall-enter- // stop as syscall-exit-stop or vice versa. In general, a syscall- // enter-stop is always followed by syscall-exit-stop, PTRACE_EVENT // stop, or the tracee's death; no other kinds of ptrace-stop can occur // in between. However, note that seccomp stops (see below) can cause // syscall-exit-stops, without preceding syscall-entry-stops. If sec‐ // comp is in use, care needs to be taken not to misinterpret such stops // as syscall-entry-stops. // WaitStatus::PtraceSyscall(pid) => { let stop = match self.tracee_state_mut(pid) { Some(state) => { match state { State::Syscalling => { *state = State::Traced; Stop::SyscallExit }, State::Traced => { *state = State::Syscalling; Stop::SyscallEnter }, State::Attaching => { // A tracee in this state is waiting for a `SIGSTOP`, which is an // artifact of `PTRACE_ATTACH`. The next wait status will thus be // either a `SIGSTOP`, `SIGKILL`, or a `PTRACE_EVENT_EXIT`. internal_error!("syscall-stop for `Attaching` tracee") }, State::Spawned => { // We only set the tracee state to `Spawned` after a successful call // to `Command::spawn()` with a pre-exec `TRACEME` request. // // The self-attached tracee will continue until `execve()`. Since it // can only self-attach with default options, the `execve()` will be // seen as a `SIGTRAP` signal-delivery-stop, not a syscall-stop or // ptrace-event-stop, and so we can never reach this case. internal_error!("syscall-stop for `Spawning` tracee") }, State::Exiting => { // If the tracee was in the `Exiting` state, we should have detached on // the next restart request. internal_error!("unexpected event for detached tracee") }, } }, None => { // Assumes any pid we are tracing is also indexed in `self.tracees`. internal_error!("syscall-stop for unregistered tracee") }, }; Tracee::new(pid, None, stop) }, // Assume `!WNOHANG`, `!WCONTINUED`. WaitStatus::Continued(_) | WaitStatus::StillAlive => internal_error!("unreachable `wait()` status"), }; Ok(Some(tracee)) } fn remove_tracee(&mut self, pid: Pid) -> Option { info!(pid = pid.as_raw(), "removing tracee"); self.tracees.remove(&pid.as_raw()) } fn tracee_state(&self, pid: Pid) -> Option { self.tracees.get(&pid.as_raw()).copied() } fn set_tracee_state(&mut self, pid: Pid, state: State) { debug!(pid = pid.as_raw(), ?state, "setting tracee state"); self.tracees.insert(pid.as_raw(), state); } fn tracee_state_mut(&mut self, pid: Pid) -> Option<&mut State> { self.tracees.get_mut(&pid.as_raw()) } // Mark `pid` as a new tracee pending attach-stop, if it isn't already known. fn mark_tracee(&mut self, pid: Pid) { debug!(pid = pid.as_raw(), "marking tracee as attaching if unknown"); if !self.tracees.contains_key(&pid.as_raw()) { info!(pid = pid.as_raw(), "attaching to new tracee"); } self.tracees.entry(pid.as_raw()).or_insert(State::Attaching); } } #[derive(Clone, Copy, Debug, Eq, PartialEq)] enum ExitType { Exit(i32), Signaled(Signal, bool), } impl ExitType { fn parse(status: u16) -> Result { // The bit layout of the word `status` is: // // 15 8 7 0 // +-------------------------+---+---------------------+ // | exit_code | c | sig_no | // +-------------------------+---+---------------------+ // // If `status[6:0]` is nonzero, then `pid` is being signaled with `sig_no`, // and a set `status[7]` bit flags a core dump. Otherwise, it is a normal // exit with exit code `status[15:8]`. let sig_no = status & 0x7f; let exiting = sig_no == 0; let ty = if exiting { // Extract, zero-extend, cast. let exit_code = (status >> 8) as u8 as u32 as i32; ExitType::Exit(exit_code) } else { use std::convert::TryFrom; let core_dump = (status & (1 << 7)) >> 7; let signal = Signal::try_from(sig_no as i32)?; let core_dump = core_dump > 0; ExitType::Signaled(signal, core_dump) }; Ok(ty) } } // Check if a wait stop with signal delivery is a group-stop. // // Assumes attach-stop has already been ruled out. fn is_group_stop(pid: Pid, sig: Signal) -> Result { use Signal::*; match sig { SIGSTOP | SIGTSTP | SIGTTIN | SIGTTOU => { // Possible group-stop. Check `siginfo` to disambiguate. // // From the manual: // // If PTRACE_GETSIGINFO fails with EINVAL, then it is definitely a // group-stop. (Other failure codes are possible, such as ESRCH // ("no such process") if a SIGKILL killed the tracee.) // match ptrace::getsiginfo(pid) { Err(Errno::EINVAL) => Ok(true), Err(err) => Err(err.into()), Ok(_) => Ok(false) } }, _ => { // Definitely not a group-stop. // // From the manual: // // The call can be avoided if the signal is not SIGSTOP, SIGTSTP, // SIGTTIN, or SIGTTOU; only these four signals are stopping signals. // If the tracer sees something else, it can't be a group-stop. // Ok(false) }, } } pete-0.13.0/src/x86.rs000064400000000000000000000022451046102023000124270ustar 00000000000000/// Register used to control or query hardware debug operations and state. /// /// Accessing debug registers directly is a privileged operation, but a tracee's debug /// registers are accessible via the `PEEKUSER` and `POKEUSER` requests. The relevant /// subset of possible `USER` requests are available via `Tracee::debug_register()` and /// `Tracee::set_debug_register()`. /// /// See: Intel SDM, Vol. 3, 17.2 #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum DebugRegister { /// Debug address register 0. Dr0 = 0, /// Debug address register 1. Dr1, /// Debug address register 2. Dr2, /// Debug address register 3. Dr3, /// Reserved. Ignored when used via `ptrace(2)`. Dr4, /// Reserved. Ignored when used via `ptrace(2)`. Dr5, /// Debug status register. Dr6, /// Debug control register. Dr7, } impl DebugRegister { /// Return the offset into debug register array in the virtual `user` struct. pub(crate) fn user_offset() -> u64 { memoffset::offset_of!(libc::user, u_debugreg) as u64 } } impl From for u64 { fn from(dr: DebugRegister) -> u64 { dr as u64 } }