/* Copyright (C) 2007-2010 The Android Open Source Project
**
** This software is licensed under the terms of the GNU General Public
** License version 2, as published by the Free Software Foundation, and
** may be copied, distributed, and modified under those terms.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
*/
/*
* Contains implementation of memory checking framework in the emulator.
*/
#include "qemu/queue.h"
#include "migration/qemu-file.h"
#include "elff_api.h"
#include "memcheck.h"
#include "memcheck_proc_management.h"
#include "memcheck_util.h"
#include "memcheck_logging.h"
// =============================================================================
// Global data
// =============================================================================
/* Controls what messages from the guest should be printed to emulator's
* stdout. This variable holds a combinations of TRACE_LIBC_XXX flags. */
uint32_t trace_flags = 0;
/* Global flag, indicating whether or not memchecking has been enabled
* for the current emulator session. 1 means that memchecking has been enabled,
* 0 means that memchecking has not been enabled. */
int memcheck_enabled = 0;
/* Global flag, indicating whether or not __ld/__stx_mmu should be instrumented
* for checking for access violations. If read / write access violation check
* has been disabled by -memcheck flags, there is no need to instrument mmu
* routines and waste performance.
* 1 means that instrumenting is required, 0 means that instrumenting is not
* required. */
int memcheck_instrument_mmu = 0;
/* Global flag, indicating whether or not memchecker is collecting call stack.
* 1 - call stack is being collected, 0 means that stack is not being
* collected. */
int memcheck_watch_call_stack = 1;
// =============================================================================
// Static routines.
// =============================================================================
/* Prints invalid pointer access violation information.
* Param:
* proc - Process that caused access violation.
* ptr - Pointer that caused access violation.
* routine - If 1, access violation has occurred in 'free' routine.
* If 2, access violation has occurred in 'realloc' routine.
*/
static void
av_invalid_pointer(ProcDesc* proc, target_ulong ptr, int routine)
{
if (trace_flags & TRACE_CHECK_INVALID_PTR_ENABLED) {
printf("memcheck: Access violation is detected in process %s[pid=%u]:\n"
" INVALID POINTER 0x%08X is used in '%s' operation.\n"
" Allocation descriptor for this pointer has not been found in the\n"
" allocation map for the process. Most likely, this is an attempt\n"
" to %s a pointer that has been freed.\n",
proc->image_path, proc->pid, ptr, routine == 1 ? "free" : "realloc",
routine == 1 ? "free" : "reallocate");
}
}
/* Prints read / write access violation information.
* Param:
* proc - Process that caused access violation.
* desc - Allocation descriptor for the violation.
* addr - Address at which vilation has occurred.
* data_size - Size of data accessed at the 'addr'.
* val - If access violation has occurred at write operation, this parameter
* contains value that's being written to 'addr'. For read violation this
* parameter is not used.
* retaddr - Code address (in TB) where access violation has occurred.
* is_read - If 1, access violation has occurred when memory at 'addr' has been
* read. If 0, access violation has occurred when memory was written.
*/
static void
av_access_violation(ProcDesc* proc,
MallocDescEx* desc,
target_ulong addr,
uint32_t data_size,
uint64_t val,
target_ulong retaddr,
int is_read)
{
target_ulong vaddr;
Elf_AddressInfo elff_info;
ELFF_HANDLE elff_handle = NULL;
desc->malloc_desc.av_count++;
if ((is_read && !(trace_flags & TRACE_CHECK_READ_VIOLATION_ENABLED)) ||
(!is_read && !(trace_flags & TRACE_CHECK_WRITE_VIOLATION_ENABLED))) {
return;
}
/* Convert host address to guest address. */
vaddr = memcheck_tpc_to_gpc(retaddr);
printf("memcheck: Access violation is detected in process %s[pid=%u]:\n",
proc->image_path, proc->pid);
/* Obtain routine, filename / line info for the address. */
const MMRangeDesc* rdesc = procdesc_get_range_desc(proc, vaddr);
if (rdesc != NULL) {
int elff_res;
printf(" In module %s at address 0x%08X\n", rdesc->path, vaddr);
elff_res =
memcheck_get_address_info(vaddr, rdesc, &elff_info, &elff_handle);
if (elff_res == 0) {
printf(" In routine %s in %s/%s:%u\n",
elff_info.routine_name, elff_info.dir_name,
elff_info.file_name, elff_info.line_number);
if (elff_info.inline_stack != NULL) {
const Elf_InlineInfo* inl = elff_info.inline_stack;
int index = 0;
for (; inl[index].routine_name != NULL; index++) {
char align[64];
size_t set_align = 4 + index * 2;
if (set_align >= sizeof(align)) {
set_align = sizeof(align) -1;
}
memset(align, ' ', set_align);
align[set_align] = '\0';
printf("%s", align);
if (inl[index].inlined_in_file == NULL) {
printf("inlined to %s in unknown location\n",
inl[index].routine_name);
} else {
printf("inlined to %s in %s/%s:%u\n",
inl[index].routine_name,
inl[index].inlined_in_file_dir,
inl[index].inlined_in_file,
inl[index].inlined_at_line);
}
}
}
elff_free_pc_address_info(elff_handle, &elff_info);
elff_close(elff_handle);
} else if (elff_res == 1) {
printf(" Unable to obtain routine information. Symbols file is not found.\n");
} else {
printf(" Unable to obtain routine information.\n"
" Symbols file doesn't contain debugging information for address 0x%08X.\n",
mmrangedesc_get_module_offset(rdesc, vaddr));
}
} else {
printf(" In unknown module at address 0x%08X\n", vaddr);
}
printf(" Process attempts to %s %u bytes %s address 0x%08X\n",
is_read ? "read" : "write", data_size,
is_read ? "from" : "to", addr);
printf(" Accessed range belongs to the %s guarding area of allocated block.\n",
addr < (target_ulong)mallocdesc_get_user_ptr(&desc->malloc_desc) ?
"prefix" : "suffix");
printf(" Allocation descriptor for this violation:\n");
memcheck_dump_malloc_desc(desc, 1, 0);
}
/* Validates access to a guest address.
* Param:
* addr - Virtual address in the guest space where memory is accessed.
* data_size - Size of the accessed data.
* proc_ptr - Upon exit from this routine contains pointer to the process
* descriptor for the current process, or NULL, if no such descriptor has
* been found.
* desc_ptr - Upon exit from this routine contains pointer to the allocation
* descriptor matching given address range, or NULL, if allocation
* descriptor for the validated memory range has not been found.
* Return:
* 0 if access to the given guest address range doesn't violate anything, or
* 1 if given guest address range doesn't match any entry in the current
* process allocation descriptors map, or
* -1 if a violation has been detected.
*/
static int
memcheck_common_access_validation(target_ulong addr,
uint32_t data_size,
ProcDesc** proc_ptr,
MallocDescEx** desc_ptr)
{
MallocDescEx* desc;
target_ulong validating_range_end;
target_ulong user_range_end;
ProcDesc* proc = get_current_process();
*proc_ptr = proc;
if (proc == NULL) {
*desc_ptr = NULL;
return 1;
}
desc = procdesc_find_malloc_for_range(proc, addr, data_size);
*desc_ptr = desc;
if (desc == NULL) {
return 1;
}
/* Verify that validating address range doesn't start before the address
* available to the user. */
if (addr < mallocdesc_get_user_ptr(&desc->malloc_desc)) {
// Stepped on the prefix guarding area.
return -1;
}
validating_range_end = addr + data_size;
user_range_end = mallocdesc_get_user_alloc_end(&desc->malloc_desc);
/* Verify that validating address range ends inside the user block.
* We may step on the suffix guarding area because of alignment issue.
* For example, the application code reads last byte in the allocated block
* with something like this:
*
* char last_byte_value = *(char*)last_byte_address;
*
* and this code got compiled into something like this:
*
* mov eax, [last_byte_address];
* mov [last_byte_value], al;
*
* In this case we will catch a read from the suffix area, even though
* there were no errors in the code. So, in order to prevent such "false
* negative" alarms, lets "forgive" this violation.
* There is one bad thing about this "forgivness" though, as it may very
* well be, that in real life some of these "out of bound" bytes will cross
* page boundaries, marching into a page that has not been mapped to the
* process.
*/
if (validating_range_end <= user_range_end) {
// Validating address range is fully contained inside the user block.
return 0;
}
/* Lets see if this AV is caused by an alignment issue.*/
if ((validating_range_end - user_range_end) < data_size) {
/* Could be an alignment. */
return 0;
}
return -1;
}
/* Checks if process has allocation descriptors for pages defined by a buffer.
* Param:
* addr - Starting address of a buffer.
* buf_size - Buffer size.
* Return:
* 1 if process has allocations descriptors for pages defined by a buffer, or
* 0 if pages containing given buffer don't have any memory allocations in
* them.
*/
static inline int
procdesc_contains_allocs(ProcDesc* proc, target_ulong addr, uint32_t buf_size) {
if (proc != NULL) {
// Beginning of the page containing last byte in range.
const target_ulong end_page = (addr + buf_size - 1) & TARGET_PAGE_MASK;
// Adjust beginning of the range to the beginning of the page.
addr &= TARGET_PAGE_MASK;
// Total size of range to check for descriptors.
buf_size = end_page - addr + TARGET_PAGE_SIZE + 1;
return procdesc_find_malloc_for_range(proc, addr, buf_size) ? 1 : 0;
} else {
return 0;
}
}
// =============================================================================
// Memchecker API.
// =============================================================================
void
memcheck_init(const char* tracing_flags)
{
if (*tracing_flags == '0') {
// Memchecker is disabled.
return;
} else if (*tracing_flags == '1') {
// Set default tracing.
trace_flags = TRACE_CHECK_LEAK_ENABLED |
TRACE_CHECK_READ_VIOLATION_ENABLED |
TRACE_CHECK_INVALID_PTR_ENABLED |
TRACE_CHECK_WRITE_VIOLATION_ENABLED;
}
// Parse -memcheck option params, converting them into tracing flags.
while (*tracing_flags) {
switch (*tracing_flags) {
case 'A':
// Enable all emulator's tracing messages.
trace_flags |= TRACE_ALL_ENABLED;
break;
case 'F':
// Enable fork() tracing.
trace_flags |= TRACE_PROC_FORK_ENABLED;
break;
case 'S':
// Enable guest process staring tracing.
trace_flags |= TRACE_PROC_START_ENABLED;
break;
case 'E':
// Enable guest process exiting tracing.
trace_flags |= TRACE_PROC_EXIT_ENABLED;
break;
case 'C':
// Enable clone() tracing.
trace_flags |= TRACE_PROC_CLONE_ENABLED;
break;
case 'N':
// Enable new PID allocation tracing.
trace_flags |= TRACE_PROC_NEW_PID_ENABLED;
break;
case 'B':
// Enable libc.so initialization tracing.
trace_flags |= TRACE_PROC_LIBC_INIT_ENABLED;
break;
case 'L':
// Enable memory leaks tracing.
trace_flags |= TRACE_CHECK_LEAK_ENABLED;
break;
case 'I':
// Enable invalid free / realloc pointer tracing.
trace_flags |= TRACE_CHECK_INVALID_PTR_ENABLED;
break;
case 'R':
// Enable reading violations tracing.
trace_flags |= TRACE_CHECK_READ_VIOLATION_ENABLED;
break;
case 'W':
// Enable writing violations tracing.
trace_flags |= TRACE_CHECK_WRITE_VIOLATION_ENABLED;
break;
case 'M':
// Enable module mapping tracing.
trace_flags |= TRACE_PROC_MMAP_ENABLED;
break;
default:
break;
}
if (trace_flags == TRACE_ALL_ENABLED) {
break;
}
tracing_flags++;
}
/* Lets see if we need to instrument MMU, injecting memory access checking.
* We instrument MMU only if we monitor read, or write memory access. */
if (trace_flags & (TRACE_CHECK_READ_VIOLATION_ENABLED |
TRACE_CHECK_WRITE_VIOLATION_ENABLED)) {
memcheck_instrument_mmu = 1;
} else {
memcheck_instrument_mmu = 0;
}
memcheck_init_proc_management();
/* Lets check env. variables needed for memory checking. */
if (getenv("ANDROID_PROJECT_OUT") == NULL) {
printf("memcheck: Missing ANDROID_PROJECT_OUT environment variable, that is used\n"
"to calculate path to symbol files.\n");
}
// Always set this flag at the very end of the initialization!
memcheck_enabled = 1;
}
void
memcheck_guest_libc_initialized(uint32_t pid)
{
ProcDesc* proc = get_process_from_pid(pid);
if (proc == NULL) {
ME("memcheck: Unable to obtain process for libc_init pid=%u", pid);
return;
}
proc->flags |= PROC_FLAG_LIBC_INITIALIZED;
/* When process initializes its own libc.so instance, it means that now
* it has fresh heap. So, at this point we must get rid of all entries
* (inherited and transition) that were collected in this process'
* allocation descriptors map. */
procdesc_empty_alloc_map(proc);
T(PROC_LIBC_INIT, "memcheck: libc.so has been initialized for %s[pid=%u]\n",
proc->image_path, proc->pid);
}
void
memcheck_guest_alloc(target_ulong guest_address)
{
MallocDescEx desc;
MallocDescEx replaced;
RBTMapResult insert_res;
ProcDesc* proc;
ThreadDesc* thread;
uint32_t indx;
// Copy allocation descriptor from guest to emulator.
memcheck_get_malloc_descriptor(&desc.malloc_desc, guest_address);
desc.flags = 0;
desc.call_stack = NULL;
desc.call_stack_count = 0;
proc = get_process_from_pid(desc.malloc_desc.allocator_pid);
if (proc == NULL) {
ME("memcheck: Unable to obtain process for allocation pid=%u",
desc.malloc_desc.allocator_pid);
memcheck_fail_alloc(guest_address);
return;
}
if (!procdesc_is_executing(proc)) {
desc.flags |= MDESC_FLAG_TRANSITION_ENTRY;
}
/* Copy thread's calling stack to the allocation descriptor. */
thread = get_current_thread();
desc.call_stack_count = thread->call_stack_count;
if (desc.call_stack_count) {
desc.call_stack = g_malloc(desc.call_stack_count * sizeof(target_ulong));
if (desc.call_stack == NULL) {
ME("memcheck: Unable to allocate %u bytes for the calling stack",
desc.call_stack_count * sizeof(target_ulong));
return;
}
}
/* Thread's calling stack is in descending order (i.e. first entry in the
* thread's stack is the most distant routine from the current one). On the
* other hand, we keep calling stack entries in allocation descriptor in
* assending order. */
for (indx = 0; indx < thread->call_stack_count; indx++) {
desc.call_stack[indx] =
thread->call_stack[thread->call_stack_count - 1 - indx].call_address;
}
// Save malloc descriptor in the map.
insert_res = procdesc_add_malloc(proc, &desc, &replaced);
if (insert_res == RBT_MAP_RESULT_ENTRY_INSERTED) {
// Invalidate TLB cache for the allocated block.
if (memcheck_instrument_mmu) {
invalidate_tlb_cache(desc.malloc_desc.ptr,
mallocdesc_get_alloc_end(&desc.malloc_desc));
}
} else if (insert_res == RBT_MAP_RESULT_ENTRY_REPLACED) {
/* We don't expect to have another entry in the map that matches
* inserting entry. This is an error condition for us, indicating
* that we somehow lost track of memory allocations. */
ME("memcheck: Duplicate allocation blocks:");
if (VERBOSE_CHECK(memcheck)) {
printf(" New block:\n");
memcheck_dump_malloc_desc(&desc, 1, 1);
printf(" Replaced block:\n");
memcheck_dump_malloc_desc(&replaced, 1, 1);
}
if (replaced.call_stack != NULL) {
g_free(replaced.call_stack);
}
} else {
ME("memcheck: Unable to insert an entry to the allocation map:");
if (VERBOSE_CHECK(memcheck)) {
memcheck_dump_malloc_desc(&desc, 1, 1);
}
memcheck_fail_alloc(guest_address);
return;
}
}
void
memcheck_guest_free(target_ulong guest_address)
{
MallocFree desc;
MallocDescEx pulled;
int pull_res;
ProcDesc* proc;
// Copy free descriptor from guest to emulator.
memcheck_get_free_descriptor(&desc, guest_address);
proc = get_process_from_pid(desc.free_pid);
if (proc == NULL) {
ME("memcheck: Unable to obtain process for pid=%u on free",
desc.free_pid);
memcheck_fail_free(guest_address);
return;
}
// Pull matching entry from the map.
pull_res = procdesc_pull_malloc(proc, desc.ptr, &pulled);
if (pull_res) {
av_invalid_pointer(proc, desc.ptr, 1);
memcheck_fail_free(guest_address);
return;
}
// Make sure that ptr has expected value
if (desc.ptr != mallocdesc_get_user_ptr(&pulled.malloc_desc)) {
if (trace_flags & TRACE_CHECK_INVALID_PTR_ENABLED) {
printf("memcheck: Access violation is detected in process %s[pid=%u]:\n",
proc->image_path, proc->pid);
printf(" INVALID POINTER 0x%08X is used in 'free' operation.\n"
" This pointer is unexpected for 'free' operation, as allocation\n"
" descriptor found for this pointer in the process' allocation map\n"
" suggests that 0x%08X is the pointer to be used to free this block.\n"
" Allocation descriptor matching the pointer:\n",
desc.ptr,
(uint32_t)mallocdesc_get_user_ptr(&pulled.malloc_desc));
memcheck_dump_malloc_desc(&pulled, 1, 0);
}
}
if (pulled.call_stack != NULL) {
g_free(pulled.call_stack);
}
}
void
memcheck_guest_query_malloc(target_ulong guest_address)
{
MallocDescQuery qdesc;
MallocDescEx* found;
ProcDesc* proc;
// Copy free descriptor from guest to emulator.
memcheck_get_query_descriptor(&qdesc, guest_address);
proc = get_process_from_pid(qdesc.query_pid);
if (proc == NULL) {
ME("memcheck: Unable to obtain process for pid=%u on query_%s",
qdesc.query_pid, qdesc.routine == 1 ? "free" : "realloc");
memcheck_fail_query(guest_address);
return;
}
// Find allocation entry for the given address.
found = procdesc_find_malloc(proc, qdesc.ptr);
if (found == NULL) {
av_invalid_pointer(proc, qdesc.ptr, qdesc.routine);
memcheck_fail_query(guest_address);
return;
}
// Copy allocation descriptor back to the guest's space.
memcheck_set_malloc_descriptor(qdesc.desc, &found->malloc_desc);
}
void
memcheck_guest_print_str(target_ulong str) {
char str_copy[4096];
memcheck_get_guest_string(str_copy, str, sizeof(str_copy));
printf("%s", str_copy);
}
/* Validates read operations, detected in __ldx_mmu routine.
* This routine is called from __ldx_mmu wrapper implemented in
* softmmu_template.h on condition that loading is occurring from user memory.
* Param:
* addr - Virtual address in the guest space where memory is read.
* data_size - Size of the read.
* retaddr - Code address (in TB) that accesses memory.
* Return:
* 1 if TLB record for the accessed page should be invalidated in order to
* ensure that subsequent attempts to access data in this page will cause
* __ld/stx_mmu to be used. If memchecker is no longer interested in monitoring
* access to this page, this routine returns 0.
*/
int
memcheck_validate_ld(target_ulong addr,
uint32_t data_size,
target_ulong retaddr)
{
ProcDesc* proc;
MallocDescEx* desc;
int res = memcheck_common_access_validation(addr, data_size, &proc, &desc);
if (res == -1) {
av_access_violation(proc, desc, addr, data_size, 0, retaddr, 1);
return 1;
}
/* Even though descriptor for the given address range has not been found,
* we need to make sure that pages containing the given address range
* don't contain other descriptors. */
return res ? procdesc_contains_allocs(proc, addr, data_size) : 0;
}
/* Validates write operations, detected in __stx_mmu routine.
* This routine is called from __stx_mmu wrapper implemented in
* softmmu_template.h on condition that storing is occurring from user memory.
* Param:
* addr - Virtual address in the guest space where memory is written.
* data_size - Size of the write.
* value - Value to be written. Note that we typecast all values to 64 bits,
* since this will fit all data sizes.
* retaddr - Code address (in TB) that accesses memory.
* Return:
* 1 if TLB record for the accessed page should be invalidated in order to
* ensure that subsequent attempts to access data in this page will cause
* __ld/stx_mmu to be used. If memchecker is no longer interested in monitoring
* access to this page, this routine returns 0.
*/
int
memcheck_validate_st(target_ulong addr,
uint32_t data_size,
uint64_t value,
target_ulong retaddr)
{
MallocDescEx* desc;
ProcDesc* proc;
int res = memcheck_common_access_validation(addr, data_size, &proc, &desc);
if (res == -1) {
av_access_violation(proc, desc, addr, data_size, value, retaddr, 0);
return 1;
}
/* Even though descriptor for the given address range has not been found,
* we need to make sure that pages containing the given address range
* don't contain other descriptors. */
return res ? procdesc_contains_allocs(proc, addr, data_size) : 0;
}
/* Checks if given address range in the context of the current process is under
* surveillance.
* Param:
* addr - Starting address of a range.
* size - Range size.
* Return:
* boolean: 1 if address range contains memory that require access violation
* detection, or 0 if given address range is in no interest to the memchecker.
*/
int
memcheck_is_checked(target_ulong addr, uint32_t size) {
return procdesc_contains_allocs(get_current_process(), addr, size) ? 1 : 0;
}