├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.md └── src ├── commands ├── examine.rs ├── find.rs ├── frames.rs ├── info.rs ├── mod.rs ├── parser.rs └── print.rs ├── coredump.rs ├── main.rs └── memory.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 = "adler" 7 | version = "1.0.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 10 | 11 | [[package]] 12 | name = "aho-corasick" 13 | version = "0.7.19" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e" 16 | dependencies = [ 17 | "memchr", 18 | ] 19 | 20 | [[package]] 21 | name = "atty" 22 | version = "0.2.14" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 25 | dependencies = [ 26 | "hermit-abi", 27 | "libc", 28 | "winapi", 29 | ] 30 | 31 | [[package]] 32 | name = "autocfg" 33 | version = "1.1.0" 34 | source = "registry+https://github.com/rust-lang/crates.io-index" 35 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 36 | 37 | [[package]] 38 | name = "bitflags" 39 | version = "1.3.2" 40 | source = "registry+https://github.com/rust-lang/crates.io-index" 41 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 42 | 43 | [[package]] 44 | name = "byteorder" 45 | version = "1.4.3" 46 | source = "registry+https://github.com/rust-lang/crates.io-index" 47 | checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" 48 | 49 | [[package]] 50 | name = "cfg-if" 51 | version = "1.0.0" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 54 | 55 | [[package]] 56 | name = "clap" 57 | version = "4.0.22" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | checksum = "91b9970d7505127a162fdaa9b96428d28a479ba78c9ec7550a63a5d9863db682" 60 | dependencies = [ 61 | "atty", 62 | "bitflags", 63 | "clap_derive", 64 | "clap_lex", 65 | "once_cell", 66 | "strsim", 67 | "termcolor", 68 | ] 69 | 70 | [[package]] 71 | name = "clap_derive" 72 | version = "4.0.21" 73 | source = "registry+https://github.com/rust-lang/crates.io-index" 74 | checksum = "0177313f9f02afc995627906bbd8967e2be069f5261954222dac78290c2b9014" 75 | dependencies = [ 76 | "heck", 77 | "proc-macro-error", 78 | "proc-macro2", 79 | "quote", 80 | "syn", 81 | ] 82 | 83 | [[package]] 84 | name = "clap_lex" 85 | version = "0.3.0" 86 | source = "registry+https://github.com/rust-lang/crates.io-index" 87 | checksum = "0d4198f73e42b4936b35b5bb248d81d2b595ecb170da0bac7655c54eedfa8da8" 88 | dependencies = [ 89 | "os_str_bytes", 90 | ] 91 | 92 | [[package]] 93 | name = "colored" 94 | version = "2.0.0" 95 | source = "registry+https://github.com/rust-lang/crates.io-index" 96 | checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd" 97 | dependencies = [ 98 | "atty", 99 | "lazy_static", 100 | "winapi", 101 | ] 102 | 103 | [[package]] 104 | name = "crc32fast" 105 | version = "1.3.2" 106 | source = "registry+https://github.com/rust-lang/crates.io-index" 107 | checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" 108 | dependencies = [ 109 | "cfg-if", 110 | ] 111 | 112 | [[package]] 113 | name = "env_logger" 114 | version = "0.9.3" 115 | source = "registry+https://github.com/rust-lang/crates.io-index" 116 | checksum = "a12e6657c4c97ebab115a42dcee77225f7f482cdd841cf7088c657a42e9e00e7" 117 | dependencies = [ 118 | "atty", 119 | "humantime", 120 | "log", 121 | "regex", 122 | "termcolor", 123 | ] 124 | 125 | [[package]] 126 | name = "fallible-iterator" 127 | version = "0.2.0" 128 | source = "registry+https://github.com/rust-lang/crates.io-index" 129 | checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" 130 | 131 | [[package]] 132 | name = "flate2" 133 | version = "1.0.24" 134 | source = "registry+https://github.com/rust-lang/crates.io-index" 135 | checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6" 136 | dependencies = [ 137 | "crc32fast", 138 | "miniz_oxide", 139 | ] 140 | 141 | [[package]] 142 | name = "fnv" 143 | version = "1.0.7" 144 | source = "registry+https://github.com/rust-lang/crates.io-index" 145 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 146 | 147 | [[package]] 148 | name = "gimli" 149 | version = "0.26.2" 150 | source = "registry+https://github.com/rust-lang/crates.io-index" 151 | checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d" 152 | dependencies = [ 153 | "fallible-iterator", 154 | "indexmap", 155 | "stable_deref_trait", 156 | ] 157 | 158 | [[package]] 159 | name = "hashbrown" 160 | version = "0.12.3" 161 | source = "registry+https://github.com/rust-lang/crates.io-index" 162 | checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" 163 | 164 | [[package]] 165 | name = "heck" 166 | version = "0.4.0" 167 | source = "registry+https://github.com/rust-lang/crates.io-index" 168 | checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" 169 | 170 | [[package]] 171 | name = "hermit-abi" 172 | version = "0.1.19" 173 | source = "registry+https://github.com/rust-lang/crates.io-index" 174 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 175 | dependencies = [ 176 | "libc", 177 | ] 178 | 179 | [[package]] 180 | name = "hex" 181 | version = "0.4.3" 182 | source = "registry+https://github.com/rust-lang/crates.io-index" 183 | checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" 184 | 185 | [[package]] 186 | name = "humantime" 187 | version = "2.1.0" 188 | source = "registry+https://github.com/rust-lang/crates.io-index" 189 | checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" 190 | 191 | [[package]] 192 | name = "indexmap" 193 | version = "1.9.1" 194 | source = "registry+https://github.com/rust-lang/crates.io-index" 195 | checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" 196 | dependencies = [ 197 | "autocfg", 198 | "hashbrown", 199 | ] 200 | 201 | [[package]] 202 | name = "lazy_static" 203 | version = "1.4.0" 204 | source = "registry+https://github.com/rust-lang/crates.io-index" 205 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 206 | 207 | [[package]] 208 | name = "leb128" 209 | version = "0.2.5" 210 | source = "registry+https://github.com/rust-lang/crates.io-index" 211 | checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" 212 | 213 | [[package]] 214 | name = "libc" 215 | version = "0.2.137" 216 | source = "registry+https://github.com/rust-lang/crates.io-index" 217 | checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" 218 | 219 | [[package]] 220 | name = "log" 221 | version = "0.4.17" 222 | source = "registry+https://github.com/rust-lang/crates.io-index" 223 | checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" 224 | dependencies = [ 225 | "cfg-if", 226 | ] 227 | 228 | [[package]] 229 | name = "memchr" 230 | version = "2.5.0" 231 | source = "registry+https://github.com/rust-lang/crates.io-index" 232 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 233 | 234 | [[package]] 235 | name = "memmap" 236 | version = "0.7.0" 237 | source = "registry+https://github.com/rust-lang/crates.io-index" 238 | checksum = "6585fd95e7bb50d6cc31e20d4cf9afb4e2ba16c5846fc76793f11218da9c475b" 239 | dependencies = [ 240 | "libc", 241 | "winapi", 242 | ] 243 | 244 | [[package]] 245 | name = "minimal-lexical" 246 | version = "0.2.1" 247 | source = "registry+https://github.com/rust-lang/crates.io-index" 248 | checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" 249 | 250 | [[package]] 251 | name = "miniz_oxide" 252 | version = "0.5.4" 253 | source = "registry+https://github.com/rust-lang/crates.io-index" 254 | checksum = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34" 255 | dependencies = [ 256 | "adler", 257 | ] 258 | 259 | [[package]] 260 | name = "nom" 261 | version = "7.1.1" 262 | source = "registry+https://github.com/rust-lang/crates.io-index" 263 | checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36" 264 | dependencies = [ 265 | "memchr", 266 | "minimal-lexical", 267 | ] 268 | 269 | [[package]] 270 | name = "num_cpus" 271 | version = "1.14.0" 272 | source = "registry+https://github.com/rust-lang/crates.io-index" 273 | checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5" 274 | dependencies = [ 275 | "hermit-abi", 276 | "libc", 277 | ] 278 | 279 | [[package]] 280 | name = "object" 281 | version = "0.29.0" 282 | source = "registry+https://github.com/rust-lang/crates.io-index" 283 | checksum = "21158b2c33aa6d4561f1c0a6ea283ca92bc54802a93b263e910746d679a7eb53" 284 | dependencies = [ 285 | "flate2", 286 | "memchr", 287 | "wasmparser", 288 | ] 289 | 290 | [[package]] 291 | name = "once_cell" 292 | version = "1.16.0" 293 | source = "registry+https://github.com/rust-lang/crates.io-index" 294 | checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" 295 | 296 | [[package]] 297 | name = "os_str_bytes" 298 | version = "6.3.1" 299 | source = "registry+https://github.com/rust-lang/crates.io-index" 300 | checksum = "3baf96e39c5359d2eb0dd6ccb42c62b91d9678aa68160d261b9e0ccbf9e9dea9" 301 | 302 | [[package]] 303 | name = "proc-macro-error" 304 | version = "1.0.4" 305 | source = "registry+https://github.com/rust-lang/crates.io-index" 306 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 307 | dependencies = [ 308 | "proc-macro-error-attr", 309 | "proc-macro2", 310 | "quote", 311 | "syn", 312 | "version_check", 313 | ] 314 | 315 | [[package]] 316 | name = "proc-macro-error-attr" 317 | version = "1.0.4" 318 | source = "registry+https://github.com/rust-lang/crates.io-index" 319 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 320 | dependencies = [ 321 | "proc-macro2", 322 | "quote", 323 | "version_check", 324 | ] 325 | 326 | [[package]] 327 | name = "proc-macro2" 328 | version = "1.0.47" 329 | source = "registry+https://github.com/rust-lang/crates.io-index" 330 | checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" 331 | dependencies = [ 332 | "unicode-ident", 333 | ] 334 | 335 | [[package]] 336 | name = "quote" 337 | version = "1.0.21" 338 | source = "registry+https://github.com/rust-lang/crates.io-index" 339 | checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" 340 | dependencies = [ 341 | "proc-macro2", 342 | ] 343 | 344 | [[package]] 345 | name = "regex" 346 | version = "1.7.0" 347 | source = "registry+https://github.com/rust-lang/crates.io-index" 348 | checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a" 349 | dependencies = [ 350 | "aho-corasick", 351 | "memchr", 352 | "regex-syntax", 353 | ] 354 | 355 | [[package]] 356 | name = "regex-syntax" 357 | version = "0.6.28" 358 | source = "registry+https://github.com/rust-lang/crates.io-index" 359 | checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" 360 | 361 | [[package]] 362 | name = "stable_deref_trait" 363 | version = "1.2.0" 364 | source = "registry+https://github.com/rust-lang/crates.io-index" 365 | checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" 366 | 367 | [[package]] 368 | name = "strsim" 369 | version = "0.10.0" 370 | source = "registry+https://github.com/rust-lang/crates.io-index" 371 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 372 | 373 | [[package]] 374 | name = "syn" 375 | version = "1.0.103" 376 | source = "registry+https://github.com/rust-lang/crates.io-index" 377 | checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d" 378 | dependencies = [ 379 | "proc-macro2", 380 | "quote", 381 | "unicode-ident", 382 | ] 383 | 384 | [[package]] 385 | name = "termcolor" 386 | version = "1.1.3" 387 | source = "registry+https://github.com/rust-lang/crates.io-index" 388 | checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" 389 | dependencies = [ 390 | "winapi-util", 391 | ] 392 | 393 | [[package]] 394 | name = "threadpool" 395 | version = "1.8.1" 396 | source = "registry+https://github.com/rust-lang/crates.io-index" 397 | checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" 398 | dependencies = [ 399 | "num_cpus", 400 | ] 401 | 402 | [[package]] 403 | name = "unicode-ident" 404 | version = "1.0.5" 405 | source = "registry+https://github.com/rust-lang/crates.io-index" 406 | checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" 407 | 408 | [[package]] 409 | name = "version_check" 410 | version = "0.9.4" 411 | source = "registry+https://github.com/rust-lang/crates.io-index" 412 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 413 | 414 | [[package]] 415 | name = "wasm-edit" 416 | version = "0.1.6" 417 | source = "registry+https://github.com/rust-lang/crates.io-index" 418 | checksum = "f8cfeb0d6384a31f7b1f4f429469f1e9dc1e78f5f7aa8dd37aa7f88eb0d92487" 419 | dependencies = [ 420 | "byteorder", 421 | "clap", 422 | "env_logger", 423 | "leb128", 424 | "log", 425 | "nom", 426 | "num_cpus", 427 | "threadpool", 428 | ] 429 | 430 | [[package]] 431 | name = "wasmgdb" 432 | version = "0.1.5" 433 | dependencies = [ 434 | "colored", 435 | "env_logger", 436 | "gimli", 437 | "hex", 438 | "log", 439 | "nom", 440 | "wasm-edit", 441 | "wasmgdb_ddbug_parser", 442 | ] 443 | 444 | [[package]] 445 | name = "wasmgdb_ddbug_parser" 446 | version = "0.3.1" 447 | dependencies = [ 448 | "fnv", 449 | "gimli", 450 | "log", 451 | "memmap", 452 | "object", 453 | ] 454 | 455 | [[package]] 456 | name = "wasmparser" 457 | version = "0.57.0" 458 | source = "registry+https://github.com/rust-lang/crates.io-index" 459 | checksum = "32fddd575d477c6e9702484139cf9f23dcd554b06d185ed0f56c857dd3a47aa6" 460 | 461 | [[package]] 462 | name = "winapi" 463 | version = "0.3.9" 464 | source = "registry+https://github.com/rust-lang/crates.io-index" 465 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 466 | dependencies = [ 467 | "winapi-i686-pc-windows-gnu", 468 | "winapi-x86_64-pc-windows-gnu", 469 | ] 470 | 471 | [[package]] 472 | name = "winapi-i686-pc-windows-gnu" 473 | version = "0.4.0" 474 | source = "registry+https://github.com/rust-lang/crates.io-index" 475 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 476 | 477 | [[package]] 478 | name = "winapi-util" 479 | version = "0.1.5" 480 | source = "registry+https://github.com/rust-lang/crates.io-index" 481 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 482 | dependencies = [ 483 | "winapi", 484 | ] 485 | 486 | [[package]] 487 | name = "winapi-x86_64-pc-windows-gnu" 488 | version = "0.4.0" 489 | source = "registry+https://github.com/rust-lang/crates.io-index" 490 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 491 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "wasmgdb" 3 | version = "0.1.5" 4 | edition = "2021" 5 | description = "gdb for WebAssembly" 6 | authors = ["Sven Sauleau "] 7 | license = "MIT" 8 | keywords = ["wasm", "gdb", "debug", "coredump", "stack"] 9 | repository = "https://github.com/xtuc/wasmgdb" 10 | 11 | 12 | [dependencies] 13 | gimli = "0.26.2" 14 | wasm-edit = { version = "0.1.6" } 15 | colored = "2.0.0" 16 | env_logger = "0.9.1" 17 | wasmgdb_ddbug_parser = { path = "../../gimli-rs/ddbug/parser", version = "0.3.1" } 18 | log = "0.4.17" 19 | nom = "7.1.1" 20 | hex = "0.4.3" 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Coredump generation 2 | 3 | Moved to [https://github.com/xtuc/wasm-coredump]. 4 | -------------------------------------------------------------------------------- /src/commands/examine.rs: -------------------------------------------------------------------------------- 1 | use crate::commands::{Expr, PrintFormat}; 2 | use crate::{BoxError, Context}; 3 | use std::fmt::Write; 4 | 5 | pub(crate) fn examine<'a, R: gimli::Reader>( 6 | ctx: &Context, 7 | what: Expr<'a>, 8 | number: Option, 9 | format: Option, 10 | ) -> Result<(), BoxError> { 11 | let addr = if let Expr::Hex(addr) = what { 12 | addr 13 | } else { 14 | unreachable!(); 15 | }; 16 | 17 | let mut out = "".to_owned(); 18 | let number = number.unwrap_or_else(|| 8); 19 | 20 | for offset in 0..number { 21 | let v = ctx.coredump[addr as usize + offset as usize]; 22 | match format { 23 | Some(PrintFormat::String) => write!(out, "{}", v as char)?, 24 | _ => write!(out, "0x{} ", v)?, 25 | }; 26 | } 27 | 28 | match format { 29 | Some(PrintFormat::String) => println!("{} ({} char(s)) = {:?}", what, number, out), 30 | _ => println!("{} ({} byte(s)) = {}", what, number, out), 31 | } 32 | Ok(()) 33 | } 34 | -------------------------------------------------------------------------------- /src/commands/find.rs: -------------------------------------------------------------------------------- 1 | use crate::commands::Expr; 2 | use crate::{BoxError, Context}; 3 | use colored::Colorize; 4 | 5 | pub(crate) fn find<'a, R: gimli::Reader>( 6 | ctx: &Context, 7 | start: Option>, 8 | end: Option>, 9 | expr: Expr<'a>, 10 | ) -> Result<(), BoxError> { 11 | let start = if let Some(Expr::Hex(v)) = start { 12 | v as usize 13 | } else { 14 | 0 15 | }; 16 | let end = if let Some(Expr::Hex(v)) = end { 17 | v as usize 18 | } else { 19 | ctx.coredump.len() 20 | }; 21 | 22 | let search_bytes = expr_to_bytes(&expr)?; 23 | let mem = &ctx.coredump[start..end]; 24 | 25 | let mut offset = 0; 26 | let mut found = 0; 27 | let mut last_offset = 0; 28 | 29 | for window in mem.windows(search_bytes.len()) { 30 | if window == search_bytes { 31 | let v = format!("0x{:x}", offset); 32 | let distance_from_last = offset - last_offset; 33 | println!("{} after {} byte(s)", v.blue(), distance_from_last); 34 | 35 | found += 1; 36 | last_offset = offset; 37 | } 38 | 39 | offset += 1; 40 | } 41 | 42 | println!("{} pattern(s) found.", found); 43 | Ok(()) 44 | } 45 | 46 | fn expr_to_bytes<'a>(expr: &Expr<'a>) -> Result, BoxError> { 47 | use Expr::*; 48 | 49 | match expr { 50 | Hex(n) => Ok(n.to_le_bytes().to_vec()), 51 | Str(s) => Ok(s.as_bytes().to_vec()), 52 | _ => Err(format!("cannot turn {} into bytes", expr).into()), 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/commands/frames.rs: -------------------------------------------------------------------------------- 1 | use crate::{coredump, memory, BoxError, Context}; 2 | use colored::Colorize; 3 | 4 | pub(crate) fn backtrace( 5 | ctx: &Context, 6 | stack_frames: &Vec, 7 | ) -> Result<(), BoxError> { 8 | let mut i = stack_frames.len(); 9 | for frame in stack_frames { 10 | i -= 1; 11 | if let Some(selected_frame) = &ctx.selected_frame { 12 | if selected_frame.binary_name == frame.binary_name { 13 | print!("#{}*\t", i); 14 | } else { 15 | print!("#{}\t", i); 16 | } 17 | } else { 18 | print!("#{}\t", i); 19 | } 20 | 21 | print_frame(ctx, &frame)?; 22 | } 23 | 24 | Ok(()) 25 | } 26 | 27 | pub(crate) fn print_frame<'a, R: gimli::Reader>( 28 | ctx: &Context, 29 | frame: &coredump::StackFrame, 30 | ) -> Result<(), BoxError> { 31 | if let Some(func) = ctx.ddbug.functions_by_linkage_name.get(&frame.binary_name) { 32 | let source = format!( 33 | "{}/{}", 34 | func.source() 35 | .directory() 36 | .unwrap_or_else(|| ""), 37 | func.source().file().unwrap_or_else(|| "") 38 | ); 39 | 40 | let function = { 41 | let name = func.name().unwrap(); 42 | 43 | let params = func 44 | .details(&ctx.ddbug) 45 | .parameters() 46 | .iter() 47 | .map(|param| { 48 | let param_name = if let Some(name) = param.name() { 49 | name 50 | } else { 51 | "???" 52 | }; 53 | 54 | // TODO: not always 4 bytes, right? 55 | let size_of = 4; 56 | 57 | let value = if let Ok(addr) = memory::get_param_addr(frame, &func, param) { 58 | let bytes = memory::read(ctx.coredump, addr, size_of).unwrap(); 59 | format!("0x{}", hex::encode(&bytes)) 60 | } else { 61 | "???".to_owned() 62 | }; 63 | format!("{}={}", param_name.green(), value) 64 | }) 65 | .collect::>() 66 | .join(", "); 67 | 68 | format!("{} ({})", name.yellow(), params) 69 | }; 70 | 71 | let addr = format!("{:0>6}", frame.funcidx).blue(); 72 | println!("{} as {} at {}", addr, function, source); 73 | } else { 74 | // Functions that are generated by Wasi and don't have a source (ie 75 | // some Wasi transpolines) don't have a mapping in DWARF. 76 | let addr = format!("{:0>6}", frame.funcidx).blue(); 77 | println!("{} as {} at ", addr, frame.binary_name); 78 | } 79 | 80 | Ok(()) 81 | } 82 | 83 | pub(crate) fn select_frame( 84 | ctx: &mut Context, 85 | frame: &coredump::StackFrame, 86 | ) -> Result<(), BoxError> { 87 | // Clear previous selected scope 88 | ctx.variables.clear(); 89 | 90 | let func = ctx 91 | .ddbug 92 | .functions_by_linkage_name 93 | .get(&frame.binary_name) 94 | .ok_or(format!("function {} not found", frame.binary_name))?; 95 | 96 | for param in func.details(&ctx.ddbug).parameters() { 97 | if let Some(name) = param.name() { 98 | ctx.variables.insert(name.to_owned(), param.clone()); 99 | } 100 | } 101 | Ok(()) 102 | } 103 | -------------------------------------------------------------------------------- /src/commands/info.rs: -------------------------------------------------------------------------------- 1 | use super::Expr; 2 | use crate::memory; 3 | use crate::print_value; 4 | use crate::{BoxError, Context}; 5 | use colored::Colorize; 6 | 7 | pub(crate) fn info<'a, R: gimli::Reader>( 8 | ctx: &Context, 9 | what: &'a str, 10 | args: Vec, 11 | ) -> Result<(), BoxError> { 12 | match what { 13 | "types" => { 14 | if ctx.ddbug.types.len() == 0 { 15 | println!("no types defined."); 16 | } 17 | for (_, t) in &ctx.ddbug.types { 18 | println!("{}", t); 19 | } 20 | 21 | Ok(()) 22 | } 23 | 24 | "locals" => { 25 | let frame = ctx.selected_frame.as_ref().ok_or("no selected frame")?; 26 | let func = ctx 27 | .ddbug 28 | .functions_by_linkage_name 29 | .get(&frame.binary_name) 30 | .ok_or(format!("function {} not found", frame.binary_name))?; 31 | 32 | for (name, param) in &ctx.variables { 33 | let ty = param.ty(&ctx.ddbug).unwrap(); 34 | 35 | let addr = memory::get_param_addr(frame, func, ¶m)?; 36 | let value = print_value(ctx, addr, ty.as_ref(), 0)?; 37 | 38 | println!("{}: {}", name, value) 39 | } 40 | 41 | Ok(()) 42 | } 43 | 44 | "symbol" => { 45 | let funcidx = args.get(0).ok_or("no func address or index specified")?; 46 | let funcidx = if let Expr::Int(funcidx) = funcidx { 47 | *funcidx as u32 48 | } else { 49 | return Err("Func index must be specified".into()); 50 | }; 51 | 52 | let func_name = ctx 53 | .source 54 | .get_func_name(funcidx) 55 | .unwrap_or_else(|| "unknown".to_string()); 56 | 57 | if let Some(func) = ctx.ddbug.functions_by_linkage_name.get(&func_name) { 58 | let source = format!( 59 | "{}/{}", 60 | func.source() 61 | .directory() 62 | .unwrap_or_else(|| ""), 63 | func.source().file().unwrap_or_else(|| "") 64 | ); 65 | 66 | let name = func.name().unwrap(); 67 | println!("{} as {} at {}", funcidx.to_string().blue(), name, source); 68 | } else { 69 | println!( 70 | "{} as ??? ({}) at ", 71 | funcidx.to_string().blue(), 72 | func_name 73 | ); 74 | } 75 | Ok(()) 76 | } 77 | 78 | "imports" => { 79 | let imports = ctx.source.imports(); 80 | println!("{} import(s) =", imports.len()); 81 | for import in imports { 82 | println!("{}.{}", import.module, import.name) 83 | } 84 | 85 | Ok(()) 86 | } 87 | 88 | _ => Err(format!("info {} not implemented", what).into()), 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/commands/mod.rs: -------------------------------------------------------------------------------- 1 | //! Handles the parsing and execution of commands 2 | 3 | use std::fmt; 4 | 5 | use crate::{coredump, BoxError, Context}; 6 | 7 | mod examine; 8 | mod find; 9 | mod frames; 10 | mod info; 11 | pub(crate) mod parser; 12 | mod print; 13 | 14 | #[derive(Debug, PartialEq, Clone)] 15 | pub(crate) enum Expr<'a> { 16 | Name(&'a str), 17 | Hex(u64), 18 | Int(i64), 19 | Deref(Box>), 20 | Cast(&'a str, Box>), 21 | MemberAccess(Box>, &'a str), 22 | Str(&'a str), 23 | } 24 | 25 | impl<'a> Expr<'a> { 26 | pub(crate) fn object(&'a self) -> Option<&'a str> { 27 | match self { 28 | Expr::Str(_) | Expr::Cast(_, _) | Expr::Hex(_) | Expr::Int(_) => None, 29 | Expr::Name(n) => Some(n), 30 | Expr::Deref(t) => t.object(), 31 | Expr::MemberAccess(o, _) => o.object(), 32 | } 33 | } 34 | } 35 | 36 | impl<'a> fmt::Display for Expr<'a> { 37 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 38 | match self { 39 | Expr::Int(n) => write!(f, "{}", n), 40 | Expr::Hex(n) => write!(f, "0x{:x}", n), 41 | Expr::Name(v) => write!(f, "{}", v), 42 | Expr::Str(v) => write!(f, "\"{}\"", v), 43 | Expr::Cast(t, v) => write!(f, "({}) {}", t, v), 44 | Expr::Deref(t) => write!(f, "*{}", t), 45 | Expr::MemberAccess(expr, v) => write!(f, "{}.{}", expr, v), 46 | } 47 | } 48 | } 49 | 50 | #[derive(Debug, PartialEq, Clone)] 51 | pub(crate) enum Command<'a> { 52 | Unknown, 53 | Backtrace, 54 | SelectFrame(usize), 55 | Print(PrintFormat, Expr<'a>), 56 | Examine(Expr<'a>, (Option, Option)), 57 | Find(Option>, Option>, Expr<'a>), 58 | Info(&'a str, Vec>), 59 | } 60 | 61 | #[derive(Debug, PartialEq, Clone)] 62 | pub(crate) enum PrintFormat { 63 | None, 64 | String, 65 | } 66 | 67 | pub(crate) fn run_command( 68 | ctx: &mut Context, 69 | stack_frames: &Vec, 70 | cmd: Command, 71 | ) -> Result<(), BoxError> { 72 | match cmd { 73 | Command::Backtrace => { 74 | frames::backtrace(ctx, stack_frames)?; 75 | } 76 | 77 | Command::Examine(what, (number, format)) => { 78 | examine::examine(&ctx, what, number, format)?; 79 | } 80 | 81 | Command::Print(format, what) => { 82 | print::print(&ctx, format, what)?; 83 | } 84 | 85 | Command::Find(start, end, expr) => { 86 | find::find(&ctx, start, end, expr)?; 87 | } 88 | 89 | Command::Info(what, args) => { 90 | info::info(&ctx, what, args)?; 91 | } 92 | 93 | Command::SelectFrame(selected_frame) => { 94 | let stack_frame = &stack_frames[stack_frames.len() - 1 - selected_frame]; 95 | 96 | frames::print_frame(ctx, &stack_frame)?; 97 | frames::select_frame(ctx, &stack_frame)?; 98 | 99 | ctx.selected_frame = Some(stack_frame.clone()); 100 | } 101 | 102 | Command::Unknown => return Err("unknow command".into()), 103 | } 104 | 105 | Ok(()) 106 | } 107 | -------------------------------------------------------------------------------- /src/commands/parser.rs: -------------------------------------------------------------------------------- 1 | use nom::branch::alt; 2 | use nom::bytes::complete::tag; 3 | use nom::bytes::complete::take_until; 4 | use nom::bytes::complete::take_while1; 5 | use nom::character::complete::alpha1; 6 | use nom::character::complete::digit1; 7 | use nom::character::complete::space0; 8 | use nom::character::complete::space1; 9 | use nom::character::complete::{char, one_of}; 10 | use nom::character::is_alphabetic; 11 | use nom::combinator::map; 12 | use nom::combinator::opt; 13 | use nom::combinator::recognize; 14 | use nom::multi::{many0, many1}; 15 | use nom::sequence::delimited; 16 | use nom::sequence::pair; 17 | use nom::sequence::preceded; 18 | use nom::sequence::terminated; 19 | use nom::sequence::tuple; 20 | use nom::IResult; 21 | 22 | use crate::commands::{Command, Expr, PrintFormat}; 23 | 24 | fn ident<'a>(input: &'a str) -> IResult<&'a str, &'a str> { 25 | take_while1(|c: char| is_alphabetic(c as u8) || c == '_')(input) 26 | } 27 | 28 | fn parse_name_expr<'a>(input: &'a str) -> IResult<&'a str, Expr<'a>> { 29 | map(ident, |n| Expr::Name(n))(input) 30 | } 31 | 32 | fn parse_parens_expr<'a>(input: &'a str) -> IResult<&'a str, Expr<'a>> { 33 | delimited(tag("("), parse_expr, tag(")"))(input) 34 | } 35 | 36 | fn parse_str_expr<'a>(input: &'a str) -> IResult<&'a str, Expr<'a>> { 37 | map(delimited(tag("\""), take_until("\""), tag("\"")), |v| { 38 | Expr::Str(v) 39 | })(input) 40 | } 41 | 42 | fn parse_int_expr<'a>(input: &'a str) -> IResult<&'a str, Expr<'a>> { 43 | map(digit1, |v: &str| { 44 | let v = v.parse::().unwrap(); 45 | Expr::Int(v) 46 | })(input) 47 | } 48 | 49 | fn parse_hex_expr<'a>(input: &'a str) -> IResult<&'a str, Expr<'a>> { 50 | let hex = preceded( 51 | alt((tag("0x"), tag("0X"))), 52 | recognize(many1(terminated( 53 | one_of("0123456789abcdefABCDEF"), 54 | many0(char('_')), 55 | ))), 56 | ); 57 | map(hex, |v: &'a str| { 58 | let v = u64::from_str_radix(&str::replace(&v, "_", ""), 16).unwrap(); 59 | Expr::Hex(v) 60 | })(input) 61 | } 62 | 63 | fn parse_expr<'a>(input: &'a str) -> IResult<&'a str, Expr<'a>> { 64 | alt(( 65 | parse_expr_member_access, 66 | parse_expr_cast, 67 | parse_str_expr, 68 | parse_parens_expr, 69 | parse_expr_deref, 70 | parse_hex_expr, 71 | parse_name_expr, 72 | parse_int_expr, 73 | ))(input) 74 | } 75 | 76 | fn parse_expr_deref<'a>(input: &'a str) -> IResult<&'a str, Expr<'a>> { 77 | map(preceded(tag("*"), parse_expr), |expr| { 78 | Expr::Deref(Box::new(expr)) 79 | })(input) 80 | } 81 | 82 | fn parse_expr_cast<'a>(input: &'a str) -> IResult<&'a str, Expr<'a>> { 83 | let (input, type_) = delimited(tag("("), take_until(")"), tag(")"))(input)?; 84 | let (input, target) = preceded(space0, parse_expr)(input)?; 85 | Ok((input, Expr::Cast(type_, Box::new(target)))) 86 | } 87 | 88 | /// Member access lhs accepts a single expression or a parenthesed expression 89 | fn parse_expr_member_access<'a>(input: &'a str) -> IResult<&'a str, Expr<'a>> { 90 | let object = alt((parse_parens_expr, parse_name_expr)); 91 | 92 | map( 93 | tuple((object, tag("->"), ident)), 94 | |(object, _, member_access)| Expr::MemberAccess(Box::new(object), member_access), 95 | )(input) 96 | } 97 | 98 | pub(crate) fn parse_command<'a>(input: &'a str) -> IResult<&'a str, Command<'a>> { 99 | let (input, word) = alpha1(input)?; 100 | 101 | Ok(match word { 102 | "bt" => (input, Command::Backtrace), 103 | "x" => { 104 | let (input, params) = opt(preceded(tag("/"), pair(digit1, opt(alpha1))))(input)?; 105 | let params = if let Some((number, format)) = params { 106 | let number = number.parse::().unwrap(); 107 | let format = match format { 108 | Some("s") => Some(PrintFormat::String), 109 | Some(_) => unimplemented!(), 110 | _ => None, 111 | }; 112 | 113 | (Some(number), format) 114 | } else { 115 | (None, None) 116 | }; 117 | 118 | let (input, what) = preceded(space1, parse_expr)(input)?; 119 | (input, Command::Examine(what, params)) 120 | } 121 | "p" => { 122 | let (input, format) = opt(tag("/s"))(input)?; 123 | 124 | let format = if let Some(format) = format { 125 | match format { 126 | "/s" => PrintFormat::String, 127 | e => unimplemented!("unknow format {}", e), 128 | } 129 | } else { 130 | PrintFormat::None 131 | }; 132 | 133 | let (input, what) = preceded(space1, parse_expr)(input)?; 134 | (input, Command::Print(format, what)) 135 | } 136 | "f" => { 137 | let (input, n) = preceded(tag(" "), digit1)(input)?; 138 | let n = n.parse::().unwrap(); 139 | 140 | (input, Command::SelectFrame(n)) 141 | } 142 | "info" => { 143 | let (input, what) = preceded(space1, ident)(input)?; 144 | let (input, arg0) = opt(preceded(space1, parse_expr))(input)?; 145 | let args = if let Some(arg0) = arg0 { 146 | vec![arg0] 147 | } else { 148 | vec![] 149 | }; 150 | 151 | (input, Command::Info(what, args)) 152 | } 153 | "find" => { 154 | let start = opt(terminated(parse_expr, tag(", "))); 155 | let end = opt(terminated(parse_expr, tag(", "))); 156 | let (input, (start, end, expr)) = 157 | preceded(space0, tuple((start, end, parse_expr)))(input)?; 158 | 159 | (input, Command::Find(start, end, expr)) 160 | } 161 | _ => (input, Command::Unknown), 162 | }) 163 | } 164 | 165 | #[cfg(test)] 166 | mod tests { 167 | use super::*; 168 | 169 | #[test] 170 | fn test_expr_name() { 171 | use Expr::*; 172 | 173 | let (_, cmd) = parse_expr("test").unwrap(); 174 | assert_eq!(cmd, Name("test")); 175 | } 176 | 177 | #[test] 178 | fn test_expr_member_access() { 179 | use Expr::*; 180 | 181 | let (_, cmd) = parse_expr("test->ab_").unwrap(); 182 | assert_eq!(cmd, MemberAccess(Box::new(Name("test")), "ab_")); 183 | } 184 | 185 | #[test] 186 | fn test_expr_deref_name() { 187 | use Expr::*; 188 | 189 | let (_, cmd) = parse_expr("*test").unwrap(); 190 | assert_eq!(cmd, Deref(Box::new(Name("test")))); 191 | } 192 | 193 | #[test] 194 | fn test_expr_deref_member_access() { 195 | use Expr::*; 196 | 197 | let (_, cmd) = parse_expr("*test->ab").unwrap(); 198 | assert_eq!( 199 | cmd, 200 | Deref(Box::new(MemberAccess(Box::new(Name("test")), "ab"))) 201 | ); 202 | } 203 | 204 | #[test] 205 | fn test_expr_deref_parens_deref() { 206 | use Expr::*; 207 | 208 | let (_, cmd) = parse_expr("*(*test)").unwrap(); 209 | assert_eq!(cmd, Deref(Box::new(Deref(Box::new(Name("test")))))); 210 | } 211 | 212 | #[test] 213 | fn test_expr_deref_parens_member_access() { 214 | use Expr::*; 215 | 216 | let (_, cmd) = parse_expr("(*test)->ab").unwrap(); 217 | assert_eq!( 218 | cmd, 219 | MemberAccess(Box::new(Deref(Box::new(Name("test")))), "ab") 220 | ); 221 | } 222 | 223 | #[test] 224 | fn test_expr_cast() { 225 | use Expr::*; 226 | 227 | let (_, expr) = parse_expr("(float) var").unwrap(); 228 | assert_eq!(expr, Cast("float", Box::new(Name("var")))); 229 | 230 | let (_, expr) = parse_expr("(float)var").unwrap(); 231 | assert_eq!(expr, Cast("float", Box::new(Name("var")))); 232 | 233 | let (_, expr) = parse_expr("(a::float) 0x1").unwrap(); 234 | assert_eq!(expr, Cast("a::float", Box::new(Hex(1)))); 235 | } 236 | 237 | #[test] 238 | fn test_expr_member_access_cast() { 239 | use Expr::*; 240 | 241 | let (_, cmd) = parse_expr("((usize) 0x3)->ab").unwrap(); 242 | assert_eq!( 243 | cmd, 244 | MemberAccess(Box::new(Cast("usize", Box::new(Hex(3)))), "ab") 245 | ); 246 | } 247 | 248 | #[test] 249 | fn test_print_var() { 250 | use Command::*; 251 | use Expr::*; 252 | 253 | let (_, cmd) = parse_command("p var").unwrap(); 254 | assert_eq!(cmd, Print(PrintFormat::None, Name("var"))); 255 | } 256 | 257 | #[test] 258 | fn test_print_var_as_string() { 259 | use Command::*; 260 | use Expr::*; 261 | 262 | let (_, cmd) = parse_command("p/s var").unwrap(); 263 | assert_eq!(cmd, Print(PrintFormat::String, Name("var"))); 264 | } 265 | 266 | #[test] 267 | fn test_info_types() { 268 | use Command::*; 269 | 270 | let (_, cmd) = parse_command("info types").unwrap(); 271 | assert_eq!(cmd, Info("types", vec![])); 272 | } 273 | 274 | #[test] 275 | fn test_info_symbol() { 276 | use Command::*; 277 | 278 | let (_, cmd) = parse_command("info symbol 1234").unwrap(); 279 | assert_eq!(cmd, Info("symbol", vec![Expr::Int(1234)])); 280 | } 281 | 282 | #[test] 283 | fn test_find() { 284 | use Command::*; 285 | 286 | let (_, cmd) = parse_command("find \"a\"").unwrap(); 287 | assert_eq!(cmd, Find(None, None, Expr::Str("a"))); 288 | 289 | let (_, cmd) = parse_command("find 0x12").unwrap(); 290 | assert_eq!(cmd, Find(None, None, Expr::Hex(0x12))); 291 | } 292 | } 293 | -------------------------------------------------------------------------------- /src/commands/print.rs: -------------------------------------------------------------------------------- 1 | use crate::commands::{Expr, PrintFormat}; 2 | use crate::print_value; 3 | use crate::{coredump, memory, BoxError, Context}; 4 | use log::error; 5 | use std::fmt::Write; 6 | use wasmgdb_ddbug_parser as ddbug_parser; 7 | 8 | fn find_type_by_name<'a>( 9 | ddbug: &ddbug_parser::FileHash<'a>, 10 | name: &'_ str, 11 | ) -> Option<&'a ddbug_parser::Type<'a>> { 12 | for (_, t) in &ddbug.types { 13 | if t.to_string() == name { 14 | return Some(t); 15 | } 16 | } 17 | 18 | None 19 | } 20 | 21 | fn get_member<'a>( 22 | ty: ddbug_parser::Type<'a>, 23 | search: &str, 24 | ) -> Result, BoxError> { 25 | for member in ty.members() { 26 | if search == member.name().unwrap() { 27 | return Ok(member.clone()); 28 | } 29 | } 30 | 31 | Err(format!("member {} not found in object type {}", search, ty).into()) 32 | } 33 | 34 | struct EvaluationCtx<'a, 'b> { 35 | ddbug: &'b ddbug_parser::FileHash<'a>, 36 | coredump: &'a [u8], 37 | } 38 | 39 | struct EvaluationResult<'a> { 40 | addr: u32, 41 | ty: Option>, 42 | expr: Expr<'a>, 43 | } 44 | 45 | fn evaluate_expr<'a, 'b>( 46 | ctx: &'b EvaluationCtx<'a, 'b>, 47 | base_addr: u32, 48 | expr: Expr<'a>, 49 | expr_type: Option>, 50 | ) -> Result, BoxError> { 51 | match &expr { 52 | Expr::Int(_) | Expr::Str(_) => { 53 | unreachable!() 54 | } 55 | 56 | Expr::Name(_) => Ok(EvaluationResult { 57 | addr: base_addr, 58 | ty: Some(expr_type.unwrap()), 59 | expr, 60 | }), 61 | Expr::Hex(addr) => Ok(EvaluationResult { 62 | addr: *addr as u32, 63 | ty: expr_type, 64 | expr, 65 | }), 66 | 67 | Expr::Cast(typename, expr) => { 68 | let type_ = find_type_by_name(ctx.ddbug, typename) 69 | .ok_or(format!("type {} not found", typename))?; 70 | evaluate_expr(ctx, base_addr, *expr.clone(), Some(type_.to_owned())) 71 | } 72 | 73 | Expr::Deref(target) => { 74 | match expr_type.unwrap().kind() { 75 | ddbug_parser::TypeKind::Modifier(type_modifier) 76 | if type_modifier.kind() == ddbug_parser::TypeModifierKind::Pointer => 77 | { 78 | // *base_addr 79 | let addr = memory::read_ptr(ctx.coredump, base_addr)?; 80 | let ty = type_modifier 81 | .ty(&ctx.ddbug) 82 | .ok_or("unknown target type")? 83 | .into_owned(); 84 | 85 | Ok(EvaluationResult { 86 | addr, 87 | ty: Some(ty), 88 | expr: *target.clone(), 89 | }) 90 | } 91 | _ => return Err(format!("variable {} is not a ptr", target).into()), 92 | } 93 | } 94 | 95 | Expr::MemberAccess(base, member_access) => { 96 | // FIXME: assume for now base is the input expr. ie only works for one level of member. 97 | let base = evaluate_expr(ctx, base_addr, *base.clone(), expr_type)?; 98 | let member = get_member(base.ty.unwrap(), member_access)?; 99 | 100 | let addr = base.addr + member.data_location().unwrap() as u32; 101 | let ty = member.ty(&ctx.ddbug).unwrap().into_owned(); 102 | 103 | Ok(EvaluationResult { 104 | addr, 105 | ty: Some(ty), 106 | expr: Expr::Name(member_access), 107 | }) 108 | } 109 | } 110 | } 111 | 112 | pub(crate) fn print<'a, R: gimli::Reader>( 113 | ctx: &Context, 114 | format: PrintFormat, 115 | what: Expr<'a>, 116 | ) -> Result<(), BoxError> { 117 | if let Some(object) = what.object() { 118 | if let Some(variable) = ctx.variables.get(object) { 119 | let selected_frame = ctx 120 | .selected_frame 121 | .as_ref() 122 | .ok_or("no frame has been selected")?; 123 | let func = *ctx 124 | .ddbug 125 | .functions_by_linkage_name 126 | .get(&selected_frame.binary_name) 127 | .ok_or(format!("function {} not found", selected_frame.binary_name))?; 128 | 129 | let what_type = variable.ty(&ctx.ddbug).unwrap(); 130 | let base_addr = memory::get_param_addr(&selected_frame, &func, &variable)?; 131 | 132 | // Evaluate the `what` expression 133 | let eval_ctx = EvaluationCtx { 134 | ddbug: &ctx.ddbug, 135 | coredump: ctx.coredump, 136 | }; 137 | let result = evaluate_expr(&eval_ctx, base_addr, what, Some(what_type.into_owned()))?; 138 | 139 | match format { 140 | PrintFormat::String => { 141 | let ptr = memory::read_ptr(ctx.coredump, result.addr)?; 142 | 143 | let mut addr = ptr; 144 | let mut out = "".to_owned(); 145 | loop { 146 | let v = ctx.coredump[addr as usize]; 147 | if v == 0 { 148 | break; 149 | } 150 | write!(out, "{}", v as char)?; 151 | addr += 1; 152 | } 153 | 154 | println!("{} ({} char(s)) = {}", result.expr, out.len(), out); 155 | } 156 | 157 | PrintFormat::None => { 158 | if let Some(ty) = &result.ty { 159 | let out = print_value(&ctx, result.addr, ty, 0)?; 160 | println!("{} (0x{:x}): {}", result.expr, result.addr, out); 161 | } else { 162 | error!("don't know how to print value"); 163 | } 164 | } 165 | } 166 | } else { 167 | error!("variable {} not found", what); 168 | } 169 | } else { 170 | // Evaluate the `what` expression 171 | let eval_ctx = EvaluationCtx { 172 | ddbug: &ctx.ddbug, 173 | coredump: ctx.coredump, 174 | }; 175 | let result = evaluate_expr(&eval_ctx, 0, what, None)?; 176 | 177 | // FIXME: copy pasted from above 178 | match format { 179 | PrintFormat::String => { 180 | let ptr = memory::read_ptr(ctx.coredump, result.addr)?; 181 | 182 | let mut addr = ptr; 183 | let mut out = "".to_owned(); 184 | loop { 185 | let v = ctx.coredump[addr as usize]; 186 | if v == 0 { 187 | break; 188 | } 189 | write!(out, "{}", v as char)?; 190 | addr += 1; 191 | } 192 | 193 | println!("{} ({} char(s)) = {}", result.expr, out.len(), out); 194 | } 195 | 196 | PrintFormat::None => { 197 | if let Some(ty) = &result.ty { 198 | let out = print_value(&ctx, result.addr, ty, 0)?; 199 | println!("{} (0x{:x}): {}", result.expr, result.addr, out); 200 | } else { 201 | error!("don't know how to print value"); 202 | } 203 | } 204 | } 205 | } 206 | 207 | Ok(()) 208 | } 209 | -------------------------------------------------------------------------------- /src/coredump.rs: -------------------------------------------------------------------------------- 1 | //! Handles parsing of `wasm-edit` coredump 2 | 3 | use crate::BoxError; 4 | use log::debug; 5 | 6 | /// Struct collect while unwinding by wasm-edit 7 | // The stack frame relies on Wasm funcidx instead of instruction binary offset 8 | // because it has been buggy and hard to debug in the past. 9 | // TODO: eventually switch back to code offsets. 10 | #[derive(Debug, Clone)] 11 | pub(crate) struct StackFrame { 12 | pub(crate) funcidx: u32, 13 | pub(crate) binary_name: String, 14 | pub(crate) locals: Vec, 15 | } 16 | 17 | pub(crate) fn decode_coredump( 18 | source: &wasm_edit::traverse::WasmModule, 19 | coredump: &[u8], 20 | ) -> Result, BoxError> { 21 | let mut addr = 0usize; 22 | let nframe = u32::from_le_bytes(coredump[addr..addr + 4].try_into().unwrap()); 23 | addr += 4; 24 | let next_frame = u32::from_le_bytes(coredump[addr..addr + 4].try_into().unwrap()); 25 | addr += 4; 26 | 27 | debug!("number of frames: {}, next_frame: {}", nframe, next_frame); 28 | 29 | let mut stack_frames = Vec::with_capacity(nframe as usize); 30 | 31 | for i in 0..nframe { 32 | let funcidx = u32::from_le_bytes(coredump[addr..addr + 4].try_into().unwrap()); 33 | addr += 4; 34 | let count_local = u32::from_le_bytes(coredump[addr..addr + 4].try_into().unwrap()); 35 | addr += 4; 36 | 37 | let mut locals = Vec::with_capacity(count_local as usize); 38 | for _ in 0..count_local { 39 | let local = u32::from_le_bytes(coredump[addr..addr + 4].try_into().unwrap()); 40 | locals.push(local); 41 | addr += 4; 42 | } 43 | 44 | let frame = StackFrame { 45 | binary_name: source 46 | .get_func_name(funcidx) 47 | .unwrap_or_else(|| "unknown".to_string()), 48 | funcidx, 49 | locals, 50 | }; 51 | debug!("#{} stack frame {:?}", nframe - i - 1, frame); 52 | stack_frames.push(frame); 53 | } 54 | if nframe > 0 { 55 | assert_eq!(next_frame as usize, addr); 56 | } 57 | 58 | Ok(stack_frames) 59 | } 60 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use colored::Colorize; 2 | use log::{error, warn}; 3 | use std::collections::HashMap; 4 | use std::fmt::Write; 5 | use std::fs::File; 6 | use std::io; 7 | use std::io::prelude::*; 8 | use std::io::Write as IoWrite; 9 | use std::sync::Arc; 10 | use std::{borrow, env}; 11 | 12 | use wasmgdb_ddbug_parser as ddbug_parser; 13 | 14 | mod commands; 15 | mod coredump; 16 | mod memory; 17 | 18 | use commands::parser::parse_command; 19 | use commands::run_command; 20 | 21 | pub(crate) type BoxError = Box; 22 | 23 | pub(crate) fn print_value( 24 | ctx: &Context, 25 | addr: u32, 26 | type_: &ddbug_parser::Type, 27 | mut depth: usize, 28 | ) -> Result { 29 | let ident = "\t".repeat(depth); 30 | 31 | match &type_.kind() { 32 | ddbug_parser::TypeKind::Modifier(type_modifier) 33 | if type_modifier.kind() == ddbug_parser::TypeModifierKind::Pointer => 34 | { 35 | let target_type = if let Some(ty) = type_modifier.ty(&ctx.ddbug) { 36 | format!("{}", ty) 37 | } else { 38 | "???".to_owned() 39 | }; 40 | Ok(format!("{}*{} = 0x{:x}", ident, target_type.yellow(), addr)) 41 | } 42 | ddbug_parser::TypeKind::Base(base_type) => { 43 | let size_of = base_type.byte_size().unwrap_or(4); 44 | let mut bytes = memory::read(ctx.coredump, addr, size_of)?.to_vec(); 45 | bytes.reverse(); 46 | let value = match base_type.encoding() { 47 | ddbug_parser::BaseTypeEncoding::Boolean => { 48 | assert_eq!(bytes.len(), 1); 49 | 50 | if bytes[0] == 0x0 { 51 | "false".to_owned() 52 | } else { 53 | "true".to_owned() 54 | } 55 | } 56 | _ => format!("0x{}", hex::encode(&bytes)), 57 | }; 58 | Ok(format!( 59 | "{}{} = {}", 60 | ident, 61 | base_type.name().unwrap().yellow(), 62 | value 63 | )) 64 | } 65 | ddbug_parser::TypeKind::Struct(struct_type) => { 66 | let mut out = "".to_owned(); 67 | write!( 68 | out, 69 | "{}{} = {{", 70 | ident, 71 | struct_type.name().unwrap().yellow() 72 | )?; 73 | 74 | if depth < 1 { 75 | write!(out, "\n")?; 76 | 77 | depth += 1; 78 | for member in struct_type.members() { 79 | if let Some(member_type) = member.ty(&ctx.ddbug) { 80 | let addr = memory::get_member_addr(addr, member)?; 81 | let value = print_value(ctx, addr, member_type.as_ref(), depth)?; 82 | 83 | let ident = "\t".repeat(depth); 84 | let member_name = member.name().unwrap_or_else(|| "").green(); 85 | 86 | write!(out, "{}{} (0x{:x}): {}\n", ident, member_name, addr, value)?; 87 | } else { 88 | write!( 89 | out, 90 | "{}{} (0x{:x}): \n", 91 | ident, 92 | member.name().unwrap().green(), 93 | addr 94 | )?; 95 | } 96 | } 97 | } else { 98 | write!(out, "…")?; 99 | } 100 | write!(out, "}}")?; 101 | 102 | Ok(out) 103 | } 104 | ddbug_parser::TypeKind::Enumeration(enum_type) => { 105 | let size_of = enum_type.byte_size(&ctx.ddbug).unwrap(); 106 | let bytes = memory::read(ctx.coredump, addr, size_of)?.to_vec(); 107 | 108 | let value = 109 | get_enum_name(ctx, &enum_type, &bytes).unwrap_or_else(|| "".to_owned()); 110 | 111 | Ok(format!( 112 | "{}{} = {}", 113 | ident, 114 | enum_type.name().unwrap_or_default(), 115 | value 116 | )) 117 | } 118 | e => unimplemented!("{:?}", e), 119 | } 120 | } 121 | 122 | fn get_enum_name<'i, R: gimli::Reader>( 123 | ctx: &Context<'i, R>, 124 | ty: &ddbug_parser::EnumerationType<'i>, 125 | bytes: &[u8], 126 | ) -> Option { 127 | for item in ty.enumerators(&ctx.ddbug) { 128 | let item_value = item.value().unwrap_or_default(); 129 | let search = match ty.byte_size(&ctx.ddbug).unwrap() { 130 | 1 => bytes[0] as i64, 131 | 4 => i32::from_le_bytes(bytes.try_into().unwrap()) as i64, 132 | 8 => i64::from_le_bytes(bytes.try_into().unwrap()), 133 | n => unimplemented!("size {:?}", n), 134 | }; 135 | 136 | if item_value == search { 137 | return item.clone().name().map(|v| v.to_owned()); 138 | } 139 | } 140 | 141 | None 142 | } 143 | 144 | fn repl( 145 | coredump: &[u8], 146 | source: &wasm_edit::traverse::WasmModule, 147 | ddbug: ddbug_parser::FileHash<'_>, 148 | ) -> Result<(), BoxError> { 149 | let coredump_wasm = wasm_edit::parser::decode(&coredump) 150 | .map_err(|err| format!("failed to parse Wasm module: {}", err))?; 151 | let coredump_wasm = wasm_edit::traverse::WasmModule::new(Arc::new(coredump_wasm)); 152 | 153 | // Load a section and return as `Cow<[u8]>`. 154 | let load_section = |id: gimli::SectionId| -> Result, gimli::Error> { 155 | if let Some(bytes) = source.get_custom_section(id.name()) { 156 | Ok(borrow::Cow::from(bytes)) 157 | } else { 158 | warn!("DWARF section {} not found", id.name()); 159 | Ok(borrow::Cow::Borrowed(&[][..])) 160 | } 161 | }; 162 | 163 | let endian = gimli::RunTimeEndian::Little; 164 | 165 | // Load all of the sections. 166 | let dwarf_cow = gimli::Dwarf::load(&load_section)?; 167 | 168 | // Borrow a `Cow<[u8]>` to create an `EndianSlice`. 169 | let borrow_section: &dyn for<'a> Fn( 170 | &'a borrow::Cow<[u8]>, 171 | ) -> gimli::EndianSlice<'a, gimli::RunTimeEndian> = 172 | &|section| gimli::EndianSlice::new(&*section, endian); 173 | 174 | // Create `EndianSlice`s for all of the sections. 175 | let dwarf = Arc::new(dwarf_cow.borrow(&borrow_section)); 176 | 177 | let stack_frames = coredump_wasm 178 | .get_custom_section("core0") 179 | .ok_or("coredump is empty")?; 180 | let stack_frames = coredump::decode_coredump(source, &stack_frames)?; 181 | if stack_frames.len() == 0 { 182 | println!("No frames recorded"); 183 | } 184 | 185 | // Start REPL 186 | let mut ctx = Context { 187 | ddbug, 188 | coredump, 189 | source, 190 | dwarf: Arc::clone(&dwarf), 191 | selected_frame: None, 192 | variables: HashMap::new(), 193 | }; 194 | 195 | let stdin = io::stdin(); 196 | loop { 197 | print!("wasmgdb> "); 198 | io::stdout().flush().unwrap(); 199 | 200 | let line = stdin.lock().lines().next().unwrap()?; 201 | 202 | match parse_command(&line) { 203 | Ok((_, cmd)) => { 204 | if let Err(err) = run_command(&mut ctx, &stack_frames, cmd) { 205 | error!("failed to run command ({}): {}", line, err); 206 | } 207 | } 208 | Err(err) => { 209 | error!("error while parsing ({}): {}", line, err); 210 | } 211 | } 212 | } 213 | } 214 | 215 | pub(crate) struct Context<'a, R: gimli::Reader> { 216 | selected_frame: Option, 217 | /// Variables present in the selected scope 218 | variables: HashMap>, 219 | 220 | /// Process memory image. 221 | coredump: &'a [u8], 222 | 223 | /// DWARF types 224 | dwarf: Arc>, 225 | 226 | /// DWARF informations 227 | ddbug: ddbug_parser::FileHash<'a>, 228 | 229 | /// Source Wasm module 230 | source: &'a wasm_edit::traverse::WasmModule, 231 | } 232 | 233 | pub fn main() -> Result<(), BoxError> { 234 | env_logger::init(); 235 | 236 | let args: Vec = env::args().collect(); 237 | let coredump_filename = args[1].clone(); 238 | let source_filename = args[2].clone(); 239 | 240 | let mut coredump = Vec::new(); 241 | { 242 | let mut file = File::open(coredump_filename).expect("File not found"); 243 | file.read_to_end(&mut coredump) 244 | .expect("Error while reading file"); 245 | } 246 | 247 | let ctx = ddbug_parser::File::parse(source_filename.clone()).unwrap(); 248 | let ddbug = ddbug_parser::FileHash::new(ctx.file()); 249 | 250 | let mut source = Vec::new(); 251 | { 252 | let mut file = File::open(source_filename).expect("File not found"); 253 | file.read_to_end(&mut source) 254 | .expect("Error while reading file"); 255 | } 256 | 257 | let source = wasm_edit::parser::decode(&source) 258 | .map_err(|err| format!("failed to parse Wasm module: {}", err))?; 259 | let source = wasm_edit::traverse::WasmModule::new(Arc::new(source)); 260 | 261 | repl(&coredump, &source, ddbug) 262 | } 263 | -------------------------------------------------------------------------------- /src/memory.rs: -------------------------------------------------------------------------------- 1 | use crate::{coredump, BoxError}; 2 | use log::warn; 3 | use wasmgdb_ddbug_parser as ddbug_parser; 4 | 5 | /// Get the absolute addr of a member in memory 6 | pub(crate) fn get_member_addr<'a>( 7 | addr: u32, 8 | member: &ddbug_parser::Member<'a>, 9 | ) -> Result { 10 | let offset = member 11 | .data_location() 12 | .ok_or("no data location for member")?; 13 | Ok(addr + offset as u32) 14 | } 15 | 16 | /// Get the absolute addr of a function parameter in memory 17 | pub(crate) fn get_param_addr<'a>( 18 | frame: &coredump::StackFrame, 19 | func: &ddbug_parser::Function<'a>, 20 | param: &ddbug_parser::Parameter<'a>, 21 | ) -> Result { 22 | let location = param.data_location().ok_or("no data location for param")?; 23 | get_addr(frame, func, location) 24 | } 25 | 26 | /// Get the absolute addr in memory 27 | pub(crate) fn get_addr<'a>( 28 | frame: &coredump::StackFrame, 29 | func: &ddbug_parser::Function<'a>, 30 | location: &ddbug_parser::DataLocation, 31 | ) -> Result { 32 | let base = func.frame_base(); 33 | let base = base.as_ref().ok_or("func has no base addr")?; 34 | 35 | let offset_from_base = 36 | if let ddbug_parser::DataLocation::OffsetFromBase(offset_from_base) = location { 37 | offset_from_base 38 | } else { 39 | unimplemented!() 40 | }; 41 | 42 | match base { 43 | ddbug_parser::DataLocation::WasmLocal(base_local) => { 44 | if let Some(base_addr) = frame.locals.get(*base_local as usize) { 45 | Ok(base_addr + *offset_from_base as u32) 46 | } else { 47 | Err(format!("failed to load base addr in local {}", base_local).into()) 48 | } 49 | } 50 | e => Err(format!("get_addr {:?} not implemented", e).into()), 51 | } 52 | } 53 | 54 | pub(crate) fn read_ptr(coredump: &[u8], addr: u32) -> Result { 55 | let bytes = read(coredump, addr, 4)?; 56 | Ok(u32::from_le_bytes(bytes.try_into()?)) 57 | } 58 | 59 | pub(crate) fn read<'a>(coredump: &'a [u8], addr: u32, size: u64) -> Result<&'a [u8], BoxError> { 60 | Ok(&coredump[(addr as usize)..(addr as usize + size as usize)]) 61 | } 62 | --------------------------------------------------------------------------------