├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── intentrace-example.jpg ├── intentrace-example.png ├── itrace.png └── src ├── auxiliary.rs ├── cli.rs ├── colors.rs ├── macro_interps.rs ├── main.rs ├── one_line_formatter.rs ├── peeker_poker.rs ├── return_resolvers.rs ├── syscall_annotations_map.rs ├── syscall_categories.rs ├── syscall_object.rs ├── syscall_skeleton_map.rs ├── types.rs ├── utilities.rs └── writer.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "adler2" 7 | version = "2.0.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" 10 | 11 | [[package]] 12 | name = "android-tzdata" 13 | version = "0.1.1" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" 16 | 17 | [[package]] 18 | name = "android_system_properties" 19 | version = "0.1.5" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" 22 | dependencies = [ 23 | "libc", 24 | ] 25 | 26 | [[package]] 27 | name = "anstream" 28 | version = "0.6.18" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" 31 | dependencies = [ 32 | "anstyle", 33 | "anstyle-parse", 34 | "anstyle-query", 35 | "anstyle-wincon", 36 | "colorchoice", 37 | "is_terminal_polyfill", 38 | "utf8parse", 39 | ] 40 | 41 | [[package]] 42 | name = "anstyle" 43 | version = "1.0.10" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" 46 | 47 | [[package]] 48 | name = "anstyle-parse" 49 | version = "0.2.6" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" 52 | dependencies = [ 53 | "utf8parse", 54 | ] 55 | 56 | [[package]] 57 | name = "anstyle-query" 58 | version = "1.1.2" 59 | source = "registry+https://github.com/rust-lang/crates.io-index" 60 | checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" 61 | dependencies = [ 62 | "windows-sys 0.59.0", 63 | ] 64 | 65 | [[package]] 66 | name = "anstyle-wincon" 67 | version = "3.0.7" 68 | source = "registry+https://github.com/rust-lang/crates.io-index" 69 | checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" 70 | dependencies = [ 71 | "anstyle", 72 | "once_cell", 73 | "windows-sys 0.59.0", 74 | ] 75 | 76 | [[package]] 77 | name = "anyhow" 78 | version = "1.0.98" 79 | source = "registry+https://github.com/rust-lang/crates.io-index" 80 | checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" 81 | 82 | [[package]] 83 | name = "autocfg" 84 | version = "1.4.0" 85 | source = "registry+https://github.com/rust-lang/crates.io-index" 86 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 87 | 88 | [[package]] 89 | name = "bitflags" 90 | version = "1.3.2" 91 | source = "registry+https://github.com/rust-lang/crates.io-index" 92 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 93 | 94 | [[package]] 95 | name = "bitflags" 96 | version = "2.9.0" 97 | source = "registry+https://github.com/rust-lang/crates.io-index" 98 | checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" 99 | 100 | [[package]] 101 | name = "bumpalo" 102 | version = "3.17.0" 103 | source = "registry+https://github.com/rust-lang/crates.io-index" 104 | checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" 105 | 106 | [[package]] 107 | name = "bytecount" 108 | version = "0.6.8" 109 | source = "registry+https://github.com/rust-lang/crates.io-index" 110 | checksum = "5ce89b21cab1437276d2650d57e971f9d548a2d9037cc231abdc0562b97498ce" 111 | 112 | [[package]] 113 | name = "cc" 114 | version = "1.2.17" 115 | source = "registry+https://github.com/rust-lang/crates.io-index" 116 | checksum = "1fcb57c740ae1daf453ae85f16e37396f672b039e00d9d866e07ddb24e328e3a" 117 | dependencies = [ 118 | "shlex", 119 | ] 120 | 121 | [[package]] 122 | name = "cfg-if" 123 | version = "1.0.0" 124 | source = "registry+https://github.com/rust-lang/crates.io-index" 125 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 126 | 127 | [[package]] 128 | name = "cfg_aliases" 129 | version = "0.2.1" 130 | source = "registry+https://github.com/rust-lang/crates.io-index" 131 | checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" 132 | 133 | [[package]] 134 | name = "chrono" 135 | version = "0.4.40" 136 | source = "registry+https://github.com/rust-lang/crates.io-index" 137 | checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c" 138 | dependencies = [ 139 | "android-tzdata", 140 | "iana-time-zone", 141 | "num-traits", 142 | "windows-link", 143 | ] 144 | 145 | [[package]] 146 | name = "clap" 147 | version = "4.5.32" 148 | source = "registry+https://github.com/rust-lang/crates.io-index" 149 | checksum = "6088f3ae8c3608d19260cd7445411865a485688711b78b5be70d78cd96136f83" 150 | dependencies = [ 151 | "clap_builder", 152 | "clap_derive", 153 | ] 154 | 155 | [[package]] 156 | name = "clap_builder" 157 | version = "4.5.32" 158 | source = "registry+https://github.com/rust-lang/crates.io-index" 159 | checksum = "22a7ef7f676155edfb82daa97f99441f3ebf4a58d5e32f295a56259f1b6facc8" 160 | dependencies = [ 161 | "anstream", 162 | "anstyle", 163 | "clap_lex", 164 | "strsim", 165 | ] 166 | 167 | [[package]] 168 | name = "clap_derive" 169 | version = "4.5.32" 170 | source = "registry+https://github.com/rust-lang/crates.io-index" 171 | checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" 172 | dependencies = [ 173 | "heck", 174 | "proc-macro2", 175 | "quote", 176 | "syn", 177 | ] 178 | 179 | [[package]] 180 | name = "clap_lex" 181 | version = "0.7.4" 182 | source = "registry+https://github.com/rust-lang/crates.io-index" 183 | checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" 184 | 185 | [[package]] 186 | name = "colorchoice" 187 | version = "1.0.3" 188 | source = "registry+https://github.com/rust-lang/crates.io-index" 189 | checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" 190 | 191 | [[package]] 192 | name = "colored" 193 | version = "3.0.0" 194 | source = "registry+https://github.com/rust-lang/crates.io-index" 195 | checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e" 196 | dependencies = [ 197 | "windows-sys 0.59.0", 198 | ] 199 | 200 | [[package]] 201 | name = "core-foundation-sys" 202 | version = "0.8.7" 203 | source = "registry+https://github.com/rust-lang/crates.io-index" 204 | checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" 205 | 206 | [[package]] 207 | name = "crc32fast" 208 | version = "1.4.2" 209 | source = "registry+https://github.com/rust-lang/crates.io-index" 210 | checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" 211 | dependencies = [ 212 | "cfg-if", 213 | ] 214 | 215 | [[package]] 216 | name = "crossterm" 217 | version = "0.28.1" 218 | source = "registry+https://github.com/rust-lang/crates.io-index" 219 | checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" 220 | dependencies = [ 221 | "bitflags 2.9.0", 222 | "crossterm_winapi", 223 | "mio", 224 | "parking_lot", 225 | "rustix", 226 | "signal-hook", 227 | "signal-hook-mio", 228 | "winapi", 229 | ] 230 | 231 | [[package]] 232 | name = "crossterm_winapi" 233 | version = "0.9.1" 234 | source = "registry+https://github.com/rust-lang/crates.io-index" 235 | checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" 236 | dependencies = [ 237 | "winapi", 238 | ] 239 | 240 | [[package]] 241 | name = "ctrlc" 242 | version = "3.4.5" 243 | source = "registry+https://github.com/rust-lang/crates.io-index" 244 | checksum = "90eeab0aa92f3f9b4e87f258c72b139c207d251f9cbc1080a0086b86a8870dd3" 245 | dependencies = [ 246 | "nix 0.29.0", 247 | "windows-sys 0.59.0", 248 | ] 249 | 250 | [[package]] 251 | name = "deranged" 252 | version = "0.4.1" 253 | source = "registry+https://github.com/rust-lang/crates.io-index" 254 | checksum = "28cfac68e08048ae1883171632c2aef3ebc555621ae56fbccce1cbf22dd7f058" 255 | dependencies = [ 256 | "powerfmt", 257 | ] 258 | 259 | [[package]] 260 | name = "downcast" 261 | version = "0.11.0" 262 | source = "registry+https://github.com/rust-lang/crates.io-index" 263 | checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1" 264 | 265 | [[package]] 266 | name = "errno" 267 | version = "0.3.10" 268 | source = "registry+https://github.com/rust-lang/crates.io-index" 269 | checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" 270 | dependencies = [ 271 | "libc", 272 | "windows-sys 0.59.0", 273 | ] 274 | 275 | [[package]] 276 | name = "flate2" 277 | version = "1.1.0" 278 | source = "registry+https://github.com/rust-lang/crates.io-index" 279 | checksum = "11faaf5a5236997af9848be0bef4db95824b1d534ebc64d0f0c6cf3e67bd38dc" 280 | dependencies = [ 281 | "crc32fast", 282 | "miniz_oxide", 283 | ] 284 | 285 | [[package]] 286 | name = "fnv" 287 | version = "1.0.7" 288 | source = "registry+https://github.com/rust-lang/crates.io-index" 289 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 290 | 291 | [[package]] 292 | name = "fragile" 293 | version = "2.0.1" 294 | source = "registry+https://github.com/rust-lang/crates.io-index" 295 | checksum = "28dd6caf6059519a65843af8fe2a3ae298b14b80179855aeb4adc2c1934ee619" 296 | 297 | [[package]] 298 | name = "heck" 299 | version = "0.5.0" 300 | source = "registry+https://github.com/rust-lang/crates.io-index" 301 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 302 | 303 | [[package]] 304 | name = "hex" 305 | version = "0.4.3" 306 | source = "registry+https://github.com/rust-lang/crates.io-index" 307 | checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" 308 | 309 | [[package]] 310 | name = "iana-time-zone" 311 | version = "0.1.62" 312 | source = "registry+https://github.com/rust-lang/crates.io-index" 313 | checksum = "b2fd658b06e56721792c5df4475705b6cda790e9298d19d2f8af083457bcd127" 314 | dependencies = [ 315 | "android_system_properties", 316 | "core-foundation-sys", 317 | "iana-time-zone-haiku", 318 | "js-sys", 319 | "log", 320 | "wasm-bindgen", 321 | "windows-core", 322 | ] 323 | 324 | [[package]] 325 | name = "iana-time-zone-haiku" 326 | version = "0.1.2" 327 | source = "registry+https://github.com/rust-lang/crates.io-index" 328 | checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" 329 | dependencies = [ 330 | "cc", 331 | ] 332 | 333 | [[package]] 334 | name = "if_chain" 335 | version = "1.0.2" 336 | source = "registry+https://github.com/rust-lang/crates.io-index" 337 | checksum = "cb56e1aa765b4b4f3aadfab769793b7087bb03a4ea4920644a6d238e2df5b9ed" 338 | 339 | [[package]] 340 | name = "intentrace" 341 | version = "0.10.3" 342 | dependencies = [ 343 | "anyhow", 344 | "clap", 345 | "colored", 346 | "ctrlc", 347 | "if_chain", 348 | "nix 0.29.0", 349 | "num-traits", 350 | "pete", 351 | "procfs", 352 | "syscalls", 353 | "tabled", 354 | "termbg", 355 | "thousands", 356 | "unicode-segmentation", 357 | "uzers", 358 | ] 359 | 360 | [[package]] 361 | name = "is_terminal_polyfill" 362 | version = "1.70.1" 363 | source = "registry+https://github.com/rust-lang/crates.io-index" 364 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" 365 | 366 | [[package]] 367 | name = "itoa" 368 | version = "1.0.15" 369 | source = "registry+https://github.com/rust-lang/crates.io-index" 370 | checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" 371 | 372 | [[package]] 373 | name = "js-sys" 374 | version = "0.3.77" 375 | source = "registry+https://github.com/rust-lang/crates.io-index" 376 | checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" 377 | dependencies = [ 378 | "once_cell", 379 | "wasm-bindgen", 380 | ] 381 | 382 | [[package]] 383 | name = "libc" 384 | version = "0.2.171" 385 | source = "registry+https://github.com/rust-lang/crates.io-index" 386 | checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" 387 | 388 | [[package]] 389 | name = "linux-raw-sys" 390 | version = "0.4.15" 391 | source = "registry+https://github.com/rust-lang/crates.io-index" 392 | checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" 393 | 394 | [[package]] 395 | name = "lock_api" 396 | version = "0.4.12" 397 | source = "registry+https://github.com/rust-lang/crates.io-index" 398 | checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" 399 | dependencies = [ 400 | "autocfg", 401 | "scopeguard", 402 | ] 403 | 404 | [[package]] 405 | name = "log" 406 | version = "0.4.27" 407 | source = "registry+https://github.com/rust-lang/crates.io-index" 408 | checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" 409 | 410 | [[package]] 411 | name = "memoffset" 412 | version = "0.7.1" 413 | source = "registry+https://github.com/rust-lang/crates.io-index" 414 | checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" 415 | dependencies = [ 416 | "autocfg", 417 | ] 418 | 419 | [[package]] 420 | name = "memoffset" 421 | version = "0.8.0" 422 | source = "registry+https://github.com/rust-lang/crates.io-index" 423 | checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1" 424 | dependencies = [ 425 | "autocfg", 426 | ] 427 | 428 | [[package]] 429 | name = "miniz_oxide" 430 | version = "0.8.5" 431 | source = "registry+https://github.com/rust-lang/crates.io-index" 432 | checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5" 433 | dependencies = [ 434 | "adler2", 435 | ] 436 | 437 | [[package]] 438 | name = "mio" 439 | version = "1.0.3" 440 | source = "registry+https://github.com/rust-lang/crates.io-index" 441 | checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" 442 | dependencies = [ 443 | "libc", 444 | "log", 445 | "wasi", 446 | "windows-sys 0.52.0", 447 | ] 448 | 449 | [[package]] 450 | name = "mockall" 451 | version = "0.13.1" 452 | source = "registry+https://github.com/rust-lang/crates.io-index" 453 | checksum = "39a6bfcc6c8c7eed5ee98b9c3e33adc726054389233e201c95dab2d41a3839d2" 454 | dependencies = [ 455 | "cfg-if", 456 | "downcast", 457 | "fragile", 458 | "mockall_derive", 459 | "predicates", 460 | "predicates-tree", 461 | ] 462 | 463 | [[package]] 464 | name = "mockall_derive" 465 | version = "0.13.1" 466 | source = "registry+https://github.com/rust-lang/crates.io-index" 467 | checksum = "25ca3004c2efe9011bd4e461bd8256445052b9615405b4f7ea43fc8ca5c20898" 468 | dependencies = [ 469 | "cfg-if", 470 | "proc-macro2", 471 | "quote", 472 | "syn", 473 | ] 474 | 475 | [[package]] 476 | name = "nix" 477 | version = "0.26.4" 478 | source = "registry+https://github.com/rust-lang/crates.io-index" 479 | checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" 480 | dependencies = [ 481 | "bitflags 1.3.2", 482 | "cfg-if", 483 | "libc", 484 | "memoffset 0.7.1", 485 | "pin-utils", 486 | ] 487 | 488 | [[package]] 489 | name = "nix" 490 | version = "0.29.0" 491 | source = "registry+https://github.com/rust-lang/crates.io-index" 492 | checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" 493 | dependencies = [ 494 | "bitflags 2.9.0", 495 | "cfg-if", 496 | "cfg_aliases", 497 | "libc", 498 | ] 499 | 500 | [[package]] 501 | name = "num-conv" 502 | version = "0.1.0" 503 | source = "registry+https://github.com/rust-lang/crates.io-index" 504 | checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" 505 | 506 | [[package]] 507 | name = "num-traits" 508 | version = "0.2.19" 509 | source = "registry+https://github.com/rust-lang/crates.io-index" 510 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 511 | dependencies = [ 512 | "autocfg", 513 | ] 514 | 515 | [[package]] 516 | name = "num_threads" 517 | version = "0.1.7" 518 | source = "registry+https://github.com/rust-lang/crates.io-index" 519 | checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" 520 | dependencies = [ 521 | "libc", 522 | ] 523 | 524 | [[package]] 525 | name = "once_cell" 526 | version = "1.21.1" 527 | source = "registry+https://github.com/rust-lang/crates.io-index" 528 | checksum = "d75b0bedcc4fe52caa0e03d9f1151a323e4aa5e2d78ba3580400cd3c9e2bc4bc" 529 | 530 | [[package]] 531 | name = "papergrid" 532 | version = "0.14.0" 533 | source = "registry+https://github.com/rust-lang/crates.io-index" 534 | checksum = "b915f831b85d984193fdc3d3611505871dc139b2534530fa01c1a6a6707b6723" 535 | dependencies = [ 536 | "bytecount", 537 | "fnv", 538 | "unicode-width", 539 | ] 540 | 541 | [[package]] 542 | name = "parking_lot" 543 | version = "0.12.3" 544 | source = "registry+https://github.com/rust-lang/crates.io-index" 545 | checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" 546 | dependencies = [ 547 | "lock_api", 548 | "parking_lot_core", 549 | ] 550 | 551 | [[package]] 552 | name = "parking_lot_core" 553 | version = "0.9.10" 554 | source = "registry+https://github.com/rust-lang/crates.io-index" 555 | checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" 556 | dependencies = [ 557 | "cfg-if", 558 | "libc", 559 | "redox_syscall", 560 | "smallvec", 561 | "windows-targets", 562 | ] 563 | 564 | [[package]] 565 | name = "pete" 566 | version = "0.12.0" 567 | source = "registry+https://github.com/rust-lang/crates.io-index" 568 | checksum = "0f09c1c1ad40df294ff8643fe88a3dc64fff3293b6bc0ed9f71aff71f7086cbd" 569 | dependencies = [ 570 | "libc", 571 | "memoffset 0.8.0", 572 | "nix 0.26.4", 573 | "thiserror 1.0.69", 574 | ] 575 | 576 | [[package]] 577 | name = "pin-utils" 578 | version = "0.1.0" 579 | source = "registry+https://github.com/rust-lang/crates.io-index" 580 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 581 | 582 | [[package]] 583 | name = "powerfmt" 584 | version = "0.2.0" 585 | source = "registry+https://github.com/rust-lang/crates.io-index" 586 | checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" 587 | 588 | [[package]] 589 | name = "predicates" 590 | version = "3.1.3" 591 | source = "registry+https://github.com/rust-lang/crates.io-index" 592 | checksum = "a5d19ee57562043d37e82899fade9a22ebab7be9cef5026b07fda9cdd4293573" 593 | dependencies = [ 594 | "anstyle", 595 | "predicates-core", 596 | ] 597 | 598 | [[package]] 599 | name = "predicates-core" 600 | version = "1.0.9" 601 | source = "registry+https://github.com/rust-lang/crates.io-index" 602 | checksum = "727e462b119fe9c93fd0eb1429a5f7647394014cf3c04ab2c0350eeb09095ffa" 603 | 604 | [[package]] 605 | name = "predicates-tree" 606 | version = "1.0.12" 607 | source = "registry+https://github.com/rust-lang/crates.io-index" 608 | checksum = "72dd2d6d381dfb73a193c7fca536518d7caee39fc8503f74e7dc0be0531b425c" 609 | dependencies = [ 610 | "predicates-core", 611 | "termtree", 612 | ] 613 | 614 | [[package]] 615 | name = "proc-macro-error-attr2" 616 | version = "2.0.0" 617 | source = "registry+https://github.com/rust-lang/crates.io-index" 618 | checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" 619 | dependencies = [ 620 | "proc-macro2", 621 | "quote", 622 | ] 623 | 624 | [[package]] 625 | name = "proc-macro-error2" 626 | version = "2.0.1" 627 | source = "registry+https://github.com/rust-lang/crates.io-index" 628 | checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" 629 | dependencies = [ 630 | "proc-macro-error-attr2", 631 | "proc-macro2", 632 | "quote", 633 | "syn", 634 | ] 635 | 636 | [[package]] 637 | name = "proc-macro2" 638 | version = "1.0.94" 639 | source = "registry+https://github.com/rust-lang/crates.io-index" 640 | checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" 641 | dependencies = [ 642 | "unicode-ident", 643 | ] 644 | 645 | [[package]] 646 | name = "procfs" 647 | version = "0.17.0" 648 | source = "registry+https://github.com/rust-lang/crates.io-index" 649 | checksum = "cc5b72d8145275d844d4b5f6d4e1eef00c8cd889edb6035c21675d1bb1f45c9f" 650 | dependencies = [ 651 | "bitflags 2.9.0", 652 | "chrono", 653 | "flate2", 654 | "hex", 655 | "procfs-core", 656 | "rustix", 657 | ] 658 | 659 | [[package]] 660 | name = "procfs-core" 661 | version = "0.17.0" 662 | source = "registry+https://github.com/rust-lang/crates.io-index" 663 | checksum = "239df02d8349b06fc07398a3a1697b06418223b1c7725085e801e7c0fc6a12ec" 664 | dependencies = [ 665 | "bitflags 2.9.0", 666 | "chrono", 667 | "hex", 668 | ] 669 | 670 | [[package]] 671 | name = "quote" 672 | version = "1.0.40" 673 | source = "registry+https://github.com/rust-lang/crates.io-index" 674 | checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" 675 | dependencies = [ 676 | "proc-macro2", 677 | ] 678 | 679 | [[package]] 680 | name = "redox_syscall" 681 | version = "0.5.10" 682 | source = "registry+https://github.com/rust-lang/crates.io-index" 683 | checksum = "0b8c0c260b63a8219631167be35e6a988e9554dbd323f8bd08439c8ed1302bd1" 684 | dependencies = [ 685 | "bitflags 2.9.0", 686 | ] 687 | 688 | [[package]] 689 | name = "rustix" 690 | version = "0.38.44" 691 | source = "registry+https://github.com/rust-lang/crates.io-index" 692 | checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" 693 | dependencies = [ 694 | "bitflags 2.9.0", 695 | "errno", 696 | "libc", 697 | "linux-raw-sys", 698 | "windows-sys 0.59.0", 699 | ] 700 | 701 | [[package]] 702 | name = "rustversion" 703 | version = "1.0.20" 704 | source = "registry+https://github.com/rust-lang/crates.io-index" 705 | checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" 706 | 707 | [[package]] 708 | name = "scopeguard" 709 | version = "1.2.0" 710 | source = "registry+https://github.com/rust-lang/crates.io-index" 711 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 712 | 713 | [[package]] 714 | name = "serde" 715 | version = "1.0.219" 716 | source = "registry+https://github.com/rust-lang/crates.io-index" 717 | checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" 718 | dependencies = [ 719 | "serde_derive", 720 | ] 721 | 722 | [[package]] 723 | name = "serde_derive" 724 | version = "1.0.219" 725 | source = "registry+https://github.com/rust-lang/crates.io-index" 726 | checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" 727 | dependencies = [ 728 | "proc-macro2", 729 | "quote", 730 | "syn", 731 | ] 732 | 733 | [[package]] 734 | name = "serde_repr" 735 | version = "0.1.20" 736 | source = "registry+https://github.com/rust-lang/crates.io-index" 737 | checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" 738 | dependencies = [ 739 | "proc-macro2", 740 | "quote", 741 | "syn", 742 | ] 743 | 744 | [[package]] 745 | name = "shlex" 746 | version = "1.3.0" 747 | source = "registry+https://github.com/rust-lang/crates.io-index" 748 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 749 | 750 | [[package]] 751 | name = "signal-hook" 752 | version = "0.3.17" 753 | source = "registry+https://github.com/rust-lang/crates.io-index" 754 | checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" 755 | dependencies = [ 756 | "libc", 757 | "signal-hook-registry", 758 | ] 759 | 760 | [[package]] 761 | name = "signal-hook-mio" 762 | version = "0.2.4" 763 | source = "registry+https://github.com/rust-lang/crates.io-index" 764 | checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" 765 | dependencies = [ 766 | "libc", 767 | "mio", 768 | "signal-hook", 769 | ] 770 | 771 | [[package]] 772 | name = "signal-hook-registry" 773 | version = "1.4.2" 774 | source = "registry+https://github.com/rust-lang/crates.io-index" 775 | checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" 776 | dependencies = [ 777 | "libc", 778 | ] 779 | 780 | [[package]] 781 | name = "simplelog" 782 | version = "0.12.2" 783 | source = "registry+https://github.com/rust-lang/crates.io-index" 784 | checksum = "16257adbfaef1ee58b1363bdc0664c9b8e1e30aed86049635fb5f147d065a9c0" 785 | dependencies = [ 786 | "log", 787 | "termcolor", 788 | "time", 789 | ] 790 | 791 | [[package]] 792 | name = "smallvec" 793 | version = "1.14.0" 794 | source = "registry+https://github.com/rust-lang/crates.io-index" 795 | checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" 796 | 797 | [[package]] 798 | name = "strsim" 799 | version = "0.11.1" 800 | source = "registry+https://github.com/rust-lang/crates.io-index" 801 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 802 | 803 | [[package]] 804 | name = "syn" 805 | version = "2.0.100" 806 | source = "registry+https://github.com/rust-lang/crates.io-index" 807 | checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" 808 | dependencies = [ 809 | "proc-macro2", 810 | "quote", 811 | "unicode-ident", 812 | ] 813 | 814 | [[package]] 815 | name = "syscalls" 816 | version = "0.6.18" 817 | source = "registry+https://github.com/rust-lang/crates.io-index" 818 | checksum = "43d0e35dc7d73976a53c7e6d7d177ef804a0c0ee774ec77bcc520c2216fd7cbe" 819 | dependencies = [ 820 | "serde", 821 | "serde_repr", 822 | ] 823 | 824 | [[package]] 825 | name = "tabled" 826 | version = "0.18.0" 827 | source = "registry+https://github.com/rust-lang/crates.io-index" 828 | checksum = "121d8171ee5687a4978d1b244f7d99c43e7385a272185a2f1e1fa4dc0979d444" 829 | dependencies = [ 830 | "papergrid", 831 | "tabled_derive", 832 | ] 833 | 834 | [[package]] 835 | name = "tabled_derive" 836 | version = "0.10.0" 837 | source = "registry+https://github.com/rust-lang/crates.io-index" 838 | checksum = "52d9946811baad81710ec921809e2af67ad77719418673b2a3794932d57b7538" 839 | dependencies = [ 840 | "heck", 841 | "proc-macro-error2", 842 | "proc-macro2", 843 | "quote", 844 | "syn", 845 | ] 846 | 847 | [[package]] 848 | name = "termbg" 849 | version = "0.6.2" 850 | source = "registry+https://github.com/rust-lang/crates.io-index" 851 | checksum = "8bf44577a1adf3dfd7fec3b8763074467e27b2ad35ff9157bc3f0a51bb0a3dd4" 852 | dependencies = [ 853 | "crossterm", 854 | "log", 855 | "mockall", 856 | "scopeguard", 857 | "simplelog", 858 | "thiserror 2.0.12", 859 | "winapi", 860 | ] 861 | 862 | [[package]] 863 | name = "termcolor" 864 | version = "1.4.1" 865 | source = "registry+https://github.com/rust-lang/crates.io-index" 866 | checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" 867 | dependencies = [ 868 | "winapi-util", 869 | ] 870 | 871 | [[package]] 872 | name = "termtree" 873 | version = "0.5.1" 874 | source = "registry+https://github.com/rust-lang/crates.io-index" 875 | checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" 876 | 877 | [[package]] 878 | name = "thiserror" 879 | version = "1.0.69" 880 | source = "registry+https://github.com/rust-lang/crates.io-index" 881 | checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" 882 | dependencies = [ 883 | "thiserror-impl 1.0.69", 884 | ] 885 | 886 | [[package]] 887 | name = "thiserror" 888 | version = "2.0.12" 889 | source = "registry+https://github.com/rust-lang/crates.io-index" 890 | checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" 891 | dependencies = [ 892 | "thiserror-impl 2.0.12", 893 | ] 894 | 895 | [[package]] 896 | name = "thiserror-impl" 897 | version = "1.0.69" 898 | source = "registry+https://github.com/rust-lang/crates.io-index" 899 | checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" 900 | dependencies = [ 901 | "proc-macro2", 902 | "quote", 903 | "syn", 904 | ] 905 | 906 | [[package]] 907 | name = "thiserror-impl" 908 | version = "2.0.12" 909 | source = "registry+https://github.com/rust-lang/crates.io-index" 910 | checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" 911 | dependencies = [ 912 | "proc-macro2", 913 | "quote", 914 | "syn", 915 | ] 916 | 917 | [[package]] 918 | name = "thousands" 919 | version = "0.2.0" 920 | source = "registry+https://github.com/rust-lang/crates.io-index" 921 | checksum = "3bf63baf9f5039dadc247375c29eb13706706cfde997d0330d05aa63a77d8820" 922 | 923 | [[package]] 924 | name = "time" 925 | version = "0.3.41" 926 | source = "registry+https://github.com/rust-lang/crates.io-index" 927 | checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" 928 | dependencies = [ 929 | "deranged", 930 | "itoa", 931 | "libc", 932 | "num-conv", 933 | "num_threads", 934 | "powerfmt", 935 | "serde", 936 | "time-core", 937 | "time-macros", 938 | ] 939 | 940 | [[package]] 941 | name = "time-core" 942 | version = "0.1.4" 943 | source = "registry+https://github.com/rust-lang/crates.io-index" 944 | checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" 945 | 946 | [[package]] 947 | name = "time-macros" 948 | version = "0.2.22" 949 | source = "registry+https://github.com/rust-lang/crates.io-index" 950 | checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" 951 | dependencies = [ 952 | "num-conv", 953 | "time-core", 954 | ] 955 | 956 | [[package]] 957 | name = "unicode-ident" 958 | version = "1.0.18" 959 | source = "registry+https://github.com/rust-lang/crates.io-index" 960 | checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" 961 | 962 | [[package]] 963 | name = "unicode-segmentation" 964 | version = "1.12.0" 965 | source = "registry+https://github.com/rust-lang/crates.io-index" 966 | checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" 967 | 968 | [[package]] 969 | name = "unicode-width" 970 | version = "0.2.0" 971 | source = "registry+https://github.com/rust-lang/crates.io-index" 972 | checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" 973 | 974 | [[package]] 975 | name = "utf8parse" 976 | version = "0.2.2" 977 | source = "registry+https://github.com/rust-lang/crates.io-index" 978 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 979 | 980 | [[package]] 981 | name = "uzers" 982 | version = "0.12.1" 983 | source = "registry+https://github.com/rust-lang/crates.io-index" 984 | checksum = "4df81ff504e7d82ad53e95ed1ad5b72103c11253f39238bcc0235b90768a97dd" 985 | dependencies = [ 986 | "libc", 987 | "log", 988 | ] 989 | 990 | [[package]] 991 | name = "wasi" 992 | version = "0.11.0+wasi-snapshot-preview1" 993 | source = "registry+https://github.com/rust-lang/crates.io-index" 994 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 995 | 996 | [[package]] 997 | name = "wasm-bindgen" 998 | version = "0.2.100" 999 | source = "registry+https://github.com/rust-lang/crates.io-index" 1000 | checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" 1001 | dependencies = [ 1002 | "cfg-if", 1003 | "once_cell", 1004 | "rustversion", 1005 | "wasm-bindgen-macro", 1006 | ] 1007 | 1008 | [[package]] 1009 | name = "wasm-bindgen-backend" 1010 | version = "0.2.100" 1011 | source = "registry+https://github.com/rust-lang/crates.io-index" 1012 | checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" 1013 | dependencies = [ 1014 | "bumpalo", 1015 | "log", 1016 | "proc-macro2", 1017 | "quote", 1018 | "syn", 1019 | "wasm-bindgen-shared", 1020 | ] 1021 | 1022 | [[package]] 1023 | name = "wasm-bindgen-macro" 1024 | version = "0.2.100" 1025 | source = "registry+https://github.com/rust-lang/crates.io-index" 1026 | checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" 1027 | dependencies = [ 1028 | "quote", 1029 | "wasm-bindgen-macro-support", 1030 | ] 1031 | 1032 | [[package]] 1033 | name = "wasm-bindgen-macro-support" 1034 | version = "0.2.100" 1035 | source = "registry+https://github.com/rust-lang/crates.io-index" 1036 | checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" 1037 | dependencies = [ 1038 | "proc-macro2", 1039 | "quote", 1040 | "syn", 1041 | "wasm-bindgen-backend", 1042 | "wasm-bindgen-shared", 1043 | ] 1044 | 1045 | [[package]] 1046 | name = "wasm-bindgen-shared" 1047 | version = "0.2.100" 1048 | source = "registry+https://github.com/rust-lang/crates.io-index" 1049 | checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" 1050 | dependencies = [ 1051 | "unicode-ident", 1052 | ] 1053 | 1054 | [[package]] 1055 | name = "winapi" 1056 | version = "0.3.9" 1057 | source = "registry+https://github.com/rust-lang/crates.io-index" 1058 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1059 | dependencies = [ 1060 | "winapi-i686-pc-windows-gnu", 1061 | "winapi-x86_64-pc-windows-gnu", 1062 | ] 1063 | 1064 | [[package]] 1065 | name = "winapi-i686-pc-windows-gnu" 1066 | version = "0.4.0" 1067 | source = "registry+https://github.com/rust-lang/crates.io-index" 1068 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1069 | 1070 | [[package]] 1071 | name = "winapi-util" 1072 | version = "0.1.9" 1073 | source = "registry+https://github.com/rust-lang/crates.io-index" 1074 | checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" 1075 | dependencies = [ 1076 | "windows-sys 0.59.0", 1077 | ] 1078 | 1079 | [[package]] 1080 | name = "winapi-x86_64-pc-windows-gnu" 1081 | version = "0.4.0" 1082 | source = "registry+https://github.com/rust-lang/crates.io-index" 1083 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1084 | 1085 | [[package]] 1086 | name = "windows-core" 1087 | version = "0.52.0" 1088 | source = "registry+https://github.com/rust-lang/crates.io-index" 1089 | checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" 1090 | dependencies = [ 1091 | "windows-targets", 1092 | ] 1093 | 1094 | [[package]] 1095 | name = "windows-link" 1096 | version = "0.1.1" 1097 | source = "registry+https://github.com/rust-lang/crates.io-index" 1098 | checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" 1099 | 1100 | [[package]] 1101 | name = "windows-sys" 1102 | version = "0.52.0" 1103 | source = "registry+https://github.com/rust-lang/crates.io-index" 1104 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 1105 | dependencies = [ 1106 | "windows-targets", 1107 | ] 1108 | 1109 | [[package]] 1110 | name = "windows-sys" 1111 | version = "0.59.0" 1112 | source = "registry+https://github.com/rust-lang/crates.io-index" 1113 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 1114 | dependencies = [ 1115 | "windows-targets", 1116 | ] 1117 | 1118 | [[package]] 1119 | name = "windows-targets" 1120 | version = "0.52.6" 1121 | source = "registry+https://github.com/rust-lang/crates.io-index" 1122 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 1123 | dependencies = [ 1124 | "windows_aarch64_gnullvm", 1125 | "windows_aarch64_msvc", 1126 | "windows_i686_gnu", 1127 | "windows_i686_gnullvm", 1128 | "windows_i686_msvc", 1129 | "windows_x86_64_gnu", 1130 | "windows_x86_64_gnullvm", 1131 | "windows_x86_64_msvc", 1132 | ] 1133 | 1134 | [[package]] 1135 | name = "windows_aarch64_gnullvm" 1136 | version = "0.52.6" 1137 | source = "registry+https://github.com/rust-lang/crates.io-index" 1138 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 1139 | 1140 | [[package]] 1141 | name = "windows_aarch64_msvc" 1142 | version = "0.52.6" 1143 | source = "registry+https://github.com/rust-lang/crates.io-index" 1144 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 1145 | 1146 | [[package]] 1147 | name = "windows_i686_gnu" 1148 | version = "0.52.6" 1149 | source = "registry+https://github.com/rust-lang/crates.io-index" 1150 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 1151 | 1152 | [[package]] 1153 | name = "windows_i686_gnullvm" 1154 | version = "0.52.6" 1155 | source = "registry+https://github.com/rust-lang/crates.io-index" 1156 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 1157 | 1158 | [[package]] 1159 | name = "windows_i686_msvc" 1160 | version = "0.52.6" 1161 | source = "registry+https://github.com/rust-lang/crates.io-index" 1162 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 1163 | 1164 | [[package]] 1165 | name = "windows_x86_64_gnu" 1166 | version = "0.52.6" 1167 | source = "registry+https://github.com/rust-lang/crates.io-index" 1168 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 1169 | 1170 | [[package]] 1171 | name = "windows_x86_64_gnullvm" 1172 | version = "0.52.6" 1173 | source = "registry+https://github.com/rust-lang/crates.io-index" 1174 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 1175 | 1176 | [[package]] 1177 | name = "windows_x86_64_msvc" 1178 | version = "0.52.6" 1179 | source = "registry+https://github.com/rust-lang/crates.io-index" 1180 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 1181 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "intentrace" 3 | version = "0.10.3" 4 | description = "intentrace is strace with intent, it goes all the way for you instead of half the way." 5 | edition = "2021" 6 | license = "MIT" 7 | repository = "https://github.com/sectordistrict/intentrace" 8 | 9 | [dependencies] 10 | anyhow = "1.0.98" 11 | clap = { version = "4.5.32", features = ["derive"] } 12 | colored = "3.0.0" 13 | ctrlc = { version = "3.4.5", features = ["termination"] } 14 | if_chain = "1.0.2" 15 | nix = { version = "0.29.0", features = ["ptrace", "uio", "signal"] } 16 | num-traits = "0.2.19" 17 | pete = { version = "0.12.0" } 18 | procfs = "0.17.0" 19 | syscalls = "0.6.18" 20 | tabled = "0.18.0" 21 | termbg = "0.6.2" 22 | thousands = "0.2.0" 23 | unicode-segmentation = "1.12.0" 24 | uzers = "0.12.1" 25 | 26 | # [profile.release] 27 | # lto = true 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) [year] [fullname] 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Intentrace 3 |

4 | 5 | # About 6 | 7 | intentrace is a strace for everyone, intentrace works similarly to strace in that it intercepts and records system calls when a process issues them, it then reasons through these syscalls by consulting an enormous backlog of syscall deduction heuristics. 8 | Due to the fact that linux syscalls almost always have dual usage that's obfuscated by libraries, seeing what a syscall is exactly asking for is immensely useful when e.g. a programmer is debugging a crashing binary. 9 | 10 |

11 | Intentrace Example 12 |

13 | 14 | Intentrace follows a similar working methodology to the one employed by the [UniKraft kernel](https://github.com/unikraft) in that it attempts to cover a high percentage of the most popular linux software despite supporting only around 166 syscalls out of the 380+ linux syscalls (see page 8 of the Unikraft Paper for an example of strategic syscall coverage: https://arxiv.org/pdf/2104.12721). It's planned eventually for intentrace to cover all linux syscalls. 15 | 16 | ## Usage 17 | 18 | #### to quickly see how intentrace works in action, you can run simple examples 19 | 20 | `intentrace ls` 21 | 22 | `intentrace google-chrome` 23 | 24 | #### to disable program output from cluttering the syscall feed add `-q` 25 | 26 | `intentrace -q ls` 27 | 28 | #### to include the child processes of multi-threaded programs add `-f` 29 | 30 | `intentrace -f docker run alpine` 31 | 32 | | Parameter | Description | Default value | 33 | | ----------------------------- | --------------------------------------------------------------------------- | ------------- | 34 | | -c
--summary-only | print a summary table | `false` | 35 | | -C
--summary | print a summary table in addition to the normal output | `false` | 36 | | -p `pid`
--attach `pid` | attach to an already running proceess | `not used` | 37 | | -o `file`
--output `file` | redirect intentrace's output to a provided file | `not used` | 38 | | --trace=`syscall1,syscall2` | trace a specific syscall or a group of syscalls delimited by ',' | `not used` | 39 | | -f
--follow-forks | trace child process when traced programs create them | `false` | 40 | | -Z
--failed-only | only print failed syscalls | `false` | 41 | | -q
--mute-stdout | mute traced program's std output | `false` | 42 | 43 | ## Installation 44 | 45 | ### Build from source 46 | 47 | Prerequisites: 48 | 49 | - Latest stable version of [Rust](https://www.rust-lang.org/tools/install) and Cargo. 50 | 51 | Build and run intentrace: 52 | 53 | ``` 54 | git clone https://github.com/sectordistrict/intentrace.git 55 | cd intentrace 56 | cargo build --release 57 | ``` 58 | 59 | ### Install from crates.io: 60 | 61 | ``` 62 | cargo install intentrace 63 | ``` 64 | 65 | ### Package Manager Availability 66 | 67 | [![Packaging status](https://repology.org/badge/vertical-allrepos/intentrace.svg)](https://repology.org/project/intentrace/versions) 68 | 69 | ## Project status 70 | 71 | intentrace is currently in beta, currently multi-threaded programs are a hit and miss. 72 | 73 | intentrace was originally intended to be a 2 window TUI, where a top panel shows a normal stream of syscalls, and a bottom panel contains metadata and clarifications, but this was abandoned in favor of the current scheme. 74 | 75 | #### Supported architecture 76 | 77 | intentrace currently only supports `x86-64`, given that the program is currently in beta, PRs for cross compatibility will unfortunately not be accepted until the program is stable enough. 78 | 79 | ## Contributing 80 | 81 | Support intentrace by contributing issues and PRs, don't feel discouraged from filing issues or creating PRs. Reading the source is a great way to learn how linux works. 82 | 83 | Feel free to file Issues and open Pull Requests that tackle any of: 84 | 85 | - providing more optimal phrasing/wording. 86 | - suggestions for granularity. 87 | - suggestions for fixes. 88 | - etc.. there are no rules, feel free to contribute as you see fit. 89 | -------------------------------------------------------------------------------- /intentrace-example.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sectordistrict/intentrace/93f34fe853b980974a735cfd4f730bcb67fd0910/intentrace-example.jpg -------------------------------------------------------------------------------- /intentrace-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sectordistrict/intentrace/93f34fe853b980974a735cfd4f730bcb67fd0910/intentrace-example.png -------------------------------------------------------------------------------- /itrace.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sectordistrict/intentrace/93f34fe853b980974a735cfd4f730bcb67fd0910/itrace.png -------------------------------------------------------------------------------- /src/auxiliary.rs: -------------------------------------------------------------------------------- 1 | pub mod constants { 2 | pub mod sizes { 3 | pub const SIGSET_SIZE: usize = size_of::(); 4 | pub const TIMESPEC_SIZE: usize = size_of::(); 5 | pub const CLONE3_ARGS_SIZE: usize = size_of::(); 6 | pub const SIGACTION_SIZE: usize = size_of::(); 7 | pub const RLIMIT_SIZE: usize = size_of::(); 8 | } 9 | pub mod general { 10 | pub const MAX_KERNEL_ULONG: usize = unsafe { std::mem::transmute::(-4095) }; 11 | // syscall: arch_prctl 12 | pub const ARCH_SET_GS: i32 = 0x1001; 13 | pub const ARCH_SET_FS: i32 = 0x1002; 14 | pub const ARCH_GET_FS: i32 = 0x1003; 15 | pub const ARCH_GET_GS: i32 = 0x1004; 16 | pub const ARCH_GET_CPUID: i32 = 0x1011; 17 | pub const ARCH_SET_CPUID: i32 = 0x1012; 18 | // syscall: landlock_add_rule 19 | pub const LANDLOCK_RULE_PATH_BENEATH: i32 = 1; 20 | } 21 | } 22 | 23 | pub mod kernel_errno { 24 | // kernel side errnos, not visible to userland 25 | use nix::libc::c_int; 26 | pub const ERESTARTSYS: c_int = 512; 27 | pub const ERESTARTNOINTR: c_int = 513; 28 | pub const ERESTARTNOHAND: c_int = 514; /* restart if no handler.. */ 29 | pub const ERESTART_RESTARTBLOCK: c_int = 516; /* restart by calling sys_restart_syscall */ 30 | pub const ENOIOCTLCMD: c_int = 515; /* No ioctl command */ 31 | pub const EPROBE_DEFER: c_int = 517; /* Driver requests probe retry */ 32 | pub const EOPENSTALE: c_int = 518; /* open found a stale dentry */ 33 | pub const ENOPARAM: c_int = 519; /* Parameter not supported */ 34 | /* Defined for the NFSv3 protocol */ 35 | pub const EBADHANDLE: c_int = 521; /* Illegal NFS file handle */ 36 | pub const ENOTSYNC: c_int = 522; /* Update synchronization mismatch */ 37 | pub const EBADCOOKIE: c_int = 523; /* Cookie is stale */ 38 | pub const ENOTSUPP: c_int = 524; /* Operation is not supported */ 39 | pub const ETOOSMALL: c_int = 525; /* Buffer or request is too small */ 40 | pub const ESERVERFAULT: c_int = 526; /* An untranslatable error occurred */ 41 | pub const EBADTYPE: c_int = 527; /* Type not supported by server */ 42 | pub const EJUKEBOX: c_int = 528; /* Request initiated, but will not complete before timeout */ 43 | pub const EIOCBQUEUED: c_int = 529; /* iocb queued, will get completion event */ 44 | pub const ERECALLCONFLICT: c_int = 530; /* conflict with recalled state */ 45 | pub const ENOGRACE: c_int = 531; /* NFS file lock reclaim refused */ 46 | 47 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 48 | #[repr(i32)] 49 | pub enum KernelErrno { 50 | UnknownErrno = 0, 51 | ERESTARTSYS = ERESTARTSYS, 52 | ERESTARTNOINTR = ERESTARTNOINTR, 53 | ERESTARTNOHAND = ERESTARTNOHAND, 54 | ERESTART_RESTARTBLOCK = ERESTART_RESTARTBLOCK, 55 | ENOIOCTLCMD = ENOIOCTLCMD, 56 | EPROBE_DEFER = EPROBE_DEFER, 57 | EOPENSTALE = EOPENSTALE, 58 | ENOPARAM = ENOPARAM, 59 | EBADHANDLE = EBADHANDLE, 60 | ENOTSYNC = ENOTSYNC, 61 | EBADCOOKIE = EBADCOOKIE, 62 | ENOTSUPP = ENOTSUPP, 63 | ETOOSMALL = ETOOSMALL, 64 | ESERVERFAULT = ESERVERFAULT, 65 | EBADTYPE = EBADTYPE, 66 | EJUKEBOX = EJUKEBOX, 67 | EIOCBQUEUED = EIOCBQUEUED, 68 | ERECALLCONFLICT = ERECALLCONFLICT, 69 | ENOGRACE = ENOGRACE, 70 | } 71 | 72 | impl std::fmt::Display for KernelErrno { 73 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 74 | write!(f, "{:?}: {}", self, self.desc()) 75 | } 76 | } 77 | 78 | impl KernelErrno { 79 | pub fn from_i32(errno: i32) -> KernelErrno { 80 | match errno { 81 | ERESTARTSYS => KernelErrno::ERESTARTSYS, 82 | ERESTARTNOINTR => KernelErrno::ERESTARTNOINTR, 83 | ERESTARTNOHAND => KernelErrno::ERESTARTNOHAND, 84 | ERESTART_RESTARTBLOCK => KernelErrno::ERESTART_RESTARTBLOCK, 85 | ENOIOCTLCMD => KernelErrno::ENOIOCTLCMD, 86 | EPROBE_DEFER => KernelErrno::EPROBE_DEFER, 87 | EOPENSTALE => KernelErrno::EOPENSTALE, 88 | ENOPARAM => KernelErrno::ENOPARAM, 89 | EBADHANDLE => KernelErrno::EBADHANDLE, 90 | ENOTSYNC => KernelErrno::ENOTSYNC, 91 | EBADCOOKIE => KernelErrno::EBADCOOKIE, 92 | ENOTSUPP => KernelErrno::ENOTSUPP, 93 | ETOOSMALL => KernelErrno::ETOOSMALL, 94 | ESERVERFAULT => KernelErrno::ESERVERFAULT, 95 | EBADTYPE => KernelErrno::EBADTYPE, 96 | EJUKEBOX => KernelErrno::EJUKEBOX, 97 | EIOCBQUEUED => KernelErrno::EIOCBQUEUED, 98 | ERECALLCONFLICT => KernelErrno::ERECALLCONFLICT, 99 | ENOGRACE => KernelErrno::ENOGRACE, 100 | _ => KernelErrno::UnknownErrno, 101 | } 102 | } 103 | 104 | pub fn desc(&self) -> &'static str { 105 | // TODO! 106 | // these messages dont communicate EINTR conversion semantics 107 | match self { 108 | KernelErrno::UnknownErrno => "Unknown errno", 109 | // interrupted syscalls 110 | 111 | // ERESTARTSYS 112 | // always restart 113 | // except if a handler was registered without SA_RESTART, then convert to EINTR 114 | KernelErrno::ERESTARTSYS => { 115 | "Interrupted by a signal, restart if it has no handler or a SA_RESTART handler exists" 116 | } 117 | 118 | // ERESTARTNOINTR 119 | // always restart 120 | KernelErrno::ERESTARTNOINTR => "Interrupted by a signal, restart always", 121 | 122 | // ERESTARTNOHAND 123 | // always restart 124 | // except if a handler was registered, then convert to EINTR 125 | KernelErrno::ERESTARTNOHAND => { 126 | "Interrupted by a signal, restart if it has no handler" 127 | } 128 | 129 | // ERESTART_RESTARTBLOCK 130 | // should be restarted using a custom function. 131 | KernelErrno::ERESTART_RESTARTBLOCK => { 132 | "Interrupted by a signal, restart by calling restart_syscall" 133 | } 134 | // 135 | // 136 | // 137 | // 138 | KernelErrno::ENOIOCTLCMD => "No ioctl command", 139 | // if a driver depends on resources that are not yet available 140 | KernelErrno::EPROBE_DEFER => "Driver requests probe retry", 141 | KernelErrno::EOPENSTALE => "Open found a stale dentry", 142 | KernelErrno::ENOPARAM => "Parameter not supported", 143 | KernelErrno::EBADHANDLE => "Illegal NFS file handle", 144 | KernelErrno::ENOTSYNC => "Update synchronization mismatch", 145 | KernelErrno::EBADCOOKIE => "Cookie is stale", 146 | KernelErrno::ENOTSUPP => "Operation is not supported", 147 | KernelErrno::ETOOSMALL => "Buffer or request is too small", 148 | KernelErrno::ESERVERFAULT => "An untranslatable error occurred", 149 | KernelErrno::EBADTYPE => "Type not supported by server", 150 | KernelErrno::EJUKEBOX => "Request initiated, but will not complete before timeout", 151 | KernelErrno::EIOCBQUEUED => "iocb queued, will get completion event", 152 | KernelErrno::ERECALLCONFLICT => "conflict with recalled state", 153 | KernelErrno::ENOGRACE => "NFS file lock reclaim refused", 154 | } 155 | } 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /src/cli.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | path::{Path, PathBuf}, 3 | str::FromStr, 4 | sync::{ 5 | atomic::{AtomicBool, AtomicUsize}, 6 | LazyLock, 7 | }, 8 | }; 9 | 10 | // TODO! Time blocks feature 11 | // pub static TIME_BLOCKS: Cell = Cell::new(false); 12 | pub static FOLLOW_FORKS: LazyLock = LazyLock::new(|| INTENTRACE_ARGS.follow_forks); 13 | pub static STRING_LIMIT: AtomicUsize = AtomicUsize::new(36); 14 | pub static FAILED_ONLY: LazyLock = LazyLock::new(|| INTENTRACE_ARGS.failed_only); 15 | pub static QUIET: LazyLock = LazyLock::new(|| INTENTRACE_ARGS.mute_stdout); 16 | pub static ANNOT: AtomicBool = AtomicBool::new(false); 17 | pub static ATTACH_PID: LazyLock> = LazyLock::new(|| INTENTRACE_ARGS.pid); 18 | pub static SUMMARY_ONLY: LazyLock = LazyLock::new(|| INTENTRACE_ARGS.summary_only); 19 | pub static SUMMARY: LazyLock = LazyLock::new(|| INTENTRACE_ARGS.summary); 20 | pub static SYSCALLS_TO_TRACE: LazyLock = LazyLock::new(|| { 21 | if INTENTRACE_ARGS.trace.is_empty() { 22 | SysnoSet::all() 23 | } else { 24 | let mut sysno_set = SysnoSet::empty(); 25 | for syscall in INTENTRACE_ARGS.trace.iter() { 26 | match Sysno::from_str(syscall) { 27 | Ok(sysno) => { 28 | sysno_set.insert(sysno); 29 | } 30 | Err(_) => { 31 | eprintln!("Invalid syscall: {}", syscall); 32 | std::process::exit(100); 33 | } 34 | } 35 | } 36 | sysno_set 37 | } 38 | }); 39 | pub static OUTPUT_FILE: LazyLock> = LazyLock::new(|| { 40 | INTENTRACE_ARGS 41 | .file 42 | .as_ref() 43 | .map(|pathbuf| pathbuf.as_path()) 44 | }); 45 | // 46 | pub static INTENTRACE_ARGS: LazyLock = LazyLock::new(IntentraceArgs::parse); 47 | pub static BINARY_AND_ARGS: LazyLock> = 48 | LazyLock::new(|| match INTENTRACE_ARGS.binary { 49 | Some(Binary::Command(ref regs)) if !regs.is_empty() => Some(regs), 50 | _ => None, 51 | }); 52 | 53 | use clap::{Parser, Subcommand}; 54 | use syscalls::{Sysno, SysnoSet}; 55 | 56 | #[derive(Parser)] 57 | #[command( 58 | about = "intentrace is a strace for everyone.", 59 | version, 60 | allow_external_subcommands = true 61 | )] 62 | pub struct IntentraceArgs { 63 | /// print a summary table 64 | #[arg(short = 'c', long, conflicts_with = "summary")] 65 | pub summary_only: bool, 66 | 67 | /// print a summary table in addition to the normal output 68 | #[arg(short = 'C', long)] 69 | pub summary: bool, 70 | 71 | /// attach to an already running proceess 72 | #[arg(short = 'p', long = "attach")] 73 | pub pid: Option, 74 | 75 | /// redirect intentrace's output to a provided file 76 | #[arg(short = 'o', long = "output")] 77 | pub file: Option, 78 | 79 | /// trace a specific syscall or a group of syscalls delimited by ',' 80 | #[arg(long, value_delimiter = ',')] 81 | pub trace: Vec, 82 | 83 | /// trace child processes when traced programs create them 84 | #[arg( 85 | short = 'f', 86 | long = "follow-forks", 87 | conflicts_with = "pid", 88 | conflicts_with = "failed_only" 89 | )] 90 | pub follow_forks: bool, 91 | 92 | /// only print failed syscalls 93 | #[arg(short = 'Z', long = "failed-only")] 94 | pub failed_only: bool, 95 | 96 | /// mute the traced program's std output 97 | #[arg(short = 'q', long = "mute-stdout")] 98 | pub mute_stdout: bool, 99 | 100 | #[command(subcommand)] 101 | pub binary: Option, 102 | } 103 | 104 | #[derive(Subcommand, Debug, PartialEq)] 105 | pub enum Binary { 106 | #[command(external_subcommand)] 107 | Command(Vec), 108 | } 109 | -------------------------------------------------------------------------------- /src/colors.rs: -------------------------------------------------------------------------------- 1 | use colored::CustomColor; 2 | use std::sync::{LazyLock, Mutex}; 3 | 4 | pub static TERMINAL_THEME: LazyLock = LazyLock::new(|| { 5 | termbg::theme(std::time::Duration::from_millis(10)).unwrap_or(termbg::Theme::Dark) 6 | }); 7 | 8 | pub static PAGES_COLOR: LazyLock = 9 | LazyLock::new(|| from_terminal_theme((0, 169, 233), (0, 169, 223))); 10 | pub static GENERAL_TEXT_COLOR: LazyLock = 11 | LazyLock::new(|| from_terminal_theme((64, 64, 64), (160, 160, 160))); 12 | pub static PID_BACKGROUND_COLOR: LazyLock = 13 | LazyLock::new(|| from_terminal_theme((146, 146, 168), (0, 0, 0))); 14 | pub static PID_NUMBER_COLOR: LazyLock = 15 | LazyLock::new(|| from_terminal_theme((0, 0, 140), (0, 173, 216))); 16 | pub static EXITED_BACKGROUND_COLOR: LazyLock = 17 | LazyLock::new(|| from_terminal_theme((250, 160, 160), (100, 0, 0))); 18 | pub static OUR_YELLOW: LazyLock = 19 | LazyLock::new(|| from_terminal_theme((112, 127, 35), (187, 142, 35))); 20 | pub static CONTINUED_COLOR: LazyLock = 21 | LazyLock::new(|| from_terminal_theme((188, 210, 230), (17, 38, 21))); 22 | pub static STOPPED_COLOR: LazyLock = 23 | LazyLock::new(|| from_terminal_theme((82, 138, 174), (47, 86, 54))); 24 | // TODO! 25 | // find a good alternate color for light mode terminals 26 | pub static PARTITION_1_COLOR: LazyLock = 27 | LazyLock::new(|| from_terminal_theme((112, 127, 35), (186, 171, 35))); 28 | pub static PARTITION_2_COLOR: LazyLock<[CustomColor; 3]> = 29 | LazyLock::new(|| match *TERMINAL_THEME { 30 | termbg::Theme::Light => [CustomColor::new(0, 169, 223), CustomColor::new(0, 169, 223), CustomColor::new(0, 169, 223)], 31 | termbg::Theme::Dark => [CustomColor::new(0, 169, 233), CustomColor::new(92, 92, 255), CustomColor::new(0, 218, 233)], 32 | }); 33 | pub static PATHLIKE_ALTERNATOR: LazyLock> = LazyLock::new(|| Mutex::new(0)); 34 | 35 | fn from_terminal_theme( 36 | (light_R, light_G, light_B): (u8, u8, u8), 37 | (dark_R, dark_G, dark_B): (u8, u8, u8), 38 | ) -> CustomColor { 39 | match *TERMINAL_THEME { 40 | termbg::Theme::Light => CustomColor::new(light_R, light_G, light_B), 41 | termbg::Theme::Dark => CustomColor::new(dark_R, dark_G, dark_B), 42 | } 43 | } 44 | 45 | pub fn switch_pathlike_color() { 46 | let prev = *PATHLIKE_ALTERNATOR.lock().unwrap(); 47 | *PATHLIKE_ALTERNATOR.lock().unwrap() = (prev + 1) % 3; 48 | } 49 | -------------------------------------------------------------------------------- /src/macro_interps.rs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #![allow( 2 | unused_doc_comments, 3 | dead_code, 4 | non_camel_case_types, 5 | unused_macros, 6 | non_snake_case, 7 | invalid_value, 8 | unused_assignments 9 | )] 10 | 11 | macro_rules! p { 12 | ($a:expr) => { 13 | println!("{:?}", $a) 14 | }; 15 | } 16 | macro_rules! pp { 17 | ($a:expr, $b:expr) => { 18 | println!("{:?}, {:?}", $a, $b) 19 | }; 20 | } 21 | macro_rules! ppp { 22 | ($a:expr, $b:expr, $c:expr) => { 23 | println!("{:?}, {:?}, {:?}", $a, $b, $c) 24 | }; 25 | } 26 | 27 | use std::{ 28 | collections::HashMap, 29 | mem::{self}, 30 | os::unix::process::CommandExt, 31 | process::{exit, Command, Stdio}, 32 | sync::atomic::Ordering, 33 | time::Duration, 34 | }; 35 | 36 | use clap::Parser; 37 | use cli::{ 38 | IntentraceArgs, ATTACH_PID, BINARY_AND_ARGS, FAILED_ONLY, FOLLOW_FORKS, QUIET, SUMMARY, 39 | SUMMARY_ONLY, 40 | }; 41 | use colored::Colorize; 42 | use colors::{GENERAL_TEXT_COLOR, STOPPED_COLOR}; 43 | use nix::{ 44 | errno::Errno, 45 | sys::{ 46 | ptrace::{self}, 47 | wait::waitpid, 48 | }, 49 | unistd::{fork, ForkResult::*, Pid}, 50 | }; 51 | use pete::{Ptracer, Restart, Stop}; 52 | use syscalls::Sysno; 53 | use utilities::{ 54 | interpret_syscall_result, set_memory_break_pre_call, syscall_is_blocking, HALT_TRACING, REGISTERS, 55 | TABLE, TABLE_FOLLOW_FORKS, 56 | }; 57 | use writer::{ 58 | empty_buffer, flush_buffer, initialize_writer, write_exiting, write_syscall_not_covered, 59 | write_text, 60 | }; 61 | 62 | mod syscall_categories; 63 | mod syscall_object; 64 | // mod syscall_annotations_map; 65 | // mod syscall_object_annotations; 66 | mod syscall_skeleton_map; 67 | mod types; 68 | use syscall_object::{SyscallObject, SyscallResult, SyscallState}; 69 | mod auxiliary; 70 | mod cli; 71 | mod colors; 72 | mod one_line_formatter; 73 | mod peeker_poker; 74 | mod return_resolvers; 75 | mod utilities; 76 | mod writer; 77 | 78 | fn main() -> anyhow::Result<()> { 79 | IntentraceArgs::parse(); 80 | initialize_writer(); 81 | ctrlc::set_handler(|| { 82 | flush_buffer(); 83 | HALT_TRACING.store(true, Ordering::SeqCst); 84 | if *SUMMARY_ONLY || *SUMMARY { 85 | print_table(); 86 | } 87 | std::process::exit(0); 88 | })?; 89 | if *FOLLOW_FORKS { 90 | follow_forks(*BINARY_AND_ARGS) 91 | } else { 92 | match *ATTACH_PID { 93 | Some(pid) => { 94 | let child = Pid::from_raw(pid as i32); 95 | ptrace::attach(child)?; 96 | parent(child); 97 | } 98 | None => match *BINARY_AND_ARGS { 99 | Some(binary_and_args) => match unsafe { fork() }? { 100 | Parent { child } => { 101 | parent(child); 102 | } 103 | Child => child_trace_me(binary_and_args), 104 | }, 105 | None => { 106 | eprintln!("Usage: must provide a command to run or attach to a PID\n"); 107 | exit(100); 108 | } 109 | }, 110 | } 111 | } 112 | if !*FAILED_ONLY { 113 | // flush_buffer(); 114 | } 115 | if *SUMMARY_ONLY || *SUMMARY { 116 | print_table(); 117 | } 118 | Ok(()) 119 | } 120 | 121 | fn child_trace_me(comm: &[String]) { 122 | let mut command = Command::new(&comm[0]); 123 | command.args(&comm[1..]); 124 | 125 | if *QUIET { 126 | command.stdout(Stdio::null()); 127 | } 128 | 129 | ptrace::traceme().unwrap(); 130 | let res = command.exec(); 131 | 132 | // unreachable unless exec fails 133 | eprintln!("Could not execute program"); 134 | std::process::exit(res.raw_os_error().unwrap()) 135 | } 136 | 137 | fn follow_forks(command_to_run: Option<&[String]>) { 138 | match command_to_run { 139 | Some(comm) => { 140 | let mut command = Command::new(&comm[0]); 141 | command.args(&comm[1..]); 142 | 143 | if *QUIET { 144 | command.stdout(Stdio::null()); 145 | } 146 | 147 | let mut ptracer = Ptracer::new(); 148 | *ptracer.poll_delay_mut() = Duration::from_nanos(1); 149 | ptracer.spawn(command).unwrap(); 150 | ptrace_ptracer(ptracer); 151 | } 152 | None => { 153 | eprintln!("Usage: must provide a command to run\n"); 154 | exit(100); 155 | } 156 | } 157 | } 158 | 159 | fn parent(tracee_pid: Pid) { 160 | // skip first execve 161 | let _res = waitpid(tracee_pid, None).unwrap(); 162 | let mut syscall_entering = true; 163 | let (mut start, mut end) = (None, None); 164 | let mut syscall = SyscallObject::default(); 165 | let (mut supported, mut skip) = (true, false); 166 | 'main_loop: loop { 167 | match ptrace::syscall(tracee_pid, None) { 168 | Ok(_void) => { 169 | let _res = waitpid(tracee_pid, None).expect("Failed waiting for child."); 170 | match syscall_entering { 171 | true => { 172 | empty_buffer(); 173 | // SYSCALL ABOUT TO RUN 174 | let ptrace_regs = nix::sys::ptrace::getregs(tracee_pid); 175 | if let Err(Errno::ESRCH) = ptrace_regs { 176 | write_exiting(tracee_pid); 177 | break 'main_loop; 178 | } 179 | let registers = ptrace_regs.unwrap(); 180 | let sysno = Sysno::from(registers.orig_rax as i32); 181 | skip = SyscallObject::should_skip_building(sysno); 182 | if !skip { 183 | if let Some(syscall_built) = SyscallObject::build(tracee_pid, sysno) { 184 | syscall = syscall_built; 185 | *REGISTERS.lock().unwrap() = [ 186 | registers.rdi, 187 | registers.rsi, 188 | registers.rdx, 189 | registers.r10, 190 | registers.r8, 191 | registers.r9, 192 | ]; 193 | syscall_will_run(&mut syscall); 194 | if *SUMMARY { 195 | start = Some(std::time::Instant::now()); 196 | } 197 | } else { 198 | write_syscall_not_covered(sysno, tracee_pid); 199 | supported = false; 200 | } 201 | } 202 | syscall_entering = false; 203 | if *SUMMARY_ONLY { 204 | start = Some(std::time::Instant::now()); 205 | } 206 | continue 'main_loop; 207 | } 208 | false => { 209 | // SYSCALL RETURNED 210 | end = Some(std::time::Instant::now()); 211 | let ptrace_regs = nix::sys::ptrace::getregs(tracee_pid); 212 | if let Err(Errno::ESRCH) = ptrace_regs { 213 | write_exiting(tracee_pid); 214 | break 'main_loop; 215 | } 216 | let registers = ptrace_regs.unwrap(); 217 | let sysno = Sysno::from(registers.orig_rax as i32); 218 | if supported && !skip { 219 | *REGISTERS.lock().unwrap() = [ 220 | registers.rdi, 221 | registers.rsi, 222 | registers.rdx, 223 | registers.r10, 224 | registers.r8, 225 | registers.r9, 226 | ]; 227 | syscall_returned(&mut syscall, registers.rax); 228 | if *SUMMARY { 229 | let mut table = TABLE.lock().unwrap(); 230 | table 231 | .entry(sysno) 232 | .and_modify(|(count, duration, errors)| { 233 | *count += 1; 234 | *duration = duration.saturating_add( 235 | end.unwrap().duration_since(start.unwrap()), 236 | ); 237 | *errors += syscall.has_errored() as usize; 238 | }) 239 | .or_insert(( 240 | 1, 241 | end.unwrap().duration_since(start.unwrap()), 242 | syscall.has_errored() as usize, 243 | )); 244 | start = None; 245 | end = None; 246 | } 247 | } 248 | if *SUMMARY_ONLY { 249 | let mut table = TABLE.lock().unwrap(); 250 | let syscall_result = interpret_syscall_result(registers.rax); 251 | let errored = matches!(syscall_result, SyscallResult::Fail(_)); 252 | table 253 | .entry(sysno) 254 | .and_modify(|(count, duration, errors)| { 255 | *count += 1; 256 | *duration = duration.saturating_add( 257 | end.unwrap().duration_since(start.unwrap()), 258 | ); 259 | *errors += errored as usize; 260 | }) 261 | .or_insert(( 262 | 1, 263 | end.unwrap().duration_since(start.unwrap()), 264 | errored as usize, 265 | )); 266 | start = None; 267 | end = None; 268 | } 269 | supported = true; 270 | skip = false; 271 | syscall_entering = true; 272 | } 273 | } 274 | } 275 | Err(errno) => { 276 | if errno == Errno::ESRCH { 277 | eprintln!("\n\nTracee died\nlast syscall: {}", syscall.sysno); 278 | } else { 279 | eprintln!("\n\nError: {errno}\nlast syscall: {}", syscall.sysno); 280 | } 281 | break 'main_loop; 282 | } 283 | } 284 | } 285 | } 286 | 287 | fn ptrace_ptracer(mut ptracer: Ptracer) { 288 | let mut last_sysno: syscalls::Sysno = unsafe { mem::zeroed() }; 289 | let mut last_pid = unsafe { mem::zeroed() }; 290 | let mut pid_syscall_map: HashMap = HashMap::new(); 291 | 292 | while let Ok(Some(tracee)) = ptracer.wait() { 293 | if HALT_TRACING.load(Ordering::SeqCst) { 294 | break; 295 | } 296 | let tracee_pid = Pid::from_raw(tracee.pid.as_raw()); 297 | match tracee.stop { 298 | Stop::SyscallEnter => { 299 | let ptrace_regs = nix::sys::ptrace::getregs(tracee_pid); 300 | if let Err(errno) = ptrace_regs { 301 | handle_getting_registers_error(errno, "enter", last_sysno) 302 | } 303 | let registers = ptrace_regs.unwrap(); 304 | 305 | check_syscall_switch(last_pid, tracee_pid, &mut pid_syscall_map); 306 | let sysno = Sysno::from(registers.orig_rax as i32); 307 | last_sysno = sysno; 308 | if !SyscallObject::should_skip_building(sysno) { 309 | let syscall_built = SyscallObject::build(tracee_pid, sysno); 310 | if let Some(mut syscall) = syscall_built { 311 | *REGISTERS.lock().unwrap() = [ 312 | registers.rdi, 313 | registers.rsi, 314 | registers.rdx, 315 | registers.r10, 316 | registers.r8, 317 | registers.r9, 318 | ]; 319 | syscall_will_run(&mut syscall); 320 | syscall.state = SyscallState::Exiting; 321 | pid_syscall_map.insert(tracee_pid, syscall); 322 | if *SUMMARY { 323 | let mut table = TABLE_FOLLOW_FORKS.lock().unwrap(); 324 | table 325 | .entry(sysno) 326 | .and_modify(|value| { 327 | *value += 1; 328 | }) 329 | .or_insert(1); 330 | } 331 | } else { 332 | write_syscall_not_covered(sysno, tracee_pid); 333 | } 334 | } 335 | if *SUMMARY_ONLY { 336 | let mut table = TABLE_FOLLOW_FORKS.lock().unwrap(); 337 | table 338 | .entry(sysno) 339 | .and_modify(|value| { 340 | *value += 1; 341 | }) 342 | .or_insert(1); 343 | } 344 | last_pid = tracee_pid; 345 | } 346 | Stop::SyscallExit => { 347 | check_syscall_switch(last_pid, tracee_pid, &mut pid_syscall_map); 348 | let ptrace_regs = nix::sys::ptrace::getregs(tracee_pid); 349 | if let Err(errno) = ptrace_regs { 350 | handle_getting_registers_error(errno, "enter", last_sysno) 351 | } 352 | let registers = ptrace_regs.unwrap(); 353 | *REGISTERS.lock().unwrap() = [ 354 | registers.rdi, 355 | registers.rsi, 356 | registers.rdx, 357 | registers.r10, 358 | registers.r8, 359 | registers.r9, 360 | ]; 361 | if let Some(syscall) = pid_syscall_map.get_mut(&tracee_pid) { 362 | syscall_returned(syscall, registers.rax); 363 | pid_syscall_map.remove(&tracee_pid).unwrap(); 364 | } 365 | last_pid = tracee_pid; 366 | } 367 | _ => {} 368 | } 369 | match ptracer.restart(tracee, Restart::Syscall) { 370 | Ok(_) => {} 371 | Err(e) => { 372 | if let pete::Error::TraceeDied { pid, source } = e { 373 | if source as i32 == Errno::ESRCH as i32 { 374 | write_exiting(Pid::from_raw(pid.as_raw())); 375 | return; 376 | } 377 | } 378 | eprintln!("{}", e); 379 | } 380 | }; 381 | } 382 | } 383 | 384 | fn syscall_will_run(syscall: &mut SyscallObject) { 385 | // handle program break point 386 | if syscall.is_mem_alloc_dealloc() { 387 | set_memory_break_pre_call(syscall.tracee_pid); 388 | } 389 | match *FOLLOW_FORKS { 390 | true => { 391 | syscall.fill_buffer(); 392 | flush_buffer(); 393 | empty_buffer(); 394 | } 395 | false => { 396 | syscall.state = SyscallState::Entering; 397 | syscall.fill_buffer(); 398 | } 399 | } 400 | } 401 | 402 | fn syscall_returned(syscall: &mut SyscallObject, return_value: u64) { 403 | syscall.result = interpret_syscall_result(return_value); 404 | 405 | match *FOLLOW_FORKS { 406 | true => { 407 | syscall.fill_buffer(); 408 | write_text("\n".white()); 409 | } 410 | false => { 411 | if *FAILED_ONLY && !syscall.has_errored() { 412 | empty_buffer(); 413 | return; 414 | } 415 | syscall.state = SyscallState::Exiting; 416 | syscall.fill_buffer(); 417 | // this line was moved from the main loops to after this check ^ 418 | // it was previously not aware of FAILED_ONLY being its edge-case 419 | // this resulted in long streaks of newlines in the output 420 | // this is also more correct 421 | write_text("\n".white()); 422 | } 423 | } 424 | if syscall.currently_blocking { 425 | write_text(syscall_is_blocking()); 426 | } 427 | flush_buffer(); 428 | empty_buffer(); 429 | } 430 | 431 | fn check_syscall_switch( 432 | last_pid: Pid, 433 | syscall_pid: Pid, 434 | pid_syscall_map: &mut HashMap, 435 | ) { 436 | if syscall_pid != last_pid { 437 | if let Some(last_syscall) = pid_syscall_map.get_mut(&last_pid) { 438 | if !last_syscall.is_exiting() { 439 | last_syscall.paused = true; 440 | write_text(" ├ ".custom_color(*GENERAL_TEXT_COLOR)); 441 | write_text(" STOPPED ".on_custom_color(*STOPPED_COLOR)); 442 | write_text("\n".white()); 443 | } 444 | } 445 | } 446 | } 447 | 448 | fn handle_getting_registers_error(errno: Errno, syscall_enter_or_exit: &str, sysno: Sysno) { 449 | if sysno == Sysno::exit || sysno == Sysno::exit_group { 450 | // no longer needed 451 | // println!("\n\nSuccessfully exited\n"); 452 | } else { 453 | match errno { 454 | Errno::ESRCH => { 455 | println!( 456 | "\n\n getting registers: syscall-{syscall_enter_or_exit} error: process \ 457 | disappeared\nsyscall: {sysno}, error: {errno}" 458 | ); 459 | exit(0); 460 | } 461 | _ => println!("Encountered error while retrieving registers"), 462 | } 463 | } 464 | } 465 | 466 | fn print_table() { 467 | if *FOLLOW_FORKS { 468 | let output = TABLE_FOLLOW_FORKS.lock().unwrap(); 469 | let mut vec = Vec::from_iter(output.iter()); 470 | vec.sort_by(|(_sysno, count), (_sysno2, count2)| count2.cmp(count)); 471 | 472 | use tabled::{builder::Builder, settings::Style}; 473 | let mut builder = Builder::new(); 474 | 475 | builder.push_record(["calls", "syscall"]); 476 | builder.push_record([""]); 477 | for (sys, count) in vec { 478 | builder.push_record([&count.to_string(), sys.name()]); 479 | } 480 | let table = builder.build().with(Style::ascii_rounded()).to_string(); 481 | 482 | eprintln!("\n{}", table); 483 | } else { 484 | let output = TABLE.lock().unwrap(); 485 | let mut vec = Vec::from_iter(output.iter()); 486 | vec.sort_by(|(_, (_, duration, _)), (_, (_, duration2, _))| duration2.cmp(duration)); 487 | 488 | use tabled::{builder::Builder, settings::Style}; 489 | let mut builder = Builder::new(); 490 | 491 | builder.push_record([ 492 | "% time", 493 | "seconds", 494 | "usecs/call", 495 | "calls", 496 | "errors", 497 | "syscall", 498 | ]); 499 | builder.push_record([""]); 500 | let total_time = vec 501 | .iter() 502 | .map(|(_, (_, time, _))| time.as_micros()) 503 | .sum::(); 504 | let empty_string = "".to_owned(); 505 | for (sys, (count, time, errors)) in vec { 506 | let time_MICROS = time.as_micros() as f64; 507 | let time = time_MICROS / 1_000_000.0; 508 | let usecs_call = (time_MICROS / *count as f64) as i64; 509 | let time_percent = time_MICROS / total_time as f64; 510 | let errors = if *errors == 0 { 511 | &empty_string 512 | } else { 513 | &errors.to_string() 514 | }; 515 | builder.push_record([ 516 | &format!("{:.2}", time_percent * 100.0), 517 | &format!("{:.6}", time), 518 | &format!("{}", usecs_call), 519 | &count.to_string(), 520 | errors, 521 | sys.name(), 522 | ]); 523 | } 524 | let table = builder.build().with(Style::ascii_rounded()).to_string(); 525 | 526 | eprintln!("\n{}", table); 527 | } 528 | } 529 | -------------------------------------------------------------------------------- /src/peeker_poker.rs: -------------------------------------------------------------------------------- 1 | // file is called peeker poker 2 | // because it's the lingo[1] that ptrace 3 | // uses to refer to reading/writing 4 | // from/to a tracee's memory 5 | // 6 | // [1]: https://en.wikipedia.org/wiki/PEEK_and_POKE 7 | // 8 | #[cfg(target_pointer_width = "32")] 9 | pub const WORD_SIZE: usize = 4; 10 | 11 | #[cfg(target_pointer_width = "64")] 12 | pub const WORD_SIZE: usize = 8; 13 | 14 | use std::{ffi::c_void, io::IoSliceMut, num::NonZeroUsize}; 15 | 16 | use nix::{ 17 | libc::{cpu_set_t, CPU_ISSET, CPU_SETSIZE}, 18 | sys::{ 19 | ptrace, 20 | uio::{process_vm_readv, RemoteIoVec}, 21 | }, 22 | unistd::Pid, 23 | }; 24 | 25 | pub fn read_bytes(addr: usize, pid: Pid) -> Option<[u8; N]> { 26 | let base = addr; 27 | let remote_iov = RemoteIoVec { base, len: N }; 28 | // TODO! 29 | // large array sizes might overflow 30 | let mut bytes_buffer = [0u8; N]; 31 | process_vm_readv( 32 | pid, 33 | &mut [IoSliceMut::new(&mut bytes_buffer)], 34 | &[remote_iov], 35 | ).ok()?; 36 | Some(bytes_buffer) 37 | } 38 | 39 | pub fn read_bytes_variable_length(base: usize, pid: Pid, len: usize) -> Option> { 40 | let remote_iov = RemoteIoVec { base, len }; 41 | let mut bytes_buffer = vec![0u8; len]; 42 | // Note, however, that these system calls 43 | // do not check the memory regions in the remote process 44 | // until just before doing the read/write. 45 | // Consequently, a partial read/write (see RETURN VALUE) may result 46 | // if one of the remote_iov elements points to an invalid memory region in the remote process. 47 | // No further reads/writes will be attempted beyond that point. 48 | // 49 | // Keep this in mind when attempting to read data of unknown length 50 | // (such as C strings that are null-terminated) from a remote process, 51 | // by avoiding spanning memory pages (typically 4 KiB) 52 | // in a single remote iovec element. 53 | // (Instead, split the remote read into two remote_iov elements 54 | // and have them merge back into a single write local_iov entry. 55 | // The first read entry goes up to the page boundary, 56 | let _ = process_vm_readv( 57 | pid, 58 | &mut [IoSliceMut::new(&mut bytes_buffer)], 59 | &[remote_iov], 60 | ) 61 | .ok()?; 62 | Some(bytes_buffer) 63 | } 64 | 65 | pub fn read_bytes_as_struct(addr: usize, pid: Pid) -> Option { 66 | let vec = read_bytes::(addr, pid)?; 67 | Some(unsafe { std::mem::transmute_copy(&vec) }) 68 | } 69 | 70 | pub fn read_one_word(address: usize, pid: Pid) -> Option { 71 | let remote_iov = RemoteIoVec { 72 | base: address, 73 | len: 1, 74 | }; 75 | let mut bytes_buffer = vec![0u8; 4]; 76 | let _ = process_vm_readv( 77 | pid, 78 | &mut [IoSliceMut::new(&mut bytes_buffer)], 79 | &[remote_iov], 80 | ) 81 | .ok()?; 82 | Some(unsafe { std::mem::transmute(&bytes_buffer) }) 83 | } 84 | 85 | pub fn read_bytes_until_null(address: usize, pid: Pid) -> Option> { 86 | let mut address = address as *mut c_void; 87 | let mut data = vec![]; 88 | 'read_loop: loop { 89 | // TODO! 90 | // change this to be similar to read_words_until_null below 91 | // i.e. if err: return collected data so far 92 | let word = ptrace::read(pid, address).ok()?; 93 | let bytes: [u8; WORD_SIZE] = unsafe { std::mem::transmute(word) }; 94 | for byte in bytes { 95 | if byte == b'\0' { 96 | break 'read_loop; 97 | } 98 | data.push(byte); 99 | } 100 | address = unsafe { address.byte_add(WORD_SIZE) }; 101 | } 102 | Some(data) 103 | } 104 | 105 | // usually used to resolve array of pointers to * 106 | pub fn read_words_until_null(address: usize, pid: Pid) -> Option> { 107 | let mut addr = address as *mut c_void; 108 | let mut data = vec![]; 109 | 'read_loop: loop { 110 | match ptrace::read(pid, addr) { 111 | Ok(word) => { 112 | if word == 0 { 113 | break 'read_loop; 114 | } 115 | data.push(word as usize); 116 | addr = unsafe { addr.byte_add(WORD_SIZE) }; 117 | } 118 | Err(_err) => return Some(data), 119 | }; 120 | } 121 | Some(data) 122 | } 123 | 124 | pub fn read_affinity_from_child(address: usize, pid: Pid) -> Option> { 125 | const CPU_SET_USIZE: usize = (CPU_SETSIZE / WORD_SIZE as i32) as usize; 126 | 127 | let cpu_set = read_bytes_as_struct::(address, pid)?; 128 | 129 | let mut vec = Vec::new(); 130 | for cpu_number in 0..std::thread::available_parallelism() 131 | .map(NonZeroUsize::get) 132 | .unwrap_or(1) as usize 133 | { 134 | if unsafe { CPU_ISSET(cpu_number, &cpu_set) } { 135 | vec.push(cpu_number) 136 | } 137 | } 138 | Some(vec) 139 | } 140 | 141 | pub fn read_string_specific_length(addr: usize, pid: Pid, size: usize) -> Option { 142 | let bytes_buffer = read_bytes_variable_length(addr, pid, size)?; 143 | Some(String::from_utf8_lossy(&bytes_buffer).into_owned()) 144 | } 145 | 146 | pub fn write_bytes(addr: usize, pid: Pid, data: [u64; N]) -> Result<(), ()> { 147 | let mut addr = addr as *mut c_void; 148 | for word in data { 149 | match ptrace::write(pid, addr, word as _) { 150 | Ok(_void) => { 151 | addr = unsafe { addr.byte_add(WORD_SIZE) }; 152 | } 153 | Err(_res) => return Err(()), 154 | }; 155 | } 156 | Ok(()) 157 | } 158 | -------------------------------------------------------------------------------- /src/return_resolvers.rs: -------------------------------------------------------------------------------- 1 | pub mod Readers_Writers { 2 | // basically all syscalls that return `ssize_t` 3 | pub fn parse_return(return_register: u64) -> String { 4 | format!("{return_register} Bytes") 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/syscall_categories.rs: -------------------------------------------------------------------------------- 1 | use crate::types::Category; 2 | use std::collections::HashMap; 3 | use syscalls::Sysno; 4 | // TODO! 5 | // revise and compare with strace's system 6 | pub fn initialize_categories_map() -> HashMap { 7 | use Category::*; 8 | let array: Vec<(Sysno, Category)> = vec![ 9 | (Sysno::read, DiskIO), 10 | (Sysno::write, DiskIO), 11 | (Sysno::pread64, DiskIO), 12 | (Sysno::pwrite64, DiskIO), 13 | (Sysno::readv, DiskIO), 14 | (Sysno::writev, DiskIO), 15 | (Sysno::preadv, DiskIO), 16 | (Sysno::pwritev, DiskIO), 17 | (Sysno::preadv2, DiskIO), 18 | (Sysno::pwritev2, DiskIO), 19 | (Sysno::sync, DiskIO), 20 | (Sysno::syncfs, DiskIO), 21 | (Sysno::fsync, DiskIO), 22 | (Sysno::fdatasync, DiskIO), 23 | (Sysno::truncate, DiskIO), 24 | (Sysno::ftruncate, DiskIO), 25 | (Sysno::getdents, DiskIO), 26 | (Sysno::getdents64, DiskIO), 27 | (Sysno::pipe, DiskIO), 28 | (Sysno::pipe2, DiskIO), 29 | (Sysno::dup, FileOp), 30 | (Sysno::dup2, FileOp), 31 | (Sysno::dup3, FileOp), 32 | (Sysno::access, FileOp), 33 | (Sysno::faccessat, FileOp), 34 | (Sysno::faccessat2, FileOp), 35 | (Sysno::open, FileOp), 36 | (Sysno::openat, FileOp), 37 | (Sysno::openat2, FileOp), 38 | (Sysno::creat, FileOp), 39 | (Sysno::getcwd, FileOp), 40 | (Sysno::chdir, FileOp), 41 | (Sysno::fchdir, FileOp), 42 | (Sysno::rename, FileOp), 43 | (Sysno::renameat, FileOp), 44 | (Sysno::renameat2, FileOp), 45 | (Sysno::mkdir, FileOp), 46 | (Sysno::mkdirat, FileOp), 47 | (Sysno::link, FileOp), 48 | (Sysno::linkat, FileOp), 49 | (Sysno::unlink, FileOp), 50 | (Sysno::unlinkat, FileOp), 51 | (Sysno::rmdir, FileOp), 52 | (Sysno::symlink, FileOp), 53 | (Sysno::symlinkat, FileOp), 54 | (Sysno::readlink, FileOp), 55 | (Sysno::readlinkat, FileOp), 56 | (Sysno::chmod, FileOp), 57 | (Sysno::fchmod, FileOp), 58 | (Sysno::fchmodat, FileOp), 59 | (Sysno::chown, FileOp), 60 | (Sysno::fchown, FileOp), 61 | (Sysno::lchown, FileOp), 62 | (Sysno::fchownat, FileOp), 63 | (Sysno::close, FileOp), 64 | (Sysno::stat, FileOp), 65 | (Sysno::fstat, FileOp), 66 | (Sysno::lstat, FileOp), 67 | (Sysno::newfstatat, FileOp), 68 | (Sysno::statx, FileOp), 69 | (Sysno::statfs, FileOp), 70 | (Sysno::fstatfs, FileOp), 71 | (Sysno::lseek, FileOp), 72 | (Sysno::fallocate, FileOp), 73 | (Sysno::ustat, Device), 74 | (Sysno::cachestat, Memory), 75 | (Sysno::mmap, Memory), 76 | (Sysno::mprotect, Memory), 77 | (Sysno::munmap, Memory), 78 | (Sysno::brk, Memory), 79 | (Sysno::mlock, Memory), 80 | (Sysno::mlock2, Memory), 81 | (Sysno::munlock, Memory), 82 | (Sysno::mlockall, Memory), 83 | (Sysno::munlockall, Memory), 84 | (Sysno::mremap, Memory), 85 | (Sysno::msync, Memory), 86 | (Sysno::mincore, Memory), 87 | (Sysno::madvise, Memory), 88 | (Sysno::select, AsyncIO), 89 | (Sysno::pselect6, AsyncIO), 90 | (Sysno::poll, AsyncIO), 91 | (Sysno::ppoll, AsyncIO), 92 | (Sysno::epoll_create, AsyncIO), 93 | (Sysno::epoll_create1, AsyncIO), 94 | (Sysno::epoll_wait, AsyncIO), 95 | (Sysno::epoll_pwait, AsyncIO), 96 | (Sysno::epoll_pwait2, AsyncIO), 97 | (Sysno::epoll_ctl, AsyncIO), 98 | (Sysno::socket, Network), 99 | (Sysno::bind, Network), 100 | (Sysno::getsockname, Network), 101 | (Sysno::getpeername, Network), 102 | (Sysno::socketpair, Network), 103 | (Sysno::setsockopt, Network), 104 | (Sysno::getsockopt, Network), 105 | (Sysno::listen, Network), 106 | (Sysno::accept, Network), 107 | (Sysno::accept4, Network), 108 | (Sysno::connect, Network), 109 | (Sysno::sendto, Network), 110 | (Sysno::sendmsg, Network), 111 | (Sysno::recvfrom, Network), 112 | (Sysno::recvmsg, Network), 113 | (Sysno::shutdown, Process), 114 | (Sysno::fcntl, FileOp), 115 | (Sysno::ioctl, Device), 116 | (Sysno::getrandom, Device), 117 | (Sysno::rt_sigaction, Signals), 118 | (Sysno::rt_sigprocmask, Signals), 119 | (Sysno::rt_sigsuspend, Signals), 120 | (Sysno::sigaltstack, Signals), 121 | (Sysno::rt_sigreturn, Signals), 122 | (Sysno::rt_sigpending, Signals), 123 | (Sysno::rt_sigtimedwait, Signals), 124 | (Sysno::rt_sigqueueinfo, Signals), 125 | (Sysno::rt_tgsigqueueinfo, Signals), 126 | (Sysno::signalfd, Signals), 127 | (Sysno::signalfd4, Signals), 128 | (Sysno::kill, Signals), 129 | (Sysno::tgkill, Signals), 130 | (Sysno::tkill, Signals), 131 | (Sysno::pause, Signals), 132 | (Sysno::pidfd_send_signal, Signals), 133 | (Sysno::arch_prctl, Process), 134 | (Sysno::sched_yield, Process), 135 | (Sysno::gettid, Process), 136 | (Sysno::getpid, Process), 137 | (Sysno::getppid, Process), 138 | (Sysno::times, Process), 139 | (Sysno::setrlimit, Process), 140 | (Sysno::getrlimit, Process), 141 | (Sysno::prlimit64, Process), 142 | (Sysno::sched_setaffinity, Process), 143 | (Sysno::sched_getaffinity, Process), 144 | (Sysno::getuid, Process), 145 | (Sysno::geteuid, Process), 146 | (Sysno::getgid, Process), 147 | (Sysno::getegid, Process), 148 | (Sysno::setuid, Process), 149 | (Sysno::setgid, Process), 150 | (Sysno::setpgid, Process), 151 | (Sysno::getpgid, Process), 152 | (Sysno::getpgrp, Process), 153 | (Sysno::exit, Process), 154 | (Sysno::exit_group, Process), 155 | (Sysno::rseq, Process), 156 | (Sysno::set_tid_address, Process), 157 | (Sysno::wait4, Process), 158 | (Sysno::waitid, Process), 159 | (Sysno::fork, Process), 160 | (Sysno::vfork, Process), 161 | (Sysno::clone3, Process), 162 | (Sysno::clone, Process), 163 | (Sysno::execve, Process), 164 | (Sysno::getrusage, System), 165 | (Sysno::sysinfo, System), 166 | (Sysno::ptrace, System), 167 | (Sysno::uname, System), 168 | (Sysno::futex, System), 169 | (Sysno::set_robust_list, System), 170 | (Sysno::get_robust_list, System), 171 | (Sysno::eventfd, System), 172 | (Sysno::eventfd2, System), 173 | (Sysno::nanosleep, System), 174 | (Sysno::landlock_create_ruleset, Security), 175 | (Sysno::landlock_add_rule, Security), 176 | (Sysno::landlock_restrict_self, Security), 177 | (Sysno::getpriority, Process), 178 | (Sysno::setpriority, Process), 179 | ]; 180 | array.into_iter().collect() 181 | } 182 | -------------------------------------------------------------------------------- /src/syscall_object.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_variables)] 2 | use crate::auxiliary::kernel_errno::{self}; 3 | use crate::cli::{SUMMARY_ONLY, SYSCALLS_TO_TRACE}; 4 | use crate::{types::Bytes, utilities::SYSKELETON_MAP}; 5 | use nix::unistd::Pid; 6 | use std::{ 7 | fmt::Display, 8 | mem::{self}, 9 | }; 10 | 11 | #[derive(Clone, Debug, PartialEq)] 12 | pub enum SyscallState { 13 | Entering, 14 | Exiting, 15 | } 16 | 17 | #[derive(Debug)] 18 | pub enum ErrnoVariant { 19 | Userland(nix::errno::Errno), 20 | Kernel(kernel_errno::KernelErrno), 21 | } 22 | impl Display for ErrnoVariant { 23 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 24 | match self { 25 | ErrnoVariant::Userland(userland_errno) => { 26 | write!(f, "{:?}: {}", userland_errno, userland_errno.desc()) 27 | } 28 | ErrnoVariant::Kernel(kernel_errno) => { 29 | write!(f, "{:?}: {}", kernel_errno, kernel_errno.desc()) 30 | } 31 | } 32 | } 33 | } 34 | 35 | impl ErrnoVariant { 36 | pub fn desc(&self) -> &'static str { 37 | match self { 38 | ErrnoVariant::Userland(errno) => errno.desc(), 39 | ErrnoVariant::Kernel(kernel_errno) => kernel_errno.desc(), 40 | } 41 | } 42 | } 43 | 44 | #[derive(Debug)] 45 | pub enum SyscallResult { 46 | Fail(ErrnoVariant), 47 | Success(u64), 48 | } 49 | 50 | use syscalls::Sysno; 51 | 52 | #[derive(Debug)] 53 | pub struct SyscallObject { 54 | pub sysno: Sysno, 55 | pub tracee_pid: Pid, 56 | pub state: SyscallState, 57 | pub paused: bool, 58 | pub result: SyscallResult, 59 | pub currently_blocking: bool, 60 | } 61 | 62 | impl Default for SyscallObject { 63 | fn default() -> Self { 64 | SyscallObject { 65 | sysno: unsafe { mem::zeroed() }, 66 | result: unsafe { mem::zeroed() }, 67 | tracee_pid: unsafe { mem::zeroed() }, 68 | state: SyscallState::Entering, 69 | paused: false, 70 | currently_blocking: false, 71 | } 72 | } 73 | } 74 | 75 | impl SyscallObject { 76 | pub fn fill_buffer(&mut self) { 77 | if let Ok(_) = self.one_line_formatter() { 78 | // let mut string = String::new(); 79 | // for string_portion in &mut self.one_line { 80 | // string.push_str(&format!("{}", string_portion)); 81 | // } 82 | // print!("{}", string) 83 | } else { 84 | // disabled for now 85 | // switch to syscallobject_annotation formatting 86 | // let mut annot_variant = SyscallObject_Annotations::from(self); 87 | // annot_variant.format() 88 | } 89 | } 90 | } 91 | 92 | impl SyscallObject { 93 | pub(crate) fn get_sysno(orig_rax: i32) -> Sysno { 94 | Sysno::from(orig_rax) 95 | } 96 | 97 | pub(crate) fn get_errno(&self) -> &ErrnoVariant { 98 | if let SyscallResult::Fail(ref errno_variant) = self.result { 99 | return errno_variant; 100 | } 101 | unreachable!() 102 | } 103 | 104 | pub(crate) fn build(tracee_pid: Pid, sysno: Sysno) -> Option { 105 | let _ = SYSKELETON_MAP.get(&sysno)?; 106 | Some(SyscallObject { 107 | sysno, 108 | tracee_pid, 109 | ..Default::default() 110 | }) 111 | } 112 | 113 | pub(crate) fn should_skip_building(sysno: Sysno) -> bool { 114 | !SYSCALLS_TO_TRACE.contains(sysno) || *SUMMARY_ONLY 115 | } 116 | 117 | fn style_bytes(register_value: u64) -> String { 118 | let bytes_amount = register_value as usize; 119 | let mut bytes = Bytes::norm(bytes_amount); 120 | if bytes_amount as f64 / 1_000_000_000.0 > 1.0 { 121 | bytes = Bytes::giga(bytes_amount as f64 / 1_000_000_000.0) 122 | } else if bytes_amount as f64 / 1_000_000.0 > 1.0 { 123 | bytes = Bytes::mega(bytes_amount as f64 / 1_000_000.0) 124 | } else if bytes_amount as f64 / 1_000.0 > 1.0 { 125 | bytes = Bytes::kilo(bytes_amount as f64 / 1_000.0) 126 | } 127 | bytes.to_string() 128 | } 129 | 130 | pub(crate) fn style_bytes_length_specific(register_value: u64) -> String { 131 | let bytes_amount = register_value as usize; 132 | let mut bytes = Bytes::norm(bytes_amount); 133 | if bytes_amount / 1_000_000_000 > 1 { 134 | bytes = Bytes::giga(bytes_amount as f64 / 1_000_000_000.0) 135 | } else if bytes_amount / 1_000_000 > 1 { 136 | bytes = Bytes::mega(bytes_amount as f64 / 1_000_000.0) 137 | } else if bytes_amount / 1_000 > 1 { 138 | bytes = Bytes::kilo(bytes_amount as f64 / 1_000.0) 139 | } 140 | bytes.to_string() 141 | } 142 | 143 | pub(crate) fn is_mem_alloc_dealloc(&self) -> bool { 144 | // TODO! 145 | // scrutinize 146 | self.sysno == Sysno::brk || self.sysno == Sysno::mmap 147 | } 148 | 149 | pub(crate) fn is_exiting(&self) -> bool { 150 | self.sysno == Sysno::exit || self.sysno == Sysno::exit_group 151 | } 152 | 153 | pub(crate) fn has_errored(&self) -> bool { 154 | matches!(self.result, SyscallResult::Fail(_)) 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/types.rs: -------------------------------------------------------------------------------- 1 | 2 | use crate::{colors::PAGES_COLOR, utilities::PAGE_SIZE}; 3 | use colored::Colorize; 4 | use std::{fmt::Display, mem::MaybeUninit}; 5 | 6 | pub type Annotation = [&'static str; 2]; 7 | 8 | pub type SysAnnotations = (&'static str, &'static [Annotation], Annotation); 9 | 10 | #[derive(Clone)] 11 | pub struct Syscall_Shape { 12 | // pub types: &'static [SysArg], 13 | pub syscall_return: SysReturn, 14 | } 15 | 16 | type FD = &'static str; 17 | type PID = &'static str; 18 | type FD_PAIR = [&'static str; 2]; 19 | type ARR = &'static [&'static str]; 20 | type Errored = MaybeUninit; 21 | type ADDRESS = &'static str; 22 | type SIGNAL = &'static str; 23 | type TEXT = &'static str; 24 | 25 | #[derive(Clone, Copy, Debug)] 26 | pub enum SysReturn { 27 | Numeric_Or_Errno, 28 | Always_Successful_Numeric, 29 | Length_Of_Bytes_Specific_Or_Errno, 30 | Address_Or_Errno(ADDRESS), 31 | Address_Or_MAP_FAILED_Errno(ADDRESS), 32 | Address_Or_Errno_getcwd(ADDRESS), 33 | Signal_Or_Errno(SIGNAL), 34 | Priority_Or_Errno(Errored), 35 | File_Descriptor_Or_Errno(FD), 36 | Does_Not_Return_Anything, 37 | Ptrace_Diverse_Or_Errno, 38 | Always_Successful_User_Group, 39 | Always_Succeeds, 40 | Always_Errors, 41 | Never_Returns, 42 | } 43 | 44 | #[derive(PartialEq, Eq, Hash, Debug, Clone, Copy)] 45 | pub enum Category { 46 | Process, 47 | System, 48 | Thread, 49 | Memory, 50 | DiskIO, 51 | FileOp, 52 | Network, 53 | CPU, 54 | Security, 55 | Device, 56 | AsyncIO, 57 | Signals, 58 | } 59 | 60 | impl Display for Category { 61 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 62 | match self { 63 | Category::CPU => { 64 | write!(f, "CPU",) 65 | } 66 | Category::Memory => { 67 | write!(f, "Memory",) 68 | } 69 | Category::DiskIO => { 70 | write!(f, "Disk",) 71 | } 72 | Category::FileOp => { 73 | write!(f, "Disk",) 74 | } 75 | Category::Device => { 76 | write!(f, "Device",) 77 | } 78 | Category::Process => { 79 | write!(f, "Process",) 80 | } 81 | Category::AsyncIO => { 82 | write!(f, "AsyncIO",) 83 | } 84 | Category::Signals => { 85 | write!(f, "Signals",) 86 | } 87 | Category::Network => { 88 | write!(f, "Network",) 89 | } 90 | Category::Thread => { 91 | write!(f, "Thread",) 92 | } 93 | Category::System => { 94 | write!(f, "System",) 95 | } 96 | Category::Security => { 97 | write!(f, "Security",) 98 | } 99 | } 100 | } 101 | } 102 | 103 | // TODO! 104 | // consider humansize crate 105 | 106 | pub enum Bytes { 107 | norm(usize), 108 | kilo(f64), 109 | mega(f64), 110 | giga(f64), 111 | } 112 | 113 | impl Display for Bytes { 114 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 115 | match self { 116 | Bytes::norm(bytes) => { 117 | write!(f, "{:.1} Bytes", bytes) 118 | } 119 | Bytes::kilo(bytes) => { 120 | write!(f, "{:.1} Kib", bytes) 121 | } 122 | Bytes::mega(bytes) => { 123 | write!(f, "{:.1} Mib", bytes) 124 | } 125 | Bytes::giga(bytes) => { 126 | write!(f, "{:.1} Gib", bytes) 127 | } 128 | } 129 | } 130 | } 131 | 132 | impl From for Bytes { 133 | fn from(value: usize) -> Self { 134 | let value_float = value as f64; 135 | if (value_float / 1_073_741_824.0) >= 1.0 { 136 | Bytes::giga(value_float / 1_073_741_824.0) 137 | } else if (value_float / 1_048_576.0) >= 1.0 { 138 | Bytes::mega(value_float / 1_048_576.0) 139 | } else if (value_float / 1_024.0) >= 1.0 { 140 | Bytes::kilo(value_float / 1_024.0) 141 | } else { 142 | Bytes::norm(value) 143 | } 144 | } 145 | } 146 | 147 | pub enum BytesPagesRelevant { 148 | PagesCeil(Bytes), 149 | PagesFloor(Bytes), 150 | } 151 | 152 | impl BytesPagesRelevant { 153 | pub fn from_ceil(value: usize) -> Self { 154 | BytesPagesRelevant::PagesCeil(Bytes::from(value)) 155 | } 156 | pub fn from_floor(value: usize) -> Self { 157 | BytesPagesRelevant::PagesFloor(Bytes::from(value)) 158 | } 159 | } 160 | 161 | impl Display for BytesPagesRelevant { 162 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 163 | use Bytes::*; 164 | use BytesPagesRelevant::*; 165 | match self { 166 | PagesCeil(bytes) => match *bytes { 167 | norm(bytes) => { 168 | let pages = format!("{} Pages", f64::ceil(bytes as f64 / *PAGE_SIZE as f64)) 169 | .custom_color(*PAGES_COLOR); 170 | write!(f, "{:.1} Bytes ({})", bytes, pages) 171 | } 172 | kilo(bytes) => { 173 | let pages = 174 | format!("{} Pages", f64::ceil((bytes * 1024.0) / *PAGE_SIZE as f64)) 175 | .custom_color(*PAGES_COLOR); 176 | write!(f, "{:.1} KiB ({})", bytes, pages) 177 | } 178 | mega(bytes) => { 179 | let pages = format!( 180 | "{} Pages", 181 | f64::ceil((bytes * 1_048_576.0) / *PAGE_SIZE as f64) 182 | ) 183 | .custom_color(*PAGES_COLOR); 184 | write!(f, "{:.1} MiB ({})", bytes, pages) 185 | } 186 | giga(bytes) => { 187 | let pages = format!( 188 | "{} Pages", 189 | f64::ceil((bytes * 1_073_741_824.0) / *PAGE_SIZE as f64) 190 | ) 191 | .custom_color(*PAGES_COLOR); 192 | write!(f, "{:.1} GiB ({})", bytes, pages) 193 | } 194 | }, 195 | PagesFloor(bytes) => match *bytes { 196 | norm(bytes) => { 197 | let pages = format!("{} Pages", f64::floor(bytes as f64 / *PAGE_SIZE as f64)) 198 | .custom_color(*PAGES_COLOR); 199 | 200 | write!(f, "{:.1} Bytes ({})", bytes, pages) 201 | } 202 | kilo(bytes) => { 203 | let pages = 204 | format!("{} Pages", f64::floor((bytes * 1024.0) / *PAGE_SIZE as f64)) 205 | .custom_color(*PAGES_COLOR); 206 | 207 | write!(f, "{:.1} KiB ({})", bytes, pages) 208 | } 209 | mega(bytes) => { 210 | let pages = format!( 211 | "{} Pages", 212 | f64::floor((bytes * 1_048_576.0) / *PAGE_SIZE as f64) 213 | ) 214 | .custom_color(*PAGES_COLOR); 215 | 216 | write!(f, "{:.1} MiB ({})", bytes, pages) 217 | } 218 | giga(bytes) => { 219 | let pages = format!( 220 | "{} Pages", 221 | f64::floor((bytes * 1_073_741_824.0) / *PAGE_SIZE as f64) 222 | ) 223 | .custom_color(*PAGES_COLOR); 224 | 225 | write!(f, "{:.1} GiB ({})", bytes, pages) 226 | } 227 | }, 228 | } 229 | } 230 | } 231 | 232 | #[repr(C)] 233 | #[derive(Debug)] 234 | pub enum mlock2 { 235 | MLOCK_ONFAULT = 1, 236 | } 237 | 238 | #[repr(C)] 239 | #[derive(Debug)] 240 | pub enum LandlockCreateFlags { 241 | LANDLOCK_CREATE_RULESET_VERSION = 1, 242 | } 243 | 244 | #[repr(C)] 245 | #[derive(Debug)] 246 | pub enum LandlockRuleTypeFlags { 247 | LANDLOCK_RULE_PATH_BENEATH = 1, 248 | } 249 | -------------------------------------------------------------------------------- /src/utilities.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | auxiliary::{constants::general::MAX_KERNEL_ULONG, kernel_errno::KernelErrno}, 3 | colors::{switch_pathlike_color, PARTITION_1_COLOR, PARTITION_2_COLOR, PATHLIKE_ALTERNATOR}, 4 | peeker_poker::{read_bytes_until_null, read_words_until_null}, 5 | syscall_categories::initialize_categories_map, 6 | syscall_object::{ErrnoVariant, SyscallResult}, 7 | syscall_skeleton_map::initialize_skeletons_map, 8 | types::{Bytes, BytesPagesRelevant, Category, Syscall_Shape}, 9 | }; 10 | 11 | use colored::{ColoredString, Colorize, CustomColor}; 12 | use core::sync::atomic::AtomicUsize; 13 | use if_chain::if_chain; 14 | use nix::{ 15 | errno::Errno, 16 | libc::{sysconf, AT_FDCWD, _SC_PAGESIZE}, 17 | sys::signal::Signal, 18 | unistd::Pid, 19 | }; 20 | use procfs::process::{MemoryMap, Process}; 21 | use std::{ 22 | borrow::Cow, 23 | collections::HashMap, 24 | hash::{DefaultHasher, Hash, Hasher}, 25 | sync::{ 26 | atomic::{AtomicBool, Ordering}, 27 | LazyLock, Mutex, 28 | }, 29 | time::Duration, 30 | }; 31 | use syscalls::Sysno; 32 | use unicode_segmentation::Graphemes; 33 | use uzers::{Groups, Users}; 34 | 35 | pub static PAGE_SIZE: LazyLock = LazyLock::new(|| unsafe { sysconf(_SC_PAGESIZE) as usize }); 36 | pub static PRE_CALL_PROGRAM_BREAK_POINT: AtomicUsize = AtomicUsize::new(0); 37 | pub static REGISTERS: Mutex<[u64; 6]> = Mutex::new([0; 6]); 38 | pub static HALT_TRACING: AtomicBool = AtomicBool::new(false); 39 | 40 | pub static TABLE: LazyLock>> = 41 | LazyLock::new(|| Mutex::new(HashMap::new())); 42 | pub static TABLE_FOLLOW_FORKS: LazyLock>> = 43 | LazyLock::new(|| Mutex::new(HashMap::new())); 44 | // pub static SYSANNOT_MAP: LazyLock> = 45 | // LazyLock::new(|| initialize_annotations_map()); 46 | pub static SYSKELETON_MAP: LazyLock> = 47 | LazyLock::new(initialize_skeletons_map); 48 | pub static SYSCATEGORIES_MAP: LazyLock> = 49 | LazyLock::new(initialize_categories_map); 50 | pub static FUTEXES: LazyLock>> = 51 | LazyLock::new(|| Mutex::new(HashMap::new())); 52 | pub static UZERS_CACHE: LazyLock> = 53 | LazyLock::new(|| Mutex::new(uzers::UsersCache::new())); 54 | pub static TRACEES: LazyLock>> = 55 | LazyLock::new(|| Mutex::new(HashMap::new())); 56 | // TODO! 57 | // switch to a string-interner implementation that remembers the last 5 pathlikes 58 | pub static LAST_PATHLIKE: LazyLock> = LazyLock::new(|| Mutex::new(0)); 59 | 60 | pub fn lose_relativity_on_path(string: &str) -> &str { 61 | match string 62 | .chars() 63 | .enumerate() 64 | .skip_while(|&(_index, chara)| chara == '.' || chara == '/') 65 | .take(1) 66 | .next() 67 | { 68 | Some((index, _chara)) => &string[index..], 69 | None => "", 70 | } 71 | } 72 | 73 | pub fn get_mem_difference_from_previous(post_call_brk: usize) -> isize { 74 | post_call_brk as isize - PRE_CALL_PROGRAM_BREAK_POINT.load(Ordering::SeqCst) as isize 75 | } 76 | 77 | pub fn set_memory_break_pre_call(tracee_pid: Pid) { 78 | let mut tracees = TRACEES.lock().unwrap(); 79 | let process = tracees 80 | .entry(tracee_pid) 81 | .or_insert_with(|| procfs::process::Process::new(i32::from(tracee_pid)).unwrap()); 82 | let stat = process.stat().unwrap(); 83 | let pre_call_brk = stat.start_brk.unwrap() as usize; 84 | PRE_CALL_PROGRAM_BREAK_POINT.store(pre_call_brk, Ordering::SeqCst); 85 | } 86 | 87 | pub fn where_in_tracee_memory(tracee_pid: Pid, address: u64) -> Option { 88 | let mut tracees = TRACEES.lock().unwrap(); 89 | let process = tracees 90 | .entry(tracee_pid) 91 | .or_insert_with(|| procfs::process::Process::new(i32::from(tracee_pid)).unwrap()); 92 | let maps = process.maps().ok()?.0; 93 | maps.into_iter() 94 | .find(|map| (address >= map.address.0) && (address <= map.address.1)) 95 | } 96 | 97 | pub fn interpret_syscall_result(return_register: u64) -> SyscallResult { 98 | // TODO! 99 | // abandon the KernelErrno check and make it manual in restartable syscalls 100 | use ErrnoVariant::*; 101 | use SyscallResult::*; 102 | 103 | // strace does something similar to this 104 | // https://github.com/strace/strace/blob/0f9f46096fa8da84e2e6a6646cd1e326bf7e83c7/src/negated_errno.h#L17 105 | // https://github.com/strace/strace/blob/0f9f46096fa8da84e2e6a6646cd1e326bf7e83c7/src/linux/x86_64/get_error.c#L26 106 | if return_register > MAX_KERNEL_ULONG as u64 { 107 | let errno_positive = parse_as_long(return_register) * -1; 108 | let userland_errno = Errno::from_raw(errno_positive as i32); 109 | if matches!(userland_errno, Errno::UnknownErrno) { 110 | let kernel_errno = KernelErrno::from_i32(errno_positive as i32); 111 | if matches!(kernel_errno, KernelErrno::UnknownErrno) { 112 | // Large number but not an error 113 | return Success(return_register); 114 | } 115 | return Fail(Kernel(kernel_errno)); 116 | } 117 | Fail(Userland(userland_errno)) 118 | } else { 119 | Success(return_register) 120 | } 121 | } 122 | 123 | pub fn display_unsupported() { 124 | // unsafe { 125 | // UNSUPPORTED.iter().for_each(|uns| println!(" - {}", uns)); 126 | // } 127 | } 128 | 129 | // this makes futexes more searchable 130 | pub fn calculate_futex_alias(mut futex_count: i32) -> String { 131 | let mut collector = String::new(); 132 | while futex_count >= 0 { 133 | let remainder = futex_count % 26; 134 | let letter = (b'A' + remainder as u8) as char; 135 | collector.insert(0, letter); 136 | futex_count = (futex_count / 26) - 1; 137 | } 138 | collector.push_str(" ->"); 139 | collector 140 | } 141 | 142 | pub fn parse_as_signal(signum: i32) -> &'static str { 143 | match Signal::try_from(signum) { 144 | Ok(signal) => signal.as_str(), 145 | Err(_e) => match signum { 146 | 32 => "SIGRT_32", 147 | 33 => "SIGRT_33", 148 | 34 => "SIGRT_34", 149 | 35 => "SIGRT_35", 150 | 36 => "SIGRT_36", 151 | 37 => "SIGRT_37", 152 | 38 => "SIGRT_38", 153 | 39 => "SIGRT_39", 154 | 40 => "SIGRT_40", 155 | 41 => "SIGRT_41", 156 | 42 => "SIGRT_42", 157 | 43 => "SIGRT_43", 158 | 44 => "SIGRT_44", 159 | 45 => "SIGRT_45", 160 | 46 => "SIGRT_46", 161 | 47 => "SIGRT_47", 162 | 48 => "SIGRT_48", 163 | 49 => "SIGRT_49", 164 | 50 => "SIGRT_50", 165 | 51 => "SIGRT_51", 166 | 52 => "SIGRT_52", 167 | 53 => "SIGRT_53", 168 | 54 => "SIGRT_54", 169 | 55 => "SIGRT_55", 170 | 56 => "SIGRT_56", 171 | 57 => "SIGRT_57", 172 | 58 => "SIGRT_58", 173 | 59 => "SIGRT_59", 174 | 60 => "SIGRT_60", 175 | 61 => "SIGRT_61", 176 | 62 => "SIGRT_62", 177 | 63 => "SIGRT_63", 178 | 64 => "SIGRT_64", 179 | _ => "[intentrace: signal not supported]", 180 | }, 181 | } 182 | } 183 | 184 | pub fn parse_as_int(register: u64) -> i32 { 185 | unsafe { std::mem::transmute::(lower_32_bits(register)) } 186 | } 187 | 188 | pub fn parse_as_long(register: u64) -> i64 { 189 | unsafe { std::mem::transmute::(register) } 190 | } 191 | 192 | #[inline(always)] 193 | pub fn parse_as_ssize_t(register: usize) -> isize { 194 | unsafe { std::mem::transmute::(register) } 195 | } 196 | 197 | pub fn lower_32_bits(value: u64) -> u32 { 198 | (value & 0xFFFFFFFF) as u32 199 | } 200 | 201 | pub fn lower_64_bits(value: usize) -> u64 { 202 | (value & 0xFFFFFFFFFFFFFFFF) as u64 203 | } 204 | 205 | // Length_Of_Bytes_Specific 206 | // memory and file indexers and seekers where negative is expected 207 | pub fn parse_as_signed_bytes(register_value: u64) -> String { 208 | let bytes = unsafe { std::mem::transmute::(register_value) }; 209 | // TODO! 210 | // phrasing should be checked for lseek and offsets in mmap 211 | format!("{bytes} Bytes") 212 | } 213 | 214 | // Length_Of_Bytes_Specific 215 | // memory and file indexers and seekers where negative is expected 216 | pub fn parse_as_unsigned_bytes(register_value: u64) -> String { 217 | format!("{register_value} Bytes") 218 | } 219 | 220 | pub fn parse_as_bytes_no_pages_ceil(register_value: usize) -> String { 221 | let bytes_pages = Bytes::from(register_value); 222 | bytes_pages.to_string() 223 | } 224 | 225 | // usually a size_t in mem syscalls 226 | pub fn parse_as_bytes_pages_ceil(register_value: usize) -> String { 227 | let bytes_pages = BytesPagesRelevant::from_ceil(register_value); 228 | bytes_pages.to_string() 229 | } 230 | 231 | // usually a size_t in mem syscalls 232 | fn parse_as_bytes_pages_floor(register_value: usize) -> String { 233 | let bytes_pages = BytesPagesRelevant::from_floor(register_value); 234 | bytes_pages.to_string() 235 | } 236 | 237 | // Use process_vm_readv(2) 238 | pub fn string_from_pointer(address: usize, tracee_pid: Pid) -> String { 239 | // TODO! 240 | // multi-threaded execve fails here for some reason 241 | match read_bytes_until_null(address, tracee_pid) { 242 | Some(data) => String::from_utf8_lossy(&data).into_owned(), 243 | None => "".to_owned(), 244 | } 245 | } 246 | 247 | pub fn get_array_of_strings(address: usize, tracee_pid: Pid) -> Vec { 248 | // TODO! 249 | // execve fails this 250 | let array_of_char_pointers = read_words_until_null(address, tracee_pid).unwrap(); 251 | let mut strings = vec![]; 252 | for char_pointer in array_of_char_pointers { 253 | strings.push(string_from_pointer(char_pointer, tracee_pid)); 254 | } 255 | strings 256 | } 257 | fn calculate_hash(t: &str) -> u64 { 258 | let mut s = DefaultHasher::new(); 259 | t.hash(&mut s); 260 | s.finish() 261 | } 262 | 263 | pub fn get_final_dentry_color_consider_repetition(repetition_dependent: &str) -> CustomColor { 264 | let last_pathlike = *LAST_PATHLIKE.lock().unwrap(); 265 | if last_pathlike == calculate_hash(repetition_dependent) { 266 | // dont switch 267 | let alternator = *PATHLIKE_ALTERNATOR.lock().unwrap(); 268 | PARTITION_2_COLOR[alternator] 269 | } else { 270 | *LAST_PATHLIKE.lock().unwrap() = calculate_hash(repetition_dependent); 271 | // switch 272 | switch_pathlike_color(); 273 | let alternator = *PATHLIKE_ALTERNATOR.lock().unwrap(); 274 | PARTITION_2_COLOR[alternator] 275 | } 276 | } 277 | 278 | pub fn partition_by_final_dentry(graphemes: Graphemes) -> (String, String) { 279 | let mut graphemes_revved = graphemes.rev(); 280 | let second_partition = graphemes_revved 281 | .by_ref() 282 | .take_while(|chara| *chara != "/") 283 | .collect::>(); 284 | let mut first_partition = graphemes_revved.rev().collect::(); 285 | first_partition.push('/'); 286 | ( 287 | first_partition, 288 | second_partition.into_iter().rev().collect::(), 289 | ) 290 | } 291 | // TODO! 292 | // lose_relativity_on_path requires book keeping 293 | // for now just print the path 294 | // 295 | pub fn get_strings_from_dirfd_anchored_file<'previous_function>( 296 | dirfd: i32, 297 | filename: &'previous_function str, 298 | tracee_pid: Pid, 299 | ) -> anyhow::Result<(Cow<'static, str>, &'previous_function str)> { 300 | if !filename.starts_with('/') { 301 | let mut tracees = TRACEES.lock().unwrap(); 302 | let tracee_process = tracees 303 | .entry(tracee_pid) 304 | .or_insert_with(|| procfs::process::Process::new(i32::from(tracee_pid)).unwrap()); 305 | 306 | if dirfd == AT_FDCWD { 307 | let current_working_directory = tracee_process.cwd()?; 308 | 309 | // remove this check when lose_relativity_on_path starts accounting for path math 310 | let repetition_dependent = if filename.starts_with("./") { 311 | &filename[2..] 312 | } else { 313 | filename.as_ref() 314 | }; 315 | // let repetition_dependent = lose_relativity_on_path(filename.as_ref()); 316 | Ok(( 317 | Cow::Owned(current_working_directory.to_string_lossy().into_owned()), 318 | repetition_dependent, 319 | )) 320 | } else { 321 | let file_info = tracee_process.fd_from_fd(dirfd)?; 322 | match file_info.target { 323 | procfs::process::FDTarget::Path(first_partition) => { 324 | // remove this check when lose_relativity_on_path starts accounting for path math 325 | let repetition_dependent = if filename.starts_with("./") { 326 | &filename[2..] 327 | } else { 328 | filename.as_ref() 329 | }; 330 | // let repetition_dependent = lose_relativity_on_path(filename.as_ref()); 331 | Ok(( 332 | Cow::Owned(first_partition.to_string_lossy().into_owned()), 333 | repetition_dependent, 334 | )) 335 | } 336 | _ => unreachable!(), 337 | } 338 | } 339 | } else { 340 | Ok((Cow::Borrowed(""), filename)) 341 | } 342 | } 343 | 344 | pub fn parse_as_file_descriptor(file_descriptor: i32, tracee_pid: Pid) -> String { 345 | use procfs::process::FDTarget; 346 | let mut colored_strings = Vec::new(); 347 | if file_descriptor == 0 { 348 | return "0 -> StdIn".bright_blue().to_string(); 349 | } else if file_descriptor == 1 { 350 | return "1 -> StdOut".bright_blue().to_string(); 351 | } else if file_descriptor == 2 { 352 | return "2 -> StdErr".bright_blue().to_string(); 353 | } else { 354 | let mut tracees = TRACEES.lock().unwrap(); 355 | let process = tracees 356 | .entry(tracee_pid) 357 | .or_insert_with(|| procfs::process::Process::new(i32::from(tracee_pid)).unwrap()); 358 | let file_info = process.fd_from_fd(file_descriptor); 359 | match file_info { 360 | Ok(file) => match file.target { 361 | FDTarget::Path(path) => { 362 | use unicode_segmentation::UnicodeSegmentation; 363 | colored_strings.push(format!("{} -> ", file.fd).bright_blue()); 364 | let graphemes = path.to_str().unwrap().graphemes(true); 365 | let (yellow, repetition_dependent) = partition_by_final_dentry(graphemes); 366 | let partition_2_color = 367 | get_final_dentry_color_consider_repetition(&repetition_dependent); 368 | colored_strings.push(yellow.custom_color(*PARTITION_1_COLOR)); 369 | colored_strings.push(repetition_dependent.custom_color(partition_2_color)); 370 | } 371 | FDTarget::Socket(socket_number) => { 372 | use procfs::net; 373 | let mut tcp = net::tcp().unwrap_or(vec![]); 374 | tcp.extend(net::tcp6().unwrap_or(vec![])); 375 | 'lookup: { 376 | if let Some(matching_socket) = 377 | tcp.into_iter().find(|entry| entry.inode == socket_number) 378 | { 379 | match matching_socket.remote_address.ip().is_loopback() { 380 | true => colored_strings.push( 381 | format!( 382 | "{} -> localhost:{}", 383 | file.fd, 384 | matching_socket.remote_address.port() 385 | ) 386 | .bright_blue(), 387 | ), 388 | false => colored_strings.push( 389 | format!( 390 | "{} -> {}:{}", 391 | file.fd, 392 | matching_socket.remote_address.ip().to_string(), 393 | matching_socket.remote_address.port() 394 | ) 395 | .bright_blue(), 396 | ), 397 | }; 398 | break 'lookup; 399 | } 400 | if_chain! { 401 | if let Ok(entries) = net::tcp6(); 402 | if let Some(matching_socket) = entries.into_iter().find(|entry| entry.inode == socket_number); 403 | then { 404 | match matching_socket.remote_address.ip().is_loopback() { 405 | true => colored_strings.push( 406 | format!( 407 | "{} -> localhost:{}", 408 | file.fd, 409 | matching_socket.remote_address.port() 410 | ) 411 | .bright_blue(), 412 | ), 413 | false => colored_strings.push( 414 | format!( 415 | "{} -> {}:{}", 416 | file.fd, 417 | matching_socket.remote_address.ip().to_string(), 418 | matching_socket.remote_address.port() 419 | ) 420 | .bright_blue(), 421 | ), 422 | }; 423 | break 'lookup; 424 | } 425 | } 426 | if_chain! { 427 | if let Ok(entries) = net::udp(); 428 | if let Some(_) = entries.into_iter().find(|entry| entry.inode == socket_number); 429 | then { 430 | colored_strings 431 | .push(format!("{} -> UDP Socket", file.fd).bright_blue()); 432 | break 'lookup; 433 | } 434 | } 435 | if_chain! { 436 | if let Ok(entries) = net::udp6(); 437 | if let Some(_) = entries.into_iter().find(|entry| entry.inode == socket_number); 438 | then { 439 | colored_strings 440 | .push(format!("{} -> UDP Socket", file.fd).bright_blue()); 441 | break 'lookup; 442 | } 443 | } 444 | if_chain! { 445 | if let Ok(entries) = net::unix(); 446 | if let Some(_) = entries.into_iter().find(|entry| entry.inode == socket_number); 447 | then { 448 | colored_strings 449 | .push(format!("{} -> Unix Domain Socket", file.fd).bright_blue()); 450 | break 'lookup; 451 | } 452 | } 453 | } 454 | } 455 | FDTarget::Net(_net) => { 456 | return format!("{} -> NET", file.fd).bright_blue().to_string() 457 | } 458 | FDTarget::Pipe(_pipe) => { 459 | return format!("{} -> Unix Pipe", file.fd) 460 | .bright_blue() 461 | .to_string() 462 | } 463 | FDTarget::AnonInode(anon_inode) => { 464 | // anon_inode is basically a file that has no inode on disk 465 | // anon_inode could've been something that was a file that is no longer on the disk 466 | // Some syscalls create file descriptors that have no inode 467 | // epoll_create, eventfd, inotify_init, signalfd, and timerfd 468 | // the entry will be a symbolic link with contents "anon_inode:" 469 | // An anon_inode shows that there's a file descriptor which has no referencing inode 470 | 471 | // open syscall can be used to create an anon inode 472 | // int fd = open( "/tmp/file", O_CREAT | O_RDWR, 0666 ); 473 | // unlink( "/tmp/file" ); 474 | return format!("{} -> {anon_inode}", file.fd) 475 | .bright_blue() 476 | .to_string(); 477 | } 478 | FDTarget::MemFD(mem_fd) => { 479 | return format!("{} -> {mem_fd}", file.fd).bright_blue().to_string() 480 | } 481 | FDTarget::Other(target, _inode_number) => { 482 | return format!("{} -> {target}", file.fd).bright_blue().to_string() 483 | } 484 | }, 485 | Err(_e) => return "ignored".to_owned(), 486 | } 487 | } 488 | String::from_iter( 489 | colored_strings 490 | .into_iter() 491 | .map(|colored_string| colored_string.to_string()), 492 | ) 493 | } 494 | 495 | pub fn find_fd_for_tracee(file_descriptor: i32, tracee_pid: Pid) -> Option { 496 | let mut tracees = TRACEES.lock().unwrap(); 497 | let process = tracees 498 | .entry(tracee_pid) 499 | .or_insert_with(|| procfs::process::Process::new(i32::from(tracee_pid)).unwrap()); 500 | let mut fds = process.fd().ok()?; 501 | let descriptor_found = fds.find(|fd_iter| fd_iter.as_ref().unwrap().fd == file_descriptor)?; 502 | let descriptor_unwrapped = descriptor_found.ok()?; 503 | if let procfs::process::FDTarget::Path(path_buf) = descriptor_unwrapped.target { 504 | Some(path_buf.to_string_lossy().to_string()) 505 | } else { 506 | None 507 | } 508 | } 509 | 510 | pub fn get_username_from_uid(owner: u32) -> Option<&'static str> { 511 | let cache = UZERS_CACHE.lock().unwrap(); 512 | let user = cache.get_user_by_uid(owner)?; 513 | let name_str = user.name().to_str()?; 514 | Some(name_str.to_owned().leak()) 515 | } 516 | 517 | pub fn get_groupname_from_uid(group: u32) -> Option<&'static str> { 518 | let cache = UZERS_CACHE.lock().unwrap(); 519 | let group_retrieved = cache.get_group_by_gid(group)?; 520 | let group = group_retrieved.name().to_str()?; 521 | Some(group.to_owned().leak()) 522 | } 523 | 524 | pub fn new_process() -> ColoredString { 525 | " 526 | 527 | ╭────────────────╮ 528 | │ │ 529 | │ NEW PROCESS │ 530 | │ │ 531 | ╰────────────────╯ 532 | " 533 | .custom_color(colored::CustomColor { 534 | r: 223, 535 | g: 128, 536 | b: 8, 537 | }) 538 | } 539 | 540 | pub fn new_thread() -> ColoredString { 541 | " 542 | 543 | ╭────────────────╮ 544 | │ │ 545 | │ NEW THREAD │ 546 | │ │ 547 | ╰────────────────╯ 548 | " 549 | .green() 550 | } 551 | 552 | // TODO! 553 | // consider blinking arrows as replacement 554 | pub fn syscall_is_blocking() -> ColoredString { 555 | " ╭─────────────────────╮ 556 | │ SYSCALL BLOCKED │ 557 | ╰─────────────────────╯ 558 | " 559 | .cyan() 560 | } 561 | 562 | // TODO! 563 | // check how strace does this, maybe its better 564 | pub fn colorize_syscall_name(sysno: &Sysno, category: &Category) -> ColoredString { 565 | match category { 566 | // green 567 | Category::Process => sysno.name().bold().green(), 568 | Category::Thread => sysno.name().bold().green(), 569 | Category::CPU => sysno.name().bold().green(), 570 | 571 | Category::Network => sysno.name().bold().green(), 572 | 573 | // ram 574 | Category::Memory => sysno.name().bold().bright_red(), 575 | 576 | // bluish 577 | Category::FileOp => sysno.name().bold().blue(), 578 | Category::DiskIO => sysno.name().bold().bright_blue(), 579 | Category::Security => sysno.name().bold().bright_cyan(), 580 | 581 | // black 582 | Category::System => sysno.name().bold().cyan(), 583 | 584 | // exotic 585 | Category::Signals => sysno.name().bold().bright_purple(), 586 | Category::Device => sysno.name().bold().bright_yellow(), 587 | Category::AsyncIO => sysno.name().bold().purple(), 588 | } 589 | } 590 | -------------------------------------------------------------------------------- /src/writer.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | cli::OUTPUT_FILE, 3 | colors::{ 4 | EXITED_BACKGROUND_COLOR, GENERAL_TEXT_COLOR, OUR_YELLOW, PAGES_COLOR, PARTITION_1_COLOR, 5 | PARTITION_2_COLOR, PATHLIKE_ALTERNATOR, PID_BACKGROUND_COLOR, 6 | }, 7 | utilities::{ 8 | calculate_futex_alias, get_final_dentry_color_consider_repetition, 9 | get_strings_from_dirfd_anchored_file, partition_by_final_dentry, FUTEXES, PAGE_SIZE, 10 | }, 11 | }; 12 | use colored::{ColoredString, Colorize, CustomColor}; 13 | use nix::unistd::Pid; 14 | use num_traits::{PrimInt, Signed, Unsigned}; 15 | use std::{ 16 | borrow::Cow, 17 | io::{BufWriter, Write}, 18 | sync::{LazyLock, Mutex, OnceLock}, 19 | }; 20 | use syscalls::Sysno; 21 | 22 | pub static BUFFER: LazyLock>> = LazyLock::new(|| Mutex::new(Vec::new())); 23 | pub static WRITER: OnceLock>>> = OnceLock::new(); 24 | 25 | pub fn initialize_writer() { 26 | // colored crate disables stderr's coloring when stdout is redirected elsewhere, e.g.: /dev/null 27 | // this is a workaround for now 28 | // https://github.com/colored-rs/colored/issues/125#issuecomment-1691155922 29 | use colored; 30 | colored::control::set_override(true); 31 | 32 | let sink: Box = if let Some(output) = *OUTPUT_FILE { 33 | match std::fs::File::options() 34 | .append(false) 35 | .write(true) 36 | .create(true) 37 | .truncate(true) 38 | .open(output) 39 | { 40 | Ok(file) => Box::new(file), 41 | Err(_) => { 42 | eprintln!("Could not open or create file: {}", output.display()); 43 | std::process::exit(100); 44 | } 45 | } 46 | } else { 47 | Box::new(std::io::stderr()) 48 | }; 49 | let _ = WRITER.set(Mutex::new(BufWriter::new(sink))); 50 | } 51 | 52 | #[inline(always)] 53 | pub(crate) fn write_general_text(arg: &str) { 54 | buffered_write(arg.custom_color(*GENERAL_TEXT_COLOR)); 55 | } 56 | 57 | #[inline(always)] 58 | pub(crate) fn write_text(text: ColoredString) { 59 | buffered_write(text); 60 | } 61 | 62 | #[inline(always)] 63 | pub fn buffered_write(data: ColoredString) { 64 | BUFFER.lock().unwrap().push(data); 65 | } 66 | 67 | #[inline(always)] 68 | pub(crate) fn errorize_pid_color(text: ColoredString) { 69 | BUFFER.lock().unwrap()[0] = text; 70 | } 71 | 72 | #[inline(always)] 73 | pub fn empty_buffer() { 74 | let mut buffer = BUFFER.lock().unwrap(); 75 | buffer.clear(); 76 | } 77 | 78 | #[inline(always)] 79 | pub fn flush_buffer() { 80 | use std::io::Write; 81 | let mut buffer = BUFFER.lock().unwrap(); 82 | let mut writer = WRITER.get().unwrap().lock().unwrap(); 83 | for colored_text in buffer.iter_mut() { 84 | write!(writer, "{}", colored_text).unwrap(); 85 | } 86 | writer.flush().unwrap(); 87 | } 88 | 89 | #[inline(always)] 90 | pub fn write_parenthesis(string: &str) { 91 | write_general_text(" ("); 92 | write_text(string.custom_color(*OUR_YELLOW)); 93 | write_general_text(")"); 94 | } 95 | 96 | #[inline(always)] 97 | pub fn write_syscall_not_covered(sysno: Sysno, tracee_pid: Pid) { 98 | buffered_write(tracee_pid.to_string().white()); 99 | buffered_write(" ".dimmed()); 100 | buffered_write(sysno.name().white()); 101 | buffered_write(" - ".dimmed()); 102 | buffered_write("[intentrace: syscall not covered yet]".white()); 103 | buffered_write("\n".dimmed()); 104 | flush_buffer(); 105 | } 106 | 107 | pub fn write_vanilla_path_file(filename: &str) { 108 | use unicode_segmentation::UnicodeSegmentation; 109 | let graphemes = filename.graphemes(true); 110 | let (yellow, repetition_dependent) = partition_by_final_dentry(graphemes); 111 | 112 | write_path_consider_repetition(&yellow, &repetition_dependent); 113 | } 114 | 115 | pub fn write_colored(filename: String) { 116 | buffered_write(filename.normal()) 117 | } 118 | 119 | pub fn write_path_consider_repetition(yellow: &str, repetition_dependent: &str) { 120 | write_partition_1_consider_repetition(yellow); 121 | write_partition_2_consider_repetition(repetition_dependent); 122 | } 123 | 124 | // repetition isnt relevant for the first partition of the path 125 | // but the name is kept for consistency 126 | #[inline(always)] 127 | pub fn write_partition_1_consider_repetition(partition1: &str) { 128 | buffered_write(partition1.custom_color(*PARTITION_1_COLOR)); 129 | } 130 | 131 | #[inline(always)] 132 | pub fn write_partition_2_consider_repetition(partition2: &str) { 133 | // don't alternate if we're operating on a directory 134 | let partition_2_color = if partition2.len() != 0 { 135 | get_final_dentry_color_consider_repetition(partition2) 136 | } else { 137 | PARTITION_2_COLOR[*PATHLIKE_ALTERNATOR.lock().unwrap()] 138 | }; 139 | buffered_write(partition2.custom_color(partition_2_color)); 140 | } 141 | 142 | pub fn write_possible_dirfd_anchor( 143 | dirfd: i32, 144 | filename: String, 145 | tracee_pid: Pid, 146 | ) -> anyhow::Result<()> { 147 | let (first_partition, second_partition) = 148 | get_strings_from_dirfd_anchored_file(dirfd, filename.as_ref(), tracee_pid)?; 149 | match first_partition { 150 | Cow::Owned(string) => { 151 | write_path_consider_repetition(string.as_ref(), second_partition); 152 | } 153 | Cow::Borrowed(_empty) => { 154 | write_vanilla_path_file(second_partition); 155 | } 156 | } 157 | Ok(()) 158 | } 159 | 160 | pub fn write_directives(mut vector: Vec) { 161 | if !vector.is_empty() { 162 | // first element 163 | write_general_text(" ("); 164 | write_text(vector.pop().unwrap()); 165 | // remaining elements 166 | for entry in vector { 167 | write_general_text(", "); 168 | write_text(entry); 169 | } 170 | write_general_text(")"); 171 | } 172 | } 173 | 174 | pub fn write_commas(mut vector: Vec) { 175 | if !vector.is_empty() { 176 | // first element 177 | write_text(vector.pop().unwrap()); 178 | // remaining elements 179 | for entry in vector { 180 | write_general_text(", "); 181 | write_text(entry); 182 | } 183 | } 184 | } 185 | 186 | pub fn write_oring(mut vector: Vec) { 187 | if !vector.is_empty() { 188 | // first element 189 | write_text(vector.pop().unwrap()); 190 | // remaining elements 191 | for entry in vector { 192 | write_general_text(", or "); 193 | write_text(entry); 194 | } 195 | } 196 | } 197 | 198 | pub fn write_anding(vector: Vec) { 199 | let mut vector_iter = vector.into_iter(); 200 | // first element 201 | if let Some(entry) = vector_iter.next() { 202 | write_text(entry); 203 | } 204 | // second and remaining elements 205 | if let Some(second_as_last) = vector_iter.next() { 206 | for entry in vector_iter { 207 | write_general_text(", "); 208 | write_text(entry); 209 | } 210 | // last element 211 | write_general_text(", and "); 212 | write_text(second_as_last); 213 | } 214 | } 215 | use thousands::Separable; 216 | pub fn write_timespec(seconds: i64, nanoseconds: i64) { 217 | if seconds == 0 { 218 | if nanoseconds == 0 { 219 | write_text("immediately".custom_color(*OUR_YELLOW)); 220 | } else { 221 | write_text("after ".custom_color(*OUR_YELLOW)); 222 | write_text( 223 | nanoseconds 224 | .separate_with_commas() 225 | .custom_color(*PAGES_COLOR), 226 | ); 227 | write_text(" nanoseconds".custom_color(*OUR_YELLOW)); 228 | } 229 | } else { 230 | write_text("after ".custom_color(*OUR_YELLOW)); 231 | write_text(seconds.separate_with_commas().custom_color(*PAGES_COLOR)); 232 | write_text(" seconds".custom_color(*OUR_YELLOW)); 233 | if nanoseconds != 0 { 234 | write_general_text(", "); 235 | write_text( 236 | nanoseconds 237 | .separate_with_commas() 238 | .custom_color(*PAGES_COLOR), 239 | ); 240 | write_text(" nanoseconds".custom_color(*OUR_YELLOW)); 241 | } 242 | } 243 | } 244 | 245 | pub fn write_timespec_non_relative(seconds: i64, nanoseconds: i64) { 246 | if seconds == 0 { 247 | if nanoseconds == 0 { 248 | write_text("0".custom_color(*PAGES_COLOR)); 249 | write_text(" nano-seconds".custom_color(*OUR_YELLOW)); 250 | } else { 251 | write_text( 252 | nanoseconds 253 | .separate_with_commas() 254 | .custom_color(*PAGES_COLOR), 255 | ); 256 | write_text(" nano-seconds".custom_color(*OUR_YELLOW)); 257 | } 258 | } else { 259 | write_text(seconds.separate_with_commas().custom_color(*PAGES_COLOR)); 260 | write_text(" seconds".custom_color(*OUR_YELLOW)); 261 | if nanoseconds != 0 { 262 | write_general_text(" and "); 263 | write_text( 264 | nanoseconds 265 | .separate_with_commas() 266 | .custom_color(*PAGES_COLOR), 267 | ); 268 | write_text(" nanoseconds".custom_color(*OUR_YELLOW)); 269 | } 270 | } 271 | } 272 | 273 | pub fn write_timeval(seconds: i64, microseconds: i64) { 274 | if seconds == 0 { 275 | if microseconds == 0 { 276 | write_text("immediately".custom_color(*OUR_YELLOW)); 277 | } else { 278 | write_text("after ".custom_color(*OUR_YELLOW)); 279 | write_text( 280 | microseconds 281 | .separate_with_commas() 282 | .custom_color(*PAGES_COLOR), 283 | ); 284 | write_text(" microseconds".custom_color(*OUR_YELLOW)); 285 | } 286 | } else { 287 | write_text("after ".custom_color(*OUR_YELLOW)); 288 | write_text(seconds.separate_with_commas().custom_color(*PAGES_COLOR)); 289 | write_text(" seconds".custom_color(*OUR_YELLOW)); 290 | if microseconds != 0 { 291 | write_general_text(", "); 292 | write_text( 293 | microseconds 294 | .separate_with_commas() 295 | .custom_color(*PAGES_COLOR), 296 | ); 297 | write_text(" microseconds".custom_color(*OUR_YELLOW)); 298 | } 299 | } 300 | } 301 | 302 | // TODO! 303 | // perf 304 | pub fn write_signed_numeric(signed_numeric: impl PrimInt + Signed) { 305 | match signed_numeric.to_isize().unwrap() { 306 | -32767 => write_text("i16::MIN + 1".custom_color(CustomColor::new(0, 169, 233))), 307 | -32768 => write_text("i16::MIN".custom_color(CustomColor::new(0, 169, 233))), 308 | -32769 => write_text("i16::MIN - 1".custom_color(CustomColor::new(0, 169, 233))), 309 | -2147483647 => write_text("i32::MIN + 1".custom_color(CustomColor::new(0, 169, 233))), 310 | -2147483648 => write_text("i32::MIN".custom_color(CustomColor::new(0, 169, 233))), 311 | -2147483649 => write_text("i32::MIN - 1".custom_color(CustomColor::new(0, 169, 233))), 312 | -9223372036854775807 => { 313 | write_text("i64::MIN + 1".custom_color(CustomColor::new(0, 169, 233))) 314 | } 315 | -9223372036854775808 => write_text("i64::MIN".custom_color(CustomColor::new(0, 169, 233))), 316 | n => write_text(n.to_string().custom_color(CustomColor::new(0, 169, 233))), 317 | }; 318 | } 319 | 320 | // TODO! 321 | // perf 322 | pub fn write_unsigned_numeric(unsigned_numeric: impl PrimInt + Unsigned) { 323 | match unsigned_numeric.to_usize().unwrap() { 324 | 32766 => write_text("i16::MAX - 1".custom_color(CustomColor::new(0, 169, 233))), 325 | 32767 => write_text("i16::MAX".custom_color(CustomColor::new(0, 169, 233))), 326 | 32768 => write_text("i16::MAX + 1".custom_color(CustomColor::new(0, 169, 233))), 327 | 65534 => write_text("u16::MAX - 1".custom_color(CustomColor::new(0, 169, 233))), 328 | 65535 => write_text("u16::MAX".custom_color(CustomColor::new(0, 169, 233))), 329 | 65536 => write_text("u16::MAX + 1".custom_color(CustomColor::new(0, 169, 233))), 330 | 2147483646 => write_text("i32::MAX - 1".custom_color(CustomColor::new(0, 169, 233))), 331 | 2147483647 => write_text("i32::MAX".custom_color(CustomColor::new(0, 169, 233))), 332 | 2147483648 => write_text("i32::MAX + 1".custom_color(CustomColor::new(0, 169, 233))), 333 | 4294967294 => write_text("u32::MAX - 1".custom_color(CustomColor::new(0, 169, 233))), 334 | 4294967295 => write_text("u32::MAX".custom_color(CustomColor::new(0, 169, 233))), 335 | 4294967296 => write_text("u32::MAX + 1".custom_color(CustomColor::new(0, 169, 233))), 336 | 9223372036854775806 => { 337 | write_text("i64::MAX - 1".custom_color(CustomColor::new(0, 169, 233))) 338 | } 339 | 9223372036854775807 => write_text("i64::MAX".custom_color(CustomColor::new(0, 169, 233))), 340 | 9223372036854775808 => { 341 | write_text("i64::MAX + 1".custom_color(CustomColor::new(0, 169, 233))) 342 | } 343 | 18446744073709551614 => { 344 | write_text("u64::MAX - 1".custom_color(CustomColor::new(0, 169, 233))) 345 | } 346 | 18446744073709551615 => write_text("u64::MAX".custom_color(CustomColor::new(0, 169, 233))), 347 | n => write_text(n.to_string().custom_color(CustomColor::new(0, 169, 233))), 348 | } 349 | } 350 | 351 | // CONVERSION OUTSIDE 352 | pub fn write_address(register_value: usize) { 353 | let pointer = register_value as *const (); 354 | if pointer.is_null() { 355 | write_text("NULL".custom_color(*OUR_YELLOW)) 356 | } else { 357 | write_text(format!("{pointer:p}").custom_color(*OUR_YELLOW)) 358 | } 359 | } 360 | 361 | // CONVERSION OUTSIDE 362 | pub fn write_page_aligned_address(register_value: usize) { 363 | let pointer = register_value as *const (); 364 | if pointer.is_null() { 365 | write_text("NULL".custom_color(*OUR_YELLOW)) 366 | } else { 367 | // the pipe notation can only be used for 4KiB & 64KiB page sizes 368 | // 16KiB is a common page size but its equivalent to 250 in decimal 369 | let pointer_formatted = format!("{pointer:p}"); 370 | match *PAGE_SIZE { 371 | 4096 => { 372 | let len = pointer_formatted.len(); 373 | write_text(pointer_formatted[..(len - 3)].custom_color(*OUR_YELLOW)); 374 | if register_value % 4096 == 0 { 375 | write_text("|".custom_color(*PAGES_COLOR)); 376 | } else { 377 | write_text("|".red()); 378 | } 379 | write_text(pointer_formatted[(len - 3)..].custom_color(*OUR_YELLOW)); 380 | } 381 | 65536 => { 382 | let len = pointer_formatted.len(); 383 | write_text(pointer_formatted[..(len - 4)].custom_color(*OUR_YELLOW)); 384 | if register_value % 65536 == 0 { 385 | write_text("|".custom_color(*PAGES_COLOR)); 386 | } else { 387 | write_text("|".red()); 388 | } 389 | write_text(pointer_formatted[(len - 4)..].custom_color(*OUR_YELLOW)); 390 | } 391 | 16384 => { 392 | let len = pointer_formatted.len(); 393 | write_text(pointer_formatted[..(len - 5)].custom_color(*OUR_YELLOW)); 394 | if register_value % 16384 == 0 { 395 | write_text( 396 | pointer_formatted[(len - 5)..(len - 4)] 397 | .custom_color(*PAGES_COLOR) 398 | .underline(), 399 | ); 400 | } else { 401 | write_text(pointer_formatted[(len - 5)..(len - 4)].red().underline()); 402 | } 403 | write_text(pointer_formatted[(len - 4)..].custom_color(*OUR_YELLOW)); 404 | } 405 | _ => write_text(pointer_formatted.custom_color(*OUR_YELLOW)), 406 | } 407 | } 408 | } 409 | 410 | pub fn write_futex(futex_address: usize) { 411 | let mut futexes = FUTEXES.lock().unwrap(); 412 | let number_of_futexes = futexes.len(); 413 | let futex_alias = futexes.entry(futex_address).or_insert_with(|| { 414 | calculate_futex_alias(number_of_futexes as _).custom_color(*PAGES_COLOR) 415 | }); 416 | write_text(futex_alias.clone()); 417 | write_text(format!(" {:p}", futex_address as *const ()).custom_color(*OUR_YELLOW)); 418 | } 419 | 420 | pub fn write_exiting(process_pid: Pid) { 421 | let exited = " EXITED ".on_custom_color(*EXITED_BACKGROUND_COLOR); 422 | let pid = format!(" {} ", process_pid).on_custom_color(*PID_BACKGROUND_COLOR); 423 | write_text("\n\n ".white()); 424 | write_text(pid); 425 | write_text(exited); 426 | write_text("\n\n".white()); 427 | } 428 | --------------------------------------------------------------------------------