blob: 65f64b000fc00a9c1faef4cbc04df64773f80a92 [file]
/* **********************************************************
* Copyright (c) 2010-2024 Google, Inc. All rights reserved.
* Copyright (c) 2007-2010 VMware, Inc. All rights reserved.
* **********************************************************/
/* Dr. Memory: the memory debugger
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License, and no later version.
* This library 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
* Library General Public License for more details.
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
/* umbra_x86.c
*
* Umbra x86 architecture specific code.
* In x86 architecture, we used a lookup table based approach.
*/
#include "dr_api.h"
#include "umbra.h"
#include "umbra_private.h"
#include "drmemory_framework.h"
#include "../framework/drmf.h"
#include "utils.h"
#include <string.h> /* for memchr */
#ifdef X64
# error x86 only
#endif
/***************************************************************************
* Umbra x86 Shadow Memory Mapping Scheme Description:
*
* We implement a table lookup based shadow memory framework here:
* - the address space is uniformly divided into 16-bit (64KB) units
* - a shadow table with 64K entries stores the displacement
* (i.e., disp = addr_shdw - addr_app) from the base, which is to shrink
* the instrumentation size.
* - a special default_block is to created for all application memory that
* does not have shadow memory allocated.
* - optimization: for saving memory usage, we allow the user to create special
* read-only blocks for all-identical 64KB chunks.
* - XXX: we do not support allocating a shadow memory across 64KB
* boundary to simplify the code.
*/
/***************************************************************************
* SHADOW TABLE DATA STRUCTURES
*/
/* We divide the address space into 16-bit (64KB) chunk units */
#define APP_BLOCK_BITS 16
#define APP_BLOCK_SIZE (1 << APP_BLOCK_BITS)
#define SHADOW_TABLE_ENTRIES (1 << (32 - APP_BLOCK_BITS))
#define SHADOW_TABLE_SIZE (sizeof(ptr_int_t)*SHADOW_TABLE_ENTRIES)
#define SHADOW_TABLE_OFFSET(addr) ((ptr_uint_t)(addr) & (APP_BLOCK_SIZE-1))
#define SHADOW_TABLE_INDEX(addr) ((ptr_uint_t)(addr) >> APP_BLOCK_BITS)
#define SHADOW_TABLE_APP_BASE(idx) ((ptr_uint_t)(idx) << APP_BLOCK_BITS)
/***************************************************************************
* SHADOW TABLE ROUTINES
*/
/* get the offset in a block */
static inline ptr_uint_t
shadow_table_get_block_offset(umbra_map_t *map, app_pc app_addr)
{
ptr_uint_t offset = SHADOW_TABLE_OFFSET(app_addr);
return umbra_map_scale_app_to_shadow(map, offset);
}
static void
shadow_table_delete_block(umbra_map_t *map, byte *shadow_start)
{
/* Different allocator usage depending on whether redzones are faulty. */
if (map->options.make_redzone_faulty) {
nonheap_free(shadow_start - map->options.redzone_size,
map->shadow_block_alloc_size, HEAPSTAT_SHADOW);
} else {
global_free(shadow_start - map->options.redzone_size,
map->shadow_block_alloc_size, HEAPSTAT_SHADOW);
}
}
static byte *
shadow_table_init_redzone(umbra_map_t *map, byte *block)
{
IF_DEBUG(bool ok;)
if (map->options.redzone_size != 0) {
if (map->options.make_redzone_faulty) {
ASSERT(ALIGNED(block, PAGE_SIZE), "pre-redzone not page aligned");
/* Set redzone permission to be faulty on reads and writes. */
IF_DEBUG(ok = )
dr_memory_protect(block, map->options.redzone_size, DR_MEMPROT_NONE);
ASSERT(ok, "-w failed: will have inconsistencies in shadow data");
} else {
memset(block, map->options.redzone_value, map->options.redzone_size);
}
}
block += map->options.redzone_size;
if (map->options.redzone_size != 0) {
byte *post_redzone = block + map->shadow_block_size;
if (map->options.make_redzone_faulty) {
ASSERT(ALIGNED(post_redzone, PAGE_SIZE), "post-redzone not page aligned");
/* Again, set redzone permission. */
IF_DEBUG(ok = )
dr_memory_protect(post_redzone, map->options.redzone_size, DR_MEMPROT_NONE);
ASSERT(ok, "-w failed: will have inconsistencies in shadow data");
} else {
memset(post_redzone, map->options.redzone_value,
map->options.redzone_size);
}
}
return block;
}
static byte *
shadow_table_create_block(umbra_map_t *map)
{
byte *block;
if (map->options.make_redzone_faulty) {
/* We need to ensure page alignment for redzones, so that we may then
* assign them no access.
*/
block = nonheap_alloc(map->shadow_block_alloc_size,
DR_MEMPROT_READ | DR_MEMPROT_WRITE,
HEAPSTAT_SHADOW);
} else {
block = global_alloc(map->shadow_block_alloc_size,
HEAPSTAT_SHADOW);
}
block = shadow_table_init_redzone(map, block);
LOG(UMBRA_VERBOSE, "created new shadow block "PFX"\n", block);
return block;
}
/* Should only be called on destroying the umbra map, b/c we can't handle
* deletion during execution.
*/
static void
shadow_table_delete_special_block(umbra_map_t *map,
special_block_t *block)
{
nonheap_free(block->start - map->options.redzone_size,
map->shadow_block_alloc_size,
HEAPSTAT_SHADOW);
memset(block, 0, sizeof(*block));
}
static void
shadow_table_delete_default_block(umbra_map_t *map)
{
/* we did not use default block for CREATE_ON_TOUCH */
if (TEST(UMBRA_MAP_CREATE_SHADOW_ON_TOUCH, map->options.flags)) {
ASSERT(map->default_block.start == NULL &&
map->default_block.value == 0 &&
map->default_block.value_size == 0,
"default block must be 0");
return;
}
shadow_table_delete_special_block(map, &map->default_block);
}
static void
shadow_table_create_special_block_helper(umbra_map_t *map,
ptr_uint_t value,
size_t value_size,
special_block_t *special_block)
{
IF_DEBUG(bool ok;)
byte *block;
/* assuming nonheap_alloc will handle unaligned size */
block = (byte *) nonheap_alloc(map->shadow_block_alloc_size,
DR_MEMPROT_READ|DR_MEMPROT_WRITE,
HEAPSTAT_SHADOW);
ASSERT(block != NULL && ALIGNED(block, PAGE_SIZE),
"fail to alloc special block");
ASSERT(value_size == 1 && value <= UCHAR_MAX,
"NYI: we only support byte-size value now");
block = shadow_table_init_redzone(map, block);
memset(block, value, map->shadow_block_size);
/* we will never write to the special */
IF_DEBUG(ok = )
dr_memory_protect(block - map->options.redzone_size,
map->shadow_block_alloc_size, DR_MEMPROT_READ);
ASSERT(ok, "-w failed: will have inconsistencies in shadow data");
LOG(UMBRA_VERBOSE, "created new shadow special block "PFX"\n", block);
special_block->start = block;
special_block->value = value;
special_block->value_size = value_size;
}
static void
shadow_table_create_default_block(umbra_map_t *map)
{
shadow_table_create_special_block_helper(map,
map->options.default_value,
map->options.default_value_size,
&map->default_block);
}
static byte *
shadow_table_lookup_special_block(umbra_map_t *map,
ptr_uint_t value,
size_t value_size)
{
int i, num_blks;
/* assuming we never update or delete a special block, so it is ok
* to do the racy lookup
*/
num_blks = map->num_special_blocks;
for (i = 0; i < num_blks; i++) {
if (map->special_blocks[i].value == value &&
map->special_blocks[i].value_size == value_size) {
return map->special_blocks[i].start;
}
}
return NULL;
}
static byte *
shadow_table_create_special_block(umbra_map_t *map,
ptr_uint_t value,
size_t value_size)
{
byte *block;
int num_blks;
umbra_map_lock(map);
num_blks = map->num_special_blocks;
block = shadow_table_lookup_special_block(map, value, value_size);
if (block != NULL || num_blks >= MAX_NUM_SPECIAL_BLOCKS) {
umbra_map_unlock(map);
return block;
}
shadow_table_create_special_block_helper(map, value, value_size,
&map->special_blocks[num_blks]);
/* update the num_special_blocks */
map->num_special_blocks = num_blks + 1;
block = map->special_blocks[num_blks].start;
umbra_map_unlock(map);
return block;
}
static void
shadow_table_set_block(umbra_map_t *map, uint idx, byte *block)
{
/* We store the displacement (shadow minus app) (PR 553724). */
ptr_uint_t base = SHADOW_TABLE_APP_BASE(idx);
base = umbra_map_scale_app_to_shadow(map, base);
map->shadow_table[idx] = (ptr_uint_t)block - base;
LOG(UMBRA_VERBOSE,
"setting shadow table idx %d for block "PFX" to "PFX"\n",
idx, block, map->shadow_table[idx]);
}
static inline byte *
shadow_table_get_block(umbra_map_t *map, uint idx)
{
ptr_uint_t base = SHADOW_TABLE_APP_BASE(idx);
base = umbra_map_scale_app_to_shadow(map, base);
return (byte *)(map->shadow_table[idx] + base);
}
static inline byte *
shadow_table_app_to_shadow(umbra_map_t *map, app_pc app_addr)
{
return (shadow_table_get_block(map, SHADOW_TABLE_INDEX(app_addr)) +
shadow_table_get_block_offset(map, app_addr));
}
int
umbra_num_scratch_regs_for_translation_arch(void)
{
return 1;
}
#if defined(X86)
/* code sequence:
* %reg_index = %reg_addr;
* %reg_index >>= 16;
* %reg_addr >>= map->scale;
* %reg_addr += table[%reg_index];
*/
static void
shadow_table_insert_app_to_shadow_arch(void *drcontext, umbra_map_t *map,
instrlist_t *ilist, instr_t *where,
reg_id_t reg_addr, reg_id_t reg_idx)
{
uint disp;
/* %reg_index = %reg_addr */
PRE(ilist, where, XINST_CREATE_move(drcontext,
opnd_create_reg(reg_idx),
opnd_create_reg(reg_addr)));
/* %reg_index >>= 16 */
PRE(ilist, where, INSTR_CREATE_shr(drcontext,
opnd_create_reg(reg_idx),
OPND_CREATE_INT8(APP_BLOCK_BITS)));
/* We assume that the addr is aligned and won't keep the offset in byte. */
if (UMBRA_MAP_SCALE_IS_DOWN(map->options.scale)) {
/* %reg_addr >>= map->scale*/
PRE(ilist, where, INSTR_CREATE_shr(drcontext,
opnd_create_reg(reg_addr),
OPND_CREATE_INT8(map->shift)));
} else if (UMBRA_MAP_SCALE_IS_UP(map->options.scale)) {
/* %reg_addr <<= map->scale*/
PRE(ilist, where, INSTR_CREATE_shl(drcontext,
opnd_create_reg(reg_addr),
OPND_CREATE_INT8(map->shift)));
}
/* %reg_addr += table[%reg_index] */
disp = (uint)(map->shadow_table);
PRE(ilist, where, INSTR_CREATE_add(drcontext,
opnd_create_reg(reg_addr),
opnd_create_base_disp(REG_NULL,
reg_idx,
sizeof(ptr_int_t),
disp,
OPSZ_PTR)));
}
#elif defined(ARM)
/* code sequence:
* %reg_idx = table
* %reg_idx += (%reg_addr >> 14)
* %reg_idx &= 0xfffffffc
* %reg_idx = 0x0(%reg_idx)
* %reg_addr = %reg_idx + (%reg_addr >> map->scale)
*/
static void
shadow_table_insert_app_to_shadow_arch(void *drcontext, umbra_map_t *map,
instrlist_t *ilist, instr_t *where,
reg_id_t reg_addr, reg_id_t reg_idx)
{
/* %reg_idx = table */
instrlist_insert_mov_immed_ptrsz(drcontext, (uint)(map->shadow_table),
opnd_create_reg(reg_idx), ilist, where,
NULL, NULL);
/* %reg_idx += (%reg_addr >> 14) */
PRE(ilist, where, INSTR_CREATE_add_shimm(drcontext,
opnd_create_reg(reg_idx),
opnd_create_reg(reg_idx),
opnd_create_reg(reg_addr),
OPND_CREATE_INT(DR_SHIFT_LSR),
OPND_CREATE_INT(14)));
/* Can't specify a bitmask of 0xfffffffc, so instead we use its inverse, BIC.
* %reg_idx &= 0xfffffffc
*/
PRE(ilist, where, INSTR_CREATE_bic(drcontext,
opnd_create_reg(reg_idx),
opnd_create_reg(reg_idx),
OPND_CREATE_INT8(0x03)));
/* %reg_idx = 0x0(%reg_idx) */
PRE(ilist, where, XINST_CREATE_load(drcontext,
opnd_create_reg(reg_idx),
OPND_CREATE_MEMPTR(reg_idx, 0)));
/* %reg_addr = %reg_idx + (%reg_addr >> map->scale) */
if (UMBRA_MAP_SCALE_IS_DOWN(map->options.scale)) {
PRE(ilist, where, INSTR_CREATE_add_shimm(drcontext,
opnd_create_reg(reg_addr),
opnd_create_reg(reg_idx),
opnd_create_reg(reg_addr),
OPND_CREATE_INT(DR_SHIFT_LSR),
OPND_CREATE_INT(map->shift)));
} else if (UMBRA_MAP_SCALE_IS_UP(map->options.scale)) {
PRE(ilist, where, INSTR_CREATE_add_shimm(drcontext,
opnd_create_reg(reg_addr),
opnd_create_reg(reg_idx),
opnd_create_reg(reg_addr),
OPND_CREATE_INT(DR_SHIFT_LSL),
OPND_CREATE_INT(map->shift)));
} else {
PRE(ilist, where, XINST_CREATE_add(drcontext,
opnd_create_reg(reg_addr),
opnd_create_reg(reg_idx)));
}
}
#else
# error NYI
#endif
static bool
shadow_table_is_in_default_block(umbra_map_t *map, byte *shadow_addr,
bool *redzone DR_PARAM_OUT)
{
if (shadow_addr >= map->default_block.start - map->options.redzone_size &&
shadow_addr < (map->default_block.start + map->shadow_block_size +
map->options.redzone_size)) {
if (redzone != NULL) {
if (map->options.redzone_size != 0 &&
(shadow_addr < map->default_block.start ||
shadow_addr >= (map->default_block.start +
map->shadow_block_size)))
*redzone = true;
else
*redzone = false;
}
return true;
}
return false;
}
static bool
shadow_table_use_default_block(umbra_map_t *map, app_pc app_addr)
{
return shadow_table_is_in_default_block
(map, shadow_table_app_to_shadow(map, app_addr), NULL);
}
static bool
shadow_table_is_in_special_block(umbra_map_t *map, byte *shadow_addr,
ptr_uint_t *value, size_t *value_size,
bool *redzone DR_PARAM_OUT)
{
uint i;
for (i = 0; i < map->num_special_blocks; i++) {
if (shadow_addr >=
(map->special_blocks[i].start - map->options.redzone_size) &&
shadow_addr <
(map->special_blocks[i].start + map->shadow_block_size +
map->options.redzone_size)) {
if (value != NULL)
*value = map->special_blocks[i].value;
if (value_size != NULL)
*value_size = map->special_blocks[i].value_size;
if (redzone != NULL) {
if (map->options.redzone_size != 0 &&
(shadow_addr < map->special_blocks[i].start ||
shadow_addr >= (map->special_blocks[i].start +
map->shadow_block_size))) {
/* XXX: we still use special block value instead of
* redzone value here.
*/
*redzone = true;
} else
*redzone = false;
}
return true;
}
}
return false;
}
static bool
shadow_table_use_special_block(umbra_map_t *map, app_pc app_addr,
ptr_uint_t *value, size_t *value_size)
{
return shadow_table_is_in_special_block
(map, shadow_table_app_to_shadow(map, app_addr),
value, value_size, NULL);
}
static bool
shadow_table_is_in_normal_block(umbra_map_t *map, byte *shadow_addr)
{
return (!shadow_table_is_in_default_block(map, shadow_addr, NULL) &&
!shadow_table_is_in_special_block(map, shadow_addr,
NULL, NULL, NULL));
}
static void
shadow_table_replace_block(umbra_map_t *map, app_pc app_base)
{
ptr_uint_t value;
size_t value_size;
byte *block;
value = map->options.default_value;
value_size = map->options.default_value_size;
umbra_map_lock(map);
if (shadow_table_use_default_block(map, app_base) ||
shadow_table_use_special_block(map, app_base, &value, &value_size)) {
ASSERT(value <= USHRT_MAX && value_size == 1,
"value_size > 1 is not supported");
block = shadow_table_create_block(map);
memset(block, value, map->shadow_block_size);
shadow_table_set_block(map, SHADOW_TABLE_INDEX(app_base), block);
}
umbra_map_unlock(map);
}
static void
shadow_table_init(umbra_map_t *map)
{
uint i;
LOG(UMBRA_VERBOSE, "shadow_table_init\n");
map->shadow_table = nonheap_alloc(SHADOW_TABLE_SIZE,
DR_MEMPROT_READ | DR_MEMPROT_WRITE,
HEAPSTAT_SHADOW);
/* sets the whole address space to default special block first */
if (!TEST(UMBRA_MAP_CREATE_SHADOW_ON_TOUCH, map->options.flags)) {
/* default block means the app memory does not have shadow memory */
shadow_table_create_default_block(map);
for (i = 0; i < SHADOW_TABLE_ENTRIES; i++)
shadow_table_set_block(map, i, map->default_block.start);
} else {
byte *start =
shadow_table_create_special_block(map, map->options.default_value,
map->options.default_value_size);
for (i = 0; i < SHADOW_TABLE_ENTRIES; i++)
shadow_table_set_block(map, i, start);
memset(&map->default_block, 0, sizeof(map->default_block));
}
}
static void
shadow_table_exit(umbra_map_t *map)
{
uint i;
LOG(UMBRA_VERBOSE, "shadow_table_exit\n");
/* free all the shadow memory */
umbra_map_lock(map);
for (i = 0; i < SHADOW_TABLE_ENTRIES; i++) {
byte *shadow_addr = shadow_table_get_block(map, i);
if (shadow_table_is_in_normal_block(map, shadow_addr)) {
LOG(UMBRA_VERBOSE, "freeing shadow block idx=%d "PFX"\n",
i, shadow_addr);
shadow_table_delete_block(map, shadow_addr);
}
}
/* free all special blocks */
for (i = 0; i < map->num_special_blocks; i++) {
if (map->special_blocks[i].start != NULL) {
shadow_table_delete_special_block(map, &map->special_blocks[i]);
}
}
shadow_table_delete_default_block(map);
nonheap_free(map->shadow_table, SHADOW_TABLE_SIZE, HEAPSTAT_SHADOW);
umbra_map_unlock(map);
}
/***************************************************************************
* EXPORT UMBRA X86 SPECIFIC CODE
*/
drmf_status_t
umbra_arch_init()
{
return DRMF_SUCCESS;
}
void
umbra_arch_exit()
{
}
drmf_status_t
umbra_map_arch_init(umbra_map_t *map, umbra_map_options_t *ops)
{
/* Override redzone size. We must make it a page size to control
* its permissions.
*
* XXX i#2283: Potentially support larger redzone sizes to be even more safe.
* It is perhaps unlikely that a translated address would be used in
* pointer arithmetic that results in jumping over the 1 paged sized
* redzone. Nevertheless, we can be even more safe and leave this
* decision up to the user, and not blindly setting the redzone size
* to a page size.
*/
if (ops->make_redzone_faulty)
map->options.redzone_size = PAGE_SIZE;
if (ops->redzone_size != 0) {
if (ops->redzone_value_size != 1 || ops->redzone_value > UCHAR_MAX) {
ASSERT(false, "NYI: we only support byte-size value now");
return DRMF_ERROR_NOT_IMPLEMENTED;
}
if (!ALIGNED(ops->redzone_size, 256)) {
return DRMF_ERROR_INVALID_PARAMETER;
}
}
map->app_block_size = APP_BLOCK_SIZE;
map->shadow_block_size = umbra_map_scale_app_to_shadow(map, APP_BLOCK_SIZE);
map->shadow_block_alloc_size =
map->shadow_block_size + 2 * map->options.redzone_size;
shadow_table_init(map);
/* We assume that the shadow block is completely within page sizes.
* This allows us to set permissions.
*/
ASSERT(map->shadow_block_size % PAGE_SIZE == 0, "Not within unit of page sizes");
return DRMF_SUCCESS;
}
void
umbra_map_arch_exit(umbra_map_t *map)
{
shadow_table_exit(map);
}
drmf_status_t
umbra_create_shadow_memory_arch(umbra_map_t *map,
uint flags,
app_pc app_addr,
size_t app_size,
ptr_uint_t value,
size_t value_size)
{
/* i#1260: end pointers are all closed (i.e., inclusive) to handle overflow */
app_pc app_blk_base, app_blk_end, app_src_end;
app_pc start, end;
size_t size, iter_size;
byte *shadow_blk;
drmf_status_t res;
if (value_size != 1 || value >= UCHAR_MAX)
return DRMF_ERROR_FEATURE_NOT_AVAILABLE;
if (POINTER_OVERFLOW_ON_ADD(app_addr, app_size-1)) /* just hitting top is ok */
return DRMF_ERROR_INVALID_SIZE;
umbra_map_lock(map);
APP_RANGE_LOOP(app_addr, app_size, app_blk_base, app_blk_end, app_src_end,
start, end, iter_size, {
if (shadow_table_use_default_block(map, app_blk_base)) {
/* no shadow memory created yet */
if (TEST(flags, UMBRA_CREATE_SHADOW_SHARED_READONLY) &&
((app_blk_base >= app_addr && app_blk_end <= app_src_end) ||
(map->options.default_value == value &&
map->options.default_value_size == value_size))) {
/* We can use a special block if either it is a whole block
* or the block value is the same as the default value.
*/
shadow_blk = shadow_table_create_special_block(map,
value,
value_size);
if (shadow_blk != NULL) {
shadow_table_set_block(map,
SHADOW_TABLE_INDEX(app_blk_base),
shadow_blk);
continue;
}
}
/* cannot use a special block, need create normal block */
shadow_table_replace_block(map, app_blk_base);
}
res = umbra_shadow_set_range_arch(map,
start,
iter_size,
&size,
value,
value_size);
if (res != DRMF_SUCCESS) {
/* We do not de-allocte the block as it is either fail to allocate
* or fail to set new value. In either case, we do not have to do
* anything since umbra_delete_shadow_memory simply sets value back.
*/
umbra_map_unlock(map);
return res;
}
});
umbra_map_unlock(map);
return DRMF_SUCCESS;
}
drmf_status_t
umbra_delete_shadow_memory_arch(umbra_map_t *map,
app_pc app_addr,
size_t app_size)
{
size_t shadow_size;
/* XXX: we can optimize it by replace it with default block */
return umbra_shadow_set_range_arch(map, app_addr, app_size,
&shadow_size,
map->options.default_value,
map->options.default_value_size);
}
drmf_status_t
umbra_read_shadow_memory_arch(DR_PARAM_IN umbra_map_t *map,
DR_PARAM_IN app_pc app_addr,
DR_PARAM_IN size_t app_size,
DR_PARAM_INOUT size_t *shadow_size,
DR_PARAM_IN byte *buffer)
{
/* i#1260: end pointers are all closed (i.e., inclusive) to handle overflow */
app_pc app_blk_base, app_blk_end, app_src_end;
app_pc start, end;
size_t size, iter_size;
byte *shadow_start;
size_t shdw_size;
if (*shadow_size < umbra_map_scale_app_to_shadow(map, app_size)) {
*shadow_size = 0;
return DRMF_ERROR_INVALID_SIZE;
}
if (POINTER_OVERFLOW_ON_ADD(app_addr, app_size-1)) /* just hitting top is ok */
return DRMF_ERROR_INVALID_SIZE;
shdw_size = 0;
APP_RANGE_LOOP(app_addr, app_size, app_blk_base, app_blk_end, app_src_end,
start, end, iter_size, {
shadow_start = shadow_table_app_to_shadow(map, start);
if (shadow_table_is_in_default_block(map, shadow_start, NULL))
return DRMF_ERROR_INVALID_PARAMETER;
size = umbra_map_scale_app_to_shadow(map, iter_size);
memcpy(buffer, shadow_start, size);
shdw_size += size;
buffer += size;
});
*shadow_size = shdw_size;
return DRMF_SUCCESS;
}
drmf_status_t
umbra_write_shadow_memory_arch(DR_PARAM_IN umbra_map_t *map,
DR_PARAM_IN app_pc app_addr,
DR_PARAM_IN size_t app_size,
DR_PARAM_INOUT size_t *shadow_size,
DR_PARAM_IN byte *buffer)
{
/* i#1260: end pointers are all closed (i.e., inclusive) to handle overflow */
app_pc app_blk_base, app_blk_end, app_src_end;
app_pc start, end;
size_t size, iter_size;
byte *shadow_start;
size_t shdw_size;
if (*shadow_size < umbra_map_scale_app_to_shadow(map, app_size)) {
*shadow_size = 0;
return DRMF_ERROR_INVALID_SIZE;
}
if (POINTER_OVERFLOW_ON_ADD(app_addr, app_size-1)) /* just hitting top is ok */
return DRMF_ERROR_INVALID_SIZE;
shdw_size = 0;
APP_RANGE_LOOP(app_addr, app_size, app_blk_base, app_blk_end, app_src_end,
start, end, iter_size, {
shadow_start = shadow_table_app_to_shadow(map, start);
if (shadow_table_is_in_default_block(map, shadow_start, NULL))
return DRMF_ERROR_INVALID_PARAMETER;
if (shadow_table_is_in_special_block(map, shadow_start,
NULL, NULL, NULL)) {
shadow_table_replace_block(map, app_blk_base);
shadow_start = shadow_table_app_to_shadow(map, start);
}
size = umbra_map_scale_app_to_shadow(map, iter_size);
memmove(shadow_start, buffer, size);
shdw_size += size;
buffer += size;
});
*shadow_size = shdw_size;
return DRMF_SUCCESS;
}
drmf_status_t
umbra_shadow_set_range_arch(DR_PARAM_IN umbra_map_t *map,
DR_PARAM_IN app_pc app_addr,
DR_PARAM_IN size_t app_size,
DR_PARAM_OUT size_t *shadow_size,
DR_PARAM_IN ptr_uint_t value,
DR_PARAM_IN size_t value_size)
{
/* i#1260: end pointers are all closed (i.e., inclusive) to handle overflow */
app_pc app_blk_base, app_blk_end, app_src_end;
app_pc start, end;
size_t size, iter_size;
byte *shadow_start;
size_t shdw_size;
ptr_uint_t blk_val;
size_t blk_val_sz;
if (value_size != 1 || value > UCHAR_MAX) {
*shadow_size = 0;
return DRMF_ERROR_NOT_IMPLEMENTED;
}
if (POINTER_OVERFLOW_ON_ADD(app_addr, app_size-1)) /* just hitting top is ok */
return DRMF_ERROR_INVALID_SIZE;
shdw_size = 0;
APP_RANGE_LOOP(app_addr, app_size, app_blk_base, app_blk_end, app_src_end,
start, end, iter_size, {
shadow_start = shadow_table_app_to_shadow(map, start);
if (shadow_table_is_in_default_block(map, shadow_start, NULL))
return DRMF_ERROR_INVALID_PARAMETER;
size = umbra_map_scale_app_to_shadow(map, iter_size);
if (shadow_table_is_in_special_block(map, shadow_start,
&blk_val, &blk_val_sz, NULL)) {
shadow_table_replace_block(map, app_blk_base);
shadow_start = shadow_table_app_to_shadow(map, start);
}
memset(shadow_start, value, size);
shdw_size += size;
});
*shadow_size = shdw_size;
return DRMF_SUCCESS;
}
drmf_status_t
umbra_shadow_copy_range_arch(DR_PARAM_IN umbra_map_t *map,
DR_PARAM_IN app_pc app_src,
DR_PARAM_IN app_pc app_dst,
DR_PARAM_IN size_t app_size_in,
DR_PARAM_OUT size_t *shadow_size_out)
{
/* i#1260: end pointers are all closed (i.e., inclusive) to handle overflow */
app_pc app_blk_base, app_blk_end, app_src_end;
app_pc start, end;
size_t app_sz, shadow_sz, tot_shadow_sz, iter_size, tail_size = 0;
byte *shadow_start, *overlap_tail = NULL;
drmf_status_t res = DRMF_SUCCESS;
app_sz = app_size_in;
if (POINTER_OVERFLOW_ON_ADD(app_src, app_sz-1) || /* just hitting top is ok */
POINTER_OVERFLOW_ON_ADD(app_dst, app_sz-1)) /* just hitting top is ok */
return DRMF_ERROR_INVALID_SIZE;
if (app_src < app_dst && app_src + (app_sz-1) >= app_dst) {
/* overlap that must be handled */
tail_size = app_src + (app_sz-1) - app_dst + 1;
overlap_tail = global_alloc(tail_size, HEAPSTAT_SHADOW);
shadow_sz = umbra_map_scale_app_to_shadow(map, tail_size);
if (umbra_read_shadow_memory_arch(map, app_dst, tail_size,
&shadow_sz, overlap_tail) != DRMF_SUCCESS)
ASSERT(false, "fail to read shadow memory");
app_sz = app_dst - app_src;
}
/* XXX, assuming the overlap with the other way is ok */
tot_shadow_sz = 0;
APP_RANGE_LOOP(app_src, app_sz, app_blk_base, app_blk_end, app_src_end,
start, end, iter_size, {
shadow_start = shadow_table_app_to_shadow(map, start);
if (shadow_table_is_in_default_block(map, shadow_start, NULL)) {
res = DRMF_ERROR_INVALID_PARAMETER;
break;
}
shadow_sz = umbra_map_scale_app_to_shadow(map, iter_size);
res = umbra_write_shadow_memory_arch(map,
app_dst,
iter_size,
&shadow_sz,
shadow_start);
if (res != DRMF_SUCCESS) {
tot_shadow_sz += shadow_sz;
break;
} else {
ASSERT(shadow_sz == umbra_map_scale_app_to_shadow(map, iter_size),
"copy size mismatch");
}
app_dst += iter_size;
tot_shadow_sz += shadow_sz;
});
if (overlap_tail != NULL) {
if (res == DRMF_SUCCESS) {
shadow_sz = umbra_map_scale_app_to_shadow(map, tail_size);
res = umbra_write_shadow_memory_arch(map,
app_dst + (app_dst - app_src),
tail_size,
&shadow_sz,
overlap_tail);
tot_shadow_sz += shadow_sz;
}
global_free(overlap_tail, tail_size, HEAPSTAT_SHADOW);
}
*shadow_size_out = tot_shadow_sz;
return res;
}
drmf_status_t
umbra_value_in_shadow_memory_arch(DR_PARAM_IN umbra_map_t *map,
DR_PARAM_INOUT app_pc *app_addr,
DR_PARAM_IN size_t app_size,
DR_PARAM_IN ptr_uint_t value,
DR_PARAM_IN size_t value_size,
DR_PARAM_OUT bool *found)
{
/* i#1260: end pointers are all closed (i.e., inclusive) to handle overflow */
app_pc app_blk_base, app_blk_end, app_src_end;
app_pc start, end;
size_t iter_size;
byte *shadow_start, *shadow_addr;
ptr_uint_t val;
size_t valsz, shadow_size;
if (value > USHRT_MAX || value_size != 1)
return DRMF_ERROR_NOT_IMPLEMENTED;
if (POINTER_OVERFLOW_ON_ADD(*app_addr, app_size-1)) /* just hitting top is ok */
return DRMF_ERROR_INVALID_SIZE;
*found = false;
APP_RANGE_LOOP(*app_addr, app_size, app_blk_base, app_blk_end, app_src_end,
start, end, iter_size, {
if (shadow_table_use_default_block(map, app_blk_base))
return DRMF_ERROR_INVALID_PARAMETER;
if (shadow_table_use_special_block(map, app_blk_base, &val, &valsz)) {
if (val == value && valsz == value_size) {
*app_addr = start;
*found = true;
return DRMF_SUCCESS;
}
continue;
}
shadow_start = shadow_table_app_to_shadow(map, start);
shadow_size = umbra_map_scale_app_to_shadow(map, iter_size);
shadow_addr = memchr(shadow_start, (int)value, shadow_size);
if (shadow_addr != NULL) {
*app_addr = start +
umbra_map_scale_shadow_to_app(map, shadow_addr - shadow_start);
*found = true;
return DRMF_SUCCESS;
}
});
return DRMF_SUCCESS;
}
drmf_status_t
umbra_insert_app_to_shadow_arch(void *drcontext,
umbra_map_t *map,
instrlist_t *ilist,
instr_t *where,
reg_id_t addr_reg,
reg_id_t *scratch_regs,
int num_scratch_regs)
{
if (num_scratch_regs < umbra_num_scratch_regs_for_translation_arch() ||
scratch_regs == NULL)
return DRMF_ERROR_INVALID_PARAMETER;
shadow_table_insert_app_to_shadow_arch(drcontext, map, ilist, where,
addr_reg, scratch_regs[0]);
return DRMF_SUCCESS;
}
drmf_status_t
umbra_iterate_shadow_memory_arch(umbra_map_t *map,
void *user_data,
shadow_iterate_func_t iter_func)
{
uint i;
umbra_shadow_memory_info_t info;
for (i = 0; i < SHADOW_TABLE_ENTRIES; i++) {
info.app_base = (app_pc)SHADOW_TABLE_APP_BASE(i);
info.shadow_base = shadow_table_app_to_shadow(map, info.app_base);
if (shadow_table_is_in_default_block(map, info.shadow_base, NULL))
continue;
info.app_size = map->app_block_size;
info.shadow_size = map->shadow_block_size;
if (shadow_table_is_in_special_block(map, info.shadow_base,
NULL, NULL, NULL))
info.shadow_type = UMBRA_SHADOW_MEMORY_TYPE_SHARED;
else
info.shadow_type = UMBRA_SHADOW_MEMORY_TYPE_NORMAL;
if (!iter_func(map, &info, user_data))
break;
}
return DRMF_SUCCESS;
}
drmf_status_t
umbra_shadow_memory_is_shared_arch(DR_PARAM_IN umbra_map_t *map,
DR_PARAM_IN byte *shadow_addr,
DR_PARAM_OUT umbra_shadow_memory_type_t *shadow_type)
{
bool redzone;
if (shadow_table_is_in_special_block(map, shadow_addr,
NULL, NULL, &redzone)) {
*shadow_type = UMBRA_SHADOW_MEMORY_TYPE_SHARED;
if (redzone)
*shadow_type |= UMBRA_SHADOW_MEMORY_TYPE_REDZONE;
return DRMF_SUCCESS;
} else
*shadow_type = UMBRA_SHADOW_MEMORY_TYPE_UNKNOWN;
return DRMF_SUCCESS;
}
drmf_status_t
umbra_get_shadow_memory_type_arch(umbra_map_t *map,
byte *shadow_addr,
umbra_shadow_memory_type_t *shadow_type)
{
uint i;
bool redzone;
byte *shadow_blk, *shadow_end;
umbra_shadow_memory_is_shared_arch(map, shadow_addr, shadow_type);
if (shadow_table_is_in_default_block(map, shadow_addr, &redzone)) {
*shadow_type = UMBRA_SHADOW_MEMORY_TYPE_SHADOW_NOT_ALLOC;
if (redzone)
*shadow_type |= UMBRA_SHADOW_MEMORY_TYPE_REDZONE;
return DRMF_SUCCESS;
}
if (TEST(UMBRA_SHADOW_MEMORY_TYPE_SHARED, *shadow_type))
return DRMF_SUCCESS;
if (umbra_address_is_app_memory(shadow_addr)) {
*shadow_type = UMBRA_SHADOW_MEMORY_TYPE_UNKNOWN;
return DRMF_SUCCESS;
}
/* now we have to walk the whole table */
for (i = 0; i < SHADOW_TABLE_ENTRIES; i++) {
shadow_blk = shadow_table_get_block(map, i);
shadow_end = shadow_blk + map->shadow_block_size;
if (shadow_addr >= shadow_blk - map->options.redzone_size &&
shadow_addr < shadow_end + map->options.redzone_size) {
*shadow_type = UMBRA_SHADOW_MEMORY_TYPE_NORMAL;
if (shadow_addr < shadow_blk || shadow_addr >= shadow_end)
*shadow_type |= UMBRA_SHADOW_MEMORY_TYPE_REDZONE;
return DRMF_SUCCESS;
}
}
*shadow_type = UMBRA_SHADOW_MEMORY_TYPE_NOT_SHADOW;
return DRMF_SUCCESS;
}
drmf_status_t
umbra_get_shadow_memory_arch(umbra_map_t *map,
app_pc app_addr,
byte **shadow_addr,
umbra_shadow_memory_info_t *shadow_info)
{
if (shadow_addr != NULL)
*shadow_addr = shadow_table_app_to_shadow(map, app_addr);
if (shadow_info != NULL) {
shadow_info->app_base = (app_pc)ALIGN_BACKWARD(app_addr,
map->app_block_size);
shadow_info->app_size = map->app_block_size;
shadow_info->shadow_size = map->shadow_block_size;
shadow_info->shadow_base =
shadow_table_app_to_shadow(map, shadow_info->app_base);
if (shadow_table_use_default_block(map, app_addr)) {
shadow_info->shadow_type =
UMBRA_SHADOW_MEMORY_TYPE_SHADOW_NOT_ALLOC;
return DRMF_SUCCESS;
}
if (shadow_table_use_special_block(map, app_addr, NULL, NULL)) {
shadow_info->shadow_type = UMBRA_SHADOW_MEMORY_TYPE_SHARED;
return DRMF_SUCCESS;
}
shadow_info->shadow_type = UMBRA_SHADOW_MEMORY_TYPE_NORMAL;
return DRMF_SUCCESS;
}
return DRMF_SUCCESS;
}
bool
umbra_address_is_app_memory(app_pc pc)
{
/* in x86, shadow memory is allocated within DR */
if (dr_memory_is_dr_internal(pc) || dr_memory_is_in_client(pc))
return false;
return true;
}
drmf_status_t
umbra_replace_shared_shadow_memory_arch(umbra_map_t *map,
app_pc app_addr,
byte **shadow_addr)
{
shadow_table_replace_block(map, app_addr);
*shadow_addr = shadow_table_app_to_shadow(map, app_addr);
return DRMF_SUCCESS;
}
drmf_status_t
umbra_create_shared_shadow_block_arch(DR_PARAM_IN umbra_map_t *map,
DR_PARAM_IN ptr_uint_t value,
DR_PARAM_IN size_t value_size,
DR_PARAM_OUT byte **block)
{
*block = shadow_table_create_special_block(map, value, value_size);
if (*block == NULL)
return DRMF_ERROR_NOMEM;
return DRMF_SUCCESS;
}
drmf_status_t
umbra_get_shared_shadow_block_arch(DR_PARAM_IN umbra_map_t *map,
DR_PARAM_IN ptr_uint_t value,
DR_PARAM_IN size_t value_size,
DR_PARAM_OUT byte **block)
{
uint i;
for (i = 0; i < map->num_special_blocks; i++) {
if (map->special_blocks[i].value == value &&
map->special_blocks[i].value_size == value_size) {
*block = map->special_blocks[i].start;
return DRMF_SUCCESS;
}
}
return DRMF_SUCCESS;
}
bool
umbra_handle_fault(void *drcontext, byte *target, dr_mcontext_t *raw_mc,
dr_mcontext_t *mc, dr_fault_fragment_info_t *info)
{
return false;
}
drmf_status_t
umbra_clear_redundant_blocks(umbra_map_t *map, uint *count)
{
byte *shadow_data;
uint i, j;
if (map == NULL)
return DRMF_ERROR_INVALID_PARAMETER;
if (count != NULL)
*count = 0;
/* Umbra needs to have create-on-touch optimization enabled. */
if (!TEST(UMBRA_MAP_CREATE_SHADOW_ON_TOUCH, map->options.flags)) {
return DRMF_ERROR_INVALID_PARAMETER;
}
umbra_map_lock(map);
for (i = 0; i < SHADOW_TABLE_ENTRIES; i++) {
shadow_data = shadow_table_get_block(map, i);
/* Redundant blocks must be "normal". */
if (!shadow_table_is_in_normal_block(map, shadow_data))
continue;
/* Check whether the entire block consists of default values. */
bool is_all_default_val = true;
for (j = 0; j < map->shadow_block_size; j++) {
if (shadow_data[j] != (byte)map->options.default_value) {
is_all_default_val = false;
break;
}
}
/* Delete block only if it consists of default values. */
if (!is_all_default_val)
continue;
byte *special_block = shadow_table_lookup_special_block(
map, map->options.default_value, map->options.default_value_size);
ASSERT(special_block[0] == (byte)map->options.default_value,
"default vals not in synch");
/* Delete block and set entry to refer to special block. */
shadow_table_delete_block(map, shadow_data);
shadow_table_set_block(map, i, special_block);
if (count != NULL)
(*count)++;
}
umbra_map_unlock(map);
return DRMF_SUCCESS;
}