rizin/librz/core/linux_heap_jemalloc.c
2026-01-27 15:31:48 +08:00

1057 lines
32 KiB
C

// SPDX-FileCopyrightText: 2017 javierptd <javierptd@gmail.com>
// SPDX-FileCopyrightText: 2026 bubblepipe <bubblepipe42@gmail.com>
// SPDX-License-Identifier: LGPL-3.0-only
#include <rz_core.h>
#include <rz_heap_jemalloc.h>
static ut64 je_get_va_symbol(RzCore *core, const char *path, const char *sym_name) {
ut64 vaddr = UT64_MAX;
RzBin *bin = core->bin;
RzBinFile *current_bf = rz_bin_cur(bin);
void **iter;
RzBinSymbol *s;
RzBinOptions opt;
rz_bin_options_init(&opt, -1, 0, 0, false);
opt.obj_opts.elf_load_sections = rz_config_get_b(core->config, "elf.load.sections");
opt.obj_opts.elf_checks_sections = rz_config_get_b(core->config, "elf.checks.sections");
opt.obj_opts.elf_checks_segments = rz_config_get_b(core->config, "elf.checks.segments");
RzBinFile *libc_bf = rz_bin_open(bin, path, &opt);
if (!libc_bf) {
return vaddr;
}
RzBinObject *o = rz_bin_cur_object(bin);
RzPVector *syms = o ? (RzPVector *)rz_bin_object_get_symbols(o) : NULL;
rz_pvector_foreach (syms, iter) {
s = *iter;
if (!strcmp(s->name, sym_name)) {
vaddr = s->vaddr;
break;
}
}
rz_bin_file_delete(bin, libc_bf);
rz_bin_file_set_obj(current_bf, current_bf->o);
rz_bin_set_cur_binfile(bin, current_bf);
return vaddr;
}
static bool rz_resolve_jemalloc(RzCore *core, const char *symname, ut64 *symbol) {
RzListIter *iter;
RzDebugMap *map;
const char *jemalloc_path = NULL;
ut64 jemalloc_addr = UT64_MAX;
const char *binary_path = NULL;
ut64 binary_addr = UT64_MAX;
if (!core || !core->dbg || !core->dbg->maps) {
return false;
}
rz_debug_map_sync(core->dbg);
rz_list_foreach (core->dbg->maps, iter, map) {
if (strstr(map->name, "libjemalloc.")) {
if (jemalloc_addr == UT64_MAX || map->addr < jemalloc_addr) {
jemalloc_addr = map->addr;
jemalloc_path = map->name;
}
}
if (!strstr(map->name, ".so") && !strstr(map->name, "lib") &&
!strstr(map->name, "[") && strlen(map->name) > 0) {
if (binary_addr == UT64_MAX || map->addr < binary_addr) {
binary_addr = map->addr;
binary_path = map->name;
}
}
}
// Try dynamic library first
if (jemalloc_path) {
char *path = rz_str_newf("%s", jemalloc_path);
if (rz_file_exists(path)) {
ut64 vaddr = je_get_va_symbol(core, path, symname);
if (jemalloc_addr != UT64_MAX && vaddr != UT64_MAX) {
*symbol = jemalloc_addr + vaddr;
free(path);
return true;
}
}
free(path);
}
// Fall back to static linking
if (binary_path) {
char *path = rz_str_newf("%s", binary_path);
if (rz_file_exists(path)) {
ut64 vaddr = je_get_va_symbol(core, path, symname);
if (binary_addr != UT64_MAX && vaddr != UT64_MAX) {
*symbol = binary_addr + vaddr;
free(path);
return true;
}
}
free(path);
}
return false;
}
/**
* \brief Detect jemalloc version by checking for version-specific symbols
*
* jemalloc 4.x has je_chunksize symbol (chunk-based architecture)
* jemalloc 5.x does NOT have je_chunksize (extent-based architecture)
*/
static bool rz_jemalloc_detect_version(RzCore *core) {
ut64 chunksize_addr;
const char *current_version = rz_config_get(core->config, "dbg.jemalloc.version");
// Try to resolve je_chunksize - only exists in jemalloc 4.5.0
if (rz_resolve_jemalloc(core, "je_chunksize", &chunksize_addr)) {
// je_chunksize found -> jemalloc 4.5.0
if (strcmp(current_version, "4.5.0") != 0) {
rz_config_set(core->config, "dbg.jemalloc.version", "4.5.0");
RZ_LOG_INFO("Detected jemalloc 4.5.0 (je_chunksize symbol found)\n");
}
return true;
} else if (rz_resolve_jemalloc(core, "je_arena_emap_global", &chunksize_addr)) {
if (strcmp(current_version, "5.3.0") != 0) {
rz_config_set(core->config, "dbg.jemalloc.version", "5.3.0");
RZ_LOG_INFO("Detected jemalloc 5.3.0 (je_arena_emap_global symbol found)\n");
}
return true;
} else {
rz_config_set(core->config, "dbg.jemalloc.version", "NULL");
RZ_LOG_WARN("jemalloc version cannot be determined\n");
return false;
}
}
static inline bool read_ptr_at(RzIO *io, ut64 addr, ut64 *value, ut8 ptr_size) {
if (ptr_size == 8) {
ut8 buf[8];
if (!rz_io_read_at_mapped(io, addr, buf, 8)) {
return false;
}
*value = rz_read_le64(buf);
} else {
ut8 buf[4];
if (!rz_io_read_at_mapped(io, addr, buf, 4)) {
return false;
}
*value = rz_read_le32(buf);
}
return true;
}
// ============================================================================
// jemalloc 4.5.0
// ============================================================================
static void jemalloc_get_chunks_450(RzCore *core, const char *input, const RzJemallocConfig450 *config) {
ut64 cnksz;
RzConsPrintablePalette *pal = &rz_cons_singleton()->context->pal;
if (!rz_resolve_jemalloc(core, "je_chunksize", &cnksz)) {
RZ_LOG_ERROR("Fail at reading symbol je_chunksize\n");
return;
}
if (!read_ptr_at(core->io, cnksz, &cnksz, config->ptr_size)) {
RZ_LOG_ERROR("Failed to read je_chunksize value\n");
return;
}
if (input[0] == '\0') {
RZ_LOG_ERROR("need an arena_t_450 to associate chunks\n");
} else if (input[0] != '*') {
const char *addr_str = (input[0] == ' ') ? input + 1 : input;
ut64 arena_addr = rz_num_math(core->num, addr_str);
arena_t_450 ar;
extent_node_t_450 node, head;
if (arena_addr == 0) {
return;
}
if (!read_and_parse_arena_t_450(core->io, arena_addr, &ar, config)) {
RZ_LOG_ERROR("Failed to read arena at 0x%" PFMT64x "\n", arena_addr);
return;
}
// Read achunks.qlh_first pointer
ut64 achunks_first = 0;
if (!read_achunks_first_450(core->io, ar.achunks_addr, &achunks_first, config)) {
RZ_LOG_ERROR("Failed to read achunks list head\n");
return;
}
if (achunks_first == 0) {
return;
}
if (!read_and_parse_extent_node_t_450(core->io, achunks_first, &head, config)) {
RZ_LOG_ERROR("Failed to read extent_node\n");
return;
}
if (head.en_addr) {
PRINT_YA(" Chunk - start: ");
PRINTF_BA("0x%08" PFMT64x, head.en_addr);
PRINT_YA(", end: ");
PRINTF_BA("0x%08" PFMT64x, head.en_addr + cnksz);
PRINT_YA(", size: ");
PRINTF_BA("0x%08" PFMT64x "\n", cnksz);
ut64 next_addr = head.ql_link_next;
while (next_addr && read_and_parse_extent_node_t_450(core->io, next_addr, &node, config)) {
if (node.en_addr == head.en_addr) {
break;
}
PRINT_YA(" Chunk - start: ");
PRINTF_BA("0x%08" PFMT64x, node.en_addr);
PRINT_YA(", end: ");
PRINTF_BA("0x%" PFMT64x, node.en_addr + cnksz);
PRINT_YA(", size: ");
PRINTF_BA("0x%08" PFMT64x "\n", cnksz);
next_addr = node.ql_link_next;
}
}
} else if (input[0] == '*') {
int i = 0;
ut64 sym;
ut64 arenas_ptr = UT64_MAX;
ut64 arena_addr = UT64_MAX;
arena_t_450 ar;
extent_node_t_450 node, head;
if (!rz_resolve_jemalloc(core, "je_arenas", &sym)) {
RZ_LOG_ERROR("Cannot resolve je_arenas\n");
return;
}
if (!read_ptr_at(core->io, sym, &arenas_ptr, config->ptr_size)) {
RZ_LOG_ERROR("Failed to read je_arenas pointer\n");
return;
}
for (;;) {
if (!read_ptr_at(core->io, arenas_ptr + i * config->ptr_size, &arena_addr, config->ptr_size)) {
break;
}
if (!arena_addr) {
break;
}
PRINTF_GA("arenas[%d]: @ 0x%" PFMT64x " { \n", i++, arena_addr);
if (!read_and_parse_arena_t_450(core->io, arena_addr, &ar, config)) {
PRINT_GA("}\n");
continue;
}
ut64 achunks_first = 0;
if (!read_achunks_first_450(core->io, ar.achunks_addr, &achunks_first, config) ||
achunks_first == 0) {
PRINT_GA("}\n");
continue;
}
if (!read_and_parse_extent_node_t_450(core->io, achunks_first, &head, config)) {
PRINT_GA("}\n");
continue;
}
if (head.en_addr != 0) {
PRINT_YA(" Chunk - start: ");
PRINTF_BA("0x%08" PFMT64x, head.en_addr);
PRINT_YA(", end: ");
PRINTF_BA("0x%" PFMT64x, head.en_addr + cnksz);
PRINT_YA(", size: ");
PRINTF_BA("0x%08" PFMT64x "\n", cnksz);
ut64 next_addr = head.ql_link_next;
while (next_addr && read_and_parse_extent_node_t_450(core->io, next_addr, &node, config)) {
if (node.en_addr == head.en_addr) {
break;
}
PRINT_YA(" Chunk - start: ");
PRINTF_BA("0x%08" PFMT64x, node.en_addr);
PRINT_YA(", end: ");
PRINTF_BA("0x%" PFMT64x, node.en_addr + cnksz);
PRINT_YA(", size: ");
PRINTF_BA("0x%" PFMT64x "\n", cnksz);
next_addr = node.ql_link_next;
}
}
PRINT_GA("}\n");
}
}
}
static void jemalloc_print_narenas_450(RzCore *core, const char *input, const RzJemallocConfig450 *config) {
ut64 symaddr;
ut64 arenas;
ut64 arena_addr = UT64_MAX;
int i = 0;
ut64 narenas = 0;
RzConsPrintablePalette *pal = &rz_cons_singleton()->context->pal;
if (input[0] == '\0') {
if (rz_resolve_jemalloc(core, "narenas_total", &symaddr)) {
if (!read_ptr_at(core->io, symaddr, &narenas, config->ptr_size)) {
RZ_LOG_ERROR("Failed to read narenas_total\n");
return;
}
PRINTF_GA("narenas : %" PFMT64d "\n", narenas);
}
if (narenas == 0) {
RZ_LOG_ERROR("No arenas allocated.\n");
return;
}
if (narenas == UT64_MAX) {
RZ_LOG_ERROR("Cannot find narenas_total\n");
return;
}
if (rz_resolve_jemalloc(core, "je_arenas", &arenas)) {
if (!read_ptr_at(core->io, arenas, &arenas, config->ptr_size)) {
RZ_LOG_ERROR("Failed to read je_arenas pointer\n");
return;
}
PRINTF_GA("arenas[%" PFMT64d "] @ 0x%" PFMT64x " {\n", narenas, arenas);
for (i = 0; i < (int)narenas; i++) {
ut64 at = arenas + (i * config->ptr_size);
if (!read_ptr_at(core->io, at, &arena_addr, config->ptr_size)) {
continue;
}
if (!arena_addr) {
PRINTF_YA(" arenas[%d]: (empty)\n", i);
continue;
}
PRINTF_YA(" arenas[%d]: ", i);
PRINTF_BA("@ 0x%" PFMT64x "\n", arena_addr);
}
}
PRINT_GA("}\n");
} else {
// Handle address argument (with or without leading space)
const char *addr_str = (input[0] == ' ') ? input + 1 : input;
arena_addr = rz_num_math(core->num, addr_str);
arena_t_450 ar;
if (!read_and_parse_arena_t_450(core->io, arena_addr, &ar, config)) {
RZ_LOG_ERROR("Failed to read arena at 0x%" PFMT64x "\n", arena_addr);
return;
}
PRINT_GA("struct arena_s {\n");
PRINTF_BA(" ind = 0x%x\n", ar.ind);
PRINTF_BA(" nthreads: application allocation = 0x%x\n", ar.nthreads[0]);
PRINTF_BA(" nthreads: internal metadata allocation = 0x%x\n", ar.nthreads[1]);
PRINTF_BA(" lock = 0x%" PFMT64x "\n", ar.lock_addr);
PRINTF_BA(" stats = 0x%" PFMT64x "\n", ar.stats_addr);
PRINTF_BA(" tcache_ql = 0x%" PFMT64x "\n", ar.tcache_ql_addr);
PRINTF_BA(" prof_accumbytes = 0x%" PFMT64x "x\n", ar.prof_accumbytes);
PRINTF_BA(" offset_state = 0x%" PFMT64x "\n", ar.offset_state);
PRINTF_BA(" dss_prec_t = 0x%x\n", ar.dss_prec);
PRINTF_BA(" achunks = 0x%" PFMT64x "\n", ar.achunks_addr);
PRINTF_BA(" extent_sn_next = 0x%" PFMT64x "\n", ar.extent_sn_next);
PRINTF_BA(" spare = 0x%" PFMT64x "\n", ar.spare);
PRINTF_BA(" lg_dirty_mult = 0x%" PFMT64x "\n", (ut64)ar.lg_dirty_mult);
PRINTF_BA(" purging = %s\n", rz_str_bool(ar.purging));
PRINTF_BA(" nactive = 0x%" PFMT64x "\n", ar.nactive);
PRINTF_BA(" ndirty = 0x%" PFMT64x "\n", ar.ndirty);
PRINTF_BA(" runs_dirty = 0x%" PFMT64x "\n", ar.runs_dirty_addr);
PRINTF_BA(" chunks_cache = 0x%" PFMT64x "\n", ar.chunks_cache_addr);
PRINTF_BA(" huge = 0x%" PFMT64x "\n", ar.huge_addr);
PRINTF_BA(" huge_mtx = 0x%" PFMT64x "\n", ar.huge_mtx_addr);
PRINTF_BA(" chunks_szsnad_cached = 0x%" PFMT64x "\n", ar.chunks_szsnad_cached_addr);
PRINTF_BA(" chunks_ad_cached = 0x%" PFMT64x "\n", ar.chunks_ad_cached_addr);
PRINTF_BA(" chunks_szsnad_retained = 0x%" PFMT64x "\n", ar.chunks_szsnad_cached_addr);
PRINTF_BA(" chunks_ad_cached = 0x%" PFMT64x "\n", ar.chunks_ad_cached_addr);
PRINTF_BA(" chunks_mtx = 0x%" PFMT64x "\n", ar.chunks_mtx_addr);
PRINTF_BA(" node_cache = 0x%" PFMT64x "\n", ar.node_cache_addr);
PRINTF_BA(" node_cache_mtx = 0x%" PFMT64x "\n", ar.node_cache_mtx_addr);
PRINTF_BA(" chunks_hooks = 0x%" PFMT64x "\n", ar.chunk_hooks_addr);
PRINTF_BA(" bins = %d 0x%" PFMT64x "\n", config->sc_nbins, ar.bins_addr);
PRINTF_BA(" runs_avail = %d 0x%" PFMT64x "\n", config->npsizes, ar.runs_avail_addr);
PRINT_GA("}\n");
}
}
// Helper to print bin info for a single arena
static void jemalloc_print_arena_bins_450(RzCore *core, ut64 arena_addr, ut64 bin_info, RzConsPrintablePalette *pal, const RzJemallocConfig450 *config) {
arena_bin_info_t_450 b;
for (ut32 j = 0; j < config->sc_nbins; j++) {
if (!read_and_parse_arena_bin_info_t_450(core->io, bin_info + j * config->bin_info_size, &b, config)) {
continue;
}
PRINT_YA(" {\n");
PRINT_YA(" regsize : ");
PRINTF_BA("0x%" PFMT64x "\n", b.reg_size);
PRINT_YA(" redzone size ");
PRINTF_BA("0x%" PFMT64x "\n", b.redzone_size);
PRINT_YA(" reg_interval : ");
PRINTF_BA("0x%" PFMT64x "\n", b.reg_interval);
PRINT_YA(" run_size : ");
PRINTF_BA("0x%" PFMT64x "\n", b.run_size);
PRINT_YA(" nregs : ");
PRINTF_BA("0x%x\n", b.nregs);
PRINT_YA(" reg0_offset : ");
PRINTF_BA("0x%x\n\n", b.reg0_offset);
PRINT_YA(" }\n");
}
}
static void jemalloc_get_bins_450(RzCore *core, const char *input, const RzJemallocConfig450 *config) {
ut64 bin_info;
ut64 arenas;
ut64 arena_addr = UT64_MAX;
RzConsPrintablePalette *pal = &rz_cons_singleton()->context->pal;
int i = 0;
if (input[0] == '\0') {
// No argument - use symbol resolution (debug mode)
if (!rz_resolve_jemalloc(core, "je_arena_bin_info", &bin_info)) {
RZ_LOG_ERROR("Cannot resolve je_arena_bin_info\n");
return;
}
if (rz_resolve_jemalloc(core, "je_arenas", &arenas)) {
if (!read_ptr_at(core->io, arenas, &arenas, config->ptr_size)) {
RZ_LOG_ERROR("Failed to read je_arenas pointer\n");
return;
}
PRINTF_GA("arenas @ 0x%" PFMT64x " {\n", arenas);
for (;;) {
if (!read_ptr_at(core->io, arenas + i * config->ptr_size, &arena_addr, config->ptr_size)) {
break;
}
if (!arena_addr) {
break;
}
PRINTF_YA(" arenas[%d]: @ 0x%" PFMT64x " {\n", i++, arena_addr);
jemalloc_print_arena_bins_450(core, arena_addr, bin_info, pal, config);
PRINT_YA(" }\n");
}
}
PRINT_GA("}\n");
} else {
// Static mode - requires two arguments: dmxb <arena_addr> <bin_info_addr>
const char *addr_str = (input[0] == ' ') ? input + 1 : input;
char *args = strdup(addr_str);
if (!args) {
return;
}
char *bin_info_str = strchr(args, ' ');
if (!bin_info_str) {
RZ_LOG_ERROR("Usage: dmxb <arena_addr> <bin_info_addr>\n");
free(args);
return;
}
*bin_info_str++ = '\0';
arena_addr = rz_num_math(core->num, args);
bin_info = rz_num_math(core->num, bin_info_str);
PRINTF_GA("arena_t @ 0x%" PFMT64x " bins[%d] {\n", arena_addr, config->sc_nbins);
jemalloc_print_arena_bins_450(core, arena_addr, bin_info, pal, config);
PRINT_GA("}\n");
free(args);
}
}
// ============================================================================
// jemalloc 5.3.0
// ============================================================================
static void jemalloc_print_extent_info_530(RzCore *core, ut64 edata_addr, const RzJemallocConfig530 *config) {
RzConsPrintablePalette *pal = &rz_cons_singleton()->context->pal;
edata_t_530 edata;
static const char *state_names[] = { "Active", "Dirty", "Muzzy", "Retained" };
if (!read_and_parse_edata_t_530(core->io, edata_addr, &edata, config)) {
RZ_LOG_ERROR("Failed to read edata at 0x%" PFMT64x "\n", edata_addr);
return;
}
ut64 e_bits = edata.e_bits;
ut64 e_addr = edata.e_addr;
ut64 e_size = edata.e_size_esn & ~((ut64)(1 << config->lg_page) - 1);
ut32 state = (e_bits & EDATA_BITS_STATE_MASK) >> EDATA_BITS_STATE_SHIFT;
bool slab = (e_bits & EDATA_BITS_SLAB_MASK) >> EDATA_BITS_SLAB_SHIFT;
PRINT_YA("Extent @ ");
PRINTF_BA("0x%" PFMT64x "\n", edata_addr);
PRINT_YA(" Allocated Address: ");
PRINTF_BA("0x%" PFMT64x "\n", e_addr);
PRINT_YA(" Size: ");
PRINTF_BA("0x%" PFMT64x "\n", e_size);
PRINT_YA(" Small class (slab): ");
PRINTF_BA("%s\n", slab ? "true" : "false");
PRINT_YA(" State: ");
PRINTF_BA("%s\n", state < 4 ? state_names[state] : "Unknown");
}
static void jemalloc_process_leaf_elm_530(RzCore *core, ut64 leaf_addr, const RzJemallocConfig530 *config,
HtUU *seen_extents, ut32 *extent_count) {
rtree_leaf_elm_t_530 leaf;
if (!read_and_parse_rtree_leaf_elm_t_530(core->io, leaf_addr, &leaf, config)) {
return;
}
ut64 edata_addr;
if (config->rtree_leaf_compact) {
// Compact leaf format with le_bits
if (leaf.le_bits == 0) {
return;
}
edata_addr = rtree_leaf_elm_bits_edata_get_530(leaf.le_bits);
} else {
// Non-compact format with direct le_edata pointer
edata_addr = leaf.le_edata;
}
if (edata_addr == 0) {
return;
}
// Skip duplicates
bool found = false;
ht_uu_find(seen_extents, edata_addr, &found);
if (found) {
return;
}
ht_uu_insert(seen_extents, edata_addr, 1);
jemalloc_print_extent_info_530(core, edata_addr, config);
rz_cons_printf("\n");
(*extent_count)++;
}
static void jemalloc_enumerate_extents_530(RzCore *core, ut64 rtree_addr, const RzJemallocConfig530 *config) {
RzConsPrintablePalette *pal = &rz_cons_singleton()->context->pal;
HtUU *seen_extents = ht_uu_new();
if (!seen_extents) {
RZ_LOG_ERROR("Failed to allocate hash table\n");
return;
}
ut32 lg_page, rtree_nsb, bits_per_level, max_subkeys;
ut64 root_offset;
rtree_params_530(config, &lg_page, &rtree_nsb, &bits_per_level, &max_subkeys, &root_offset);
ut64 root_addr = rtree_addr + root_offset;
ut32 extent_count = 0;
ut32 rtree_height = config->rtree_height;
PRINT_GA("Enumerating extents from rtree...\n");
if (rtree_height == 2) {
// 2-level tree: root -> leaf (x86_64, aarch64, i386, arm32)
for (ut32 i = 0; i < max_subkeys; i++) {
rtree_node_elm_t_530 node;
ut64 node_addr = root_addr + i * config->rtree_node_elm_size;
if (!read_and_parse_rtree_node_elm_t_530(core->io, node_addr, &node, config)) {
continue;
}
ut64 leaf_base = node.child;
if (leaf_base == 0) {
continue;
}
// Level 2: iterate through leaf nodes
for (ut32 j = 0; j < max_subkeys; j++) {
ut64 leaf_addr = leaf_base + j * config->rtree_leaf_elm_size;
jemalloc_process_leaf_elm_530(core, leaf_addr, config, seen_extents, &extent_count);
}
}
} else if (rtree_height == 3) {
// 3-level tree: root -> node -> leaf (riscv64)
for (ut32 i = 0; i < max_subkeys; i++) {
rtree_node_elm_t_530 level1_node;
ut64 level1_addr = root_addr + i * config->rtree_node_elm_size;
if (!read_and_parse_rtree_node_elm_t_530(core->io, level1_addr, &level1_node, config)) {
continue;
}
ut64 level2_base = level1_node.child;
if (level2_base == 0) {
continue;
}
// Level 2: intermediate nodes
for (ut32 j = 0; j < max_subkeys; j++) {
rtree_node_elm_t_530 level2_node;
ut64 level2_addr = level2_base + j * config->rtree_node_elm_size;
if (!read_and_parse_rtree_node_elm_t_530(core->io, level2_addr, &level2_node, config)) {
continue;
}
ut64 leaf_base = level2_node.child;
if (leaf_base == 0) {
continue;
}
// Level 3: leaf nodes
for (ut32 k = 0; k < max_subkeys; k++) {
ut64 leaf_addr = leaf_base + k * config->rtree_leaf_elm_size;
jemalloc_process_leaf_elm_530(core, leaf_addr, config, seen_extents, &extent_count);
}
}
}
} else {
RZ_LOG_ERROR("Unsupported rtree height: %u\n", rtree_height);
}
PRINTF_GA("Total extents found: %u\n", extent_count);
ht_uu_free(seen_extents);
}
static ut64 jemalloc_rtree_lookup_530(RzCore *core, ut64 rtree_addr, ut64 addr, const RzJemallocConfig530 *config) {
ut32 lg_page, rtree_nsb, bits_per_level, max_subkeys;
ut64 root_offset;
rtree_params_530(config, &lg_page, &rtree_nsb, &bits_per_level, &max_subkeys, &root_offset);
// Remove page offset to get the key
ut64 key = addr >> lg_page;
ut32 mask = (1U << bits_per_level) - 1;
ut64 root_addr = rtree_addr + root_offset;
ut32 rtree_height = config->rtree_height;
ut64 leaf_base = 0;
if (rtree_height == 2) {
// 2-level tree: root -> leaf
ut32 root_idx = (key >> bits_per_level) & mask;
ut32 leaf_idx = key & mask;
rtree_node_elm_t_530 node;
ut64 node_addr = root_addr + (ut64)root_idx * config->rtree_node_elm_size;
if (!read_and_parse_rtree_node_elm_t_530(core->io, node_addr, &node, config)) {
return 0;
}
leaf_base = node.child;
if (leaf_base == 0) {
return 0;
}
rtree_leaf_elm_t_530 leaf;
ut64 leaf_addr = leaf_base + (ut64)leaf_idx * config->rtree_leaf_elm_size;
if (!read_and_parse_rtree_leaf_elm_t_530(core->io, leaf_addr, &leaf, config)) {
return 0;
}
if (config->rtree_leaf_compact) {
if (leaf.le_bits == 0) {
return 0;
}
return rtree_leaf_elm_bits_edata_get_530(leaf.le_bits);
} else {
return leaf.le_edata;
}
} else if (rtree_height == 3) {
// 3-level tree: root -> node -> leaf (riscv64)
ut32 level1_idx = (key >> (2 * bits_per_level)) & mask;
ut32 level2_idx = (key >> bits_per_level) & mask;
ut32 leaf_idx = key & mask;
// Level 1: root node
rtree_node_elm_t_530 level1_node;
ut64 level1_addr = root_addr + (ut64)level1_idx * config->rtree_node_elm_size;
if (!read_and_parse_rtree_node_elm_t_530(core->io, level1_addr, &level1_node, config)) {
return 0;
}
ut64 level2_base = level1_node.child;
if (level2_base == 0) {
return 0;
}
// Level 2: intermediate node
rtree_node_elm_t_530 level2_node;
ut64 level2_addr = level2_base + (ut64)level2_idx * config->rtree_node_elm_size;
if (!read_and_parse_rtree_node_elm_t_530(core->io, level2_addr, &level2_node, config)) {
return 0;
}
leaf_base = level2_node.child;
if (leaf_base == 0) {
return 0;
}
// Level 3: leaf
rtree_leaf_elm_t_530 leaf;
ut64 leaf_addr = leaf_base + (ut64)leaf_idx * config->rtree_leaf_elm_size;
if (!read_and_parse_rtree_leaf_elm_t_530(core->io, leaf_addr, &leaf, config)) {
return 0;
}
if (config->rtree_leaf_compact) {
if (leaf.le_bits == 0) {
return 0;
}
return rtree_leaf_elm_bits_edata_get_530(leaf.le_bits);
} else {
return leaf.le_edata;
}
}
return 0;
}
static void jemalloc_find_extent_530(RzCore *core, const char *input, const RzJemallocConfig530 *config) {
ut64 je_arena_emap_global_addr;
RzConsPrintablePalette *pal = &rz_cons_singleton()->context->pal;
if (input[0] == '\0') {
// No argument: enumerate all extents
if (rz_resolve_jemalloc(core, "je_arena_emap_global", &je_arena_emap_global_addr)) {
jemalloc_enumerate_extents_530(core, je_arena_emap_global_addr, config);
} else {
RZ_LOG_ERROR("Cannot resolve je_arena_emap_global\n");
}
} else {
// Address argument: lookup single address in rtree
const char *addr_str = (input[0] == ' ') ? input + 1 : input;
ut64 lookup_addr = rz_num_math(core->num, addr_str);
if (!rz_resolve_jemalloc(core, "je_arena_emap_global", &je_arena_emap_global_addr)) {
RZ_LOG_ERROR("Cannot resolve je_arena_emap_global\n");
return;
}
ut64 edata_addr = jemalloc_rtree_lookup_530(core, je_arena_emap_global_addr, lookup_addr, config);
if (edata_addr == 0) {
PRINTF_RA("No extent found for address 0x%" PFMT64x "\n", lookup_addr);
return;
}
jemalloc_print_extent_info_530(core, edata_addr, config);
}
}
static void jemalloc_extent_info_530(RzCore *core, const char *input, const RzJemallocConfig530 *config) {
if (input[0] == '\0') {
RZ_LOG_ERROR("Usage: dmxei <edata_addr>\n");
return;
}
const char *addr_str = (input[0] == ' ') ? input + 1 : input;
ut64 edata_addr = rz_num_math(core->num, addr_str);
jemalloc_print_extent_info_530(core, edata_addr, config);
}
static void jemalloc_print_arena_bins_530(RzCore *core, ut64 arena, ut64 bin_info_addr, const RzJemallocConfig530 *config) {
RzConsPrintablePalette *pal = &rz_cons_singleton()->context->pal;
bin_info_t_530 bin_info;
bin_t_530 bin;
ut64 bins_off = config->arena_offsets.bins;
for (ut32 j = 0; j < config->sc_nbins; j++) {
if (!read_and_parse_bin_info_t_530(core->io, bin_info_addr + j * config->bin_info_size, &bin_info, config)) {
continue;
}
ut64 bin_addr = arena + bins_off + j * config->bin_size;
if (!read_and_parse_bin_t_530(core->io, bin_addr, &bin, config)) {
continue;
}
PRINTF_YA(" bin[%d] @ 0x%" PFMT64x " {\n", j, bin_addr);
PRINT_YA(" reg_size : ");
PRINTF_BA("0x%" PFMT64x "\n", bin_info.reg_size);
PRINT_YA(" slab_size : ");
PRINTF_BA("0x%" PFMT64x "\n", bin_info.slab_size);
PRINT_YA(" nregs : ");
PRINTF_BA("0x%x\n", bin_info.nregs);
PRINT_YA(" n_shards : ");
PRINTF_BA("0x%x\n", bin_info.n_shards);
PRINT_YA(" slabcur : ");
PRINTF_BA("0x%" PFMT64x "\n", bin.slabcur);
PRINT_YA(" }\n");
}
}
static void jemalloc_get_bins_530(RzCore *core, const char *input, const RzJemallocConfig530 *config) {
int i = 0;
ut64 bin_info;
ut64 arenas_sym;
ut64 arena = UT64_MAX;
RzConsPrintablePalette *pal = &rz_cons_singleton()->context->pal;
if (input[0] == '\0') {
// No argument - use symbol resolution (debug mode)
if (!rz_resolve_jemalloc(core, "je_bin_infos", &bin_info)) {
RZ_LOG_ERROR("Cannot resolve je_bin_infos\n");
return;
}
if (rz_resolve_jemalloc(core, "je_arenas", &arenas_sym)) {
PRINTF_GA("arenas @ 0x%" PFMT64x " {\n", arenas_sym);
for (;;) {
if (!read_ptr_at(core->io, arenas_sym + i * config->ptr_size, &arena, config->ptr_size)) {
break;
}
if (!arena) {
break;
}
PRINTF_YA(" arenas[%d]: @ 0x%" PFMT64x " {\n", i++, arena);
jemalloc_print_arena_bins_530(core, arena, bin_info, config);
PRINT_YA(" }\n");
}
}
PRINT_GA("}\n");
} else {
// Static mode - requires two arguments: dmxb <arena_addr> <bin_info_addr>
const char *addr_str = (input[0] == ' ') ? input + 1 : input;
char *args = strdup(addr_str);
if (!args) {
return;
}
char *bin_info_str = strchr(args, ' ');
if (!bin_info_str) {
RZ_LOG_ERROR("Usage: dmxb <arena_addr> <bin_info_addr>\n");
free(args);
return;
}
*bin_info_str++ = '\0';
arena = rz_num_math(core->num, args);
bin_info = rz_num_math(core->num, bin_info_str);
PRINTF_GA("arena_t @ 0x%" PFMT64x " bins[%d] {\n", arena, config->sc_nbins);
jemalloc_print_arena_bins_530(core, arena, bin_info, config);
PRINT_GA("}\n");
free(args);
}
}
static void jemalloc_print_narenas_530(RzCore *core, const char *input, const RzJemallocConfig530 *config) {
ut64 symaddr;
ut64 arenas;
ut64 arena = UT64_MAX;
int i = 0;
ut64 narenas = 0;
RzConsPrintablePalette *pal = &rz_cons_singleton()->context->pal;
if (input[0] == '\0') { // no args, list all arenas
if (rz_resolve_jemalloc(core, "narenas_total", &symaddr)) {
if (!read_ptr_at(core->io, symaddr, &narenas, config->ptr_size)) {
RZ_LOG_ERROR("Failed to read narenas_total\n");
return;
}
PRINTF_GA("narenas : %" PFMT64d "\n", narenas);
}
if (narenas == 0) {
RZ_LOG_ERROR("No arenas allocated.\n");
return;
}
if (narenas == UT64_MAX) {
RZ_LOG_ERROR("Cannot find narenas_total\n");
return;
}
if (rz_resolve_jemalloc(core, "je_arenas", &arenas)) {
PRINTF_GA("arenas[%" PFMT64d "] @ 0x%" PFMT64x " {\n", narenas, arenas);
for (i = 0; i < (int)narenas; i++) {
ut64 at = arenas + (i * config->ptr_size);
if (!read_ptr_at(core->io, at, &arena, config->ptr_size)) {
continue;
}
if (!arena) {
PRINTF_YA(" arenas[%d]: (empty)\n", i);
continue;
}
PRINTF_YA(" arenas[%d]: ", i);
PRINTF_BA("@ 0x%" PFMT64x "\n", arena);
}
}
PRINT_GA("}\n");
} else {
const char *addr_str = (input[0] == ' ') ? input + 1 : input;
arena = rz_num_math(core->num, addr_str);
arena_t_530 ar;
if (!read_and_parse_arena_t_530(core->io, arena, &ar, config)) {
RZ_LOG_ERROR("Failed to read arena at 0x%" PFMT64x "\n", arena);
return;
}
PRINT_GA("struct arena_s {\n");
PRINTF_BA(" ind = 0x%x\n", ar.ind);
PRINTF_BA(" nthreads: application allocation = 0x%x\n", ar.nthreads[0]);
PRINTF_BA(" nthreads: internal metadata allocation = 0x%x\n", ar.nthreads[1]);
PRINTF_BA(" binshard_next = 0x%x\n", ar.binshard_next);
PRINTF_BA(" last_thd = 0x%" PFMT64x "\n", ar.last_thd);
PRINTF_BA(" stats = 0x%" PFMT64x "\n", ar.stats_addr);
PRINTF_BA(" tcache_ql = 0x%" PFMT64x "\n", ar.tcache_ql_addr);
PRINTF_BA(" cache_bin_array_descriptor_ql = 0x%" PFMT64x "\n", ar.cache_bin_array_descriptor_ql_addr);
PRINTF_BA(" tcache_ql_mtx = 0x%" PFMT64x "\n", ar.tcache_ql_mtx_addr);
PRINTF_BA(" dss_prec = 0x%x\n", ar.dss_prec);
PRINTF_BA(" large = 0x%" PFMT64x "\n", ar.large_addr);
PRINTF_BA(" large_mtx = 0x%" PFMT64x "\n", ar.large_mtx_addr);
PRINTF_BA(" pa_shard = 0x%" PFMT64x "\n", ar.pa_shard_addr);
PRINTF_BA(" base = 0x%" PFMT64x "\n", ar.base);
PRINTF_BA(" create_time.ns = 0x%" PFMT64x "\n", ar.create_time_ns);
PRINTF_BA(" bins = 0x%" PFMT64x "\n", ar.bins_addr);
PRINT_GA("}\n");
}
}
// ============================================================================
// page size detection
// ============================================================================
static const char *detect_page_size_from_smaps(int pid) {
#if __linux__
if (pid <= 0) {
return NULL;
}
char path[64];
snprintf(path, sizeof(path), "/proc/%d/smaps", pid);
FILE *f = fopen(path, "r");
if (!f) {
return NULL;
}
char line[256];
const char *result = NULL;
while (fgets(line, sizeof(line), f)) {
int page_kb = 0;
if (sscanf(line, "KernelPageSize: %d kB", &page_kb) == 1) {
switch (page_kb) {
case 4:
result = "4k";
break;
case 16:
result = "16k";
break;
case 64:
result = "64k";
break;
default:
result = "4k";
break;
}
break;
}
}
fclose(f);
return result;
#else
return NULL;
#endif
}
static const char *detect_page_size(RzCore *core, const char *arch, const char *os, int bits) {
const char *page_size = rz_config_get(core->config, "dbg.jemalloc.page_size");
if (page_size && strcmp(page_size, "auto") != 0) {
return page_size;
}
bool is_darwin = os && (!strcmp(os, "darwin") || !strcmp(os, "macos") || !strcmp(os, "ios"));
bool is_linux = os && !strcmp(os, "linux");
bool is_aarch64 = arch && (!strcmp(arch, "arm") || !strcmp(arch, "aarch64")) && bits == 64;
// darwin on aarch64: always 16KB
if (is_darwin && is_aarch64) {
return "16k";
}
// aarch64 linux: Can be 4k, 16k, or 64k
if (is_linux && is_aarch64) {
int pid = core->dbg ? core->dbg->pid : -1;
const char *detected = detect_page_size_from_smaps(pid);
if (detected) {
return detected;
}
// Fall back to 4k (most common)
}
// everything else: 4k (x86, x86_64, riscv, arm32)
return "4k";
}
// ============================================================================
// dispatcher
// ============================================================================
void cmd_dbg_map_jemalloc(RzCore *core, char dmx_variant, const char *arg) {
const char *version = rz_config_get(core->config, "dbg.jemalloc.version");
if (!version || strcmp(version, "auto") == 0 || strcmp(version, "NULL") == 0 || version[0] == '\0') {
rz_jemalloc_detect_version(core);
version = rz_config_get(core->config, "dbg.jemalloc.version");
}
const char *arch = rz_config_get(core->config, "asm.arch");
const char *os = rz_config_get(core->config, "asm.os");
int bits = rz_config_get_i(core->config, "asm.bits");
const char *page_size = detect_page_size(core, arch, os, bits);
if (version && strcmp(version, "4.5.0") == 0) {
const RzJemallocConfig450 *config = rz_jemalloc_get_config_450(arch, os, bits, page_size);
if (!config) {
RZ_LOG_ERROR("Failed to get jemalloc 4.5.0 configuration\n");
return;
}
switch (dmx_variant) {
case 'a': // dmxa
jemalloc_print_narenas_450(core, arg, config);
break;
case 'b': // dmxb
jemalloc_get_bins_450(core, arg, config);
break;
case 'c': // dmxc
jemalloc_get_chunks_450(core, arg, config);
break;
default:
RZ_LOG_ERROR("Command not supported for jemalloc 4.5.0\n");
break;
}
} else if (version && strcmp(version, "5.3.0") == 0) {
const RzJemallocConfig530 *config = rz_jemalloc_get_config_530(arch, os, bits, page_size);
if (!config) {
RZ_LOG_ERROR("Failed to get jemalloc 5.3.0 configuration\n");
return;
}
switch (dmx_variant) {
case 'a': // dmxa - print arena
jemalloc_print_narenas_530(core, arg, config);
break;
case 'b': // dmxb - bin info
jemalloc_get_bins_530(core, arg, config);
break;
case 'e': // dmxe - find extent for malloc'd address
jemalloc_find_extent_530(core, arg, config);
break;
case 'i': // dmxei - extent info
jemalloc_extent_info_530(core, arg, config);
break;
default:
RZ_LOG_ERROR("Command not supported for jemalloc 5.3.0\n");
break;
}
} else {
RZ_LOG_ERROR("Unknown jemalloc version. Please set dbg.jemalloc.version to '4.5.0' or '5.3.0'\n");
}
}