blob: 975d143d48cb9e5b5b727da13ebd4a8b45716866 [file] [log] [blame]
/* **********************************************************
* Copyright (c) 2011-2014 Google, Inc. All rights reserved.
* Copyright (c) 2000-2010 VMware, Inc. All rights reserved.
* **********************************************************/
/*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of VMware, Inc. nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL VMWARE, INC. OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*/
/* Copyright (c) 2003-2007 Determina Corp. */
/* Copyright (c) 2001-2003 Massachusetts Institute of Technology */
/* Copyright (c) 2000-2001 Hewlett-Packard Company */
/* file "opnd_shared.c" -- IR opnd utilities */
#include "../globals.h"
#include "opnd.h"
#include "arch.h"
/* FIXME i#1551: refactor this file and avoid this x86-specific include in base arch/ */
#include "x86/decode_private.h"
#include <string.h> /* for memcpy */
#if defined(DEBUG) && !defined(STANDALONE_DECODER)
/* case 10450: give messages to clients */
/* we can't undef ASSERT b/c of DYNAMO_OPTION */
# undef ASSERT_TRUNCATE
# undef ASSERT_BITFIELD_TRUNCATE
# undef ASSERT_NOT_REACHED
# define ASSERT_TRUNCATE DO_NOT_USE_ASSERT_USE_CLIENT_ASSERT_INSTEAD
# define ASSERT_BITFIELD_TRUNCATE DO_NOT_USE_ASSERT_USE_CLIENT_ASSERT_INSTEAD
# define ASSERT_NOT_REACHED DO_NOT_USE_ASSERT_USE_CLIENT_ASSERT_INSTEAD
#endif
#undef opnd_is_null
#undef opnd_is_immed_int
#undef opnd_is_immed_float
#undef opnd_is_near_pc
#undef opnd_is_near_instr
#undef opnd_is_reg
#undef opnd_is_base_disp
#undef opnd_is_far_pc
#undef opnd_is_far_instr
#undef opnd_is_mem_instr
#undef opnd_is_valid
bool opnd_is_null (opnd_t op) { return OPND_IS_NULL(op); }
bool opnd_is_immed_int (opnd_t op) { return OPND_IS_IMMED_INT(op); }
bool opnd_is_immed_float(opnd_t op) { return OPND_IS_IMMED_FLOAT(op); }
bool opnd_is_near_pc (opnd_t op) { return OPND_IS_NEAR_PC(op); }
bool opnd_is_near_instr (opnd_t op) { return OPND_IS_NEAR_INSTR(op); }
bool opnd_is_reg (opnd_t op) { return OPND_IS_REG(op); }
bool opnd_is_base_disp (opnd_t op) { return OPND_IS_BASE_DISP(op); }
bool opnd_is_far_pc (opnd_t op) { return OPND_IS_FAR_PC(op); }
bool opnd_is_far_instr (opnd_t op) { return OPND_IS_FAR_INSTR(op); }
bool opnd_is_mem_instr (opnd_t op) { return OPND_IS_MEM_INSTR(op); }
bool opnd_is_valid (opnd_t op) { return OPND_IS_VALID(op); }
#define opnd_is_null OPND_IS_NULL
#define opnd_is_immed_int OPND_IS_IMMED_INT
#define opnd_is_immed_float OPND_IS_IMMED_FLOAT
#define opnd_is_near_pc OPND_IS_NEAR_PC
#define opnd_is_near_instr OPND_IS_NEAR_INSTR
#define opnd_is_reg OPND_IS_REG
#define opnd_is_base_disp OPND_IS_BASE_DISP
#define opnd_is_far_pc OPND_IS_FAR_PC
#define opnd_is_far_instr OPND_IS_FAR_INSTR
#define opnd_is_mem_instr OPND_IS_MEM_INSTR
#define opnd_is_valid OPND_IS_VALID
#ifdef X64
# undef opnd_is_rel_addr
bool opnd_is_rel_addr(opnd_t op) { return OPND_IS_REL_ADDR(op); }
# define opnd_is_rel_addr OPND_IS_REL_ADDR
#endif
/* We allow overlap between ABS_ADDR_kind and BASE_DISP_kind w/ no base or index */
bool
opnd_is_abs_base_disp(opnd_t opnd) {
return (opnd_is_base_disp(opnd) && opnd_get_base(opnd) == REG_NULL &&
opnd_get_index(opnd) == REG_NULL);
}
bool opnd_is_abs_addr(opnd_t opnd) {
return IF_X64(opnd.kind == ABS_ADDR_kind ||) opnd_is_abs_base_disp(opnd);
}
bool opnd_is_near_abs_addr(opnd_t opnd) {
return opnd_is_abs_addr(opnd) IF_X86(&& opnd.aux.segment == REG_NULL);
}
bool opnd_is_far_abs_addr(opnd_t opnd) {
return IF_X86_ELSE(opnd_is_abs_addr(opnd) && opnd.aux.segment != REG_NULL,
false);
}
bool
opnd_is_vsib(opnd_t op)
{
return (opnd_is_base_disp(op) && reg_is_xmm(opnd_get_index(op)));
}
bool
opnd_is_reg_32bit(opnd_t opnd)
{
if (opnd_is_reg(opnd))
return reg_is_32bit(opnd_get_reg(opnd));
return false;
}
bool
reg_is_32bit(reg_id_t reg)
{
return (reg >= REG_START_32 && reg <= REG_STOP_32);
}
bool
opnd_is_reg_64bit(opnd_t opnd)
{
if (opnd_is_reg(opnd))
return reg_is_64bit(opnd_get_reg(opnd));
return false;
}
bool
reg_is_64bit(reg_id_t reg)
{
return (reg >= REG_START_64 && reg <= REG_STOP_64);
}
bool
opnd_is_reg_pointer_sized(opnd_t opnd)
{
if (opnd_is_reg(opnd))
return reg_is_pointer_sized(opnd_get_reg(opnd));
return false;
}
bool
opnd_is_reg_partial(opnd_t opnd)
{
return (opnd_is_reg(opnd) && opnd.size != 0 &&
opnd_get_size(opnd) != reg_get_size(opnd_get_reg(opnd)));
}
bool
reg_is_pointer_sized(reg_id_t reg)
{
#ifdef X64
return (reg >= REG_START_64 && reg <= REG_STOP_64);
#else
return (reg >= REG_START_32 && reg <= REG_STOP_32);
#endif
}
#undef opnd_get_reg
reg_id_t
opnd_get_reg(opnd_t opnd)
{
return OPND_GET_REG(opnd);
}
#define opnd_get_reg OPND_GET_REG
#undef opnd_get_flags
dr_opnd_flags_t
opnd_get_flags(opnd_t opnd)
{
return OPND_GET_FLAGS(opnd);
}
#define opnd_get_flags OPND_GET_FLAGS
opnd_size_t
opnd_get_size(opnd_t opnd)
{
switch(opnd.kind) {
case REG_kind:
return (opnd.size == 0 ? reg_get_size(opnd_get_reg(opnd)) : opnd.size);
case IMMED_INTEGER_kind:
case IMMED_FLOAT_kind:
case BASE_DISP_kind:
#ifdef X64
case REL_ADDR_kind:
case ABS_ADDR_kind:
#endif
case MEM_INSTR_kind:
case INSTR_kind:
return opnd.size;
case PC_kind:
return OPSZ_PTR;
case FAR_PC_kind:
case FAR_INSTR_kind:
return OPSZ_6_irex10_short4;
case NULL_kind:
return OPSZ_NA;
default:
CLIENT_ASSERT(false, "opnd_get_size: unknown opnd type");
return OPSZ_NA;
}
}
void
opnd_set_size(opnd_t *opnd, opnd_size_t newsize)
{
switch(opnd->kind) {
case IMMED_INTEGER_kind:
case BASE_DISP_kind:
#ifdef X64
case REL_ADDR_kind:
case ABS_ADDR_kind:
#endif
case REG_kind:
case MEM_INSTR_kind:
case INSTR_kind:
opnd->size = newsize;
return;
default:
CLIENT_ASSERT(false, "opnd_set_size: unknown opnd type");
}
}
/* immediate operands */
opnd_t
opnd_create_immed_int(ptr_int_t i, opnd_size_t size)
{
opnd_t opnd;
opnd.kind = IMMED_INTEGER_kind;
CLIENT_ASSERT(size < OPSZ_LAST_ENUM, "opnd_create_immed_int: invalid size");
opnd.size = size;
opnd.value.immed_int = i;
DOCHECK(1, {
uint sz = opnd_size_in_bytes(size);
if (sz == 1) {
CLIENT_ASSERT(CHECK_TRUNCATE_TYPE_sbyte(i),
"opnd_create_immed_int: value too large for 8-bit size");
} else if (sz == 2) {
CLIENT_ASSERT(CHECK_TRUNCATE_TYPE_short(i),
"opnd_create_immed_int: value too large for 16-bit size");
} else if (sz == 4) {
CLIENT_ASSERT(CHECK_TRUNCATE_TYPE_int(i),
"opnd_create_immed_int: value too large for 32-bit size");
}
});
return opnd;
}
/* NOTE: requires caller to be under PRESERVE_FLOATING_POINT_STATE */
opnd_t
opnd_create_immed_float(float i)
{
opnd_t opnd;
opnd.kind = IMMED_FLOAT_kind;
/* note that manipulating floats is dangerous - see case 4360
* even this copy can end up using fp load/store instrs and could
* trigger a pending fp exception (i#386)
*/
opnd.value.immed_float = i;
/* currently only used for implicit constants that have no size */
opnd.size = OPSZ_0;
return opnd;
}
opnd_t
opnd_create_immed_float_for_opcode(uint opcode)
{
opnd_t opnd;
uint float_value;
opnd.kind = IMMED_FLOAT_kind;
/* avoid any fp instrs (xref i#386) */
float_value = opnd_immed_float_arch(opcode);
*(uint*)(&opnd.value.immed_float) = float_value;
/* currently only used for implicit constants that have no size */
opnd.size = OPSZ_0;
return opnd;
}
ptr_int_t
opnd_get_immed_int(opnd_t opnd)
{
CLIENT_ASSERT(opnd_is_immed_int(opnd), "opnd_get_immed_int called on non-immed-int");
return opnd.value.immed_int;
}
/* NOTE: requires caller to be under PRESERVE_FLOATING_POINT_STATE */
float
opnd_get_immed_float(opnd_t opnd)
{
CLIENT_ASSERT(opnd_is_immed_float(opnd),
"opnd_get_immed_float called on non-immed-float");
/* note that manipulating floats is dangerous - see case 4360
* this return shouldn't require any fp state, though
*/
return opnd.value.immed_float;
}
/* address operands */
/* N.B.: seg_selector is a segment selector, not a SEG_ constant */
opnd_t
opnd_create_far_pc(ushort seg_selector, app_pc pc)
{
opnd_t opnd;
opnd.kind = FAR_PC_kind;
opnd.aux.far_pc_seg_selector = seg_selector;
opnd.value.pc = pc;
return opnd;
}
opnd_t
opnd_create_instr_ex(instr_t *instr, opnd_size_t size, ushort shift)
{
opnd_t opnd;
opnd.kind = INSTR_kind;
opnd.value.instr = instr;
opnd.aux.shift = shift;
opnd.size = size;
return opnd;
}
opnd_t
opnd_create_instr(instr_t *instr)
{
return opnd_create_instr_ex(instr, OPSZ_PTR, 0);
}
opnd_t
opnd_create_far_instr(ushort seg_selector, instr_t *instr)
{
opnd_t opnd;
opnd.kind = FAR_INSTR_kind;
opnd.aux.far_pc_seg_selector = seg_selector;
opnd.value.instr = instr;
return opnd;
}
DR_API
opnd_t
opnd_create_mem_instr(instr_t *instr, short disp, opnd_size_t data_size)
{
opnd_t opnd;
opnd.kind = MEM_INSTR_kind;
opnd.size = data_size;
opnd.aux.disp = disp;
opnd.value.instr = instr;
return opnd;
}
app_pc
opnd_get_pc(opnd_t opnd)
{
if (opnd_is_pc(opnd))
return opnd.value.pc;
else {
SYSLOG_INTERNAL_ERROR("opnd type is %d", opnd.kind);
CLIENT_ASSERT(false, "opnd_get_pc called on non-pc");
return NULL;
}
}
ushort
opnd_get_segment_selector(opnd_t opnd)
{
if (opnd_is_far_pc(opnd) || opnd_is_far_instr(opnd)) {
return opnd.aux.far_pc_seg_selector;
}
CLIENT_ASSERT(false, "opnd_get_segment_selector called on invalid opnd type");
return REG_INVALID;
}
instr_t *
opnd_get_instr(opnd_t opnd)
{
CLIENT_ASSERT(opnd_is_instr(opnd) || opnd_is_mem_instr(opnd),
"opnd_get_instr called on non-instr");
return opnd.value.instr;
}
DR_API
ushort
opnd_get_shift(opnd_t opnd)
{
CLIENT_ASSERT(opnd_is_near_instr(opnd), "opnd_get_shift called on non-near-instr");
return opnd.aux.shift;
}
short
opnd_get_mem_instr_disp(opnd_t opnd)
{
CLIENT_ASSERT(opnd_is_mem_instr(opnd),
"opnd_get_mem_instr_disp called on non-mem-instr");
return opnd.aux.disp;
}
/* Base+displacement+scaled index operands */
opnd_t
opnd_create_base_disp_ex(reg_id_t base_reg, reg_id_t index_reg, int scale, int disp,
opnd_size_t size, bool encode_zero_disp, bool force_full_disp,
bool disp_short_addr)
{
return opnd_create_far_base_disp_ex(REG_NULL, base_reg, index_reg, scale, disp, size,
encode_zero_disp, force_full_disp,
disp_short_addr);
}
opnd_t
opnd_create_base_disp(reg_id_t base_reg, reg_id_t index_reg, int scale, int disp,
opnd_size_t size)
{
return opnd_create_far_base_disp_ex(REG_NULL, base_reg, index_reg, scale, disp, size,
false, false, false);
}
static inline void
opnd_set_disp_helper(opnd_t *opnd, int disp)
{
IF_X86_ELSE({
opnd->value.base_disp.disp = disp;
}, {
if (disp < 0) {
opnd->aux.flags |= DR_OPND_SHIFTED;
opnd->value.base_disp.disp = -disp;
} else
opnd->value.base_disp.disp = disp;
});
}
opnd_t
opnd_create_far_base_disp_ex(reg_id_t seg, reg_id_t base_reg, reg_id_t index_reg,
int scale, int disp, opnd_size_t size,
bool encode_zero_disp, bool force_full_disp,
bool disp_short_addr)
{
opnd_t opnd;
opnd.kind = BASE_DISP_kind;
CLIENT_ASSERT(size < OPSZ_LAST_ENUM, "opnd_create_*base_disp*: invalid size");
opnd.size = size;
CLIENT_ASSERT(scale == 0 || scale == 1 || scale == 2 || scale == 4 || scale == 8,
"opnd_create_*base_disp*: invalid scale");
IF_X86(CLIENT_ASSERT(index_reg == REG_NULL || scale > 0,
"opnd_create_*base_disp*: index requires scale"));
CLIENT_ASSERT(seg == REG_NULL
IF_X86(|| (seg >= REG_START_SEGMENT && seg <= REG_STOP_SEGMENT)),
"opnd_create_*base_disp*: invalid segment");
CLIENT_ASSERT(base_reg <= REG_LAST_ENUM, "opnd_create_*base_disp*: invalid base");
CLIENT_ASSERT(index_reg <= REG_LAST_ENUM, "opnd_create_*base_disp*: invalid index");
CLIENT_ASSERT_BITFIELD_TRUNCATE(SCALE_SPECIFIER_BITS, scale,
"opnd_create_*base_disp*: invalid scale");
/* reg_id_t is now a ushort, but we can only accept low values */
CLIENT_ASSERT_BITFIELD_TRUNCATE(REG_SPECIFIER_BITS, base_reg,
"opnd_create_*base_disp*: invalid base");
CLIENT_ASSERT_BITFIELD_TRUNCATE(REG_SPECIFIER_BITS, index_reg,
"opnd_create_*base_disp*: invalid index");
IF_X86_ELSE({
opnd.aux.segment = seg;
}, {
opnd.value.base_disp.shift_type = DR_SHIFT_NONE;
CLIENT_ASSERT(disp == 0 || index_reg == REG_NULL,
"opnd_create_*base_disp*: cannot have both disp and index");
});
opnd.value.base_disp.base_reg = base_reg;
opnd.value.base_disp.index_reg = index_reg;
IF_X86_ELSE({
opnd.value.base_disp.scale = (byte) scale;
}, {
if (scale > 1) {
opnd.value.base_disp.shift_type = DR_SHIFT_LSL;
opnd.value.base_disp.shift_amount_minus_1 =
/* we store the amount minus one */
(scale == 2 ? 0 : (scale == 4 ? 1 : 2));
}
});
opnd_set_disp_helper(&opnd, disp);
opnd.value.base_disp.encode_zero_disp = (byte) encode_zero_disp;
opnd.value.base_disp.force_full_disp = (byte) force_full_disp;
opnd.value.base_disp.disp_short_addr = (byte) disp_short_addr;
return opnd;
}
opnd_t
opnd_create_far_base_disp(reg_id_t seg, reg_id_t base_reg, reg_id_t index_reg, int scale,
int disp, opnd_size_t size)
{
return opnd_create_far_base_disp_ex(seg, base_reg, index_reg, scale, disp, size,
false, false, false);
}
opnd_t
opnd_create_base_disp_arm(reg_id_t base_reg, reg_id_t index_reg,
dr_shift_type_t shift_type, uint shift_amount, int disp,
dr_opnd_flags_t flags, opnd_size_t size)
{
opnd_t opnd;
opnd.kind = BASE_DISP_kind;
CLIENT_ASSERT(size < OPSZ_LAST_ENUM, "opnd_create_*base_disp*: invalid size");
opnd.size = size;
CLIENT_ASSERT(disp == 0 || index_reg == REG_NULL,
"opnd_create_base_disp_arm: cannot have both disp and index");
CLIENT_ASSERT(base_reg <= REG_LAST_ENUM, "opnd_create_base_disp_arm: invalid base");
CLIENT_ASSERT(index_reg <= REG_LAST_ENUM, "opnd_create_base_disp_arm: invalid index");
/* reg_id_t is now a ushort, but we can only accept low values */
CLIENT_ASSERT_BITFIELD_TRUNCATE(REG_SPECIFIER_BITS, base_reg,
"opnd_create_base_disp_arm: invalid base");
CLIENT_ASSERT_BITFIELD_TRUNCATE(REG_SPECIFIER_BITS, index_reg,
"opnd_create_base_disp_arm: invalid index");
opnd.value.base_disp.base_reg = base_reg;
opnd.value.base_disp.index_reg = index_reg;
opnd_set_disp_helper(&opnd, disp);
/* Set the flags before the shift as the shift will change the flags */
opnd.aux.flags = flags;
if (!opnd_set_index_shift(&opnd, shift_type, shift_amount))
CLIENT_ASSERT(false, "opnd_create_base_disp_arm: invalid shift type/amount");
opnd.value.base_disp.encode_zero_disp = 0;
opnd.value.base_disp.force_full_disp = 0;
opnd.value.base_disp.disp_short_addr = 0;
return opnd;
}
#undef opnd_get_base
#undef opnd_get_disp
#undef opnd_get_index
#undef opnd_get_scale
#undef opnd_get_segment
reg_id_t opnd_get_base (opnd_t opnd) { return OPND_GET_BASE(opnd); }
int opnd_get_disp (opnd_t opnd) { return OPND_GET_DISP(opnd); }
reg_id_t opnd_get_index (opnd_t opnd) { return OPND_GET_INDEX(opnd); }
int opnd_get_scale (opnd_t opnd) { return OPND_GET_SCALE(opnd); }
reg_id_t opnd_get_segment(opnd_t opnd) { return OPND_GET_SEGMENT(opnd); }
#define opnd_get_base OPND_GET_BASE
#define opnd_get_disp OPND_GET_DISP
#define opnd_get_index OPND_GET_INDEX
#define opnd_get_scale OPND_GET_SCALE
#define opnd_get_segment OPND_GET_SEGMENT
dr_shift_type_t
opnd_get_index_shift(opnd_t opnd, uint *amount OUT)
{
if (!opnd_is_base_disp(opnd)) {
CLIENT_ASSERT(false, "opnd_get_index_shift called on invalid opnd type");
return DR_SHIFT_NONE;
}
if (amount != NULL && opnd.value.base_disp.shift_type != DR_SHIFT_NONE)
*amount = opnd.value.base_disp.shift_amount_minus_1 + 1;
return opnd.value.base_disp.shift_type;
}
bool
opnd_set_index_shift(opnd_t *opnd, dr_shift_type_t shift, uint amount)
{
if (!opnd_is_base_disp(*opnd)) {
CLIENT_ASSERT(false, "opnd_set_index_shift called on invalid opnd type");
return false;
}
switch (shift) {
case DR_SHIFT_NONE:
if (amount != 0) {
/* Called from opnd_create_base_disp_arm() so we have a generic msg */
CLIENT_ASSERT(false, "opnd index shift: invalid shift amount");
return false;
}
break;
case DR_SHIFT_LSL:
case DR_SHIFT_ROR:
if (amount < 1 || amount > 31) {
CLIENT_ASSERT(false, "opnd index shift: invalid shift amount");
return false;
}
opnd->value.base_disp.shift_amount_minus_1 = (byte)amount - 1;
break;
case DR_SHIFT_LSR:
case DR_SHIFT_ASR:
if (amount < 1 || amount > 32) {
CLIENT_ASSERT(false, "opnd index shift: invalid shift amount");
return false;
}
opnd->value.base_disp.shift_amount_minus_1 = (byte)amount - 1;
break;
case DR_SHIFT_RRX:
if (amount != 1) {
CLIENT_ASSERT(false, "opnd index shift: invalid shift amount");
return false;
}
opnd->value.base_disp.shift_amount_minus_1 = (byte)amount - 1;
break;
default:
CLIENT_ASSERT(false, "opnd index shift: invalid shift type");
return false;
}
if (shift == DR_SHIFT_NONE)
opnd->aux.flags &= ~DR_OPND_SHIFTED;
else
opnd->aux.flags |= DR_OPND_SHIFTED;
opnd->value.base_disp.shift_type = shift;
return true;
}
bool
opnd_is_disp_encode_zero(opnd_t opnd)
{
if (opnd_is_base_disp(opnd))
return opnd.value.base_disp.encode_zero_disp;
CLIENT_ASSERT(false, "opnd_is_disp_encode_zero called on invalid opnd type");
return false;
}
bool
opnd_is_disp_force_full(opnd_t opnd)
{
if (opnd_is_base_disp(opnd))
return opnd.value.base_disp.force_full_disp;
CLIENT_ASSERT(false, "opnd_is_disp_force_full called on invalid opnd type");
return false;
}
bool
opnd_is_disp_short_addr(opnd_t opnd)
{
if (opnd_is_base_disp(opnd))
return opnd.value.base_disp.disp_short_addr;
CLIENT_ASSERT(false, "opnd_is_disp_short_addr called on invalid opnd type");
return false;
}
void
opnd_set_disp(opnd_t *opnd, int disp)
{
if (opnd_is_base_disp(*opnd))
opnd_set_disp_helper(opnd, disp);
else
CLIENT_ASSERT(false, "opnd_set_disp called on invalid opnd type");
}
void
opnd_set_disp_ex(opnd_t *opnd, int disp, bool encode_zero_disp, bool force_full_disp,
bool disp_short_addr)
{
if (opnd_is_base_disp(*opnd)) {
opnd->value.base_disp.encode_zero_disp = (byte) encode_zero_disp;
opnd->value.base_disp.force_full_disp = (byte) force_full_disp;
opnd->value.base_disp.disp_short_addr = (byte) disp_short_addr;
opnd_set_disp_helper(opnd, disp);
} else
CLIENT_ASSERT(false, "opnd_set_disp_ex called on invalid opnd type");
}
opnd_t
opnd_create_abs_addr(void *addr, opnd_size_t data_size)
{
return opnd_create_far_abs_addr(REG_NULL, addr, data_size);
}
opnd_t
opnd_create_far_abs_addr(reg_id_t seg, void *addr, opnd_size_t data_size)
{
/* PR 253327: For x64, there's no way to create 0xa0-0xa3 w/ addr
* prefix since we'll make a base-disp instead: but our IR is
* supposed to be at a higher abstraction level anyway, though w/
* the sib byte the base-disp ends up being one byte longer.
*/
if (IF_X64_ELSE((ptr_uint_t)addr <= UINT_MAX, true)) {
bool need_addr32 = false;
CLIENT_ASSERT(CHECK_TRUNCATE_TYPE_uint((ptr_uint_t)addr),
"internal error: abs addr too large");
#ifdef X64
/* To reach the high 2GB of the lower 4GB we need the addr32 prefix */
if ((ptr_uint_t)addr > INT_MAX)
need_addr32 = X64_MODE_DC(get_thread_private_dcontext());
#endif
return opnd_create_far_base_disp_ex(seg, REG_NULL, REG_NULL, 0,
(int)(ptr_int_t)addr, data_size,
false, false, need_addr32);
}
#ifdef X64
else {
opnd_t opnd;
opnd.kind = ABS_ADDR_kind;
CLIENT_ASSERT(data_size < OPSZ_LAST_ENUM, "opnd_create_base_disp: invalid size");
opnd.size = data_size;
CLIENT_ASSERT(seg == REG_NULL
IF_X86(|| (seg >= REG_START_SEGMENT && seg <= REG_STOP_SEGMENT)),
"opnd_create_far_abs_addr: invalid segment");
IF_X86(opnd.aux.segment = seg);
opnd.value.addr = addr;
return opnd;
}
#endif
}
#if defined(X64) || defined(ARM)
opnd_t
opnd_create_rel_addr(void *addr, opnd_size_t data_size)
{
return opnd_create_far_rel_addr(REG_NULL, addr, data_size);
}
/* PR 253327: We represent rip-relative w/ an address-size prefix
* (i.e., 32 bits instead of 64) as simply having the top 32 bits of
* "addr" zeroed out. This means that we never encode an address
* prefix, and we if one already exists in the raw bits we have to go
* looking for it at encode time.
*/
opnd_t
opnd_create_far_rel_addr(reg_id_t seg, void *addr, opnd_size_t data_size)
{
opnd_t opnd;
opnd.kind = REL_ADDR_kind;
CLIENT_ASSERT(data_size < OPSZ_LAST_ENUM, "opnd_create_base_disp: invalid size");
opnd.size = data_size;
CLIENT_ASSERT(seg == REG_NULL
IF_X86(|| (seg >= REG_START_SEGMENT && seg <= REG_STOP_SEGMENT)),
"opnd_create_far_rel_addr: invalid segment");
IF_X86(opnd.aux.segment = seg);
opnd.value.addr = addr;
return opnd;
}
#endif /* X64 || ARM */
void *
opnd_get_addr(opnd_t opnd)
{
/* check base-disp first since opnd_is_abs_addr() says yes for it */
if (opnd_is_abs_base_disp(opnd))
return (void *)(ptr_int_t) opnd_get_disp(opnd);
#ifdef X64
if (opnd_is_rel_addr(opnd) || opnd_is_abs_addr(opnd))
return opnd.value.addr;
#endif
CLIENT_ASSERT(false, "opnd_get_addr called on invalid opnd type");
return NULL;
}
bool
opnd_is_memory_reference(opnd_t opnd)
{
return (opnd_is_base_disp(opnd)
IF_X64(|| opnd_is_abs_addr(opnd) || opnd_is_rel_addr(opnd)) ||
opnd_is_mem_instr(opnd));
}
bool
opnd_is_far_memory_reference(opnd_t opnd)
{
return (opnd_is_far_base_disp(opnd)
IF_X64(|| opnd_is_far_abs_addr(opnd) || opnd_is_far_rel_addr(opnd)));
}
bool
opnd_is_near_memory_reference(opnd_t opnd)
{
return (opnd_is_near_base_disp(opnd)
IF_X64(|| opnd_is_near_abs_addr(opnd) || opnd_is_near_rel_addr(opnd)) ||
opnd_is_mem_instr(opnd));
}
int
opnd_num_regs_used(opnd_t opnd)
{
switch (opnd.kind) {
case NULL_kind:
case IMMED_INTEGER_kind:
case IMMED_FLOAT_kind:
case PC_kind:
case FAR_PC_kind:
case INSTR_kind:
case FAR_INSTR_kind:
case MEM_INSTR_kind:
return 0;
case REG_kind:
return 1;
case BASE_DISP_kind:
return (((opnd_get_base(opnd)==REG_NULL) ? 0 : 1) +
((opnd_get_index(opnd)==REG_NULL) ? 0 : 1) +
((opnd_get_segment(opnd)==REG_NULL) ? 0 : 1));
#ifdef X64
case REL_ADDR_kind:
case ABS_ADDR_kind:
return ((opnd_get_segment(opnd) == REG_NULL) ? 0 : 1);
#endif
default:
CLIENT_ASSERT(false, "opnd_num_regs_used called on invalid opnd type");
return 0;
}
}
reg_id_t
opnd_get_reg_used(opnd_t opnd, int index)
{
switch (opnd.kind) {
case NULL_kind:
case IMMED_INTEGER_kind:
case IMMED_FLOAT_kind:
case PC_kind:
case FAR_PC_kind:
case MEM_INSTR_kind:
CLIENT_ASSERT(false, "opnd_get_reg_used called on invalid opnd type");
return REG_NULL;
case REG_kind:
if (index == 0)
return opnd_get_reg(opnd);
else {
CLIENT_ASSERT(false, "opnd_get_reg_used called on invalid opnd type");
return REG_NULL;
}
case BASE_DISP_kind:
if (index == 0) {
if (opnd_get_base(opnd) != REG_NULL)
return opnd_get_base(opnd);
else if (opnd_get_index(opnd) != REG_NULL)
return opnd_get_index(opnd);
else
return opnd_get_segment(opnd);
} else if (index == 1) {
if (opnd_get_index(opnd) != REG_NULL)
return opnd_get_index(opnd);
else
return opnd_get_segment(opnd);
} else if (index == 2)
return opnd_get_segment(opnd);
else {
CLIENT_ASSERT(false, "opnd_get_reg_used called on invalid opnd type");
return REG_NULL;
}
#ifdef X64
case REL_ADDR_kind:
case ABS_ADDR_kind:
if (index == 0)
return opnd_get_segment(opnd);
else {
/* We only assert if beyond the number possible: not if beyond the
* number present. Should we assert on the latter?
*/
CLIENT_ASSERT(false, "opnd_get_reg_used called on invalid opnd type");
return REG_NULL;
}
#endif
default:
CLIENT_ASSERT(false, "opnd_get_reg_used called on invalid opnd type");
return REG_NULL;
}
}
/***************************************************************************/
/* utility routines */
const reg_id_t regparms[] = {
#ifdef X64
REGPARM_0, REGPARM_1, REGPARM_2, REGPARM_3,
# ifdef UNIX
REGPARM_4, REGPARM_5,
# endif
#endif
REG_INVALID
};
/*
opnd_uses_reg is now changed so that it does consider 8/16 bit
register overlaps. i think this change is OK and correct, but not
sure. as far as I'm aware, only my optimization stuff and the
register stealing code (which is now not used, right?) relies on
this code ==> but we now export it via CI API */
bool
opnd_uses_reg(opnd_t opnd, reg_id_t reg)
{
if (reg == REG_NULL)
return false;
switch (opnd.kind) {
case NULL_kind:
case IMMED_INTEGER_kind:
case IMMED_FLOAT_kind:
case PC_kind:
case FAR_PC_kind:
case INSTR_kind:
case FAR_INSTR_kind:
case MEM_INSTR_kind:
return false;
case REG_kind:
return (dr_reg_fixer[reg] == dr_reg_fixer[opnd_get_reg(opnd)]);
case BASE_DISP_kind:
return (dr_reg_fixer[reg] == dr_reg_fixer[opnd_get_base(opnd)] ||
dr_reg_fixer[reg] == dr_reg_fixer[opnd_get_index(opnd)] ||
dr_reg_fixer[reg] == dr_reg_fixer[opnd_get_segment(opnd)]);
#ifdef X64
case REL_ADDR_kind:
case ABS_ADDR_kind:
return (dr_reg_fixer[reg] == dr_reg_fixer[opnd_get_segment(opnd)]);
#endif
default:
CLIENT_ASSERT(false, "opnd_uses_reg: unknown opnd type");
return false;
}
}
bool
opnd_replace_reg(opnd_t *opnd, reg_id_t old_reg, reg_id_t new_reg)
{
switch (opnd->kind) {
case NULL_kind:
case IMMED_INTEGER_kind:
case IMMED_FLOAT_kind:
case PC_kind:
case FAR_PC_kind:
case INSTR_kind:
case FAR_INSTR_kind:
case MEM_INSTR_kind:
return false;
case REG_kind:
if (old_reg == opnd_get_reg(*opnd)) {
*opnd = opnd_create_reg(new_reg);
return true;
}
return false;
case BASE_DISP_kind:
{
reg_id_t ob = opnd_get_base(*opnd);
reg_id_t oi = opnd_get_index(*opnd);
reg_id_t os = opnd_get_segment(*opnd);
opnd_size_t size = opnd_get_size(*opnd);
if (old_reg == ob || old_reg == oi || old_reg == os) {
reg_id_t b = (old_reg == ob) ? new_reg : ob;
reg_id_t i = (old_reg == oi) ? new_reg : oi;
reg_id_t s = (old_reg == os) ? new_reg : os;
int sc = opnd_get_scale(*opnd);
int d = opnd_get_disp(*opnd);
*opnd = opnd_create_far_base_disp_ex(s, b, i, sc, d, size,
opnd_is_disp_encode_zero(*opnd),
opnd_is_disp_force_full(*opnd),
opnd_is_disp_short_addr(*opnd));
return true;
}
}
return false;
#ifdef X64
case REL_ADDR_kind:
if (old_reg == opnd_get_segment(*opnd)) {
*opnd = opnd_create_far_rel_addr(new_reg, opnd_get_addr(*opnd),
opnd_get_size(*opnd));
return true;
}
return false;
case ABS_ADDR_kind:
if (old_reg == opnd_get_segment(*opnd)) {
*opnd = opnd_create_far_abs_addr(new_reg, opnd_get_addr(*opnd),
opnd_get_size(*opnd));
return true;
}
return false;
#endif
default:
CLIENT_ASSERT(false, "opnd_replace_reg: invalid opnd type");
return false;
}
}
/* this is not conservative -- only considers two memory references to
* be the same if their constituent components (registers, displacement)
* are the same.
* different from opnd_same b/c this routine ignores data size!
*/
bool opnd_same_address(opnd_t op1, opnd_t op2)
{
if (op1.kind != op2.kind)
return false;
if (!opnd_is_memory_reference(op1) || !opnd_is_memory_reference(op2))
return false;
if (opnd_get_segment(op1) != opnd_get_segment(op2))
return false;
if (opnd_is_base_disp(op1)) {
if (!opnd_is_base_disp(op2))
return false;
if (opnd_get_base(op1) != opnd_get_base(op2))
return false;
if (opnd_get_index(op1) != opnd_get_index(op2))
return false;
if (opnd_get_scale(op1) != opnd_get_scale(op2))
return false;
if (opnd_get_disp(op1) != opnd_get_disp(op2))
return false;
} else {
#ifdef X64
CLIENT_ASSERT(opnd_is_abs_addr(op1) || opnd_is_rel_addr(op1),
"internal type error in opnd_same_address");
if (opnd_get_addr(op1) != opnd_get_addr(op2))
return false;
#else
CLIENT_ASSERT(false, "internal type error in opnd_same_address");
#endif
}
/* we ignore size */
return true;
}
bool opnd_same(opnd_t op1, opnd_t op2)
{
if (op1.kind != op2.kind)
return false;
else if (!opnd_same_sizes_ok(opnd_get_size(op1), opnd_get_size(op2),
opnd_is_reg(op1)) &&
(opnd_is_immed_int(op1) ||
opnd_is_reg(op1) ||
opnd_is_memory_reference(op1)))
return false;
/* If we could rely on unused bits being 0 could avoid dispatch on type.
* Presumably not on critical path, though, so not bothering to try and
* asssert that those bits are 0.
*/
switch (op1.kind) {
case NULL_kind:
return true;
case IMMED_INTEGER_kind:
return op1.value.immed_int == op2.value.immed_int;
case IMMED_FLOAT_kind:
/* avoid any fp instrs (xref i#386) */
return *(int*)(&op1.value.immed_float) == *(int*)(&op2.value.immed_float);
case PC_kind:
return op1.value.pc == op2.value.pc;
case FAR_PC_kind:
return (op1.aux.far_pc_seg_selector == op2.aux.far_pc_seg_selector &&
op1.value.pc == op2.value.pc);
case INSTR_kind:
return (op1.value.instr == op2.value.instr &&
op1.aux.shift == op2.aux.shift &&
op1.size == op2.size);
case FAR_INSTR_kind:
return op1.value.instr == op2.value.instr;
case REG_kind:
return op1.value.reg == op2.value.reg;
case BASE_DISP_kind:
return (IF_X86(op1.aux.segment == op2.aux.segment &&)
op1.value.base_disp.base_reg == op2.value.base_disp.base_reg &&
op1.value.base_disp.index_reg == op2.value.base_disp.index_reg &&
IF_X86_ELSE(op1.value.base_disp.scale ==
op2.value.base_disp.scale &&,
op1.value.base_disp.shift_type ==
op2.value.base_disp.shift_type &&
op1.value.base_disp.shift_amount_minus_1 ==
op2.value.base_disp.shift_amount_minus_1 &&)
op1.value.base_disp.disp == op2.value.base_disp.disp &&
op1.value.base_disp.encode_zero_disp ==
op2.value.base_disp.encode_zero_disp &&
op1.value.base_disp.force_full_disp ==
op2.value.base_disp.force_full_disp &&
/* disp_short_addr only matters if no registers are set */
(((op1.value.base_disp.base_reg != REG_NULL ||
op1.value.base_disp.index_reg != REG_NULL) &&
(op2.value.base_disp.base_reg != REG_NULL ||
op2.value.base_disp.index_reg != REG_NULL)) ||
op1.value.base_disp.disp_short_addr ==
op2.value.base_disp.disp_short_addr));
#ifdef X64
case REL_ADDR_kind:
case ABS_ADDR_kind:
return (IF_X86(op1.aux.segment == op2.aux.segment &&)
op1.value.addr == op2.value.addr);
#endif
case MEM_INSTR_kind:
return (op1.value.instr == op2.value.instr &&
op1.aux.disp == op2.aux.disp);
default:
CLIENT_ASSERT(false, "opnd_same: invalid opnd type");
return false;
}
}
bool opnd_share_reg(opnd_t op1, opnd_t op2)
{
switch (op1.kind) {
case NULL_kind:
case IMMED_INTEGER_kind:
case IMMED_FLOAT_kind:
case PC_kind:
case FAR_PC_kind:
case INSTR_kind:
case FAR_INSTR_kind:
case MEM_INSTR_kind:
return false;
case REG_kind:
return opnd_uses_reg(op2, opnd_get_reg(op1));
case BASE_DISP_kind:
return (opnd_uses_reg(op2, opnd_get_base(op1)) ||
opnd_uses_reg(op2, opnd_get_index(op1)) ||
opnd_uses_reg(op2, opnd_get_segment(op1)));
#ifdef X64
case REL_ADDR_kind:
case ABS_ADDR_kind:
return (opnd_uses_reg(op2, opnd_get_segment(op1)));
#endif
default:
CLIENT_ASSERT(false, "opnd_share_reg: invalid opnd type");
return false;
}
}
static bool
range_overlap(ptr_uint_t a1, ptr_uint_t a2, size_t s1, size_t s2)
{
ptr_uint_t min, max;
size_t min_plus;
if (a1 < a2) {
min = a1;
min_plus = s1;
max = a2;
} else {
min = a2;
min_plus = s2;
max = a1;
}
return (min + min_plus > max); /* open-ended */
}
/* Returns true if def, considered as a write, affects use.
* Is conservative, so if both def and use are memory references,
* will return true unless it can disambiguate them.
*/
bool opnd_defines_use(opnd_t def, opnd_t use)
{
switch (def.kind) {
case NULL_kind:
case IMMED_INTEGER_kind:
case IMMED_FLOAT_kind:
case PC_kind:
case FAR_PC_kind:
case INSTR_kind:
case FAR_INSTR_kind:
return false;
case REG_kind:
return opnd_uses_reg(use, opnd_get_reg(def));
case BASE_DISP_kind:
if (!opnd_is_memory_reference(use))
return false;
#ifdef X64
if (!opnd_is_base_disp(use))
return true;
#endif
/* try to disambiguate the two memory references
* for now, only consider identical regs and different disp
*/
if (opnd_get_base(def) != opnd_get_base(use))
return true;
if (opnd_get_index(def) != opnd_get_index(use))
return true;
if (opnd_get_scale(def) != opnd_get_scale(use))
return true;
if (opnd_get_segment(def) != opnd_get_segment(use))
return true;
/* everything is identical, now make sure disps don't overlap */
return range_overlap(opnd_get_disp(def), opnd_get_disp(use),
opnd_size_in_bytes(opnd_get_size(def)),
opnd_size_in_bytes(opnd_get_size(use)));
#ifdef X64
case REL_ADDR_kind:
case ABS_ADDR_kind:
if (!opnd_is_memory_reference(use))
return false;
if (opnd_is_base_disp(use))
return true;
if (opnd_get_segment(def) != opnd_get_segment(use))
return true;
return range_overlap((ptr_uint_t)opnd_get_addr(def),
(ptr_uint_t)opnd_get_addr(use),
opnd_size_in_bytes(opnd_get_size(def)),
opnd_size_in_bytes(opnd_get_size(use)));
#endif
case MEM_INSTR_kind:
if (!opnd_is_memory_reference(use))
return false;
/* we don't know our address so we have to assume true */
return true;
default:
CLIENT_ASSERT(false, "opnd_defines_use: invalid opnd type");
return false;
}
}
uint
opnd_size_in_bytes(opnd_size_t size)
{
/* allow some REG_ constants, convert them to OPSZ_ constants */
if (size < OPSZ_FIRST)
size = reg_get_size(size);
switch (size) {
case OPSZ_0:
return 0;
case OPSZ_1:
case OPSZ_1_reg4: /* mem size */
case OPSZ_1_of_8:
case OPSZ_1_of_16:
case OPSZ_1b: /* round up */
case OPSZ_2b:
case OPSZ_3b:
case OPSZ_4b:
case OPSZ_5b:
case OPSZ_6b:
return 1;
case OPSZ_2_of_8:
case OPSZ_2_of_16:
case OPSZ_2_short1: /* default size */
case OPSZ_2:
case OPSZ_2_reg4: /* mem size */
case OPSZ_12b: /* round up */
return 2;
case OPSZ_3:
return 3;
case OPSZ_4_of_8:
case OPSZ_4_of_16:
case OPSZ_4_rex8_of_16:
case OPSZ_4_short2: /* default size */
#ifndef X64
case OPSZ_4x8: /* default size */
case OPSZ_4x8_short2: /* default size */
case OPSZ_4x8_short2xi8: /* default size */
#endif
case OPSZ_4_short2xi4: /* default size */
case OPSZ_4_rex8_short2: /* default size */
case OPSZ_4_rex8:
case OPSZ_4:
case OPSZ_4_reg16: /* mem size */
case OPSZ_25b: /* round up */
return 4;
case OPSZ_6_irex10_short4: /* default size */
case OPSZ_6:
return 6;
case OPSZ_8_of_16:
case OPSZ_8_of_16_vex32:
case OPSZ_8_short2:
case OPSZ_8_short4:
case OPSZ_8:
#ifdef X64
case OPSZ_4x8: /* default size */
case OPSZ_4x8_short2: /* default size */
case OPSZ_4x8_short2xi8: /* default size */
#endif
case OPSZ_8_rex16: /* default size */
case OPSZ_8_rex16_short4: /* default size */
return 8;
case OPSZ_16:
case OPSZ_16_vex32:
case OPSZ_16_of_32:
return 16;
case OPSZ_6x10:
/* table base + limit; w/ addr16, different format, but same total footprint */
return IF_X64_ELSE(6, 10);
case OPSZ_10:
return 10;
case OPSZ_12:
case OPSZ_12_of_16:
case OPSZ_12_rex8_of_16:
case OPSZ_12_rex40_short6: /* default size */
return 12;
case OPSZ_14_of_16:
case OPSZ_14:
return 14;
case OPSZ_15_of_16:
case OPSZ_15:
return 15;
case OPSZ_20:
return 20;
case OPSZ_24:
return 24;
case OPSZ_28_short14: /* default size */
case OPSZ_28:
return 28;
case OPSZ_32:
case OPSZ_32_short16: /* default size */
return 32;
case OPSZ_36:
return 36;
case OPSZ_40:
return 40;
case OPSZ_44:
return 44;
case OPSZ_48:
return 48;
case OPSZ_52:
return 52;
case OPSZ_56:
return 56;
case OPSZ_60:
return 60;
case OPSZ_64:
return 64;
case OPSZ_94:
return 94;
case OPSZ_108_short94: /* default size */
case OPSZ_108:
return 108;
case OPSZ_512:
return 512;
case OPSZ_xsave:
return 0; /* > 512 bytes: use cpuid to determine */
default:
CLIENT_ASSERT(false, "opnd_size_in_bytes: invalid opnd type");
return 0;
}
}
DR_API
uint
opnd_size_in_bits(opnd_size_t size)
{
switch (size) {
case OPSZ_1b: return 1;
case OPSZ_2b: return 2;
case OPSZ_3b: return 3;
case OPSZ_4b: return 4;
case OPSZ_5b: return 5;
case OPSZ_6b: return 6;
case OPSZ_12b: return 12;
case OPSZ_25b: return 25;
default: return opnd_size_in_bytes(size) * 8;
}
}
DR_API
opnd_size_t
opnd_size_from_bytes(uint bytes)
{
switch (bytes) {
case 0: return OPSZ_0;
case 1: return OPSZ_1;
case 2: return OPSZ_2;
case 3: return OPSZ_3;
case 4: return OPSZ_4;
case 6: return OPSZ_6;
case 8: return OPSZ_8;
case 10: return OPSZ_10;
case 12: return OPSZ_12;
case 14: return OPSZ_14;
case 15: return OPSZ_15;
case 16: return OPSZ_16;
case 20: return OPSZ_20;
case 24: return OPSZ_24;
case 28: return OPSZ_28;
case 32: return OPSZ_32;
case 36: return OPSZ_36;
case 40: return OPSZ_40;
case 44: return OPSZ_44;
case 48: return OPSZ_48;
case 52: return OPSZ_52;
case 56: return OPSZ_56;
case 60: return OPSZ_60;
case 64: return OPSZ_64;
case 94: return OPSZ_94;
case 108: return OPSZ_108;
case 512: return OPSZ_512;
default: return OPSZ_NA;
}
}
/* shrinks all 32-bit registers in opnd to 16 bits. also shrinks the size of
* immed ints and mem refs from OPSZ_4 to OPSZ_2.
*/
opnd_t
opnd_shrink_to_16_bits(opnd_t opnd)
{
int i;
for (i=0; i<opnd_num_regs_used(opnd); i++) {
reg_id_t reg = opnd_get_reg_used(opnd, i);
if (reg >= REG_START_32 && reg <= REG_STOP_32) {
opnd_replace_reg(&opnd, reg, reg_32_to_16(reg));
}
}
if ((opnd_is_immed_int(opnd) || opnd_is_memory_reference(opnd)) &&
opnd_get_size(opnd) == OPSZ_4) /* OPSZ_*_short2 will shrink at encode time */
opnd_set_size(&opnd, OPSZ_2);
return opnd;
}
#ifdef X64
/* shrinks all 64-bit registers in opnd to 32 bits. also shrinks the size of
* immed ints and mem refs from OPSZ_8 to OPSZ_4.
*/
opnd_t
opnd_shrink_to_32_bits(opnd_t opnd)
{
int i;
for (i=0; i<opnd_num_regs_used(opnd); i++) {
reg_id_t reg = opnd_get_reg_used(opnd, i);
if (reg >= REG_START_64 && reg <= REG_STOP_64) {
opnd_replace_reg(&opnd, reg, reg_64_to_32(reg));
}
}
if ((opnd_is_immed_int(opnd) || opnd_is_memory_reference(opnd)) &&
opnd_get_size(opnd) == OPSZ_8)
opnd_set_size(&opnd, OPSZ_4);
return opnd;
}
#endif
static reg_t
reg_get_value_helper(reg_id_t reg, priv_mcontext_t *mc)
{
CLIENT_ASSERT(reg_is_pointer_sized(reg),
"reg_get_value_helper(): internal error non-ptr sized reg");
if (reg == REG_NULL)
return 0;
return *(reg_t *)((byte *)mc + opnd_get_reg_mcontext_offs(reg));
}
/* Returns the value of the register reg, selected from the passed-in
* register values.
*/
reg_t
reg_get_value_priv(reg_id_t reg, priv_mcontext_t *mc)
{
if (reg == REG_NULL)
return 0;
#ifdef X64
if (reg >= REG_START_64 && reg <= REG_STOP_64)
return reg_get_value_helper(reg, mc);
if (reg >= REG_START_32 && reg <= REG_STOP_32) {
reg_t val = reg_get_value_helper(dr_reg_fixer[reg], mc);
return (val & 0x00000000ffffffff);
}
#else
if (reg >= REG_START_32 && reg <= REG_STOP_32) {
return reg_get_value_helper(reg, mc);
}
#endif
#ifdef X86
if (reg >= REG_START_8 && reg <= REG_STOP_8) {
reg_t val = reg_get_value_helper(dr_reg_fixer[reg], mc);
if (reg >= REG_AH && reg <= REG_BH)
return ((val & 0x0000ff00) >> 8);
else /* all others are the lower 8 bits */
return (val & 0x000000ff);
}
if (reg >= REG_START_16 && reg <= REG_STOP_16) {
reg_t val = reg_get_value_helper(dr_reg_fixer[reg], mc);
return (val & 0x0000ffff);
}
#endif
/* mmx and segment cannot be part of address.
* xmm/ymm can with VSIB, but we'd have to either return a larger type,
* or take in an offset within the xmm/ymm register -- so we leave this
* routine supporting only GPR and have a separate routine for VSIB
* (opnd_compute_VSIB_index()).
* if want to use this routine for more than just effective address
* calculations, need to pass in mmx/xmm state, or need to grab it
* here. would then need to check dr_mcontext_t.size.
*/
CLIENT_ASSERT(false, "reg_get_value: unsupported register");
return 0;
}
DR_API
reg_t
reg_get_value(reg_id_t reg, dr_mcontext_t *mc)
{
/* only supports GPRs so we ignore mc.size */
return reg_get_value_priv(reg, dr_mcontext_as_priv_mcontext(mc));
}
DR_API
/* Supports all but floating-point */
bool
reg_get_value_ex(reg_id_t reg, dr_mcontext_t *mc, OUT byte *val)
{
#ifdef X86
if (reg >= DR_REG_START_MMX && reg <= DR_REG_STOP_MMX) {
get_mmx_val((uint64 *)val, reg - DR_REG_START_MMX);
} else if (reg >= DR_REG_START_XMM && reg <= DR_REG_STOP_XMM) {
if (!TEST(DR_MC_MULTIMEDIA, mc->flags) || mc->size != sizeof(dr_mcontext_t))
return false;
memcpy(val, &mc->ymm[reg - DR_REG_START_XMM], XMM_REG_SIZE);
} else if (reg >= DR_REG_START_YMM && reg <= DR_REG_STOP_YMM) {
if (!TEST(DR_MC_MULTIMEDIA, mc->flags) || mc->size != sizeof(dr_mcontext_t))
return false;
memcpy(val, &mc->ymm[reg - DR_REG_START_YMM], YMM_REG_SIZE);
} else {
reg_t regval = reg_get_value(reg, mc);
*(reg_t *)val = regval;
}
#else
CLIENT_ASSERT(false, "NYI i#1551");
#endif
return true;
}
/* Sets the register reg in the passed in mcontext to value. Currently only works
* with ptr sized registers. FIXME - handle other sized registers. */
void
reg_set_value_priv(reg_id_t reg, priv_mcontext_t *mc, reg_t value)
{
CLIENT_ASSERT(reg_is_pointer_sized(reg),
"reg_get_value_helper(): internal error non-ptr sized reg");
if (reg == REG_NULL)
return;
*(reg_t *)((byte *)mc + opnd_get_reg_mcontext_offs(reg)) = value;
}
DR_API
void
reg_set_value(reg_id_t reg, dr_mcontext_t *mc, reg_t value)
{
/* only supports GPRs so we ignore mc.size */
reg_set_value_priv(reg, dr_mcontext_as_priv_mcontext(mc), value);
}
/* helper for sharing w/ VSIB computations */
app_pc
opnd_compute_address_helper(opnd_t opnd, priv_mcontext_t *mc, ptr_int_t scaled_index)
{
reg_id_t base;
int disp;
app_pc seg_base = NULL;
app_pc addr = NULL;
CLIENT_ASSERT(opnd_is_memory_reference(opnd),
"opnd_compute_address: must pass memory reference");
if (opnd_is_far_base_disp(opnd)) {
#ifdef X86
# ifdef STANDALONE_DECODER
seg_base = NULL; /* not supported */
# else
seg_base = get_app_segment_base(opnd_get_segment(opnd));
if (seg_base == (app_pc) POINTER_MAX) /* failure */
seg_base = NULL;
# endif
#endif
}
#ifdef X64
if (opnd_is_abs_addr(opnd) || opnd_is_rel_addr(opnd)) {
return (app_pc) opnd_get_addr(opnd) + (ptr_uint_t) seg_base;
}
#endif
addr = seg_base;
base = opnd_get_base(opnd);
disp = opnd_get_disp(opnd);
logopnd(get_thread_private_dcontext(), 4, opnd, "opnd_compute_address for");
addr += reg_get_value_priv(base, mc);
LOG(THREAD_GET, LOG_ALL, 4, "\tbase => "PFX"\n", addr);
addr += scaled_index;
LOG(THREAD_GET, LOG_ALL, 4, "\tindex,scale => "PFX"\n", addr);
addr += disp;
LOG(THREAD_GET, LOG_ALL, 4, "\tdisp => "PFX"\n", addr);
return addr;
}
/* Returns the effective address of opnd, computed using the passed-in
* register values. If opnd is a far address, ignores that aspect
* except for TLS references on Windows (fs: for 32-bit, gs: for 64-bit)
* or typical fs: or gs: references on Linux. For far addresses the
* calling thread's segment selector is used.
*
* XXX: this does not support VSIB. All callers should really be switched to
* use instr_compute_address_ex_priv().
*/
app_pc
opnd_compute_address_priv(opnd_t opnd, priv_mcontext_t *mc)
{
ptr_int_t scaled_index = 0;
if (opnd_is_base_disp(opnd)) {
reg_id_t index = opnd_get_index(opnd);
ptr_int_t scale = opnd_get_scale(opnd);
scaled_index = scale * reg_get_value_priv(index, mc);
}
return opnd_compute_address_helper(opnd, mc, scaled_index);
}
DR_API
app_pc
opnd_compute_address(opnd_t opnd, dr_mcontext_t *mc)
{
/* only uses GPRs so we ignore mc.size */
return opnd_compute_address_priv(opnd, dr_mcontext_as_priv_mcontext(mc));
}
/***************************************************************************
*** Register utility functions
***************************************************************************/
const char *
get_register_name(reg_id_t reg)
{
return reg_names[reg];
}
reg_id_t
reg_to_pointer_sized(reg_id_t reg)
{
return dr_reg_fixer[reg];
}
reg_id_t
reg_32_to_16(reg_id_t reg)
{
#ifdef X86
CLIENT_ASSERT(reg >= REG_START_32 && reg <= REG_STOP_32,
"reg_32_to_16: passed non-32-bit reg");
return (reg - REG_START_32) + REG_START_16;
#elif defined(ARM)
CLIENT_ASSERT(false, "reg_32_to_8 not supported on ARM");
return REG_NULL;
#endif
}
reg_id_t
reg_32_to_8(reg_id_t reg)
{
#ifdef X86
reg_id_t r8;
CLIENT_ASSERT(reg >= REG_START_32 && reg <= REG_STOP_32,
"reg_32_to_16: passed non-32-bit reg");
r8 = (reg - REG_START_32) + REG_START_8;
if (r8 >= REG_START_x86_8 && r8 <= REG_STOP_x86_8) {
# ifdef X64
r8 += (REG_START_x64_8 - REG_START_x86_8);
# else
r8 = REG_NULL;
# endif
}
return r8;
#elif defined(ARM)
CLIENT_ASSERT(false, "reg_32_to_8 not supported on ARM");
return REG_NULL;
#endif
}
#ifdef X64
reg_id_t
reg_32_to_64(reg_id_t reg)
{
CLIENT_ASSERT(reg >= REG_START_32 && reg <= REG_STOP_32,
"reg_32_to_64: passed non-32-bit reg");
return (reg - REG_START_32) + REG_START_64;
}
reg_id_t
reg_64_to_32(reg_id_t reg)
{
CLIENT_ASSERT(reg >= REG_START_64 && reg <= REG_STOP_64,
"reg_64_to_32: passed non-64-bit reg");
return (reg - REG_START_64) + REG_START_32;
}
bool
reg_is_extended(reg_id_t reg)
{
/* Note that we do consider spl, bpl, sil, and dil to be "extended" */
return ((reg >= REG_START_64+8 && reg <= REG_STOP_64) ||
(reg >= REG_START_32+8 && reg <= REG_STOP_32) ||
(reg >= REG_START_16+8 && reg <= REG_STOP_16) ||
(reg >= REG_START_8+8 && reg <= REG_STOP_8) ||
(reg >= REG_START_x64_8 && reg <= REG_STOP_x64_8) ||
(reg >= REG_START_XMM+8 && reg <= REG_STOP_XMM) ||
(reg >= REG_START_YMM+8 && reg <= REG_STOP_YMM) ||
(reg >= REG_START_DR+8 && reg <= REG_STOP_DR) ||
(reg >= REG_START_CR+8 && reg <= REG_STOP_CR));
}
#endif
reg_id_t
reg_32_to_opsz(reg_id_t reg, opnd_size_t sz)
{
CLIENT_ASSERT(reg >= REG_START_32 && reg <= REG_STOP_32,
"reg_32_to_opsz: passed non-32-bit reg");
if (sz == OPSZ_4)
return reg;
else if (sz == OPSZ_2)
return reg_32_to_16(reg);
else if (sz == OPSZ_1)
return reg_32_to_8(reg);
#ifdef X64
else if (sz == OPSZ_8)
return reg_32_to_64(reg);
#endif
else
CLIENT_ASSERT(false, "reg_32_to_opsz: invalid size parameter");
return reg;
}
reg_id_t
reg_resize_to_opsz(reg_id_t reg, opnd_size_t sz)
{
CLIENT_ASSERT(reg_is_gpr(reg), "reg_resize_to_opsz: passed non GPR reg");
reg = reg_to_pointer_sized(reg);
return reg_32_to_opsz(IF_X64_ELSE(reg_64_to_32(reg), reg), sz);
}
int
reg_parameter_num(reg_id_t reg)
{
int r;
for (r = 0; r < NUM_REGPARM; r++) {
if (reg == regparms[r])
return r;
}
return -1;
}
int
opnd_get_reg_dcontext_offs(reg_id_t reg)
{
switch (reg) {
#ifdef X86
case REG_XAX: return XAX_OFFSET;
case REG_XBX: return XBX_OFFSET;
case REG_XCX: return XCX_OFFSET;
case REG_XDX: return XDX_OFFSET;
case REG_XSP: return XSP_OFFSET;
case REG_XBP: return XBP_OFFSET;
case REG_XSI: return XSI_OFFSET;
case REG_XDI: return XDI_OFFSET;
# ifdef X64
case REG_R8: return R8_OFFSET;
case REG_R9: return R9_OFFSET;
case REG_R10: return R10_OFFSET;
case REG_R11: return R11_OFFSET;
case REG_R12: return R12_OFFSET;
case REG_R13: return R13_OFFSET;
case REG_R14: return R14_OFFSET;
case REG_R15: return R15_OFFSET;
# endif
#endif
default: CLIENT_ASSERT(false, "opnd_get_reg_dcontext_offs: invalid reg");
return -1;
}
}
int
opnd_get_reg_mcontext_offs(reg_id_t reg)
{
return opnd_get_reg_dcontext_offs(reg) - MC_OFFS;
}
bool
reg_overlap(reg_id_t r1, reg_id_t r2)
{
if (r1 == REG_NULL || r2 == REG_NULL)
return false;
#ifdef X86
/* The XH registers do NOT overlap with the XL registers; else, the
* dr_reg_fixer is the answer.
*/
if ((r1 >= REG_START_8HL && r1 <= REG_STOP_8HL) &&
(r2 >= REG_START_8HL && r2 <= REG_STOP_8HL) &&
r1 != r2)
return false;
#endif
return (dr_reg_fixer[r1] == dr_reg_fixer[r2]);
}
/* returns the register's representation as 3 bits in a modrm byte,
* callers do not expect it to fail
*/
enum {REG_INVALID_BITS = 0x0}; /* returns a valid register nevertheless */
byte
reg_get_bits(reg_id_t reg)
{
#ifdef X86
# ifdef X64
if (reg >= REG_START_64 && reg <= REG_STOP_64)
return (byte) ((reg - REG_START_64) % 8);
# endif
if (reg >= REG_START_32 && reg <= REG_STOP_32)
return (byte) ((reg - REG_START_32) % 8);
if (reg >= REG_START_8 && reg <= REG_R15L)
return (byte) ((reg - REG_START_8) % 8);
# ifdef X64
if (reg >= REG_START_x64_8 && reg <= REG_STOP_x64_8) /* alternates to AH-BH */
return (byte) ((reg - REG_START_x64_8 + 4) % 8);
# endif
if (reg >= REG_START_16 && reg <= REG_STOP_16)
return (byte) ((reg - REG_START_16) % 8);
if (reg >= REG_START_MMX && reg <= REG_STOP_MMX)
return (byte) ((reg - REG_START_MMX) % 8);
if (reg >= REG_START_XMM && reg <= REG_STOP_XMM)
return (byte) ((reg - REG_START_XMM) % 8);
if (reg >= REG_START_YMM && reg <= REG_STOP_YMM)
return (byte) ((reg - REG_START_YMM) % 8);
if (reg >= REG_START_SEGMENT && reg <= REG_STOP_SEGMENT)
return (byte) ((reg - REG_START_SEGMENT) % 8);
if (reg >= REG_START_DR && reg <= REG_STOP_DR)
return (byte) ((reg - REG_START_DR) % 8);
if (reg >= REG_START_CR && reg <= REG_STOP_CR)
return (byte) ((reg - REG_START_CR) % 8);
#else
CLIENT_ASSERT(false, "i#1551: NYI");
#endif
CLIENT_ASSERT(false, "reg_get_bits: invalid register");
return REG_INVALID_BITS; /* callers don't expect a failure - return some value */
}
/* returns the OPSZ_ field appropriate for the register */
opnd_size_t
reg_get_size(reg_id_t reg)
{
#ifdef X64
if (reg >= REG_START_64 && reg <= REG_STOP_64)
return OPSZ_8;
#endif
if (reg >= REG_START_32 && reg <= REG_STOP_32)
return OPSZ_4;
#ifdef X86
if (reg >= REG_START_8 && reg <= REG_STOP_8)
return OPSZ_1;
#endif
#if defined(X86) && defined(X64)
if (reg >= REG_START_x64_8 && reg <= REG_STOP_x64_8) /* alternates to AH-BH */
return OPSZ_1;
#endif
#ifdef X86
if (reg >= REG_START_16 && reg <= REG_STOP_16)
return OPSZ_2;
if (reg >= REG_START_MMX && reg <= REG_STOP_MMX)
return OPSZ_8;
if (reg >= REG_START_XMM && reg <= REG_STOP_XMM)
return OPSZ_16;
if (reg >= REG_START_YMM && reg <= REG_STOP_YMM)
return OPSZ_32;
if (reg >= REG_START_SEGMENT && reg <= REG_STOP_SEGMENT)
return OPSZ_2;
if (reg >= REG_START_DR && reg <= REG_STOP_DR)
return IF_X64_ELSE(OPSZ_8, OPSZ_4);
if (reg >= REG_START_CR && reg <= REG_STOP_CR)
return IF_X64_ELSE(OPSZ_8, OPSZ_4);
/* i#176 add reg size handling for floating point registers */
if (reg >= REG_START_FLOAT && reg <= REG_STOP_FLOAT)
return OPSZ_10;
#elif defined(ARM)
if (reg >= DR_REG_Q0 && reg <= DR_REG_Q31)
return OPSZ_16;
if (reg >= DR_REG_D0 && reg <= DR_REG_D31)
return OPSZ_8;
if (reg >= DR_REG_S0 && reg <= DR_REG_S31)
return OPSZ_4;
if (reg >= DR_REG_H0 && reg <= DR_REG_H31)
return OPSZ_2;
if (reg >= DR_REG_B0 && reg <= DR_REG_B31)
return OPSZ_1;
if (reg >= DR_REG_CR0 && reg <= DR_REG_CR15)
return OPSZ_PTR;
#endif
CLIENT_ASSERT(false, "reg_get_size: invalid register");
return OPSZ_NA;
}
#ifndef STANDALONE_DECODER
/****************************************************************************/
/* dcontext convenience routines */
static opnd_t
dcontext_opnd_common(dcontext_t *dcontext, bool absolute, reg_id_t basereg,
int offs, opnd_size_t size)
{
IF_X64(ASSERT_NOT_IMPLEMENTED(!absolute));
/* offs is not raw offset, but includes upcontext size, so we
* can tell unprotected from normal
*/
if (TEST(SELFPROT_DCONTEXT, dynamo_options.protect_mask) &&
offs < sizeof(unprotected_context_t)) {
/* FIXME i#1551: what's the default reg on ARM? */
IF_ARM(ASSERT(absolute || basereg != REG_NULL));
return opnd_create_base_disp(absolute ? REG_NULL :
(IF_X86((basereg == REG_NULL) ? REG_XSI :) basereg),
REG_NULL, 0,
((int)(ptr_int_t)(absolute ?
dcontext->upcontext.separate_upcontext : 0))
+ offs, size);
} else {
if (offs >= sizeof(unprotected_context_t))
offs -= sizeof(unprotected_context_t);
/* FIXME i#1551: what's the default reg on ARM? */
IF_ARM(ASSERT(absolute || basereg != REG_NULL));
return opnd_create_base_disp(absolute ? REG_NULL :
(IF_X86((basereg == REG_NULL) ? REG_XDI :) basereg),
REG_NULL, 0,
((int)(ptr_int_t)
(absolute ? dcontext : 0)) + offs, size);
}
}
opnd_t
opnd_create_dcontext_field_sz(dcontext_t *dcontext, int offs, opnd_size_t sz)
{
return dcontext_opnd_common(dcontext, true, REG_NULL, offs, sz);
}
opnd_t
opnd_create_dcontext_field(dcontext_t *dcontext, int offs)
{
return dcontext_opnd_common(dcontext, true, REG_NULL, offs, OPSZ_PTR);
}
/* use basereg==REG_NULL to get default (xdi, or xsi for upcontext) */
opnd_t
opnd_create_dcontext_field_via_reg_sz(dcontext_t *dcontext, reg_id_t basereg,
int offs, opnd_size_t sz)
{
return dcontext_opnd_common(dcontext, false, basereg, offs, sz);
}
/* use basereg==REG_NULL to get default (xdi, or xsi for upcontext) */
opnd_t
opnd_create_dcontext_field_via_reg(dcontext_t *dcontext, reg_id_t basereg, int offs)
{
return dcontext_opnd_common(dcontext, false, basereg, offs, OPSZ_PTR);
}
opnd_t
opnd_create_dcontext_field_byte(dcontext_t *dcontext, int offs)
{
return dcontext_opnd_common(dcontext, true, REG_NULL, offs, OPSZ_1);
}
opnd_t
update_dcontext_address(opnd_t op, dcontext_t *old_dcontext,
dcontext_t *new_dcontext)
{
int offs;
CLIENT_ASSERT(opnd_is_near_base_disp(op) &&
opnd_get_base(op) == REG_NULL &&
opnd_get_index(op) == REG_NULL, "update_dcontext_address: invalid opnd");
IF_X64(ASSERT_NOT_IMPLEMENTED(false));
offs = opnd_get_disp(op) - (uint)(ptr_uint_t)old_dcontext;
if (offs >= 0 && offs < sizeof(dcontext_t)) {
/* don't pass raw offset, add in upcontext size */
offs += sizeof(unprotected_context_t);
return opnd_create_dcontext_field(new_dcontext, offs);
}
/* some fields are in a separate memory region! */
else {
CLIENT_ASSERT(TEST(SELFPROT_DCONTEXT, dynamo_options.protect_mask),
"update_dcontext_address: inconsistent layout");
IF_X64(ASSERT_NOT_IMPLEMENTED(false));
offs = opnd_get_disp(op) -
(uint)(ptr_uint_t)(old_dcontext->upcontext.separate_upcontext);
if (offs >= 0 && offs < sizeof(unprotected_context_t)) {
/* raw offs is what we want for upcontext */
return opnd_create_dcontext_field(new_dcontext, offs);
}
}
/* not a dcontext offset: just return original value */
return op;
}
opnd_t
opnd_create_tls_slot(int offs)
{
return opnd_create_sized_tls_slot(offs, OPSZ_PTR);
}
opnd_t
opnd_create_sized_tls_slot(int offs, opnd_size_t size)
{
/* We do not request disp_short_addr or force_full_disp, letting
* encode_base_disp() choose whether to use the 0x67 addr prefix
* (assuming offs is small).
*/
return opnd_create_far_base_disp(SEG_TLS, REG_NULL, REG_NULL, 0, offs, size);
}
#endif /* !STANDALONE_DECODER */
/****************************************************************************/