/* This routine serves as entry point for 'malloc'. * Primary responsibility of this routine is to allocate requested number of * bytes (plus prefix, and suffix guards), and report allocation to the * emulator. */ void* qemu_instrumented_malloc(size_t bytes) { MallocDesc desc; /* Initialize block descriptor and allocate memory. Note that dlmalloc * returns a valid pointer on zero allocation. Lets mimic this behavior. */ desc.prefix_size = DEFAULT_PREFIX_SIZE; desc.requested_bytes = bytes; desc.suffix_size = DEFAULT_SUFFIX_SIZE; desc.ptr = dlmalloc(mallocdesc_alloc_size(&desc)); if (desc.ptr == NULL) { qemu_error_log("<libc_pid=%03u, pid=%03u> malloc(%u): dlmalloc(%u) failed.", malloc_pid, getpid(), bytes, mallocdesc_alloc_size(&desc)); return NULL; } // Fire up event in the emulator. if (notify_qemu_malloc(&desc)) { log_mdesc(error, &desc, "<libc_pid=%03u, pid=%03u>: malloc: notify_malloc failed for ", malloc_pid, getpid()); dlfree(desc.ptr); return NULL; } else { #if TEST_ACCESS_VIOLATIONS test_access_violation(&desc); #endif // TEST_ACCESS_VIOLATIONS log_mdesc(info, &desc, "+++ <libc_pid=%03u, pid=%03u> malloc(%u) -> ", malloc_pid, getpid(), bytes); return mallocdesc_user_ptr(&desc); } }
/* Dumps content of memory allocation descriptor to a string. * Param: * str - String to dump descriptor to. * str_buf_size - Size of string's buffer. * desc - Descriptor to dump. */ static void dump_malloc_descriptor(char* str, size_t str_buf_size, const MallocDesc* desc) { if (str_buf_size) { snprintf(str, str_buf_size, "MDesc: %p: %X <-> %X [%u + %u + %u] by pid=%03u in libc_pid=%03u", mallocdesc_user_ptr(desc), (uint32_t)desc->ptr, (uint32_t)mallocdesc_alloc_end(desc), desc->prefix_size, desc->requested_bytes, desc->suffix_size, desc->allocator_pid, desc->libc_pid); str[str_buf_size - 1] = '\0'; } }
/* Causes an access violation on allocation descriptor, and verifies that * violation has been detected by memory checker in the emulator. */ static void test_access_violation(const MallocDesc* desc) { MallocDesc desc_chk; char ch; volatile char* prefix = (volatile char*)desc->ptr; volatile char* suffix = (volatile char*)mallocdesc_user_ptr(desc) + desc->requested_bytes; /* We're causing AV by reading from the prefix and suffix areas of the * allocated block. This should produce two access violations, so when we * get allocation descriptor from QEMU, av_counter should be bigger than * av_counter of the original descriptor by 2. */ ch = *prefix; ch = *suffix; if (!query_qemu_malloc_info(mallocdesc_user_ptr(desc), &desc_chk, 2) && desc_chk.av_count != (desc->av_count + 2)) { log_mdesc(error, &desc_chk, "<libc_pid=%03u, pid=%03u>: malloc: Access violation test failed:\n" "Expected violations count %u is not equal to the actually reported %u", malloc_pid, getpid(), desc->av_count + 2, desc_chk.av_count); } }
/* This routine serves as entry point for 'memalign'. * This routine behaves similarly to qemu_instrumented_malloc. */ void* qemu_instrumented_memalign(size_t alignment, size_t bytes) { MallocDesc desc; if (bytes == 0) { // Just let go zero bytes allocation. qemu_info_log("::: <libc_pid=%03u, pid=%03u>: memalign(%X, %u) redir to malloc", malloc_pid, getpid(), alignment, bytes); return qemu_instrumented_malloc(0); } /* Prefix size for aligned allocation must be equal to the alignment used * for allocation in order to ensure proper alignment of the returned * pointer, in case that alignment requirement is greater than prefix * size. */ desc.prefix_size = alignment > DEFAULT_PREFIX_SIZE ? alignment : DEFAULT_PREFIX_SIZE; desc.requested_bytes = bytes; desc.suffix_size = DEFAULT_SUFFIX_SIZE; desc.ptr = dlmemalign(desc.prefix_size, mallocdesc_alloc_size(&desc)); if (desc.ptr == NULL) { error_log("<libc_pid=%03u, pid=%03u> memalign(%X, %u): dlmalloc(%u) failed.", malloc_pid, getpid(), alignment, bytes, mallocdesc_alloc_size(&desc)); return NULL; } if (notify_qemu_malloc(&desc)) { log_mdesc(error, &desc, "<libc_pid=%03u, pid=%03u>: memalign(%X, %u): notify_malloc failed for ", malloc_pid, getpid(), alignment, bytes); dlfree(desc.ptr); return NULL; } #if TEST_ACCESS_VIOLATIONS test_access_violation(&desc); #endif // TEST_ACCESS_VIOLATIONS log_mdesc(info, &desc, "@@@ <libc_pid=%03u, pid=%03u> memalign(%X, %u) -> ", malloc_pid, getpid(), alignment, bytes); return mallocdesc_user_ptr(&desc); }
/* This routine serves as entry point for 'malloc'. * Primary responsibility of this routine is to free requested memory, and * report free block to the emulator. */ void qemu_instrumented_free(void* mem) { MallocDesc desc; if (mem == NULL) { // Just let go NULL free dlfree(mem); return; } // Query emulator for the freeing block information. if (query_qemu_malloc_info(mem, &desc, 1)) { error_log("<libc_pid=%03u, pid=%03u>: free(%p) query_info failed.", malloc_pid, getpid(), mem); return; } #if TEST_ACCESS_VIOLATIONS test_access_violation(&desc); #endif // TEST_ACCESS_VIOLATIONS /* Make sure that pointer that's being freed matches what we expect * for this memory block. Note that this violation should be already * caught in the emulator. */ if (mem != mallocdesc_user_ptr(&desc)) { log_mdesc(error, &desc, "<libc_pid=%03u, pid=%03u>: free(%p) is invalid for ", malloc_pid, getpid(), mem); return; } // Fire up event in the emulator and free block that was actually allocated. if (notify_qemu_free(mem)) { log_mdesc(error, &desc, "<libc_pid=%03u, pid=%03u>: free(%p) notify_free failed for ", malloc_pid, getpid(), mem); } else { log_mdesc(info, &desc, "--- <libc_pid=%03u, pid=%03u> free(%p) -> ", malloc_pid, getpid(), mem); dlfree(desc.ptr); } }
size_t qemu_instrumented_malloc_usable_size(const void* mem) { MallocDesc cur_desc; // Query emulator for the reallocating block information. if (query_qemu_malloc_info(mem, &cur_desc, 2)) { // Note that this violation should be already caught in the emulator. error_log("<libc_pid=%03u, pid=%03u>: malloc_usable_size(%p) query_info failed.", malloc_pid, getpid(), mem); return 0; } /* Make sure that reallocating pointer value is what we would expect * for this memory block. Note that this violation should be already caught * in the emulator.*/ if (mem != mallocdesc_user_ptr(&cur_desc)) { log_mdesc(error, &cur_desc, "<libc_pid=%03u, pid=%03u>: malloc_usable_size(%p) is invalid for ", malloc_pid, getpid(), mem); return 0; } /* during instrumentation, we can't really report anything more than requested_bytes */ return cur_desc.requested_bytes; }
/* This routine serves as entry point for 'realloc'. * This routine behaves similarly to qemu_instrumented_free + * qemu_instrumented_malloc. Note that this modifies behavior of "shrinking" an * allocation, but overall it doesn't seem to matter, as caller of realloc * should not expect that pointer returned after shrinking will remain the same. */ void* qemu_instrumented_realloc(void* mem, size_t bytes) { MallocDesc new_desc; MallocDesc cur_desc; size_t to_copy; void* ret; if (mem == NULL) { // Nothing to realloc. just do regular malloc. qemu_info_log("::: <libc_pid=%03u, pid=%03u>: realloc(%p, %u) redir to malloc", malloc_pid, getpid(), mem, bytes); return qemu_instrumented_malloc(bytes); } if (bytes == 0) { // This is a "free" condition. qemu_info_log("::: <libc_pid=%03u, pid=%03u>: realloc(%p, %u) redir to free and malloc", malloc_pid, getpid(), mem, bytes); qemu_instrumented_free(mem); // This is what dlrealloc does for a "free" realloc. return NULL; } // Query emulator for the reallocating block information. if (query_qemu_malloc_info(mem, &cur_desc, 2)) { // Note that this violation should be already caught in the emulator. error_log("<libc_pid=%03u, pid=%03u>: realloc(%p, %u) query_info failed.", malloc_pid, getpid(), mem, bytes); return NULL; } #if TEST_ACCESS_VIOLATIONS test_access_violation(&cur_desc); #endif // TEST_ACCESS_VIOLATIONS /* Make sure that reallocating pointer value is what we would expect * for this memory block. Note that this violation should be already caught * in the emulator.*/ if (mem != mallocdesc_user_ptr(&cur_desc)) { log_mdesc(error, &cur_desc, "<libc_pid=%03u, pid=%03u>: realloc(%p, %u) is invalid for ", malloc_pid, getpid(), mem, bytes); return NULL; } /* TODO: We're a bit inefficient here, always allocating new block from * the heap. If this realloc shrinks current buffer, we can just do the * shrinking "in place", adjusting suffix_size in the allocation descriptor * for this block that is stored in the emulator. */ // Initialize descriptor for the new block. new_desc.prefix_size = DEFAULT_PREFIX_SIZE; new_desc.requested_bytes = bytes; new_desc.suffix_size = DEFAULT_SUFFIX_SIZE; new_desc.ptr = dlmalloc(mallocdesc_alloc_size(&new_desc)); if (new_desc.ptr == NULL) { log_mdesc(error, &cur_desc, "<libc_pid=%03u, pid=%03u>: realloc(%p, %u): dlmalloc(%u) failed on ", malloc_pid, getpid(), mem, bytes, mallocdesc_alloc_size(&new_desc)); return NULL; } ret = mallocdesc_user_ptr(&new_desc); // Copy user data from old block to the new one. to_copy = bytes < cur_desc.requested_bytes ? bytes : cur_desc.requested_bytes; if (to_copy != 0) { memcpy(ret, mallocdesc_user_ptr(&cur_desc), to_copy); } // Register new block with emulator. if (notify_qemu_malloc(&new_desc)) { log_mdesc(error, &new_desc, "<libc_pid=%03u, pid=%03u>: realloc(%p, %u) notify_malloc failed -> ", malloc_pid, getpid(), mem, bytes); log_mdesc(error, &cur_desc, " <- "); dlfree(new_desc.ptr); return NULL; } #if TEST_ACCESS_VIOLATIONS test_access_violation(&new_desc); #endif // TEST_ACCESS_VIOLATIONS // Free old block. if (notify_qemu_free(mem)) { log_mdesc(error, &cur_desc, "<libc_pid=%03u, pid=%03u>: realloc(%p, %u): notify_free failed for ", malloc_pid, getpid(), mem, bytes); /* Since we registered new decriptor with the emulator, we need * to unregister it before freeing newly allocated block. */ notify_qemu_free(mallocdesc_user_ptr(&new_desc)); dlfree(new_desc.ptr); return NULL; } dlfree(cur_desc.ptr); log_mdesc(info, &new_desc, "=== <libc_pid=%03u, pid=%03u>: realloc(%p, %u) -> ", malloc_pid, getpid(), mem, bytes); log_mdesc(info, &cur_desc, " <- "); return ret; }
/* This routine serves as entry point for 'calloc'. * This routine behaves similarly to qemu_instrumented_malloc. */ void* qemu_instrumented_calloc(size_t n_elements, size_t elem_size) { if (n_elements == 0 || elem_size == 0) { // Just let go zero bytes allocation. qemu_info_log("::: <libc_pid=%03u, pid=%03u>: Zero calloc redir to malloc", malloc_pid, getpid()); return qemu_instrumented_malloc(0); } /* Fail on overflow - just to be safe even though this code runs only * within the debugging C library, not the production one */ if (n_elements && MAX_SIZE_T / n_elements < elem_size) { return NULL; } MallocDesc desc; /* Calculating prefix size. The trick here is to make sure that * first element (returned to the caller) is properly aligned. */ if (DEFAULT_PREFIX_SIZE >= elem_size) { /* If default alignment is bigger than element size, we will * set our prefix size to the default alignment size. */ desc.prefix_size = DEFAULT_PREFIX_SIZE; /* For the suffix we will use whatever bytes remain from the prefix * allocation size, aligned to the size of an element, plus the usual * default suffix size. */ desc.suffix_size = (DEFAULT_PREFIX_SIZE % elem_size) + DEFAULT_SUFFIX_SIZE; } else { /* Make sure that prefix, and suffix sizes is at least elem_size, * and first element returned to the caller is properly aligned. */ desc.prefix_size = elem_size + DEFAULT_PREFIX_SIZE - 1; desc.prefix_size &= ~(malloc_alignment - 1); desc.suffix_size = DEFAULT_SUFFIX_SIZE; } desc.requested_bytes = n_elements * elem_size; size_t total_size = desc.requested_bytes + desc.prefix_size + desc.suffix_size; size_t total_elements = total_size / elem_size; total_size %= elem_size; if (total_size != 0) { // Add extra to the suffix area. total_elements++; desc.suffix_size += (elem_size - total_size); } desc.ptr = dlcalloc(total_elements, elem_size); if (desc.ptr == NULL) { error_log("<libc_pid=%03u, pid=%03u> calloc: dlcalloc(%u(%u), %u) (prx=%u, sfx=%u) failed.", malloc_pid, getpid(), n_elements, total_elements, elem_size, desc.prefix_size, desc.suffix_size); return NULL; } if (notify_qemu_malloc(&desc)) { log_mdesc(error, &desc, "<libc_pid=%03u, pid=%03u>: calloc(%u(%u), %u): notify_malloc failed for ", malloc_pid, getpid(), n_elements, total_elements, elem_size); dlfree(desc.ptr); return NULL; } else { #if TEST_ACCESS_VIOLATIONS test_access_violation(&desc); #endif // TEST_ACCESS_VIOLATIONS log_mdesc(info, &desc, "### <libc_pid=%03u, pid=%03u> calloc(%u(%u), %u) -> ", malloc_pid, getpid(), n_elements, total_elements, elem_size); return mallocdesc_user_ptr(&desc); } }