blob: 6a5f60b1bf0da331efe40801813b8edd068f368c [file] [log] [blame]
/* **********************************************************
* Copyright (c) 2011-2014 Google, Inc. All rights reserved.
* Copyright (c) 2000-2010 VMware, Inc. All rights reserved.
* **********************************************************/
/*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of VMware, Inc. nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL VMWARE, INC. OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*/
/* Copyright (c) 2003-2007 Determina Corp. */
/* Copyright (c) 2001-2003 Massachusetts Institute of Technology */
/* Copyright (c) 2000-2001 Hewlett-Packard Company */
/* file "instrlist.c" */
#include "../globals.h"
#include "instrlist.h"
#include "instr.h"
#include <string.h>
#if defined(DEBUG) && defined(CLIENT_INTERFACE)
/* case 10450: give messages to clients */
# undef ASSERT /* N.B.: if have issues w/ DYNAMO_OPTION, re-instate */
# undef ASSERT_TRUNCATE
# undef ASSERT_BITFIELD_TRUNCATE
# undef ASSERT_NOT_REACHED
# define ASSERT DO_NOT_USE_ASSERT_USE_CLIENT_ASSERT_INSTEAD
# define ASSERT_TRUNCATE DO_NOT_USE_ASSERT_USE_CLIENT_ASSERT_INSTEAD
# define ASSERT_BITFIELD_TRUNCATE DO_NOT_USE_ASSERT_USE_CLIENT_ASSERT_INSTEAD
# define ASSERT_NOT_REACHED DO_NOT_USE_ASSERT_USE_CLIENT_ASSERT_INSTEAD
#endif
/* returns an empty instrlist_t object */
instrlist_t*
instrlist_create(dcontext_t *dcontext)
{
instrlist_t *ilist = (instrlist_t*) heap_alloc(dcontext, sizeof(instrlist_t)
HEAPACCT(ACCT_IR));
CLIENT_ASSERT(ilist != NULL, "instrlist_create: allocation error");
instrlist_init(ilist);
return ilist;
}
/* initializes an instrlist_t object */
void
instrlist_init(instrlist_t *ilist)
{
CLIENT_ASSERT(ilist != NULL, "instrlist_create: NULL parameter");
ilist->first = ilist->last = NULL;
ilist->flags = 0; /* no flags set */
ilist->translation_target = NULL;
#ifdef CLIENT_INTERFACE
ilist->fall_through_bb = NULL;
#endif
}
/* frees the instrlist_t object */
void
instrlist_destroy(dcontext_t *dcontext, instrlist_t *ilist)
{
CLIENT_ASSERT(ilist->first == NULL && ilist->last == NULL,
"instrlist_destroy: list not empty");
heap_free(dcontext, ilist, sizeof(instrlist_t) HEAPACCT(ACCT_IR));
}
/* frees the Instrs in the instrlist_t */
void
instrlist_clear(dcontext_t *dcontext, instrlist_t *ilist)
{
instr_t *instr;
while (NULL != (instr = instrlist_first(ilist))) {
instrlist_remove(ilist, instr);
instr_destroy(dcontext, instr);
}
}
/* frees the Instrs in the instrlist_t and the instrlist_t object itself */
void
instrlist_clear_and_destroy(dcontext_t *dcontext, instrlist_t *ilist)
{
instrlist_clear(dcontext,ilist);
instrlist_destroy(dcontext,ilist);
}
#ifdef CLIENT_INTERFACE
/* Specifies the fall-through target of a basic block if its last
* instruction is a conditional branch instruction.
* It can only be called in basic block building event callbacks
* and has NO EFFECT in other cases, e.g. trace.
*/
bool
instrlist_set_fall_through_target(instrlist_t *bb, app_pc tgt)
{
bb->fall_through_bb = tgt;
return true;
}
app_pc
instrlist_get_fall_through_target(instrlist_t *bb)
{
return bb->fall_through_bb;
}
/* Specifies the return target of a basic block if its last
* instruction is a call instruction.
* It can only be called in basic block building event callbacks
* and has NO EFFECT in other cases.
*/
bool
instrlist_set_return_target(instrlist_t *bb, app_pc tgt)
{
bb->fall_through_bb = tgt;
return true;
}
app_pc
instrlist_get_return_target(instrlist_t *bb)
{
return bb->fall_through_bb;
}
#endif /* CLIENT_INTERFACE */
/* All future Instrs inserted into ilist that do not have raw bits
* will have instr_set_translation called with pc as the target
*/
void
instrlist_set_translation_target(instrlist_t *ilist, app_pc pc)
{
ilist->translation_target = pc;
}
app_pc
instrlist_get_translation_target(instrlist_t *ilist)
{
return ilist->translation_target;
}
void
instrlist_set_our_mangling(instrlist_t *ilist, bool ours)
{
if (ours)
ilist->flags |= INSTR_OUR_MANGLING;
else
ilist->flags &= ~INSTR_OUR_MANGLING;
}
bool
instrlist_get_our_mangling(instrlist_t *ilist)
{
return TEST(INSTR_OUR_MANGLING, ilist->flags);
}
/* returns the first inst in the list */
instr_t*
instrlist_first(instrlist_t *ilist)
{
return ilist->first;
}
/* returns the first app (non-meta) inst in the list */
DR_API
instr_t *
instrlist_first_app(instrlist_t *ilist)
{
instr_t *first = ilist->first;
if (first == NULL)
return NULL;
if (instr_is_app(first))
return first;
return instr_get_next_app(first);
}
/* returns the last inst in the list */
instr_t*
instrlist_last(instrlist_t *ilist)
{
return ilist->last;
}
static inline void
check_translation(instrlist_t *ilist, instr_t *inst)
{
if (ilist->translation_target != NULL && instr_get_translation(inst) == NULL) {
instr_set_translation(inst, ilist->translation_target);
}
if (instrlist_get_our_mangling(ilist))
instr_set_our_mangling(inst, true);
}
/* appends inst to the list ("inst" can be a chain of insts) */
void
instrlist_append(instrlist_t *ilist, instr_t *inst)
{
instr_t *top = inst;
instr_t *bot;
CLIENT_ASSERT(instr_get_prev(inst) == NULL,
"instrlist_append: cannot add middle of list");
check_translation(ilist, inst);
while (instr_get_next(inst)) {
inst = instr_get_next(inst);
check_translation(ilist, inst);
}
bot = inst;
if (ilist->last) {
instr_set_next(ilist->last, top);
instr_set_prev(top, ilist->last);
ilist->last = bot;
}
else {
ilist->first = top;
ilist->last = bot;
}
}
/* prepends inst to the list ("inst" can be a chain of insts) */
void
instrlist_prepend(instrlist_t *ilist, instr_t *inst)
{
instr_t *top = inst;
instr_t *bot;
CLIENT_ASSERT(instr_get_prev(inst) == NULL,
"instrlist_prepend: cannot add middle of list");
check_translation(ilist, inst);
while (instr_get_next(inst)) {
inst = instr_get_next(inst);
check_translation(ilist, inst);
}
bot = inst;
if (ilist->first) {
instr_set_next(bot, ilist->first);
instr_set_prev(ilist->first, bot);
ilist->first = top;
}
else {
ilist->first = top;
ilist->last = bot;
}
}
/* inserts "inst" before "where" ("inst" can be a chain of insts) */
void
instrlist_preinsert(instrlist_t *ilist, instr_t *where, instr_t *inst)
{
instr_t *whereprev;
instr_t *top = inst;
instr_t *bot;
if (where == NULL) {
/* if where is NULL there is no inst to send for a "before" */
instrlist_append(ilist, inst);
return;
}
CLIENT_ASSERT(where != NULL, "instrlist_preinsert: where cannot be NULL");
CLIENT_ASSERT(instr_get_prev(inst) == NULL,
"instrlist_preinsert: cannot add middle of list");
whereprev = instr_get_prev(where);
check_translation(ilist, inst);
while (instr_get_next(inst)) {
inst = instr_get_next(inst);
check_translation(ilist, inst);
}
bot = inst;
if (whereprev) {
instr_set_next(whereprev, top);
instr_set_prev(top, whereprev);
}
else {
ilist->first = top;
}
instr_set_next(bot, where);
instr_set_prev(where, bot);
}
/* inserts "inst" after "where" ("inst" can be a chain of insts) */
void
instrlist_postinsert(instrlist_t *ilist, instr_t *where, instr_t *inst)
{
instr_t *wherenext;
instr_t *top = inst;
instr_t *bot;
if (where == NULL) {
/* if where is NULL there is no inst to send for an "after" */
instrlist_prepend(ilist, inst);
return;
}
CLIENT_ASSERT(where != NULL, "instrlist_postinsert: where cannot be NULL");
CLIENT_ASSERT(instr_get_prev(inst) == NULL,
"instrlist_postinsert: cannot add middle of list");
wherenext = instr_get_next(where);
check_translation(ilist, inst);
while (instr_get_next(inst)) {
inst = instr_get_next(inst);
check_translation(ilist, inst);
}
bot = inst;
instr_set_next(where, top);
instr_set_prev(top, where);
if (wherenext) {
instr_set_next(bot, wherenext);
instr_set_prev(wherenext, bot);
}
else {
ilist->last = bot;
}
}
/* replace oldinst with newinst, remove oldinst from ilist, and return oldinst
(newinst can be a chain of insts) */
instr_t*
instrlist_replace(instrlist_t *ilist, instr_t *oldinst, instr_t *newinst)
{
instr_t *where;
CLIENT_ASSERT(oldinst != NULL, "instrlist_replace: oldinst cannot be NULL");
CLIENT_ASSERT(instr_get_prev(newinst) == NULL,
"instrlist_replace: cannot add middle of list");
where = instr_get_prev(oldinst);
instrlist_remove(ilist, oldinst);
if (where)
instrlist_postinsert(ilist, where, newinst);
else
instrlist_prepend(ilist, newinst);
return oldinst;
}
/* removes "inst" from the instrlist_t it currently belongs to */
void
instrlist_remove(instrlist_t *ilist, instr_t *inst)
{
if (instr_get_prev(inst))
instr_set_next(instr_get_prev(inst), instr_get_next(inst));
else
ilist->first = instr_get_next(inst);
if (instr_get_next(inst))
instr_set_prev(instr_get_next(inst), instr_get_prev(inst));
else
ilist->last = instr_get_prev(inst);
instr_set_prev(inst, NULL);
instr_set_next(inst, NULL);
}
instrlist_t*
instrlist_clone(dcontext_t *dcontext, instrlist_t *old)
{
instr_t *inst, *copy;
instrlist_t *newlist = instrlist_create(dcontext);
inst = instrlist_first(old);
while (inst != NULL) {
copy = instr_clone(dcontext, inst);
/* to copy instr targets we temporarily clobber note field */
instr_set_note(inst, (void *)copy);
instrlist_append(newlist, copy);
inst = instr_get_next(inst);
}
/* Fix up instr src if it is an instr and restore note field */
/* Note: we do not allows instruction update code cache,
* which is very dangerous.
* So we do not support instr as dst opnd and won't fix up here if any.
*/
for (inst = instrlist_first(old), copy = instrlist_first(newlist);
inst != NULL && copy != NULL;
inst = instr_get_next(inst), copy = instr_get_next(copy)) {
int i;
for (i = 0; i < inst->num_srcs; i++) {
instr_t *tgt;
opnd_t op = instr_get_src(copy, i);
if (!opnd_is_instr(op))
continue;
CLIENT_ASSERT(opnd_get_instr(op) != NULL,
"instrlist_clone: NULL instr operand");
tgt = (instr_t *) instr_get_note(opnd_get_instr(op));
CLIENT_ASSERT(tgt != NULL,
"instrlist_clone: operand instr not in instrlist");
if (opnd_is_far_instr(op)) {
instr_set_src(copy, i,
opnd_create_far_instr
(opnd_get_segment_selector(op), tgt));
} else
instr_set_src(copy, i,
opnd_create_instr(tgt));
}
}
for (inst = instrlist_first(old), copy = instrlist_first(newlist);
inst != NULL && copy != NULL;
inst = instr_get_next(inst), copy = instr_get_next(copy)) {
/* restore note field */
instr_set_note(inst, instr_get_note(copy));
}
#ifdef CLIENT_INTERFACE
newlist->fall_through_bb = old->fall_through_bb;
#endif
return newlist;
}
/*
* puts a whole list (prependee) onto the front of ilist
* frees prependee when done because it will contain nothing useful
*/
void
instrlist_prepend_instrlist(dcontext_t *dcontext,instrlist_t *ilist,
instrlist_t *prependee)
{
instr_t *first=instrlist_first(prependee);
if (!first)
return;
instrlist_prepend(ilist,first);
instrlist_init(prependee);
instrlist_destroy(dcontext,prependee);
}
void instrlist_append_instrlist(dcontext_t *dcontext,instrlist_t *ilist,
instrlist_t *appendee)
{
instr_t *first=instrlist_first(appendee);
if (!first)
return;
instrlist_append(ilist,first);
instrlist_init(appendee);
instrlist_destroy(dcontext,appendee);
}
/* If has_instr_jmp_targets is true, this routine trashes the note field
* of each instr_t to store the offset in order to properly encode
* the relative pc for an instr_t jump target
*/
byte *
instrlist_encode_to_copy(dcontext_t *dcontext, instrlist_t *ilist, byte *copy_pc,
byte *final_pc, byte *max_pc, bool has_instr_jmp_targets)
{
instr_t *inst;
int len = 0;
if (has_instr_jmp_targets || max_pc != NULL) {
/* must set note fields first with offset, or compute length */
for (inst = instrlist_first(ilist); inst; inst = instr_get_next(inst)) {
if (has_instr_jmp_targets)
instr_set_note(inst, (void *)(ptr_int_t)len);
len += instr_length(dcontext, inst);
}
}
if (max_pc != NULL &&
(copy_pc + len > max_pc || POINTER_OVERFLOW_ON_ADD(copy_pc, len)))
return NULL;
for (inst = instrlist_first(ilist); inst != NULL; inst = instr_get_next(inst)) {
byte *pc = instr_encode_to_copy(dcontext, inst, copy_pc, final_pc);
if (pc == NULL)
return NULL;
final_pc += pc - copy_pc;
copy_pc = pc;
}
return copy_pc;
}
byte *
instrlist_encode(dcontext_t *dcontext, instrlist_t *ilist, byte *pc,
bool has_instr_jmp_targets)
{
return instrlist_encode_to_copy(dcontext, ilist, pc, pc, NULL, has_instr_jmp_targets);
}