diff options
Diffstat (limited to 'memcheck/memcheck.c')
-rw-r--r-- | memcheck/memcheck.c | 648 |
1 files changed, 648 insertions, 0 deletions
diff --git a/memcheck/memcheck.c b/memcheck/memcheck.c new file mode 100644 index 0000000..9308c9f --- /dev/null +++ b/memcheck/memcheck.c @@ -0,0 +1,648 @@ +/* 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. + */ + +/* This file should compile iff qemu is built with memory checking + * configuration turned on. */ +#ifndef CONFIG_MEMCHECK +#error CONFIG_MEMCHECK is not defined. +#endif // CONFIG_MEMCHECK + +#include "sys-queue.h" +#include "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(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 = qemu_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) { + qemu_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) { + qemu_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(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; +} |