diff options
Diffstat (limited to 'libc/bionic/malloc_debug_qemu.c')
-rw-r--r-- | libc/bionic/malloc_debug_qemu.c | 1014 |
1 files changed, 1014 insertions, 0 deletions
diff --git a/libc/bionic/malloc_debug_qemu.c b/libc/bionic/malloc_debug_qemu.c new file mode 100644 index 0000000..4b694e9 --- /dev/null +++ b/libc/bionic/malloc_debug_qemu.c @@ -0,0 +1,1014 @@ +/* + * 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 <stdlib.h> +#include <stddef.h> +#include <stdio.h> +#include <fcntl.h> +#include <sys/mman.h> +#include <pthread.h> +#include <unistd.h> +#include <errno.h> +#include "dlmalloc.h" +#include "logd.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. + */ +typedef 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; +} MallocDesc; + +/* 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. + */ +typedef 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. + */ + 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; +} MallocDescQuery; + +/* 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. + */ +typedef 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; +} MallocFree; + +// ============================================================================= +// 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 mesages via + * __libc_android_log_print API 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 debug_log(format, ...) \ + do { \ + __libc_android_log_print(ANDROID_LOG_DEBUG, "memcheck", \ + (format), ##__VA_ARGS__ ); \ + if (tracing_flags & DEBUG_TRACING_ENABLED) { \ + qemu_log(ANDROID_LOG_DEBUG, (format), ##__VA_ARGS__ ); \ + } \ + } while (0) + +#define error_log(format, ...) \ + do { \ + __libc_android_log_print(ANDROID_LOG_ERROR, "memcheck", \ + (format), ##__VA_ARGS__ ); \ + if (tracing_flags & ERROR_TRACING_ENABLED) { \ + qemu_log(ANDROID_LOG_ERROR, (format), ##__VA_ARGS__ ); \ + } \ + } while (0) + +#define info_log(format, ...) \ + do { \ + __libc_android_log_print(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. + * frmt + rest - Formats message preceding dumped descriptor. +*/ +#define log_mdesc(type, desc, frmt, ...) \ + do { \ + if (tracing_enabled(type)) { \ + char log_str[4096]; \ + size_t str_len; \ + snprintf(log_str, sizeof(log_str), frmt, ##__VA_ARGS__); \ + log_str[sizeof(log_str) - 1] = '\0'; \ + str_len = strlen(log_str); \ + dump_malloc_descriptor(log_str + str_len, \ + sizeof(log_str) - str_len, \ + (desc)); \ + type##_log(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 dlmalloc. + * 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 (char*)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 (char*)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, uint32_t val) +{ + if (NULL != qtrace) { + *(volatile uint32_t*)((uint32_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, (uint32_t)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, (uint32_t)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, (uint32_t)&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(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, (uint32_t)&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: %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'; + } +} + +#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, + "<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); + } +} +#endif // TEST_ACCESS_VIOLATIONS + +// ============================================================================= +// API routines +// ============================================================================= + +void* qemu_instrumented_malloc(size_t bytes); +void qemu_instrumented_free(void* mem); +void* qemu_instrumented_calloc(size_t n_elements, size_t elem_size); +void* qemu_instrumented_realloc(void* mem, size_t bytes); +void* qemu_instrumented_memalign(size_t alignment, size_t bytes); + +/* 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. +*/ +int +malloc_debug_initialize(void) +{ + /* 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(0, 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. +*/ +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); + + 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. + */ +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) { + 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); + } +} + +/* 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); + } +} + +/* 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) +{ + MallocDesc desc; + void* ret; + size_t total_size; + size_t total_elements; + + if (n_elements == 0 || elem_size == 0) { + // Just let go zero bytes allocation. + 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; + } + + /* 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; + total_size = desc.requested_bytes + desc.prefix_size + desc.suffix_size; + 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); + } +} + +/* 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. + 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. + 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 '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. + 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); +} |