├── .cargo └── config.toml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── other_things ├── cpuid.c ├── debug_registers_experiment.c ├── echo_input ├── echo_input.c ├── expr_sketch.rs ├── interp_sketch.rs ├── poketext_experiment.cpp ├── pretty.txt ├── profiling.txt ├── questions_to_debugger_people.txt ├── singlestep_vs_group_stop ├── singlestep_vs_group_stop.cpp ├── some_dwarf_stats.txt ├── tui_sketch.rs └── varint_benchmark.c ├── release.sh ├── src ├── arena.rs ├── bin │ ├── demangler_exploration.rs │ ├── dwarf_exploration.rs │ ├── hashmap_vs_sort_bench.rs │ ├── load_symbols.rs │ └── ui_prototype.rs ├── common_ui.rs ├── context.rs ├── core_dumper.rs ├── debugger.rs ├── disassembly.rs ├── doc.rs ├── dwarf.rs ├── elf.rs ├── error.rs ├── executor.rs ├── expr.rs ├── imgui.rs ├── interp.rs ├── layout.rs ├── lib.rs ├── log.rs ├── main.rs ├── persistent.rs ├── pool.rs ├── pretty.rs ├── process_info.rs ├── procfs.rs ├── range_index.rs ├── registers.rs ├── search.rs ├── settings.rs ├── symbols.rs ├── symbols_registry.rs ├── terminal.rs ├── types.rs ├── ui.rs ├── unwind.rs ├── util.rs └── widgets.rs ├── testprogs ├── absl │ ├── CMakeLists.txt │ ├── build.sh │ └── containers.cpp ├── arrays.cpp ├── build.sh ├── containers.cpp ├── containers_rs.rs ├── containers_zig.zig ├── exception.cpp ├── global_variables.cpp ├── its_a_trap.c ├── many_threads.cpp ├── panic.rs ├── self_reference.cpp ├── signal_handler_with_threads.c ├── simple_async_rs.rs ├── simple_codegen.c ├── simple_coro.cpp ├── simple_inline.c ├── simple_loop.cpp ├── simple_mutex.cpp ├── simple_recursion.c ├── simple_signal_handler.c ├── simple_simd.c ├── simple_threads.cpp ├── simple_tls.cpp ├── string.cpp ├── string_rs.rs ├── structs_with_same_name.cpp ├── tiny.c ├── types.cpp ├── types_across_units │ ├── a.c │ ├── a.o │ ├── b.c │ ├── b.o │ ├── build.sh │ ├── main │ ├── main.c │ ├── main.o │ └── p.h ├── types_rs.rs └── vdso.c └── todo /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | target = "x86_64-unknown-linux-musl" 3 | rustflags = [ 4 | "-C", "force-unwind-tables=yes", # backtrace on panic doen't work in debug mode without this (but works in release for some reason) 5 | "-C", "force-frame-pointers=yes", # make profilers work slightly better, maybe 6 | ] 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /DWARF5.pdf 2 | /ignored_in_dropbox.txt 3 | /target 4 | /testprogs/build 5 | /testprogs/absl/build 6 | /testprogs/absl/lib 7 | /strategy_post_draft.txt 8 | /temp_post.txt 9 | -------------------------------------------------------------------------------- /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 = "adler2" 7 | version = "2.0.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" 10 | 11 | [[package]] 12 | name = "base64" 13 | version = "0.22.1" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" 16 | 17 | [[package]] 18 | name = "bitflags" 19 | version = "1.3.2" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 22 | 23 | [[package]] 24 | name = "bytes" 25 | version = "1.10.0" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9" 28 | 29 | [[package]] 30 | name = "cc" 31 | version = "1.2.15" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "c736e259eea577f443d5c86c304f9f4ae0295c43f3ba05c21f1d66b5f06001af" 34 | dependencies = [ 35 | "shlex", 36 | ] 37 | 38 | [[package]] 39 | name = "cfg-if" 40 | version = "1.0.0" 41 | source = "registry+https://github.com/rust-lang/crates.io-index" 42 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 43 | 44 | [[package]] 45 | name = "cpp_demangle" 46 | version = "0.4.4" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "96e58d342ad113c2b878f16d5d034c03be492ae460cdbc02b7f0f2284d310c7d" 49 | dependencies = [ 50 | "cfg-if", 51 | ] 52 | 53 | [[package]] 54 | name = "crc32fast" 55 | version = "1.3.2" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" 58 | dependencies = [ 59 | "cfg-if", 60 | ] 61 | 62 | [[package]] 63 | name = "equivalent" 64 | version = "1.0.1" 65 | source = "registry+https://github.com/rust-lang/crates.io-index" 66 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 67 | 68 | [[package]] 69 | name = "fallible-iterator" 70 | version = "0.3.0" 71 | source = "registry+https://github.com/rust-lang/crates.io-index" 72 | checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" 73 | 74 | [[package]] 75 | name = "flate2" 76 | version = "1.0.35" 77 | source = "registry+https://github.com/rust-lang/crates.io-index" 78 | checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" 79 | dependencies = [ 80 | "crc32fast", 81 | "miniz_oxide", 82 | ] 83 | 84 | [[package]] 85 | name = "fnv" 86 | version = "1.0.7" 87 | source = "registry+https://github.com/rust-lang/crates.io-index" 88 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 89 | 90 | [[package]] 91 | name = "getrandom" 92 | version = "0.2.10" 93 | source = "registry+https://github.com/rust-lang/crates.io-index" 94 | checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" 95 | dependencies = [ 96 | "cfg-if", 97 | "libc", 98 | "wasi", 99 | ] 100 | 101 | [[package]] 102 | name = "gimli" 103 | version = "0.31.1" 104 | source = "registry+https://github.com/rust-lang/crates.io-index" 105 | checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" 106 | dependencies = [ 107 | "fallible-iterator", 108 | "indexmap", 109 | "stable_deref_trait", 110 | ] 111 | 112 | [[package]] 113 | name = "hashbrown" 114 | version = "0.15.2" 115 | source = "registry+https://github.com/rust-lang/crates.io-index" 116 | checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" 117 | 118 | [[package]] 119 | name = "http" 120 | version = "1.2.0" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea" 123 | dependencies = [ 124 | "bytes", 125 | "fnv", 126 | "itoa", 127 | ] 128 | 129 | [[package]] 130 | name = "httparse" 131 | version = "1.10.0" 132 | source = "registry+https://github.com/rust-lang/crates.io-index" 133 | checksum = "f2d708df4e7140240a16cd6ab0ab65c972d7433ab77819ea693fde9c43811e2a" 134 | 135 | [[package]] 136 | name = "iced-x86" 137 | version = "1.19.0" 138 | source = "registry+https://github.com/rust-lang/crates.io-index" 139 | checksum = "b7cc8d38244d84278262c8ebe6930cc44283d194cbabae2651f6112103802fb5" 140 | dependencies = [ 141 | "lazy_static", 142 | ] 143 | 144 | [[package]] 145 | name = "indexmap" 146 | version = "2.6.0" 147 | source = "registry+https://github.com/rust-lang/crates.io-index" 148 | checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" 149 | dependencies = [ 150 | "equivalent", 151 | "hashbrown", 152 | ] 153 | 154 | [[package]] 155 | name = "itoa" 156 | version = "1.0.14" 157 | source = "registry+https://github.com/rust-lang/crates.io-index" 158 | checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" 159 | 160 | [[package]] 161 | name = "lazy_static" 162 | version = "1.4.0" 163 | source = "registry+https://github.com/rust-lang/crates.io-index" 164 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 165 | 166 | [[package]] 167 | name = "libc" 168 | version = "0.2.170" 169 | source = "registry+https://github.com/rust-lang/crates.io-index" 170 | checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828" 171 | 172 | [[package]] 173 | name = "log" 174 | version = "0.4.26" 175 | source = "registry+https://github.com/rust-lang/crates.io-index" 176 | checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" 177 | 178 | [[package]] 179 | name = "md5" 180 | version = "0.7.0" 181 | source = "registry+https://github.com/rust-lang/crates.io-index" 182 | checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" 183 | 184 | [[package]] 185 | name = "miniz_oxide" 186 | version = "0.8.5" 187 | source = "registry+https://github.com/rust-lang/crates.io-index" 188 | checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5" 189 | dependencies = [ 190 | "adler2", 191 | ] 192 | 193 | [[package]] 194 | name = "nnd" 195 | version = "0.1.0" 196 | dependencies = [ 197 | "bitflags", 198 | "cpp_demangle", 199 | "crc32fast", 200 | "flate2", 201 | "gimli", 202 | "iced-x86", 203 | "libc", 204 | "md5", 205 | "rand", 206 | "rustc-demangle", 207 | "unicode-segmentation", 208 | "unicode-width", 209 | "ureq", 210 | ] 211 | 212 | [[package]] 213 | name = "once_cell" 214 | version = "1.20.3" 215 | source = "registry+https://github.com/rust-lang/crates.io-index" 216 | checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" 217 | 218 | [[package]] 219 | name = "percent-encoding" 220 | version = "2.3.1" 221 | source = "registry+https://github.com/rust-lang/crates.io-index" 222 | checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" 223 | 224 | [[package]] 225 | name = "ppv-lite86" 226 | version = "0.2.17" 227 | source = "registry+https://github.com/rust-lang/crates.io-index" 228 | checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" 229 | 230 | [[package]] 231 | name = "rand" 232 | version = "0.8.5" 233 | source = "registry+https://github.com/rust-lang/crates.io-index" 234 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 235 | dependencies = [ 236 | "libc", 237 | "rand_chacha", 238 | "rand_core", 239 | ] 240 | 241 | [[package]] 242 | name = "rand_chacha" 243 | version = "0.3.1" 244 | source = "registry+https://github.com/rust-lang/crates.io-index" 245 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 246 | dependencies = [ 247 | "ppv-lite86", 248 | "rand_core", 249 | ] 250 | 251 | [[package]] 252 | name = "rand_core" 253 | version = "0.6.4" 254 | source = "registry+https://github.com/rust-lang/crates.io-index" 255 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 256 | dependencies = [ 257 | "getrandom", 258 | ] 259 | 260 | [[package]] 261 | name = "ring" 262 | version = "0.17.11" 263 | source = "registry+https://github.com/rust-lang/crates.io-index" 264 | checksum = "da5349ae27d3887ca812fb375b45a4fbb36d8d12d2df394968cd86e35683fe73" 265 | dependencies = [ 266 | "cc", 267 | "cfg-if", 268 | "getrandom", 269 | "libc", 270 | "untrusted", 271 | "windows-sys", 272 | ] 273 | 274 | [[package]] 275 | name = "rustc-demangle" 276 | version = "0.1.23" 277 | source = "registry+https://github.com/rust-lang/crates.io-index" 278 | checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" 279 | 280 | [[package]] 281 | name = "rustls" 282 | version = "0.23.23" 283 | source = "registry+https://github.com/rust-lang/crates.io-index" 284 | checksum = "47796c98c480fce5406ef69d1c76378375492c3b0a0de587be0c1d9feb12f395" 285 | dependencies = [ 286 | "log", 287 | "once_cell", 288 | "ring", 289 | "rustls-pki-types", 290 | "rustls-webpki", 291 | "subtle", 292 | "zeroize", 293 | ] 294 | 295 | [[package]] 296 | name = "rustls-pemfile" 297 | version = "2.2.0" 298 | source = "registry+https://github.com/rust-lang/crates.io-index" 299 | checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" 300 | dependencies = [ 301 | "rustls-pki-types", 302 | ] 303 | 304 | [[package]] 305 | name = "rustls-pki-types" 306 | version = "1.11.0" 307 | source = "registry+https://github.com/rust-lang/crates.io-index" 308 | checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" 309 | 310 | [[package]] 311 | name = "rustls-webpki" 312 | version = "0.102.8" 313 | source = "registry+https://github.com/rust-lang/crates.io-index" 314 | checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" 315 | dependencies = [ 316 | "ring", 317 | "rustls-pki-types", 318 | "untrusted", 319 | ] 320 | 321 | [[package]] 322 | name = "shlex" 323 | version = "1.3.0" 324 | source = "registry+https://github.com/rust-lang/crates.io-index" 325 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 326 | 327 | [[package]] 328 | name = "stable_deref_trait" 329 | version = "1.2.0" 330 | source = "registry+https://github.com/rust-lang/crates.io-index" 331 | checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" 332 | 333 | [[package]] 334 | name = "subtle" 335 | version = "2.6.1" 336 | source = "registry+https://github.com/rust-lang/crates.io-index" 337 | checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" 338 | 339 | [[package]] 340 | name = "unicode-segmentation" 341 | version = "1.10.1" 342 | source = "registry+https://github.com/rust-lang/crates.io-index" 343 | checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" 344 | 345 | [[package]] 346 | name = "unicode-width" 347 | version = "0.1.10" 348 | source = "registry+https://github.com/rust-lang/crates.io-index" 349 | checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" 350 | 351 | [[package]] 352 | name = "untrusted" 353 | version = "0.9.0" 354 | source = "registry+https://github.com/rust-lang/crates.io-index" 355 | checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" 356 | 357 | [[package]] 358 | name = "ureq" 359 | version = "3.0.6" 360 | source = "registry+https://github.com/rust-lang/crates.io-index" 361 | checksum = "ca2e2dbdf4e95780e5d41804fab88b928a24585721018409eb429b1d29356bde" 362 | dependencies = [ 363 | "base64", 364 | "flate2", 365 | "log", 366 | "percent-encoding", 367 | "rustls", 368 | "rustls-pemfile", 369 | "rustls-pki-types", 370 | "ureq-proto", 371 | "utf-8", 372 | "webpki-roots", 373 | ] 374 | 375 | [[package]] 376 | name = "ureq-proto" 377 | version = "0.3.0" 378 | source = "registry+https://github.com/rust-lang/crates.io-index" 379 | checksum = "2c51fe73e1d8c4e06bb2698286f7e7453c6fc90528d6d2e7fc36bb4e87fe09b1" 380 | dependencies = [ 381 | "base64", 382 | "http", 383 | "httparse", 384 | "log", 385 | ] 386 | 387 | [[package]] 388 | name = "utf-8" 389 | version = "0.7.6" 390 | source = "registry+https://github.com/rust-lang/crates.io-index" 391 | checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" 392 | 393 | [[package]] 394 | name = "wasi" 395 | version = "0.11.0+wasi-snapshot-preview1" 396 | source = "registry+https://github.com/rust-lang/crates.io-index" 397 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 398 | 399 | [[package]] 400 | name = "webpki-roots" 401 | version = "0.26.8" 402 | source = "registry+https://github.com/rust-lang/crates.io-index" 403 | checksum = "2210b291f7ea53617fbafcc4939f10914214ec15aace5ba62293a668f322c5c9" 404 | dependencies = [ 405 | "rustls-pki-types", 406 | ] 407 | 408 | [[package]] 409 | name = "windows-sys" 410 | version = "0.52.0" 411 | source = "registry+https://github.com/rust-lang/crates.io-index" 412 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 413 | dependencies = [ 414 | "windows-targets", 415 | ] 416 | 417 | [[package]] 418 | name = "windows-targets" 419 | version = "0.52.6" 420 | source = "registry+https://github.com/rust-lang/crates.io-index" 421 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 422 | dependencies = [ 423 | "windows_aarch64_gnullvm", 424 | "windows_aarch64_msvc", 425 | "windows_i686_gnu", 426 | "windows_i686_gnullvm", 427 | "windows_i686_msvc", 428 | "windows_x86_64_gnu", 429 | "windows_x86_64_gnullvm", 430 | "windows_x86_64_msvc", 431 | ] 432 | 433 | [[package]] 434 | name = "windows_aarch64_gnullvm" 435 | version = "0.52.6" 436 | source = "registry+https://github.com/rust-lang/crates.io-index" 437 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 438 | 439 | [[package]] 440 | name = "windows_aarch64_msvc" 441 | version = "0.52.6" 442 | source = "registry+https://github.com/rust-lang/crates.io-index" 443 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 444 | 445 | [[package]] 446 | name = "windows_i686_gnu" 447 | version = "0.52.6" 448 | source = "registry+https://github.com/rust-lang/crates.io-index" 449 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 450 | 451 | [[package]] 452 | name = "windows_i686_gnullvm" 453 | version = "0.52.6" 454 | source = "registry+https://github.com/rust-lang/crates.io-index" 455 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 456 | 457 | [[package]] 458 | name = "windows_i686_msvc" 459 | version = "0.52.6" 460 | source = "registry+https://github.com/rust-lang/crates.io-index" 461 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 462 | 463 | [[package]] 464 | name = "windows_x86_64_gnu" 465 | version = "0.52.6" 466 | source = "registry+https://github.com/rust-lang/crates.io-index" 467 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 468 | 469 | [[package]] 470 | name = "windows_x86_64_gnullvm" 471 | version = "0.52.6" 472 | source = "registry+https://github.com/rust-lang/crates.io-index" 473 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 474 | 475 | [[package]] 476 | name = "windows_x86_64_msvc" 477 | version = "0.52.6" 478 | source = "registry+https://github.com/rust-lang/crates.io-index" 479 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 480 | 481 | [[package]] 482 | name = "zeroize" 483 | version = "1.8.1" 484 | source = "registry+https://github.com/rust-lang/crates.io-index" 485 | checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" 486 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "nnd" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [profile.dev] 7 | panic = 'abort' 8 | opt-level = 1 9 | 10 | [profile.release] 11 | panic = 'abort' 12 | #debug = true 13 | 14 | [profile.dbgo] 15 | inherits = "release" 16 | debug = true 17 | debug-assertions = true 18 | overflow-checks = true 19 | 20 | [dependencies] 21 | libc = "0.2.146" # syscall wrappers 22 | iced-x86 = {version = "1.19", default-features = false, features = ["std", "decoder", "instr_info", "nasm"]} # disassember 23 | bitflags = "^1.3" # TODO: update to 2.* 24 | gimli = {version = "^0.31.1", default-features = false, features = ["std", "read"]} # for parsing DWARF; takes forever to compile, maybe we should roll our own parsing 25 | #gimli = {path = "/home/al13n/Dropbox/coding/gimli"} 26 | crc32fast = "^1.3.2" # a few things in ELF and DWARF use crc32 27 | flate2 = "^1.0.26" # for decompressing ELF sections 28 | md5 = "^0.7" # for checking source file hashes against debug symbols; but it seems that hashes are never present in the debug symbols in practice, so maybe we should remove this 29 | cpp_demangle = "^0.4.4" # for demangling function names (unavoidable in .symtab, convenient in .debug_info) 30 | #cpp_demangle = {path = "/home/al13n/Dropbox/coding/cpp_demangle"} 31 | rand = "^0.8.5" 32 | unicode-segmentation = "^1.10" 33 | unicode-width = "^0.1" 34 | rustc-demangle = "^0.1" 35 | # HTTP+TLS client for downloading from debuginfod. This accounts for more than half of the dependency tree (see `cargo tree`) 36 | # and adds the only non-cargo dependency: musl-tools (use e.g. `apt install musl-tools`). But I couldn't find anything better :( 37 | ureq = "^3.0" 38 | 39 | [dev-dependencies] 40 | gimli = {version = "^0.31.1", default-features = false, features = ["std", "read", "write"]} 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | A debugger for Linux. Partially inspired by RemedyBG. 2 | 3 | Mom, can we have RAD Debugger on Linux? 4 | No, we have debugger at home. 5 | Debugger at home: 6 | 7 | ![screenshot](https://github.com/user-attachments/assets/e0b03f1e-c1d1-4e38-a992-2ace7321bb75) 8 | 9 | Properties: 10 | * Fast. 11 | * TUI. 12 | * Not based on gdb or lldb, implemented mostly from scratch. 13 | * Works on large executables. (Tested mostly on 2.5 GB ClickHouse.) 14 | 15 | What we mean by "fast": 16 | * Operations that can be instantaneous should be instantaneous. I.e. snappy UI, no random freezes, no long waits. 17 | (Known exception: if the program has >~2k threads things become pretty slow. This will be improved.) 18 | * Operations that can't be instantaneous (loading debug info, searching for functions and types) should be reasonably efficient, multi-threaded, asynchronous, cancellable, and have progress bars. 19 | 20 | Limitations: 21 | * Linux only 22 | * x86 only 23 | * 64-bit only 24 | * for native code only (e.g. C++ or Rust, not Java or Python) 25 | * TUI only (no REPL, no GUI) 26 | * no remote debugging (but works fine over ssh) 27 | * single process (doesn't follow forks) 28 | * no record/replay or backwards stepping 29 | 30 | Development status: 31 | * Most standard debugger features are there. E.g. breakpoints, conditional breakpoints (but no data breakpoints yet), stepping, showing code and disassembly, watch expressions, builtin pretty-printers for most of C++ and Rust standard library. Many quality-of-life features are there (e.g. auto-downcasting abstract classes to concrete classes based on vtable). But I'm sure there are lots of missing features that I never needed but other people consider essential. Let me know. 32 | * I use it every day and find it very helpful. 33 | * Not widely tested - I only tried it on a few machines and a few real executables. 34 | * Many features are probably not very discoverable. I should make some tutorial videos or something. For now, just play around, check the hints at the top left, and read `--help-*`. 35 | 36 | Distributed as a single 6 MB executable file with no dependencies. 37 | 38 | "Installation": 39 | ```bash 40 | curl -L -o nnd 'https://github.com/al13n321/nnd/releases/latest/download/nnd' 41 | chmod +x nnd 42 | # try `./nnd --help` to get started 43 | ``` 44 | 45 | Or build from source: 46 | ```bash 47 | # Prerequisites: 48 | # 1. Install Rust. 49 | # 2. Install musl target: 50 | rustup target add x86_64-unknown-linux-musl 51 | # 3. Install musl-tools 52 | sudo apt install musl-tools 53 | 54 | # Build: 55 | cargo build --profile dbgo --bin nnd 56 | 57 | # The executable is at target/x86_64-unknown-linux-musl/dbgo/nnd 58 | ``` 59 | 60 | Run `nnd --help` for documentation. 61 | -------------------------------------------------------------------------------- /other_things/cpuid.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include 3 | #include 4 | #include 5 | 6 | int main() { 7 | unsigned int eax, ebx, ecx, edx; 8 | unsigned int max_subleaf; 9 | __cpuid_count(0xD, 0, eax, ebx, ecx, edx); 10 | max_subleaf = eax; 11 | for (unsigned int i = 0; i <= max_subleaf; i++) { 12 | __cpuid_count(0xD, i, eax, ebx, ecx, edx); 13 | if (eax == 0 && ebx == 0 && ecx == 0 && edx == 0) continue; 14 | printf("Component %2u: Size=%u Offset=%u Flags=0x%08x\n", i, eax, ebx, ecx); 15 | } 16 | return 0; 17 | } 18 | -------------------------------------------------------------------------------- /other_things/debug_registers_experiment.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #define CALL(x) if (x < 0) { perror(#x); exit(1); } 13 | 14 | int main() { 15 | int pid = fork(); 16 | if (pid < 0) { perror("fork"); exit(1); } 17 | if (pid == 0) { 18 | printf("hi\n"); 19 | __asm__("mov $0, %rax\n" 20 | "loop:\n" 21 | "inc %rax\n" 22 | "jmp loop\n"); 23 | printf("bye?\n"); 24 | return 1; 25 | } 26 | 27 | CALL(ptrace(PTRACE_SEIZE, pid, 0, PTRACE_O_EXITKILL | PTRACE_O_TRACECLONE | PTRACE_O_TRACEEXEC | PTRACE_O_TRACEFORK | PTRACE_O_TRACESYSGOOD | PTRACE_O_TRACEVFORK | PTRACE_O_TRACEVFORKDONE)); 28 | 29 | sleep(1); 30 | 31 | CALL(ptrace(PTRACE_INTERRUPT, pid, 0, 0)); 32 | 33 | int assigned = 0; 34 | 35 | for (int i = 0; i < 30; ++i) { 36 | int w; 37 | CALL(wait(&w)); 38 | 39 | if (!WIFSTOPPED(w)) { fprintf(stderr, "unexpected wait result: %d", w); exit(1); } 40 | int sig = WSTOPSIG(w); 41 | if (sig != SIGTRAP) { fprintf(stderr, "unexpected signal: %d", sig); exit(1); } 42 | 43 | struct user_regs_struct regs; 44 | CALL(ptrace(PTRACE_GETREGS, pid, 0, ®s)); 45 | 46 | printf("rip: %llu, rax: %llu\n", regs.rip, regs.rax); 47 | 48 | //if (!assigned) { 49 | CALL(ptrace(PTRACE_POKEUSER, pid, offsetof(struct user, u_debugreg[0]), regs.rip)); 50 | CALL(ptrace(PTRACE_POKEUSER, pid, offsetof(struct user, u_debugreg[7]), 1 << 0)); 51 | //} 52 | 53 | //CALL(ptrace(PTRACE_SINGLESTEP, pid, 0, 0)); 54 | CALL(ptrace(PTRACE_CONT, pid, 0, 0)); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /other_things/echo_input: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/al13n321/nnd/f3f6d614991ee1034cbcd0117962b37f3f8517e2/other_things/echo_input -------------------------------------------------------------------------------- /other_things/echo_input.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | // Save original termios settings 11 | struct termios orig_termios; 12 | 13 | // Function to restore the original terminal settings 14 | void restore_terminal() { 15 | tcsetattr(STDIN_FILENO, TCSANOW, &orig_termios); 16 | printf("\033[?1049l"); // Switch back to main screen 17 | printf("\033[?25h"); // Show cursor 18 | printf("\x1b[?9l\x1b[?1000l\x1b[?1002l\x1b[?1003l\x1b[?1006l\x1b[?1007l\x1b[?1015l\x1b[?1016l\x1b[?1004l"); 19 | fflush(stdout); 20 | } 21 | 22 | // Function to switch to alternate screen and raw mode 23 | void setup_terminal() { 24 | tcgetattr(STDIN_FILENO, &orig_termios); 25 | struct termios raw = orig_termios; 26 | cfmakeraw(&raw); 27 | tcsetattr(STDIN_FILENO, TCSANOW, &raw); 28 | printf("\033[?1049h"); // Switch to alternate screen 29 | printf("\033[H\033[J"); // Clear screen 30 | printf("\033[?25l"); // Hide cursor 31 | // https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking 32 | printf("\x1b[?1003h\x1b[?1007h\x1b[?1006h\x1b[?1004h"); 33 | fflush(stdout); 34 | } 35 | 36 | // Function to get the terminal height 37 | int get_terminal_height() { 38 | struct winsize ws; 39 | ioctl(STDIN_FILENO, TIOCGWINSZ, &ws); 40 | return ws.ws_row; 41 | } 42 | 43 | // Function to print the buffer with escaped non-alphanumeric characters 44 | void print_buffer(const char *buffer, ssize_t len) { 45 | for (ssize_t i = 0; i < len; ++i) { 46 | if (buffer[i] == '\e') { 47 | printf("\\e"); 48 | } else if (buffer[i] >= 33 && buffer[i] <= 126) { 49 | putchar(buffer[i]); 50 | } else { 51 | printf("\\x%02x", (unsigned char)buffer[i]); 52 | } 53 | } 54 | putchar('\n'); 55 | fflush(stdout); 56 | } 57 | 58 | int main() { 59 | setup_terminal(); 60 | atexit(restore_terminal); // Ensure terminal settings are restored on exit 61 | 62 | struct pollfd fds = { 63 | .fd = STDIN_FILENO, 64 | .events = POLLIN 65 | }; 66 | 67 | char buffer[128]; 68 | int row = 0; 69 | int max_row = get_terminal_height(); 70 | 71 | while (1) { 72 | if (poll(&fds, 1, -1) > 0) { 73 | if (fds.revents & POLLIN) { 74 | ssize_t n = read(STDIN_FILENO, buffer, sizeof(buffer) - 1); 75 | if (n > 0) { 76 | buffer[n] = '\0'; 77 | if (buffer[0] == 'q') { 78 | break; 79 | } 80 | printf("\033[%d;1H", row + 1); // Move cursor to the start of the next line 81 | print_buffer(buffer, n); 82 | row++; 83 | if (row >= max_row) { 84 | printf("\033[2J\033[H"); // Clear screen and move cursor to top 85 | fflush(stdout); 86 | row = 0; 87 | } 88 | } 89 | } 90 | } 91 | } 92 | 93 | return 0; 94 | } 95 | -------------------------------------------------------------------------------- /other_things/interp_sketch.rs: -------------------------------------------------------------------------------- 1 | struct InterpState { 2 | // 3 addressable memory ranges. 3 | stack: Vec, 4 | heap: Vec, 5 | debuggee_memory: MemReader, 6 | 7 | call_stack: Vec, 8 | 9 | error: Option, 10 | 11 | debuggee_memory_cache: [(/*addr*/ usize, Box<[u8; 4096]>)]; 12 | } 13 | 14 | impl Memory { 15 | fn read_usize<'a>(&'a mut self, addr: usize) -> usize { 16 | let address_space = addr >> 48; 17 | let addr = addr & 0xffffffffffff; 18 | let slice: &'a[u8] = match address_space { 19 | 0xdebe => { 20 | ...debuggee_memory_cache etc; 21 | } 22 | 0x57ac => { 23 | unsafe {std::slice::from_raw_parts(self.stack.as_ptr() as *const u8, self.stack.len() * 8)} 24 | } 25 | 0x4eab => { 26 | &self.heap 27 | } 28 | }; 29 | if addr + 8 > slice.len() { 30 | self.error = Some(error!(Runtime, "{} read out-of-bounds: {} + 8 > {}", if address_space == 0x57ac {"stack"} else {"heap"}, addr, slice.len())); 31 | return 0; 32 | } 33 | ...load_unaligned; 34 | } 35 | } 36 | 37 | // Inspired by https://github.com/maximecb/uvm/blob/main/vm/src/vm.rs 38 | 39 | #[repr(u8)] 40 | enum Instruction { 41 | Panic, 42 | Nop, 43 | 44 | PushU64, // ; push(imm); 45 | Pop, 46 | Dup, // v = stack[stack.len()-1]; push(v); 47 | Swap, 48 | GetN, // ; v = stack[stack.len()-1-offset]; push(v); 49 | SetN, // ; v = pop(); stack[stack.len()-1-offset] = v; 50 | 51 | LoadU8, // addr = pop(); v = memory.read_u8(addr); push(v); 52 | LoadU16, 53 | LoadU32, 54 | LoadU64, 55 | LoadI8, // sign-extended 56 | LoadI16, 57 | LoadI32, 58 | LoadF32, 59 | 60 | Store8, // addr = pop(); v = pop(); memory.write_u8(addr, v); 61 | Store16, 62 | Store32, 63 | Store64, 64 | 65 | SxI8, // sign-extend i8 -> i64 66 | SxI16, 67 | SxI32, 68 | TruncU8, 69 | TruncU16, 70 | TruncU32, 71 | 72 | IToF, // i64 -> f64 73 | UToF, // u64 -> f64 74 | FToI, 75 | 76 | Add, 77 | Sub, // y = pop(); x = pop(); push(x - y); 78 | Mul, 79 | UDiv, 80 | UMod, 81 | IDiv, 82 | IMod, 83 | FAdd, 84 | FSub, 85 | FMul, 86 | FDiv, 87 | FMod, 88 | And, 89 | Or, 90 | Xor, 91 | Not, 92 | LShift, 93 | RShift, 94 | IRShift, 95 | 96 | Eq, 97 | Ne, 98 | ULt, // y = pop(); x = pop(); v = if x < y {!0usize} else {0}; push(v); 99 | UGt, 100 | ULe, 101 | UGe, 102 | ILt, 103 | IGt, 104 | ILe, 105 | IGe, 106 | FLt, 107 | FGt, 108 | FLe, 109 | FGe, 110 | 111 | Jmp, // ; pc += offset 112 | Jz, // ; f = pop(); if f == 0 {pc += offset;} 113 | Jnz, 114 | 115 | Call, // function_idx: u32 116 | Ret, 117 | 118 | Register, // ; push(registers.get_int(idx)); 119 | Alloc, // ; ; push(address); 120 | Memcpy, // size = pop(); to = pop(); from = pop(); 121 | } 122 | 123 | 124 | 125 | #pretty("DB::PODArray") 126 | fn pretty_PODArray(a: $A) -> *[A::value_type] { 127 | use T = A::value_type; 128 | a.c_begin as *T .. a.c_end as *T 129 | } 130 | 131 | #pretty("::*std::*list", fields=["__end_"]) 132 | fn pretty_list_libcpp(list: $L) { 133 | use T = L::value_type; 134 | let end = &list.__end_; // if list is a copy, this is silently wrong! 135 | let elem = end.__next_; 136 | pretty_value("std::list of size {}", list.__size_alloc_); 137 | while elem != end { 138 | pretty_element((elem + 1) as *T); 139 | elem = elem.__next_; 140 | } 141 | } 142 | 143 | #pretty("::*std::*list", fields=["_M_next"]) 144 | fn pretty_list_libstdcpp(list: $L) { 145 | use T = L::value_type; 146 | pretty_value("std::list of size {}", list._M_size); 147 | let first = list._M_next; 148 | let elem = first; 149 | while elem->_M_next != first { 150 | pretty_element((elem + 1) as *T); 151 | elem = elem._M_next; 152 | } 153 | } 154 | 155 | watch expressions as auto-template functions; template over types of all identifiers and types; and over whether addresses are known; 156 | variables persisting across watch expressions; 157 | downcasting; where does auto-downcasting fit in?; 158 | passing big values to pretty printers - can be done without copying?; 159 | how to prevent taking address of local variables whose address is not known; 160 | prevent assignment to program variables and registers at compile time; 161 | dynamic arrays syntax and methods; 162 | strings; 163 | sorting?; 164 | yield (?); pagination?; 165 | pretty-printers for types introduced in the script itself; 166 | template structs; 167 | pretty-printers for synthetic types; 168 | ui to check which pretty-printer was applied, see template instantiation errors and runtime errors; 169 | printing, eprintln (shown in tooltip?) 170 | -------------------------------------------------------------------------------- /other_things/poketext_experiment.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | using namespace std; 14 | 15 | #define CHECK(x) if (!(x)) { cerr << "check failed on line " << __LINE__ << ": " << #x << endl; exit(1); } 16 | #define CHECK_EQ(a, b) { auto hyg_a = (a); auto hyg_b = (b); if (hyg_a != hyg_b) { cerr << "check failed on line " << __LINE__ << ": " #a " == " #b " (" << hyg_a << " != " << hyg_b << ")" << endl; exit(1); } } 17 | #define CALL(x) if ((x) < 0) { perror(#x); exit(1); } 18 | 19 | int main() { 20 | //cout<> 16, PTRACE_EVENT_CLONE); 45 | pid_t tid; 46 | CALL(ptrace(PTRACE_GETEVENTMSG, pid, 0, &tid)); 47 | cout<<"thread "<>16, PTRACE_EVENT_STOP); 55 | // We get either SIGTRAP or SIGSTOP, seemingly at random, different from run to run. 56 | CHECK(WSTOPSIG(w) == SIGTRAP || WSTOPSIG(w) == SIGSTOP); 57 | 58 | //CALL(ptrace(PTRACE_CONT, tid, 0, 0)); cout<<"continued "<>16, 0); 92 | 93 | cout<<"all done"< vec 2 | __begin_: *T, __end_: *T, __end_cap_: *T -> vec 3 | __data_: *char, __size_: u64 -> str, use name to rule out str 4 | _M_str: *char, _M_len: u64 -> str 5 | __data_: *T, __size_: u64 -> slice 6 | _M_ptr: *T, _M_extent: u64 -> slice 7 | __begin_: *T, __end_: *T -> slice 8 | _M_data: *T, _M_size: u64 -> slice 9 | c_start: *T, c_end: *T, c_end_of_storage: *T -> slice 10 | 11 | ptr: *T, cap: u64 -> RawVec 12 | buf: RawVec, len: u64 -> vec 13 | data_ptr: *T, length: u64 -> slice, use name to rule in str 14 | head: usize, len: usize, buf: RawVec -> VecDeque 15 | 16 | 17 | C++ 18 | remove common prefix and suffix (_M_*, __*_, __*, c_*) 19 | start|begin|data|ptr|str: *T 20 | (finish|end: *T)|(size|len|extent: u64) [, (end_of_storage|end_cap: *T)|(capacity|cap: u64)] -> slice [with capacity] 21 | ptr 22 | refcount|cntrl -> shared ptr 23 | TODO: unordered_{map,...}, {map,...}, optional, deque, tuple, list, forward_list 24 | TODO: absl::InlinedVector, absl::flat_hash_map, google::dense_hash_map, google::sparse_hash_map, Field, boost::intrusive::list, boost::intrusive::{set,multiset,map,multimap}, boost::container::flat_{set,map}, boost::container::small_vector, boost::shared_ptr 25 | Rust 26 | ptr: *T, cap: u64 -> slice // RawVec 27 | buf 28 | buf is RawVec 29 | len 30 | head -> VecDeque 31 | - -> slice with capacity, stringness from name (if "str" or "Str" in pre-template part of the name; use pre-unwrapping name!) 32 | data_ptr: *T 33 | length: u64 -> slice, stringness from name 34 | TODO: HashMap, HashSet 35 | 36 | 37 | 38 | 39 | 40 | absl::InlinedVector: 41 | {metadata_: usize, data_: union {allocated: {allocated_data: *T, allocated_capacity: *T}, inlined: {inlined_data: [i8; cap]}}} 42 | metadata_ & 1 - is_allocated 43 | metadata_ >> 1 - len 44 | 45 | 46 | Rust to do: 47 | Vec, String, OSString, PathBuf: {buf: {ptr, cap}, len} 48 | &[T], &str, Box<[T]>: {data_ptr, length} 49 | VecDeque: {head, len, buf: {ptr, cap}} 50 | 51 | Rust meh: 52 | BinaryHeap: {data: Vec} 53 | LinkedList: {head: Some({element, next: ..., prev: ...}), tail: Some(...), len} - traversable by hand 54 | BTreeMap, BTreeSet: looks traversable by hand 55 | RefCell: {borrow, value} 56 | Mutex, RWLock: {inner, poison, data} 57 | slice iter: {ptr, end_or_len} 58 | Rc, Arc, Weak: {ptr: {pointer: {strong, weak, value}}} 59 | 60 | 61 | C++ to do: 62 | vector: {_M_start, _M_finish, _M_end_of_storage} 63 | string_view: {_M_str, _M_len} 64 | span: {_M_ptr, _M_extent} 65 | valarray: {_M_size, _M_data} 66 | 67 | list: {_M_size, _M_next: {_M_next, _M_prev}, _M_prev: ...}, values after links 68 | forward_list: ... _M_next, values after links 69 | set, multiset, map, multimap: {_M_node_count, _M_header: {_M_parent: root, _M_left: min, _M_right: max}}, values after nodes 70 | unordered_{set,map,multiset,multimap}: {_M_element_count, _M_bucket_count, _M_buckets}, buckets are forward_list-like 71 | optional: {_M_payload, _M_engaged} 72 | tuple: chain of bases with multiple inheritance and _M_head_impl 73 | shared_ptr, weak_ptr: {_M_ptr, _M_refcount} 74 | 75 | C++ to figure out: 76 | string, wstring, u16string, u32string: {_M_string_length, _M_dataplus: {_M_p}, union {_M_local_buf, _M_allocated_capacity}} - ? 77 | deque, stack, queue: {_M_map, _M_map_size, _M_start, _M_finish} - ? 78 | 79 | C++ meh: 80 | priority_queue: vector 81 | any: ? 82 | 83 | 84 | libc++ to do: 85 | vector: {__begin_, __end_, __end_cap_} 86 | string_view, span: {__data_, __size_} 87 | valarray: {__begin_, __end_} 88 | 89 | list: {__size_alloc_, __end_: {__prev_, __next_}}, values after links 90 | forward_list: {__next_, 1: {__value_}} 91 | shared_ptr, weak_ptr: {__ptr_, __cntrl_} 92 | string, wstring, u16string, u32string: {__s: {0: {__is_long_, __size_}, __data_}, __l: {0: {__is_long_, __cap_}, __size_, __data_}, __r} 93 | optional: {union 0: {__null_state_, __val_}, __engaged_} 94 | variant: {__index, __data: {__dummy, __head: value, __tail: {__dummy, __head, __tail: {...}}}} 95 | 96 | libc++ to figure out: 97 | set, multiset, map, multimap: {__pair3__, __pair1_: {__left_, __right_, __parent_}} - ? 98 | unordered_{set,multiset,map,multimap}: {__bucket_list_: {__value_, __value_}, __p1_, __p2_, __p3_} - ? 99 | deque, stack, queue: {__size_, __start_, __map_: {__first_, __begin_, __end_, __end_cap_}} - ? 100 | 101 | libc++ meh: 102 | priority_queue: vector 103 | tuple: {__base_: {#base: {__value_}, #base: {__value_}, ...}} 104 | any: ? 105 | 106 | 107 | PODArrayBase: {c_start: *char, c_end, c_end_of_storage} 108 | PODArray: cast to *value_type 109 | -------------------------------------------------------------------------------- /other_things/profiling.txt: -------------------------------------------------------------------------------- 1 | `samply record` 2 | 3 | or: 4 | sudo perf record -F 1000 --call-graph=dwarf -- 5 | sudo chown al13n perf.data 6 | `hotspot` or profiler.firefox.com or `perf report` 7 | -------------------------------------------------------------------------------- /other_things/questions_to_debugger_people.txt: -------------------------------------------------------------------------------- 1 | How's the 'breakpoint vs jump' distinguishing problem usually solved? Guesswork, or is there a reliably way? Spurious post-singlestep SIGTRAPs don't help. 2 | What's up with 70% of line program sequences starting at address 0 (DW_LNE_set_address 0)? Missing relocations? Intentional or compiler bug? See unrelocated_line_number_program_sequences.txt for examples. (Mostly in debug build, clang-16.) 3 | -------------------------------------------------------------------------------- /other_things/singlestep_vs_group_stop: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/al13n321/nnd/f3f6d614991ee1034cbcd0117962b37f3f8517e2/other_things/singlestep_vs_group_stop -------------------------------------------------------------------------------- /other_things/singlestep_vs_group_stop.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | using namespace std; 15 | 16 | #define CHECK(x) if (!(x)) { cerr << "check failed on line " << __LINE__ << ": " << #x << endl; exit(1); } 17 | #define CHECK_EQ(a, b) { auto hyg_a = (a); auto hyg_b = (b); if (hyg_a != hyg_b) { cerr << "check failed on line " << __LINE__ << ": " #a " == " #b " (" << hyg_a << " != " << hyg_b << ")" << endl; exit(1); } } 18 | #define ERRNO(x) if ((x) < 0) { perror(#x); exit(1); } 19 | 20 | // This reproduces a somewhat unexpected behavior of PTRACE_SINGLESTEP. Scenario: 21 | // 1. PTRACE_SINGLESTEP for a thread that's blocked on syscall. 22 | // 2. The thread gets stopped because of some signal (unrelated to the SINGLESTEP). 23 | // 3. wait() reports a SIGTRAP instead of the signal that stopped the thread - a little unexpected, but 24 | // understandable (the signal delivery interrupted the syscall, which can be considered a step). 25 | // 4. PTRACE_CONT. 26 | // 5. The thread immediately stops again and reports the signal. 27 | // 28 | // A similar but more weird behavior that I saw in practice: 29 | // 1. PTRACE_SINGLESTEP for a thread that's blocked on syscall. 30 | // 2. The thread gets stopped by a group-stop. 31 | // 3. wait() reports the group-stop. 32 | // 4. PTRACE_CONT. 33 | // 5. wait() reports SIGTRAP. This seems unexpected and difficult to handle correctly! 34 | // I guess what happens is that both the group-stop event and SIGTRAP event get enqueued for delivery at once, 35 | // then get delivered in arbitrary order, so we may get either this scenario or the scenario above arbitrarily. 36 | 37 | void __attribute__((noinline)) child_run() { 38 | struct timespec req, rem; 39 | req.tv_sec = 5; 40 | req.tv_nsec = 0; 41 | int pid = syscall(SYS_getpid); 42 | 43 | syscall(SYS_tgkill, pid, pid, SIGSTOP); 44 | syscall(SYS_nanosleep, &req, &rem); 45 | syscall(SYS_exit, 42); 46 | } 47 | 48 | int main(int argc, char**argv) { 49 | if (argc == 2) { 50 | cout<<"running child function only"<>16, 0); 71 | 72 | cout<<"continuing"<>16, 0); 83 | ERRNO(ptrace(PTRACE_GETREGS, pid, 0, ®s)); 84 | cout<<"stopped at 0x"<>16, 0); 97 | ERRNO(ptrace(PTRACE_GETREGS, pid, 0, ®s)); 98 | cout<<"stopped at 0x"<>16, 0); CHECK_EQ(WSTOPSIG(w), SIGSTOP); 105 | } else { 106 | CHECK_EQ(WSTOPSIG(w), SIGSTOP); 107 | } 108 | 109 | cout<<"sleeping"<>16, 0); 119 | 120 | cout<<"done"<() = 64 158 | [src/bin/dwarf_exploration.rs:999] max_line = 239378 159 | [src/bin/dwarf_exploration.rs:1000] max_column = 368 160 | [src/bin/dwarf_exploration.rs:1005] all_anchors.len() = 38042814 161 | [src/bin/dwarf_exploration.rs:1008] all_anchors.len() = 23140727 162 | [src/bin/dwarf_exploration.rs:1025] longest_run_without_anchors = 222016 163 | sorting anchors took 1.431s 164 | 165 | dwarf5 166 | [src/bin/dwarf_exploration.rs:682] units = 5815 167 | [src/bin/dwarf_exploration.rs:683] dies = 98669598 168 | [src/bin/dwarf_exploration.rs:684] attrs = 323438477 169 | [src/bin/dwarf_exploration.rs:685] funcs_including_decls_and_inline = 13391865 170 | [src/bin/dwarf_exploration.rs:686] funcs = 579365 171 | [src/bin/dwarf_exploration.rs:687] deduped_funcs = 355413 172 | [src/bin/dwarf_exploration.rs:688] template_funcs as f64 / funcs as f64 = 0.18408775124489743 173 | [src/bin/dwarf_exploration.rs:689] funcs_without_name as f64 / funcs as f64 = 0.008288384697038999 174 | [src/bin/dwarf_exploration.rs:690] funcs_without_link_name as f64 / funcs as f64 = 0.0454463075953846 175 | [src/bin/dwarf_exploration.rs:691] funcs_with_multiple_ranges = 0 176 | [src/bin/dwarf_exploration.rs:692] func_ranges = 579365 177 | [src/bin/dwarf_exploration.rs:693] vars = 23414680 178 | [src/bin/dwarf_exploration.rs:694] locs = 30913530 179 | [src/bin/dwarf_exploration.rs:695] types = 2150699 180 | [src/bin/dwarf_exploration.rs:696] template_types as f64 / types as f64 = 0.644047818871911 181 | [src/bin/dwarf_exploration.rs:697] fields = 2118337 182 | [src/bin/dwarf_exploration.rs:698] inlined_funcs = 12812233 183 | [src/bin/dwarf_exploration.rs:699] inlined_func_ranges = 16413872 184 | [src/bin/dwarf_exploration.rs:700] inlined_funcs_without_ranges = 0 185 | [src/bin/dwarf_exploration.rs:701] total_func_link_name_len = 73269012 186 | [src/bin/dwarf_exploration.rs:702] total_func_full_name_len = 25915453 187 | [src/bin/dwarf_exploration.rs:703] total_func_local_name_len = 24997566 188 | [src/bin/dwarf_exploration.rs:704] total_var_name_len = 127335519 189 | [src/bin/dwarf_exploration.rs:705] total_type_full_name_len = 278636616 190 | [src/bin/dwarf_exploration.rs:706] longest_origin_chain = 4 191 | [src/bin/dwarf_exploration.rs:707] vars_without_loc = 3384588 192 | [src/bin/dwarf_exploration.rs:708] func_instr_len = 175790195 193 | 194 | dwarf5-debug 195 | .eh_frame took 5.161s 196 | [src/bin/dwarf_exploration.rs:108] cies.len() = 3 197 | [src/bin/dwarf_exploration.rs:109] fde_count = 2653469 198 | [src/bin/dwarf_exploration.rs:110] row_count = 10931015 199 | [src/bin/dwarf_exploration.rs:111] bytes_covered_by_fdes = 452438729 200 | [src/bin/dwarf_exploration.rs:112] bytes_covered_by_rows = 452438729 201 | [src/bin/dwarf_exploration.rs:113] register_rules = 19295924 202 | [src/bin/dwarf_exploration.rs:114] max_rows_per_fde = 924 203 | ----- 204 | .debug_info pre-pass took 0.541s 205 | [src/bin/dwarf_exploration.rs:150] units_vec.len() = 8720 206 | [src/bin/dwarf_exploration.rs:151] line_programs.len() = 8720 207 | ----- 208 | .debug_info took 49.173s 209 | [src/bin/dwarf_exploration.rs:730] dies = 129375765 210 | [src/bin/dwarf_exploration.rs:731] attrs = 441002637 211 | [src/bin/dwarf_exploration.rs:732] funcs_including_decls_and_inline = 27152480 212 | [src/bin/dwarf_exploration.rs:733] funcs = 7175532 213 | [src/bin/dwarf_exploration.rs:734] deduped_funcs = 2654177 214 | [src/bin/dwarf_exploration.rs:735] template_funcs as f64 / funcs as f64 = 0.3630610246041687 215 | [src/bin/dwarf_exploration.rs:736] funcs_without_name as f64 / funcs as f64 = 0.0015041393446506824 216 | [src/bin/dwarf_exploration.rs:737] funcs_without_link_name as f64 / funcs as f64 = 0.00800219412302809 217 | [src/bin/dwarf_exploration.rs:738] funcs_with_multiple_ranges = 0 218 | [src/bin/dwarf_exploration.rs:739] func_ranges = 7175532 219 | [src/bin/dwarf_exploration.rs:740] vars = 20578836 220 | [src/bin/dwarf_exploration.rs:741] locs = 20828691 221 | [src/bin/dwarf_exploration.rs:742] types = 3800629 222 | [src/bin/dwarf_exploration.rs:743] template_types as f64 / types as f64 = 0.6688858607351572 223 | [src/bin/dwarf_exploration.rs:744] fields = 3374186 224 | [src/bin/dwarf_exploration.rs:745] inlined_funcs = 865915 225 | [src/bin/dwarf_exploration.rs:746] inlined_func_ranges = 1020198 226 | [src/bin/dwarf_exploration.rs:747] inlined_funcs_without_ranges = 0 227 | [src/bin/dwarf_exploration.rs:748] total_func_link_name_len = 970940656 228 | [src/bin/dwarf_exploration.rs:749] total_func_full_name_len = 473425651 229 | [src/bin/dwarf_exploration.rs:750] total_func_local_name_len = 459483480 230 | [src/bin/dwarf_exploration.rs:751] total_var_name_len = 126784836 231 | [src/bin/dwarf_exploration.rs:752] total_type_full_name_len = 499787777 232 | [src/bin/dwarf_exploration.rs:753] longest_origin_chain = 3 233 | [src/bin/dwarf_exploration.rs:754] vars_without_loc = 250023 234 | [src/bin/dwarf_exploration.rs:755] func_instr_len = 855639507 235 | ----- 236 | .debug_line took 4.499s 237 | [src/bin/dwarf_exploration.rs:972] num_empty_sequences = 0 238 | [src/bin/dwarf_exploration.rs:973] num_sequences_starting_at_zero = 4522910 239 | [src/bin/dwarf_exploration.rs:974] max_address_in_sequences_starting_at_zero = 52455 240 | [src/bin/dwarf_exploration.rs:975] num_sequences = 2090403 241 | [src/bin/dwarf_exploration.rs:976] num_all_rows = 84872875 242 | [src/bin/dwarf_exploration.rs:977] num_rows = 45021504 243 | [src/bin/dwarf_exploration.rs:978] num_include_directories = 29961 244 | [src/bin/dwarf_exploration.rs:979] num_files = 929705 245 | [src/bin/dwarf_exploration.rs:980] file_to_idx.len() = 27170 246 | [src/bin/dwarf_exploration.rs:981] kinda_num_statements = 34033305 247 | [src/bin/dwarf_exploration.rs:982] kinda_num_basic_blocks = 0 248 | [src/bin/dwarf_exploration.rs:983] kinda_num_prologues = 7162965 249 | [src/bin/dwarf_exploration.rs:984] kinda_num_epilogues = 7027519 250 | [src/bin/dwarf_exploration.rs:985] num_programs_where_files_have_md5 = 2096 251 | [src/bin/dwarf_exploration.rs:986] addr_bytes_covered = 460979119 252 | [src/bin/dwarf_exploration.rs:987] total_program_header_bytes = 5931269 253 | [src/bin/dwarf_exploration.rs:988] total_program_bytes = 465423316 254 | [src/bin/dwarf_exploration.rs:989] max_program_header_bytes = 7540 255 | [src/bin/dwarf_exploration.rs:990] max_program_bytes = 8293728 256 | [src/bin/dwarf_exploration.rs:991] max_include_directories = 71 257 | [src/bin/dwarf_exploration.rs:992] max_files = 623 258 | [src/bin/dwarf_exploration.rs:993] max_rows_per_program = 1651641 259 | [src/bin/dwarf_exploration.rs:994] max_rows_per_sequence = 146011 260 | [src/bin/dwarf_exploration.rs:995] duplicate_sequences = 0 261 | [src/bin/dwarf_exploration.rs:996] overlapping_sequences = 0 262 | [src/bin/dwarf_exploration.rs:997] deduped_addr_bytes_covered = 460979119 263 | [src/bin/dwarf_exploration.rs:998] mem::size_of::() = 64 264 | [src/bin/dwarf_exploration.rs:999] max_line = 333055 265 | [src/bin/dwarf_exploration.rs:1000] max_column = 408 266 | [src/bin/dwarf_exploration.rs:1005] all_anchors.len() = 74567209 267 | [src/bin/dwarf_exploration.rs:1008] all_anchors.len() = 55052832 268 | [src/bin/dwarf_exploration.rs:1025] longest_run_without_anchors = 30779 269 | sorting anchors took 2.879s 270 | 271 | dwz-dwarf4-official-release 272 | [src/bin/dwarf_exploration.rs:684] units = 92605 273 | [src/bin/dwarf_exploration.rs:685] dies = 59109648 274 | [src/bin/dwarf_exploration.rs:686] attrs = 197105241 275 | [src/bin/dwarf_exploration.rs:687] funcs_including_decls_and_inline = 7129631 276 | [src/bin/dwarf_exploration.rs:688] funcs = 316368 277 | [src/bin/dwarf_exploration.rs:689] deduped_funcs = 316355 278 | [src/bin/dwarf_exploration.rs:690] template_funcs as f64 / funcs as f64 = 0.08716115409902392 279 | [src/bin/dwarf_exploration.rs:691] funcs_without_name as f64 / funcs as f64 = 0.010901861123754615 280 | [src/bin/dwarf_exploration.rs:692] funcs_without_link_name as f64 / funcs as f64 = 0.06794935012390634 281 | [src/bin/dwarf_exploration.rs:693] funcs_with_multiple_ranges = 0 282 | [src/bin/dwarf_exploration.rs:694] func_ranges = 316368 283 | [src/bin/dwarf_exploration.rs:695] vars = 16021617 284 | [src/bin/dwarf_exploration.rs:696] locs = 21227504 285 | [src/bin/dwarf_exploration.rs:697] types = 1153774 286 | [src/bin/dwarf_exploration.rs:698] template_types as f64 / types as f64 = 0.6476155642266163 287 | [src/bin/dwarf_exploration.rs:699] fields = 1747701 288 | [src/bin/dwarf_exploration.rs:700] inlined_funcs = 9474795 289 | [src/bin/dwarf_exploration.rs:701] inlined_func_ranges = 12344834 290 | [src/bin/dwarf_exploration.rs:702] inlined_funcs_without_ranges = 0 291 | [src/bin/dwarf_exploration.rs:703] total_func_link_name_len = 40081317 292 | [src/bin/dwarf_exploration.rs:704] total_func_full_name_len = 12118588 293 | [src/bin/dwarf_exploration.rs:705] total_func_local_name_len = 11534211 294 | [src/bin/dwarf_exploration.rs:706] total_var_name_len = 97429214 295 | [src/bin/dwarf_exploration.rs:707] total_type_full_name_len = 168595996 296 | [src/bin/dwarf_exploration.rs:708] longest_origin_chain = 4 297 | [src/bin/dwarf_exploration.rs:709] vars_without_loc = 2374822 298 | [src/bin/dwarf_exploration.rs:710] func_instr_len = 133758066 299 | -------------------------------------------------------------------------------- /other_things/tui_sketch.rs: -------------------------------------------------------------------------------- 1 | figure out input; 2 | focus path, assembled from pieces; 3 | use the focus mechanism for table selection?; 4 | do multiple renders if keys dispatch to different boxes, up to some time limit (have timestamp with each key, discard if too old); 5 | think more about context menus, main menu, mouse, gui; 6 | 7 | // Immediate-mode UI library, inspired by https://www.rfleury.com/p/ui-series-table-of-contents 8 | 9 | struct Rect { 10 | x: usize, 11 | y: usize, 12 | w: usize, 13 | h: usize, 14 | } 15 | 16 | bitflags! { pub struct WidgetFlags : u32 { 17 | WIDTH_FROM_CONTENT, 18 | HEIGHT_FROM_CONTENT, 19 | FLOATING_X, 20 | FLOATING_Y, 21 | 22 | SCROLL_X = 0x8, 23 | SCROLL_Y = 0x10, 24 | SCROLLBAR_Y = 0x20, // even when the scrollbar is not visible, one column of width is reserved for it 25 | SCROLL_HERE, // the nearest ancestor scrollable area should scroll such that this widget is visible 26 | 27 | LINE_WRAP, 28 | ELLIPSIS_FOR_TRUNCATED_TEXT, 29 | HORIZONTAL_CLIPPING_ARROWS, // if the line of text is clipped by parent scroll area, show arrows at the clipped end(s) 30 | REPEAT_TEXT_VERTICALLY, 31 | REPEAT_TEXT_HORIZONTALLY, 32 | FILL_BACKGROUND, 33 | }} 34 | 35 | struct Widget { 36 | // State that may be used across frames. 37 | 38 | identity: usize, // identifier for recognizing the widget across frames; 0 means unset and not preserved across frames 39 | rect: Rect, 40 | flags: WidgetFlags, 41 | 42 | parent: WidgetIdx, 43 | first_child: WidgetIdx, 44 | last_child: WidgetIdx, 45 | prev_sibling: WidgetIdx, 46 | next_sibling: WidgetIdx, 47 | 48 | // State only used for current frame. 49 | 50 | background: Option, 51 | text: Range, // range of lines in all_text 52 | line_wrap_limit: usize, // max height when LINE_WRAP is enabled 53 | } 54 | impl Default for Widget; 55 | struct WidgetIdx(usize); 56 | 57 | struct UI { 58 | // State preserved across frames. 59 | prev_widgets: Vec, // sorted by identity 60 | 61 | all_text: StyledText, 62 | widgets: Vec, 63 | parent_stack: Vec, 64 | 65 | } 66 | impl Frame { 67 | fn add(&mut self, w: Widget); 68 | } 69 | 70 | 71 | hints 72 | panel has fixed height 73 | horizontal scrolling 74 | always 2 columns 75 | two text areas with widths determined by content; 76 | status 77 | panel has fixed height 78 | state line is a full-width widget with background color 79 | other info is just a text area; 80 | tabs 81 | scrollable x, stack x 82 | scroll indicator arrows 83 | auto scroll to selected tab 84 | each tab header has width determined by content 85 | separators; 86 | watches 87 | table header 88 | name and value columns, widths as percentage of parent 89 | row has two flags: expanded, line-wrapped 90 | if has children, right arrow goes collapsed -> expanded -> expanded+wrapped; left arrow always goes to collapsed+non-wrapped 91 | line-wrapped rows wrap to at most ~20 lines, in each column separately 92 | non-wrapped rows truncate text with ellipsis 93 | arrow and indentation are their own widgets, so that name column line-wraps correctly, and to make the arrow clickable in future 94 | non-wrapped lines grouped into placeholders when offscreen 95 | wrapped lines always emitted individually and height recalculated every frame even if offscreen 96 | scrollable y, scrollbar 97 | auto-scroll to selected line 98 | when editing a watch, temporarily disable line wrapping for its row; 99 | disassembly 100 | horizontally scrollable area, inside it the header row and the main vertically scrollable area 101 | scrollable y, scrollbar 102 | each visible line is a text area, sized by content, with arrow truncation indicators (HORIZONTAL_CLIPPING_ARROWS) 103 | placeholders for offscreen lines, use longest line width as width 104 | tabs 105 | header line; 106 | code 107 | same as disassembly: tabs, header line, text area per line, etc; 108 | binaries, breakpoints, stack, threads 109 | table widget 110 | h scroll area, inside it are table header and v scrollable area 111 | no placeholders 112 | all columns have content-based width, long column is last 113 | optional min width for each column (e.g. for address and idx to avoid jumping around too much) 114 | binaries: optional second line for error message, maybe with merged rows 115 | binaries: progress bar, merge cells, use parent width (don't bother excluding it from horizontal scrolling; no special treatment in Table) 116 | stack: truncation message is just a row 117 | windowed mode: early layout, placeholders; 118 | threads 119 | some min column widths manually calculated based on full list of threads 120 | filter: box, text input, affects table contents 121 | sorting by any column, highlight it in the header if not default; 122 | windows 123 | border lines are widgets with repeated text 124 | just keep layout.rs for now; 125 | search dialog 126 | separate tree 127 | borders and title 128 | progress bar 129 | windowed table with one column and no header; 130 | maximizing a panel: move to separate tree; 131 | 132 | struct Table; 133 | impl Table { 134 | fn new(frame: &mut Frame) -> Self; 135 | fn enable_selected_line_indicator(&mut self); 136 | fn header_cell(&mut self, text: usize, min_width: usize) -> WidgetIdx; 137 | fn finish_header(&mut self); 138 | fn hide_header(&mut self); 139 | fn enable_windowed_mode(&mut self, num_rows: usize, selected_row: usize) -> Range; 140 | fn cell(&mut self, text: usize) -> WidgetIdx; 141 | fn merged_cell(&mut self, text: usize, count: usize) -> WidgetIdx; 142 | fn finish_subrow(&mut self); 143 | fn select_current_row(&mut self); 144 | fn finish_row(&mut self); 145 | fn finish(&mut self, &mut Frame); 146 | } 147 | -------------------------------------------------------------------------------- /other_things/varint_benchmark.c: -------------------------------------------------------------------------------- 1 | // Compile with: gcc -g -O2 -mbmi2 -mbmi a.c 2 | 3 | // Benchmark some varint parsing implementations. 4 | // Conclusions: 5 | // * When all lengths are the same, the simple implementation wins. parse_hybrid() is a tiny bit faster. Anything more clever than that is much slower (4x) on short lengths. 6 | // * When lengths are random, branchful implementations take a 3x hit, while branchless ones don't care and win by 2x. 7 | 8 | #define _GNU_SOURCE 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #define DATA_SIZE (1024 * 1024) 17 | #define NUM_RUNS 1000 18 | #define NUM_TRIALS 2 19 | 20 | inline uint64_t parse_simple(const uint8_t** ptr) { 21 | uint64_t result = 0; 22 | unsigned shift = 0; 23 | uint8_t byte; 24 | do { 25 | byte = *(*ptr)++; 26 | result |= ((uint64_t)(byte & 0x7f) << shift); 27 | shift += 7; 28 | } while (byte & 0x80); 29 | return result; 30 | } 31 | 32 | inline uint64_t collect_bits(uint64_t x) { 33 | // '#' - bit from the source, '.' - zero bit, '-' - garbage bit. 34 | // Our goal is to go from: 35 | // -#######-#######-#######-#######-#######-#######-#######-####### 36 | // to 37 | // ........######################################################## 38 | 39 | // -#######-#######-#######-#######-#######-#######-#######-####### 40 | x = ((x & 0x7f007f007f007f00) >> 1) | (x & 0x007f007f007f007f); 41 | // ..##############..##############..##############..############## 42 | x = ((x & 0xffff0000ffff0000) >> 2) | (x & 0x0000ffff0000ffff); 43 | // ....############################....############################ 44 | x = ((x & 0xffffffff00000000) >> 4) | (x & 0x00000000ffffffff); 45 | // ........######################################################## 46 | return x; 47 | } 48 | 49 | inline uint64_t collect_bits2(uint64_t x) { 50 | // '#' - bit from the source, '.' - zero bit, '-' - garbage bit. 51 | // Our goal is to go from: 52 | // -#######-#######-#######-#######-#######-#######-#######-####### 53 | // to 54 | // ........######################################################## 55 | 56 | // -#######-#######-#######-#######-#######-#######-#######-####### 57 | x = ((x & 0x7f007f007f007f00) >> 1) | (x & 0x007f007f007f007f); 58 | x = ((x & 0xffff000000000000) >> 6) | 59 | ((x & 0x0000ffff00000000) >> 4) | 60 | ((x & 0x00000000ffff0000) >> 2) | 61 | (x & 0x000000000000ffff); 62 | return x; 63 | } 64 | 65 | inline uint64_t parse_hybrid(const uint8_t** ptr) { 66 | uint64_t chunk = *(uint64_t*)*ptr; 67 | if ((chunk & 0x80) == 0) { // 1 byte 68 | *ptr += 1; 69 | return chunk & 0x7f; 70 | } else if ((chunk & 0x0000008080808080) != 0x0000008080808080) { // 2..5 bytes 71 | uint64_t res = (chunk & 0x7f) | ((chunk & 0x7f00) >> 1); 72 | *ptr += 2; 73 | if (chunk & 0x8000) { 74 | res |= (chunk & 0x7f0000) >> 2; 75 | *ptr += 1; 76 | } 77 | if ((chunk & 0x808000) == 0x808000) { 78 | res |= (chunk & 0x7f000000) >> 3; 79 | *ptr += 1; 80 | } 81 | if ((chunk & 0x80808000) == 0x80808000) { 82 | res |= (chunk & 0x7f00000000) >> 4; 83 | *ptr += 1; 84 | } 85 | return res; 86 | } else if ((chunk & 0x8080808080808080) != 0x8080808080808080) { // 6..8 bytes 87 | int bytes = _tzcnt_u64((~chunk) & 0x8080808080808080UL) >> 3; 88 | uint64_t res = collect_bits2(chunk); 89 | res &= UINT64_MAX >> (64 - 7 - bytes*7); 90 | *ptr += bytes + 1; 91 | return res; 92 | } else { // 9..10 bytes 93 | uint64_t res = collect_bits2(chunk); 94 | uint64_t high = (uint64_t)*(uint16_t*)(*ptr + 8); 95 | res |= high << 56; 96 | res &= (high << 55) | 0x7fffffffffffffff; 97 | *ptr += 9 + ((high >> 7) & 1); 98 | return res; 99 | } 100 | } 101 | 102 | inline uint64_t parse_branchless(const uint8_t** ptr) { 103 | uint64_t chunk = *(uint64_t*)*ptr; 104 | int bytes = _tzcnt_u64((~chunk) & 0x8080808080808080UL) >> 3; 105 | uint64_t res; 106 | if (bytes < 8) { 107 | res = _pext_u64(chunk, 0x7f7f7f7f7f7f7f7fUL >> (8 * (7 - bytes))); 108 | } else { 109 | res = _pext_u64(chunk, 0x7f7f7f7f7f7f7f7fUL); 110 | chunk = *(uint64_t*)(*ptr + 8); 111 | res |= (chunk & 0x7f) << 56; 112 | uint64_t extra = (chunk >> 7) & 1; 113 | bytes += extra; 114 | res |= ((chunk >> 8) & extra) << 63; 115 | } 116 | 117 | *ptr += bytes + 1; 118 | return res; 119 | } 120 | 121 | inline uint64_t parse_branchless2(const uint8_t** ptr) { 122 | uint64_t one = *(uint64_t*)*ptr; 123 | uint64_t two = *(uint64_t*)(*ptr + 8); 124 | 125 | uint64_t bytes1 = _tzcnt_u64((~one) & 0x8080808080808080UL) >> 3; 126 | uint64_t is_long = bytes1 >> 3; 127 | uint64_t res = _pext_u64(one, 0x7f7f7f7f7f7f7f7fUL >> (8 * (7 + is_long - bytes1))); 128 | 129 | uint8_t long_mask_byte = -is_long; 130 | uint64_t bytes2 = (two >> 7) & is_long; 131 | two &= (two | 0xff) >> 1; 132 | two &= long_mask_byte; 133 | res |= two << 56; 134 | 135 | uint64_t bytes = bytes1 + 1 + bytes2; 136 | *ptr += bytes; 137 | return res; 138 | } 139 | 140 | inline __m128i collect_bits_twice(__m128i x) { 141 | // Just like collect_bits(), but for two 64-bit numbers simultaneously. 142 | x = _mm_or_si128( 143 | _mm_srli_epi64(_mm_and_si128(x, _mm_set1_epi16(0x7f00)), 1), 144 | _mm_and_si128(x, _mm_set1_epi16(0x007f)) 145 | ); 146 | x = _mm_or_si128( 147 | _mm_srli_epi64(_mm_and_si128(x, _mm_set1_epi32(0xffff0000)), 2), 148 | _mm_and_si128(x, _mm_set1_epi32(0x0000ffff)) 149 | ); 150 | x = _mm_or_si128( 151 | _mm_srli_epi64(_mm_and_si128(x, _mm_set1_epi64x(0xffffffff00000000)), 4), 152 | _mm_and_si128(x, _mm_set1_epi64x(0x00000000ffffffff)) 153 | ); 154 | return x; 155 | } 156 | 157 | inline uint64_t parse_branchless3(const uint8_t** ptr) { 158 | uint64_t one = *(uint64_t*)*ptr; 159 | uint64_t two = *(uint64_t*)(*ptr + 8); 160 | 161 | uint64_t bytes1 = _tzcnt_u64((~one) & 0x8080808080808080UL) >> 3; 162 | uint64_t is_long = bytes1 >> 3; 163 | uint64_t res = collect_bits(one & (0x7f7f7f7f7f7f7f7fUL >> (8 * (7 + is_long - bytes1)))); 164 | 165 | uint8_t long_mask_byte = -is_long; 166 | uint64_t bytes2 = (two >> 7) & is_long; 167 | two &= (two | 0xff) >> 1; 168 | two &= long_mask_byte; 169 | res |= two << 56; 170 | 171 | uint64_t bytes = bytes1 + 1 + bytes2; 172 | *ptr += bytes; 173 | return res; 174 | } 175 | 176 | inline uint64_t parse_branchless4(const uint8_t** ptr) { 177 | uint64_t one = *(uint64_t*)*ptr; 178 | uint64_t two = *(uint64_t*)(*ptr + 8); 179 | 180 | uint64_t bytes1 = _tzcnt_u64((~one) & 0x8080808080808080UL) >> 3; 181 | uint64_t is_long = bytes1 >> 3; 182 | uint64_t res = collect_bits2(one & (0x7f7f7f7f7f7f7f7fUL >> (8 * (7 + is_long - bytes1)))); 183 | 184 | uint8_t long_mask_byte = -is_long; 185 | uint64_t bytes2 = (two >> 7) & is_long; 186 | two &= (two | 0xff) >> 1; 187 | two &= long_mask_byte; 188 | res |= two << 56; 189 | 190 | uint64_t bytes = bytes1 + 1 + bytes2; 191 | *ptr += bytes; 192 | return res; 193 | } 194 | 195 | inline uint64_t parse_simd(const uint8_t** ptr) { 196 | __m128i data = _mm_loadu_si128((__m128i*)*ptr); 197 | 198 | __m128i cont = _mm_and_si128(data, _mm_set1_epi8(0x80)); 199 | uint32_t cont_mask = _mm_movemask_epi8(cont); 200 | int bytes = _tzcnt_u32(~cont_mask) + 1; 201 | *ptr += bytes; 202 | 203 | __m128i halves = collect_bits_twice(data); 204 | uint64_t lo = _mm_cvtsi128_si64(halves); 205 | // Shift by one byte, so the lower byte of upper half turns into the upper byte of the result. 206 | uint64_t hi = _mm_cvtsi128_si64(_mm_srli_si128(halves, 1)); 207 | uint64_t res = lo | (hi & 0xff00000000000000); 208 | uint64_t discard_bits = 64 - bytes*7; 209 | discard_bits &= ~(discard_bits >> 32); 210 | res &= UINT64_MAX >> (uint8_t)discard_bits; 211 | return res; 212 | } 213 | 214 | // Returns number of bytes written 215 | int encode_uleb128(uint64_t value, uint8_t* ptr) { 216 | int n = 0; 217 | do { 218 | uint8_t byte = value & 0x7f; 219 | value >>= 7; 220 | if (value != 0) byte |= 0x80; 221 | *ptr++ = byte; 222 | n++; 223 | } while (value != 0); 224 | return n; 225 | } 226 | 227 | // Generate random n-bit number 228 | uint64_t rand_bits(int n) { 229 | if (n == 0) return 0; 230 | uint64_t mask = UINT64_MAX >> (64 - n); 231 | uint64_t r = 0; 232 | for (int i = 0; i < 64; i += 16) 233 | r |= ((uint64_t)rand() & 0xffff) << i; 234 | return r & mask; 235 | } 236 | 237 | void benchmark() { 238 | printf("RAND_MAX: %d\n", RAND_MAX); 239 | 240 | // Generate test data. 241 | const int NUM_DATASETS = 10; 242 | uint8_t* data[NUM_DATASETS]; 243 | uint64_t expected_sums[NUM_DATASETS]; 244 | for (int i = 0; i < NUM_DATASETS; ++i) { 245 | data[i] = aligned_alloc(16, DATA_SIZE + 100); 246 | expected_sums[i] = 0; 247 | int pos = 0; 248 | int count = 0; 249 | 250 | int bits = 7*(i+1); 251 | if (bits > 64) bits = 64; 252 | while (pos < DATA_SIZE) { 253 | if (i == 9) bits = rand() % 64 + 1; 254 | uint64_t num = rand_bits(bits); 255 | expected_sums[i] += num; 256 | pos += encode_uleb128(num, data[i] + pos); 257 | count += 1; 258 | } 259 | 260 | printf("dataset %d count: %d (avg %f bytes), sum: %lu\n", i, count, DATA_SIZE * 1. / count, expected_sums[i]); 261 | } 262 | 263 | // Benchmark 264 | for (int trial = 0; trial < NUM_TRIALS; trial++) { 265 | printf("\nTrial %d:\n", trial + 1); 266 | 267 | for (int ds = 0; ds < NUM_DATASETS; ++ds) { 268 | printf("\n Dataset %d:\n", ds); 269 | 270 | struct timespec start, end; 271 | uint64_t sum; 272 | double elapsed; 273 | 274 | #define BENCH(f) \ 275 | clock_gettime(CLOCK_MONOTONIC, &start); \ 276 | for (int run = 0; run < NUM_RUNS; run++) { \ 277 | sum = 0; \ 278 | const uint8_t* ptr = data[ds]; \ 279 | const uint8_t* end = data[ds] + DATA_SIZE; \ 280 | while (ptr < end) { \ 281 | uint64_t val = f(&ptr); \ 282 | sum += val; \ 283 | } \ 284 | } \ 285 | clock_gettime(CLOCK_MONOTONIC, &end); \ 286 | elapsed = (end.tv_sec - start.tv_sec) + (end.tv_nsec - start.tv_nsec) / 1e9; \ 287 | printf(#f ": %.3f ms%s\n", elapsed * 1000, sum == expected_sums[ds] ? "" : " (sum incorrect!)"); 288 | 289 | BENCH(parse_simple); 290 | BENCH(parse_hybrid); 291 | BENCH(parse_branchless); 292 | BENCH(parse_branchless2); 293 | BENCH(parse_branchless3); 294 | //BENCH(parse_branchless4); 295 | BENCH(parse_simd); 296 | } 297 | } 298 | } 299 | 300 | void test() { 301 | uint8_t buf[100]; 302 | for (uint64_t i = 0; i < 10000000000; ++i) { 303 | if (i % 100000000 == 0) 304 | printf("... i = %#018lx\n", i); 305 | 306 | int bits = rand() % 64 + 1; 307 | uint64_t num = rand_bits(bits); 308 | 309 | int len = encode_uleb128(num, buf); 310 | 311 | const uint8_t* ptr = buf; 312 | uint64_t val = parse_hybrid(&ptr); 313 | 314 | if (ptr != buf + len || val != num) { 315 | printf("Failed on i = %lu. Expected: %#018lx (%d bytes)\nFound: %#018lx (%ld bytes)\n", i, num, len, val, ptr - buf); 316 | break; 317 | } 318 | } 319 | } 320 | 321 | int main() { 322 | //srand(time(0)); 323 | //test(); 324 | benchmark(); 325 | return 0; 326 | } 327 | -------------------------------------------------------------------------------- /release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | if [ "$1" != "allow-uncommitted" ] 5 | then 6 | status="$(git status --porcelain)" 7 | if [ "$status" != "" ] 8 | then 9 | echo "uncommitted changes" 10 | exit 1 11 | fi 12 | fi 13 | 14 | RUST_BACKTRACE=1 cargo test && cargo build --profile dbgo && cargo build -r 15 | 16 | scp target/x86_64-unknown-linux-musl/dbgo/nnd dev:bin/new-nnd && scp target/x86_64-unknown-linux-musl/release/nnd dev:bin/new-nnd-release && ssh dev 'mv bin/new-nnd bin/nnd; mv bin/new-nnd-release bin/nnd-release;' 17 | 18 | cp target/x86_64-unknown-linux-musl/dbgo/nnd ../../nnd-release/nnd-new && mv ../../nnd-release/nnd-new ../../nnd-release/nnd && cp target/x86_64-unknown-linux-musl/release/nnd ../../nnd-release/nnd-release-new && mv ../../nnd-release/nnd-release-new ../../nnd-release/nnd-release 19 | 20 | butler push target/x86_64-unknown-linux-musl/release/nnd al13n/nnd:linux 21 | 22 | TAG_NUM=`git tag | grep -Po '(?<=^v0\.)\d+$' | sort -n | tail -n1` 23 | if [ "$TAG_NUM" == "" ] 24 | then 25 | echo "couldn't find latest tag number" 26 | fi 27 | TAG="v0.$TAG_NUM" 28 | 29 | if gh release view $TAG >/dev/null 30 | then 31 | TAG_NUM=$(($TAG_NUM + 1)) 32 | TAG="v0.$TAG_NUM" 33 | git tag "$TAG" 34 | git push origin "$TAG" 35 | fi 36 | 37 | cp target/x86_64-unknown-linux-musl/dbgo/nnd nnd-dbgo 38 | trap 'rm nnd-dbgo' EXIT 39 | 40 | gh release create "$TAG" --notes "" target/x86_64-unknown-linux-musl/release/nnd nnd-dbgo 41 | 42 | echo "all done!" 43 | -------------------------------------------------------------------------------- /src/arena.rs: -------------------------------------------------------------------------------- 1 | use std::{mem, mem::MaybeUninit, ptr, alloc::{alloc, Layout, handle_alloc_error, dealloc}, slice, marker::PhantomData, io, path::Path, fmt, os::unix::ffi::OsStrExt, io::Write as ioWrite}; 2 | 3 | // Arena - a simple untyped arena. 4 | // StringTable - arena equipped with a list of pointers to allocated strings, allowing search. 5 | // E.g. put all type names in it, each associated with an id, then iterate over them sequentially for fast search. 6 | // 7 | // Rust borrow checker really doesn't combine well with arenas, so we try to use arrays or pools where possible. 8 | // But for the type system I couldn't come up with any reasonable way to avoid arenas; I tried implementing it with pools, 9 | // but it quickly got out of hand because types from different pools may interact and even reference each other (when temporary types are introduced in watch expressions). 10 | // So we use arenas for that, with some manual transmuting of lifetimes and lots of unsafe dereferencing of *const TypeInfo. 11 | 12 | #[derive(Default)] 13 | pub struct Arena { 14 | chunks: Vec, 15 | current_chunk: Option, 16 | } 17 | 18 | pub struct StringTable { 19 | pub arena: Arena, 20 | pub strings: Vec, 21 | } 22 | 23 | #[derive(Clone, Copy, Eq, PartialEq, Debug)] 24 | pub struct StringRef { 25 | pub s: &'static [u8], 26 | pub id: usize, 27 | } 28 | 29 | 30 | struct Chunk { 31 | data: *mut u8, // avoiding Box<[u8]> because I'm not sure whether it's valid with aliasing rules 32 | allocated: usize, 33 | capacity: usize, // currently always equal to `allocated`, but we can make it smaller for SIMD padding if needed 34 | used: usize, 35 | } 36 | unsafe impl Send for Chunk {} 37 | unsafe impl Sync for Chunk {} 38 | 39 | const CHUNK_SIZE: usize = 1 << 18; 40 | 41 | impl Arena { 42 | pub fn new() -> Self { Self::default() } 43 | 44 | pub fn alloc(&mut self, size: usize, align: usize) -> &mut [u8] { 45 | assert!(align != 0 && (align - 1) & align == 0); 46 | let chunk_idx = self.pick_chunk(size + align - 1, size); 47 | let c = &mut self.chunks[chunk_idx]; 48 | let start = (c.used + align - 1) & !(align - 1); 49 | c.used = start + size; 50 | unsafe {slice::from_raw_parts_mut(c.data.add(c.used-size), size)} 51 | } 52 | 53 | pub fn alloc_slice(&mut self, len: usize) -> &mut [MaybeUninit] { 54 | assert!(!mem::needs_drop::()); 55 | unsafe { 56 | let r: *mut MaybeUninit = self.alloc(mem::size_of::() * len, mem::align_of::()).as_mut_ptr() as _; 57 | slice::from_raw_parts_mut(r, len) 58 | } 59 | } 60 | 61 | // Copies an value into this arena, bytewise. Immutable version casts away the lifetime. 62 | // Mutable version doesn't, to prevent UB if the caller tries to iterate over the arena while still holding a mutable reference to its element, contradicting the reference. 63 | // Source: https://doc.rust-lang.org/std/cell/struct.UnsafeCell.html ("The precise Rust aliasing rules are somewhat in flux, but the main points are not contentious: [...]") 64 | pub fn add_mut(&mut self, val: T) -> &mut T { 65 | assert!(!mem::needs_drop::()); 66 | let r: &mut T = unsafe {&mut *(self.alloc(mem::size_of::(), mem::align_of::()).as_mut_ptr() as *mut T)}; 67 | *r = val; 68 | r 69 | } 70 | pub fn add(&mut self, val: T) -> &'static T { unsafe {mem::transmute(self.add_mut(val))} } 71 | 72 | pub fn add_slice_mut<'a, T>(&'a mut self, a: &[T]) -> &'a mut [T] { 73 | assert!(!mem::needs_drop::()); 74 | unsafe { 75 | let r: *mut T = self.alloc(mem::size_of::() * a.len(), mem::align_of::()).as_mut_ptr() as _; 76 | ptr::copy_nonoverlapping(a.as_ptr(), r, a.len()); 77 | slice::from_raw_parts_mut(r, a.len()) 78 | } 79 | } 80 | pub fn add_slice(&mut self, a: &[T]) -> &'static [T] { unsafe {mem::transmute(self.add_slice_mut(a))} } 81 | 82 | pub fn add_str(&mut self, s: &str) -> &'static str { unsafe {mem::transmute(self.add_slice(s.as_bytes()))} } 83 | pub fn add_path(&mut self, p: &Path) -> &'static Path { unsafe {mem::transmute(self.add_slice(p.as_os_str().as_bytes()))} } 84 | 85 | // If the given array was the last thing allocated by this arena, and there's enough room for one more element, grows the array in-place. 86 | // Otherwise allocates a new array of size len+1 and copies the old array to it. Needless to say, this should only be used if the latter case is rare. 87 | // TODO: Delet dis and have a scratch Vec in LoaderStackEntry instead. 88 | pub fn push_to_array_likely_at_end(&mut self, start: &mut *const T, len: &mut usize, add: T) { 89 | assert!(!mem::needs_drop::()); 90 | unsafe { 91 | let size = mem::size_of::(); 92 | let align = mem::align_of::(); 93 | assert!(size % align == 0); 94 | let end = (*start).add(*len) as *const u8; 95 | if let &Some(i) = &self.current_chunk { 96 | let c = &mut self.chunks[i]; 97 | if end == c.data.add(c.used) && c.used + size <= c.capacity { 98 | // Fast path. 99 | unsafe {*(c.data.add(c.used) as *mut T) = add}; 100 | c.used += size; 101 | *len += 1; 102 | return; 103 | } 104 | } 105 | if let Some(c) = self.chunks.last_mut() { 106 | if end == c.data.add(c.used) && c.used + size <= c.capacity { 107 | unsafe {*(c.data.add(c.used) as *mut T) = add}; 108 | c.used += size; 109 | *len += 1; 110 | return; 111 | } 112 | } 113 | 114 | // Grow chunk size exponentially to avoid rapidly eating all memory with O(n^2) waste if one array is bigger than default chunk size (ask how I know). 115 | let bytes = size * (*len + 1); 116 | let i = self.pick_chunk(bytes * 2 + align - 1, bytes); 117 | let c = &mut self.chunks[i]; 118 | c.used = ((c.used + align - 1) & !(align - 1)) + bytes; 119 | let r: *mut T = c.data.add(c.used-bytes) as _; 120 | ptr::copy_nonoverlapping(*start, r, *len); 121 | *r.add(*len) = add; 122 | *start = r as _; 123 | *len += 1; 124 | } 125 | } 126 | 127 | pub fn capacity(&self) -> usize { 128 | let mut r = 0; 129 | for c in &self.chunks { 130 | r += c.capacity; 131 | } 132 | r 133 | } 134 | pub fn used(&self) -> usize { 135 | let mut r = 0; 136 | for c in &self.chunks { 137 | r += c.used; 138 | } 139 | r 140 | } 141 | 142 | // Intended for single-type arenas. 143 | pub unsafe fn iter<'a, T: 'a>(&'a self) -> impl Iterator { Iter {arena: self, chunk_idx: 0, offset: 0, _boo: PhantomData::default()} } 144 | pub unsafe fn iter_mut<'a, T: 'a>(&'a mut self) -> impl Iterator { IterMut {arena: self, chunk_idx: 0, offset: 0, _boo: PhantomData::default()} } 145 | 146 | pub fn write<'a>(&'a mut self) -> ArenaWrite<'a> { 147 | ArenaWrite {arena: self, len: 0, chunk_idx: usize::MAX, start: ptr::null_mut(), capacity: 0} 148 | } 149 | 150 | fn pick_chunk(&mut self, size: usize, likely_usage: usize) -> usize { 151 | if let &Some(i) = &self.current_chunk { 152 | if self.chunks[i].capacity - self.chunks[i].used >= size { 153 | return i; 154 | } 155 | // This may be useful in case the arena was cleared. 156 | if i + 1 < self.chunks.len() && self.chunks[i+1].capacity - self.chunks[i+1].used >= size { 157 | self.current_chunk = Some(i+1); 158 | return i+1; 159 | } 160 | } 161 | let res = self.chunks.len(); 162 | 163 | let capacity = CHUNK_SIZE.max(size); 164 | let allocated = capacity; 165 | let layout = Layout::from_size_align(allocated, 1).unwrap(); 166 | let p = unsafe { alloc(layout) }; 167 | if p.is_null() { 168 | handle_alloc_error(layout); 169 | } 170 | let c = Chunk {data: p, allocated, capacity, used: 0}; 171 | 172 | // Between the new chunk and current_chunk, nominate one to be current and the other to be immutable. 173 | // Useful if there are both small and huge (> CHUNK_SIZE) allocations. 174 | if let &Some(i) = &self.current_chunk { 175 | if self.chunks[i].capacity - self.chunks[i].used < capacity - likely_usage { 176 | self.current_chunk = Some(res); 177 | } 178 | } else { 179 | self.current_chunk = Some(res); 180 | } 181 | 182 | self.chunks.push(c); 183 | res 184 | } 185 | } 186 | 187 | impl Drop for Chunk { 188 | fn drop(&mut self) { 189 | unsafe { dealloc(self.data, Layout::from_size_align(self.allocated as usize, 1).unwrap()); } 190 | } 191 | } 192 | 193 | struct Iter<'a, T: 'a> { 194 | arena: &'a Arena, 195 | chunk_idx: usize, 196 | offset: usize, 197 | _boo: PhantomData<&'a [T]>, 198 | } 199 | impl<'a, T: 'a> Iterator for Iter<'a, T> { 200 | type Item = &'a T; 201 | 202 | fn next(&mut self) -> Option { 203 | let entry_size = mem::size_of::(); 204 | loop { 205 | if self.chunk_idx >= self.arena.chunks.len() { 206 | return None; 207 | } 208 | let chunk = &self.arena.chunks[self.chunk_idx]; 209 | if self.offset + entry_size > chunk.used { 210 | self.chunk_idx += 1; 211 | self.offset = 0; 212 | continue; 213 | } 214 | let r: &'a T = unsafe {&*(chunk.data.add(self.offset) as *const T)}; 215 | self.offset += entry_size; 216 | return Some(r) 217 | } 218 | } 219 | } 220 | 221 | struct IterMut<'a, T: 'a> { 222 | arena: &'a mut Arena, 223 | chunk_idx: usize, 224 | offset: usize, 225 | _boo: PhantomData<&'a mut [T]>, 226 | } 227 | impl<'a, T: 'a> Iterator for IterMut<'a, T> { 228 | type Item = &'a mut T; 229 | 230 | fn next(&mut self) -> Option { 231 | let entry_size = mem::size_of::(); 232 | loop { 233 | if self.chunk_idx >= self.arena.chunks.len() { 234 | return None; 235 | } 236 | let chunk = &mut self.arena.chunks[self.chunk_idx]; 237 | if self.offset + entry_size > chunk.used { 238 | self.chunk_idx += 1; 239 | self.offset = 0; 240 | continue; 241 | } 242 | let r: &'a mut T = unsafe {&mut *(chunk.data.add(self.offset) as *mut T)}; 243 | self.offset += entry_size; 244 | return Some(r) 245 | } 246 | } 247 | } 248 | 249 | pub struct ArenaWrite<'a> { 250 | arena: &'a mut Arena, 251 | len: usize, 252 | 253 | chunk_idx: usize, 254 | start: *mut u8, 255 | capacity: usize, 256 | } 257 | 258 | impl<'a> ArenaWrite<'a> { 259 | // Completes the write. If the ArenaWrite is dropped without calling this, the write is cancelled and nothing breaks. 260 | pub fn finish(self) -> &'static [u8] { 261 | if self.len == 0 { 262 | return &mut []; 263 | } 264 | let c = &mut self.arena.chunks[self.chunk_idx]; 265 | c.used += self.len; 266 | unsafe {slice::from_raw_parts(c.data.add(c.used-self.len), self.len)} 267 | } 268 | pub fn finish_str(self) -> &'static str { unsafe {mem::transmute(self.finish())} } 269 | } 270 | 271 | impl io::Write for ArenaWrite<'_> { 272 | fn write(&mut self, buf: &[u8]) -> io::Result { 273 | let new_size = self.len + buf.len(); 274 | if new_size > self.capacity { 275 | // Migrate to new chunk. 276 | 277 | // Exponential growth if this one string is bigger than a whole default chunk size. 278 | let new_idx = self.arena.pick_chunk(new_size * 2, new_size); 279 | let new_chunk = &mut self.arena.chunks[new_idx]; 280 | let new_start = unsafe {new_chunk.data.add(new_chunk.used)}; 281 | if self.len != 0 { 282 | unsafe { ptr::copy_nonoverlapping(self.start, new_start, self.len); } 283 | } 284 | 285 | self.chunk_idx = new_idx; 286 | self.start = new_start; 287 | self.capacity = new_chunk.capacity - new_chunk.used; 288 | } 289 | 290 | unsafe { ptr::copy_nonoverlapping(buf.as_ptr(), self.start.add(self.len), buf.len()); } 291 | self.len += buf.len(); 292 | 293 | Ok(buf.len()) 294 | } 295 | 296 | fn flush(&mut self) -> io::Result<()> { Ok(()) } 297 | } 298 | impl cpp_demangle::DemangleWrite for ArenaWrite<'_> { 299 | fn write_string(&mut self, s: &str) -> fmt::Result { 300 | self.write(s.as_bytes()).unwrap(); 301 | Ok(()) 302 | } 303 | } 304 | 305 | impl StringTable { 306 | pub fn new() -> Self { Self {arena: Arena::new(), strings: Vec::new()} } 307 | 308 | pub fn add_slice(&mut self, s: &[u8], id: usize) -> &'static [u8] { 309 | let s = self.arena.add_slice(s); 310 | self.strings.push(StringRef {s, id}); 311 | s 312 | } 313 | pub fn add_str(&mut self, s: &str, id: usize) -> &'static str { unsafe {mem::transmute(self.add_slice(s.as_bytes(), id))} } 314 | pub fn add_path(&mut self, p: &Path, id: usize) -> &'static Path { unsafe {mem::transmute(self.add_slice(p.as_os_str().as_bytes(), id))} } 315 | 316 | pub fn write<'a>(&'a mut self) -> StringTableWrite<'a> { StringTableWrite {table: self, w: self.arena.write()} } 317 | 318 | pub fn binary_search(&self, s: &[u8]) -> Option { 319 | let i = self.strings.partition_point(|x| x.s < s); 320 | if self.strings.get(i).is_some_and(|x| x.s == s) { 321 | Some(self.strings[i].id) 322 | } else { 323 | None 324 | } 325 | } 326 | } 327 | 328 | pub struct StringTableWrite<'a> { 329 | table: *mut StringTable, 330 | w: ArenaWrite<'a>, 331 | } 332 | 333 | impl StringTableWrite<'_> { 334 | pub fn finish(self, id: usize) -> &'static [u8] { 335 | let s = self.w.finish(); 336 | let table = unsafe {&mut *self.table}; 337 | table.strings.push(StringRef {s, id}); 338 | s 339 | } 340 | pub fn finish_str(self, id: usize) -> &'static str { unsafe {mem::transmute(self.finish(id))} } 341 | } 342 | impl io::Write for StringTableWrite<'_> { fn write(&mut self, buf: &[u8]) -> io::Result { self.w.write(buf) } fn flush(&mut self) -> io::Result<()> { Ok(()) } } 343 | impl cpp_demangle::DemangleWrite for StringTableWrite<'_> { fn write_string(&mut self, s: &str) -> fmt::Result { self.w.write_string(s) } } 344 | 345 | // Small helper for appending things like "#2", "#3", etc to names of type/variable/etc to make them unique. 346 | // Notice that: 347 | // * '#' is less than any character we expect to see in type/variable names (there are 3 printable characters less than '#': '"', '!', and ' '). 348 | // * Suffixes in the same group of equal names have the same length, e.g. "x#01", "x#02", ..., "x#42". 349 | // Together these two properties ensure that a sorted list of names remains sorted after appending these suffixes. 350 | #[derive(Clone, Copy)] 351 | pub struct DisambiguatingSuffixes { 352 | pub num_digits: usize, 353 | } 354 | impl DisambiguatingSuffixes { 355 | pub fn new(count: usize) -> Self { 356 | let num_digits = if count > 1 { 357 | ((count + 1) as f64).log10().ceil() as usize 358 | } else { 359 | 0 360 | }; 361 | Self {num_digits} 362 | } 363 | 364 | pub fn disambiguate(self, s: &str, idx: usize, id: usize, table: &mut StringTable) -> (&'static str, /*changed*/ bool) { 365 | if idx == 0 { 366 | (table.add_str(s, id), false) 367 | } else { 368 | let mut out = table.write(); 369 | write!(out, "{}#{:0>2$}", s, idx + 1, self.num_digits).unwrap(); 370 | (out.finish_str(id), true) 371 | } 372 | } 373 | } 374 | 375 | #[cfg(test)] 376 | mod tests { 377 | use crate::arena::*; 378 | #[test] 379 | fn arena_push() { 380 | let mut arena = Arena::new(); 381 | let mut p: *const u64 = [].as_ptr(); 382 | let mut l: usize = 0; 383 | arena.push_to_array_likely_at_end(&mut p, &mut l, 10); 384 | arena.push_to_array_likely_at_end(&mut p, &mut l, 20); 385 | arena.push_to_array_likely_at_end(&mut p, &mut l, 30); 386 | arena.push_to_array_likely_at_end(&mut p, &mut l, 40); 387 | assert_eq!(arena.chunks.len(), 1); 388 | assert_eq!(arena.chunks[0].used, 8*4); 389 | arena.add(8u8); 390 | assert_eq!(arena.chunks[0].used, 8*4+1); 391 | arena.push_to_array_likely_at_end(&mut p, &mut l, 50); 392 | assert_eq!(arena.chunks[0].used, 8*10); 393 | let s = unsafe {slice::from_raw_parts(p, l)}; 394 | assert_eq!(s, [10, 20, 30, 40, 50]); 395 | } 396 | 397 | #[test] 398 | fn string_table() { 399 | let mut t = StringTable::new(); 400 | t.add_str("hello", 10); 401 | assert_eq!(t.arena.chunks.len(), 1); 402 | t.add_slice(&vec![b'-'; CHUNK_SIZE - 2], 20); 403 | assert_eq!(t.arena.chunks.len(), 2); 404 | t.add_str("world", 30); 405 | { 406 | let mut w = t.write(); 407 | w.write(&vec![b'+'; CHUNK_SIZE - 13]).unwrap(); 408 | w.finish(40); 409 | } 410 | assert_eq!(t.arena.chunks.len(), 3); 411 | let mut w = t.write(); 412 | assert_eq!(w.w.arena.chunks.len(), 3); 413 | w.write(&vec![b'%'; CHUNK_SIZE - 1]).unwrap(); 414 | w.write(&[]).unwrap(); 415 | assert_eq!(w.w.arena.chunks.len(), 4); 416 | w.write(&[b'%']).unwrap(); 417 | assert_eq!(w.w.arena.chunks.len(), 4); 418 | w.write(&[b'%'; CHUNK_SIZE/2]).unwrap(); 419 | w.finish(50); 420 | assert_eq!(t.arena.chunks.len(), 4); 421 | 422 | assert_eq!(t.strings.len(), 5); 423 | assert_eq!(t.strings[0], StringRef {s: b"hello".as_slice(), id: 10}); 424 | assert_eq!(t.strings[2], StringRef {s: b"world".as_slice(), id: 30}); 425 | assert_eq!(t.strings[3].s, vec![b'+'; CHUNK_SIZE - 13].as_slice()); 426 | assert_eq!(t.strings[3].id, 40); 427 | assert_eq!(t.strings[1].s, vec![b'-'; CHUNK_SIZE - 2].as_slice()); 428 | assert_eq!(t.strings[1].id, 20); 429 | assert_eq!(t.strings[4].s, vec![b'%'; CHUNK_SIZE + CHUNK_SIZE/2].as_slice()); 430 | assert_eq!(t.strings[4].id, 50); 431 | 432 | assert_eq!(t.arena.used(), CHUNK_SIZE*3 + CHUNK_SIZE/2 - 13 - 2 + "hello".len() + "world".len()); 433 | } 434 | } 435 | -------------------------------------------------------------------------------- /src/bin/demangler_exploration.rs: -------------------------------------------------------------------------------- 1 | // I gave up on this for now. 2 | // Problems: 3 | // * The mangling scheme is way too complicated. 4 | // * Even if we just want function names, we have to parse pretty much the whole grammar, because template arguments may have arbitrary complicated type names and expressions. 5 | // * Even if we want to ignore parts of the name (e.g. function arguments), we have to parse them anyway because they can be referenced by substitutions later. 6 | fn main() {} 7 | /* 8 | use std::{fmt, str}; 9 | 10 | type Result = std::result::Result; 11 | 12 | struct Ctx { 13 | out: String, 14 | } 15 | 16 | macro_rules! return_err { 17 | () => ( 18 | return Err(fmt::Error::default()); 19 | ); 20 | } 21 | 22 | macro_rules! require { 23 | ($pred:expr) => ( 24 | if !($pred) { 25 | return_err!() 26 | } 27 | ); 28 | } 29 | 30 | impl From for fmt::Error { fn from(error: str::Utf8Error) -> Self { Self::default(); } } 31 | 32 | // Function names are grammar symbols from https://itanium-cxx-abi.github.io/cxx-abi/abi.html#mangling 33 | 34 | fn mangled_name(ctx: &mut Ctx, s: &mut &[u8]) -> Result<()> { 35 | // ::= _Z [ . ] 36 | 37 | require!(s.starts_with(b"_Z")); 38 | *s = &s[2..]; 39 | encoding(ctx, s)?; 40 | 41 | if !s.is_empty() { 42 | // 43 | require!(s[0] == b'.') 44 | self.out.write_str(str::from_utf8(*s)?)?; 45 | } 46 | 47 | Ok(()) 48 | } 49 | 50 | fn encoding(ctx: &mut Ctx, s: &mut &[u8]) -> Result<()> { 51 | // ::= [ ] 52 | // ::= 53 | 54 | // ::= TV # virtual table 55 | // ::= TT # VTT structure (construction vtable index) 56 | // ::= TI # typeinfo structure 57 | // ::= TS # typeinfo name (null-terminated byte string) 58 | // ::= T 59 | // ::= Tc 60 | // ::= GV # Guard variable for one-time initialization 61 | // ::= GR _ # First temporary 62 | // ::= GR _ # Subsequent temporaries 63 | // ::= GTt 64 | // ::= h _ 65 | // ::= v _ 66 | // ::= # non-virtual base override 67 | // ::= _ # virtual base override, with vcall offset 68 | 69 | require!(s.len() >= 2); 70 | let original = *s; 71 | let token = s[..2]; 72 | *s = &s[2..]; 73 | match token { 74 | b"TV" => { ctx.out.push_str("vtable "); type_(ctx, s)?; } 75 | b"TT" => { ctx.out.push_str("vtt "); type_(ctx, s)?; } 76 | b"TI" => { ctx.out.push_str("typeinfo "); type_(ctx, s)?; } 77 | b"TS" => { ctx.out.push_str("typeinfo name "); type_(ctx, s)?; } 78 | b"Th" | b"Tv" => { 79 | *s = original[1..]; 80 | ctx.out.push_str("virtual override thunk "); 81 | call_offset(ctx, s)?; 82 | ctx.out.push_str(", "); 83 | encoding(ctx, s)?; 84 | } 85 | b"Tc" => { 86 | ctx.out.push_str("virtual override thunk "); 87 | call_offset(ctx, s)?; 88 | ctx.out.push_str(", "); 89 | call_offset(ctx, s)?; 90 | ctx.out.push_str(", "); 91 | encoding(ctx, s)?; 92 | } 93 | b"GV" => { ctx.out.push_str("guard variable "); name(ctx, s)?; } 94 | b"GR" => { 95 | ctx.out.push_str("guard temporary "); 96 | name(ctx, s)?; 97 | if !s.starts_with(b"_") { 98 | let seq = seq_id(ctx, s)?; 99 | write!(ctx.out, " {}", seq); 100 | } 101 | require!(s.starts_with(b"_")); 102 | *s = &s[1..]; 103 | } 104 | //etc 105 | _ => return_err!() 106 | } 107 | } 108 | 109 | fn name(ctx: &mut Ctx, s: &mut &[u8]) -> Result<()> { 110 | // ::= N [] [] E # nested-name 111 | // ::= N [] [] E # nested-name 112 | // ::= 113 | // ::= 114 | // ::= 115 | // ::= + 116 | // ::= # class template specialization 117 | // ::= M # , variable template, initializer of a variable or data member 118 | // ::= [ ] M # , initializer of a variable or data member 119 | // ::= # template type parameter 120 | // ::= # decltype qualifier 121 | // ::= 122 | // ::= # global template 123 | // ::= # nested template 124 | // ::= # template template parameter 125 | // ::= 126 | // ::= * 127 | // ::= 128 | // ::= 129 | // ::= 130 | // ::= DC + E # structured binding declaration 131 | // ::= B 132 | // ::= C1 # complete object constructor 133 | // ::= C2 # base object constructor 134 | // ::= C3 # complete object allocating constructor 135 | // ::= CI1 # complete object inheriting constructor 136 | // ::= CI2 # base object inheriting constructor 137 | // ::= D0 # deleting destructor 138 | // ::= D1 # complete object destructor 139 | // ::= D2 # base object destructor 140 | // ::= Ut [ ] _ 141 | // ::= Ul E [ ] _ 142 | // ::= + # Parameter types or "v" if the lambda has no parameters 143 | // ::= 144 | // ::= St # ::std:: 145 | // ::= 146 | // ::= 147 | // ::= Z E [] 148 | // ::= Z E s [] 149 | // ::= Z Ed [ ] _ 150 | // ::= _ # when number < 10 151 | // ::= __ _ # when number >= 10 152 | 153 | // ::= 154 | // ::= 155 | 156 | // ::= [r] [V] [K] # restrict (C99), volatile, const 157 | // ::= (R | O) # &, && 158 | 159 | // ::= I + E 160 | // ::= # type or template 161 | // ::= X E # expression 162 | // ::= # simple expressions 163 | // ::= J * E # argument pack 164 | 165 | // ::= T [] _ 166 | 167 | // ::= 168 | // ::= 169 | 170 | // ::= <0-9A-Z>+ $ base 36 171 | 172 | // ::= [n] # 'n' is a minus sign 173 | 174 | // ::= + # types are possible return type, then parameter types 175 | 176 | // ::= 177 | // ::= 178 | // ::= 179 | // ::= 180 | // ::= 181 | // ::= 182 | // ::= 183 | // ::= 184 | // ::= 185 | // ::= P # pointer 186 | // ::= R # l-value reference 187 | // ::= O # r-value reference (C++11) 188 | // ::= C # complex pair (C99) 189 | // ::= G # imaginary (C99) 190 | // ::= 191 | // ::= Dp # pack expansion (C++11) 192 | 193 | // ::= Dt E # decltype of an id-expression or class member access (C++11) 194 | // ::= DT E # decltype of an expression (C++11) 195 | 196 | // ::= S _ 197 | // ::= S_ 198 | // ::= St # ::std:: 199 | // ::= Sa # ::std::allocator 200 | // ::= Sb # ::std::basic_string 201 | // ::= Ss # ::std::basic_string, ::std::allocator > 202 | // ::= Si # ::std::basic_istream > 203 | // ::= So # ::std::basic_ostream > 204 | // ::= Sd # ::std::basic_iostream > 205 | 206 | // ::= nw # new 207 | // ::= na # new[] 208 | // ::= dl # delete 209 | // ::= da # delete[] 210 | // ::= aw # co_await 211 | // ::= ps # + (unary) 212 | // ::= ng # - (unary) 213 | // ::= ad # & (unary) 214 | // ::= de # * (unary) 215 | // ::= co # ~ 216 | // ::= pl # + 217 | // ::= mi # - 218 | // ::= ml # * 219 | // ::= dv # / 220 | // ::= rm # % 221 | // ::= an # & 222 | // ::= or # | 223 | // ::= eo # ^ 224 | // ::= aS # = 225 | // ::= pL # += 226 | // ::= mI # -= 227 | // ::= mL # *= 228 | // ::= dV # /= 229 | // ::= rM # %= 230 | // ::= aN # &= 231 | // ::= oR # |= 232 | // ::= eO # ^= 233 | // ::= ls # << 234 | // ::= rs # >> 235 | // ::= lS # <<= 236 | // ::= rS # >>= 237 | // ::= eq # == 238 | // ::= ne # != 239 | // ::= lt # < 240 | // ::= gt # > 241 | // ::= le # <= 242 | // ::= ge # >= 243 | // ::= ss # <=> 244 | // ::= nt # ! 245 | // ::= aa # && 246 | // ::= oo # || 247 | // ::= pp # ++ (postfix in context) 248 | // ::= mm # -- (postfix in context) 249 | // ::= cm # , 250 | // ::= pm # ->* 251 | // ::= pt # -> 252 | // ::= cl # () 253 | // ::= ix # [] 254 | // ::= qu # ? 255 | // ::= cv # (cast) 256 | // ::= li # operator "" 257 | // ::= v # vendor extended operator 258 | } 259 | 260 | fn main() -> Result<()> { 261 | let mut ctx = Ctx {out: String::new()}; 262 | let s = b"_Z2hi"; 263 | let mut p = &s; 264 | mangled_name(&mut ctx, &mut p)?; 265 | println!("{}", ctx.out); 266 | Ok(()) 267 | } 268 | */ 269 | -------------------------------------------------------------------------------- /src/bin/hashmap_vs_sort_bench.rs: -------------------------------------------------------------------------------- 1 | use rand; // 0.8.5 2 | use rand::{Rng, distributions::Alphanumeric}; 3 | use std::{time::Instant, collections::HashMap, collections::hash_map::DefaultHasher, hash::{Hash, Hasher}, ops::Range}; 4 | 5 | fn main() { 6 | let prep_start = Instant::now(); 7 | let mut rng = rand::thread_rng(); 8 | let mut pool: String = String::new(); 9 | let mut ranges: Vec> = Vec::new(); 10 | for _ in 0..10000000 { 11 | let start = pool.len(); 12 | let len = rng.gen_range(1..20); 13 | for _ in 0..len { 14 | pool.push(rng.sample(Alphanumeric) as char); 15 | } 16 | ranges.push(start..pool.len()); 17 | } 18 | let mut strs: Vec<&str> = ranges.iter().map(|r| pool.get(r.clone()).unwrap()).collect(); 19 | 20 | let hash_start = Instant::now(); 21 | println!("prep: {:.3}ms, len: {:.3}M", (hash_start - prep_start).as_millis(), pool.len() as f64 / 1e6); 22 | 23 | let mut hashes: Vec<(u64, [usize; 4])> = Vec::with_capacity(strs.len()); 24 | for s in strs.iter().copied() { 25 | let mut hasher = DefaultHasher::new(); 26 | s.hash(&mut hasher); 27 | hashes.push((hasher.finish(), [0; 4])); 28 | } 29 | 30 | let map_start = Instant::now(); 31 | println!("hash: {:.3}ms", (map_start - hash_start).as_millis()); 32 | 33 | let mut map: HashMap<&str, usize> = HashMap::new(); 34 | for s in strs.iter().copied() { 35 | let len = map.len(); 36 | map.entry(s).or_insert(len); 37 | } 38 | 39 | let sort_start = Instant::now(); 40 | println!("map: {:.3}ms, len: {:.3}M", (sort_start - map_start).as_millis(), map.len() as f64 / 1e6); 41 | 42 | strs.sort_unstable(); 43 | let hsort_start = Instant::now(); 44 | println!("sort: {:.3}ms, first: {}, last: {}", (hsort_start - sort_start).as_millis(), strs[0], strs.last().unwrap()); 45 | 46 | hashes.sort_unstable_by_key(|h| h.0); 47 | let main_end = Instant::now(); 48 | println!("hsort: {:.3}ms, first: {}, last: {}", (main_end - hsort_start).as_millis(), hashes[0].0, hashes.last().unwrap().0); 49 | } 50 | -------------------------------------------------------------------------------- /src/bin/load_symbols.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_imports)] 2 | extern crate nnd; 3 | use nnd::{*, error::*, elf::*, log::*, symbols::*, types::*, arena::*, util::*}; 4 | use std::{fs::{File}, io::{self, Write}, sync::Arc, mem, sync::atomic::{AtomicBool, Ordering}, cell::UnsafeCell, thread, thread::JoinHandle}; 5 | 6 | fn main() -> Result<()> { 7 | std::env::set_var("RUST_BACKTRACE", "1"); 8 | let args: Vec = std::env::args().collect(); 9 | if args.len() < 2 { 10 | eprintln!("Usage: {} binary", args[0]); 11 | std::process::exit(1); 12 | } 13 | let path = args[1].clone(); 14 | 15 | dbg!(mem::size_of::()); 16 | dbg!(mem::size_of::()); 17 | dbg!(mem::size_of::()); 18 | dbg!(mem::size_of::()); 19 | dbg!(mem::size_of::()); 20 | dbg!(mem::size_of::()); 21 | dbg!(mem::size_of::()); 22 | dbg!(mem::size_of::()); 23 | 24 | let file = File::open(&path)?; 25 | let elf = Arc::new(ElfFile::from_file(path.clone(), &file, file.metadata()?.len())?); 26 | let symbols; 27 | 28 | { 29 | let _prof = ProfileScope::new("loading symbols".to_string()); 30 | 31 | let status = Arc::new(SymbolsLoadingStatus::new()); 32 | let num_threads = thread::available_parallelism().map_or(8, |n| n.get()) - 1; 33 | let loader = Arc::new(SyncUnsafeCell::new(SymbolsLoader::new(vec![elf], 1, num_threads, status.clone())?)); 34 | let num_shards = unsafe {&*loader.get()}.num_shards; 35 | let mut stage_idx = 1usize; 36 | while unsafe {&mut *loader.get()}.prepare_stage(stage_idx)? { 37 | eprintln!("stage {}", stage_idx); 38 | let threads: Vec> = (0..num_shards).map(|shard_idx| { 39 | let loader_copy = loader.clone(); 40 | let status_copy = status.clone(); 41 | thread::spawn(move || if let Err(e) = unsafe {&*loader_copy.get()}.run(stage_idx, shard_idx) { 42 | status_copy.cancel.store(true, Ordering::Relaxed); 43 | eprintln!("error: {}", e); 44 | }) 45 | }).collect(); 46 | for t in threads { 47 | t.join().unwrap(); 48 | } 49 | if status.cancel.load(Ordering::Relaxed) { 50 | return err!(Dwarf, "failed to load"); 51 | } 52 | stage_idx += 1; 53 | } 54 | symbols = Arc::into_inner(loader).unwrap().into_inner().into_result(); 55 | eprint!("\r"); 56 | } 57 | 58 | //dbg!(symbols.types.types.len()); 59 | //dbg!(symbols.types.name_to_type.len()); 60 | //dbg!(symbols.types.type_names.calculate_used_bytes()); 61 | //dbg!(symbols.types.misc_strings.calculate_used_bytes()); 62 | //dbg!(symbols.types.type_names.calculate_count()); 63 | //dbg!(symbols.types.misc_strings.calculate_count()); 64 | //dbg!(symbols.types.fields.len()); 65 | //dbg!(symbols.types.enumerands.len()); 66 | //dbg!(symbols.types.descriptor_to_type.len()); 67 | //dbg!(symbols.local_variables.len()); 68 | 69 | let mut out = io::BufWriter::new(io::stdout()); 70 | writeln!(out, "{} functions", symbols.functions.len())?; 71 | //for i in 0..symbols.types.types.len() { 72 | //writeln!(out, "{:?}", DumpType {types: &symbols.types, idx: TypeIdx(i)})?; 73 | //} 74 | 75 | /* 76 | for i in 0..symbols.local_variables.len() { 77 | let v = &symbols.local_variables[i]; 78 | writeln!(out, "{}: {} {:?}", i, unsafe {v.name()}, v)?; 79 | } 80 | for f in &symbols.addr_to_function { 81 | writeln!(out, "{:x}: {} {:?}", f.addr, unsafe {symbols.function_mangled_names.get_str_or(StringRef(f.mangled_name), "")}, f.local_variables)?; 82 | }*/ 83 | 84 | Ok(()) 85 | } 86 | -------------------------------------------------------------------------------- /src/context.rs: -------------------------------------------------------------------------------- 1 | use crate::{*, executor::*, settings::*, util::*}; 2 | use std::{sync::Arc}; 3 | 4 | pub struct Context { 5 | pub settings: Settings, 6 | pub executor: Executor, 7 | // Wakes up the main thread, triggering a UI update. E.g. async search widget uses it to notify the UI of completion. 8 | pub wake_main_thread: Arc, 9 | } 10 | 11 | impl Context { 12 | pub fn invalid() -> Arc { Arc::new(Self {settings: Settings::default(), executor: Executor::invalid(), wake_main_thread: Arc::new(EventFD::new())}) } 13 | } 14 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use std::{fmt, io}; 2 | 3 | #[derive(Clone, Copy, Debug)] 4 | pub enum ErrorCode { 5 | MalformedExecutable = 1, 6 | UnsupportedExecutable = 2, 7 | Internal = 3, 8 | Usage = 4, 9 | Cancelled = 5, 10 | Format = 6, 11 | Dwarf = 7, 12 | ProcessState = 9, 13 | NotImplemented = 10, 14 | NoSection = 12, 15 | Loading = 13, 16 | OptimizedAway = 14, 17 | MissingSymbols = 15, 18 | Sanity = 16, 19 | OutOfHardwareBreakpoints = 17, 20 | NoCodeLocations = 18, 21 | TooLong = 20, 22 | Syntax = 21, 23 | NoVariable = 22, 24 | TypeMismatch = 23, 25 | Runtime = 24, 26 | NoFunction = 25, 27 | Environment = 26, 28 | NotCalculated = 27, 29 | NoField = 28, 30 | NotContainer = 29, 31 | Network = 30, 32 | Disabled = 31, 33 | } 34 | 35 | #[derive(Debug)] 36 | pub enum ErrorEnum { 37 | IO(io::Error), 38 | Code(ErrorCode), 39 | } 40 | 41 | #[derive(Clone)] 42 | pub struct Error { 43 | pub error: ErrorEnum, 44 | // TODO: Make it enum between &'static str and String, to make simple errors fast. 45 | pub message: String, 46 | } 47 | 48 | pub type Result = std::result::Result; 49 | 50 | pub trait ResultWithClonableError { fn as_ref_clone_error(&self) -> Result<&T>; } 51 | impl ResultWithClonableError for Result { fn as_ref_clone_error(&self) -> Result<&T> { match self { Ok(t) => Ok(t), Err(e) => Err(e.clone()) } } } 52 | 53 | impl Error { 54 | pub fn new(code: ErrorCode, message: String) -> Error { 55 | Error {error: ErrorEnum::Code(code), message} 56 | } 57 | 58 | pub fn from_io_error(e: io::Error, message: String) -> Error { 59 | Error {error: ErrorEnum::IO(e), message} 60 | } 61 | 62 | pub fn is_no_section(&self) -> bool { match self.error { ErrorEnum::Code(ErrorCode::NoSection) => true, _ => false, } } 63 | pub fn is_loading(&self) -> bool { match self.error { ErrorEnum::Code(ErrorCode::Loading) => true, _ => false, } } 64 | pub fn is_usage(&self) -> bool { match self.error { ErrorEnum::Code(ErrorCode::Usage) => true, _ => false, } } 65 | pub fn is_missing_symbols(&self) -> bool { match self.error { ErrorEnum::Code(ErrorCode::MissingSymbols) => true, _ => false, } } 66 | pub fn is_out_of_hardware_breakpoints(&self) -> bool { match self.error { ErrorEnum::Code(ErrorCode::OutOfHardwareBreakpoints) => true, _ => false, } } 67 | pub fn is_too_long(&self) -> bool { match self.error { ErrorEnum::Code(ErrorCode::TooLong) => true, _ => false, } } 68 | pub fn is_io_not_found(&self) -> bool { match &self.error { ErrorEnum::IO(e) if e.kind() == io::ErrorKind::NotFound => true, _ => false, } } 69 | pub fn is_io_permission_denied(&self) -> bool { match &self.error { ErrorEnum::IO(e) if e.kind() == io::ErrorKind::PermissionDenied => true, _ => false, } } 70 | pub fn is_not_calculated(&self) -> bool { match &self.error { ErrorEnum::Code(ErrorCode::NotCalculated) => true, _ => false, } } 71 | pub fn is_no_field(&self) -> bool { match &self.error { ErrorEnum::Code(ErrorCode::NoField) => true, _ => false, } } 72 | pub fn is_no_function(&self) -> bool { match &self.error { ErrorEnum::Code(ErrorCode::NoFunction) => true, _ => false, } } 73 | pub fn is_no_variable(&self) -> bool { match &self.error { ErrorEnum::Code(ErrorCode::NoVariable) => true, _ => false, } } 74 | pub fn is_not_container(&self) -> bool { match &self.error { ErrorEnum::Code(ErrorCode::NotContainer) => true, _ => false, } } 75 | pub fn is_type_mismatch(&self) -> bool { match &self.error { ErrorEnum::Code(ErrorCode::TypeMismatch) => true, _ => false, } } 76 | pub fn is_not_implemented(&self) -> bool { match &self.error { ErrorEnum::Code(ErrorCode::NotImplemented) => true, _ => false, } } 77 | pub fn is_disabled(&self) -> bool { match &self.error { ErrorEnum::Code(ErrorCode::Disabled) => true, _ => false, } } 78 | } 79 | 80 | impl From for Error { 81 | fn from(error: io::Error) -> Self { 82 | Error {error: ErrorEnum::IO(error), message: String::new()} 83 | } 84 | } 85 | 86 | impl From for Error { 87 | fn from(error: std::num::ParseIntError) -> Self { 88 | Error {error: ErrorEnum::Code(ErrorCode::Format), message: format!("{}", error)} 89 | } 90 | } 91 | 92 | impl From for Error { 93 | fn from(error: gimli::Error) -> Self { 94 | Error {error: ErrorEnum::Code(ErrorCode::Dwarf), message: format!("{}", error)} 95 | } 96 | } 97 | 98 | impl From for Error { 99 | fn from(error: std::str::Utf8Error) -> Self { 100 | Error {error: ErrorEnum::Code(ErrorCode::Format), message: format!("{}", error)} 101 | } 102 | } 103 | 104 | impl From for Error { 105 | fn from(error: std::string::FromUtf8Error) -> Self { 106 | Error {error: ErrorEnum::Code(ErrorCode::Format), message: format!("{}", error)} 107 | } 108 | } 109 | 110 | impl From for Error { 111 | fn from(error: std::fmt::Error) -> Self { 112 | Error {error: ErrorEnum::Code(ErrorCode::Format), message: format!("{}", error)} 113 | } 114 | } 115 | 116 | impl From for Error { 117 | fn from(error: std::env::VarError) -> Self { 118 | Error {error: ErrorEnum::Code(ErrorCode::Environment), message: format!("{}", error)} 119 | } 120 | } 121 | 122 | // For printing to log. 123 | impl fmt::Debug for Error { 124 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 125 | match &self.error { 126 | &ErrorEnum::Code(code) => write!(f, "{}: {}", code as i64, self.message), 127 | ErrorEnum::IO(error) => write!(f, "{}: {}", self.message, error), 128 | } 129 | } 130 | } 131 | 132 | // For showing to the user. 133 | impl fmt::Display for Error { 134 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 135 | match &self.error { 136 | &ErrorEnum::Code(_) => write!(f, "{}", self.message), 137 | ErrorEnum::IO(error) if self.message.is_empty() => write!(f, "{}", error), 138 | ErrorEnum::IO(error) => write!(f, "{}: {}", self.message, error), 139 | } 140 | } 141 | } 142 | 143 | impl Clone for ErrorEnum { 144 | fn clone(&self) -> Self { 145 | match self { 146 | Self::Code(c) => Self::Code(c.clone()), 147 | Self::IO(e) => Self::IO(match e.raw_os_error() { 148 | Some(os) => io::Error::from_raw_os_error(os), 149 | None => e.kind().into(), 150 | }), 151 | } 152 | } 153 | } 154 | 155 | #[macro_export] 156 | macro_rules! error { 157 | ($code:ident, $($arg:tt)*) => ( 158 | Error {error: ErrorEnum::Code(ErrorCode::$code), message: format!($($arg)*)} 159 | ); 160 | } 161 | 162 | #[macro_export] 163 | macro_rules! err { 164 | ($code:ident, $($arg:tt)*) => ( 165 | Err(error!($code, $($arg)*)) 166 | ); 167 | } 168 | 169 | #[macro_export] 170 | macro_rules! errno_err { 171 | ($($arg:tt)*) => ( 172 | //panic!("{:?}", Error {error: ErrorEnum::IO(io::Error::last_os_error()), message: format!($($arg)*)}) 173 | Err(Error {error: ErrorEnum::IO(::std::io::Error::last_os_error()), message: format!($($arg)*)}) 174 | ); 175 | } 176 | -------------------------------------------------------------------------------- /src/executor.rs: -------------------------------------------------------------------------------- 1 | use std::{sync::{Arc, Mutex, Condvar}, thread::{self, JoinHandle}, collections::VecDeque}; 2 | 3 | pub struct Executor { 4 | pub num_threads: usize, 5 | shared: Arc, 6 | threads: Vec>, 7 | } 8 | 9 | impl Executor { 10 | pub fn invalid() -> Self { Self {num_threads: 0, shared: Arc::new(Shared {tasks: Mutex::new(VecDeque::new()), wake_workers: Condvar::new()}), threads: Vec::new()} } 11 | 12 | pub fn new(num_threads: usize) -> Self { 13 | assert!(num_threads > 0); 14 | let mut exec = Self {num_threads, shared: Arc::new(Shared {tasks: Mutex::new(VecDeque::new()), wake_workers: Condvar::new()}), threads: Vec::new()}; 15 | for i in 0..num_threads { 16 | let shared_clone = exec.shared.clone(); 17 | exec.threads.push(thread::Builder::new().name("debworker".into()).spawn(|| Self::worker_thread(shared_clone)).unwrap()); 18 | } 19 | exec 20 | } 21 | 22 | pub fn add_boxed(&self, f: Box) { 23 | self.shared.tasks.lock().unwrap().push_back(Task {f}); 24 | self.shared.wake_workers.notify_one(); 25 | } 26 | 27 | pub fn add(&self, f: F) { 28 | self.add_boxed(Box::new(f)); 29 | } 30 | 31 | fn worker_thread(shared: Arc) { 32 | loop { 33 | let task; 34 | { 35 | let mut lock = shared.tasks.lock().unwrap(); 36 | while lock.is_empty() { 37 | lock = shared.wake_workers.wait(lock).unwrap(); 38 | } 39 | task = lock.pop_front().unwrap(); 40 | } 41 | (task.f)(); 42 | } 43 | } 44 | } 45 | 46 | impl Drop for Executor { 47 | fn drop(&mut self) { 48 | // Currently we only have one Executor, which lives until the end of the program. 49 | // So we don't join threads, that would just add a few seconds of delay when exiting the program sometimes, for no benefit. 50 | } 51 | } 52 | 53 | struct Task { 54 | f: Box, 55 | } 56 | 57 | struct Shared { 58 | // We do at most hundreds of tasks per second, so not implementing anything fancy here, and not worrying about the cost of having lots of Arc-s and pointer chasing (here and in SymbolsRegistry, etc). 59 | tasks: Mutex>, 60 | wake_workers: Condvar, 61 | } 62 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | #![allow(unused_variables)] 3 | #![allow(unused_imports)] 4 | 5 | // This is only needed on the 'use gimli::*' statements (for constants like DW_AT_name), but it doesn't work there. 6 | #![allow(non_upper_case_globals)] 7 | 8 | // For the offsetof macro in util.rs, because Rust doesn't support attaching an [allow()] on an individual unsafe{} block (which in turn is only needed because Rust doesn't have offsetof, ugh). 9 | #![allow(unused_unsafe)] 10 | 11 | pub mod error; 12 | pub mod elf; 13 | pub mod debugger; 14 | pub mod util; 15 | pub mod ui; 16 | pub mod log; 17 | pub mod process_info; 18 | pub mod symbols; 19 | pub mod symbols_registry; 20 | pub mod procfs; 21 | pub mod unwind; 22 | pub mod range_index; 23 | pub mod registers; 24 | pub mod disassembly; 25 | pub mod pool; 26 | pub mod layout; 27 | pub mod settings; 28 | pub mod types; 29 | pub mod expr; 30 | pub mod search; 31 | pub mod widgets; 32 | pub mod arena; 33 | pub mod executor; 34 | pub mod context; 35 | pub mod interp; 36 | pub mod pretty; 37 | pub mod persistent; 38 | pub mod doc; 39 | pub mod common_ui; 40 | pub mod imgui; 41 | pub mod terminal; 42 | pub mod dwarf; 43 | pub mod core_dumper; 44 | -------------------------------------------------------------------------------- /src/log.rs: -------------------------------------------------------------------------------- 1 | use crate::{*, util::*, common_ui::*}; 2 | use std::{collections::VecDeque, time::Instant, mem, fmt::Write, sync::atomic::{AtomicUsize, AtomicU64, Ordering}}; 3 | use core::arch::x86_64::_rdtsc; 4 | 5 | // We should rework logging. I initially thought we'd want to show a very minimal log on screen. 6 | // (Not at all like a typical server log, where you spam 1 MB/s of garbage.) 7 | // That's what this current struct is about. 8 | // But that's probably not very useful, and instead we should have two separate things: 9 | // 1. Showing nice errors in intelligently chosen places in the UI. E.g. if a binary is missing debug symbols, show that in the list of binaries. We already do this. 10 | // 2. Optionally writing various warnings to a log file, intended for the developer (me). E.g. if DWARF contains something unexpected or is missing something important. 11 | // Currently I'm using stderr for that, which may or may not be good enough. 12 | pub struct Log { 13 | pub lines: VecDeque, 14 | pub prof: Profiling, 15 | } 16 | 17 | const MAX_LINES: usize = 100; 18 | 19 | impl Log { 20 | pub fn new() -> Log { 21 | Log {lines: VecDeque::new(), prof: Profiling::new()} 22 | } 23 | 24 | pub fn add_line(&mut self, line: String) { 25 | self.lines.push_back(line); 26 | while self.lines.len() > MAX_LINES { 27 | self.lines.pop_front(); 28 | } 29 | } 30 | 31 | pub fn clear(&mut self) { 32 | self.lines.clear(); 33 | } 34 | } 35 | #[macro_export] 36 | macro_rules! log { 37 | ($log:expr, $($arg:tt)*) => ( 38 | ($log).add_line(format!($($arg)*)) 39 | ); 40 | } 41 | 42 | // A very minimal profiling thing. Prints to stderr in destructor. 43 | pub struct ProfileScope { 44 | name: String, 45 | start: Instant, 46 | start_tsc: u64, 47 | total_tsc: u64, 48 | threshold_secs: f64, 49 | active: bool, 50 | multi: bool, 51 | } 52 | 53 | fn rdtsc() -> u64 { 54 | unsafe {_rdtsc()} // why is it unsafe? 55 | } 56 | 57 | impl ProfileScope { 58 | pub fn new(name: String) -> Self { 59 | ProfileScope {start: Instant::now(), start_tsc: 0, total_tsc: 0, name: name, threshold_secs: 0.0, active: true, multi: false} 60 | } 61 | 62 | pub fn with_threshold(secs: f64, name: String) -> Self { 63 | ProfileScope {start: Instant::now(), start_tsc: 0, total_tsc: 0, name: name, threshold_secs: secs, active: true, multi: false} 64 | } 65 | 66 | pub fn multi(name: String) -> Self { 67 | ProfileScope {start: Instant::now(), start_tsc: rdtsc(), total_tsc: 0, name: name, threshold_secs: 0.0, active: true, multi: true} 68 | } 69 | 70 | pub fn multi_with_threshold(secs: f64, name: String) -> Self { 71 | ProfileScope {start: Instant::now(), start_tsc: rdtsc(), total_tsc: 0, name: name, threshold_secs: secs, active: true, multi: true} 72 | } 73 | 74 | pub fn add(&mut self, tsc: u64) { 75 | assert!(self.multi); 76 | self.total_tsc += tsc; 77 | } 78 | } 79 | 80 | impl Drop for ProfileScope { 81 | fn drop(&mut self) { 82 | if !self.active { 83 | return; 84 | } 85 | let mut secs = self.start.elapsed().as_secs_f64(); 86 | if self.multi { 87 | let end_tsc = rdtsc(); 88 | let end_tsc = end_tsc.max(self.start_tsc + 1); 89 | secs = secs * (self.total_tsc as f64 / ((end_tsc - self.start_tsc) as f64)); 90 | } 91 | if self.threshold_secs <= 0.0 || secs >= self.threshold_secs { 92 | eprintln!("info: {} took {:.3}s", self.name, secs); 93 | } 94 | } 95 | } 96 | 97 | pub struct TscScope { 98 | start: u64, 99 | } 100 | impl TscScope { 101 | pub fn new() -> Self { 102 | Self {start: rdtsc()} 103 | } 104 | 105 | pub fn restart(&mut self) -> u64 { 106 | let t = rdtsc(); 107 | t.saturating_sub(mem::replace(&mut self.start, t)) 108 | } 109 | 110 | pub fn finish(mut self) -> u64 { 111 | self.restart() 112 | } 113 | } 114 | 115 | pub struct TscScopeExcludingSyscalls { 116 | scope: TscScope, 117 | prev_syscall_tsc: u64, 118 | } 119 | impl TscScopeExcludingSyscalls { 120 | pub fn new(prof: &ProfileBucket) -> Self { Self {scope: TscScope::new(), prev_syscall_tsc: prof.syscall_tsc} } 121 | 122 | pub fn restart(&mut self, prof: &ProfileBucket) -> u64 { 123 | let t = prof.syscall_tsc; 124 | self.scope.restart().saturating_sub(t.saturating_sub(mem::replace(&mut self.prev_syscall_tsc, t))) 125 | } 126 | pub fn finish(mut self, prof: &ProfileBucket) -> u64 { 127 | self.restart(prof) 128 | } 129 | } 130 | 131 | pub struct LogTimestampInDestructor(pub &'static str); 132 | impl Drop for LogTimestampInDestructor { 133 | fn drop(&mut self) { 134 | let mut t: libc::timespec; 135 | let r; 136 | unsafe { 137 | t = mem::zeroed(); 138 | r = libc::clock_gettime(libc::CLOCK_MONOTONIC, &mut t as _); 139 | } 140 | assert!(r == 0); 141 | eprintln!("[{}.{:09}] {}", t.tv_sec, t.tv_nsec, self.0); 142 | } 143 | } 144 | 145 | pub struct ProfileBucket { 146 | // Currently not all syscalls are instrumented, only the few ones that are likely to be frequent in practice (especially syscalls that happen for each thread or for each ptrace event). 147 | pub syscall_count: usize, 148 | pub syscall_tsc: u64, 149 | pub debugger_count: usize, 150 | pub debugger_tsc: u64, 151 | pub ui_max_tsc: u64, 152 | pub other_tsc: u64, 153 | 154 | pub ui_build_max_tsc: u64, 155 | pub ui_render_max_tsc: u64, 156 | pub ui_fill_max_tsc: u64, 157 | pub ui_input_bytes: usize, 158 | pub ui_output_bytes: usize, 159 | 160 | pub s_per_tsc: f64, 161 | 162 | pub start_tsc: u64, 163 | pub start_time: Instant, 164 | pub end_tsc: u64, 165 | pub end_time: Instant, 166 | } 167 | impl ProfileBucket { 168 | pub fn new(start_time: Instant, start_tsc: u64) -> Self { 169 | let mut r = ProfileBucket::invalid(); 170 | r.start_time = start_time; 171 | r.start_tsc = start_tsc; 172 | r 173 | } 174 | 175 | pub fn invalid() -> Self { Self {start_time: unsafe {mem::zeroed()}, start_tsc: 0, syscall_count: 0, syscall_tsc: 0, debugger_count: 0, debugger_tsc: 0, ui_max_tsc: 0, s_per_tsc: 0.0, end_tsc: 0, end_time: unsafe {mem::zeroed()}, other_tsc: 0, ui_build_max_tsc: 0, ui_render_max_tsc: 0, ui_fill_max_tsc: 0, ui_input_bytes: 0, ui_output_bytes: 0} } 176 | 177 | pub fn finish(&mut self, end_time: Instant, end_tsc: u64) { 178 | assert!(self.start_tsc != 0); 179 | // rdtsc may go backwards e.g. after hibernation. 180 | let end_tsc = end_tsc.max(self.start_tsc + 1); 181 | self.end_tsc = end_tsc; 182 | self.end_time = end_time; 183 | self.s_per_tsc = (end_time - self.start_time).as_secs_f64() / (end_tsc - self.start_tsc) as f64; 184 | } 185 | } 186 | 187 | // Global counters that are periodically added to ProfileBucket and zeroed. Because passing ProfileBucket to all syscall sites would be too inconvenient (specifically for MemReader, all other sites would be fine with explicit passing of ProfileBucket). 188 | pub static SYSCALL_COUNT: AtomicUsize = AtomicUsize::new(0); 189 | pub static SYSCALL_TSC: AtomicU64 = AtomicU64::new(0); 190 | 191 | pub struct Profiling { 192 | pub buckets: VecDeque, 193 | pub bucket: ProfileBucket, 194 | } 195 | impl Profiling { 196 | pub fn new() -> Self { Self {buckets: VecDeque::new(), bucket: ProfileBucket::new(Instant::now(), rdtsc())} } 197 | 198 | // (Mutates global variables.) 199 | pub fn advance_bucket(&mut self) { 200 | // May have torn read between the two atomics. This is fine, in part because we currently only do syscalls in main thread. 201 | self.bucket.syscall_count += SYSCALL_COUNT.swap(0, Ordering::Relaxed); 202 | self.bucket.syscall_tsc += SYSCALL_TSC.swap(0, Ordering::Relaxed); 203 | let time = Instant::now(); 204 | let tsc = rdtsc(); 205 | self.bucket.finish(time, tsc); 206 | let new_bucket = ProfileBucket::new(time, tsc); 207 | self.buckets.push_back(mem::replace(&mut self.bucket, new_bucket)); 208 | 209 | while self.buckets.len() > 1000 { 210 | self.buckets.pop_front(); 211 | } 212 | } 213 | } 214 | 215 | #[macro_export] 216 | macro_rules! profile_syscall { 217 | ($($code:tt)*) => {{ 218 | let timer = TscScope::new(); 219 | let r = {$($code)*}; 220 | SYSCALL_TSC.fetch_add(timer.finish(), std::sync::atomic::Ordering::Relaxed); 221 | SYSCALL_COUNT.fetch_add(1, std::sync::atomic::Ordering::Relaxed); 222 | r 223 | }}; 224 | } 225 | -------------------------------------------------------------------------------- /src/pool.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | 3 | #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] 4 | pub struct Id { 5 | pub slot: usize, 6 | pub seqno: usize, 7 | } 8 | 9 | struct Slot { 10 | seqno: usize, 11 | val: Option, 12 | } 13 | 14 | pub struct Pool { 15 | slots: Vec>, 16 | vacant: Vec, 17 | next_seqno: usize, 18 | } 19 | 20 | impl Default for Id { 21 | fn default() -> Id { 22 | Id {slot: usize::MAX, seqno: usize::MAX} 23 | } 24 | } 25 | 26 | impl Pool { 27 | pub fn new() -> Self { 28 | Self {slots: Vec::new(), vacant: Vec::new(), next_seqno: 1} 29 | } 30 | 31 | pub fn add(&mut self, x: T) -> (Id, &mut T) { 32 | let seqno = self.next_seqno; 33 | self.next_seqno += 1; 34 | if let Some(slot) = self.vacant.pop() { 35 | let s = &mut self.slots[slot]; 36 | s.seqno = seqno; 37 | s.val = Some(x); 38 | (Id {slot, seqno}, s.val.as_mut().unwrap()) 39 | } else { 40 | self.slots.push(Slot {seqno, val: Some(x)}); 41 | (Id {slot: self.slots.len() - 1, seqno}, self.slots.last_mut().unwrap().val.as_mut().unwrap()) 42 | } 43 | } 44 | 45 | pub fn remove(&mut self, id: Id) -> Option { 46 | let s = match self.slots.get_mut(id.slot) { 47 | None => return None, 48 | Some(s) => s }; 49 | if s.seqno != id.seqno { 50 | return None; 51 | } 52 | let v = s.val.take(); 53 | if v.is_some() { 54 | self.vacant.push(id.slot); 55 | } 56 | v 57 | } 58 | 59 | pub fn clear(&mut self) { 60 | for slot in 0..self.slots.len() { 61 | if self.slots[slot].val.take().is_some() { 62 | self.vacant.push(slot); 63 | } 64 | } 65 | } 66 | 67 | pub fn try_get(&self, id: Id) -> Option<&T> { 68 | let s = match self.slots.get(id.slot) { 69 | None => return None, 70 | Some(s) => s }; 71 | if s.seqno != id.seqno { 72 | return None; 73 | } 74 | s.val.as_ref() 75 | } 76 | 77 | pub fn try_get_mut(&mut self, id: Id) -> Option<&mut T> { 78 | let s = match self.slots.get_mut(id.slot) { 79 | None => return None, 80 | Some(s) => s }; 81 | if s.seqno != id.seqno { 82 | return None; 83 | } 84 | s.val.as_mut() 85 | } 86 | 87 | pub fn get(&self, id: Id) -> &T { self.try_get(id).unwrap() } 88 | pub fn get_mut(&mut self, id: Id) -> &mut T { self.try_get_mut(id).unwrap() } 89 | 90 | pub fn iter(&self) -> PoolIter { 91 | PoolIter {pool: self, slot: 0} 92 | } 93 | 94 | pub fn iter_mut(&mut self) -> PoolIterMut { 95 | PoolIterMut {iter: self.slots.iter_mut(), idx: 0, slot: 0} 96 | } 97 | } 98 | 99 | pub struct PoolIter<'a, T> { 100 | pool: &'a Pool, 101 | slot: usize, 102 | } 103 | 104 | impl<'a, T> Iterator for PoolIter<'a, T> { 105 | type Item = (Id, &'a T); 106 | 107 | fn next(&mut self) -> Option { 108 | let mut slot = self.slot; 109 | while slot < self.pool.slots.len() && self.pool.slots[slot].val.is_none() { 110 | slot += 1; 111 | } 112 | self.slot = slot + 1; 113 | match self.pool.slots.get(slot) { 114 | None => None, 115 | Some(s) => Some((Id {slot, seqno: s.seqno}, s.val.as_ref().unwrap())) 116 | } 117 | } 118 | } 119 | 120 | pub struct PoolIterMut<'a, T> where T: 'a { 121 | iter: std::slice::IterMut<'a, Slot>, 122 | idx: usize, 123 | slot: usize, 124 | } 125 | 126 | impl<'a, T: 'a> Iterator for PoolIterMut<'a, T> { 127 | type Item = (Id, &'a mut T); 128 | 129 | fn next(&mut self) -> Option { 130 | loop { 131 | let idx = self.idx; 132 | self.idx += 1; 133 | match self.iter.next() { 134 | None => return None, 135 | Some(slot) => match &mut slot.val { 136 | None => (), 137 | Some(val) => return Some((Id {slot: idx, seqno: slot.seqno}, val)), 138 | } 139 | } 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/range_index.rs: -------------------------------------------------------------------------------- 1 | use crate::{util::*}; 2 | use std::ops::Range; 3 | 4 | pub struct RangeIndexEntry { 5 | pub start: usize, 6 | pub end: usize, 7 | // Max value of `end` among this entry and all entries before it. 8 | pub max_end: usize, 9 | pub value: T, 10 | } 11 | 12 | pub struct RangeIndex { 13 | // Sorted by (start, value). 14 | pub ranges: Vec>, 15 | } 16 | 17 | impl RangeIndex { 18 | pub fn new(mut ranges: Vec>, name_for_logging: &str) -> Self { 19 | ranges.shrink_to_fit(); 20 | // Sort by value when start is equal. This makes inlined function work correctly automatically: the innermost function will be returned first. 21 | ranges.sort_unstable_by_key(|r| (r.start, r.value.clone())); 22 | let mut max_end = 0usize; 23 | let mut max_idx = 0usize; 24 | for i in 0..ranges.len() { 25 | if i - max_idx > 100000 { 26 | // We don't expect any address range (variable, function, or inlined function) to overlap a lot of other ranges. 27 | // If it does, this data structure gets very inefficient. If this turns out to happen in practice, we should switch 28 | // to a different data structure (and maybe we should switch anyway, this doesn't seem super efficient or compact even in good conditions). 29 | // This sanity check limits how slow we can get, but the index lookups will be missing entries (not just the bad entry 30 | // that we're skipping, some other entries too because we're reverting max_end to current range end rather than the second-max). 31 | eprintln!("warning: range {}-{} covers over {} other ranges (in {}). This is unexpectedly many, please investigate.", ranges[max_idx].start, ranges[max_idx].end, i - max_idx - 1, name_for_logging); 32 | max_end = ranges[i].end; 33 | max_idx = i; 34 | } else if ranges[i].end >= max_end { 35 | max_end = ranges[i].end; 36 | max_idx = i; 37 | } 38 | ranges[i].max_end = max_end; 39 | } 40 | 41 | Self {ranges: ranges} 42 | } 43 | 44 | // Index of the first entry with start > addr (or len() if there are none). 45 | // To get all entries containing addr, walk backwards: 46 | // while idx > 0 && ranges[idx-1].max_end > addr { 47 | // idx -= 1; 48 | // if ranges[idx].end > addr { 49 | // // ranges[idx] contains addr 50 | // } 51 | // } 52 | // Maybe we should make an iterator for this. 53 | pub fn upper_bound(&self, addr: usize) -> usize { 54 | self.ranges.partition_point(|r| r.start <= addr) 55 | } 56 | 57 | pub fn find(&self, addr: usize) -> Option<&RangeIndexEntry> { 58 | let mut idx = self.upper_bound(addr); 59 | while idx > 0 && self.ranges[idx-1].max_end > addr { 60 | idx -= 1; 61 | if self.ranges[idx].end > addr { 62 | return Some(&self.ranges[idx]); 63 | } 64 | } 65 | return None; 66 | } 67 | 68 | pub fn find_ranges(&self, sorted_addr_ranges: &Vec>) -> Vec<&RangeIndexEntry> { 69 | assert!(sorted_addr_ranges.windows(2).all(|a| a[0].end < a[1].start)); 70 | let mut res: Vec<&RangeIndexEntry> = Vec::new(); 71 | let mut idx = 0usize; 72 | let mut steps = 0usize; 73 | for range in sorted_addr_ranges { 74 | idx.set_max(self.ranges.partition_point(|r| r.max_end <= range.start)); 75 | while idx < self.ranges.len() && self.ranges[idx].start < range.end { 76 | steps += 1; 77 | if steps > 100000 { 78 | eprintln!("warning: lookup ranges overlap over {} index ranges", steps-1); 79 | return res; 80 | } 81 | 82 | if self.ranges[idx].end > range.start { 83 | res.push(&self.ranges[idx]); 84 | } 85 | idx += 1; 86 | } 87 | } 88 | res 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /testprogs/absl/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | project(i_dont_want_to_use_cmake) 3 | 4 | set(CMAKE_CXX_STANDARD 17) 5 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 6 | 7 | set(CMAKE_BUILD_TYPE Debug) 8 | set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -g -Og") 9 | 10 | add_subdirectory(lib) 11 | 12 | add_executable(containers containers.cpp) 13 | 14 | target_link_libraries(containers 15 | absl::flat_hash_map 16 | absl::flat_hash_set 17 | absl::node_hash_map 18 | absl::node_hash_set 19 | absl::btree 20 | absl::inlined_vector 21 | absl::fixed_array 22 | ) 23 | -------------------------------------------------------------------------------- /testprogs/absl/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | BASEDIR=$(dirname "$0") 5 | 6 | mkdir -p "$BASEDIR/lib" 7 | if [ -z "$(ls -A "$BASEDIR/lib")" ] 8 | then 9 | git clone git@github.com:abseil/abseil-cpp.git "$BASEDIR/lib" 10 | fi 11 | mkdir -p "$BASEDIR/build" 12 | ( 13 | cd "$BASEDIR/build" 14 | cmake .. 15 | make -j8 containers 16 | ) 17 | -------------------------------------------------------------------------------- /testprogs/absl/containers.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "absl/container/flat_hash_map.h" 6 | #include "absl/container/flat_hash_set.h" 7 | #include "absl/container/node_hash_map.h" 8 | #include "absl/container/node_hash_set.h" 9 | #include "absl/container/btree_map.h" 10 | #include "absl/container/btree_set.h" 11 | #include "absl/container/inlined_vector.h" 12 | #include "absl/container/fixed_array.h" 13 | 14 | std::string random_string(std::mt19937& gen, int len) { 15 | static const char alphanum[] = 16 | "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; 17 | std::uniform_int_distribution<> dis(0, sizeof(alphanum) - 2); 18 | std::string str(len, ' '); 19 | for (int i = 0; i < len; ++i) { 20 | str[i] = alphanum[dis(gen)]; 21 | } 22 | return str; 23 | } 24 | 25 | int main() { 26 | std::mt19937 gen(42); 27 | std::uniform_int_distribution<> dis(1, 1000); 28 | 29 | absl::flat_hash_map fhm; 30 | for (int i = 0; i < 200; ++i) { 31 | fhm[i] = random_string(gen, 10); 32 | } 33 | 34 | absl::flat_hash_set fhs; 35 | for (int i = 0; i < 200; ++i) { 36 | fhs.insert(dis(gen)); 37 | } 38 | 39 | absl::node_hash_map nhm; 40 | for (int i = 0; i < 200; ++i) { 41 | nhm[i] = random_string(gen, 10); 42 | } 43 | 44 | absl::node_hash_set nhs; 45 | for (int i = 0; i < 200; ++i) { 46 | nhs.insert(dis(gen)); 47 | } 48 | 49 | absl::btree_map btm; 50 | for (int i = 0; i < 200; ++i) { 51 | btm[i] = random_string(gen, 10); 52 | } 53 | 54 | absl::btree_set bts; 55 | for (int i = 0; i < 200; ++i) { 56 | bts.insert(dis(gen)); 57 | } 58 | 59 | absl::InlinedVector small_iv; 60 | for (int i = 0; i < 5; ++i) { 61 | small_iv.push_back(dis(gen)); 62 | } 63 | 64 | absl::InlinedVector large_iv; 65 | for (int i = 0; i < 20; ++i) { 66 | large_iv.push_back(dis(gen)); 67 | } 68 | 69 | absl::FixedArray fa(50); 70 | for (int i = 0; i < 50; ++i) { 71 | fa[i] = dis(gen); 72 | } 73 | 74 | volatile int dummy = fhm.size() + fhs.size() + nhm.size() + nhs.size() + 75 | btm.size() + bts.size() + small_iv.size() + large_iv.size() + 76 | fa.size(); 77 | (void)dummy; 78 | 79 | return 0; 80 | } 81 | -------------------------------------------------------------------------------- /testprogs/arrays.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | using namespace std; 5 | 6 | int main() { 7 | volatile int a[5] = {1, 2, 3, 4, 5}; 8 | volatile array b = {"a", "b", "c", "d"}; 9 | volatile long c[3][2] = {{1, 2}, {3, 4}, {5, 6}}; 10 | volatile int len = 10; 11 | volatile long d[len]; 12 | d[0] = 13; 13 | d[5] = 7; 14 | volatile int e[4][3][2] = {{{1, 2}, {3, 4}, {5, 6}}, {{7, 8}, {9, 10}, {11, 12}}, {{13, 14}, {15, 16}, {17, 18}}, {{19, 20}, {21, 22}, {23, 24}}}; 15 | cout<<"hi"< 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | int main() { 33 | // Comments say whether pretty-printers work for [libstdc++, libc++]. 34 | 35 | // Slice-like. 36 | std::vector vec = {1, 2, 3}; // [yes, yes] 37 | std::string_view strview = "hello"; // [yes, yes] 38 | std::valarray va = {1, 2, 3}; // [yes, yes] 39 | std::span sp(vec); // [yes, yes] 40 | 41 | // Smart pointers. 42 | std::shared_ptr sptr = std::make_shared(42); // [yes, yes] 43 | std::weak_ptr wptr = sptr; // [yes, yes] 44 | 45 | // String. 46 | std::string short_str = "hello"; // [yes, yes] 47 | std::string long_str(100, '.'); 48 | 49 | // Wide strings. 50 | std::wstring wstr = L"hello"; // [good enough, good enough] 51 | std::u16string u16str = u"hello"; // [good enough, good enough] 52 | std::u32string u32str = U"hello"; // [good enough, good enough] 53 | 54 | auto pv = std::make_shared>(std::vector({10, 20, 30})); 55 | 56 | // Deque. 57 | std::deque deq = {1, 2, 3}; // [yes, yes] 58 | std::stack stk; // [yes, yes] 59 | stk.push(1); 60 | std::queue que; // [yes, yes] 61 | que.push(1); 62 | for (int i = 0; i < 100; ++i) { 63 | deq.push_back(i * 10); 64 | stk.push(i * 100); 65 | que.push(i * 1000); 66 | } 67 | for (int i = 0; i < 50; ++i) { 68 | deq.pop_front(); 69 | stk.pop(); 70 | que.pop(); 71 | } 72 | 73 | // Linked lists. 74 | std::list lst = {1, 2, 3}; // [yes, yes] 75 | std::forward_list flst = {1, 2, 3}; // [yes, yes] 76 | 77 | // Trees. 78 | std::set set = {1, 2, 3}; // [yes, yes] 79 | std::multiset mset = {1, 1, 2, 3}; // [yes, yes] 80 | std::map map = {{1, "one"}, {2, "two"}}; // [yes, yes] 81 | std::multimap mmap = {{1, "one"}, {1, "uno"}, {2, "two"}}; // [yes, yes] 82 | 83 | // Hash tables. 84 | std::unordered_set uset = {1, 2, 3}; // [yes, yes] 85 | std::unordered_multiset umset = {1, 1, 2, 3}; // [yes, yes] 86 | std::unordered_map umap = {{1, "one"}, {2, "two"}}; // [yes, yes] 87 | std::unordered_multimap ummap = {{1, "one"}, {1, "uno"}, {2, "two"}}; // [yes, yes] 88 | 89 | // Discriminated unions. 90 | std::optional opt = 42; // [no, no] 91 | std::variant var = 3.14; // [no, no] 92 | 93 | // Complicated and not worth it. 94 | std::any any_val = 42; // [no, no] 95 | std::function func = [](int x) { return x * 2; }; // [no, no] 96 | std::future fut = std::async(std::launch::deferred, []() { return 42; }); // [no, no] 97 | std::promise prom; // [no, no] 98 | std::exception_ptr eptr = std::make_exception_ptr(std::runtime_error("test")); // [no, no] 99 | std::error_code ec = std::make_error_code(std::errc::invalid_argument); // [no, no] 100 | 101 | // Things that are ok without designated pretty printers. 102 | std::array arr = {1, 2, 3}; 103 | int raw_array[5] = {1, 2, 3, 4, 5}; 104 | std::priority_queue pque; // not sorted, but meh 105 | pque.push(1); 106 | std::unique_ptr uptr = std::make_unique(42); 107 | std::pair pair = {1, 2.0}; 108 | std::tuple tuple = {1, 2.0, 'a'}; 109 | auto now = std::chrono::system_clock::now(); 110 | auto duration = std::chrono::hours(1); 111 | std::thread t([](){}); 112 | std::mutex mtx; 113 | std::condition_variable cv; 114 | std::bitset<8> bits(42); 115 | std::complex c(1.0, 2.0); 116 | auto range = std::views::iota(1, 10); 117 | 118 | // Prevent optimizations 119 | volatile int dummy = vec[0] + arr[0] + arr[2] + *uptr + *sptr + short_str[0] + long_str[0] + pair.first + std::get<0>(tuple) + 120 | *opt + (int)std::get(var) + std::any_cast(any_val) + func(1) + 121 | bits.count() + c.real() + va[0] + sp[0] + *range.begin() + strview[0] + 122 | wstr[0] + u16str[0] + u32str[0] + (*pv)[0]; 123 | (void)dummy; 124 | 125 | t.join(); 126 | 127 | 128 | // Make everything big. 129 | std::vector>> mat; 130 | for (int i = 0; i < 1000000; ++i) { 131 | vec.push_back(i * 10); 132 | long_str.push_back((char)('a'+__builtin_popcount(i))); 133 | deq.push_back(i * 11); 134 | stk.push(i * 12); 135 | lst.push_back(i * 13); 136 | flst.push_front(i * 14); 137 | set.insert(i * 15); 138 | mset.insert(i / 10); 139 | map.emplace(i * 16, std::to_string(i * 160)); 140 | mmap.emplace(i / 5, std::to_string(i / 5 * 10)); 141 | uset.insert(i * 17); 142 | umset.insert(i / 6); 143 | umap.emplace(i * 18, std::to_string(i * 180)); 144 | ummap.emplace(i / 4, std::to_string(i / 4 * 10)); 145 | 146 | int row = i / 1000; 147 | mat.resize(row + 1); 148 | mat[row].emplace_back((double)i, (double)(i/10)); 149 | } 150 | auto big_array = std::make_unique>(); 151 | 152 | volatile int dummy2 = vec[0] + arr[0] + *uptr + *sptr + short_str[0] + long_str[0] + pair.first + std::get<0>(tuple) + 153 | *opt + (int)std::get(var) + std::any_cast(any_val) + func(1) + 154 | bits.count() + c.real() + va[0] + sp[0] + *range.begin() + strview[0] + 155 | wstr[0] + u16str[0] + u32str[0] + (*pv)[0] + mat.size() + mat[10].size(); 156 | (void)dummy2; 157 | 158 | return 0; 159 | } 160 | -------------------------------------------------------------------------------- /testprogs/containers_rs.rs: -------------------------------------------------------------------------------- 1 | use std::collections::*; 2 | use std::sync::{Arc, Mutex, RwLock, Weak}; 3 | use std::cell::{Cell, RefCell}; 4 | use std::rc::Rc; 5 | use std::time::{Duration, Instant, SystemTime}; 6 | use std::ffi::{CString, OsString}; 7 | use std::path::PathBuf; 8 | use std::borrow::Cow; 9 | use std::pin::Pin; 10 | use std::marker::PhantomData; 11 | use std::num::NonZeroU8; 12 | use std::sync::atomic::{AtomicBool, AtomicI32, AtomicU64}; 13 | use std::hint::black_box; 14 | 15 | fn main() { 16 | // Comments say whether it's implemented. 17 | 18 | // Slice-like. 19 | let slice: &[i32] = &[1, 2, 3]; // yes 20 | let mut_slice: &mut [i32] = &mut [1, 2, 3]; // yes 21 | let v: Vec = vec![1, 2, 3]; // yes 22 | let mut empty_v: Vec = Vec::new(); 23 | let bh: BinaryHeap = BinaryHeap::from(vec![1, 2, 3]); // good enough 24 | 25 | let vd: VecDeque = VecDeque::from(vec![1, 2, 3]); // yes 26 | 27 | let string = String::from("hello"); // yes 28 | let str_slice: &str = "world"; // yes 29 | let cstring = CString::new("hello").unwrap(); // yes 30 | let osstring = OsString::from("hello"); // yes 31 | let pathbuf = PathBuf::from("/path/to/file"); // yes 32 | 33 | let ll: LinkedList = LinkedList::from_iter(vec![1, 2, 3]); // no 34 | 35 | let bm: BTreeMap = BTreeMap::from([(1, "one"), (2, "two")]); // no 36 | let bs: BTreeSet = BTreeSet::from_iter(vec![1, 2, 3]); // no 37 | 38 | let mut hm: HashMap = HashMap::new(); // yes 39 | for i in 1..200 { 40 | let x = i*1310%10000; 41 | hm.insert(x, format!("v{}", x)); 42 | } 43 | let hs: HashSet = HashSet::from_iter(vec![1, 2, 3]); // yes 44 | 45 | let rc: Rc = Rc::new(42); // no 46 | let arc: Arc = Arc::new(42); // no 47 | let weak: Weak = Arc::downgrade(&arc); // no 48 | 49 | // Complicated and not worth it. 50 | let fn_ptr: fn(i32) -> i32 = |x| x + 1; 51 | let closure = |x: i32| x + 1; 52 | 53 | // No designated pretty-printers needed below. 54 | 55 | let b: Box = Box::new(42); 56 | let raw_ptr: *const i32 = &42; 57 | let raw_mut_ptr: *mut i32 = &mut 42; 58 | let cell: Cell = Cell::new(42); 59 | let atomic_bool = AtomicBool::new(true); 60 | let atomic_i32 = AtomicI32::new(42); 61 | let atomic_u64 = AtomicU64::new(42); 62 | let pin = Pin::new(&42); 63 | let phantom: PhantomData = PhantomData; 64 | let non_zero = NonZeroU8::new(42).unwrap(); 65 | 66 | let refcell: RefCell = RefCell::new(42); 67 | let mutex: Mutex = Mutex::new(42); 68 | let rwlock: RwLock = RwLock::new(42); 69 | 70 | let some: Option = Some(42); 71 | let none: Option = None; 72 | let ok: Result = Ok(42); 73 | let err: Result = Err("error"); 74 | 75 | // Iterators 76 | black_box(&v); 77 | let iter = v.iter(); 78 | let _ = black_box(iter); 79 | let into_iter = v.into_iter(); 80 | black_box(&empty_v); 81 | let iter_mut = empty_v.iter_mut(); 82 | 83 | let range = 0..10; 84 | let range_inclusive = 0..=10; 85 | let range_to = ..10; 86 | let range_from = 10..; 87 | 88 | // Moo 89 | let cow1: Cow = Cow::Borrowed("hello"); 90 | let cow2: Cow = Cow::Owned("world".to_string()); 91 | 92 | let duration = Duration::from_secs(42); 93 | let instant = Instant::now(); 94 | let system_time = SystemTime::now(); 95 | 96 | black_box(vd); 97 | black_box(ll); 98 | black_box(hm); 99 | black_box(bm); 100 | black_box(hs); 101 | black_box(bs); 102 | black_box(bh); 103 | black_box(b); 104 | black_box(rc); 105 | black_box(arc); 106 | black_box(weak); 107 | black_box(cell); 108 | black_box(refcell); 109 | black_box(mutex); 110 | black_box(rwlock); 111 | black_box(atomic_bool); 112 | black_box(atomic_i32); 113 | black_box(atomic_u64); 114 | black_box(some); 115 | black_box(none); 116 | let _ = black_box(ok); 117 | let _ = black_box(err); 118 | black_box(string); 119 | black_box(str_slice); 120 | black_box(cstring); 121 | black_box(osstring); 122 | black_box(pathbuf); 123 | let _ = black_box(into_iter); 124 | let _ = black_box(iter_mut); 125 | black_box(range); 126 | black_box(range_inclusive); 127 | black_box(range_to); 128 | black_box(range_from); 129 | black_box(slice); 130 | black_box(mut_slice); 131 | black_box(cow1); 132 | black_box(cow2); 133 | black_box(pin); 134 | black_box(phantom); 135 | black_box(non_zero); 136 | black_box(duration); 137 | black_box(instant); 138 | black_box(system_time); 139 | black_box(fn_ptr); 140 | black_box(&closure); 141 | black_box(raw_ptr); 142 | black_box(raw_mut_ptr); 143 | } 144 | -------------------------------------------------------------------------------- /testprogs/containers_zig.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | const Shape = union(enum) { 4 | circle: f32, 5 | rectangle: struct { 6 | width: f32, 7 | height: f32, 8 | }, 9 | hyperrectangle: std.ArrayList(f32), 10 | }; 11 | 12 | fn shapeArea(shape: Shape) f32 { 13 | return switch (shape) { 14 | .circle => |radius| std.math.pi * radius * radius, 15 | .rectangle => |rect| rect.width * rect.height, 16 | .hyperrectangle => |sides| { 17 | var p: f32 = 1.0; 18 | for (sides.items) |s| { 19 | p *= s; 20 | } 21 | return p; 22 | } 23 | }; 24 | } 25 | 26 | fn optionalShapeArea(maybeShape: ?Shape) f32 { 27 | if (maybeShape) |shape| { 28 | return shapeArea(shape); 29 | } else { 30 | return 0.0; 31 | } 32 | } 33 | 34 | fn lessThan(context: void, a: u32, b: u32) std.math.Order { 35 | _ = context; 36 | return std.math.order(a, b); 37 | } 38 | 39 | pub fn main() !void { 40 | var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); 41 | defer arena.deinit(); 42 | const allocator = arena.allocator(); 43 | 44 | var plain_array: [5]f32 = [_]f32{ 1.1, 2.5, 3.14, 4.2, 5.0 }; 45 | const plain_slice: []f32 = &plain_array; 46 | 47 | var list = std.ArrayList(f32).init(allocator); 48 | try list.appendSlice(plain_slice); 49 | 50 | // Optional and discriminated union. 51 | var shapes: [4]?Shape = undefined; 52 | shapes[0] = Shape{ .circle = 5.0 }; 53 | shapes[1] = Shape{ .rectangle = .{ .width = 4.0, .height = 6.0 } }; 54 | shapes[2] = Shape{ .hyperrectangle = list }; 55 | shapes[3] = null; 56 | 57 | var total_area: f32 = 0.0; 58 | for (shapes) |maybeShape| { 59 | total_area += optionalShapeArea(maybeShape); 60 | } 61 | 62 | // Some containers. 63 | var map = std.AutoHashMap(u64, u32).init(allocator); 64 | var string_map = std.StringHashMap(f64).init(allocator); 65 | var bounded_array = std.BoundedArray(i16, 210).init(0) catch unreachable; 66 | var array_map = std.ArrayHashMap(u64, bool, std.array_hash_map.AutoContext(u64), false).init(allocator); 67 | 68 | const Person = struct { 69 | name: []const u8, 70 | age: u8, 71 | }; 72 | var people = std.MultiArrayList(Person){}; 73 | 74 | const PriorityQueue = std.PriorityQueue(u32, void, lessThan); 75 | var queue = PriorityQueue.init(allocator, {}); 76 | 77 | var sll = std.SinglyLinkedList(u64){}; 78 | var buf_set = std.BufSet.init(allocator); 79 | var bit_set = std.bit_set.IntegerBitSet(512).initEmpty(); 80 | var seg_list = std.SegmentedList(usize, 16){}; 81 | 82 | for (0..200) |i| { 83 | try map.put(@intCast(i*10), @intCast(i*100)); 84 | 85 | const str_key = try std.fmt.allocPrint(allocator, "val{d}", .{i}); 86 | try string_map.put(str_key, @as(f64, @floatFromInt(i)) / 10.0); 87 | 88 | if (i < bounded_array.capacity()) { 89 | try bounded_array.append(@as(i16, @intCast(i)) - 100); 90 | } 91 | 92 | try array_map.put(@intCast(i * 10000), i % 2 == 0); 93 | 94 | const name = try std.fmt.allocPrint(allocator, "Person{d}", .{i}); 95 | try people.append(allocator, .{ .name = name, .age = @intCast(i % 100) }); 96 | 97 | try queue.add(@intCast(i * 5)); 98 | 99 | const node = try allocator.create(@TypeOf(sll).Node); 100 | node.* = .{ .data = i * 1000, .next = sll.first }; 101 | sll.first = node; 102 | 103 | const set_item = try std.fmt.allocPrint(allocator, "item{d}", .{i}); 104 | try buf_set.insert(set_item); 105 | 106 | if (i < bit_set.capacity() and i%2 == 0) { 107 | bit_set.set(i); 108 | } 109 | 110 | try seg_list.append(allocator, i * 10); 111 | } 112 | 113 | std.mem.doNotOptimizeAway( 114 | @as(u32, @intFromFloat(plain_array[0])) + 115 | @as(u32, @intFromFloat(plain_slice[0])) + 116 | map.get(40).? + 117 | @as(u32, @intFromFloat(string_map.get("val1").?)) + 118 | @as(u32, @as(u16, @bitCast(bounded_array.buffer[0]))) + 119 | @as(u32, @intFromBool(array_map.get(20000).?)) + 120 | @as(u32, people.items(.age)[0]) + 121 | queue.items[0] + 122 | @as(u32, @truncate(sll.first.?.data)) + 123 | @as(u32, @intFromBool(buf_set.contains("item1"))) + 124 | @as(u32, @intFromBool(bit_set.isSet(1))) + 125 | @as(u32, @intCast(seg_list.at(0).*)) + 126 | @as(u32, @intFromFloat(total_area)) 127 | ); 128 | 129 | @breakpoint(); 130 | } 131 | -------------------------------------------------------------------------------- /testprogs/exception.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | void __attribute__((noinline)) f(int x) { 5 | throw std::string("hi"); 6 | } 7 | 8 | void __attribute__((noinline)) g(int a, int b) { 9 | f(a+b); 10 | } 11 | 12 | int main() { 13 | try { 14 | g(4, 5); 15 | } catch (std::string & e) { 16 | std::cout << e << std::endl; 17 | } 18 | 19 | g(2, 3); 20 | } 21 | -------------------------------------------------------------------------------- /testprogs/global_variables.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int __attribute__((noinline)) f(int x) { 5 | int v = 1; 6 | const int C = 10; 7 | constexpr int CE = 20; 8 | static int S = 30; 9 | static const int SC = 40; 10 | static constexpr int SCE = 50; 11 | return x + v + C + CE + S + SC + SCE; 12 | } 13 | 14 | inline int g(int x) { 15 | int v = 1; 16 | const int C = 10; 17 | constexpr int CE = 20; 18 | static int S = 30; 19 | static const int SC = 40; 20 | static constexpr int SCE = 50; 21 | return x + v + C + CE + S + SC + SCE; 22 | } 23 | 24 | int G = 99; 25 | const int GC = 100; 26 | constexpr int GCE = 200; 27 | static int GS = 300; 28 | static const int GSC = 400; 29 | static constexpr int GSCE = 500; 30 | 31 | struct str { 32 | static int NS; 33 | static const int NSC = 4000; 34 | static constexpr int NSCE = 5000; 35 | }; 36 | int str::NS = 3000; 37 | 38 | constexpr std::string_view GSV = "meow"; 39 | 40 | template 41 | struct T { 42 | int f = X; 43 | static constexpr int XX = X; 44 | }; 45 | 46 | int main() { 47 | volatile T<1337> t; 48 | printf("%d", f(42) + g(42) + t.f + T<>::XX + G + GC + GCE + GS + GSC + GSCE); 49 | } 50 | -------------------------------------------------------------------------------- /testprogs/its_a_trap.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main() { 4 | printf("one\n"); 5 | __builtin_debugtrap(); 6 | printf("two\n"); 7 | __asm__ volatile("int3"); 8 | printf("three\n"); 9 | __builtin_trap(); 10 | } 11 | -------------------------------------------------------------------------------- /testprogs/many_threads.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | void do_something() { 6 | volatile long i = 0; 7 | while (i < 100000) { 8 | i = i + 1; 9 | if (i % 5 == 0) 10 | std::this_thread::sleep_for(std::chrono::milliseconds(100)); 11 | } 12 | } 13 | 14 | int main() { 15 | std::vector t; 16 | for (int i = 0; i < 10000; ++i) 17 | t.emplace_back([] { do_something(); }); 18 | for (auto & x : t) 19 | x.join(); 20 | } 21 | -------------------------------------------------------------------------------- /testprogs/panic.rs: -------------------------------------------------------------------------------- 1 | #[inline(never)] 2 | fn f() { 3 | panic!("aaaaaa"); 4 | } 5 | 6 | #[inline(never)] 7 | fn g() { 8 | f(); 9 | } 10 | 11 | fn main() { 12 | if std::panic::catch_unwind(|| g()).is_err() { 13 | println!("panicked"); 14 | } 15 | g(); 16 | } 17 | -------------------------------------------------------------------------------- /testprogs/self_reference.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | using namespace std; 3 | 4 | struct A { 5 | A volatile & a; 6 | volatile int x = 42; 7 | 8 | A() : a(*this) {} 9 | }; 10 | 11 | int main() { 12 | A a; 13 | cout< 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | void signalHandler(int) { 9 | volatile int i = 42; 10 | i = i + 27; 11 | } 12 | 13 | void* noOpLoop(void* arg) { 14 | volatile int i = 0; 15 | while (1) { 16 | i = i + 1; 17 | } 18 | return NULL; 19 | } 20 | 21 | void* signalSender(void* arg) { 22 | pthread_t target_thread = *((pthread_t*)arg); 23 | while (1) { 24 | pthread_kill(target_thread, SIGUSR1); 25 | usleep(10000); 26 | } 27 | return NULL; 28 | } 29 | 30 | int main() { 31 | if (signal(SIGUSR1, signalHandler) == SIG_ERR) { 32 | printf("error setting signal handler.\n"); 33 | return 1; 34 | } 35 | 36 | pthread_t thread1, thread2; 37 | 38 | if (pthread_create(&thread1, NULL, noOpLoop, NULL) != 0) { 39 | printf("error creating thread.\n"); 40 | return 1; 41 | } 42 | 43 | if (pthread_create(&thread2, NULL, signalSender, &thread1) != 0) { 44 | printf("error creating thread.\n"); 45 | return 1; 46 | } 47 | 48 | pthread_join(thread1, NULL); 49 | pthread_join(thread2, NULL); 50 | 51 | printf("???\n"); 52 | 53 | return 0; 54 | } 55 | -------------------------------------------------------------------------------- /testprogs/simple_async_rs.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | future::Future, 3 | pin::Pin, 4 | task::{Context, Poll, Waker, RawWaker, RawWakerVTable}, 5 | }; 6 | 7 | struct ManualExecutor { 8 | future: Pin>>, 9 | } 10 | 11 | impl ManualExecutor { 12 | fn run(future: impl Future + 'static) -> Self { 13 | Self { 14 | future: Box::pin(future), 15 | } 16 | } 17 | 18 | fn step(&mut self) -> bool { 19 | let waker = unsafe { Waker::from_raw(dummy_raw_waker()) }; 20 | let mut cx = Context::from_waker(&waker); 21 | 22 | match self.future.as_mut().poll(&mut cx) { 23 | Poll::Ready(()) => false, 24 | Poll::Pending => true, 25 | } 26 | } 27 | } 28 | 29 | fn dummy_raw_waker() -> RawWaker { 30 | unsafe fn clone(_: *const ()) -> RawWaker { 31 | dummy_raw_waker() 32 | } 33 | unsafe fn wake(_: *const ()) {} 34 | unsafe fn wake_by_ref(_: *const ()) {} 35 | unsafe fn drop(_: *const ()) {} 36 | 37 | RawWaker::new(std::ptr::null(), &RawWakerVTable::new(clone, wake, wake_by_ref, drop)) 38 | } 39 | 40 | struct Suspend { 41 | polled: bool, 42 | } 43 | 44 | impl Future for Suspend { 45 | type Output = (); 46 | 47 | fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> { 48 | if self.polled { 49 | Poll::Ready(()) 50 | } else { 51 | self.polled = true; 52 | Poll::Pending 53 | } 54 | } 55 | } 56 | 57 | fn suspend() -> Suspend { 58 | Suspend {polled: false} 59 | } 60 | 61 | async fn an_async_function() { 62 | println!("1"); 63 | suspend().await; 64 | println!("3"); 65 | suspend().await; 66 | println!("5"); 67 | } 68 | 69 | fn main() { 70 | let mut exec = ManualExecutor::run(an_async_function()); 71 | println!("0"); 72 | assert!(exec.step()); 73 | println!("2"); 74 | assert!(exec.step()); 75 | println!("4"); 76 | assert!(!exec.step()); 77 | println!("6 - done"); 78 | } 79 | 80 | -------------------------------------------------------------------------------- /testprogs/simple_codegen.c: -------------------------------------------------------------------------------- 1 | // (written mostly by Claude) 2 | 3 | #define _DEFAULT_SOURCE 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | // Function pointer type for our dynamically created function 11 | typedef int (*MulFunc)(int); 12 | 13 | int main() { 14 | // Machine code for a function that multiplies its argument by 2 15 | // This is x64 assembly: mov eax, edi; add eax, eax; ret 16 | unsigned char code[] = {0x89, 0xf8, 0x01, 0xc0, 0xc3}; 17 | size_t code_size = sizeof(code); 18 | 19 | // Allocate executable memory using mmap 20 | void *mem = mmap(NULL, code_size, PROT_READ | PROT_WRITE | PROT_EXEC, 21 | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); 22 | 23 | if (mem == MAP_FAILED) { 24 | perror("mmap"); 25 | exit(1); 26 | } 27 | 28 | // Copy our machine code to the allocated memory 29 | memcpy(mem, code, code_size); 30 | 31 | // Cast the memory to our function pointer type 32 | MulFunc multiply = (MulFunc)mem; 33 | 34 | // Test our function 35 | int result = multiply(5); 36 | printf("5 * 2 = %d\n", result); 37 | 38 | // Clean up 39 | if (munmap(mem, code_size) == -1) { 40 | perror("munmap"); 41 | exit(1); 42 | } 43 | 44 | return 0; 45 | } 46 | -------------------------------------------------------------------------------- /testprogs/simple_coro.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | struct Coroutine { 7 | struct promise_type { 8 | std::suspend_always initial_suspend() { return {}; } 9 | std::suspend_always final_suspend() noexcept { return {}; } 10 | void return_void() {} 11 | void unhandled_exception() {} 12 | std::suspend_always yield_value(int) { return {}; } 13 | 14 | Coroutine get_return_object() { 15 | return Coroutine{std::coroutine_handle::from_promise(*this)}; 16 | } 17 | }; 18 | 19 | std::coroutine_handle handle; 20 | 21 | Coroutine(std::coroutine_handle h) : handle(h) {} 22 | Coroutine(Coroutine && c) : handle(std::exchange(c.handle, nullptr)) {} 23 | ~Coroutine() { if (handle) handle.destroy(); } 24 | }; 25 | 26 | Coroutine a_coroutine_function() { 27 | std::cout << "1" << std::endl; 28 | co_await std::suspend_always{}; 29 | std::cout << "3" << std::endl; 30 | co_await std::suspend_always{}; 31 | std::cout << "5" << std::endl; 32 | } 33 | 34 | struct ManualExecutor { 35 | Coroutine coroutine; 36 | 37 | ManualExecutor(Coroutine coro) : coroutine(std::move(coro)) {} 38 | 39 | bool step() { 40 | if (!coroutine.handle.done()) { 41 | coroutine.handle.resume(); 42 | return true; 43 | } 44 | return false; 45 | } 46 | }; 47 | 48 | int main() { 49 | { 50 | ManualExecutor exec(a_coroutine_function()); 51 | std::cout << "0" << std::endl; 52 | if (!exec.step()) std::abort(); 53 | std::cout << "2" << std::endl; 54 | if (!exec.step()) std::abort(); 55 | std::cout << "4" << std::endl; 56 | if (!exec.step()) std::abort(); 57 | std::cout << "6" << std::endl; 58 | if (exec.step()) std::abort(); 59 | std::cout << "7" << std::endl; 60 | } 61 | std::cout << "8 - done" << std::endl; 62 | } 63 | -------------------------------------------------------------------------------- /testprogs/simple_inline.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | inline void f(int n) { 4 | printf("%d", n*2); 5 | } 6 | 7 | inline void g(int n) { 8 | f(n*10); 9 | } 10 | 11 | void h(int n) { 12 | g(n+1); 13 | } 14 | 15 | int main() { 16 | g(1); 17 | } 18 | -------------------------------------------------------------------------------- /testprogs/simple_loop.cpp: -------------------------------------------------------------------------------- 1 | int main() { 2 | volatile long i = 0; 3 | while (true) { 4 | i = i + 1; 5 | i = i + 2; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /testprogs/simple_mutex.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | using namespace std; 5 | 6 | int main() { 7 | mutex m; 8 | 9 | auto f = [&] { 10 | for (int i = 0; i < 60; ++i) { 11 | { 12 | unique_lock lock(m); 13 | cout << i << endl; 14 | this_thread::sleep_for(chrono::seconds(1)); 15 | } 16 | this_thread::sleep_for(chrono::milliseconds(10)); 17 | } 18 | }; 19 | 20 | thread t1(f); 21 | thread t2(f); 22 | t1.join(); 23 | t2.join(); 24 | } 25 | -------------------------------------------------------------------------------- /testprogs/simple_recursion.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int __attribute__((noinline)) f(int n) { 4 | volatile int x = 0; 5 | x = x + 1; 6 | if (n > 0) 7 | x = x + f(n-1); 8 | x = x + 1; 9 | return x; 10 | } 11 | 12 | int main() { 13 | printf("%d", f(5)); 14 | } 15 | -------------------------------------------------------------------------------- /testprogs/simple_signal_handler.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | void signalHandler(int) { 5 | printf("received signal\n"); 6 | } 7 | 8 | int main() { 9 | if (signal(SIGUSR1, signalHandler) == SIG_ERR) { 10 | printf("error setting signal handler.\n"); 11 | return 1; 12 | } 13 | 14 | raise(SIGUSR1); 15 | 16 | printf("bye\n"); 17 | 18 | return 0; 19 | } 20 | -------------------------------------------------------------------------------- /testprogs/simple_simd.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | static void cpuid(int leaf, int subleaf, unsigned int *eax, unsigned int *ebx, unsigned int *ecx, unsigned int *edx) { 9 | __cpuid_count(leaf, subleaf, *eax, *ebx, *ecx, *edx); 10 | } 11 | 12 | static int has_avx512() { 13 | unsigned int eax, ebx, ecx, edx; 14 | cpuid(0, 0, &eax, &ebx, &ecx, &edx); 15 | if (eax < 7) return 0; 16 | cpuid(7, 0, &eax, &ebx, &ecx, &edx); 17 | return (ebx & (1 << 16)) != 0; 18 | } 19 | 20 | void fill_avx_registers() { 21 | uint32_t vals[16]; 22 | for (int i = 0; i < 16; i++) { 23 | vals[i] = (i + 1) * 0x01010101; 24 | } 25 | __asm__ volatile ( 26 | "vbroadcastss 0(%[vals]), %%ymm0\n\t" 27 | "vbroadcastss 4(%[vals]), %%ymm1\n\t" 28 | "vbroadcastss 8(%[vals]), %%ymm2\n\t" 29 | "vbroadcastss 12(%[vals]), %%ymm3\n\t" 30 | "vbroadcastss 16(%[vals]), %%ymm4\n\t" 31 | "vbroadcastss 20(%[vals]), %%ymm5\n\t" 32 | "vbroadcastss 24(%[vals]), %%ymm6\n\t" 33 | "vbroadcastss 28(%[vals]), %%ymm7\n\t" 34 | "vbroadcastss 32(%[vals]), %%ymm8\n\t" 35 | "vbroadcastss 36(%[vals]), %%ymm9\n\t" 36 | "vbroadcastss 40(%[vals]), %%ymm10\n\t" 37 | "vbroadcastss 44(%[vals]), %%ymm11\n\t" 38 | "vbroadcastss 48(%[vals]), %%ymm12\n\t" 39 | "vbroadcastss 52(%[vals]), %%ymm13\n\t" 40 | "vbroadcastss 56(%[vals]), %%ymm14\n\t" 41 | "vbroadcastss 60(%[vals]), %%ymm15\n\t" 42 | : 43 | : [vals] "r"(vals) 44 | : "memory" 45 | ); 46 | } 47 | 48 | __attribute__((target("avx512f"))) 49 | void fill_avx512_registers() { 50 | uint32_t vals[32]; 51 | uint8_t kmasks[8]; 52 | 53 | for (int i = 0; i < 32; i++) { 54 | vals[i] = (i + 1) * 0x01010101; 55 | } 56 | for (int i = 0; i < 8; i++) { 57 | kmasks[i] = (1U << i); 58 | } 59 | 60 | __asm__ volatile ( 61 | "vbroadcastss 0(%[vals]), %%zmm0\n\t" 62 | "vbroadcastss 4(%[vals]), %%zmm1\n\t" 63 | "vbroadcastss 8(%[vals]), %%zmm2\n\t" 64 | "vbroadcastss 12(%[vals]), %%zmm3\n\t" 65 | "vbroadcastss 16(%[vals]), %%zmm4\n\t" 66 | "vbroadcastss 20(%[vals]), %%zmm5\n\t" 67 | "vbroadcastss 24(%[vals]), %%zmm6\n\t" 68 | "vbroadcastss 28(%[vals]), %%zmm7\n\t" 69 | "vbroadcastss 32(%[vals]), %%zmm8\n\t" 70 | "vbroadcastss 36(%[vals]), %%zmm9\n\t" 71 | "vbroadcastss 40(%[vals]), %%zmm10\n\t" 72 | "vbroadcastss 44(%[vals]), %%zmm11\n\t" 73 | "vbroadcastss 48(%[vals]), %%zmm12\n\t" 74 | "vbroadcastss 52(%[vals]), %%zmm13\n\t" 75 | "vbroadcastss 56(%[vals]), %%zmm14\n\t" 76 | "vbroadcastss 60(%[vals]), %%zmm15\n\t" 77 | "vbroadcastss 64(%[vals]), %%zmm16\n\t" 78 | "vbroadcastss 68(%[vals]), %%zmm17\n\t" 79 | "vbroadcastss 72(%[vals]), %%zmm18\n\t" 80 | "vbroadcastss 76(%[vals]), %%zmm19\n\t" 81 | "vbroadcastss 80(%[vals]), %%zmm20\n\t" 82 | "vbroadcastss 84(%[vals]), %%zmm21\n\t" 83 | "vbroadcastss 88(%[vals]), %%zmm22\n\t" 84 | "vbroadcastss 92(%[vals]), %%zmm23\n\t" 85 | "vbroadcastss 96(%[vals]), %%zmm24\n\t" 86 | "vbroadcastss 100(%[vals]), %%zmm25\n\t" 87 | "vbroadcastss 104(%[vals]), %%zmm26\n\t" 88 | "vbroadcastss 108(%[vals]), %%zmm27\n\t" 89 | "vbroadcastss 112(%[vals]), %%zmm28\n\t" 90 | "vbroadcastss 116(%[vals]), %%zmm29\n\t" 91 | "vbroadcastss 120(%[vals]), %%zmm30\n\t" 92 | "vbroadcastss 124(%[vals]), %%zmm31\n\t" 93 | 94 | "kmovb 0(%[kmasks]), %%k0\n\t" 95 | "kmovb 1(%[kmasks]), %%k1\n\t" 96 | "kmovb 2(%[kmasks]), %%k2\n\t" 97 | "kmovb 3(%[kmasks]), %%k3\n\t" 98 | "kmovb 4(%[kmasks]), %%k4\n\t" 99 | "kmovb 5(%[kmasks]), %%k5\n\t" 100 | "kmovb 6(%[kmasks]), %%k6\n\t" 101 | "kmovb 7(%[kmasks]), %%k7\n\t" 102 | : 103 | : [vals] "r"(vals), [kmasks] "r"(kmasks) 104 | : "memory" 105 | ); 106 | } 107 | 108 | void add_some_numbers_using_avx() { 109 | int data[32]; 110 | srand(time(NULL)); 111 | for (int i = 0; i < 32; i++) { 112 | data[i] = rand() % 100; 113 | } 114 | 115 | __m256i accum = _mm256_setzero_si256(); 116 | 117 | for (int i = 0; i < 32; i += 8) { 118 | __m256i vec = _mm256_loadu_si256((__m256i*)&data[i]); 119 | accum = _mm256_add_epi32(accum, vec); 120 | } 121 | 122 | int temp[8]; 123 | _mm256_storeu_si256((__m256i*)temp, accum); 124 | 125 | int sum = 0; 126 | for (int i = 0; i < 8; i++) { 127 | sum += temp[i]; 128 | } 129 | printf("sum: %d\n", sum); 130 | } 131 | 132 | int main() { 133 | fill_avx_registers(); 134 | if (has_avx512()) { 135 | fill_avx512_registers(); 136 | } 137 | add_some_numbers_using_avx(); 138 | return 0; 139 | } 140 | -------------------------------------------------------------------------------- /testprogs/simple_threads.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | using namespace std; 6 | 7 | int main(){ 8 | cout<<"started"< 2 | #include 3 | #include 4 | #include 5 | 6 | thread_local int simple_int = 42; 7 | thread_local int simple_array[5] = {10, 20, 30, 40, 50}; 8 | thread_local std::vector tls_vector = {1, 2, 3}; 9 | 10 | std::mutex cout_mutex; 11 | 12 | void thread_function(int thread_id) { 13 | // Modify the thread-local variables to prevent optimization 14 | simple_int += thread_id; 15 | simple_array[0] += thread_id; 16 | tls_vector.push_back(thread_id); 17 | 18 | // Print the values to show they are thread-specific 19 | std::lock_guard lock(cout_mutex); 20 | std::cout << "Thread " << thread_id << ":\n"; 21 | std::cout << " simple_int = " << simple_int << "\n"; 22 | std::cout << " simple_array[0] = " << simple_array[0] << "\n"; 23 | std::cout << " tls_vector = {"; 24 | for (size_t i = 0; i < tls_vector.size(); ++i) { 25 | if (i > 0) std::cout << ", "; 26 | std::cout << tls_vector[i]; 27 | } 28 | std::cout << "}\n"; 29 | } 30 | 31 | int main() { 32 | std::thread t1(thread_function, 1); 33 | std::thread t2(thread_function, 2); 34 | std::thread t3(thread_function, 3); 35 | 36 | t1.join(); 37 | t2.join(); 38 | t3.join(); 39 | 40 | // Access in main thread to prevent optimization 41 | simple_int += 100; 42 | simple_array[1] += 100; 43 | tls_vector.push_back(100); 44 | 45 | std::cout << "Main thread:\n"; 46 | std::cout << " simple_int = " << simple_int << "\n"; 47 | std::cout << " simple_array[1] = " << simple_array[1] << "\n"; 48 | std::cout << " tls_vector = {"; 49 | for (size_t i = 0; i < tls_vector.size(); ++i) { 50 | if (i > 0) std::cout << ", "; 51 | std::cout << tls_vector[i]; 52 | } 53 | std::cout << "}\n"; 54 | 55 | return 0; 56 | } 57 | -------------------------------------------------------------------------------- /testprogs/string.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | int main() { 7 | volatile const char * a = "asd"; 8 | std::string b = "qwe"; 9 | std::string_view c = b; 10 | std::string d(1000000, 'x'); 11 | volatile const char * e = d.c_str(); 12 | volatile size_t s = (size_t)std::strlen((const char*)a) + b.size() + c.size() + d.size() + (size_t)strlen((const char*)e); 13 | std::cout << s; 14 | } 15 | -------------------------------------------------------------------------------- /testprogs/string_rs.rs: -------------------------------------------------------------------------------- 1 | use std::{ffi::{OsStr, OsString}, path::{Path, PathBuf}}; 2 | 3 | fn main() { 4 | let a: &'static str = "asd"; 5 | let b: String = "qwe".to_string(); 6 | let c = OsStr::new("zxc"); 7 | let d = OsString::from("123"); 8 | let e = Path::new("rty"); 9 | let f = PathBuf::from("fgh"); 10 | let g = [10usize, 20, 30]; 11 | let h = &g[2..]; 12 | let s = a.len() + b.len() + c.len() + d.len() + e.as_os_str().len() + f.as_os_str().len() + g.len() + h.len(); 13 | std::hint::black_box(s); 14 | } 15 | -------------------------------------------------------------------------------- /testprogs/structs_with_same_name.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | using namespace std; 3 | 4 | int main(){ 5 | { 6 | struct A{int f() {return 1;}}; 7 | cout< 2 | 3 | enum class En : unsigned int { 4 | A = 1, 5 | B = 0xffffffff, 6 | C = 0, 7 | }; 8 | 9 | enum class Neg : int { 10 | A = -1, 11 | B = -2, 12 | C = -100, 13 | D = 5, 14 | }; 15 | 16 | int main() { 17 | std::array a {En::A, En::B, En::C, (En)42}; 18 | std::array b {(Neg)42, Neg::A, Neg::B, Neg::C, Neg::D}; 19 | volatile unsigned s = 0; 20 | for (auto x : a) s = s + (unsigned)x; 21 | for (auto x : b) s = s + (unsigned)x; 22 | (void)s; 23 | } 24 | -------------------------------------------------------------------------------- /testprogs/types_across_units/a.c: -------------------------------------------------------------------------------- 1 | #include "p.h" 2 | #include 3 | 4 | struct A { 5 | int a; 6 | }; 7 | 8 | struct A a; 9 | 10 | void fa(struct Pair *p) { 11 | a.a = 42; 12 | p->a = &a; 13 | printf("fa %d\n", p->a->a); 14 | } 15 | -------------------------------------------------------------------------------- /testprogs/types_across_units/a.o: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/al13n321/nnd/f3f6d614991ee1034cbcd0117962b37f3f8517e2/testprogs/types_across_units/a.o -------------------------------------------------------------------------------- /testprogs/types_across_units/b.c: -------------------------------------------------------------------------------- 1 | #include "p.h" 2 | #include 3 | #include 4 | 5 | struct B { 6 | double b; 7 | }; 8 | 9 | void fb(struct Pair *p) { 10 | p->b = (struct B*)malloc(sizeof(struct B)); 11 | p->b->b = 1.23; 12 | printf("fb %f\n", p->b->b); 13 | } 14 | -------------------------------------------------------------------------------- /testprogs/types_across_units/b.o: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/al13n321/nnd/f3f6d614991ee1034cbcd0117962b37f3f8517e2/testprogs/types_across_units/b.o -------------------------------------------------------------------------------- /testprogs/types_across_units/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | BASEDIR=$(dirname "$0") 5 | gcc -g -O2 -c -o "$BASEDIR/a.o" "$BASEDIR/a.c" 6 | gcc -g -O2 -c -o "$BASEDIR/b.o" "$BASEDIR/b.c" 7 | gcc -g -O2 -c -o "$BASEDIR/main.o" "$BASEDIR/main.c" 8 | gcc -g -O2 -o "$BASEDIR/main" "$BASEDIR/main.o" "$BASEDIR/a.o" "$BASEDIR/b.o" 9 | -------------------------------------------------------------------------------- /testprogs/types_across_units/main: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/al13n321/nnd/f3f6d614991ee1034cbcd0117962b37f3f8517e2/testprogs/types_across_units/main -------------------------------------------------------------------------------- /testprogs/types_across_units/main.c: -------------------------------------------------------------------------------- 1 | #include "p.h" 2 | #include 3 | 4 | int main() { 5 | struct Pair p; 6 | fa(&p); 7 | fb(&p); 8 | printf("%lu %lu\n", (unsigned long)p.a, (unsigned long)p.b); 9 | } 10 | -------------------------------------------------------------------------------- /testprogs/types_across_units/main.o: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/al13n321/nnd/f3f6d614991ee1034cbcd0117962b37f3f8517e2/testprogs/types_across_units/main.o -------------------------------------------------------------------------------- /testprogs/types_across_units/p.h: -------------------------------------------------------------------------------- 1 | // No compilation unit has both A and B defined. So the debugger can't get a full definition of Pair without relying on names and combining types from different units. 2 | 3 | struct A; 4 | struct B; 5 | 6 | struct Pair { 7 | struct A *a; 8 | struct B *b; 9 | }; 10 | 11 | void fa(struct Pair *p); 12 | void fb(struct Pair *p); 13 | -------------------------------------------------------------------------------- /testprogs/types_rs.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | use std::hint::black_box; 3 | 4 | enum E { 5 | X, 6 | Y(usize), 7 | Z {a: String, b: usize}, 8 | W, 9 | A(usize, usize), 10 | } 11 | 12 | enum F { 13 | X, 14 | Y, 15 | Z, 16 | W, 17 | } 18 | 19 | fn main() { 20 | let x = E::X; 21 | let y = E::Y(42); 22 | let z = E::Z {a: "hi".to_string(), b: 13}; 23 | let w = E::W; 24 | let a = E::A(10, 20); 25 | black_box((x, y, z, w, a)); 26 | 27 | let mut a = [E::X, E::W, E::Y(1), E::Y(2), E::Z {a: "meow".to_string(), b: 3}]; 28 | let s = &a[1..3]; 29 | black_box(s); 30 | let s = &mut a[1..3]; 31 | black_box(s); 32 | black_box(a); 33 | 34 | let t = (E::X, 1337, "asd", E::Y(1)); 35 | black_box(t); 36 | 37 | let fs = [F::X, F::Y, F::Z, F::W]; 38 | black_box(fs); 39 | } 40 | -------------------------------------------------------------------------------- /testprogs/vdso.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main() { 5 | struct timespec t; 6 | clock_gettime(0, &t); 7 | printf("%ld\n", t.tv_sec); 8 | } 9 | -------------------------------------------------------------------------------- /todo: -------------------------------------------------------------------------------- 1 | bugs: 2 | get stuck when debuggee is OOM-killed by linux 3 | investigate `clickhouse local` progress spinner appearing in debugger window despite -t 4 | fix empty symbols loading stage name near the end 5 | investigate regression in symbols loading speed (stuck on "parsing drawf" for ~10 seconds) 6 | investigate why many types are not deduped, e.g. there's DB::Block#441 7 | make stack unwinding work when control is in .plt or .plt.got, see "Recognise PLT entries such as" in libunwind code 8 | fix incorrect column numbers with tab indentation (tabs should be counted as 1 column, but we turn them into multiple spaces) 9 | fix line-wrapped copy-paste (seems to copy only the first line of selection) and line-wrapped C-k (deletes only to the end of post-wrapping line, should use pre-wrapping line) 10 | 11 | performance: 12 | optimize symbols loading 13 | profile attribute loading 14 | madvise 15 | benchmark with file pre-evicted from page cache (using vmtouch) 16 | fix warning about unsupported forms, especially implicit_const on flags 17 | try caching chase results (71M chases, 15M unique results, 205M total DIEs) 18 | test with dwarf 4 19 | replace strlen inside .debug_str etc lookups 20 | simple things found by profiler (samply is nice) 21 | look at profiler on ch server (1500 threads) 22 | test with 10k threads 23 | fix O(n^2) stuff until syscalls are the slowest part 24 | time waitpid separately 25 | waitpid is suspiciously slow; does it iterate over threads? can partially avoid it by anticipating which threads will get events? 26 | if there are enough free hw breakpoints, activate new breakpoints as hw and thread-specific right away without waiting for them to be spuriously hit by other threads 27 | maybe avoid duplicate POKEUSER calls for debug registers if the thread already had them assigned 28 | test with watches window filled with long arrays (I saw 50ms frames when there are ~5 of them; maybe just decrease length limit) 29 | investigate unresponsive ui when conditional breakpoint is hit at a high rate 30 | 31 | todo: 32 | help dialog 33 | try with odin 34 | zig pretty-printers 35 | support gnu_debugaltlink, as found in zsh in debuginfod 36 | output window (plain text) 37 | get debuglink binaries from debuginfod 38 | "Expected an attribute value to be a string form" on zsh 39 | buffer input when program is quitting, especially k+r and steps 40 | test with breakpad minidumps 41 | function pointers 42 | modifying debuggee variables (memory and registers), modifying ip 43 | log a message if the process forked away 44 | add alternative default key binds for next/prev search result, F3 is not always available on mac 45 | async fetch from debuginfod (load symbols without waiting for it, then on success start over) 46 | show an error if program failed to start (e.g. executable doesn't exist) 47 | panic screen: show stack trace and process state, type something to resume+detach or kill or email stack trace or quit 48 | allow specifying cgroup and user (or maybe it's too hard, need to maybe inherit groups from parent somehow too) 49 | maybe refresh global variables in watches window on periodic timer tick when the program is running 50 | make watch window show dereferenced string-like values by default (otherwise expanding it shows the array of characters and eats all vertical space) 51 | update rust HashMap pretty printer 52 | detect and show if a pointer points to a symbol (function, global variable, vtable) 53 | do something for downcasts in conditional breakpoints (coalesce()? maybe()? ?()? ()?? checkbox to stop on error? 'is' operator? explicit checked downcast? also function to check field presense without evaluating it) 54 | edit breakpoint condition on click 55 | `in` operator, e.g. x in [1, 2, 3] 56 | make names in MetaType etc unexpandable as array 57 | make '^' apply to expressions instead of variable names, e.g. ^(this.query_context) 58 | allow adding unconditional breakpoint on the same line as conditional one, somehow 59 | global variables from .symtab, maybe from .debug_pubnames (maybe also look into .debug_pubtypes) 60 | more built-in pretty printers: boost::multi_index sparsehash, absl, rust b-trees 61 | hotkey to switch between .h/.cpp (/.cc/.hh/.hpp/.cxx - maybe just anything after '.') 62 | reordering watches 63 | reordering tabs 64 | rearranging windows 65 | maximizing windows 66 | meta functions like checking for field presense of superclass type, and also just slice length etc, for use in conditions 67 | key to freeze value in watches window; can also be used for controlling when to run the expression if it has side effects 68 | key to add watch value as new watch, maybe cast to type, e.g. *(0x1234 as *DB::Block) 69 | tooltip in status window showing more log lines with line wrapping 70 | make return key do something in each window: go from threads to stack -> code -> disassembly -> code; from locals create a watch; from watch sub-value create another watch for that field/element; from breakpoints to code/disassembly/watch (depending on breakpoint type) 71 | allow expressions (especially conditions) to have multiple statements separated by ';' 72 | "unexpectedly big memory read" (16 bytes) for f64 local variables on the stack in rust 73 | some kind of non-stop mode to prevent clickhouse server from losing keeper connection when stepping through the code 74 | allow navigating from source to variables (especially global) and types declared there 75 | allow to put breakpoint on a whole file (by adding breakpoint on the first line, since there's never any real code on first line in practice; have a new icon for it; internally use line 0 to cover the garbage LineInfo-s with line 0; or maybe put breakpoint only on function entry points instead of everything) 76 | function pointers and field pointers 77 | handle tail calls: if you step-into a tail call (by single-stepping from instruction pointed by DW_AT_call_pc of a DW_TAG_call_site with DW_AT_call_tail_call), the tail-callee should be added to stack digest (so its stack frame is selected instead of parent) 78 | find function by address (like addr2line) 79 | use actual function name instead of "_" in namespace path, or do something else to make function-static variables usable 80 | show number of function inlined sites in disassembly window, allow setting breakpoint on it, allow opening inline-only functions 81 | show stop reason in status window, next to "suspended" 82 | follow forks, maybe even support multiple processes 83 | allow cast `foo as typeof(bar)`, also `foo as typeof(bar)::T` 84 | show argument values in stack trace 85 | detect dynamic library loads using r_debug thing 86 | hotkey to step to end of basic block or next call/ret 87 | key to follow a jump in disas 88 | allow line-based steps in .plt* (maybe by just turning them into single-instruction-steps) 89 | in disassembly window, make 'left' key jump to the start of inlined function 90 | maximizing windows (hot key and button in a corner) 91 | special breakpoints: panic (rust_panic, core::panicking::panic_fmt, or maybe std::panicking::rust_panic_with_hook; maybe disable find_catch_blocks() for rust at least when panic bp is enabled), exception (__cxa_throw), signals, main function, dynamic library load 92 | make source window autoscroll horizontally in addition to vertically 93 | -v for build datetime 94 | handle partially-optimized-out struct values (common in rust) 95 | read color scheme from file 96 | color threads based on stack hash 97 | search in watches window 98 | assigning to debuggee variables and registers, including rip 99 | key to teleport ip to current line 100 | 101 | watches, expressions 102 | first, implement simple expression watches 103 | second, write many pretty printers in imaginary language, figure out what it should be; std::vector, unordered_map, unique_ptr, shared_ptr, list, optional, Rust Vec, HashMap, Option, Box, deb's Pool, etc; see if per-field format options are needed 104 | stateful expression interpreter, yield, functions 105 | associating print expressions with types (by substring match to cover templates?) 106 | always show raw value as one of the children, including for container elements (yield raw value when recursing) 107 | print options: hex, raw, array length (expression), expanded (i.e. omit array contents etc) 108 | maybe: api to output a tree of nested values, skipping subtrees if collapsed (`if $yield(val, nested)`, `$yield((), end)`?) 109 | manual refresh for slow expressions, exec time limit (press refresh to double the limit and retry) 110 | special functions/operators like sizeof() ($sizeof()?), type_name, type_offset, type_binary, offsetof, type_of (treating type as value of a special type, expandable in watch window) 111 | format matrices prettily 112 | format options for simd registers to show as vectors of given type and length (expandable) 113 | make basic types like u16 available without big search, with consistent names 114 | also consider subset of the language for injecting conditional breakpoint code 115 | 116 | unfocus search bars when moving up/down the list (but not when scrolling) 117 | show disassembly even if symbols are missing 118 | group threads by stack trace, to make it easy to exclude pool threads waiting for work 119 | show return value after step-out (and other steps that happened to step out) 120 | data breakpoints (added by pressing 'b' on a watch, the breakpoint would use the address of the watched value at the time of setting the breakpoint, with some indication if it's different from current); maybe added as a new watch with address as a literal; or added in breakpoints window and focus jumps there 121 | thread-specific breakpoints (controlled in breakpoints window), a key to lock all breakpoints to current thread 122 | hotkeys for switching to specific windows 123 | resolve dynamic library call targets in disassembly, instead of showing them as "call .plt.sec+1234h" 124 | research symtab function range overlaps, don't ignore lengths (e.g. in ld-linux-x86-64.so.2, entry point is a NOTYPE symbol _start, which we incorrectly attribute to previous function _dl_help that ends just before the program entry point) 125 | pretty print variable locations (inline frame base and cfa, turn simple expressions from postfix to infix notation) 126 | handle subset of fields being optimized out (seen e.g. for metric_active_threads in ThreadPoolImpl::worker) 127 | maybe show various stop conditions (program start, crashing signals, stepping) uniformly as virtual breakpoints in breakpoints window 128 | test symbols loader with TSAN (for the sketchy bespoke locking in types traversal) 129 | parse and colorize function names, especially the template stuff 130 | parse and colorize type names, especially the template stuff 131 | 132 | try refactoring Debugger to move threads outside to avoid re-lookups everywhere 133 | show code in disassembly 134 | show variable names in disassembly 135 | deferred breakpoints that are resolved on future dynamic library loads; allow opening files that don't exist, or something 136 | locking windows 137 | test on very large disassembled functions and large source files 138 | disassembly: show basic block boundaries (jump destinations) 139 | disassembly: show current jump destination like in perf 140 | maybe handle tail calls somehow, for step-over/step-out purposes; probably show it in ui 141 | if a step is interrupted, focus on the stack frame of the step, not the top frame 142 | allow calling functions 143 | refactor loader stack to not contain leaves 144 | snapshots and "step back"; probably don't actually revert the process state, just allow inspecting snapshot of memory+registers 145 | --------------------------------------------------------------------------------