diff --git a/.gitignore b/.gitignore index 0db5b0393..5a5ea87a5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ *.pyc +*.*~ libc/kernel/original diff --git a/libc/bionic/malloc_debug_common.c b/libc/bionic/malloc_debug_common.c index 421091595..a86255a0a 100644 --- a/libc/bionic/malloc_debug_common.c +++ b/libc/bionic/malloc_debug_common.c @@ -27,8 +27,8 @@ */ /* - * Contains definition of global variables and implementation of routines - * that are used by malloc leak detection code and other components in + * Contains definition of structures, global variables, and implementation of + * routines that are used by malloc leak detection code and other components in * the system. The trick is that some components expect these data and * routines to be defined / implemented in libc.so library, regardless * whether or not MALLOC_LEAK_CHECK macro is defined. To make things even @@ -237,11 +237,11 @@ void* memalign(size_t alignment, size_t bytes) { // ============================================================================= #define debug_log(format, ...) \ - __libc_android_log_print(ANDROID_LOG_DEBUG, "libc", (format), ##__VA_ARGS__ ) + __libc_android_log_print(ANDROID_LOG_DEBUG, "libc", (format), ##__VA_ARGS__ ) #define error_log(format, ...) \ - __libc_android_log_print(ANDROID_LOG_ERROR, "libc", (format), ##__VA_ARGS__ ) + __libc_android_log_print(ANDROID_LOG_ERROR, "libc", (format), ##__VA_ARGS__ ) #define info_log(format, ...) \ - __libc_android_log_print(ANDROID_LOG_INFO, "libc", (format), ##__VA_ARGS__ ) + __libc_android_log_print(ANDROID_LOG_INFO, "libc", (format), ##__VA_ARGS__ ) /* Table for dispatching malloc calls, depending on environment. */ static MallocDebug gMallocUse __attribute__((aligned(32))) = { @@ -259,43 +259,64 @@ extern char* __progname; * CHK_SENTINEL_VALUE, and CHK_FILL_FREE macros. * 10 - For adding pre-, and post- allocation stubs in order to detect * buffer overruns. - * 20 - For enabling emulator memory allocation instrumentation detecting - * memory leaks and buffer overruns. + * Note that emulator's memory allocation instrumentation is not controlled by + * libc.debug.malloc value, but rather by emulator, started with -memcheck + * option. Note also, that if emulator has started with -memcheck option, + * emulator's instrumented memory allocation will take over value saved in + * libc.debug.malloc. In other words, if emulator has started with -memcheck + * option, libc.debug.malloc value is ignored. * Actual functionality for debug levels 1-10 is implemented in - * libc_malloc_debug_leak.so, while functionality for debug level 20 is - * implemented in libc_malloc_debug_qemu.so and can be run inside the - * emulator only. + * libc_malloc_debug_leak.so, while functionality for emultor's instrumented + * allocations is implemented in libc_malloc_debug_qemu.so and can be run inside + * the emulator only. */ static void* libc_malloc_impl_handle = NULL; +/* Make sure we have MALLOC_ALIGNMENT that matches the one that is + * used in dlmalloc. Emulator's memchecker needs this value to properly + * align its guarding zones. + */ +#ifndef MALLOC_ALIGNMENT +#define MALLOC_ALIGNMENT ((size_t)8U) +#endif /* MALLOC_ALIGNMENT */ + /* Initializes memory allocation framework once per process. */ static void malloc_init_impl(void) { const char* so_name = NULL; - unsigned int debug_level = 0; + MallocDebugInit malloc_debug_initialize = NULL; unsigned int qemu_running = 0; - int len; + unsigned int debug_level = 0; + unsigned int memcheck_enabled = 0; char env[PROP_VALUE_MAX]; + char memcheck_tracing[PROP_VALUE_MAX]; - // Get custom malloc debug level. - len = __system_property_get("libc.debug.malloc", env); - if (len) { + /* Get custom malloc debug level. Note that emulator started with + * memory checking option will have priority over debug level set in + * libc.debug.malloc system property. */ + if (__system_property_get("ro.kernel.qemu", env) && atoi(env)) { + qemu_running = 1; + if (__system_property_get("ro.kernel.memcheck", memcheck_tracing)) { + if (memcheck_tracing[0] != '0') { + // Emulator has started with memory tracing enabled. Enforce it. + debug_level = 20; + memcheck_enabled = 1; + } + } + } + + /* If debug level has not been set by memcheck option in the emulator, + * lets grab it from libc.debug.malloc system property. */ + if (!debug_level && __system_property_get("libc.debug.malloc", env)) { debug_level = atoi(env); } /* Debug level 0 means that we should use dlxxx allocation - * routines (default). - */ + * routines (default). */ if (!debug_level) { return; } - // Get emulator running status. - len = __system_property_get("ro.kernel.qemu", env); - if (len) { - qemu_running = atoi(env); - } - // Lets see which .so must be loaded for the requested debug level switch (debug_level) { case 1: @@ -304,17 +325,23 @@ static void malloc_init_impl(void) so_name = "/system/lib/libc_malloc_debug_leak.so"; break; case 20: - // Quick check: debug level 20 can only be handled in emulator + // Quick check: debug level 20 can only be handled in emulator. if (!qemu_running) { - info_log("%s: Debug level %d can only be set in emulator\n", - __progname, debug_level); + error_log("%s: Debug level %d can only be set in emulator\n", + __progname, debug_level); + return; + } + // Make sure that memory checking has been enabled in emulator. + if (!memcheck_enabled) { + error_log("%s: Memory checking is not enabled in the emulator\n", + __progname); return; } so_name = "/system/lib/libc_malloc_debug_qemu.so"; break; default: - info_log("%s: Debug level %d is unknown\n", - __progname, debug_level); + error_log("%s: Debug level %d is unknown\n", + __progname, debug_level); return; } @@ -326,6 +353,36 @@ static void malloc_init_impl(void) return; } + // Initialize malloc debugging in the loaded module. + malloc_debug_initialize = + dlsym(libc_malloc_impl_handle, "malloc_debug_initialize"); + if (malloc_debug_initialize == NULL) { + error_log("%s: Initialization routine is not found in %s\n", + __progname, so_name); + dlclose(libc_malloc_impl_handle); + return; + } + if (malloc_debug_initialize()) { + dlclose(libc_malloc_impl_handle); + return; + } + + if (debug_level == 20) { + // For memory checker we need to do extra initialization. + int (*memcheck_initialize)(int, const char*) = + dlsym(libc_malloc_impl_handle, "memcheck_initialize"); + if (memcheck_initialize == NULL) { + error_log("%s: memcheck_initialize routine is not found in %s\n", + __progname, so_name); + dlclose(libc_malloc_impl_handle); + return; + } + if (memcheck_initialize(MALLOC_ALIGNMENT, memcheck_tracing)) { + dlclose(libc_malloc_impl_handle); + return; + } + } + // Initialize malloc dispatch table with appropriate routines. switch (debug_level) { case 1: @@ -374,8 +431,8 @@ static void malloc_init_impl(void) break; case 20: __libc_android_log_print(ANDROID_LOG_INFO, "libc", - "%s using MALLOC_DEBUG = %d (instrumented for emulator)\n", - __progname, debug_level); + "%s[%u] using MALLOC_DEBUG = %d (instrumented for emulator)\n", + __progname, getpid(), debug_level); gMallocUse.malloc = dlsym(libc_malloc_impl_handle, "qemu_instrumented_malloc"); gMallocUse.free = @@ -421,9 +478,8 @@ static pthread_once_t malloc_init_once_ctl = PTHREAD_ONCE_INIT; */ void malloc_debug_init(void) { - /* We need to initialize malloc iff we impelement here custom - * malloc routines (i.e. USE_DL_PREFIX is defined) for libc.so - */ + /* We need to initialize malloc iff we implement here custom + * malloc routines (i.e. USE_DL_PREFIX is defined) for libc.so */ #if defined(USE_DL_PREFIX) && !defined(LIBC_STATIC) if (pthread_once(&malloc_init_once_ctl, malloc_init_impl)) { error_log("Unable to initialize malloc_debug component."); diff --git a/libc/bionic/malloc_debug_common.h b/libc/bionic/malloc_debug_common.h index a6301b386..87600d64a 100644 --- a/libc/bionic/malloc_debug_common.h +++ b/libc/bionic/malloc_debug_common.h @@ -70,13 +70,28 @@ struct HashTable { /* Entry in malloc dispatch table. */ typedef struct MallocDebug MallocDebug; struct MallocDebug { + /* Address of the actual malloc routine. */ void* (*malloc)(size_t bytes); + /* Address of the actual free routine. */ void (*free)(void* mem); + /* Address of the actual calloc routine. */ void* (*calloc)(size_t n_elements, size_t elem_size); + /* Address of the actual realloc routine. */ void* (*realloc)(void* oldMem, size_t bytes); + /* Address of the actual memalign routine. */ void* (*memalign)(size_t alignment, size_t bytes); }; +/* Malloc debugging initialization routine. + * This routine must be implemented in .so modules that implement malloc + * debugging. This routine is called once per process from malloc_init_impl + * routine implemented in bionic/libc/bionic/malloc_debug_common.c when malloc + * debugging gets initialized for the process. + * Return: + * 0 on success, -1 on failure. + */ +typedef int (*MallocDebugInit)(void); + #ifdef __cplusplus }; /* end of extern "C" */ #endif diff --git a/libc/bionic/malloc_debug_leak.c b/libc/bionic/malloc_debug_leak.c index 7b8822c24..e11606dcd 100644 --- a/libc/bionic/malloc_debug_leak.c +++ b/libc/bionic/malloc_debug_leak.c @@ -629,3 +629,12 @@ void* leak_memalign(size_t alignment, size_t bytes) } return base; } + +/* Initializes malloc debugging framework. + * See comments on MallocDebugInit in malloc_debug_common.h + */ +int malloc_debug_initialize(void) +{ + // We don't really have anything that requires initialization here. + return 0; +} diff --git a/libc/bionic/malloc_debug_qemu.c b/libc/bionic/malloc_debug_qemu.c index b63ddb4f8..4b694e9f7 100644 --- a/libc/bionic/malloc_debug_qemu.c +++ b/libc/bionic/malloc_debug_qemu.c @@ -27,55 +27,988 @@ */ /* - * Contains implementation of memeory allocation routines instrumented for + * 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 "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. +/* 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 +#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 // ============================================================================= -// log functions +// Communication structures // ============================================================================= -#define debug_log(format, ...) \ - __libc_android_log_print(ANDROID_LOG_DEBUG, "malloc_qemu", (format), ##__VA_ARGS__ ) -#define error_log(format, ...) \ - __libc_android_log_print(ANDROID_LOG_ERROR, "malloc_qemu", (format), ##__VA_ARGS__ ) -#define info_log(format, ...) \ - __libc_android_log_print(ANDROID_LOG_INFO, "malloc_qemu", (format), ##__VA_ARGS__ ) +/* 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; -void* qemu_instrumented_malloc(size_t bytes) + /* 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 dlmalloc(bytes); + return (char*)desc->ptr + desc->prefix_size; } -void qemu_instrumented_free(void* mem) +/* 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) { - dlfree(mem); + return desc->prefix_size + desc->requested_bytes + desc->suffix_size; } -void* qemu_instrumented_calloc(size_t n_elements, size_t elem_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 dlcalloc(n_elements, elem_size); + return (char*)desc->ptr + mallocdesc_alloc_size(desc); } -void* qemu_instrumented_realloc(void* mem, size_t bytes) +/* 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) { - return dlrealloc(mem, bytes); + if (NULL != qtrace) { + *(volatile uint32_t*)((uint32_t)qtrace + ((code - 1024) << 2)) = val; + } } -void* qemu_instrumented_memalign(size_t alignment, size_t bytes) +/* 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) { - return dlmemalign(alignment, bytes); + 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, + ": 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(" 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, ": 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, "+++ 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(": 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); + 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("::: : 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(" 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, ": 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, "### 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("::: : 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("::: : 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(": 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, ": 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, ": 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, ": 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, ": 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, "=== : 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("::: : 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(" 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, ": 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, "@@@ memalign(%X, %u) -> ", + malloc_pid, getpid(), alignment, bytes); + return mallocdesc_user_ptr(&desc); }