From 5389aa19033153c09556d1362a8b8a56abccb8f5 Mon Sep 17 00:00:00 2001 From: Vladimir Chtchetkine Date: Tue, 16 Feb 2010 10:38:35 -0800 Subject: Merge memory checking from sandbox Change-id: Ibce845d0 --- memcheck/memcheck.c | 648 +++++++++++++++++++++++++++++ memcheck/memcheck.h | 194 +++++++++ memcheck/memcheck_api.h | 107 +++++ memcheck/memcheck_common.h | 484 ++++++++++++++++++++++ memcheck/memcheck_logging.h | 94 +++++ memcheck/memcheck_malloc_map.c | 289 +++++++++++++ memcheck/memcheck_malloc_map.h | 150 +++++++ memcheck/memcheck_mmrange_map.c | 236 +++++++++++ memcheck/memcheck_mmrange_map.h | 127 ++++++ memcheck/memcheck_proc_management.c | 799 ++++++++++++++++++++++++++++++++++++ memcheck/memcheck_proc_management.h | 327 +++++++++++++++ memcheck/memcheck_util.c | 270 ++++++++++++ memcheck/memcheck_util.h | 245 +++++++++++ 13 files changed, 3970 insertions(+) create mode 100644 memcheck/memcheck.c create mode 100644 memcheck/memcheck.h create mode 100644 memcheck/memcheck_api.h create mode 100644 memcheck/memcheck_common.h create mode 100644 memcheck/memcheck_logging.h create mode 100644 memcheck/memcheck_malloc_map.c create mode 100644 memcheck/memcheck_malloc_map.h create mode 100644 memcheck/memcheck_mmrange_map.c create mode 100644 memcheck/memcheck_mmrange_map.h create mode 100644 memcheck/memcheck_proc_management.c create mode 100644 memcheck/memcheck_proc_management.h create mode 100644 memcheck/memcheck_util.c create mode 100644 memcheck/memcheck_util.h (limited to 'memcheck') 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; +} diff --git a/memcheck/memcheck.h b/memcheck/memcheck.h new file mode 100644 index 0000000..a9a6422 --- /dev/null +++ b/memcheck/memcheck.h @@ -0,0 +1,194 @@ +/* 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 declarations of types, constants, and routines used by memory + * checking framework. + */ + +#ifndef QEMU_MEMCHECK_MEMCHECK_H +#define QEMU_MEMCHECK_MEMCHECK_H + +/* 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 "memcheck_common.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Initializes memory access checking framework. + * This routine is called from emulator's main routine on condition, + * that emulator has been started with -memcheck option. + * Param: + * tracing_flags - Parameters set for the -memcheck option. These parameters + * contain abbreviation for memchecking tracing messages that should be enabled + * for the emulator and guest systems. + */ +void memcheck_init(const char* tracing_flags); + +// ============================================================================= +// Handlers for memory allocation events, generated by the guest system. +// ============================================================================= + +/* Libc.so has been initialized by a process in guest's system. + * This routine is called in response to TRACE_DEV_REG_LIBC_INIT event that is + * fired up by the guest system on /dev/qemu_trace mapped page. + * Param: + * pid - ID of the process in context of which libc.so has been initialized. + */ +void memcheck_guest_libc_initialized(uint32_t pid); + +/* Guest system has allocated memory from heap. + * This routine is called in response to TRACE_DEV_REG_MALLOC event that is + * fired up by the guest system on /dev/qemu_trace mapped page. + * Param: + * guest_address - Virtual address of allocation descriptor (MallocDesc) that + * contains information about allocated memory block. Note that this + * descriptor is located in the guests's user memory. Note also that + * emulator reports failure back to the guest by zeroing out libc_pid field + * of the structure, addressed by this parameter. + */ +void memcheck_guest_alloc(target_ulong guest_address); + +/* Guest system is freeing memory to heap. + * This routine is called in response to TRACE_DEV_REG_FREE_PTR event, + * fired up by the guest system on /dev/qemu_trace mapped page. + * Param: + * guest_address - Virtual address of free descriptor (MallocFree) that + * contains information about memory block that's being freed. Note that + * this descriptor is located in the guests's user memory. Note also that + * emulator reports failure back to the guest by zeroing out libc_pid field + * of the structure, addressed by this parameter. + */ +void memcheck_guest_free(target_ulong guest_address); + +/* Guest system has queried information about an address in its virtual memory. + * This routine is called in response to TRACE_DEV_REG_QUERY_MALLOC event, + * fired up by the guest system on /dev/qemu_trace mapped page. + * Param: + * guest_address - Virtual address in the guest's space of the MallocDescQuery + * structure, that describes the query and receives the response. Note + * that emulator reports failure back to the guest by zeroing out libc_pid + * field of the structure, addressed by this parameter. + */ +void memcheck_guest_query_malloc(target_ulong guest_address); + +/* Prints a string to emulator's stdout. + * This routine is called in response to TRACE_DEV_REG_PRINT_USER_STR event, + * fired up by the guest system on /dev/qemu_trace mapped page. + * Param: + * str - Virtual address in the guest's space of the string to print. + */ +void memcheck_guest_print_str(target_ulong str); + +// ============================================================================= +// Handlers for events, generated by the kernel. +// ============================================================================= + +/* Handles PID initialization event. + * This routine is called in response to TRACE_DEV_REG_INIT_PID event, which + * indicates that new process has been initialized (but not yet executed). + * Param: + * pid - ID of the process that is being initialized. This value will also be + * used as main thread ID for the intializing process. + */ +void memcheck_init_pid(uint32_t pid); + +/* Handles thread switch event. + * This routine is called in response to TRACE_DEV_REG_SWITCH event, which + * indicates that thread switch occurred in the guest system. + * Param: + * tid - ID of the thread that becomes active. + */ +void memcheck_switch(uint32_t tid); + +/* Handles process forking / new process creation event. + * This routine is called in response to TRACE_DEV_REG_FORK event, which + * indicates that new process has been forked / created. It's assumed, that + * process that is forking new process is the current process. + * Param: + * tgid - TODO: Clarify that! + * new_pid - Process ID that's been assigned to the forked process. + */ +void memcheck_fork(uint32_t tgid, uint32_t new_pid); + +/* Handles new thread creation event. + * This routine is called in response to TRACE_DEV_REG_CLONE event, which + * indicates that new thread has been created in context of the current process. + * Param: + * tgid - TODO: Clarify that! + * new_tid - Thread ID that's been assigned to the new thread. + */ +void memcheck_clone(uint32_t tgid, uint32_t new_tid); + +/* Sets process command line. + * This routine is called in response to TRACE_DEV_REG_CMDLINE event, which + * is used to grab first command line argument, and use it is image path to + * the current process. + * Param: + * cmg_arg - Command line arguments. + * cmdlen - Length of the command line arguments string. + */ +void memcheck_set_cmd_line(const char* cmd_arg, unsigned cmdlen); + +/* Handles thread / process exiting event. + * This routine is called in response to TRACE_DEV_REG_EXIT event, which + * indicates that current thread is exiting. We consider that process is + * exiting when last thread for that process is exiting. + * Param: + * exit_code - Thread exit code. + */ +void memcheck_exit(uint32_t exit_code); + +/* Handles memory mapping of a module in guest address space. + * This routine is called in response to TRACE_DEV_REG_EXECVE_VMSTART, + * TRACE_DEV_REG_EXECVE_VMEND, TRACE_DEV_REG_EXECVE_OFFSET, and + * TRACE_DEV_REG_MMAP_EXEPATH events, which indicate that a module has been + * loaded and mapped on the guest system. + * Param: + * vstart - Guest address where mapping starts. + * vend - Guest address where mapping ends. + * exec_offset - Exec offset inside module mapping. + * path - Path to the module that has been mapped. + */ +void memcheck_mmap_exepath(target_ulong vstart, + target_ulong vend, + target_ulong exec_offset, + const char* path); + +/* Handles memory unmapping of a module in guest address space. + * This routine is called in response to TRACE_DEV_REG_UNMAP_START, and + * TRACE_DEV_REG_UNMAP_END events, which indicate that a module has been + * unmapped on the guest system. + * Param: + * vstart - Guest address where unmapping starts. + * vend - Guest address where unmapping ends. + */ +void memcheck_unmap(target_ulong vstart, target_ulong vend); + +/* Global flag, indicating whether or not memchecking has been enabled + * for the current emulator session. If set to zero, indicates that memchecking + * is not enabled. Value other than zero indicates that memchecking is enabled + * for the current emulator session. + */ +extern int memcheck_enabled; + +#ifdef __cplusplus +}; /* end of extern "C" */ +#endif + +#endif // QEMU_MEMCHECK_MEMCHECK_H diff --git a/memcheck/memcheck_api.h b/memcheck/memcheck_api.h new file mode 100644 index 0000000..1961465 --- /dev/null +++ b/memcheck/memcheck_api.h @@ -0,0 +1,107 @@ +/* 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 declarations of memchecker external variables and routines, used by + * other qemu components. + */ + +#ifndef QEMU_MEMCHECK_MEMCHECK_API_H +#define QEMU_MEMCHECK_MEMCHECK_API_H + +/* 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 + +/* 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. The variable + * is declared in memchec/memcheck.c */ +extern int memcheck_enabled; + +/* Flags wether or not mmu instrumentation is enabled by memchecker. + * 1 - enabled, 0 - is not enabled. */ +extern int memcheck_instrument_mmu; + +/* 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. The variable is declared in memchec/memcheck.c */ +extern int memcheck_watch_call_stack; + +/* Array of (tb_pc, guest_pc) pairs, big enough for all translations. This + * array is used to obtain guest PC address from a translated PC address. + * tcg_gen_code_common will fill it up when memchecker is enabled. The array is + * declared in ./translate_all.c */ +extern target_ulong* gen_opc_tpc2gpc_ptr; + +/* Number of (tb_pc, guest_pc) pairs stored in gen_opc_tpc2gpc array. + * The variable is declared in ./translate_all.c */ +extern unsigned int gen_opc_tpc2gpc_pairs; + +/* Checks if given address range in the context of the current process is + * under surveillance by memchecker. + * Param: + * addr - Starting address of a range. + * size - Range size. + * Return: + * boolean: 1 if address range contains memory that requires 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); + +/* Validates __ldx_mmu operations. + * 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 Address should be invalidated in TLB cache, in order to ensure that + * subsequent attempts to read from that page will launch __ld/__stx_mmu. + * If this routine returns zero, no page invalidation is requried. + */ +int memcheck_validate_ld(target_ulong addr, + uint32_t data_size, + target_ulong retaddr); + +/* Validates __stx_mmu operations. + * 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 Address should be invalidated in TLB cache, in order to ensure that + * subsequent attempts to read from that page will launch __ld/__stx_mmu. + * If this routine returns zero, no page invalidation is requried. + */ +int memcheck_validate_st(target_ulong addr, + uint32_t data_size, + uint64_t value, + target_ulong retaddr); + +/* Memchecker's handler for on_call callback. + * Param: + * pc - Guest address where call has been made. + * ret - Guest address where called routine will return. + */ +void memcheck_on_call(target_ulong pc, target_ulong ret); + +/* Memchecker's handler for on_ret callback. + * Param: + * pc - Guest address where routine has returned. + */ +void memcheck_on_ret(target_ulong pc); + +#endif // QEMU_MEMCHECK_MEMCHECK_API_H diff --git a/memcheck/memcheck_common.h b/memcheck/memcheck_common.h new file mode 100644 index 0000000..668b78c --- /dev/null +++ b/memcheck/memcheck_common.h @@ -0,0 +1,484 @@ +/* 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 declarations of structures, routines, etc. that are commonly used + * in memechecker framework. + */ + +#ifndef QEMU_MEMCHECK_MEMCHECK_COMMON_H +#define QEMU_MEMCHECK_MEMCHECK_COMMON_H + +/* 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 "qemu-common.h" +#include "cpu.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// ============================================================================= +// Events generated by the guest system. +// ============================================================================= + +/* 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 being allocated. + * Event's value parameter points to MallocDesc instance in the guest's address + * space 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 failure emulator will zero that filed 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 instance in the + * guest's address space 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 failure emulator will zero + * that filed before completing this event. + */ +#define TRACE_DEV_REG_FREE_PTR 1538 + +/* Queries the emulator about memory block information. + * Event's value parameter points to MallocDescQuery descriptor instance in the + * guest's address space 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 failure emulator will zero that filed 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 zero-terminated string to be printed. Note + * that this string is located in the guest's address space. + */ +#define TRACE_DEV_REG_PRINT_USER_STR 1540 + +// ============================================================================= +// 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) is also declared + * in the libc's sources. So, every time a change is made to any of these + * two declaration, another one must be also updated accordingly. */ +typedef struct MallocDesc { + /* Poniter 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. + */ + target_ulong ptr; + + /* Nuber 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; +/* Helpers for addressing field in MallocDesc structure, using which emulator + * reports an error back to the guest. + */ +#define ALLOC_RES_OFFSET ((uint32_t)&(((MallocDesc*)0)->libc_pid)) +#define ALLOC_RES_ADDRESS(p) (p + ALLOC_RES_OFFSET) + +/* 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, memalign, or realloc. Since + * we don't keep this information directlry 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) is also declared in the libc's sources. 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. + */ + target_ulong 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 in guest's virtual space of memory allocation descriptor for the + * queried pointer. Descriptor, addressed by this field is initialized by + * the emulator in response to the query. + */ + target_ulong desc; +} MallocDescQuery; +/* Helpers for addressing field in MallocDescQuery structure using which + * emulator reports an error back to the guest. + */ +#define QUERY_RES_OFFSET ((uint32_t)&(((MallocDescQuery*)0)->libc_pid)) +#define QUERY_RES_ADDRESS(p) (p + QUERY_RES_OFFSET) + +/* 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) is also declared in the libc's + * sources. 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. */ + uint32_t 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; +/* Helpers for addressing field in MallocFree structure, using which emulator + * reports an error back to the guest. + */ +#define FREE_RES_OFFSET ((uint32_t)&(((MallocFree*)0)->libc_pid)) +#define FREE_RES_ADDRESS(p) (p + FREE_RES_OFFSET) + +/* Extends MallocDesc structure with additional information, used by memchecker. + */ +typedef struct MallocDescEx { + /* Allocation descriptor this structure extends. */ + MallocDesc malloc_desc; + + /* Call stack that lead to memory allocation. The array is arranged in + * accending order, where entry at index 0 corresponds to the routine + * that allocated memory. */ + target_ulong* call_stack; + + /* Number of entries in call_stack array. */ + uint32_t call_stack_count; + + /* Set of misc. flags. See MDESC_FLAG_XXX bellow. */ + uint32_t flags; +} MallocDescEx; + +/* Indicates that memory has been allocated before process started execution. + * After a process has been forked, but before it actually starts executing, + * allocations can be made in context of that process PID. This flag marks such + * allocations in the process' allocation descriptors map. + */ +#define MDESC_FLAG_TRANSITION_ENTRY 0x00000001 + +/* Indicates that memory block has been inherited from the parent process. + * When a process is forked from its parent process, the forked process inherits + * a copy of the parent process' heap. Thus, all allocations that were recorded + * for the parent process must be also recorded for the forked process. This + * flag marks entries in the forked process' allocation descriptors map that + * were copied over from the parent process' allocation descriptors map. + */ +#define MDESC_FLAG_INHERITED_ON_FORK 0x00000002 + +/* Describes a memory mapping of an execution module in the guest system. */ +typedef struct MMRangeDesc { + /* Starting address of mmapping of a module in the guest's address space. */ + target_ulong map_start; + + /* Ending address of mmapping of a module in the guest's address space. */ + target_ulong map_end; + + /* Mmapping's execution offset. */ + target_ulong exec_offset; + + /* Image path of the module that has been mapped with this mmapping. */ + char* path; +} MMRangeDesc; + +/* Enumerates returned values for insert routines implemeted for red-black + * tree maps. + */ +typedef enum { + /* New entry has been inserted into the map. */ + RBT_MAP_RESULT_ENTRY_INSERTED = 0, + + /* An entry, matching the new one already exists in the map. */ + RBT_MAP_RESULT_ENTRY_ALREADY_EXISTS, + + /* An existing entry, matching the new one has been replaced + * with the new entry. + */ + RBT_MAP_RESULT_ENTRY_REPLACED, + + /* An error has occurred when inserting entry into the map. */ + RBT_MAP_RESULT_ERROR = -1, +} RBTMapResult; + +/* Encapsulates an array of guest addresses, sorted in accending order. */ +typedef struct AddrArray { + /* Array of addresses. */ + target_ulong* addr; + + /* Number of elements in the array. */ + int num; +} AddrArray; + +// ============================================================================= +// Inlines +// ============================================================================= + +/* 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 target_ulong +mallocdesc_get_user_ptr(const MallocDesc* desc) +{ + return desc->ptr + desc->prefix_size; +} + +/* Gets total size of the allocated block for the given descriptor. + * Param: + * desc - Descriptor for the memory block, allocated in malloc handler. + * Return: + * Total size of memory block allocated in malloc handler. + */ +static inline uint32_t +mallocdesc_get_alloc_size(const MallocDesc* desc) +{ + return desc->prefix_size + desc->requested_bytes + desc->suffix_size; +} + +/* Gets 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 the allocated block (next byte past the block). + */ +static inline target_ulong +mallocdesc_get_alloc_end(const MallocDesc* desc) +{ + return desc->ptr + mallocdesc_get_alloc_size(desc); +} + +/* Gets the end of the allocated block available to the user for the given + * descriptor. + * Param: + * desc - Descriptor for the memory block, allocated in malloc handler. + * Return: + * Pointer to the end of the allocated block available to the user (next byte + * past the block - suffix guarding area). + */ +static inline target_ulong +mallocdesc_get_user_alloc_end(const MallocDesc* desc) +{ + return mallocdesc_get_user_ptr(desc) + desc->requested_bytes; +} + +/* Checks if allocation has been made before process started execution. + * Param: + * desc - Allocation descriptor to check. + * Return: + * boolean: 1 if allocation has been made before process started execution, + * or 0 if allocation has been made after process started execution. + */ +static inline int +mallocdescex_is_transition_entry(const MallocDescEx* desc) +{ + return (desc->flags & MDESC_FLAG_TRANSITION_ENTRY) != 0; +} + +/* Checks if allocation block has been inherited on fork. + * Param: + * desc - Allocation descriptor to check. + * Return: + * boolean: 1 if allocation has been inherited on fork, or 0 if allocation + * has been made by this process.. + */ +static inline int +mallocdescex_is_inherited_on_fork(const MallocDescEx* desc) +{ + return (desc->flags & MDESC_FLAG_INHERITED_ON_FORK) != 0; +} + +/* Gets offset for the given address inside a mapped module. + * Param: + * address - Address to get offset for. + * Return: + * Offset of the given address inside a mapped module, represented with the + * given mmaping range descriptor. + */ +static inline target_ulong +mmrangedesc_get_module_offset(const MMRangeDesc* rdesc, target_ulong address) +{ + return address - rdesc->map_start + rdesc->exec_offset; +} + +/* Checks if given address is contained in the given address array. + * Return: + * boolean: 1 if address is contained in the array, or zero if it's not. + */ +static inline int +addrarray_check(const AddrArray* addr_array, target_ulong addr) +{ + if (addr_array->num != 0) { + int m_min = 0; + int m_max = addr_array->num - 1; + + /* May be odd for THUMB mode. */ + addr &= ~1; + /* Since array is sorted we can do binary search here. */ + while (m_min <= m_max) { + const int m = (m_min + m_max) >> 1; + const target_ulong saved = addr_array->addr[m]; + if (addr == saved) { + return 1; + } + if (addr < saved) { + m_max = m - 1; + } else { + m_min = m + 1; + } + } + } + return 0; +} + +/* Adds an address to the address array. + * Return: + * 1 - Address has been added to the array. + * -1 - Address already exists in the array. + * 0 - Unable to expand the array. + */ +static inline int +addrarray_add(AddrArray* addr_array, target_ulong addr) +{ + target_ulong* new_arr; + int m_min; + int m_max; + + /* May be odd for THUMB mode. */ + addr &= ~1; + if (addr_array->num == 0) { + /* First element. */ + addr_array->addr = qemu_malloc(sizeof(target_ulong)); + assert(addr_array->addr != NULL); + if (addr_array->addr == NULL) { + return 0; + } + *addr_array->addr = addr; + addr_array->num++; + return 1; + } + + /* Using binary search find the place where to insert new address. */ + m_min = 0; + m_max = addr_array->num - 1; + while (m_min <= m_max) { + const int m = (m_min + m_max) >> 1; + const target_ulong saved = addr_array->addr[m]; + if (addr == saved) { + return -1; + } + if (addr < saved) { + m_max = m - 1; + } else { + m_min = m + 1; + } + } + if (m_max < 0) { + m_max = 0; + } + /* Expand the array. */ + new_arr = qemu_malloc(sizeof(target_ulong) * (addr_array->num + 1)); + assert(new_arr != NULL); + if (new_arr == NULL) { + return 0; + } + /* Copy preceding elements to the new array. */ + if (m_max != 0) { + memcpy(new_arr, addr_array->addr, m_max * sizeof(target_ulong)); + } + if (addr > addr_array->addr[m_max]) { + new_arr[m_max] = addr_array->addr[m_max]; + m_max++; + } + /* Insert new address. */ + new_arr[m_max] = addr; + /* Copy remaining elements to the new array. */ + if (m_max < addr_array->num) { + memcpy(new_arr + m_max + 1, addr_array->addr + m_max, + (addr_array->num - m_max) * sizeof(target_ulong)); + } + /* Swap arrays. */ + qemu_free(addr_array->addr); + addr_array->addr = new_arr; + addr_array->num++; + return 1; +} + +#ifdef __cplusplus +}; /* end of extern "C" */ +#endif + +#endif // QEMU_MEMCHECK_MEMCHECK_COMMON_H diff --git a/memcheck/memcheck_logging.h b/memcheck/memcheck_logging.h new file mode 100644 index 0000000..c2ae6e9 --- /dev/null +++ b/memcheck/memcheck_logging.h @@ -0,0 +1,94 @@ +/* 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 declarations of logging macros used in memchecker framework. + */ + +#ifndef QEMU_MEMCHECK_MEMCHECK_LOGGING_H +#define QEMU_MEMCHECK_MEMCHECK_LOGGING_H + +/* 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 "qemu-common.h" +#include "android/utils/debug.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Prints debug message under the 'memcheck' tag. */ +#define MD(...) VERBOSE_PRINT(memcheck, __VA_ARGS__) + +/* Prints an error message under the 'memcheck' tag. */ +#define ME(...) \ + do { if (VERBOSE_CHECK(memcheck)) derror(__VA_ARGS__); } while (0) + +// ============================================================================= +// Tracing flags (see trace_flags declared in memcheck.c), and macros +// ============================================================================= + +/* Enables fork() tracing. */ +#define TRACE_PROC_FORK_ENABLED 0x00000001 +/* Enables clone() tracing. */ +#define TRACE_PROC_CLONE_ENABLED 0x00000002 +/* Enables new PID allocation tracing. */ +#define TRACE_PROC_NEW_PID_ENABLED 0x00000004 +/* Enables guest process starting tracing. */ +#define TRACE_PROC_START_ENABLED 0x00000008 +/* Enables guest process exiting tracing. */ +#define TRACE_PROC_EXIT_ENABLED 0x00000010 +/* Enables libc.so initialization tracing. */ +#define TRACE_PROC_LIBC_INIT_ENABLED 0x00000020 +/* Enables leaking tracing. */ +#define TRACE_CHECK_LEAK_ENABLED 0x00000040 +/* Enables invalid pointer access tracing. */ +#define TRACE_CHECK_INVALID_PTR_ENABLED 0x00000080 +/* Enables reading violations tracing. */ +#define TRACE_CHECK_READ_VIOLATION_ENABLED 0x00000100 +/* Enables writing violations tracing. */ +#define TRACE_CHECK_WRITE_VIOLATION_ENABLED 0x00000200 +/* Enables module mapping tracing. */ +#define TRACE_PROC_MMAP_ENABLED 0x00000400 +/* All tracing flags combined. */ +#define TRACE_ALL_ENABLED (TRACE_PROC_FORK_ENABLED | \ + TRACE_PROC_CLONE_ENABLED | \ + TRACE_PROC_NEW_PID_ENABLED | \ + TRACE_PROC_START_ENABLED | \ + TRACE_PROC_LIBC_INIT_ENABLED | \ + TRACE_PROC_EXIT_ENABLED | \ + TRACE_CHECK_INVALID_PTR_ENABLED | \ + TRACE_CHECK_READ_VIOLATION_ENABLED | \ + TRACE_CHECK_WRITE_VIOLATION_ENABLED | \ + TRACE_PROC_MMAP_ENABLED | \ + TRACE_CHECK_LEAK_ENABLED) + +/* Prints a trace to the stdout. */ +#define T(level, ...) \ + do { \ + if (trace_flags & TRACE_##level##_ENABLED) { \ + printf(__VA_ARGS__); \ + } \ + } while (0) + +/* Set of tracing flags (declared in memchek.c). */ +extern uint32_t trace_flags; + +#ifdef __cplusplus +}; /* end of extern "C" */ +#endif + +#endif // QEMU_MEMCHECK_MEMCHECK_LOGGING_H diff --git a/memcheck/memcheck_malloc_map.c b/memcheck/memcheck_malloc_map.c new file mode 100644 index 0000000..07ae889 --- /dev/null +++ b/memcheck/memcheck_malloc_map.c @@ -0,0 +1,289 @@ +/* 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 routines that implement a red-black tree of + * memory blocks allocated by the guest system. + */ + +/* 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 "memcheck_malloc_map.h" +#include "memcheck_util.h" +#include "memcheck_logging.h" + +/* Global flag, indicating whether or not __ld/__stx_mmu should be instrumented + * for checking for access violations. If read / write access violation check. + * Defined in memcheck.c + */ +extern int memcheck_instrument_mmu; + +/* Allocation descriptor stored in the map. */ +typedef struct AllocMapEntry { + /* R-B tree entry. */ + RB_ENTRY(AllocMapEntry) rb_entry; + + /* Allocation descriptor for this entry. */ + MallocDescEx desc; +} AllocMapEntry; + +// ============================================================================= +// Inlines +// ============================================================================= + +/* Gets address of the beginning of an allocation block for the given entry in + * the map. + * Param: + * adesc - Entry in the allocation descriptors map. + * Return: + * Address of the beginning of an allocation block for the given entry in the + * map. + */ +static inline target_ulong +allocmapentry_alloc_begins(const AllocMapEntry* adesc) +{ + return adesc->desc.malloc_desc.ptr; +} + +/* Gets address of the end of an allocation block for the given entry in + * the map. + * Param: + * adesc - Entry in the allocation descriptors map. + * Return: + * Address of the end of an allocation block for the given entry in the map. + */ +static inline target_ulong +allocmapentry_alloc_ends(const AllocMapEntry* adesc) +{ + return mallocdesc_get_alloc_end(&adesc->desc.malloc_desc); +} + +// ============================================================================= +// R-B Tree implementation +// ============================================================================= + +/* Compare routine for the allocation descriptors map. + * Param: + * d1 - First map entry to compare. + * d2 - Second map entry to compare. + * Return: + * 0 - Descriptors are equal. Note that descriptors are considered to be + * equal iff memory blocks they describe intersect in any part. + * 1 - d1 is greater than d2 + * -1 - d1 is less than d2. + */ +static inline int +cmp_rb(AllocMapEntry* d1, AllocMapEntry* d2) +{ + const target_ulong start1 = allocmapentry_alloc_begins(d1); + const target_ulong start2 = allocmapentry_alloc_begins(d2); + + if (start1 < start2) { + return (allocmapentry_alloc_ends(d1) - 1) < start2 ? -1 : 0; + } + return (allocmapentry_alloc_ends(d2) - 1) < start1 ? 1 : 0; +} + +/* Expands RB macros here. */ +RB_GENERATE(AllocMap, AllocMapEntry, rb_entry, cmp_rb); + +// ============================================================================= +// Static routines +// ============================================================================= + +/* Inserts new (or replaces existing) entry into allocation descriptors map. + * See comments on allocmap_insert routine in the header file for details + * about this routine. + */ +static RBTMapResult +allocmap_insert_desc(AllocMap* map, + AllocMapEntry* adesc, + MallocDescEx* replaced) +{ + AllocMapEntry* existing = AllocMap_RB_INSERT(map, adesc); + if (existing == NULL) { + return RBT_MAP_RESULT_ENTRY_INSERTED; + } + + // Matching entry exists. Lets see if we need to replace it. + if (replaced == NULL) { + return RBT_MAP_RESULT_ENTRY_ALREADY_EXISTS; + } + + /* Copy existing entry to the provided buffer and replace it + * with the new one. */ + memcpy(replaced, &existing->desc, sizeof(MallocDescEx)); + AllocMap_RB_REMOVE(map, existing); + qemu_free(existing); + AllocMap_RB_INSERT(map, adesc); + return RBT_MAP_RESULT_ENTRY_REPLACED; +} + +/* Finds an entry in the allocation descriptors map that matches the given + * address range. + * Param: + * map - Allocation descriptors map where to search for an entry. + * address - Virtual address in the guest's user space to find matching + * entry for. + * Return: + * Address of an allocation descriptors map entry that matches the given + * address, or NULL if no such entry has been found. + */ +static inline AllocMapEntry* +allocmap_find_entry(const AllocMap* map, + target_ulong address, + uint32_t block_size) +{ + AllocMapEntry adesc; + adesc.desc.malloc_desc.ptr = address; + adesc.desc.malloc_desc.requested_bytes = block_size; + adesc.desc.malloc_desc.prefix_size = 0; + adesc.desc.malloc_desc.suffix_size = 0; + return AllocMap_RB_FIND((AllocMap*)map, &adesc); +} + +// ============================================================================= +// Map API +// ============================================================================= + +void +allocmap_init(AllocMap* map) +{ + RB_INIT(map); +} + +RBTMapResult +allocmap_insert(AllocMap* map, const MallocDescEx* desc, MallocDescEx* replaced) +{ + RBTMapResult ret; + + // Allocate and initialize new map entry. + AllocMapEntry* adesc = qemu_malloc(sizeof(AllocMapEntry)); + if (adesc == NULL) { + ME("memcheck: Unable to allocate new AllocMapEntry on insert."); + return RBT_MAP_RESULT_ERROR; + } + memcpy(&adesc->desc, desc, sizeof(MallocDescEx)); + + // Insert new entry into the map. + ret = allocmap_insert_desc(map, adesc, replaced); + if (ret == RBT_MAP_RESULT_ENTRY_ALREADY_EXISTS || + ret == RBT_MAP_RESULT_ERROR) { + /* Another descriptor already exists for this block, or an error + * occurred. We have to tree new descriptor, as it wasn't inserted. */ + qemu_free(adesc); + } + return ret; +} + +MallocDescEx* +allocmap_find(const AllocMap* map, target_ulong address, uint32_t block_size) +{ + AllocMapEntry* adesc = allocmap_find_entry(map, address, block_size); + return adesc != NULL ? &adesc->desc : NULL; +} + +int +allocmap_pull(AllocMap* map, target_ulong address, MallocDescEx* pulled) +{ + AllocMapEntry* adesc = allocmap_find_entry(map, address, 1); + if (adesc != NULL) { + memcpy(pulled, &adesc->desc, sizeof(MallocDescEx)); + AllocMap_RB_REMOVE(map, adesc); + qemu_free(adesc); + return 0; + } else { + return -1; + } +} + +int +allocmap_pull_first(AllocMap* map, MallocDescEx* pulled) +{ + AllocMapEntry* first = RB_MIN(AllocMap, map); + if (first != NULL) { + memcpy(pulled, &first->desc, sizeof(MallocDescEx)); + AllocMap_RB_REMOVE(map, first); + qemu_free(first); + return 0; + } else { + return -1; + } +} + +int +allocmap_copy(AllocMap* to, + const AllocMap* from, + uint32_t set_flags, + uint32_t clear_flags) +{ + AllocMapEntry* entry; + RB_FOREACH(entry, AllocMap, (AllocMap*)from) { + RBTMapResult ins_res; + AllocMapEntry* new_entry = + (AllocMapEntry*)qemu_malloc(sizeof(AllocMapEntry)); + if (new_entry == NULL) { + ME("memcheck: Unable to allocate new AllocMapEntry on copy."); + return -1; + } + memcpy(new_entry, entry, sizeof(AllocMapEntry)); + new_entry->desc.flags &= ~clear_flags; + new_entry->desc.flags |= set_flags; + if (entry->desc.call_stack_count) { + new_entry->desc.call_stack = + qemu_malloc(entry->desc.call_stack_count * sizeof(target_ulong)); + memcpy(new_entry->desc.call_stack, entry->desc.call_stack, + entry->desc.call_stack_count * sizeof(target_ulong)); + } else { + new_entry->desc.call_stack = NULL; + } + new_entry->desc.call_stack_count = entry->desc.call_stack_count; + ins_res = allocmap_insert_desc(to, new_entry, NULL); + if (ins_res == RBT_MAP_RESULT_ENTRY_INSERTED) { + if (memcheck_instrument_mmu) { + // Invalidate TLB cache for inserted entry. + invalidate_tlb_cache(new_entry->desc.malloc_desc.ptr, + mallocdesc_get_alloc_end(&new_entry->desc.malloc_desc)); + } + } else { + ME("memcheck: Unable to insert new map entry on copy. Insert returned %u", + ins_res); + if (new_entry->desc.call_stack != NULL) { + qemu_free(new_entry->desc.call_stack); + } + qemu_free(new_entry); + return -1; + } + } + + return 0; +} + +int +allocmap_empty(AllocMap* map) +{ + MallocDescEx pulled; + int removed = 0; + + while (!allocmap_pull_first(map, &pulled)) { + removed++; + if (pulled.call_stack != NULL) { + qemu_free(pulled.call_stack); + } + } + + return removed; +} diff --git a/memcheck/memcheck_malloc_map.h b/memcheck/memcheck_malloc_map.h new file mode 100644 index 0000000..b356180 --- /dev/null +++ b/memcheck/memcheck_malloc_map.h @@ -0,0 +1,150 @@ +/* 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 declarations of structures and routines that implement a red-black + * tree (a map) of memory blocks allocated by the guest system. The map is + * organized in such a way, that each entry in the map describes a virtual + * address range that belongs to a memory block allocated in the guest's space. + * The range includes block's suffix and prefix, as well as block returned to + * malloc's caller. Map considers two blocks to be equal if their address ranges + * intersect in any part. Allocation descriptor maps are instantiated one per + * each process running on the guest system. + */ + +#ifndef QEMU_MEMCHECK_MEMCHECK_MALLOC_MAP_H +#define QEMU_MEMCHECK_MEMCHECK_MALLOC_MAP_H + +/* 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-tree.h" +#include "memcheck_common.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Allocation descriptors map. */ +typedef struct AllocMap { + /* Head of the map. */ + struct AllocMapEntry* rbh_root; +} AllocMap; + +// ============================================================================= +// Map API +// ============================================================================= + +/* Initializes allocation descriptors map. + * Param: + * map - Allocation descriptors map to initialize. + */ +void allocmap_init(AllocMap* map); + +/* Inserts new (or replaces existing) entry in the allocation descriptors map. + * Insertion, or replacement is controlled by the value, passed to this routine + * with 'replaced' parameter. If this parameter is NULL, insertion will fail if + * a matching entry already exists in the map. If 'replaced' parameter is not + * NULL, and a matching entry exists in the map, content of the existing entry + * will be copied to the descriptor, addressed by 'replace' parameter, existing + * entry will be removed from the map, and new entry will be inserted. + * Param: + * map - Allocation descriptors map where to insert new, or replace existing + * entry. + * desc - Allocation descriptor to insert to the map. + * replaced - If not NULL, upon return from this routine contains descriptor + * that has been replaced in the map with the new entry. Note that if this + * routine returns with value other than RBT_MAP_RESULT_ENTRY_REPLACED, + * content of the 'replaced' buffer is not defined, as no replacement has + * actually occurred. + * Return + * See RBTMapResult for the return codes. + */ +RBTMapResult allocmap_insert(AllocMap* map, + const MallocDescEx* desc, + MallocDescEx* replaced); + +/* Finds an entry in the allocation descriptors map that matches the given + * address. + * Param: + * map - Allocation descriptors map where to search for an entry. + * address - Virtual address in the guest's user space to find matching + * entry for. Entry matches the address, if address is contained within + * allocated memory range (including guarding areas), as defined by the + * memory allocation descriptor for that entry. + * block_size - Size of the block, beginning with 'address'. + * Return: + * Pointer to the allocation descriptor found in a map entry, or NULL if no + * matching entry has been found in the map. + */ +MallocDescEx* allocmap_find(const AllocMap* map, + target_ulong address, + uint32_t block_size); + +/* Pulls (finds and removes) an entry from the allocation descriptors map that + * matches the given address. + * Param: + * map - Allocation descriptors map where to search for an entry. + * address - Virtual address in the guest's user space to find matching + * entry for. Entry matches the address, if address is contained within + * allocated memory range (including guarding areas), as defined by the + * memory allocation descriptor for that entry. + * pulled - Upon successful return contains allocation descriptor data pulled + * from the map. + * Return: + * Zero if an allocation descriptor that matches the given address has + * been pulled, or 1 if no matching entry has been found in the map. + */ +int allocmap_pull(AllocMap* map, target_ulong address, MallocDescEx* pulled); + +/* Pulls (removes) an entry from the head of the allocation descriptors map. + * Param: + * map - Allocation descriptors map where to pull an entry from. + * pulled - Upon successful return contains allocation descriptor data pulled + * from the head of the map. + * Return: + * Zero if an allocation descriptor has been pulled from the head of the map, + * or 1 if map is empty. + */ +int allocmap_pull_first(AllocMap* map, MallocDescEx* pulled); + +/* Copies content of one memory allocation descriptors map to another. + * Param: + * to - Map where to copy entries to. + * from - Map where to copy entries from. + * set_flags - Flags that should be set in the copied entry's 'flags' field. + * celar_flags - Flags that should be cleared in the copied entry's 'flags' + * field. + * Return: + * Zero on success, or -1 on error. + */ +int allocmap_copy(AllocMap* to, + const AllocMap* from, + uint32_t set_flags, + uint32_t clear_flags); + +/* Empties the map. + * Param: + * map - Map to empty. + * Return: + * Number of entries removed from the map. + */ +int allocmap_empty(AllocMap* map); + +#ifdef __cplusplus +}; /* end of extern "C" */ +#endif + +#endif // QEMU_MEMCHECK_MEMCHECK_MALLOC_MAP_H diff --git a/memcheck/memcheck_mmrange_map.c b/memcheck/memcheck_mmrange_map.c new file mode 100644 index 0000000..f2609df --- /dev/null +++ b/memcheck/memcheck_mmrange_map.c @@ -0,0 +1,236 @@ +/* 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 routines that implement a red-black tree of + * memory mappings in the guest system. + */ + +/* 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 "memcheck_mmrange_map.h" +#include "memcheck_logging.h" + +/* Memory range descriptor stored in the map. */ +typedef struct MMRangeMapEntry { + /* R-B tree entry. */ + RB_ENTRY(MMRangeMapEntry) rb_entry; + + /* Memory range descriptor for this entry. */ + MMRangeDesc desc; +} MMRangeMapEntry; + +// ============================================================================= +// R-B Tree implementation +// ============================================================================= + +/* Compare routine for the map. + * Param: + * d1 - First map entry to compare. + * d2 - Second map entry to compare. + * Return: + * 0 - Descriptors are equal. Note that descriptors are considered to be + * equal iff memory blocks they describe intersect in any part. + * 1 - d1 is greater than d2 + * -1 - d1 is less than d2. + */ +static inline int +cmp_rb(MMRangeMapEntry* d1, MMRangeMapEntry* d2) +{ + const target_ulong start1 = d1->desc.map_start; + const target_ulong start2 = d2->desc.map_start; + + if (start1 < start2) { + return (d1->desc.map_end - 1) < start2 ? -1 : 0; + } + return (d2->desc.map_end - 1) < start1 ? 1 : 0; +} + +/* Expands RB macros here. */ +RB_GENERATE(MMRangeMap, MMRangeMapEntry, rb_entry, cmp_rb); + +// ============================================================================= +// Static routines +// ============================================================================= + +/* Inserts new (or replaces existing) entry into the map. + * See comments on mmrangemap_insert routine in the header file for details + * about this routine. + */ +static RBTMapResult +mmrangemap_insert_desc(MMRangeMap* map, + MMRangeMapEntry* rdesc, + MMRangeDesc* replaced) +{ + MMRangeMapEntry* existing = MMRangeMap_RB_INSERT(map, rdesc); + if (existing == NULL) { + return RBT_MAP_RESULT_ENTRY_INSERTED; + } + + // Matching entry exists. Lets see if we need to replace it. + if (replaced == NULL) { + return RBT_MAP_RESULT_ENTRY_ALREADY_EXISTS; + } + + /* Copy existing entry to the provided buffer and replace it + * with the new one. */ + memcpy(replaced, &existing->desc, sizeof(MMRangeDesc)); + MMRangeMap_RB_REMOVE(map, existing); + qemu_free(existing); + MMRangeMap_RB_INSERT(map, rdesc); + return RBT_MAP_RESULT_ENTRY_REPLACED; +} + +/* Finds an entry in the map that matches the given address range. + * Param: + * map - Map where to search for an entry. + * start - Starting address of a mapping range. + * end - Ending address of a mapping range. + * Return: + * Address of a map entry that matches the given range, or NULL if no + * such entry has been found. + */ +static inline MMRangeMapEntry* +mmrangemap_find_entry(const MMRangeMap* map, + target_ulong start, + target_ulong end) +{ + MMRangeMapEntry rdesc; + rdesc.desc.map_start = start; + rdesc.desc.map_end = end; + return MMRangeMap_RB_FIND((MMRangeMap*)map, &rdesc); +} + +// ============================================================================= +// Map API +// ============================================================================= + +void +mmrangemap_init(MMRangeMap* map) +{ + RB_INIT(map); +} + +RBTMapResult +mmrangemap_insert(MMRangeMap* map, + const MMRangeDesc* desc, + MMRangeDesc* replaced) +{ + RBTMapResult ret; + + // Allocate and initialize new map entry. + MMRangeMapEntry* rdesc = qemu_malloc(sizeof(MMRangeMapEntry)); + if (rdesc == NULL) { + ME("memcheck: Unable to allocate new MMRangeMapEntry on insert."); + return RBT_MAP_RESULT_ERROR; + } + memcpy(&rdesc->desc, desc, sizeof(MMRangeDesc)); + + // Insert new entry into the map. + ret = mmrangemap_insert_desc(map, rdesc, replaced); + if (ret == RBT_MAP_RESULT_ENTRY_ALREADY_EXISTS || + ret == RBT_MAP_RESULT_ERROR) { + /* Another descriptor already exists for this block, or an error + * occurred. We have to free new descriptor, as it wasn't inserted. */ + qemu_free(rdesc); + } + return ret; +} + +MMRangeDesc* +mmrangemap_find(const MMRangeMap* map, target_ulong start, target_ulong end) +{ + MMRangeMapEntry* rdesc = mmrangemap_find_entry(map, start, end); + return rdesc != NULL ? &rdesc->desc : NULL; +} + +int +mmrangemap_pull(MMRangeMap* map, + target_ulong start, + target_ulong end, + MMRangeDesc* pulled) +{ + MMRangeMapEntry* rdesc = mmrangemap_find_entry(map, start, end); + if (rdesc != NULL) { + memcpy(pulled, &rdesc->desc, sizeof(MMRangeDesc)); + MMRangeMap_RB_REMOVE(map, rdesc); + qemu_free(rdesc); + return 0; + } else { + return -1; + } +} + +int +mmrangemap_pull_first(MMRangeMap* map, MMRangeDesc* pulled) +{ + MMRangeMapEntry* first = RB_MIN(MMRangeMap, map); + if (first != NULL) { + memcpy(pulled, &first->desc, sizeof(MMRangeDesc)); + MMRangeMap_RB_REMOVE(map, first); + qemu_free(first); + return 0; + } else { + return -1; + } +} + +int +mmrangemap_copy(MMRangeMap* to, const MMRangeMap* from) +{ + MMRangeMapEntry* entry; + RB_FOREACH(entry, MMRangeMap, (MMRangeMap*)from) { + RBTMapResult ins_res; + MMRangeMapEntry* new_entry = + (MMRangeMapEntry*)qemu_malloc(sizeof(MMRangeMapEntry)); + if (new_entry == NULL) { + ME("memcheck: Unable to allocate new MMRangeMapEntry on copy."); + return -1; + } + memcpy(new_entry, entry, sizeof(MMRangeMapEntry)); + new_entry->desc.path = qemu_malloc(strlen(entry->desc.path) + 1); + if (new_entry->desc.path == NULL) { + ME("memcheck: Unable to allocate new path for MMRangeMapEntry on copy."); + qemu_free(new_entry); + return -1; + } + strcpy(new_entry->desc.path, entry->desc.path); + ins_res = mmrangemap_insert_desc(to, new_entry, NULL); + if (ins_res != RBT_MAP_RESULT_ENTRY_INSERTED) { + ME("memcheck: Unable to insert new range map entry on copy. Insert returned %u", + ins_res); + qemu_free(new_entry->desc.path); + qemu_free(new_entry); + return -1; + } + } + + return 0; +} + +int +mmrangemap_empty(MMRangeMap* map) +{ + MMRangeDesc pulled; + int removed = 0; + + while (!mmrangemap_pull_first(map, &pulled)) { + qemu_free(pulled.path); + removed++; + } + + return removed; +} diff --git a/memcheck/memcheck_mmrange_map.h b/memcheck/memcheck_mmrange_map.h new file mode 100644 index 0000000..f2c9701 --- /dev/null +++ b/memcheck/memcheck_mmrange_map.h @@ -0,0 +1,127 @@ +/* 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 declarations of structures and routines that implement a red-black + * tree (a map) of module memory mapping ranges in the guest system. The map is + * organized in such a way, that each entry in the map describes a virtual + * address range that belongs to a mapped execution module in the guest system. + * Map considers two ranges to be equal if their address ranges intersect in + * any part. + */ + +#ifndef QEMU_MEMCHECK_MEMCHECK_MMRANGE_MAP_H +#define QEMU_MEMCHECK_MEMCHECK_MMRANGE_MAP_H + +/* 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-tree.h" +#include "memcheck_common.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Memory mapping range map. */ +typedef struct MMRangeMap { + /* Head of the map. */ + struct MMRangeMapEntry* rbh_root; +} MMRangeMap; + +// ============================================================================= +// Map API +// ============================================================================= + +/* Initializes the map. + * Param: + * map - Map to initialize. + */ +void mmrangemap_init(MMRangeMap* map); + +/* Inserts new (or replaces existing) entry in the map. + * Insertion, or replacement is controlled by the value, passed to this routine + * with 'replaced' parameter. If this parameter is NULL, insertion will fail if + * a matching entry already exists in the map. If 'replaced' parameter is not + * NULL, and a matching entry exists in the map, content of the existing entry + * will be copied to the descriptor, addressed by 'replace' parameter, existing + * entry will be removed from the map, and new entry will be inserted. + * Param: + * map - Map where to insert new, or replace existing entry. + * desc - Descriptor to insert to the map. + * replaced - If not NULL, upon return from this routine contains descriptor + * that has been replaced in the map with the new entry. Note that if this + * routine returns with value other than RBT_MAP_RESULT_ENTRY_REPLACED, + * content of the 'replaced' buffer is not defined, as no replacement has + * actually occurred. + * Return + * See RBTMapResult for the return codes. + */ +RBTMapResult mmrangemap_insert(MMRangeMap* map, + const MMRangeDesc* desc, + MMRangeDesc* replaced); + +/* Finds an entry in the map that matches the given address. + * Param: + * map - Map where to search for an entry. + * start - Starting address of a mapping range. + * end - Ending address of a mapping range. + * Return: + * Pointer to the descriptor found in a map entry, or NULL if no matching + * entry has been found in the map. + */ +MMRangeDesc* mmrangemap_find(const MMRangeMap* map, + target_ulong start, + target_ulong end); + +/* Pulls (finds and removes) an entry from the map that matches the given + * address. + * Param: + * map - Map where to search for an entry. + * start - Starting address of a mapping range. + * end - Ending address of a mapping range. + * pulled - Upon successful return contains descriptor data pulled from the + * map. + * Return: + * Zero if a descriptor that matches the given address has been pulled, or 1 + * if no matching entry has been found in the map. + */ +int mmrangemap_pull(MMRangeMap* map, + target_ulong start, + target_ulong end, + MMRangeDesc* pulled); + +/* Copies content of one memory map to another. + * Param: + * to - Map where to copy entries to. + * from - Map where to copy entries from. + * Return: + * Zero on success, or -1 on error. + */ +int mmrangemap_copy(MMRangeMap* to, const MMRangeMap* from); + +/* Empties the map. + * Param: + * map - Map to empty. + * Return: + * Number of entries removed from the map. + */ +int mmrangemap_empty(MMRangeMap* map); + +#ifdef __cplusplus +}; /* end of extern "C" */ +#endif + +#endif // QEMU_MEMCHECK_MEMCHECK_MMRANGE_MAP_H diff --git a/memcheck/memcheck_proc_management.c b/memcheck/memcheck_proc_management.c new file mode 100644 index 0000000..531ec4a --- /dev/null +++ b/memcheck/memcheck_proc_management.c @@ -0,0 +1,799 @@ +/* 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 routines related to process management in + * memchecker framework. + */ + +/* 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 "elff/elff_api.h" +#include "memcheck.h" +#include "memcheck_proc_management.h" +#include "memcheck_logging.h" + +/* Current thread id. + * This value is updated with each call to memcheck_switch, saving here + * ID of the thread that becomes current. */ +static uint32_t current_tid = 0; + +/* Current thread descriptor. + * This variable is used to cache current thread descriptor. This value gets + * initialized on "as needed" basis, when descriptor for the current thread + * is requested for the first time. + * Note that every time memcheck_switch routine is called, this value gets + * NULL'ed, since another thread becomes current. */ +static ThreadDesc* current_thread = NULL; + +/* Current process descriptor. + * This variable is used to cache current process descriptor. This value gets + * initialized on "as needed" basis, when descriptor for the current process + * is requested for the first time. + * Note that every time memcheck_switch routine is called, this value gets + * NULL'ed, since new thread becomes current, thus process switch may have + * occurred as well. */ +static ProcDesc* current_process = NULL; + +/* List of running processes. */ +static LIST_HEAD(proc_list, ProcDesc) proc_list; + +/* List of running threads. */ +static LIST_HEAD(thread_list, ThreadDesc) thread_list; + +// ============================================================================= +// Static routines +// ============================================================================= + +/* Creates and lists thread descriptor for a new thread. + * This routine will allocate and initialize new thread descriptor. After that + * this routine will insert the descriptor into the global list of running + * threads, as well as thread list in the process descriptor of the process + * in context of which this thread is created. + * Param: + * proc - Process descriptor of the process, in context of which new thread + * is created. + * tid - Thread ID of the thread that's being created. + * Return: + * New thread descriptor on success, or NULL on failure. + */ +static ThreadDesc* +create_new_thread(ProcDesc* proc, uint32_t tid) +{ + ThreadDesc* new_thread = (ThreadDesc*)qemu_malloc(sizeof(ThreadDesc)); + if (new_thread == NULL) { + ME("memcheck: Unable to allocate new thread descriptor."); + return NULL; + } + new_thread->tid = tid; + new_thread->process = proc; + new_thread->call_stack = NULL; + new_thread->call_stack_count = 0; + new_thread->call_stack_max = 0; + LIST_INSERT_HEAD(&thread_list, new_thread, global_entry); + LIST_INSERT_HEAD(&proc->threads, new_thread, proc_entry); + return new_thread; +} + +/* Creates and lists process descriptor for a new process. + * This routine will allocate and initialize new process descriptor. After that + * this routine will create main thread descriptor for the process (with the + * thread ID equal to the new process ID), and then new process descriptor will + * be inserted into the global list of running processes. + * Param: + * pid - Process ID of the process that's being created. + * parent_pid - Process ID of the parent process. + * Return: + * New process descriptor on success, or NULL on failure. + */ +static ProcDesc* +create_new_process(uint32_t pid, uint32_t parent_pid) +{ + // Create and init new process descriptor. + ProcDesc* new_proc = (ProcDesc*)qemu_malloc(sizeof(ProcDesc)); + if (new_proc == NULL) { + ME("memcheck: Unable to allocate new process descriptor"); + return NULL; + } + LIST_INIT(&new_proc->threads); + allocmap_init(&new_proc->alloc_map); + mmrangemap_init(&new_proc->mmrange_map); + new_proc->pid = pid; + new_proc->parent_pid = parent_pid; + new_proc->image_path = NULL; + new_proc->flags = 0; + + if (parent_pid != 0) { + /* If new process has been forked, it inherits a copy of parent's + * process heap, as well as parent's mmaping of loaded modules. So, on + * fork we're required to copy parent's allocation descriptors map, as + * well as parent's mmapping map to the new process. */ + int failed; + ProcDesc* parent = get_process_from_pid(parent_pid); + if (parent == NULL) { + ME("memcheck: Unable to get parent process pid=%u for new process pid=%u", + parent_pid, pid); + qemu_free(new_proc); + return NULL; + } + + /* Copy parent's allocation map, setting "inherited" flag, and clearing + * parent's "transition" flag in the copied entries. */ + failed = allocmap_copy(&new_proc->alloc_map, &parent->alloc_map, + MDESC_FLAG_INHERITED_ON_FORK, + MDESC_FLAG_TRANSITION_ENTRY); + if (failed) { + ME("memcheck: Unable to copy process' %s[pid=%u] allocation map to new process pid=%u", + parent->image_path, parent_pid, pid); + allocmap_empty(&new_proc->alloc_map); + qemu_free(new_proc); + return NULL; + } + + // Copy parent's memory mappings map. + failed = mmrangemap_copy(&new_proc->mmrange_map, &parent->mmrange_map); + if (failed) { + ME("memcheck: Unable to copy process' %s[pid=%u] mmrange map to new process pid=%u", + parent->image_path, parent_pid, pid); + mmrangemap_empty(&new_proc->mmrange_map); + allocmap_empty(&new_proc->alloc_map); + qemu_free(new_proc); + return NULL; + } + } + + // Create and register main thread descriptor for new process. + if(create_new_thread(new_proc, pid) == NULL) { + mmrangemap_empty(&new_proc->mmrange_map); + allocmap_empty(&new_proc->alloc_map); + qemu_free(new_proc); + return NULL; + } + + // List new process. + LIST_INSERT_HEAD(&proc_list, new_proc, global_entry); + + return new_proc; +} + +/* Finds thread descriptor for a thread id in the global list of running + * threads. + * Param: + * tid - Thread ID to look up thread descriptor for. + * Return: + * Found thread descriptor, or NULL if thread descriptor has not been found. + */ +static ThreadDesc* +get_thread_from_tid(uint32_t tid) +{ + ThreadDesc* thread; + + /* There is a pretty good chance that when this call is made, it's made + * to get descriptor for the current thread. Lets see if it is so, so + * we don't have to iterate through the entire list. */ + if (tid == current_tid && current_thread != NULL) { + return current_thread; + } + + LIST_FOREACH(thread, &thread_list, global_entry) { + if (tid == thread->tid) { + if (tid == current_tid) { + current_thread = thread; + } + return thread; + } + } + return NULL; +} + +/* Gets thread descriptor for the current thread. + * Return: + * Found thread descriptor, or NULL if thread descriptor has not been found. + */ +ThreadDesc* +get_current_thread(void) +{ + // Lets see if current thread descriptor has been cached. + if (current_thread == NULL) { + /* Descriptor is not cached. Look it up in the list. Note that + * get_thread_from_tid(current_tid) is not used here in order to + * optimize this code for performance, as this routine is called from + * the performance sensitive path. */ + ThreadDesc* thread; + LIST_FOREACH(thread, &thread_list, global_entry) { + if (current_tid == thread->tid) { + current_thread = thread; + return current_thread; + } + } + } + return current_thread; +} + +/* Finds process descriptor for a thread id. + * Param: + * tid - Thread ID to look up process descriptor for. + * Return: + * Process descriptor for the thread, or NULL, if process descriptor + * has not been found. + */ +static inline ProcDesc* +get_process_from_tid(uint32_t tid) +{ + const ThreadDesc* thread = get_thread_from_tid(tid); + return (thread != NULL) ? thread->process : NULL; +} + +/* Sets, or replaces process image path in process descriptor. + * Generally, new process' image path is unknown untill we calculate it in + * the handler for TRACE_DEV_REG_CMDLINE event. This routine is called from + * TRACE_DEV_REG_CMDLINE event handler to set, or replace process image path. + * Param: + * proc - Descriptor of the process where to set, or replace image path. + * image_path - Image path to the process, transmitted with + * TRACE_DEV_REG_CMDLINE event. + * set_flags_on_replace - Flags to be set when current image path for the + * process has been actually replaced with the new one. + * Return: + * Zero on success, or -1 on failure. + */ +static int +procdesc_set_image_path(ProcDesc* proc, + const char* image_path, + uint32_t set_flags_on_replace) +{ + if (image_path == NULL || proc == NULL) { + return 0; + } + + if (proc->image_path != NULL) { + /* Process could have been forked, and inherited image path of the + * parent process. However, it seems that "fork" in terms of TRACE_XXX + * is not necessarly a strict "fork", but rather new process creation + * in general. So, if that's the case we need to override image path + * inherited from the parent process. */ + if (!strcmp(proc->image_path, image_path)) { + // Paths are the same. Just bail out. + return 0; + } + qemu_free(proc->image_path); + proc->image_path = NULL; + } + + // Save new image path into process' descriptor. + proc->image_path = qemu_malloc(strlen(image_path) + 1); + if (proc->image_path == NULL) { + ME("memcheck: Unable to allocate %u bytes for image path %s to set it for pid=%u", + strlen(image_path) + 1, image_path, proc->pid); + return -1; + } + strcpy(proc->image_path, image_path); + proc->flags |= set_flags_on_replace; + return 0; +} + +/* Frees thread descriptor. */ +static void +threaddesc_free(ThreadDesc* thread) +{ + uint32_t indx; + + if (thread == NULL) { + return; + } + + if (thread->call_stack != NULL) { + for (indx = 0; indx < thread->call_stack_count; indx++) { + if (thread->call_stack[indx].module_path != NULL) { + qemu_free(thread->call_stack[indx].module_path); + } + } + qemu_free(thread->call_stack); + } + qemu_free(thread); +} + +// ============================================================================= +// Process management API +// ============================================================================= + +void +memcheck_init_proc_management(void) +{ + LIST_INIT(&proc_list); + LIST_INIT(&thread_list); +} + +ProcDesc* +get_process_from_pid(uint32_t pid) +{ + ProcDesc* proc; + + /* Chances are that pid addresses the current process. Lets check this, + * so we don't have to iterate through the entire project list. */ + if (current_thread != NULL && current_thread->process->pid == pid) { + current_process = current_thread->process; + return current_process; + } + + LIST_FOREACH(proc, &proc_list, global_entry) { + if (pid == proc->pid) { + break; + } + } + return proc; +} + +ProcDesc* +get_current_process(void) +{ + if (current_process == NULL) { + const ThreadDesc* cur_thread = get_current_thread(); + if (cur_thread != NULL) { + current_process = cur_thread->process; + } + } + return current_process; +} + +void +memcheck_on_call(target_ulong from, target_ulong ret) +{ + const uint32_t grow_by = 32; + const uint32_t max_stack = grow_by; + ThreadDesc* thread = get_current_thread(); + if (thread == NULL) { + return; + } + + /* We're not saving call stack until process starts execution. */ + if (!procdesc_is_executing(thread->process)) { + return; + } + + const MMRangeDesc* rdesc = procdesc_get_range_desc(thread->process, from); + if (rdesc == NULL) { + ME("memcheck: Unable to find mapping for guest PC 0x%08X in process %s[pid=%u]", + from, thread->process->image_path, thread->process->pid); + return; + } + + /* Limit calling stack size. There are cases when calling stack can be + * quite deep due to recursion (up to 4000 entries). */ + if (thread->call_stack_count >= max_stack) { +#if 0 + /* This happens quite often. */ + MD("memcheck: Thread stack for %s[pid=%u, tid=%u] is too big: %u", + thread->process->image_path, thread->process->pid, thread->tid, + thread->call_stack_count); +#endif + return; + } + + if (thread->call_stack_count >= thread->call_stack_max) { + /* Expand calling stack array buffer. */ + thread->call_stack_max += grow_by; + ThreadCallStackEntry* new_array = + qemu_malloc(thread->call_stack_max * sizeof(ThreadCallStackEntry)); + if (new_array == NULL) { + ME("memcheck: Unable to allocate %u bytes for calling stack.", + thread->call_stack_max * sizeof(ThreadCallStackEntry)); + thread->call_stack_max -= grow_by; + return; + } + if (thread->call_stack_count != 0) { + memcpy(new_array, thread->call_stack, + thread->call_stack_count * sizeof(ThreadCallStackEntry)); + } + if (thread->call_stack != NULL) { + qemu_free(thread->call_stack); + } + thread->call_stack = new_array; + } + thread->call_stack[thread->call_stack_count].call_address = from; + thread->call_stack[thread->call_stack_count].call_address_rel = + mmrangedesc_get_module_offset(rdesc, from); + thread->call_stack[thread->call_stack_count].ret_address = ret; + thread->call_stack[thread->call_stack_count].ret_address_rel = + mmrangedesc_get_module_offset(rdesc, ret); + thread->call_stack[thread->call_stack_count].module_path = + qemu_malloc(strlen(rdesc->path) + 1); + if (thread->call_stack[thread->call_stack_count].module_path == NULL) { + ME("memcheck: Unable to allocate %u bytes for module path in the thread calling stack.", + strlen(rdesc->path) + 1); + return; + } + strcpy(thread->call_stack[thread->call_stack_count].module_path, + rdesc->path); + thread->call_stack_count++; +} + +void +memcheck_on_ret(target_ulong ret) +{ + ThreadDesc* thread = get_current_thread(); + if (thread == NULL) { + return; + } + + /* We're not saving call stack until process starts execution. */ + if (!procdesc_is_executing(thread->process)) { + return; + } + + if (thread->call_stack_count > 0) { + int indx = (int)thread->call_stack_count - 1; + for (; indx >= 0; indx--) { + if (thread->call_stack[indx].ret_address == ret) { + thread->call_stack_count = indx; + return; + } + } + } +} + +// ============================================================================= +// Handlers for events, generated by the kernel. +// ============================================================================= + +void +memcheck_init_pid(uint32_t new_pid) +{ + create_new_process(new_pid, 0); + T(PROC_NEW_PID, "memcheck: init_pid(pid=%u) in current thread tid=%u\n", + new_pid, current_tid); +} + +void +memcheck_switch(uint32_t tid) +{ + /* Since new thread became active, we have to invalidate cached + * descriptors for current thread and process. */ + current_thread = NULL; + current_process = NULL; + current_tid = tid; +} + +void +memcheck_fork(uint32_t tgid, uint32_t new_pid) +{ + ProcDesc* parent_proc; + ProcDesc* new_proc; + + /* tgid may match new_pid, in which case current process is the + * one that's being forked, otherwise tgid identifies process + * that's being forked. */ + if (new_pid == tgid) { + parent_proc = get_current_process(); + } else { + parent_proc = get_process_from_tid(tgid); + } + + if (parent_proc == NULL) { + ME("memcheck: FORK(%u, %u): Unable to look up parent process. Current tid=%u", + tgid, new_pid, current_tid); + return; + } + + if (parent_proc->pid != get_current_process()->pid) { + MD("memcheck: FORK(%u, %u): parent %s[pid=%u] is not the current process %s[pid=%u]", + tgid, new_pid, parent_proc->image_path, parent_proc->pid, + get_current_process()->image_path, get_current_process()->pid); + } + + new_proc = create_new_process(new_pid, parent_proc->pid); + if (new_proc == NULL) { + return; + } + + /* Since we're possibly forking parent process, we need to inherit + * parent's image path in the forked process. */ + procdesc_set_image_path(new_proc, parent_proc->image_path, 0); + + T(PROC_FORK, "memcheck: FORK(tgid=%u, new_pid=%u) by %s[pid=%u] (tid=%u)\n", + tgid, new_pid, parent_proc->image_path, parent_proc->pid, current_tid); +} + +void +memcheck_clone(uint32_t tgid, uint32_t new_tid) +{ + ProcDesc* parent_proc; + + /* tgid may match new_pid, in which case current process is the + * one that creates thread, otherwise tgid identifies process + * that creates thread. */ + if (new_tid == tgid) { + parent_proc = get_current_process(); + } else { + parent_proc = get_process_from_tid(tgid); + } + + if (parent_proc == NULL) { + ME("memcheck: CLONE(%u, %u) Unable to look up parent process. Current tid=%u", + tgid, new_tid, current_tid); + return; + } + + if (parent_proc->pid != get_current_process()->pid) { + ME("memcheck: CLONE(%u, %u): parent %s[pid=%u] is not the current process %s[pid=%u]", + tgid, new_tid, parent_proc->image_path, parent_proc->pid, + get_current_process()->image_path, get_current_process()->pid); + } + + create_new_thread(parent_proc, new_tid); + + T(PROC_CLONE, "memcheck: CLONE(tgid=%u, new_tid=%u) by %s[pid=%u] (tid=%u)\n", + tgid, new_tid, parent_proc->image_path, parent_proc->pid, current_tid); +} + +void +memcheck_set_cmd_line(const char* cmd_arg, unsigned cmdlen) +{ + char parsed[4096]; + int n; + + ProcDesc* current_proc = get_current_process(); + if (current_proc == NULL) { + ME("memcheck: CMDL(%s, %u): Unable to look up process for current tid=%3u", + cmd_arg, cmdlen, current_tid); + return; + } + + /* Image path is the first agrument in cmd line. Note that due to + * limitations of TRACE_XXX cmdlen can never exceed CLIENT_PAGE_SIZE */ + memcpy(parsed, cmd_arg, cmdlen); + + // Cut first argument off the entire command line. + for (n = 0; n < cmdlen; n++) { + if (parsed[n] == ' ') { + break; + } + } + parsed[n] = '\0'; + + // Save process' image path into descriptor. + procdesc_set_image_path(current_proc, parsed, + PROC_FLAG_IMAGE_PATH_REPLACED); + current_proc->flags |= PROC_FLAG_EXECUTING; + + /* At this point we need to discard memory mappings inherited from + * the parent process, since this process has become "independent" from + * its parent. */ + mmrangemap_empty(¤t_proc->mmrange_map); + T(PROC_START, "memcheck: Executing process %s[pid=%u]\n", + current_proc->image_path, current_proc->pid); +} + +void +memcheck_exit(uint32_t exit_code) +{ + ProcDesc* proc; + int leaks_reported = 0; + MallocDescEx leaked_alloc; + + // Exiting thread descriptor. + ThreadDesc* thread = get_current_thread(); + if (thread == NULL) { + ME("memcheck: EXIT(%u): Unable to look up thread for current tid=%u", + exit_code, current_tid); + return; + } + proc = thread->process; + + // Since current thread is exiting, we need to NULL its cached descriptor. + current_thread = NULL; + + // Unlist the thread from its process as well as global lists. + LIST_REMOVE(thread, proc_entry); + LIST_REMOVE(thread, global_entry); + threaddesc_free(thread); + + /* Lets see if this was last process thread, which would indicate + * process termination. */ + if (!LIST_EMPTY(&proc->threads)) { + return; + } + + // Process is terminating. Report leaks and free resources. + proc->flags |= PROC_FLAG_EXITING; + + /* Empty allocation descriptors map for the exiting process, + * reporting leaking blocks in the process. */ + while (!allocmap_pull_first(&proc->alloc_map, &leaked_alloc)) { + /* We should "forgive" blocks that were inherited from the + * parent process on fork, or were allocated while process was + * in "transition" state. */ + if (!mallocdescex_is_inherited_on_fork(&leaked_alloc) && + !mallocdescex_is_transition_entry(&leaked_alloc)) { + if (!leaks_reported) { + // First leak detected. Print report's header. + T(CHECK_LEAK, "memcheck: Process %s[pid=%u] is exiting leaking allocated blocks:\n", + proc->image_path, proc->pid); + } + if (trace_flags & TRACE_CHECK_LEAK_ENABLED) { + // Dump leaked block information. + printf(" Leaked block %u:\n", leaks_reported + 1); + memcheck_dump_malloc_desc(&leaked_alloc, 0, 0); + if (leaked_alloc.call_stack != NULL) { + const int max_stack = 24; + if (max_stack >= leaked_alloc.call_stack_count) { + printf(" Call stack:\n"); + } else { + printf(" Call stack (first %u of %u entries):\n", + max_stack, leaked_alloc.call_stack_count); + } + uint32_t stk; + for (stk = 0; + stk < leaked_alloc.call_stack_count && stk < max_stack; + stk++) { + const MMRangeDesc* rdesc = + procdesc_find_mapentry(proc, + leaked_alloc.call_stack[stk]); + if (rdesc != NULL) { + Elf_AddressInfo elff_info; + ELFF_HANDLE elff_handle = NULL; + uint32_t rel = + mmrangedesc_get_module_offset(rdesc, + leaked_alloc.call_stack[stk]); + printf(" Frame %u: PC=0x%08X (relative 0x%08X) in module %s\n", + stk, leaked_alloc.call_stack[stk], rel, + rdesc->path); + if (memcheck_get_address_info(leaked_alloc.call_stack[stk], + rdesc, &elff_info, + &elff_handle) == 0) { + printf(" Routine %s @ %s/%s:%u\n", + elff_info.routine_name, + elff_info.dir_name, + elff_info.file_name, + elff_info.line_number); + elff_free_pc_address_info(elff_handle, + &elff_info); + elff_close(elff_handle); + } + } else { + printf(" Frame %u: PC=0x%08X in module \n", + stk, leaked_alloc.call_stack[stk]); + + } + } + } + } + leaks_reported++; + } + } + + if (leaks_reported) { + T(CHECK_LEAK, "memcheck: Process %s[pid=%u] is leaking %u allocated blocks.\n", + proc->image_path, proc->pid, leaks_reported); + } + + T(PROC_EXIT, "memcheck: Exiting process %s[pid=%u] in thread %u. Memory leaks detected: %u\n", + proc->image_path, proc->pid, current_tid, leaks_reported); + + /* Since current process is exiting, we need to NULL its cached descriptor, + * and unlist it from the list of running processes. */ + current_process = NULL; + LIST_REMOVE(proc, global_entry); + + // Empty process' mmapings map. + mmrangemap_empty(&proc->mmrange_map); + if (proc->image_path != NULL) { + qemu_free(proc->image_path); + } + qemu_free(proc); +} + +void +memcheck_mmap_exepath(target_ulong vstart, + target_ulong vend, + target_ulong exec_offset, + const char* path) +{ + MMRangeDesc desc; + MMRangeDesc replaced; + RBTMapResult ins_res; + + ProcDesc* proc = get_current_process(); + if (proc == NULL) { + ME("memcheck: MMAP(0x%08X, 0x%08X, 0x%08X, %s) Unable to look up current process. Current tid=%u", + vstart, vend, exec_offset, path, current_tid); + return; + } + + /* First, unmap an overlapped section */ + memcheck_unmap(vstart, vend); + + /* Add new mapping. */ + desc.map_start = vstart; + desc.map_end = vend; + desc.exec_offset = exec_offset; + desc.path = qemu_malloc(strlen(path) + 1); + if (desc.path == NULL) { + ME("memcheck: MMAP(0x%08X, 0x%08X, 0x%08X, %s) Unable to allocate path for the entry.", + vstart, vend, exec_offset, path); + return; + } + strcpy(desc.path, path); + + ins_res = mmrangemap_insert(&proc->mmrange_map, &desc, &replaced); + if (ins_res == RBT_MAP_RESULT_ERROR) { + ME("memcheck: %s[pid=%u] unable to insert memory mapping entry: 0x%08X - 0x%08X", + proc->image_path, proc->pid, vstart, vend); + qemu_free(desc.path); + return; + } + + if (ins_res == RBT_MAP_RESULT_ENTRY_REPLACED) { + MD("memcheck: %s[pid=%u] MMRANGE %s[0x%08X - 0x%08X] is replaced with %s[0x%08X - 0x%08X]", + proc->image_path, proc->pid, replaced.path, replaced.map_start, + replaced.map_end, desc.path, desc.map_start, desc.map_end); + qemu_free(replaced.path); + } + + T(PROC_MMAP, "memcheck: %s[pid=%u] %s is mapped: 0x%08X - 0x%08X + 0x%08X\n", + proc->image_path, proc->pid, path, vstart, vend, exec_offset); +} + +void +memcheck_unmap(target_ulong vstart, target_ulong vend) +{ + MMRangeDesc desc; + ProcDesc* proc = get_current_process(); + if (proc == NULL) { + ME("memcheck: UNMAP(0x%08X, 0x%08X) Unable to look up current process. Current tid=%u", + vstart, vend, current_tid); + return; + } + + if (mmrangemap_pull(&proc->mmrange_map, vstart, vend, &desc)) { + return; + } + + if (desc.map_start >= vstart && desc.map_end <= vend) { + /* Entire mapping has been deleted. */ + T(PROC_MMAP, "memcheck: %s[pid=%u] %s is unmapped: [0x%08X - 0x%08X + 0x%08X]\n", + proc->image_path, proc->pid, desc.path, vstart, vend, desc.exec_offset); + qemu_free(desc.path); + return; + } + + /* This can be first stage of "remap" request, when part of the existing + * mapping has been unmapped. If that's so, lets cut unmapped part from the + * block that we just pulled, and add whatever's left back to the map. */ + T(PROC_MMAP, "memcheck: REMAP(0x%08X, 0x%08X + 0x%08X) -> (0x%08X, 0x%08X)\n", + desc.map_start, desc.map_end, desc.exec_offset, vstart, vend); + if (desc.map_start == vstart) { + /* We cut part from the beginning. Add the tail back. */ + desc.exec_offset += vend - desc.map_start; + desc.map_start = vend; + mmrangemap_insert(&proc->mmrange_map, &desc, NULL); + } else if (desc.map_end == vend) { + /* We cut part from the tail. Add the beginning back. */ + desc.map_end = vstart; + mmrangemap_insert(&proc->mmrange_map, &desc, NULL); + } else { + /* We cut piece in the middle. */ + MMRangeDesc tail; + tail.map_start = vend; + tail.map_end = desc.map_end; + tail.exec_offset = vend - desc.map_start + desc.exec_offset; + tail.path = qemu_malloc(strlen(desc.path) + 1); + strcpy(tail.path, desc.path); + mmrangemap_insert(&proc->mmrange_map, &tail, NULL); + desc.map_end = vstart; + mmrangemap_insert(&proc->mmrange_map, &desc, NULL); + } +} diff --git a/memcheck/memcheck_proc_management.h b/memcheck/memcheck_proc_management.h new file mode 100644 index 0000000..d5525b1 --- /dev/null +++ b/memcheck/memcheck_proc_management.h @@ -0,0 +1,327 @@ +/* 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 declarations of structures, routines, etc. related to process + * management in memchecker framework. + */ + +#ifndef QEMU_MEMCHECK_MEMCHECK_PROC_MANAGEMENT_H +#define QEMU_MEMCHECK_MEMCHECK_PROC_MANAGEMENT_H + +/* 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 "memcheck_common.h" +#include "memcheck_malloc_map.h" +#include "memcheck_mmrange_map.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// ============================================================================= +// Process management structures +// ============================================================================= + +/* Describes a process that is monitored by memchecker framework. */ +typedef struct ProcDesc { + /* Map of memory blocks allocated in context of this process. */ + AllocMap alloc_map; + + /* Map of memory mapped modules loaded in context of this process. */ + MMRangeMap mmrange_map; + + /* Descriptor's entry in the global process list. */ + LIST_ENTRY(ProcDesc) global_entry; + + /* List of threads running in context of this process. */ + LIST_HEAD(threads, ThreadDesc) threads; + + /* Path to the process' image file. */ + char* image_path; + + /* Process id. */ + uint32_t pid; + + /* Parent process id. */ + uint32_t parent_pid; + + /* Misc. process flags. See PROC_FLAG_XXX */ + uint32_t flags; +} ProcDesc; + +/* Process is executing. */ +#define PROC_FLAG_EXECUTING 0x00000001 +/* Process is exiting. */ +#define PROC_FLAG_EXITING 0x00000002 +/* ProcDesc->image_path has been replaced during process execution. */ +#define PROC_FLAG_IMAGE_PATH_REPLACED 0x00000004 +/* libc.so instance has been initialized for this process. */ +#define PROC_FLAG_LIBC_INITIALIZED 0x00000008 + +/* Entry in the thread's calling stack array. */ +typedef struct ThreadCallStackEntry { + /* Guest PC where call has been made. */ + target_ulong call_address; + /* Guest PC where call has been made, relative to the beginning of the + * mapped module that contains call_address. */ + target_ulong call_address_rel; + /* Guest PC where call will return. */ + target_ulong ret_address; + /* Guest PC where call will return, relative to the beginning of the + * mapped module that contains ret_address. */ + target_ulong ret_address_rel; + /* Path to the image file of the module containing call_address. */ + char* module_path; +} ThreadCallStackEntry; + +/* Describes a thread that is monitored by memchecker framework. */ +typedef struct ThreadDesc { + /* Descriptor's entry in the global thread list. */ + LIST_ENTRY(ThreadDesc) global_entry; + + /* Descriptor's entry in the process' thread list. */ + LIST_ENTRY(ThreadDesc) proc_entry; + + /* Descriptor of the process this thread belongs to. */ + ProcDesc* process; + + /* Calling stack for this thread. */ + ThreadCallStackEntry* call_stack; + + /* Number of entries in the call_stack array. */ + uint32_t call_stack_count; + + /* Maximum number of entries that can fit into call_stack buffer. */ + uint32_t call_stack_max; + + /* Thread id. */ + uint32_t tid; +} ThreadDesc; + +// ============================================================================= +// Inlines +// ============================================================================= + +/* Checks if process has been forked, rather than created from a "fresh" PID. + * Param: + * proc - Descriptor for the process to check. + * Return: + * boolean: 1 if process has been forked, or 0 if it was + * created from a "fresh" PID. + */ +static inline int +procdesc_is_forked(const ProcDesc* proc) +{ + return proc->parent_pid != 0; +} + +/* Checks if process is executing. + * Param: + * proc - Descriptor for the process to check. + * Return: + * boolean: 1 if process is executing, or 0 if it is not executing. + */ +static inline int +procdesc_is_executing(const ProcDesc* proc) +{ + return (proc->flags & PROC_FLAG_EXECUTING) != 0; +} + +/* Checks if process is exiting. + * Param: + * proc - Descriptor for the process to check. + * Return: + * boolean: 1 if process is exiting, or 0 if it is still alive. + */ +static inline int +procdesc_is_exiting(const ProcDesc* proc) +{ + return (proc->flags & PROC_FLAG_EXITING) != 0; +} + +/* Checks if process has initialized its libc.so instance. + * Param: + * proc - Descriptor for the process to check. + * Return: + * boolean: 1 if process has initialized its libc.so instance, or 0 otherwise. + */ +static inline int +procdesc_is_libc_initialized(const ProcDesc* proc) +{ + return (proc->flags & PROC_FLAG_LIBC_INITIALIZED) != 0; +} + +/* Checks if process image path has been replaced. + * Param: + * proc - Descriptor for the process to check. + * Return: + * boolean: 1 if process image path has been replaced, + * or 0 if it was not replaced. + */ +static inline int +procdesc_is_image_path_replaced(const ProcDesc* proc) +{ + return (proc->flags & PROC_FLAG_IMAGE_PATH_REPLACED) != 0; +} + +// ============================================================================= +// Process management API +// ============================================================================= + +/* Gets thread descriptor for the current thread. + * Return: + * Found thread descriptor, or NULL if thread descriptor has not been found. + */ +ThreadDesc* get_current_thread(void); + +/* Initializes process management API. */ +void memcheck_init_proc_management(void); + +/* Gets process descriptor for the current process. + * Return: + * Process descriptor for the current process, or NULL, if process descriptor + * has not been found. + */ +ProcDesc* get_current_process(void); + +/* Finds process descriptor for a process id. + * Param: + * pid - Process ID to look up process descriptor for. + * Return: + * Process descriptor for the PID, or NULL, if process descriptor + * has not been found. + */ +ProcDesc* get_process_from_pid(uint32_t pid); + +/* Inserts new (or replaces existing) entry in the allocation descriptors map + * for the given process. + * See allocmap_insert for more information on this routine, its parameters + * and returning value. + * Param: + * proc - Process descriptor where to add new allocation entry info. + */ +static inline RBTMapResult +procdesc_add_malloc(ProcDesc* proc, + const MallocDescEx* desc, + MallocDescEx* replaced) +{ + return allocmap_insert(&proc->alloc_map, desc, replaced); +} + +/* Finds an entry in the allocation descriptors map for the given process, + * matching given address range. + * See allocmap_find for more information on this routine, its parameters + * and returning value. + * Param: + * proc - Process descriptor where to find an allocation entry. + */ +static inline MallocDescEx* +procdesc_find_malloc_for_range(ProcDesc* proc, + target_ulong address, + uint32_t block_size) +{ + return allocmap_find(&proc->alloc_map, address, block_size); +} + +/* Finds an entry in the allocation descriptors map for the given process, + * matching given address. + * See allocmap_find for more information on this routine, its parameters + * and returning value. + * Param: + * proc - Process descriptor where to find an allocation entry. + */ +static inline MallocDescEx* +procdesc_find_malloc(ProcDesc* proc, target_ulong address) +{ + return procdesc_find_malloc_for_range(proc, address, 1); +} + +/* Pulls (finds and removes) an entry from the allocation descriptors map for + * the given process, matching given address. + * See allocmap_pull for more information on this routine, its parameters + * and returning value. + * Param: + * proc - Process descriptor where to pull an allocation entry from. + */ +static inline int +procdesc_pull_malloc(ProcDesc* proc, target_ulong address, MallocDescEx* pulled) +{ + return allocmap_pull(&proc->alloc_map, address, pulled); +} + +/* Empties allocation descriptors map for the process. + * Param: + * proc - Process to empty allocation map for. + * Return: + * Number of entries deleted from the allocation map. + */ +static inline int +procdesc_empty_alloc_map(ProcDesc* proc) +{ + return allocmap_empty(&proc->alloc_map); +} + +/* Finds mmapping entry for the given address in the given process. + * Param: + * proc - Descriptor of the process where to look for an entry. + * addr - Address in the guest space for which to find an entry. + * Return: + * Mapped entry, or NULL if no mapping for teh given address exists in the + * process address space. + */ +static inline MMRangeDesc* +procdesc_find_mapentry(const ProcDesc* proc, target_ulong addr) +{ + return mmrangemap_find(&proc->mmrange_map, addr, addr + 1); +} + +/* Gets module descriptor for the given address. + * Param: + * proc - Descriptor of the process where to look for a module. + * addr - Address in the guest space for which to find a module descriptor. + * Return: + * module descriptor for the module containing the given address, or NULL if no + * such module has been found in the process' map of mmaped modules. + */ +static inline const MMRangeDesc* +procdesc_get_range_desc(const ProcDesc* proc, target_ulong addr) +{ + return procdesc_find_mapentry(proc, addr); +} + +/* Gets name of the module mmaped in context of the given process for the + * given address. + * Param: + * proc - Descriptor of the process where to look for a module. + * addr - Address in the guest space for which to find a module. + * Return: + * Image path to the module containing the given address, or NULL if no such + * module has been found in the process' map of mmaped modules. + */ +static inline const char* +procdesc_get_module_path(const ProcDesc* proc, target_ulong addr) +{ + MMRangeDesc* rdesc = procdesc_find_mapentry(proc, addr); + return rdesc != NULL ? rdesc->path : NULL; +} + +#ifdef __cplusplus +}; /* end of extern "C" */ +#endif + +#endif // QEMU_MEMCHECK_MEMCHECK_PROC_MANAGEMENT_H diff --git a/memcheck/memcheck_util.c b/memcheck/memcheck_util.c new file mode 100644 index 0000000..cc4182d --- /dev/null +++ b/memcheck/memcheck_util.c @@ -0,0 +1,270 @@ +/* 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 utility routines for memchecker framework. + */ + +/* 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 "stdio.h" +#include "qemu-common.h" +#include "android/utils/path.h" +#include "cpu.h" +#include "softmmu_outside_jit.h" +#include "memcheck_proc_management.h" +#include "memcheck_logging.h" +#include "memcheck_util.h" + +/* Gets symblos file path for the given module. + * Param: + * module_path - Path to the module to get sympath for. + * sym_path - Buffer, where to save path to the symbols file path for the givem + * module. NOTE: This buffer must be big enough to contain the largest + * path possible. + * max_char - Character size of the buffer addressed by sym_path parameter. + * Return: + * 0 on success, or -1 if symbols file has not been found, or sym_path buffer + * was too small to contain entire path. + */ +static int +get_sym_path(const char* module_path, char* sym_path, size_t max_char) +{ + const char* sym_path_root = getenv("ANDROID_PROJECT_OUT"); + if (sym_path_root == NULL || strlen(sym_path_root) >= max_char) { + return -1; + } + + strcpy(sym_path, sym_path_root); + max_char -= strlen(sym_path_root); + if (sym_path[strlen(sym_path)-1] != PATH_SEP_C) { + strcat(sym_path, PATH_SEP); + max_char--; + } + if (strlen("symbols") >= max_char) { + return -1; + } + strcat(sym_path, "symbols"); + max_char -= strlen("symbols"); + if (strlen(module_path) >= max_char) { + return -1; + } + strcat(sym_path, module_path); + + /* Sometimes symbol file for a module is placed into a parent symbols + * directory. Lets iterate through all parent sym dirs, until we find + * sym file, or reached symbols root. */ + while (!path_exists(sym_path)) { + /* Select module name. */ + char* name = strrchr(sym_path, PATH_SEP_C); + assert(name != NULL); + *name = '\0'; + /* Parent directory. */ + char* parent = strrchr(sym_path, PATH_SEP_C); + assert(parent != NULL); + *parent = '\0'; + if (strcmp(sym_path, sym_path_root) == 0) { + return -1; + } + *parent = PATH_SEP_C; + memmove(parent+1, name + 1, strlen(name + 1) + 1); + } + + return 0; +} + +// ============================================================================= +// Transfering data between guest and emulator address spaces. +// ============================================================================= + +void +memcheck_get_guest_buffer(void* qemu_address, + target_ulong guest_address, + size_t buffer_size) +{ + /* Byte-by-byte copying back and forth between guest's and emulator's memory + * appears to be efficient enough (at least on small blocks used in + * memchecker), so there is no real need to optimize it by aligning guest + * buffer to 32 bits and use ld/stl_user instead of ld/stub_user to + * read / write guest's memory. */ + while (buffer_size) { + *(uint8_t*)qemu_address = ldub_user(guest_address); + (uint32_t)qemu_address++; + guest_address++; + buffer_size--; + } +} + +void +memcheck_set_guest_buffer(target_ulong guest_address, + const void* qemu_address, + size_t buffer_size) +{ + while (buffer_size) { + stb_user(guest_address, *(uint8_t*)qemu_address); + guest_address++; + (uint32_t)qemu_address++; + buffer_size--; + } +} + +size_t +memcheck_get_guest_string(char* qemu_str, + target_ulong guest_str, + size_t qemu_buffer_size) +{ + size_t copied = 0; + + if (qemu_buffer_size > 1) { + for (copied = 0; copied < qemu_buffer_size - 1; copied++) { + qemu_str[copied] = ldub_user(guest_str + copied); + if (qemu_str[copied] == '\0') { + return copied; + } + } + } + qemu_str[copied] = '\0'; + return copied; +} + +size_t +memcheck_get_guest_kernel_string(char* qemu_str, + target_ulong guest_str, + size_t qemu_buffer_size) +{ + size_t copied = 0; + + if (qemu_buffer_size > 1) { + for (copied = 0; copied < qemu_buffer_size - 1; copied++) { + qemu_str[copied] = ldub_kernel(guest_str + copied); + if (qemu_str[copied] == '\0') { + return copied; + } + } + } + qemu_str[copied] = '\0'; + return copied; +} + +// ============================================================================= +// Helpers for transfering memory allocation information. +// ============================================================================= + +void +memcheck_fail_alloc(target_ulong guest_address) +{ + stl_user(ALLOC_RES_ADDRESS(guest_address), 0); +} + +void +memcheck_fail_free(target_ulong guest_address) +{ + stl_user(FREE_RES_ADDRESS(guest_address), 0); +} + +void +memcheck_fail_query(target_ulong guest_address) +{ + stl_user(QUERY_RES_ADDRESS(guest_address), 0); +} + +// ============================================================================= +// Misc. utility routines. +// ============================================================================= + +void +invalidate_tlb_cache(target_ulong start, target_ulong end) +{ + target_ulong index = (start >> TARGET_PAGE_BITS) & (CPU_TLB_SIZE - 1); + const target_ulong to = ((end - 1) >> TARGET_PAGE_BITS) & (CPU_TLB_SIZE-1); + for (; index <= to; index++, start += TARGET_PAGE_SIZE) { + target_ulong tlb_addr = cpu_single_env->tlb_table[1][index].addr_write; + if ((start & TARGET_PAGE_MASK) == + (tlb_addr & (TARGET_PAGE_MASK | TLB_INVALID_MASK))) { + cpu_single_env->tlb_table[1][index].addr_write ^= TARGET_PAGE_MASK; + } + tlb_addr = cpu_single_env->tlb_table[1][index].addr_read; + if ((start & TARGET_PAGE_MASK) == + (tlb_addr & (TARGET_PAGE_MASK | TLB_INVALID_MASK))) { + cpu_single_env->tlb_table[1][index].addr_read ^= TARGET_PAGE_MASK; + } + } +} + +void +memcheck_dump_malloc_desc(const MallocDescEx* desc_ex, + int print_flags, + int print_proc_info) +{ + const MallocDesc* desc = &desc_ex->malloc_desc; + printf(" User range: 0x%08X - 0x%08X, %u bytes\n", + (uint32_t)mallocdesc_get_user_ptr(desc), + (uint32_t)mallocdesc_get_user_ptr(desc) + desc->requested_bytes, + desc->requested_bytes); + printf(" Prefix guarding area: 0x%08X - 0x%08X, %u bytes\n", + desc->ptr, desc->ptr + desc->prefix_size, desc->prefix_size); + printf(" Suffix guarding area: 0x%08X - 0x%08X, %u bytes\n", + mallocdesc_get_user_alloc_end(desc), + mallocdesc_get_user_alloc_end(desc) + desc->suffix_size, + desc->suffix_size); + if (print_proc_info) { + ProcDesc* proc = get_process_from_pid(desc->allocator_pid); + if (proc != NULL) { + printf(" Allocated by: %s[pid=%u]\n", + proc->image_path, proc->pid); + } + } + if (print_flags) { + printf(" Flags: 0x%08X\n", desc_ex->flags); + } +} + +int +memcheck_get_address_info(target_ulong abs_pc, + const MMRangeDesc* rdesc, + Elf_AddressInfo* info, + ELFF_HANDLE* elff_handle) +{ + char sym_path[MAX_PATH]; + ELFF_HANDLE handle; + + if (get_sym_path(rdesc->path, sym_path, MAX_PATH)) { + return 1; + } + + handle = elff_init(sym_path); + if (handle == NULL) { + return -1; + } + + if (!elff_is_exec(handle)) { + /* Debug info for shared library is created for the relative address. */ + target_ulong rel_pc = mmrangedesc_get_module_offset(rdesc, abs_pc); + if (elff_get_pc_address_info(handle, rel_pc, info)) { + elff_close(handle); + return -1; + } + } else { + /* Debug info for executables is created for the absoulte address. */ + if (elff_get_pc_address_info(handle, abs_pc, info)) { + elff_close(handle); + return -1; + } + } + + *elff_handle = handle; + return 0; +} diff --git a/memcheck/memcheck_util.h b/memcheck/memcheck_util.h new file mode 100644 index 0000000..d4f6c8a --- /dev/null +++ b/memcheck/memcheck_util.h @@ -0,0 +1,245 @@ +/* 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 declarations of utility routines for memchecker framework. + */ + +#ifndef QEMU_MEMCHECK_MEMCHECK_UTIL_H +#define QEMU_MEMCHECK_MEMCHECK_UTIL_H + +/* 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 "memcheck_common.h" +#include "elff_api.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// ============================================================================= +// Transfering data between guest and emulator address spaces. +// ============================================================================= + +/* Copies buffer residing in the guest's virtual address space to a buffer + * in the emulator's address space. + * Param: + * guest_address - Address of the bufer in guest's virtual address space. + * qemu_address - Address of the bufer in the emulator's address space. + * buffer_size - Byte size of the guest's buffer. + */ +void memcheck_get_guest_buffer(void* qemu_address, + target_ulong guest_address, + size_t buffer_size); + +/* Copies buffer residing in the emulator's address space to a buffer in the + * guest's virtual address space. + * Param: + * qemu_address - Address of the bufer in the emulator's address space. + * guest_address - Address of the bufer in guest's virtual address space. + * buffer_size - Byte size of the emualtor's buffer. + */ +void memcheck_set_guest_buffer(target_ulong guest_address, + const void* qemu_address, + size_t buffer_size); + +/* Copies zero-terminated string residing in the guest's virtual address space + * to a string buffer in emulator's address space. + * Param: + * qemu_str - Address of the string bufer in the emulator's address space. + * guest_str - Address of the string in guest's virtual address space. + * qemu_buffer_size - Size of the emulator's string buffer. + * Return + * Length of the string that has been copied. + */ +size_t memcheck_get_guest_string(char* qemu_str, + target_ulong guest_str, + size_t qemu_buffer_size); + +/* Copies zero-terminated string residing in the guest's kernel address space + * to a string buffer in emulator's address space. + * Param: + * qemu_str - Address of the string bufer in the emulator's address space. + * guest_str - Address of the string in guest's kernel address space. + * qemu_buffer_size - Size of the emulator's string buffer. + * Return + * Length of the string that has been copied. + */ +size_t memcheck_get_guest_kernel_string(char* qemu_str, + target_ulong guest_str, + size_t qemu_buffer_size); + +// ============================================================================= +// Helpers for transfering memory allocation information. +// ============================================================================= + +/* Copies memory allocation descriptor from the guest's address space to the + * emulator's memory. + * Param: + * qemu_address - Descriptor address in the emulator's address space where to + * copy descriptor. + * guest_address - Descriptor address in the guest's address space. + */ +static inline void +memcheck_get_malloc_descriptor(MallocDesc* qemu_address, + target_ulong guest_address) +{ + memcheck_get_guest_buffer(qemu_address, guest_address, sizeof(MallocDesc)); +} + +/* Copies memory allocation descriptor from the emulator's memory to the guest's + * address space. + * Param: + * guest_address - Descriptor address in the guest's address space. + * qemu_address - Descriptor address in the emulator's address space where to + * copy descriptor. + */ +static inline void +memcheck_set_malloc_descriptor(target_ulong guest_address, + const MallocDesc* qemu_address) +{ + memcheck_set_guest_buffer(guest_address, qemu_address, sizeof(MallocDesc)); +} + +/* Copies memory free descriptor from the guest's address space to the + * emulator's memory. + * Param: + * qemu_address - Descriptor address in the emulator's address space where to + * copy descriptor. + * guest_address - Descriptor address in the guest's address space. + */ +static inline void +memcheck_get_free_descriptor(MallocFree* qemu_address, + target_ulong guest_address) +{ + memcheck_get_guest_buffer(qemu_address, guest_address, sizeof(MallocFree)); +} + +/* Copies memory allocation query descriptor from the guest's address space to + * the emulator's memory. + * Param: + * guest_address - Descriptor address in the guest's address space. + * qemu_address - Descriptor address in the emulator's address space where to + * copy descriptor. + */ +static inline void +memcheck_get_query_descriptor(MallocDescQuery* qemu_address, + target_ulong guest_address) +{ + memcheck_get_guest_buffer(qemu_address, guest_address, + sizeof(MallocDescQuery)); +} + +/* Fails allocation request (TRACE_DEV_REG_MALLOC event). + * Allocation request failure is reported by zeroing 'libc_pid' filed in the + * allocation descriptor in the guest's address space. + * Param: + * guest_address - Allocation descriptor address in the guest's address space, + * where to record failure. + */ +void memcheck_fail_alloc(target_ulong guest_address); + +/* Fails free request (TRACE_DEV_REG_FREE_PTR event). + * Free request failure is reported by zeroing 'libc_pid' filed in the free + * descriptor in the guest's address space. + * Param: + * guest_address - Free descriptor address in the guest's address space, where + * to record failure. + */ +void memcheck_fail_free(target_ulong guest_address); + +/* Fails memory allocation query request (TRACE_DEV_REG_QUERY_MALLOC event). + * Query request failure is reported by zeroing 'libc_pid' filed in the query + * descriptor in the guest's address space. + * Param: + * guest_address - Query descriptor address in the guest's address space, where + * to record failure. + */ +void memcheck_fail_query(target_ulong guest_address); + +// ============================================================================= +// Misc. utility routines. +// ============================================================================= + +/* Converts PC address in the translated block to a corresponded PC address in + * the guest address space. + * Param: + * tb_pc - PC address in the translated block. + * Return: + * Corresponded PC address in the guest address space on success, or NULL if + * conversion has failed. + */ +static inline target_ulong +memcheck_tpc_to_gpc(target_ulong tb_pc) +{ + const TranslationBlock* tb = tb_find_pc(tb_pc); + return tb != NULL ? tb_search_guest_pc_from_tb_pc(tb, tb_pc) : 0; +} + +/* Invalidates TLB table pages that contain given memory range. + * This routine is called after new entry is inserted into allocation map, so + * every access to the allocated block will cause __ld/__stx_mmu to be called. + * Param: + * start - Beginning of the allocated block to invalidate pages for. + * end - End of (past one byte after) the allocated block to invalidate pages + * for. + */ +void invalidate_tlb_cache(target_ulong start, target_ulong end); + +/* Gets routine, file path and line number information for a PC address in the + * given module. + * Param: + * abs_pc - PC address. + * rdesc - Mapped memory range descriptor for the module containing abs_pc. + * info - Upon successful return will contain routine, file path and line + * information for the given PC address in the given module. + * NOTE: Pathnames, saved into this structure are contained in mapped + * sections of the symbols file for the module addressed by module_path. + * Thus, pathnames are accessible only while elff_handle returned from this + * routine remains opened. + * NOTE: each successful call to this routine requires the caller to call + * elff_free_pc_address_info for Elf_AddressInfo structure. + * elff_handle - Upon successful return will contain a handle to the ELFF API + * that wraps symbols file for the module, addressed by module_path. The + * handle must remain opened for as long as pathnames in the info structure + * are accessed, and must be eventually closed via call to elff_close. + * Return: + * 0 on success, 1, if symbols file for the module has not been found, or -1 on + * other failures. If a failure is returned from this routine content of info + * and elff_handle parameters is undefined. + */ +int memcheck_get_address_info(target_ulong abs_pc, + const MMRangeDesc* rdesc, + Elf_AddressInfo* info, + ELFF_HANDLE* elff_handle); + +/* Dumps content of an allocation descriptor to stdout. + * Param desc - Allocation descriptor to dump. + * print_flags - If 1, flags field of the descriptor will be dumped to stdout. + * If 0, flags filed will not be dumped. + * print_proc_info - If 1, allocator's process information for the descriptor + * will be dumped to stdout. If 0, allocator's process information will + * not be dumped. + */ +void memcheck_dump_malloc_desc(const MallocDescEx* desc, + int print_flags, + int print_proc_info); + +#ifdef __cplusplus +}; /* end of extern "C" */ +#endif + +#endif // QEMU_MEMCHECK_MEMCHECK_UTIL_H -- cgit v1.1