blob: 5a4f5454ca907ec90a0a0ab5e1c71b7f88638334 [file] [log] [blame]
/* **********************************************************
* Copyright (c) 2011-2014 Google, Inc. All rights reserved.
* Copyright (c) 2007-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 "dr_api.h"
#include <string.h>
#define MINSERT instrlist_meta_preinsert
/* FIXME - module events are not supported on Linux */
#ifdef WINDOWS
static app_pc start = NULL;
static app_pc end = NULL;
#endif
static bool use_unlink = false;
static bool delay_flush_at_next_build = false;
static int bb_build_count = 0;
static uint callback_count = 0;
/* Keep a list that tracks which tags have been created and deleted.
* We need to make sure we're informed of all flushed fragments.
* The list is global -- we should use a mutex when accessing, but
* the test is single threaded.
*/
typedef struct _elem_t {
void *tag;
int count;
struct _elem_t *next;
struct _elem_t *prev;
} elem_t;
elem_t *head = NULL;
elem_t *tail = NULL;
static
elem_t *find(void *tag)
{
elem_t *iter = head;
while (iter) {
if (iter->tag == tag)
return iter;
iter = iter->next;
}
return NULL;
}
static
void increment(void *tag)
{
elem_t *elem;
elem = find(tag);
if (elem != NULL) {
elem->count++;
}
else {
elem = dr_global_alloc(sizeof(elem_t));
elem->tag = tag;
elem->count = 1;
elem->next = NULL;
elem->prev = NULL;
if (head == NULL) {
head = elem;
tail = elem;
}
else {
tail->next = elem;
elem->prev = tail;
tail = elem;
}
}
}
static
void decrement(void *tag)
{
elem_t *elem;
elem = find(tag);
if (elem == NULL) {
dr_fprintf(STDERR, "ERROR removing "PFX"\n", tag);
}
else {
elem->count--;
if (elem->count == 0) {
if (head == elem) {
head = elem->next;
}
if (tail == elem) {
tail = elem->prev;
}
if (elem->next) {
elem->next->prev = elem->prev;
}
if (elem->prev) {
elem->prev->next = elem->next;
}
dr_global_free(elem, sizeof(elem_t));
}
}
}
static
void exit_event(void)
{
int count = 0;
elem_t *iter = head;
while (iter) {
elem_t *next = iter->next;
count += iter->count;
dr_fprintf(STDERR, "ERROR: "PFX" undeleted\n", iter->tag);
dr_global_free(iter, sizeof(elem_t));
iter = next;
}
dr_fprintf(STDERR, "%d undeleted fragments\n", count);
/* get around nondeterminism */
if (bb_build_count >= 5 && bb_build_count <= 15)
dr_fprintf(STDERR, "constructed BB 5-15 times\n");
else
dr_fprintf(STDERR, "constructed BB %d times\n", bb_build_count);
}
static
dr_emit_flags_t trace_event(void *drcontext, void *tag, instrlist_t *trace,
bool translating)
{
if (!translating)
increment(tag);
return DR_EMIT_DEFAULT;
}
static
void deleted_event(void *dcontext, void *tag)
{
decrement(tag);
}
void flush_event(int flush_id)
{
dr_fprintf(STDERR, "Flush completion id=%d\n", flush_id);
}
static
void callback(void *tag, app_pc next_pc)
{
callback_count++;
/* Flush all fragments containing this tag twice every hundred calls alternating
* between a sync_all and delay flush (if available) and an unlink and delay flush
* (if available). */
if (callback_count % 100 == 0) {
if (callback_count % 200 == 0) {
/* For windows test dr_flush_region() half the time */
dr_mcontext_t mcontext = {sizeof(mcontext),DR_MC_ALL,};
dr_delay_flush_region((app_pc)tag - 20, 30, callback_count, flush_event);
dr_get_mcontext(dr_get_current_drcontext(), &mcontext);
mcontext.pc = next_pc;
dr_flush_region(tag, 1);
dr_redirect_execution(&mcontext);
*(volatile uint *)NULL = 0; /* ASSERT_NOT_REACHED() */
} else if (use_unlink) {
/* Test dr_unlink_flush_region() half the time (if available).
* FIXME - extend once we add unlink callback. */
delay_flush_at_next_build = true;
dr_unlink_flush_region(tag, 1);
}
}
}
#ifdef WINDOWS
static
bool string_match(const char *str1, const char *str2)
{
if (str1 == NULL || str2 == NULL)
return false;
while (*str1 == *str2) {
if (*str1 == '\0')
return true;
str1++;
str2++;
}
return false;
}
static
void module_load_event(void *dcontext, const module_data_t *data, bool loaded)
{
if (string_match(dr_module_preferred_name(data), "client.flush.exe")) {
start = data->start;
end = data->end;
}
}
#endif
static
dr_emit_flags_t bb_event(void* drcontext, void *tag, instrlist_t *bb,
bool for_trace, bool translating)
{
instr_t *instr;
if (!translating)
increment(tag);
/* I'm looking for a specific BB in the test .exe. I've marked
* it with a couple nops.
*/
#ifdef WINDOWS
if ((app_pc)tag >= start && (app_pc)tag < end) {
#endif
instr = instrlist_first(bb);
if (instr_is_nop(instr)) {
instr_t *next = instr_get_next(instr);
/* The test app uses two nops as a marker to identify a specific bb. Since
* 2 nop instructions in a row aren't that uncommon on Linux (where we can't
* restrict our search to just the test.exe module) we use an unusual nop
* for the second one: xchg xbp, xbp */
if (next != NULL && instr_is_nop(next) &&
instr_get_opcode(next) == OP_xchg &&
instr_writes_to_exact_reg(next, REG_XBP, DR_QUERY_DEFAULT)) {
bb_build_count++;
if (delay_flush_at_next_build) {
delay_flush_at_next_build = false;
dr_delay_flush_region((app_pc)tag - 20, 30, callback_count, flush_event);
}
dr_insert_clean_call(drcontext, bb, instr, (void *)callback,
false, 2, OPND_CREATE_INTPTR(tag),
OPND_CREATE_INTPTR(instr_get_app_pc(instr)));
}
}
#ifdef WINDOWS
}
#endif
return DR_EMIT_DEFAULT;
}
DR_EXPORT
void dr_init(client_id_t id)
{
const char *options = dr_get_options(id);
dr_fprintf(STDERR, "options = %s\n", options);
if (options != NULL && strstr(options, "use_unlink") != NULL) {
use_unlink = true;
}
#ifdef WINDOWS
dr_register_module_load_event(module_load_event);
#endif
dr_register_exit_event(exit_event);
dr_register_trace_event(trace_event);
dr_register_delete_event(deleted_event);
dr_register_bb_event(bb_event);
}