-
-
Notifications
You must be signed in to change notification settings - Fork 577
Add Rust trait-object devirtualization #6608
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
historicattle
wants to merge
1
commit into
rizinorg:dev
Choose a base branch
from
historicattle:rust
base: dev
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+1,814
−2
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,335 @@ | ||
| // SPDX-FileCopyrightText: 2026 historicattle <sirigere.naren@gmail.com> | ||
| // SPDX-License-Identifier: LGPL-3.0-only | ||
|
|
||
| #include "analysis_private.h" | ||
|
|
||
| #define RUST_VTABLE_HEADER_SLOTS 3 | ||
| #define RUST_MAX_TRAIT_METHOD_SLOTS 128 | ||
|
|
||
| typedef struct rust_trait_method_t { | ||
| ut64 addr; | ||
| ut64 vtable_offset; | ||
| char *name; | ||
| char *real_name; | ||
| char *impl_type; | ||
| char *trait_name; | ||
| } RustTraitMethod; | ||
|
|
||
| static void rust_trait_method_fini(void *e, void *user) { | ||
| RustTraitMethod *method = e; | ||
| RZ_FREE(method->name); | ||
| RZ_FREE(method->real_name); | ||
| RZ_FREE(method->impl_type); | ||
| RZ_FREE(method->trait_name); | ||
| } | ||
|
|
||
| static bool section_can_contain_rust_vtable(RzBinSection *section) { | ||
| if (!section || section->is_segment || RZ_STR_ISEMPTY(section->name)) { | ||
| return false; | ||
| } | ||
|
|
||
| return !strcmp(section->name, ".rodata") || | ||
| !strcmp(section->name, ".rdata") || | ||
| !strcmp(section->name, ".data.rel.ro") || | ||
| !strcmp(section->name, ".data.rel.ro.local") || | ||
| rz_str_endswith(section->name, "__const"); | ||
|
Comment on lines
+31
to
+35
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. does this cover also win binaries?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I havent tested it on PE |
||
| } | ||
|
|
||
| static bool addr_in_text_section(RzAnalysis *analysis, ut64 addr) { | ||
| RzBinSection *section = analysis->binb.get_vsect_at(analysis->binb.bin, addr); | ||
| return section && (section->perm & RZ_PERM_X); | ||
| } | ||
|
|
||
| static bool rust_vtable_header_is_valid(RVTableContext *context, ut64 addr) { | ||
| ut64 drop = UT64_MAX; | ||
| ut64 size = UT64_MAX; | ||
| ut64 align = UT64_MAX; | ||
| if (!context->read_addr(context->analysis, addr, &drop) || | ||
| !context->read_addr(context->analysis, addr + context->word_size, &size) || | ||
| !context->read_addr(context->analysis, addr + (2 * context->word_size), &align)) { | ||
| return false; | ||
| } | ||
| if (drop && !addr_in_text_section(context->analysis, drop)) { | ||
| return false; | ||
| } | ||
| if (rz_bits_count_ones_ut64(align) != 1) { | ||
| return false; | ||
| } | ||
| return !size || (!(size % align) && align <= size); | ||
| } | ||
|
|
||
| static char *demangled_name_at(RzAnalysis *analysis, ut64 addr) { | ||
| RzBinObject *obj = rz_bin_cur_object(analysis->binb.bin); | ||
| if (!obj) { | ||
| return NULL; | ||
| } | ||
|
|
||
| RzBinSymbol *sym = rz_bin_object_get_symbol_at(obj, addr, true); | ||
| if (sym && RZ_STR_ISNOTEMPTY(sym->dname)) { | ||
| return rz_str_dup(sym->dname); | ||
| } | ||
|
|
||
| if (sym && RZ_STR_ISNOTEMPTY(sym->name)) { | ||
| char *demangled = NULL; | ||
| if (analysis->binb.demangle) { | ||
| demangled = analysis->binb.demangle(analysis->binb.bin, "rust", sym->name); | ||
| } | ||
| if (demangled) { | ||
| return demangled; | ||
| } | ||
| RZ_FREE(demangled); | ||
| } | ||
| return NULL; | ||
| } | ||
|
|
||
| static char *dup_trimmed_range(const char *start, const char *end) { | ||
| while (start < end && IS_WHITESPACE(*start)) { | ||
| start++; | ||
| } | ||
| while (end > start && IS_WHITESPACE(end[-1])) { | ||
| end--; | ||
| } | ||
| if (start < end) { | ||
| return rz_str_ndup(start, (int)(end - start)); | ||
| } | ||
| return NULL; | ||
| } | ||
|
|
||
| static bool parse_demangled_trait_method(const char *demangled, RustTraitMethod *method) { | ||
| if (RZ_STR_ISEMPTY(demangled) || demangled[0] != '<') { | ||
| return false; | ||
| } | ||
|
|
||
| const char *inside = demangled + 1; | ||
| const char *impl_end = NULL; | ||
| const char *trait_start = NULL; | ||
| const char *trait_end = NULL; | ||
| int depth = 0; | ||
|
|
||
| for (const char *p = inside; *p; p++) { | ||
| if (*p == '<') { | ||
| depth++; | ||
| continue; | ||
| } | ||
| if (*p == '>') { | ||
| if (!depth) { | ||
| if (strncmp(p, ">::", 3)) { | ||
| return false; | ||
| } | ||
| trait_end = p; | ||
| break; | ||
| } | ||
| depth--; | ||
| continue; | ||
| } | ||
| if (!depth && !trait_start && !strncmp(p, " as ", 4)) { | ||
| impl_end = p; | ||
| trait_start = p + 4; | ||
| p += 3; | ||
| } | ||
| } | ||
|
|
||
| if (!impl_end || !trait_start || !trait_end || trait_start >= trait_end) { | ||
| return false; | ||
| } | ||
|
|
||
| const char *suffix = trait_end + 3; | ||
| if (RZ_STR_ISEMPTY(suffix)) { | ||
| return false; | ||
| } | ||
| const char *last_sep = rz_str_rstr(suffix, "::"); | ||
| method->impl_type = dup_trimmed_range(inside, impl_end); | ||
| method->trait_name = dup_trimmed_range(trait_start, trait_end); | ||
| if (last_sep) { | ||
| method->name = rz_str_dup(last_sep + 2); | ||
| } else { | ||
| method->name = rz_str_dup(suffix); | ||
| } | ||
|
|
||
| return method->impl_type && method->trait_name && method->name; | ||
| } | ||
|
|
||
| static bool parse_rust_method_name(RzAnalysis *analysis, ut64 addr, RustTraitMethod *method) { | ||
| char *name = demangled_name_at(analysis, addr); | ||
| if (!name) { | ||
| return false; | ||
| } | ||
|
|
||
| bool ok = parse_demangled_trait_method(name, method); | ||
| if (ok) { | ||
| method->real_name = name; | ||
| } else { | ||
| rust_trait_method_fini(method, NULL); | ||
| RZ_FREE(name); | ||
| } | ||
| return ok; | ||
| } | ||
|
|
||
| static RZ_OWN RzVector /*<RustTraitMethod>*/ *parse_rust_vtable(RVTableContext *context, ut64 vtable_addr) { | ||
| if (!rust_vtable_header_is_valid(context, vtable_addr)) { | ||
| return NULL; | ||
| } | ||
|
|
||
| RzVector *methods = rz_vector_new(sizeof(RustTraitMethod), rust_trait_method_fini, NULL); | ||
| if (!methods) { | ||
| return NULL; | ||
| } | ||
|
|
||
| ut64 slot_addr = vtable_addr + (RUST_VTABLE_HEADER_SLOTS * context->word_size); | ||
| for (ut64 i = 0; i < RUST_MAX_TRAIT_METHOD_SLOTS; i++, slot_addr += context->word_size) { | ||
| ut64 method_addr = UT64_MAX; | ||
| if (!context->read_addr(context->analysis, slot_addr, &method_addr)) { | ||
| break; | ||
| } | ||
|
|
||
| bool next_header = rust_vtable_header_is_valid(context, slot_addr); | ||
| if (method_addr == 0) { | ||
| break; | ||
| } | ||
|
|
||
| if (!addr_in_text_section(context->analysis, method_addr)) { | ||
| break; | ||
| } | ||
|
|
||
| RustTraitMethod method = { | ||
| .addr = method_addr, | ||
| .vtable_offset = slot_addr - vtable_addr, | ||
| }; | ||
| if (!parse_rust_method_name(context->analysis, method_addr, &method)) { | ||
| if (next_header) { | ||
| break; | ||
| } | ||
| continue; | ||
| } | ||
|
|
||
| if (!rz_vector_push(methods, &method)) { | ||
| rust_trait_method_fini(&method, NULL); | ||
| break; | ||
| } | ||
| } | ||
|
|
||
| if (rz_vector_empty(methods)) { | ||
| rz_vector_free(methods); | ||
| return NULL; | ||
| } | ||
| return methods; | ||
| } | ||
|
|
||
| static char *class_name_from_methods(RzVector /*<RustTraitMethod>*/ *methods) { | ||
| RustTraitMethod *best = NULL; | ||
| ut64 best_count = 0; | ||
| RustTraitMethod *candidate; | ||
| rz_vector_foreach (methods, candidate) { | ||
| ut64 count = 0; | ||
| RustTraitMethod *method; | ||
| rz_vector_foreach (methods, method) { | ||
| if (method->trait_name && !strcmp(candidate->trait_name, method->trait_name)) { | ||
| count++; | ||
| } | ||
| } | ||
| if (count > best_count) { | ||
| best = candidate; | ||
| best_count = count; | ||
| } | ||
| } | ||
| if (best) { | ||
| return rz_str_newf("impl %s as %s", best->impl_type, best->trait_name); | ||
| } | ||
| return NULL; | ||
| } | ||
|
|
||
| static void apply_rust_vtable(RVTableContext *context, ut64 vtable_addr, RzVector /*<RustTraitMethod>*/ *methods) { | ||
| RzAnalysis *analysis = context->analysis; | ||
| char *class_name = class_name_from_methods(methods); | ||
| if (!class_name) { | ||
| return; | ||
| } | ||
|
|
||
| if (!rz_analysis_class_exists(analysis, class_name)) { | ||
| rz_analysis_class_create(analysis, class_name); | ||
| } | ||
|
|
||
| RustTraitMethod *last_method = rz_vector_index_ptr(methods, rz_vector_len(methods) - 1); | ||
| RzAnalysisVTable vtable = { | ||
| .addr = vtable_addr, | ||
| .offset = 0, | ||
| .size = last_method->vtable_offset + context->word_size, | ||
| }; | ||
|
|
||
| rz_analysis_class_vtable_set(analysis, class_name, &vtable); | ||
| rz_analysis_class_vtable_fini(&vtable); | ||
| RustTraitMethod *rmethod; | ||
| rz_vector_foreach (methods, rmethod) { | ||
| if (rz_analysis_class_method_exists_by_addr(analysis, class_name, rmethod->addr)) { | ||
| continue; | ||
| } | ||
| RzAnalysisMethod method = { | ||
| .name = rz_str_dup(rmethod->name), | ||
| .real_name = rz_str_dup(rmethod->real_name), | ||
| .addr = rmethod->addr, | ||
| .vtable_offset = rmethod->vtable_offset, | ||
| .method_type = RZ_ANALYSIS_CLASS_METHOD_VIRTUAL, | ||
| }; | ||
| rz_analysis_class_method_set(analysis, class_name, &method); | ||
| rz_analysis_class_method_fini(&method); | ||
| } | ||
| RZ_FREE(class_name); | ||
| } | ||
|
|
||
| /** | ||
| * Scan the binary for Rust trait-object vtables and recover class metadata. | ||
| * | ||
| * This function iterates through read-only sections of the binary, attempting to identify | ||
| * and parse Rust trait-object vtables based on their memory layout and symbol metadata. | ||
| */ | ||
| RZ_API void rz_analysis_rtti_rust(RZ_NONNULL RzAnalysis *analysis) { | ||
| RVTableContext context; | ||
| if (!rz_analysis_vtable_begin(analysis, &context)) { | ||
| return; | ||
| } | ||
|
|
||
| RzBinObject *obj = rz_bin_cur_object(analysis->binb.bin); | ||
| const RzPVector *sections = NULL; | ||
| if (obj) { | ||
| sections = analysis->binb.get_sections(obj); | ||
| } | ||
| if (!sections) { | ||
| return; | ||
| } | ||
|
|
||
| rz_cons_break_push(NULL, NULL); | ||
| void **iter; | ||
| RzBinSection *section; | ||
| rz_pvector_foreach (sections, iter) { | ||
| if (rz_cons_is_breaked()) { | ||
| break; | ||
| } | ||
| section = *iter; | ||
| if (!section_can_contain_rust_vtable(section)) { | ||
| continue; | ||
| } | ||
| if (section->vsize < RUST_VTABLE_HEADER_SLOTS * context.word_size) { | ||
| continue; | ||
| } | ||
|
|
||
| ut64 addr = RZ_ROUND(section->vaddr, context.word_size); | ||
| ut64 end = section->vaddr + section->vsize - (RUST_VTABLE_HEADER_SLOTS * context.word_size); | ||
| while (addr <= end) { | ||
| if (rz_cons_is_breaked()) { | ||
| break; | ||
| } | ||
| if (!analysis->iob.is_valid_offset(analysis->iob.io, addr, 0)) { | ||
| addr += context.word_size; | ||
| continue; | ||
| } | ||
|
|
||
| RzVector *methods = parse_rust_vtable(&context, addr); | ||
| if (methods) { | ||
| apply_rust_vtable(&context, addr, methods); | ||
| rz_vector_free(methods); | ||
| } | ||
| addr += context.word_size; | ||
| } | ||
| } | ||
| rz_cons_break_pop(); | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if you have resource links (official docs, sources, etc..) for this, please add a comment to it!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
RUST_VTABLE_HEADER_SLOTSjust refers to drop, size, align entries in the headerRUST_MAX_TRAIT_METHOD_SLOTSis a defensive bound i added, it is not a rust ABI limit.Im sorry, i will remove this
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no, i wasnt talking about this.
i was actually talking to just add some references on how the rtti for rust works as a doxygen comment above those lines
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
having a limit is ok