* Copyright (c) 2010-2022 Google, Inc. All rights reserved.
* Copyright (c) 2010 Massachusetts Institute of Technology 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.
*/
#include "../globals.h"
#include "../link.h"
#include "../fragment.h"
#include "arch.h"
#include "instr.h"
#include "instr_create_shared.h"
#include "instrlist.h"
#include "decode.h"
#include "decode_fast.h"
#include "decode_private.h"
#include "disassemble.h"
#include "../hashtable.h"
#include "instrument.h"
#include "../fcache.h"
#include "../translate.h"
#ifdef RCT_IND_BRANCH
# include "../rct.h"
#endif
#ifdef UNIX
# include <sys/syscall.h>
#endif
#ifdef ANNOTATIONS
# include "../annotations.h"
#endif
* We mark everything we add as non-app instr.
*/
#define POST instrlist_meta_postinsert
#define PRE instrlist_meta_preinsert
void
mangle_arch_init(void)
{
}
int
insert_out_of_line_context_switch(dcontext_t *dcontext, instrlist_t *ilist,
instr_t *instr, bool save, byte *encode_pc)
{
if (save) {
* so we can have call/return pair to take advantage of hardware
* call return stack for better performance.
* Xref emit_clean_call_save @ x86/emit_utils.c
* The precise adjustment amount is relied upon in
* find_next_fragment_from_gencode()'s handling of in_clean_call_save().
*/
PRE(ilist, instr,
INSTR_CREATE_lea(
dcontext, opnd_create_reg(DR_REG_XSP),
opnd_create_base_disp(DR_REG_XSP, DR_REG_NULL, 0,
-(int)(get_clean_call_switch_stack_size() +
get_clean_call_temp_stack_size()),
OPSZ_lea)));
}
insert_reachable_cti(dcontext, ilist, instr, encode_pc,
save ? get_clean_call_save(dcontext _IF_X64(GENCODE_X64))
: get_clean_call_restore(dcontext _IF_X64(GENCODE_X64)),
false , true , false ,
CALL_SCRATCH_REG, NULL);
return get_clean_call_switch_stack_size();
}
void
insert_clear_eflags(dcontext_t *dcontext, clean_call_info_t *cci, instrlist_t *ilist,
instr_t *instr)
{
if (cci == NULL || !cci->skip_clear_flags) {
if (dynamo_options.cleancall_ignore_eflags) {
* DF is cleared at each function.
*/
PRE(ilist, instr, INSTR_CREATE_cld(dcontext));
} else {
PRE(ilist, instr, INSTR_CREATE_push_imm(dcontext, OPND_CREATE_INT32(0)));
PRE(ilist, instr, INSTR_CREATE_popf(dcontext));
}
}
}
* priv_mcontext_t order.
* The current stack pointer alignment should be passed. Use 1 if
* unknown (NOT 0).
* Returns the amount of data pushed. Does NOT fix up the xsp value pushed
* to be the value prior to any pushes for x64 as no caller needs that
* currently (they all build a priv_mcontext_t and have to do further xsp
* fixups anyway).
* Includes xmm0-5 for PR 264138.
*/
uint
insert_push_all_registers(dcontext_t *dcontext, clean_call_info_t *cci,
instrlist_t *ilist, instr_t *instr, uint alignment,
opnd_t push_pc, reg_id_t scratch )
{
uint dstack_offs = 0;
int offs_beyond_xmm = 0;
if (cci == NULL)
cci = &default_clean_call_info;
ASSERT(proc_num_simd_registers() == MCXT_NUM_SIMD_SLOTS ||
proc_num_simd_registers() == MCXT_NUM_SIMD_SSE_AVX_SLOTS);
if (clean_call_needs_simd(cci)) {
int offs =
MCXT_TOTAL_SIMD_SLOTS_SIZE + MCXT_TOTAL_OPMASK_SLOTS_SIZE + PRE_XMM_PADDING;
if (cci->preserve_mcontext && cci->skip_save_flags) {
offs_beyond_xmm = 2 * XSP_SZ;
offs += offs_beyond_xmm;
}
PRE(ilist, instr,
INSTR_CREATE_lea(dcontext, opnd_create_reg(REG_XSP),
OPND_CREATE_MEM_lea(REG_XSP, REG_NULL, 0, -offs)));
dstack_offs += offs;
}
if (!cci->skip_save_flags) {
ASSERT(offs_beyond_xmm == 0);
if (opnd_is_immed_int(push_pc))
PRE(ilist, instr, INSTR_CREATE_push_imm(dcontext, push_pc));
else
PRE(ilist, instr, INSTR_CREATE_push(dcontext, push_pc));
dstack_offs += XSP_SZ;
offs_beyond_xmm += XSP_SZ;
PRE(ilist, instr, INSTR_CREATE_pushf(dcontext));
dstack_offs += XSP_SZ;
offs_beyond_xmm += XSP_SZ;
} else {
ASSERT(offs_beyond_xmm == 2 * XSP_SZ || !cci->preserve_mcontext);
}
ASSERT(preserve_xmm_caller_saved() || !ZMM_ENABLED());
if (preserve_xmm_caller_saved()) {
int i;
ASSERT(proc_has_feature(FEATURE_SSE));
ASSERT(proc_num_simd_saved() == proc_num_simd_registers() ||
proc_num_simd_saved() == proc_num_simd_sse_avx_registers());
instr_t *post_push = NULL;
instr_t *pre_avx512_push = NULL;
if (ZMM_ENABLED()) {
post_push = INSTR_CREATE_label(dcontext);
pre_avx512_push = INSTR_CREATE_label(dcontext);
PRE(ilist, instr,
INSTR_CREATE_cmp(dcontext,
OPND_CREATE_ABSMEM(vmcode_get_executable_addr(
(byte *)d_r_avx512_code_in_use),
OPSZ_1),
OPND_CREATE_INT8(0)));
PRE(ilist, instr,
INSTR_CREATE_jcc(dcontext, OP_jnz, opnd_create_instr(pre_avx512_push)));
}
uint opcode = move_mm_reg_opcode(ALIGNED(alignment, 16), ALIGNED(alignment, 32));
for (i = 0; i < proc_num_simd_sse_avx_saved(); i++) {
* which opcode is better. Note that the AMD optimization
* guide says to use movlps+movhps for unaligned stores, but
* for simplicity and smaller code I'm using movups anyway.
*/
* cost of vmovdqu and whether worth arranging 32-byte alignment
* for all callers. B/c we put ymm at end of priv_mcontext_t, we do
* currently have 32-byte alignment for clean calls.
*/
if (!cci->simd_skip[i]) {
PRE(ilist, instr,
instr_create_1dst_1src(
dcontext, opcode,
opnd_create_base_disp(REG_XSP, REG_NULL, 0,
PRE_XMM_PADDING + i * MCXT_SIMD_SLOT_SIZE +
offs_beyond_xmm,
OPSZ_SAVED_XMM),
opnd_create_reg(REG_SAVED_XMM0 + (reg_id_t)i)));
}
}
if (ZMM_ENABLED()) {
PRE(ilist, instr, INSTR_CREATE_jmp(dcontext, opnd_create_instr(post_push)));
PRE(ilist, instr, pre_avx512_push );
uint opcode_avx512 = move_mm_avx512_reg_opcode(ALIGNED(alignment, 64));
for (i = 0; i < proc_num_simd_registers(); i++) {
if (!cci->simd_skip[i]) {
instr_t *simdmov = instr_create_1dst_2src(
dcontext, opcode_avx512,
opnd_create_base_disp(REG_XSP, REG_NULL, 0,
PRE_XMM_PADDING + i * MCXT_SIMD_SLOT_SIZE +
offs_beyond_xmm,
OPSZ_SAVED_ZMM),
opnd_create_reg(DR_REG_K0),
opnd_create_reg(DR_REG_START_ZMM + (reg_id_t)i));
PRE(ilist, instr, simdmov);
}
}
for (i = 0; i < proc_num_opmask_registers(); i++) {
if (!cci->opmask_skip[i]) {
instr_t *maskmov = instr_create_1dst_1src(
dcontext,
proc_has_feature(FEATURE_AVX512BW) ? OP_kmovq : OP_kmovw,
opnd_create_base_disp(
REG_XSP, REG_NULL, 0,
PRE_XMM_PADDING + MCXT_TOTAL_SIMD_SLOTS_SIZE +
i * OPMASK_AVX512BW_REG_SIZE + offs_beyond_xmm,
OPSZ_SAVED_OPMASK),
opnd_create_reg(DR_REG_START_OPMASK + (reg_id_t)i));
PRE(ilist, instr, maskmov);
}
}
PRE(ilist, instr, post_push );
}
}
#ifdef X64
if (!cci->reg_skip[REG_R15 - REG_XAX])
PRE(ilist, instr, INSTR_CREATE_push(dcontext, opnd_create_reg(REG_R15)));
if (!cci->reg_skip[REG_R14 - REG_XAX])
PRE(ilist, instr, INSTR_CREATE_push(dcontext, opnd_create_reg(REG_R14)));
if (!cci->reg_skip[REG_R13 - REG_XAX])
PRE(ilist, instr, INSTR_CREATE_push(dcontext, opnd_create_reg(REG_R13)));
if (!cci->reg_skip[REG_R12 - REG_XAX])
PRE(ilist, instr, INSTR_CREATE_push(dcontext, opnd_create_reg(REG_R12)));
if (!cci->reg_skip[REG_R11 - REG_XAX])
PRE(ilist, instr, INSTR_CREATE_push(dcontext, opnd_create_reg(REG_R11)));
if (!cci->reg_skip[REG_R10 - REG_XAX])
PRE(ilist, instr, INSTR_CREATE_push(dcontext, opnd_create_reg(REG_R10)));
if (!cci->reg_skip[REG_R9 - REG_XAX])
PRE(ilist, instr, INSTR_CREATE_push(dcontext, opnd_create_reg(REG_R9)));
if (!cci->reg_skip[REG_R8 - REG_XAX])
PRE(ilist, instr, INSTR_CREATE_push(dcontext, opnd_create_reg(REG_R8)));
if (!cci->reg_skip[REG_RAX - REG_XAX])
PRE(ilist, instr, INSTR_CREATE_push(dcontext, opnd_create_reg(REG_RAX)));
if (!cci->reg_skip[REG_RCX - REG_XAX])
PRE(ilist, instr, INSTR_CREATE_push(dcontext, opnd_create_reg(REG_RCX)));
if (!cci->reg_skip[REG_RDX - REG_XAX])
PRE(ilist, instr, INSTR_CREATE_push(dcontext, opnd_create_reg(REG_RDX)));
if (!cci->reg_skip[REG_RBX - REG_XAX])
PRE(ilist, instr, INSTR_CREATE_push(dcontext, opnd_create_reg(REG_RBX)));
if (!cci->reg_skip[REG_RSP - REG_XAX])
PRE(ilist, instr, INSTR_CREATE_push(dcontext, opnd_create_reg(REG_RSP)));
if (!cci->reg_skip[REG_RBP - REG_XAX])
PRE(ilist, instr, INSTR_CREATE_push(dcontext, opnd_create_reg(REG_RBP)));
if (!cci->reg_skip[REG_RSI - REG_XAX])
PRE(ilist, instr, INSTR_CREATE_push(dcontext, opnd_create_reg(REG_RSI)));
if (!cci->reg_skip[REG_RDI - REG_XAX])
PRE(ilist, instr, INSTR_CREATE_push(dcontext, opnd_create_reg(REG_RDI)));
dstack_offs += (DR_NUM_GPR_REGS - cci->num_regs_skip) * XSP_SZ;
#else
PRE(ilist, instr, INSTR_CREATE_pusha(dcontext));
dstack_offs += 8 * XSP_SZ;
#endif
ASSERT(cci->skip_save_flags || cci->num_simd_skip != 0 || cci->num_opmask_skip != 0 ||
cci->num_regs_skip != 0 ||
dstack_offs == (uint)get_clean_call_switch_stack_size());
return dstack_offs;
}
* alignment at the end of all the popping, not the alignment prior to
* the popping.
*/
void
insert_pop_all_registers(dcontext_t *dcontext, clean_call_info_t *cci, instrlist_t *ilist,
instr_t *instr, uint alignment)
{
int offs_beyond_xmm = 0;
if (cci == NULL)
cci = &default_clean_call_info;
#ifdef X64
if (!cci->reg_skip[REG_RDI - REG_XAX])
PRE(ilist, instr, INSTR_CREATE_pop(dcontext, opnd_create_reg(REG_RDI)));
if (!cci->reg_skip[REG_RSI - REG_XAX])
PRE(ilist, instr, INSTR_CREATE_pop(dcontext, opnd_create_reg(REG_RSI)));
if (!cci->reg_skip[REG_RBP - REG_XAX])
PRE(ilist, instr, INSTR_CREATE_pop(dcontext, opnd_create_reg(REG_RBP)));
if (!cci->reg_skip[REG_RSP - REG_XAX]) {
ASSERT(!cci->reg_skip[REG_RBX - REG_XAX]);
PRE(ilist, instr, INSTR_CREATE_pop(dcontext, opnd_create_reg(REG_RBX)));
}
if (!cci->reg_skip[REG_RBX - REG_XAX])
PRE(ilist, instr, INSTR_CREATE_pop(dcontext, opnd_create_reg(REG_RBX)));
if (!cci->reg_skip[REG_RDX - REG_XAX])
PRE(ilist, instr, INSTR_CREATE_pop(dcontext, opnd_create_reg(REG_RDX)));
if (!cci->reg_skip[REG_RCX - REG_XAX])
PRE(ilist, instr, INSTR_CREATE_pop(dcontext, opnd_create_reg(REG_RCX)));
if (!cci->reg_skip[REG_RAX - REG_XAX])
PRE(ilist, instr, INSTR_CREATE_pop(dcontext, opnd_create_reg(REG_RAX)));
if (!cci->reg_skip[REG_R8 - REG_XAX])
PRE(ilist, instr, INSTR_CREATE_pop(dcontext, opnd_create_reg(REG_R8)));
if (!cci->reg_skip[REG_R9 - REG_XAX])
PRE(ilist, instr, INSTR_CREATE_pop(dcontext, opnd_create_reg(REG_R9)));
if (!cci->reg_skip[REG_R10 - REG_XAX])
PRE(ilist, instr, INSTR_CREATE_pop(dcontext, opnd_create_reg(REG_R10)));
if (!cci->reg_skip[REG_R11 - REG_XAX])
PRE(ilist, instr, INSTR_CREATE_pop(dcontext, opnd_create_reg(REG_R11)));
if (!cci->reg_skip[REG_R12 - REG_XAX])
PRE(ilist, instr, INSTR_CREATE_pop(dcontext, opnd_create_reg(REG_R12)));
if (!cci->reg_skip[REG_R13 - REG_XAX])
PRE(ilist, instr, INSTR_CREATE_pop(dcontext, opnd_create_reg(REG_R13)));
if (!cci->reg_skip[REG_R14 - REG_XAX])
PRE(ilist, instr, INSTR_CREATE_pop(dcontext, opnd_create_reg(REG_R14)));
if (!cci->reg_skip[REG_R15 - REG_XAX])
PRE(ilist, instr, INSTR_CREATE_pop(dcontext, opnd_create_reg(REG_R15)));
#else
PRE(ilist, instr, INSTR_CREATE_popa(dcontext));
#endif
offs_beyond_xmm = 2 * XSP_SZ;
ASSERT(preserve_xmm_caller_saved() || !ZMM_ENABLED());
if (preserve_xmm_caller_saved()) {
int i;
* is better. */
uint opcode = move_mm_reg_opcode(ALIGNED(alignment, 16), ALIGNED(alignment, 32));
ASSERT(proc_has_feature(FEATURE_SSE));
ASSERT(proc_num_simd_saved() == proc_num_simd_registers() ||
proc_num_simd_saved() == proc_num_simd_sse_avx_registers());
instr_t *post_pop = NULL;
instr_t *pre_avx512_pop = NULL;
if (ZMM_ENABLED()) {
post_pop = INSTR_CREATE_label(dcontext);
pre_avx512_pop = INSTR_CREATE_label(dcontext);
PRE(ilist, instr,
INSTR_CREATE_cmp(dcontext,
OPND_CREATE_ABSMEM(vmcode_get_executable_addr(
(byte *)d_r_avx512_code_in_use),
OPSZ_1),
OPND_CREATE_INT8(0)));
PRE(ilist, instr,
INSTR_CREATE_jcc(dcontext, OP_jnz, opnd_create_instr(pre_avx512_pop)));
}
for (i = 0; i < proc_num_simd_sse_avx_saved(); i++) {
if (!cci->simd_skip[i]) {
PRE(ilist, instr,
instr_create_1dst_1src(
dcontext, opcode, opnd_create_reg(REG_SAVED_XMM0 + (reg_id_t)i),
opnd_create_base_disp(REG_XSP, REG_NULL, 0,
PRE_XMM_PADDING + i * MCXT_SIMD_SLOT_SIZE +
offs_beyond_xmm,
OPSZ_SAVED_XMM)));
}
}
if (ZMM_ENABLED()) {
PRE(ilist, instr, INSTR_CREATE_jmp(dcontext, opnd_create_instr(post_pop)));
PRE(ilist, instr, pre_avx512_pop );
uint opcode_avx512 = move_mm_avx512_reg_opcode(ALIGNED(alignment, 64));
for (i = 0; i < proc_num_simd_registers(); i++) {
if (!cci->simd_skip[i]) {
instr_t *simdmov = instr_create_1dst_2src(
dcontext, opcode_avx512,
opnd_create_reg(DR_REG_START_ZMM + (reg_id_t)i),
opnd_create_reg(DR_REG_K0),
opnd_create_base_disp(REG_XSP, REG_NULL, 0,
PRE_XMM_PADDING + i * MCXT_SIMD_SLOT_SIZE +
offs_beyond_xmm,
OPSZ_SAVED_ZMM));
PRE(ilist, instr, simdmov);
}
}
for (i = 0; i < proc_num_opmask_registers(); i++) {
if (!cci->opmask_skip[i]) {
instr_t *maskmov = instr_create_1dst_1src(
dcontext,
proc_has_feature(FEATURE_AVX512BW) ? OP_kmovq : OP_kmovw,
opnd_create_reg(DR_REG_START_OPMASK + (reg_id_t)i),
opnd_create_base_disp(
REG_XSP, REG_NULL, 0,
PRE_XMM_PADDING + MCXT_TOTAL_SIMD_SLOTS_SIZE +
i * OPMASK_AVX512BW_REG_SIZE + offs_beyond_xmm,
OPSZ_SAVED_OPMASK));
PRE(ilist, instr, maskmov);
}
}
PRE(ilist, instr, post_pop );
}
}
if (!cci->skip_save_flags) {
PRE(ilist, instr, INSTR_CREATE_popf(dcontext));
offs_beyond_xmm = XSP_SZ;
}
PRE(ilist, instr,
INSTR_CREATE_lea(
dcontext, opnd_create_reg(REG_XSP),
OPND_CREATE_MEM_lea(REG_XSP, REG_NULL, 0,
PRE_XMM_PADDING + MCXT_TOTAL_SIMD_SLOTS_SIZE +
MCXT_TOTAL_OPMASK_SLOTS_SIZE + offs_beyond_xmm)));
}
reg_id_t
shrink_reg_for_param(reg_id_t regular, opnd_t arg)
{
#ifdef X64
if (opnd_get_size(arg) == OPSZ_4) {
* assume user wants sign-extension; that is after all what happens
* on a push of a 32-bit immed) */
if (!opnd_is_immed_int(arg) || (opnd_get_immed_int(arg) & 0x80000000) == 0)
return reg_64_to_32(regular);
}
#endif
return regular;
}
* N.B.: due to stack alignment and minimum stack reservation, do
* not use parameters involving esp/rsp, as its value can change!
*
* This routine only supports passing arguments that are integers or
* pointers of a size equal or smaller than the register size: i.e., no
* floating-point, multimedia, or aggregate data types.
*
* For 64-bit mode, if a 32-bit immediate integer is specified as an
* argument and it has its top bit set, we assume it is intended to be
* sign-extended to 64-bits; otherwise we zero-extend it.
*
* For 64-bit mode, variable-sized argument operands may not work
* properly.
*
* Arguments that reference REG_XSP will work for clean calls, but are not guaranteed
* to work for non-clean, especially in the presence of stack alignment. Arguments that
* reference sub-register portions of REG_XSP are not supported.
*
* XXX PR 307874: w/ a post optimization pass, or perhaps more clever use of
* existing passes, we could do much better on calling convention and xsp conflicting
* args. We should also really consider inlining client callees (PR 218907), since
* clean calls for 64-bit are enormous (71 instrs/264 bytes for 2-arg x64; 26
* instrs/99 bytes for x86) and we could avoid all the xmm saves and replace pushf w/
* lahf.
*/
uint
insert_parameter_preparation(dcontext_t *dcontext, instrlist_t *ilist, instr_t *instr,
bool clean_call, uint num_args, opnd_t *args)
{
uint i;
int r;
uint preparm_padding = 0;
uint param_stack = 0, total_stack = 0;
bool push = true;
bool restore_xax = false;
bool restore_xsp = false;
* instrs prior to the regular param prep. So params are POST-mark, while
* pre-param-prep is POST-prev or PRE-mark.
*/
#ifdef X64
uint arg_pre_push = 0, total_pre_push = 0;
#endif
instr_t *prev = (instr == NULL) ? instrlist_last(ilist) : instr_get_prev(instr);
instr_t *mark = INSTR_CREATE_label(dcontext);
PRE(ilist, instr, mark);
* Rather than use as scratch and restore prior to each param that uses it,
* we restore once up front if any use it, and use d_r_regparms[0] as scratch,
* which is symmetric with non-clean-calls: d_r_regparms[0] is dead since we're
* doing args in reverse order. However, we then can't use d_r_regparms[0]
* directly if referenced in earlier params, but similarly for xax, so
* there's no clear better way. (prepare_for_clean_call also clobbers xsp,
* but we just disallow args that use it).
*/
ASSERT(num_args == 0 || args != NULL);
* regs to be able to refer to priv_mcontext_t as well as potentially being
* pushed: but we need to know the total # pushes ahead of time (since hard
* to mark for post-patching)
*/
for (i = 0; i < num_args; i++) {
IF_X64(bool is_pre_push = false;)
for (r = 0; r < opnd_num_regs_used(args[i]); r++) {
reg_id_t used = opnd_get_reg_used(args[i], r);
IF_X64(int parm;)
LOG(THREAD, LOG_INTERP, 4, "ipp: considering arg %d reg %d == %s\n", i, r,
reg_names[used]);
if (clean_call && !restore_xax && reg_overlap(used, REG_XAX))
restore_xax = true;
if (reg_overlap(used, REG_XSP)) {
IF_X64(CLIENT_ASSERT(clean_call,
"Non-clean-call argument: REG_XSP not supported"));
CLIENT_ASSERT(used == REG_XSP,
"Call argument: sub-reg-xsp not supported");
if (clean_call && parameters_stack_padded() && !restore_xsp)
restore_xsp = true;
}
#ifdef X64
parm = reg_parameter_num(used);
* arg later (we do reverse order), except arg0, which we use as
* scratch (we don't always need it, but not worth another pre-pass
* through all args to find out), and xsp. Otherwise, if a plain reg,
* we point at mcontext (we restore xsp slot in mcontext if nec.).
* If a mem ref, we need to pre-push onto stack.
* N.B.: this conditional is duplicated in 2nd loop.
*/
if (!is_pre_push &&
((parm == 0 && num_args > 1) || parm > (int)i ||
reg_overlap(used, REG_XSP)) &&
(!clean_call || !opnd_is_reg(args[i]))) {
total_pre_push++;
is_pre_push = true;
}
#endif
}
}
if (parameters_stack_padded()) {
* do next best thing and reserve it prior to setting up the args.
*/
push = false;
total_stack = REGPARM_MINSTACK;
if (num_args > NUM_REGPARM)
total_stack += XSP_SZ * (num_args - NUM_REGPARM);
param_stack = total_stack;
IF_X64(total_stack += XSP_SZ * total_pre_push);
* to be 16-byte aligned for x64 SysV (note that retaddr will then make
* rsp 8-byte-aligned, which is ok: callee has to rectify that).
* For clean calls, prepare_for_clean_call leaves rsp aligned for x64.
* XXX PR 218790: we require users of dr_insert_call to ensure
* alignment; should we put in support to dynamically align?
*/
preparm_padding =
ALIGN_FORWARD_UINT(total_stack, REGPARM_END_ALIGN) - total_stack;
total_stack += preparm_padding;
} else {
ASSERT(NUM_REGPARM == 0);
ASSERT(push);
IF_X64(ASSERT(total_pre_push == 0));
total_stack = XSP_SZ * num_args;
}
LOG(THREAD, LOG_INTERP, 3,
"insert_parameter_preparation: %d args, %d in-reg, %d pre-push, %d/%d stack\n",
num_args, NUM_REGPARM, IF_X64_ELSE(total_pre_push, 0), param_stack, total_stack);
for (i = 0; i < num_args; i++) {
* of these args references app memory. We should pull the state from
* the priv_mcontext_t on the stack if in a clean call. FIXME: what if not?
*/
opnd_t arg = args[i];
CLIENT_ASSERT(opnd_get_size(arg) == OPSZ_PTR ||
opnd_is_immed_int(arg) IF_X64(|| opnd_get_size(arg) == OPSZ_4),
"Clean call arg has unsupported size");
#ifdef X64
for (r = 0; r < opnd_num_regs_used(arg); r++) {
reg_id_t used = opnd_get_reg_used(arg, r);
int parm = reg_parameter_num(used);
if ((parm == 0 && num_args > 1) || parm > (int)i ||
reg_overlap(used, REG_XSP)) {
int disp = 0;
if (clean_call && opnd_is_reg(arg)) {
* priv_mcontext_t is at the base of dstack: compute offset
* from xsp to the field we want and replace arg.
*/
disp += opnd_get_reg_dcontext_offs(opnd_get_reg(arg));
disp += clean_call_beyond_mcontext();
disp += total_stack;
} else {
* could try to optimize by juggling registers, but
* not worth it.
*/
disp = param_stack + XSP_SZ * arg_pre_push;
if (opnd_is_reg(arg) && opnd_get_size(arg) == OPSZ_PTR) {
POST(ilist, prev,
INSTR_CREATE_mov_st(dcontext,
OPND_CREATE_MEMPTR(REG_XSP, disp), arg));
} else {
reg_id_t xsp_scratch = d_r_regparms[0];
* can't do mem-to-mem so go through scratch reg */
if (reg_overlap(used, REG_XSP)) {
if (opnd_uses_reg(arg, d_r_regparms[0])) {
xsp_scratch = REG_XAX;
ASSERT(!opnd_uses_reg(arg, REG_XAX));
* slot, better to just do local get from dcontext
* like we do for 32-bit below? */
POST(ilist, prev,
instr_create_restore_from_tls(dcontext, REG_XAX,
TLS_XAX_SLOT));
}
opnd_replace_reg(&arg, REG_XSP, xsp_scratch);
}
POST(ilist, prev,
INSTR_CREATE_mov_st(dcontext,
OPND_CREATE_MEMPTR(REG_XSP, disp),
opnd_create_reg(d_r_regparms[0])));
POST(ilist, prev,
INSTR_CREATE_mov_ld(dcontext,
opnd_create_reg(shrink_reg_for_param(
d_r_regparms[0], arg)),
arg));
if (reg_overlap(used, REG_XSP)) {
int xsp_disp = opnd_get_reg_dcontext_offs(REG_XSP) +
clean_call_beyond_mcontext() + total_stack;
POST(ilist, prev,
INSTR_CREATE_mov_ld(
dcontext, opnd_create_reg(xsp_scratch),
OPND_CREATE_MEMPTR(REG_XSP, xsp_disp)));
if (xsp_scratch == REG_XAX) {
POST(ilist, prev,
instr_create_save_to_tls(dcontext, REG_XAX,
TLS_XAX_SLOT));
}
}
if (opnd_uses_reg(arg, d_r_regparms[0])) {
int mc_disp = opnd_get_reg_dcontext_offs(d_r_regparms[0]) +
clean_call_beyond_mcontext() + total_stack;
POST(ilist, prev,
INSTR_CREATE_mov_ld(
dcontext, opnd_create_reg(d_r_regparms[0]),
OPND_CREATE_MEMPTR(REG_XSP, mc_disp)));
}
}
arg_pre_push++;
}
arg =
opnd_create_base_disp(REG_XSP, REG_NULL, 0, disp, opnd_get_size(arg));
break;
}
}
#endif
if (i < NUM_REGPARM) {
reg_id_t regparm = shrink_reg_for_param(d_r_regparms[i], arg);
if (opnd_is_immed_int(arg) || opnd_is_instr(arg)) {
POST(ilist, mark,
INSTR_CREATE_mov_imm(dcontext, opnd_create_reg(regparm), arg));
} else {
POST(ilist, mark,
INSTR_CREATE_mov_ld(dcontext, opnd_create_reg(regparm), arg));
}
} else {
if (push) {
IF_X64(ASSERT_NOT_REACHED());
if (opnd_is_immed_int(arg) || opnd_is_instr(arg))
POST(ilist, mark, INSTR_CREATE_push_imm(dcontext, arg));
else {
if (clean_call && opnd_uses_reg(arg, REG_XSP)) {
* spill eax, mc->eax, esp->eax, arg->eax, push eax, restore eax
*/
reg_id_t scratch = REG_XAX;
if (opnd_uses_reg(arg, scratch)) {
scratch = REG_XCX;
ASSERT(!opnd_uses_reg(arg, scratch));
}
opnd_replace_reg(&arg, REG_XSP, scratch);
POST(ilist, mark,
instr_create_restore_from_tls(dcontext, scratch,
TLS_XAX_SLOT));
POST(ilist, mark, INSTR_CREATE_push(dcontext, arg));
POST(ilist, mark,
instr_create_restore_from_dc_via_reg(dcontext, scratch,
scratch, XSP_OFFSET));
insert_get_mcontext_base(dcontext, ilist, instr_get_next(mark),
scratch);
POST(ilist, mark,
instr_create_save_to_tls(dcontext, scratch, TLS_XAX_SLOT));
} else
POST(ilist, mark, INSTR_CREATE_push(dcontext, arg));
}
} else {
uint offs = REGPARM_MINSTACK + XSP_SZ * (i - NUM_REGPARM);
#ifdef X64
if (opnd_is_immed_int(arg) || opnd_is_instr(arg)) {
* so go through scratch reg */
ASSERT(NUM_REGPARM > 0);
POST(ilist, mark,
INSTR_CREATE_mov_st(dcontext, OPND_CREATE_MEMPTR(REG_XSP, offs),
opnd_create_reg(d_r_regparms[0])));
POST(ilist, mark,
INSTR_CREATE_mov_imm(dcontext, opnd_create_reg(d_r_regparms[0]),
arg));
} else {
#endif
if (opnd_is_memory_reference(arg)) {
reg_id_t scratch;
if (NUM_REGPARM > 0)
scratch = d_r_regparms[0];
else {
* FIXME i#1370: not safe if later arg uses xax:
* local spill? Review how d_r_regparms[0] is preserved.
*/
scratch = REG_XAX;
}
POST(ilist, mark,
INSTR_CREATE_mov_st(dcontext,
OPND_CREATE_MEMPTR(REG_XSP, offs),
opnd_create_reg(scratch)));
POST(ilist, mark,
INSTR_CREATE_mov_ld(
dcontext,
opnd_create_reg(shrink_reg_for_param(scratch, arg)),
arg));
} else {
POST(ilist, mark,
INSTR_CREATE_mov_st(dcontext,
OPND_CREATE_MEMPTR(REG_XSP, offs), arg));
}
#ifdef X64
}
#endif
}
}
}
if (!push && total_stack > 0) {
POST(ilist, prev,
INSTR_CREATE_lea(
dcontext, opnd_create_reg(REG_XSP),
OPND_CREATE_MEM_lea(REG_XSP, REG_NULL, 0, -(int)total_stack)));
} else if (total_stack % get_ABI_stack_alignment() != 0) {
int off = get_ABI_stack_alignment() - (total_stack % get_ABI_stack_alignment());
total_stack += off;
POST(ilist, prev,
INSTR_CREATE_lea(dcontext, opnd_create_reg(REG_XSP),
OPND_CREATE_MEM_lea(REG_XSP, REG_NULL, 0, -off)));
}
if (restore_xsp) {
int disp = opnd_get_reg_dcontext_offs(REG_XSP);
instr_t *where = instr_get_next(prev);
disp += clean_call_beyond_mcontext();
insert_get_mcontext_base(dcontext, ilist, where, REG_XAX);
PRE(ilist, where,
instr_create_restore_from_dc_via_reg(dcontext, REG_XAX, REG_XAX, XSP_OFFSET));
PRE(ilist, where,
INSTR_CREATE_mov_st(dcontext, OPND_CREATE_MEMPTR(REG_XSP, disp),
opnd_create_reg(REG_XAX)));
prev = instr_get_prev(where);
}
if (restore_xax) {
int disp = opnd_get_reg_dcontext_offs(REG_XAX);
disp += clean_call_beyond_mcontext();
POST(ilist, prev,
INSTR_CREATE_mov_ld(dcontext, opnd_create_reg(REG_XAX),
OPND_CREATE_MEMPTR(REG_XSP, disp)));
}
return total_stack;
}
*/
void
insert_clean_call_with_arg_jmp_if_ret_true(dcontext_t *dcontext, instrlist_t *ilist,
instr_t *instr, void *callee, int arg,
app_pc jmp_tag, instr_t *jmp_instr)
{
instr_t *false_popa, *jcc;
byte *encode_pc = vmcode_get_start();
prepare_for_clean_call(dcontext, NULL, ilist, instr, encode_pc);
dr_insert_call(dcontext, ilist, instr, callee, 1, OPND_CREATE_INT32(arg));
PRE(ilist, instr,
INSTR_CREATE_test(dcontext, opnd_create_reg(REG_XAX), opnd_create_reg(REG_XAX)));
jcc = INSTR_CREATE_jcc(dcontext, OP_jz, opnd_create_pc(NULL));
PRE(ilist, instr, jcc);
* passed in by caller
*/
cleanup_after_clean_call(dcontext, NULL, ilist, instr, encode_pc);
if (jmp_instr == NULL) {
instrlist_preinsert(ilist, instr,
INSTR_CREATE_jmp(dcontext, opnd_create_pc(jmp_tag)));
} else {
PRE(ilist, instr, INSTR_CREATE_jmp(dcontext, opnd_create_instr(jmp_instr)));
}
false_popa = instr_get_prev(instr);
cleanup_after_clean_call(dcontext, NULL, ilist, instr, encode_pc);
false_popa = instr_get_next(false_popa);
instr_set_target(jcc, opnd_create_instr(false_popa));
}
* writing an instrlist to gencode so not sure of exact placement but
* within a page).
* If encode_pc == vmcode_get_start(), checks reachability of whole
* vmcode region (meant for code going somewhere not precisely known
* in the code cache).
* Returns whether ended up using a direct cti. If inlined_tgt_instr != NULL,
* and an inlined target was used, returns a pointer to that instruction
* in *inlined_tgt_instr.
*/
bool
insert_reachable_cti(dcontext_t *dcontext, instrlist_t *ilist, instr_t *where,
byte *encode_pc, byte *target, bool jmp, bool returns, bool precise,
reg_id_t scratch, instr_t **inlined_tgt_instr)
{
byte *encode_start;
byte *encode_end;
if (precise) {
encode_start = target + JMP_LONG_LENGTH;
encode_end = encode_start;
} else if (encode_pc == vmcode_get_start()) {
encode_start = encode_pc;
encode_end = vmcode_get_end();
} else {
encode_start = (byte *)PAGE_START(encode_pc - PAGE_SIZE);
encode_end = (byte *)ALIGN_FORWARD(encode_pc + PAGE_SIZE, PAGE_SIZE);
}
if (REL32_REACHABLE(encode_start, target) && REL32_REACHABLE(encode_end, target)) {
* users are precise so we'll leave that for i#56.
*/
if (jmp)
PRE(ilist, where, INSTR_CREATE_jmp(dcontext, opnd_create_pc(target)));
else
PRE(ilist, where, INSTR_CREATE_call(dcontext, opnd_create_pc(target)));
return true;
} else {
opnd_t ind_tgt;
instr_t *inlined_tgt = NULL;
if (scratch == DR_REG_NULL) {
inlined_tgt = instr_build_bits(dcontext, OP_UNDECODED, sizeof(target));
* for clean disassembly
*/
instr_set_raw_bytes(inlined_tgt, (byte *)&target, sizeof(target));
* the lifetime of the target param
*/
instr_allocate_raw_bits(dcontext, inlined_tgt, sizeof(target));
ind_tgt = opnd_create_mem_instr(inlined_tgt, 0, OPSZ_PTR);
if (inlined_tgt_instr != NULL)
*inlined_tgt_instr = inlined_tgt;
} else {
PRE(ilist, where,
INSTR_CREATE_mov_imm(dcontext, opnd_create_reg(scratch),
OPND_CREATE_INTPTR(target)));
ind_tgt = opnd_create_reg(scratch);
if (inlined_tgt_instr != NULL)
*inlined_tgt_instr = NULL;
}
if (jmp)
PRE(ilist, where, INSTR_CREATE_jmp_ind(dcontext, ind_tgt));
else
PRE(ilist, where, INSTR_CREATE_call_ind(dcontext, ind_tgt));
if (inlined_tgt != NULL)
PRE(ilist, where, inlined_tgt);
return false;
}
}
*###########################################################################
*
* MANGLING ROUTINES
*/
* The "first" and "last" from insert_mov_immed_arch() should be passed here,
* along with the encoded start pc of "first" as "pc".
* Keep this in sync with insert_mov_immed_arch().
* This is *not* a hot-patchable patch: i.e., it is subject to races.
*/
void
patch_mov_immed_arch(dcontext_t *dcontext, ptr_int_t val, byte *pc, instr_t *first,
instr_t *last)
{
byte *write_pc = vmcode_get_writable_addr(pc);
byte *immed_pc;
ASSERT(first != NULL);
#ifdef X64
if (X64_MODE_DC(dcontext) && last != NULL) {
immed_pc = write_pc + instr_length(dcontext, first) - sizeof(int);
ATOMIC_4BYTE_WRITE(immed_pc, (int)val, NOT_HOT_PATCHABLE);
immed_pc = write_pc + instr_length(dcontext, first) +
instr_length(dcontext, last) - sizeof(int);
ATOMIC_4BYTE_WRITE(immed_pc, (int)(val >> 32), NOT_HOT_PATCHABLE);
} else {
#endif
immed_pc = write_pc + instr_length(dcontext, first) - sizeof(val);
ATOMIC_ADDR_WRITE(immed_pc, val, NOT_HOT_PATCHABLE);
ASSERT(last == NULL);
#ifdef X64
}
#endif
}
static opnd_size_t
stack_entry_size(instr_t *instr, opnd_size_t opsize)
{
if (instr_get_opcode(instr) == OP_call_far ||
instr_get_opcode(instr) == OP_call_far_ind ||
instr_get_opcode(instr) == OP_ret_far) {
if (opsize == OPSZ_4)
return OPSZ_2;
else if (opsize == OPSZ_8)
return OPSZ_4;
else {
#ifdef X64
ASSERT(opsize == OPSZ_16);
return OPSZ_8;
#else
ASSERT_NOT_REACHED();
#endif
}
} else if (instr_get_opcode(instr) == OP_iret) {
if (opsize == OPSZ_6)
return OPSZ_2;
else if (opsize == OPSZ_12)
return OPSZ_4;
else {
#ifdef X64
ASSERT(opsize == OPSZ_40);
return OPSZ_8;
#else
ASSERT_NOT_REACHED();
#endif
}
}
return opsize;
}
bool
instr_check_xsp_mangling(dcontext_t *dcontext, instr_t *inst, int *xsp_adjust)
{
ASSERT(xsp_adjust != NULL);
if (instr_get_opcode(inst) == OP_push || instr_get_opcode(inst) == OP_push_imm) {
LOG(THREAD_GET, LOG_INTERP, 4, "\tstate track: push or push_imm\n");
*xsp_adjust -= opnd_size_in_bytes(opnd_get_size(instr_get_dst(inst, 1)));
} else if (instr_get_opcode(inst) == OP_pop) {
LOG(THREAD_GET, LOG_INTERP, 4, "\tstate track: pop\n");
*xsp_adjust += opnd_size_in_bytes(opnd_get_size(instr_get_src(inst, 1)));
}
else if (instr_get_opcode(inst) == OP_lea &&
opnd_get_reg(instr_get_dst(inst, 0)) == REG_XSP &&
opnd_get_base(instr_get_src(inst, 0)) == REG_XSP &&
opnd_get_index(instr_get_src(inst, 0)) == REG_NULL) {
LOG(THREAD_GET, LOG_INTERP, 4, "\tstate track: lea xsp adjust\n");
*xsp_adjust += opnd_get_disp(instr_get_src(inst, 0));
}
else if (instr_get_opcode(inst) == OP_mov_st &&
opnd_is_base_disp(instr_get_dst(inst, 0)) &&
opnd_get_base(instr_get_dst(inst, 0)) == REG_XSP &&
opnd_get_index(instr_get_dst(inst, 0)) == REG_NULL) {
LOG(THREAD_GET, LOG_INTERP, 4, "\tstate track: store to stack\n");
}
else if ((instr_get_opcode(inst) == OP_movzx &&
reg_overlap(opnd_get_reg(instr_get_dst(inst, 0)), REG_XCX)) ||
(instr_get_opcode(inst) == OP_mov_ld &&
reg_overlap(opnd_get_reg(instr_get_dst(inst, 0)), REG_XCX))) {
LOG(THREAD_GET, LOG_INTERP, 4, "\tstate track: ib tgt to *cx\n");
}
else if (instr_get_opcode(inst) == OP_mov_ld &&
opnd_is_base_disp(instr_get_src(inst, 0)) &&
opnd_get_base(instr_get_src(inst, 0)) == REG_XSP &&
opnd_get_index(instr_get_src(inst, 0)) == REG_NULL) {
LOG(THREAD_GET, LOG_INTERP, 4, "\tstate track: load from stack\n");
}
* need to not fail when walking over a movzx to a pop cs (right now we
* do not read the stack for the pop cs).
*/
else if (instr_get_opcode(inst) == OP_movzx &&
opnd_get_reg(instr_get_dst(inst, 0)) == REG_CX) {
LOG(THREAD_GET, LOG_INTERP, 4, "\tstate track: movzx to cx\n");
}
else if (instr_get_opcode(inst) == OP_add && opnd_is_reg(instr_get_dst(inst, 0)) &&
opnd_get_reg(instr_get_dst(inst, 0)) == REG_XSP &&
opnd_is_immed_int(instr_get_src(inst, 0))) {
LOG(THREAD_GET, LOG_INTERP, 4, "\tstate track: add to xsp\n");
ASSERT(CHECK_TRUNCATE_TYPE_int(opnd_get_immed_int(instr_get_src(inst, 0))));
*xsp_adjust += (int)opnd_get_immed_int(instr_get_src(inst, 0));
}
else if (instr_get_opcode(inst) == OP_popf) {
LOG(THREAD_GET, LOG_INTERP, 4, "\tstate track: popf\n");
*xsp_adjust += opnd_size_in_bytes(opnd_get_size(instr_get_src(inst, 1)));
} else {
return false;
}
return true;
}
* to xl8 to the next program counter post original app instruction.
*/
bool
instr_supports_simple_mangling_epilogue(dcontext_t *dcontext, instr_t *inst)
{
* of mangling overlaps with rip-rel mangling than the supported ones. Currently,
* this are only rip-rel control-flow instructions which are excluded here.
*/
return !instr_is_cti(inst);
}
void
insert_push_retaddr(dcontext_t *dcontext, instrlist_t *ilist, instr_t *instr,
ptr_int_t retaddr, opnd_size_t opsize)
{
if (opsize == OPSZ_2) {
ptr_int_t val = retaddr & (ptr_int_t)0x0000ffff;
PRE(ilist, instr,
INSTR_CREATE_lea(dcontext, opnd_create_reg(REG_XSP),
opnd_create_base_disp(REG_XSP, REG_NULL, 0, -2, OPSZ_lea)));
PRE(ilist, instr,
INSTR_CREATE_mov_st(dcontext, OPND_CREATE_MEM16(REG_XSP, 2),
OPND_CREATE_INT16((short)val)));
} else if (opsize ==
OPSZ_PTR IF_X64(|| (!X64_CACHE_MODE_DC(dcontext) && opsize == OPSZ_4))) {
insert_push_immed_ptrsz(dcontext, retaddr, ilist, instr, NULL, NULL);
} else {
#ifdef X64
ptr_int_t val = retaddr & (ptr_int_t)0xffffffff;
ASSERT(opsize == OPSZ_4);
PRE(ilist, instr,
INSTR_CREATE_lea(dcontext, opnd_create_reg(REG_XSP),
opnd_create_base_disp(REG_XSP, REG_NULL, 0, -4, OPSZ_lea)));
PRE(ilist, instr,
INSTR_CREATE_mov_st(dcontext, OPND_CREATE_MEM32(REG_XSP, 0),
OPND_CREATE_INT32((int)val)));
#else
ASSERT_NOT_REACHED();
#endif
}
}
static void
insert_mov_ptr_uint_beyond_TOS(dcontext_t *dcontext, instrlist_t *ilist, instr_t *instr,
ptr_int_t value, opnd_size_t opsize)
{
* if the ret itself faulted, barring races) for simplicity: o/w our
* our-mangling sequence gets broken up and more complex.
*/
if (opsize == OPSZ_2) {
ptr_int_t val = value & (ptr_int_t)0x0000ffff;
PRE(ilist, instr,
INSTR_CREATE_mov_st(dcontext, OPND_CREATE_MEM16(REG_XSP, -2),
OPND_CREATE_INT16(val)));
} else if (opsize == OPSZ_4) {
ptr_int_t val = value & (ptr_int_t)0xffffffff;
PRE(ilist, instr,
INSTR_CREATE_mov_st(dcontext, OPND_CREATE_MEM32(REG_XSP, -4),
OPND_CREATE_INT32(val)));
} else {
#ifdef X64
ptr_int_t val_low = value & (ptr_int_t)0xffffffff;
ASSERT(opsize == OPSZ_8);
if (CHECK_TRUNCATE_TYPE_int(value)) {
PRE(ilist, instr,
INSTR_CREATE_mov_st(dcontext, OPND_CREATE_MEM64(REG_XSP, -8),
OPND_CREATE_INT32(val_low)));
} else {
ptr_int_t val_high = (value >> 32);
PRE(ilist, instr,
INSTR_CREATE_mov_st(dcontext, OPND_CREATE_MEM32(REG_XSP, -8),
OPND_CREATE_INT32(val_low)));
PRE(ilist, instr,
INSTR_CREATE_mov_st(dcontext, OPND_CREATE_MEM32(REG_XSP, -4),
OPND_CREATE_INT32(val_high)));
}
#else
ASSERT_NOT_REACHED();
#endif
}
}
static void
insert_push_cs(dcontext_t *dcontext, instrlist_t *ilist, instr_t *instr,
ptr_int_t retaddr, opnd_size_t opsize)
{
#ifdef X64
if (X64_CACHE_MODE_DC(dcontext)) {
* i#823 covers doing this more generally.
*/
insert_push_retaddr(dcontext, ilist, instr,
X64_MODE_DC(dcontext) ? CS64_SELECTOR : CS32_SELECTOR,
opsize);
} else {
#endif
opnd_t stackop;
instr_t *push = INSTR_CREATE_push(dcontext, opnd_create_reg(SEG_CS));
stackop = instr_get_dst(push, 1);
opnd_set_size(&stackop, opsize);
instr_set_dst(push, 1, stackop);
PRE(ilist, instr, push);
#ifdef X64
}
#endif
}
* and to TLS MANGLE_XCX_SPILL_SLOT for shared fragments.
* (Except for DYNAMO_OPTION(private_ib_in_tls), for which all use tls,
* but that has a performance hit because of the extra data cache line)
* We can get away with the split by having the shared ibl routine copy
* xcx to the private dcontext, and by having the private ibl never
* target shared fragments.
* We also have to modify the xcx spill from tls to private dcontext when
* adding a shared basic block to a trace.
*
* FIXME: if we do make non-trace-head basic blocks valid indirect branch
* targets, we should have the private ibl have special code to test the
* flags and copy xcx to the tls slot if necessary.
*/
#define SAVE_TO_DC_OR_TLS(dc, flags, reg, tls_offs, dc_offs) \
((DYNAMO_OPTION(private_ib_in_tls) || TEST(FRAG_SHARED, (flags))) \
? instr_create_save_to_tls(dc, reg, tls_offs) \
: instr_create_save_to_dcontext((dc), (reg), (dc_offs)))
#define SAVE_TO_DC_OR_TLS_OR_REG(dc, flags, reg, tls_offs, dc_offs, dest_reg) \
((X64_CACHE_MODE_DC(dc) && \
!X64_MODE_DC(dc) IF_X64(&&DYNAMO_OPTION(x86_to_x64_ibl_opt))) \
? INSTR_CREATE_mov_ld(dc, opnd_create_reg(dest_reg), opnd_create_reg(reg)) \
: SAVE_TO_DC_OR_TLS(dc, flags, reg, tls_offs, dc_offs))
#define RESTORE_FROM_DC_OR_TLS(dc, flags, reg, tls_offs, dc_offs) \
((DYNAMO_OPTION(private_ib_in_tls) || TEST(FRAG_SHARED, (flags))) \
? instr_create_restore_from_tls(dc, reg, tls_offs) \
: instr_create_restore_from_dcontext((dc), (reg), (dc_offs)))
static void
mangle_far_direct_helper(dcontext_t *dcontext, instrlist_t *ilist, instr_t *instr,
instr_t *next_instr, uint flags)
{
* If the app wants to change segments in a WOW64 process, we will
* do the right thing for standard cs selector values (xref i#49).
* For other cs changes or in other modes, we do go through far_ibl
* today although we do not enact the cs change (nor bother to pass
* the selector in xbx).
*
* For WOW64, I tried keeping this a direct jmp for nice linking by doing the
* mode change in-fragment and then using a 64-bit stub with a 32-bit fragment,
* but that gets messy b/c a lot of code assumes it can create or calculate the
* size of exit stubs given nothing but the fragment flags. I tried adding
* FRAG_ENDS_IN_FAR_DIRECT but still need to pass another param to all the stub
* macros and routines for mid-trace exits and for prefixes for -disable_traces.
* So, going for treating as indirect and using the far_ibl. It's a trace
* barrier anyway, and rare. We treat it as indirect in all modes (including
* x86 builds) for simplicity (and eventually for full i#823 we'll want
* to issue cs changes there too).
*/
app_pc pc = opnd_get_pc(instr_get_target(instr));
#ifdef X64
if (!X64_MODE_DC(dcontext) &&
opnd_get_segment_selector(instr_get_target(instr)) == CS64_SELECTOR) {
PRE(ilist, instr,
SAVE_TO_DC_OR_TLS_OR_REG(dcontext, flags, REG_XBX, MANGLE_FAR_SPILL_SLOT,
XBX_OFFSET, REG_R10));
PRE(ilist, instr,
INSTR_CREATE_mov_imm(dcontext, opnd_create_reg(REG_EBX),
OPND_CREATE_INT32(CS64_SELECTOR)));
}
#endif
PRE(ilist, instr,
SAVE_TO_DC_OR_TLS_OR_REG(dcontext, flags, REG_XCX, MANGLE_XCX_SPILL_SLOT,
XCX_OFFSET, REG_R9));
ASSERT((ptr_uint_t)pc < UINT_MAX);
PRE(ilist, instr,
INSTR_CREATE_mov_imm(dcontext, opnd_create_reg(REG_ECX),
OPND_CREATE_INT32((ptr_uint_t)pc)));
}
* DIRECT CALL
* Returns new next_instr
*/
instr_t *
mangle_direct_call(dcontext_t *dcontext, instrlist_t *ilist, instr_t *instr,
instr_t *next_instr, bool mangle_calls, uint flags)
{
ptr_uint_t retaddr;
app_pc target = NULL;
opnd_t pushop = instr_get_dst(instr, 1);
opnd_size_t pushsz = stack_entry_size(instr, opnd_get_size(pushop));
if (opnd_is_near_pc(instr_get_target(instr)))
target = opnd_get_pc(instr_get_target(instr));
else if (opnd_is_instr(instr_get_target(instr))) {
instr_t *tgt = opnd_get_instr(instr_get_target(instr));
target = instr_get_raw_bits(tgt);
ASSERT(target != 0);
* assume it matches current cs */
} else if (opnd_is_far_pc(instr_get_target(instr))) {
target = opnd_get_pc(instr_get_target(instr));
} else
ASSERT_NOT_REACHED();
if (!mangle_calls) {
instr_set_raw_bits_valid(instr, false);
return next_instr;
}
retaddr = get_call_return_address(dcontext, ilist, instr);
#ifdef CHECK_RETURNS_SSE2
* matching ret! */
if (target == (app_pc)retaddr) {
LOG(THREAD, LOG_INTERP, 3, "found call to next instruction " PFX "\n", target);
} else {
check_return_handle_call(dcontext, ilist, next_instr);
}
#endif
if (instr_get_opcode(instr) == OP_call_far) {
* if the app wants to change segments, we won't actually issue
* a segment change, and so will only work properly if the new segment
* is also 0-based. To properly issue new segments, we'd need a special
* ibl that ends in a far cti, and all prior address manipulations would
* need to be relative to the new segment, w/o messing up current segment.
* FIXME: can we do better without too much work?
* XXX: yes, for wow64: i#823: TODO mangle this like a far direct jmp
*/
SYSLOG_INTERNAL_WARNING_ONCE("Encountered a far direct call");
STATS_INC(num_far_dir_calls);
mangle_far_direct_helper(dcontext, ilist, instr, next_instr, flags);
insert_push_cs(dcontext, ilist, instr, 0, pushsz);
}
insert_push_retaddr(dcontext, ilist, instr, retaddr, pushsz);
instrlist_remove(ilist, instr);
instr_destroy(dcontext, instr);
return next_instr;
}
#ifdef UNIX
* Mangle the memory reference operand that uses fs/gs semgents,
* get the segment base of fs/gs into reg, and
* replace oldop with newop using reg instead of fs/gs
* The reg must not be used in the oldop, otherwise, the reg value
* is corrupted.
*/
static ushort tls_slots[4] = { TLS_XAX_SLOT, TLS_XCX_SLOT, TLS_XDX_SLOT, TLS_XBX_SLOT };
opnd_t
mangle_seg_ref_opnd(dcontext_t *dcontext, instrlist_t *ilist, instr_t *instr,
opnd_t oldop, reg_id_t reg)
{
opnd_t newop;
reg_id_t seg;
ASSERT(opnd_is_far_base_disp(oldop));
seg = opnd_get_segment(oldop);
* (an assumption throughout the code, and always true for x64).
*/
if (seg != SEG_GS && seg != SEG_FS)
return oldop;
if (seg == LIB_SEG_TLS && !INTERNAL_OPTION(private_loader))
return oldop;
ASSERT(!opnd_uses_reg(oldop, reg));
PRE(ilist, instr,
instr_create_restore_from_tls(dcontext, reg, os_get_app_tls_base_offset(seg)));
if ((opnd_get_base(oldop) != DR_REG_NULL &&
reg_get_size(opnd_get_base(oldop)) == OPSZ_2) ||
(opnd_get_index(oldop) != DR_REG_NULL &&
reg_get_size(opnd_get_index(oldop)) == OPSZ_2)) {
* need another scratch reg to first compute thet 16-bit-reg address.
*/
reg_id_t scratch2;
for (scratch2 = REG_XAX; scratch2 <= REG_XBX; scratch2++) {
if (!instr_uses_reg(instr, scratch2) && scratch2 != reg)
break;
}
ASSERT(scratch2 <= REG_XBX);
PRE(ilist, instr,
instr_create_save_to_tls(dcontext, scratch2, tls_slots[scratch2 - REG_XAX]));
instr_set_our_mangling(instr, true);
PRE(ilist, instr_get_next(instr),
instr_set_translation_mangling_epilogue(
dcontext, ilist,
instr_create_restore_from_tls(dcontext, scratch2,
tls_slots[scratch2 - REG_XAX])));
* easier to read (does not affect encoding or correctness).
*/
PRE(ilist, instr,
instr_set_prefix_flag(
INSTR_CREATE_lea(dcontext, opnd_create_reg(scratch2),
opnd_create_base_disp(opnd_get_base(oldop),
opnd_get_index(oldop),
opnd_get_scale(oldop),
opnd_get_disp(oldop), OPSZ_lea)),
PREFIX_ADDR));
uint prefixes = instr_get_prefixes(instr);
instr_set_prefixes(instr, prefixes & (~PREFIX_ADDR));
return opnd_create_base_disp(reg, scratch2, 1, 0, opnd_get_size(oldop));
}
if (opnd_get_index(oldop) != REG_NULL && opnd_get_base(oldop) != REG_NULL) {
* lea [base, reg, 1] => reg
* to get the base + seg_base into reg.
*/
PRE(ilist, instr,
INSTR_CREATE_lea(
dcontext, opnd_create_reg(reg),
opnd_create_base_disp(opnd_get_base(oldop), reg, 1, 0, OPSZ_lea)));
}
if (opnd_get_index(oldop) != REG_NULL) {
newop = opnd_create_base_disp(reg, opnd_get_index(oldop), opnd_get_scale(oldop),
opnd_get_disp(oldop), opnd_get_size(oldop));
} else {
newop = opnd_create_base_disp(opnd_get_base(oldop), reg, 1, opnd_get_disp(oldop),
opnd_get_size(oldop));
}
return newop;
}
#endif
* INDIRECT CALL
*/
static reg_id_t
mangle_far_indirect_helper(dcontext_t *dcontext, instrlist_t *ilist, instr_t *instr,
instr_t *next_instr, uint flags, opnd_t *target_out)
{
opnd_t target = *target_out;
opnd_size_t addr_size;
reg_id_t reg_target = REG_NULL;
ASSERT(instr_get_opcode(instr) == OP_jmp_far_ind ||
instr_get_opcode(instr) == OP_call_far_ind);
* If the app wants to change segments in a WOW64 process, we will
* do the right thing for standard cs selector values (xref i#49).
* For other cs changes or in other modes, we do go through far_ibl
* today although we do not enact the cs change (nor bother to pass
* the selector in xbx).
*/
* memory location, not specified as segment prefix on instr
* we assume register operands are marked as invalid instrs long
* before this point.
*/
ASSERT(opnd_is_base_disp(target));
* For non-mixed-mode, we ignore it.
* We assume DS base == target cti CS base.
*/
* if x64 mode and Intel and rex then 8 bytes for address */
ASSERT((X64_MODE_DC(dcontext) && opnd_get_size(target) == OPSZ_10 &&
proc_get_vendor() != VENDOR_AMD) ||
opnd_get_size(target) == OPSZ_6 || opnd_get_size(target) == OPSZ_4);
if (opnd_get_size(target) == OPSZ_10) {
addr_size = OPSZ_8;
reg_target = REG_RCX;
} else if (opnd_get_size(target) == OPSZ_6) {
addr_size = OPSZ_4;
reg_target = REG_ECX;
} else {
addr_size = OPSZ_2;
reg_target = REG_XCX;
}
#ifdef X64
if (mixed_mode_enabled()) {
* mode changes using standard cs selector values (i#823).
* We save the selector into xbx.
*/
opnd_t sel = target;
opnd_set_disp(&sel, opnd_get_disp(target) + opnd_size_in_bytes(addr_size));
opnd_set_size(&sel, OPSZ_2);
ASSERT(TEST(FRAG_SHARED, flags) || DYNAMO_OPTION(private_ib_in_tls));
PRE(ilist, instr,
SAVE_TO_DC_OR_TLS_OR_REG(dcontext, flags, REG_XBX, MANGLE_FAR_SPILL_SLOT,
XBX_OFFSET, REG_R10));
PRE(ilist, instr, INSTR_CREATE_movzx(dcontext, opnd_create_reg(REG_EBX), sel));
if (instr_uses_reg(instr, REG_XBX)) {
* a register, so we spill to the riprel (== xax) slot
*/
PRE(ilist, instr,
SAVE_TO_DC_OR_TLS(dcontext, flags, REG_XBX, MANGLE_RIPREL_SPILL_SLOT,
XAX_OFFSET));
POST(ilist, instr,
instr_create_restore_from_tls(dcontext, REG_XBX,
MANGLE_RIPREL_SPILL_SLOT));
}
}
#endif
opnd_set_size(target_out, addr_size);
return reg_target;
}
instr_t *
mangle_indirect_call(dcontext_t *dcontext, instrlist_t *ilist, instr_t *instr,
instr_t *next_instr, bool mangle_calls, uint flags)
{
opnd_t target;
ptr_uint_t retaddr;
opnd_t pushop = instr_get_dst(instr, 1);
opnd_size_t pushsz = stack_entry_size(instr, opnd_get_size(pushop));
reg_id_t reg_target = REG_XCX;
if (!mangle_calls)
return next_instr;
retaddr = get_call_return_address(dcontext, ilist, instr);
* jumps to indirect_branch_lookup was already inserted into the
* instr list by interp EXCEPT for the case in which we're converting
* an indirect call to a direct call. In that case, mangle later
* inserts a direct exit stub.
*/
* FIXME Just a note that converted calls are not subjected to any of
* the specialized builds' processing further down.
*/
if (TEST(INSTR_IND_CALL_DIRECT, instr->flags)) {
insert_push_retaddr(dcontext, ilist, instr, retaddr, pushsz);
instrlist_remove(ilist, instr);
instr_destroy(dcontext, instr);
return next_instr;
}
* the target, b/c if target depends on xsp we must use
* the value of xsp prior to this call instruction!
* we insert before next_instr to accomplish this.
*/
if (instr_get_opcode(instr) == OP_call_far_ind) {
insert_push_cs(dcontext, ilist, next_instr, 0, pushsz);
* though we do go ahead and push cs, we won't pop into cs
*/
}
insert_push_retaddr(dcontext, ilist, next_instr, retaddr, pushsz);
PRE(ilist, instr,
SAVE_TO_DC_OR_TLS_OR_REG(dcontext, flags, REG_XCX, MANGLE_XCX_SPILL_SLOT,
XCX_OFFSET, REG_R9));
target = instr_get_src(instr, 0);
if (instr_get_opcode(instr) == OP_call_far_ind) {
SYSLOG_INTERNAL_WARNING_ONCE("Encountered a far indirect call");
STATS_INC(num_far_ind_calls);
reg_target = mangle_far_indirect_helper(dcontext, ilist, instr, next_instr, flags,
&target);
}
#ifdef UNIX
if (INTERNAL_OPTION(mangle_app_seg) && opnd_is_far_base_disp(target)) {
* in "target" and cause failure in mangle_seg_ref_opnd.
* We need to spill another register in that case.
*/
ASSERT_BUG_NUM(107,
!opnd_uses_reg(target, REG_XCX) ||
(opnd_get_segment(target) != SEG_FS &&
opnd_get_segment(target) != SEG_GS));
target = mangle_seg_ref_opnd(dcontext, ilist, instr, target, REG_XCX);
}
#endif
instr_free(dcontext, instr);
instr_set_num_opnds(dcontext, instr, 1, 1);
instr_set_opcode(instr, opnd_get_size(target) == OPSZ_2 ? OP_movzx : OP_mov_ld);
instr_set_dst(instr, 0, opnd_create_reg(reg_target));
instr_set_src(instr, 0, target);
if (instrlist_get_translation_target(ilist) != NULL) {
instr_set_translation(instr, instr_get_raw_bits(instr));
}
instr_set_our_mangling(instr, true);
#ifdef CHECK_RETURNS_SSE2
check_return_handle_call(dcontext, ilist, next_instr);
#endif
return next_instr;
}
* RETURN
*/
#ifdef X64
* for far_ibl.
*/
static void
mangle_far_return_save_selector(dcontext_t *dcontext, instrlist_t *ilist, instr_t *instr,
uint flags)
{
if (mixed_mode_enabled()) {
* mode changes using standard cs selector values (i#823).
* We save the selector into xbx.
*/
ASSERT(TEST(FRAG_SHARED, flags) || DYNAMO_OPTION(private_ib_in_tls));
PRE(ilist, instr,
SAVE_TO_DC_OR_TLS_OR_REG(dcontext, flags, REG_XBX, MANGLE_FAR_SPILL_SLOT,
XBX_OFFSET, REG_R10));
PRE(ilist, instr,
INSTR_CREATE_movzx(dcontext, opnd_create_reg(REG_EBX),
OPND_CREATE_MEM16(REG_XSP, 0)));
}
}
#endif
void
mangle_return(dcontext_t *dcontext, instrlist_t *ilist, instr_t *instr,
instr_t *next_instr, uint flags)
{
instr_t *pop;
opnd_t retaddr;
opnd_size_t retsz;
#ifdef CHECK_RETURNS_SSE2
check_return_handle_return(dcontext, ilist, next_instr);
#endif
* fact that xcx is dead at the return and not bother saving it?
* The jump to the exit_stub that jumps to indirect_branch_lookup
* was already inserted into the instr list by interp. */
PRE(ilist, instr,
SAVE_TO_DC_OR_TLS_OR_REG(dcontext, flags, REG_XCX, MANGLE_XCX_SPILL_SLOT,
XCX_OFFSET, REG_R9));
if (instr_num_srcs(instr) > 0 && opnd_is_immed_int(instr_get_src(instr, 0))) {
* AFTER the return address is popped
*/
int val = (int)opnd_get_immed_int(instr_get_src(instr, 0));
IF_X64(ASSERT_TRUNCATE(val, int, opnd_get_immed_int(instr_get_src(instr, 0))));
* except that clobbers the flags, so we use lea */
PRE(ilist, next_instr,
INSTR_CREATE_lea(dcontext, opnd_create_reg(REG_XSP),
opnd_create_base_disp(REG_XSP, REG_NULL, 0, val, OPSZ_lea)));
}
retaddr = instr_get_src(instr, instr_num_srcs(instr) - 1);
retsz = stack_entry_size(instr, opnd_get_size(retaddr));
if (X64_CACHE_MODE_DC(dcontext) && retsz == OPSZ_4) {
if (instr_get_opcode(instr) == OP_iret || instr_get_opcode(instr) == OP_ret_far) {
* size 4 in 64-bit mode (making them, along w/ call_far, the only stack
* operation instructions to do so). So if we see an iret or far ret with
* OPSZ_4 in 64-bit mode we need a 4-byte pop, but since we can't actually
* generate a 4-byte pop we have to emulate it here. */
SYSLOG_INTERNAL_WARNING_ONCE("Encountered iretd/lretd in 64-bit mode!");
}
PRE(ilist, instr,
INSTR_CREATE_mov_ld(dcontext, opnd_create_reg(REG_ECX),
OPND_CREATE_MEM32(REG_RSP, 0)));
* lret could combine w/ segment lea below: but not perf-crit instr, and
* anticipating cs preservation PR 271317 I'm leaving separate. */
PRE(ilist, instr,
INSTR_CREATE_lea(dcontext, opnd_create_reg(REG_XSP),
opnd_create_base_disp(REG_XSP, REG_NULL, 0, 4, OPSZ_lea)));
} else {
opnd_t memop = retaddr;
pop = INSTR_CREATE_pop(dcontext, opnd_create_reg(REG_XCX));
opnd_set_size(&memop, retsz);
instr_set_src(pop, 1, memop);
if (retsz == OPSZ_2)
instr_set_dst(pop, 0, opnd_create_reg(REG_CX));
* handled above we should never see one. */
ASSERT(!X64_MODE_DC(dcontext) || retsz != OPSZ_4);
PRE(ilist, instr, pop);
if (retsz == OPSZ_2) {
PRE(ilist, instr,
INSTR_CREATE_movzx(dcontext, opnd_create_reg(REG_ECX),
opnd_create_reg(REG_CX)));
}
}
if (TEST(INSTR_CLOBBER_RETADDR, instr->flags)) {
ptr_uint_t val = (ptr_uint_t)instr->offset;
insert_mov_ptr_uint_beyond_TOS(dcontext, ilist, instr, val, retsz);
}
if (instr_get_opcode(instr) == OP_ret_far) {
* If the app wants to change segments in a WOW64 process, we will
* do the right thing for standard cs selector values (xref i#49).
* For other cs changes or in other modes, we do go through far_ibl
* today although we do not enact the cs change (nor bother to pass
* the selector in xbx).
*/
SYSLOG_INTERNAL_WARNING_ONCE("Encountered a far ret");
STATS_INC(num_far_rets);
#ifdef X64
mangle_far_return_save_selector(dcontext, ilist, instr, flags);
#endif
* (the 16-bit selector is expanded to 32 bits on the push, unless data16)
*/
PRE(ilist, instr,
INSTR_CREATE_lea(dcontext, opnd_create_reg(REG_XSP),
opnd_create_base_disp(REG_XSP, REG_NULL, 0,
opnd_size_in_bytes(retsz), OPSZ_lea)));
}
if (instr_get_opcode(instr) == OP_iret) {
instr_t *popf;
LOG(THREAD, LOG_INTERP, 2, "Encountered iret at " PFX " - mangling\n",
instr_get_translation(instr));
STATS_INC(num_irets);
* 64-bit mode (with either 32-bit or 64-bit operand size,
* despite the (wrong) Intel manual pseudocode: see i#833 and
* the win32.mixedmode test) extends
* the above and additionally adds pop->RSP pop->ss. N.B.: like OP_far_ret we
* ignore the CS (except mixed-mode WOW64) and SS segment changes
* (see the comments there).
*/
#ifdef X64
mangle_far_return_save_selector(dcontext, ilist, instr, flags);
#endif
* (unless in mixed-mode, handled above) so
* adjust stack pointer. Note we can use an add here since the eflags will
* be written below. */
PRE(ilist, instr,
INSTR_CREATE_add(dcontext, opnd_create_reg(REG_XSP),
OPND_CREATE_INT8(opnd_size_in_bytes(retsz))));
* (it's difficult to tell because in the docs iret lists the flags it does
* set while popf lists the flags it doesn't set). The docs aren't entirely
* clear, but any flag that we or a user mode program would care about should
* be right. */
popf = INSTR_CREATE_popf(dcontext);
if (X64_CACHE_MODE_DC(dcontext) && retsz == OPSZ_4) {
* simulate one. For now we'll do a 64-bit popf and fixup the stack offset.
* If AMD/INTEL ever start using the top half of the rflags register then
* we could have problems here. We could also break stack transparency and
* do a mov, push, popf to zero extend the value. */
PRE(ilist, instr, popf);
PRE(ilist, instr,
INSTR_CREATE_lea(
dcontext, opnd_create_reg(REG_XSP),
opnd_create_base_disp(REG_XSP, REG_NULL, 0, -4, OPSZ_lea)));
} else {
opnd_t memop = retaddr;
opnd_set_size(&memop, retsz);
DOCHECK(1, {
if (retsz == OPSZ_2)
ASSERT_NOT_TESTED();
});
instr_set_src(popf, 1, memop);
PRE(ilist, instr, popf);
}
mangle_possible_single_step(dcontext, ilist, popf);
#ifdef X64
if (X64_MODE_DC(dcontext)) {
if (retsz == OPSZ_8)
PRE(ilist, instr, INSTR_CREATE_pop(dcontext, opnd_create_reg(REG_RSP)));
else if (retsz == OPSZ_4) {
PRE(ilist, instr,
INSTR_CREATE_mov_ld(dcontext, opnd_create_reg(REG_ESP),
OPND_CREATE_MEM32(REG_RSP, 0)));
} else {
ASSERT_NOT_TESTED();
PRE(ilist, instr,
INSTR_CREATE_movzx(dcontext, opnd_create_reg(REG_ESP),
OPND_CREATE_MEM16(REG_RSP, 0)));
}
* to do anything to adjust the stack for the pop (since the pop would have
* occurred with the old RSP). */
}
#endif
}
instrlist_remove(ilist, instr);
instr_destroy(dcontext, instr);
}
* INDIRECT JUMP
*/
instr_t *
mangle_indirect_jump(dcontext_t *dcontext, instrlist_t *ilist, instr_t *instr,
instr_t *next_instr, uint flags)
{
opnd_t target;
reg_id_t reg_target = REG_XCX;
* jump to the exit_stub that jumps to indirect_branch_lookup
* was already inserted into the instr list by interp. */
PRE(ilist, instr,
SAVE_TO_DC_OR_TLS_OR_REG(dcontext, flags, REG_XCX, MANGLE_XCX_SPILL_SLOT,
XCX_OFFSET, REG_R9));
target = instr_get_target(instr);
if (instr_get_opcode(instr) == OP_jmp_far_ind) {
SYSLOG_INTERNAL_WARNING_ONCE("Encountered a far indirect jump");
STATS_INC(num_far_ind_jmps);
reg_target = mangle_far_indirect_helper(dcontext, ilist, instr, next_instr, flags,
&target);
}
#ifdef UNIX
if (INTERNAL_OPTION(mangle_app_seg) && opnd_is_far_base_disp(target)) {
* in "target" and cause failure in mangle_seg_ref_opnd.
* We need to spill another register in that case.
*/
ASSERT_BUG_NUM(107,
!opnd_uses_reg(target, REG_XCX) ||
(opnd_get_segment(target) != SEG_FS &&
opnd_get_segment(target) != SEG_GS));
target = mangle_seg_ref_opnd(dcontext, ilist, instr, target, REG_XCX);
}
#endif
instr_free(dcontext, instr);
instr_set_num_opnds(dcontext, instr, 1, 1);
instr_set_opcode(instr, opnd_get_size(target) == OPSZ_2 ? OP_movzx : OP_mov_ld);
instr_set_dst(instr, 0, opnd_create_reg(reg_target));
instr_set_src(instr, 0, target);
if (instrlist_get_translation_target(ilist) != NULL) {
instr_set_translation(instr, instr_get_raw_bits(instr));
}
instr_set_our_mangling(instr, true);
* because no branch can simultaneously use 3 registers, right?
* Maximum is 2, in something like "jmp *(edi,ebx,4)"?
* If it is possible, need to make sure stealing's use of ecx
* doesn't conflict w/ our use = FIXME
*/
return next_instr;
}
* FAR DIRECT JUMP
*/
void
mangle_far_direct_jump(dcontext_t *dcontext, instrlist_t *ilist, instr_t *instr,
instr_t *next_instr, uint flags)
{
SYSLOG_INTERNAL_WARNING_ONCE("Encountered a far direct jmp");
STATS_INC(num_far_dir_jmps);
mangle_far_direct_helper(dcontext, ilist, instr, next_instr, flags);
instrlist_remove(ilist, instr);
instr_destroy(dcontext, instr);
}
* SYSCALL
*/
#ifdef UNIX
* instr is the syscall instr itself.
* Assumes that instructions exist beyond instr in ilist.
*
* CAUTION: don't use a lot of stack in the generated code because
* get_clone_record() makes assumptions about the usage of stack being
* less than a page.
*/
void
mangle_insert_clone_code(dcontext_t *dcontext, instrlist_t *ilist,
instr_t *instr _IF_X64(gencode_mode_t mode))
{
* xchg xax,xcx
* jecxz child
* jmp parent
* child:
* xchg xax,xcx
* # i#149/PR 403015: the child is on the dstack so no need to swap stacks
* jmp new_thread_dynamo_start
* parent:
* xchg xax,xcx
* <post system call, etc.>
*/
instr_t *in = instr_get_next(instr);
instr_t *child = INSTR_CREATE_label(dcontext);
instr_t *parent = INSTR_CREATE_label(dcontext);
ASSERT(in != NULL);
PRE(ilist, in,
INSTR_CREATE_xchg(dcontext, opnd_create_reg(REG_XAX), opnd_create_reg(REG_XCX)));
PRE(ilist, in, INSTR_CREATE_jecxz(dcontext, opnd_create_instr(child)));
PRE(ilist, in, INSTR_CREATE_jmp(dcontext, opnd_create_instr(parent)));
PRE(ilist, in, child);
PRE(ilist, in,
INSTR_CREATE_xchg(dcontext, opnd_create_reg(REG_XAX), opnd_create_reg(REG_XCX)));
* calls, but not once we eliminated clean calls out of the DR cache
* for security purposes. Thus it can be a meta jmp, or an indirect jmp.
*/
insert_reachable_cti(dcontext, ilist, in, vmcode_get_start(),
(byte *)get_new_thread_start(dcontext _IF_X64(mode)),
true , false , false ,
DR_REG_NULL , NULL);
instr_set_meta(instr_get_prev(in));
PRE(ilist, in, parent);
PRE(ilist, in,
INSTR_CREATE_xchg(dcontext, opnd_create_reg(REG_XAX), opnd_create_reg(REG_XCX)));
}
#endif
#ifdef WINDOWS
* For this reason, mangle_syscall() might be called with a 'next_instr' that's
* not an original app instruction but one inserted by the earlier mangling phase.
*/
#endif
* and interrupted_inlined_syscall() which have assumptions about the
* exact code around inlined system calls.
*/
void
mangle_syscall_arch(dcontext_t *dcontext, instrlist_t *ilist, uint flags, instr_t *instr,
instr_t *next_instr)
{
#ifdef UNIX
* and inserted the signal barrier and non-auto-restart nop.
* If we get here, we're dealing with an ignorable syscall.
*/
# ifdef MACOS
if (instr_get_opcode(instr) == OP_sysenter) {
* We get control back here and then go to the ret ibl (since normally
* there's a call to a shared routine that does "pop edx").
*/
instr_t *post_sysenter = INSTR_CREATE_label(dcontext);
PRE(ilist, instr,
SAVE_TO_DC_OR_TLS(dcontext, flags, REG_XDX, TLS_XDX_SLOT, XDX_OFFSET));
instrlist_insert_mov_instr_addr(dcontext, post_sysenter, NULL ,
opnd_create_reg(REG_XDX), ilist, instr, NULL,
NULL);
PRE(ilist, next_instr, post_sysenter);
* mangling epilogue.
*/
PRE(ilist, next_instr,
RESTORE_FROM_DC_OR_TLS(dcontext, flags, REG_XDX, TLS_XDX_SLOT, XDX_OFFSET));
PRE(ilist, next_instr,
SAVE_TO_DC_OR_TLS(dcontext, flags, REG_XCX, TLS_XCX_SLOT, XCX_OFFSET));
PRE(ilist, next_instr,
INSTR_CREATE_mov_st(dcontext, opnd_create_reg(REG_XCX),
opnd_create_reg(REG_XDX)));
} else if (TEST(INSTR_BRANCH_SPECIAL_EXIT, instr->flags)) {
int num = instr_get_interrupt_number(instr);
ASSERT(instr_get_opcode(instr) == OP_int);
if (num == 0x81 || num == 0x82) {
int reason = (num == 0x81) ? EXIT_REASON_NI_SYSCALL_INT_0x81
: EXIT_REASON_NI_SYSCALL_INT_0x82;
if (DYNAMO_OPTION(private_ib_in_tls) || TEST(FRAG_SHARED, flags)) {
insert_shared_get_dcontext(dcontext, ilist, instr, true );
PRE(ilist, instr,
INSTR_CREATE_mov_st(
dcontext,
opnd_create_dcontext_field_via_reg_sz(
dcontext, REG_NULL , EXIT_REASON_OFFSET, OPSZ_2),
OPND_CREATE_INT16(reason)));
insert_shared_restore_dcontext_reg(dcontext, ilist, instr);
} else {
PRE(ilist, instr,
instr_create_save_immed16_to_dcontext(dcontext, reason,
EXIT_REASON_OFFSET));
}
}
}
# endif
#else
* in do_syscall
*/
* do we need support for exiting mid-fragment prior to a syscall
* like we do on Linux, to bound time in cache?
*/
if (does_syscall_ret_to_callsite()) {
uint len = instr_length(dcontext, instr);
if (TEST(INSTR_SHARED_SYSCALL, instr->flags)) {
ASSERT(DYNAMO_OPTION(shared_syscalls));
* we just need to place a return address into the dcontext
* xsi slot or the mangle-next-tag tls slot
*/
if (DYNAMO_OPTION(shared_fragment_shared_syscalls)) {
# ifdef X64
ASSERT(instr_raw_bits_valid(instr));
* FIXME: would be nice to move this to the stub and
* use the dead rbx register!
*/
PRE(ilist, instr,
instr_create_save_to_tls(dcontext, REG_XCX, MANGLE_NEXT_TAG_SLOT));
PRE(ilist, instr,
INSTR_CREATE_mov_imm(dcontext, opnd_create_reg(REG_XCX),
OPND_CREATE_INTPTR((instr->bytes + len))));
PRE(ilist, instr,
INSTR_CREATE_xchg(
dcontext,
opnd_create_tls_slot(os_tls_offset(MANGLE_NEXT_TAG_SLOT)),
opnd_create_reg(REG_XCX)));
# else
PRE(ilist, instr,
INSTR_CREATE_mov_st(
dcontext,
opnd_create_tls_slot(os_tls_offset(MANGLE_NEXT_TAG_SLOT)),
OPND_CREATE_INTPTR((instr->bytes + len))));
# endif
} else {
PRE(ilist, instr,
instr_create_save_immed32_to_dcontext(
dcontext, (uint)(ptr_uint_t)(instr->bytes + len), XSI_OFFSET));
}
}
* destroyed and removed from the list at the end of this func.
*/
else if (!TEST(INSTR_NI_SYSCALL, instr->flags)) {
if (get_syscall_method() == SYSCALL_METHOD_INT && DYNAMO_OPTION(sygate_int)) {
* is anyone going to get screwed up by this change
* (say flags change?) [-ignore_syscalls only]*/
ASSERT_NOT_TESTED();
instrlist_replace(ilist, instr, create_syscall_instr(dcontext));
instr_destroy(dcontext, instr);
} else if (get_syscall_method() == SYSCALL_METHOD_SYSCALL)
ASSERT_NOT_TESTED();
else if (get_syscall_method() == SYSCALL_METHOD_WOW64)
ASSERT_NOT_TESTED();
return;
}
} else if (get_syscall_method() == SYSCALL_METHOD_SYSENTER) {
* return pt of the sysenter, which is 0x7ffe0304 (except for
* SP2-patched XP), which is bad since it would clobber whatever's after
* the ret there (unless we used a 0xcc, like Visual Studio 2005 debugger
* does), or replacing the ret addr on the stack -- we choose the
* latter as the lesser of two transparency evils. Note that the
* page at 0x7ffe0000 can't be made writable anyway, so hooking
* isn't possible.
*/
if (TEST(INSTR_SHARED_SYSCALL, instr->flags)) {
ASSERT(DYNAMO_OPTION(shared_syscalls));
}
* destroyed and removed from the list at the end of this func.
*/
else if (!TEST(INSTR_NI_SYSCALL, instr->flags)) {
instr_t *mov_imm;
ASSERT(next_instr != NULL);
ASSERT(DYNAMO_OPTION(indcall2direct));
* shared_syscall, but here we could be shared so would need to
* grab dcontext first etc. */
ASSERT_NOT_IMPLEMENTED(!DYNAMO_OPTION(sygate_sysenter));
IF_X64(ASSERT_NOT_IMPLEMENTED(false));
* app stack reference here. It's marked as our own mangling
* so we'll at least return failure from our translate routine.
*/
mov_imm = INSTR_CREATE_mov_st(dcontext, OPND_CREATE_MEM32(REG_XSP, 0),
opnd_create_instr(next_instr));
ASSERT(instr_is_mov_imm_to_tos(mov_imm));
PRE(ilist, instr, mov_imm);
* o/w will lose pc-relative opnd
*/
* 'ret', which is executed natively. */
instr_set_meta(instr_get_prev(instr));
return;
}
} else {
SYSLOG_INTERNAL_ERROR("unsupported system call method");
LOG(THREAD, LOG_INTERP, 1, "don't know convention for this syscall method\n");
if (!TEST(INSTR_NI_SYSCALL, instr->flags))
return;
ASSERT_NOT_IMPLEMENTED(false);
}
instrlist_remove(ilist, instr);
instr_destroy(dcontext, instr);
#endif
}
* NON-SYSCALL INTERRUPT
*/
void
mangle_interrupt(dcontext_t *dcontext, instrlist_t *ilist, instr_t *instr,
instr_t *next_instr)
{
#ifdef WINDOWS
int num;
if (instr_get_opcode(instr) != OP_int)
return;
num = instr_get_interrupt_number(instr);
if (num == 0x2b) {
* point of the thread with the instruction "int 2b".
* The interrupt ends the block; remove the instruction
* since we'll come back to dynamo to perform the
* interrupt.
*/
instrlist_remove(ilist, instr);
instr_destroy(dcontext, instr);
}
#endif
}
* Single step exceptions catching
*/
void
mangle_possible_single_step(dcontext_t *dcontext, instrlist_t *ilist, instr_t *instr)
{
* exception might occur is in the same basic block and so that the
* translation of a single step exception points back to the instruction
* which set the trap flag.
* The single step exception is a problem because
* the ExceptionAddress should be the next EIP.
*/
POST(ilist, instr, INSTR_CREATE_nop(dcontext));
POST(ilist, instr, INSTR_CREATE_nop(dcontext));
}
* Single step exceptions generation
*/
void
mangle_single_step(dcontext_t *dcontext, instrlist_t *ilist, uint flags, instr_t *instr)
{
if (DYNAMO_OPTION(private_ib_in_tls) || TEST(FRAG_SHARED, flags)) {
insert_shared_get_dcontext(dcontext, ilist, instr, true );
PRE(ilist, instr,
INSTR_CREATE_mov_st(
dcontext,
opnd_create_dcontext_field_via_reg_sz(dcontext, REG_NULL ,
EXIT_REASON_OFFSET, OPSZ_2),
OPND_CREATE_INT16(EXIT_REASON_SINGLE_STEP)));
insert_shared_restore_dcontext_reg(dcontext, ilist, instr);
} else {
PRE(ilist, instr,
instr_create_save_immed16_to_dcontext(dcontext, EXIT_REASON_SINGLE_STEP,
EXIT_REASON_OFFSET));
}
}
* FLOATING POINT PC
*/
#define FNSAVE_PC_OFFS 12
#define FXSAVE_PC_OFFS 8
#define FXSAVE_SIZE 512
void
float_pc_update(dcontext_t *dcontext)
{
byte *state = *(byte **)(((byte *)dcontext->local_state) + FLOAT_PC_STATE_SLOT);
app_pc orig_pc, xl8_pc;
uint offs = 0;
LOG(THREAD, LOG_INTERP, 2, "%s: fp state " PFX "\n", __FUNCTION__, state);
if (dcontext->upcontext.upcontext.exit_reason == EXIT_REASON_FLOAT_PC_XSAVE ||
dcontext->upcontext.upcontext.exit_reason == EXIT_REASON_FLOAT_PC_XSAVE64) {
uint64 header_bv = *(uint64 *)(state + FXSAVE_SIZE);
if (!TEST(XCR0_FP, header_bv)) {
LOG(THREAD, LOG_INTERP, 2, "%s: xsave did not save FP state => nop\n",
__FUNCTION__);
}
return;
}
if (dcontext->upcontext.upcontext.exit_reason == EXIT_REASON_FLOAT_PC_FNSAVE) {
offs = FNSAVE_PC_OFFS;
} else {
offs = FXSAVE_PC_OFFS;
}
if (dcontext->upcontext.upcontext.exit_reason == EXIT_REASON_FLOAT_PC_FXSAVE64 ||
dcontext->upcontext.upcontext.exit_reason == EXIT_REASON_FLOAT_PC_XSAVE64)
orig_pc = *(app_pc *)(state + offs);
else
orig_pc = (app_pc)(ptr_uint_t) * (uint *)(state + offs);
if (orig_pc == NULL) {
LOG(THREAD, LOG_INTERP, 2, "%s: pc is NULL\n", __FUNCTION__);
return;
}
if (!in_fcache(orig_pc) &&
!(in_generated_routine(dcontext, orig_pc) || is_dynamo_address(orig_pc) ||
is_in_dynamo_dll(orig_pc) || is_in_client_lib(orig_pc))) {
bool no_xl8 = true;
#ifdef X64
if (dcontext->upcontext.upcontext.exit_reason != EXIT_REASON_FLOAT_PC_FXSAVE64 &&
dcontext->upcontext.upcontext.exit_reason != EXIT_REASON_FLOAT_PC_XSAVE64) {
ptr_uint_t vmcode = (ptr_uint_t)vmcode_get_start();
if ((vmcode & 0xffffffff00000000) > 0) {
byte *orig_try =
(byte *)((vmcode & 0xffffffff00000000) | (ptr_uint_t)orig_pc);
if (in_fcache(orig_try)) {
LOG(THREAD, LOG_INTERP, 2,
"%s: speculating: pc " PFX " + top half of vmcode = " PFX "\n",
__FUNCTION__, orig_pc, orig_try);
orig_pc = orig_try;
no_xl8 = false;
}
}
}
#endif
if (no_xl8) {
LOG(THREAD, LOG_INTERP, 2, "%s: pc " PFX " is translated already\n",
__FUNCTION__, orig_pc);
return;
}
}
d_r_mutex_lock(&thread_initexit_lock);
xl8_pc = recreate_app_pc(dcontext, orig_pc, NULL);
d_r_mutex_unlock(&thread_initexit_lock);
LOG(THREAD, LOG_INTERP, 2, "%s: translated " PFX " to " PFX "\n", __FUNCTION__,
orig_pc, xl8_pc);
if (dcontext->upcontext.upcontext.exit_reason == EXIT_REASON_FLOAT_PC_FXSAVE64 ||
dcontext->upcontext.upcontext.exit_reason == EXIT_REASON_FLOAT_PC_XSAVE64)
*(app_pc *)(state + offs) = xl8_pc;
else
*(uint *)(state + offs) = (uint)(ptr_uint_t)xl8_pc;
}
void
mangle_float_pc(dcontext_t *dcontext, instrlist_t *ilist, instr_t *instr,
instr_t *next_instr, uint *flags INOUT)
{
* Otherwise, we go back to d_r_dispatch. In the latter case we do not support
* building traces across the float pc save: we assume it's rare.
*/
app_pc prior_float = NULL;
bool exit_is_normal = false;
int op = instr_get_opcode(instr);
opnd_t memop = instr_get_dst(instr, 0);
ASSERT(opnd_is_memory_reference(memop));
* We also don't support xsave, as it optionally writes the fpstate.
*/
if (opnd_is_base_disp(memop) && op != OP_xsave32 && op != OP_xsaveopt32 &&
op != OP_xsave64 && op != OP_xsaveopt64 && op != OP_xsavec32 &&
op != OP_xsavec64) {
instr_t *prev;
for (prev = instr_get_prev_expanded(dcontext, ilist, instr); prev != NULL;
prev = instr_get_prev_expanded(dcontext, ilist, prev)) {
dr_fp_type_t type;
if (instr_is_app(prev) && instr_is_floating_ex(prev, &type)) {
bool control_instr = false;
if (type == DR_FP_STATE &&
(op == OP_fnclex || op == OP_fldcw || op == OP_fnstcw ||
op == OP_fnstsw || op == OP_fnstenv || op == OP_fldenv ||
op == OP_fwait))
control_instr = true;
if (!control_instr) {
prior_float = get_app_instr_xl8(prev);
break;
}
}
}
}
if (prior_float != NULL) {
exit_is_normal = true;
STATS_INC(float_pc_from_cache);
* If the app memory is unwritable, instr would have already crashed.
*/
if (op == OP_fnsave || op == OP_fnstenv) {
opnd_set_disp(&memop, opnd_get_disp(memop) + FNSAVE_PC_OFFS);
opnd_set_size(&memop, OPSZ_4);
PRE(ilist, next_instr,
INSTR_CREATE_mov_st(dcontext, memop,
OPND_CREATE_INT32((int)(ptr_int_t)prior_float)));
} else if (op == OP_fxsave32) {
opnd_set_disp(&memop, opnd_get_disp(memop) + FXSAVE_PC_OFFS);
opnd_set_size(&memop, OPSZ_4);
PRE(ilist, next_instr,
INSTR_CREATE_mov_st(dcontext, memop,
OPND_CREATE_INT32((int)(ptr_int_t)prior_float)));
} else if (op == OP_fxsave64) {
opnd_set_disp(&memop, opnd_get_disp(memop) + FXSAVE_PC_OFFS);
opnd_set_size(&memop, OPSZ_8);
insert_mov_immed_ptrsz(dcontext, (ptr_int_t)prior_float, memop, ilist,
next_instr, NULL, NULL);
} else
ASSERT_NOT_REACHED();
} else if (!DYNAMO_OPTION(translate_fpu_pc)) {
* XXX: we can't recover the loss of coarse-grained: we live with that.
*/
exit_is_normal = true;
ASSERT_CURIOSITY(!TEST(FRAG_CANNOT_BE_TRACE, *flags) ||
TEST(FRAG_SELFMOD_SANDBOXED, *flags));
} else {
int reason = 0;
CLIENT_ASSERT(!TEST(FRAG_IS_TRACE, *flags),
"removing an FPU instr in a trace with an FPU state save "
"is not supported");
switch (op) {
case OP_fnsave:
case OP_fnstenv: reason = EXIT_REASON_FLOAT_PC_FNSAVE; break;
case OP_fxsave32: reason = EXIT_REASON_FLOAT_PC_FXSAVE; break;
case OP_fxsave64: reason = EXIT_REASON_FLOAT_PC_FXSAVE64; break;
case OP_xsave32:
case OP_xsavec32:
case OP_xsaveopt32: reason = EXIT_REASON_FLOAT_PC_XSAVE; break;
case OP_xsave64:
case OP_xsavec64:
case OP_xsaveopt64: reason = EXIT_REASON_FLOAT_PC_XSAVE64; break;
default: ASSERT_NOT_REACHED();
}
if (DYNAMO_OPTION(private_ib_in_tls) || TEST(FRAG_SHARED, *flags)) {
insert_shared_get_dcontext(dcontext, ilist, instr, true );
PRE(ilist, instr,
INSTR_CREATE_mov_st(
dcontext,
opnd_create_dcontext_field_via_reg_sz(dcontext, REG_NULL ,
EXIT_REASON_OFFSET, OPSZ_2),
OPND_CREATE_INT16(reason)));
} else {
PRE(ilist, instr,
instr_create_save_immed16_to_dcontext(dcontext, reason,
EXIT_REASON_OFFSET));
PRE(ilist, instr,
instr_create_save_to_tls(dcontext, REG_XDI, DCONTEXT_BASE_SPILL_SLOT));
}
*
* XXX: handle far refs! Xref drutil_insert_get_mem_addr(), and sandbox_write()
* hitting this same issue.
*/
ASSERT_CURIOSITY(!opnd_is_far_memory_reference(memop));
if (opnd_is_base_disp(memop)) {
opnd_set_size(&memop, OPSZ_lea);
PRE(ilist, instr,
INSTR_CREATE_lea(dcontext, opnd_create_reg(REG_XDI), memop));
} else {
ASSERT(opnd_is_abs_addr(memop) IF_X64(|| opnd_is_rel_addr(memop)));
PRE(ilist, instr,
INSTR_CREATE_mov_imm(dcontext, opnd_create_reg(REG_XDI),
OPND_CREATE_INTPTR(opnd_get_addr(memop))));
}
PRE(ilist, instr,
instr_create_save_to_tls(dcontext, REG_XDI, FLOAT_PC_STATE_SLOT));
if (TEST(FRAG_SHARED, *flags))
insert_shared_restore_dcontext_reg(dcontext, ilist, instr);
else {
PRE(ilist, instr,
instr_create_restore_from_tls(dcontext, REG_XDI,
DCONTEXT_BASE_SPILL_SLOT));
}
}
if (exit_is_normal && DYNAMO_OPTION(translate_fpu_pc)) {
instr_t *exit_jmp = next_instr;
while (exit_jmp != NULL && !instr_is_exit_cti(exit_jmp))
exit_jmp = instr_get_next(next_instr);
ASSERT(exit_jmp != NULL);
ASSERT(instr_branch_special_exit(exit_jmp));
instr_branch_set_special_exit(exit_jmp, false);
* cannot-be-trace that we're undoing here...
*/
if (TEST(FRAG_CANNOT_BE_TRACE, *flags))
*flags &= ~FRAG_CANNOT_BE_TRACE;
}
}
* CPUID FOOLING
*/
#ifdef FOOL_CPUID
* minus mmx (==0x00800000 in CPUID_1_EDX)
* FIXME: change model number to a Pentium w/o MMX!
*/
# define CPUID_0_EAX 0x00000001
# define CPUID_0_EBX 0x756e6547
# define CPUID_0_ECX 0x6c65746e
# define CPUID_0_EDX 0x49656e69
# define CPUID_1_EAX 0x00000581
# define CPUID_1_EBX 0x00000000
# define CPUID_1_ECX 0x00000000
# define CPUID_1_EDX 0x000001bf
static void
mangle_cpuid(dcontext_t *dcontext, instrlist_t *ilist, instr_t *instr,
instr_t *next_instr)
{
* on instr prior to that and prev is an inc instr.
* alternative is to insert conditional branch...and save eflags, etc.
*/
instr_t *prev = instr_get_prev(instr);
opnd_t op;
int input, out_eax, out_ebx, out_ecx, out_edx;
LOG(THREAD, LOG_INTERP, 1, "fooling cpuid instruction!\n");
ASSERT(prev != NULL);
prev = instr_get_prev_expanded(dcontext, ilist, instr);
instr_decode(dcontext, instr);
if (!instr_valid(instr))
goto cpuid_give_up;
d_r_loginst(dcontext, 2, prev, "prior to cpuid");
* this hack, which is based on photoshop, which either does
* "xor eax,eax" or "xor eax,eax; inc eax"
*/
if (!instr_is_mov_constant(prev, &input)) {
if (instr_get_opcode(prev) != OP_inc)
goto cpuid_give_up;
op = instr_get_dst(prev, 0);
if (!opnd_is_reg(op) || opnd_get_reg(op) != REG_EAX)
goto cpuid_give_up;
prev = instr_get_prev(prev);
if (!instr_is_mov_constant(prev, &input) || input != 0)
goto cpuid_give_up;
input = 1;
}
if (instr_num_dsts(prev) == 0)
goto cpuid_give_up;
op = instr_get_dst(prev, 0);
if (!opnd_is_reg(op) || opnd_get_reg(op) != REG_EAX)
goto cpuid_give_up;
if (input == 0) {
out_eax = CPUID_0_EAX;
out_ebx = CPUID_0_EBX;
out_ecx = CPUID_0_ECX;
out_edx = CPUID_0_EDX;
} else {
out_eax = CPUID_1_EAX;
out_ebx = CPUID_1_EBX;
out_ecx = CPUID_1_ECX;
out_edx = CPUID_1_EDX;
}
PRE(ilist, instr,
INSTR_CREATE_mov_imm(dcontext, opnd_create_reg(REG_EAX),
OPND_CREATE_INT32(out_eax)));
PRE(ilist, instr,
INSTR_CREATE_mov_imm(dcontext, opnd_create_reg(REG_EBX),
OPND_CREATE_INT32(out_ebx)));
PRE(ilist, instr,
INSTR_CREATE_mov_imm(dcontext, opnd_create_reg(REG_ECX),
OPND_CREATE_INT32(out_ecx)));
PRE(ilist, instr,
INSTR_CREATE_mov_imm(dcontext, opnd_create_reg(REG_EDX),
OPND_CREATE_INT32(out_edx)));
instrlist_remove(ilist, instr);
instr_destroy(dcontext, instr);
return;
cpuid_give_up:
LOG(THREAD, LOG_INTERP, 1, "\tcpuid fool: giving up\n");
return;
}
#endif
void
mangle_exit_cti_prefixes(dcontext_t *dcontext, instr_t *instr)
{
uint prefixes = instr_get_prefixes(instr);
if (prefixes != 0) {
bool remove = false;
* prefixes, our patching and other routines make assumptions about
* the length of exit ctis. Plus our elision removes the whole
* instr in any case.
*/
if (instr_is_cbr(instr)) {
if (TESTANY(~(PREFIX_JCC_TAKEN | PREFIX_JCC_NOT_TAKEN), prefixes)) {
remove = true;
prefixes &= (PREFIX_JCC_TAKEN | PREFIX_JCC_NOT_TAKEN);
}
} else {
* our size assumptions so drop them (i#435)
*/
remove = true;
prefixes = 0;
}
if (remove) {
LOG(THREAD, LOG_INTERP, 4,
"\tremoving unknown prefixes " PFX " from " PFX "\n", prefixes,
instr_get_raw_bits(instr));
ASSERT(instr_operands_valid(instr));
instr_set_prefixes(instr, prefixes);
}
} else if ((instr_get_opcode(instr) == OP_jmp &&
instr_length(dcontext, instr) > JMP_LONG_LENGTH) ||
(instr_is_cbr(instr) && instr_length(dcontext, instr) > CBR_LONG_LENGTH)) {
* i#1312 covers marking as actual prefixes, and we should keep them.
*/
LOG(THREAD, LOG_INTERP, 4, "\tremoving unknown jmp prefixes from " PFX "\n",
instr_get_raw_bits(instr));
instr_set_raw_bits_valid(instr, false);
}
}
#ifdef X64
* (done only for x86: i#393) and changing next_instr (done only for ARM).
*/
instr_t *
mangle_rel_addr(dcontext_t *dcontext, instrlist_t *ilist, instr_t *instr,
instr_t *next_instr)
{
uint opc = instr_get_opcode(instr);
app_pc tgt;
opnd_t dst, src;
ASSERT(instr_has_rel_addr_reference(instr));
instr_get_rel_addr_target(instr, &tgt);
STATS_INC(rip_rel_instrs);
# ifdef RCT_IND_BRANCH
if (TEST(OPTION_ENABLED, DYNAMO_OPTION(rct_ind_call)) ||
TEST(OPTION_ENABLED, DYNAMO_OPTION(rct_ind_jump))) {
rct_add_rip_rel_addr(dcontext, tgt _IF_DEBUG(instr_get_translation(instr)));
}
# endif
if (opc == OP_lea) {
if (!rel32_reachable_from_vmcode(tgt)) {
opnd_t immed;
dst = instr_get_dst(instr, 0);
src = instr_get_src(instr, 0);
ASSERT(opnd_is_reg(dst));
ASSERT(opnd_is_rel_addr(src));
ASSERT(opnd_get_addr(src) == tgt);
* Table 3-59 "64-bit Mode LEA Operation with Address and Operand Size
* Attributes" */
if (reg_get_size(opnd_get_reg(dst)) == OPSZ_8) {
* that decode or the user already zeroed out the top bits
* if there was an addr32 prefix byte or the user wants
* that effect */
immed = OPND_CREATE_INTPTR((ptr_int_t)tgt);
} else if (reg_get_size(opnd_get_reg(dst)) == OPSZ_4)
immed = OPND_CREATE_INT32((int)(ptr_int_t)tgt);
else {
ASSERT(reg_get_size(opnd_get_reg(dst)) == OPSZ_2);
immed = OPND_CREATE_INT16((short)(ptr_int_t)tgt);
}
PRE(ilist, instr, INSTR_CREATE_mov_imm(dcontext, dst, immed));
instrlist_remove(ilist, instr);
instr_destroy(dcontext, instr);
STATS_INC(rip_rel_lea);
DOSTATS({
if (tgt >= get_application_base() && tgt < get_application_end())
STATS_INC(rip_rel_app_lea);
});
return NULL;
}
return next_instr;
} else {
* but if it doesn't we need to handle that here (since that
* involves an encoding length change, which complicates many
* use cases if done at instr encode time).
* We don't yet know exactly where we're going to encode this bb,
* so we're conservative and check for all reachability from our
* heap (assumed to be a single heap: xref PR 215395, and xref
* potential secondary code caches PR 253446.
*/
if (!rel32_reachable_from_vmcode(tgt)) {
int si = -1, di = -1;
opnd_t relop, newop;
bool spill = true;
reg_id_t scratch_reg = REG_XAX;
si = instr_get_rel_addr_src_idx(instr);
di = instr_get_rel_addr_dst_idx(instr);
if (si >= 0) {
relop = instr_get_src(instr, si);
ASSERT(di < 0 || opnd_same(relop, instr_get_dst(instr, di)));
if (instr_num_srcs(instr) == 1 &&
instr_num_dsts(instr) == 1 &&
opnd_is_reg(instr_get_dst(instr, 0)) && !instr_is_predicated(instr)) {
opnd_size_t sz = opnd_get_size(instr_get_dst(instr, 0));
reg_id_t reg = opnd_get_reg(instr_get_dst(instr, 0));
* (for 32-bit, top 32 bits are cleared) */
if (reg_is_gpr(reg) && (reg_is_32bit(reg) || reg_is_64bit(reg))) {
spill = false;
scratch_reg = opnd_get_reg(instr_get_dst(instr, 0));
if (sz == OPSZ_4)
scratch_reg = reg_32_to_64(scratch_reg);
ASSERT(
!instr_reads_from_reg(instr, scratch_reg, DR_QUERY_DEFAULT));
STATS_INC(rip_rel_unreachable_nospill);
}
}
} else {
relop = instr_get_dst(instr, di);
}
* our no-spill optimization above may miss some writes.
*/
if (spill && instr_uses_reg(instr, scratch_reg)) {
* get here: can't use registers (except xsp) */
ASSERT(scratch_reg == REG_XAX);
do {
scratch_reg++;
ASSERT(scratch_reg <= REG_STOP_64);
} while (instr_uses_reg(instr, scratch_reg));
}
ASSERT(!instr_reads_from_reg(instr, scratch_reg, DR_QUERY_DEFAULT));
ASSERT(!spill || !instr_writes_to_reg(instr, scratch_reg, DR_QUERY_DEFAULT));
* sharing single spill across whole bb, or possibly building local code
* cache to avoid unreachability: all depending on how many rip-rel
* instrs we see. We'll watch the stats.
*/
if (spill) {
PRE(ilist, instr,
SAVE_TO_DC_OR_TLS(dcontext, 0, scratch_reg, MANGLE_RIPREL_SPILL_SLOT,
XAX_OFFSET));
}
PRE(ilist, instr,
INSTR_CREATE_mov_imm(dcontext, opnd_create_reg(scratch_reg),
OPND_CREATE_INTPTR((ptr_int_t)tgt)));
newop = opnd_create_far_base_disp(opnd_get_segment(relop), scratch_reg,
REG_NULL, 0, 0, opnd_get_size(relop));
if (si >= 0)
instr_set_src(instr, si, newop);
if (di >= 0)
instr_set_dst(instr, di, newop);
instr_set_our_mangling(instr, true);
if (spill) {
* instruction in the mangling code of any control-flow app instruction
* is always before the last commit point of the app instruction, i.e.
* does not xl8 to a PC post app instruction. This should be safe to
* assume for any control-flow instruction. We therefore do not mark the
* rip-rel related restores here as 'epilogue'. Secondly, we assume that
* no instructions in mangled code that require xsp adjustment to xl8 app
* state are instructions that can be fully rolled back. There is a check
* in translate_walk_restore that makes sure there is no xsp_adjust for
* instructions in mangling epilogue. Both of this includes instructions
* with further mangling after the rip-rel mangling code that require
* roll-back. We assume here that this is supported for all such
* instructions.
*/
instr_t *restore = instr_create_restore_from_tls(
dcontext, scratch_reg, MANGLE_RIPREL_SPILL_SLOT);
PRE(ilist, next_instr,
instr_supports_simple_mangling_epilogue(dcontext, instr)
? instr_set_translation_mangling_epilogue(dcontext, ilist,
restore)
: restore);
}
STATS_INC(rip_rel_unreachable);
DOSTATS({
if (tgt >= get_application_base() && tgt < get_application_end())
STATS_INC(rip_rel_app_unreachable);
});
}
}
return next_instr;
}
#endif
* Reference with segment register (fs/gs)
*/
#ifdef UNIX
static int
instr_get_seg_ref_dst_idx(instr_t *instr)
{
int i;
opnd_t opnd;
if (!instr_valid(instr))
return -1;
for (i = 0; i < instr_num_dsts(instr); i++) {
opnd = instr_get_dst(instr, i);
if (opnd_is_far_base_disp(opnd) &&
(opnd_get_segment(opnd) == SEG_GS || opnd_get_segment(opnd) == SEG_FS))
return i;
}
return -1;
}
static int
instr_get_seg_ref_src_idx(instr_t *instr)
{
int i;
opnd_t opnd;
if (!instr_valid(instr))
return -1;
for (i = 0; i < instr_num_srcs(instr); i++) {
opnd = instr_get_src(instr, i);
if (opnd_is_far_base_disp(opnd) &&
(opnd_get_segment(opnd) == SEG_GS || opnd_get_segment(opnd) == SEG_FS))
return i;
}
return -1;
}
* read/update the segment register.
*/
void
mangle_mov_seg(dcontext_t *dcontext, instrlist_t *ilist, instr_t *instr,
instr_t *next_instr)
{
reg_id_t seg;
opnd_t opnd, dst;
opnd_size_t dst_sz;
ASSERT(instr_get_opcode(instr) == OP_mov_seg);
ASSERT(instr_num_srcs(instr) == 1);
ASSERT(instr_num_dsts(instr) == 1);
STATS_INC(app_mov_seg_mangled);
* update it when dynamorio entering code cache to execute
* this basic block.
*/
dst = instr_get_dst(instr, 0);
if (opnd_is_reg(dst) && reg_is_segment(opnd_get_reg(dst))) {
app_pc xl8;
seg = opnd_get_reg(dst);
if (seg == LIB_SEG_TLS && !INTERNAL_OPTION(private_loader))
return;
xl8 = get_app_instr_xl8(instr);
instr_reuse(dcontext, instr);
instr_set_opcode(instr, OP_nop);
instr_set_num_opnds(dcontext, instr, 0, 0);
instr_set_translation(instr, xl8);
return;
}
opnd = instr_get_src(instr, 0);
ASSERT(opnd_is_reg(opnd));
seg = opnd_get_reg(opnd);
ASSERT(reg_is_segment(seg));
if (seg != SEG_FS && seg != SEG_GS)
return;
if (seg == LIB_SEG_TLS && !INTERNAL_OPTION(private_loader))
return;
* 8C/r MOV r/m16,Sreg Move segment register to r/m16
* REX.W + 8C/r MOV r/m64,Sreg Move zero extended 16-bit segment
* register to r/m64
* Note, In 32-bit mode, the assembler may insert the 16-bit operand-size
* prefix with this instruction.
*/
dst = instr_get_dst(instr, 0);
dst_sz = opnd_get_size(dst);
opnd =
opnd_create_sized_tls_slot(os_tls_offset(os_get_app_tls_reg_offset(seg)), OPSZ_2);
if (opnd_is_reg(dst)) {
instr_set_src(instr, 0, opnd);
instr_set_opcode(instr, OP_mov_ld);
if (dst_sz != OPSZ_2)
instr_set_opcode(instr, OP_movzx);
} else {
reg_id_t reg;
instr_t *ti;
for (reg = REG_XAX; reg < REG_XBX; reg++) {
if (!instr_uses_reg(instr, reg))
break;
}
* so only use the first four registers.
*/
ASSERT(reg <= REG_XBX);
PRE(ilist, instr,
instr_create_save_to_tls(dcontext, reg, tls_slots[reg - REG_XAX]));
PRE(ilist, next_instr,
instr_create_restore_from_tls(dcontext, reg, tls_slots[reg - REG_XAX]));
switch (dst_sz) {
case OPSZ_8: IF_NOT_X64(ASSERT(false);) break;
case OPSZ_4: IF_X64(reg = reg_64_to_32(reg);) break;
case OPSZ_2:
IF_X64(reg = reg_64_to_32(reg);)
reg = reg_32_to_16(reg);
break;
default: ASSERT(false);
}
ti = INSTR_CREATE_mov_ld(dcontext, opnd_create_reg(reg), opnd);
if (dst_sz != OPSZ_2)
instr_set_opcode(ti, OP_movzx);
PRE(ilist, instr, ti);
instr_set_src(instr, 0, opnd_create_reg(reg));
instr_set_opcode(instr, OP_mov_st);
instr_set_our_mangling(instr, true);
}
}
void
mangle_seg_ref(dcontext_t *dcontext, instrlist_t *ilist, instr_t *instr,
instr_t *next_instr)
{
int si = -1, di = -1;
opnd_t segop, newop;
bool spill = true;
reg_id_t scratch_reg = REG_XAX, seg = REG_NULL;
if (instr_is_exit_cti(instr))
return;
if (instr_is_mbr(instr))
return;
if (instr_get_opcode(instr) == OP_lea)
return;
* more efficient as it only examines a few byte and avoid fully decoding
* the instruction. For simplicity, we examine every operands instead.
*/
si = instr_get_seg_ref_src_idx(instr);
di = instr_get_seg_ref_dst_idx(instr);
if (si < 0 && di < 0)
return;
if (si >= 0) {
segop = instr_get_src(instr, si);
ASSERT(di < 0 || opnd_same(segop, instr_get_dst(instr, di)));
} else {
segop = instr_get_dst(instr, di);
}
seg = opnd_get_segment(segop);
if (seg != SEG_GS && seg != SEG_FS)
return;
if (seg == LIB_SEG_TLS && !INTERNAL_OPTION(private_loader))
return;
STATS_INC(app_seg_refs_mangled);
DOLOG(3, LOG_INTERP,
{ d_r_loginst(dcontext, 3, instr, "reference with fs/gs segment"); });
if (si >= 0 && instr_num_srcs(instr) == 1 &&
instr_num_dsts(instr) == 1 &&
opnd_is_reg(instr_get_dst(instr, 0)) && !instr_is_predicated(instr)) {
reg_id_t reg = opnd_get_reg(instr_get_dst(instr, 0));
* (for 32-bit, top 32 bits are cleared) */
if (reg_is_gpr(reg) && (reg_is_32bit(reg) || reg_is_64bit(reg)) &&
!instr_reads_from_reg(instr, reg, DR_QUERY_DEFAULT) &&
reg != DR_REG_XSP) {
spill = false;
scratch_reg = reg;
# ifdef X64
if (opnd_get_size(instr_get_dst(instr, 0)) == OPSZ_4)
scratch_reg = reg_32_to_64(reg);
# endif
}
}
if (spill) {
* that has direct TLS slots.
*/
for (scratch_reg = REG_XAX; scratch_reg <= REG_XBX; scratch_reg++) {
* because we will mangle it when executing the instr (no read from),
* and restore it after that instr (no write to).
*/
if (!instr_uses_reg(instr, scratch_reg))
break;
}
ASSERT(scratch_reg <= REG_XBX);
PRE(ilist, instr,
instr_create_save_to_tls(dcontext, scratch_reg,
tls_slots[scratch_reg - REG_XAX]));
}
newop = mangle_seg_ref_opnd(dcontext, ilist, instr, segop, scratch_reg);
if (si >= 0)
instr_set_src(instr, si, newop);
if (di >= 0)
instr_set_dst(instr, di, newop);
instr_set_our_mangling(instr, true);
DOLOG(3, LOG_INTERP,
{ d_r_loginst(dcontext, 3, instr, "re-wrote app tls reference"); });
if (spill) {
PRE(ilist, next_instr,
instr_set_translation_mangling_epilogue(
dcontext, ilist,
instr_create_restore_from_tls(dcontext, scratch_reg,
tls_slots[scratch_reg - REG_XAX])));
}
}
#endif
#ifdef ANNOTATIONS
* DR and Valgrind annotations
*/
void
mangle_annotation_helper(dcontext_t *dcontext, instr_t *label, instrlist_t *ilist)
{
dr_instr_label_data_t *label_data = instr_get_label_data_area(label);
dr_annotation_handler_t *handler = GET_ANNOTATION_HANDLER(label_data);
dr_annotation_receiver_t *receiver = handler->receiver_list;
opnd_t *args = NULL;
ASSERT(handler->type == DR_ANNOTATION_HANDLER_CALL);
LOG(THREAD, LOG_INTERP, 3, "inserting call to annotation handler\n");
while (receiver != NULL) {
if (handler->num_args != 0) {
args = HEAP_ARRAY_ALLOC(dcontext, opnd_t, handler->num_args, ACCT_CLEANCALL,
UNPROTECTED);
memcpy(args, handler->args, sizeof(opnd_t) * handler->num_args);
}
if (handler->pass_pc_in_slot) {
app_pc pc = GET_ANNOTATION_APP_PC(label_data);
instrlist_insert_mov_immed_ptrsz(
dcontext, (ptr_int_t)pc, dr_reg_spill_slot_opnd(dcontext, SPILL_SLOT_2),
ilist, label, NULL, NULL);
}
dr_insert_clean_call_ex_varg(
dcontext, ilist, label, receiver->instrumentation.callback,
(receiver->save_fpstate ? DR_CLEANCALL_SAVE_FLOAT : 0) |
* instruction, so we do not set the DR_CLEANCALL_WRITES_APP_CONTEXT flag.
*/
DR_CLEANCALL_READS_APP_CONTEXT,
handler->num_args, args);
if (handler->num_args != 0) {
HEAP_ARRAY_FREE(dcontext, args, opnd_t, handler->num_args, ACCT_CLEANCALL,
UNPROTECTED);
}
receiver = receiver->next;
}
}
#endif
*###########################################################################
*###########################################################################
*/
*
* When we detect it, we take an exit that targets our own routine
* fragment_self_write. Dispatch checks for that target and if it finds it,
* it calls that routine, so don't worry about building a bb for it.
* Returns false if the bb has invalid instrs in the middle and it should
* be rebuilt from scratch.
*/
#undef SAVE_TO_DC_OR_TLS
#undef RESTORE_FROM_DC_OR_TLS
#ifdef X64
# define SAVE_TO_DC_OR_TLS(dc, reg, tls_offs, dc_offs) \
instr_create_save_to_tls((dc), (reg), (tls_offs))
# define RESTORE_FROM_DC_OR_TLS(dc, reg, tls_offs, dc_offs) \
instr_create_restore_from_tls((dc), (reg), (tls_offs))
#else
# define SAVE_TO_DC_OR_TLS(dc, reg, tls_offs, dc_offs) \
instr_create_save_to_dcontext((dc), (reg), (dc_offs))
# define RESTORE_FROM_DC_OR_TLS(dc, reg, tls_offs, dc_offs) \
instr_create_restore_from_dcontext((dc), (reg), (dc_offs))
#endif
static void
sandbox_rep_instr(dcontext_t *dcontext, instrlist_t *ilist, instr_t *instr, instr_t *next,
app_pc start_pc, app_pc end_pc )
{
* after instr (even if overwrite self will execute rep to completion)
* want to read DF to find direction (0=inc xsi/xdi, 1=dec),
* but only way to read is to do a pushf!
* Solution: if cld or std right before rep instr, use that info,
* otherwise check for BOTH directions!
* xcx is a pre-check, xsi/xdi are inc/dec after memory op, so
* xdi+xcx*opndsize == instr of NEXT write, so open-ended there:
* if DF==0:
* if (xdi < end_pc && xdi+xcx*opndsize > start_pc) => self-write
* if DF==1:
* if (xdi > start_pc && xdi-xcx*opndsize > end_pc) => self-write
* both:
* if (xdi-xcx*opndsize < end_pc && xdi+xcx*opndsize > start_pc) => self-write
* opndsize is 1,2, or 4 => use lea for mul
* lea (xdi,xcx,opndsize),xcx
*
* save flags and xax
* save xbx
* lea (xdi,xcx,opndsize),xbx
* if x64 && (start_pc > 4GB || end_pc > 4GB): save xdx
* if x64 && start_pc > 4GB: mov start_pc, xdx
* cmp xbx, IF_X64_>4GB_ELSE(xdx, start_pc)
* mov $0,xbx # for if ok
* jle ok # open b/c address of next rep write
* lea (,xcx,opndsize),xbx
* neg xbx # sub does dst - src
* add xdi,xbx
* if x64 && end_pc > 4GB: mov end_pc, xdx
* cmp xbx, IF_X64_>4GB_ELSE(xdx, end_pc)
* mov $0,xbx # for if ok
* jge ok # end is open
* mov $1,xbx
* ok:
* restore flags and xax (xax used by stos)
* if x64 && (start_pc > 4GB || end_pc > 4GB): restore xdx
* <rep instr> # doesn't use xbx
* (PR 267764/i#398: we special-case restore xbx on cxt xl8 if this instr faults)
* mov xbx,xcx # we can use xcx, it's dead since 0 after rep
* restore xbx
* jecxz ok2 # if xbx was 1 we'll fall through and exit
* mov $0,xcx
* jmp <instr after write, flag as INSTR_BRANCH_SPECIAL_EXIT>
* ok2:
* <label> # ok2 can't == next, b/c next may be ind br -> mangled w/ instrs
* # inserted before it, so jecxz would target too far
*/
instr_t *ok = INSTR_CREATE_label(dcontext);
instr_t *ok2 = INSTR_CREATE_label(dcontext);
instr_t *jmp;
app_pc after_write;
uint opndsize = opnd_size_in_bytes(opnd_get_size(instr_get_dst(instr, 0)));
uint flags =
instr_eflags_to_fragment_eflags(forward_eflags_analysis(dcontext, ilist, next));
bool use_tls = IF_X64_ELSE(true, false);
IF_X64(bool x86_to_x64_ibl_opt = DYNAMO_OPTION(x86_to_x64_ibl_opt);)
instr_t *next_app = next;
DOLOG(3, LOG_INTERP, { d_r_loginst(dcontext, 3, instr, "writes memory"); });
ASSERT(!instr_is_call_indirect(instr));
while (next_app != NULL && instr_is_meta(next_app))
next_app = instr_get_next(next_app);
if (next_app != NULL) {
* (xref PR 472190)
*/
if (instr_get_app_pc(next_app) != NULL)
after_write = instr_get_app_pc(next_app);
else if (!instr_raw_bits_valid(next_app)) {
ASSERT(instr_is_ubr(next_app) && instr_get_next(next_app) == NULL);
after_write = opnd_get_pc(instr_get_target(next_app));
} else
after_write = instr_get_raw_bits(next_app);
} else {
after_write = end_pc;
}
insert_save_eflags(dcontext, ilist, instr, flags, use_tls,
!use_tls _IF_X64(X64_CACHE_MODE_DC(dcontext) &&
!X64_MODE_DC(dcontext) && x86_to_x64_ibl_opt));
PRE(ilist, instr, SAVE_TO_DC_OR_TLS(dcontext, REG_XBX, TLS_XBX_SLOT, XBX_OFFSET));
PRE(ilist, instr,
INSTR_CREATE_lea(dcontext, opnd_create_reg(REG_XBX),
opnd_create_base_disp(REG_XDI, REG_XCX, opndsize, 0, OPSZ_lea)));
#ifdef X64
if ((ptr_uint_t)start_pc > UINT_MAX || (ptr_uint_t)end_pc > UINT_MAX) {
PRE(ilist, instr, SAVE_TO_DC_OR_TLS(dcontext, REG_XDX, TLS_XDX_SLOT, XDX_OFFSET));
}
if ((ptr_uint_t)start_pc > UINT_MAX) {
PRE(ilist, instr,
INSTR_CREATE_mov_imm(dcontext, opnd_create_reg(REG_XDX),
OPND_CREATE_INTPTR(start_pc)));
PRE(ilist, instr,
INSTR_CREATE_cmp(dcontext, opnd_create_reg(REG_XBX),
opnd_create_reg(REG_XDX)));
} else {
#endif
PRE(ilist, instr,
INSTR_CREATE_cmp(dcontext, opnd_create_reg(REG_XBX),
OPND_CREATE_INT32((int)(ptr_int_t)start_pc)));
#ifdef X64
}
#endif
PRE(ilist, instr,
INSTR_CREATE_mov_imm(dcontext, opnd_create_reg(REG_XBX), OPND_CREATE_INT32(0)));
PRE(ilist, instr, INSTR_CREATE_jcc(dcontext, OP_jle, opnd_create_instr(ok)));
PRE(ilist, instr,
INSTR_CREATE_lea(
dcontext, opnd_create_reg(REG_XBX),
opnd_create_base_disp(REG_NULL, REG_XCX, opndsize, 0, OPSZ_lea)));
PRE(ilist, instr, INSTR_CREATE_neg(dcontext, opnd_create_reg(REG_XBX)));
PRE(ilist, instr,
INSTR_CREATE_add(dcontext, opnd_create_reg(REG_XBX), opnd_create_reg(REG_XDI)));
#ifdef X64
if ((ptr_uint_t)end_pc > UINT_MAX) {
PRE(ilist, instr,
INSTR_CREATE_mov_imm(dcontext, opnd_create_reg(REG_XDX),
OPND_CREATE_INTPTR(end_pc)));
PRE(ilist, instr,
INSTR_CREATE_cmp(dcontext, opnd_create_reg(REG_XBX),
opnd_create_reg(REG_XDX)));
} else {
#endif
PRE(ilist, instr,
INSTR_CREATE_cmp(dcontext, opnd_create_reg(REG_XBX),
OPND_CREATE_INT32((int)(ptr_int_t)end_pc)));
#ifdef X64
}
#endif
PRE(ilist, instr,
INSTR_CREATE_mov_imm(dcontext, opnd_create_reg(REG_XBX), OPND_CREATE_INT32(0)));
PRE(ilist, instr, INSTR_CREATE_jcc(dcontext, OP_jge, opnd_create_instr(ok)));
PRE(ilist, instr,
INSTR_CREATE_mov_imm(dcontext, opnd_create_reg(REG_XBX), OPND_CREATE_INT32(1)));
PRE(ilist, instr, ok);
insert_restore_eflags(dcontext, ilist, instr, flags, use_tls,
!use_tls _IF_X64(X64_CACHE_MODE_DC(dcontext) &&
!X64_MODE_DC(dcontext) && x86_to_x64_ibl_opt));
#ifdef X64
if ((ptr_uint_t)start_pc > UINT_MAX || (ptr_uint_t)end_pc > UINT_MAX) {
PRE(ilist, instr,
RESTORE_FROM_DC_OR_TLS(dcontext, REG_XDX, TLS_XDX_SLOT, XDX_OFFSET));
}
#endif
PRE(ilist, next,
INSTR_CREATE_mov_ld(dcontext, opnd_create_reg(REG_XCX),
opnd_create_reg(REG_XBX)));
PRE(ilist, next, RESTORE_FROM_DC_OR_TLS(dcontext, REG_XBX, TLS_XBX_SLOT, XBX_OFFSET));
PRE(ilist, next, INSTR_CREATE_jecxz(dcontext, opnd_create_instr(ok2)));
PRE(ilist, next,
INSTR_CREATE_mov_imm(dcontext, opnd_create_reg(REG_XCX),
OPND_CREATE_INT32(0)));
jmp = INSTR_CREATE_jmp(dcontext, opnd_create_pc(after_write));
instr_branch_set_special_exit(jmp, true);
instrlist_preinsert(ilist, next, jmp);
PRE(ilist, next, ok2);
}
static void
sandbox_write(dcontext_t *dcontext, instrlist_t *ilist, instr_t *instr, instr_t *next,
opnd_t op, app_pc start_pc, app_pc end_pc )
{
* if (addr < end_pc && addr+opndsize > start_pc) => self-write
* <write memory>
* save xbx
* lea memory,xbx
* save flags and xax # after lea of memory in case memory includes xax
* if x64 && (start_pc > 4GB || end_pc > 4GB): save xcx
* if x64 && end_pc > 4GB: mov end_pc, xcx
* cmp xbx, IF_X64_>4GB_ELSE(xcx, end_pc)
* jge ok # end is open
* lea opndsize(xbx),xbx
* if x64 && start_pc > 4GB: mov start_pc, xcx
* cmp xbx, IF_X64_>4GB_ELSE(xcx, start_pc)
* jle ok # open since added size
* restore flags (using xbx) and xax
* restore xbx
* if x64 && (start_pc > 4GB || end_pc > 4GB): restore xcx
* jmp <instr after write, flag as INSTR_BRANCH_SPECIAL_EXIT>
* ok:
* restore flags and xax
* restore xbx
* if x64 && (start_pc > 4GB || end_pc > 4GB): restore xcx
*/
instr_t *ok = INSTR_CREATE_label(dcontext), *jmp;
app_pc after_write = NULL;
uint opndsize = opnd_size_in_bytes(opnd_get_size(op));
uint flags =
instr_eflags_to_fragment_eflags(forward_eflags_analysis(dcontext, ilist, next));
bool use_tls = IF_X64_ELSE(true, false);
IF_X64(bool x86_to_x64_ibl_opt = DYNAMO_OPTION(x86_to_x64_ibl_opt);)
instr_t *next_app = next;
instr_t *get_addr_at = next;
int opcode = instr_get_opcode(instr);
DOLOG(3, LOG_INTERP, { d_r_loginst(dcontext, 3, instr, "writes memory"); });
while (next_app != NULL && instr_is_meta(next_app))
next_app = instr_get_next(next_app);
if (next_app != NULL) {
* (xref PR 472190)
*/
if (instr_get_app_pc(next_app) != NULL)
after_write = instr_get_app_pc(next_app);
else if (!instr_raw_bits_valid(next_app)) {
ASSERT(instr_is_ubr(next_app) && instr_get_next(next_app) == NULL);
* doesn't have to be a direct jmp but instead
* it could be the exit branch we add as an
* for an indirect call - which is the only ind branch
* that writes to memory
* CALL* already means that we're leaving the block and it cannot be a selfmod
* instruction even though it writes to memory
*/
DOLOG(4, LOG_INTERP,
{ d_r_loginst(dcontext, 4, next_app, "next app instr"); });
after_write = opnd_get_pc(instr_get_target(next_app));
LOG(THREAD, LOG_INTERP, 4, "after_write = " PFX " next should be final jmp\n",
after_write);
} else
after_write = instr_get_raw_bits(next_app);
} else {
ASSERT_NOT_TESTED();
after_write = end_pc;
}
if (opcode == OP_ins || opcode == OP_movs || opcode == OP_stos) {
* get the address pre-write. None of them touch xbx.
*/
get_addr_at = instr;
ASSERT(!instr_writes_to_reg(instr, REG_XBX, DR_QUERY_DEFAULT) &&
!instr_reads_from_reg(instr, REG_XBX, DR_QUERY_DEFAULT));
}
PRE(ilist, get_addr_at,
SAVE_TO_DC_OR_TLS(dcontext, REG_XBX, TLS_XBX_SLOT, XBX_OFFSET));
* segments for its x86 sandbox, although they are 0 based with a limit.
* qq.exe has them in sandboxed code.
*/
ASSERT_CURIOSITY(!opnd_is_far_memory_reference(op) ||
opcode == OP_ins || opcode == OP_movs || opcode == OP_stos);
if (opnd_is_base_disp(op)) {
opnd_set_size(&op, OPSZ_lea);
PRE(ilist, get_addr_at, INSTR_CREATE_lea(dcontext, opnd_create_reg(REG_XBX), op));
if ((opcode == OP_push && opnd_is_base_disp(op) &&
opnd_get_index(op) == DR_REG_NULL &&
reg_to_pointer_sized(opnd_get_base(op)) == DR_REG_XSP) ||
opcode == OP_push_imm || opcode == OP_pushf || opcode == OP_pusha ||
opcode == OP_pop || opcode == OP_call ||
opcode == OP_call_ind || opcode == OP_call_far || opcode == OP_call_far_ind) {
* We could use get_addr_at to acquire the address pre-instruction
* for some of these, but some can read or write ebx.
*/
PRE(ilist, next,
INSTR_CREATE_lea(dcontext, opnd_create_reg(REG_XBX),
opnd_create_base_disp(REG_NULL, REG_XBX, 1,
-opnd_get_disp(op), OPSZ_lea)));
}
} else {
* time. Might happen frequently if a JIT stores data on the same page
* as its code. For now we hook into existing sandboxing code.
*/
app_pc abs_addr;
ASSERT(opnd_is_abs_addr(op) IF_X64(|| opnd_is_rel_addr(op)));
abs_addr = opnd_get_addr(op);
PRE(ilist, get_addr_at,
INSTR_CREATE_mov_imm(dcontext, opnd_create_reg(REG_XBX),
OPND_CREATE_INTPTR(abs_addr)));
}
insert_save_eflags(dcontext, ilist, next, flags, use_tls,
!use_tls _IF_X64(X64_CACHE_MODE_DC(dcontext) &&
!X64_MODE_DC(dcontext) && x86_to_x64_ibl_opt));
#ifdef X64
if ((ptr_uint_t)start_pc > UINT_MAX || (ptr_uint_t)end_pc > UINT_MAX) {
PRE(ilist, next, SAVE_TO_DC_OR_TLS(dcontext, REG_XCX, TLS_XCX_SLOT, XCX_OFFSET));
}
if ((ptr_uint_t)end_pc > UINT_MAX) {
PRE(ilist, next,
INSTR_CREATE_mov_imm(dcontext, opnd_create_reg(REG_XCX),
OPND_CREATE_INTPTR(end_pc)));
PRE(ilist, next,
INSTR_CREATE_cmp(dcontext, opnd_create_reg(REG_XBX),
opnd_create_reg(REG_XCX)));
} else {
#endif
PRE(ilist, next,
INSTR_CREATE_cmp(dcontext, opnd_create_reg(REG_XBX),
OPND_CREATE_INT32((int)(ptr_int_t)end_pc)));
#ifdef X64
}
#endif
PRE(ilist, next, INSTR_CREATE_jcc(dcontext, OP_jge, opnd_create_instr(ok)));
PRE(ilist, next,
INSTR_CREATE_lea(
dcontext, opnd_create_reg(REG_XBX),
opnd_create_base_disp(REG_XBX, REG_NULL, 0, opndsize, OPSZ_lea)));
#ifdef X64
if ((ptr_uint_t)start_pc > UINT_MAX) {
PRE(ilist, next,
INSTR_CREATE_mov_imm(dcontext, opnd_create_reg(REG_XCX),
OPND_CREATE_INTPTR(start_pc)));
PRE(ilist, next,
INSTR_CREATE_cmp(dcontext, opnd_create_reg(REG_XBX),
opnd_create_reg(REG_XCX)));
} else {
#endif
PRE(ilist, next,
INSTR_CREATE_cmp(dcontext, opnd_create_reg(REG_XBX),
OPND_CREATE_INT32((int)(ptr_int_t)start_pc)));
#ifdef X64
}
#endif
PRE(ilist, next, INSTR_CREATE_jcc(dcontext, OP_jle, opnd_create_instr(ok)));
insert_restore_eflags(dcontext, ilist, next, flags, use_tls,
!use_tls _IF_X64(X64_CACHE_MODE_DC(dcontext) &&
!X64_MODE_DC(dcontext) && x86_to_x64_ibl_opt));
PRE(ilist, next, RESTORE_FROM_DC_OR_TLS(dcontext, REG_XBX, TLS_XBX_SLOT, XBX_OFFSET));
#ifdef X64
if ((ptr_uint_t)start_pc > UINT_MAX || (ptr_uint_t)end_pc > UINT_MAX) {
PRE(ilist, next,
RESTORE_FROM_DC_OR_TLS(dcontext, REG_XCX, TLS_XCX_SLOT, XCX_OFFSET));
}
#endif
jmp = INSTR_CREATE_jmp(dcontext, opnd_create_pc(after_write));
instr_branch_set_special_exit(jmp, true);
instrlist_preinsert(ilist, next, jmp);
PRE(ilist, next, ok);
insert_restore_eflags(dcontext, ilist, next, flags, use_tls,
!use_tls _IF_X64(X64_CACHE_MODE_DC(dcontext) &&
!X64_MODE_DC(dcontext) && x86_to_x64_ibl_opt));
PRE(ilist, next, RESTORE_FROM_DC_OR_TLS(dcontext, REG_XBX, TLS_XBX_SLOT, XBX_OFFSET));
#ifdef X64
if ((ptr_uint_t)start_pc > UINT_MAX || (ptr_uint_t)end_pc > UINT_MAX) {
PRE(ilist, next,
RESTORE_FROM_DC_OR_TLS(dcontext, REG_XCX, TLS_XCX_SLOT, XCX_OFFSET));
}
#endif
}
static bool
sandbox_top_of_bb_check_s2ro(dcontext_t *dcontext, app_pc start_pc)
{
return (DYNAMO_OPTION(sandbox2ro_threshold) > 0 &&
!is_address_on_stack(dcontext, start_pc) &&
!is_driver_address(start_pc));
}
static void
sandbox_top_of_bb(dcontext_t *dcontext, instrlist_t *ilist, bool s2ro, uint flags,
app_pc start_pc, app_pc end_pc,
bool for_cache,
patch_list_t *patchlist, cache_pc *copy_start_loc,
cache_pc *copy_end_loc)
{
* copy we saved, stored in cache right after fragment itself. leave its
* start address blank here, will be touched up after emitting this ilist.
*
* FIXME case 8165/PR 212600: optimize this: move reg restores to
* custom fcache_return, use cmpsd instead of cmpsb, etc.
*
* if eflags live entering this bb:
* save xax
* lahf
* seto %al
* endif
* if (-sandbox2ro_threshold > 0)
* if x64: save xcx
* incl &vm_area_t->exec_count (for x64, via xcx)
* cmp sandbox2ro_threshold, vm_area_t->exec_count (for x64, via xcx)
* if eflags live entering this bb, or x64:
* jl past_threshold
* if x64: restore xcx
* if eflags live entering this bb:
* jmp restore_eflags_and_exit
* else
* jmp start_pc marked as selfmod exit
* endif
* past_threshold:
* else
* jge start_pc marked as selfmod exit
* endif
* endif
* if (-sandbox2ro_threshold == 0) && !x64)
* save xcx
* endif
* save xsi
* save xdi
* if stats:
* inc num_sandbox_execs stat (for x64, via xsi)
* endif
* mov start_pc,xsi
* mov copy_start_pc,xdi # 1 opcode byte, then offset
* # => patch point 1
* cmpsb
* if copy_size > 1 # not an opt: for correctness: if "repe cmpsb" has xcx==0, it
* # doesn't touch eflags and we treat cmp results as cmpsb results
* jne check_results
* if x64 && start_pc > 4GB
* mov start_pc, xcx
* cmp xsi, xcx
* else
* cmp xsi, start_pc
* endif
* mov copy_size-1, xcx # -1 b/c we already checked 1st byte
* jge forward
* mov copy_end_pc - 1, xdi # -1 b/c it is the end of this basic block
* # => patch point 2
* mov end_pc - 1, xsi
* forward:
* repe cmpsb
* endif # copy_size > 1
* check_results:
* restore xcx
* restore xsi
* restore xdi
* if eflags live:
* je start_bb
* restore_eflags_and_exit:
* add $0x7f,%al
* sahf
* restore xax
* jmp start_pc marked as selfmod exit
* else
* jne start_pc marked as selfmod exit
* endif
* start_bb:
* if eflags live:
* add $0x7f,%al
* sahf
* restore xax
* endif
*/
instr_t *instr, *jmp;
instr_t *restore_eflags_and_exit = NULL;
bool use_tls = IF_X64_ELSE(true, false);
IF_X64(bool x86_to_x64_ibl_opt = DYNAMO_OPTION(x86_to_x64_ibl_opt);)
bool saved_xcx = false;
instr_t *check_results = INSTR_CREATE_label(dcontext);
instr = instrlist_first_expanded(dcontext, ilist);
insert_save_eflags(dcontext, ilist, instr, flags, use_tls,
!use_tls _IF_X64(X64_CACHE_MODE_DC(dcontext) &&
!X64_MODE_DC(dcontext) && x86_to_x64_ibl_opt));
if (s2ro) {
* counter but no lock, and thus need a relative comparison, while
* lea/jecxz can only do an exact comparison. We could be exact by
* having a separate counter per (private) fragment but by spilling
* eflags we can inc memory, making the scheme here not inefficient.
*/
uint thresh = DYNAMO_OPTION(sandbox2ro_threshold);
uint *counter;
if (for_cache)
counter = get_selfmod_exec_counter(start_pc);
else {
* a recreation post-flush). Won't execute, so NULL is fine.
*/
counter = NULL;
}
#ifdef X64
PRE(ilist, instr, SAVE_TO_DC_OR_TLS(dcontext, REG_XCX, TLS_XCX_SLOT, XCX_OFFSET));
saved_xcx = true;
PRE(ilist, instr,
INSTR_CREATE_mov_imm(dcontext, opnd_create_reg(REG_XCX),
OPND_CREATE_INTPTR(counter)));
PRE(ilist, instr, INSTR_CREATE_inc(dcontext, OPND_CREATE_MEM32(REG_XCX, 0)));
PRE(ilist, instr,
INSTR_CREATE_cmp(dcontext, OPND_CREATE_MEM32(REG_XCX, 0),
OPND_CREATE_INT_32OR8((int)thresh)));
#else
PRE(ilist, instr,
INSTR_CREATE_inc(dcontext, OPND_CREATE_ABSMEM(counter, OPSZ_4)));
PRE(ilist, instr,
INSTR_CREATE_cmp(dcontext, OPND_CREATE_ABSMEM(counter, OPSZ_4),
OPND_CREATE_INT_32OR8(thresh)));
#endif
if (TEST(FRAG_WRITES_EFLAGS_6, flags) IF_X64(&&false)) {
jmp = INSTR_CREATE_jcc(dcontext, OP_jge, opnd_create_pc(start_pc));
instr_branch_set_special_exit(jmp, true);
instrlist_preinsert(ilist, instr, jmp);
} else {
instr_t *past_threshold = INSTR_CREATE_label(dcontext);
PRE(ilist, instr,
INSTR_CREATE_jcc_short(dcontext, OP_jl_short,
opnd_create_instr(past_threshold)));
#ifdef X64
PRE(ilist, instr,
RESTORE_FROM_DC_OR_TLS(dcontext, REG_XCX, TLS_XCX_SLOT, XCX_OFFSET));
#endif
if (!TEST(FRAG_WRITES_EFLAGS_6, flags)) {
ASSERT(restore_eflags_and_exit == NULL);
restore_eflags_and_exit = INSTR_CREATE_label(dcontext);
PRE(ilist, instr,
INSTR_CREATE_jmp(dcontext,
opnd_create_instr(restore_eflags_and_exit)));
}
#ifdef X64
else {
jmp = INSTR_CREATE_jmp(dcontext, opnd_create_pc(start_pc));
instr_branch_set_special_exit(jmp, true);
instrlist_preinsert(ilist, instr, jmp);
}
#endif
PRE(ilist, instr, past_threshold);
}
}
if (!saved_xcx) {
PRE(ilist, instr, SAVE_TO_DC_OR_TLS(dcontext, REG_XCX, TLS_XCX_SLOT, XCX_OFFSET));
}
PRE(ilist, instr, SAVE_TO_DC_OR_TLS(dcontext, REG_XSI, TLS_XBX_SLOT, XSI_OFFSET));
PRE(ilist, instr, SAVE_TO_DC_OR_TLS(dcontext, REG_XDI, TLS_XDX_SLOT, XDI_OFFSET));
DOSTATS({
if (GLOBAL_STATS_ON()) {
* We don't care about races: ballpark figure is good enough.
* We could do a direct inc of memory for 32-bit.
*/
PRE(ilist, instr,
INSTR_CREATE_mov_imm(
dcontext, opnd_create_reg(REG_XSI),
OPND_CREATE_INTPTR(GLOBAL_STAT_ADDR(num_sandbox_execs))));
PRE(ilist, instr,
INSTR_CREATE_inc(
dcontext,
opnd_create_base_disp(REG_XSI, REG_NULL, 0, 0, OPSZ_STATS)));
}
});
PRE(ilist, instr,
INSTR_CREATE_mov_imm(dcontext, opnd_create_reg(REG_XSI),
OPND_CREATE_INTPTR(start_pc)));
PRE(ilist, instr,
INSTR_CREATE_mov_imm(dcontext, opnd_create_reg(REG_XDI),
OPND_CREATE_INTPTR(start_pc)));
if (patchlist != NULL) {
ASSERT(copy_start_loc != NULL);
add_patch_marker(patchlist, instr_get_prev(instr), PATCH_ASSEMBLE_ABSOLUTE,
-(short)sizeof(cache_pc), (ptr_uint_t *)copy_start_loc);
}
PRE(ilist, instr, INSTR_CREATE_cmps_1(dcontext));
* touch eflags and we'll treat the cmp results as cmpsb results, which
* doesn't work (cmp will never be equal)
*/
if (end_pc - start_pc > 1) {
instr_t *forward = INSTR_CREATE_label(dcontext);
PRE(ilist, instr,
INSTR_CREATE_jcc(dcontext, OP_jne, opnd_create_instr(check_results)));
#ifdef X64
if ((ptr_uint_t)start_pc > UINT_MAX) {
PRE(ilist, instr,
INSTR_CREATE_mov_imm(dcontext, opnd_create_reg(REG_XCX),
OPND_CREATE_INTPTR(start_pc)));
PRE(ilist, instr,
INSTR_CREATE_cmp(dcontext, opnd_create_reg(REG_XSI),
opnd_create_reg(REG_XCX)));
} else {
#endif
PRE(ilist, instr,
INSTR_CREATE_cmp(dcontext, opnd_create_reg(REG_XSI),
OPND_CREATE_INT32((int)(ptr_int_t)start_pc)));
#ifdef X64
}
#endif
PRE(ilist, instr,
INSTR_CREATE_mov_imm(dcontext, opnd_create_reg(REG_XCX),
OPND_CREATE_INTPTR(end_pc - (start_pc + 1))));
* than start_pc after cmps, and the jump branch will not be taken.
*/
PRE(ilist, instr, INSTR_CREATE_jcc(dcontext, OP_jge, opnd_create_instr(forward)));
* since it will be modified in finalize_selfmod_sandbox.
*/
PRE(ilist, instr,
INSTR_CREATE_mov_imm(dcontext, opnd_create_reg(REG_XDI),
OPND_CREATE_INTPTR(end_pc - 1)));
if (patchlist != NULL) {
ASSERT(copy_end_loc != NULL);
add_patch_marker(patchlist, instr_get_prev(instr), PATCH_ASSEMBLE_ABSOLUTE,
-(short)sizeof(cache_pc), (ptr_uint_t *)copy_end_loc);
}
* and thus it should be started at end_pc - 1
* because current basic block is [start_pc:end_pc-1].
*/
PRE(ilist, instr,
INSTR_CREATE_mov_imm(dcontext, opnd_create_reg(REG_XSI),
OPND_CREATE_INTPTR(end_pc - 1)));
PRE(ilist, instr, forward);
PRE(ilist, instr, INSTR_CREATE_rep_cmps_1(dcontext));
}
PRE(ilist, instr, check_results);
PRE(ilist, instr,
RESTORE_FROM_DC_OR_TLS(dcontext, REG_XCX, TLS_XCX_SLOT, XCX_OFFSET));
PRE(ilist, instr,
RESTORE_FROM_DC_OR_TLS(dcontext, REG_XSI, TLS_XBX_SLOT, XSI_OFFSET));
PRE(ilist, instr,
RESTORE_FROM_DC_OR_TLS(dcontext, REG_XDI, TLS_XDX_SLOT, XDI_OFFSET));
if (!TEST(FRAG_WRITES_EFLAGS_6, flags)) {
instr_t *start_bb = INSTR_CREATE_label(dcontext);
PRE(ilist, instr, INSTR_CREATE_jcc(dcontext, OP_je, opnd_create_instr(start_bb)));
if (restore_eflags_and_exit != NULL)
PRE(ilist, instr, restore_eflags_and_exit);
insert_restore_eflags(dcontext, ilist, instr, flags, use_tls,
!use_tls _IF_X64(X64_CACHE_MODE_DC(dcontext) &&
!X64_MODE_DC(dcontext) &&
x86_to_x64_ibl_opt));
jmp = INSTR_CREATE_jmp(dcontext, opnd_create_pc(start_pc));
instr_branch_set_special_exit(jmp, true);
instrlist_preinsert(ilist, instr, jmp);
PRE(ilist, instr, start_bb);
} else {
jmp = INSTR_CREATE_jcc(dcontext, OP_jne, opnd_create_pc(start_pc));
instr_branch_set_special_exit(jmp, true);
instrlist_preinsert(ilist, instr, jmp);
}
insert_restore_eflags(dcontext, ilist, instr, flags, use_tls,
!use_tls _IF_X64(X64_CACHE_MODE_DC(dcontext) &&
!X64_MODE_DC(dcontext) && x86_to_x64_ibl_opt));
}
* invalid instrs, elided ctis, etc.
*/
bool
insert_selfmod_sandbox(dcontext_t *dcontext, instrlist_t *ilist, uint flags,
app_pc start_pc, app_pc end_pc,
bool record_translation, bool for_cache)
{
instr_t *instr, *next;
if (!INTERNAL_OPTION(hw_cache_consistency))
return true;
ASSERT((flags & FRAG_HAS_DIRECT_CTI) == 0);
instr = instrlist_first_expanded(dcontext, ilist);
instrlist_set_our_mangling(ilist, true);
if (record_translation) {
while (instr != NULL && instr_is_meta(instr))
instr = instr_get_next_expanded(dcontext, ilist, instr);
ASSERT(instr != NULL && instr_get_translation(instr) != NULL);
instrlist_set_translation_target(ilist, instr_get_translation(instr));
}
sandbox_top_of_bb(dcontext, ilist, sandbox_top_of_bb_check_s2ro(dcontext, start_pc),
flags, start_pc, end_pc, for_cache, NULL, NULL, NULL);
if (INTERNAL_OPTION(sandbox_writes)) {
for (; instr != NULL; instr = next) {
int i, opcode;
opnd_t op;
opcode = instr_get_opcode(instr);
if (!instr_valid(instr)) {
* to do that and get all flags right is to re-build it,
* but this time we'll use full decode so we'll avoid the discrepancy
* between fast and full decode on invalid instr detection.
*/
if (record_translation)
instrlist_set_translation_target(ilist, NULL);
instrlist_set_our_mangling(ilist, false);
return false;
}
next = instr_get_next_expanded(dcontext, ilist, instr);
if (instr_is_meta(instr))
continue;
if (record_translation) {
ASSERT(instr_get_translation(instr) != NULL);
instrlist_set_translation_target(ilist, instr_get_translation(instr));
}
if (opcode == OP_rep_ins || opcode == OP_rep_movs || opcode == OP_rep_stos) {
sandbox_rep_instr(dcontext, ilist, instr, next, start_pc, end_pc);
continue;
}
for (i = 0; i < instr_num_dsts(instr); i++) {
op = instr_get_dst(instr, i);
if (opnd_is_memory_reference(op)) {
if (instr_is_call_indirect(instr)) {
ASSERT(next != NULL && !instr_raw_bits_valid(next));
* instruction modifying anything?
*/
* transforms this into a direct CALL,
* in that case 'next' is a direct jmp
* fall through, so has no exit flags
*/
ASSERT(EXIT_IS_CALL(instr_exit_branch_type(next)) ||
(DYNAMO_OPTION(IAT_convert) &&
TEST(INSTR_IND_CALL_DIRECT, instr->flags)));
LOG(THREAD, LOG_INTERP, 3,
" ignoring CALL* at end of fragment\n");
* destinations, but since it is rare it is faster
* to do it here. Using continue instead of break in case
* it gets moved out.
*/
continue;
}
if (opnd_is_abs_addr(op) IF_X64(|| opnd_is_rel_addr(op))) {
app_pc abs_addr = opnd_get_addr(op);
uint size = opnd_size_in_bytes(opnd_get_size(op));
if (!POINTER_OVERFLOW_ON_ADD(abs_addr, size) &&
(abs_addr + size < start_pc || abs_addr >= end_pc)) {
* outside the current basic block and doesn't need
* sandboxing.
*/
continue;
}
}
sandbox_write(dcontext, ilist, instr, next, op, start_pc, end_pc);
}
}
}
}
if (record_translation)
instrlist_set_translation_target(ilist, NULL);
instrlist_set_our_mangling(ilist, false);
return true;
}
* the code is emitted, as the values depend on the emitted address.
* These vary by whether sandbox_top_of_bb_check_s2ro() and whether
* eflags are not written, all written, or just OF is written.
* For the copy_size == 1 variation, we simply ignore the 2nd patch point.
*/
static bool selfmod_s2ro[] = { false, true };
static uint selfmod_eflags[] = { FRAG_WRITES_EFLAGS_6, FRAG_WRITES_EFLAGS_OF, 0 };
#define SELFMOD_NUM_S2RO (sizeof(selfmod_s2ro) / sizeof(selfmod_s2ro[0]))
#define SELFMOD_NUM_EFLAGS (sizeof(selfmod_eflags) / sizeof(selfmod_eflags[0]))
#ifdef X64
static app_pc selfmod_gt4G[] = { NULL, (app_pc)(POINTER_MAX - 2) };
# define SELFMOD_NUM_GT4G (sizeof(selfmod_gt4G) / sizeof(selfmod_gt4G[0]))
#endif
uint selfmod_copy_start_offs[SELFMOD_NUM_S2RO]
[SELFMOD_NUM_EFLAGS] IF_X64([SELFMOD_NUM_GT4G]);
uint selfmod_copy_end_offs[SELFMOD_NUM_S2RO]
[SELFMOD_NUM_EFLAGS] IF_X64([SELFMOD_NUM_GT4G]);
void
set_selfmod_sandbox_offsets(dcontext_t *dcontext)
{
int i, j;
#ifdef X64
int k;
#endif
instrlist_t ilist;
patch_list_t patch;
static byte buf[256];
uint len;
* synch on accessing buf */
ASSERT(!dynamo_initialized);
for (i = 0; i < SELFMOD_NUM_S2RO; i++) {
for (j = 0; j < SELFMOD_NUM_EFLAGS; j++) {
#ifdef X64
for (k = 0; k < SELFMOD_NUM_GT4G; k++) {
#endif
cache_pc start_pc, end_pc;
app_pc app_start;
instr_t *inst;
instrlist_init(&ilist);
instrlist_append(&ilist, INSTR_CREATE_label(dcontext));
init_patch_list(&patch, PATCH_TYPE_ABSOLUTE);
app_start = IF_X64_ELSE(selfmod_gt4G[k], NULL);
sandbox_top_of_bb(dcontext, &ilist, selfmod_s2ro[i], selfmod_eflags[j],
* both patch points */
app_start, app_start + 2, false, &patch, &start_pc,
&end_pc);
* they'd be mangled away) so we munge them first
*/
for (inst = instrlist_first(&ilist); inst != NULL;
inst = instr_get_next(inst)) {
if (instr_is_exit_cti(inst)) {
instr_set_target(inst, opnd_create_pc(buf));
}
}
len = encode_with_patch_list(dcontext, &patch, &ilist, buf);
ASSERT(len < BUFFER_SIZE_BYTES(buf));
IF_X64(ASSERT(CHECK_TRUNCATE_TYPE_uint(start_pc - buf)));
selfmod_copy_start_offs[i][j] IF_X64([k]) = (uint)(start_pc - buf);
IF_X64(ASSERT(CHECK_TRUNCATE_TYPE_uint(end_pc - buf)));
selfmod_copy_end_offs[i][j] IF_X64([k]) = (uint)(end_pc - buf);
LOG(THREAD, LOG_EMIT, 3, "selfmod offs %d %d" IF_X64(" %d") ": %u %u\n",
i, j, IF_X64_(k) selfmod_copy_start_offs[i][j] IF_X64([k]),
selfmod_copy_end_offs[i][j] IF_X64([k]));
instrlist_clear(dcontext, &ilist);
#ifdef X64
}
#endif
}
}
}
void
finalize_selfmod_sandbox(dcontext_t *dcontext, fragment_t *f)
{
cache_pc copy_pc = FRAGMENT_SELFMOD_COPY_PC(f);
byte *pc;
int i, j;
#ifdef X64
int k = ((ptr_uint_t)f->tag) > UINT_MAX ? 1 : 0;
#endif
i = (sandbox_top_of_bb_check_s2ro(dcontext, f->tag)) ? 1 : 0;
j = (TEST(FRAG_WRITES_EFLAGS_6, f->flags)
? 0
: (TEST(FRAG_WRITES_EFLAGS_OF, f->flags) ? 1 : 2));
pc = FCACHE_ENTRY_PC(f) + selfmod_copy_start_offs[i][j] IF_X64([k]);
*((cache_pc *)vmcode_get_writable_addr(pc)) = copy_pc;
if (FRAGMENT_SELFMOD_COPY_CODE_SIZE(f) > 1) {
pc = FCACHE_ENTRY_PC(f) + selfmod_copy_end_offs[i][j] IF_X64([k]);
* This value will be used in the case where the direction flag is set.
* It will then be the starting point for the backward repe cmps.
*/
*((cache_pc *)vmcode_get_writable_addr(pc)) =
(copy_pc + FRAGMENT_SELFMOD_COPY_CODE_SIZE(f) - 1);
}
}