diff --git a/librz/arch/meson.build b/librz/arch/meson.build index 7d86bee3b17..29a57b95734 100644 --- a/librz/arch/meson.build +++ b/librz/arch/meson.build @@ -475,6 +475,7 @@ arch_common_sources = [ 'rtti_msvc.c', 'serialize_analysis.c', 'similarity.c', + 'rtti_rust.c', 'swift_rtti.c', 'switch.c', 'types.c', diff --git a/librz/arch/rtti.c b/librz/arch/rtti.c index 627eb32e526..1de007e642e 100644 --- a/librz/arch/rtti.c +++ b/librz/arch/rtti.c @@ -90,6 +90,9 @@ RZ_API void rz_analysis_rtti_recover_all(RzAnalysis *analysis) { return; } switch (bin_obj->lang) { + case RZ_BIN_LANGUAGE_RUST: + rz_analysis_rtti_rust(analysis); + break; case RZ_BIN_LANGUAGE_SWIFT: rz_analysis_rtti_swift(analysis); break; diff --git a/librz/arch/rtti_rust.c b/librz/arch/rtti_rust.c new file mode 100644 index 00000000000..6cee586c7eb --- /dev/null +++ b/librz/arch/rtti_rust.c @@ -0,0 +1,335 @@ +// SPDX-FileCopyrightText: 2026 historicattle +// 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"); +} + +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 /**/ *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 /**/ *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 /**/ *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(); +} diff --git a/librz/core/cmd/cmd_analysis.c b/librz/core/cmd/cmd_analysis.c index 1eca5574951..f0e9730b68b 100644 --- a/librz/core/cmd/cmd_analysis.c +++ b/librz/core/cmd/cmd_analysis.c @@ -6276,8 +6276,11 @@ RZ_IPI RzCmdStatus rz_analysis_devirtualize_handler(RzCore *core, int argc, cons case RZ_BIN_LANGUAGE_SWIFT: rz_core_analysis_devirtualize_objc_methods(core); break; + case RZ_BIN_LANGUAGE_RUST: + rz_core_analysis_devirtualize_rust_methods(core); + break; default: - RZ_LOG_ERROR("Devirtualization is only supported for C++ and Objective-C binaries.\n"); + RZ_LOG_ERROR("Devirtualization is only supported for C++, Objective-C, and Rust binaries.\n"); break; } return RZ_CMD_STATUS_OK; diff --git a/librz/core/core_private.h b/librz/core/core_private.h index 71580dbefa1..f74cc1f274b 100644 --- a/librz/core/core_private.h +++ b/librz/core/core_private.h @@ -47,6 +47,7 @@ RZ_IPI void rz_core_il_colorize_body(RZ_NONNULL RzConsContext *ctx, RZ_NULLABLE RZ_IPI void rz_core_analysis_devirtualize_cxx_methods(RZ_NULLABLE RzCore *core); RZ_IPI void rz_core_analysis_devirtualize_objc_methods(RZ_NULLABLE RzCore *core); +RZ_IPI void rz_core_analysis_devirtualize_rust_methods(RZ_NULLABLE RzCore *core); RZ_IPI void rz_core_analysis_virtual_xrefs_print(RZ_NONNULL RzCore *core, RZ_NONNULL const char *vfunc); RZ_IPI void rz_core_analysis_virtual_xrefs_print_table(RZ_NONNULL RzCore *core, RZ_NONNULL const char *vfunc, RZ_NONNULL RzTable *table); diff --git a/librz/core/devirtualize_rust.c b/librz/core/devirtualize_rust.c new file mode 100644 index 00000000000..97b6410347c --- /dev/null +++ b/librz/core/devirtualize_rust.c @@ -0,0 +1,1196 @@ +// SPDX-FileCopyrightText: 2026 historicattle +// SPDX-License-Identifier: LGPL-3.0-only + +#include +#include +#include +#include "core_private.h" + +#define RUST_VTABLE_HEADER_SLOTS 3 +#define RUST_MAX_TRAIT_METHOD_SLOTS 128 +#define RUST_TYPE_ID_BYTES 16 +#define RUST_MAX_CALL_ARGS 8 +#define RUST_TRACK_MEM_ADDR 0x10000000 +#define RUST_TRACK_MEM_SIZE 0x50000 +#define RUST_STACK_PTR (RUST_TRACK_MEM_ADDR + (RUST_TRACK_MEM_SIZE / 2)) +#define RUST_SCRATCH_HEAP_ADDR (RUST_TRACK_MEM_ADDR + 0x30000) +#define RUST_SCRATCH_HEAP_SIZE 0x20000 +#define RUST_SCRATCH_ALLOC_STEP 0x100 + +typedef struct rust_any_vtable_t { + ut64 vtable_addr; + ut64 type_id_method; + ut64 type_id_low; + ut64 type_id_high; + bool has_type_id; + bool has_type_id_high; + char *concrete_type; +} RustAnyVTable; + +typedef struct rust_arg_regs_t { + const char *regs[RUST_MAX_CALL_ARGS]; + ut64 count; +} RustArgRegs; + +typedef struct rust_call_seed_t { + const char *regs[RUST_MAX_CALL_ARGS]; + ut64 values[RUST_MAX_CALL_ARGS]; + bool has_value[RUST_MAX_CALL_ARGS]; + ut64 count; +} RustCallSeed; + +static void rust_any_vtable_fini(void *e, void *user) { + RustAnyVTable *vtable = e; + RZ_FREE(vtable->concrete_type); +} + +static ut64 ptr_size(RzCore *core) { + return rz_asm_get_bits(core->rasm) == 64 ? 8 : 4; +} + +static bool read_ptr(RzCore *core, ut64 addr, ut64 *out) { + return rz_io_read_i(core->io, addr, out, ptr_size(core), rz_asm_is_big_endian_set(core->rasm)); +} + +static bool addr_is_executable(RzCore *core, ut64 addr) { + RzBinObject *obj = rz_bin_cur_object(core->bin); + if (!obj) { + return false; + } + RzBinSection *section = rz_bin_get_section_at(obj, addr, true); + return section && (section->perm & RZ_PERM_X); +} + +static RZ_OWN ut8 *read_mapped_range(RzCore *core, ut64 start, ut64 end) { + if (end <= start) { + return NULL; + } + ut8 *bytes = malloc(end - start); + if (!bytes) { + return NULL; + } + if (!rz_io_read_at_mapped(core->io, start, bytes, end - start)) { + RZ_FREE(bytes); + return NULL; + } + return bytes; +} + +static RZ_OWN char *get_method_name(RzCore *core, ut64 addr) { + if (!addr || addr == UT64_MAX || !rz_io_is_valid_offset(core->io, addr, RZ_PERM_R)) { + return NULL; + } + RzAnalysisFunction *function = rz_analysis_get_fcn_in(core->analysis, addr, RZ_ANALYSIS_FCN_TYPE_NULL); + if (function && RZ_STR_ISNOTEMPTY(function->name)) { + return rz_str_dup(function->name); + } + RzFlagItem *flag = rz_flag_get_i(core->flags, addr); + if (flag) { + return rz_str_dup(flag->name); + } + return NULL; +} + +static bool is_any_trait_name(const char *trait_name) { + if (RZ_STR_ISEMPTY(trait_name)) { + return false; + } + + return (rz_str_startswith(trait_name, "core") || rz_str_startswith(trait_name, "std")) && + (rz_str_endswith(trait_name, "any::Any") || rz_str_endswith(trait_name, "any::Any_")); +} + +static const char *rust_trait_name_from_class_name(const char *class_name) { + if (RZ_STR_ISEMPTY(class_name)) { + return NULL; + } + if (rz_str_startswith(class_name, "impl ")) { + const char *as = rz_str_rstr(class_name, " as "); + if (!as) { + return NULL; + } + const char *trait_name = as + strlen(" as "); + if (RZ_STR_ISNOTEMPTY(trait_name)) { + return trait_name; + } + return NULL; + } + + const char *as = rz_str_rstr(class_name, "_as_"); + if (!as || as == class_name) { + return NULL; + } + const char *trait_name = as + strlen("_as_"); + if (RZ_STR_ISNOTEMPTY(trait_name)) { + return trait_name; + } + return NULL; +} + +static bool is_any_class_name(const char *class_name) { + return is_any_trait_name(rust_trait_name_from_class_name(class_name)); +} + +static RZ_OWN char *concrete_type_from_any_class_name(const char *class_name) { + if (!is_any_class_name(class_name)) { + return NULL; + } + if (rz_str_startswith(class_name, "impl ")) { + const char *impl_type = class_name + strlen("impl "); + const char *as = rz_str_rstr(class_name, " as "); + if (as > impl_type) { + return rz_str_ndup(impl_type, (as - impl_type)); + } + return NULL; + } + + const char *as = rz_str_rstr(class_name, "_as_"); + if (as > class_name) { + return rz_str_ndup(class_name, (as - class_name)); + } + return NULL; +} + +static bool is_rust_vtable(RzCore *core, ut64 vtable_addr) { + ut64 drop = UT64_MAX; + ut64 size = UT64_MAX; + ut64 align = UT64_MAX; + ut64 psize = ptr_size(core); + if (!read_ptr(core, vtable_addr, &drop) || !read_ptr(core, vtable_addr + psize, &size) || + !read_ptr(core, vtable_addr + (2 * psize), &align)) { + return false; + } + + if (drop && !addr_is_executable(core, drop)) { + return false; + } + if (rz_bits_count_ones_ut64(align) != 1) { + return false; + } + return !size || (!(size % align) && align <= size); +} + +static bool rust_type_id_push_imm(ut64 values[2], ut64 *count, ut64 value) { + if (value == UT64_MAX) { + return false; + } + + for (ut64 i = 0; i < *count; i++) { + if (values[i] == value) { + return true; + } + } + + if (*count >= 2) { + return false; + } + + values[(*count)++] = value; + return true; +} + +static bool rust_type_id_push_data(RzCore *core, ut64 values[2], ut64 *count, ut64 addr) { + ut8 buf[RUST_TYPE_ID_BYTES] = { 0 }; + if (!addr || addr == UT64_MAX || !rz_io_read_at_mapped(core->io, addr, buf, sizeof(buf))) { + return false; + } + + bool big_endian = rz_asm_is_big_endian_set(core->rasm); + values[0] = rz_read_ble64(buf, big_endian); + values[1] = rz_read_ble64(buf + 8, big_endian); + *count = 2; + return true; +} + +static bool rust_type_id_from_method(RzCore *core, ut64 method_addr, RZ_OUT ut64 *low, RZ_OUT ut64 *high, RZ_OUT bool *has_high) { + RzAnalysisFunction *function = rz_analysis_get_fcn_in(core->analysis, method_addr, RZ_ANALYSIS_FCN_TYPE_NULL); + if (!function) { + return false; + } + + ut64 start = function->addr; + ut64 end = rz_analysis_function_max_addr(function); + if (end <= start) { + return false; + } + + ut8 *bytes = read_mapped_range(core, start, end); + if (!bytes) { + return false; + } + + ut64 values[2] = { 0 }; + ut64 count = 0; + ut64 offset = 0; + RzAnalysisOp *op = rz_analysis_op_new(); + if (!op) { + RZ_FREE(bytes); + return false; + } + + while (start < end && count < 2) { + if (rz_analysis_op(core->analysis, op, start, bytes + offset, end - start, RZ_ANALYSIS_OP_MASK_ALL) <= 0 || op->size < 1) { + break; + } + ut32 type = op->type & RZ_ANALYSIS_OP_TYPE_MASK; + if (op->ptr && op->ptr != UT64_MAX && op->refptr >= RUST_TYPE_ID_BYTES && + rust_type_id_push_data(core, values, &count, op->ptr)) { + rz_analysis_op_fini(op); + break; + } + if (type == RZ_ANALYSIS_OP_TYPE_MOV || type == RZ_ANALYSIS_OP_TYPE_CMOV) { + if (op->val != UT64_MAX) { + rust_type_id_push_imm(values, &count, op->val); + } + for (ut64 i = 0; i < RZ_ARRAY_SIZE(op->analysis_vals); i++) { + if (op->analysis_vals[i].imm != ST64_MAX && op->analysis_vals[i].imm) { + rust_type_id_push_imm(values, &count, op->analysis_vals[i].imm); + } + } + } + if (type == RZ_ANALYSIS_OP_TYPE_RET) { + rz_analysis_op_fini(op); + break; + } + start += op->size; + offset += op->size; + rz_analysis_op_fini(op); + } + + rz_analysis_op_free(op); + RZ_FREE(bytes); + if (!count) { + return false; + } + + *low = values[0]; + *high = 0; + if (count > 1) { + *high = values[1]; + } + *has_high = count > 1; + return true; +} + +static bool rust_method_is_type_id(RzAnalysisMethod *method) { + if (!method) { + return false; + } + if (rz_str_startswith(method->name, "type_id") || rz_str_startswith(method->name, "get_type_id")) { + return true; + } + return method->real_name && (strstr(method->real_name, "::type_id") || strstr(method->real_name, "::get_type_id")); +} + +static RzAnalysisMethod *rust_any_type_id_method(RzCore *core, RzVector /**/ *methods) { + if (!methods) { + return NULL; + } + + RzAnalysisMethod *method; + rz_vector_foreach (methods, method) { + if (rust_method_is_type_id(method)) { + return method; + } + } + return NULL; +} + +static bool push_any_vtable_from_class(RzCore *core, RzVector /**/ *any_vtables, const char *class_name, RzAnalysisVTable *vtable, RzAnalysisMethod *type_id_method) { + if (!type_id_method || !type_id_method->addr || type_id_method->addr == UT64_MAX || + (vtable && (!vtable->addr || vtable->addr == UT64_MAX))) { + return false; + } + + RustAnyVTable any = { + .vtable_addr = UT64_MAX, + .type_id_method = type_id_method->addr, + .concrete_type = concrete_type_from_any_class_name(class_name), + }; + if (vtable) { + any.vtable_addr = vtable->addr; + } + + if (!any.concrete_type) { + return false; + } + + any.has_type_id = rust_type_id_from_method(core, any.type_id_method, &any.type_id_low, &any.type_id_high, &any.has_type_id_high); + if (!rz_vector_push(any_vtables, &any)) { + rust_any_vtable_fini(&any, NULL); + return false; + } + return true; +} + +static RZ_OWN RzVector /**/ *rust_any_vtables_from_classes(RzCore *core) { + RzPVector *classes = rz_analysis_class_get_all(core->analysis, false); + if (!classes) { + return NULL; + } + + RzVector *any_vtables = rz_vector_new(sizeof(RustAnyVTable), rust_any_vtable_fini, NULL); + if (!any_vtables) { + rz_pvector_free(classes); + return NULL; + } + + void **iter; + rz_pvector_foreach (classes, iter) { + SdbKv *kv = *iter; + const char *class_name = sdbkv_key(kv); + if (!is_any_class_name(class_name)) { + continue; + } + + RzVector *methods = rz_analysis_class_method_get_all(core->analysis, class_name); + RzAnalysisMethod *type_id_method = rust_any_type_id_method(core, methods); + if (!type_id_method) { + rz_vector_free(methods); + continue; + } + + RzVector *vtables = rz_analysis_class_vtable_get_all(core->analysis, class_name); + if (!vtables || rz_vector_empty(vtables)) { + push_any_vtable_from_class(core, any_vtables, class_name, NULL, type_id_method); + rz_vector_free(vtables); + rz_vector_free(methods); + continue; + } + + RzAnalysisVTable *vtable; + rz_vector_foreach (vtables, vtable) { + push_any_vtable_from_class(core, any_vtables, class_name, vtable, type_id_method); + } + rz_vector_free(vtables); + rz_vector_free(methods); + } + + rz_pvector_free(classes); + if (rz_vector_empty(any_vtables)) { + rz_vector_free(any_vtables); + return NULL; + } + return any_vtables; +} + +static RustAnyVTable *any_vtable_by_addr(RzVector /**/ *vtables, ut64 vtable_addr) { + if (!vtables || !vtable_addr || vtable_addr == UT64_MAX) { + return NULL; + } + + RustAnyVTable *any; + rz_vector_foreach (vtables, any) { + if (any->vtable_addr == vtable_addr) { + return any; + } + } + return NULL; +} + +static RustAnyVTable *any_vtable_by_type_id_method(RzVector /**/ *vtables, ut64 method_addr) { + if (!vtables) { + return NULL; + } + + RustAnyVTable *any; + rz_vector_foreach (vtables, any) { + if (any->type_id_method == method_addr) { + return any; + } + } + return NULL; +} + +static bool type_id_values_match(ut64 low, ut64 high, bool has_high, RustAnyVTable *any) { + if (!any || !any->has_type_id) { + return false; + } + if (has_high && any->has_type_id_high) { + return low == any->type_id_low && high == any->type_id_high; + } + + return low == any->type_id_low; +} + +static RustAnyVTable *any_vtable_by_type_id_values(RzVector /**/ *vtables, ut64 low, ut64 high, bool has_high) { + if (!vtables) { + return NULL; + } + + RustAnyVTable *any; + rz_vector_foreach (vtables, any) { + if (type_id_values_match(low, high, has_high, any)) { + return any; + } + } + return NULL; +} + +static bool resolve_slot(RzCore *core, ut64 vtable_addr, ut64 slot_addr, RZ_OUT ut64 *target) { + ut64 psize = ptr_size(core); + if (slot_addr < vtable_addr) { + return false; + } + + ut64 method_offset = slot_addr - vtable_addr; + if (method_offset < RUST_VTABLE_HEADER_SLOTS * psize || method_offset % psize) { + return false; + } + + ut64 method_slot = (method_offset / psize) - RUST_VTABLE_HEADER_SLOTS; + if (method_slot >= RUST_MAX_TRAIT_METHOD_SLOTS || !is_rust_vtable(core, vtable_addr)) { + return false; + } + + return read_ptr(core, slot_addr, target) && *target && *target != UT64_MAX && addr_is_executable(core, *target); +} + +static ut64 il_value_to_ut64(RZ_NULLABLE RzILVal *val) { + if (!val) { + return UT64_MAX; + } + + RzBitVector *bv = rz_il_value_to_bv(val); + if (!bv) { + return UT64_MAX; + } + + ut64 ret = rz_bv_to_ut64(bv); + rz_bv_free(bv); + return ret; +} + +static ut64 get_reg_value(RzAnalysis *analysis, const char *reg_name) { + if (!reg_name) { + return UT64_MAX; + } + + RzAnalysisILVM *vm = rz_analysis_get_il_vm(analysis); + if (!vm) { + return UT64_MAX; + } + + RzILVal *il_reg = rz_il_vm_get_var_value(vm->vm, RZ_IL_VAR_KIND_GLOBAL, reg_name); + return il_value_to_ut64(il_reg); +} + +static bool is_real_reg_name(RzCore *core, const char *reg_name) { + if (RZ_STR_ISEMPTY(reg_name) || !strcmp(reg_name, "none")) { + return false; + } + + RzReg *reg = rz_analysis_get_reg(core->analysis); + return reg && rz_reg_get(reg, reg_name, RZ_REG_TYPE_ANY); +} + +static void advance_il_pc(RzCore *core, ut64 addr) { + RzReg *reg = rz_analysis_get_reg(core->analysis); + if (reg) { + rz_reg_set_value_by_role(reg, RZ_REG_NAME_PC, addr); + } +} + +static bool analysis_value_is_mem(RzAnalysisValue *value) { + return value && value->memref > 0; +} + +static bool reg_name_is_pc(RzCore *core, const char *reg_name) { + RzReg *reg = rz_analysis_get_reg(core->analysis); + if (!reg) { + return false; + } + const char *pc = rz_reg_get_name(reg, RZ_REG_NAME_PC); + return pc && !strcmp(reg_name, pc); +} + +static bool analysis_value_addr(RzCore *core, RZ_NULLABLE const RzAnalysisOp *op, RzAnalysisValue *value, RZ_OUT ut64 *addr) { + if (!analysis_value_is_mem(value)) { + return false; + } + + ut64 result = value->base; + const char *base_reg = NULL; + if (value && value->reg) { + base_reg = value->reg->name; + } + if (base_reg) { + ut64 base = UT64_MAX; + if (op && reg_name_is_pc(core, base_reg)) { + base = op->addr + op->size; + } else { + base = get_reg_value(core->analysis, base_reg); + } + if (!base || base == UT64_MAX) { + return false; + } + result += base; + } + + if (value->regdelta) { + ut64 index = get_reg_value(core->analysis, value->regdelta->name); + if (index == UT64_MAX) { + return false; + } + ut64 scale = value->mul; + if (!scale) { + scale = 1; + } + result += index * scale; + } + + result += value->delta; + if (!result) { + return false; + } + *addr = result; + return true; +} + +static bool value_mem_access_is_safe(RzCore *core, RzAnalysisOp *op, RzAnalysisValue *value, bool write) { + if (!analysis_value_is_mem(value)) { + return true; + } + + ut64 addr = UT64_MAX; + if (!analysis_value_addr(core, op, value, &addr)) { + return false; + } + + ut64 access_size = 1; + if (value->memref > 0) { + access_size = value->memref; + } + if (access_size && addr >= RUST_TRACK_MEM_ADDR) { + ut64 offset = addr - RUST_TRACK_MEM_ADDR; + if (offset < RUST_TRACK_MEM_SIZE && access_size <= RUST_TRACK_MEM_SIZE - offset) { + return true; + } + } + + if (write || !rz_io_is_valid_offset(core->io, addr, RZ_PERM_R)) { + return false; + } + + RzBinObject *obj = rz_bin_cur_object(core->bin); + RzBinSection *section = NULL; + if (obj) { + section = rz_bin_get_section_at(obj, addr, true); + } + return section && !(section->perm & RZ_PERM_X); +} + +static bool op_memory_access_is_safe(RzCore *core, RzAnalysisOp *op) { + if ((op->type & RZ_ANALYSIS_OP_TYPE_MASK) == RZ_ANALYSIS_OP_TYPE_LEA) { + return true; + } + if (op->dst && analysis_value_is_mem(op->dst) && !value_mem_access_is_safe(core, op, op->dst, true)) { + return false; + } + + for (ut64 i = 0; i < RZ_ARRAY_SIZE(op->src); i++) { + if (op->src[i] && analysis_value_is_mem(op->src[i]) && + !value_mem_access_is_safe(core, op, op->src[i], false)) { + return false; + } + } + return true; +} + +static const char *op_dst_reg_name(RzAnalysisOp *op) { + if (!op->dst || op->dst->type != RZ_ANALYSIS_VAL_REG || !op->dst->reg) { + return NULL; + } + return op->dst->reg->name; +} + +static void get_arg_regs(RzCore *core, RzAnalysisFunction *function, RZ_OUT RustArgRegs *args) { + const char *cc = rz_analysis_cc_default(core->analysis); + for (ut64 i = 0; i < RUST_MAX_CALL_ARGS; i++) { + const char *reg = rz_analysis_cc_arg(core->analysis, cc, (int)i); + if (!reg) { + break; + } + if (!is_real_reg_name(core, reg)) { + break; + } + args->regs[args->count++] = reg; + } +} + +static bool seed_contains_rust_vtable(RzCore *core, RustCallSeed *seed) { + for (ut64 i = 0; i < seed->count; i++) { + if (seed->has_value[i] && is_rust_vtable(core, seed->values[i])) { + return true; + } + } + return false; +} + +static bool seed_equals(RustCallSeed *a, RustCallSeed *b) { + if (a->count != b->count) { + return false; + } + + for (ut64 i = 0; i < a->count; i++) { + if (a->has_value[i] != b->has_value[i]) { + return false; + } + if (a->has_value[i] && a->values[i] != b->values[i]) { + return false; + } + } + return true; +} + +static void push_unique_seed(RzCore *core, RzVector /**/ *seeds, RustCallSeed *seed) { + if (!seed_contains_rust_vtable(core, seed)) { + return; + } + + RustCallSeed *it; + rz_vector_foreach (seeds, it) { + if (seed_equals(it, seed)) { + return; + } + } + rz_vector_push(seeds, seed); +} + +static bool is_vtable_slot_disp(RzCore *core, ut64 disp) { + ut64 psize = ptr_size(core); + ut64 min = RUST_VTABLE_HEADER_SLOTS * psize; + ut64 max = (RUST_VTABLE_HEADER_SLOTS + RUST_MAX_TRAIT_METHOD_SLOTS) * psize; + return disp >= min && disp < max && !(disp % psize); +} + +static bool op_type_is_unknown_call_or_jump(ut32 type) { + ut32 base = type & RZ_ANALYSIS_OP_TYPE_MASK; + return base == RZ_ANALYSIS_OP_TYPE_UCALL || base == RZ_ANALYSIS_OP_TYPE_UCCALL || base == RZ_ANALYSIS_OP_TYPE_UJMP || base == RZ_ANALYSIS_OP_TYPE_UCJMP; +} + +static bool op_type_is_memory_jump(ut32 type) { + ut32 base = type & RZ_ANALYSIS_OP_TYPE_MASK; + return (type & RZ_ANALYSIS_OP_TYPE_MEM) && (base == RZ_ANALYSIS_OP_TYPE_JMP || base == RZ_ANALYSIS_OP_TYPE_CJMP); +} + +static bool op_is_vtable_slot_dispatch(RzCore *core, RzAnalysisOp *op) { + return is_real_reg_name(core, op->reg) && is_vtable_slot_disp(core, op->disp) && + (op->type & (RZ_ANALYSIS_OP_TYPE_IND | RZ_ANALYSIS_OP_TYPE_MEM)) && + (op_type_is_unknown_call_or_jump(op->type) || op_type_is_memory_jump(op->type)); +} + +static bool op_is_register_target_dispatch(RzCore *core, RzAnalysisOp *op) { + return is_real_reg_name(core, op->reg) && (op->type & RZ_ANALYSIS_OP_TYPE_REG) && + !(op->type & (RZ_ANALYSIS_OP_TYPE_IND | RZ_ANALYSIS_OP_TYPE_MEM)) && op_type_is_unknown_call_or_jump(op->type); +} + +static bool op_is_dispatch_candidate(RzCore *core, RzAnalysisOp *op) { + return op_is_vtable_slot_dispatch(core, op) || op_is_register_target_dispatch(core, op); +} + +static void track_init(RzCore *core, RZ_NULLABLE const RustCallSeed *seed) { + rz_core_analysis_esil_init_mem(core, NULL, RUST_TRACK_MEM_ADDR, RUST_TRACK_MEM_SIZE); + rz_core_analysis_il_reinit(core); + + if (rz_asm_is_arch(core->rasm, "x86")) { + rz_analysis_il_vm_set_unsigned(core->analysis, "rbp", RUST_STACK_PTR); + rz_analysis_il_vm_set_unsigned(core->analysis, "rsp", RUST_STACK_PTR); + } else if (rz_asm_is_arch(core->rasm, "arm")) { + rz_analysis_il_vm_set_unsigned(core->analysis, "fp", RUST_STACK_PTR); + rz_analysis_il_vm_set_unsigned(core->analysis, "sp", RUST_STACK_PTR); + } else { + const char *arch = rz_core_get_arch(core); + RZ_LOG_WARN("arch %s is not supported\n", arch); + } + + if (!seed) { + return; + } + + for (ut64 i = 0; i < seed->count; i++) { + if (seed->has_value[i]) { + rz_analysis_il_vm_set_unsigned(core->analysis, seed->regs[i], seed->values[i]); + } + } +} + +static void track_fini(RzCore *core) { + rz_core_analysis_il_reinit(core); + rz_core_analysis_esil_init_mem_del(core, NULL, RUST_TRACK_MEM_ADDR, RUST_TRACK_MEM_SIZE); +} + +static void add_virtual_xrefs(RzAnalysis *analysis, const char *method_name, ut64 addr) { + bool found = false; + HtSP *ht_virtual_xrefs = rz_analysis_get_virtual_xrefs(analysis); + if (!ht_virtual_xrefs) { + return; + } + + RzSetU *set = ht_sp_find(ht_virtual_xrefs, method_name, &found); + if (!found) { + set = rz_set_u_new(); + if (!set) { + return; + } + if (!ht_sp_insert(ht_virtual_xrefs, method_name, set)) { + rz_set_u_free(set); + return; + } + } + if (!set) { + return; + } + rz_set_u_add(set, addr); +} + +static void add_virtual_xrefs_for_method(RzCore *core, const char *method_name, ut64 method_addr, ut64 xref_addr) { + add_virtual_xrefs(core->analysis, method_name, xref_addr); + const RzList *flags = rz_flag_get_list(core->flags, method_addr); + RzListIter *it; + RzFlagItem *flag; + rz_list_foreach (flags, it, flag) { + if (RZ_STR_ISNOTEMPTY(flag->name) && strcmp(flag->name, method_name)) { + add_virtual_xrefs(core->analysis, flag->name, xref_addr); + } + } +} + +static bool type_id_data_matches(RzCore *core, ut64 addr, ut64 low, ut64 high, bool has_high) { + ut8 buf[RUST_TYPE_ID_BYTES] = { 0 }; + if (!addr || addr == UT64_MAX || !rz_io_read_at_mapped(core->io, addr, buf, sizeof(buf))) { + return false; + } + + bool big_endian = rz_asm_is_big_endian_set(core->rasm); + ut64 data_low = rz_read_ble64(buf, big_endian); + ut64 data_high = rz_read_ble64(buf + 8, big_endian); + if (has_high) { + return data_low == low && data_high == high; + } + return data_low == low || data_high == low; +} + +static bool op_mem_value_has_type_id(RzCore *core, RzAnalysisOp *op, RzAnalysisValue *value, ut64 low, ut64 high, bool has_high) { + if (!analysis_value_is_mem(value) || value->memref < RUST_TYPE_ID_BYTES) { + return false; + } + + ut64 addr = UT64_MAX; + return analysis_value_addr(core, op, value, &addr) && type_id_data_matches(core, addr, low, high, has_high); +} + +static bool op_has_scalar_imm(RzAnalysisOp *op, ut64 value) { + if (op->val != UT64_MAX && op->val == value) { + return true; + } + + for (ut64 i = 0; i < RZ_ARRAY_SIZE(op->analysis_vals); i++) { + if (op->analysis_vals[i].imm != ST64_MAX && op->analysis_vals[i].imm == value) { + return true; + } + } + return false; +} + +static bool op_has_type_id(RzCore *core, RzAnalysisOp *op, ut64 low, ut64 high, bool has_high) { + if (op->ptr && op->ptr != UT64_MAX && op->refptr >= RUST_TYPE_ID_BYTES && + type_id_data_matches(core, op->ptr, low, high, has_high)) { + return true; + } + + for (ut64 i = 0; i < RZ_ARRAY_SIZE(op->src); i++) { + if (op_mem_value_has_type_id(core, op, op->src[i], low, high, has_high)) { + return true; + } + } + if (op_mem_value_has_type_id(core, op, op->dst, low, high, has_high)) { + return true; + } + + return op_has_scalar_imm(op, low) || (has_high && op_has_scalar_imm(op, high)); +} + +static void annotate_any_type_id_compare(RzCore *core, RzAnalysisOp *op, RzVector /**/ *any_vtables) { + if (!any_vtables) { + return; + } + + ut32 type = op->type & RZ_ANALYSIS_OP_TYPE_MASK; + if (type != RZ_ANALYSIS_OP_TYPE_CMP && type != RZ_ANALYSIS_OP_TYPE_ACMP) { + return; + } + + RustAnyVTable *any; + rz_vector_foreach (any_vtables, any) { + if (!any->has_type_id) { + continue; + } + if (!op_has_type_id(core, op, any->type_id_low, any->type_id_high, any->has_type_id_high)) { + continue; + } + char *comment = rz_str_newf("Any downcast: %s", any->concrete_type); + if (comment) { + rz_core_meta_comment_add(core, comment, op->addr); + free(comment); + } + break; + } +} + +static void devirtualize_step(RzCore *core, RzAnalysisOp *op, RzVector /**/ *any_vtables) { + ut64 target = UT64_MAX; + ut64 vtable_addr = UT64_MAX; + RustAnyVTable *target_any = NULL; + bool target_is_any_type_id = false; + bool found = false; + if (op_is_vtable_slot_dispatch(core, op)) { + ut64 slot_addr = UT64_MAX; + vtable_addr = get_reg_value(core->analysis, op->reg); + if (!vtable_addr || vtable_addr == UT64_MAX) { + return; + } + + slot_addr = vtable_addr + op->disp; + if (slot_addr == UT64_MAX) { + return; + } + + if (is_real_reg_name(core, op->ireg)) { + ut64 index = get_reg_value(core->analysis, op->ireg); + if (index == UT64_MAX) { + return; + } + ut64 scale = op->scale; + if (!scale) { + scale = 1; + } + slot_addr += index * scale; + } + found = resolve_slot(core, vtable_addr, slot_addr, &target); + } else if (op_is_register_target_dispatch(core, op)) { + target = get_reg_value(core->analysis, op->reg); + if (!target || target == UT64_MAX) { + return; + } + + target_any = any_vtable_by_type_id_method(any_vtables, target); + if (!target_any) { + ut64 type_id_low = 0; + ut64 type_id_high = 0; + bool type_id_has_high = false; + if (rust_type_id_from_method(core, target, &type_id_low, &type_id_high, &type_id_has_high)) { + target_any = any_vtable_by_type_id_values(any_vtables, type_id_low, type_id_high, type_id_has_high); + } + } + if (target_any) { + vtable_addr = target_any->vtable_addr; + target_is_any_type_id = true; + } + found = true; + } else { + return; + } + + if (!found) { + return; + } + + char *method_name = get_method_name(core, target); + if (!method_name) { + return; + } + + RustAnyVTable *any = target_any; + if (!any) { + any = any_vtable_by_addr(any_vtables, vtable_addr); + } + char *comment = NULL; + if (any && (target_is_any_type_id || target == any->type_id_method)) { + comment = rz_str_newf("Any::type_id: %s", any->concrete_type); + } else { + comment = rz_str_newf("Virtual call: %s", method_name); + } + + if (comment) { + rz_core_meta_comment_add(core, comment, op->addr); + RZ_FREE(comment); + } + + add_virtual_xrefs_for_method(core, method_name, target, op->addr); + if (target_is_any_type_id && any->type_id_method != target) { + char *class_method_name = get_method_name(core, any->type_id_method); + if (class_method_name) { + add_virtual_xrefs_for_method(core, class_method_name, any->type_id_method, op->addr); + RZ_FREE(class_method_name); + } + } + RZ_FREE(method_name); +} + +static void clear_dst_reg_for_skipped_op(RzCore *core, RzAnalysisOp *op) { + const char *dst = op_dst_reg_name(op); + if (is_real_reg_name(core, dst)) { + rz_analysis_il_vm_set_unsigned(core->analysis, dst, 0); + } +} + +static void track_step_or_skip(RzCore *core, RzAnalysisOp *op, ut64 next_addr) { + if (!op->il_op || !op_memory_access_is_safe(core, op)) { + clear_dst_reg_for_skipped_op(core, op); + advance_il_pc(core, next_addr); + return; + } + + advance_il_pc(core, op->addr); + if (!rz_core_il_step(core, 1)) { + advance_il_pc(core, next_addr); + return; + } +} + +static bool function_has_rust_dispatch_candidate(RzCore *core, RzAnalysisFunction *function) { + ut64 start = function->addr; + ut64 end = rz_analysis_function_max_addr(function); + if (!addr_is_executable(core, start)) { + return false; + } + + ut8 *bytes = read_mapped_range(core, start, end); + if (!bytes) { + return false; + } + + bool found = false; + ut64 offset = 0; + RzAnalysisOp *op = rz_analysis_op_new(); + if (!op) { + RZ_FREE(bytes); + return false; + } + + while (start < end) { + if (rz_analysis_op(core->analysis, op, start, bytes + offset, end - start, RZ_ANALYSIS_OP_MASK_ALL) <= 0 || op->size < 1) { + break; + } + if (op_is_dispatch_candidate(core, op)) { + found = true; + rz_analysis_op_fini(op); + break; + } + start += op->size; + offset += op->size; + rz_analysis_op_fini(op); + } + + rz_analysis_op_free(op); + RZ_FREE(bytes); + return found; +} + +static void devirtualize_rust_function(RzCore *core, RzAnalysisFunction *function, RzVector /**/ *any_vtables, RZ_NULLABLE const RustCallSeed *seed) { + ut64 start = function->addr; + ut64 end = rz_analysis_function_max_addr(function); + if (!addr_is_executable(core, start)) { + return; + } + + RzAnalysisOp *op = rz_analysis_op_new(); + if (!op) { + return; + } + + ut8 *bytes = read_mapped_range(core, start, end); + if (!bytes) { + RZ_LOG_ERROR("Cannot read at offset 0x%08" PFMT64x "\n", start); + rz_analysis_op_free(op); + return; + } + + ut64 old_offset = core->offset; + ut64 offset = 0; + core->offset = start; + track_init(core, seed); + while (start < end) { + if (rz_analysis_op(core->analysis, op, start, bytes + offset, end - start, RZ_ANALYSIS_OP_MASK_ALL) <= 0 || op->size < 1) { + break; + } + + devirtualize_step(core, op, any_vtables); + annotate_any_type_id_compare(core, op, any_vtables); + ut64 next = start + op->size; + if (rz_analysis_op_is_eob(op) || rz_analysis_op_is_call(op)) { + advance_il_pc(core, next); + } else { + track_step_or_skip(core, op, next); + } + + start = next; + offset += op->size; + core->offset = start; + rz_analysis_op_fini(op); + } + + core->offset = old_offset; + track_fini(core); + rz_analysis_op_free(op); + RZ_FREE(bytes); +} + +static void caller_replay_step(RzCore *core, RzAnalysisOp *op, ut64 next_addr, const char *ret_reg, RZ_INOUT ut64 *next_scratch) { + if (rz_analysis_op_is_call(op)) { + if (is_real_reg_name(core, ret_reg)) { + ut64 heap_end = RUST_SCRATCH_HEAP_ADDR + RUST_SCRATCH_HEAP_SIZE; + if (*next_scratch <= heap_end - RUST_SCRATCH_ALLOC_STEP) { + rz_analysis_il_vm_set_unsigned(core->analysis, ret_reg, *next_scratch); + *next_scratch += RUST_SCRATCH_ALLOC_STEP; + } else { + rz_analysis_il_vm_set_unsigned(core->analysis, ret_reg, 0); + } + } + advance_il_pc(core, next_addr); + return; + } + + if (rz_analysis_op_is_eob(op)) { + advance_il_pc(core, next_addr); + return; + } + + track_step_or_skip(core, op, next_addr); +} + +static void collect_caller_args_from_site(RzCore *core, RzAnalysisFunction *caller, ut64 call_addr, RustArgRegs *args, RzVector /**/ *seeds) { + ut64 start = caller->addr; + ut64 end = RZ_MIN(call_addr, rz_analysis_function_max_addr(caller)); + if (!addr_is_executable(core, start)) { + return; + } + + ut8 *bytes = read_mapped_range(core, start, end); + if (!bytes) { + return; + } + + RzAnalysisOp *op = rz_analysis_op_new(); + if (!op) { + RZ_FREE(bytes); + return; + } + + RustCallSeed seed = { + .count = args->count, + }; + + for (ut64 i = 0; i < args->count; i++) { + seed.regs[i] = args->regs[i]; + } + + const char *cc = rz_analysis_cc_default(core->analysis); + const char *ret_reg = rz_analysis_cc_ret(core->analysis, cc); + ut64 old_offset = core->offset; + ut64 next_scratch = RUST_SCRATCH_HEAP_ADDR; + ut64 offset = 0; + core->offset = start; + track_init(core, NULL); + while (start < end) { + if (rz_analysis_op(core->analysis, op, start, bytes + offset, end - start, RZ_ANALYSIS_OP_MASK_ALL) <= 0 || op->size < 1) { + break; + } + + ut64 next = start + op->size; + caller_replay_step(core, op, next, ret_reg, &next_scratch); + start = next; + offset += op->size; + core->offset = start; + rz_analysis_op_fini(op); + } + + for (ut64 i = 0; i < args->count; i++) { + ut64 value = get_reg_value(core->analysis, args->regs[i]); + if (value && value != UT64_MAX) { + seed.values[i] = value; + seed.has_value[i] = true; + } + } + + push_unique_seed(core, seeds, &seed); + core->offset = old_offset; + track_fini(core); + rz_analysis_op_free(op); + RZ_FREE(bytes); +} + +static RZ_OWN RzVector /**/ *collect_caller_args(RzCore *core, RzAnalysisFunction *function, RustArgRegs *args) { + RzVector *seeds = rz_vector_new(sizeof(RustCallSeed), NULL, NULL); + if (!seeds) { + return NULL; + } + + RzList *xrefs = rz_analysis_xrefs_get_to(core->analysis, function->addr); + if (!xrefs) { + rz_vector_free(seeds); + return NULL; + } + + RzListIter *it; + RzAnalysisXRef *xref; + rz_list_foreach (xrefs, it, xref) { + if (xref->type != RZ_ANALYSIS_XREF_TYPE_CALL && xref->type != RZ_ANALYSIS_XREF_TYPE_CODE) { + continue; + } + RzAnalysisFunction *caller = rz_analysis_get_fcn_in(core->analysis, xref->from, RZ_ANALYSIS_FCN_TYPE_NULL); + if (!caller) { + continue; + } + collect_caller_args_from_site(core, caller, xref->from, args, seeds); + } + rz_list_free(xrefs); + + if (rz_vector_empty(seeds)) { + rz_vector_free(seeds); + return NULL; + } + return seeds; +} + +static void devirtualize_rust_trait_object(RzCore *core) { + RzAnalysisFunction *function = rz_analysis_get_fcn_in(core->analysis, core->offset, RZ_ANALYSIS_FCN_TYPE_NULL); + if (!function) { + RZ_LOG_ERROR("Cannot find function at 0x%08" PFMT64x "\n", core->offset); + return; + } + + if (!function_has_rust_dispatch_candidate(core, function)) { + return; + } + + RustArgRegs args = { 0 }; + get_arg_regs(core, function, &args); + RzVector *any_vtables = rust_any_vtables_from_classes(core); + devirtualize_rust_function(core, function, any_vtables, NULL); + + RzVector *seeds = NULL; + if (args.count) { + seeds = collect_caller_args(core, function, &args); + } + RustCallSeed *seed; + if (seeds) { + rz_vector_foreach (seeds, seed) { + devirtualize_rust_function(core, function, any_vtables, seed); + } + } + + rz_vector_free(seeds); + rz_vector_free(any_vtables); +} + +/** + * \brief devirtualize Rust trait object calls in the current function + */ +RZ_IPI void rz_core_analysis_devirtualize_rust_methods(RZ_NULLABLE RzCore *core) { + if (!core) { + return; + } + devirtualize_rust_trait_object(core); +} diff --git a/librz/core/meson.build b/librz/core/meson.build index 9db4c9736d4..cd7e2342eee 100644 --- a/librz/core/meson.build +++ b/librz/core/meson.build @@ -53,6 +53,7 @@ rz_core_sources = [ 'cvfile.c', 'devirtualize_cxx.c', 'devirtualize_objc.c', + 'devirtualize_rust.c', 'disasm.c', 'fortune.c', 'golang.c', diff --git a/librz/include/rz_analysis.h b/librz/include/rz_analysis.h index f6782431f75..797337cea86 100644 --- a/librz/include/rz_analysis.h +++ b/librz/include/rz_analysis.h @@ -2093,6 +2093,7 @@ RZ_API bool rz_analysis_rtti_msvc_print_at_vtable(RVTableContext *context, ut64 RZ_API void rz_analysis_rtti_msvc_recover_all(RVTableContext *vt_context, RzList /**/ *vtables); RZ_API void rz_analysis_rtti_swift(RzAnalysis *analysis); RZ_API void rz_analysis_rtti_objc(RZ_NONNULL RzAnalysis *analysis); +RZ_API void rz_analysis_rtti_rust(RZ_NONNULL RzAnalysis *analysis); RZ_API char *rz_analysis_rtti_itanium_demangle_class_name(RVTableContext *context, const char *name); RZ_API bool rz_analysis_rtti_itanium_print_at_vtable(RVTableContext *context, ut64 addr, RzOutputMode mode); diff --git a/test/db/cmd/cmd_ac b/test/db/cmd/cmd_ac index a404b89e1e5..5fbae7fd3a1 100644 --- a/test/db/cmd/cmd_ac +++ b/test/db/cmd/cmd_ac @@ -639,3 +639,73 @@ attr.Flaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa attrtypes.Flaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaatline=vtable,method EOF RUN + +NAME=Rust tree traversal +FILE=bins/abi_bins/elf/languages/rust/tree_traversal +CMDS=<::say::h068f985a4f49ea94 +impl_rust_any::Dog_as_rust_any::Pet: + 0 vtable 0x512f8 @ +0x0 size:+0x20 +EOF +RUN \ No newline at end of file diff --git a/test/db/cmd/cmd_acll b/test/db/cmd/cmd_acll index 6bd98a8d2a6..e7c73f2447f 100644 --- a/test/db/cmd/cmd_acll +++ b/test/db/cmd/cmd_acll @@ -597,4 +597,4 @@ nth addr vt_offset type name 1 0x100003e48 ---------- DEFAULT speak EOF -RUN \ No newline at end of file +RUN diff --git a/test/db/cmd/cmd_avD b/test/db/cmd/cmd_avD index 5ce577a795c..bfe46803039 100644 --- a/test/db/cmd/cmd_avD +++ b/test/db/cmd/cmd_avD @@ -1258,4 +1258,205 @@ EXPECT=<(struct &mut [i32] values) +| ; var int64_t arg1 @ rdi +| ; var int64_t arg2 @ rsi +| ; var long long unsigned int arg3 @ stack - 0xb8 +| ; var int64_t var_a9h @ stack - 0xa9 +| ; var int64_t var_a8h @ stack - 0xa8 +| ; var int64_t var_a0h @ stack - 0xa0 +| ; var int64_t arg4 @ stack - 0x98 +| ; var int64_t var_90h @ stack - 0x90 +| ; var int64_t var_88h @ stack - 0x88 +| ; var int64_t var_80h @ stack - 0x80 +| ; var long long unsigned int var_78h @ stack - 0x78 +| ; var long long unsigned int var_70h @ stack - 0x70 +| ; var int64_t var_68h @ stack - 0x68 +| ; var int64_t var_59h @ stack - 0x59 +| ; var int64_t var_58h @ stack - 0x58 +| ; var int64_t var_50h @ stack - 0x50 +| ; var int64_t arg_47h @ stack - 0x48 +| ; var int64_t var_40h @ stack - 0x40 +| ; var int64_t var_38h @ stack - 0x38 +| ; var int64_t var_30h @ stack - 0x30 +| ; var int64_t var_28h @ stack - 0x28 +| ; var int64_t var_20h @ stack - 0x20 +| ; var int64_t var_18h @ stack - 0x18 +| ; var int64_t var_10h @ stack - 0x10 +| ; var int64_t var_8h @ stack - 0x8 +| ; arg struct &mut [i32] values @ stack + 0x90 +| 0x00005270 sub rsp, 0xb8 ; rust.rs:1 ; rust::bubble_sort::h0777bc845caabc60 +| 0x00005277 mov qword [var_28h], rdi ; arg1 +| 0x0000527f mov qword [var_20h], rsi ; arg2 +| 0x00005287 mov qword [var_70h], rdi ; arg1 +| 0x0000528c mov qword [var_78h], rsi ; arg2 +| 0x00005291 call dbg.len ; rust.rs:2 ; usize len(struct &[i32] self) +| 0x00005296 mov qword [var_68h], rax +| 0x0000529b mov byte [var_59h], 0x01 ; rust.rs:3 +| ; CODE XREF from dbg.bubble_sort @ 0x5441 +| .-> 0x000052a0 test byte [var_59h], 0x01 ; rust.rs:5 +| ,==< 0x000052a5 jnz 0x52af +| |: 0x000052a7 add rsp, 0xb8 ; rust.rs:17 +| |: 0x000052ae ret +| `--> 0x000052af mov byte [var_59h], 0x00 ; rust.rs:6 +| : 0x000052b4 mov rax, qword [var_68h] ; rust.rs:8 +| : 0x000052b9 mov qword [var_58h], 0x01 +| : 0x000052c2 mov qword [var_50h], rax +| : 0x000052c7 mov rdi, qword [var_58h] ; int64_t arg1 +| : 0x000052cc mov rsi, qword [var_50h] ; int64_t arg2 +| : 0x000052d1 call dbg.into_iter> ; struct Range into_iter>(struct Range self) +| : 0x000052d6 mov qword [var_80h], rax +| : 0x000052db mov qword [var_88h], rdx +| : 0x000052e0 mov rax, qword [var_80h] ; rust.rs +| : 0x000052e5 mov qword [arg_47h], rax ; rust.rs:8 +| : 0x000052ea mov rcx, qword [var_88h] +| : 0x000052ef mov qword [var_40h], rcx +| : ; CODE XREF from dbg.bubble_sort @ 0x5432 +| .--> 0x000052f4 lea rdi, qword [arg_47h] ; rust.rs ; int64_t arg_47h +| :: 0x000052f9 call dbg.next ; rust.rs:8 ; struct Option next(struct Range *self) +| :: 0x000052fe mov qword [var_30h], rdx +| :: 0x00005306 mov qword [var_38h], rax +| :: 0x0000530e mov rax, qword [var_38h] +| :: 0x00005316 test rax, rax +| ,===< 0x00005319 jz 0x531f +| ,====< 0x0000531b jmp 0x531d +| ||:: ; CODE XREF from dbg.bubble_sort @ 0x531b +| ,`----> 0x0000531d jmp 0x5342 +| | `---> 0x0000531f mov rax, qword [var_68h] ; rust.rs:15 +| | :: 0x00005324 sub rax, 0x01 +| | :: 0x0000532a setb cl +| | :: 0x0000532d test cl, 0x01 ; r8 ; "ELF\U00000002\U00000001\U00000001" +| | :: 0x00005330 mov qword [var_90h], rax +| | ,===< 0x00005335 jnz 0x54ba +| |,====< 0x0000533b jmp 0x5437 +.. +| |||:: ; CODE XREF from dbg.bubble_sort @ 0x531d +| `-----> 0x00005342 mov rax, qword [var_30h] ; rust.rs:8 +| ||:: 0x0000534a mov qword [var_18h], rax +| ||:: 0x00005352 mov qword [var_10h], rax +| ||:: 0x0000535a mov qword [var_8h], rax +| ||:: 0x00005362 mov rcx, rax ; rust.rs:9 +| ||:: 0x00005365 sub rcx, 0x01 +| ||:: 0x0000536c setb dl +| ||:: 0x0000536f test dl, 0x01 ; r8 ; "ELF\U00000002\U00000001\U00000001" +| ||:: 0x00005372 mov qword [arg4], rax +| ||:: 0x00005377 mov qword [var_a0h], rcx +| ,=====< 0x0000537c jnz 0x5446 +| |||:: 0x00005382 mov rax, qword [var_a0h] ; rust.rs +| |||:: 0x00005387 mov rcx, qword [var_78h] +| |||:: 0x0000538c cmp rax, rcx ; rust.rs:9 +| |||:: 0x0000538f setb dl +| |||:: 0x00005392 test dl, 0x01 ; r8 ; "ELF\U00000002\U00000001\U00000001" +| ,======< 0x00005395 jnz 0x539c +| ,=======< 0x00005397 jmp 0x5464 +| |`------> 0x0000539c mov rax, qword [var_a0h] ; rust.rs +| | |||:: 0x000053a1 shl rax, 0x02 ; rust.rs:9 +| | |||:: 0x000053a5 mov rcx, qword [var_70h] +| | |||:: 0x000053aa add rcx, rax +| | |||:: 0x000053ad mov rax, qword [arg4] +| | |||:: 0x000053b2 mov rdx, qword [var_78h] +| | |||:: 0x000053b7 cmp rax, rdx +| | |||:: 0x000053ba setb sil +| | |||:: 0x000053be test sil, 0x01 ; r8 ; "ELF\U00000002\U00000001\U00000001" +| | |||:: 0x000053c2 mov qword [var_a8h], rcx +| |,======< 0x000053c7 jnz 0x53ce +| ========< 0x000053c9 jmp 0x5480 +| |`------> 0x000053ce mov rax, qword [arg4] ; rust.rs +| | |||:: 0x000053d3 shl rax, 0x02 ; rust.rs:9 +| | |||:: 0x000053d7 mov rcx, qword [var_70h] +| | |||:: 0x000053dc add rcx, rax +| | |||:: 0x000053df mov rdi, qword [var_a8h] ; int64_t arg1 +| | |||:: 0x000053e4 mov rsi, rcx ; long long unsigned int arg2 +| | |||:: 0x000053e7 call dbg.gt +| | |||:: 0x000053ec mov byte [var_a9h], al +| | |||:: 0x000053f0 mov al, byte [var_a9h] ; rust.rs +| | |||:: 0x000053f4 test al, 0x01 ; rust.rs:9 ; r8 ; "ELF\U00000002\U00000001\U00000001" +| |,======< 0x000053f6 jnz 0x53fa +| ========< 0x000053f8 jmp 0x5432 +| |`------> 0x000053fa mov rax, qword [arg4] ; rust.rs +| | |||:: 0x000053ff sub rax, 0x01 ; rust.rs:10 +| | |||:: 0x00005405 setb cl +| | |||:: 0x00005408 test cl, 0x01 ; r8 ; "ELF\U00000002\U00000001\U00000001" +| | |||:: 0x0000540b mov qword [rsp], rax +| |,======< 0x0000540f jnz 0x549c +| |||||:: 0x00005415 mov rdi, qword [var_70h] ; rust.rs ; int64_t arg1 +| |||||:: 0x0000541a mov rsi, qword [var_78h] ; long long unsigned int arg2 +| |||||:: 0x0000541f mov rdx, qword [rsp] ; long long unsigned int arg3 +| |||||:: 0x00005423 mov rcx, qword [arg4] ; int64_t arg4 +| |||||:: 0x00005428 call dbg.swap ; rust.rs:10 ; swap(struct &mut [i32] self, usize a, usize b) +| |||||:: 0x0000542d mov byte [var_59h], 0x01 ; rust.rs:11 +| ||||||: ; CODE XREF from dbg.bubble_sort @ 0x53f8 +| -----`==< 0x00005432 jmp 0x52f4 ; rust.rs:8 +| ||||| : ; CODE XREF from dbg.bubble_sort @ 0x533b +| |||`----> 0x00005437 mov rax, qword [var_90h] ; rust.rs +| ||| | : 0x0000543c mov qword [var_68h], rax ; rust.rs:15 +| ||| | `=< 0x00005441 jmp 0x52a0 ; rust.rs:5 +| ||| | ; DATA XREF from method._rustc_demangle::legacy::Demangle_as_core::fmt::Display_::fmt.hbbd9e1822966e201 @ 0x2385c +| ||| | ;-- (0x00005447) data.00005447: +| ||`-----> 0x00005446 ~ lea rdi, qword obj.str.0 ; rust.rs:9 ; 0x2a010 ; "attempt to subtract with overflowBefore: \nAfter: beachartcardealempty/usr/src/rustc-1.43.0/src/libcore/slice/mod.rscalled `Opti" +| || | 0x0000544d lea rdx, qword sym..data.rel.ro ; 0x382b8 ; "" +| || | 0x00005454 lea rax, qword [dbg.panic] ; 0x25930 ; "H\x83\xecHH\x89|$\bH\x89t$\U00000010H\x8dD$\bH\x89D$\U00000018H\xc7D$ \U00000001" +| || | 0x0000545b mov esi, 0x21 ; '!' +| || | 0x00005460 call rax ; Virtual call: dbg.panic +| || | 0x00005462 ud2 +| || | ; CODE XREF from dbg.bubble_sort @ 0x5397 +| `-------> 0x00005464 lea rdi, qword data.000382d0 ; 0x382d0 ; "" +| | | 0x0000546b lea rax, qword [dbg.panic_bounds_check] ; 0x25980 ; "H\x83\xechH\x89\xf8H\x89t$\bH\x89T$\U00000010H\x8dL$\U00000010H\x89L$\U00000018H\x8d\r\U0000001e\xfe\xff\xffH\x89L$ H\x8dT$\bH\x" +| | | 0x00005472 mov rsi, qword [var_a0h] +| | | 0x00005477 mov rdx, qword [var_78h] +| | | 0x0000547c call rax ; Virtual call: dbg.panic_bounds_check +| | | 0x0000547e ud2 +| | | ; CODE XREF from dbg.bubble_sort @ 0x53c9 +| --------> 0x00005480 lea rdi, qword data.000382e8 ; 0x382e8 ; "" +| | | 0x00005487 lea rax, qword [dbg.panic_bounds_check] ; 0x25980 ; "H\x83\xechH\x89\xf8H\x89t$\bH\x89T$\U00000010H\x8dL$\U00000010H\x89L$\U00000018H\x8d\r\U0000001e\xfe\xff\xffH\x89L$ H\x8dT$\bH\x" +| | | 0x0000548e mov rsi, qword [arg4] +| | | 0x00005493 mov rdx, qword [var_78h] +| | | 0x00005498 call rax ; Virtual call: dbg.panic_bounds_check +| | | 0x0000549a ud2 +| `------> 0x0000549c lea rdi, qword obj.str.0 ; rust.rs:10 ; 0x2a010 ; "attempt to subtract with overflowBefore: \nAfter: beachartcardealempty/usr/src/rustc-1.43.0/src/libcore/slice/mod.rscalled `Opti" +| | 0x000054a3 lea rdx, qword data.00038300 ; 0x38300 ; "" +| | 0x000054aa lea rax, qword [dbg.panic] ; 0x25930 ; "H\x83\xecHH\x89|$\bH\x89t$\U00000010H\x8dD$\bH\x89D$\U00000018H\xc7D$ \U00000001" +| | 0x000054b1 mov esi, 0x21 ; '!' +| | 0x000054b6 call rax ; Virtual call: dbg.panic +| | 0x000054b8 ud2 +| `---> 0x000054ba lea rdi, qword obj.str.0 ; rust.rs:15 ; 0x2a010 ; "attempt to subtract with overflowBefore: \nAfter: beachartcardealempty/usr/src/rustc-1.43.0/src/libcore/slice/mod.rscalled `Opti" +| 0x000054c1 lea rdx, qword data.00038318 ; 0x38318 ; "" +| 0x000054c8 lea rax, qword [dbg.panic] ; 0x25930 ; "H\x83\xecHH\x89|$\bH\x89t$\U00000010H\x8dD$\bH\x89D$\U00000018H\xc7D$ \U00000001" +| 0x000054cf mov esi, 0x21 ; '!' +| 0x000054d4 call rax ; Virtual call: dbg.panic +\ 0x000054d6 ud2 +EOF RUN \ No newline at end of file