/* * Copyright (C) 2009 The Android Open Source Project * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * Contains implementation of memory allocation routines instrumented for * usage in the emulator to detect memory allocation violations, such as * memory leaks, buffer overruns, etc. * Code, implemented here is intended to run in the emulated environment only, * and serves simply as hooks into memory allocation routines. Main job of this * code is to notify the emulator about memory being allocated/deallocated, * providing information about each allocation. The idea is that emulator will * keep list of currently allocated blocks, and, knowing boundaries of each * block it will be able to verify that ld/st access to these blocks don't step * over boundaries set for the user. To enforce that, each memory block * allocated by this code is guarded with "prefix" and "suffix" areas, so * every time emulator detects access to any of these guarding areas, it can be * considered as access violation. */ #include #include #include #include #include #include #include #include #include "private/libc_logging.h" #include "malloc_debug_common.h" /* This file should be included into the build only when * MALLOC_QEMU_INSTRUMENT macro is defined. */ #ifndef MALLOC_QEMU_INSTRUMENT #error MALLOC_QEMU_INSTRUMENT is not defined. #endif // !MALLOC_QEMU_INSTRUMENT /* Controls access violation test performed to make sure that we catch AVs * all the time they occur. See test_access_violation for more info. This macro * is used for internal testing purposes and should always be set to zero for * the production builds. */ #define TEST_ACCESS_VIOLATIONS 0 // ============================================================================= // Communication structures // ============================================================================= /* Describes memory block allocated from the heap. This structure is passed * along with TRACE_DEV_REG_MALLOC event. This descriptor is used to inform * the emulator about new memory block being allocated from the heap. The entire * structure is initialized by the guest system before event is fired up. It is * important to remember that same structure (an exact copy, except for * replacing pointers with target_ulong) is also declared in the emulator's * sources (file memcheck/memcheck_common.h). So, every time a change is made to * any of these two declaration, another one must be also updated accordingly. */ struct MallocDesc { /* Pointer to the memory block actually allocated from the heap. Note that * this is not the pointer that is returned to the malloc's caller. Pointer * returned to the caller is calculated by adding value stored in this field * to the value stored in prefix_size field of this structure. */ void* ptr; /* Number of bytes requested by the malloc's caller. */ uint32_t requested_bytes; /* Byte size of the prefix data. Actual pointer returned to the malloc's * caller is calculated by adding value stored in this field to the value * stored in in the ptr field of this structure. */ uint32_t prefix_size; /* Byte size of the suffix data. */ uint32_t suffix_size; /* Id of the process that initialized libc instance, in which allocation * has occurred. This field is used by the emulator to report errors in * the course of TRACE_DEV_REG_MALLOC event handling. In case of an error, * emulator sets this field to zero (invalid value for a process ID). */ uint32_t libc_pid; /* Id of the process in context of which allocation has occurred. * Value in this field may differ from libc_pid value, if process that * is doing allocation has been forked from the process that initialized * libc instance. */ uint32_t allocator_pid; /* Number of access violations detected on this allocation. */ uint32_t av_count; }; /* Describes memory block info queried from emulator. This structure is passed * along with TRACE_DEV_REG_QUERY_MALLOC event. When handling free and realloc * calls, it is required that we have information about memory blocks that were * actually allocated in previous calls to malloc, calloc, memalign, or realloc. * Since we don't keep this information directly in the allocated block, but * rather we keep it in the emulator, we need to query emulator for that * information with TRACE_DEV_REG_QUERY_MALLOC query. The entire structure is * initialized by the guest system before event is fired up. It is important to * remember that same structure (an exact copy, except for replacing pointers * with target_ulong) is also declared in the emulator's sources (file * memcheck/memecheck_common.h). So, every time a change is made to any of these * two declaration, another one must be also updated accordingly. */ struct MallocDescQuery { /* Pointer, for which information is queried. Note that this pointer doesn't * have to be exact pointer returned to malloc's caller, but can point * anywhere inside an allocated block, including guarding areas. Emulator * will respond with information about allocated block that contains this * pointer. */ const void* ptr; /* Id of the process that initialized libc instance, in which this query * is called. This field is used by the emulator to report errors in * the course of TRACE_DEV_REG_QUERY_MALLOC event handling. In case of an * error, emulator sets this field to zero (invalid value for a process ID). */ uint32_t libc_pid; /* Process ID in context of which query is made. */ uint32_t query_pid; /* Code of the allocation routine, in context of which query has been made: * 1 - free * 2 - realloc */ uint32_t routine; /* Address of memory allocation descriptor for the queried pointer. * Descriptor, addressed by this field is initialized by the emulator in * response to the query. */ MallocDesc* desc; }; /* Describes memory block that is being freed back to the heap. This structure * is passed along with TRACE_DEV_REG_FREE_PTR event. The entire structure is * initialized by the guest system before event is fired up. It is important to * remember that same structure (an exact copy, except for replacing pointers * with target_ulong) is also declared in the emulator's sources (file * memcheck/memecheck_common.h). So, every time a change is made to any of these * two declaration, another one must be also updated accordingly. */ struct MallocFree { /* Pointer to be freed. */ void* ptr; /* Id of the process that initialized libc instance, in which this free * is called. This field is used by the emulator to report errors in * the course of TRACE_DEV_REG_FREE_PTR event handling. In case of an * error, emulator sets this field to zero (invalid value for a process ID). */ uint32_t libc_pid; /* Process ID in context of which memory is being freed. */ uint32_t free_pid; }; // ============================================================================= // Communication events // ============================================================================= /* Notifies the emulator that libc has been initialized for a process. * Event's value parameter is PID for the process in context of which libc has * been initialized. */ #define TRACE_DEV_REG_LIBC_INIT 1536 /* Notifies the emulator about new memory block been allocated. * Event's value parameter points to MallocDesc instance that contains * allocated block information. Note that 'libc_pid' field of the descriptor * is used by emulator to report failure in handling this event. In case * of a failure emulator will zero that field before completing this event. */ #define TRACE_DEV_REG_MALLOC 1537 /* Notifies the emulator about memory block being freed. * Event's value parameter points to MallocFree descriptor that contains * information about block that's being freed. Note that 'libc_pid' field * of the descriptor is used by emulator to report failure in handling this * event. In case of a failure emulator will zero that field before completing * this event. */ #define TRACE_DEV_REG_FREE_PTR 1538 /* Queries the emulator about allocated memory block information. * Event's value parameter points to MallocDescQuery descriptor that contains * query parameters. Note that 'libc_pid' field of the descriptor is used by * emulator to report failure in handling this event. In case of a failure * emulator will zero that field before completing this event. */ #define TRACE_DEV_REG_QUERY_MALLOC 1539 /* Queries the emulator to print a string to its stdout. * Event's value parameter points to a zero-terminated string to be printed. */ #define TRACE_DEV_REG_PRINT_USER_STR 1540 static void notify_qemu_string(const char* str); static void qemu_log(int prio, const char* fmt, ...); static void dump_malloc_descriptor(char* str, size_t str_buf_size, const MallocDesc* desc); // ============================================================================= // Macros // ============================================================================= /* Defines default size of allocation prefix. * Note that we make prefix area quite large in order to increase chances of * catching buffer overflow. */ #define DEFAULT_PREFIX_SIZE (malloc_alignment * 4) /* Defines default size of allocation suffix. * Note that we make suffix area quite large in order to increase chances of * catching buffer overflow. */ #define DEFAULT_SUFFIX_SIZE (malloc_alignment * 4) /* Debug tracing has been enabled by the emulator. */ #define DEBUG_TRACING_ENABLED 0x00000001 /* Error tracing has been enabled by the emulator. */ #define ERROR_TRACING_ENABLED 0x00000002 /* Info tracing has been enabled by the emulator. */ #define INFO_TRACING_ENABLED 0x00000004 /* All tracing flags combined. */ #define ALL_TRACING_ENABLED (DEBUG_TRACING_ENABLED | \ ERROR_TRACING_ENABLED | \ INFO_TRACING_ENABLED) /* Prints a string to the emulator's stdout. * In early stages of system loading, logging messages to logcat * is not available, because ADB API has not been * hooked up yet. So, in order to see such messages we need to print them to * the emulator's stdout. * Parameters passed to this macro are the same as parameters for printf * routine. */ #define TR(...) \ do { \ char tr_str[4096]; \ snprintf(tr_str, sizeof(tr_str), __VA_ARGS__); \ tr_str[sizeof(tr_str) - 1] = '\0'; \ notify_qemu_string(&tr_str[0]); \ } while (0) // ============================================================================= // Logging macros. Note that we simultaneously log messages to ADB and emulator. // ============================================================================= /* * Helper macros for checking if particular trace level is enabled. */ #define debug_LOG_ENABLED ((tracing_flags & DEBUG_TRACING_ENABLED) != 0) #define error_LOG_ENABLED ((tracing_flags & ERROR_TRACING_ENABLED) != 0) #define info_LOG_ENABLED ((tracing_flags & INFO_TRACING_ENABLED) != 0) #define tracing_enabled(type) (type##_LOG_ENABLED) /* * Logging helper macros. */ #define qemu_debug_log(format, ...) \ do { \ __libc_format_log(ANDROID_LOG_DEBUG, "memcheck", (format), ##__VA_ARGS__); \ if (tracing_flags & DEBUG_TRACING_ENABLED) { \ qemu_log(ANDROID_LOG_DEBUG, (format), ##__VA_ARGS__); \ } \ } while (0) #define qemu_error_log(format, ...) \ do { \ __libc_format_log(ANDROID_LOG_ERROR, "memcheck", (format), ##__VA_ARGS__); \ if (tracing_flags & ERROR_TRACING_ENABLED) { \ qemu_log(ANDROID_LOG_ERROR, (format), ##__VA_ARGS__); \ } \ } while (0) #define qemu_info_log(format, ...) \ do { \ __libc_format_log(ANDROID_LOG_INFO, "memcheck", (format), ##__VA_ARGS__); \ if (tracing_flags & INFO_TRACING_ENABLED) { \ qemu_log(ANDROID_LOG_INFO, (format), ##__VA_ARGS__); \ } \ } while (0) /* Logs message dumping MallocDesc instance at the end of the message. * Param: * type - Message type: debug, error, or info * desc - MallocDesc instance to dump. * fmt + rest - Formats message preceding dumped descriptor. */ #define log_mdesc(type, desc, fmt, ...) \ do { \ if (tracing_enabled(type)) { \ char log_str[4096]; \ __libc_format_buffer(log_str, sizeof(log_str), fmt, ##__VA_ARGS__); \ log_str[sizeof(log_str) - 1] = '\0'; \ size_t str_len = strlen(log_str); \ dump_malloc_descriptor(log_str + str_len, \ sizeof(log_str) - str_len, \ (desc)); \ type##_log("%s", log_str); \ } \ } while (0) // ============================================================================= // Static data // ============================================================================= /* Emulator's magic page address. * This page (mapped on /dev/qemu_trace device) is used to fire up events * in the emulator. */ static volatile void* qtrace = NULL; /* Cached PID of the process in context of which this libc instance * has been initialized. */ static uint32_t malloc_pid = 0; /* Memory allocation alignment that is used in the malloc implementation. * This variable is updated by memcheck_initialize routine. */ static uint32_t malloc_alignment = 8; /* Tracing flags. These flags control which types of logging messages are * enabled by the emulator. See XXX_TRACING_ENABLED for the values of flags * stored in this variable. This variable is updated by memcheck_initialize * routine. */ static uint32_t tracing_flags = 0; // ============================================================================= // Static routines // ============================================================================= /* Gets pointer, returned to malloc caller for the given allocation decriptor. * Param: * desc - Allocation descriptor. * Return: * Pointer to the allocated memory returned to the malloc caller. */ static inline void* mallocdesc_user_ptr(const MallocDesc* desc) { return static_cast(desc->ptr) + desc->prefix_size; } /* Gets size of memory block actually allocated from the heap for the given * allocation decriptor. * Param: * desc - Allocation descriptor. * Return: * Size of memory block actually allocated from the heap. */ static inline uint32_t mallocdesc_alloc_size(const MallocDesc* desc) { return desc->prefix_size + desc->requested_bytes + desc->suffix_size; } /* Gets pointer to the end of the allocated block for the given descriptor. * Param: * desc - Descriptor for the memory block, allocated in malloc handler. * Return: * Pointer to the end of (one byte past) the allocated block. */ static inline void* mallocdesc_alloc_end(const MallocDesc* desc) { return static_cast(desc->ptr) + mallocdesc_alloc_size(desc); } /* Fires up an event in the emulator. * Param: * code - Event code (one of the TRACE_DEV_XXX). * val - Event's value parameter. */ static inline void notify_qemu(uint32_t code, uintptr_t val) { if (NULL != qtrace) { *(volatile uintptr_t*)((uintptr_t)qtrace + ((code - 1024) << 2)) = val; } } /* Prints a zero-terminated string to the emulator's stdout (fires up * TRACE_DEV_REG_PRINT_USER_STR event in the emulator). * Param: * str - Zero-terminated string to print. */ static void notify_qemu_string(const char* str) { if (str != NULL) { notify_qemu(TRACE_DEV_REG_PRINT_USER_STR, reinterpret_cast(str)); } } /* Fires up TRACE_DEV_REG_LIBC_INIT event in the emulator. * Param: * pid - ID of the process that initialized libc. */ static void notify_qemu_libc_initialized(uint32_t pid) { notify_qemu(TRACE_DEV_REG_LIBC_INIT, pid); } /* Fires up TRACE_DEV_REG_MALLOC event in the emulator. * Param: * desc - Pointer to MallocDesc instance containing allocated block * information. * Return: * Zero on success, or -1 on failure. Note that on failure libc_pid field of * the desc parameter passed to this routine has been zeroed out by the * emulator. */ static inline int notify_qemu_malloc(volatile MallocDesc* desc) { desc->libc_pid = malloc_pid; desc->allocator_pid = getpid(); desc->av_count = 0; notify_qemu(TRACE_DEV_REG_MALLOC, reinterpret_cast(desc)); /* Emulator reports failure by zeroing libc_pid field of the * descriptor. */ return desc->libc_pid != 0 ? 0 : -1; } /* Fires up TRACE_DEV_REG_FREE_PTR event in the emulator. * Param: * ptr - Pointer to the memory block that's being freed. * Return: * Zero on success, or -1 on failure. */ static inline int notify_qemu_free(void* ptr_to_free) { volatile MallocFree free_desc; free_desc.ptr = ptr_to_free; free_desc.libc_pid = malloc_pid; free_desc.free_pid = getpid(); notify_qemu(TRACE_DEV_REG_FREE_PTR, reinterpret_cast(&free_desc)); /* Emulator reports failure by zeroing libc_pid field of the * descriptor. */ return free_desc.libc_pid != 0 ? 0 : -1; } /* Fires up TRACE_DEV_REG_QUERY_MALLOC event in the emulator. * Param: * ptr - Pointer to request allocation information for. * desc - Pointer to MallocDesc instance that will receive allocation * information. * routine - Code of the allocation routine, in context of which query is made: * 1 - free * 2 - realloc * Return: * Zero on success, or -1 on failure. */ static inline int query_qemu_malloc_info(const void* ptr, MallocDesc* desc, uint32_t routine) { volatile MallocDescQuery query; query.ptr = ptr; query.libc_pid = malloc_pid; query.query_pid = getpid(); query.routine = routine; query.desc = desc; notify_qemu(TRACE_DEV_REG_QUERY_MALLOC, reinterpret_cast(&query)); /* Emulator reports failure by zeroing libc_pid field of the * descriptor. */ return query.libc_pid != 0 ? 0 : -1; } /* Logs a message to emulator's stdout. * Param: * prio - Message priority (debug, info, or error) * fmt + rest - Message format and parameters. */ static void qemu_log(int prio, const char* fmt, ...) { va_list ap; char buf[4096]; const char* prefix; /* Choose message prefix depending on the priority value. */ switch (prio) { case ANDROID_LOG_ERROR: if (!tracing_enabled(error)) { return; } prefix = "E"; break; case ANDROID_LOG_INFO: if (!tracing_enabled(info)) { return; } prefix = "I"; break; case ANDROID_LOG_DEBUG: default: if (!tracing_enabled(debug)) { return; } prefix = "D"; break; } va_start(ap, fmt); vsnprintf(buf, sizeof(buf), fmt, ap); va_end(ap); buf[sizeof(buf) - 1] = '\0'; TR("%s/memcheck: %s\n", prefix, buf); } /* 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: %p <-> %p [%u + %u + %u] by pid=%03u in libc_pid=%03u", mallocdesc_user_ptr(desc), desc->ptr, 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'; } } #if TEST_ACCESS_VIOLATIONS /* 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, ": 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); } } #endif // TEST_ACCESS_VIOLATIONS // ============================================================================= // API routines // ============================================================================= extern "C" void* qemu_instrumented_malloc(size_t bytes); extern "C" void qemu_instrumented_free(void* mem); extern "C" void* qemu_instrumented_calloc(size_t n_elements, size_t elem_size); extern "C" void* qemu_instrumented_realloc(void* mem, size_t bytes); extern "C" void* qemu_instrumented_memalign(size_t alignment, size_t bytes); extern "C" size_t qemu_instrumented_malloc_usable_size(const void* mem); /* Initializes malloc debugging instrumentation for the emulator. * This routine is called from malloc_init_impl routine implemented in * bionic/libc/bionic/malloc_debug_common.c when malloc debugging gets * initialized for a process. The way malloc debugging implementation is * done, it is guaranteed that this routine will be called just once per * process. * Return: * 0 on success, or -1 on failure. */ extern "C" int malloc_debug_initialize() { /* We will be using emulator's magic page to report memory allocation * activities. In essence, what magic page does, it translates writes to * the memory mapped spaces into writes to an I/O port that emulator * "listens to" on the other end. Note that until we open and map that * device, logging to emulator's stdout will not be available. */ int fd = open("/dev/qemu_trace", O_RDWR); if (fd < 0) { error_log("Unable to open /dev/qemu_trace"); return -1; } else { qtrace = mmap(NULL, PAGESIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); close(fd); if (qtrace == MAP_FAILED) { qtrace = NULL; error_log("Unable to mmap /dev/qemu_trace"); return -1; } } /* Cache pid of the process this library has been initialized for. */ malloc_pid = getpid(); return 0; } /* Completes malloc debugging instrumentation for the emulator. * Note that this routine is called after successful return from * malloc_debug_initialize, which means that connection to the emulator via * "magic page" has been established. * Param: * alignment - Alignment requirement set for memiry allocations. * memcheck_param - Emulator's -memcheck option parameters. This string * contains abbreviation for guest events that are enabled for tracing. * Return: * 0 on success, or -1 on failure. */ extern "C" int memcheck_initialize(int alignment, const char* memcheck_param) { malloc_alignment = alignment; /* Parse -memcheck parameter for the guest tracing flags. */ while (*memcheck_param != '\0') { switch (*memcheck_param) { case 'a': // Enable all messages from the guest. tracing_flags |= ALL_TRACING_ENABLED; break; case 'd': // Enable debug messages from the guest. tracing_flags |= DEBUG_TRACING_ENABLED; break; case 'e': // Enable error messages from the guest. tracing_flags |= ERROR_TRACING_ENABLED; break; case 'i': // Enable info messages from the guest. tracing_flags |= INFO_TRACING_ENABLED; break; default: break; } if (tracing_flags == ALL_TRACING_ENABLED) { break; } memcheck_param++; } notify_qemu_libc_initialized(malloc_pid); qemu_debug_log("Instrumented for pid=%03u: malloc=%p, free=%p, calloc=%p, realloc=%p, memalign=%p", malloc_pid, qemu_instrumented_malloc, qemu_instrumented_free, qemu_instrumented_calloc, qemu_instrumented_realloc, qemu_instrumented_memalign); return 0; } /* 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. */ extern "C" void* qemu_instrumented_malloc(size_t bytes) { MallocDesc desc; /* Initialize block descriptor and allocate memory. Note that malloc * 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 = Malloc(malloc)(mallocdesc_alloc_size(&desc)); if (desc.ptr == NULL) { qemu_error_log(" malloc(%zd): malloc(%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, ": malloc: notify_malloc failed for ", malloc_pid, getpid()); Malloc(free)(desc.ptr); return NULL; } else { #if TEST_ACCESS_VIOLATIONS test_access_violation(&desc); #endif // TEST_ACCESS_VIOLATIONS log_mdesc(info, &desc, "+++ malloc(%zd) -> ", malloc_pid, getpid(), 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. */ extern "C" void qemu_instrumented_free(void* mem) { MallocDesc desc; if (mem == NULL) { // Just let go NULL free Malloc(free)(mem); return; } // Query emulator for the freeing block information. if (query_qemu_malloc_info(mem, &desc, 1)) { error_log(": 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, ": 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, ": free(%p) notify_free failed for ", malloc_pid, getpid(), mem); } else { log_mdesc(info, &desc, "--- free(%p) -> ", malloc_pid, getpid(), mem); Malloc(free)(desc.ptr); } } /* This routine serves as entry point for 'calloc'. * This routine behaves similarly to qemu_instrumented_malloc. */ extern "C" 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("::: : 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 = Malloc(calloc)(total_elements, elem_size); if (desc.ptr == NULL) { error_log(" calloc: calloc(%zd(%zd), %zd) (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, ": calloc(%zd(%zd), %zd): notify_malloc failed for ", malloc_pid, getpid(), n_elements, total_elements, elem_size); Malloc(free)(desc.ptr); return NULL; } else { #if TEST_ACCESS_VIOLATIONS test_access_violation(&desc); #endif // TEST_ACCESS_VIOLATIONS log_mdesc(info, &desc, "### calloc(%zd(%zd), %zd) -> ", malloc_pid, getpid(), n_elements, total_elements, elem_size); return mallocdesc_user_ptr(&desc); } } /* 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. */ extern "C" 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("::: : realloc(%p, %zd) redir to malloc", malloc_pid, getpid(), mem, bytes); return qemu_instrumented_malloc(bytes); } if (bytes == 0) { // This is a "free" condition. qemu_info_log("::: : realloc(%p, %zd) redir to free and malloc", malloc_pid, getpid(), mem, bytes); qemu_instrumented_free(mem); // This is what realloc 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(": realloc(%p, %zd) 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, ": realloc(%p, %zd) 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 = Malloc(malloc)(mallocdesc_alloc_size(&new_desc)); if (new_desc.ptr == NULL) { log_mdesc(error, &cur_desc, ": realloc(%p, %zd): malloc(%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, ": realloc(%p, %zd) notify_malloc failed -> ", malloc_pid, getpid(), mem, bytes); log_mdesc(error, &cur_desc, " <- "); Malloc(free)(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, ": realloc(%p, %zd): 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)); Malloc(free)(new_desc.ptr); return NULL; } Malloc(free)(cur_desc.ptr); log_mdesc(info, &new_desc, "=== : realloc(%p, %zd) -> ", malloc_pid, getpid(), mem, bytes); log_mdesc(info, &cur_desc, " <- "); return ret; } /* This routine serves as entry point for 'memalign'. * This routine behaves similarly to qemu_instrumented_malloc. */ extern "C" void* qemu_instrumented_memalign(size_t alignment, size_t bytes) { MallocDesc desc; if (bytes == 0) { // Just let go zero bytes allocation. qemu_info_log("::: : memalign(%zx, %zd) 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 = Malloc(memalign)(desc.prefix_size, mallocdesc_alloc_size(&desc)); if (desc.ptr == NULL) { error_log(" memalign(%zx, %zd): malloc(%u) failed.", malloc_pid, getpid(), alignment, bytes, mallocdesc_alloc_size(&desc)); return NULL; } if (notify_qemu_malloc(&desc)) { log_mdesc(error, &desc, ": memalign(%zx, %zd): notify_malloc failed for ", malloc_pid, getpid(), alignment, bytes); Malloc(free)(desc.ptr); return NULL; } #if TEST_ACCESS_VIOLATIONS test_access_violation(&desc); #endif // TEST_ACCESS_VIOLATIONS log_mdesc(info, &desc, "@@@ memalign(%zx, %zd) -> ", malloc_pid, getpid(), alignment, bytes); return mallocdesc_user_ptr(&desc); } extern "C" 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(": 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, ": 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; }