885f3b9cad
- Implemented chk_memalign.
- Fixed a few bugs in leak_memalign.
- Implemented {leak,fill,check,qemu}_malloc_usable_size.
- Make malloc_usable_size update at run time.
- Add malloc_test.cpp as a small set of tests for the
malloc debug routines.
- Fix the qemu routines since it's been broken since it moved to C++.
- Add support for the %u format to the out_vformat in libc_logging.cpp.
This is used by the emulator code.
Tested using the bionic-unit-tests with setprop libc.debug.malloc
set to 1, 5, and 10.
I tested as much as possible on the emulator, but tracing doesn't appear
to be working properly.
Bug: 6143477
Merge change from internal master.
(cherry-picked from commit 3d594c2580
)
Change-Id: I4ae00fffba82315a8c283f35893fd554460722fb
994 lines
40 KiB
C++
994 lines
40 KiB
C++
/*
|
|
* 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 "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 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 static_cast<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 static_cast<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(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, (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
|
|
// =============================================================================
|
|
|
|
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 dlmalloc
|
|
* returns a valid pointer on zero allocation. Lets mimic this behavior. */
|
|
desc.prefix_size = DEFAULT_PREFIX_SIZE;
|
|
desc.requested_bytes = bytes;
|
|
desc.suffix_size = DEFAULT_SUFFIX_SIZE;
|
|
desc.ptr = dlmalloc(mallocdesc_alloc_size(&desc));
|
|
if (desc.ptr == NULL) {
|
|
qemu_error_log("<libc_pid=%03u, pid=%03u> malloc(%u): dlmalloc(%u) failed.",
|
|
malloc_pid, getpid(), bytes, mallocdesc_alloc_size(&desc));
|
|
return NULL;
|
|
}
|
|
|
|
// Fire up event in the emulator.
|
|
if (notify_qemu_malloc(&desc)) {
|
|
log_mdesc(error, &desc, "<libc_pid=%03u, pid=%03u>: malloc: notify_malloc failed for ",
|
|
malloc_pid, getpid());
|
|
dlfree(desc.ptr);
|
|
return NULL;
|
|
} else {
|
|
#if TEST_ACCESS_VIOLATIONS
|
|
test_access_violation(&desc);
|
|
#endif // TEST_ACCESS_VIOLATIONS
|
|
log_mdesc(info, &desc, "+++ <libc_pid=%03u, pid=%03u> malloc(%u) -> ",
|
|
malloc_pid, getpid(), bytes);
|
|
return mallocdesc_user_ptr(&desc);
|
|
}
|
|
}
|
|
|
|
/* 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
|
|
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.
|
|
*/
|
|
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("::: <libc_pid=%03u, pid=%03u>: Zero calloc redir to malloc",
|
|
malloc_pid, getpid());
|
|
return qemu_instrumented_malloc(0);
|
|
}
|
|
|
|
/* Fail on overflow - just to be safe even though this code runs only
|
|
* within the debugging C library, not the production one */
|
|
if (n_elements && MAX_SIZE_T / n_elements < elem_size) {
|
|
return NULL;
|
|
}
|
|
|
|
MallocDesc desc;
|
|
|
|
/* Calculating prefix size. The trick here is to make sure that
|
|
* first element (returned to the caller) is properly aligned. */
|
|
if (DEFAULT_PREFIX_SIZE >= elem_size) {
|
|
/* If default alignment is bigger than element size, we will
|
|
* set our prefix size to the default alignment size. */
|
|
desc.prefix_size = DEFAULT_PREFIX_SIZE;
|
|
/* For the suffix we will use whatever bytes remain from the prefix
|
|
* allocation size, aligned to the size of an element, plus the usual
|
|
* default suffix size. */
|
|
desc.suffix_size = (DEFAULT_PREFIX_SIZE % elem_size) +
|
|
DEFAULT_SUFFIX_SIZE;
|
|
} else {
|
|
/* Make sure that prefix, and suffix sizes is at least elem_size,
|
|
* and first element returned to the caller is properly aligned. */
|
|
desc.prefix_size = elem_size + DEFAULT_PREFIX_SIZE - 1;
|
|
desc.prefix_size &= ~(malloc_alignment - 1);
|
|
desc.suffix_size = DEFAULT_SUFFIX_SIZE;
|
|
}
|
|
desc.requested_bytes = n_elements * elem_size;
|
|
size_t total_size = desc.requested_bytes + desc.prefix_size + desc.suffix_size;
|
|
size_t total_elements = total_size / elem_size;
|
|
total_size %= elem_size;
|
|
if (total_size != 0) {
|
|
// Add extra to the suffix area.
|
|
total_elements++;
|
|
desc.suffix_size += (elem_size - total_size);
|
|
}
|
|
desc.ptr = dlcalloc(total_elements, elem_size);
|
|
if (desc.ptr == NULL) {
|
|
error_log("<libc_pid=%03u, pid=%03u> calloc: dlcalloc(%u(%u), %u) (prx=%u, sfx=%u) failed.",
|
|
malloc_pid, getpid(), n_elements, total_elements, elem_size,
|
|
desc.prefix_size, desc.suffix_size);
|
|
return NULL;
|
|
}
|
|
|
|
if (notify_qemu_malloc(&desc)) {
|
|
log_mdesc(error, &desc, "<libc_pid=%03u, pid=%03u>: calloc(%u(%u), %u): notify_malloc failed for ",
|
|
malloc_pid, getpid(), n_elements, total_elements, elem_size);
|
|
dlfree(desc.ptr);
|
|
return NULL;
|
|
} else {
|
|
#if TEST_ACCESS_VIOLATIONS
|
|
test_access_violation(&desc);
|
|
#endif // TEST_ACCESS_VIOLATIONS
|
|
log_mdesc(info, &desc, "### <libc_pid=%03u, pid=%03u> calloc(%u(%u), %u) -> ",
|
|
malloc_pid, getpid(), n_elements, total_elements, elem_size);
|
|
return mallocdesc_user_ptr(&desc);
|
|
}
|
|
}
|
|
|
|
/* 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("::: <libc_pid=%03u, pid=%03u>: realloc(%p, %u) redir to malloc",
|
|
malloc_pid, getpid(), mem, bytes);
|
|
return qemu_instrumented_malloc(bytes);
|
|
}
|
|
|
|
if (bytes == 0) {
|
|
// This is a "free" condition.
|
|
qemu_info_log("::: <libc_pid=%03u, pid=%03u>: realloc(%p, %u) redir to free and malloc",
|
|
malloc_pid, getpid(), mem, bytes);
|
|
qemu_instrumented_free(mem);
|
|
|
|
// This is what dlrealloc does for a "free" realloc.
|
|
return NULL;
|
|
}
|
|
|
|
// Query emulator for the reallocating block information.
|
|
if (query_qemu_malloc_info(mem, &cur_desc, 2)) {
|
|
// Note that this violation should be already caught in the emulator.
|
|
error_log("<libc_pid=%03u, pid=%03u>: realloc(%p, %u) query_info failed.",
|
|
malloc_pid, getpid(), mem, bytes);
|
|
return NULL;
|
|
}
|
|
|
|
#if TEST_ACCESS_VIOLATIONS
|
|
test_access_violation(&cur_desc);
|
|
#endif // TEST_ACCESS_VIOLATIONS
|
|
|
|
/* Make sure that reallocating pointer value is what we would expect
|
|
* for this memory block. Note that this violation should be already caught
|
|
* in the emulator.*/
|
|
if (mem != mallocdesc_user_ptr(&cur_desc)) {
|
|
log_mdesc(error, &cur_desc, "<libc_pid=%03u, pid=%03u>: realloc(%p, %u) is invalid for ",
|
|
malloc_pid, getpid(), mem, bytes);
|
|
return NULL;
|
|
}
|
|
|
|
/* TODO: We're a bit inefficient here, always allocating new block from
|
|
* the heap. If this realloc shrinks current buffer, we can just do the
|
|
* shrinking "in place", adjusting suffix_size in the allocation descriptor
|
|
* for this block that is stored in the emulator. */
|
|
|
|
// Initialize descriptor for the new block.
|
|
new_desc.prefix_size = DEFAULT_PREFIX_SIZE;
|
|
new_desc.requested_bytes = bytes;
|
|
new_desc.suffix_size = DEFAULT_SUFFIX_SIZE;
|
|
new_desc.ptr = dlmalloc(mallocdesc_alloc_size(&new_desc));
|
|
if (new_desc.ptr == NULL) {
|
|
log_mdesc(error, &cur_desc, "<libc_pid=%03u, pid=%03u>: realloc(%p, %u): dlmalloc(%u) failed on ",
|
|
malloc_pid, getpid(), mem, bytes,
|
|
mallocdesc_alloc_size(&new_desc));
|
|
return NULL;
|
|
}
|
|
ret = mallocdesc_user_ptr(&new_desc);
|
|
|
|
// Copy user data from old block to the new one.
|
|
to_copy = bytes < cur_desc.requested_bytes ? bytes :
|
|
cur_desc.requested_bytes;
|
|
if (to_copy != 0) {
|
|
memcpy(ret, mallocdesc_user_ptr(&cur_desc), to_copy);
|
|
}
|
|
|
|
// Register new block with emulator.
|
|
if (notify_qemu_malloc(&new_desc)) {
|
|
log_mdesc(error, &new_desc, "<libc_pid=%03u, pid=%03u>: realloc(%p, %u) notify_malloc failed -> ",
|
|
malloc_pid, getpid(), mem, bytes);
|
|
log_mdesc(error, &cur_desc, " <- ");
|
|
dlfree(new_desc.ptr);
|
|
return NULL;
|
|
}
|
|
|
|
#if TEST_ACCESS_VIOLATIONS
|
|
test_access_violation(&new_desc);
|
|
#endif // TEST_ACCESS_VIOLATIONS
|
|
|
|
// Free old block.
|
|
if (notify_qemu_free(mem)) {
|
|
log_mdesc(error, &cur_desc, "<libc_pid=%03u, pid=%03u>: realloc(%p, %u): notify_free failed for ",
|
|
malloc_pid, getpid(), mem, bytes);
|
|
/* Since we registered new decriptor with the emulator, we need
|
|
* to unregister it before freeing newly allocated block. */
|
|
notify_qemu_free(mallocdesc_user_ptr(&new_desc));
|
|
dlfree(new_desc.ptr);
|
|
return NULL;
|
|
}
|
|
dlfree(cur_desc.ptr);
|
|
|
|
log_mdesc(info, &new_desc, "=== <libc_pid=%03u, pid=%03u>: realloc(%p, %u) -> ",
|
|
malloc_pid, getpid(), mem, bytes);
|
|
log_mdesc(info, &cur_desc, " <- ");
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* This routine serves as entry point for '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("::: <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);
|
|
}
|
|
|
|
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("<libc_pid=%03u, pid=%03u>: malloc_usable_size(%p) query_info failed.",
|
|
malloc_pid, getpid(), mem);
|
|
return 0;
|
|
}
|
|
|
|
/* Make sure that reallocating pointer value is what we would expect
|
|
* for this memory block. Note that this violation should be already caught
|
|
* in the emulator.*/
|
|
if (mem != mallocdesc_user_ptr(&cur_desc)) {
|
|
log_mdesc(error, &cur_desc, "<libc_pid=%03u, pid=%03u>: malloc_usable_size(%p) is invalid for ",
|
|
malloc_pid, getpid(), mem);
|
|
return 0;
|
|
}
|
|
|
|
/* during instrumentation, we can't really report anything more than requested_bytes */
|
|
return cur_desc.requested_bytes;
|
|
}
|