├── .appveyor.yml ├── .editorconfig ├── .gitattributes ├── .gitignore ├── 3rd ├── LICENSE-MIT ├── README.md ├── rust-demangle.c └── rust-demangle.h ├── LICENSE ├── README.md ├── genie ├── genie.lua └── rdebug.lua ├── inc └── rdebug.h ├── makefile └── src ├── pdb_file.cpp ├── pdb_file.h ├── process.cpp ├── rdebug_pch.cpp ├── rdebug_pch.h ├── symbol_parsing.cpp ├── symbols.cpp ├── symbols_map.cpp ├── symbols_map.h └── symbols_types.h /.appveyor.yml: -------------------------------------------------------------------------------- 1 | 2 | environment: 3 | matrix: 4 | - job_name: Windows 5 | APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2022 6 | configuration: Debug 7 | 8 | - job_name: Windows 9 | APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2022 10 | configuration: Release 11 | 12 | - job_name: Windows 13 | APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2022 14 | configuration: Retail 15 | 16 | - job_name: Linux 17 | APPVEYOR_BUILD_WORKER_IMAGE: Ubuntu2204 18 | buildcfg: debug64 19 | 20 | - job_name: Linux 21 | APPVEYOR_BUILD_WORKER_IMAGE: Ubuntu2204 22 | buildcfg: release64 23 | 24 | - job_name: Linux 25 | APPVEYOR_BUILD_WORKER_IMAGE: Ubuntu2204 26 | buildcfg: retail64 27 | 28 | - job_name: OSX 29 | APPVEYOR_BUILD_WORKER_IMAGE: macOS-sonoma 30 | buildcfg: debug64 31 | 32 | - job_name: OSX 33 | APPVEYOR_BUILD_WORKER_IMAGE: macOS-sonoma 34 | buildcfg: release64 35 | 36 | - job_name: OSX 37 | APPVEYOR_BUILD_WORKER_IMAGE: macOS-sonoma 38 | buildcfg: retail64 39 | 40 | shallow_clone: true 41 | 42 | install: 43 | - git submodule init 44 | - git submodule update 45 | 46 | for: 47 | 48 | # ====================================== 49 | # Windows 50 | # ====================================== 51 | 52 | - 53 | matrix: 54 | only: 55 | - job_name: Windows 56 | 57 | init: 58 | - git clone --depth 1 https://github.com/RudjiGames/rdebug rdebug 59 | - git clone --depth 1 https://github.com/RudjiGames/build build 60 | - git clone --depth 1 https://github.com/RudjiGames/rbase rbase 61 | - git clone --depth 1 https://github.com/RudjiGames/DIA DIA 62 | 63 | install: 64 | - build\tools\bin\windows\genie.exe --file=rdebug\genie\genie.lua vs2022 65 | 66 | build: 67 | project: .build\windows\vs2022\rdebug\projects\rdebug.sln 68 | 69 | # ====================================== 70 | # Linux 71 | # ====================================== 72 | 73 | - 74 | matrix: 75 | only: 76 | - job_name: Linux 77 | 78 | init: 79 | - git clone --depth 1 https://github.com/RudjiGames/rdebug rdebug 80 | - git clone --depth 1 https://github.com/RudjiGames/build build 81 | - git clone --depth 1 https://github.com/RudjiGames/rbase rbase 82 | - git clone --depth 1 https://github.com/bkaradzic/GENie GENie # build GENie from source: `GLIBC_2.29' not found 83 | - cd GENie && make && cd .. 84 | 85 | install: 86 | - ./GENie/bin/linux/genie --file=rdebug/genie/genie.lua --gcc=linux-gcc gmake 87 | 88 | build_script: 89 | - cd ./.build/linux/linux-gcc/rdebug/projects/ 90 | - make config=${buildcfg} 91 | 92 | # ====================================== 93 | # OSX 94 | # ====================================== 95 | 96 | - 97 | matrix: 98 | only: 99 | - job_name: OSX 100 | 101 | init: 102 | - git clone --depth 1 https://github.com/RudjiGames/rdebug rdebug 103 | - git clone --depth 1 https://github.com/RudjiGames/build build 104 | - git clone --depth 1 https://github.com/RudjiGames/rbase rbase 105 | - git clone --depth 1 https://github.com/bkaradzic/GENie GENie # build GENie from source: we don't know the target CPU, can't use ARM binary from 'build' 106 | - cd GENie && make && cd .. 107 | 108 | install: 109 | - ./GENie/bin/darwin/genie --file=rdebug/genie/genie.lua --gcc=osx-x64 gmake 110 | 111 | build_script: 112 | - cd ./.build/osx/clang/rdebug/projects/ 113 | - make config=${buildcfg} 114 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = tab 5 | indent_size = 4 6 | end_of_line = lf 7 | max_line_length = 150 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | max_line_length = 80 14 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.c eol=lf 2 | *.cpp eol=lf 3 | *.h eol=lf 4 | *.hpp eol=lf 5 | *.hlsl eol=lf 6 | *.md eol=lf 7 | *.lua eol=lf 8 | *.mk eol=lf 9 | makefile eol=lf 10 | 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .build 2 | .DS_Store 3 | 4 | 5 | -------------------------------------------------------------------------------- /3rd/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /3rd/README.md: -------------------------------------------------------------------------------- 1 | # `rust-demangle.c` 2 | 3 | This is a single-file C99 port of the official Rust symbol demangler ([`rustc-demangle`](https://github.com/rust-lang/rustc-demangle), a Rust library). 4 | 5 | ## Usecases 6 | 7 | This C port is intended for situations in which a Rust dependency is hard to 8 | justify, or effectively impossible (e.g. platform toolchains, that would be used 9 | *while building Rust*, not the other way around). 10 | 11 | If a Rust dependency is acceptable, [using `rustc-demangle` from C](https://github.com/rust-lang/rustc-demangle#usage-from-non-rust-languages) 12 | (or other languages via FFI) is possible, and may be preferred over this C port. 13 | 14 | ## Status 15 | 16 | As this C port was originally (see the [History](#history) section) *only* for the 17 | `rustc-demangle` code demangling the (new at the time) [Rust RFC2603 (aka "`v0`") mangling scheme](https://rust-lang.github.io/rfcs/2603-rust-symbol-name-mangling-v0.html), 18 | it may lag behind `rustc-demangle` in functionality, for now. 19 | 20 | The current port status by category is: 21 | * **ported** `legacy` (pre-RFC2603 Rust symbols) demangling 22 | * `v0` ([RFC2603](https://rust-lang.github.io/rfcs/2603-rust-symbol-name-mangling-v0.html) Rust symbols) demangling 23 | * **ported** PRs: 24 | * [[#23] Support demangling the new Rust mangling scheme (v0).](https://github.com/rust-lang/rustc-demangle/pull/23) 25 | * [[#26] v0: allow identifiers to start with a digit.](https://github.com/rust-lang/rustc-demangle/pull/26) 26 | * [[#53] v0: replace `skip_*` methods with `print_*` methods in a "skip printing" mode.](https://github.com/rust-lang/rustc-demangle/pull/53) 27 | * arguably backported to Rust, as the C port always took this approach 28 | * symbol prefix flexibility (`__R` and `R`, instead of `_R`) 29 | * [[#39] Add support for `min_const_generics` constants](https://github.com/rust-lang/rustc-demangle/pull/39) 30 | * [[#40] Elide the type when the const value is a placeholder `p`](https://github.com/rust-lang/rustc-demangle/pull/40) 31 | * [[#55] v0: demangle structural constants and &str.](https://github.com/rust-lang/rustc-demangle/pull/55) 32 | (only usable in `const` generics on unstable Rust) 33 | * **(UNPORTED)** recursion limits 34 | * miscellaneous 35 | * **ported** PRs: 36 | * [[#30] v0: also support preserving extra suffixes found after mangled symbol.](https://github.com/rust-lang/rustc-demangle/pull/30) 37 | * **(UNPORTED)** output size limits 38 | 39 | Notable differences (intentionally) introduced by porting: 40 | * `rustc-demangle` can't use the heap (as it's `#![no_std]`), but the C port does 41 | * this is mainly dictated by the ergonomics of the `rust_demangle` API, which 42 | requires `malloc`/`realloc` to return a new C string allocation 43 | * if there is demand for it, `rust_demangle` support could be made optional, 44 | forcing heap-less users to always use `rust_demangle_with_callback` instead 45 | * a subtler consequence is that `rustc-demangle` uses a fixed-size buffer on 46 | the stack for punycode decoding, while the C port can allocate it on the heap 47 | * Unicode support is always handrolled in the C port, and often simplified 48 | 49 | ## Usage 50 | 51 | Get `rust-demangle.c` and `rust-demangle.h` (via `git submodule`, vendoring, etc.), 52 | add them to your project's build system (as C source, and include path, respectively), 53 | then you can call `rust_demangle` with a symbol and some flags, e.g.: 54 | ```c 55 | #include 56 | #include 57 | 58 | int main() { 59 | const char *sym = "_RNvNtCsbmNqQUJIY6D_4core3foo3bar"; 60 | 61 | printf("demangle(%s) = %s\n", sym, rust_demangle(sym, 0)); 62 | 63 | printf( 64 | "demangle(%s, VERBOSE) = %s\n", sym, 65 | rust_demangle(sym, RUST_DEMANGLE_FLAG_VERBOSE) 66 | ); 67 | } 68 | ``` 69 | which prints out, when ran: 70 | ``` 71 | demangle(_RNvNtCsbmNqQUJIY6D_4core3foo3bar) = core::foo::bar 72 | demangle(_RNvNtCsbmNqQUJIY6D_4core3foo3bar, VERBOSE) = core[846817f741e54dfd]::foo::bar 73 | ``` 74 | 75 | Note that the example leaks the returned C strings, ideally you would `free` them. 76 | 77 | ### Advanced usage (callback-based API) 78 | 79 | If you want to avoid the cost of allocating the output in memory, you can also 80 | use `rust_demangle_with_callback`, which takes a "printing" callback instead, e.g.: 81 | ```c 82 | #include 83 | #include 84 | 85 | static void fwrite_callback(const char *data, size_t len, void *opaque) { 86 | fwrite(data, len, 1, (FILE *)opaque); 87 | } 88 | 89 | int main() { 90 | const char *sym = "_RNvNtCsbmNqQUJIY6D_4core3foo3bar"; 91 | 92 | printf("demangle(%s) = ", sym); 93 | rust_demangle_with_callback(sym, 0, fwrite_callback, stdout); 94 | printf("\n"); 95 | 96 | printf("demangle(%s, VERBOSE) = ", sym); 97 | rust_demangle_with_callback( 98 | sym, RUST_DEMANGLE_FLAG_VERBOSE, fwrite_callback, stdout 99 | ); 100 | printf("\n"); 101 | } 102 | ``` 103 | (with identical output to the simpler example) 104 | 105 | ## Testing 106 | 107 | `cargo test` will run built-in tests - it's implemented in Rust (in `test-harness`) 108 | so that it can depend on `rustc-demangle` itself for comparisons. 109 | 110 | Additionally, `cargo run -q --release --example check-csv-dataset path/to/syms/*.csv` 111 | can be used to provide CSV files with additional mangled symbols test data, but such 112 | datasets aren't trivial to obtain (existing ones required building `rust-lang/rust` 113 | with a compiler patch that reacts to a custom environment variable). 114 | They're also quite large (~1GiB uncompressed) so none have been published anywhere yet. 115 | 116 | ## History 117 | 118 | This C port was started while the [Rust RFC2603 (aka "`v0`") mangling scheme](https://rust-lang.github.io/rfcs/2603-rust-symbol-name-mangling-v0.html) 119 | was still being developed, with the intent of upstreaming it into `libiberty` 120 | (which provides demangling for `binutils`, `gdb`, Linux `perf`, etc.) and other 121 | projects (e.g. `valgrind`) - you can see some of that upstreaming history 122 | [on the `v0` tracking issue](https://github.com/rust-lang/rust/issues/60705). 123 | 124 | At the time, the expectation was that most projects could either depend on 125 | `libiberty`, or vendor a copy of its code, so the C port focused on upstreaming 126 | to it, rather than producing an independent reusable C codebase. 127 | 128 | That meant that instead of a `git` repository, the [code revisions were only tracked by a gist](https://gist.github.com/eddyb/c41a69378750a433767cf53fe2316768/revisions), 129 | and the GNU code style was followed (including C89 comments and variable declarations). 130 | 131 | However, the LGPL license of `libiberty` turned out to be a problem for adoption, 132 | compared to the typical MIT/Apache-2.0 dual licensing of Rust projects. 133 | 134 | ### The `rust-demangle.c` fork 135 | 136 | This repository started out as a fork of [the original gist](https://gist.github.com/eddyb/c41a69378750a433767cf53fe2316768/revisions), at commit [`e2c30407516a87c0f8c3820cf152640bd08805dd`](https://github.com/LykenSol/rust-demangle.c/commit/e2c30407516a87c0f8c3820cf152640bd08805dd), *just before `libiberty` 137 | integration* (which was in commit [`0e6f57b0e86ccec4395f8850f4885b1e391a9f4b`](https://gist.github.com/eddyb/c41a69378750a433767cf53fe2316768/0e6f57b0e86ccec4395f8850f4885b1e391a9f4b)). 138 | 139 | Any changes since that gist are either fresh C ports of the Rust `rustc-demangle` 140 | code, or completely new code, in order to maintain the [MIT/Apache-2.0 dual licensing](#license). 141 | 142 | While this has the disadvantage of starting behind `libiberty` (which kept its 143 | Rust `legacy` demangler, and also got a few more features during/since upstreaming), 144 | the relationship may reverse eventually, where this port could get new features 145 | that would then have to be upstreamed into `libiberty`. 146 | 147 | ## License 148 | 149 | [Like `rustc-demangle`](https://github.com/rust-lang/rustc-demangle#license), this project is licensed under either of 150 | 151 | * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or 152 | http://www.apache.org/licenses/LICENSE-2.0) 153 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or 154 | http://opensource.org/licenses/MIT) 155 | 156 | at your option. 157 | 158 | ### Contribution 159 | 160 | Unless you explicitly state otherwise, any contribution intentionally submitted 161 | for inclusion in `rust-demangle.c` you, as defined in the Apache-2.0 license, shall 162 | be dual licensed as above, without any additional terms or conditions. 163 | -------------------------------------------------------------------------------- /3rd/rust-demangle.c: -------------------------------------------------------------------------------- 1 | // FIXME(eddyb) should this use ``? 2 | #include "rust-demangle.h" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | struct rust_demangler { 10 | const char *sym; 11 | size_t sym_len; 12 | 13 | void *callback_opaque; 14 | void (*callback)(const char *data, size_t len, void *opaque); 15 | 16 | // Position of the next character to read from the symbol. 17 | size_t next; 18 | 19 | // `true` if any error occurred. 20 | bool errored; 21 | 22 | // `true` if nothing should be printed. 23 | bool skipping_printing; 24 | 25 | // `true` if printing should be verbose (e.g. include hashes). 26 | bool verbose; 27 | 28 | // Rust mangling version, with legacy mangling being -1. 29 | int version; 30 | 31 | uint64_t bound_lifetime_depth; 32 | }; 33 | 34 | #define ERROR_AND(x) \ 35 | do { \ 36 | rdm->errored = true; \ 37 | x; \ 38 | } while (0) 39 | #define CHECK_OR(cond, x) \ 40 | do { \ 41 | if (!(cond)) \ 42 | ERROR_AND(x); \ 43 | } while (0) 44 | 45 | // FIXME(eddyb) consider renaming these to not start with `IS` (UB?). 46 | #define IS_DIGIT(c) ((c) >= '0' && (c) <= '9') 47 | #define IS_UPPER(c) ((c) >= 'A' && (c) <= 'Z') 48 | #define IS_LOWER(c) ((c) >= 'a' && (c) <= 'z') 49 | 50 | // Parsing functions. 51 | 52 | static char peek(const struct rust_demangler *rdm) { 53 | if (rdm->next < rdm->sym_len) 54 | return rdm->sym[rdm->next]; 55 | return 0; 56 | } 57 | 58 | static bool eat(struct rust_demangler *rdm, char c) { 59 | if (peek(rdm) == c) { 60 | rdm->next++; 61 | return true; 62 | } else 63 | return false; 64 | } 65 | 66 | static char next(struct rust_demangler *rdm) { 67 | char c = peek(rdm); 68 | CHECK_OR(c, return 0); 69 | rdm->next++; 70 | return c; 71 | } 72 | 73 | struct hex_nibbles { 74 | const char *nibbles; 75 | size_t nibbles_len; 76 | }; 77 | 78 | static struct hex_nibbles parse_hex_nibbles(struct rust_demangler *rdm) { 79 | struct hex_nibbles hex; 80 | 81 | hex.nibbles = NULL; 82 | hex.nibbles_len = 0; 83 | 84 | size_t start = rdm->next, hex_len = 0; 85 | while (!eat(rdm, '_')) { 86 | char c = next(rdm); 87 | CHECK_OR(IS_DIGIT(c) || (c >= 'a' && c <= 'f'), return hex); 88 | hex_len++; 89 | } 90 | 91 | hex.nibbles = rdm->sym + start; 92 | hex.nibbles_len = hex_len; 93 | return hex; 94 | } 95 | 96 | static struct hex_nibbles 97 | parse_hex_nibbles_for_const_uint(struct rust_demangler *rdm) { 98 | struct hex_nibbles hex = parse_hex_nibbles(rdm); 99 | CHECK_OR(!rdm->errored, return hex); 100 | 101 | // Trim leading `0`s. 102 | while (hex.nibbles_len > 0 && *hex.nibbles == '0') { 103 | hex.nibbles++; 104 | hex.nibbles_len--; 105 | } 106 | 107 | return hex; 108 | } 109 | 110 | static struct hex_nibbles 111 | parse_hex_nibbles_for_const_bytes(struct rust_demangler *rdm) { 112 | struct hex_nibbles hex = parse_hex_nibbles(rdm); 113 | CHECK_OR(!rdm->errored && (hex.nibbles_len % 2 == 0), return hex); 114 | return hex; 115 | } 116 | 117 | static uint8_t decode_hex_nibble(char nibble) { 118 | return nibble >= 'a' ? 10 + (nibble - 'a') : nibble - '0'; 119 | } 120 | 121 | static uint64_t parse_integer_62(struct rust_demangler *rdm) { 122 | if (eat(rdm, '_')) 123 | return 0; 124 | 125 | uint64_t x = 0; 126 | while (!eat(rdm, '_')) { 127 | char c = next(rdm); 128 | x *= 62; 129 | if (IS_DIGIT(c)) 130 | x += c - '0'; 131 | else if (IS_LOWER(c)) 132 | x += 10 + (c - 'a'); 133 | else if (IS_UPPER(c)) 134 | x += 10 + 26 + (c - 'A'); 135 | else 136 | ERROR_AND(return 0); 137 | } 138 | return x + 1; 139 | } 140 | 141 | static uint64_t parse_opt_integer_62(struct rust_demangler *rdm, char tag) { 142 | if (!eat(rdm, tag)) 143 | return 0; 144 | return 1 + parse_integer_62(rdm); 145 | } 146 | 147 | static uint64_t parse_disambiguator(struct rust_demangler *rdm) { 148 | return parse_opt_integer_62(rdm, 's'); 149 | } 150 | 151 | struct rust_mangled_ident { 152 | // ASCII part of the identifier. 153 | const char *ascii; 154 | size_t ascii_len; 155 | 156 | // Punycode insertion codes for Unicode codepoints, if any. 157 | const char *punycode; 158 | size_t punycode_len; 159 | }; 160 | 161 | static struct rust_mangled_ident parse_ident(struct rust_demangler *rdm) { 162 | struct rust_mangled_ident ident; 163 | 164 | ident.ascii = NULL; 165 | ident.ascii_len = 0; 166 | ident.punycode = NULL; 167 | ident.punycode_len = 0; 168 | 169 | bool is_punycode = false; 170 | if (rdm->version != -1) { 171 | is_punycode = eat(rdm, 'u'); 172 | } 173 | 174 | char c = next(rdm); 175 | CHECK_OR(IS_DIGIT(c), return ident); 176 | size_t len = c - '0'; 177 | 178 | if (c != '0') 179 | while (IS_DIGIT(peek(rdm))) 180 | len = len * 10 + (next(rdm) - '0'); 181 | 182 | if (rdm->version != -1) { 183 | // Skip past the optional `_` separator. 184 | eat(rdm, '_'); 185 | } 186 | 187 | size_t start = rdm->next; 188 | rdm->next += len; 189 | // Check for overflows. 190 | CHECK_OR((start <= rdm->next) && (rdm->next <= rdm->sym_len), return ident); 191 | 192 | ident.ascii = rdm->sym + start; 193 | ident.ascii_len = len; 194 | 195 | if (is_punycode) { 196 | ident.punycode_len = 0; 197 | while (ident.ascii_len > 0) { 198 | ident.ascii_len--; 199 | 200 | // The last '_' is a separator between ascii & punycode. 201 | if (ident.ascii[ident.ascii_len] == '_') 202 | break; 203 | 204 | ident.punycode_len++; 205 | } 206 | CHECK_OR(ident.punycode_len > 0, return ident); 207 | ident.punycode = ident.ascii + (len - ident.punycode_len); 208 | } 209 | 210 | if (ident.ascii_len == 0) 211 | ident.ascii = NULL; 212 | 213 | return ident; 214 | } 215 | 216 | // Printing functions. 217 | 218 | static void 219 | print_str(struct rust_demangler *rdm, const char *data, size_t len) { 220 | if (!rdm->errored && !rdm->skipping_printing) 221 | rdm->callback(data, len, rdm->callback_opaque); 222 | } 223 | 224 | #define PRINT(s) print_str(rdm, s, strlen(s)) 225 | 226 | static void print_uint64(struct rust_demangler *rdm, uint64_t x) { 227 | char s[21]; 228 | sprintf(s, "%" PRIu64, x); 229 | PRINT(s); 230 | } 231 | 232 | static void print_uint64_hex(struct rust_demangler *rdm, uint64_t x) { 233 | char s[17]; 234 | sprintf(s, "%" PRIx64, x); 235 | PRINT(s); 236 | } 237 | 238 | static void 239 | print_quoted_escaped_char(struct rust_demangler *rdm, char quote, uint32_t c) { 240 | CHECK_OR(c < 0xd800 || (c > 0xdfff && c < 0x10ffff), return); 241 | 242 | switch (c) { 243 | case '\0': 244 | PRINT("\\0"); 245 | break; 246 | 247 | case '\t': 248 | PRINT("\\t"); 249 | break; 250 | 251 | case '\r': 252 | PRINT("\\r"); 253 | break; 254 | 255 | case '\n': 256 | PRINT("\\n"); 257 | break; 258 | 259 | case '\\': 260 | PRINT("\\\\"); 261 | break; 262 | 263 | case '"': 264 | if (quote == '"') { 265 | PRINT("\\\""); 266 | } else { 267 | PRINT("\""); 268 | } 269 | break; 270 | 271 | case '\'': 272 | if (quote == '\'') { 273 | PRINT("\\'"); 274 | } else { 275 | PRINT("'"); 276 | } 277 | break; 278 | 279 | default: 280 | if (c >= 0x20 && c <= 0x7e) { 281 | // Printable ASCII 282 | char v = (char)c; 283 | print_str(rdm, &v, 1); 284 | } else { 285 | // FIXME show printable unicode characters without hex encoding 286 | PRINT("\\u{"); 287 | char s[9] = {0}; 288 | sprintf(s, "%" PRIx32, c); 289 | PRINT(s); 290 | PRINT("}"); 291 | } 292 | } 293 | } 294 | 295 | static void 296 | print_ident(struct rust_demangler *rdm, struct rust_mangled_ident ident) { 297 | if (rdm->errored || rdm->skipping_printing) 298 | return; 299 | 300 | if (!ident.punycode) { 301 | print_str(rdm, ident.ascii, ident.ascii_len); 302 | return; 303 | } 304 | 305 | size_t len = 0; 306 | size_t cap = 4; 307 | while (cap < ident.ascii_len) { 308 | cap *= 2; 309 | // Check for overflows. 310 | CHECK_OR((cap * 4) / 4 == cap, return); 311 | } 312 | 313 | // Store the output codepoints as groups of 4 UTF-8 bytes. 314 | uint8_t *out = (uint8_t *)malloc(cap * 4); 315 | CHECK_OR(out, return); 316 | 317 | // Populate initial output from ASCII fragment. 318 | for (len = 0; len < ident.ascii_len; len++) { 319 | uint8_t *p = out + 4 * len; 320 | p[0] = 0; 321 | p[1] = 0; 322 | p[2] = 0; 323 | p[3] = ident.ascii[len]; 324 | } 325 | 326 | // Punycode parameters and initial state. 327 | size_t base = 36; 328 | size_t t_min = 1; 329 | size_t t_max = 26; 330 | size_t skew = 38; 331 | size_t damp = 700; 332 | size_t bias = 72; 333 | size_t i = 0; 334 | uint32_t c = 0x80; 335 | 336 | size_t punycode_pos = 0; 337 | while (punycode_pos < ident.punycode_len) { 338 | // Read one delta value. 339 | size_t delta = 0; 340 | size_t w = 1; 341 | size_t k = 0; 342 | size_t t; 343 | uint8_t d; 344 | do { 345 | k += base; 346 | t = k < bias ? 0 : (k - bias); 347 | if (t < t_min) 348 | t = t_min; 349 | if (t > t_max) 350 | t = t_max; 351 | 352 | CHECK_OR(punycode_pos < ident.punycode_len, goto cleanup); 353 | d = ident.punycode[punycode_pos++]; 354 | 355 | if (IS_LOWER(d)) 356 | d = d - 'a'; 357 | else if (IS_DIGIT(d)) 358 | d = 26 + (d - '0'); 359 | else 360 | ERROR_AND(goto cleanup); 361 | 362 | delta += d * w; 363 | w *= base - t; 364 | } while (d >= t); 365 | 366 | // Compute the new insert position and character. 367 | len++; 368 | i += delta; 369 | c += (uint32_t)(i / len); 370 | i %= len; 371 | 372 | // Ensure enough space is available. 373 | if (cap < len) { 374 | cap *= 2; 375 | // Check for overflows. 376 | CHECK_OR((cap * 4) / 4 == cap, goto cleanup); 377 | CHECK_OR(cap >= len, goto cleanup); 378 | } 379 | uint8_t *p = (uint8_t *)realloc(out, cap * 4); 380 | CHECK_OR(p, goto cleanup); 381 | out = p; 382 | 383 | // Move the characters after the insert position. 384 | p = out + i * 4; 385 | memmove(p + 4, p, (len - i - 1) * 4); 386 | 387 | // Insert the new character, as UTF-8 bytes. 388 | p[0] = (uint8_t)(c >= 0x10000 ? 0xf0 | (c >> 18) : 0); 389 | p[1] = 390 | c >= 0x800 ? (c < 0x10000 ? 0xe0 : 0x80) | ((c >> 12) & 0x3f) : 0; 391 | p[2] = (c < 0x800 ? 0xc0 : 0x80) | ((c >> 6) & 0x3f); 392 | p[3] = 0x80 | (c & 0x3f); 393 | 394 | // If there are no more deltas, decoding is complete. 395 | if (punycode_pos == ident.punycode_len) 396 | break; 397 | 398 | i++; 399 | 400 | // Perform bias adaptation. 401 | delta /= damp; 402 | damp = 2; 403 | 404 | delta += delta / len; 405 | k = 0; 406 | while (delta > ((base - t_min) * t_max) / 2) { 407 | delta /= base - t_min; 408 | k += base; 409 | } 410 | bias = k + ((base - t_min + 1) * delta) / (delta + skew); 411 | } 412 | 413 | // Remove all the 0 bytes to leave behind an UTF-8 string. 414 | size_t j; 415 | for (i = 0, j = 0; i < len * 4; i++) 416 | if (out[i] != 0) 417 | out[j++] = out[i]; 418 | 419 | print_str(rdm, (const char *)out, j); 420 | 421 | cleanup: 422 | free(out); 423 | } 424 | 425 | /// Print the lifetime according to the previously decoded index. 426 | /// An index of `0` always refers to `'_`, but starting with `1`, 427 | /// indices refer to late-bound lifetimes introduced by a binder. 428 | static void print_lifetime_from_index(struct rust_demangler *rdm, uint64_t lt) { 429 | PRINT("'"); 430 | if (lt == 0) { 431 | PRINT("_"); 432 | return; 433 | } 434 | 435 | uint64_t depth = rdm->bound_lifetime_depth - lt; 436 | // Try to print lifetimes alphabetically first. 437 | if (depth < 26) { 438 | char c = (char)('a' + depth); 439 | print_str(rdm, &c, 1); 440 | } else { 441 | // Use `'_123` after running out of letters. 442 | PRINT("_"); 443 | print_uint64(rdm, depth); 444 | } 445 | } 446 | 447 | // Demangling functions. 448 | 449 | static void demangle_binder(struct rust_demangler *rdm); 450 | static void demangle_path(struct rust_demangler *rdm, bool in_value); 451 | static void demangle_generic_arg(struct rust_demangler *rdm); 452 | static void demangle_type(struct rust_demangler *rdm); 453 | static bool demangle_path_maybe_open_generics(struct rust_demangler *rdm); 454 | static void demangle_dyn_trait(struct rust_demangler *rdm); 455 | static void demangle_const(struct rust_demangler *rdm, bool in_value); 456 | static void demangle_const_uint(struct rust_demangler *rdm, char ty_tag); 457 | static void demangle_const_str_literal(struct rust_demangler *rdm); 458 | 459 | /// Optionally enter a binder ('G') for late-bound lifetimes, 460 | /// printing e.g. `for<'a, 'b> `, and make those lifetimes visible 461 | /// to the caller (via depth level, which the caller should reset). 462 | static void demangle_binder(struct rust_demangler *rdm) { 463 | CHECK_OR(!rdm->errored, return); 464 | 465 | uint64_t bound_lifetimes = parse_opt_integer_62(rdm, 'G'); 466 | if (bound_lifetimes > 0) { 467 | PRINT("for<"); 468 | for (uint64_t i = 0; i < bound_lifetimes; i++) { 469 | if (i > 0) 470 | PRINT(", "); 471 | rdm->bound_lifetime_depth++; 472 | print_lifetime_from_index(rdm, 1); 473 | } 474 | PRINT("> "); 475 | } 476 | } 477 | 478 | static void demangle_path(struct rust_demangler *rdm, bool in_value) { 479 | CHECK_OR(!rdm->errored, return); 480 | 481 | char tag = next(rdm); 482 | switch (tag) { 483 | case 'C': { 484 | uint64_t dis = parse_disambiguator(rdm); 485 | struct rust_mangled_ident name = parse_ident(rdm); 486 | 487 | print_ident(rdm, name); 488 | if (rdm->verbose) { 489 | PRINT("["); 490 | print_uint64_hex(rdm, dis); 491 | PRINT("]"); 492 | } 493 | break; 494 | } 495 | case 'N': { 496 | char ns = next(rdm); 497 | CHECK_OR(IS_LOWER(ns) || IS_UPPER(ns), return); 498 | 499 | demangle_path(rdm, in_value); 500 | 501 | uint64_t dis = parse_disambiguator(rdm); 502 | struct rust_mangled_ident name = parse_ident(rdm); 503 | 504 | if (IS_UPPER(ns)) { 505 | // Special namespaces, like closures and shims. 506 | PRINT("::{"); 507 | switch (ns) { 508 | case 'C': 509 | PRINT("closure"); 510 | break; 511 | case 'S': 512 | PRINT("shim"); 513 | break; 514 | default: 515 | print_str(rdm, &ns, 1); 516 | } 517 | if (name.ascii || name.punycode) { 518 | PRINT(":"); 519 | print_ident(rdm, name); 520 | } 521 | PRINT("#"); 522 | print_uint64(rdm, dis); 523 | PRINT("}"); 524 | } 525 | else { 526 | // Implementation-specific/unspecified namespaces. 527 | 528 | if (name.ascii || name.punycode) { 529 | PRINT("::"); 530 | print_ident(rdm, name); 531 | } 532 | } 533 | break; 534 | } 535 | case 'M': 536 | case 'X': 537 | { 538 | // Ignore the `impl`'s own path. 539 | parse_disambiguator(rdm); 540 | bool was_skipping_printing = rdm->skipping_printing; 541 | rdm->skipping_printing = true; 542 | demangle_path(rdm, in_value); 543 | rdm->skipping_printing = was_skipping_printing; 544 | } 545 | //__attribute__((fallthrough)); 546 | 547 | case 'Y': 548 | PRINT("<"); 549 | demangle_type(rdm); 550 | if (tag != 'M') { 551 | PRINT(" as "); 552 | demangle_path(rdm, false); 553 | } 554 | PRINT(">"); 555 | break; 556 | case 'I': 557 | demangle_path(rdm, in_value); 558 | if (in_value) 559 | PRINT("::"); 560 | PRINT("<"); 561 | for (size_t i = 0; !rdm->errored && !eat(rdm, 'E'); i++) { 562 | if (i > 0) 563 | PRINT(", "); 564 | demangle_generic_arg(rdm); 565 | } 566 | PRINT(">"); 567 | break; 568 | case 'B': { 569 | size_t backref = (size_t)parse_integer_62(rdm); 570 | if (!rdm->skipping_printing) { 571 | size_t old_next = rdm->next; 572 | rdm->next = backref; 573 | demangle_path(rdm, in_value); 574 | rdm->next = old_next; 575 | } 576 | break; 577 | } 578 | default: 579 | ERROR_AND(return); 580 | } 581 | } 582 | 583 | static void demangle_generic_arg(struct rust_demangler *rdm) { 584 | if (eat(rdm, 'L')) { 585 | uint64_t lt = parse_integer_62(rdm); 586 | print_lifetime_from_index(rdm, lt); 587 | } else if (eat(rdm, 'K')) 588 | demangle_const(rdm, false); 589 | else 590 | demangle_type(rdm); 591 | } 592 | 593 | static const char *basic_type(char tag) { 594 | switch (tag) { 595 | case 'b': 596 | return "bool"; 597 | case 'c': 598 | return "char"; 599 | case 'e': 600 | return "str"; 601 | case 'u': 602 | return "()"; 603 | case 'a': 604 | return "i8"; 605 | case 's': 606 | return "i16"; 607 | case 'l': 608 | return "i32"; 609 | case 'x': 610 | return "i64"; 611 | case 'n': 612 | return "i128"; 613 | case 'i': 614 | return "isize"; 615 | case 'h': 616 | return "u8"; 617 | case 't': 618 | return "u16"; 619 | case 'm': 620 | return "u32"; 621 | case 'y': 622 | return "u64"; 623 | case 'o': 624 | return "u128"; 625 | case 'j': 626 | return "usize"; 627 | case 'f': 628 | return "f32"; 629 | case 'd': 630 | return "f64"; 631 | case 'z': 632 | return "!"; 633 | case 'p': 634 | return "_"; 635 | case 'v': 636 | return "..."; 637 | 638 | default: 639 | return NULL; 640 | } 641 | } 642 | 643 | static void demangle_type(struct rust_demangler *rdm) { 644 | CHECK_OR(!rdm->errored, return); 645 | 646 | char tag = next(rdm); 647 | 648 | const char *basic = basic_type(tag); 649 | if (basic) { 650 | PRINT(basic); 651 | return; 652 | } 653 | 654 | switch (tag) { 655 | case 'R': 656 | case 'Q': 657 | PRINT("&"); 658 | if (eat(rdm, 'L')) { 659 | uint64_t lt = parse_integer_62(rdm); 660 | if (lt) { 661 | print_lifetime_from_index(rdm, lt); 662 | PRINT(" "); 663 | } 664 | } 665 | if (tag != 'R') 666 | PRINT("mut "); 667 | demangle_type(rdm); 668 | break; 669 | case 'P': 670 | case 'O': 671 | PRINT("*"); 672 | if (tag != 'P') 673 | PRINT("mut "); 674 | else 675 | PRINT("const "); 676 | demangle_type(rdm); 677 | break; 678 | case 'A': 679 | case 'S': 680 | PRINT("["); 681 | demangle_type(rdm); 682 | if (tag == 'A') { 683 | PRINT("; "); 684 | demangle_const(rdm, true); 685 | } 686 | PRINT("]"); 687 | break; 688 | case 'T': { 689 | PRINT("("); 690 | size_t i; 691 | for (i = 0; !rdm->errored && !eat(rdm, 'E'); i++) { 692 | if (i > 0) 693 | PRINT(", "); 694 | demangle_type(rdm); 695 | } 696 | if (i == 1) 697 | PRINT(","); 698 | PRINT(")"); 699 | break; 700 | } 701 | case 'F': { 702 | uint64_t old_bound_lifetime_depth = rdm->bound_lifetime_depth; 703 | demangle_binder(rdm); 704 | 705 | if (eat(rdm, 'U')) 706 | PRINT("unsafe "); 707 | 708 | if (eat(rdm, 'K')) { 709 | struct rust_mangled_ident abi; 710 | 711 | if (eat(rdm, 'C')) { 712 | abi.ascii = "C"; 713 | abi.ascii_len = 1; 714 | } else { 715 | abi = parse_ident(rdm); 716 | CHECK_OR(abi.ascii && !abi.punycode, goto restore); 717 | } 718 | 719 | PRINT("extern \""); 720 | 721 | // If the ABI had any `-`, they were replaced with `_`, 722 | // so the parts between `_` have to be re-joined with `-`. 723 | for (size_t i = 0; i < abi.ascii_len; i++) { 724 | if (abi.ascii[i] == '_') { 725 | print_str(rdm, abi.ascii, i); 726 | PRINT("-"); 727 | abi.ascii += i + 1; 728 | abi.ascii_len -= i + 1; 729 | i = 0; 730 | } 731 | } 732 | print_str(rdm, abi.ascii, abi.ascii_len); 733 | 734 | PRINT("\" "); 735 | } 736 | 737 | PRINT("fn("); 738 | for (size_t i = 0; !rdm->errored && !eat(rdm, 'E'); i++) { 739 | if (i > 0) 740 | PRINT(", "); 741 | demangle_type(rdm); 742 | } 743 | PRINT(")"); 744 | 745 | if (eat(rdm, 'u')) { 746 | // Skip printing the return type if it's 'u', i.e. `()`. 747 | } else { 748 | PRINT(" -> "); 749 | demangle_type(rdm); 750 | } 751 | 752 | // Restore `bound_lifetime_depth` to outside the binder. 753 | restore: 754 | rdm->bound_lifetime_depth = old_bound_lifetime_depth; 755 | break; 756 | } 757 | case 'D': 758 | PRINT("dyn "); 759 | { 760 | uint64_t old_bound_lifetime_depth = rdm->bound_lifetime_depth; 761 | demangle_binder(rdm); 762 | 763 | for (size_t i = 0; !rdm->errored && !eat(rdm, 'E'); i++) { 764 | if (i > 0) 765 | PRINT(" + "); 766 | demangle_dyn_trait(rdm); 767 | } 768 | 769 | // Restore `bound_lifetime_depth` to outside the binder. 770 | rdm->bound_lifetime_depth = old_bound_lifetime_depth; 771 | 772 | CHECK_OR(eat(rdm, 'L'), return); 773 | uint64_t lt = parse_integer_62(rdm); 774 | if (lt) { 775 | PRINT(" + "); 776 | print_lifetime_from_index(rdm, lt); 777 | } 778 | } 779 | break; 780 | case 'B': { 781 | {size_t backref = (size_t)parse_integer_62(rdm); 782 | if (!rdm->skipping_printing) { 783 | size_t old_next = rdm->next; 784 | rdm->next = backref; 785 | demangle_type(rdm); 786 | rdm->next = old_next; 787 | }} 788 | break; 789 | } 790 | default: 791 | // Go back to the tag, so `demangle_path` also sees it. 792 | rdm->next--; 793 | demangle_path(rdm, false); 794 | } 795 | } 796 | 797 | /// A trait in a trait object may have some "existential projections" 798 | /// (i.e. associated type bindings) after it, which should be printed 799 | /// in the `<...>` of the trait, e.g. `dyn Trait`. 800 | /// To this end, this method will keep the `<...>` of an 'I' path 801 | /// open, by omitting the `>`, and return `Ok(true)` in that case. 802 | static bool demangle_path_maybe_open_generics(struct rust_demangler *rdm) { 803 | bool open = false; 804 | 805 | CHECK_OR(!rdm->errored, return open); 806 | 807 | if (eat(rdm, 'B')) { 808 | size_t backref = (size_t)parse_integer_62(rdm); 809 | if (!rdm->skipping_printing) { 810 | size_t old_next = rdm->next; 811 | rdm->next = backref; 812 | open = demangle_path_maybe_open_generics(rdm); 813 | rdm->next = old_next; 814 | } 815 | } else if (eat(rdm, 'I')) { 816 | demangle_path(rdm, false); 817 | PRINT("<"); 818 | open = true; 819 | for (size_t i = 0; !rdm->errored && !eat(rdm, 'E'); i++) { 820 | if (i > 0) 821 | PRINT(", "); 822 | demangle_generic_arg(rdm); 823 | } 824 | } else 825 | demangle_path(rdm, false); 826 | return open; 827 | } 828 | 829 | static void demangle_dyn_trait(struct rust_demangler *rdm) { 830 | CHECK_OR(!rdm->errored, return); 831 | 832 | bool open = demangle_path_maybe_open_generics(rdm); 833 | 834 | while (eat(rdm, 'p')) { 835 | if (!open) 836 | PRINT("<"); 837 | else 838 | PRINT(", "); 839 | open = true; 840 | 841 | struct rust_mangled_ident name = parse_ident(rdm); 842 | print_ident(rdm, name); 843 | PRINT(" = "); 844 | demangle_type(rdm); 845 | } 846 | 847 | if (open) 848 | PRINT(">"); 849 | } 850 | 851 | static void demangle_const(struct rust_demangler *rdm, bool in_value) { 852 | CHECK_OR(!rdm->errored, return); 853 | 854 | bool opened_brace = false; 855 | 856 | char ty_tag = next(rdm); 857 | switch (ty_tag) { 858 | case 'p': 859 | PRINT("_"); 860 | break; 861 | 862 | // Unsigned integer types. 863 | case 'h': 864 | case 't': 865 | case 'm': 866 | case 'y': 867 | case 'o': 868 | case 'j': 869 | demangle_const_uint(rdm, ty_tag); 870 | break; 871 | 872 | case 'a': 873 | case 's': 874 | case 'l': 875 | case 'x': 876 | case 'n': 877 | case 'i': 878 | if (eat(rdm, 'n')) { 879 | PRINT("-"); 880 | } 881 | demangle_const_uint(rdm, ty_tag); 882 | break; 883 | 884 | case 'b': { 885 | struct hex_nibbles hex = parse_hex_nibbles_for_const_uint(rdm); 886 | CHECK_OR(!rdm->errored && hex.nibbles_len <= 1, return); 887 | uint8_t v = hex.nibbles_len > 0 ? decode_hex_nibble(hex.nibbles[0]) : 0; 888 | CHECK_OR(v <= 1, return); 889 | PRINT(v == 1 ? "true" : "false"); 890 | break; 891 | } 892 | 893 | case 'c': { 894 | struct hex_nibbles hex = parse_hex_nibbles_for_const_uint(rdm); 895 | CHECK_OR(!rdm->errored && hex.nibbles_len <= 6, return); 896 | 897 | uint32_t c = 0; 898 | for (size_t i = 0; i < hex.nibbles_len; i++) 899 | c = (c << 4) | decode_hex_nibble(hex.nibbles[i]); 900 | 901 | PRINT("'"); 902 | print_quoted_escaped_char(rdm, '\'', c); 903 | PRINT("'"); 904 | 905 | break; 906 | } 907 | 908 | case 'e': 909 | // NOTE(eddyb) a string literal `"..."` has type `&str`, so 910 | // to get back the type `str`, `*"..."` syntax is needed 911 | // (even if that may not be valid in Rust itself). 912 | if (!in_value) { 913 | opened_brace = true; 914 | PRINT("{"); 915 | } 916 | PRINT("*"); 917 | 918 | demangle_const_str_literal(rdm); 919 | break; 920 | 921 | case 'R': 922 | case 'Q': 923 | if (ty_tag == 'R' && eat(rdm, 'e')) { 924 | // NOTE(eddyb) this prints `"..."` instead of `&*"..."`, which 925 | // is what `Re..._` would imply (see comment for `str` above). 926 | demangle_const_str_literal(rdm); 927 | break; 928 | } 929 | 930 | if (!in_value) { 931 | opened_brace = true; 932 | PRINT("{"); 933 | } 934 | 935 | PRINT("&"); 936 | if (ty_tag != 'R') { 937 | PRINT("mut "); 938 | } 939 | 940 | demangle_const(rdm, true); 941 | break; 942 | 943 | case 'A': { 944 | if (!in_value) { 945 | opened_brace = true; 946 | PRINT("{"); 947 | } 948 | 949 | PRINT("["); 950 | 951 | size_t i = 0; 952 | while (!eat(rdm, 'E')) { 953 | CHECK_OR(!rdm->errored, return); 954 | 955 | if (i > 0) 956 | PRINT(", "); 957 | 958 | demangle_const(rdm, true); 959 | 960 | i += 1; 961 | } 962 | 963 | PRINT("]"); 964 | break; 965 | } 966 | 967 | case 'T': { 968 | if (!in_value) { 969 | opened_brace = true; 970 | PRINT("{"); 971 | } 972 | 973 | PRINT("("); 974 | 975 | size_t i = 0; 976 | while (!eat(rdm, 'E')) { 977 | CHECK_OR(!rdm->errored, return); 978 | 979 | if (i > 0) 980 | PRINT(", "); 981 | 982 | demangle_const(rdm, true); 983 | 984 | i += 1; 985 | } 986 | 987 | if (i == 1) 988 | PRINT(","); 989 | 990 | PRINT(")"); 991 | break; 992 | } 993 | 994 | case 'V': 995 | if (!in_value) { 996 | opened_brace = true; 997 | PRINT("{"); 998 | } 999 | 1000 | demangle_path(rdm, true); 1001 | 1002 | switch (next(rdm)) { 1003 | case 'U': 1004 | break; 1005 | 1006 | case 'T': { 1007 | PRINT("("); 1008 | 1009 | size_t i = 0; 1010 | while (!eat(rdm, 'E')) { 1011 | CHECK_OR(!rdm->errored, return); 1012 | 1013 | if (i > 0) 1014 | PRINT(", "); 1015 | 1016 | demangle_const(rdm, true); 1017 | 1018 | i += 1; 1019 | } 1020 | 1021 | PRINT(")"); 1022 | break; 1023 | } 1024 | 1025 | case 'S': { 1026 | PRINT(" { "); 1027 | 1028 | size_t i = 0; 1029 | while (!eat(rdm, 'E')) { 1030 | CHECK_OR(!rdm->errored, return); 1031 | 1032 | if (i > 0) 1033 | PRINT(", "); 1034 | 1035 | parse_disambiguator(rdm); 1036 | 1037 | struct rust_mangled_ident name = parse_ident(rdm); 1038 | print_ident(rdm, name); 1039 | 1040 | PRINT(": "); 1041 | 1042 | demangle_const(rdm, true); 1043 | 1044 | i += 1; 1045 | } 1046 | 1047 | PRINT(" }"); 1048 | break; 1049 | } 1050 | 1051 | default: 1052 | ERROR_AND(return); 1053 | } 1054 | 1055 | break; 1056 | 1057 | case 'B': { 1058 | size_t backref = (size_t)parse_integer_62(rdm); 1059 | if (!rdm->skipping_printing) { 1060 | size_t old_next = rdm->next; 1061 | rdm->next = backref; 1062 | demangle_const(rdm, in_value); 1063 | rdm->next = old_next; 1064 | } 1065 | break; 1066 | } 1067 | 1068 | default: 1069 | ERROR_AND(return); 1070 | } 1071 | 1072 | if (opened_brace) { 1073 | PRINT("}"); 1074 | } 1075 | } 1076 | 1077 | static void demangle_const_uint(struct rust_demangler *rdm, char ty_tag) { 1078 | CHECK_OR(!rdm->errored, return); 1079 | 1080 | struct hex_nibbles hex = parse_hex_nibbles_for_const_uint(rdm); 1081 | CHECK_OR(!rdm->errored, return); 1082 | 1083 | // Print anything that doesn't fit in `uint64_t` verbatim. 1084 | if (hex.nibbles_len > 16) { 1085 | PRINT("0x"); 1086 | print_str(rdm, hex.nibbles, hex.nibbles_len); 1087 | } else { 1088 | uint64_t v = 0; 1089 | for (size_t i = 0; i < hex.nibbles_len; i++) 1090 | v = (v << 4) | decode_hex_nibble(hex.nibbles[i]); 1091 | print_uint64(rdm, v); 1092 | } 1093 | 1094 | if (rdm->verbose) 1095 | PRINT(basic_type(ty_tag)); 1096 | } 1097 | 1098 | // UTF-8 uses an unary encoding for its "length" field (`1`s followed by a `0`). 1099 | struct utf8_byte { 1100 | // Decoded "length" field of an UTF-8 byte, including the special cases: 1101 | // - `0` indicates this is a lone ASCII byte 1102 | // - `1` indicates a continuation byte (cannot start an UTF-8 sequence) 1103 | size_t seq_len; 1104 | 1105 | // Remaining (`payload_width`) bits in the UTF-8 byte, contributing to 1106 | // the Unicode scalar value being encoded in the UTF-8 sequence. 1107 | uint8_t payload; 1108 | size_t payload_width; 1109 | }; 1110 | static struct utf8_byte utf8_decode(uint8_t byte) { 1111 | struct utf8_byte utf8; 1112 | 1113 | utf8.seq_len = 0; 1114 | utf8.payload = byte; 1115 | utf8.payload_width = 8; 1116 | 1117 | // FIXME(eddyb) figure out if using "count leading ones/zeros" is an option. 1118 | while (utf8.seq_len <= 6) { 1119 | uint8_t msb = 0x80 >> utf8.seq_len; 1120 | utf8.payload &= ~msb; 1121 | utf8.payload_width--; 1122 | if ((byte & msb) == 0) 1123 | break; 1124 | utf8.seq_len++; 1125 | } 1126 | 1127 | return utf8; 1128 | } 1129 | 1130 | static void demangle_const_str_literal(struct rust_demangler *rdm) { 1131 | CHECK_OR(!rdm->errored, return); 1132 | 1133 | struct hex_nibbles hex = parse_hex_nibbles_for_const_bytes(rdm); 1134 | CHECK_OR(!rdm->errored, return); 1135 | 1136 | PRINT("\""); 1137 | for (size_t i = 0; i < hex.nibbles_len; i += 2) { 1138 | struct utf8_byte utf8 = utf8_decode( 1139 | (decode_hex_nibble(hex.nibbles[i]) << 4) | 1140 | decode_hex_nibble(hex.nibbles[i + 1]) 1141 | ); 1142 | uint32_t c = utf8.payload; 1143 | if (utf8.seq_len > 0) { 1144 | CHECK_OR(utf8.seq_len >= 2 && utf8.seq_len <= 4, return); 1145 | for (size_t extra = utf8.seq_len - 1; extra > 0; extra--) { 1146 | i += 2; 1147 | utf8 = utf8_decode( 1148 | (decode_hex_nibble(hex.nibbles[i]) << 4) | 1149 | decode_hex_nibble(hex.nibbles[i + 1]) 1150 | ); 1151 | CHECK_OR(utf8.seq_len == 1, return); 1152 | c = (c << utf8.payload_width) | utf8.payload; 1153 | } 1154 | } 1155 | print_quoted_escaped_char(rdm, '"', c); 1156 | } 1157 | PRINT("\""); 1158 | } 1159 | 1160 | static bool is_rust_hash(struct rust_mangled_ident name) { 1161 | if (name.ascii[0] != 'h') { 1162 | return false; 1163 | } 1164 | for (size_t i = 1; i < name.ascii_len; i++) { 1165 | if (!IS_DIGIT(name.ascii[i]) && 1166 | !(name.ascii[i] >= 'a' && name.ascii[i] <= 'f')) { 1167 | return false; 1168 | } 1169 | } 1170 | return true; 1171 | } 1172 | 1173 | static void print_legacy_ident( 1174 | struct rust_demangler *rdm, struct rust_mangled_ident ident 1175 | ) { 1176 | if (rdm->errored || rdm->skipping_printing) 1177 | return; 1178 | 1179 | CHECK_OR(!ident.punycode, return); 1180 | 1181 | if (ident.ascii[0] == '_' && ident.ascii[1] == '$') { 1182 | ident.ascii += 1; 1183 | ident.ascii_len -= 1; 1184 | } 1185 | 1186 | while (1) { 1187 | if (ident.ascii_len == 0) { 1188 | break; 1189 | } else if (ident.ascii[0] == '.') { 1190 | if (ident.ascii_len >= 2 && ident.ascii[1] == '.') { 1191 | PRINT("::"); 1192 | ident.ascii += 2; 1193 | ident.ascii_len -= 2; 1194 | } else { 1195 | PRINT("."); 1196 | ident.ascii += 1; 1197 | ident.ascii_len -= 1; 1198 | } 1199 | } else if (ident.ascii[0] == '$') { 1200 | const char *end_ptr = 1201 | (const char *)memchr(&ident.ascii[1], '$', ident.ascii_len - 1); 1202 | if (!end_ptr) 1203 | break; 1204 | const char *escape = &ident.ascii[1]; 1205 | size_t escape_len = end_ptr - escape; 1206 | 1207 | if (strncmp(escape, "SP", 2) == 0) { 1208 | PRINT("@"); 1209 | } else if (strncmp(escape, "BP", 2) == 0) { 1210 | PRINT("*"); 1211 | } else if (strncmp(escape, "RF", 2) == 0) { 1212 | PRINT("&"); 1213 | } else if (strncmp(escape, "LT", 2) == 0) { 1214 | PRINT("<"); 1215 | } else if (strncmp(escape, "GT", 2) == 0) { 1216 | PRINT(">"); 1217 | } else if (strncmp(escape, "LP", 2) == 0) { 1218 | PRINT("("); 1219 | } else if (strncmp(escape, "RP", 2) == 0) { 1220 | PRINT(")"); 1221 | } else if (strncmp(escape, "C", 1) == 0) { 1222 | PRINT(","); 1223 | } else { 1224 | if (escape[0] != 'u') { 1225 | break; 1226 | } 1227 | 1228 | const char *digits = &escape[1]; 1229 | size_t digits_len = escape_len - 1; 1230 | 1231 | bool invalid = false; 1232 | for (size_t i = 1; i < digits_len; i++) { 1233 | if (!IS_DIGIT(digits[i]) && 1234 | !(digits[i] >= 'a' && digits[i] <= 'f')) { 1235 | invalid = true; 1236 | break; 1237 | } 1238 | } 1239 | if (invalid) 1240 | break; 1241 | 1242 | struct hex_nibbles hex; 1243 | 1244 | hex.nibbles = digits; 1245 | hex.nibbles_len = digits_len; 1246 | 1247 | uint32_t c = 0; 1248 | for (size_t i = 0; i < hex.nibbles_len; i++) 1249 | c = (c << 4) | decode_hex_nibble(hex.nibbles[i]); 1250 | 1251 | if (!(c < 0xd800 || (c > 0xdfff && c < 0x10ffff))) { 1252 | break; // Not a valid unicode scalar 1253 | } 1254 | 1255 | if (c >= 0x20 && c <= 0x7e) { 1256 | // Printable ASCII 1257 | char v = (char)c; 1258 | print_str(rdm, &v, 1); 1259 | } else { 1260 | // FIXME show printable unicode characters without hex 1261 | // encoding 1262 | PRINT("\\u{"); 1263 | char s[9] = {0}; 1264 | sprintf(s, "%" PRIx32, c); 1265 | PRINT(s); 1266 | PRINT("}"); 1267 | } 1268 | } 1269 | 1270 | ident.ascii += escape_len + 2; 1271 | ident.ascii_len -= escape_len + 2; 1272 | } else { 1273 | bool found = false; 1274 | for (size_t i = 0; i < ident.ascii_len; i++) { 1275 | if (ident.ascii[i] == '$' || ident.ascii[i] == '.') { 1276 | print_str(rdm, ident.ascii, i); 1277 | ident.ascii += i; 1278 | ident.ascii_len -= i; 1279 | found = true; 1280 | break; 1281 | } 1282 | } 1283 | if (!found) { 1284 | break; 1285 | } 1286 | } 1287 | } 1288 | 1289 | print_str(rdm, ident.ascii, ident.ascii_len); 1290 | } 1291 | 1292 | static void demangle_legacy_path(struct rust_demangler *rdm) { 1293 | bool first = true; 1294 | 1295 | while (1) { 1296 | if (eat(rdm, 'E')) { 1297 | // FIXME Maybe check if at end of symbol? 1298 | return; 1299 | } 1300 | 1301 | struct rust_mangled_ident name = parse_ident(rdm); 1302 | 1303 | if (!rdm->verbose && peek(rdm) == 'E' && is_rust_hash(name)) { 1304 | // Skip printing the hash if verbose mode is disabled. 1305 | eat(rdm, 'E'); 1306 | break; 1307 | } 1308 | 1309 | if (!first) { 1310 | PRINT("::"); 1311 | } 1312 | first = false; 1313 | 1314 | print_legacy_ident(rdm, name); 1315 | 1316 | CHECK_OR(!rdm->errored, return); 1317 | } 1318 | } 1319 | 1320 | bool rust_demangle_with_callback( 1321 | const char *whole_mangled_symbol, int flags, 1322 | void (*callback)(const char *data, size_t len, void *opaque), void *opaque 1323 | ) { 1324 | struct rust_demangler rdm; 1325 | 1326 | rdm.sym = whole_mangled_symbol; 1327 | rdm.sym_len = 0; 1328 | 1329 | rdm.callback_opaque = opaque; 1330 | rdm.callback = callback; 1331 | 1332 | rdm.next = 0; 1333 | rdm.errored = false; 1334 | rdm.skipping_printing = false; 1335 | rdm.verbose = (flags & RUST_DEMANGLE_FLAG_VERBOSE) != 0; 1336 | rdm.version = -2; // Invalid version 1337 | rdm.bound_lifetime_depth = 0; 1338 | 1339 | // Rust symbols always start with R, _R or __R for the v0 scheme or ZN, _ZN 1340 | // or __ZN for the legacy scheme. 1341 | if (strncmp(rdm.sym, "_R", 2) == 0) { 1342 | rdm.sym += 2; 1343 | rdm.version = 0; // v0 1344 | } else if (rdm.sym[0] == 'R') { 1345 | // On Windows, dbghelp strips leading underscores, so we accept "R..." 1346 | // form too. 1347 | rdm.sym += 1; 1348 | rdm.version = 0; // v0 1349 | } else if (strncmp(rdm.sym, "__R", 3) == 0) { 1350 | // On OSX, symbols are prefixed with an extra _ 1351 | rdm.sym += 3; 1352 | rdm.version = 0; // v0 1353 | } else if (strncmp(rdm.sym, "_ZN", 3) == 0) { 1354 | rdm.sym += 3; 1355 | rdm.version = -1; // legacy 1356 | } else if (strncmp(rdm.sym, "ZN", 2) == 0) { 1357 | // On Windows, dbghelp strips leading underscores, so we accept "R..." 1358 | // form too. 1359 | rdm.sym += 2; 1360 | rdm.version = -1; // legacy 1361 | } else if (strncmp(rdm.sym, "__ZN", 4) == 0) { 1362 | // On OSX, symbols are prefixed with an extra _ 1363 | rdm.sym += 4; 1364 | rdm.version = -1; // legacy 1365 | } else { 1366 | return false; 1367 | } 1368 | 1369 | if (rdm.version != -1) { 1370 | // Paths always start with uppercase characters. 1371 | if (!IS_UPPER(rdm.sym[0])) 1372 | return false; 1373 | } 1374 | 1375 | // Rust symbols only use ASCII characters. 1376 | for (const char *p = rdm.sym; *p; p++) { 1377 | if ((*p & 0x80) != 0) 1378 | return false; 1379 | 1380 | if (*p == '.' && strncmp(p, ".llvm.", 6) == 0) { 1381 | // Ignore .llvm. suffixes 1382 | break; 1383 | } 1384 | 1385 | rdm.sym_len++; 1386 | } 1387 | 1388 | if (rdm.version == -1) { 1389 | demangle_legacy_path(&rdm); 1390 | } else { 1391 | demangle_path(&rdm, true); 1392 | 1393 | // Skip instantiating crate. 1394 | if (!rdm.errored && rdm.next < rdm.sym_len && peek(&rdm) >= 'A' && 1395 | peek(&rdm) <= 'Z') { 1396 | rdm.skipping_printing = true; 1397 | demangle_path(&rdm, false); 1398 | } 1399 | } 1400 | 1401 | if (!rdm.errored && (rdm.sym_len - rdm.next > 0)) { 1402 | for (const char *p = rdm.sym + rdm.next; *p; p++) { 1403 | // FIXME match is_symbol_like from rustc-demangle 1404 | if (!((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z') || 1405 | (*p >= '0' && *p <= '9') || *p == '.')) { 1406 | // Suffix is not a symbol like string 1407 | return false; 1408 | } 1409 | } 1410 | 1411 | // Print LLVM produced suffix 1412 | print_str(&rdm, rdm.sym + rdm.next, rdm.sym_len - rdm.next); 1413 | } 1414 | 1415 | return !rdm.errored; 1416 | } 1417 | 1418 | // Growable string buffers. 1419 | struct str_buf { 1420 | char *ptr; 1421 | size_t len; 1422 | size_t cap; 1423 | bool errored; 1424 | }; 1425 | 1426 | static void str_buf_reserve(struct str_buf *buf, size_t extra) { 1427 | // Allocation failed before. 1428 | if (buf->errored) 1429 | return; 1430 | 1431 | size_t available = buf->cap - buf->len; 1432 | 1433 | if (extra <= available) 1434 | return; 1435 | 1436 | size_t min_new_cap = buf->cap + (extra - available); 1437 | 1438 | // Check for overflows. 1439 | if (min_new_cap < buf->cap) { 1440 | buf->errored = true; 1441 | return; 1442 | } 1443 | 1444 | size_t new_cap = buf->cap; 1445 | 1446 | if (new_cap == 0) 1447 | new_cap = 4; 1448 | 1449 | // Double capacity until sufficiently large. 1450 | while (new_cap < min_new_cap) { 1451 | new_cap *= 2; 1452 | 1453 | // Check for overflows. 1454 | if (new_cap < buf->cap) { 1455 | buf->errored = true; 1456 | return; 1457 | } 1458 | } 1459 | 1460 | char *new_ptr = (char *)realloc(buf->ptr, new_cap); 1461 | if (new_ptr == NULL) { 1462 | free(buf->ptr); 1463 | buf->ptr = NULL; 1464 | buf->len = 0; 1465 | buf->cap = 0; 1466 | buf->errored = true; 1467 | } else { 1468 | buf->ptr = new_ptr; 1469 | buf->cap = new_cap; 1470 | } 1471 | } 1472 | 1473 | static void str_buf_append(struct str_buf *buf, const char *data, size_t len) { 1474 | str_buf_reserve(buf, len); 1475 | if (buf->errored) 1476 | return; 1477 | 1478 | memcpy(buf->ptr + buf->len, data, len); 1479 | buf->len += len; 1480 | } 1481 | 1482 | static void 1483 | str_buf_demangle_callback(const char *data, size_t len, void *opaque) { 1484 | str_buf_append((str_buf*)opaque, data, len); 1485 | } 1486 | 1487 | char *rust_demangle(const char *mangled, int flags) { 1488 | struct str_buf out; 1489 | 1490 | out.ptr = NULL; 1491 | out.len = 0; 1492 | out.cap = 0; 1493 | out.errored = false; 1494 | 1495 | bool success = rust_demangle_with_callback( 1496 | mangled, flags, str_buf_demangle_callback, &out 1497 | ); 1498 | 1499 | if (!success) { 1500 | free(out.ptr); 1501 | return NULL; 1502 | } 1503 | 1504 | str_buf_append(&out, "\0", 1); 1505 | return out.ptr; 1506 | } 1507 | -------------------------------------------------------------------------------- /3rd/rust-demangle.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #define RUST_DEMANGLE_FLAG_VERBOSE 1 5 | 6 | #ifdef __cplusplus 7 | extern "C" { 8 | #endif 9 | 10 | bool rust_demangle_with_callback( 11 | const char *mangled, int flags, 12 | void (*callback)(const char *data, size_t len, void *opaque), void *opaque 13 | ); 14 | char *rust_demangle(const char *mangled, int flags); 15 | 16 | #ifdef __cplusplus 17 | } 18 | #endif 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright 2025 Milos Tosic 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [![Build status](https://ci.appveyor.com/api/projects/status/dly9lqpteiy0v5k1?svg=true)](https://ci.appveyor.com/project/milostosic/rdebug-shw5x) 4 | [![License](https://img.shields.io/badge/license-BSD--2%20clause-blue.svg)](https://github.com/RudjiGames/rdebug/blob/master/LICENSE) 5 | 6 | **rdebug** is a debug symbol resolution library. 7 | 8 | Source Code 9 | ====== 10 | 11 | You can get the latest source code by cloning it from github: 12 | 13 | git clone https://github.com/RudjiGames/rdebug.git 14 | 15 | Dependencies 16 | ====== 17 | 18 | Dependencies can be obtained by cloning the following repositories: 19 | 20 | git clone https://github.com/RudjiGames/build.git 21 | git clone https://github.com/RudjiGames/rbase.git 22 | 23 | DIA (Debug Interface Access) SDK - **Windows only** 24 | 25 | git clone https://github.com/milostosic/DIA.git 26 | 27 | License (BSD 2-clause) 28 | ====== 29 | 30 | 31 | 32 | 33 | 34 | Copyright 2025 Milos Tosic. All rights reserved. 35 | 36 | https://github.com/RudjiGames/rdebug 37 | 38 | Redistribution and use in source and binary forms, with or without 39 | modification, are permitted provided that the following conditions are met: 40 | 41 | 1. Redistributions of source code must retain the above copyright notice, 42 | this list of conditions and the following disclaimer. 43 | 44 | 2. Redistributions in binary form must reproduce the above copyright 45 | notice, this list of conditions and the following disclaimer in the 46 | documentation and/or other materials provided with the distribution. 47 | 48 | THIS SOFTWARE IS PROVIDED BY COPYRIGHT HOLDER ``AS IS'' AND ANY EXPRESS OR 49 | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 50 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO 51 | EVENT SHALL COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 52 | INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 53 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 54 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 55 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 56 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 57 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 58 | -------------------------------------------------------------------------------- /genie/genie.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- Copyright 2025 Milos Tosic. All rights reserved. 3 | -- License: http://www.opensource.org/licenses/BSD-2-Clause 4 | -- 5 | 6 | local cp = (function(x) return debug.getinfo(2, "S").source:sub(2):match("(.*[/\\])") end)() 7 | while os.isdir(cp) do cp = path.getabsolute(cp .. "/..") local tp = cp .. "/build/build.lua" 8 | if os.isfile(tp) then dofile (tp) break end end if RTM_ROOT_DIR == nil then 9 | print("EROR: dependency missing - build. Cannot continue.") os.exit() end 10 | 11 | solution "rdebug" 12 | setPlatforms() 13 | 14 | loadProject("rdebug") 15 | 16 | -------------------------------------------------------------------------------- /genie/rdebug.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- Copyright 2025 Milos Tosic. All rights reserved. 3 | -- License: http://www.opensource.org/licenses/BSD-2-Clause 4 | -- 5 | 6 | function projectDependencies_rdebug() 7 | return { "rbase" } 8 | end 9 | 10 | DIAErrorPrinted = false 11 | 12 | function projectExtraConfigExecutable_rdebug() 13 | if getTargetOS() == "windows" then 14 | local DIApath = getProjectPath("DIA", ProjectPath.Root); 15 | if DIApath == nil then 16 | if DIAErrorPrinted == false then 17 | print("DIA dependency is missing, please clone from https://github.com/RudjiGames/DIA!") 18 | end 19 | DIAErrorPrinted = true 20 | return 21 | end 22 | 23 | configuration {"windows", "x32", "not gmake" } 24 | includedirs { getProjectPath("DIA", ProjectPath.Root) } 25 | libdirs { getProjectPath("DIA", ProjectPath.Dir) .. "/lib/x32/" } 26 | links {"diaguids"} 27 | configuration {"windows", "x64", "not gmake" } 28 | includedirs { getProjectPath("DIA", ProjectPath.Root) } 29 | libdirs { getProjectPath("DIA", ProjectPath.Dir) .. "/lib/x64/" } 30 | links {"diaguids"} 31 | configuration {} 32 | end 33 | end 34 | 35 | function projectExtraConfig_rdebug() 36 | configuration { "gmake" } 37 | if "mingw-gcc" == _OPTIONS["gcc"] then -- on windows, we patch heap functions, no need to wrap malloc family of funcs 38 | buildoptions { "-Wno-unknown-pragmas" } 39 | end 40 | end 41 | 42 | function projectAdd_rdebug() 43 | addProject_lib("rdebug", Lib.Tool) 44 | end 45 | 46 | -------------------------------------------------------------------------------- /inc/rdebug.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------// 2 | /// Copyright 2025 Milos Tosic. All Rights Reserved. /// 3 | /// License: http://www.opensource.org/licenses/BSD-2-Clause /// 4 | //--------------------------------------------------------------------------// 5 | 6 | #ifndef RTM_DEBUG_H 7 | #define RTM_DEBUG_H 8 | 9 | #include 10 | 11 | typedef struct _rtmLibInterface rtmLibInterface; 12 | 13 | namespace rdebug { 14 | 15 | /// Toolchain description structure 16 | struct Toolchain 17 | { 18 | enum Type 19 | { 20 | Unknown, // 21 | MSVC, // pdb 22 | GCC, // nm, addr2line, c++filt 23 | PS4, // 24 | PS5, // 25 | PS3SNC // ps3bin 26 | }; 27 | 28 | Toolchain::Type m_type; 29 | char m_toolchainPath[2048]; // or symbol store path list for MSVC 30 | char m_toolchainPrefix[64]; 31 | 32 | Toolchain(); 33 | }; 34 | 35 | /// Module information structure 36 | struct ModuleInfo 37 | { 38 | uint64_t m_baseAddress; 39 | uint64_t m_size; 40 | uint64_t m_loadTime; 41 | uint64_t m_unloadTime; 42 | char m_modulePath[1024]; 43 | Toolchain m_toolchain; 44 | 45 | inline bool checkAddress(uint64_t _address) const 46 | { 47 | return (_address - m_baseAddress <= m_size); 48 | } 49 | 50 | inline bool checkAddressAndTime(uint64_t _address, uint64_t _operationTime) const 51 | { 52 | return ((_operationTime >= m_loadTime) && 53 | (_operationTime <= m_unloadTime) && 54 | (_address - m_baseAddress <= m_size)); 55 | } 56 | }; 57 | 58 | /// Single entry (frame) in stack trace 59 | struct StackFrame 60 | { 61 | char m_moduleName[256]; 62 | char m_file[1024]; 63 | char m_func[16384]; // very big in order to support dumb code (templates, lambdas and such) 64 | uint32_t m_line; 65 | }; 66 | 67 | /// Module loading callback, called per module 68 | typedef void (*module_load_cb)(const char* _name, void* _customData); 69 | 70 | /// Initialize rdebug library 71 | /// 72 | /// @param _libInterface 73 | /// 74 | bool init(rtmLibInterface* _libInterface = 0); 75 | 76 | /// Shut down rdebug library and release internal resources 77 | /// 78 | void shutDown(); 79 | 80 | /// Sets symbol server path 81 | /// 82 | /// @param _symStore 83 | /// 84 | void symbolSetServerSource(const wchar_t* _symStore); 85 | 86 | /// Creates debug symbol resolver based on 87 | /// 88 | /// @param _moduleInfos 89 | /// @param _numInfos 90 | /// @param _tc 91 | /// @param _executable 92 | /// 93 | uintptr_t symbolResolverCreate(ModuleInfo* _moduleInfos, uint32_t _numInfos, const char* _executable, module_load_cb _callback = 0, void* _data = 0); 94 | 95 | /// Creates debug symbol resolver based on 96 | /// 97 | uintptr_t symbolResolverCreateForCurrentProcess(); 98 | 99 | /// Creates debug symbol resolver based on 100 | /// 101 | /// @param _resolver 102 | /// 103 | void symbolResolverDelete(uintptr_t _resolver); 104 | 105 | /// Creates debug symbol resolver based on 106 | /// 107 | /// @param _resolver 108 | /// @param _address 109 | /// @param _frame 110 | /// 111 | void symbolResolverGetFrame(uintptr_t _resolver, uint64_t _address, StackFrame* _frame); 112 | 113 | /// Creates debug symbol resolver based on 114 | /// 115 | /// @param _resolver 116 | /// @param _address 117 | /// @param _skipCount 118 | /// 119 | uint64_t symbolResolverGetAddressID(uintptr_t _resolver, uint64_t _address); 120 | 121 | /// Returns true if binary at the given path is 64bit 122 | /// 123 | /// @param _path 124 | /// 125 | bool processIs64bitBinary(const char* _path); 126 | 127 | /// Creates and runs a new process with injected DLL 128 | /// 129 | /// @param _executablePath 130 | /// @param _DLLPath 131 | /// @param _cmdLine 132 | /// @param _workingDir 133 | /// 134 | bool processInjectDLL(const char* _executablePath, const char* _DLLPath, const char* _cmdLine, const char* _workingDir, uint32_t* _pid = 0); 135 | 136 | /// Create and run a new process given the command line 137 | /// 138 | /// @param _cmdLine 139 | /// 140 | bool processRun(const char* _cmdLine, bool _hideWindow = false, uint32_t* _exitCode = 0); 141 | 142 | /// Run a new process and return the console output 143 | /// 144 | /// @param _cmdLine 145 | /// 146 | char* processGetOutputOf(const char* _cmdLine, bool _redirectIO = false); 147 | 148 | /// Release memory previously allocated by processGetOutputOf 149 | /// 150 | /// @param _cmdLine 151 | /// 152 | void processReleaseOutput(const char* _output); 153 | 154 | /// 155 | void addressToString(uint64_t _address, char* _buffer); 156 | 157 | } // namespace rdebug 158 | 159 | #endif // RTM_DEBUG_H 160 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2025 Milos Toisc. All rights reserved. 3 | # License: https://github.com/bkaradzic/bgfx#license-bsd-2-clause 4 | # 5 | 6 | GENIE=../build/tools/bin/$(OS)/genie 7 | 8 | all: 9 | $(GENIE) vs2015 10 | $(GENIE) vs2017 11 | $(GENIE) --gcc=android-arm gmake 12 | $(GENIE) --gcc=android-mips gmake 13 | $(GENIE) --gcc=android-x86 gmake 14 | $(GENIE) --gcc=mingw-gcc gmake 15 | $(GENIE) --gcc=linux-gcc gmake 16 | $(GENIE) --gcc=osx gmake 17 | $(GENIE) --gcc=ios-arm gmake 18 | $(GENIE) --gcc=ios-simulator gmake 19 | $(GENIE) --gcc=ios-simulator64 gmake 20 | $(GENIE) xcode4 21 | 22 | gmake-linux: 23 | $(GENIE) --file=genie/genie.lua --gcc=linux-gcc gmake 24 | linux-debug32: gmake-linux 25 | make -R -C ../.build/linux/gcc/rdebug/projects config=debug32 26 | linux-release32: gmake-linux 27 | make -R -C ../.build/linux/gcc/rdebug/projects config=release32 28 | linux-debug64: gmake-linux 29 | make -R -C ../.build/linux/gcc/rdebug/projects config=debug64 30 | linux-release64: gmake-linux 31 | make -R -C ../.build/linux/gcc/rdebug/projects config=release64 32 | linux: linux-debug32 linux-release32 linux-debug64 linux-release64 33 | 34 | gmake-mingw-gcc: 35 | $(GENIE) --file=genie/genie.lua --gcc=mingw-gcc gmake 36 | mingw-gcc-debug32: gmake-mingw-gcc 37 | make -R -C ../.build/windows/mingw-gcc/rdebug/projects config=debug32 38 | mingw-gcc-release32: gmake-mingw-gcc 39 | make -R -C ../.build/windows/mingw-gcc/rdebug/projects config=release32 40 | mingw-gcc-debug64: gmake-mingw-gcc 41 | make -R -C ../.build/windows/mingw-gcc/rdebug/projects config=debug64 42 | mingw-gcc-release64: gmake-mingw-gcc 43 | make -R -C ../.build/windows/mingw-gcc/rdebug/projects config=release64 44 | mingw-gcc: mingw-gcc-debug32 mingw-gcc-release32 mingw-gcc-debug64 mingw-gcc-release64 45 | 46 | gmake-mingw-clang: 47 | $(GENIE) --file=genie/genie.lua --clang=mingw-clang gmake 48 | mingw-clang-debug32: gmake-mingw-clang 49 | make -R -C ../.build/windows/mingw-clang/rdebug/projects config=debug32 50 | mingw-clang-release32: gmake-mingw-clang 51 | make -R -C ../.build/windows/mingw-clang/rdebug/projects config=release32 52 | mingw-clang-debug64: gmake-mingw-clang 53 | make -R -C ../.build/windows/mingw-clang/rdebug/projects config=debug64 54 | mingw-clang-release64: gmake-mingw-clang 55 | make -R -C ../.build/windows/mingw-clang/rdebug/projects config=release64 56 | mingw-clang: mingw-clang-debug32 mingw-clang-release32 mingw-clang-debug64 mingw-clang-release64 57 | 58 | vs2015: 59 | $(GENIE) --file=genie/genie.lua vs2015 60 | 61 | vs2017: 62 | $(GENIE) --file=genie/genie.lua vs2017 63 | 64 | ../.build/osx/gcc/rdebug/projects: 65 | $(GENIE) --file=genie/genie.lua --gcc=osx gmake 66 | osx-debug64: ../.build/osx/gcc/rdebug/projects 67 | make -C ../.build/osx/gcc/rdebug/projects config=debug64 68 | osx-release64: ../.build/osx/gcc/rdebug/projects 69 | make -C ../.build/osx/gcc/rdebug/projects config=release64 70 | osx: osx-debug64 osx-release64 71 | 72 | clean: 73 | @echo Cleaning... 74 | -@rm -rf ../.build 75 | 76 | ### 77 | 78 | SILENT ?= @ 79 | 80 | UNAME := $(shell uname) 81 | ifeq ($(UNAME),$(filter $(UNAME),Linux GNU Darwin)) 82 | ifeq ($(UNAME),$(filter $(UNAME),Darwin)) 83 | OS=darwin 84 | else 85 | OS=linux 86 | endif 87 | else 88 | OS=windows 89 | endif 90 | 91 | -------------------------------------------------------------------------------- /src/pdb_file.cpp: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------// 2 | /// Copyright 2025 Milos Tosic. All Rights Reserved. /// 3 | /// License: http://www.opensource.org/licenses/BSD-2-Clause /// 4 | //--------------------------------------------------------------------------// 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #if RTM_PLATFORM_WINDOWS 11 | 12 | #define WIN32_LEAN_AND_MEAN 13 | #include 14 | 15 | #if RTM_COMPILER_MSVC 16 | #pragma warning (disable: 4091) // 'typedef ': ignored on left of '' when no variable is declared 17 | #include 18 | #include 19 | #include 20 | #pragma warning (default: 4091) 21 | #else 22 | typedef uint16_t __wchar_t; 23 | #include 24 | const GUID CLSID_DiaSource = { 0X2735412E, 0X7F64, 0X5B0F, 0X8F, 0X00, 0X5D, 0X77, 0XAF, 0XBE, 0X26, 0X1E }; 25 | const GUID IID_IDiaDataSource = { 0X79F1BB5F, 0XB66E, 0X48E5, 0XB6, 0XA9, 0X15, 0X45, 0XC3, 0X23, 0XCA, 0X3D }; 26 | const GUID IID_IDiaLoadCallback2 = { 0x4688A074, 0x5A4D, 0x4486, 0xAE, 0xA8, 0x7B, 0x90, 0x71, 0x1D, 0x9F, 0x7C }; 27 | const GUID IID_IDiaLoadCallback = { 0xC32ADB82, 0x73F4, 0x421B, 0x95, 0xD5, 0xA4, 0x70, 0x6E, 0xDF, 0x5D, 0xBE }; 28 | #endif // RTM_COMPILER_MSVC 29 | 30 | 31 | #ifndef UNDNAME_COMPLETE 32 | #define UNDNAME_COMPLETE (0x0000) // Enable full undecoration 33 | #define UNDNAME_NO_LEADING_UNDERSCORES (0x0001) // Remove leading underscores from MS extended keywords 34 | #define UNDNAME_NO_MS_KEYWORDS (0x0002) // Disable expansion of MS extended keywords 35 | #define UNDNAME_NO_FUNCTION_RETURNS (0x0004) // Disable expansion of return type for primary declaration 36 | #define UNDNAME_NO_ALLOCATION_MODEL (0x0008) // Disable expansion of the declaration model 37 | #define UNDNAME_NO_ALLOCATION_LANGUAGE (0x0010) // Disable expansion of the declaration language specifier 38 | #define UNDNAME_NO_MS_THISTYPE (0x0020) // NYI Disable expansion of MS keywords on the 'this' type for primary declaration 39 | #define UNDNAME_NO_CV_THISTYPE (0x0040) // NYI Disable expansion of CV modifiers on the 'this' type for primary declaration 40 | #define UNDNAME_NO_THISTYPE (0x0060) // Disable all modifiers on the 'this' type 41 | #define UNDNAME_NO_ACCESS_SPECIFIERS (0x0080) // Disable expansion of access specifiers for members 42 | #define UNDNAME_NO_THROW_SIGNATURES (0x0100) // Disable expansion of 'throw-signatures' for functions and pointers to functions 43 | #define UNDNAME_NO_MEMBER_TYPE (0x0200) // Disable expansion of 'static' or 'virtual'ness of members 44 | #define UNDNAME_NO_RETURN_UDT_MODEL (0x0400) // Disable expansion of MS model for UDT returns 45 | #define UNDNAME_32_BIT_DECODE (0x0800) // Undecorate 32-bit decorated names 46 | #define UNDNAME_NAME_ONLY (0x1000) // Crack only the name for primary declaration; 47 | 48 | #define UNDNAME_NO_ARGUMENTS (0x2000) // Don't undecorate arguments to function 49 | #define UNDNAME_NO_SPECIAL_SYMS (0x4000) // Don't undecorate special names (v-table, vcall, vector xxx, metatype, etc) 50 | #endif 51 | 52 | #define UND_CODE ( \ 53 | UNDNAME_NO_THROW_SIGNATURES | \ 54 | UNDNAME_NO_SPECIAL_SYMS | \ 55 | UNDNAME_NO_MEMBER_TYPE | \ 56 | UNDNAME_COMPLETE | \ 57 | UNDNAME_NO_LEADING_UNDERSCORES | \ 58 | UNDNAME_NO_THISTYPE | \ 59 | UNDNAME_NO_ACCESS_SPECIFIERS | \ 60 | UNDNAME_NO_ALLOCATION_LANGUAGE | \ 61 | UNDNAME_NO_ALLOCATION_MODEL | \ 62 | UNDNAME_32_BIT_DECODE | \ 63 | 0) 64 | 65 | namespace rdebug { 66 | 67 | class DiaLoadCallBack : public IDiaLoadCallback2 68 | { 69 | private: 70 | uint32_t m_refCount; 71 | wchar_t* m_buffer; 72 | 73 | public: 74 | DiaLoadCallBack(wchar_t* inBuffer) : m_refCount(0), m_buffer(inBuffer) {} 75 | virtual ~DiaLoadCallBack() {} 76 | 77 | // IUnknown 78 | ULONG STDMETHODCALLTYPE AddRef() 79 | { 80 | m_refCount++; 81 | return m_refCount; 82 | } 83 | ULONG STDMETHODCALLTYPE Release() 84 | { 85 | if (--m_refCount == 0) 86 | delete this; 87 | return m_refCount; 88 | } 89 | HRESULT STDMETHODCALLTYPE QueryInterface( REFIID rid, void **ppUnk ) 90 | { 91 | if (ppUnk == 0) 92 | return E_INVALIDARG; 93 | 94 | if (rid == IID_IDiaLoadCallback2) *ppUnk = (IDiaLoadCallback2*)this; 95 | else if (rid == IID_IDiaLoadCallback) *ppUnk = (IDiaLoadCallback*)this; 96 | else if (rid == IID_IUnknown) *ppUnk = (IUnknown*)this; 97 | else *ppUnk = 0; 98 | 99 | if (*ppUnk != 0) 100 | { 101 | AddRef(); 102 | return S_OK; 103 | } 104 | 105 | return E_NOINTERFACE; 106 | } 107 | 108 | // Rest 109 | HRESULT STDMETHODCALLTYPE NotifyOpenPDB(LPCOLESTR _pdbPath, HRESULT _resultCode) 110 | { 111 | if (_resultCode == S_OK) 112 | wcscpy(m_buffer, _pdbPath); 113 | #if RTM_DEBUG 114 | else 115 | { 116 | OutputDebugStringW(L"Try open \""); 117 | OutputDebugStringW(_pdbPath); 118 | OutputDebugStringW(L"\" failed.\n"); 119 | } 120 | #endif 121 | return S_OK; 122 | } 123 | HRESULT STDMETHODCALLTYPE NotifyOpenDBG(LPCOLESTR, HRESULT) { return S_OK; } 124 | HRESULT STDMETHODCALLTYPE NotifyDebugDir(BOOL, DWORD, BYTE[]) { return S_OK; } 125 | HRESULT STDMETHODCALLTYPE RestrictRegistryAccess() { return S_OK; } 126 | HRESULT STDMETHODCALLTYPE RestrictSymbolServerAccess() { return S_OK; } 127 | HRESULT STDMETHODCALLTYPE RestrictOriginalPathAccess() { return S_OK; } 128 | HRESULT STDMETHODCALLTYPE RestrictReferencePathAccess() { return S_OK; } 129 | HRESULT STDMETHODCALLTYPE RestrictDBGAccess() { return S_OK; } 130 | HRESULT STDMETHODCALLTYPE RestrictSystemRootAccess() { return S_OK; } 131 | }; 132 | 133 | HRESULT createDiaDataSource(void** _ptr) 134 | { 135 | HRESULT hr = ::CoCreateInstance(CLSID_DiaSource, 0, CLSCTX_INPROC_SERVER, IID_IDiaDataSource, _ptr); 136 | 137 | #if RTM_COMPILER_MSVC 138 | if(FAILED(hr)) hr = NoRegCoCreate(L"msdia140.dll", __uuidof(DiaSource), __uuidof(IDiaDataSource), _ptr); 139 | #endif // RTM_COMPILER_MSVC 140 | return hr; 141 | } 142 | 143 | extern wchar_t g_symStore[ResolveInfo::SYM_SERVER_BUFFER_SIZE]; 144 | 145 | bool findSymbol(const char* _path, wchar_t _outSymbolPath[4096], const char* _symbolStore) 146 | { 147 | IDiaDataSource* pIDiaDataSource = nullptr; 148 | 149 | HRESULT hr = createDiaDataSource((void**)&pIDiaDataSource); 150 | 151 | if(FAILED(hr)) 152 | return false; 153 | 154 | // The local file path must use '\\', and http(s) url must use '/' 155 | // Do not change the the slashes only if you detect each part in the _symbolStore. 156 | rtm::MultiToWide symbolStore(_symbolStore, false); 157 | 158 | wchar_t symStoreBuffer[32 * 1024]; 159 | wcscpy(symStoreBuffer, g_symStore); 160 | 161 | // The file path is not needed in the search path, loadDataForExe will find from src path automatically. 162 | // The semicolon is necessary between each path (or srv*). 163 | wchar_t moduleName[8 * 1024]; 164 | 165 | if (!_path || (rtm::strLen(_path) == 0)) 166 | { 167 | GetModuleFileNameW(nullptr, moduleName, sizeof(moduleName)); 168 | } 169 | else 170 | { 171 | rtm::MultiToWide widePath(_path); 172 | wcscpy(moduleName, widePath); 173 | } 174 | 175 | if (symbolStore.size() > 1) // not ("" or null) 176 | { 177 | wcscat(symStoreBuffer, symbolStore); 178 | } 179 | 180 | { 181 | size_t len = wcslen(symStoreBuffer); 182 | GetEnvironmentVariableW(L"_NT_SYMBOL_PATH", (LPWSTR)&symStoreBuffer[len], sizeof(symStoreBuffer)); 183 | } 184 | 185 | wchar_t outSymbolPath[32 * 1024]; 186 | DiaLoadCallBack callback(outSymbolPath); 187 | callback.AddRef(); 188 | 189 | hr = pIDiaDataSource->loadDataForExe(moduleName, (LPOLESTR)symStoreBuffer, &callback); 190 | 191 | if (FAILED(hr)) 192 | { 193 | // Hacky desperate attempt to find PDB file where EXE resides 194 | size_t len = wcslen(moduleName) - 1; 195 | while ((len > 0) && (moduleName[len] != '.')) 196 | --len; 197 | 198 | if (len > 0) 199 | { 200 | wcscpy(moduleName, L".pdb"); 201 | if (INVALID_FILE_ATTRIBUTES != GetFileAttributesW(moduleName)) 202 | { 203 | wcscpy(_outSymbolPath, moduleName); 204 | pIDiaDataSource->Release(); 205 | return true; 206 | } 207 | } 208 | pIDiaDataSource->Release(); 209 | return false; 210 | } 211 | 212 | wcscpy(_outSymbolPath, outSymbolPath); 213 | pIDiaDataSource->Release(); 214 | return true; 215 | } 216 | 217 | } // namespace rdebug 218 | 219 | const char* s_PDB_File_Extension = "pdb"; 220 | 221 | PDBFile::PDBFile() : 222 | m_pIDiaDataSource(nullptr), 223 | m_pIDiaSession(nullptr), 224 | m_pIDiaSymbol(nullptr) 225 | { 226 | m_isStripped = false; 227 | } 228 | 229 | PDBFile::~PDBFile() 230 | { 231 | close(); 232 | } 233 | 234 | void PDBFile::close() 235 | { 236 | if ( m_pIDiaSymbol ) 237 | { 238 | m_pIDiaSymbol->Release(); 239 | m_pIDiaSymbol = nullptr; 240 | } 241 | if ( m_pIDiaSession ) 242 | { 243 | m_pIDiaSession->Release(); 244 | m_pIDiaSession = nullptr; 245 | } 246 | if (m_pIDiaDataSource) 247 | { 248 | m_pIDiaDataSource->Release(); 249 | m_pIDiaDataSource = nullptr; 250 | } 251 | } 252 | 253 | bool PDBFile::load(const wchar_t* _filename) 254 | { 255 | if (!_filename) return false; 256 | if (wcslen(_filename) == 0) return false; 257 | 258 | if (m_pIDiaDataSource == nullptr) 259 | { 260 | HRESULT hr = rdebug::createDiaDataSource((void**)&m_pIDiaDataSource); 261 | 262 | if (FAILED(hr)) 263 | return false; 264 | } 265 | 266 | bool bRet = false; 267 | 268 | if (INVALID_FILE_ATTRIBUTES != GetFileAttributesW(_filename)) 269 | { 270 | if (loadSymbolsFileWithoutValidation(_filename)) 271 | { 272 | bRet = m_pIDiaSession->get_globalScope( &m_pIDiaSymbol ) == S_OK ? true : false; 273 | 274 | m_isStripped = false; 275 | if( m_pIDiaSymbol ) 276 | { 277 | BOOL b = FALSE; 278 | HRESULT hr = m_pIDiaSymbol->get_isStripped(&b); 279 | if(hr==S_OK) 280 | { 281 | m_isStripped = b?true:false; 282 | } 283 | } 284 | 285 | RTM_ASSERT(bRet, ""); 286 | } 287 | } 288 | 289 | return bRet; 290 | } 291 | 292 | bool PDBFile::isLoaded() const 293 | { 294 | return m_pIDiaDataSource != nullptr; 295 | } 296 | 297 | bool PDBFile::getSymbolByAddress(uint64_t _address, rdebug::StackFrame& _frame) 298 | { 299 | rtm::strlCpy(_frame.m_file, RTM_NUM_ELEMENTS(_frame.m_file), "Unknown"); 300 | rdebug::addressToString(_address, _frame.m_func); 301 | _frame.m_line = 0; 302 | 303 | if( m_pIDiaSession ) 304 | { 305 | IDiaSymbol* sym = nullptr; 306 | 307 | _address -= 1; // get address of previous instruction 308 | 309 | if (!sym) m_pIDiaSession->findSymbolByVA((ULONGLONG)_address, SymTagFunction, &sym); 310 | if (!sym) m_pIDiaSession->findSymbolByVA((ULONGLONG)_address, SymTagPublicSymbol, &sym); 311 | 312 | if (sym) 313 | { 314 | BSTR SymName = nullptr; 315 | BSTR FileName = nullptr; 316 | DWORD LineNo = 0; 317 | 318 | if (FAILED(sym->get_undecoratedNameEx(UND_CODE, &SymName))) 319 | { 320 | sym->Release(); 321 | return false; 322 | } 323 | 324 | if (SymName == nullptr) 325 | sym->get_name(&SymName); 326 | 327 | IDiaEnumLineNumbers* lineEnum = nullptr; 328 | if (FAILED(m_pIDiaSession->findLinesByVA(_address,1,&lineEnum))) 329 | return false; 330 | 331 | ULONG celt = 0; 332 | for (;;) 333 | { 334 | IDiaLineNumber* Line = nullptr; 335 | lineEnum->Next(1, &Line, &celt); 336 | if (!Line) 337 | celt = 1; // hack, no file and line but has symbol name 338 | 339 | if (celt==1) 340 | { 341 | IDiaSourceFile* SrcFile = nullptr; 342 | 343 | if (Line) 344 | { 345 | Line->get_sourceFile(&SrcFile); 346 | Line->get_lineNumber(&LineNo); 347 | } 348 | if (SrcFile) 349 | SrcFile->get_fileName(&FileName); 350 | 351 | _bstr_t a = SymName; 352 | const wchar_t* nameC = a.operator const wchar_t*(); 353 | rtm::WideToMulti name(nameC); 354 | rtm::strlCpy(_frame.m_func, RTM_NUM_ELEMENTS(_frame.m_func), name.m_ptr); 355 | 356 | if (FileName) 357 | { 358 | a = FileName; 359 | nameC = a.operator const wchar_t*(); 360 | rtm::WideToMulti file(nameC); 361 | rtm::strlCpy(_frame.m_file, RTM_NUM_ELEMENTS(_frame.m_file), file.m_ptr); 362 | _frame.m_line = LineNo; 363 | } 364 | else 365 | _frame.m_line = 0; 366 | 367 | SysFreeString(SymName); 368 | 369 | if (FileName) 370 | SysFreeString(FileName); 371 | 372 | if (Line) 373 | Line->Release(); 374 | 375 | if (SrcFile) 376 | SrcFile->Release(); 377 | 378 | lineEnum->Release(); 379 | sym->Release(); 380 | return true; 381 | } 382 | 383 | if (celt != 1) 384 | break; 385 | } 386 | 387 | SysFreeString(SymName); 388 | lineEnum->Release(); 389 | sym->Release(); 390 | } 391 | } 392 | return false; 393 | } 394 | 395 | uint64_t PDBFile::getSymbolID(uint64_t _address) 396 | { 397 | DWORD ID = 0; 398 | if (m_pIDiaSession) 399 | { 400 | IDiaSymbol* sym = nullptr; 401 | 402 | _address -= 1; // get address of previous instruction 403 | 404 | if (!sym) m_pIDiaSession->findSymbolByVA((ULONGLONG)_address, SymTagFunction, &sym); 405 | if (!sym) m_pIDiaSession->findSymbolByVA((ULONGLONG)_address, SymTagPublicSymbol, &sym); 406 | 407 | if (sym) 408 | { 409 | sym->get_symIndexId(&ID); 410 | sym->Release(); 411 | } 412 | } 413 | 414 | return ID; 415 | } 416 | 417 | bool PDBFile::loadSymbolsFileWithoutValidation(const wchar_t* _PdbFileName) 418 | { 419 | bool bRet = false; 420 | IDiaDataSource* pIDiaDataSource = m_pIDiaDataSource; 421 | 422 | if(pIDiaDataSource) 423 | { 424 | bool bContinue = false; 425 | 426 | HRESULT hr = pIDiaDataSource->loadDataFromPdb(_PdbFileName); 427 | 428 | if(SUCCEEDED(hr)) 429 | bContinue = true; 430 | else 431 | { 432 | switch(hr) 433 | { 434 | case E_PDB_NOT_FOUND: 435 | case E_PDB_FORMAT: 436 | case E_INVALIDARG: 437 | break; 438 | case E_UNEXPECTED: 439 | bContinue = true; 440 | break; 441 | default: 442 | break; 443 | } 444 | } 445 | 446 | if(bContinue) 447 | bRet = pIDiaDataSource->openSession( &m_pIDiaSession )==S_OK?true:false; 448 | } 449 | return bRet; 450 | } 451 | 452 | #endif // RTM_PLATFORM_WINDOWS 453 | -------------------------------------------------------------------------------- /src/pdb_file.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------// 2 | /// Copyright 2025 Milos Tosic. All Rights Reserved. /// 3 | /// License: http://www.opensource.org/licenses/BSD-2-Clause /// 4 | //--------------------------------------------------------------------------// 5 | 6 | #ifndef RTM_RDEBUG_PDB_H 7 | #define RTM_RDEBUG_PDB_H 8 | 9 | #include 10 | 11 | #if RTM_PLATFORM_WINDOWS 12 | 13 | #include 14 | #include 15 | 16 | #define WIN32_LEAN_AND_MEAN 17 | #include 18 | #include 19 | #include 20 | 21 | class PDBFile 22 | { 23 | private: 24 | std::string m_sFileName; 25 | IDiaDataSource* m_pIDiaDataSource; 26 | IDiaSession* m_pIDiaSession; 27 | IDiaSymbol* m_pIDiaSymbol; 28 | bool m_isStripped; 29 | 30 | public: 31 | PDBFile(); 32 | ~PDBFile(); 33 | 34 | bool load(const wchar_t* _filename); 35 | bool isLoaded() const; 36 | bool getSymbolByAddress(uint64_t _address, rdebug::StackFrame& _frame); 37 | uint64_t getSymbolID(uint64_t _address); 38 | void close(); 39 | 40 | private: 41 | bool loadSymbolsFileWithoutValidation(const wchar_t* _PdbFileName); 42 | }; 43 | 44 | #endif // RTM_PLATFORM_WINDOWS 45 | 46 | #endif // RTM_RDEBUG_PDB_H 47 | -------------------------------------------------------------------------------- /src/process.cpp: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------// 2 | /// Copyright 2025 Milos Tosic. All Rights Reserved. /// 3 | /// License: http://www.opensource.org/licenses/BSD-2-Clause /// 4 | //--------------------------------------------------------------------------// 5 | 6 | #include 7 | 8 | #include 9 | 10 | #if RTM_PLATFORM_WINDOWS 11 | #define WIN32_LEAN_AND_MEAN 12 | #include 13 | #endif // RTM_PLATFORM_WINDOWS 14 | 15 | namespace rdebug { 16 | 17 | #if RTM_PLATFORM_WINDOWS 18 | 19 | const DWORD g_bufferSize = 4096*160; 20 | 21 | bool processIs64bitBinary(const char* _path) 22 | { 23 | rtm::MultiToWide path(_path); 24 | 25 | DWORD type; 26 | if (GetBinaryTypeW(path, &type)) 27 | { 28 | if (SCS_64BIT_BINARY == type) 29 | return true; 30 | } 31 | return false; 32 | } 33 | 34 | bool acquireDebugPrivileges(HANDLE _process) 35 | { 36 | bool success = false; 37 | 38 | HANDLE hToken; 39 | TOKEN_PRIVILEGES tokenPriv; 40 | LUID luidDebug; 41 | if(OpenProcessToken(_process, TOKEN_ADJUST_PRIVILEGES, &hToken)) 42 | { 43 | if(LookupPrivilegeValueA("", SE_DEBUG_NAME, &luidDebug)) 44 | { 45 | tokenPriv.PrivilegeCount = 1; 46 | tokenPriv.Privileges[0].Luid = luidDebug; 47 | tokenPriv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; 48 | 49 | if(AdjustTokenPrivileges(hToken, FALSE, &tokenPriv, sizeof(tokenPriv), NULL, NULL) == 0) 50 | success = false; 51 | else 52 | success = true; 53 | } 54 | else 55 | success = false; 56 | } 57 | else 58 | success = false; 59 | 60 | return success; 61 | } 62 | 63 | bool processInjectDLL(const char* _executablePath, const char* _DLLPath, const char* _cmdLine, const char* _workingDir, uint32_t* _pid) 64 | { 65 | STARTUPINFOW startInfo; 66 | PROCESS_INFORMATION pInfo; 67 | memset(&startInfo, 0, sizeof(STARTUPINFOW)); 68 | memset(&pInfo, 0, sizeof(PROCESS_INFORMATION)); 69 | startInfo.cb = sizeof(STARTUPINFOW); 70 | 71 | wchar_t cmdLine[32768]; 72 | wcscpy(cmdLine, rtm::MultiToWide(_executablePath)); 73 | wcscat(cmdLine, L" "); 74 | wcscat(cmdLine, rtm::MultiToWide(_cmdLine, false)); 75 | 76 | if (CreateProcessW(0, cmdLine, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, rtm::MultiToWide(_workingDir), &startInfo, &pInfo) != TRUE) 77 | return false; 78 | 79 | if (!acquireDebugPrivileges(pInfo.hProcess)) 80 | { 81 | return false; 82 | } 83 | 84 | HMODULE kernel32 = GetModuleHandle("kernel32.dll"); 85 | LPTHREAD_START_ROUTINE loadLib = (LPTHREAD_START_ROUTINE)GetProcAddress(kernel32, "LoadLibraryW"); 86 | 87 | char dllPath[2048]; 88 | rtm::strlCpy(dllPath, RTM_NUM_ELEMENTS(dllPath), _DLLPath); 89 | rtm::pathCanonicalize(dllPath); 90 | 91 | rtm::MultiToWide dllPathWide(dllPath); 92 | size_t dllPathLen = wcslen(dllPathWide) + 1; 93 | LPVOID remoteMem = VirtualAllocEx(pInfo.hProcess, NULL, dllPathLen*2, MEM_COMMIT, PAGE_READWRITE); 94 | SIZE_T numBytesWritten; 95 | if (!WriteProcessMemory(pInfo.hProcess, remoteMem, dllPathWide, dllPathLen*2, &numBytesWritten)) 96 | { 97 | TerminateProcess(pInfo.hProcess, 0); 98 | CloseHandle(pInfo.hProcess); 99 | return false; 100 | } 101 | 102 | HANDLE remoteThread = CreateRemoteThread(pInfo.hProcess, NULL, 0, loadLib, remoteMem, 0, NULL); 103 | 104 | if (remoteThread == NULL) 105 | { 106 | TerminateProcess(pInfo.hProcess, 0); 107 | CloseHandle(pInfo.hProcess); 108 | return false; 109 | } 110 | else 111 | { 112 | WaitForSingleObject(remoteThread, INFINITE); 113 | } 114 | 115 | ResumeThread(pInfo.hThread); 116 | if (_pid) 117 | *_pid = pInfo.dwProcessId; 118 | 119 | return true; 120 | } 121 | 122 | bool processRun(const char* _cmdLine, bool _hideWindow, uint32_t* _exitCode) 123 | { 124 | STARTUPINFOW startInfo; 125 | PROCESS_INFORMATION pInfo; 126 | memset(&startInfo, 0, sizeof(STARTUPINFOW)); 127 | memset(&pInfo, 0, sizeof(PROCESS_INFORMATION)); 128 | startInfo.cb = sizeof(STARTUPINFOW); 129 | 130 | if (_hideWindow) 131 | { 132 | startInfo.dwFlags = STARTF_USESHOWWINDOW; 133 | startInfo.wShowWindow = SW_HIDE; 134 | } 135 | 136 | rtm::MultiToWide cmdLine(_cmdLine, false); 137 | 138 | if (CreateProcessW(0, cmdLine, NULL, NULL, FALSE, 0, NULL, NULL, &startInfo, &pInfo) != TRUE) 139 | return false; 140 | 141 | WaitForSingleObject(pInfo.hProcess, INFINITE); 142 | DWORD exitCode = 0; 143 | GetExitCodeProcess(pInfo.hProcess, &exitCode); 144 | 145 | if (_exitCode) 146 | *_exitCode = exitCode; 147 | 148 | CloseHandle(pInfo.hProcess); 149 | return true; 150 | } 151 | 152 | struct PipeHandles 153 | { 154 | HANDLE m_stdIn_Read; 155 | HANDLE m_stdIn_Write; 156 | HANDLE m_stdOut_Read; 157 | HANDLE m_stdOut_Write; 158 | 159 | PipeHandles() 160 | { 161 | m_stdIn_Read = NULL; 162 | m_stdIn_Write = NULL; 163 | m_stdOut_Read = NULL; 164 | m_stdOut_Write = NULL; 165 | } 166 | 167 | ~PipeHandles() 168 | { 169 | CloseHandle(m_stdIn_Read); 170 | CloseHandle(m_stdIn_Write); 171 | CloseHandle(m_stdOut_Read); 172 | CloseHandle(m_stdOut_Write); 173 | } 174 | }; 175 | 176 | BOOL createChildProcess(const char* _cmdLine, PipeHandles* _handles, bool _redirectIO, std::string& _buffer) 177 | { 178 | PROCESS_INFORMATION piProcInfo; 179 | STARTUPINFOW siStartInfo; 180 | BOOL bSuccess = FALSE; 181 | 182 | ZeroMemory( &piProcInfo, sizeof(PROCESS_INFORMATION) ); 183 | ZeroMemory( &siStartInfo, sizeof(STARTUPINFOW) ); 184 | siStartInfo.cb = sizeof(STARTUPINFOW); 185 | 186 | if (_redirectIO) 187 | { 188 | siStartInfo.hStdError = _handles->m_stdOut_Write; 189 | siStartInfo.hStdOutput = _handles->m_stdOut_Write; 190 | siStartInfo.hStdInput = _handles->m_stdIn_Read; 191 | siStartInfo.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW; 192 | siStartInfo.wShowWindow = SW_HIDE; 193 | } 194 | else 195 | siStartInfo.dwFlags = STARTF_USESTDHANDLES; 196 | 197 | rtm::MultiToWide cmdLine(_cmdLine, false); 198 | bSuccess = CreateProcessW(NULL, cmdLine.m_ptr, NULL, NULL, TRUE, 0, NULL, NULL, &siStartInfo, &piProcInfo); 199 | 200 | if (bSuccess) 201 | { 202 | HANDLE h[2]; 203 | h[0] = piProcInfo.hThread; 204 | h[1] = piProcInfo.hProcess; 205 | 206 | std::string buffer, wbuffer; 207 | buffer.reserve(g_bufferSize); 208 | wbuffer.reserve(g_bufferSize); 209 | 210 | for (;;) 211 | { 212 | bool stillRunning = (WaitForSingleObject(piProcInfo.hProcess,0) == WAIT_TIMEOUT); 213 | 214 | DWORD dwRead = 0; 215 | DWORD bytesAvailable = 0; 216 | PeekNamedPipe(_handles->m_stdOut_Read, NULL, 0, NULL, &bytesAvailable, NULL); 217 | bSuccess = bytesAvailable && (ReadFile(_handles->m_stdOut_Read, &buffer[0], g_bufferSize, &dwRead, NULL) == TRUE); 218 | 219 | if (bSuccess) 220 | { 221 | buffer[dwRead] = '\0'; 222 | _buffer += buffer.c_str(); 223 | } 224 | 225 | if (!stillRunning) 226 | break; 227 | } 228 | 229 | WaitForMultipleObjects(2,h,TRUE,999); 230 | CloseHandle(piProcInfo.hThread); 231 | CloseHandle(piProcInfo.hProcess); 232 | } 233 | return bSuccess; 234 | } 235 | 236 | void CreatePipes(PipeHandles* _handles) 237 | { 238 | SECURITY_ATTRIBUTES saAttr; 239 | 240 | saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); 241 | saAttr.bInheritHandle = TRUE; 242 | saAttr.lpSecurityDescriptor = NULL; 243 | 244 | CreatePipe(&_handles->m_stdOut_Read, &_handles->m_stdOut_Write, &saAttr, 4096*160); 245 | SetHandleInformation(_handles->m_stdOut_Read, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT); 246 | 247 | saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); 248 | saAttr.bInheritHandle = TRUE; 249 | saAttr.lpSecurityDescriptor = NULL; 250 | 251 | CreatePipe(&_handles->m_stdIn_Read, &_handles->m_stdIn_Write, &saAttr, 4096*160); 252 | SetHandleInformation(_handles->m_stdIn_Write, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT); 253 | } 254 | 255 | DWORD ReadFromPipe(std::string& _buffer, PipeHandles* _handles) 256 | { 257 | DWORD dwRead = 0; 258 | BOOL bSuccess = FALSE; 259 | HANDLE hParentStdOut = GetStdHandle(STD_OUTPUT_HANDLE); 260 | 261 | char utf8buffer[8192+1]; 262 | for (;;) 263 | { 264 | DWORD bytesAvailable = 0; 265 | PeekNamedPipe(_handles->m_stdOut_Read, NULL, 0, NULL, &bytesAvailable, NULL); 266 | bSuccess = bytesAvailable && (ReadFile(_handles->m_stdOut_Read, utf8buffer, 8192, &dwRead, NULL) == TRUE); 267 | if (!bSuccess) 268 | break; 269 | 270 | utf8buffer[dwRead] = '\0'; 271 | _buffer += utf8buffer; 272 | 273 | if (dwRead < 8192) 274 | break; 275 | } 276 | CloseHandle(hParentStdOut); 277 | return dwRead; 278 | } 279 | 280 | bool processGetOutputOf(const char* _cmdLine, std::string& _buffer, bool _redirect) 281 | { 282 | PipeHandles handles; 283 | CreatePipes(&handles); 284 | 285 | BOOL success = createChildProcess(_cmdLine, &handles, _redirect, _buffer); 286 | 287 | if (!success) 288 | return false; 289 | 290 | ReadFromPipe(_buffer, &handles); 291 | return true; 292 | } 293 | 294 | char* processGetOutputOf(const char* _cmdLine, bool _redirectIO) 295 | { 296 | std::string buffer; 297 | processGetOutputOf(_cmdLine, buffer, _redirectIO); 298 | size_t len = rtm::strLen(buffer.c_str()); 299 | if (len) 300 | { 301 | char* res = (char*)rtm_alloc(sizeof(char) * (len + 1)); 302 | rtm::strlCpy(res, (uint32_t)(len + 1), buffer.c_str()); 303 | return res; 304 | } 305 | return 0; 306 | } 307 | 308 | #else // RTM_PLATFORM_WINDOWS 309 | 310 | bool processIs64bitBinary(const char* _path) 311 | { 312 | RTM_UNUSED(_path); 313 | return false; 314 | } 315 | 316 | bool processInjectDLL(const char* _executablePath, const char* _DLLPath, const char* _cmdLine, const char* _workingDir) 317 | { 318 | RTM_UNUSED(_executablePath); 319 | RTM_UNUSED(_DLLPath); 320 | RTM_UNUSED(_workingDir); 321 | RTM_UNUSED(_cmdLine); 322 | return false; 323 | } 324 | 325 | bool processRun(const char* _cmdLine, bool _hideWindow, uint32_t* _exitCode) 326 | { 327 | RTM_UNUSED(_cmdLine); 328 | RTM_UNUSED(_hideWindow); 329 | RTM_UNUSED(_exitCode); 330 | return false; 331 | } 332 | 333 | char* processGetOutputOf(const char* _cmdLine, bool _redirectIO) 334 | { 335 | RTM_UNUSED(_cmdLine); 336 | RTM_UNUSED(_redirectIO); 337 | return 0; 338 | } 339 | 340 | #endif // RTM_PLATFORM_WINDOWS 341 | 342 | void processReleaseOutput(const char* _output) 343 | { 344 | if (_output) 345 | rtm_free((void*)_output); 346 | } 347 | 348 | void addressToString(uint64_t _address, char* _buffer) 349 | { 350 | union { uint64_t a; char h[8]; uint32_t d[2]; } c; 351 | c.a = _address; 352 | _buffer[0] = '0'; 353 | _buffer[1] = 'x'; 354 | 355 | int ptr = 2; 356 | for (int i=c.d[1]?0:4; i<8; ++i) 357 | { 358 | char cc = c.h[7 - i]; 359 | _buffer[ptr++] = rtm::toHexNum((char)(cc >> 4)); 360 | _buffer[ptr++] = rtm::toHexNum((char)(cc & 0xf)); 361 | } 362 | _buffer[ptr] = '\0'; 363 | } 364 | 365 | } // namespace rdebug 366 | -------------------------------------------------------------------------------- /src/rdebug_pch.cpp: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------// 2 | /// Copyright 2025 Milos Tosic. All Rights Reserved. /// 3 | /// License: http://www.opensource.org/licenses/BSD-2-Clause /// 4 | //--------------------------------------------------------------------------// 5 | 6 | #include 7 | 8 | #define RTM_LIBHANDLER_DEFINE 9 | #include 10 | 11 | namespace rdebug { 12 | 13 | Toolchain::Toolchain() 14 | : m_type(Toolchain::Unknown) 15 | { 16 | m_toolchainPath[0] = 0; 17 | m_toolchainPrefix[0] = 0; 18 | } 19 | 20 | bool init(rtmLibInterface* _libInterface) 21 | { 22 | g_allocator = _libInterface ? _libInterface->m_memory : 0; 23 | g_errorHandler = _libInterface ? _libInterface->m_error : 0; 24 | 25 | return true; 26 | } 27 | 28 | void shutDown() 29 | { 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/rdebug_pch.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------// 2 | /// Copyright 2025 Milos Tosic. All Rights Reserved. /// 3 | /// License: http://www.opensource.org/licenses/BSD-2-Clause /// 4 | //--------------------------------------------------------------------------// 5 | 6 | #ifndef RTM_TOOLUTIL_TOOLUTIL_PCH_H 7 | #define RTM_TOOLUTIL_TOOLUTIL_PCH_H 8 | 9 | #define RBASE_NAMESPACE rdebug 10 | #define RTM_DEFINE_STL_STRING 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include 19 | 20 | #include 21 | 22 | #endif // RTM_TOOLUTIL_TOOLUTIL_PCH_H 23 | 24 | -------------------------------------------------------------------------------- /src/symbol_parsing.cpp: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------// 2 | /// Copyright 2025 Milos Tosic. All Rights Reserved. /// 3 | /// License: http://www.opensource.org/licenses/BSD-2-Clause /// 4 | //--------------------------------------------------------------------------// 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | namespace rdebug { 12 | 13 | inline static bool charIsDigit(char _c) 14 | { 15 | return ((_c >= '0') && (_c <= '9')); 16 | } 17 | 18 | inline static bool charIsEOL(char _c) 19 | { 20 | return ((_c == '\r') || (_c == '\n')); 21 | } 22 | 23 | inline static bool charIsBlank(char _c) 24 | { 25 | return ((_c == ' ') || (_c == '\t')); 26 | } 27 | 28 | inline static bool charIsTab(char _c) 29 | { 30 | return (_c == '\t'); 31 | } 32 | 33 | inline static bool charIsSpace(char _c) 34 | { 35 | return (_c == ' '); 36 | } 37 | 38 | void parseAddr2LineSymbolInfo(const char* _str, StackFrame& _frame) 39 | { 40 | size_t len = rtm::strLen(_str); 41 | 42 | size_t l = len; 43 | int idx = 0; 44 | while (--l) 45 | { 46 | // ugly but we own the data 47 | if (_str[l] == '\r') { const_cast(_str)[l] = '\0'; idx++; } 48 | if (_str[l] == '\n') const_cast(_str)[l] = '\0'; 49 | 50 | if (idx == 2) break; 51 | } 52 | 53 | if (rtm::strCmp(_str,"??") != 0) 54 | rtm::strlCpy(_frame.m_func, sizeof(_frame.m_func), _str); 55 | 56 | const char* ptr = &_str[l+2]; 57 | if (rtm::strCmp(ptr,"??:0") != 0) 58 | { 59 | rtm::strlCpy(_frame.m_file, sizeof(_frame.m_file), ptr); 60 | len = rtm::strLen(_frame.m_file); 61 | while (_frame.m_file[--len] != ':'); 62 | _frame.m_file[len] = '\0'; 63 | _frame.m_line = atoi(&_frame.m_file[len+1]); 64 | } 65 | } 66 | 67 | void parsePlayStationSymbolInfo(const char* _str, StackFrame& _frame) 68 | { 69 | const char* add = "Address: "; 70 | const char* dir = "Directory: "; 71 | const char* fil = "File Name: "; 72 | const char* lin = "Line Number: "; 73 | const char* sym = "Symbol: "; 74 | 75 | const char* address = rtm::strStr(_str, add); 76 | const char* directory = rtm::strStr(_str, dir); 77 | const char* file = rtm::strStr(_str, fil); 78 | const char* line = rtm::strStr(_str, lin); 79 | const char* symbol = rtm::strStr(_str, sym); 80 | 81 | if (address && directory && file && line && symbol) 82 | { 83 | size_t len = rtm::strLen(_str); 84 | while (--len) 85 | { 86 | // ugly but we own the data 87 | if (_str[len] == '\r') const_cast(_str)[len] = '\0'; 88 | if (_str[len] == '\n') const_cast(_str)[len] = '\0'; 89 | } 90 | 91 | size_t offset = rtm::strLen(add); 92 | if ((rtm::strCmp(&directory[offset], "??") != 0) && (rtm::strCmp(&file[offset], "??") != 0)) 93 | { 94 | rtm::strlCpy(_frame.m_file, RTM_NUM_ELEMENTS(_frame.m_file), &directory[offset]); 95 | rtm::strlCat(_frame.m_file, RTM_NUM_ELEMENTS(_frame.m_file), "/"); 96 | rtm::strlCat(_frame.m_file, RTM_NUM_ELEMENTS(_frame.m_file), &file[offset]); 97 | } 98 | rtm::strlCpy(_frame.m_func, RTM_NUM_ELEMENTS(_frame.m_func), &symbol[offset]); 99 | _frame.m_line = atoi(&line[offset]); 100 | } 101 | } 102 | 103 | void parseFile(std::string& _dst, uint32_t& _line, const char*& _buffer) 104 | { 105 | while (charIsBlank(*_buffer) && !charIsEOL(*_buffer)) ++_buffer; 106 | while (!charIsEOL(*_buffer)) 107 | { 108 | if (*_buffer == ':') 109 | { 110 | if (charIsDigit(_buffer[1])) 111 | { 112 | _line = atoi(&_buffer[1]); 113 | break; 114 | } 115 | } 116 | 117 | _dst += *_buffer; 118 | ++_buffer; 119 | } 120 | } 121 | 122 | void parseSym(std::string& _dst, const char*& _buffer) 123 | { 124 | while (charIsBlank(*_buffer) && !charIsEOL(*_buffer)) ++_buffer; 125 | while (!charIsTab(*_buffer) && !charIsEOL(*_buffer)) 126 | { 127 | _dst += *_buffer; 128 | ++_buffer; 129 | } 130 | } 131 | 132 | bool isHex(char _c) 133 | { 134 | if ((_c >= '0') && (_c <= '9')) 135 | return true; 136 | else 137 | if ((_c >= 'a') && (_c <= 'z')) 138 | return true; 139 | else 140 | if ((_c >= 'A') && (_c <= 'Z')) 141 | return true; 142 | return false; 143 | } 144 | 145 | uint64_t fromHex(char _c, bool& _stop) 146 | { 147 | if ((_c >= '0') && (_c <= '9')) 148 | return (_c - '0'); 149 | else 150 | if ((_c >= 'a') && (_c <= 'z')) 151 | return 10 + (_c - 'a'); 152 | else 153 | if ((_c >= 'A') && (_c <= 'Z')) 154 | return 10 + (_c - 'A'); 155 | _stop = true; 156 | return 0; 157 | } 158 | 159 | bool parseHex(uint64_t& _offset, const char*& _buffer) 160 | { 161 | const char* buffer = _buffer; 162 | if (!buffer) 163 | { 164 | _offset = 0; 165 | return true; 166 | } 167 | 168 | while (charIsSpace(*buffer)) ++buffer; 169 | 170 | uint64_t offset = 0; 171 | if ((buffer[0] == '0') && (buffer[1] == 'x')) 172 | buffer = &buffer[2]; 173 | 174 | if (!isHex(*buffer)) 175 | return false; 176 | 177 | int cnt = 0; 178 | do 179 | { 180 | if (charIsSpace(*buffer)) 181 | break; 182 | 183 | bool stop = false; 184 | offset = (offset << 4) | fromHex(*buffer, stop); 185 | ++buffer; 186 | if (stop) 187 | return false; 188 | ++cnt; 189 | } 190 | while (*buffer && isHex(*buffer) && !charIsSpace(*buffer)); 191 | 192 | if ((cnt != 8) && (cnt != 16)) 193 | return false; 194 | 195 | _offset = offset; 196 | _buffer = ++buffer; 197 | return true; 198 | } 199 | 200 | void parseSymbolMapLineGNU(const char* _line, SymbolMap& _symMap) 201 | { 202 | // offset [size] t/T symbol file:line 203 | Symbol sym; 204 | 205 | uint64_t offset = 0; 206 | bool charIsDigit = parseHex(offset, _line); 207 | if (!charIsDigit) 208 | return; 209 | 210 | uint64_t size = 0; 211 | parseHex(size, _line); 212 | 213 | char type = _line[0]; 214 | ++_line; 215 | 216 | if ((type != 't') && (type != 'T')) 217 | return; 218 | 219 | parseSym(sym.m_name, _line); 220 | sym.m_nameHash = rtm::hashStr(sym.m_name.c_str()); 221 | sym.m_offset = offset; 222 | sym.m_size = size; 223 | 224 | parseFile(sym.m_file, sym.m_line, _line); 225 | 226 | _symMap.addSymbol(sym); 227 | } 228 | 229 | void parseSymbolMapLinePS3SNC(const char* _line, SymbolMap& _symMap) 230 | { 231 | // offset scope Function segment symbol 232 | Symbol sym; 233 | 234 | uint64_t offset = 0; 235 | bool charIsDigit = parseHex(offset, _line); 236 | if (!charIsDigit) 237 | return; 238 | 239 | while (!charIsSpace(*_line) && !charIsEOL(*_line)) ++_line; 240 | while ( charIsSpace(*_line) && !charIsEOL(*_line)) ++_line; 241 | 242 | if (rtm::strCmp(_line, "Function", rtm::strLen("Function")) != 0) 243 | return; 244 | 245 | while (!charIsSpace(*_line) && !charIsEOL(*_line)) ++_line; 246 | while ( charIsSpace(*_line) && !charIsEOL(*_line)) ++_line; 247 | while (!charIsSpace(*_line) && !charIsEOL(*_line)) ++_line; 248 | while ( charIsSpace(*_line) && !charIsEOL(*_line)) ++_line; 249 | 250 | parseSym(sym.m_name, _line); 251 | sym.m_offset = (uint32_t)offset; 252 | sym.m_nameHash = rtm::hashStr(sym.m_name.c_str()); 253 | 254 | _symMap.addSymbol(sym); 255 | } 256 | 257 | void parseSymbolMapGNU(const char* _buffer, SymbolMap& _symMap) 258 | { 259 | size_t len = rtm::strLen(_buffer); 260 | size_t pos = 0; 261 | 262 | while (pos < len) 263 | { 264 | const char* line = &_buffer[pos]; 265 | 266 | parseSymbolMapLineGNU(line, _symMap); 267 | 268 | while (!charIsEOL(_buffer[pos])) 269 | ++pos; 270 | 271 | while (charIsEOL(_buffer[pos])) 272 | ++pos; 273 | } 274 | 275 | _symMap.sort(); 276 | } 277 | 278 | void parseSymbolMapPS3(const char* _buffer, SymbolMap& _symMap) 279 | { 280 | size_t len = rtm::strLen(_buffer); 281 | size_t pos = 0; 282 | 283 | while (pos < len) 284 | { 285 | const char* line = &_buffer[pos]; 286 | 287 | parseSymbolMapLinePS3SNC(line, _symMap); 288 | 289 | while (!charIsEOL(_buffer[pos])) 290 | ++pos; 291 | 292 | while (charIsEOL(_buffer[pos])) 293 | ++pos; 294 | } 295 | 296 | _symMap.sort(); 297 | } 298 | 299 | } // namespace rdebug 300 | -------------------------------------------------------------------------------- /src/symbols.cpp: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------// 2 | /// Copyright 2025 Milos Tosic. All Rights Reserved. /// 3 | /// License: http://www.opensource.org/licenses/BSD-2-Clause /// 4 | //--------------------------------------------------------------------------// 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "../3rd/rust-demangle.h" 12 | #include "../3rd/rust-demangle.c" 13 | 14 | #include 15 | 16 | #if RTM_PLATFORM_WINDOWS 17 | #define WIN32_LEAN_AND_MEAN 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | inline static uint16_t read16(FILE* _file, uint16_t _pos) 24 | { 25 | fseek(_file, _pos, SEEK_SET); 26 | uint8_t buf[2]; 27 | fread(buf, 1, 2, _file); 28 | return (uint32_t)buf[0] | (uint32_t)buf[1] << 8; 29 | } 30 | 31 | inline static uint32_t read32(FILE* _file, uint16_t _pos) 32 | { 33 | fseek(_file, _pos, SEEK_SET); 34 | uint8_t buf[4]; 35 | fread(buf, 1, 4, _file); 36 | return (uint32_t)buf[0] | (uint32_t)buf[1] << 8 | (uint32_t)buf[2] << 16 | (uint32_t)buf[3] << 24; 37 | } 38 | 39 | #define RH_RET(_x) { fclose(file); return _x; } 40 | 41 | int hasRichHeader(char const* _filePath) 42 | { 43 | FILE* file = fopen(_filePath, "rb"); 44 | if (!file) 45 | return -1; 46 | 47 | uint16_t mz = read16(file, 0); 48 | if (mz != 0x5A4D) 49 | RH_RET(0); 50 | 51 | uint16_t numRel = read16(file, 6); 52 | uint16_t header = read16(file, 8); 53 | if (header < 4) 54 | RH_RET(0); 55 | 56 | uint16_t relOffset = read16(file, 0x18); 57 | uint16_t peOffset = read16(file, 0x3c); 58 | if (peOffset < header * 16) 59 | RH_RET(0); 60 | 61 | uint32_t t = read32(file, peOffset); 62 | if (t != 0x4550) 63 | RH_RET(0); 64 | 65 | if (numRel > 0) 66 | relOffset += 4 * numRel; 67 | 68 | if (relOffset % 16) 69 | relOffset += 16 - (relOffset % 16); 70 | 71 | uint16_t roffset = 0; 72 | for (uint16_t i = relOffset; i < peOffset; i += 4) 73 | { 74 | t = read32(file, i); 75 | if (t == 0x68636952) 76 | { 77 | roffset = i + 4; 78 | break; 79 | } 80 | } 81 | 82 | if (roffset == 0) 83 | RH_RET(0); 84 | 85 | RH_RET(1); 86 | } 87 | 88 | #if RTM_COMPILER_MSVC 89 | #pragma warning (disable: 4091) // 'typedef ': ignored on left of '' when no variable is declared 90 | #include 91 | #pragma warning (default: 4091) 92 | 93 | #pragma comment(lib, "dbghelp.lib") 94 | //#pragma comment(lib, "psapi.lib") 95 | #endif // RTM_COMPILER_MSVC 96 | 97 | typedef BOOL (WINAPI * fnGetModuleInformation)(HANDLE hProcess, HMODULE hModule, LPMODULEINFO lpmodinfo, DWORD cb); 98 | typedef BOOL (WINAPI * fnEnumProcessModules)(HANDLE hProcess, HMODULE* lphModule, DWORD cb, LPDWORD lpcbNeeded); 99 | typedef DWORD (WINAPI * fnGetModuleFileNameExW)(HANDLE hProcess, HMODULE hModule, LPWSTR lpFilename, DWORD nSize); 100 | 101 | static fnGetModuleInformation gFn_getModuleInformation = 0; 102 | static fnEnumProcessModules gFn_enumProcessModules = 0; 103 | static fnGetModuleFileNameExW gFn_getModuleFileNameExW = 0; 104 | 105 | FARPROC loadFunc(HMODULE _kernel, HMODULE _psapi, const char* _name) 106 | { 107 | FARPROC ret = ::GetProcAddress(_kernel, _name); 108 | if (!ret && (_psapi != 0)) 109 | ret = ::GetProcAddress(_psapi, _name); 110 | return ret; 111 | } 112 | #endif // RTM_PLATFORM_WINDOWS 113 | 114 | class PDBFile; 115 | 116 | namespace rdebug { 117 | bool findSymbol(const char* _path, wchar_t _outSymbolPath[4096], const char* _symbolStore); 118 | } 119 | 120 | namespace rdebug { 121 | 122 | void parseAddr2LineSymbolInfo(const char* _str, StackFrame& _frame); 123 | void parsePlayStationSymbolInfo(const char* _str, StackFrame& _frame); 124 | 125 | void parseSymbolMapGNU(const char* _buffer, SymbolMap& _symMap); 126 | void parseSymbolMapPS3(const char* _buffer, SymbolMap& _symMap); 127 | 128 | #if RTM_PLATFORM_WINDOWS 129 | extern wchar_t g_symStore[ResolveInfo::SYM_SERVER_BUFFER_SIZE]; 130 | 131 | bool loadPDB(Module& _module) 132 | { 133 | if (!_module.m_resolver->m_PDBFile) 134 | { 135 | _module.m_resolver->m_PDBFile = rtm_new(); 136 | wchar_t symbolPath[1024]; 137 | wcscpy(symbolPath, L""); 138 | const char* symStore = _module.m_resolver->m_symbolStore ? _module.m_resolver->m_symbolStore : (const char*)g_symStore; 139 | findSymbol(_module.m_module.m_modulePath, symbolPath, symStore); 140 | 141 | if (wcscmp(symbolPath, L"") != 0) 142 | _module.m_resolver->m_PDBFile->load(symbolPath); 143 | else 144 | return false; 145 | } 146 | return _module.m_resolver->m_PDBFile->isLoaded(); 147 | } 148 | #endif // RTM_PLATFORM_WINDOWS 149 | 150 | wchar_t g_symStore[ResolveInfo::SYM_SERVER_BUFFER_SIZE] = { 0 }; 151 | 152 | void symbolSetServerSource(const wchar_t* _symStore) 153 | { 154 | size_t len = wcslen(_symStore); 155 | if (!len) 156 | return; 157 | 158 | wcscpy(g_symStore, _symStore); 159 | } 160 | 161 | uintptr_t symbolResolverCreate(ModuleInfo* _moduleInfos, uint32_t _numInfos, const char* _executable, module_load_cb _callback, void* _data) 162 | { 163 | RTM_UNUSED_2(_callback, _data); 164 | RTM_ASSERT(_moduleInfos, "Either module info array or toolchain desc can't be NULL"); 165 | 166 | Resolver* resolver = rtm_new(); 167 | 168 | const char* executablePath = 0; 169 | const char* exeName = _executable ? rtm::pathGetFileName(_executable) : 0; 170 | 171 | for (uint32_t i=0; i<_numInfos; ++i) 172 | { 173 | Module module; 174 | module.m_module = _moduleInfos[i]; 175 | module.m_moduleName = rtm::pathGetFileName(module.m_module.m_modulePath); 176 | module.m_resolver = rtm_new(); 177 | 178 | char tmpName[1024]; 179 | rtm::strlCpy(tmpName, RTM_NUM_ELEMENTS(tmpName), module.m_moduleName); 180 | rtm::strToUpper(tmpName); 181 | 182 | if ((rtm::striCmp(tmpName,"MTUNERDLL32.DLL") == 0) || (rtm::striCmp(tmpName,"MTUNERDLL64.DLL") == 0)) 183 | module.m_isRTMdll = true; 184 | 185 | const char* ext = rtm::pathGetExt(tmpName); 186 | const bool crossToolChain = ((rtm::striCmp(ext, "ELF") == 0) || (rtm::striCmp(ext, "SELF") == 0)) ? true : false; 187 | 188 | // on Windows, fix toolchain for each module 189 | if (!crossToolChain) 190 | { 191 | #if RTM_PLATFORM_WINDOWS 192 | int hasRH = hasRichHeader(module.m_module.m_modulePath); 193 | if (hasRH >= 0) 194 | module.m_module.m_toolchain.m_type = (hasRH == 1) ? rdebug::Toolchain::MSVC : rdebug::Toolchain::GCC; 195 | #endif // RTM_PLATFORM_WINDOWS 196 | } 197 | 198 | if (ext) 199 | { 200 | if ((rtm::striCmp(ext, "EXE") == 0) || crossToolChain) 201 | executablePath = _moduleInfos[i].m_modulePath; 202 | 203 | if (((rtm::striCmp(module.m_moduleName, exeName) == 0)) && crossToolChain) 204 | module.m_resolver->m_baseAddress4addr2Line = module.m_module.m_baseAddress; 205 | } 206 | 207 | if (executablePath) 208 | { 209 | module.m_resolver->m_executablePath = module.m_resolver->scratch(executablePath); 210 | module.m_resolver->m_executableName = module.m_resolver->m_executablePath ? rtm::pathGetFileName(module.m_resolver->m_executablePath) : 0; 211 | } 212 | 213 | std::string append_nm; 214 | std::string append_a2l; 215 | std::string append_cppf; 216 | 217 | std::string quote; 218 | 219 | if ((module.m_module.m_toolchain.m_type == rdebug::Toolchain::GCC) || 220 | (module.m_module.m_toolchain.m_type == rdebug::Toolchain::PS4) || 221 | (module.m_module.m_toolchain.m_type == rdebug::Toolchain::PS5)) 222 | { 223 | if (module.m_module.m_toolchain.m_type == rdebug::Toolchain::GCC) 224 | quote = "\""; 225 | 226 | append_nm = "\" -C --print-size --numeric-sort --line-numbers " + quote; 227 | append_nm += executablePath; 228 | append_nm += quote; 229 | 230 | append_a2l = "\" -f -e " + quote; 231 | append_a2l += executablePath; 232 | append_a2l += quote + " 0x%x"; 233 | 234 | append_cppf = "\" -t -n "; 235 | } 236 | 237 | if (module.m_module.m_toolchain.m_type == rdebug::Toolchain::PS3SNC) 238 | { 239 | append_nm = "\" -dsy \""; 240 | append_nm += executablePath; 241 | append_nm += "\""; 242 | 243 | append_a2l = "\" -a2l 0x%x -i \""; 244 | append_a2l += executablePath; 245 | append_a2l += "\""; 246 | 247 | append_cppf = "\" -t -n "; 248 | } 249 | 250 | #if RTM_PLATFORM_WINDOWS 251 | append_nm = ".exe" + append_nm; 252 | append_a2l = ".exe" + append_a2l; 253 | append_cppf = ".exe" + append_cppf; 254 | #endif 255 | 256 | quote = "\""; 257 | 258 | switch (module.m_module.m_toolchain.m_type) 259 | { 260 | case rdebug::Toolchain::MSVC: 261 | module.m_resolver->m_parseSym = 0; 262 | module.m_resolver->m_parseSymMap = 0; 263 | module.m_resolver->m_symbolStore = 0; 264 | module.m_resolver->m_tc_addr2line = 0; 265 | module.m_resolver->m_tc_nm = 0; 266 | module.m_resolver->m_tc_cppfilt = 0; 267 | break; 268 | 269 | case rdebug::Toolchain::GCC: 270 | case rdebug::Toolchain::PS4: 271 | case rdebug::Toolchain::PS5: 272 | module.m_resolver->m_parseSym = parseAddr2LineSymbolInfo; 273 | module.m_resolver->m_parseSymMap = parseSymbolMapGNU; 274 | module.m_resolver->m_symbolStore = 0; 275 | module.m_resolver->m_tc_addr2line = module.m_resolver->scratch((quote + module.m_module.m_toolchain.m_toolchainPath + module.m_module.m_toolchain.m_toolchainPrefix + "addr2line" + append_a2l).c_str()); 276 | module.m_resolver->m_tc_nm = module.m_resolver->scratch((quote + module.m_module.m_toolchain.m_toolchainPath + module.m_module.m_toolchain.m_toolchainPrefix + "nm" + append_nm).c_str()); 277 | module.m_resolver->m_tc_cppfilt = module.m_resolver->scratch((quote + module.m_module.m_toolchain.m_toolchainPath + module.m_module.m_toolchain.m_toolchainPrefix + "c++filt" + append_cppf).c_str()); 278 | break; 279 | 280 | case rdebug::Toolchain::PS3SNC: 281 | module.m_resolver->m_parseSym = parsePlayStationSymbolInfo; 282 | module.m_resolver->m_parseSymMap = parseSymbolMapPS3; 283 | module.m_resolver->m_symbolStore = 0; 284 | module.m_resolver->m_tc_addr2line = module.m_resolver->scratch((quote + module.m_module.m_toolchain.m_toolchainPath + module.m_module.m_toolchain.m_toolchainPrefix + "ps3bin" + append_a2l).c_str()); 285 | module.m_resolver->m_tc_nm = module.m_resolver->scratch((quote + module.m_module.m_toolchain.m_toolchainPath + module.m_module.m_toolchain.m_toolchainPrefix + "ps3bin" + append_nm).c_str()); 286 | module.m_resolver->m_tc_cppfilt = module.m_resolver->scratch((quote + module.m_module.m_toolchain.m_toolchainPath + module.m_module.m_toolchain.m_toolchainPrefix + "ps3name" + append_cppf).c_str()); 287 | break; 288 | 289 | case rdebug::Toolchain::Unknown: 290 | rtm::Console::info("Toolchain is not configured, no symbols can be resolved!\n"); 291 | }; 292 | 293 | module.m_resolver->m_symbolStore = module.m_resolver->scratch(module.m_module.m_toolchain.m_toolchainPath); 294 | 295 | #if RTM_PLATFORM_WINDOWS 296 | if (loadPDB(module) && _callback) 297 | _callback(module.m_moduleName, _data); 298 | #endif 299 | 300 | resolver->m_modules.push_back(module); 301 | } 302 | 303 | std::sort(&resolver->m_modules[0], &resolver->m_modules[resolver->m_modules.size()-1], 304 | [](const Module& a, const Module& b) 305 | { 306 | return a.m_module.m_baseAddress < b.m_module.m_baseAddress; 307 | }); 308 | 309 | return (uintptr_t)resolver; 310 | } 311 | 312 | uintptr_t symbolResolverCreateForCurrentProcess() 313 | { 314 | #if RTM_PLATFORM_WINDOWS 315 | rtm::FixedArray modules; 316 | 317 | HMODULE kerneldll32 = ::GetModuleHandleA("kernel32"); 318 | HMODULE psapiDLL = ::LoadLibraryA("Psapi.dll"); 319 | 320 | gFn_getModuleInformation = (fnGetModuleInformation)loadFunc(kerneldll32, psapiDLL, "GetModuleInformation"); 321 | gFn_enumProcessModules = (fnEnumProcessModules) loadFunc(kerneldll32, psapiDLL, "EnumProcessModules"); 322 | gFn_getModuleFileNameExW = (fnGetModuleFileNameExW)loadFunc(kerneldll32, psapiDLL, "GetModuleFileNameExW"); 323 | 324 | Toolchain toolchain; 325 | 326 | #if RTM_COMPILER_MSVC 327 | wchar_t symStoreBuffer[4096]; 328 | if (0 == GetEnvironmentVariableW(L"_NT_SYMBOL_PATH", (LPWSTR)symStoreBuffer, sizeof(symStoreBuffer))) 329 | wcscpy(symStoreBuffer, L""); 330 | rtm::WideToMulti symStore(symStoreBuffer); 331 | 332 | toolchain.m_type = Toolchain::MSVC; 333 | rtm::strlCpy(toolchain.m_toolchainPath, RTM_NUM_ELEMENTS(toolchain.m_toolchainPath), symStore); 334 | #else 335 | toolchain.m_type = Toolchain::GCC; 336 | rtm::strlCpy(toolchain.m_toolchainPath, RTM_NUM_ELEMENTS(toolchain.m_toolchainPath), ""); 337 | #endif 338 | rtm::strlCpy(toolchain.m_toolchainPrefix, RTM_NUM_ELEMENTS(toolchain.m_toolchainPrefix), ""); 339 | 340 | 341 | HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPALL, 0); 342 | if (snapshot != INVALID_HANDLE_VALUE) 343 | { 344 | MODULEENTRY32W me; 345 | BOOL cap = Module32FirstW(snapshot, &me); 346 | if (!cap) 347 | { 348 | // fall back on enumerating modules 349 | HMODULE hMods[1024]; 350 | DWORD cbNeeded; 351 | 352 | if (gFn_enumProcessModules(GetCurrentProcess(), hMods, sizeof(hMods), &cbNeeded)) 353 | { 354 | for (uint32_t i=0; i<(cbNeeded/sizeof(HMODULE)); ++i) 355 | { 356 | wchar_t szModName[MAX_PATH]; 357 | 358 | MODULEINFO mi; 359 | gFn_getModuleInformation(GetCurrentProcess(), hMods[i], &mi, sizeof(mi) ); 360 | 361 | if (gFn_getModuleFileNameExW(GetCurrentProcess(), hMods[i], szModName, sizeof(szModName) / sizeof(wchar_t))) 362 | { 363 | rtm::WideToMulti modulePath(szModName); 364 | 365 | uint64_t modBase = (uint64_t)mi.lpBaseOfDll; 366 | uint64_t modSize = (uint64_t)mi.SizeOfImage; 367 | ModuleInfo module; 368 | module.m_baseAddress = modBase; 369 | module.m_size = modSize; 370 | rtm::strlCpy(module.m_modulePath, RTM_NUM_ELEMENTS(module.m_modulePath), modulePath.m_ptr); 371 | modules.push_back(module); 372 | } 373 | } 374 | } 375 | } 376 | else 377 | while (cap) 378 | { 379 | rtm::WideToMulti exePath(me.szExePath); 380 | 381 | uint64_t modBase = (uint64_t)me.modBaseAddr; 382 | uint64_t modSize = (uint64_t)me.modBaseSize; 383 | ModuleInfo module; 384 | module.m_baseAddress = modBase; 385 | module.m_size = modSize; 386 | rtm::strlCpy(module.m_modulePath, RTM_NUM_ELEMENTS(module.m_modulePath), exePath.m_ptr); 387 | modules.push_back(module); 388 | cap = Module32NextW(snapshot, &me); 389 | } 390 | 391 | CloseHandle(snapshot); 392 | } 393 | 394 | return symbolResolverCreate(&modules[0], modules.size(), 0); 395 | #else 396 | return 0; 397 | #endif 398 | } 399 | 400 | void symbolResolverDelete(uintptr_t _resolver) 401 | { 402 | Resolver* resolver = (Resolver*)_resolver; 403 | 404 | for (uint32_t i=0; im_modules.size(); ++i) 405 | { 406 | Module& module = resolver->m_modules[i]; 407 | if (module.m_resolver) 408 | rtm_delete(module.m_resolver); 409 | } 410 | 411 | if (resolver) 412 | rtm_delete(resolver); 413 | } 414 | 415 | ResolveInfo::ResolveInfo() 416 | { 417 | m_scratch = (char*)rtm_alloc(sizeof(char) * SCRATCH_MEM_SIZE); 418 | m_scratchPos = 0; 419 | m_tc_addr2line = 0; 420 | m_tc_nm = 0; 421 | m_tc_cppfilt = 0; 422 | m_executablePath = 0; 423 | m_executableName = 0; 424 | m_parseSym = 0; 425 | m_parseSymMap = 0; 426 | m_baseAddress4addr2Line = 0; 427 | m_symbolStore = 0; 428 | m_symbolMapInitialized = false; 429 | m_symbolCache = 0; 430 | #if RTM_PLATFORM_WINDOWS 431 | m_PDBFile = 0; 432 | #endif // RTM_PLATFORM_WINDOWS 433 | } 434 | 435 | ResolveInfo::~ResolveInfo() 436 | { 437 | #if RTM_PLATFORM_WINDOWS 438 | if (m_PDBFile) 439 | rtm_delete(m_PDBFile); 440 | #endif // RTM_PLATFORM_WINDOWS 441 | rtm_free(m_scratch); 442 | } 443 | 444 | char* ResolveInfo::scratch(const char* _str) 445 | { 446 | RTM_ASSERT(_str != 0, "null string!"); 447 | size_t len = rtm::strLen(_str) + 1; 448 | RTM_ASSERT(m_scratchPos + len < SCRATCH_MEM_SIZE, "Scratch buffer full!"); 449 | char* ret = &m_scratch[m_scratchPos]; 450 | rtm::strlCpy(ret, SCRATCH_MEM_SIZE - m_scratchPos, _str); 451 | m_scratchPos += (uint32_t)len; 452 | return ret; 453 | } 454 | 455 | #if RTM_PLATFORM_WINDOWS 456 | 457 | class DiaLoadCallBack : public IDiaLoadCallback2 458 | { 459 | private: 460 | uint32_t m_RefCount; 461 | wchar_t* m_Buffer; 462 | 463 | public: 464 | DiaLoadCallBack(wchar_t inBuffer[1024]) : m_RefCount(0), m_Buffer(inBuffer) {} 465 | virtual ~DiaLoadCallBack() {} 466 | 467 | // IUnknown 468 | ULONG STDMETHODCALLTYPE AddRef() { m_RefCount++; return m_RefCount; } 469 | ULONG STDMETHODCALLTYPE Release() { if (--m_RefCount == 0) delete this; return m_RefCount; } 470 | HRESULT STDMETHODCALLTYPE QueryInterface( REFIID rid, void **ppUnk ) 471 | { 472 | if (ppUnk == NULL) 473 | return E_INVALIDARG; 474 | 475 | if (rid == IID_IDiaLoadCallback2) 476 | *ppUnk = (IDiaLoadCallback2 *)this; 477 | else if (rid == IID_IDiaLoadCallback) 478 | *ppUnk = (IDiaLoadCallback *)this; 479 | else if (rid == IID_IUnknown) 480 | *ppUnk = (IUnknown *)this; 481 | else 482 | *ppUnk = NULL; 483 | if ( *ppUnk != NULL ) 484 | { 485 | AddRef(); 486 | return S_OK; 487 | } 488 | return E_NOINTERFACE; 489 | } 490 | 491 | // Rest 492 | HRESULT STDMETHODCALLTYPE NotifyDebugDir(BOOL, DWORD, BYTE[]) { return S_OK; } 493 | HRESULT STDMETHODCALLTYPE NotifyOpenDBG(LPCOLESTR, HRESULT) { return S_OK; } 494 | 495 | HRESULT STDMETHODCALLTYPE NotifyOpenPDB(LPCOLESTR pdbPath, HRESULT resultCode) 496 | { 497 | if (resultCode == S_OK) 498 | wcscpy(m_Buffer, pdbPath); 499 | return S_OK; 500 | } 501 | 502 | HRESULT STDMETHODCALLTYPE RestrictRegistryAccess() { return S_OK; } 503 | HRESULT STDMETHODCALLTYPE RestrictSymbolServerAccess() { return S_OK; } 504 | HRESULT STDMETHODCALLTYPE RestrictOriginalPathAccess() { return S_OK; } 505 | HRESULT STDMETHODCALLTYPE RestrictReferencePathAccess() { return S_OK; } 506 | HRESULT STDMETHODCALLTYPE RestrictDBGAccess() { return S_OK; } 507 | HRESULT STDMETHODCALLTYPE RestrictSystemRootAccess() { return S_OK; } 508 | }; 509 | #endif // RTM_PLATFORM_WINDOWS 510 | 511 | inline const Module* addressGetModule(uintptr_t _resolver, uint64_t _address) 512 | { 513 | const Resolver* resolver = (Resolver*)_resolver; 514 | 515 | int32_t minIndex = 0; 516 | int32_t maxIndex = resolver->m_modules.size() - 1; 517 | 518 | while (minIndex <= maxIndex) 519 | { 520 | uint32_t curIndex = minIndex + (maxIndex - minIndex ) / 2; 521 | 522 | const Module& module = resolver->m_modules[curIndex]; 523 | if (module.m_module.checkAddress(_address)) 524 | return &module; 525 | 526 | if (_address < module.m_module.m_baseAddress) 527 | maxIndex = curIndex - 1; 528 | else 529 | minIndex = curIndex + 1; 530 | } 531 | 532 | return 0; 533 | } 534 | 535 | struct StringData 536 | { 537 | uint32_t m_length; 538 | char m_data[32 * 1024 - 4]; 539 | StringData() : m_length(0) {} 540 | }; 541 | 542 | void rustDemangleCallback(const char* data, size_t len, void* opaque) 543 | { 544 | StringData* str = (StringData*)opaque; 545 | memcpy(&str->m_data[str->m_length], data, len); 546 | } 547 | 548 | void symbolResolverGetFrame(uintptr_t _resolver, uint64_t _address, StackFrame* _frame) 549 | { 550 | rtm::strlCpy(_frame->m_moduleName, RTM_NUM_ELEMENTS(_frame->m_moduleName), "Unknown"); 551 | rtm::strlCpy(_frame->m_file, RTM_NUM_ELEMENTS(_frame->m_file), "Unknown"); 552 | rdebug::addressToString(_address, _frame->m_func); 553 | _frame->m_line = 0; 554 | 555 | Resolver* resolver = (Resolver*)_resolver; 556 | if (!resolver) 557 | return; 558 | 559 | const Module* module = addressGetModule(_resolver, _address); 560 | if (!module) 561 | return; 562 | 563 | #if RTM_PLATFORM_WINDOWS 564 | if (module->m_resolver->m_PDBFile && module->m_resolver->m_PDBFile->isLoaded()) 565 | { 566 | bool found = module->m_resolver->m_PDBFile->getSymbolByAddress(_address - module->m_module.m_baseAddress, *_frame); 567 | rtm::strlCpy(_frame->m_moduleName, RTM_NUM_ELEMENTS(_frame->m_moduleName), rtm::pathGetFileName(module->m_module.m_modulePath)); 568 | 569 | StringData str; 570 | if (rust_demangle_with_callback(_frame->m_func, 0, rustDemangleCallback, &str)) 571 | rtm::strlCpy(_frame->m_func, RTM_NUM_ELEMENTS(_frame->m_func), str.m_data); 572 | 573 | if (found) 574 | return; 575 | } 576 | #endif // RTM_PLATFORM_WINDOWS 577 | 578 | if (module->m_resolver->m_tc_addr2line && (module->m_resolver->m_tc_addr2line[0] != '\0')) 579 | { 580 | rtm::strlCpy(_frame->m_moduleName, RTM_NUM_ELEMENTS(_frame->m_moduleName), module->m_resolver->m_executableName); 581 | 582 | constexpr int MAX_CMDLINE_SIZE = 16384 + 8192; 583 | char cmdline[MAX_CMDLINE_SIZE]; 584 | #if RTM_PLATFORM_WINDOWS && RTM_COMPILER_MSVC 585 | sprintf_s(cmdline, MAX_CMDLINE_SIZE, module->m_resolver->m_tc_addr2line, _address - module->m_resolver->m_baseAddress4addr2Line); 586 | #else 587 | snprintf(cmdline, MAX_CMDLINE_SIZE, module->m_resolver->m_tc_addr2line, _address - module->m_resolver->m_baseAddress4addr2Line); 588 | #endif 589 | char* procOut = processGetOutputOf(cmdline, true); 590 | if (procOut && !rtm::strStr(procOut, "No such file")) 591 | { 592 | module->m_resolver->m_parseSym(&procOut[0], *_frame); 593 | rtm::pathCanonicalize(_frame->m_file); 594 | processReleaseOutput(procOut); 595 | } 596 | 597 | if (rtm::strCmp(_frame->m_func, "Unknown") != 0) 598 | if (rtm::strLen(module->m_resolver->m_tc_cppfilt) != 0) 599 | { 600 | #if RTM_PLATFORM_WINDOWS && RTM_COMPILER_MSVC 601 | sprintf_s(cmdline, MAX_CMDLINE_SIZE, "%s%s", module->m_resolver->m_tc_cppfilt, _frame->m_func); 602 | #else 603 | snprintf(cmdline, MAX_CMDLINE_SIZE, "%s%s", module->m_resolver->m_tc_cppfilt, _frame->m_func); 604 | #endif 605 | procOut = processGetOutputOf(cmdline, true); 606 | if (procOut) 607 | { 608 | size_t len = rtm::strLen(procOut); 609 | size_t s = 0; 610 | while (s < len) 611 | { 612 | if ((procOut[s] == '\r') || 613 | (procOut[s] == '\n')) 614 | { 615 | procOut[s] = 0; 616 | break; 617 | } 618 | ++s; 619 | } 620 | rtm::strlCpy(_frame->m_func, RTM_NUM_ELEMENTS(_frame->m_func), procOut); 621 | 622 | StringData str; 623 | if (rust_demangle_with_callback(_frame->m_func, 0, rustDemangleCallback, &str)) 624 | rtm::strlCpy(_frame->m_func, RTM_NUM_ELEMENTS(_frame->m_func), str.m_data); 625 | 626 | processReleaseOutput(procOut); 627 | } 628 | } 629 | } 630 | } 631 | 632 | 633 | uint64_t symbolResolverGetAddressID(uintptr_t _resolver, uint64_t _address) 634 | { 635 | Resolver* resolver = (Resolver*)_resolver; 636 | if (!resolver) 637 | return _address; 638 | 639 | const Module* module = addressGetModule(_resolver, _address); 640 | if (!module) 641 | return _address; 642 | 643 | if (module->m_isRTMdll) 644 | return 0; 645 | 646 | #if RTM_PLATFORM_WINDOWS 647 | if (module->m_resolver->m_PDBFile) 648 | { 649 | uint64_t id = module->m_resolver->m_PDBFile->getSymbolID(_address - module->m_module.m_baseAddress); 650 | return id + module->m_module.m_baseAddress; 651 | } 652 | #endif // RTM_PLATFORM_WINDOWS 653 | 654 | if (module->m_resolver->m_tc_nm && (rtm::strLen(module->m_resolver->m_tc_nm) != 0) && (!module->m_resolver->m_symbolMapInitialized)) 655 | { 656 | char cmdline[4096 * 2]; 657 | rtm::strlCpy(cmdline, RTM_NUM_ELEMENTS(cmdline), module->m_resolver->m_tc_nm); 658 | 659 | const char* procOut = processGetOutputOf(cmdline, true); 660 | 661 | if (procOut) 662 | { 663 | if (!rtm::strStr(procOut, "No such file")) 664 | module->m_resolver->m_parseSymMap(procOut, module->m_resolver->m_symbolMap); 665 | module->m_resolver->m_symbolMapInitialized = true; 666 | 667 | processReleaseOutput(procOut); 668 | } 669 | } 670 | 671 | rdebug::Symbol* sym = module->m_resolver->m_symbolMap.findSymbol(_address); 672 | if (sym) 673 | return (uint64_t)sym->m_nameHash; 674 | else 675 | return _address; 676 | } 677 | 678 | } // namespace rdebug 679 | -------------------------------------------------------------------------------- /src/symbols_map.cpp: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------// 2 | /// Copyright 2025 Milos Tosic. All Rights Reserved. /// 3 | /// License: http://www.opensource.org/licenses/BSD-2-Clause /// 4 | //--------------------------------------------------------------------------// 5 | 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | namespace rdebug { 12 | 13 | Symbol::Symbol() 14 | : m_offset(0) 15 | , m_size(0) 16 | , m_line(0) 17 | , m_nameHash(0) 18 | { 19 | } 20 | 21 | void SymbolMap::addSymbol(const Symbol& _sym) 22 | { 23 | if (!m_symbols.empty()) 24 | { 25 | Symbol& s = m_symbols[m_symbols.size()-1]; 26 | if (s.m_offset == _sym.m_offset) 27 | { 28 | m_symbols[m_symbols.size()-1] = _sym; 29 | return; 30 | } 31 | } 32 | 33 | m_symbols.push_back(_sym); 34 | } 35 | 36 | Symbol* SymbolMap::findSymbol(uint64_t _address) 37 | { 38 | size_t len = m_symbols.size(); 39 | if (!len) 40 | return 0; 41 | 42 | size_t sidx = 0; 43 | size_t eidx = len - 1; 44 | 45 | while (eidx > sidx) 46 | { 47 | size_t midx = (sidx + eidx) / 2; 48 | Symbol& sym = m_symbols[midx]; 49 | 50 | if (sym.m_offset < (int64_t)_address) 51 | sidx = midx; 52 | else 53 | eidx = midx; 54 | 55 | if (eidx-sidx == 1) 56 | { 57 | sym = m_symbols[sidx]; 58 | 59 | if (uint64_t(_address - sym.m_offset) >= sym.m_size) 60 | { 61 | sym = m_symbols[eidx]; 62 | if (uint64_t(_address - sym.m_offset) >= sym.m_size) 63 | return 0; 64 | } 65 | 66 | return &sym; 67 | } 68 | } 69 | return 0; 70 | } 71 | 72 | static inline bool sortSymbols(const Symbol& _s1, const Symbol& _s2) 73 | { 74 | return _s1.m_offset < _s2.m_offset; 75 | } 76 | 77 | static inline bool isInvalid(const Symbol& _sym) 78 | { 79 | return _sym.m_size == 0; 80 | } 81 | 82 | void SymbolMap::sort() 83 | { 84 | if (!m_symbols.size()) 85 | return; 86 | 87 | std::sort(m_symbols.begin(), m_symbols.end(), sortSymbols); 88 | std::vector::iterator it = m_symbols.begin(); 89 | std::vector::iterator end = m_symbols.end() - 1; 90 | 91 | while (it != end) 92 | { 93 | Symbol& sym = *it; 94 | if (sym.m_size == 0) 95 | { 96 | Symbol& nextSym = *(it+1); 97 | sym.m_size = nextSym.m_offset - 1; 98 | } 99 | ++it; 100 | } 101 | 102 | auto itInvalid = std::remove_if(m_symbols.begin(), m_symbols.end(), isInvalid); 103 | m_symbols.erase(itInvalid, m_symbols.end()); 104 | } 105 | 106 | } // namespace rdebug 107 | -------------------------------------------------------------------------------- /src/symbols_map.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------// 2 | /// Copyright 2025 Milos Tosic. All Rights Reserved. /// 3 | /// License: http://www.opensource.org/licenses/BSD-2-Clause /// 4 | //--------------------------------------------------------------------------// 5 | 6 | #ifndef RTM_RDEBUG_SYMBOLS_MAP_H 7 | #define RTM_RDEBUG_SYMBOLS_MAP_H 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | namespace rdebug { 14 | 15 | struct Symbol 16 | { 17 | int64_t m_offset; 18 | uint64_t m_size; 19 | uint32_t m_line; 20 | uint32_t m_nameHash; 21 | std::string m_file; 22 | std::string m_name; 23 | 24 | Symbol(); 25 | }; 26 | 27 | struct SymbolMap 28 | { 29 | std::vector m_symbols; 30 | 31 | void addSymbol(const Symbol& _sym); 32 | void sort(); 33 | Symbol* findSymbol(uint64_t _address); 34 | }; 35 | 36 | } // namespace rdebug 37 | 38 | #endif // RTM_RDEBUG_SYMBOLS_MAP_H 39 | -------------------------------------------------------------------------------- /src/symbols_types.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------// 2 | /// Copyright 2025 Milos Tosic. All Rights Reserved. /// 3 | /// License: http://www.opensource.org/licenses/BSD-2-Clause /// 4 | //--------------------------------------------------------------------------// 5 | 6 | #ifndef RTM_RQT_SYMBOLS_TYPES_H 7 | #define RTM_RQT_SYMBOLS_TYPES_H 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | class PDBFile; 14 | 15 | namespace rdebug { 16 | 17 | struct ResolveInfo 18 | { 19 | static const uint32_t SYM_SERVER_BUFFER_SIZE = 32 * 1024; 20 | static const uint32_t SCRATCH_MEM_SIZE = 64 * 1024; 21 | 22 | typedef void(*fnParseSymbol)(const char* _buff, StackFrame& _frame); 23 | typedef void(*fnParseSymbolMap)(const char* _buffer, SymbolMap& _symMap); 24 | 25 | char* m_scratch; 26 | uint32_t m_scratchPos; 27 | const char* m_tc_addr2line; 28 | const char* m_tc_nm; 29 | const char* m_tc_cppfilt; 30 | const char* m_executablePath; 31 | const char* m_executableName; 32 | #if RTM_PLATFORM_WINDOWS 33 | PDBFile* m_PDBFile; 34 | #endif // RTM_PLATFORM_WINDOWS 35 | 36 | fnParseSymbol m_parseSym; 37 | fnParseSymbolMap m_parseSymMap; 38 | uint64_t m_baseAddress4addr2Line; 39 | const char* m_symbolStore; 40 | SymbolMap m_symbolMap; 41 | bool m_symbolMapInitialized; 42 | const char* m_symbolCache; 43 | 44 | ResolveInfo(); 45 | ~ResolveInfo(); 46 | 47 | char* scratch(const char* _str); 48 | }; 49 | 50 | struct Module 51 | { 52 | ModuleInfo m_module; 53 | const char* m_moduleName; 54 | bool m_isRTMdll; 55 | ResolveInfo* m_resolver; 56 | 57 | Module() 58 | { 59 | m_module.m_baseAddress = 0; 60 | m_module.m_size = 0; 61 | m_module.m_modulePath[0] = '\0'; 62 | m_moduleName = 0; 63 | m_isRTMdll = false; 64 | } 65 | }; 66 | 67 | struct Resolver 68 | { 69 | static const uint32_t MAX_MODULES = 512; 70 | 71 | typedef rtm::FixedArray ModuleArray; 72 | 73 | ModuleArray m_modules; 74 | }; 75 | 76 | } // namespace rdebug 77 | 78 | #endif // RTM_RQT_SYMBOLS_TYPES_H 79 | --------------------------------------------------------------------------------