├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.mkdn ├── demo ├── keybad-fw ├── keybad-fw-snapshot.zip └── memory-image.bin ├── notes ├── 20230620.mkdn └── 20230621.mkdn └── src ├── bin ├── lildb.rs └── snaptool.rs └── lib.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 = 3 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.19.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "a76fd60b23679b7d19bd066031410fb7e458ccc5e958eb5c325888ce4baedc97" 10 | dependencies = [ 11 | "gimli 0.27.3", 12 | ] 13 | 14 | [[package]] 15 | name = "adler" 16 | version = "1.0.2" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 19 | 20 | [[package]] 21 | name = "aes" 22 | version = "0.8.3" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2" 25 | dependencies = [ 26 | "cfg-if", 27 | "cipher", 28 | "cpufeatures", 29 | ] 30 | 31 | [[package]] 32 | name = "aho-corasick" 33 | version = "1.0.2" 34 | source = "registry+https://github.com/rust-lang/crates.io-index" 35 | checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" 36 | dependencies = [ 37 | "memchr", 38 | ] 39 | 40 | [[package]] 41 | name = "ansi_term" 42 | version = "0.12.1" 43 | source = "registry+https://github.com/rust-lang/crates.io-index" 44 | checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" 45 | dependencies = [ 46 | "winapi", 47 | ] 48 | 49 | [[package]] 50 | name = "anstream" 51 | version = "0.3.2" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163" 54 | dependencies = [ 55 | "anstyle", 56 | "anstyle-parse", 57 | "anstyle-query", 58 | "anstyle-wincon", 59 | "colorchoice", 60 | "is-terminal", 61 | "utf8parse", 62 | ] 63 | 64 | [[package]] 65 | name = "anstyle" 66 | version = "1.0.1" 67 | source = "registry+https://github.com/rust-lang/crates.io-index" 68 | checksum = "3a30da5c5f2d5e72842e00bcb57657162cdabef0931f40e2deb9b4140440cecd" 69 | 70 | [[package]] 71 | name = "anstyle-parse" 72 | version = "0.2.1" 73 | source = "registry+https://github.com/rust-lang/crates.io-index" 74 | checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333" 75 | dependencies = [ 76 | "utf8parse", 77 | ] 78 | 79 | [[package]] 80 | name = "anstyle-query" 81 | version = "1.0.0" 82 | source = "registry+https://github.com/rust-lang/crates.io-index" 83 | checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" 84 | dependencies = [ 85 | "windows-sys", 86 | ] 87 | 88 | [[package]] 89 | name = "anstyle-wincon" 90 | version = "1.0.1" 91 | source = "registry+https://github.com/rust-lang/crates.io-index" 92 | checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188" 93 | dependencies = [ 94 | "anstyle", 95 | "windows-sys", 96 | ] 97 | 98 | [[package]] 99 | name = "anyhow" 100 | version = "1.0.71" 101 | source = "registry+https://github.com/rust-lang/crates.io-index" 102 | checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" 103 | dependencies = [ 104 | "backtrace", 105 | ] 106 | 107 | [[package]] 108 | name = "autocfg" 109 | version = "1.1.0" 110 | source = "registry+https://github.com/rust-lang/crates.io-index" 111 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 112 | 113 | [[package]] 114 | name = "backtrace" 115 | version = "0.3.67" 116 | source = "registry+https://github.com/rust-lang/crates.io-index" 117 | checksum = "233d376d6d185f2a3093e58f283f60f880315b6c60075b01f36b3b85154564ca" 118 | dependencies = [ 119 | "addr2line", 120 | "cc", 121 | "cfg-if", 122 | "libc", 123 | "miniz_oxide 0.6.2", 124 | "object 0.30.4", 125 | "rustc-demangle", 126 | ] 127 | 128 | [[package]] 129 | name = "base64ct" 130 | version = "1.6.0" 131 | source = "registry+https://github.com/rust-lang/crates.io-index" 132 | checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" 133 | 134 | [[package]] 135 | name = "bitflags" 136 | version = "1.3.2" 137 | source = "registry+https://github.com/rust-lang/crates.io-index" 138 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 139 | 140 | [[package]] 141 | name = "block-buffer" 142 | version = "0.10.4" 143 | source = "registry+https://github.com/rust-lang/crates.io-index" 144 | checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 145 | dependencies = [ 146 | "generic-array", 147 | ] 148 | 149 | [[package]] 150 | name = "byteorder" 151 | version = "1.4.3" 152 | source = "registry+https://github.com/rust-lang/crates.io-index" 153 | checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" 154 | 155 | [[package]] 156 | name = "bzip2" 157 | version = "0.4.4" 158 | source = "registry+https://github.com/rust-lang/crates.io-index" 159 | checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8" 160 | dependencies = [ 161 | "bzip2-sys", 162 | "libc", 163 | ] 164 | 165 | [[package]] 166 | name = "bzip2-sys" 167 | version = "0.1.11+1.0.8" 168 | source = "registry+https://github.com/rust-lang/crates.io-index" 169 | checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" 170 | dependencies = [ 171 | "cc", 172 | "libc", 173 | "pkg-config", 174 | ] 175 | 176 | [[package]] 177 | name = "cc" 178 | version = "1.0.79" 179 | source = "registry+https://github.com/rust-lang/crates.io-index" 180 | checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" 181 | dependencies = [ 182 | "jobserver", 183 | ] 184 | 185 | [[package]] 186 | name = "cfg-if" 187 | version = "1.0.0" 188 | source = "registry+https://github.com/rust-lang/crates.io-index" 189 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 190 | 191 | [[package]] 192 | name = "cipher" 193 | version = "0.4.4" 194 | source = "registry+https://github.com/rust-lang/crates.io-index" 195 | checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" 196 | dependencies = [ 197 | "crypto-common", 198 | "inout", 199 | ] 200 | 201 | [[package]] 202 | name = "clap" 203 | version = "4.3.5" 204 | source = "registry+https://github.com/rust-lang/crates.io-index" 205 | checksum = "2686c4115cb0810d9a984776e197823d08ec94f176549a89a9efded477c456dc" 206 | dependencies = [ 207 | "clap_builder", 208 | "clap_derive", 209 | "once_cell", 210 | ] 211 | 212 | [[package]] 213 | name = "clap_builder" 214 | version = "4.3.5" 215 | source = "registry+https://github.com/rust-lang/crates.io-index" 216 | checksum = "2e53afce1efce6ed1f633cf0e57612fe51db54a1ee4fd8f8503d078fe02d69ae" 217 | dependencies = [ 218 | "anstream", 219 | "anstyle", 220 | "bitflags", 221 | "clap_lex", 222 | "strsim", 223 | ] 224 | 225 | [[package]] 226 | name = "clap_derive" 227 | version = "4.3.2" 228 | source = "registry+https://github.com/rust-lang/crates.io-index" 229 | checksum = "b8cd2b2a819ad6eec39e8f1d6b53001af1e5469f8c177579cdaeb313115b825f" 230 | dependencies = [ 231 | "heck", 232 | "proc-macro2", 233 | "quote", 234 | "syn", 235 | ] 236 | 237 | [[package]] 238 | name = "clap_lex" 239 | version = "0.5.0" 240 | source = "registry+https://github.com/rust-lang/crates.io-index" 241 | checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" 242 | 243 | [[package]] 244 | name = "clipboard-win" 245 | version = "4.5.0" 246 | source = "registry+https://github.com/rust-lang/crates.io-index" 247 | checksum = "7191c27c2357d9b7ef96baac1773290d4ca63b24205b82a3fd8a0637afcf0362" 248 | dependencies = [ 249 | "error-code", 250 | "str-buf", 251 | "winapi", 252 | ] 253 | 254 | [[package]] 255 | name = "colorchoice" 256 | version = "1.0.0" 257 | source = "registry+https://github.com/rust-lang/crates.io-index" 258 | checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" 259 | 260 | [[package]] 261 | name = "constant_time_eq" 262 | version = "0.1.5" 263 | source = "registry+https://github.com/rust-lang/crates.io-index" 264 | checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" 265 | 266 | [[package]] 267 | name = "cpufeatures" 268 | version = "0.2.8" 269 | source = "registry+https://github.com/rust-lang/crates.io-index" 270 | checksum = "03e69e28e9f7f77debdedbaafa2866e1de9ba56df55a8bd7cfc724c25a09987c" 271 | dependencies = [ 272 | "libc", 273 | ] 274 | 275 | [[package]] 276 | name = "crc32fast" 277 | version = "1.3.2" 278 | source = "registry+https://github.com/rust-lang/crates.io-index" 279 | checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" 280 | dependencies = [ 281 | "cfg-if", 282 | ] 283 | 284 | [[package]] 285 | name = "crossbeam-utils" 286 | version = "0.8.16" 287 | source = "registry+https://github.com/rust-lang/crates.io-index" 288 | checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" 289 | dependencies = [ 290 | "cfg-if", 291 | ] 292 | 293 | [[package]] 294 | name = "crypto-common" 295 | version = "0.1.6" 296 | source = "registry+https://github.com/rust-lang/crates.io-index" 297 | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 298 | dependencies = [ 299 | "generic-array", 300 | "typenum", 301 | ] 302 | 303 | [[package]] 304 | name = "debugdb" 305 | version = "0.1.0" 306 | source = "git+https://github.com/cbiffle/debugdb#68c28e3465767f08be45ac6a7cf2146eefee74a6" 307 | dependencies = [ 308 | "ansi_term", 309 | "anyhow", 310 | "clap", 311 | "fallible-iterator", 312 | "gimli 0.26.2", 313 | "indexmap 1.9.3", 314 | "object 0.26.2", 315 | "parse_int", 316 | "rangemap", 317 | "regex", 318 | "rustyline", 319 | "scroll", 320 | "thiserror", 321 | ] 322 | 323 | [[package]] 324 | name = "digest" 325 | version = "0.10.7" 326 | source = "registry+https://github.com/rust-lang/crates.io-index" 327 | checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" 328 | dependencies = [ 329 | "block-buffer", 330 | "crypto-common", 331 | "subtle", 332 | ] 333 | 334 | [[package]] 335 | name = "dirs-next" 336 | version = "2.0.0" 337 | source = "registry+https://github.com/rust-lang/crates.io-index" 338 | checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" 339 | dependencies = [ 340 | "cfg-if", 341 | "dirs-sys-next", 342 | ] 343 | 344 | [[package]] 345 | name = "dirs-sys-next" 346 | version = "0.1.2" 347 | source = "registry+https://github.com/rust-lang/crates.io-index" 348 | checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" 349 | dependencies = [ 350 | "libc", 351 | "redox_users", 352 | "winapi", 353 | ] 354 | 355 | [[package]] 356 | name = "endian-type" 357 | version = "0.1.2" 358 | source = "registry+https://github.com/rust-lang/crates.io-index" 359 | checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" 360 | 361 | [[package]] 362 | name = "equivalent" 363 | version = "1.0.0" 364 | source = "registry+https://github.com/rust-lang/crates.io-index" 365 | checksum = "88bffebc5d80432c9b140ee17875ff173a8ab62faad5b257da912bd2f6c1c0a1" 366 | 367 | [[package]] 368 | name = "errno" 369 | version = "0.3.1" 370 | source = "registry+https://github.com/rust-lang/crates.io-index" 371 | checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" 372 | dependencies = [ 373 | "errno-dragonfly", 374 | "libc", 375 | "windows-sys", 376 | ] 377 | 378 | [[package]] 379 | name = "errno-dragonfly" 380 | version = "0.1.2" 381 | source = "registry+https://github.com/rust-lang/crates.io-index" 382 | checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" 383 | dependencies = [ 384 | "cc", 385 | "libc", 386 | ] 387 | 388 | [[package]] 389 | name = "error-code" 390 | version = "2.3.1" 391 | source = "registry+https://github.com/rust-lang/crates.io-index" 392 | checksum = "64f18991e7bf11e7ffee451b5318b5c1a73c52d0d0ada6e5a3017c8c1ced6a21" 393 | dependencies = [ 394 | "libc", 395 | "str-buf", 396 | ] 397 | 398 | [[package]] 399 | name = "fallible-iterator" 400 | version = "0.2.0" 401 | source = "registry+https://github.com/rust-lang/crates.io-index" 402 | checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" 403 | 404 | [[package]] 405 | name = "fd-lock" 406 | version = "3.0.12" 407 | source = "registry+https://github.com/rust-lang/crates.io-index" 408 | checksum = "39ae6b3d9530211fb3b12a95374b8b0823be812f53d09e18c5675c0146b09642" 409 | dependencies = [ 410 | "cfg-if", 411 | "rustix", 412 | "windows-sys", 413 | ] 414 | 415 | [[package]] 416 | name = "flate2" 417 | version = "1.0.26" 418 | source = "registry+https://github.com/rust-lang/crates.io-index" 419 | checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743" 420 | dependencies = [ 421 | "crc32fast", 422 | "miniz_oxide 0.7.1", 423 | ] 424 | 425 | [[package]] 426 | name = "generic-array" 427 | version = "0.14.7" 428 | source = "registry+https://github.com/rust-lang/crates.io-index" 429 | checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" 430 | dependencies = [ 431 | "typenum", 432 | "version_check", 433 | ] 434 | 435 | [[package]] 436 | name = "getrandom" 437 | version = "0.2.10" 438 | source = "registry+https://github.com/rust-lang/crates.io-index" 439 | checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" 440 | dependencies = [ 441 | "cfg-if", 442 | "libc", 443 | "wasi", 444 | ] 445 | 446 | [[package]] 447 | name = "gimli" 448 | version = "0.26.2" 449 | source = "registry+https://github.com/rust-lang/crates.io-index" 450 | checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d" 451 | dependencies = [ 452 | "fallible-iterator", 453 | "indexmap 1.9.3", 454 | "stable_deref_trait", 455 | ] 456 | 457 | [[package]] 458 | name = "gimli" 459 | version = "0.27.3" 460 | source = "registry+https://github.com/rust-lang/crates.io-index" 461 | checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e" 462 | 463 | [[package]] 464 | name = "hashbrown" 465 | version = "0.12.3" 466 | source = "registry+https://github.com/rust-lang/crates.io-index" 467 | checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" 468 | 469 | [[package]] 470 | name = "hashbrown" 471 | version = "0.14.0" 472 | source = "registry+https://github.com/rust-lang/crates.io-index" 473 | checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" 474 | 475 | [[package]] 476 | name = "heck" 477 | version = "0.4.1" 478 | source = "registry+https://github.com/rust-lang/crates.io-index" 479 | checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" 480 | 481 | [[package]] 482 | name = "hermit-abi" 483 | version = "0.3.1" 484 | source = "registry+https://github.com/rust-lang/crates.io-index" 485 | checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" 486 | 487 | [[package]] 488 | name = "hmac" 489 | version = "0.12.1" 490 | source = "registry+https://github.com/rust-lang/crates.io-index" 491 | checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" 492 | dependencies = [ 493 | "digest", 494 | ] 495 | 496 | [[package]] 497 | name = "indexmap" 498 | version = "1.9.3" 499 | source = "registry+https://github.com/rust-lang/crates.io-index" 500 | checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" 501 | dependencies = [ 502 | "autocfg", 503 | "hashbrown 0.12.3", 504 | ] 505 | 506 | [[package]] 507 | name = "indexmap" 508 | version = "2.0.0" 509 | source = "registry+https://github.com/rust-lang/crates.io-index" 510 | checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" 511 | dependencies = [ 512 | "equivalent", 513 | "hashbrown 0.14.0", 514 | ] 515 | 516 | [[package]] 517 | name = "inout" 518 | version = "0.1.3" 519 | source = "registry+https://github.com/rust-lang/crates.io-index" 520 | checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" 521 | dependencies = [ 522 | "generic-array", 523 | ] 524 | 525 | [[package]] 526 | name = "io-lifetimes" 527 | version = "1.0.11" 528 | source = "registry+https://github.com/rust-lang/crates.io-index" 529 | checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" 530 | dependencies = [ 531 | "hermit-abi", 532 | "libc", 533 | "windows-sys", 534 | ] 535 | 536 | [[package]] 537 | name = "is-terminal" 538 | version = "0.4.7" 539 | source = "registry+https://github.com/rust-lang/crates.io-index" 540 | checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" 541 | dependencies = [ 542 | "hermit-abi", 543 | "io-lifetimes", 544 | "rustix", 545 | "windows-sys", 546 | ] 547 | 548 | [[package]] 549 | name = "jobserver" 550 | version = "0.1.26" 551 | source = "registry+https://github.com/rust-lang/crates.io-index" 552 | checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2" 553 | dependencies = [ 554 | "libc", 555 | ] 556 | 557 | [[package]] 558 | name = "libc" 559 | version = "0.2.146" 560 | source = "registry+https://github.com/rust-lang/crates.io-index" 561 | checksum = "f92be4933c13fd498862a9e02a3055f8a8d9c039ce33db97306fd5a6caa7f29b" 562 | 563 | [[package]] 564 | name = "lilosdbg" 565 | version = "0.1.0" 566 | dependencies = [ 567 | "ansi_term", 568 | "anyhow", 569 | "clap", 570 | "debugdb", 571 | "gimli 0.26.2", 572 | "object 0.26.2", 573 | "parse_int", 574 | "rangemap", 575 | "regex", 576 | "rustyline", 577 | "scopeguard", 578 | "thiserror", 579 | "toml", 580 | "zip", 581 | ] 582 | 583 | [[package]] 584 | name = "linux-raw-sys" 585 | version = "0.3.8" 586 | source = "registry+https://github.com/rust-lang/crates.io-index" 587 | checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" 588 | 589 | [[package]] 590 | name = "log" 591 | version = "0.4.19" 592 | source = "registry+https://github.com/rust-lang/crates.io-index" 593 | checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" 594 | 595 | [[package]] 596 | name = "memchr" 597 | version = "2.5.0" 598 | source = "registry+https://github.com/rust-lang/crates.io-index" 599 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 600 | 601 | [[package]] 602 | name = "miniz_oxide" 603 | version = "0.6.2" 604 | source = "registry+https://github.com/rust-lang/crates.io-index" 605 | checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" 606 | dependencies = [ 607 | "adler", 608 | ] 609 | 610 | [[package]] 611 | name = "miniz_oxide" 612 | version = "0.7.1" 613 | source = "registry+https://github.com/rust-lang/crates.io-index" 614 | checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" 615 | dependencies = [ 616 | "adler", 617 | ] 618 | 619 | [[package]] 620 | name = "nibble_vec" 621 | version = "0.1.0" 622 | source = "registry+https://github.com/rust-lang/crates.io-index" 623 | checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43" 624 | dependencies = [ 625 | "smallvec", 626 | ] 627 | 628 | [[package]] 629 | name = "nix" 630 | version = "0.26.2" 631 | source = "registry+https://github.com/rust-lang/crates.io-index" 632 | checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a" 633 | dependencies = [ 634 | "bitflags", 635 | "cfg-if", 636 | "libc", 637 | "static_assertions", 638 | ] 639 | 640 | [[package]] 641 | name = "num-traits" 642 | version = "0.2.15" 643 | source = "registry+https://github.com/rust-lang/crates.io-index" 644 | checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" 645 | dependencies = [ 646 | "autocfg", 647 | ] 648 | 649 | [[package]] 650 | name = "object" 651 | version = "0.26.2" 652 | source = "registry+https://github.com/rust-lang/crates.io-index" 653 | checksum = "39f37e50073ccad23b6d09bcb5b263f4e76d3bb6038e4a3c08e52162ffa8abc2" 654 | dependencies = [ 655 | "flate2", 656 | "memchr", 657 | ] 658 | 659 | [[package]] 660 | name = "object" 661 | version = "0.30.4" 662 | source = "registry+https://github.com/rust-lang/crates.io-index" 663 | checksum = "03b4680b86d9cfafba8fc491dc9b6df26b68cf40e9e6cd73909194759a63c385" 664 | dependencies = [ 665 | "memchr", 666 | ] 667 | 668 | [[package]] 669 | name = "once_cell" 670 | version = "1.18.0" 671 | source = "registry+https://github.com/rust-lang/crates.io-index" 672 | checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" 673 | 674 | [[package]] 675 | name = "parse_int" 676 | version = "0.6.0" 677 | source = "registry+https://github.com/rust-lang/crates.io-index" 678 | checksum = "2d695b79916a2c08bcff7be7647ab60d1402885265005a6658ffe6d763553c5a" 679 | dependencies = [ 680 | "num-traits", 681 | ] 682 | 683 | [[package]] 684 | name = "password-hash" 685 | version = "0.4.2" 686 | source = "registry+https://github.com/rust-lang/crates.io-index" 687 | checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700" 688 | dependencies = [ 689 | "base64ct", 690 | "rand_core", 691 | "subtle", 692 | ] 693 | 694 | [[package]] 695 | name = "pbkdf2" 696 | version = "0.11.0" 697 | source = "registry+https://github.com/rust-lang/crates.io-index" 698 | checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" 699 | dependencies = [ 700 | "digest", 701 | "hmac", 702 | "password-hash", 703 | "sha2", 704 | ] 705 | 706 | [[package]] 707 | name = "pkg-config" 708 | version = "0.3.27" 709 | source = "registry+https://github.com/rust-lang/crates.io-index" 710 | checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" 711 | 712 | [[package]] 713 | name = "proc-macro2" 714 | version = "1.0.60" 715 | source = "registry+https://github.com/rust-lang/crates.io-index" 716 | checksum = "dec2b086b7a862cf4de201096214fa870344cf922b2b30c167badb3af3195406" 717 | dependencies = [ 718 | "unicode-ident", 719 | ] 720 | 721 | [[package]] 722 | name = "quote" 723 | version = "1.0.28" 724 | source = "registry+https://github.com/rust-lang/crates.io-index" 725 | checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" 726 | dependencies = [ 727 | "proc-macro2", 728 | ] 729 | 730 | [[package]] 731 | name = "radix_trie" 732 | version = "0.2.1" 733 | source = "registry+https://github.com/rust-lang/crates.io-index" 734 | checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" 735 | dependencies = [ 736 | "endian-type", 737 | "nibble_vec", 738 | ] 739 | 740 | [[package]] 741 | name = "rand_core" 742 | version = "0.6.4" 743 | source = "registry+https://github.com/rust-lang/crates.io-index" 744 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 745 | 746 | [[package]] 747 | name = "rangemap" 748 | version = "1.3.0" 749 | source = "registry+https://github.com/rust-lang/crates.io-index" 750 | checksum = "8b9283c6b06096b47afc7109834fdedab891175bb5241ee5d4f7d2546549f263" 751 | 752 | [[package]] 753 | name = "redox_syscall" 754 | version = "0.2.16" 755 | source = "registry+https://github.com/rust-lang/crates.io-index" 756 | checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" 757 | dependencies = [ 758 | "bitflags", 759 | ] 760 | 761 | [[package]] 762 | name = "redox_users" 763 | version = "0.4.3" 764 | source = "registry+https://github.com/rust-lang/crates.io-index" 765 | checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" 766 | dependencies = [ 767 | "getrandom", 768 | "redox_syscall", 769 | "thiserror", 770 | ] 771 | 772 | [[package]] 773 | name = "regex" 774 | version = "1.8.4" 775 | source = "registry+https://github.com/rust-lang/crates.io-index" 776 | checksum = "d0ab3ca65655bb1e41f2a8c8cd662eb4fb035e67c3f78da1d61dffe89d07300f" 777 | dependencies = [ 778 | "aho-corasick", 779 | "memchr", 780 | "regex-syntax", 781 | ] 782 | 783 | [[package]] 784 | name = "regex-syntax" 785 | version = "0.7.2" 786 | source = "registry+https://github.com/rust-lang/crates.io-index" 787 | checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78" 788 | 789 | [[package]] 790 | name = "rustc-demangle" 791 | version = "0.1.23" 792 | source = "registry+https://github.com/rust-lang/crates.io-index" 793 | checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" 794 | 795 | [[package]] 796 | name = "rustix" 797 | version = "0.37.20" 798 | source = "registry+https://github.com/rust-lang/crates.io-index" 799 | checksum = "b96e891d04aa506a6d1f318d2771bcb1c7dfda84e126660ace067c9b474bb2c0" 800 | dependencies = [ 801 | "bitflags", 802 | "errno", 803 | "io-lifetimes", 804 | "libc", 805 | "linux-raw-sys", 806 | "windows-sys", 807 | ] 808 | 809 | [[package]] 810 | name = "rustyline" 811 | version = "11.0.0" 812 | source = "registry+https://github.com/rust-lang/crates.io-index" 813 | checksum = "5dfc8644681285d1fb67a467fb3021bfea306b99b4146b166a1fe3ada965eece" 814 | dependencies = [ 815 | "bitflags", 816 | "cfg-if", 817 | "clipboard-win", 818 | "dirs-next", 819 | "fd-lock", 820 | "libc", 821 | "log", 822 | "memchr", 823 | "nix", 824 | "radix_trie", 825 | "scopeguard", 826 | "unicode-segmentation", 827 | "unicode-width", 828 | "utf8parse", 829 | "winapi", 830 | ] 831 | 832 | [[package]] 833 | name = "scopeguard" 834 | version = "1.1.0" 835 | source = "registry+https://github.com/rust-lang/crates.io-index" 836 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 837 | 838 | [[package]] 839 | name = "scroll" 840 | version = "0.10.2" 841 | source = "registry+https://github.com/rust-lang/crates.io-index" 842 | checksum = "fda28d4b4830b807a8b43f7b0e6b5df875311b3e7621d84577188c175b6ec1ec" 843 | 844 | [[package]] 845 | name = "serde" 846 | version = "1.0.164" 847 | source = "registry+https://github.com/rust-lang/crates.io-index" 848 | checksum = "9e8c8cf938e98f769bc164923b06dce91cea1751522f46f8466461af04c9027d" 849 | 850 | [[package]] 851 | name = "serde_spanned" 852 | version = "0.6.3" 853 | source = "registry+https://github.com/rust-lang/crates.io-index" 854 | checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186" 855 | dependencies = [ 856 | "serde", 857 | ] 858 | 859 | [[package]] 860 | name = "sha1" 861 | version = "0.10.5" 862 | source = "registry+https://github.com/rust-lang/crates.io-index" 863 | checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" 864 | dependencies = [ 865 | "cfg-if", 866 | "cpufeatures", 867 | "digest", 868 | ] 869 | 870 | [[package]] 871 | name = "sha2" 872 | version = "0.10.7" 873 | source = "registry+https://github.com/rust-lang/crates.io-index" 874 | checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8" 875 | dependencies = [ 876 | "cfg-if", 877 | "cpufeatures", 878 | "digest", 879 | ] 880 | 881 | [[package]] 882 | name = "smallvec" 883 | version = "1.10.0" 884 | source = "registry+https://github.com/rust-lang/crates.io-index" 885 | checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" 886 | 887 | [[package]] 888 | name = "stable_deref_trait" 889 | version = "1.2.0" 890 | source = "registry+https://github.com/rust-lang/crates.io-index" 891 | checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" 892 | 893 | [[package]] 894 | name = "static_assertions" 895 | version = "1.1.0" 896 | source = "registry+https://github.com/rust-lang/crates.io-index" 897 | checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" 898 | 899 | [[package]] 900 | name = "str-buf" 901 | version = "1.0.6" 902 | source = "registry+https://github.com/rust-lang/crates.io-index" 903 | checksum = "9e08d8363704e6c71fc928674353e6b7c23dcea9d82d7012c8faf2a3a025f8d0" 904 | 905 | [[package]] 906 | name = "strsim" 907 | version = "0.10.0" 908 | source = "registry+https://github.com/rust-lang/crates.io-index" 909 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 910 | 911 | [[package]] 912 | name = "subtle" 913 | version = "2.5.0" 914 | source = "registry+https://github.com/rust-lang/crates.io-index" 915 | checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" 916 | 917 | [[package]] 918 | name = "syn" 919 | version = "2.0.18" 920 | source = "registry+https://github.com/rust-lang/crates.io-index" 921 | checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e" 922 | dependencies = [ 923 | "proc-macro2", 924 | "quote", 925 | "unicode-ident", 926 | ] 927 | 928 | [[package]] 929 | name = "thiserror" 930 | version = "1.0.40" 931 | source = "registry+https://github.com/rust-lang/crates.io-index" 932 | checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" 933 | dependencies = [ 934 | "thiserror-impl", 935 | ] 936 | 937 | [[package]] 938 | name = "thiserror-impl" 939 | version = "1.0.40" 940 | source = "registry+https://github.com/rust-lang/crates.io-index" 941 | checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" 942 | dependencies = [ 943 | "proc-macro2", 944 | "quote", 945 | "syn", 946 | ] 947 | 948 | [[package]] 949 | name = "time" 950 | version = "0.3.22" 951 | source = "registry+https://github.com/rust-lang/crates.io-index" 952 | checksum = "ea9e1b3cf1243ae005d9e74085d4d542f3125458f3a81af210d901dcd7411efd" 953 | dependencies = [ 954 | "serde", 955 | "time-core", 956 | ] 957 | 958 | [[package]] 959 | name = "time-core" 960 | version = "0.1.1" 961 | source = "registry+https://github.com/rust-lang/crates.io-index" 962 | checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" 963 | 964 | [[package]] 965 | name = "toml" 966 | version = "0.7.6" 967 | source = "registry+https://github.com/rust-lang/crates.io-index" 968 | checksum = "c17e963a819c331dcacd7ab957d80bc2b9a9c1e71c804826d2f283dd65306542" 969 | dependencies = [ 970 | "serde", 971 | "serde_spanned", 972 | "toml_datetime", 973 | "toml_edit", 974 | ] 975 | 976 | [[package]] 977 | name = "toml_datetime" 978 | version = "0.6.3" 979 | source = "registry+https://github.com/rust-lang/crates.io-index" 980 | checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" 981 | dependencies = [ 982 | "serde", 983 | ] 984 | 985 | [[package]] 986 | name = "toml_edit" 987 | version = "0.19.12" 988 | source = "registry+https://github.com/rust-lang/crates.io-index" 989 | checksum = "c500344a19072298cd05a7224b3c0c629348b78692bf48466c5238656e315a78" 990 | dependencies = [ 991 | "indexmap 2.0.0", 992 | "serde", 993 | "serde_spanned", 994 | "toml_datetime", 995 | "winnow", 996 | ] 997 | 998 | [[package]] 999 | name = "typenum" 1000 | version = "1.16.0" 1001 | source = "registry+https://github.com/rust-lang/crates.io-index" 1002 | checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" 1003 | 1004 | [[package]] 1005 | name = "unicode-ident" 1006 | version = "1.0.9" 1007 | source = "registry+https://github.com/rust-lang/crates.io-index" 1008 | checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" 1009 | 1010 | [[package]] 1011 | name = "unicode-segmentation" 1012 | version = "1.10.1" 1013 | source = "registry+https://github.com/rust-lang/crates.io-index" 1014 | checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" 1015 | 1016 | [[package]] 1017 | name = "unicode-width" 1018 | version = "0.1.10" 1019 | source = "registry+https://github.com/rust-lang/crates.io-index" 1020 | checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" 1021 | 1022 | [[package]] 1023 | name = "utf8parse" 1024 | version = "0.2.1" 1025 | source = "registry+https://github.com/rust-lang/crates.io-index" 1026 | checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" 1027 | 1028 | [[package]] 1029 | name = "version_check" 1030 | version = "0.9.4" 1031 | source = "registry+https://github.com/rust-lang/crates.io-index" 1032 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 1033 | 1034 | [[package]] 1035 | name = "wasi" 1036 | version = "0.11.0+wasi-snapshot-preview1" 1037 | source = "registry+https://github.com/rust-lang/crates.io-index" 1038 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1039 | 1040 | [[package]] 1041 | name = "winapi" 1042 | version = "0.3.9" 1043 | source = "registry+https://github.com/rust-lang/crates.io-index" 1044 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1045 | dependencies = [ 1046 | "winapi-i686-pc-windows-gnu", 1047 | "winapi-x86_64-pc-windows-gnu", 1048 | ] 1049 | 1050 | [[package]] 1051 | name = "winapi-i686-pc-windows-gnu" 1052 | version = "0.4.0" 1053 | source = "registry+https://github.com/rust-lang/crates.io-index" 1054 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1055 | 1056 | [[package]] 1057 | name = "winapi-x86_64-pc-windows-gnu" 1058 | version = "0.4.0" 1059 | source = "registry+https://github.com/rust-lang/crates.io-index" 1060 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1061 | 1062 | [[package]] 1063 | name = "windows-sys" 1064 | version = "0.48.0" 1065 | source = "registry+https://github.com/rust-lang/crates.io-index" 1066 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 1067 | dependencies = [ 1068 | "windows-targets", 1069 | ] 1070 | 1071 | [[package]] 1072 | name = "windows-targets" 1073 | version = "0.48.0" 1074 | source = "registry+https://github.com/rust-lang/crates.io-index" 1075 | checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" 1076 | dependencies = [ 1077 | "windows_aarch64_gnullvm", 1078 | "windows_aarch64_msvc", 1079 | "windows_i686_gnu", 1080 | "windows_i686_msvc", 1081 | "windows_x86_64_gnu", 1082 | "windows_x86_64_gnullvm", 1083 | "windows_x86_64_msvc", 1084 | ] 1085 | 1086 | [[package]] 1087 | name = "windows_aarch64_gnullvm" 1088 | version = "0.48.0" 1089 | source = "registry+https://github.com/rust-lang/crates.io-index" 1090 | checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" 1091 | 1092 | [[package]] 1093 | name = "windows_aarch64_msvc" 1094 | version = "0.48.0" 1095 | source = "registry+https://github.com/rust-lang/crates.io-index" 1096 | checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" 1097 | 1098 | [[package]] 1099 | name = "windows_i686_gnu" 1100 | version = "0.48.0" 1101 | source = "registry+https://github.com/rust-lang/crates.io-index" 1102 | checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" 1103 | 1104 | [[package]] 1105 | name = "windows_i686_msvc" 1106 | version = "0.48.0" 1107 | source = "registry+https://github.com/rust-lang/crates.io-index" 1108 | checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" 1109 | 1110 | [[package]] 1111 | name = "windows_x86_64_gnu" 1112 | version = "0.48.0" 1113 | source = "registry+https://github.com/rust-lang/crates.io-index" 1114 | checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" 1115 | 1116 | [[package]] 1117 | name = "windows_x86_64_gnullvm" 1118 | version = "0.48.0" 1119 | source = "registry+https://github.com/rust-lang/crates.io-index" 1120 | checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" 1121 | 1122 | [[package]] 1123 | name = "windows_x86_64_msvc" 1124 | version = "0.48.0" 1125 | source = "registry+https://github.com/rust-lang/crates.io-index" 1126 | checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" 1127 | 1128 | [[package]] 1129 | name = "winnow" 1130 | version = "0.4.8" 1131 | source = "registry+https://github.com/rust-lang/crates.io-index" 1132 | checksum = "a9482fe6ceabdf32f3966bfdd350ba69256a97c30253dc616fe0005af24f164e" 1133 | dependencies = [ 1134 | "memchr", 1135 | ] 1136 | 1137 | [[package]] 1138 | name = "zip" 1139 | version = "0.6.6" 1140 | source = "registry+https://github.com/rust-lang/crates.io-index" 1141 | checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261" 1142 | dependencies = [ 1143 | "aes", 1144 | "byteorder", 1145 | "bzip2", 1146 | "constant_time_eq", 1147 | "crc32fast", 1148 | "crossbeam-utils", 1149 | "flate2", 1150 | "hmac", 1151 | "pbkdf2", 1152 | "sha1", 1153 | "time", 1154 | "zstd", 1155 | ] 1156 | 1157 | [[package]] 1158 | name = "zstd" 1159 | version = "0.11.2+zstd.1.5.2" 1160 | source = "registry+https://github.com/rust-lang/crates.io-index" 1161 | checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4" 1162 | dependencies = [ 1163 | "zstd-safe", 1164 | ] 1165 | 1166 | [[package]] 1167 | name = "zstd-safe" 1168 | version = "5.0.2+zstd.1.5.2" 1169 | source = "registry+https://github.com/rust-lang/crates.io-index" 1170 | checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db" 1171 | dependencies = [ 1172 | "libc", 1173 | "zstd-sys", 1174 | ] 1175 | 1176 | [[package]] 1177 | name = "zstd-sys" 1178 | version = "2.0.8+zstd.1.5.5" 1179 | source = "registry+https://github.com/rust-lang/crates.io-index" 1180 | checksum = "5556e6ee25d32df2586c098bbfa278803692a20d0ab9565e049480d52707ec8c" 1181 | dependencies = [ 1182 | "cc", 1183 | "libc", 1184 | "pkg-config", 1185 | ] 1186 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lilosdbg" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | ansi_term = "0.12.1" 10 | anyhow = "1.0.71" 11 | clap = { version = "4.3.5", features = ["derive"] } 12 | debugdb = {git = "https://github.com/cbiffle/debugdb"} 13 | gimli = "0.26.1" 14 | object = "0.26" 15 | parse_int = "0.6.0" 16 | rangemap = "1.3.0" 17 | regex = "1.8.4" 18 | rustyline = "11.0.0" 19 | scopeguard = "1.1.0" 20 | thiserror = "1.0.40" 21 | toml = "0.7.6" 22 | zip = "0.6.6" 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. 374 | -------------------------------------------------------------------------------- /README.mkdn: -------------------------------------------------------------------------------- 1 | # `lilosdbg`: a wee debugger for `lilos` 2 | 3 | **[Technology Readiness Level]:** TRL 0 (leaning tower of hacks) 4 | 5 | This is a _very early prototype_ of a debugger for [`lilos`], my async embedded 6 | operating system. I'm publishing it despite its immaturity to attempt to push 7 | the conversation forward on at-rest debugging of futures generated by `async 8 | fn`. 9 | 10 | This is built on top of my DWARF program analysis crate, [`debugdb`]. 11 | 12 | **`lilosdbg` supports `lilos` 0.3.x and 1.0.0 at this time.** 13 | 14 | ## How to use it 15 | 16 | `lilosdbg` currently can't talk directly to your target over SWD, and instead 17 | relies on a snapshot on disk. (This is deliberate; I want to make sure we can do 18 | snapshot / postmortem debugging, so I'm making sure that use case works great 19 | before attempting live debugging.) 20 | 21 | Extract a memory snapshot of your running `lilos` program into a file. Using 22 | `openocd` the command is (address and size for an STM32G030 shown, you will need 23 | to adjust them): 24 | 25 | ``` 26 | dump_image memory-image.bin 0x20000000 8192 27 | ``` 28 | 29 | Alternatively, in GDB, the command would be 30 | 31 | ``` 32 | dump binary memory memory-image.bin 0x20000000 0x20002000 33 | ``` 34 | 35 | (If you don't have a `lilos` program, you can also test using the files in the 36 | `demo` directory. There's an ELF file and a saved memory snapshot from one of my 37 | programs.) 38 | 39 | Now, run `lildb` with the path to the ELF file for your `lilos` program: 40 | 41 | ``` 42 | cargo run -q --bin lildb YOURPROJECT/target/thumbv6m-none-eabi/yourproject 43 | ``` 44 | 45 | (You'll need to change that path, in case that was unclear.) 46 | 47 | At the `lildb` prompt, load your memory image to the right address: 48 | 49 | ``` 50 | load memory-image.bin 0x20000000 51 | ``` 52 | 53 | Then try asking about the status of your tasks by running `tasks`. Example 54 | output from the demo image in this repo: 55 | 56 | ``` 57 | current tick-time is: 1620 58 | 59 | task 0: 60 | async fn keybad_fw::serial::task 61 | suspended at src/serial.rs:35 62 | waiting on: async fn lilos::spsc::{impl#4}::pop 63 | suspended at /home/cbiffle/proj/lilos/os/src/spsc.rs:313 64 | waiting for data in spsc queue at 0x20001d7c 65 | queue type: lilos::spsc::Queue <.debug_info+0x0000e5b4> 66 | 67 | task 1: 68 | async fn keybad_fw::scanner::task 69 | suspended at src/scanner.rs:134 70 | waiting on: async fn lilos::exec::{impl#6}::next_time 71 | suspended at /home/cbiffle/proj/lilos/os/src/exec.rs:965 72 | waiting on: async fn lilos::exec::sleep_until 73 | suspended at /home/cbiffle/proj/lilos/os/src/exec.rs:815 74 | sleeping until: 1621 (1 ms from now) 75 | ``` 76 | 77 | [`lilos`]: https://github.com/cbiffle/lilos 78 | [`debugdb`]: https://github.com/cbiffle/debugdb 79 | [Technology Readiness Level]: https://en.wikipedia.org/wiki/Technology_readiness_level 80 | -------------------------------------------------------------------------------- /demo/keybad-fw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cbiffle/lilosdbg/8f9763c9192cd2652ff9e4cc5d221949a8404161/demo/keybad-fw -------------------------------------------------------------------------------- /demo/keybad-fw-snapshot.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cbiffle/lilosdbg/8f9763c9192cd2652ff9e4cc5d221949a8404161/demo/keybad-fw-snapshot.zip -------------------------------------------------------------------------------- /demo/memory-image.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cbiffle/lilosdbg/8f9763c9192cd2652ff9e4cc5d221949a8404161/demo/memory-image.bin -------------------------------------------------------------------------------- /notes/20230620.mkdn: -------------------------------------------------------------------------------- 1 | Alrighty. I am going to attempt to manually perform the steps a debugger would 2 | perform if I walked up to my keypad scanner PCB and asked it what's up. 3 | 4 | debugdb can parse the firmware, which is a great start 5 | 6 | We're now haltedin the idle loop, which is the best place to halt (all futures 7 | are sleeping). 8 | 9 | First, we need to find the list of futures. I just added a static for this in 10 | lilos. 11 | 12 | ``` 13 | >> var lilos::exec::TASK_FUTURES 14 | lilos::exec::TASK_FUTURES @ <.debug_info+0x00015c32> 15 | - type: core::option::Option<*mut [core::pin::Pin<*mut dyn core::future::future::Future>]> <.debug_info+0x000171b2> 16 | - address: 0x20000600 17 | ``` 18 | 19 | That Option is (according to dwarf) a non-clever Option: first word's the 20 | discriminator, 0 means None, 1 means Some. Easy. It is 3 words in length. What 21 | are the three words at that address? 22 | 23 | 0x20000600: 00000001 20001fd4 00000002 24 | 25 | So, we have Some, and two words. The raw-slice type says it consists of a data 26 | pointer, and a length. That tracks. So our data pointer is 0x20001fd4 and our 27 | length is 2. 28 | 29 | The type of the pointed-to data: this mouthful: 30 | 31 | core::pin::Pin<*mut dyn core::future::future::Future> 32 | 33 | Types longer than 80 characters! I feel like I'm a young C++ programmer again! 34 | 35 | Each of those Pins contains just a pointer, which is a `*mut dyn yadda`, which 36 | has the representation... 37 | 38 | ``` 39 | 0 1 2 3 40 | +------+------+------+------+ 41 | 0000 | 0 | 42 | +------+------+------+------+ 43 | 0004 | 1 | 44 | +------+------+------+------+ 45 | where: 46 | 0 = pointer: *_ dyn core::future::future::Future <.debug_info+0x00017c06> 47 | 1 = vtable: &[usize; 3] <.debug_info+0x00017c16> 48 | ``` 49 | 50 | So, in our slice in memory, we should expect to find 2x 8-byte thingies, each of 51 | which contains a data pointer and a vtable pointer. Let's see what's out there! 52 | 53 | 0x20001fd4: 20001da0 08003c40 20001f40 08003c50 54 | 55 | Yup, that's a RAM pointer, Flash pointer, repeat. Tracks so far. 56 | 57 | We need to learn the concrete type of these though. We can do that by querying 58 | the debugdb for those vtable pointers. 59 | 60 | ``` 61 | >> addr 0x08003c40 62 | Offset +0x0 into static ::{vtable} 63 | - range 0x8003c40..0x8003c50 64 | - type ::{vtable_type} <.debug_info+0x0001296e> 65 | - .drop_in_place +0x0 (in ::{vtable_type}) 66 | 67 | >> addr 0x08003c50 68 | Offset +0x0 into static ::{vtable} 69 | - range 0x8003c50..0x8003c60 70 | - type ::{vtable_type} <.debug_info+0x00012d2f> 71 | - .drop_in_place +0x0 (in ::{vtable_type}) 72 | ``` 73 | 74 | There's no DWARF metadata to link these to async fns, but we can apply 75 | heuristics. Let's consider just the first one. 76 | 77 | Name: 78 | `::{vtable}` 79 | 80 | - this is a vtable 81 | - it is a vtable for the trait core::future::future::future::future::etc 82 | - it is a vtable for a type known as `.....{async_fn_env#0}` 83 | - and in fact the DWARF has a `containing_type` link that debugdb currently 84 | ignores 85 | 86 | We have learned the type of the data pointer then: it's the `containing_type` 87 | for the vtable (or, the type named in the vtable type name). It is: 88 | 89 | keybad_fw::serial::task::{async_fn_env#0} <.debug_info+0x0000732f>: enum type 90 | 91 | Neat! So we believe we have one of those at 0x20001da0. It's a 416-byte type 92 | (whew!). Here's a dump. 93 | 94 | ``` 95 | 0x20001da0: 08007000 08007800 20001fe4 20000628 20001d7c 20001fe4 20001fe4 20000628 96 | 0x20001dc0: 20001d7c 20001d94 04002100 7ffe0500 20001db0 20001d90 20001db0 079d5e03 97 | disc -----^^ 98 | 0x20001de0: 5a1825c7 20001dd0 00000001 20001fe4 20001fe4 9a7701b3 6900e1e1 db1a5094 99 | 0x20001e00: 2320b1f4 5139c930 920c9dd7 f40d913d 114c4f47 8f9798b0 8733d9ee 7f250206 100 | 0x20001e20: 0761feb6 18d34ae7 e13fc6b5 7f219de9 4dc4d4df 1b8ccc0a aba66abb ffbf1e1e 101 | 0x20001e40: aa189cfc 33f7584b 0003f384 e2b88c52 6482c792 e5997051 32102c4c 3244226b 102 | 0x20001e60: 4c30d9f7 7ff3729e 1a9ef6fc 94bfe603 e59693d1 bfb770bb 5d529ff9 4f8d46e5 103 | 0x20001e80: e9567efb 3ef7f599 6d76d271 eefb0143 efdbd33a 91fd63fa 9fa5bddf eeb13a2a 104 | 0x20001ea0: 24814a84 a3dda1d8 8b24f8ef 148b051f 45957af0 3e494cdd 102cd475 7ce006ca 105 | 0x20001ec0: d207d7a9 cdfd9818 362f7fc7 5153b33b 21b045a6 93bf29ea 0e508b76 b6cb4bf7 106 | 0x20001ee0: 62a1da2b 174d07c7 d486d5ed fd070206 088247b3 ce943444 c1035fda bc364203 107 | 0x20001f00: 3660e99d 173c3a81 2c407729 d59ecca0 634c9a74 d1af412e 2184e477 5be5083e 108 | 0x20001f20: 079afffc fdf7de21 0bd1bff9 c9fd6271 56fafb34 b4334d61 36c1aef5 7d7da2b7 109 | ``` 110 | 111 | tysh reports that type should have a discriminator at offset 43 (decimal, 0x2b). 112 | I've marked it above. 113 | 114 | Discriminator value 4 means that we have the Suspend1 type, 115 | 116 | keybad_fw::serial::task::{async_fn_env#0}::Suspend1 <.debug_info+0x0000750b> 117 | 118 | ``` 119 | 08007000 - storage: keybad_fw::flash::Storage 120 | 08007800 / 121 | 20001fe4 - uart: &stm32g0::stm32g030::USART1 122 | 20000628 - keymap: &[[u8; 8]; 8] 123 | 20001d7c - from_scanner: lilos::spsc::Pop 124 | 20001fe4 (unused) 125 | 20001fe4 - gpioa: &stm32g0::stm32g030::GPIOA 126 | 20000628 (unused) 127 | 20001d7c (unused) 128 | 20001d94 - config_to_scanner: lilos::handoff::Push 129 | 04002100 (unused) 130 | 7ffe0500 - lower byte only = setup_mode: bool 131 | 20001db0 - __awaitee: lilos::spsc::{impl#4}::pop::{async_fn_env#0} <.debug_info+0x0000e16e> 132 | 20001d90 | 133 | 20001db0 | 134 | 079d5e03 / 135 | (rest is currently unused) 136 | ``` 137 | 138 | Cool cool. Suspend1 you say. What can I tell the user about Suspend1? 139 | 140 | Well, debugdb doesn't really parse this great, but over in dwarfdump I note that 141 | the variant member for discriminator 4 in our async-fn-env has decl file 142 | coordinates. Specifically, 143 | 144 | src/serial.rs:35 145 | 146 | That is a line reading 147 | 148 | let event = from_scanner.pop().await; 149 | 150 | So the first frame in our stack trace could be reasonably labeled 151 | 152 | async fn keybad_fw::serial::task 153 | suspend point 1 (serial.rs:35) 154 | 155 | Neat. NEXT 156 | 157 | How about that `__awaitee`, eh? Repeating its value for ease of interpretation: 158 | 159 | ``` 160 | 20001db0 - __awaitee: lilos::spsc::{impl#4}::pop::{async_fn_env#0} <.debug_info+0x0000e16e> 161 | 20001d90 | 162 | 20001db0 | 163 | 079d5e03 / 164 | ``` 165 | 166 | That type is another async-fn-env, and has its discriminator in the low byte of 167 | the last word -- so, it's 3. 3, as usual, means Suspend0. 168 | 169 | lilos::spsc::{impl#4}::pop::{async_fn_env#0}::Suspend0 <.debug_info+0x0000e203> 170 | 171 | dwarfdump shows that that member is "defined" at 172 | 173 | /home/cbiffle/proj/lilos/os/src/spsc.rs:313 174 | 175 | so we've got our second stack frame, which reads 176 | 177 | async fn lilos::spsc::{impl#4}::pop 178 | suspend point 0 (spsc.rs:313) 179 | 180 | From the definition of Suspend0 we can interpret those bytes: 181 | 182 | ``` 183 | 20001db0 - __awaitee: futures_util::future::poll_fn::PollFn, lilos::spsc::{impl#4}::pop::{async_fn#0}::{closure_env#0}>> <.debug_info+0x0000f594> 184 | 20001d90 / 185 | 20001db0 - self: &mut lilos::spsc::Pop <.debug_info+0x00012806> | 186 | 079d5e03 (unused except discriminator, which is 3) 187 | ``` 188 | 189 | So we've got a local we could print there, which is nice. 190 | 191 | NEXT 192 | 193 | The next awaitee is not an async fn! It is an explicitly implemented future. 194 | Because it comes from `futures_util`, I think it's reasonable to expect that a 195 | debugger might special case it. It would be very nice to say _which_ fn is being 196 | polled. 197 | 198 | There's not a lot to go on in the metadata. Suffice to say, the `PollFn` future 199 | wraps a single member, `f`, of a function type. In this case, it's a closure. 200 | 201 | lilos::exec::{impl#1}::until::{closure_env#0}, lilos::spsc::{impl#4}::pop::{async_fn#0}::{closure_env#0}> <.debug_info+0x0000c6a3> 202 | 203 | Decoding it as a struct, we get two fields, `cond` and `self`. So we can sort of 204 | decode it: 205 | 206 | ``` 207 | 20001db0 cond: lilos::spsc::{impl#4}::pop::{async_fn#0}::{closure_env#0} <.debug_info+0x0000e227> 208 | 20001d90 self: &lilos::exec::Notify <.debug_info+0x0001279a> 209 | ``` 210 | 211 | Following cond, we wind up with a field of type 212 | 213 | &mut lilos::spsc::Pop <.debug_info+0x00012806> 214 | 215 | Frustratingly, there's no decl coords. However, if we parse the type name (ew) 216 | and recognize that it's a `{closure_env#0}`, we can go hunting for `{closure#0}` 217 | in the same namespace in the DWARF tree. And it can be found: it's at 218 | `<.debug_info+0xe23a>` as a subprogram. 219 | 220 | So, we can at least give _those_ decl coords, which are: 221 | 222 | /home/cbiffle/proj/lilos/os/src/spsc.rs:313 223 | 224 | I would propose printing this frame as something like 225 | 226 | poll_fn of closure in async fn lilos::spsc::{impl#4}::pop 227 | defined at lilos/os/src/spsc.rs:313 228 | 229 | However, we could also special-case the "until" closure as indicating 230 | `Notify::until`. It would be FUCKING AMAZING if we could say _what_ Notify we're 231 | parked at. 232 | 233 | 20001db0 is the address of a `Pop`. That's just a pointer to a queue. 234 | So we can find the queue, at least. Reading memory... 235 | 236 | Queue is at 0x20001d7c 237 | 238 | I happen to know this queue is on the stack -- it's above sp and below stack 239 | base. So now I'm walking the stack trace of main back, manually. 240 | 241 | ``` 242 | pc=0x080004ee 243 | fp=r7+8=0x20001cd8 244 | caller LR = 0x0800039d 245 | caller FP = 0x20001cd8 246 | 247 | pc=0x08000398, probably 248 | CFA = r7+8 = 0x20001ce0 249 | caller LR = 0x0800040d 250 | caller r7 = 0x20001d18 251 | 252 | pc=0x08000408, probably 253 | CFA = r7+8 = 0x20001d20 254 | caller LR = 0x080003c1 255 | caller r7 = 0x20001d20 256 | 257 | pc = 0x080003bc, probably 258 | CFA = r7 + 8 = 0x20001d28 259 | caller LR = 0x08001e97 260 | caller r7 = 0x20001ff0 261 | 262 | pc = 0x08001e92, probably (we are now in main 263 | CFA = r7+8 = 0x20001ff8 264 | caller LR = 0x08001cd5 265 | caller r7 = 0x20001ff8 266 | caller r6 = 0xffffffff 267 | caller r4 = 0xffffffff 268 | ``` 269 | 270 | In main, at 84 above the stack pointer of 0x20001d28, we find the 271 | `scan_event_to_serial` variable, which is a lilos queue. If I were an OS-aware 272 | debugger, I would describe this as 273 | 274 | waiting to pop from spsc queue: scan_event_to_serial 275 | defined at: src/main.rs:160 276 | --- 277 | 278 | So, for this future, from innermost to outermost, we could display this stack 279 | trace using information readily available in the binary: 280 | 281 | ``` 282 | await stack for future at 0x20001da0: 283 | waiting to pop from spsc queue: scan_event_to_serial 284 | defined at: src/main.rs:160 285 | 0x20001dd0 async fn lilos::spsc::{impl#4}::pop 286 | suspend point 0: lilos/os/src/spsc.rs:313 287 | 0x20001da0 async fn keybad_fw::serial::task 288 | suspend point 1: src/serial.rs:35 289 | ``` 290 | 291 | Cool. 292 | 293 | Now the other one! 294 | 295 | --- 296 | 297 | It's at 0x20001f40 and its type is 298 | 299 | keybad_fw::scanner::task::{async_fn_env#0} <.debug_info+0x00009a9a>: enum type 300 | 301 | Here are its contents from the debugger: 302 | 303 | ``` 304 | 0x20001f40: 00000001 00000000 0000d29e 00000000 20001d94 20001fe4 20001d7c 20000000 305 | 0x20001f60: 20001d94 20001fe4 20001d7c ef97eb95 ffffff9f eb951f00 ff9fef97 1f00ffff 306 | 0x20001f80: d9a7af03 a79fecb6 0000d29e 00000000 0000d29e 00000000 00000002 08003f7c 307 | disc-----^^ 308 | 0x20001fa0: 0000d29e 00000000 20001ce8 20001ce8 20001f98 00000100 20001f98 f1010003 309 | 0x20001fc0: 20001f40 20001f40 b1e0ed03 d966cd2b 310 | ``` 311 | 312 | It has a discriminator at offset 0x40, marked above. The discriminator is 3, 313 | which, as usual, means Suspend0. 314 | 315 | This gives us decl coords: 316 | 317 | keybad/fw/src/scanner.rs:134 318 | 319 | and an interpretation for those bytes: 320 | 321 | ``` 322 | 00000001 - scan_gate: lilos::exec::PeriodicGate 323 | 00000000 | 324 | 0000d29e | 325 | 00000000 / 326 | 20001d94 - config_update: lilos::handoff::Pop 327 | 20001fe4 - gpio: &stm32g0::stm32g030::GPIOA 328 | 20001d7c - out_queue: lilos::spsc::Push 329 | 20000000 - debouncers: &mut [[Debounce; 8]; 8] 330 | 20001d94 (unused) 331 | 20001fe4 (unused) 332 | 20001d7c (unused) 333 | ef97eb95 (unused) 334 | ffffff9f low half unused, top two bytes start Config struct 335 | eb951f00 more config 336 | ff9fef97 more config 337 | 1f00ffff (more config) 338 | d9a7af03 (unused) 339 | a79fecb6 (unused) 340 | 0000d29e - __awaitee: lilos::exec::{impl#6}::next_time::{async_fn_env#0} <.debug_info+0x0000d213> 341 | 00000000 | 342 | 0000d29e | 343 | 00000000 | 344 | 00000002 | 345 | 08003f7c | 346 | 0000d29e | 347 | 00000000 | 348 | 20001ce8 | 349 | 20001ce8 | 350 | 20001f98 | 351 | 00000100 | 352 | 20001f98 | 353 | f1010003 | 354 | 20001f40 | 355 | 20001f40 | 356 | b1e0ed03 | 357 | d966cd2b / 358 | ``` 359 | 360 | Goodness, that's a large awaitee. Let's describe our frame and move down. 361 | 362 | ``` 363 | 0x20001f40 async fn keybad_fw::scanner::task 364 | suspend point 0: src/scanner.rs:134 365 | ``` 366 | 367 | Next has the contents below, with a discriminator of 3, meaning Suspend0. Its 368 | decl coords are 369 | 370 | lilos/os/src/exec.rs:965 371 | 372 | ``` 373 | 0000d29e __awaitee: lilos::exec::sleep_until::{async_fn_env#0} <.debug_info+0x0000c842> 374 | 00000000 | 375 | 0000d29e | 376 | 00000000 | 377 | 00000002 | 378 | 08003f7c | 379 | 0000d29e | 380 | 00000000 | 381 | 20001ce8 | 382 | 20001ce8 | 383 | 20001f98 | 384 | 00000100 | 385 | 20001f98 | 386 | f1010003 / 387 | 20001f40 self: &mut lilos::exec::PeriodicGate <.debug_info+0x00012d74> 388 | 20001f40 (unused) 389 | b1e0ed03 <-- disc, otherwise unused 390 | d966cd2b (unused) 391 | ``` 392 | 393 | Not much going on here. 394 | 395 | ``` 396 | 0x20001f88 async fn lilos::exec::{impl#6}::next_time 397 | suspend point 0: lilos/os/src/exec.rs:965 398 | ``` 399 | 400 | 401 | Next: 402 | 403 | ``` 404 | 0000d29e (unused) 405 | 00000000 (unused) 406 | 0000d29e deadline: lilos::time::TickTime 407 | 00000000 / 408 | 00000002 __2: lilos::list::Node 409 | 08003f7c | 410 | 0000d29e | 411 | 00000000 | 412 | 20001ce8 | 413 | 20001ce8 / 414 | 20001f98 __awaitee: lilos::list::WaitForDetach> <.debug_info+0x0000e7a4> 415 | 00000100 / 416 | 20001f98 node: core::pin::Pin<&mut lilos::list::Node> <.debug_info+0x00001c74> 417 | f1010003 <-- disc 418 | ``` 419 | 420 | decl coords: lilos/os/src/exec.rs:815 421 | 422 | Frame: 423 | 424 | ``` 425 | 0x20001f88 async fn lilos::exec::sleep_until 426 | suspend point 0: lilos/os/src/exec.rs:815 427 | ``` 428 | 429 | Next: 430 | 431 | The next future is not an async fn, it's the lilos WaitForDetach. If I were 432 | writing a lilos-aware debugger, I would special-case it. What's inside it? 433 | 434 | - pointer to the node 435 | - flag recording whether we've been polled to notice detach 436 | - cleanup function 437 | 438 | oooooh we can say _what list we're on._ 439 | 440 | The node pointer is 0x20001f98. LET'S FOLLOW THE LIST 441 | 442 | - We are sleeping until time 0xd29e. It is currently 0xd29d. 443 | - prev is 20001ce8 444 | - This node has a 0 timestamp and a bogus waker. It is likely in a list. 445 | 446 | Sure enough, `lilos::exec::TIMER_LIST` contains `0x20001ce8`. 447 | 448 | I would probably just describe this as: 449 | 450 | ``` 451 | 0x20001fb0 lilos::list::insert_and_wait 452 | waiting for time 0xd29e (1 tick left) 453 | behind 0 other waiters 454 | ``` 455 | 456 | Okay! Stacky tracey: 457 | 458 | ``` 459 | await stack for future at 0x20001f40 460 | 0x20001fb0 waiting for time 0xd29e (1 tick left) 461 | behind 0 other waiters 462 | 0x20001f88 async fn lilos::exec::sleep_until 463 | suspend point 0: lilos/os/src/exec.rs:815 464 | 0x20001f88 async fn lilos::exec::{impl#6}::next_time 465 | suspend point 0: lilos/os/src/exec.rs:965 466 | 0x20001f40 async fn keybad_fw::scanner::task 467 | suspend point 0: src/scanner.rs:134 468 | ``` 469 | 470 | I've saved the memory image, here are the registers if I want to try to do 471 | something with those: 472 | 473 | ``` 474 | (0) r0 (/32): 0x00000008 475 | (1) r1 (/32): 0x50000418 476 | (2) r2 (/32): 0x08003f7c 477 | (3) r3 (/32): 0x20001fb4 478 | (4) r4 (/32): 0x2000060c 479 | (5) r5 (/32): 0x00000000 480 | (6) r6 (/32): 0x20001fe4 481 | (7) r7 (/32): 0x20001cd0 482 | (8) r8 (/32): 0xffffffff 483 | (9) r9 (/32): 0xffffffff 484 | (10) r10 (/32): 0xffffffff 485 | (11) r11 (/32): 0xffffffff 486 | (12) r12 (/32): 0xffffffff 487 | (13) sp (/32): 0x20001cb0 488 | (14) lr (/32): 0x080004cf 489 | (15) pc (/32): 0x080004ee 490 | (16) xPSR (/32): 0x01000000 491 | (17) msp (/32): 0x20001cb0 492 | (18) psp (/32): 0xfffffffc 493 | (20) primask (/1): 0x01 494 | (21) basepri (/8): 0x00 495 | (22) faultmask (/1): 0x00 496 | (23) control (/3): 0x00 497 | ``` 498 | -------------------------------------------------------------------------------- /notes/20230621.mkdn: -------------------------------------------------------------------------------- 1 | # Redux! 2 | 3 | I have built more tools now. Let's use them. 4 | 5 | List of futures: 6 | 7 | ``` 8 | >> var lilos::exec::TASK_FUTURES 9 | lilos::exec::TASK_FUTURES @ <.debug_info+0x00015c32> 10 | - type: core::option::Option<*mut [core::pin::Pin<*mut dyn core::future::future::Future>]> <.debug_info+0x000171b2> 11 | - address: 0x20000600 12 | 13 | >> decode core::option::Option<*mut [core::pin::Pin<*mut dyn core::future::future::Future>]> 14 | Paste hex-encoded memory blob. Whitespace OK. 15 | Address prefix ending in colon will be removed. 16 | Enter a blank line to end. 17 | 0x20000600: 01 00 00 00 d4 1f 00 20 02 00 00 00 18 | 19 | core::option::Option<*mut [core::pin::Pin<*mut dyn core::future::future::Future>]> <.debug_info+0x000171b2>: 20 | core::option::Option<*mut [core::pin::Pin<*mut dyn core::future::future::Future>]>::Some(*mut [core::pin::Pin<*mut dyn core::future::future::Future>] { 21 | data_ptr: 0x20001fd4 as *_ core::pin::Pin<*mut dyn core::future::future::Future>, 22 | length: 2_u32, 23 | }) 24 | ``` 25 | 26 | Gross! Cleaned up a bit and we get: 27 | 28 | ``` 29 | Option<*mut [Pin<*mut dyn Future>]>::Some(*mut [Pin<*mut dyn Future>] { 30 | data_ptr: 0x20001fd4 as *_ Pin<*mut dyn Future>, 31 | length: 2_u32, 32 | }) 33 | ``` 34 | 35 | So we need to decode a 2-array of those pin thingies at 0x20001fd4. 36 | Unfortunately tysh can't look up array types. We'll have to do each element 37 | manually. (I'm now cleaning up the long qualified type names in the output 38 | manually each time.) 39 | 40 | ``` 41 | >> decode core::pin::Pin<*mut dyn core::future::future::Future> 42 | Paste hex-encoded memory blob. Whitespace OK. 43 | Address prefix ending in colon will be removed. 44 | Enter a blank line to end. 45 | 0x20001fd4: a0 1d 00 20 40 3c 00 08 46 | 47 | core::pin::Pin<*mut dyn core::future::future::Future> <.debug_info+0x00001fef>: 48 | Pin<*mut dyn Future> { 49 | pointer: 0x20001da0 as &mut keybad_fw::serial::task::{async_fn_env#0} as &mut dyn Future, 50 | } 51 | 52 | >> decode core::pin::Pin<*mut dyn core::future::future::Future> 53 | Paste hex-encoded memory blob. Whitespace OK. 54 | Address prefix ending in colon will be removed. 55 | Enter a blank line to end. 56 | 0x20001fdc: 40 1f 00 20 50 3c 00 08 57 | 58 | core::pin::Pin<*mut dyn core::future::future::Future> <.debug_info+0x00001fef>: 59 | Pin<*mut dyn Future> { 60 | pointer: 0x20001f40 as &mut keybad_fw::scanner::task::{async_fn_env#0} as &mut dyn Future, 61 | } 62 | ``` 63 | 64 | Let's print the first one. 65 | 66 | ``` 67 | >> sizeof keybad_fw::serial::task::{async_fn_env#0} 68 | keybad_fw::serial::task::{async_fn_env#0} <.debug_info+0x0000732f>: 416 bytes 69 | 70 | >> decode keybad_fw::serial::task::{async_fn_env#0} 71 | Paste hex-encoded memory blob. Whitespace OK. 72 | Address prefix ending in colon will be removed. 73 | Enter a blank line to end. 74 | 0x20001da0: 00 70 00 08 00 78 00 08 e4 1f 00 20 28 06 00 20 7c 1d 00 20 e4 1f 00 20 e4 1f 00 20 28 06 00 20 75 | 0x20001dc0: 7c 1d 00 20 94 1d 00 20 b7 ef 14 04 00 05 fe 7f b0 1d 00 20 90 1d 00 20 b0 1d 00 20 03 5e 99 03 76 | 0x20001de0: c7 25 18 5a 14 c8 84 f6 89 4e 39 1b c0 4d e8 de de b1 02 11 28 36 77 9a e1 e1 40 69 96 50 9a db 77 | 0x20001e00: d4 e1 20 23 11 c9 39 51 d7 9d 8c b2 3d 81 0d f4 47 0f 4c 11 b0 98 9f 8b ea d9 33 87 06 12 25 7d 78 | 0x20001e20: 96 7f 61 07 a7 62 d3 18 b5 c2 3f c1 e8 1f 21 7f df d4 c4 0d 0b 8c 8c 1b bb 6a a6 af 1e 1e bf ff 79 | 0x20001e40: fc 9c 08 aa 4b 59 87 33 94 f3 03 00 53 8c b8 e2 9a c7 80 64 58 70 99 e5 0c 2c 10 70 3b 22 04 32 80 | 0x20001e60: ff f9 30 4c 9e 72 f3 7f fc f6 9e 1a 03 e6 bf 9c d1 93 97 e5 bb f0 b7 bf f9 9f 52 5d e5 4e 8d 47 81 | 0x20001e80: fb 7e 57 e1 d9 f5 f7 3e 71 da 5c 6d 43 01 fb ce 3a d3 93 ef f2 63 ed 91 df bd a5 9f 2a 3a 11 ee 82 | 0x20001ea0: 84 4a 80 24 d8 a1 5d a3 ef f8 24 8b 1f 05 8b 54 f0 7a 95 41 dd 4c 48 3e 75 d4 3c 10 ca 06 e8 6d 83 | 0x20001ec0: e9 f7 07 c2 58 98 fd cd c7 7f 2f 36 3b 93 53 50 a6 45 b0 21 ea 29 3f 93 76 9b 50 0e f7 4a cb b6 84 | 0x20001ee0: ab da a1 42 c7 0f 4d 17 fc d4 86 54 0a 02 07 fd 33 47 82 08 44 34 94 c6 da 5f 03 c1 03 42 36 bc 85 | 0x20001f00: 9d e9 60 16 81 3a 3c 15 29 77 40 2c a0 cc 9e d5 74 9a 6c 62 2e 41 af d2 77 e4 84 21 3e 00 e5 5b 86 | 0x20001f20: fc ff 1a 07 01 de f7 fd f9 bf d1 0a 71 62 dd c9 34 fb fa 56 65 4d 33 b4 f5 ae 41 36 b7 a2 7f 7d 87 | 88 | keybad_fw::serial::task::{async_fn_env#0} <.debug_info+0x0000732f>: 89 | use keybad_fw::flash::Storage as Storage; 90 | use lilos::NotSyncMarker as NotSyncMarker; 91 | use stm32g0::stm32g030::FLASH as FLASH; 92 | keybad_fw::serial::task::{async_fn_env#0}::4 { 93 | uart: 0x20001fe4 as &stm32g0::stm32g030::USART1, 94 | keymap: 0x20000628 as &[[u8; 8]; 8], 95 | from_scanner: lilos::spsc::Pop { 96 | q: 0x20001d7c as &lilos::spsc::Queue, 97 | _marker: NotSyncMarker(core::marker::PhantomData>), 98 | }, 99 | __awaitee: lilos::spsc::{impl#4}::pop::{async_fn_env#0}::3 { 100 | __awaitee: futures_util::future::poll_fn::PollFn, lilos::spsc::{impl#4}::pop::{async_fn#0}::{closure_env#0}>> { 101 | f: lilos::exec::{impl#1}::until::{closure_env#0}, lilos::spsc::{impl#4}::pop::{async_fn#0}::{closure_env#0}> { 102 | cond: lilos::spsc::{impl#4}::pop::{async_fn#0}::{closure_env#0} { 103 | self: 0x20001db0 as &mut lilos::spsc::Pop, 104 | }, 105 | self: 0x20001d90 as &lilos::exec::Notify, 106 | }, 107 | }, 108 | self: 0x20001db0 as &mut lilos::spsc::Pop, 109 | }, 110 | uart: 0x20001fe4 as &stm32g0::stm32g030::USART1, 111 | gpioa: 0x20001fe4 as &stm32g0::stm32g030::GPIOA, 112 | keymap: 0x20000628 as &[[u8; 8]; 8], 113 | setup_mode: false, 114 | from_scanner: lilos::spsc::Pop { 115 | q: 0x20001d7c as &lilos::spsc::Queue, 116 | _marker: NotSyncMarker(core::marker::PhantomData>), 117 | }, 118 | config_to_scanner: lilos::handoff::Push(0x20001d94 as &lilos::handoff::Handoff), 119 | storage: Storage { 120 | flash: FLASH { 121 | _marker: core::marker::PhantomData<*const ()>, 122 | }, 123 | pages: [ 124 | 0x8007000 as *mut [u64; 256], 125 | 0x8007800 as *mut [u64; 256], 126 | ], 127 | }, 128 | } 129 | ``` 130 | 131 | And with some new heuristics: 132 | 133 | ``` 134 | >> decode-async keybad_fw::serial::task::{async_fn_env#0} 135 | Paste hex-encoded memory blob. Whitespace OK. 136 | Address prefix ending in colon will be removed. 137 | Enter a blank line to end. 138 | 139 | (giant blob omitted) 140 | 141 | keybad_fw::serial::task::{async_fn_env#0} <.debug_info+0x0000732f>: 142 | async fn keybad_fw::serial::task 143 | state 4: keybad_fw::serial::task::{async_fn_env#0}::Suspend1 144 | waiting on: async fn lilos::spsc::{impl#4}::pop 145 | state 3: lilos::spsc::{impl#4}::pop::{async_fn_env#0}::Suspend0 146 | waiting on: futures_util::future::poll_fn::PollFn, lilos::spsc::{impl#4}::pop::{async_fn#0}::{closure_env#0}>> (not an async fn) 147 | ``` 148 | -------------------------------------------------------------------------------- /src/bin/lildb.rs: -------------------------------------------------------------------------------- 1 | // I intend to switch these back to warnings once things are cleaner. 2 | #![allow(clippy::type_complexity)] 3 | #![allow(clippy::len_zero)] 4 | 5 | // This one I may leave on -- I often use one-variant matches when I expect 6 | // extension in the future. 7 | #![allow(clippy::single_match)] 8 | 9 | use std::cmp; 10 | use std::collections::BTreeMap; 11 | use std::io::Read; 12 | use std::sync::atomic::Ordering; 13 | use std::{fmt::Display, io::BufRead}; 14 | 15 | use anyhow::{Result, bail}; 16 | use clap::Parser; 17 | use debugdb::{EntityId, VarId}; 18 | use debugdb::value::{ValueWithDb, self}; 19 | use object::{Object, ObjectSegment}; 20 | use rangemap::{RangeMap, RangeInclusiveMap}; 21 | 22 | use debugdb::{Type, Encoding, TypeId, Struct, Member, DebugDb, Enum, VariantShape, value::Value}; 23 | use debugdb::load::{Load, ImgMachine, Machine, LoadError}; 24 | use regex::Regex; 25 | use ansi_term::Colour; 26 | 27 | static ASYNC_FN_BLOCK_ENV: &str = 28 | r#"^(?.*)::\{async_(fn|block)_env#(?[0-9]+)\}(?<.*)?$"#; 29 | 30 | #[derive(Debug, Parser)] 31 | struct Lildb { 32 | filename: std::path::PathBuf, 33 | } 34 | 35 | fn main() -> Result<()> { 36 | let args = Lildb::parse(); 37 | 38 | let everything; 39 | let mut segments = RangeInclusiveMap::new(); 40 | let mut registers = BTreeMap::new(); 41 | 42 | let input = std::fs::File::open(&args.filename)?; 43 | 44 | // First, try loading as a snapshot. 45 | if let Ok(mut snap) = lilosdbg::load_snapshot(input) { 46 | let elf_files = snap.elf_files().collect::>(); 47 | if elf_files.len() == 0 { 48 | bail!("snapshot does not contain ELF file."); 49 | } else if elf_files.len() > 1 { 50 | bail!("snapshot contains too many ELF files."); 51 | } 52 | 53 | let mut image = vec![]; 54 | snap.file_by_index(elf_files[0].0).read_to_end(&mut image)?; 55 | let object = object::File::parse(&*image)?; 56 | for seg in object.segments() { 57 | if seg.size() == 0 { 58 | continue; 59 | } 60 | segments.insert( 61 | seg.address()..=seg.address() + (seg.size() - 1), 62 | seg.data()?.to_vec(), 63 | ); 64 | } 65 | everything = debugdb::parse_file(&object)?; 66 | 67 | // TODO ugh, zip wants &mut to do reads, making it damn hard to iterate 68 | // over the snapshot... this crate might not be the right crate 69 | let range_copy = snap.ranges().map(|(r, f)| (r, f.index)).collect::>(); 70 | for (addrs, index) in range_copy { 71 | let mut image = vec![]; 72 | snap.file_by_index(index).read_to_end(&mut image)?; 73 | segments.insert(addrs, image); 74 | } 75 | 76 | for (i, v) in snap.registers() { 77 | registers.insert(i, v); 78 | } 79 | } else { 80 | let buffer = std::fs::read(args.filename)?; 81 | let object = object::File::parse(&*buffer)?; 82 | for seg in object.segments() { 83 | if seg.size() == 0 { 84 | continue; 85 | } 86 | segments.insert( 87 | seg.address()..=seg.address() + (seg.size() - 1), 88 | seg.data()?.to_vec(), 89 | ); 90 | } 91 | everything = debugdb::parse_file(&object)?; 92 | } 93 | 94 | println!("Loaded; {} types found in program.", everything.type_count()); 95 | println!("To quit: ^D or exit"); 96 | 97 | let mut rl = rustyline::Editor::<(), _>::new()?; 98 | let prompt = ansi_term::Colour::Green.paint(">> ").to_string(); 99 | let mut ctx = Ctx { 100 | segments, 101 | registers, 102 | }; 103 | 'lineloop: 104 | loop { 105 | match rl.readline(&prompt) { 106 | Ok(line) => { 107 | let line = line.trim(); 108 | let (cmd, rest) = line.split_once(char::is_whitespace) 109 | .unwrap_or((line, "")); 110 | if line.is_empty() { 111 | continue 'lineloop; 112 | } 113 | 114 | rl.add_history_entry(line)?; 115 | 116 | match cmd { 117 | "exit" => break, 118 | "help" => { 119 | println!("commands:"); 120 | let name_len = COMMANDS.iter() 121 | .map(|(name, _, _)| name.len()) 122 | .max() 123 | .unwrap_or(12); 124 | for (name, _, desc) in COMMANDS { 125 | println!("{:name_len$} {}", name, desc); 126 | } 127 | } 128 | _ => { 129 | for (name, imp, _) in COMMANDS { 130 | if *name == cmd { 131 | imp(&everything, &mut ctx, rest); 132 | continue 'lineloop; 133 | } 134 | } 135 | println!("unknown command: {}", cmd); 136 | println!("for help, try: help"); 137 | } 138 | } 139 | } 140 | Err(rustyline::error::ReadlineError::Interrupted) => { 141 | println!("^C"); 142 | continue; 143 | } 144 | Err(rustyline::error::ReadlineError::Eof) => { 145 | break; 146 | } 147 | Err(e) => { 148 | println!("{:?}", e); 149 | break; 150 | } 151 | } 152 | } 153 | println!("exiting debugger."); 154 | 155 | Ok(()) 156 | } 157 | 158 | struct Goff(gimli::UnitSectionOffset); 159 | 160 | impl std::fmt::Display for Goff { 161 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 162 | match self.0 { 163 | gimli::UnitSectionOffset::DebugInfoOffset(gimli::DebugInfoOffset(x)) => { 164 | write!(f, "<.debug_info+0x{:08x}>", x) 165 | } 166 | gimli::UnitSectionOffset::DebugTypesOffset(gimli::DebugTypesOffset(x)) => { 167 | write!(f, "<.debug_types+0x{:08x}>", x) 168 | } 169 | } 170 | } 171 | } 172 | 173 | struct NamedGoff<'a>(&'a debugdb::DebugDb, TypeId); 174 | 175 | impl std::fmt::Display for NamedGoff<'_> { 176 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 177 | let bold = ansi_term::Style::new().bold(); 178 | let dim = ansi_term::Style::new().dimmed(); 179 | 180 | let n = if let Some(name) = self.0.type_name(self.1) { 181 | name 182 | } else { 183 | "".into() 184 | }; 185 | 186 | write!(f, "{}", bold.paint(n))?; 187 | match self.1.0 { 188 | gimli::UnitSectionOffset::DebugInfoOffset(gimli::DebugInfoOffset(x)) => { 189 | write!(f, " {}<.debug_info+0x{:08x}>{}", dim.prefix(), x, dim.suffix()) 190 | } 191 | gimli::UnitSectionOffset::DebugTypesOffset(gimli::DebugTypesOffset(x)) => { 192 | write!(f, " {}<.debug_types+0x{:08x}>{}", dim.prefix(), x, dim.suffix()) 193 | } 194 | } 195 | } 196 | } 197 | 198 | struct Ctx { 199 | segments: RangeInclusiveMap>, 200 | registers: BTreeMap, 201 | } 202 | 203 | impl Ctx { 204 | pub fn program_counter(&self) -> Option { 205 | // TODO very ARM-specific! 206 | self.registers.get(&15).copied() 207 | } 208 | pub fn register(&self, index: u16) -> Option { 209 | self.registers.get(&index).copied() 210 | } 211 | } 212 | 213 | type Command = fn(&debugdb::DebugDb, &mut Ctx, &str); 214 | 215 | static COMMANDS: &[(&str, Command, &str)] = &[ 216 | ("list", cmd_list, "print names of ALL types, or types containing a string"), 217 | ("info", cmd_info, "print a summary of a type"), 218 | ("load", cmd_load, "loads additional segment data"), 219 | ("def", cmd_def, "print a type as a pseudo-Rust definition"), 220 | ("sizeof", cmd_sizeof, "print size of type in bytes"), 221 | ("alignof", cmd_alignof, "print alignment of type in bytes"), 222 | ("addr", cmd_addr, "look up information about an address"), 223 | ("addr2line", cmd_addr2line, "look up line number information"), 224 | ("addr2stack", cmd_addr2stack, "display inlined stack frames"), 225 | ("vars", cmd_vars, "list static variables"), 226 | ("var", cmd_var, "get info on a static variable"), 227 | ("unwind", cmd_unwind, "get unwind info for an address"), 228 | ("decode", cmd_decode, "interpret RAM/ROM as a type"), 229 | ("decode-async", cmd_decode_async, "interpret RAM/ROM as a suspended future"), 230 | ("decode-blob", cmd_decode_blob, "attempt to interpret bytes as a type"), 231 | ("decode-async-blob", cmd_decode_async_blob, "attempt to interpret bytes as a suspended future"), 232 | 233 | ("tasks", cmd_tasks, "print lilos task status"), 234 | ("time", cmd_time, "print current lilos tick time"), 235 | ("stack", cmd_stack, "print information about stack usage"), 236 | ("stacktrace", cmd_stacktrace, "experimental backtrace support"), 237 | ("reg", cmd_reg, "register access"), 238 | ]; 239 | 240 | fn cmd_list( 241 | db: &debugdb::DebugDb, 242 | _ctx: &mut Ctx, 243 | args: &str, 244 | ) { 245 | // We're gonna make a copy to sort it, because alphabetical order seems 246 | // polite. 247 | let mut types_copy = db.canonical_types() 248 | .filter(|(goff, _ty)| { 249 | if !args.is_empty() { 250 | if let Some(name) = db.type_name(*goff) { 251 | return name.contains(args); 252 | } else { 253 | return false; 254 | } 255 | } 256 | true 257 | }) 258 | .collect::>(); 259 | 260 | types_copy.sort_by_key(|(goff, _ty)| db.type_name(*goff)); 261 | 262 | for (goff, ty) in types_copy { 263 | let kind = match ty { 264 | Type::Base(_) => "base", 265 | Type::Struct(_) => "struct", 266 | Type::Enum(_) => "enum", 267 | Type::CEnum(_) => "c-enum", 268 | Type::Array(_) => "array", 269 | Type::Pointer(_) => "ptr", 270 | Type::Union(_) => "union", 271 | Type::Subroutine(_) => "subr", 272 | Type::Unresolved(_) => "missing", 273 | }; 274 | 275 | let aliases = db.aliases_of_type(goff); 276 | if let Some(aliases) = aliases { 277 | println!("{:6} {} ({} aliases)", kind, NamedGoff(db, goff), aliases.len()); 278 | } else { 279 | println!("{:6} {}", kind, NamedGoff(db, goff)); 280 | } 281 | } 282 | } 283 | 284 | fn parse_type_name(s: &str) -> Option> { 285 | if s.starts_with("<.debug_") && s.ends_with('>') { 286 | // Try parsing as a debug section reference. 287 | let rest = &s[8..]; 288 | return if rest.starts_with("info+0x") { 289 | let num = &rest[7..rest.len() - 1]; 290 | if let Ok(n) = usize::from_str_radix(num, 16) { 291 | Some(ParsedTypeName::Goff(TypeId(gimli::DebugInfoOffset(n).into()))) 292 | } else { 293 | println!("can't parse {} as hex", num); 294 | None 295 | } 296 | } else if rest.starts_with("types+0x") { 297 | let num = &rest[8..rest.len() - 1]; 298 | if let Ok(n) = usize::from_str_radix(num, 16) { 299 | Some(ParsedTypeName::Goff(TypeId(gimli::DebugTypesOffset(n).into()))) 300 | } else { 301 | println!("can't parse {} as hex", num); 302 | None 303 | } 304 | } else { 305 | println!("bad offset reference: {}", s); 306 | None 307 | }; 308 | } 309 | 310 | Some(ParsedTypeName::Name(s)) 311 | } 312 | 313 | enum ParsedTypeName<'a> { 314 | Name(&'a str), 315 | Goff(TypeId), 316 | } 317 | 318 | fn simple_query_cmd( 319 | db: &debugdb::DebugDb, 320 | args: &str, 321 | q: fn(&debugdb::DebugDb, &debugdb::Type), 322 | ) { 323 | let type_name = args.trim(); 324 | let types: Vec<_> = match parse_type_name(type_name) { 325 | None => return, 326 | Some(ParsedTypeName::Name(n)) => { 327 | db.types_by_name(n).collect() 328 | } 329 | Some(ParsedTypeName::Goff(o)) => { 330 | db.type_by_id(o).into_iter() 331 | .map(|t| (o, t)) 332 | .collect() 333 | } 334 | }; 335 | if type_name.starts_with("<.debug_") && type_name.ends_with('>') { 336 | // Try parsing as a debug section reference. 337 | let rest = &type_name[8..]; 338 | if rest.starts_with("info+0x") { 339 | // TODO what was I doing here 340 | } else if rest.starts_with("types+0x") { 341 | // TODO seriously though 342 | } 343 | } 344 | 345 | let many = match types.len() { 346 | 0 => { 347 | println!("{}", ansi_term::Colour::Red.paint("No types found.")); 348 | return; 349 | } 350 | 1 => false, 351 | n => { 352 | println!("{}{} types found with that name:", 353 | ansi_term::Color::Yellow.paint("note: "), 354 | n, 355 | ); 356 | true 357 | } 358 | }; 359 | 360 | for (goff, t) in types { 361 | if many { println!() } 362 | print!("{}: ", NamedGoff(db, goff)); 363 | q(db, t); 364 | } 365 | } 366 | 367 | fn cmd_info(db: &debugdb::DebugDb, _ctx: &mut Ctx, args: &str) { 368 | simple_query_cmd(db, args, |db, t| { 369 | match t { 370 | Type::Base(s) => { 371 | println!("base type"); 372 | println!("- encoding: {:?}", s.encoding); 373 | println!("- byte size: {}", s.byte_size); 374 | } 375 | Type::Pointer(s) => { 376 | println!("pointer type"); 377 | println!("- points to: {}", NamedGoff(db, s.type_id)); 378 | } 379 | Type::Array(s) => { 380 | println!("array type"); 381 | println!("- element type: {}", NamedGoff(db, s.element_type_id)); 382 | println!("- lower bound: {}", s.lower_bound); 383 | if let Some(n) = s.count { 384 | println!("- count: {}", n); 385 | } else { 386 | println!("- size not given"); 387 | } 388 | } 389 | Type::Struct(s) => { 390 | if s.tuple_like { 391 | println!("struct type (tuple-like)"); 392 | } else { 393 | println!("struct type"); 394 | } 395 | if let Some(z) = s.byte_size { 396 | println!("- byte size: {z}"); 397 | } 398 | if let Some(a) = s.alignment { 399 | println!("- alignment: {}", a); 400 | } else { 401 | println!("- not aligned"); 402 | } 403 | if !s.template_type_parameters.is_empty() { 404 | println!("- template type parameters:"); 405 | for ttp in &s.template_type_parameters { 406 | println!(" - {} = {}", ttp.name, NamedGoff(db, ttp.type_id)); 407 | } 408 | } 409 | if !s.members.is_empty() { 410 | println!("- members:"); 411 | for (i, mem) in s.members.iter().enumerate() { 412 | if let Some(name) = &mem.name { 413 | println!(" {i}. {name}: {}", NamedGoff(db, mem.type_id)); 414 | } else { 415 | println!(" - : {}", NamedGoff(db, mem.type_id)); 416 | } 417 | println!(" - offset: {} bytes", mem.location); 418 | if let Some(s) = db.type_by_id(mem.type_id).unwrap().byte_size(db) { 419 | println!(" - size: {} bytes", s); 420 | } 421 | if let Some(a) = mem.alignment { 422 | println!(" - aligned: {} bytes", a); 423 | } 424 | if mem.artificial { 425 | println!(" - artificial"); 426 | } 427 | } 428 | } else { 429 | println!("- no members"); 430 | } 431 | 432 | struct_picture(db, s, db.pointer_size()); 433 | } 434 | Type::Enum(s) => { 435 | println!("enum type"); 436 | if let Some(z) = s.byte_size { 437 | println!("- byte size: {z}"); 438 | } 439 | if let Some(a) = s.alignment { 440 | println!("- alignment: {}", a); 441 | } else { 442 | println!("- not aligned"); 443 | } 444 | if !s.template_type_parameters.is_empty() { 445 | println!("- type parameters:"); 446 | for ttp in &s.template_type_parameters { 447 | println!(" - {} = {}", ttp.name, NamedGoff(db, ttp.type_id)); 448 | } 449 | } 450 | 451 | match &s.shape { 452 | debugdb::VariantShape::Zero => { 453 | println!("- empty (uninhabited) enum"); 454 | } 455 | debugdb::VariantShape::One(v) => { 456 | println!("- single variant enum w/o discriminator"); 457 | println!(" - content type: {}", NamedGoff(db, v.member.type_id)); 458 | println!(" - offset: {} bytes", v.member.location); 459 | if let Some(a) = v.member.alignment { 460 | println!(" - aligned: {} bytes", a); 461 | } 462 | if !v.member.artificial { 463 | println!(" - not artificial, oddly"); 464 | } 465 | } 466 | debugdb::VariantShape::Many { member, variants, .. }=> { 467 | if let Some(dname) = db.type_name(member.type_id) { 468 | println!("- {} variants discriminated by {} at offset {}", variants.len(), dname, member.location); 469 | } else { 470 | println!("- {} variants discriminated by an anonymous type at offset {}", variants.len(), member.location); 471 | } 472 | if !member.artificial { 473 | println!(" - not artificial, oddly"); 474 | } 475 | 476 | // Print explicit values first 477 | for (val, var) in variants { 478 | if let Some(val) = val { 479 | println!("- when discriminator == {}", val); 480 | println!(" - contains type: {}", NamedGoff(db, var.member.type_id)); 481 | println!(" - at offset: {} bytes", var.member.location); 482 | if let Some(a) = var.member.alignment { 483 | println!(" - aligned: {} bytes", a); 484 | } 485 | } 486 | } 487 | // Now, default. 488 | for (val, var) in variants { 489 | if val.is_none() { 490 | println!("- any other discriminator value"); 491 | println!(" - contains type: {}", NamedGoff(db, var.member.type_id)); 492 | println!(" - at offset: {} bytes", var.member.location); 493 | if let Some(a) = var.member.alignment { 494 | println!(" - aligned: {} bytes", a); 495 | } 496 | } 497 | } 498 | } 499 | } 500 | enum_picture(db, s, db.pointer_size()); 501 | } 502 | Type::CEnum(s) => { 503 | println!("C-like enum type"); 504 | println!("- byte size: {}", s.byte_size); 505 | if let Some(a) = s.alignment { 506 | println!("- alignment: {a}"); 507 | } 508 | println!("- {} values defined", s.enumerators.len()); 509 | for e in s.enumerators.values() { 510 | println!(" - {} = 0x{:x}", e.name, e.const_value); 511 | 512 | } 513 | } 514 | Type::Union(s) => { 515 | println!("union type"); 516 | println!("- byte size: {}", s.byte_size); 517 | println!("- alignment: {}", s.alignment); 518 | if !s.template_type_parameters.is_empty() { 519 | println!("- template type parameters:"); 520 | for ttp in &s.template_type_parameters { 521 | println!(" - {} = {}", ttp.name, NamedGoff(db, ttp.type_id)); 522 | } 523 | } 524 | if !s.members.is_empty() { 525 | println!("- members:"); 526 | for mem in &s.members { 527 | if let Some(name) = &mem.name { 528 | println!(" - {}: {}", name, NamedGoff(db, mem.type_id)); 529 | } else { 530 | println!(" - : {}", NamedGoff(db, mem.type_id)); 531 | } 532 | println!(" - offset: {} bytes", mem.location); 533 | if let Some(a) = mem.alignment { 534 | println!(" - aligned: {} bytes", a); 535 | } 536 | if mem.artificial { 537 | println!(" - artificial"); 538 | } 539 | } 540 | } else { 541 | println!("- no members"); 542 | } 543 | } 544 | Type::Subroutine(s) => { 545 | println!("subroutine type"); 546 | if let Some(rt) = s.return_type_id { 547 | println!("- return type: {}", NamedGoff(db, rt)); 548 | } 549 | if !s.formal_parameters.is_empty() { 550 | println!("- formal parameters:"); 551 | for &fp in &s.formal_parameters { 552 | println!(" - {}", NamedGoff(db, fp)); 553 | } 554 | } 555 | } 556 | Type::Unresolved(_) => { 557 | println!("type not found in debug info!"); 558 | } 559 | } 560 | }) 561 | } 562 | 563 | fn cmd_sizeof(db: &debugdb::DebugDb, _ctx: &mut Ctx, args: &str) { 564 | simple_query_cmd(db, args, |db, t| { 565 | if let Some(sz) = t.byte_size(db) { 566 | println!("{} bytes", sz); 567 | } else { 568 | println!("unsized"); 569 | } 570 | }) 571 | } 572 | 573 | fn cmd_alignof(db: &debugdb::DebugDb, _ctx: &mut Ctx, args: &str) { 574 | simple_query_cmd(db, args, |db, t| { 575 | if let Some(sz) = t.alignment(db) { 576 | println!("align to {} bytes", sz); 577 | } else { 578 | println!("no alignment information"); 579 | } 580 | }) 581 | } 582 | 583 | fn cmd_def(db: &debugdb::DebugDb, _ctx: &mut Ctx, args: &str) { 584 | simple_query_cmd(db, args, |db, t| { 585 | println!(); 586 | match t { 587 | Type::Base(s) => { 588 | print!("type _ = "); 589 | match (s.encoding, s.byte_size) { 590 | (_, 0) => print!("()"), 591 | (Encoding::Unsigned, 1) => print!("u8"), 592 | (Encoding::Unsigned, 2) => print!("u16"), 593 | (Encoding::Unsigned, 4) => print!("u32"), 594 | (Encoding::Unsigned, 8) => print!("u64"), 595 | (Encoding::Unsigned, 16) => print!("u128"), 596 | (Encoding::Signed, 1) => print!("i8"), 597 | (Encoding::Signed, 2) => print!("i16"), 598 | (Encoding::Signed, 4) => print!("i32"), 599 | (Encoding::Signed, 8) => print!("i64"), 600 | (Encoding::Signed, 16) => print!("i128"), 601 | (Encoding::Float, 4) => print!("f32"), 602 | (Encoding::Float, 8) => print!("f64"), 603 | (Encoding::Boolean, 1) => print!("bool"), 604 | (Encoding::UnsignedChar, 1) => print!("c_uchar"), 605 | (Encoding::SignedChar, 1) => print!("c_schar"), 606 | (Encoding::UtfChar, 4) => print!("char"), 607 | 608 | (e, s) => print!("Unhandled{:?}{}", e, s), 609 | } 610 | println!(";"); 611 | } 612 | Type::Pointer(_s) => { 613 | print!("type _ = {};", t.name(db)); 614 | } 615 | Type::Array(s) => { 616 | let name = db.type_name(s.element_type_id).unwrap(); 617 | if let Some(n) = s.count { 618 | println!("[{}; {}]", name, n); 619 | } else { 620 | println!("[{}]", name); 621 | } 622 | } 623 | Type::Struct(s) => { 624 | print!("struct {}", s.name); 625 | 626 | if !s.template_type_parameters.is_empty() { 627 | print!("<"); 628 | for ttp in &s.template_type_parameters { 629 | print!("{},", ttp.name); 630 | } 631 | print!(">"); 632 | } 633 | 634 | if s.members.is_empty() { 635 | println!(";"); 636 | } else if s.tuple_like { 637 | println!("("); 638 | for mem in &s.members { 639 | println!(" {},", db.type_name(mem.type_id).unwrap()); 640 | } 641 | println!(");"); 642 | } else { 643 | println!(" {{"); 644 | for mem in &s.members { 645 | if let Some(name) = &mem.name { 646 | println!(" {}: {},", name, db.type_name(mem.type_id).unwrap()); 647 | } else { 648 | println!(" ANON: {},", db.type_name(mem.type_id).unwrap()); 649 | } 650 | } 651 | println!("}}"); 652 | } 653 | } 654 | Type::Enum(s) => { 655 | print!("enum {}", s.name); 656 | if !s.template_type_parameters.is_empty() { 657 | print!("<"); 658 | for ttp in &s.template_type_parameters { 659 | print!("{}", ttp.name); 660 | } 661 | print!(">"); 662 | } 663 | println!(" {{"); 664 | 665 | match &s.shape { 666 | debugdb::VariantShape::Zero => (), 667 | debugdb::VariantShape::One(var) => { 668 | if let Some(name) = &var.member.name { 669 | print!(" {}", name); 670 | } else { 671 | print!(" ANON"); 672 | } 673 | 674 | let mty = db.type_by_id(var.member.type_id) 675 | .unwrap(); 676 | if let Type::Struct(s) = mty { 677 | if !s.members.is_empty() { 678 | if s.tuple_like { 679 | println!("("); 680 | for mem in &s.members { 681 | let mtn = db.type_name(mem.type_id).unwrap(); 682 | println!(" {},", mtn); 683 | } 684 | print!(" )"); 685 | } else { 686 | println!(" {{"); 687 | for mem in &s.members { 688 | let mtn = db.type_name(mem.type_id).unwrap(); 689 | println!(" {}: {},", mem.name.as_ref().unwrap(), mtn); 690 | } 691 | print!(" }}"); 692 | } 693 | } 694 | } else { 695 | print!("(unexpected weirdness)"); 696 | } 697 | 698 | println!(","); 699 | } 700 | debugdb::VariantShape::Many { variants, .. }=> { 701 | for var in variants.values() { 702 | if let Some(name) = &var.member.name { 703 | print!(" {}", name); 704 | } else { 705 | print!(" ANON"); 706 | } 707 | 708 | let mty = db.type_by_id(var.member.type_id) 709 | .unwrap(); 710 | if let Type::Struct(s) = mty { 711 | if !s.members.is_empty() { 712 | if s.tuple_like { 713 | println!("("); 714 | for mem in &s.members { 715 | let mtn = db.type_name(mem.type_id).unwrap(); 716 | println!(" {},", mtn); 717 | } 718 | print!(" )"); 719 | } else { 720 | println!(" {{"); 721 | for mem in &s.members { 722 | let mtn = db.type_name(mem.type_id).unwrap(); 723 | println!(" {}: {},", mem.name.as_ref().unwrap(), mtn); 724 | } 725 | print!(" }}"); 726 | } 727 | } 728 | } else { 729 | print!("(unexpected weirdness)"); 730 | } 731 | 732 | println!(","); 733 | } 734 | } 735 | } 736 | println!("}}"); 737 | 738 | } 739 | Type::CEnum(s) => { 740 | println!("enum {} {{", s.name); 741 | for (val, e) in &s.enumerators { 742 | println!(" {} = 0x{:x},", e.name, val); 743 | } 744 | println!("}}"); 745 | } 746 | Type::Union(s) => { 747 | print!("union {}", s.name); 748 | 749 | if !s.template_type_parameters.is_empty() { 750 | print!("<"); 751 | for ttp in &s.template_type_parameters { 752 | print!("{},", ttp.name); 753 | } 754 | print!(">"); 755 | } 756 | 757 | println!(" {{"); 758 | for mem in &s.members { 759 | if let Some(name) = &mem.name { 760 | println!(" {}: {},", name, db.type_name(mem.type_id).unwrap()); 761 | } else { 762 | println!(" ANON: {},", db.type_name(mem.type_id).unwrap()); 763 | } 764 | } 765 | println!("}}"); 766 | } 767 | Type::Subroutine(s) => { 768 | println!("fn("); 769 | for &p in &s.formal_parameters { 770 | println!(" {},", db.type_name(p).unwrap()); 771 | } 772 | if let Some(rt) = s.return_type_id { 773 | println!(") -> {} {{", db.type_name(rt).unwrap()); 774 | } else { 775 | println!(") {{"); 776 | } 777 | println!(" // code goes here"); 778 | println!(" // (this is a subroutine type, _not_ a fn ptr)"); 779 | println!(" unimplemented!();"); 780 | println!("}}"); 781 | } 782 | Type::Unresolved(_) => { 783 | println!("(type not found in debug info!)"); 784 | } 785 | } 786 | }) 787 | } 788 | 789 | fn cmd_addr2line(db: &debugdb::DebugDb, _ctx: &mut Ctx, args: &str) { 790 | let addr = if let Some(rest) = args.strip_prefix("0x") { 791 | if let Ok(a) = u64::from_str_radix(rest, 16) { 792 | a 793 | } else { 794 | println!("can't parse {} as an address", args); 795 | return; 796 | } 797 | } else if let Ok(a) = args.parse::() { 798 | a 799 | } else { 800 | println!("can't parse {} as an address", args); 801 | return; 802 | }; 803 | 804 | if let Some(row) = db.lookup_line_row(addr) { 805 | print!("{}:", row.file); 806 | if let Some(line) = row.line { 807 | print!("{}:", line); 808 | } else { 809 | print!("?:"); 810 | } 811 | if let Some(col) = row.column { 812 | print!("{}", col); 813 | } else { 814 | print!("?"); 815 | } 816 | println!(); 817 | } else { 818 | println!("no line number information available for address"); 819 | } 820 | } 821 | 822 | fn cmd_addr2stack(db: &debugdb::DebugDb, _ctx: &mut Ctx, args: &str) { 823 | let addr = if let Some(rest) = args.strip_prefix("0x") { 824 | if let Ok(a) = u64::from_str_radix(rest, 16) { 825 | a 826 | } else { 827 | println!("can't parse {} as an address", args); 828 | return; 829 | } 830 | } else if let Ok(a) = args.parse::() { 831 | a 832 | } else { 833 | println!("can't parse {} as an address", args); 834 | return; 835 | }; 836 | 837 | let bold = ansi_term::Style::new().bold(); 838 | let dim = ansi_term::Style::new().dimmed(); 839 | 840 | match db.static_stack_for_pc(addr) { 841 | Ok(Some(trc)) => { 842 | println!("Static stack trace fragment for address 0x{:x}", addr); 843 | println!("(innermost / most recent first)"); 844 | for (i, record) in trc.iter().rev().enumerate() { 845 | let subp = db.subprogram_by_id(record.subprogram).unwrap(); 846 | 847 | print!("{:4} ", i); 848 | if let Some(n) = &subp.name { 849 | println!("{}", bold.paint(n)); 850 | } else { 851 | println!("{}", bold.paint("")); 852 | } 853 | print!("{}", dim.prefix()); 854 | print!(" {}:", record.file); 855 | if let Some(line) = record.line { 856 | print!("{}:", line); 857 | } else { 858 | print!("?:"); 859 | } 860 | if let Some(col) = record.column { 861 | print!("{}", col); 862 | } else { 863 | print!("?"); 864 | } 865 | print!("{}", dim.suffix()); 866 | println!(); 867 | } 868 | } 869 | Ok(None) => { 870 | println!("no stack information available for address {addr:#x?}"); 871 | } 872 | Err(e) => { 873 | println!("failed: {e}"); 874 | } 875 | } 876 | } 877 | 878 | fn cmd_vars(db: &debugdb::DebugDb, _ctx: &mut Ctx, args: &str) { 879 | for (_id, v) in db.static_variables() { 880 | if !args.is_empty() && !v.name.contains(args) { 881 | continue; 882 | } 883 | 884 | println!("0x{:0width$x} {}: {}", v.location, v.name, NamedGoff(db, v.type_id), 885 | width = db.pointer_size() * 2); 886 | } 887 | } 888 | 889 | fn cmd_var(db: &debugdb::DebugDb, ctx: &mut Ctx, args: &str) { 890 | let results = db.static_variables_by_name(args).collect::>(); 891 | 892 | match results.len() { 893 | 0 => println!("no variables found by that name"), 894 | 1 => (), 895 | n => println!("note: {} variables found by that name", n), 896 | } 897 | 898 | for (_id, v) in results { 899 | println!("{} @ {}", v.name, Goff(v.offset)); 900 | println!("- type: {}", NamedGoff(db, v.type_id)); 901 | println!("- address: 0x{:x}", v.location); 902 | let Some(ty) = db.type_by_id(v.type_id) else { continue }; 903 | 904 | match Value::from_state(&ctx.segments, v.location, db, ty) { 905 | Ok(v) => { 906 | println!("- current contents: {}", 907 | ValueWithDb(v, db)); 908 | } 909 | Err(e) => { 910 | println!("- unable to display: {e}"); 911 | } 912 | } 913 | } 914 | } 915 | 916 | fn cmd_addr(db: &debugdb::DebugDb, _ctx: &mut Ctx, args: &str) { 917 | let addr = if let Some(rest) = args.strip_prefix("0x") { 918 | if let Ok(a) = u64::from_str_radix(rest, 16) { 919 | a 920 | } else { 921 | println!("can't parse {} as an address", args); 922 | return; 923 | } 924 | } else if let Ok(a) = args.parse::() { 925 | a 926 | } else { 927 | println!("can't parse {} as an address", args); 928 | return; 929 | }; 930 | 931 | let es = db.entities_by_address(addr).collect::>(); 932 | 933 | match es.len() { 934 | 0 => println!("Nothing known about address 0x{:x}.", addr), 935 | 1 => (), 936 | n => println!("note: {} overlapping entities claim address 0x{:x}", n, addr), 937 | } 938 | 939 | let bold = ansi_term::Style::new().bold(); 940 | let dim = ansi_term::Style::new().dimmed(); 941 | 942 | for e in es { 943 | let offset = addr - e.range.start; 944 | print!("Offset +0x{:x} into ", offset); 945 | match e.entity { 946 | debugdb::EntityId::Var(vid) => { 947 | let v = db.static_variable_by_id(vid).unwrap(); 948 | println!("static {}", bold.paint(&v.name)); 949 | println!("- range 0x{:x}..0x{:x}", 950 | e.range.start, e.range.end); 951 | println!("- type {}", NamedGoff(db, v.type_id)); 952 | 953 | // Try to determine path within type. 954 | offset_to_path(db, v.type_id, offset); 955 | } 956 | debugdb::EntityId::Prog(pid) => { 957 | let p = db.subprogram_by_id(pid).unwrap(); 958 | if let Some(n) = &p.name { 959 | println!("subprogram {}", bold.paint(n)); 960 | } else { 961 | println!("subprogram {}", bold.paint("ANON")); 962 | } 963 | println!("- range 0x{:x}..0x{:x}", 964 | e.range.start, e.range.end); 965 | match db.static_stack_for_pc(addr) { 966 | Ok(Some(trc)) => { 967 | println!("- stack fragment with inlines:"); 968 | for (i, record) in trc.iter().rev().enumerate() { 969 | let subp = db.subprogram_by_id(record.subprogram).unwrap(); 970 | 971 | print!(" {:4} ", i); 972 | if let Some(n) = &subp.name { 973 | println!("{}", bold.paint(n)); 974 | } else { 975 | println!("{}", bold.paint("")); 976 | } 977 | print!("{}", dim.prefix()); 978 | print!(" {}:", record.file); 979 | if let Some(line) = record.line { 980 | print!("{}:", line); 981 | } else { 982 | print!("?:"); 983 | } 984 | if let Some(col) = record.column { 985 | print!("{}", col); 986 | } else { 987 | print!("?"); 988 | } 989 | print!("{}", dim.suffix()); 990 | println!(); 991 | } 992 | } 993 | Ok(None) => { 994 | println!("- no stack fragment is available"); 995 | } 996 | Err(e) => { 997 | println!("- could not get stack fragment: {}", e); 998 | } 999 | } 1000 | } 1001 | } 1002 | } 1003 | } 1004 | 1005 | fn offset_to_path( 1006 | db: &debugdb::DebugDb, 1007 | tid: TypeId, 1008 | offset: u64, 1009 | ) { 1010 | let t = db.type_by_id(tid).unwrap(); 1011 | match t { 1012 | Type::Array(a) => { 1013 | let et = db.type_by_id(a.element_type_id).unwrap(); 1014 | if let Some(esz) = et.byte_size(db) { 1015 | if esz > 0 { 1016 | let index = offset / esz; 1017 | let new_offset = offset % esz; 1018 | println!(" - index [{}] +0x{:x}", index, new_offset); 1019 | offset_to_path(db, a.element_type_id, new_offset); 1020 | } 1021 | } 1022 | } 1023 | Type::Struct(s) => { 1024 | // This is where an offsetof-to-member index would be convenient 1025 | 1026 | for m in &s.members { 1027 | if offset < m.location { 1028 | continue; 1029 | } 1030 | let new_offset = offset - m.location; 1031 | let mt = db.type_by_id(m.type_id).unwrap(); 1032 | if let Some(msz) = mt.byte_size(db) { 1033 | if msz > 0 { 1034 | if let Some(n) = &m.name { 1035 | println!(" - .{} +0x{:x} (in {})", n, new_offset, s.name); 1036 | } else { 1037 | return; 1038 | } 1039 | offset_to_path(db, m.type_id, new_offset); 1040 | break; 1041 | } 1042 | } 1043 | } 1044 | } 1045 | _ => (), 1046 | } 1047 | } 1048 | 1049 | fn cmd_unwind(db: &debugdb::DebugDb, _ctx: &mut Ctx, args: &str) { 1050 | let addr = if let Some(rest) = args.strip_prefix("0x") { 1051 | if let Ok(a) = u64::from_str_radix(rest, 16) { 1052 | a 1053 | } else { 1054 | println!("can't parse {} as an address", args); 1055 | return; 1056 | } 1057 | } else if let Ok(a) = args.parse::() { 1058 | a 1059 | } else { 1060 | println!("can't parse {} as an address", args); 1061 | return; 1062 | }; 1063 | 1064 | use gimli::UnwindSection; 1065 | let mut ctx = gimli::UnwindContext::new(); 1066 | let bases = gimli::BaseAddresses::default(); 1067 | match db.debug_frame.unwind_info_for_address(&bases, &mut ctx, addr, gimli::DebugFrame::cie_from_offset) { 1068 | Ok(ui) => { 1069 | println!("saved args: {} bytes", ui.saved_args_size()); 1070 | print!("cfa: "); 1071 | match ui.cfa() { 1072 | gimli::CfaRule::RegisterAndOffset { register, offset } => { 1073 | println!("reg #{}, offset {}", register.0, offset); 1074 | } 1075 | other => panic!("unsupported CFA rule type: {:?}", other), 1076 | } 1077 | for (n, rule) in ui.registers() { 1078 | print!(" caller reg #{} ", n.0); 1079 | match rule { 1080 | gimli::RegisterRule::Offset(n) => { 1081 | if *n < 0 { 1082 | println!("at CFA-{}", -n); 1083 | } else { 1084 | println!("at CFA+{}", n); 1085 | } 1086 | } 1087 | gimli::RegisterRule::ValOffset(n) => { 1088 | if *n < 0 { 1089 | println!("= CFA-{}", -n); 1090 | } else { 1091 | println!("= CFA+{}", n); 1092 | } 1093 | } 1094 | gimli::RegisterRule::SameValue => { 1095 | println!("preserved"); 1096 | } 1097 | gimli::RegisterRule::Register(n) => { 1098 | println!("in reg# {}", n.0); 1099 | } 1100 | _ => println!("{:?}", rule), 1101 | } 1102 | } 1103 | } 1104 | Err(e) => { 1105 | println!("failed: {}", e); 1106 | } 1107 | } 1108 | } 1109 | 1110 | fn struct_picture(db: &DebugDb, s: &Struct, width: usize) { 1111 | struct_picture_inner( 1112 | db, 1113 | s.byte_size, 1114 | s.members.iter().enumerate().map(|(i, m)| (i, m, true)), 1115 | width, 1116 | ) 1117 | } 1118 | 1119 | fn struct_picture_inner<'a, N: Eq + Clone + Display>( 1120 | db: &DebugDb, 1121 | byte_size: Option, 1122 | members: impl IntoIterator, 1123 | width: usize, 1124 | ) { 1125 | let Some(size) = byte_size else { 1126 | println!("type has no size"); 1127 | return; 1128 | }; 1129 | 1130 | if size == 0 { 1131 | println!("(type is 0 bytes long)"); 1132 | return; 1133 | } 1134 | 1135 | let mut member_spans: RangeMap = RangeMap::new(); 1136 | let mut member_labels = vec![]; 1137 | for (i, m, in_legend) in members { 1138 | if in_legend { 1139 | member_labels.push({ 1140 | let label = if db.type_by_id(m.type_id).unwrap().byte_size(db) == Some(0) { 1141 | "(ZST)".to_string() 1142 | } else { 1143 | i.to_string() 1144 | }; 1145 | 1146 | let name = if let Some(name) = &m.name { 1147 | name.as_str() 1148 | } else { 1149 | "_" 1150 | }; 1151 | if label == name { 1152 | format!("{name}: {}", NamedGoff(db, m.type_id)) 1153 | } else { 1154 | format!("{label} = {name}: {}", NamedGoff(db, m.type_id)) 1155 | } 1156 | }); 1157 | } 1158 | let offset = m.location; 1159 | let Some(size) = db.type_by_id(m.type_id).unwrap().byte_size(db) else { 1160 | continue; 1161 | }; 1162 | if size != 0 { 1163 | member_spans.insert(offset..offset + size, i); 1164 | } 1165 | } 1166 | 1167 | byte_picture(size, width, |off| { 1168 | member_spans.get(&off).map(|x| x.to_string()) 1169 | }); 1170 | if !member_labels.is_empty() { 1171 | println!(" where:"); 1172 | for label in member_labels { 1173 | println!(" {label}"); 1174 | } 1175 | } 1176 | } 1177 | 1178 | fn enum_picture(db: &DebugDb, s: &Enum, width: usize) { 1179 | let Some(size) = s.byte_size else { 1180 | println!("type has no size"); 1181 | return; 1182 | }; 1183 | 1184 | if size == 0 { 1185 | println!("(type is 0 bytes long)"); 1186 | return; 1187 | } 1188 | 1189 | println!(); 1190 | 1191 | match &s.shape { 1192 | VariantShape::Zero => { 1193 | println!("this enum is empty and cannot be diagrammed."); 1194 | } 1195 | VariantShape::One(_v) => { 1196 | println!("this enum has only one variant (TODO)"); 1197 | } 1198 | VariantShape::Many { member, .. } => { 1199 | let Some(dlen) = db.type_by_id(member.type_id).unwrap().byte_size(db) else { 1200 | println!("discriminator type has no size?"); 1201 | return; 1202 | }; 1203 | let drange = member.location .. member.location + dlen; 1204 | println!("Discriminator position:"); 1205 | byte_picture(size, width, |off| { 1206 | if drange.contains(&off) { 1207 | Some("DISC".to_string()) 1208 | } else { 1209 | Some("body".to_string()) 1210 | } 1211 | }); 1212 | /* 1213 | for (disc, var) in variants { 1214 | let show_disc = if let Some(v) = disc { 1215 | print!("DISC == {v:#x} => body: "); 1216 | true 1217 | } else { 1218 | print!("else => body: "); 1219 | false 1220 | }; 1221 | println!("{}", NamedGoff(db, var.member.type_id)); 1222 | let vt = db.type_by_id(var.member.type_id).unwrap(); 1223 | match vt { 1224 | Type::Struct(s) => { 1225 | let mut all_members = vec![]; 1226 | if show_disc { 1227 | all_members.push(("DISC", member, false)); 1228 | } 1229 | all_members.extend( 1230 | s.members.iter().map(|(n, m)| { 1231 | let mut n = n.as_str(); 1232 | if n.len() > 6 { 1233 | n = &n[..6]; 1234 | } 1235 | 1236 | (n, m, true) 1237 | }) 1238 | ); 1239 | struct_picture_inner(db, s.byte_size, all_members, width); 1240 | }, 1241 | _ => println!("(can't display non-struct)"), 1242 | } 1243 | } 1244 | */ 1245 | } 1246 | } 1247 | } 1248 | 1249 | const fn corner(u: bool, d: bool, l: bool, r: bool) -> char { 1250 | static DIRS: [char; 16] = [ 1251 | '·', '╶', '╴', '─', // 0000, 0001, 0010, 0011 1252 | '╷', '┌', '┐', '┬', // 0100, 0101, 0110, 0111 1253 | '╵', '└', '┘', '┴', // 1000, 1001, 1010, 1011 1254 | '│', '├', '┤', '┼', // 1100, 1101, 1110, 1111 1255 | ]; 1256 | let idx = (u as usize) << 3 | (d as usize) << 2 | (l as usize) << 1 | r as usize; 1257 | DIRS[idx] 1258 | } 1259 | 1260 | fn byte_picture( 1261 | size: u64, 1262 | width: usize, 1263 | owner: impl Fn(u64) -> Option, 1264 | ) { 1265 | const S: char = ' '; 1266 | const H: char = corner(false, false, true, true); 1267 | const V: char = corner(true, true, false, false); 1268 | const UR: char = corner(true, false, false, true); 1269 | 1270 | let width = width as u64; 1271 | print!("{S}{S}{S}{S}{S}{S}"); 1272 | for byte in 0..u64::min(size, width) { 1273 | print!(" {byte:^6}"); 1274 | } 1275 | println!(); 1276 | 1277 | let wordcount = (size + (width - 1)) / width; 1278 | let mut current = None; 1279 | let mut above = vec![None; width as usize]; 1280 | for word in 0..wordcount { 1281 | print!(" {}", corner(word > 0, true, false, true)); 1282 | for byte in 0..width { 1283 | let n = owner(word * width + byte); 1284 | let bw = byte == width - 1; 1285 | if above[byte as usize] == Some(n) { 1286 | print!("{S}{S}{S}{S}{S}{S}{}", corner(word > 0 && bw, bw, bw, false)); 1287 | } else { 1288 | print!("{H}{H}{H}{H}{H}{H}{}", corner(word > 0, true, true, !bw)); 1289 | } 1290 | } 1291 | println!(); 1292 | 1293 | print!("{:04x} {V}", word * width); 1294 | for byte in 0..width { 1295 | let off = word * width + byte; 1296 | let n = owner(off); 1297 | if Some(&n) != current.as_ref() { 1298 | if byte != 0 { 1299 | print!("{V}"); 1300 | } 1301 | if let Some(i) = &n { 1302 | print!("{:^6}", i); 1303 | } else if off < size { 1304 | print!(" pad "); 1305 | } else { 1306 | print!(" "); 1307 | } 1308 | current = Some(n.clone()); 1309 | } else { 1310 | if byte != 0 { 1311 | print!(" "); 1312 | } 1313 | print!(" "); 1314 | } 1315 | 1316 | if byte == width - 1 { 1317 | if off < size { 1318 | println!("{V}"); 1319 | } else { 1320 | println!(); 1321 | } 1322 | } 1323 | 1324 | above[byte as usize] = Some(n); 1325 | } 1326 | } 1327 | print!(" {UR}"); 1328 | let final_bar = if size % width == 0 { width } else { size % width }; 1329 | for idx in 0..final_bar { 1330 | print!("{H}{H}{H}{H}{H}{H}{}", corner(true, false, true, idx != final_bar - 1)); 1331 | } 1332 | println!(); 1333 | } 1334 | 1335 | fn cmd_decode(db: &debugdb::DebugDb, ctx: &mut Ctx, args: &str) { 1336 | let (addrstr, mut typestr) = if let Some(space) = args.find(' ') { 1337 | args.split_at(space) 1338 | } else { 1339 | println!("usage: decode [addr] [typename blah blah]"); 1340 | return; 1341 | }; 1342 | let addr = match parse_int::parse::(addrstr) { 1343 | Ok(x) => x, 1344 | Err(e) => { 1345 | println!("bad address: {e}"); 1346 | return; 1347 | } 1348 | }; 1349 | 1350 | let asmutref = Regex::new(r#"^ +as +[&*]mut (.*)$"#).unwrap(); 1351 | let asref = Regex::new(r#"^ +as +&(.*)$"#).unwrap(); 1352 | let asptr = Regex::new(r#"^ +as +\*(const|_) (.*)$"#).unwrap(); 1353 | 1354 | if let Some(c) = asmutref.captures(typestr) { 1355 | typestr = c.get(1).unwrap().as_str(); 1356 | } else if let Some(c) = asref.captures(typestr) { 1357 | typestr = c.get(1).unwrap().as_str(); 1358 | } else if let Some(c) = asptr.captures(typestr) { 1359 | typestr = c.get(2).unwrap().as_str(); 1360 | } 1361 | 1362 | let types: Vec<_> = match parse_type_name(typestr.trim()) { 1363 | None => return, 1364 | Some(ParsedTypeName::Name(n)) => { 1365 | db.types_by_name(n).collect() 1366 | } 1367 | Some(ParsedTypeName::Goff(o)) => { 1368 | db.type_by_id(o).into_iter() 1369 | .map(|t| (o, t)) 1370 | .collect() 1371 | } 1372 | }; 1373 | 1374 | let many = match types.len() { 1375 | 0 => { 1376 | println!("{}", ansi_term::Colour::Red.paint("No types found.")); 1377 | return; 1378 | } 1379 | 1 => false, 1380 | n => { 1381 | println!("{}{} types found with that name:", 1382 | ansi_term::Color::Yellow.paint("note: "), 1383 | n, 1384 | ); 1385 | true 1386 | } 1387 | }; 1388 | 1389 | for (goff, t) in types { 1390 | if many { println!() } 1391 | println!("{}: ", NamedGoff(db, goff)); 1392 | match Value::from_state(&ctx.segments, addr, db, t) { 1393 | Ok(v) => { 1394 | println!("{}", ValueWithDb(v, db)); 1395 | } 1396 | Err(e) => { 1397 | println!("could not parse as this type: {e}"); 1398 | } 1399 | } 1400 | } 1401 | } 1402 | 1403 | fn cmd_decode_async(db: &debugdb::DebugDb, ctx: &mut Ctx, args: &str) { 1404 | let (addrstr, typestr) = if let Some(space) = args.find(' ') { 1405 | args.split_at(space) 1406 | } else { 1407 | println!("usage: decode-async [addr] [typename blah blah]"); 1408 | return; 1409 | }; 1410 | let addr = match parse_int::parse::(addrstr) { 1411 | Ok(x) => x, 1412 | Err(e) => { 1413 | println!("bad address: {e}"); 1414 | return; 1415 | } 1416 | }; 1417 | let types: Vec<_> = match parse_type_name(typestr.trim()) { 1418 | None => return, 1419 | Some(ParsedTypeName::Name(n)) => { 1420 | db.types_by_name(n).collect() 1421 | } 1422 | Some(ParsedTypeName::Goff(o)) => { 1423 | db.type_by_id(o).into_iter() 1424 | .map(|t| (o, t)) 1425 | .collect() 1426 | } 1427 | }; 1428 | 1429 | let many = match types.len() { 1430 | 0 => { 1431 | println!("{}", ansi_term::Colour::Red.paint("No types found.")); 1432 | return; 1433 | } 1434 | 1 => false, 1435 | n => { 1436 | println!("{}{} types found with that name:", 1437 | ansi_term::Color::Yellow.paint("note: "), 1438 | n, 1439 | ); 1440 | true 1441 | } 1442 | }; 1443 | 1444 | for (goff, t) in types { 1445 | if many { println!() } 1446 | println!("{}: ", NamedGoff(db, goff)); 1447 | let mut v = &match Value::from_state(&ctx.segments, addr, db, t) { 1448 | Ok(v) => v, 1449 | Err(e) => { 1450 | println!("could not parse as this type: {e}"); 1451 | return; 1452 | } 1453 | }; 1454 | let parts = Regex::new(ASYNC_FN_BLOCK_ENV).unwrap(); 1455 | let suspend_state = Regex::new(r#"::Suspend([0-9]+)$"#).unwrap(); 1456 | let mut first = true; 1457 | let bold = ansi_term::Style::new().bold(); 1458 | loop { 1459 | if !first { 1460 | print!("waiting on: "); 1461 | } 1462 | first = false; 1463 | let Value::Enum(e) = v else { 1464 | println!("{}hand-rolled future{}", bold.prefix(), bold.suffix()); 1465 | println!(" type: {}", v.type_name()); 1466 | break; 1467 | }; 1468 | let Some(parts) = parts.captures(&e.name) else { 1469 | println!("(name {} is weird for an async fn env)", e.name); 1470 | break; 1471 | }; 1472 | let name = &parts["name"]; 1473 | let parms = parts.name("tmpl").map(|m| m.as_str()).unwrap_or(""); 1474 | println!("async fn {}{name}{parms}{}", bold.prefix(), bold.suffix()); 1475 | let state = &e.disc; 1476 | let state_name = &e.value.name; 1477 | 1478 | if state_name.ends_with("Unresumed") { 1479 | println!(" future has not yet been polled"); 1480 | break; 1481 | } else if state_name.ends_with("Returned") { 1482 | println!(" future has already resolved"); 1483 | break; 1484 | } else if state_name.ends_with("Panicked") { 1485 | println!(" future panicked on previous poll"); 1486 | break; 1487 | } else if let Some(sc) = suspend_state.captures(state_name) { 1488 | if let Ok(n) = sc[1].parse::() { 1489 | println!(" suspended at await point {n}"); 1490 | } else { 1491 | println!(" unrecognized state {state}: {state_name}"); 1492 | } 1493 | } else { 1494 | println!(" unrecognized state {state}: {state_name}"); 1495 | } 1496 | 1497 | let mut awaitees = e.value.members_named("__awaitee"); 1498 | let Some(awaitee) = awaitees.next() else { 1499 | println!(" (stopped unexpectedly)"); 1500 | break; 1501 | }; 1502 | if awaitees.next().is_some() { 1503 | println!(" (multiple __awaitee fields)"); 1504 | break; 1505 | } 1506 | v = awaitee; 1507 | } 1508 | } 1509 | } 1510 | 1511 | fn cmd_decode_blob(db: &debugdb::DebugDb, _ctx: &mut Ctx, args: &str) { 1512 | let type_name = args.trim(); 1513 | let types: Vec<_> = match parse_type_name(type_name) { 1514 | None => return, 1515 | Some(ParsedTypeName::Name(n)) => { 1516 | db.types_by_name(n).collect() 1517 | } 1518 | Some(ParsedTypeName::Goff(o)) => { 1519 | db.type_by_id(o).into_iter() 1520 | .map(|t| (o, t)) 1521 | .collect() 1522 | } 1523 | }; 1524 | 1525 | let many = match types.len() { 1526 | 0 => { 1527 | println!("{}", ansi_term::Colour::Red.paint("No types found.")); 1528 | return; 1529 | } 1530 | 1 => false, 1531 | n => { 1532 | println!("{}{} types found with that name:", 1533 | ansi_term::Color::Yellow.paint("note: "), 1534 | n, 1535 | ); 1536 | true 1537 | } 1538 | }; 1539 | 1540 | println!("Paste hex-encoded memory blob. Whitespace OK."); 1541 | println!("Address prefix ending in colon will be removed."); 1542 | println!("Enter a blank line to end."); 1543 | 1544 | let stdin = std::io::stdin().lock(); 1545 | let mut img = vec![]; 1546 | for line in stdin.lines() { 1547 | let line = match line { 1548 | Err(e) => { 1549 | println!("input error: {e}"); 1550 | return; 1551 | } 1552 | Ok(v) => v, 1553 | }; 1554 | let mut line = line.trim(); 1555 | if line.is_empty() { 1556 | break; 1557 | } 1558 | if let Some(colon) = line.find(':') { 1559 | line = &line.split_at(colon).1[1..]; 1560 | } 1561 | 1562 | let mut hexits = vec![]; 1563 | for b in line.bytes() { 1564 | match b { 1565 | b'0'..=b'9' | b'A'..=b'F' | b'a'..=b'f' => { 1566 | hexits.push(b); 1567 | } 1568 | b' ' | b'\t' | b'\r' | b'\n' => (), 1569 | _ => { 1570 | println!("unexpected byte in input: {b:#x?}"); 1571 | return; 1572 | } 1573 | } 1574 | } 1575 | 1576 | let bytes = hexits.chunks_exact(2) 1577 | .map(|chunk| u8::from_str_radix(std::str::from_utf8(chunk).unwrap(), 16)) 1578 | .collect::, _>>(); 1579 | match bytes { 1580 | Err(e) => { 1581 | println!("couldn't parse that: {e}"); 1582 | return; 1583 | } 1584 | Ok(b) => img.extend(b), 1585 | } 1586 | } 1587 | 1588 | for (goff, t) in types { 1589 | if many { println!() } 1590 | println!("{}: ", NamedGoff(db, goff)); 1591 | let Some(size) = t.byte_size(db) else { 1592 | println!(" (type is unsized, cannot decode)"); 1593 | continue; 1594 | }; 1595 | let Ok(size) = usize::try_from(size) else { 1596 | println!(" (type too big for this platform)"); 1597 | continue; 1598 | }; 1599 | let mut this_img = img.clone(); 1600 | if size > this_img.len() { 1601 | println!("(padding entered data to {size} bytes)"); 1602 | this_img.resize(size, 0); 1603 | } 1604 | let machine = ImgMachine::new(this_img); 1605 | match Value::from_state(&machine, 0, db, t) { 1606 | Ok(v) => { 1607 | println!("{}", ValueWithDb(v, db)); 1608 | } 1609 | Err(e) => { 1610 | println!("could not parse as this type: {e}"); 1611 | } 1612 | } 1613 | } 1614 | } 1615 | 1616 | fn cmd_decode_async_blob(db: &debugdb::DebugDb, _ctx: &mut Ctx, args: &str) { 1617 | let type_name = args.trim(); 1618 | let types: Vec<_> = match parse_type_name(type_name) { 1619 | None => return, 1620 | Some(ParsedTypeName::Name(n)) => { 1621 | db.types_by_name(n).collect() 1622 | } 1623 | Some(ParsedTypeName::Goff(o)) => { 1624 | db.type_by_id(o).into_iter() 1625 | .map(|t| (o, t)) 1626 | .collect() 1627 | } 1628 | }; 1629 | 1630 | let many = match types.len() { 1631 | 0 => { 1632 | println!("{}", ansi_term::Colour::Red.paint("No types found.")); 1633 | return; 1634 | } 1635 | 1 => false, 1636 | n => { 1637 | println!("{}{} types found with that name:", 1638 | ansi_term::Color::Yellow.paint("note: "), 1639 | n, 1640 | ); 1641 | true 1642 | } 1643 | }; 1644 | 1645 | println!("Paste hex-encoded memory blob. Whitespace OK."); 1646 | println!("Address prefix ending in colon will be removed."); 1647 | println!("Enter a blank line to end."); 1648 | 1649 | let stdin = std::io::stdin().lock(); 1650 | let mut img = vec![]; 1651 | for line in stdin.lines() { 1652 | let line = match line { 1653 | Err(e) => { 1654 | println!("input error: {e}"); 1655 | return; 1656 | } 1657 | Ok(v) => v, 1658 | }; 1659 | let mut line = line.trim(); 1660 | if line.is_empty() { 1661 | break; 1662 | } 1663 | if let Some(colon) = line.find(':') { 1664 | line = &line.split_at(colon).1[1..]; 1665 | } 1666 | 1667 | let mut hexits = vec![]; 1668 | for b in line.bytes() { 1669 | match b { 1670 | b'0'..=b'9' | b'A'..=b'F' | b'a'..=b'f' => { 1671 | hexits.push(b); 1672 | } 1673 | b' ' | b'\t' | b'\r' | b'\n' => (), 1674 | _ => { 1675 | println!("unexpected byte in input: {b:#x?}"); 1676 | return; 1677 | } 1678 | } 1679 | } 1680 | 1681 | let bytes = hexits.chunks_exact(2) 1682 | .map(|chunk| u8::from_str_radix(std::str::from_utf8(chunk).unwrap(), 16)) 1683 | .collect::, _>>(); 1684 | match bytes { 1685 | Err(e) => { 1686 | println!("couldn't parse that: {e}"); 1687 | return; 1688 | } 1689 | Ok(b) => img.extend(b), 1690 | } 1691 | } 1692 | 1693 | for (goff, t) in types { 1694 | if many { println!() } 1695 | println!("{}: ", NamedGoff(db, goff)); 1696 | let Some(size) = t.byte_size(db) else { 1697 | println!(" (type is unsized, cannot decode)"); 1698 | continue; 1699 | }; 1700 | let Ok(size) = usize::try_from(size) else { 1701 | println!(" (type too big for this platform)"); 1702 | continue; 1703 | }; 1704 | let mut this_img = img.clone(); 1705 | if size > this_img.len() { 1706 | println!("(padding entered data to {size} bytes)"); 1707 | this_img.resize(size, 0); 1708 | } 1709 | let machine = ImgMachine::new(this_img); 1710 | let mut v = &match Value::from_state(&machine, 0, db, t) { 1711 | Ok(v) => v, 1712 | Err(e) => { 1713 | println!("could not parse as this type: {e}"); 1714 | return; 1715 | } 1716 | }; 1717 | let parts = Regex::new(ASYNC_FN_BLOCK_ENV).unwrap(); 1718 | let suspend_state = Regex::new(r#"::Suspend([0-9]+)$"#).unwrap(); 1719 | let mut first = true; 1720 | loop { 1721 | if !first { 1722 | print!("waiting on: "); 1723 | } 1724 | first = false; 1725 | let Value::Enum(e) = v else { 1726 | println!("hand-rolled future"); 1727 | println!(" type: {}", v.type_name()); 1728 | break; 1729 | }; 1730 | let Some(parts) = parts.captures(&e.name) else { 1731 | println!("(name {} is weird for an async fn env)", e.name); 1732 | break; 1733 | }; 1734 | let name = &parts["name"]; 1735 | let parms = parts.name("tmpl").map(|m| m.as_str()).unwrap_or(""); 1736 | println!("async fn {name}{parms}"); 1737 | let state = &e.disc; 1738 | let state_name = &e.value.name; 1739 | 1740 | if state_name.ends_with("Unresumed") { 1741 | println!(" future has not yet been polled"); 1742 | break; 1743 | } else if state_name.ends_with("Returned") { 1744 | println!(" future has already resolved"); 1745 | break; 1746 | } else if state_name.ends_with("Panicked") { 1747 | println!(" future panicked on previous poll"); 1748 | break; 1749 | } else if let Some(sc) = suspend_state.captures(state_name) { 1750 | if let Ok(n) = sc[1].parse::() { 1751 | println!(" suspended at await point {n}"); 1752 | } else { 1753 | println!(" unrecognized state {state}: {state_name}"); 1754 | } 1755 | } else { 1756 | println!(" unrecognized state {state}: {state_name}"); 1757 | } 1758 | 1759 | let mut awaitees = e.value.members_named("__awaitee"); 1760 | let Some(awaitee) = awaitees.next() else { 1761 | println!(" (stopped unexpectedly)"); 1762 | break; 1763 | }; 1764 | if awaitees.next().is_some() { 1765 | println!(" (multiple __awaitee fields)"); 1766 | break; 1767 | } 1768 | v = awaitee; 1769 | } 1770 | } 1771 | } 1772 | 1773 | 1774 | fn cmd_load( 1775 | _db: &debugdb::DebugDb, 1776 | ctx: &mut Ctx, 1777 | args: &str, 1778 | ) { 1779 | let args = args.trim(); 1780 | let words = args.split_whitespace().collect::>(); 1781 | if words.len() != 2 { 1782 | println!("usage: load [filename] [address]"); 1783 | return; 1784 | } 1785 | let filename = words[0]; 1786 | let address = match parse_int::parse::(words[1]) { 1787 | Ok(a) => a, 1788 | Err(e) => { 1789 | println!("bad address: {e}"); 1790 | return; 1791 | } 1792 | }; 1793 | 1794 | let image = match std::fs::read(filename) { 1795 | Ok(bytes) => bytes, 1796 | Err(e) => { 1797 | println!("unable to read file: {e}"); 1798 | return; 1799 | } 1800 | }; 1801 | 1802 | let end = address + u64::try_from(image.len()).unwrap(); 1803 | 1804 | ctx.segments.insert(address..=end, image); 1805 | } 1806 | 1807 | fn cmd_stack(db: &debugdb::DebugDb, ctx: &mut Ctx, _args: &str) { 1808 | let (stack_ty, dead_val, wid) = match db.pointer_size() { 1809 | 4 => ("u32", 0xDEDEDEDE, 10), 1810 | 8 => ("u64", 0xDEDEDEDE_DEDEDEDE, 18), 1811 | _ => { 1812 | println!("unsupported image pointer size"); 1813 | return; 1814 | } 1815 | }; 1816 | match get_stack_used(&ctx.segments, db, stack_ty, dead_val) { 1817 | Ok(Some((base, used, top))) => { 1818 | let bytes_used = top - used; 1819 | let bytes_avail = top - base; 1820 | let free = used - base; 1821 | println!("stack top: {top:#wid$x}"); 1822 | println!("lowest stack used: {used:#wid$x}"); 1823 | println!("bytes used: {bytes_used} / {bytes_avail} ({free} free)"); 1824 | } 1825 | Ok(None) => { 1826 | println!("can't determine stack shape"); 1827 | } 1828 | Err(e) => { 1829 | println!("can't access stack: {e}"); 1830 | } 1831 | } 1832 | } 1833 | 1834 | fn cmd_time(db: &debugdb::DebugDb, ctx: &mut Ctx, _args: &str) { 1835 | if let Some(tv) = find_time_vars(db) { 1836 | match get_time(&ctx.segments, db, &tv) { 1837 | Ok(t) => { 1838 | println!("current tick-time is: {t}"); 1839 | }, 1840 | Err(e) => { 1841 | println!("error loading time: {e}"); 1842 | } 1843 | } 1844 | } else { 1845 | println!("could not find time vars"); 1846 | } 1847 | } 1848 | 1849 | fn cmd_tasks(db: &debugdb::DebugDb, ctx: &mut Ctx, args: &str) { 1850 | let mut verbose = false; 1851 | for word in args.split_whitespace() { 1852 | match word { 1853 | "-v" => verbose = true, 1854 | _ => { 1855 | println!("usage: tasks {{-v}}"); 1856 | return; 1857 | } 1858 | } 1859 | } 1860 | 1861 | let time = { 1862 | if let Some(tv) = find_time_vars(db) { 1863 | match get_time(&ctx.segments, db, &tv) { 1864 | Ok(t) => { 1865 | println!("current tick-time is: {t}"); 1866 | Some(t) 1867 | }, 1868 | Err(e) => { 1869 | println!("error loading time: {e}"); 1870 | None 1871 | } 1872 | } 1873 | } else { 1874 | println!("could not find time vars"); 1875 | None 1876 | } 1877 | }; 1878 | 1879 | let Some((_, futures)) = db.unique_static_variable_by_name("lilos::exec::TASK_FUTURES") else { 1880 | println!("{}", ansi_term::Colour::Red.paint("missing lilos::exec::TASK_FUTURES var")); 1881 | return; 1882 | }; 1883 | 1884 | let Some(ty) = db.type_by_id(futures.type_id) else { 1885 | println!("{}", ansi_term::Colour::Red.paint("lilos::exec::TASK_FUTURES var has invalid type")); 1886 | return; 1887 | }; 1888 | 1889 | let val = match Value::from_state(&ctx.segments, futures.location, db, ty) { 1890 | Ok(v) => v, 1891 | Err(e) => { 1892 | println!("{}{e}", ansi_term::Colour::Red.paint("can't load lilos::exec::TASK_FUTURES: ")); 1893 | return; 1894 | } 1895 | }; 1896 | let Value::Enum(val) = val else { 1897 | println!("{}", ansi_term::Colour::Red.paint("lilos::exec::TASK_FUTURES type has wrong shape")); 1898 | return; 1899 | }; 1900 | if val.disc != "Some" { 1901 | println!("{}", ansi_term::Colour::Red.paint("lilos::exec::TASK_FUTURES not set - has scheduler started?")); 1902 | return; 1903 | } 1904 | 1905 | let Some(Value::Struct(slice)) = val.value.members_named("__0").next() else { 1906 | println!("{}", ansi_term::Colour::Red.paint("lilos::exec::TASK_FUTURES type has wrong shape")); 1907 | return; 1908 | }; 1909 | let Some(Value::Pointer(data_ptr)) = slice.members_named("data_ptr").next() else { 1910 | println!("{}", ansi_term::Colour::Red.paint("lilos::exec::TASK_FUTURES type has wrong shape")); 1911 | return; 1912 | }; 1913 | let Some(Value::Base(length)) = slice.members_named("length").next() else { 1914 | println!("{}", ansi_term::Colour::Red.paint("lilos::exec::TASK_FUTURES type has wrong shape")); 1915 | return; 1916 | }; 1917 | let length = length.as_u64().unwrap(); 1918 | let Some(pointee) = db.type_by_id(data_ptr.dest_type_id) else { 1919 | println!("{}", ansi_term::Colour::Red.paint("pointee type missing ???")); 1920 | return; 1921 | }; 1922 | let Some(pointee_size) = pointee.byte_size(db) else { 1923 | println!("{}", ansi_term::Colour::Red.paint("pointee unsized ???")); 1924 | return; 1925 | }; 1926 | let dynptr = Regex::new(r#"^[&*](mut )?dyn (.*)$"#).unwrap(); 1927 | for i in 0..length { 1928 | println!("{}task {i}:{}", Colour::Green.prefix(), Colour::Green.suffix()); 1929 | let addr = data_ptr.value + i * pointee_size; 1930 | let elt = match value::Struct::from_state(&ctx.segments, addr, db, pointee) { 1931 | Ok(e) => e, 1932 | Err(e) => { 1933 | println!("{} failed to load task pointer {i}: {e}", ansi_term::Colour::Red.paint("error:")); 1934 | return; 1935 | } 1936 | }; 1937 | let Some(Value::Struct(fat_pointer)) = elt.any_member_named("pointer") else { 1938 | println!("Pin missing pointer member"); 1939 | return; 1940 | }; 1941 | let Some(_dyn_caps) = dynptr.captures(&fat_pointer.name) else { 1942 | println!("dyn pointer name format unexpected: {}", 1943 | &fat_pointer.name); 1944 | return; 1945 | }; 1946 | let Some(data_pointer) = fat_pointer.any_member_named("pointer") else { 1947 | println!("bad fat pointer"); 1948 | return; 1949 | }; 1950 | let Some(vtable) = fat_pointer.any_member_named("vtable") else { 1951 | println!("bad fat pointer"); 1952 | return; 1953 | }; 1954 | 1955 | let Some(data_addr) = data_pointer.pointer_value() else { 1956 | println!("data_ptr not a pointer?"); 1957 | return; 1958 | }; 1959 | let Some(vt_addr) = vtable.pointer_value() else { 1960 | println!("vtable not a pointer?"); 1961 | return; 1962 | }; 1963 | 1964 | let mut concrete_type = None; 1965 | 'searchloop: 1966 | for e in db.entities_by_address(vt_addr) { 1967 | if vt_addr != e.range.start { 1968 | continue; 1969 | } 1970 | let EntityId::Var(v) = e.entity else { continue }; 1971 | let Some(v) = db.static_variable_by_id(v) else { continue }; 1972 | 1973 | let vtable = Regex::new(r#"^<(.*) as (.*)>::\{vtable\}$"#).unwrap(); 1974 | let Some(vc) = vtable.captures(&v.name) else { continue }; 1975 | let concrete = &vc[1]; 1976 | 1977 | for (tid, ty) in db.types_by_name(concrete) { 1978 | if matches!(ty, Type::Enum(_)) { 1979 | // Good enough. 1980 | concrete_type = Some((tid, ty)); 1981 | break 'searchloop; 1982 | } 1983 | } 1984 | } 1985 | 1986 | let Some((_concrete_tid, concrete_ty)) = concrete_type else { 1987 | println!("concrete type for vtable not found: {vt_addr:#x}"); 1988 | return; 1989 | }; 1990 | 1991 | let outer = match Value::from_state(&ctx.segments, data_addr, db, concrete_ty) { 1992 | Ok(v) => v, 1993 | Err(e) => { 1994 | println!("could not parse as this type: {e}"); 1995 | return; 1996 | } 1997 | }; 1998 | let mut v = &outer; 1999 | let mut first = true; 2000 | let bold = ansi_term::Style::new().bold(); 2001 | loop { 2002 | if !first { 2003 | print!("waiting on: "); 2004 | } 2005 | first = false; 2006 | 2007 | let next = await_trace_frame( 2008 | ctx, 2009 | db, 2010 | time, 2011 | v, 2012 | ); 2013 | if let Some(n) = next { 2014 | v = n; 2015 | } else { 2016 | break; 2017 | } 2018 | } 2019 | if verbose { 2020 | println!("{}", bold.paint("full dump:")); 2021 | println!("{}", ValueWithDb(outer, db)); 2022 | } 2023 | } 2024 | } 2025 | 2026 | fn await_trace_frame<'v>( 2027 | ctx: &Ctx, 2028 | db: &DebugDb, 2029 | time: Option, 2030 | value: &'v Value, 2031 | ) -> Option<&'v Value> { 2032 | let parts = Regex::new(ASYNC_FN_BLOCK_ENV).unwrap(); 2033 | let suspend_state = Regex::new(r#"::Suspend([0-9]+)$"#).unwrap(); 2034 | let bold = ansi_term::Style::new().bold(); 2035 | 2036 | let Value::Enum(e) = value else { 2037 | return await_trace_handroll(ctx, db, time, value); 2038 | }; 2039 | let Some(parts) = parts.captures(&e.name) else { 2040 | println!("(name {} is weird for an async fn env)", e.name); 2041 | return None; 2042 | }; 2043 | let name = &parts["name"]; 2044 | let parms = parts.name("tmpl").map(|m| m.as_str()).unwrap_or(""); 2045 | println!("async fn {}{name}{parms}{}", bold.prefix(), bold.suffix()); 2046 | let state = &e.disc; 2047 | let state_name = &e.value.name; 2048 | 2049 | let state = if state_name.ends_with("Unresumed") { 2050 | AsyncFnState::Unresumed 2051 | } else if state_name.ends_with("Returned") { 2052 | AsyncFnState::Returned 2053 | } else if state_name.ends_with("Panicked") { 2054 | AsyncFnState::Panicked 2055 | } else if let Some(sc) = suspend_state.captures(state_name) { 2056 | if let Ok(n) = sc[1].parse::() { 2057 | AsyncFnState::Suspend(n) 2058 | } else { 2059 | println!(" unrecognized state {state}: {state_name}"); 2060 | return None; 2061 | } 2062 | } else { 2063 | println!(" unrecognized state {state}: {state_name}"); 2064 | return None; 2065 | }; 2066 | 2067 | // Try and report the line number (decl coords). The decl coords are present 2068 | // on the enum variant member. That is, 2069 | // 2070 | // enum yadda::yadda::{async_fn_env#0} 2071 | // variant 2 2072 | // member 2073 | // decl coords here 2074 | // 2075 | // So, we need to find the actual type (rather than the reflected enum, 2076 | // which we've been using as a shortcut), and then find the variant. The 2077 | // variant names don't match the state struct names (e.g. they are not 2078 | // things like Suspend0), so we have to do the matching by _also_ getting 2079 | // the tid of the state struct, and checking if the member matches that 2080 | // type. 2081 | // 2082 | // Whee. 2083 | // 2084 | // First we'll see if we can find a tid for the state struct. 2085 | let mut has_decl_coords = false; 2086 | if let Some((state_tid, _)) = db.types_by_name(state_name).next() { 2087 | // Cool. See if we can find the enum type. There can easily be more than 2088 | // one such type; we'll process the first one that seems vaguely 2089 | // plausible. 2090 | 'enumloop: 2091 | for (_tid, ty) in db.types_by_name(&e.name) { 2092 | // We expect the type to be an enum. 2093 | let Type::Enum(ty) = ty else { continue }; 2094 | // We expect the enum to have at least four variants, because 2095 | // that's how async fn enums currently work, which means we only 2096 | // have to handle one 2097 | // of the possible variantshapes. 2098 | let VariantShape::Many { variants, .. } = &ty.shape 2099 | else { continue }; 2100 | // We expect one of the variants' members to correspond to the state 2101 | // struct tid. 2102 | for v in variants.values() { 2103 | if v.member.type_id == state_tid { 2104 | // Wow! We found it! 2105 | // 2106 | // ... does it have decl coord information? 2107 | if v.member.decl_coord.is_useful() { 2108 | let d = &v.member.decl_coord; // shorthand 2109 | print!(" suspended at {}:", d.file.as_deref().unwrap_or("???")); 2110 | if let Some(n) = d.line { 2111 | print!("{n}"); 2112 | } else { 2113 | print!("???"); 2114 | } 2115 | // Be more tolerant of missing columns; they're often 2116 | // missing. 2117 | if let Some(n) = d.column { 2118 | print!(":{n}"); 2119 | } 2120 | println!(); 2121 | has_decl_coords = true; 2122 | // Stop processing things 2123 | break 'enumloop; 2124 | } 2125 | } 2126 | } 2127 | } 2128 | } 2129 | 2130 | 2131 | match name { 2132 | "lilos::time::sleep_until" | "lilos::exec::sleep_until" => { 2133 | match get_async_fn_local(value, "deadline") { 2134 | Ok(Some(deadline)) => { 2135 | if let Some(t) = deadline.newtype("lilos::time::TickTime") { 2136 | if let Some(t) = t.u64_value() { 2137 | print!(" sleeping until: {}", t); 2138 | if let Some(time) = time { 2139 | match time.cmp(&t) { 2140 | cmp::Ordering::Less => { 2141 | let n = t - time; 2142 | print!(" ({n} ms from now)"); 2143 | } 2144 | cmp::Ordering::Equal => { 2145 | print!(" (now)"); 2146 | } 2147 | cmp::Ordering::Greater => { 2148 | let n = time - t; 2149 | print!(" {}({n} ms ago!){}", 2150 | Colour::Red.prefix(), 2151 | Colour::Red.suffix()); 2152 | } 2153 | } 2154 | } 2155 | println!(); 2156 | } 2157 | } 2158 | return None; 2159 | } 2160 | Ok(None) => println!("no local"), 2161 | Err(e) => println!("{e:?}"), 2162 | } 2163 | } 2164 | "lilos::spsc::{impl#4}::pop" => { 2165 | match get_async_fn_local(value, "self") { 2166 | Ok(Some(shelf)) => { 2167 | if let Value::Pointer(p) = shelf { 2168 | match get_spsc_from_handle( 2169 | &ctx.segments, 2170 | db, 2171 | p.value, 2172 | p.dest_type_id, 2173 | ) { 2174 | Ok((qty, addr)) => { 2175 | println!(" waiting for data in spsc queue at {addr:#x}"); 2176 | println!(" queue type: {}", NamedGoff(db, qty)); 2177 | } 2178 | Err(e) => { 2179 | println!(" can't interpret: {e}"); 2180 | } 2181 | } 2182 | return None; 2183 | } 2184 | } 2185 | Ok(None) => println!("no local"), 2186 | Err(e) => println!("{e:?}"), 2187 | } 2188 | } 2189 | _ => (), 2190 | } 2191 | 2192 | match state { 2193 | AsyncFnState::Unresumed => { 2194 | println!(" future has not yet been polled"); 2195 | return None; 2196 | } 2197 | AsyncFnState::Returned => { 2198 | println!(" future has already resolved"); 2199 | return None; 2200 | } 2201 | AsyncFnState::Panicked => { 2202 | println!(" future panicked on previous poll"); 2203 | return None; 2204 | } 2205 | AsyncFnState::Suspend(n) => { 2206 | if !has_decl_coords { 2207 | println!(" suspended at await point {n}"); 2208 | } 2209 | } 2210 | } 2211 | 2212 | let mut awaitees = e.value.members_named("__awaitee"); 2213 | let Some(awaitee) = awaitees.next() else { 2214 | println!(" (stopped unexpectedly)"); 2215 | return None; 2216 | }; 2217 | if awaitees.next().is_some() { 2218 | println!(" (multiple __awaitee fields)"); 2219 | return None; 2220 | } 2221 | Some(awaitee) 2222 | } 2223 | 2224 | fn await_trace_handroll<'v>( 2225 | _ctx: &Ctx, 2226 | db: &DebugDb, 2227 | _time: Option, 2228 | value: &'v Value, 2229 | ) -> Option<&'v Value> { 2230 | let bold = ansi_term::Style::new().bold(); 2231 | 2232 | match value { 2233 | Value::Struct(s) => { 2234 | if s.name.starts_with("lilos::exec::Until<") { 2235 | if let Some(Value::Pointer(p)) = s.unique_member_named("notify") { 2236 | print!("{}notify ", bold.prefix()); 2237 | 2238 | let mut named = false; 2239 | for ar in db.entities_by_address(p.value) { 2240 | if ar.range.start == p.value { 2241 | if let EntityId::Var(v) = ar.entity { 2242 | let v = db.static_variable_by_id(v).unwrap(); 2243 | print!("{}", v.name); 2244 | named = true; 2245 | break; 2246 | } 2247 | } 2248 | } 2249 | 2250 | if !named { 2251 | print!("at {:#x}", p.value); 2252 | } 2253 | println!("{}", bold.suffix()); 2254 | 2255 | if let Some(cond) = s.unique_member_named("cond") { 2256 | println!(" predicate: {}", cond.type_name()); 2257 | } 2258 | return None; 2259 | } 2260 | } 2261 | } 2262 | _ => (), 2263 | } 2264 | 2265 | println!("{}hand-rolled future{}", bold.prefix(), bold.suffix()); 2266 | println!(" type: {}", value.type_name()); 2267 | None 2268 | } 2269 | 2270 | enum AsyncFnState { 2271 | Unresumed, 2272 | Returned, 2273 | Panicked, 2274 | Suspend(usize), 2275 | } 2276 | 2277 | fn get_async_fn_local<'e>(env: &'e Value, name: &str) -> Result, LocalError> { 2278 | let Value::Enum(env) = env else { return Err(LocalError::NotEnum) }; 2279 | let parts = Regex::new(ASYNC_FN_BLOCK_ENV).unwrap(); 2280 | let Some(_parts) = parts.captures(&env.name) else { 2281 | return Err(LocalError::NotAnEnv); 2282 | }; 2283 | 2284 | let mut members = env.value.members_named(name); 2285 | let Some(m) = members.next() else { return Ok(None) }; 2286 | //if members.next().is_some() { return Err(LocalError::Ambiguous); } 2287 | 2288 | Ok(Some(m)) 2289 | } 2290 | 2291 | #[derive(Copy, Clone, Debug)] 2292 | enum LocalError { 2293 | NotEnum, 2294 | NotAnEnv, 2295 | } 2296 | 2297 | fn find_time_vars(db: &DebugDb) -> Option { 2298 | let (tick, _) = db.unique_static_variable_by_name("lilos::time::TICK")?; 2299 | let epoch = db.unique_static_variable_by_name("lilos::time::EPOCH") 2300 | .map(|(id, _info)| id); 2301 | Some(TimeVars { tick, epoch }) 2302 | } 2303 | 2304 | struct TimeVars { 2305 | tick: VarId, 2306 | epoch: Option, 2307 | } 2308 | 2309 | fn get_time(machine: &M, db: &DebugDb, time_vars: &TimeVars) -> Result> { 2310 | let low = { 2311 | let tick = db.static_variable_by_id(time_vars.tick).unwrap(); 2312 | let tick_type = db.type_by_id(tick.type_id).unwrap(); 2313 | let low = core::sync::atomic::AtomicU32::from_state(machine, tick.location, db, tick_type)?; 2314 | low.load(Ordering::Relaxed) 2315 | }; 2316 | 2317 | let high = if let Some(epoch) = time_vars.epoch { 2318 | let ep = db.static_variable_by_id(epoch).unwrap(); 2319 | let ep_type = db.type_by_id(ep.type_id).unwrap(); 2320 | let high = core::sync::atomic::AtomicU32::from_state(machine, ep.location, db, ep_type)?; 2321 | high.load(Ordering::Relaxed) 2322 | } else { 2323 | 0 2324 | }; 2325 | 2326 | Ok(u64::from(low) | u64::from(high) << 32) 2327 | } 2328 | 2329 | fn get_spsc_from_handle(machine: &M, db: &DebugDb, address: u64, handle_tid: TypeId) -> Result<(TypeId, u64), LoadError> { 2330 | 2331 | let handle_ty = db.type_by_id(handle_tid).unwrap(); 2332 | let Type::Struct(handle_ty) = handle_ty else { 2333 | return Err(LoadError::NotAStruct); 2334 | }; 2335 | let Some(q_m) = handle_ty.unique_member("q") else { 2336 | return Err(LoadError::MissingMember("q".to_string())); 2337 | }; 2338 | let q_ty = db.type_by_id(q_m.type_id).unwrap(); 2339 | let p = value::Pointer::from_state(machine, address + q_m.location, db, q_ty)?; 2340 | Ok((p.dest_type_id, p.value)) 2341 | } 2342 | 2343 | fn get_stack_used(machine: &M, db: &DebugDb, unit: &str, value: u64) -> Result, LoadError> { 2344 | let stack_start = db.unique_raw_symbol_by_name("_stack_start").unwrap(); 2345 | 2346 | let stack_limit = db.unique_raw_symbol_by_name("_stack_limit"); 2347 | 2348 | let stack_base = if let Some(limit) = stack_limit { 2349 | // Trust an explicit symbol over any heuristic. 2350 | limit 2351 | } else { 2352 | let heap_start = db.unique_raw_symbol_by_name("__sheap").unwrap(); 2353 | 2354 | if stack_start > heap_start { 2355 | // Assume conventional memory layout (open to stack clash) 2356 | heap_start 2357 | } else { 2358 | println!("cannot determine maximum extent of stack."); 2359 | println!("stack does not grow toward heap, and no _stack_limit symbol is present."); 2360 | return Ok(None); 2361 | } 2362 | }; 2363 | 2364 | let (_, unit_ty) = db.types_by_name(unit).next().unwrap(); 2365 | let unit = unit_ty.byte_size(db).unwrap(); 2366 | let unit = usize::try_from(unit).unwrap(); 2367 | 2368 | for addr in (stack_base..stack_start).step_by(unit) { 2369 | let chunk = value::Base::from_state(machine, addr, db, unit_ty)?; 2370 | if chunk.as_u64().unwrap() != value { 2371 | // We've found the first written word of the stack! 2372 | return Ok(Some((stack_base, addr, stack_start))); 2373 | } 2374 | } 2375 | // If we fall out of the loop, it's because the entire stack has intact 2376 | // scribbles. 2377 | Ok(Some((stack_base, stack_start, stack_start))) 2378 | } 2379 | 2380 | fn cmd_stacktrace(db: &debugdb::DebugDb, ctx: &mut Ctx, args: &str) { 2381 | let verbose = args.trim() == "-v"; 2382 | let Some(mut pc) = ctx.program_counter() else { 2383 | println!("program counter not present in machine state"); 2384 | return; 2385 | }; 2386 | 2387 | let orig_regs = ctx.registers.clone(); 2388 | let mut ctx = scopeguard::guard(ctx, |ctx| { 2389 | ctx.registers = orig_regs; 2390 | }); 2391 | 2392 | println!("stack trace:"); 2393 | let bold = ansi_term::Style::new().bold(); 2394 | let dim = ansi_term::Style::new().dimmed(); 2395 | 2396 | loop { 2397 | let mut printed_info = false; 2398 | if let Ok(Some(trc)) = db.static_stack_for_pc(pc) { 2399 | let last = trc.len() - 1; 2400 | for (i, record) in trc.iter().rev().enumerate() { 2401 | let subp = db.subprogram_by_id(record.subprogram).unwrap(); 2402 | 2403 | if i != last { 2404 | print!("{:10} ", "(inline)"); 2405 | } else { 2406 | print!("{pc:#010x} "); 2407 | } 2408 | 2409 | if let Some(n) = &subp.name { 2410 | print!("{}", bold.prefix()); 2411 | if verbose { 2412 | print!("{n}"); 2413 | } else { 2414 | let (prefix, ellipsis) = n.split_once('<').map(|(before, _after)| (before, "<...>")).unwrap_or((n, "")); 2415 | print!("{prefix}{ellipsis}"); 2416 | } 2417 | println!("{}", bold.suffix()); 2418 | } else { 2419 | println!("{}", bold.paint("")); 2420 | } 2421 | print!("{}", dim.prefix()); 2422 | print!(" at {}:", record.file); 2423 | if let Some(line) = record.line { 2424 | print!("{}:", line); 2425 | } else { 2426 | print!("?:"); 2427 | } 2428 | if let Some(col) = record.column { 2429 | print!("{}", col); 2430 | } else { 2431 | print!("?"); 2432 | } 2433 | print!("{}", dim.suffix()); 2434 | println!(); 2435 | } 2436 | printed_info = true; 2437 | } else { 2438 | for e in db.entities_by_address(pc) { 2439 | let offset = pc - e.range.start; 2440 | if let EntityId::Prog(pid) = e.entity { 2441 | if let Some(p) = db.subprogram_by_id(pid) { 2442 | if let Some(n) = &p.name { 2443 | println!("{n} + {offset:#x}"); 2444 | if let Some(row) = db.lookup_line_row(pc) { 2445 | print!(" {}:", row.file); 2446 | if let Some(line) = row.line { 2447 | print!("{}:", line); 2448 | } else { 2449 | print!("?:"); 2450 | } 2451 | if let Some(col) = row.column { 2452 | print!("{}", col); 2453 | } else { 2454 | print!("?"); 2455 | } 2456 | println!(); 2457 | } 2458 | printed_info = true; 2459 | break; 2460 | } 2461 | } 2462 | } 2463 | } 2464 | } 2465 | if !printed_info { 2466 | println!("{pc:#010x} (unknown!)"); 2467 | } 2468 | 2469 | if verbose { 2470 | for (r, v) in &ctx.registers { 2471 | println!(" reg {r} = {v:#x}"); 2472 | } 2473 | } 2474 | 2475 | use gimli::UnwindSection; 2476 | let mut uctx = gimli::UnwindContext::new(); 2477 | let bases = gimli::BaseAddresses::default(); 2478 | let fde = match db.debug_frame.fde_for_address(&bases, pc, gimli::DebugFrame::cie_from_offset) { 2479 | Ok(fde) => fde, 2480 | Err(e) => { 2481 | println!("Unable to get FDE for address {pc:#x}: {e:?}"); 2482 | return; 2483 | } 2484 | }; 2485 | 2486 | let ra_reg = fde.cie().return_address_register(); 2487 | 2488 | let unwind = match fde.unwind_info_for_address(&db.debug_frame, &bases, &mut uctx, pc) { 2489 | Ok(u) => u, 2490 | Err(e) => { 2491 | println!("Unable to get unwind info for address {pc:#x}: {e:?}"); 2492 | return; 2493 | } 2494 | }; 2495 | 2496 | let caller_cfa = match unwind.cfa() { 2497 | gimli::CfaRule::RegisterAndOffset { register, offset } => { 2498 | let Some(regval) = ctx.register(register.0) else { 2499 | println!("register {} not found in machine state", register.0); 2500 | return; 2501 | }; 2502 | if *offset < 0 { 2503 | regval - (-*offset) as u64 2504 | } else { 2505 | regval + *offset as u64 2506 | } 2507 | } 2508 | other => panic!("unsupported CFA rule type: {:?}", other), 2509 | }; 2510 | let mut caller_regs: BTreeMap = BTreeMap::new(); 2511 | 2512 | // TODO ARM-specific 2513 | caller_regs.insert(13, caller_cfa); 2514 | 2515 | for (n, rule) in unwind.registers() { 2516 | let value = match rule { 2517 | gimli::RegisterRule::Offset(n) => { 2518 | let addr = if *n < 0 { 2519 | caller_cfa - (-n) as u64 2520 | } else { 2521 | caller_cfa + *n as u64 2522 | }; 2523 | // TODO: hmmmmm how do we know the size of registers 2524 | // This current code is a haaaack 2525 | if db.pointer_size() == 8 { 2526 | let t = db.types_by_name("u64").next().unwrap().1; 2527 | match u64::from_state(&ctx.segments, addr, db, t) { 2528 | Ok(x) => Some(x), 2529 | Err(e) => { 2530 | println!("{e:?}"); 2531 | return; 2532 | } 2533 | } 2534 | } else { 2535 | let t = db.types_by_name("u32").next().unwrap().1; 2536 | match u32::from_state(&ctx.segments, addr, db, t) { 2537 | Ok(x) => Some(x as u64), 2538 | Err(e) => { 2539 | println!("{e:?}"); 2540 | return; 2541 | } 2542 | } 2543 | } 2544 | } 2545 | gimli::RegisterRule::ValOffset(n) => { 2546 | Some(if *n < 0 { 2547 | caller_cfa - (-n) as u64 2548 | } else { 2549 | caller_cfa + *n as u64 2550 | }) 2551 | } 2552 | gimli::RegisterRule::SameValue => { 2553 | ctx.register(n.0) 2554 | } 2555 | gimli::RegisterRule::Register(n) => { 2556 | ctx.register(n.0) 2557 | } 2558 | _ => { 2559 | println!("unsupported reg rule {:?}", rule); 2560 | return; 2561 | } 2562 | }; 2563 | 2564 | let Some(value) = value else { 2565 | println!("data missing from machine snapshot"); 2566 | return; 2567 | }; 2568 | 2569 | caller_regs.insert(n.0, value); 2570 | } 2571 | 2572 | let Some(&return_address) = caller_regs.get(&ra_reg.0) else { 2573 | println!("return address missing from frame."); 2574 | return; 2575 | }; 2576 | 2577 | ctx.registers = caller_regs; 2578 | pc = return_address; 2579 | } 2580 | } 2581 | 2582 | fn cmd_reg(_db: &debugdb::DebugDb, ctx: &mut Ctx, args: &str) { 2583 | let mut words = args.split_whitespace(); 2584 | let Some(regnum_str) = words.next() else { 2585 | println!("missing required register number argument"); 2586 | return; 2587 | }; 2588 | let value_str = words.next(); 2589 | 2590 | let Ok(regnum) = parse_int::parse::(regnum_str) else { 2591 | println!("could not parse register: {regnum_str}"); 2592 | return; 2593 | }; 2594 | 2595 | if let Some(value_str) = value_str { 2596 | let Ok(value) = parse_int::parse::(value_str) else { 2597 | println!("could not parse value: {value_str}"); 2598 | return; 2599 | }; 2600 | ctx.registers.insert(regnum, value); 2601 | } else if let Some(x) = ctx.register(regnum) { 2602 | println!("register {regnum} = {x:#x}"); 2603 | } else { 2604 | println!("register {regnum} not present in machine state"); 2605 | } 2606 | } 2607 | -------------------------------------------------------------------------------- /src/bin/snaptool.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | use anyhow::Result; 3 | use std::path::PathBuf; 4 | 5 | #[derive(Debug, Parser)] 6 | struct Snaptool { 7 | #[clap(subcommand)] 8 | sub: Sub, 9 | } 10 | 11 | #[derive(Debug, Parser)] 12 | enum Sub { 13 | List { 14 | path: PathBuf, 15 | }, 16 | } 17 | 18 | fn main() -> Result<()> { 19 | let args = Snaptool::parse(); 20 | match args.sub { 21 | Sub::List { path } => { 22 | let file = std::fs::File::open(path)?; 23 | let snapshot = lilosdbg::load_snapshot(file)?; 24 | 25 | println!("snapshot format version: {}", 26 | snapshot.format_version()); 27 | println!(); 28 | 29 | if snapshot.has_registers() { 30 | println!("Register state:"); 31 | for (r, v) in snapshot.registers() { 32 | println!(" reg {r} = {v:#x}"); 33 | } 34 | } 35 | 36 | if snapshot.has_elf_files() { 37 | println!("ELF files:"); 38 | for (_, f) in snapshot.elf_files() { 39 | println!(" {f}"); 40 | } 41 | } 42 | 43 | let mut addr_width = 8 + 2; 44 | let mut size_width = 8; 45 | for (range, _info) in snapshot.ranges() { 46 | if *range.start() > u64::from(u32::MAX) || *range.end() > u64::from(u32::MAX) { 47 | addr_width = 16 + 2; 48 | } 49 | let n = range.end() - range.start() + 1; 50 | // Expensive hacks are convenient on std platforms: 51 | let decimal = format!("{}", n); 52 | size_width = size_width.max(decimal.len()); 53 | } 54 | println!("Segments:"); 55 | println!("{:addr_width$} {:addr_width$} {:>size_width$} SOURCE", 56 | "START", "END", "SIZE"); 57 | for (range, info) in snapshot.ranges() { 58 | let base = range.start(); 59 | let end = range.end(); 60 | let size = end - base + 1; 61 | let name = &info.name; 62 | println!("{base:#0addr_width$x} ..= {end:#0addr_width$x} {size:>size_width$} {name}"); 63 | } 64 | 65 | } 66 | } 67 | Ok(()) 68 | } 69 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::{io::{Read, Seek}, ops::RangeInclusive, collections::BTreeMap}; 2 | 3 | use rangemap::RangeInclusiveMap; 4 | use regex::Regex; 5 | use thiserror::Error; 6 | use zip::ZipArchive; 7 | 8 | pub fn load_snapshot( 9 | source: F, 10 | ) -> Result, SnapshotError> { 11 | let segname = Regex::new(r#"^([0-9a-fA-F]+)@([0-9a-fA-F]+)\.bin$"#).unwrap(); 12 | let comment_pattern = Regex::new(r#"^lilosdbg snapshot v([0-9]+)$"#).unwrap(); 13 | let mut archive = zip::ZipArchive::new(source)?; 14 | let comment = std::str::from_utf8(archive.comment()) 15 | .map_err(|_| SnapshotError::NotASnapshot)?; 16 | let comment_parts = comment_pattern.captures(comment) 17 | .ok_or(SnapshotError::NotASnapshot)?; 18 | 19 | let format_version = comment_parts[1].parse::() 20 | .map_err(|_| SnapshotError::NotASnapshot)?; 21 | match format_version { 22 | 1 => (), 23 | _ => return Err(SnapshotError::UnsupportedVersion(format_version)), 24 | } 25 | 26 | 27 | let mut elf_files = vec![]; 28 | let mut segment_files = vec![]; 29 | 30 | let mut registers = BTreeMap::new(); 31 | 32 | for i in 0..archive.len() { 33 | let mut file = archive.by_index(i)?; 34 | let name = file.name(); 35 | if let Some((root, rest)) = name.split_once('/') { 36 | if root == "seg" { 37 | if let Some(c) = segname.captures(rest) { 38 | let hex_address = &c[1]; 39 | let order = &c[2]; 40 | if let Ok(address) = u64::from_str_radix(hex_address, 16) { 41 | if let Ok(order) = u64::from_str_radix(order, 16) { 42 | if let Some(size_m1) = file.size().checked_sub(1) { 43 | let segrange = address..=address + size_m1; 44 | segment_files.push((segrange, order, i, name.to_string())); 45 | } 46 | } 47 | } 48 | } 49 | } else if root == "elf" && !rest.is_empty() { 50 | elf_files.push((i, name.to_string())); 51 | } 52 | } else if name == "registers.toml" { 53 | let mut contents = vec![]; 54 | file.read_to_end(&mut contents)?; 55 | let contents = std::str::from_utf8(&contents).map_err(SnapshotError::Reg)?; 56 | let r: BTreeMap = toml::de::from_str(contents).map_err(SnapshotError::RegToml)?; 57 | registers = r.into_iter().map(|(r, v)| { 58 | let r = r.parse::().map_err(SnapshotError::RegTomlKey)?; 59 | Ok::<_, SnapshotError>((r, v)) 60 | }).collect::>()?; 61 | } 62 | } 63 | 64 | elf_files.sort_by(|a, b| a.1.cmp(&b.1)); 65 | 66 | segment_files.sort_unstable_by_key(|(addrs, order, index, name)| (*addrs.start(), *order, *index, name.clone())); // sigh 67 | 68 | let mut segment_files_by_address = RangeInclusiveMap::new(); 69 | for (addrs, order, index, name) in segment_files { 70 | segment_files_by_address.insert(addrs.clone(), FileInfo { 71 | index, 72 | range: addrs, 73 | order, 74 | name: name.to_string(), 75 | }); 76 | } 77 | 78 | Ok(Snapshot { 79 | format_version, 80 | archive, 81 | elf_files, 82 | segment_files_by_address, 83 | registers, 84 | }) 85 | } 86 | 87 | #[derive(Debug, Error)] 88 | pub enum SnapshotError { 89 | #[error("this file is a ZIP file, but is not a snapshot")] 90 | NotASnapshot, 91 | #[error("snapshot is format version {0}, which we don't understand")] 92 | UnsupportedVersion(u64), 93 | #[error("ZIP file access or format error")] 94 | Zip(#[from] zip::result::ZipError), 95 | #[error("could not load register file as UTF-8")] 96 | Reg(#[source] std::str::Utf8Error), 97 | #[error("could not parse register file as TOML")] 98 | RegToml(#[source] toml::de::Error), 99 | #[error("could not parse register name as integer")] 100 | RegTomlKey(#[source] std::num::ParseIntError), 101 | #[error("problem accessing file within ZIP archive")] 102 | Io(#[from] std::io::Error), 103 | } 104 | 105 | pub struct Snapshot { 106 | format_version: u64, 107 | archive: ZipArchive, 108 | elf_files: Vec<(usize, String)>, 109 | segment_files_by_address: RangeInclusiveMap, 110 | registers: BTreeMap, 111 | } 112 | 113 | impl Snapshot { 114 | pub fn format_version(&self) -> u64 { 115 | self.format_version 116 | } 117 | 118 | pub fn ranges(&self) -> impl Iterator, &FileInfo)> { 119 | self.segment_files_by_address.iter().map(|(r, f)| (r.clone(), f)) 120 | } 121 | 122 | pub fn has_elf_files(&self) -> bool { 123 | !self.elf_files.is_empty() 124 | } 125 | 126 | pub fn elf_files(&self) -> impl Iterator { 127 | self.elf_files.iter() 128 | .map(|(i, name)| (*i, name.as_str())) 129 | } 130 | 131 | pub fn has_registers(&self) -> bool { 132 | !self.registers.is_empty() 133 | } 134 | 135 | pub fn registers(&self) -> impl Iterator + '_ { 136 | self.registers.iter().map(|(r, v)| (*r, *v)) 137 | } 138 | } 139 | 140 | #[derive(Debug, Clone, Eq, PartialEq)] 141 | pub struct FileInfo { 142 | pub index: usize, 143 | pub range: RangeInclusive, 144 | pub order: u64, 145 | pub name: String, 146 | } 147 | 148 | impl Snapshot { 149 | pub fn read(&mut self, address: u64, dest: &mut [u8]) -> Result { 150 | let len = u64::try_from(dest.len()).unwrap(); 151 | if len == 0 { 152 | return Ok(0); 153 | } 154 | 155 | let requested_range = address..=address + (len - 1); 156 | // Check if there are any gaps in the overlap, and truncate the read at 157 | // that point. 158 | let end = if let Some(gap) = self.segment_files_by_address.gaps(&requested_range).next() { 159 | *gap.start() 160 | } else { 161 | *requested_range.end() 162 | }; 163 | let available_size = end - address + 1; 164 | let read_size = usize::min( 165 | dest.len(), 166 | usize::try_from(available_size).unwrap_or(usize::MAX), 167 | ); 168 | 169 | let mut address = address; 170 | let mut dest = dest; 171 | // We can be confident there are no gaps now. 172 | for (overlap_range, info) in self.segment_files_by_address.overlapping(&(address..=end)) { 173 | // overlap_range is a subset of file_range because of how 174 | // RangeInclusiveMap works. 175 | // 176 | // file_range includes address because we've already checked for 177 | // gaps. 178 | 179 | // We want to transfer at most dest.len() bytes from this segment, 180 | // starting at `address`. We can compute our offset into the backing 181 | // segment file, which is _not the same_ as our offset into the 182 | // overlapping range from the iterator (because a larger segment may 183 | // be split by a smaller one): 184 | let segment_offset = address - info.range.start(); 185 | // Compute the number of bytes available in this overlap (which may 186 | // differ from the file range if something overlaps us): 187 | let range_len = usize::try_from(overlap_range.end() - address + 1).unwrap_or(usize::MAX); 188 | // And compute how much we can actually read: 189 | let chunk_len = usize::min(range_len, dest.len()); 190 | let (next, rest) = dest.split_at_mut(chunk_len); 191 | 192 | let mut file = self.archive.by_index(info.index)?; 193 | // ZipFile doesn't impl Seek. So, we need to skip to our segment 194 | // offset the hard way. TODO: this will probably become a 195 | // performance issue. 196 | skip(&mut file, segment_offset)?; 197 | file.read_exact(next)?; 198 | 199 | address += u64::try_from(chunk_len).unwrap(); 200 | dest = rest; 201 | } 202 | 203 | Ok(read_size) 204 | } 205 | 206 | pub fn file_by_index(&mut self, i: usize) -> impl Read + '_ { 207 | self.archive.by_index(i).unwrap() 208 | } 209 | } 210 | 211 | fn skip(file: &mut impl Read, mut amount: u64) -> Result<(), std::io::Error> { 212 | let mut discard = vec![0; 1024]; 213 | while amount > 0 { 214 | let chunk = usize::min( 215 | discard.len(), 216 | usize::try_from(amount).unwrap_or(usize::MAX), 217 | ); 218 | let discard = &mut discard[..chunk]; 219 | file.read_exact(discard)?; 220 | amount -= u64::try_from(chunk).unwrap(); 221 | } 222 | Ok(()) 223 | } 224 | 225 | #[cfg(test)] 226 | mod tests { 227 | } 228 | --------------------------------------------------------------------------------