/* This handles the in place resize of a block, as performed by the VALGRIND_RESIZEINPLACE_BLOCK client request. It is unrelated to, and not used for, handling of the normal libc realloc() function. */ void MC_(handle_resizeInPlace)(ThreadId tid, Addr p, SizeT oldSizeB, SizeT newSizeB, SizeT rzB) { MC_Chunk* mc = VG_(HT_lookup) ( MC_(malloc_list), (UWord)p ); if (!mc || mc->szB != oldSizeB || newSizeB == 0) { /* Reject if: p is not found, or oldSizeB is wrong, or new block would be empty. */ MC_(record_free_error) ( tid, p ); return; } if (oldSizeB == newSizeB) return; mc->szB = newSizeB; if (newSizeB < oldSizeB) { MC_(make_mem_noaccess)( p + newSizeB, oldSizeB - newSizeB + rzB ); } else { ExeContext* ec = VG_(record_ExeContext)(tid, 0/*first_ip_delta*/); UInt ecu = VG_(get_ECU_from_ExeContext)(ec); MC_(make_mem_undefined_w_otag)( p + oldSizeB, newSizeB - oldSizeB, ecu | MC_OKIND_HEAP ); if (rzB > 0) MC_(make_mem_noaccess)( p + newSizeB, rzB ); } }
/* Compute a quick summary of the leak check. */ static void make_summary(void) { Int i; for(i = 0; i < lc_n_shadows; i++) { SizeT size = lc_shadows[i]->szB; switch(lc_markstack[i].state) { case Unreached: blocks_leaked++; MC_(bytes_leaked) += size; break; case Proper: blocks_reachable++; MC_(bytes_reachable) += size; break; case Interior: blocks_dubious++; MC_(bytes_dubious) += size; break; case IndirectLeak: /* shouldn't happen */ blocks_indirect++; MC_(bytes_indirect) += size; break; } } }
void MC_(create_mempool)(Addr pool, UInt rzB, Bool is_zeroed) { MC_Mempool* mp; if (VG_(clo_verbosity) > 2) { VG_(message)(Vg_UserMsg, "create_mempool(0x%lx, %d, %d)\n", pool, rzB, is_zeroed); VG_(get_and_pp_StackTrace) (VG_(get_running_tid)(), MEMPOOL_DEBUG_STACKTRACE_DEPTH); } mp = VG_(HT_lookup)(MC_(mempool_list), (UWord)pool); if (mp != NULL) { VG_(tool_panic)("MC_(create_mempool): duplicate pool creation"); } mp = VG_(malloc)("mc.cm.1", sizeof(MC_Mempool)); mp->pool = pool; mp->rzB = rzB; mp->is_zeroed = is_zeroed; mp->chunks = VG_(HT_construct)( "MC_(create_mempool)" ); check_mempool_sane(mp); /* Paranoia ... ensure this area is off-limits to the client, so the mp->data field isn't visible to the leak checker. If memory management is working correctly, anything pointer returned by VG_(malloc) should be noaccess as far as the client is concerned. */ if (!MC_(check_mem_is_noaccess)( (Addr)mp, sizeof(MC_Mempool), NULL )) { VG_(tool_panic)("MC_(create_mempool): shadow area is accessible"); } VG_(HT_add_node)( MC_(mempool_list), mp ); }
/* Allocate a shadow chunk, put it on the appropriate list. If needed, release oldest blocks from freed list. */ static MC_Chunk* create_MC_Chunk ( ThreadId tid, Addr p, SizeT szB, MC_AllocKind kind) { MC_Chunk* mc = VG_(allocEltPA)(MC_(chunk_poolalloc)); mc->data = p; mc->szB = szB; mc->allockind = kind; switch ( MC_(n_where_pointers)() ) { case 2: mc->where[1] = 0; // fallback to 1 case 1: mc->where[0] = 0; // fallback to 0 case 0: break; default: tl_assert(0); } MC_(set_allocated_at) (tid, mc); /* Each time a new MC_Chunk is created, release oldest blocks if the free list volume is exceeded. */ if (VG_(free_queue_volume) > MC_(clo_freelist_vol)) release_oldest_block(); /* Paranoia ... ensure the MC_Chunk is off-limits to the client, so the mc->data field isn't visible to the leak checker. If memory management is working correctly, any pointer returned by VG_(malloc) should be noaccess as far as the client is concerned. */ if (!MC_(check_mem_is_noaccess)( (Addr)mc, sizeof(MC_Chunk), NULL )) { VG_(tool_panic)("create_MC_Chunk: shadow area is accessible"); } return mc; }
void MC_(mempool_free)(Addr pool, Addr addr) { MC_Mempool* mp; MC_Chunk* mc; ThreadId tid = VG_(get_running_tid)(); mp = VG_(HT_lookup)(MC_(mempool_list), (UWord)pool); if (mp == NULL) { MC_(record_illegal_mempool_error)(tid, pool); return; } if (VG_(clo_verbosity) > 2) { VG_(message)(Vg_UserMsg, "mempool_free(0x%lx, 0x%lx)\n", pool, addr); VG_(get_and_pp_StackTrace) (tid, MEMPOOL_DEBUG_STACKTRACE_DEPTH); } if (MP_DETAILED_SANITY_CHECKS) check_mempool_sane(mp); mc = VG_(HT_remove)(mp->chunks, (UWord)addr); if (mc == NULL) { MC_(record_free_error)(tid, (Addr)addr); return; } if (VG_(clo_verbosity) > 2) { VG_(message)(Vg_UserMsg, "mempool_free(0x%lx, 0x%lx) freed chunk of %ld bytes\n", pool, addr, mc->szB + 0UL); } die_and_free_mem ( tid, mc, mp->rzB ); if (MP_DETAILED_SANITY_CHECKS) check_mempool_sane(mp); }
/* Put a shadow chunk on the freed blocks queue, possibly freeing up some of the oldest blocks in the queue at the same time. */ static void add_to_freed_queue ( MC_Chunk* mc ) { const Bool show = False; const int l = (mc->szB >= MC_(clo_freelist_big_blocks) ? 0 : 1); /* Put it at the end of the freed list, unless the block would be directly released any way : in this case, we put it at the head of the freed list. */ if (freed_list_end[l] == NULL) { tl_assert(freed_list_start[l] == NULL); mc->next = NULL; freed_list_end[l] = freed_list_start[l] = mc; } else { tl_assert(freed_list_end[l]->next == NULL); if (mc->szB >= MC_(clo_freelist_vol)) { mc->next = freed_list_start[l]; freed_list_start[l] = mc; } else { mc->next = NULL; freed_list_end[l]->next = mc; freed_list_end[l] = mc; } } VG_(free_queue_volume) += (Long)mc->szB; if (show) VG_(printf)("mc_freelist: acquire: volume now %lld\n", VG_(free_queue_volume)); VG_(free_queue_length)++; }
void MC_(mempool_change)(Addr pool, Addr addrA, Addr addrB, SizeT szB) { MC_Mempool* mp; MC_Chunk* mc; ThreadId tid = VG_(get_running_tid)(); if (VG_(clo_verbosity) > 2) { VG_(message)(Vg_UserMsg, "mempool_change(0x%lx, 0x%lx, 0x%lx, %ld)\n", pool, addrA, addrB, szB); VG_(get_and_pp_StackTrace) (tid, MEMPOOL_DEBUG_STACKTRACE_DEPTH); } mp = VG_(HT_lookup)(MC_(mempool_list), (UWord)pool); if (mp == NULL) { MC_(record_illegal_mempool_error)(tid, pool); return; } check_mempool_sane(mp); mc = VG_(HT_remove)(mp->chunks, (UWord)addrA); if (mc == NULL) { MC_(record_free_error)(tid, (Addr)addrA); return; } mc->data = addrB; mc->szB = szB; VG_(HT_add_node)( mp->chunks, mc ); check_mempool_sane(mp); }
void MC_(print_malloc_stats) ( void ) { MC_Chunk* mc; SizeT nblocks = 0; ULong nbytes = 0; if (VG_(clo_verbosity) == 0) return; if (VG_(clo_xml)) return; /* Count memory still in use. */ VG_(HT_ResetIter)(MC_(malloc_list)); while ( (mc = VG_(HT_Next)(MC_(malloc_list))) ) { nblocks++; nbytes += (ULong)mc->szB; } VG_(message)(Vg_UserMsg, "malloc/free: in use at exit: %'llu bytes in %'lu blocks.", nbytes, nblocks); VG_(message)(Vg_UserMsg, "malloc/free: %'lu allocs, %'lu frees, %'llu bytes allocated.", cmalloc_n_mallocs, cmalloc_n_frees, cmalloc_bs_mallocd); if (VG_(clo_verbosity) > 1) VG_(message)(Vg_UserMsg, ""); }
void MC_(print_malloc_stats) ( void ) { MC_Chunk* mc; SizeT nblocks = 0; ULong nbytes = 0; if (VG_(clo_verbosity) == 0) return; if (VG_(clo_xml)) return; /* Count memory still in use. */ VG_(HT_ResetIter)(MC_(malloc_list)); while ( (mc = VG_(HT_Next)(MC_(malloc_list))) ) { nblocks++; nbytes += (ULong)mc->szB; } VG_(umsg)( "HEAP SUMMARY:\n" " in use at exit: %'llu bytes in %'lu blocks\n" " total heap usage: %'lu allocs, %'lu frees, %'llu bytes allocated\n" "\n", nbytes, nblocks, cmalloc_n_mallocs, cmalloc_n_frees, cmalloc_bs_mallocd ); }
void MC_(mempool_alloc)(ThreadId tid, Addr pool, Addr addr, SizeT szB) { MC_Mempool* mp; if (VG_(clo_verbosity) > 2) { VG_(message)(Vg_UserMsg, "mempool_alloc(0x%lx, 0x%lx, %ld)\n", pool, addr, szB); VG_(get_and_pp_StackTrace) (tid, MEMPOOL_DEBUG_STACKTRACE_DEPTH); } mp = VG_(HT_lookup) ( MC_(mempool_list), (UWord)pool ); if (mp == NULL) { MC_(record_illegal_mempool_error) ( tid, pool ); } else { if (MP_DETAILED_SANITY_CHECKS) check_mempool_sane(mp); MC_(new_block)(tid, addr, szB, /*ignored*/0, mp->is_zeroed, MC_AllocCustom, mp->chunks); if (mp->rzB > 0) { // This is not needed if the user application has properly // marked the superblock noaccess when defining the mempool. // We however still mark the redzones noaccess to still catch // some bugs if user forgot. MC_(make_mem_noaccess) ( addr - mp->rzB, mp->rzB); MC_(make_mem_noaccess) ( addr + szB, mp->rzB); } if (MP_DETAILED_SANITY_CHECKS) check_mempool_sane(mp); } }
void MC_(destroy_mempool)(Addr pool) { MC_Chunk* mc; MC_Mempool* mp; if (VG_(clo_verbosity) > 2) { VG_(message)(Vg_UserMsg, "destroy_mempool(0x%lx)\n", pool); VG_(get_and_pp_StackTrace) (VG_(get_running_tid)(), MEMPOOL_DEBUG_STACKTRACE_DEPTH); } mp = VG_(HT_remove) ( MC_(mempool_list), (UWord)pool ); if (mp == NULL) { ThreadId tid = VG_(get_running_tid)(); MC_(record_illegal_mempool_error) ( tid, pool ); return; } check_mempool_sane(mp); // Clean up the chunks, one by one VG_(HT_ResetIter)(mp->chunks); while ( (mc = VG_(HT_Next)(mp->chunks)) ) { /* Note: make redzones noaccess again -- just in case user made them accessible with a client request... */ MC_(make_mem_noaccess)(mc->data-mp->rzB, mc->szB + 2*mp->rzB ); } // Destroy the chunk table VG_(HT_destruct)(mp->chunks, (void (*)(void *))delete_MC_Chunk); VG_(free)(mp); }
// Scan a block of memory between [start, start+len). This range may // be bogus, inaccessable, or otherwise strange; we deal with it. For each // valid aligned word we assume it's a pointer to a chunk a push the chunk // onto the mark stack if so. static void lc_scan_memory(Addr start, SizeT len, Bool is_prior_definite, Int clique) { Addr ptr = VG_ROUNDUP(start, sizeof(Addr)); Addr end = VG_ROUNDDN(start+len, sizeof(Addr)); vki_sigset_t sigmask; if (VG_DEBUG_LEAKCHECK) VG_(printf)("scan %#lx-%#lx (%lu)\n", start, end, len); VG_(sigprocmask)(VKI_SIG_SETMASK, NULL, &sigmask); VG_(set_fault_catcher)(scan_all_valid_memory_catcher); // We might be in the middle of a page. Do a cheap check to see if // it's valid; if not, skip onto the next page. if (!VG_(am_is_valid_for_client)(ptr, sizeof(Addr), VKI_PROT_READ)) ptr = VG_PGROUNDUP(ptr+1); // First page is bad - skip it. while (ptr < end) { Addr addr; // Skip invalid chunks. if ( ! MC_(is_within_valid_secondary)(ptr) ) { ptr = VG_ROUNDUP(ptr+1, SM_SIZE); continue; } // Look to see if this page seems reasonable. if ((ptr % VKI_PAGE_SIZE) == 0) { if (!VG_(am_is_valid_for_client)(ptr, sizeof(Addr), VKI_PROT_READ)) { ptr += VKI_PAGE_SIZE; // Bad page - skip it. continue; } } if (__builtin_setjmp(memscan_jmpbuf) == 0) { if ( MC_(is_valid_aligned_word)(ptr) ) { lc_scanned_szB += sizeof(Addr); addr = *(Addr *)ptr; // If we get here, the scanned word is in valid memory. Now // let's see if its contents point to a chunk. lc_push_if_a_chunk_ptr(addr, clique, is_prior_definite); } else if (0 && VG_DEBUG_LEAKCHECK) { VG_(printf)("%#lx not valid\n", ptr); } ptr += sizeof(Addr); } else { // We need to restore the signal mask, because we were // longjmped out of a signal handler. VG_(sigprocmask)(VKI_SIG_SETMASK, &sigmask, NULL); ptr = VG_PGROUNDUP(ptr+1); // Bad page - skip it. } } VG_(sigprocmask)(VKI_SIG_SETMASK, &sigmask, NULL); VG_(set_fault_catcher)(NULL); }
void* MC_(calloc) ( ThreadId tid, SizeT nmemb, SizeT size1 ) { if (complain_about_silly_args2(nmemb, size1)) { return NULL; } else { return MC_(new_block) ( tid, 0, nmemb*size1, VG_(clo_alignment), /*is_zeroed*/True, MC_AllocMalloc, MC_(malloc_list)); } }
void* MC_(memalign) ( ThreadId tid, SizeT alignB, SizeT n ) { if (complain_about_silly_args(n, "memalign")) { return NULL; } else { return MC_(new_block) ( tid, 0, n, alignB, /*is_zeroed*/False, MC_AllocMalloc, MC_(malloc_list)); } }
void* MC_(__builtin_vec_new) ( ThreadId tid, SizeT n ) { if (complain_about_silly_args(n, "__builtin_vec_new")) { return NULL; } else { return MC_(new_block) ( tid, 0, n, VG_(clo_alignment), /*is_zeroed*/False, MC_AllocNewVec, MC_(malloc_list)); } }
void* MC_(memalign) ( ThreadId tid, SizeT alignB, SizeT n ) { if (MC_(record_fishy_value_error)(tid, "memalign", "size", n)) { return NULL; } else { return MC_(new_block) ( tid, 0, n, alignB, /*is_zeroed*/False, MC_AllocMalloc, MC_(malloc_list)); } }
void* MC_(__builtin_vec_new) ( ThreadId tid, SizeT n ) { if (MC_(record_fishy_value_error)(tid, "__builtin_vec_new", "size", n)) { return NULL; } else { return MC_(new_block) ( tid, 0, n, VG_(clo_alignment), /*is_zeroed*/False, MC_AllocNewVec, MC_(malloc_list)); } }
void* MC_(malloc) ( ThreadId tid, SizeT n ) { if (complain_about_silly_args(n, "malloc")) { return NULL; } else { return MC_(new_block) ( tid, 0, n, VG_(clo_alignment), MC_MALLOC_REDZONE_SZB, /*is_zeroed*/False, MC_AllocMalloc, MC_(malloc_list)); } }
void* MC_(calloc) ( ThreadId tid, SizeT nmemb, SizeT size1 ) { if (MC_(record_fishy_value_error)(tid, "calloc", "nmemb", nmemb) || MC_(record_fishy_value_error)(tid, "calloc", "size", size1)) { return NULL; } else { return MC_(new_block) ( tid, 0, nmemb*size1, VG_(clo_alignment), /*is_zeroed*/True, MC_AllocMalloc, MC_(malloc_list)); } }
static void mc_describe_addr ( Addr a, AddrInfo* ai ) { ShadowChunk* sc; Bool ok; ThreadId tid; /* Nested functions, yeah. Need the lexical scoping of 'a'. */ /* Closure for searching thread stacks */ Bool addr_is_in_bounds(Addr stack_min, Addr stack_max) { return (stack_min <= a && a <= stack_max); } /* Closure for searching malloc'd and free'd lists */ Bool addr_is_in_block(ShadowChunk *sh_ch) { return VG_(addr_is_in_block) ( a, VG_(get_sc_data)(sh_ch), VG_(get_sc_size)(sh_ch) ); } /* Perhaps it's a user-def'd block ? */ ok = MC_(client_perm_maybe_describe)( a, ai ); if (ok) return; /* Perhaps it's on a thread's stack? */ tid = VG_(any_matching_thread_stack)(addr_is_in_bounds); if (tid != VG_INVALID_THREADID) { ai->akind = Stack; ai->stack_tid = tid; return; } /* Search for a recently freed block which might bracket it. */ sc = MC_(any_matching_freed_ShadowChunks)(addr_is_in_block); if (NULL != sc) { ai->akind = Freed; ai->blksize = VG_(get_sc_size)(sc); ai->rwoffset = (Int)(a) - (Int)(VG_(get_sc_data)(sc)); ai->lastchange = (ExeContext*)( VG_(get_sc_extra)(sc, 0) ); return; } /* Search for a currently malloc'd block which might bracket it. */ sc = VG_(any_matching_mallocd_ShadowChunks)(addr_is_in_block); if (NULL != sc) { ai->akind = Mallocd; ai->blksize = VG_(get_sc_size)(sc); ai->rwoffset = (Int)(a) - (Int)(VG_(get_sc_data)(sc)); ai->lastchange = (ExeContext*)( VG_(get_sc_extra)(sc, 0) ); return; } /* Clueless ... */ ai->akind = Unknown; return; }
static void record_freemismatch_error (ThreadId tid, MC_Chunk* mc) { /* MC_(record_freemismatch_error) reports errors for still allocated blocks but we are in the middle of freeing it. To report the error correctly, we re-insert the chunk (making it again a "clean allocated block", report the error, and then re-remove the chunk. This avoids to do a VG_(HT_lookup) followed by a VG_(HT_remove) in all "non-erroneous cases". */ VG_(HT_add_node)( MC_(malloc_list), mc ); MC_(record_freemismatch_error) ( tid, mc ); if ((mc != VG_(HT_remove) ( MC_(malloc_list), (UWord)mc->data ))) tl_assert(0); }
// True if mc is a live block (not yet freed). static Bool live_block (MC_Chunk* mc) { if (mc->allockind == MC_AllocCustom) { MC_Mempool* mp; VG_(HT_ResetIter)(MC_(mempool_list)); while ( (mp = VG_(HT_Next)(MC_(mempool_list))) ) { if ( in_block_list (mp->chunks, mc) ) return True; } } /* Note: we fallback here for a not found MC_AllocCustom as such a block can be inserted in MC_(malloc_list) by VALGRIND_MALLOCLIKE_BLOCK. */ return in_block_list ( MC_(malloc_list), mc ); }
/* This one called from generated code. */ void MC_(record_value_error) ( Int size ) { MemCheckError err_extra; MC_(clear_MemCheckError)( &err_extra ); err_extra.size = size; VG_(maybe_record_error)( NULL, ValueErr, /*addr*/0, /*s*/NULL, &err_extra ); }
static void synth_LOADV ( Int sz, Int a_reg, Int tv_reg, RRegSet regs_live_before, RRegSet regs_live_after ) { Addr helper; UInt argv[] = { a_reg }; UInt tagv[] = { RealReg }; switch (sz) { case 4: helper = (Addr) & MC_(helperc_LOADV4); break; case 2: helper = (Addr) & MC_(helperc_LOADV2); break; case 1: helper = (Addr) & MC_(helperc_LOADV1); break; default: VG_(skin_panic)("synth_LOADV"); } VG_(synth_ccall) ( helper, 1, 1, argv, tagv, tv_reg, regs_live_before, regs_live_after ); }
SizeT MC_(malloc_usable_size) ( ThreadId tid, void* p ) { MC_Chunk* mc = VG_(HT_lookup) ( MC_(malloc_list), (UWord)p ); // There may be slop, but pretend there isn't because only the asked-for // area will be marked as addressable. return ( mc ? mc->szB : 0 ); }
static void synth_STOREV ( Int sz, Int tv_tag, Int tv_val, Int a_reg, RRegSet regs_live_before, RRegSet regs_live_after ) { Addr helper; UInt argv[] = { a_reg, tv_val }; Tag tagv[] = { RealReg, tv_tag }; sk_assert(tv_tag == RealReg || tv_tag == Literal); switch (sz) { case 4: helper = (Addr) MC_(helperc_STOREV4); break; case 2: helper = (Addr) MC_(helperc_STOREV2); break; case 1: helper = (Addr) MC_(helperc_STOREV1); break; default: VG_(skin_panic)("synth_STOREV"); } VG_(synth_ccall) ( helper, 2, 2, argv, tagv, INVALID_REALREG, regs_live_before, regs_live_after ); }
void MC_(handle_free) ( ThreadId tid, Addr p, UInt rzB, MC_AllocKind kind ) { MC_Chunk* mc; cmalloc_n_frees++; mc = VG_(HT_remove) ( MC_(malloc_list), (UWord)p ); if (mc == NULL) { MC_(record_free_error) ( tid, p ); } else { /* check if it is a matching free() / delete / delete [] */ if (kind != mc->allockind) { tl_assert(p == mc->data); MC_(record_freemismatch_error) ( tid, mc ); } die_and_free_mem ( tid, mc, rzB ); } }
void MC_(mempool_alloc)(ThreadId tid, Addr pool, Addr addr, SizeT szB) { MC_Mempool* mp; if (VG_(clo_verbosity) > 2) { VG_(message)(Vg_UserMsg, "mempool_alloc(0x%lx, 0x%lx, %ld)", pool, addr, szB); VG_(get_and_pp_StackTrace) (tid, MEMPOOL_DEBUG_STACKTRACE_DEPTH); } mp = VG_(HT_lookup) ( MC_(mempool_list), (UWord)pool ); if (mp == NULL) { MC_(record_illegal_mempool_error) ( tid, pool ); } else { check_mempool_sane(mp); MC_(new_block)(tid, addr, szB, /*ignored*/0, mp->rzB, mp->is_zeroed, MC_AllocCustom, mp->chunks); check_mempool_sane(mp); } }
Bool MC_(mempool_exists)(Addr pool) { MC_Mempool* mp; mp = VG_(HT_lookup)(MC_(mempool_list), (UWord)pool); if (mp == NULL) { return False; } return True; }
/* This one called from non-generated code */ void MC_(record_user_error) ( ThreadState* tst, Addr a, Bool isWrite ) { MemCheckError err_extra; sk_assert(NULL != tst); MC_(clear_MemCheckError)( &err_extra ); err_extra.addrinfo.akind = Undescribed; err_extra.isWrite = isWrite; VG_(maybe_record_error)( tst, UserErr, a, /*s*/NULL, &err_extra ); }