diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index ec05579..479b44e 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -32,11 +32,19 @@ jobs: - uses: actions/checkout@v3 - name: Run test ubuntu run: docker build . --file test/DockerfileTest_ubuntu --build-arg VARIANT="ubuntu:24.04" --build-arg GDB_VERSION="16.3" --tag core_analyzer:ubuntu$(date +%s) + - name: Clean up disk space + run: docker system prune -f - name: Run test redhat run: docker build . --file test/DockerfileTest_redhat --build-arg VARIANT="redhat/ubi10" --build-arg GDB_VERSION="16.3" --tag core_analyzer:rdht$(date +%s) + - name: Clean up disk space + run: docker system prune -f - name: Run test fedora run: docker build . --file test/DockerfileTest_redhat --build-arg VARIANT="fedora:42" --build-arg GDB_VERSION="16.3" --tag core_analyzer:rdht$(date +%s) + - name: Clean up disk space + run: docker system prune -f - name: Run test suse run: docker build . --file test/DockerfileTest_suse --build-arg VARIANT="opensuse/leap:16.0" --build-arg GDB_VERSION="16.3" --tag core_analyzer:suse$(date +%s) - - name: Run test 9 + - name: Clean up disk space + run: docker system prune -f + - name: Run test gdb 9.2 run: docker build . --file test/DockerfileTest_gdb_9_2 --tag core_analyzer:9_2$(date +%s) diff --git a/README.md b/README.md index 7a57a64..93790b9 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,7 @@ The latest release passed the build and sanity tests (with a few exceptions) on * Heap Manager - glibc/ptmalloc - gperftools/tcmalloc + - microsoft/mimalloc - jemalloc - Windows/mscrt - Darwin diff --git a/build_mimalloc.sh b/build_mimalloc.sh new file mode 100755 index 0000000..c36d794 --- /dev/null +++ b/build_mimalloc.sh @@ -0,0 +1,62 @@ +#!/usr/bin/env bash + +# ============================================================================= +# FILENAME : build_mimalloc.sh +# AUTHOR : Michael Yan +# CREATION : 2026-04-02 +# Script to build microsoft/mimalloc with specified version. +# +# This script will the do the following steps +# 1. Create working directory +# 2. clone the mimalloc source +# 3. checkout the desired release version +# 4. configure, build and install +# ============================================================================= +set -e + +if [ "$#" -ne 1 ] +then + echo "Usage: $0 " + echo " For example, \"$0 2.2.7\"" + echo " Please refer to https://github.com/microsoft/mimalloc/tags" + exit 1 +fi + +PROJECT_FOLDER=$(pwd) +release_tag=$1 +# prefix release tag with 'v' if not already +if [[ $release_tag != v* ]]; then + release_tag="v$release_tag" +fi + +echo "Current project folder is $PROJECT_FOLDER" +build_folder=$PROJECT_FOLDER/build +mkdir -p $build_folder +cd $build_folder + +# workaround the problem of multi builds from the same source folder, for example, 2.15 and 2.14 +rm -rf ./mimalloc + +scr_dir="mimalloc" +if [ ! -d $scr_dir ] +then + echo "cloning mimalloc ..." + git clone https://github.com/microsoft/mimalloc.git +fi +cd $scr_dir + +echo "checkout $release_tag" +branch_name=mimalloc-$release_tag +if [ -n "$(git branch --list ${branch_name})" ] +then + echo "Branch name $branch_name already exists." +else + git checkout tags/$release_tag -b $branch_name +fi + +mkdir -p out/release +cd out/release + +echo "building..." +cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo ../.. +make clean && make -j 4 && sudo make install diff --git a/gdbplus/gdb-12.1/gdb/Makefile.in b/gdbplus/gdb-12.1/gdb/Makefile.in index b65a7ae..85b8114 100644 --- a/gdbplus/gdb-12.1/gdb/Makefile.in +++ b/gdbplus/gdb-12.1/gdb/Makefile.in @@ -1098,6 +1098,7 @@ COMMON_SFILES = \ heap_ptmalloc_2_35.c \ heap_tcmalloc.c \ heap_jemalloc.c \ + heap_mimalloc.c \ heapcmd.c \ i386-decode.c \ inf-child.c \ diff --git a/gdbplus/gdb-12.1/gdb/heap_mimalloc.c b/gdbplus/gdb-12.1/gdb/heap_mimalloc.c new file mode 100644 index 0000000..4cff587 --- /dev/null +++ b/gdbplus/gdb-12.1/gdb/heap_mimalloc.c @@ -0,0 +1,652 @@ +/* + * heap_mimalloc.c + * + * Created on: March 30, 2026 + * Author: Michael Yan + * + * This Implementation uses gdb specific types. Hence not portable to non-Linux + * platforms + */ + +#include "segment.h" +#include "heap_mimalloc.h" +#include + +#define CA_DEBUG 0 +#if CA_DEBUG +#define CA_PRINT_DBG CA_PRINT +#else +#define CA_PRINT_DBG(format,args...) +#endif + + +// Globals +static int mi_version_major = 0; +static int mi_version_minor = 0; +static int mi_version_patch = 0; +static bool mi_guard_page = false; +static bool mi_encode_freelist = false; + +static bool g_initialized = false; +static int g_bin_count = 0; // number of size classes (or "bins") of pages + +static std::set g_cached_blocks; // free blocks +static std::vector g_pages; + +// Forward declaration +static bool gdb_symbol_prelude(void); +static bool read_mi_version(void); +static bool parse_thread_local_heap(void); +static bool parse_page(struct value* page_val, int bin_index); +static bool parse_page_queue(struct value* page_queue_val, int bin_index); + +static ca_page* find_page(address_t addr); +static ca_page* find_next_page(address_t addr); +static bool is_block_cached(address_t); + +static void add_one_big_block(struct heap_block *, unsigned int, + struct heap_block *); + +/****************************************************************************** + * Exposed functions + *****************************************************************************/ +static const char * +heap_version(void) +{ + return "mimalloc"; +} + +static bool +init_heap(void) +{ + // Start with a clean slate + g_initialized = false; + g_cached_blocks.clear(); + g_pages.clear(); + + // Start with allocator's version + read_mi_version(); + + // Parse thread local heaps + if (!parse_thread_local_heap()) { + CA_PRINT("Failed to parse thread local heap\n"); + return false; + } + + // Sort pages by start address for future binary search + std::sort(g_pages.begin(), g_pages.end()); + + g_initialized = true; + return true; +} + +static bool +get_heap_block_info(address_t addr, struct heap_block* blk) +{ + if (g_initialized == false) { + CA_PRINT("mimalloc heap was not initialized successfully\n"); + return false; + } + + // Found the page that contains the block + ca_page* page = find_page(addr); + if (page == nullptr) + return false; + + // Traverse the blocks in the page to find the block that contains the address + for (address_t block_addr = page->start; block_addr < page->end; block_addr += page->block_size) { + if (addr >= block_addr && addr < block_addr + page->block_size) { + blk->addr = block_addr; + blk->size = page->block_size; + blk->inuse = !is_block_cached(block_addr); + return true; + } + } + return false; +} + +static bool +get_next_heap_block(address_t addr, struct heap_block* blk) +{ + if (g_initialized == false || g_pages.empty()) { + CA_PRINT("mimalloc heap was not initialized successfully\n"); + return false; + } + + // If addr is 0, return the first block of the first page + if (addr == 0) { + blk->addr = g_pages[0].start; + blk->size = g_pages[0].block_size; + blk->inuse = !is_block_cached(blk->addr); + return true; + } + + // Find the page that contains the address + ca_page* page = find_page(addr); + if (page) { + // If the next block in the same page is valid, return it + if (addr + page->block_size >= page->start && addr + page->block_size < page->end) { + blk->addr = addr + page->block_size; + blk->size = page->block_size; + blk->inuse = !is_block_cached(blk->addr); + return true; + } else { + // If the page is the last page, return false + if (page == &g_pages.back()) + return false; + // Otherwise, return the first block of the next page + page++; + blk->addr = page->start; + blk->size = page->block_size; + blk->inuse = !is_block_cached(blk->addr); + return true; + } + } else { + // If the address is not in any page, + // find the next page that has a start address greater than the given address + page = find_next_page(addr); + if (page) { + blk->addr = page->start; + blk->size = page->block_size; + blk->inuse = !is_block_cached(blk->addr); + return true; + } + } + + return false; +} + +/* Return true if the block belongs to a heap */ +static bool +is_heap_block(address_t addr) +{ + + if (g_initialized == false) { + CA_PRINT("mimalloc heap was not initialized successfully\n"); + return false; + } + + return find_page(addr) != nullptr; +} + +/* + * Traverse all pages unless a non-zero address is given, in which case the + * specific page is walked + */ +static bool +heap_walk(address_t heapaddr, bool verbose) +{ + if (g_initialized == false) { + CA_PRINT("mimalloc heap was not initialized successfully\n"); + return false; + } + + // If heapaddr is given, find the page that contains the address and display blocks in the page + if (heapaddr) { + ca_page* page = find_page(heapaddr); + if (page) { + CA_PRINT("Page [%#lx - %#lx], block size: %zu\n", page->start, page->end, page->block_size); + for (address_t addr = page->start; addr < page->end; addr += page->block_size) { + bool inuse = !is_block_cached(addr); + CA_PRINT("\t[%#lx - %#lx] %ld bytes %s\n", addr, addr + page->block_size, + page->block_size, inuse ? "inuse" : "free"); + } + return true; + } else { + CA_PRINT("No page found for address %p\n", (void*)heapaddr); + return false; + } + } + + // If heapaddr is not given, display all pages and blocks + std::unique_ptr bins(new ca_bin[g_bin_count]); + for (const auto& page : g_pages) { + if (page.bin_index >= 0 && page.bin_index < g_bin_count) { + bins[page.bin_index].bin_index = page.bin_index; + bins[page.bin_index].page_count++; + bins[page.bin_index].block_size = page.block_size; + for (address_t addr = page.start; addr < page.end; addr += page.block_size) { + if (is_block_cached(addr)) + bins[page.bin_index].free_blks++; + else + bins[page.bin_index].inuse_blks++; + } + } else { + CA_PRINT("Invalid bin index %d for page [%#lx - %#lx]\n", page.bin_index, page.start, page.end); + return false; + } + } + + // Display version and tuning parameters + CA_PRINT("mimalloc version: %d.%d.%d\n", mi_version_major, mi_version_minor, mi_version_patch); + CA_PRINT("\tguard page: %s\n", mi_guard_page ? "enabled" : "disabled"); + CA_PRINT("\tencode freelist: %s\n", mi_encode_freelist ? "enabled" : "disabled"); + // Print one blank line + CA_PRINT("\n"); + + // Display statistics + CA_PRINT(" size_class num_pages block_size inuse_blks inuse_bytes free_blks free_bytes\n"); + for (int i = 0; i < g_bin_count; i++) { + size_t inuse_bytes = bins[i].inuse_blks * bins[i].block_size; + size_t free_bytes = bins[i].free_blks * bins[i].block_size; + CA_PRINT("%10d %10zu %10zu %10zu %12zu %10zu %12zu\n", + i, bins[i].page_count, bins[i].block_size, bins[i].inuse_blks, inuse_bytes, bins[i].free_blks, free_bytes); + } + + size_t total_inuse_blks = 0; + size_t total_free_blks = 0; + size_t total_inuse_bytes = 0; + size_t total_free_bytes = 0; + for (int i = 0; i < g_bin_count; i++) { + total_inuse_blks += bins[i].inuse_blks; + total_free_blks += bins[i].free_blks; + total_inuse_bytes += bins[i].inuse_blks * bins[i].block_size; + total_free_bytes += bins[i].free_blks * bins[i].block_size; + } + CA_PRINT("------------------------------------------------------------------------------------\n"); + CA_PRINT(" Total %10zu %10s %10zu %12zu %10zu %12zu\n", + g_pages.size(), "", total_inuse_blks, total_inuse_bytes, total_free_blks, total_free_bytes); + + return true; +} + +static bool +get_biggest_blocks(struct heap_block* blks, unsigned int num) +{ + if (g_initialized == false) { + CA_PRINT("mimalloc heap was not initialized successfully\n"); + return false; + } + + if (num == 0) + return true; + memset(blks, 0, num * sizeof *blks); + + // Traverse big blocks (size class MI_BIN_FULL) first and populate the returned array + for (auto const& page : g_pages) { + if (page.bin_index == MI_BIN_FULL) { + for (address_t addr = page.start; addr < page.end; addr += page.block_size) { + if (!is_block_cached(addr)) { + struct heap_block blk = {addr, page.block_size, true}; + add_one_big_block(blks, num, &blk); + } + } + } + } + + // Return if we have enough big blocks + struct heap_block* smallest = &blks[num - 1]; + if (smallest->size > 0) + return true; + + // continue to traverse normal blocks (index != MI_BIN_FULL) and populate the returned array + for (auto const& page : g_pages) { + if (page.bin_index != MI_BIN_FULL) { + for (address_t addr = page.start; addr < page.end; addr += page.block_size) { + if (!is_block_cached(addr) && page.block_size > smallest->size) { + struct heap_block blk = {addr, page.block_size, true}; + add_one_big_block(blks, num, &blk); + } + } + } + } + + return true; +} + +static bool +walk_inuse_blocks(struct inuse_block* opBlocks, unsigned long* opCount) +{ + if (g_initialized == false) { + CA_PRINT("mimalloc heap was not initialized successfully\n"); + return false; + } + + if (opCount == nullptr) + return false; + + // Traverse all pages and blocks to find inuse blocks + *opCount = 0; + for (const auto& page : g_pages) { + for (address_t addr = page.start; addr < page.end; addr += page.block_size) { + if (!is_block_cached(addr)) { + (*opCount)++; + if (opBlocks) { + opBlocks->addr = addr; + opBlocks->size = page.block_size; + opBlocks++; + } + } + } + } + return true; +} + + +CoreAnalyzerHeapInterface sMiMallHeapManager = { + heap_version, + init_heap, + heap_walk, + is_heap_block, + get_heap_block_info, + get_next_heap_block, + get_biggest_blocks, + walk_inuse_blocks, +}; + +void register_mi_malloc() { + bool my_heap = gdb_symbol_prelude(); + return register_heap_manager("mi", &sMiMallHeapManager, my_heap); +} +/****************************************************************************** + * Helper Functions + *****************************************************************************/ +static bool read_mi_version(void) +{ + // hack it by reading the 2nd byte of funciton mi_version() + struct symbol* sym = lookup_symbol("mi_version", 0, VAR_DOMAIN, 0).symbol; + if (sym == nullptr) { + CA_PRINT_DBG("Failed to lookup function \"mi_version\"\n"); + return false; + } + struct value* func_val = value_of_variable(sym, 0); + address_t func_addr = value_as_address(func_val); + unsigned char version = 0; + if (!read_memory_wrapper(nullptr, func_addr + 1, &version, sizeof(version))) { + CA_PRINT_DBG("Failed to read mi_version at address %p\n", (void*)(func_addr + 1)); + return false; + } + mi_version_major = version/100; + mi_version_minor = (version%100)/10; + mi_version_patch = version%10; + CA_PRINT_DBG("Detected mimalloc version: %d.%d.%d\n", + mi_version_major, mi_version_minor, mi_version_patch); + + return true; +} + +static bool parse_page(struct value* page_val, int bin_index) +{ + struct ca_segment *segment; + + CA_PRINT_DBG("\tParsing mi_page_t at address %p for bin index %d\n", + (void*)(page_val->address()), bin_index); + + // If the page has "keys" field, it means the free list is encoded. + // We currently don't support parsing encoded free list, so skip this page + static bool once = false; + if (!once) { + struct value* keys_val = ca_get_field_gdb_value(page_val, "keys"); + if (keys_val != nullptr) { + mi_encode_freelist = true; + CA_PRINT_DBG("\tmi_page_t has encoded free list\n"); + } + once = true; + } + + // Calculate address of the blocks in the page + struct value* page_start_val = ca_get_field_gdb_value(page_val, "page_start"); + struct value* capacity_val = ca_get_field_gdb_value(page_val, "capacity"); + struct value* block_size_val = ca_get_field_gdb_value(page_val, "block_size"); + if (page_start_val == nullptr || capacity_val == nullptr || block_size_val == nullptr) { + CA_PRINT("Failed to get fields of mi_page_t: page_start, capacity, block_size\n"); + return false; + } + address_t block_start = value_as_address(page_start_val); + size_t capacity = value_as_long(capacity_val); + size_t block_size = value_as_long(block_size_val); + if (block_start == 0 || block_size == 0 || capacity == 0) { + CA_PRINT("Invalid block_start, block_size or capacity in mi_page_t\n"); + return false; + } + address_t block_end = block_start + block_size * capacity; + // Get the free blocks in the page + // include mi_page_t::free, mi_page_t::local_free and mi_page_t::xthread_free + size_t free_blk_count = 0; + struct value* free_val = ca_get_field_gdb_value(page_val, "free"); + address_t block_addr = value_as_address(free_val); + while (block_addr != 0) { + if (block_addr < block_start || block_addr >= block_end) { + CA_PRINT("Invalid free block address %p in mi_page_t\n", (void*)block_addr); + return false; + } + g_cached_blocks.insert(block_addr); + free_blk_count++; + + // read the next pointer in the free list + if (!read_memory_wrapper(nullptr, block_addr, &block_addr, sizeof(block_addr))) { + CA_PRINT("Failed to read free block at address %p\n", (void*)block_addr); + return false; + } + } + size_t local_free_blk_count = 0; + struct value* local_free_val = ca_get_field_gdb_value(page_val, "local_free"); + block_addr = value_as_address(local_free_val); + while (block_addr != 0) { + if (block_addr < block_start || block_addr >= block_end) { + CA_PRINT("Invalid local free block address %p in mi_page_t\n", (void*)block_addr); + return false; + } + g_cached_blocks.insert(block_addr); + local_free_blk_count++; + + // read the next pointer in the local free list + if (!read_memory_wrapper(nullptr, block_addr, &block_addr, sizeof(block_addr))) { + CA_PRINT("Failed to read local free block at address %p\n", (void*)block_addr); + return false; + } + } + size_t xthread_free_blk_count = 0; + struct value* xthread_free_val = ca_get_field_gdb_value(page_val, "xthread_free"); + block_addr = value_as_address(xthread_free_val); + // bottom 2 bits of the pointer for mi_delayed_t flags + block_addr &= ~0x3; + while (block_addr != 0) { + if (block_addr < block_start || block_addr >= block_end) { + CA_PRINT("Invalid xthread free block address %p in mi_page_t %p\n", + (void*)block_addr, (void*)value_address(page_val)); + return false; + } + g_cached_blocks.insert(block_addr); + xthread_free_blk_count++; + + // read the next pointer in the xthread free list + if (!read_memory_wrapper(nullptr, block_addr, &block_addr, sizeof(block_addr))) { + CA_PRINT("Failed to read xthread free block at address %p\n", (void*)block_addr); + return false; + } + block_addr &= ~0x3; + } + + // Check the free block count against the page used and capacity + // `used + |free| + |local_free| == capacity` + // actual blocks that are in use (alive) == `used - |xthread_free|` + struct value* used_val = ca_get_field_gdb_value(page_val, "used"); + size_t used_blk_count = value_as_long(used_val); + if (used_blk_count + free_blk_count + local_free_blk_count != capacity) { + CA_PRINT_DBG("Invalid used or free block count in mi_page_t: used %zu + free %zu + local_free %zu != capacity %zu\n", + used_blk_count, free_blk_count, local_free_blk_count, capacity); + } + // Add the page to the global page list for future reference + ca_page page = { value_address(page_val), block_start, block_end, block_size, bin_index }; + g_pages.push_back(page); + + // update the segment that the page belongs to + segment = get_segment(block_start, block_end - block_start); + if (segment && segment->m_type == ENUM_UNKNOWN) { + segment->m_type = ENUM_HEAP; + } + return true; +} + +static bool parse_page_queue(struct value* page_queue_val, int bin_index) +{ + struct value* first_val = ca_get_field_gdb_value(page_queue_val, "first"); + struct value* page = first_val; + while (page && value_as_address(page) != 0) { + page = value_ind(page); + if (!parse_page(page, bin_index)) { + CA_PRINT("Failed to parse mi_page_t at address %p\n", (void*)value_as_address(page)); + return false; + } + // Get the next page in the queue + page = ca_get_field_gdb_value(page, "next"); + } + + return true; +} + +static int thread_local_heap (struct thread_info *info, void *data) +{ + struct symbol *sym; + struct value *thread_heap_p, *thread_heap; + + switch_to_thread (info); + + // __thread mi_heap_t* _mi_heap_default + sym = lookup_global_symbol("_mi_heap_default", nullptr, + VAR_DOMAIN).symbol; + if (sym == NULL) { + CA_PRINT("Failed to lookup gv \"_mi_heap_default\"\n"); + return false; + } + thread_heap_p = value_of_variable(sym, 0); + CA_PRINT_DBG("Thread %d: _mi_heap_default at address %p\n", info->global_num, (void*)value_as_address(thread_heap_p)); + thread_heap = value_ind(thread_heap_p); + + // If the heap has "guarded_size_min" field, it means the heap has guard page enabled + static bool once = false; + if (!once) { + struct value* guarded_size_min_val = ca_get_field_gdb_value(thread_heap, "guarded_size_min"); + if (guarded_size_min_val) { + mi_guard_page = true; + } + once = true; + } + + // Traverse heap list + while (thread_heap) + { + CA_PRINT_DBG("Process heap at address %p\n", (void*)thread_heap->address()); + struct value* page_count_val = ca_get_field_gdb_value(thread_heap, "page_count"); + if (page_count_val && value_as_long(page_count_val) > 0) { + // Field "pages" is an array of mi_page_queue_t + struct value* pages_val = ca_get_field_gdb_value(thread_heap, "pages"); + if (g_bin_count == 0) { + LONGEST low_bound, high_bound; + if (get_array_bounds (value_type(pages_val), &low_bound, &high_bound) == 0) { + CA_PRINT("Could not determine \"pages\" bounds\n"); + return false; + } + g_bin_count = high_bound - low_bound + 1; + CA_PRINT_DBG("Detected mi_heap_t::pages[%ld-%ld] array length %d\n", + low_bound, high_bound, g_bin_count); + if (g_bin_count <= 0) { + CA_PRINT("Invalid \"pages\" array bounds: low %ld, high %ld\n", low_bound, high_bound); + return false; + } + } + for (int index = 0; index < g_bin_count; index++) { + struct value *val = value_subscript(pages_val, index); + if (parse_page_queue(val, index) == false) { + CA_PRINT("Failed to parse mi_page_queue_t at index %d\n", index); + break; + } + } + } + // mi_heap_t::thread_delayed_free is a list of delayed free blocks. + struct value* delayed_free_val = ca_get_field_gdb_value(thread_heap, "thread_delayed_free"); + address_t block_addr = value_as_address(delayed_free_val); + while (block_addr != 0) { + g_cached_blocks.insert(block_addr); + // Assume singly linked list + if (!read_memory_wrapper(nullptr, block_addr, &block_addr, sizeof(block_addr))) { + CA_PRINT("Failed to read mi_heap_t::thread_delayed_free at address %p\n", (void*)block_addr); + break; + } + } + + // next heap + struct value* heap_next = ca_get_field_gdb_value(thread_heap, "next"); + if (heap_next == nullptr || value_as_address(heap_next) == 0) + thread_heap = nullptr; + else + thread_heap = value_ind(heap_next); + } + + return 0; +} + +static bool parse_thread_local_heap(void) +{ + struct thread_info* old; + // Traverse all threads for thread-local variables `_mi_heap_default` + // remember current thread + old = inferior_thread(); + // switch to all threads + iterate_over_threads(thread_local_heap, NULL); + // resume the old thread + switch_to_thread (old); + + return true; +} + +static void add_one_big_block(struct heap_block *blks, unsigned int num, + struct heap_block *blk) +{ + unsigned int i; + + for (i = 0; i < num; i++) { + if (blk->size > blks[i].size) { + int k; + // Insert blk->blks[i] + // Move blks[i]->blks[i+1], .., blks[num-2]->blks[num-1] + for (k = ((int)num) - 2; k >= (int)i; k--) + blks[k+1] = blks[k]; + blks[i] = *blk; + break; + } + } +} + +static bool gdb_symbol_prelude(void) +{ + // static __attribute__((aligned(64))) mi_arena_t* mi_arenas[MI_MAX_ARENAS]; + struct symbol *sym = lookup_symbol("mi_arenas", nullptr, VAR_DOMAIN, nullptr).symbol; + if (sym == nullptr) { + CA_PRINT_DBG("Failed to lookup gv \"mi_arenas\"\n"); + return false; + } + return true; +} + +static bool is_block_cached(address_t addr) +{ + auto itr = g_cached_blocks.find(addr); + return itr != g_cached_blocks.end(); +} + +static ca_page* find_next_page(address_t addr) +{ + // Found the next page after the address + auto page_itr = std::upper_bound(g_pages.begin(), g_pages.end(), addr, [](const address_t& pageaddr, const ca_page& page) { + return pageaddr < page.start; + }); + if (page_itr == g_pages.end()) + return nullptr; + return &(*page_itr); +} + +static ca_page* find_page(address_t addr) +{ + // Found the page that contains the block + auto page_itr = std::upper_bound(g_pages.begin(), g_pages.end(), addr, [](const address_t& pageaddr, const ca_page& page) { + return pageaddr < page.start; + }); + if (page_itr == g_pages.begin()) + return nullptr; + page_itr--; + if (addr < page_itr->start || addr >= page_itr->end) + return nullptr; + return &(*page_itr); +} diff --git a/gdbplus/gdb-12.1/gdb/heap_mimalloc.h b/gdbplus/gdb-12.1/gdb/heap_mimalloc.h new file mode 120000 index 0000000..8598590 --- /dev/null +++ b/gdbplus/gdb-12.1/gdb/heap_mimalloc.h @@ -0,0 +1 @@ +../../../src/heap_mimalloc.h \ No newline at end of file diff --git a/gdbplus/gdb-16.3/gdb/Makefile.in b/gdbplus/gdb-16.3/gdb/Makefile.in index d5ceada..6c33eab 100644 --- a/gdbplus/gdb-16.3/gdb/Makefile.in +++ b/gdbplus/gdb-16.3/gdb/Makefile.in @@ -1149,6 +1149,7 @@ COMMON_SFILES = \ heap_ptmalloc_2_35.c \ heap_tcmalloc.c \ heap_jemalloc.c \ + heap_mimalloc.c \ heapcmd.c \ i386-decode.c \ inf-child.c \ diff --git a/gdbplus/gdb-16.3/gdb/gdb_dep.c b/gdbplus/gdb-16.3/gdb/gdb_dep.c index 1987880..777d74b 100644 --- a/gdbplus/gdb-16.3/gdb/gdb_dep.c +++ b/gdbplus/gdb-16.3/gdb/gdb_dep.c @@ -1451,7 +1451,6 @@ print_func_locals (void) /* check local vars */ block = get_frame_block (frame, 0); while (block) { - struct block_iterator iter; struct value *val; CORE_ADDR val_addr; diff --git a/gdbplus/gdb-16.3/gdb/heap_mimalloc.c b/gdbplus/gdb-16.3/gdb/heap_mimalloc.c new file mode 120000 index 0000000..1f75891 --- /dev/null +++ b/gdbplus/gdb-16.3/gdb/heap_mimalloc.c @@ -0,0 +1 @@ +../../../src/heap_mimalloc.cpp \ No newline at end of file diff --git a/gdbplus/gdb-16.3/gdb/heap_mimalloc.h b/gdbplus/gdb-16.3/gdb/heap_mimalloc.h new file mode 120000 index 0000000..8598590 --- /dev/null +++ b/gdbplus/gdb-16.3/gdb/heap_mimalloc.h @@ -0,0 +1 @@ +../../../src/heap_mimalloc.h \ No newline at end of file diff --git a/gdbplus/gdb-9.2/gdb/heap.c b/gdbplus/gdb-9.2/gdb/heap.c deleted file mode 120000 index dd209cc..0000000 --- a/gdbplus/gdb-9.2/gdb/heap.c +++ /dev/null @@ -1 +0,0 @@ -../../../src/heap.cpp \ No newline at end of file diff --git a/gdbplus/gdb-9.2/gdb/heap.c b/gdbplus/gdb-9.2/gdb/heap.c new file mode 100644 index 0000000..18342b9 --- /dev/null +++ b/gdbplus/gdb-9.2/gdb/heap.c @@ -0,0 +1,1757 @@ +/* + * heap.cpp + * Functions for heap memory + * + * Created on: Dec 13, 2011 + * Author: myan + */ +#include "defs.h" +#include "heap.h" +#include "segment.h" +#include "search.h" + +CoreAnalyzerHeapInterface* gCAHeap; + +#define ENSURE_CA_HEAP() \ + do { \ + if (!CA_HEAP) { \ + CA_PRINT("No heap manager is detedted or selected.\n"); \ + return false; \ + } \ + } while (0) + +std::map gCoreAnalyzerHeaps; + +static std::vector gHeapRegistrationFuncs = { + #ifdef WIN32 + register_mscrt_malloc, + #else + register_pt_malloc_2_27, + register_pt_malloc_2_31, + register_pt_malloc_2_35, + register_tc_malloc, + register_je_malloc, + #endif +}; + +bool init_heap_managers() { + gCoreAnalyzerHeaps.clear(); + gCAHeap = nullptr; + + for (auto f: gHeapRegistrationFuncs) + f(); + + if (gCAHeap) { + if (!CA_HEAP->init_heap()) { + CA_PRINT("failed to init heap\n"); + return false; + } + return true; + } + CA_PRINT("failed to parse heap data\n"); + return false; +} + +void register_heap_manager(std::string name, CoreAnalyzerHeapInterface* heapIntf, bool detected) { + gCoreAnalyzerHeaps[name] = heapIntf; + if (detected) { + /* TODO we need to resolve the scenario that multi heap managers are present */ + gCAHeap = heapIntf; + } +} + +std::string get_supported_heaps() { + std::string lSupportedHeaps; + bool first_entry = true; + for (const auto &itr : gCoreAnalyzerHeaps) { + if (!first_entry) + { + lSupportedHeaps += ", "; + } + if (itr.second == gCAHeap) + lSupportedHeaps += "(current)"; + lSupportedHeaps += itr.first; + first_entry = false; + } + return lSupportedHeaps; +} + +// Used to search for variables that allocate/reach the most heap memory +struct heap_owner +{ + struct object_reference ref; + size_t aggr_size; + unsigned long aggr_count; +}; + +#define LINE_BUF_SZ 1024 + +// Forward declaration +static bool +mark_blocks_referenced_by_globals_locals(std::vector&, unsigned int*); + +static void +display_histogram(const char*, unsigned int, + const size_t*, const unsigned long*, const size_t*); + +static unsigned long +get_next_queued_index(unsigned int*, unsigned long, unsigned long); + +static void add_owner(struct heap_owner*, unsigned int, struct heap_owner*); + +static size_t +heap_aggregate_size(struct reachable_block*, std::vector&, + unsigned int*, unsigned long*); + +static bool +build_block_index_map(struct reachable_block*, std::vector&); + +// Global Vars +static struct MemHistogram g_mem_hist; + +static struct inuse_block *g_inuse_blocks = NULL; +static unsigned long g_num_inuse_blocks = 0; + +// Binary search if addr belongs to one of the blocks +static int +inuse_block_cmp(const void *key, const void *elmt) +{ + const struct inuse_block *a = (struct inuse_block *)key; + const struct inuse_block *b = (struct inuse_block *)elmt; + if (a->addr < b->addr) { + return -1; + } else if (a->addr >= b->addr + b->size) { + return 1; + } else { + return 0; + } +} + +struct inuse_block * +find_inuse_block(address_t addr, struct inuse_block *blocks, unsigned long total_blocks) +{ + struct inuse_block key = {addr, 1}; + return (struct inuse_block *) bsearch(&key, blocks, total_blocks, + sizeof(*blocks), inuse_block_cmp); +} + +static struct reachable_block * +find_reachable_block(address_t addr, std::vector& blocks) +{ + struct reachable_block key(addr, 1); + return (struct reachable_block *) bsearch(&key, &blocks[0], blocks.size(), + sizeof(blocks[0]), inuse_block_cmp); +} + +/* + * Parse user options and invoke corresponding heap-related functions + */ +bool +heap_command_impl(char* args) +{ + ENSURE_CA_HEAP(); + + bool rc = true; + + address_t addr = 0; + bool verbose = false; + bool check_leak = false; + bool calc_usage = false; + bool block_info = false; + bool cluster_blocks = false; + bool top_block = false; + bool top_user = false; + bool exlusive_opt = false; + bool all_reachable_blocks = false; // experimental option + char* expr = NULL; + +#define check_exclusive_option() \ + if (exlusive_opt) { \ + CA_PRINT("Option [%s] conflicts with one of the previous options\n", option); \ + return false; \ + } else { \ + exlusive_opt = true; \ + } + + // Parse user input options + // argument is either an address or /v or /leak + if (args) { + char* options[MAX_NUM_OPTIONS]; + int num_options = ca_parse_options(args, options); + int i; + + for (i = 0; i < num_options; i++) { + char* option = options[i]; + if (*option == '/') { + if (strcmp(option, "/m") == 0) { + check_exclusive_option(); + CA_PRINT("Target allocator: %s\n", CA_HEAP->heap_version()); + return true; + } else if (strcmp(option, "/leak") == 0 || strcmp(option, "/l") == 0) { + check_leak = true; + check_exclusive_option(); + } else if (strcmp(option, "/verbose") == 0 || strcmp(option, "/v") == 0) { + verbose = true; + } else if (strcmp(option, "/block") == 0 || strcmp(option, "/b") == 0) { + block_info = true; + check_exclusive_option(); + } else if (strcmp(option, "/cluster") == 0 || strcmp(option, "/c") == 0) { + cluster_blocks = true; + check_exclusive_option(); + } else if (strcmp(option, "/usage") == 0 || strcmp(option, "/u") == 0) { + calc_usage = true; + check_exclusive_option(); + } else if (strcmp(option, "/topblock") == 0 || strcmp(option, "/tb") == 0) { + top_block = true; + check_exclusive_option(); + } else if (strcmp(option, "/topuser") == 0 || strcmp(option, "/tu") == 0) { + top_user = true; + check_exclusive_option(); + } else if (strcmp(option, "/all") == 0 || strcmp(option, "/a") == 0) { + all_reachable_blocks = true; + } else { + CA_PRINT("Invalid option: [%s]\n", option); + return false; + } + } else if (calc_usage) { + expr = option; + break; + } else if (addr == 0) { + addr = ca_eval_address (option); + } else { + CA_PRINT("Invalid option: [%s]\n", option); + return false; + } + } + } + + if (check_leak) { + if (addr) + CA_PRINT("Unexpected address expression\n"); + else + display_heap_leak_candidates(); + } else if (block_info) { + if (!addr) + CA_PRINT("Heap block address is expected\n"); + else { + struct heap_block heap_block; + if (CA_HEAP->get_heap_block_info(addr, &heap_block)) { + if (heap_block.inuse) + CA_PRINT("\t[In-use]\n"); + else + CA_PRINT("\t[Free]\n"); + CA_PRINT("\t[Address] " PRINT_FORMAT_POINTER "\n", heap_block.addr); + CA_PRINT("\t[Size] " PRINT_FORMAT_SIZE "\n", heap_block.size); + CA_PRINT("\t[Offset] " PRINT_FORMAT_SIZE "\n", addr - heap_block.addr); + } else { + CA_PRINT("[Error] Failed to query the memory block\n"); + } + } + } + else if (cluster_blocks) { + if (addr) { + if (!CA_HEAP->heap_walk(addr, verbose)) + CA_PRINT("[Error] Failed to walk heap\n"); + } else { + CA_PRINT("Heap block address is expected\n"); + } + } else if (calc_usage) { + if (expr) + calc_heap_usage(expr); + else + CA_PRINT("An expression of heap memory owner is expected\n"); + } else if (top_block || top_user) { + unsigned int n = (unsigned int)addr; + if (n == 0) + CA_PRINT("A number is expected\n"); + else if (top_user) + biggest_heap_owners_generic(n, all_reachable_blocks); + else + biggest_blocks(n); + } else { + if (addr) + CA_PRINT("Unexpected address expression\n"); + else if (!CA_HEAP->heap_walk(addr, verbose)) + CA_PRINT("[Error] Failed to walk heap\n"); + } + + return rc; +} + +/* + * Parse user options and invoke corresponding search functions + */ +bool ref_command_impl(char* args) +{ + ENSURE_CA_HEAP(); + + int rc; + bool threadref = false; + address_t addr = 0; + size_t size = 0; + size_t level = 0; + + // Parse user input options + // argument is in the form of [size] [level] + if (args) + { + char* options[MAX_NUM_OPTIONS]; + int num_options = ca_parse_options(args, options); + int i; + for (i = 0; i < num_options; i++) + { + char* option = options[i]; + if (strcmp(option, "/thread") == 0 || strcmp(option, "/t") == 0) + threadref = true; + else if (addr == 0) + { + addr = ca_eval_address (option); + if (addr == 0) + { + CA_PRINT("Invalid address [%s] argument\n", option); + return false; + } + } + else if (size == 0) + { + size = ca_eval_address (option); + if (size == 0) + { + CA_PRINT("Invalid size [%s] argument\n", option); + return false; + } + } + else if (level == 0) + { + level = ca_eval_address (option); + if (level == 0) + { + CA_PRINT("Invalid level [%s] argument\n", option); + return false; + } + } + else + { + CA_PRINT("Too many arguments: %s\n", option); + return false; + } + } + } + + if (addr == 0) + { + CA_PRINT("Missing object address."); + return false; + } + + if (threadref) + { + if (size == 0) + size = 1; + if (level == 0) + level = 1; + CA_PRINT("Search for thread references to " PRINT_FORMAT_POINTER " size " PRINT_FORMAT_SIZE " up to " PRINT_FORMAT_SIZE " levels of indirection\n", + addr, size, level); + rc = find_object_refs_on_threads(addr, size, level); + } + else + { + if (size == 0) + { + CA_PRINT("Search for object type associated with " PRINT_FORMAT_POINTER "\n", addr); + rc = find_object_type(addr); + } + else + { + if (level == 0) + level = 1; + CA_PRINT("Search for references to " PRINT_FORMAT_POINTER " size " PRINT_FORMAT_SIZE " up to " PRINT_FORMAT_SIZE " levels of indirection\n", + addr, size, level); + rc = find_object_refs(addr, size, level); + } + } + if (!rc) + CA_PRINT("No result found\n"); + + return true; +} + +/* + * Parse user options and invoke corresponding segment functions + */ +static void +print_segment(struct ca_segment* segment) +{ + CA_PRINT("[" PRINT_FORMAT_POINTER " - " PRINT_FORMAT_POINTER "] %6ldK %c%c%c ", + segment->m_vaddr, segment->m_vaddr+segment->m_vsize, + segment->m_vsize/1024, + segment->m_read?'r':'-', segment->m_write?'w':'-', segment->m_exec?'x':'-'); + + //if (g_debug_core && segment->m_fsize != segment->m_vsize) + // CA_PRINT(" (fsize=%ldK)", segment->m_fsize/1024); + + if (segment->m_type == ENUM_MODULE_TEXT) + CA_PRINT("[.text/.rodata] [%s]", segment->m_module_name); + else if (segment->m_type == ENUM_MODULE_DATA) + CA_PRINT("[.data/.bss] [%s]", segment->m_module_name); + else if (segment->m_type == ENUM_STACK) + { + CA_PRINT("[stack] [tid=%d]", segment->m_thread.tid); + if (segment->m_thread.lwp) + CA_PRINT(" [lwp=%ld]", segment->m_thread.lwp); + } + else if (segment->m_type == ENUM_HEAP) + CA_PRINT("[heap]"); + CA_PRINT("\n"); +} + +bool segment_command_impl(char* args) +{ + struct ca_segment* segment; + + if (args && *args != '\0') + { + address_t addr = ca_eval_address (args); + segment = get_segment(addr, 0); + if (segment) + { + CA_PRINT("Address %s belongs to segment:\n", args); + print_segment(segment); + } + else + CA_PRINT("Address %s doesn't belong to any segment\n", args); + } + else + { + unsigned int i; + CA_PRINT("vaddr size perm name\n"); + CA_PRINT("=====================================================\n"); + for (i=0; i 0 && ret != EOF) + ret += fscanf (fp, "%[^\n]\n", filename); + ret = fscanf (fp, "Size: %ld kB\n", &vsize); + ret = fscanf (fp, "Rss: %ld kB\n", &rss); + for (k = 0; k < 11; k++) + fgets(filename, 128, fp); // Pss, etc. + //CA_PRINT("Segment: 0x%lx SIZE=%ld RSS=%ld\n", addr, vsize, rss); + if (vsize > rss) + { + struct ca_segment* segment = get_segment(addr, 1); + size_t gap = (vsize - rss) * 1024; + if (segment) + { + if (segment->m_type == ENUM_STACK) + stack_gap += gap; + else if (segment->m_type == ENUM_MODULE_TEXT || segment->m_type == ENUM_MODULE_DATA) + module_gap += gap; + else if (segment->m_type == ENUM_HEAP) + heap_gap += gap; + } + } + } while(1); + fclose(fp); + + CA_PRINT("Gap between SIZE and RSS:\n"); + CA_PRINT("\tmodules: "); + print_size(module_gap); + CA_PRINT("\n"); + CA_PRINT("\tthreads: "); + print_size(stack_gap); + CA_PRINT("\n"); + CA_PRINT("\theap: "); + print_size(heap_gap); + CA_PRINT("\n"); + } + }*/ + } + return true; +} + +/* + * Parse user options and invoke corresponding pattern function + */ +bool pattern_command_impl(char* args) +{ + ENSURE_CA_HEAP(); + + address_t lo = 0, hi = 0; + // Parse user input options + // argument is in the form of + if (args) + { + char* options[MAX_NUM_OPTIONS]; + int num_options = ca_parse_options(args, options); + if (num_options != 2) + { + CA_PRINT("Expect arguments: \n"); + return false; + } + lo = ca_eval_address (options[0]); + hi = ca_eval_address (options[1]); + if (hi <= lo) + { + CA_PRINT("Invalid memory address range (start >= end)\n"); + return false; + } + } + else + { + CA_PRINT("Missing object address."); + return false; + } + + print_memory_pattern(lo, hi); + + return true; +} + +/* + * Return an array of struct inuse_block, of all in-use blocks + * the array is cached for repeated usage unless a live process has changed + */ +struct inuse_block * +build_inuse_heap_blocks(unsigned long* opCount) +{ + struct inuse_block* blocks = NULL; + unsigned long total_inuse = 0; + + if (g_inuse_blocks && g_num_inuse_blocks) + { + if (g_debug_core) + { + *opCount = g_num_inuse_blocks; + return g_inuse_blocks; + } + else + { + // FIXME + // Even for a live process, return here if it hasn't change since last time + free(g_inuse_blocks); + g_inuse_blocks = NULL; + g_num_inuse_blocks = 0; + } + } + + *opCount = 0; + // 1st walk counts the number of in-use blocks + if (CA_HEAP->walk_inuse_blocks(NULL, &total_inuse) && total_inuse) + { + // allocate memory for inuse_block array + blocks = (struct inuse_block*) calloc(total_inuse, sizeof(struct inuse_block)); + if (!blocks) + { + CA_PRINT("Failed: Out of Memory\n"); + return NULL; + } + // 2nd walk populate the array for in-use block info + if (!CA_HEAP->walk_inuse_blocks(blocks, opCount) || *opCount != total_inuse) + { + CA_PRINT("Unexpected error while walking in-use blocks\n"); + *opCount = 0; + free (blocks); + return NULL; + } + // sanity check whether the array is sorted by address, as required. + if (total_inuse >= 2) + { + unsigned long count; + struct inuse_block* cursor; + for (count = 0, cursor = blocks; count < total_inuse - 1; count++, cursor++) + { + if (cursor->addr + cursor->size > (cursor+1)->addr) + { + CA_PRINT("Internal error: in-use array is not properly sorted at %ld\n", count); + CA_PRINT("\t[%ld] " PRINT_FORMAT_POINTER " size=%ld\n", count, cursor->addr, cursor->size); + CA_PRINT("\t[%ld] " PRINT_FORMAT_POINTER "\n", count+1, (cursor+1)->addr); + free (blocks); + *opCount = 0; + return NULL; + } + } + } + } + // cache the data + g_inuse_blocks = blocks; + g_num_inuse_blocks = total_inuse; + + return blocks; +} + +static bool +build_reachable_blocks(std::vector& orBlocks) +{ + struct inuse_block *inuse_blocks; + unsigned long index; + unsigned long count; + + inuse_blocks = build_inuse_heap_blocks(&count); + if (!inuse_blocks || count == 0) { + CA_PRINT("Failed: no in-use heap block is found\n"); + return false; + } + + orBlocks.reserve(count); + for (index = 0; index < count; index++) + orBlocks.push_back(inuse_blocks[index]); + + return true; +} + +/* + * Bitmap for in-use blocks is used + * Each block uses two bits(queued/visited) + * one "int" may contain bits for 16 blocks + */ +#define QUEUED 0x01u +#define VISITED 0x02u + +/*static inline void set_visited(unsigned int* bitmap, unsigned long index) +{ + unsigned long bit = (index & 0xf) << 1; + bitmap[index >> 4] |= (VISITED << bit); +} + +static inline unsigned int is_queued_or_visited(unsigned int* bitmap, unsigned long index) +{ + unsigned long bit = (index & 0xf) << 1; + return (bitmap[index >> 4] & ((QUEUED | VISITED) << bit)); +} + +static inline void reset_queued(unsigned int* bitmap, unsigned long index) +{ + unsigned long bit = (index & 0xf) << 1; + bitmap[index >> 4] &= ~(QUEUED << bit); +} + +static inline void set_queued(unsigned int* bitmap, unsigned long index) +{ + unsigned long bit = (index & 0xf) << 1; + bitmap[index >> 4] |= (QUEUED << bit); +} + +static inline unsigned int is_queued(unsigned int* bitmap, unsigned long index) +{ + unsigned long bit = (index & 0xf) << 1; + return (bitmap[index >> 4] & (QUEUED << bit)); +}*/ +#define set_visited(bitmap,index) bitmap[(index) >> 4] |= (VISITED << (((index) & 0xf) << 1)) +#define is_queued_or_visited(bitmap,index) (bitmap[(index) >> 4] & ((QUEUED | VISITED) << (((index) & 0xf) << 1))) +#define reset_queued(bitmap,index) bitmap[(index) >> 4] &= ~(QUEUED << (((index) & 0xf) << 1)) +#define set_queued(bitmap,index) bitmap[(index) >> 4] |= (QUEUED << (((index) & 0xf) << 1)) +#define is_queued(bitmap,index) (bitmap[(index) >> 4] & (QUEUED << (((index) & 0xf) << 1))) +#define is_visited(bitmap,index) (bitmap[(index) >> 4] & (VISITED << (((index) & 0xf) << 1))) +#define set_queued_and_visited(bitmap,index) bitmap[(index) >> 4] |= ((QUEUED | VISITED) << (((index) & 0xf) << 1)) + +static const size_t GB = 1024*1024*1024; +static const size_t MB = 1024*1024; +static const size_t KB = 1024; + +// A utility function +void print_size(size_t sz) +{ + if (sz > GB) + CA_PRINT("%.1fGB", (double)sz/(double)GB); + else if (sz > MB) + CA_PRINT(PRINT_FORMAT_SIZE"MB", sz/MB); + else if (sz > KB) + CA_PRINT(PRINT_FORMAT_SIZE"KB", sz/KB); + else + CA_PRINT(PRINT_FORMAT_SIZE, sz); +} + +static void fprint_size(char* buf, size_t sz) +{ + if (sz > GB) + sprintf(buf, "%.1fGB", (double)sz/(double)GB); + else if (sz > MB) + sprintf(buf, PRINT_FORMAT_SIZE"MB", sz/MB); + else if (sz > KB) + sprintf(buf, PRINT_FORMAT_SIZE"KB", sz/KB); + else + sprintf(buf, PRINT_FORMAT_SIZE, sz); +} + +// Find the top n memory blocks in term of size +bool biggest_blocks(unsigned int num) +{ + bool rc = true; + struct heap_block* blocks; + + if (num == 0) + return true; + else if (num > 1024 * 1024) + { + CA_PRINT("The number %d is too big, I am not sure I can do it\n", num); + return false; + } + + blocks = (struct heap_block*) calloc (num, sizeof(struct heap_block)); + if (!blocks) + return false; + + if (CA_HEAP->get_biggest_blocks (blocks, num)) + { + unsigned int i; + // display big blocks + CA_PRINT("Top %d biggest in-use heap memory blocks:\n", num); + for (i=0; i> 3; + struct heap_owner *owners; + struct heap_owner *smallest; + + struct ca_segment *segment; + size_t total_bytes = 0; + size_t processed_bytes = 0; + + std::vector blocks; + unsigned long num_blocks; + unsigned long inuse_index; + + struct reachable_block *blk; + struct object_reference ref; + size_t aggr_size; + unsigned long aggr_count; + address_t start, end, cursor; + + // Allocate an array for the biggest num of owners + if (num == 0) + return false; + owners = (struct heap_owner *) calloc(num, sizeof(struct heap_owner)); + if (!owners) + goto clean_out; + smallest = &owners[num - 1]; + + // First, create and populate an array of all in-use blocks + if (!build_reachable_blocks(blocks)) { + CA_PRINT("Failed: no in-use heap block is found\n"); + goto clean_out; + } + num_blocks = blocks.size(); + + // estimate the work to enable progress bar + for (i=0; im_type == ENUM_STACK || segment->m_type == ENUM_MODULE_DATA) + total_bytes += segment->m_fsize; + } + init_progress_bar(total_bytes); + + // Walk through all segments of threads' registers/stacks or globals + for (i=0; im_type == ENUM_STACK || segment->m_type == ENUM_MODULE_DATA) + { + int tid = 0; + // check registers if it is a thread's stack segment + if (segment->m_type == ENUM_STACK) + { + tid = get_thread_id (segment); + // allocate register value buffer for once + if (!nregs && !regs_buf) + { + nregs = read_registers (NULL, NULL, 0); + if (nregs) + regs_buf = (struct reg_value*) malloc(nregs * sizeof(struct reg_value)); + } + // check each register for heap reference + if (nregs && regs_buf) + { + int k; + int nread = read_registers (segment, regs_buf, nregs); + for (k = 0; k < nread; k++) + { + if (regs_buf[k].reg_width == ptr_sz) + { + blk = find_reachable_block(regs_buf[k].value, blocks); + if (blk) + { + ref.storage_type = ENUM_REGISTER; + ref.vaddr = 0; + ref.value = blk->addr; + ref.where.reg.tid = tid; + ref.where.reg.reg_num = k; + ref.where.reg.name = NULL; + calc_aggregate_size(&ref, ptr_sz, all_reachable_blocks, blocks, &aggr_size, &aggr_count); + if (aggr_size > smallest->aggr_size) + { + struct heap_owner newowner; + newowner.ref = ref; + newowner.aggr_size = aggr_size; + newowner.aggr_count = aggr_count; + add_owner(owners, num, &newowner); + } + } + } + } + } + } + + // Calculate the memory region to search + if (segment->m_type == ENUM_STACK) + { + start = get_rsp(segment); + if (start < segment->m_vaddr || start >= segment->m_vaddr + segment->m_vsize) + start = segment->m_vaddr; + if (start - segment->m_vaddr >= segment->m_fsize) + end = start; + else + end = segment->m_vaddr + segment->m_fsize; + } + else if (segment->m_type == ENUM_MODULE_DATA) + { + start = segment->m_vaddr; + end = segment->m_vaddr + segment->m_fsize; + } + else + continue; + + // Evaluate each variable or raw pointer in the target memory region + cursor = ALIGN(start, ptr_sz); + while (cursor < end) + { + size_t val_len = ptr_sz; + address_t sym_addr; + size_t sym_sz; + bool known_sym = false; + + // If the address belongs to a known variable, include all its subfields + // FIXME + // consider subfields that are of pointer-like types, however, it will miss + // references in an unstructured buffer + ref.storage_type = segment->m_type; + ref.vaddr = cursor; + if (segment->m_type == ENUM_STACK) + { + ref.where.stack.tid = tid; + ref.where.stack.frame = get_frame_number(segment, cursor, &ref.where.stack.offset); + if (known_stack_sym(&ref, &sym_addr, &sym_sz) && sym_sz) + known_sym = true; + } + else if (segment->m_type == ENUM_MODULE_DATA) + { + ref.where.module.base = segment->m_vaddr; + ref.where.module.size = segment->m_vsize; + ref.where.module.name = segment->m_module_name; + if (known_global_sym(&ref, &sym_addr, &sym_sz) && sym_sz) + known_sym = true; + } + // In rare case, symbol can be wacky; use it only if it matches the input address + if (known_sym && cursor == sym_addr) + { + val_len = sym_sz; + } + + // Query heap for aggregated memory size/count originated from the candidate variable + if (val_len >= ptr_sz) + { + calc_aggregate_size(&ref, val_len, all_reachable_blocks, blocks, &aggr_size, &aggr_count); + // update the top list if applies + if (aggr_size >= smallest->aggr_size) + { + struct heap_owner newowner; + if (val_len == ptr_sz) + read_memory_wrapper(NULL, ref.vaddr, (void*)&ref.value, ptr_sz); + else + ref.value = 0; + newowner.ref = ref; + newowner.aggr_size = aggr_size; + newowner.aggr_count = aggr_count; + add_owner(owners, num, &newowner); + } + } + cursor = ALIGN(cursor + val_len, ptr_sz); + } + processed_bytes += segment->m_fsize; + set_current_progress(processed_bytes); + } + } + end_progress_bar(); + + if (!all_reachable_blocks) + { + // Big memory blocks may be referenced indirectly by local/global variables + // check all in-use blocks + for (inuse_index = 0; inuse_index < num_blocks; inuse_index++) + { + blk = &blocks[inuse_index]; + ref.storage_type = ENUM_HEAP; + ref.vaddr = blk->addr; + ref.where.heap.addr = blk->addr; + ref.where.heap.size = blk->size; + ref.where.heap.inuse = 1; + calc_aggregate_size(&ref, ptr_sz, false, blocks, &aggr_size, &aggr_count); + // update the top list if applies + if (aggr_size >= smallest->aggr_size) + { + struct heap_owner newowner; + ref.value = 0; + newowner.ref = ref; + newowner.aggr_size = aggr_size; + newowner.aggr_count = aggr_count; + add_owner(owners, num, &newowner); + } + } + } + + // Print the result + for (i = 0; i < num; i++) + { + struct heap_owner *owner = &owners[i]; + if (owner->aggr_size) + { + CA_PRINT("[%d] ", i+1); + print_ref(&owner->ref, 0, false, false); + CA_PRINT(" |--> "); + print_size(owner->aggr_size); + CA_PRINT(" (%ld blocks)\n", owner->aggr_count); + } + } + rc = true; + +clean_out: + // clean up + if (regs_buf) + free (regs_buf); + if (owners) + free (owners); + + return rc; +} + +/* + * Given a reference, a variable or a pointer to a heap block, with known size, + * Return its aggregated reachable in-use blocks + */ +bool +calc_aggregate_size(const struct object_reference *ref, + size_t var_len, + bool all_reachable_blocks, + std::vector& inuse_blocks, + size_t *total_size, + unsigned long *total_count) +{ + address_t addr, cursor, end; + size_t ptr_sz = g_ptr_bit >> 3; + size_t aggr_size = 0; + unsigned long aggr_count = 0; + struct reachable_block *blk; + size_t bitmap_sz = ((inuse_blocks.size() + 15) * 2 / 32) * sizeof(unsigned int); + + static unsigned int* qv_bitmap = NULL; // Bit flags of whether a block is queued/visited + static unsigned long bitmap_capacity = 0; // in terms of number of blocks handled + + // ground return values + *total_size = 0; + *total_count = 0; + + // Prepare bitmap with the clean state + if (bitmap_capacity < inuse_blocks.size()) + { + if (qv_bitmap) + free (qv_bitmap); + // Each block uses two bits(queued/visited) + qv_bitmap = (unsigned int*) malloc(bitmap_sz); + if (!qv_bitmap) + { + bitmap_capacity = 0; + CA_PRINT("Out of Memory\n"); + return false; + } + bitmap_capacity = inuse_blocks.size(); + } + memset(qv_bitmap, 0, bitmap_sz); + + // Input is a pointer to an in-use memory block + if (ref->storage_type == ENUM_REGISTER || ref->storage_type == ENUM_HEAP) + { + if (var_len != ptr_sz) + return false; + blk = find_reachable_block(ref->vaddr, inuse_blocks); + if (blk) + { + // cached result is available, return now + if (all_reachable_blocks && blk->aggr_size) + { + *total_size = blk->aggr_size; + *total_count = blk->aggr_count; + return true; + } + else + { + // search starts with the memory block + cursor = blk->addr; + end = cursor + blk->size; + aggr_size = blk->size; + aggr_count = 1; + set_visited(qv_bitmap, blk - &inuse_blocks[0]); + } + } + else + return false; + } + // input reference is an object with given size, e.g. a local/global variable + else + { + if (all_reachable_blocks && var_len == ptr_sz) + { + // input is of pointer size, which is candidate for cache value + if(read_memory_wrapper(NULL, ref->vaddr, (void*)&addr, ptr_sz)) + { + blk = find_reachable_block(addr, inuse_blocks); + if (blk) + { + if (blk->aggr_size) + { + *total_size = blk->aggr_size; + *total_count = blk->aggr_count; + return true; + } + } + else + return false; + } + else + return false; + } + cursor = ref->vaddr; + end = cursor + var_len; + } + + // We now have a range of memory to search + cursor = ALIGN(cursor, ptr_sz); + while (cursor < end) + { + if(!read_memory_wrapper(NULL, cursor, (void*)&addr, ptr_sz)) + break; + blk = find_reachable_block(addr, inuse_blocks); + if (blk && !is_queued_or_visited(qv_bitmap, blk - &inuse_blocks[0])) + { + if (all_reachable_blocks) + { + unsigned long sub_count = 0; + aggr_size += heap_aggregate_size(blk, inuse_blocks, qv_bitmap, &sub_count); + aggr_count += sub_count; + } + else + { + aggr_size += blk->size; + aggr_count++; + set_visited(qv_bitmap, blk - &inuse_blocks[0]); + } + } + cursor += ptr_sz; + } + + // can we cache the result? + if (all_reachable_blocks && aggr_size) + { + if (ref->storage_type == ENUM_REGISTER || ref->storage_type == ENUM_HEAP) + { + blk = find_reachable_block(ref->vaddr, inuse_blocks); + blk->aggr_size = aggr_size; + blk->aggr_count = aggr_count; + } + else if (var_len == ptr_sz) + { + if (read_memory_wrapper(NULL, ref->vaddr, (void*)&addr, ptr_sz)) + { + blk = find_reachable_block(addr, inuse_blocks); + if (blk) + { + blk->aggr_size = aggr_size; + blk->aggr_count = aggr_count; + } + } + } + } + + // return happily + *total_size = aggr_size; + *total_count = aggr_count; + return true; +} + +// A not-so-fast leak checking based on the concept what a heap block without any +// reference directly or indirectly from a global or local variable is a lost one +bool display_heap_leak_candidates(void) +{ + bool rc = true; + unsigned long total_blocks = 0; + std::vector blocks; + struct reachable_block* blk; + unsigned int* qv_bitmap = NULL; // Bit flags of whether a block is queued/visited + unsigned long cur_index; + size_t total_leak_bytes; + size_t total_bytes; + unsigned long leak_count; + + // create and populate an array of all in-use blocks + if (!build_reachable_blocks(blocks)) { + CA_PRINT("Failed: no in-use heap block is found\n"); + return false; + } + total_blocks = blocks.size(); + + // Prepare bitmap with the clean state + // Each block uses two bits(queued/visited) + qv_bitmap = (unsigned int*) calloc((total_blocks+15)*2/32, sizeof(unsigned int)); + if (!qv_bitmap) + { + CA_PRINT("Out of Memory\n"); + rc = false; + goto leak_check_out; + } + + // search global/local(module's .text/.data/.bss and thread stack) memory + // for all references to these in-use blocks, mark them queued and visited + if (!mark_blocks_referenced_by_globals_locals(blocks, qv_bitmap)) + { + rc = false; + goto leak_check_out; + } + + // Within in-use blocks, + // repeatedly use queued blocks to find unvisited ones through reference + // mark newly found blocks queued and visited + // until no queued blocks any more to work with + cur_index = 0; + do + { + cur_index = get_next_queued_index(qv_bitmap, total_blocks, cur_index); + if (cur_index < total_blocks) + { + unsigned int* indexp; + blk = &blocks[cur_index]; + if (!blk->index_map) + { + if (!build_block_index_map(blk, blocks)) + { + rc = false; + goto leak_check_out; + } + } + // We have index map to work with by now + indexp = blk->index_map; + while (*indexp != UINT_MAX) + { + unsigned int index = *indexp; + if (!is_queued_or_visited(qv_bitmap, index)) + { + set_queued_and_visited(qv_bitmap, index); + } + indexp++; + } + // done with this block + reset_queued(qv_bitmap, cur_index); + } + else + break; + } while (1); + + // Display blocks that found no references to them directly or indirectly from global/local areas + CA_PRINT("Potentially leaked heap memory blocks:\n"); + total_leak_bytes = 0; + total_bytes = 0; + leak_count = 0; + for (cur_index = 0, blk = &blocks[0]; cur_index < total_blocks; cur_index++, blk++) + { + total_bytes += blk->size; + if (!is_visited(qv_bitmap, cur_index)) + { + leak_count++; + CA_PRINT("[%ld] addr=" PRINT_FORMAT_POINTER " size=" PRINT_FORMAT_SIZE "\n", + leak_count, blk->addr, blk->size); + total_leak_bytes += blk->size; + } + } + if (leak_count) + { + CA_PRINT("Total %ld (", leak_count); + print_size(total_leak_bytes); + CA_PRINT(") leak candidates out of %ld (", total_blocks); + print_size(total_bytes); + CA_PRINT(") in-use memory blocks\n"); + } + else + CA_PRINT("All %ld heap blocks are referenced, no leak candidate\n", total_blocks); + +leak_check_out: + if (qv_bitmap) + free (qv_bitmap); + return rc; +} + +/* + * Histogram functions + */ +void display_mem_histogram(const char* prefix) +{ + if (!g_mem_hist.num_buckets || !g_mem_hist.bucket_sizes + || !g_mem_hist.inuse_cnt || !g_mem_hist.inuse_bytes + || !g_mem_hist.free_cnt || !g_mem_hist.free_bytes) + return; + + CA_PRINT("%s========== In-use Memory Histogram ==========\n", prefix); + display_histogram(prefix, g_mem_hist.num_buckets, g_mem_hist.bucket_sizes, g_mem_hist.inuse_cnt, g_mem_hist.inuse_bytes); + + CA_PRINT("%s========== Free Memory Histogram ==========\n", prefix); + display_histogram(prefix, g_mem_hist.num_buckets, g_mem_hist.bucket_sizes, g_mem_hist.free_cnt, g_mem_hist.free_bytes); +} + +void release_mem_histogram(void) +{ + if (g_mem_hist.bucket_sizes) + free(g_mem_hist.bucket_sizes); + if (g_mem_hist.inuse_cnt) + free(g_mem_hist.inuse_cnt); + if (g_mem_hist.inuse_bytes) + free(g_mem_hist.inuse_bytes); + if (g_mem_hist.free_cnt) + free(g_mem_hist.free_cnt); + if (g_mem_hist.free_bytes) + free(g_mem_hist.free_bytes); + memset(&g_mem_hist, 0, sizeof(g_mem_hist)); +} + +void init_mem_histogram(unsigned int nbuckets) +{ + unsigned int i; + + release_mem_histogram(); + + g_mem_hist.num_buckets = nbuckets; + g_mem_hist.bucket_sizes = (size_t*)malloc(nbuckets * sizeof(size_t)); + for (i = 0; i < nbuckets; i++) + g_mem_hist.bucket_sizes[i] = 16 << i; + g_mem_hist.inuse_cnt = (unsigned long*)malloc((nbuckets+1) * sizeof(unsigned long)); + g_mem_hist.inuse_bytes = (size_t*)malloc((nbuckets+1) * sizeof(size_t)); + g_mem_hist.free_cnt = (unsigned long*)malloc((nbuckets+1) * sizeof(unsigned long)); + g_mem_hist.free_bytes = (size_t*)malloc((nbuckets+1) * sizeof(size_t)); + for (i = 0; i < nbuckets + 1; i++) + { + g_mem_hist.inuse_cnt[i] = 0; + g_mem_hist.inuse_bytes[i] = 0; + g_mem_hist.free_cnt[i] = 0; + g_mem_hist.free_bytes[i] = 0; + } +} + +void add_block_mem_histogram(size_t size, bool inuse, unsigned int num_block) +{ + unsigned int n; + + if (!g_mem_hist.num_buckets || !g_mem_hist.bucket_sizes + || !g_mem_hist.inuse_cnt || !g_mem_hist.inuse_bytes + || !g_mem_hist.free_cnt || !g_mem_hist.free_bytes) + return; + + for (n = 0; n < g_mem_hist.num_buckets; n++) + { + if (size <= g_mem_hist.bucket_sizes[n]) + break; + } + if (inuse) + { + g_mem_hist.inuse_cnt[n] += num_block; + g_mem_hist.inuse_bytes[n] += size * num_block; + } + else + { + g_mem_hist.free_cnt[n] += num_block; + g_mem_hist.free_bytes[n] += size * num_block; + } +} + +static void fill_space_til_pos(char* buf, size_t to_pos) +{ + size_t len = strlen(buf); + if (len < to_pos - 1) + { + while (len < to_pos - 1) + buf[len++] = ' '; + buf[len] = '\0'; + } + else + { + buf[len] = ' '; + buf[len+1] = '\0'; + } +} + +/* + * Helper functions + */ +static void display_histogram(const char* prefix, + unsigned int nbuckets, + const size_t* bucket_sizes, + const unsigned long* block_cnt, + const size_t* block_bytes) +{ + unsigned int n; + unsigned long total_cnt, total_cnt2; + size_t total_bytes; + char linebuf[LINE_BUF_SZ]; + int pos = 0; + const int second_col_pos = 16; + const int third_col_pos = 28; + + // title + sprintf(linebuf, "%sSize-Range", prefix); + fill_space_til_pos(linebuf, strlen(prefix)+second_col_pos); + pos = strlen(linebuf); + sprintf(linebuf + pos, "Count"); + fill_space_til_pos(linebuf, strlen(prefix)+third_col_pos); + pos = strlen(linebuf); + sprintf(linebuf + pos, "Total-Bytes"); + CA_PRINT("%s\n", linebuf); + + total_cnt = 0; + total_bytes = 0; + for (n = 0; n <= nbuckets; n++) + { + total_cnt += block_cnt[n]; + total_bytes += block_bytes[n]; + } + + total_cnt2 = 0; + for (n = 0; n <= nbuckets && total_cnt2 < total_cnt; n++) + { + if (block_cnt[n] > 0) + { + sprintf(linebuf, "%s", prefix); + pos = strlen(linebuf); + + // bucket size range + if (n == 0) + { + strcat(linebuf, "0 - "); + pos = strlen(linebuf); + fprint_size(linebuf + pos, bucket_sizes[n]); + pos = strlen(linebuf); + } + else if (n == nbuckets) + { + fprint_size(linebuf + pos, bucket_sizes[n-1]); + pos = strlen(linebuf); + strcat(linebuf, " - "); + pos = strlen(linebuf); + } + else + { + fprint_size(linebuf + pos, bucket_sizes[n-1]); + pos = strlen(linebuf); + strcat(linebuf, " - "); + pos = strlen(linebuf); + fprint_size(linebuf + pos, bucket_sizes[n]); + pos = strlen(linebuf); + } + fill_space_til_pos(linebuf, strlen(prefix)+second_col_pos); + pos = strlen(linebuf); + + // count + sprintf(linebuf + pos, "%ld(%ld%%)", + block_cnt[n], block_cnt[n] * 100 / total_cnt); + fill_space_til_pos(linebuf, strlen(prefix)+third_col_pos); + pos = strlen(linebuf); + + // total bytes + fprint_size(linebuf + pos, block_bytes[n]); + pos = strlen(linebuf); + sprintf(linebuf + pos, "(%ld%%)", block_bytes[n] * 100 / total_bytes); + + // output + CA_PRINT("%s\n", linebuf); + + total_cnt2 += block_cnt[n]; + } + } + sprintf(linebuf, "%sTotal", prefix); + fill_space_til_pos(linebuf, strlen(prefix)+second_col_pos); + pos = strlen(linebuf); + sprintf(linebuf + pos, "%ld", total_cnt); + fill_space_til_pos(linebuf, strlen(prefix)+third_col_pos); + pos = strlen(linebuf); + fprint_size(linebuf + pos, total_bytes); + CA_PRINT("%s\n", linebuf); +} + +static bool +mark_blocks_referenced_by_globals_locals(std::vector& blocks, + unsigned int* qv_bitmap) +{ + unsigned int seg_index; + size_t ptr_sz = g_ptr_bit >> 3; + + for (seg_index = 0; seg_index < g_segment_count; seg_index++) + { + struct ca_segment* segment = &g_segments[seg_index]; + + // This search may take long, bail out if user is impatient + if (user_request_break()) + { + CA_PRINT("Abort searching\n"); + break; + } + + if (segment->m_fsize == 0) + continue; + + // Only local/global variables are checked + if (segment->m_type == ENUM_STACK + || segment->m_type == ENUM_MODULE_DATA + || segment->m_type == ENUM_MODULE_TEXT) + { + address_t start, next, end; + + start = segment->m_vaddr; + end = start + segment->m_fsize; + // ignore stack memory below stack pointer + if (segment->m_type == ENUM_STACK) + { + address_t rsp = get_rsp(segment); + if (rsp >= segment->m_vaddr && rsp < segment->m_vaddr + segment->m_vsize) + start = rsp; + } + + next = ALIGN(start, ptr_sz); + while (next + ptr_sz <= end) + { + address_t ptr; + struct reachable_block* blk; + + if (!read_memory_wrapper(segment, next, &ptr, ptr_sz)) + break; + + blk = find_reachable_block(ptr, blocks); + if (blk) + { + unsigned long index = blk - &blocks[0]; + set_queued_and_visited(qv_bitmap, index); + } + next += ptr_sz; + } + } + } + + return true; +} + +static unsigned long get_next_queued_index(unsigned int* bitmap, unsigned long max_index, unsigned long cur_index) +{ + unsigned long next_index; + unsigned int indexes; + + next_index = cur_index + 1; + while (next_index < max_index) + { + indexes = bitmap[next_index >> 4]; + if (indexes & 0x55555555) // check 16 queue bits at once + { + unsigned long bit = (next_index & 0xf) << 1; + if (indexes & (QUEUED << bit)) + return next_index; + next_index++; + } + else + { + // skip all bits within this "int" + next_index = (next_index & (~0x0Ful)) + 16; + } + } + next_index = 0; + while (next_index < cur_index) + { + indexes = bitmap[next_index >> 4]; + if (indexes & 0x55555555) // check 16 queue bits at once + { + unsigned long bit = (next_index & 0xf) << 1; + if (indexes & (QUEUED << bit)) + return next_index; + next_index++; + } + else + { + // skip all bits within this "int" + next_index = (next_index & (~0x0Ful)) + 16; + } + } + return UINT_MAX; +} + +static unsigned int* get_index_map_buffer(unsigned int len) +{ + static unsigned int* g_index_map_buffer = NULL; + static unsigned int g_index_map_buffer_size = 0; + if (g_index_map_buffer_size < len) + { + // beware of overflow + if (len == UINT_MAX) + g_index_map_buffer_size = UINT_MAX; + else + g_index_map_buffer_size = len + 1; + // free previous, smaller buffer + if (g_index_map_buffer) + free (g_index_map_buffer); + // allocate a buffer big enough for current request + g_index_map_buffer = (unsigned int*) malloc(g_index_map_buffer_size * sizeof(unsigned int)); + if (!g_index_map_buffer) + { + CA_PRINT("Out-of-memory\n"); + return NULL; + } + } + return g_index_map_buffer; +} + +/* + * block's index map is an array of indexes of sub blocks + */ +static bool build_block_index_map(struct reachable_block* blk, + std::vector& blocks) +{ + size_t ptr_sz = g_ptr_bit >> 3; + // Prepare this + if (!blk->index_map) + { + address_t start, end, cursor; + unsigned int max_sub_blocks, total_sub_blocks; + unsigned int* index_buf = NULL; + unsigned int i, index; + + // Queue possible pointers to heap memory contained by this block + start = ALIGN(blk->addr, ptr_sz); + end = start + blk->size; + cursor = start; + + max_sub_blocks = (end - start) / ptr_sz; + total_sub_blocks = 0; + index_buf = get_index_map_buffer(max_sub_blocks + 1); // one for terminator + while (cursor < end) + { + address_t ptr; + struct reachable_block *sub_blk; + if (read_memory_wrapper(NULL, cursor, (void*)&ptr, ptr_sz) && ptr) + { + sub_blk = find_reachable_block(ptr, blocks); + if (sub_blk) + { + bool found_dup = false; + // avoid duplicate, which is not uncommon + // FIXME, consider non-linear search + index = sub_blk - &blocks[0]; + for (i = 0; i < total_sub_blocks; i++) + { + if (index_buf[i] == index) + { + found_dup = true; + break; + } + } + if (!found_dup) + { + index_buf[total_sub_blocks++] = index; + if (total_sub_blocks == UINT_MAX) + { + CA_PRINT("Internal fatal error: number of sub blocks exceeds 4 billion\n"); + return false; + } + } + } + } + cursor += ptr_sz; + } + // allocate cache to hold the indexes of this block + index_buf[total_sub_blocks++] = UINT_MAX; // this value serves as terminator + blk->index_map = (unsigned int*) malloc(total_sub_blocks * sizeof(unsigned int)); + if (!blk->index_map) + { + CA_PRINT("Out-of-memory\n"); + return false; + } + memcpy(blk->index_map, index_buf, total_sub_blocks * sizeof(unsigned int)); + } + return true; +} + +/* + * Input is a heap memory address + * Return the sum of sizes of all memory blocks (and their count) reachable by the block + * i.e. referenced directly or indirectly by it (avoid duplicates) + */ +static size_t +heap_aggregate_size(struct reachable_block *blk, + std::vector& inuse_blocks, + unsigned int* qv_bitmap, + unsigned long *aggr_count) +{ + size_t sum = 0; + + // Get the inuse_block struct of the input address + *aggr_count = 0; + if (is_queued_or_visited(qv_bitmap, blk - &inuse_blocks[0])) + return 0; + + // Big loop until all reachable have been visited + while (blk) + { + struct reachable_block *nextblk = NULL; + unsigned int* indexp; + unsigned long blk_index = blk - &inuse_blocks[0]; + + // mark this block is reachable and accounted for + sum += blk->size; + (*aggr_count)++; + reset_queued(qv_bitmap, blk_index); + set_visited(qv_bitmap, blk_index); + + // Prepare this block's index map, which is an array of indexes of sub blocks + if (!blk->index_map) + { + if (!build_block_index_map(blk, inuse_blocks)) + return 0; + } + + // We have index map to work with by now + indexp = blk->index_map; + while (*indexp != UINT_MAX) + { + unsigned int index = *indexp; + //if (index >= num_inuse_blocks) + //{ + // CA_PRINT("Internal fatal error: sub block index out of bound\n"); + // return 0; + //} + if (!is_queued_or_visited(qv_bitmap, index)) + { + set_queued(qv_bitmap, index); + if (!nextblk) + nextblk = &inuse_blocks[index]; + } + indexp++; + } + + // Get the next block that is queued + if (!nextblk) + { + // block processed doesn't have any subfield that points to an unvisited in-use block + // starts from the current block and wrap around + unsigned long next_index = get_next_queued_index(qv_bitmap, inuse_blocks.size(), blk_index); + if (next_index < inuse_blocks.size()) + nextblk = &inuse_blocks[next_index]; + } + blk = nextblk; + } + return sum; +} + +// find the insertion point so that the array is sorted properly +static void add_owner(struct heap_owner *owners, unsigned int num, struct heap_owner *newowner) +{ + unsigned int i; + + // If the new owner is just an alias of an existing owner, don't add it + // but replace the existing one if the new one has more symbolic information + if (newowner->ref.value) // non-zero ref.value indicates reference is of a pointer type + { + for (i = 0; i < num; i++) + { + if (newowner->ref.value == owners[i].ref.value) + { + if ((owners[i].ref.storage_type == ENUM_STACK && newowner->ref.storage_type == ENUM_MODULE_DATA) + || (owners[i].ref.storage_type == ENUM_REGISTER && newowner->ref.storage_type != ENUM_REGISTER)) + { + owners[i] = *newowner; + } + return; + } + } + } + + // the first owner (index i) that is smaller than the new one + for (i = 0; i < num; i++) + { + if (newowner->aggr_size > owners[i].aggr_size) + break; + } + // insert new owner before owners[i] unless the new one is too small + if (i <= num - 1) + { + // if necessary, move owners[i, .., num-2] to owners[i+1, .., num-1] + if (num >= 2 && i < num - 1) + { + unsigned int j; + for (j = num - 2; ; j--) + { + owners[j + 1] = owners[j]; + if (j == i) + break; + } + } + // insert new owner at index i + owners[i] = *newowner; + } +} diff --git a/src/heap.cpp b/src/heap.cpp index 18342b9..bbc5a8f 100644 --- a/src/heap.cpp +++ b/src/heap.cpp @@ -31,6 +31,7 @@ static std::vector gHeapRegistrationFuncs = { register_pt_malloc_2_35, register_tc_malloc, register_je_malloc, + register_mi_malloc, #endif }; diff --git a/src/heap.h b/src/heap.h index 64f0d38..a21d1da 100644 --- a/src/heap.h +++ b/src/heap.h @@ -97,6 +97,7 @@ extern void register_pt_malloc_2_35(); extern void register_tc_malloc(); extern void register_je_malloc(); extern void register_mscrt_malloc(); +extern void register_mi_malloc(); extern std::string get_supported_heaps(); diff --git a/src/heap_mimalloc.cpp b/src/heap_mimalloc.cpp new file mode 100644 index 0000000..6b0a46d --- /dev/null +++ b/src/heap_mimalloc.cpp @@ -0,0 +1,651 @@ +/* + * heap_mimalloc.c + * + * Created on: March 30, 2026 + * Author: Michael Yan + * + * This Implementation uses gdb specific types. Hence not portable to non-Linux + * platforms + */ + +#include "segment.h" +#include "heap_mimalloc.h" +#include + +#define CA_DEBUG 0 +#if CA_DEBUG +#define CA_PRINT_DBG CA_PRINT +#else +#define CA_PRINT_DBG(format,args...) +#endif + + +// Globals +static int mi_version_major = 0; +static int mi_version_minor = 0; +static int mi_version_patch = 0; +static bool mi_guard_page = false; +static bool mi_encode_freelist = false; + +static bool g_initialized = false; +static int g_bin_count = 0; // number of size classes (or "bins") of pages + +static std::set g_cached_blocks; // free blocks +static std::vector g_pages; + +// Forward declaration +static bool gdb_symbol_prelude(void); +static bool read_mi_version(void); +static bool parse_thread_local_heap(void); +static bool parse_page(struct value* page_val, int bin_index); +static bool parse_page_queue(struct value* page_queue_val, int bin_index); + +static ca_page* find_page(address_t addr); +static ca_page* find_next_page(address_t addr); +static bool is_block_cached(address_t); + +static void add_one_big_block(struct heap_block *, unsigned int, + struct heap_block *); + +/****************************************************************************** + * Exposed functions + *****************************************************************************/ +static const char * +heap_version(void) +{ + return "mimalloc"; +} + +static bool +init_heap(void) +{ + // Start with a clean slate + g_initialized = false; + g_cached_blocks.clear(); + g_pages.clear(); + + // Start with allocator's version + read_mi_version(); + + // Parse thread local heaps + if (!parse_thread_local_heap()) { + CA_PRINT("Failed to parse thread local heap\n"); + return false; + } + + // Sort pages by start address for future binary search + std::sort(g_pages.begin(), g_pages.end()); + + g_initialized = true; + return true; +} + +static bool +get_heap_block_info(address_t addr, struct heap_block* blk) +{ + if (g_initialized == false) { + CA_PRINT("mimalloc heap was not initialized successfully\n"); + return false; + } + + // Found the page that contains the block + ca_page* page = find_page(addr); + if (page == nullptr) + return false; + + // Traverse the blocks in the page to find the block that contains the address + for (address_t block_addr = page->start; block_addr < page->end; block_addr += page->block_size) { + if (addr >= block_addr && addr < block_addr + page->block_size) { + blk->addr = block_addr; + blk->size = page->block_size; + blk->inuse = !is_block_cached(block_addr); + return true; + } + } + return false; +} + +static bool +get_next_heap_block(address_t addr, struct heap_block* blk) +{ + if (g_initialized == false || g_pages.empty()) { + CA_PRINT("mimalloc heap was not initialized successfully\n"); + return false; + } + + // If addr is 0, return the first block of the first page + if (addr == 0) { + blk->addr = g_pages[0].start; + blk->size = g_pages[0].block_size; + blk->inuse = !is_block_cached(blk->addr); + return true; + } + + // Find the page that contains the address + ca_page* page = find_page(addr); + if (page) { + // If the next block in the same page is valid, return it + if (addr + page->block_size >= page->start && addr + page->block_size < page->end) { + blk->addr = addr + page->block_size; + blk->size = page->block_size; + blk->inuse = !is_block_cached(blk->addr); + return true; + } else { + // If the page is the last page, return false + if (page == &g_pages.back()) + return false; + // Otherwise, return the first block of the next page + page++; + blk->addr = page->start; + blk->size = page->block_size; + blk->inuse = !is_block_cached(blk->addr); + return true; + } + } else { + // If the address is not in any page, + // find the next page that has a start address greater than the given address + page = find_next_page(addr); + if (page) { + blk->addr = page->start; + blk->size = page->block_size; + blk->inuse = !is_block_cached(blk->addr); + return true; + } + } + + return false; +} + +/* Return true if the block belongs to a heap */ +static bool +is_heap_block(address_t addr) +{ + + if (g_initialized == false) { + CA_PRINT("mimalloc heap was not initialized successfully\n"); + return false; + } + + return find_page(addr) != nullptr; +} + +/* + * Traverse all pages unless a non-zero address is given, in which case the + * specific page is walked + */ +static bool +heap_walk(address_t heapaddr, bool verbose) +{ + if (g_initialized == false) { + CA_PRINT("mimalloc heap was not initialized successfully\n"); + return false; + } + + // If heapaddr is given, find the page that contains the address and display blocks in the page + if (heapaddr) { + ca_page* page = find_page(heapaddr); + if (page) { + CA_PRINT("Page [%#lx - %#lx], block size: %zu\n", page->start, page->end, page->block_size); + for (address_t addr = page->start; addr < page->end; addr += page->block_size) { + bool inuse = !is_block_cached(addr); + CA_PRINT("\t[%#lx - %#lx] %ld bytes %s\n", addr, addr + page->block_size, + page->block_size, inuse ? "inuse" : "free"); + } + return true; + } else { + CA_PRINT("No page found for address %p\n", (void*)heapaddr); + return false; + } + } + + // If heapaddr is not given, display all pages and blocks + std::unique_ptr bins(new ca_bin[g_bin_count]); + for (const auto& page : g_pages) { + if (page.bin_index >= 0 && page.bin_index < g_bin_count) { + bins[page.bin_index].bin_index = page.bin_index; + bins[page.bin_index].page_count++; + bins[page.bin_index].block_size = page.block_size; + for (address_t addr = page.start; addr < page.end; addr += page.block_size) { + if (is_block_cached(addr)) + bins[page.bin_index].free_blks++; + else + bins[page.bin_index].inuse_blks++; + } + } else { + CA_PRINT("Invalid bin index %d for page [%#lx - %#lx]\n", page.bin_index, page.start, page.end); + return false; + } + } + + // Display version and tuning parameters + CA_PRINT("mimalloc version: %d.%d.%d\n", mi_version_major, mi_version_minor, mi_version_patch); + CA_PRINT("\tguard page: %s\n", mi_guard_page ? "enabled" : "disabled"); + CA_PRINT("\tencode freelist: %s\n", mi_encode_freelist ? "enabled" : "disabled"); + // Print one blank line + CA_PRINT("\n"); + + // Display statistics + CA_PRINT(" size_class num_pages block_size inuse_blks inuse_bytes free_blks free_bytes\n"); + for (int i = 0; i < g_bin_count; i++) { + size_t inuse_bytes = bins[i].inuse_blks * bins[i].block_size; + size_t free_bytes = bins[i].free_blks * bins[i].block_size; + CA_PRINT("%10d %10zu %10zu %10zu %12zu %10zu %12zu\n", + i, bins[i].page_count, bins[i].block_size, bins[i].inuse_blks, inuse_bytes, bins[i].free_blks, free_bytes); + } + + size_t total_inuse_blks = 0; + size_t total_free_blks = 0; + size_t total_inuse_bytes = 0; + size_t total_free_bytes = 0; + for (int i = 0; i < g_bin_count; i++) { + total_inuse_blks += bins[i].inuse_blks; + total_free_blks += bins[i].free_blks; + total_inuse_bytes += bins[i].inuse_blks * bins[i].block_size; + total_free_bytes += bins[i].free_blks * bins[i].block_size; + } + CA_PRINT("------------------------------------------------------------------------------------\n"); + CA_PRINT(" Total %10zu %10s %10zu %12zu %10zu %12zu\n", + g_pages.size(), "", total_inuse_blks, total_inuse_bytes, total_free_blks, total_free_bytes); + + return true; +} + +static bool +get_biggest_blocks(struct heap_block* blks, unsigned int num) +{ + if (g_initialized == false) { + CA_PRINT("mimalloc heap was not initialized successfully\n"); + return false; + } + + if (num == 0) + return true; + memset(blks, 0, num * sizeof *blks); + + // Traverse big blocks (size class MI_BIN_FULL) first and populate the returned array + for (auto const& page : g_pages) { + if (page.bin_index == MI_BIN_FULL) { + for (address_t addr = page.start; addr < page.end; addr += page.block_size) { + if (!is_block_cached(addr)) { + struct heap_block blk = {addr, page.block_size, true}; + add_one_big_block(blks, num, &blk); + } + } + } + } + + // Return if we have enough big blocks + struct heap_block* smallest = &blks[num - 1]; + if (smallest->size > 0) + return true; + + // continue to traverse normal blocks (index != MI_BIN_FULL) and populate the returned array + for (auto const& page : g_pages) { + if (page.bin_index != MI_BIN_FULL) { + for (address_t addr = page.start; addr < page.end; addr += page.block_size) { + if (!is_block_cached(addr) && page.block_size > smallest->size) { + struct heap_block blk = {addr, page.block_size, true}; + add_one_big_block(blks, num, &blk); + } + } + } + } + + return true; +} + +static bool +walk_inuse_blocks(struct inuse_block* opBlocks, unsigned long* opCount) +{ + if (g_initialized == false) { + CA_PRINT("mimalloc heap was not initialized successfully\n"); + return false; + } + + if (opCount == nullptr) + return false; + + // Traverse all pages and blocks to find inuse blocks + *opCount = 0; + for (const auto& page : g_pages) { + for (address_t addr = page.start; addr < page.end; addr += page.block_size) { + if (!is_block_cached(addr)) { + (*opCount)++; + if (opBlocks) { + opBlocks->addr = addr; + opBlocks->size = page.block_size; + opBlocks++; + } + } + } + } + return true; +} + + +CoreAnalyzerHeapInterface sMiMallHeapManager = { + heap_version, + init_heap, + heap_walk, + is_heap_block, + get_heap_block_info, + get_next_heap_block, + get_biggest_blocks, + walk_inuse_blocks, +}; + +void register_mi_malloc() { + bool my_heap = gdb_symbol_prelude(); + return register_heap_manager("mi", &sMiMallHeapManager, my_heap); +} +/****************************************************************************** + * Helper Functions + *****************************************************************************/ +static bool read_mi_version(void) +{ + // hack it by reading the 2nd byte of funciton mi_version() + struct symbol* sym = lookup_symbol("mi_version", nullptr, SEARCH_FUNCTION_DOMAIN, nullptr).symbol; + if (sym == nullptr) { + CA_PRINT_DBG("Failed to lookup function \"mi_version\"\n"); + return false; + } + struct value* func_val = value_of_variable(sym, 0); + address_t func_addr = value_as_address(func_val); + unsigned char version = 0; + if (!read_memory_wrapper(nullptr, func_addr + 1, &version, sizeof(version))) { + CA_PRINT_DBG("Failed to read mi_version at address %p\n", (void*)(func_addr + 1)); + return false; + } + mi_version_major = version/100; + mi_version_minor = (version%100)/10; + mi_version_patch = version%10; + CA_PRINT_DBG("Detected mimalloc version: %d.%d.%d\n", + mi_version_major, mi_version_minor, mi_version_patch); + + return true; +} + +static bool parse_page(struct value* page_val, int bin_index) +{ + struct ca_segment *segment; + + CA_PRINT_DBG("\tParsing mi_page_t at address %p for bin index %d\n", + (void*)(page_val->address()), bin_index); + + // If the page has "keys" field, it means the free list is encoded. + // We currently don't support parsing encoded free list, so skip this page + static bool once = false; + if (!once) { + struct value* keys_val = ca_get_field_gdb_value(page_val, "keys"); + if (keys_val != nullptr) { + mi_encode_freelist = true; + CA_PRINT_DBG("\tmi_page_t has encoded free list\n"); + } + once = true; + } + + // Calculate address of the blocks in the page + struct value* page_start_val = ca_get_field_gdb_value(page_val, "page_start"); + struct value* capacity_val = ca_get_field_gdb_value(page_val, "capacity"); + struct value* block_size_val = ca_get_field_gdb_value(page_val, "block_size"); + if (page_start_val == nullptr || capacity_val == nullptr || block_size_val == nullptr) { + CA_PRINT("Failed to get fields of mi_page_t: page_start, capacity, block_size\n"); + return false; + } + address_t block_start = value_as_address(page_start_val); + size_t capacity = value_as_long(capacity_val); + size_t block_size = value_as_long(block_size_val); + if (block_start == 0 || block_size == 0 || capacity == 0) { + CA_PRINT("Invalid block_start, block_size or capacity in mi_page_t\n"); + return false; + } + address_t block_end = block_start + block_size * capacity; + // Get the free blocks in the page + // include mi_page_t::free, mi_page_t::local_free and mi_page_t::xthread_free + size_t free_blk_count = 0; + struct value* free_val = ca_get_field_gdb_value(page_val, "free"); + address_t block_addr = value_as_address(free_val); + while (block_addr != 0) { + if (block_addr < block_start || block_addr >= block_end) { + CA_PRINT("Invalid free block address %p in mi_page_t\n", (void*)block_addr); + return false; + } + g_cached_blocks.insert(block_addr); + free_blk_count++; + + // read the next pointer in the free list + if (!read_memory_wrapper(nullptr, block_addr, &block_addr, sizeof(block_addr))) { + CA_PRINT("Failed to read free block at address %p\n", (void*)block_addr); + return false; + } + } + size_t local_free_blk_count = 0; + struct value* local_free_val = ca_get_field_gdb_value(page_val, "local_free"); + block_addr = value_as_address(local_free_val); + while (block_addr != 0) { + if (block_addr < block_start || block_addr >= block_end) { + CA_PRINT("Invalid local free block address %p in mi_page_t\n", (void*)block_addr); + return false; + } + g_cached_blocks.insert(block_addr); + local_free_blk_count++; + + // read the next pointer in the local free list + if (!read_memory_wrapper(nullptr, block_addr, &block_addr, sizeof(block_addr))) { + CA_PRINT("Failed to read local free block at address %p\n", (void*)block_addr); + return false; + } + } + size_t xthread_free_blk_count = 0; + struct value* xthread_free_val = ca_get_field_gdb_value(page_val, "xthread_free"); + block_addr = value_as_address(xthread_free_val); + // bottom 2 bits of the pointer for mi_delayed_t flags + block_addr &= ~0x3; + while (block_addr != 0) { + if (block_addr < block_start || block_addr >= block_end) { + CA_PRINT("Invalid xthread free block address %p in mi_page_t %p\n", (void*)block_addr, (void*)page_val->address()); + return false; + } + g_cached_blocks.insert(block_addr); + xthread_free_blk_count++; + + // read the next pointer in the xthread free list + if (!read_memory_wrapper(nullptr, block_addr, &block_addr, sizeof(block_addr))) { + CA_PRINT("Failed to read xthread free block at address %p\n", (void*)block_addr); + return false; + } + block_addr &= ~0x3; + } + + // Check the free block count against the page used and capacity + // `used + |free| + |local_free| == capacity` + // actual blocks that are in use (alive) == `used - |xthread_free|` + struct value* used_val = ca_get_field_gdb_value(page_val, "used"); + size_t used_blk_count = value_as_long(used_val); + if (used_blk_count + free_blk_count + local_free_blk_count != capacity) { + CA_PRINT_DBG("Invalid used or free block count in mi_page_t: used %zu + free %zu + local_free %zu != capacity %zu\n", + used_blk_count, free_blk_count, local_free_blk_count, capacity); + } + // Add the page to the global page list for future reference + ca_page page = { page_val->address(), block_start, block_end, block_size, bin_index }; + g_pages.push_back(page); + + // update the segment that the page belongs to + segment = get_segment(block_start, block_end - block_start); + if (segment && segment->m_type == ENUM_UNKNOWN) { + segment->m_type = ENUM_HEAP; + } + return true; +} + +static bool parse_page_queue(struct value* page_queue_val, int bin_index) +{ + struct value* first_val = ca_get_field_gdb_value(page_queue_val, "first"); + struct value* page = first_val; + while (page && value_as_address(page) != 0) { + page = value_ind(page); + if (!parse_page(page, bin_index)) { + CA_PRINT("Failed to parse mi_page_t at address %p\n", (void*)value_as_address(page)); + return false; + } + // Get the next page in the queue + page = ca_get_field_gdb_value(page, "next"); + } + + return true; +} + +static int thread_local_heap (struct thread_info *info, void *data) +{ + struct symbol *sym; + struct value *thread_heap_p, *thread_heap; + + switch_to_thread (info); + + // __thread mi_heap_t* _mi_heap_default + sym = lookup_global_symbol("_mi_heap_default", nullptr, + SEARCH_VAR_DOMAIN).symbol; + if (sym == NULL) { + CA_PRINT("Failed to lookup gv \"_mi_heap_default\"\n"); + return false; + } + thread_heap_p = value_of_variable(sym, 0); + CA_PRINT_DBG("Thread %d: _mi_heap_default at address %p\n", info->global_num, (void*)value_as_address(thread_heap_p)); + thread_heap = value_ind(thread_heap_p); + + // If the heap has "guarded_size_min" field, it means the heap has guard page enabled + static bool once = false; + if (!once) { + struct value* guarded_size_min_val = ca_get_field_gdb_value(thread_heap, "guarded_size_min"); + if (guarded_size_min_val) { + mi_guard_page = true; + } + once = true; + } + + // Traverse heap list + while (thread_heap) + { + CA_PRINT_DBG("Process heap at address %p\n", (void*)thread_heap->address()); + struct value* page_count_val = ca_get_field_gdb_value(thread_heap, "page_count"); + if (page_count_val && value_as_long(page_count_val) > 0) { + // Field "pages" is an array of mi_page_queue_t + struct value* pages_val = ca_get_field_gdb_value(thread_heap, "pages"); + if (g_bin_count == 0) { + LONGEST low_bound, high_bound; + if (get_array_bounds (pages_val->type(), &low_bound, &high_bound) == 0) { + CA_PRINT("Could not determine \"pages\" bounds\n"); + return false; + } + g_bin_count = high_bound - low_bound + 1; + CA_PRINT_DBG("Detected mi_heap_t::pages[%ld-%ld] array length %d\n", + low_bound, high_bound, g_bin_count); + if (g_bin_count <= 0) { + CA_PRINT("Invalid \"pages\" array bounds: low %ld, high %ld\n", low_bound, high_bound); + return false; + } + } + for (int index = 0; index < g_bin_count; index++) { + struct value *val = value_subscript(pages_val, index); + if (parse_page_queue(val, index) == false) { + CA_PRINT("Failed to parse mi_page_queue_t at index %d\n", index); + break; + } + } + } + // mi_heap_t::thread_delayed_free is a list of delayed free blocks. + struct value* delayed_free_val = ca_get_field_gdb_value(thread_heap, "thread_delayed_free"); + address_t block_addr = value_as_address(delayed_free_val); + while (block_addr != 0) { + g_cached_blocks.insert(block_addr); + // Assume singly linked list + if (!read_memory_wrapper(nullptr, block_addr, &block_addr, sizeof(block_addr))) { + CA_PRINT("Failed to read mi_heap_t::thread_delayed_free at address %p\n", (void*)block_addr); + break; + } + } + + // next heap + struct value* heap_next = ca_get_field_gdb_value(thread_heap, "next"); + if (heap_next == nullptr || value_as_address(heap_next) == 0) + thread_heap = nullptr; + else + thread_heap = value_ind(heap_next); + } + + return 0; +} + +static bool parse_thread_local_heap(void) +{ + struct thread_info* old; + // Traverse all threads for thread-local variables `_mi_heap_default` + // remember current thread + old = inferior_thread(); + // switch to all threads + iterate_over_threads(thread_local_heap, NULL); + // resume the old thread + switch_to_thread (old); + + return true; +} + +static void add_one_big_block(struct heap_block *blks, unsigned int num, + struct heap_block *blk) +{ + unsigned int i; + + for (i = 0; i < num; i++) { + if (blk->size > blks[i].size) { + int k; + // Insert blk->blks[i] + // Move blks[i]->blks[i+1], .., blks[num-2]->blks[num-1] + for (k = ((int)num) - 2; k >= (int)i; k--) + blks[k+1] = blks[k]; + blks[i] = *blk; + break; + } + } +} + +static bool gdb_symbol_prelude(void) +{ + // static __attribute__((aligned(64))) mi_arena_t* mi_arenas[MI_MAX_ARENAS]; + struct symbol *sym = lookup_symbol("mi_arenas", nullptr, SEARCH_VAR_DOMAIN, nullptr).symbol; + if (sym == nullptr) { + CA_PRINT_DBG("Failed to lookup gv \"mi_arenas\"\n"); + return false; + } + return true; +} + +static bool is_block_cached(address_t addr) +{ + auto itr = g_cached_blocks.find(addr); + return itr != g_cached_blocks.end(); +} + +static ca_page* find_next_page(address_t addr) +{ + // Found the next page after the address + auto page_itr = std::upper_bound(g_pages.begin(), g_pages.end(), addr, [](const address_t& pageaddr, const ca_page& page) { + return pageaddr < page.start; + }); + if (page_itr == g_pages.end()) + return nullptr; + return &(*page_itr); +} + +static ca_page* find_page(address_t addr) +{ + // Found the page that contains the block + auto page_itr = std::upper_bound(g_pages.begin(), g_pages.end(), addr, [](const address_t& pageaddr, const ca_page& page) { + return pageaddr < page.start; + }); + if (page_itr == g_pages.begin()) + return nullptr; + page_itr--; + if (addr < page_itr->start || addr >= page_itr->end) + return nullptr; + return &(*page_itr); +} diff --git a/src/heap_mimalloc.h b/src/heap_mimalloc.h new file mode 100644 index 0000000..7da6268 --- /dev/null +++ b/src/heap_mimalloc.h @@ -0,0 +1,149 @@ +/* + * heap_mimalloc.h + * mimalloc data structure + * + * Created on: August 27, 2016 + * Author: myan + */ +#ifndef _MM_MIMALLOC_H +#define _MM_MIMALLOC_H + +#include +#include + +#include "heap.h" + +// In debug mode there is a padding structure at the end of the blocks to check for buffer overflows +#if (MI_PADDING) +typedef struct mi_padding_s { + uint32_t canary; // encoded block value to check validity of the padding (in case of overflow) + uint32_t delta; // padding bytes before the block. (mi_usable_size(p) - delta == exact allocated bytes) +} mi_padding_t; +#define MI_PADDING_SIZE (sizeof(mi_padding_t)) +#define MI_PADDING_WSIZE ((MI_PADDING_SIZE + MI_INTPTR_SIZE - 1) / MI_INTPTR_SIZE) +#else +#define MI_PADDING_SIZE 0 +#define MI_PADDING_WSIZE 0 +#endif + +#define MI_SMALL_WSIZE_MAX (128) +#define MI_PAGES_DIRECT (MI_SMALL_WSIZE_MAX + MI_PADDING_WSIZE + 1) + +#define MI_BIN_HUGE (73U) +#define MI_BIN_FULL (MI_BIN_HUGE+1) + +typedef struct mi_tld_s mi_tld_t; +typedef struct mi_page_s mi_page_t; +typedef struct mi_page_queue_s mi_page_queue_t; +typedef size_t mi_threadid_t; +typedef int mi_arena_id_t; +typedef uintptr_t mi_encoded_t; + +typedef struct mi_block_s { + mi_encoded_t next; +} mi_block_t; + +typedef struct mi_random_cxt_s { + uint32_t input[16]; + uint32_t output[16]; + int output_available; + bool weak; +} mi_random_ctx_t; + +typedef struct mi_page_queue_s { + mi_page_t* first; + mi_page_t* last; + size_t block_size; +} mi_page_queue_t; + +typedef struct mi_heap_s { + mi_tld_t* tld; + mi_block_t* thread_delayed_free; + mi_threadid_t thread_id; + mi_arena_id_t arena_id; + uintptr_t cookie; + uintptr_t keys[2]; + mi_random_ctx_t random; + size_t page_count; + size_t page_retired_min; + size_t page_retired_max; + long generic_count; + long generic_collect_count; + struct mi_heap_s* next; + bool no_reclaim; + uint8_t tag; + /* The followng fields are defined if MI_GUARDED is enabled */ + size_t guarded_size_min; + size_t guarded_size_max; + size_t guarded_sample_rate; + size_t guarded_sample_count; + /* End of fields defined if MI_GUARDED is enabled */ + mi_page_t* pages_free_direct[MI_PAGES_DIRECT]; + mi_page_queue_t pages[MI_BIN_FULL + 1]; +} mi_heap_t; + +typedef uintptr_t mi_thread_free_t; + +typedef union mi_page_flags_s { + uint8_t full_aligned; + struct { + uint8_t in_full : 1; + uint8_t has_aligned : 1; + } x; +} mi_page_flags_t; + +typedef struct mi_page_s { + // "owned" by the segment + uint32_t slice_count; + uint32_t slice_offset; + uint8_t is_committed:1; + uint8_t is_zero_init:1; + uint8_t is_huge:1; + // layout like this to optimize access in `mi_malloc` and `mi_free` + uint16_t capacity; + uint16_t reserved; + mi_page_flags_t flags; + uint8_t free_is_zero:1; + uint8_t retire_expire:7; + + mi_block_t* free; + mi_block_t* local_free; + uint16_t used; + uint8_t block_size_shift; + uint8_t heap_tag; + + size_t block_size; + uint8_t* page_start; + + // The following field is defined if (MI_ENCODE_FREELIST || MI_PADDING) + uintptr_t keys[2]; + // end of fields defined if (MI_ENCODE_FREELIST || MI_PADDING) + + mi_thread_free_t xthread_free; + uintptr_t xheap; + + struct mi_page_s* next; + struct mi_page_s* prev; + + // 64-bit 11 words, 32-bit 13 words, (+2 for secure) + void* padding[1]; +} mi_page_t; + +struct ca_page { + address_t addr; // mi_page_t address + address_t start; + address_t end; + size_t block_size; + int bin_index; + bool operator<(const ca_page& other) const { return start < other.start; } +}; + +struct ca_bin { + size_t block_size = 0; + size_t page_count = 0; + size_t inuse_blks = 0; + size_t free_blks = 0; + int bin_index = -1; +}; + +#endif /* _MM_MIMALLOC_H */ diff --git a/test/DockerfileTest_redhat b/test/DockerfileTest_redhat index 72eb242..da8656a 100644 --- a/test/DockerfileTest_redhat +++ b/test/DockerfileTest_redhat @@ -25,6 +25,11 @@ RUN ./build_gdb.sh ${GDB_VERSION} WORKDIR test RUN make check +WORKDIR /workspaces/core_analyzer/ +RUN ./build_mimalloc.sh 2.2.7 +WORKDIR test +RUN make check-mimalloc + WORKDIR /workspaces/core_analyzer/ RUN ./build_jemalloc.sh 5.3.0 WORKDIR test diff --git a/test/DockerfileTest_suse b/test/DockerfileTest_suse index e7ae53a..5242031 100644 --- a/test/DockerfileTest_suse +++ b/test/DockerfileTest_suse @@ -25,6 +25,11 @@ RUN ./build_gdb.sh ${GDB_VERSION} WORKDIR test RUN make check +WORKDIR /workspaces/core_analyzer/ +RUN ./build_mimalloc.sh 2.2.7 +WORKDIR test +RUN make check-mimalloc + WORKDIR /workspaces/core_analyzer/ RUN ./build_jemalloc.sh 5.3.0 WORKDIR test diff --git a/test/DockerfileTest_ubuntu b/test/DockerfileTest_ubuntu index 1c9dffe..60ba949 100644 --- a/test/DockerfileTest_ubuntu +++ b/test/DockerfileTest_ubuntu @@ -25,6 +25,12 @@ RUN ./build_gdb.sh ${GDB_VERSION} WORKDIR test RUN make check +ARG SKIP_MIMALLOC +RUN if [[ -z "$SKIP_MIMALLOC" ]] ; then \ + cd /workspaces/core_analyzer/ && ./build_mimalloc.sh 2.2.7 && \ + cd test && make check-mimalloc ; \ + fi + WORKDIR /workspaces/core_analyzer/ RUN ./build_jemalloc.sh 5.3.0 WORKDIR test diff --git a/test/makefile b/test/makefile index c35cc4e..fc95480 100644 --- a/test/makefile +++ b/test/makefile @@ -41,5 +41,14 @@ mallocTest_jemalloc: mallocTest_jemalloc.o check-jemalloc: mallocTest_jemalloc export LD_LIBRARY_PATH=/usr/local/lib; $(GDB) mallocTest_jemalloc -q -x verify.py +mallocTest_mimalloc.o: mallocTest.cpp + $(CXX) $(COMP_OPT) -DMIMALLOC_TEST -c -o $@ $^ + +mallocTest_mimalloc: mallocTest_mimalloc.o + $(CXX) $(COMP_OPT) -o $@ $^ $(LIBS) -lmimalloc + +check-mimalloc: mallocTest_mimalloc + export LD_LIBRARY_PATH=/usr/local/lib64; $(GDB) mallocTest_mimalloc -q -x verify.py + clean: rm *.o ${TARGETS} diff --git a/test/mallocTest.cpp b/test/mallocTest.cpp index 1d5273e..5b73acb 100644 --- a/test/mallocTest.cpp +++ b/test/mallocTest.cpp @@ -17,6 +17,8 @@ #include #elif defined(JEMALLOC_TEST) #include +#elif defined(MIMALLOC_TEST) +#include #else #include #endif @@ -168,6 +170,8 @@ region_size(void *p) return tc_malloc_size(p); #elif defined(JEMALLOC_TEST) return malloc_usable_size(p); +#elif defined(MIMALLOC_TEST) + return mi_usable_size(p); #else return malloc_usable_size(p); #endif diff --git a/test/regression.sh b/test/regression.sh index df6099f..3b0858d 100755 --- a/test/regression.sh +++ b/test/regression.sh @@ -19,6 +19,9 @@ set -ex # jemalloc # 5.3.0, 5.2.1, 5.2.0 # +# mimalloc +# 2.2.7 +# # distros # ubuntu:24.04, ubuntu:22.04, ubuntu:20.04 # debian:trixie(13), debian:bookworm(12) (debian:bullseye(11) fails for tcmalloc 2.16 due to gcc/g++ version) @@ -33,8 +36,9 @@ docker build --build-arg VARIANT="ubuntu:24.04" --build-arg GDB_VERSION="16.3" - docker system prune -af > /dev/null docker build --build-arg VARIANT="ubuntu:22.04" --build-arg GDB_VERSION="16.3" -t ca_test -q -f test/DockerfileTest_ubuntu . +# cmake 3.16 in ubuntu:20.04 is too old to build mimalloc 2.2.7, so skip it docker system prune -af > /dev/null -docker build --build-arg VARIANT="ubuntu:20.04" --build-arg GDB_VERSION="12.1" -t ca_test -q -f test/DockerfileTest_ubuntu . +docker build --build-arg VARIANT="ubuntu:20.04" --build-arg GDB_VERSION="12.1" --build-arg SKIP_MIMALLOC="1" -t ca_test -q -f test/DockerfileTest_ubuntu . docker system prune -af > /dev/null docker build --build-arg VARIANT="debian:trixie" --build-arg GDB_VERSION="16.3" -t ca_test -q -f test/DockerfileTest_ubuntu . diff --git a/test/setup_redhat.sh b/test/setup_redhat.sh index 22bcd6a..ac05979 100755 --- a/test/setup_redhat.sh +++ b/test/setup_redhat.sh @@ -11,7 +11,7 @@ set -ex dnf update -y dnf install -y wget procps-ng git autoconf gettext \ - gcc gcc-c++ make automake zlib-devel libtool diffutils \ + gcc gcc-c++ make automake cmake zlib-devel libtool diffutils \ libcurl-devel sqlite-devel xz \ python3-devel python3-pip sudo yum-utils cpan patch diff --git a/test/setup_suse.sh b/test/setup_suse.sh index 759cb3e..060bf35 100755 --- a/test/setup_suse.sh +++ b/test/setup_suse.sh @@ -13,7 +13,7 @@ zypper install -y gcc gcc-c++ && \ zypper install -y wget sudo texinfo && \ zypper install -y tar gzip xz && \ zypper install -y gmp-devel mpfr-devel && \ - zypper install -y git make makeinfo m4 automake libtool python3-devel patch + zypper install -y git make cmake makeinfo m4 automake libtool python3-devel patch zypper mr -ea && \ zypper install -y glibc-debuginfo && \ diff --git a/test/setup_ubuntu.sh b/test/setup_ubuntu.sh index 7f6fa69..7f78770 100755 --- a/test/setup_ubuntu.sh +++ b/test/setup_ubuntu.sh @@ -16,6 +16,7 @@ apt-get install -y \ libmpfr-dev \ libtool \ build-essential \ + cmake \ gawk \ wget \ python3-dev \