/* 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;
}