blob: 66a23831f99e98d5bc54855ddc9456a0fb239599 [file] [log] [blame]
/* **********************************************************
* Copyright (c) 2008-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.
*/
/* Code Manipulation API Sample:
* trace-inline.c
*
* Uses the custom trace API to inline entire callees into traces.
*/
#include "dr_api.h"
#include <string.h> /* memset */
/****************************************************************************/
/* global */
static int num_complete_inlines;
static void *htable_mutex; /* for multithread support */
static void event_exit(void);
static dr_emit_flags_t event_basic_block(void *drcontext, void *tag, instrlist_t *bb,
bool for_trace, bool translating);
static void event_fragment_deleted(void *drcontext, void *tag);
static dr_custom_trace_action_t
query_end_trace(void *drcontext, void * trace_tag, void *next_tag);
/****************************************************************************/
/* hashtable so we know if a particular tag is for a call trace or a
* normal back branch trace
*/
typedef struct _trace_head_entry_t {
void *tag;
bool is_trace_head;
bool has_ret;
/* have to end at next block after see return */
int end_next;
/* some callees are too large to inline, so have size limit */
uint size;
struct _trace_head_entry_t *next;
} trace_head_entry_t;
/* global table */
static trace_head_entry_t **htable;
/* max call-trace size */
#define INLINE_SIZE_LIMIT (4*1024)
/* no instruction alignment -> use the lsb! */
#define HASH_MASK(num_bits) ((~0U)>>(32-(num_bits)))
#define HASH_FUNC_BITS(val, num_bits) ((val) & (HASH_MASK(num_bits)))
#define HASH_FUNC(val, mask) ((val) & (mask))
#define HASHTABLE_SIZE(num_bits) (1U << (num_bits))
#define HASH_BITS 13
#define TABLE_SIZE HASHTABLE_SIZE(HASH_BITS) * sizeof(trace_head_entry_t*)
/* if drcontext == NULL uses global memory */
static trace_head_entry_t **
htable_create(void *drcontext)
{
trace_head_entry_t **table = (trace_head_entry_t**) dr_global_alloc(TABLE_SIZE);
/* assume during process init so no lock needed */
memset(table, 0, TABLE_SIZE);
return table;
}
/* if drcontext == NULL uses global memory */
static void
htable_free(void *drcontext, trace_head_entry_t **table)
{
/* assume during process exit so no lock needed */
int i;
/* clean up memory */
for (i = 0; i < HASHTABLE_SIZE(HASH_BITS); i++) {
trace_head_entry_t *e = table[i];
while (e) {
trace_head_entry_t *nexte = e->next;
dr_global_free(e, sizeof(trace_head_entry_t));
e = nexte;
}
table[i] = NULL;
}
dr_global_free(table, TABLE_SIZE);
}
/* Caller must hold htable_mutex if drcontext == NULL. */
static trace_head_entry_t *
add_trace_head_entry(void *drcontext, void *tag)
{
trace_head_entry_t **table = htable;
trace_head_entry_t *e;
uint hindex;
e = (trace_head_entry_t *) dr_global_alloc(sizeof(trace_head_entry_t));
e->tag = tag;
e->end_next = 0;
e->size = 0;
e->has_ret = false;
e->is_trace_head = false;
hindex = (uint) HASH_FUNC_BITS((ptr_uint_t)tag, HASH_BITS);
e->next = table[hindex];
table[hindex] = e;
return e;
}
/* Lookup an entry by pc and return a pointer to the corresponding entry .
* Returns NULL if no such entry exists.
* Caller must hold htable_mutex if drcontext == NULL.
*/
static trace_head_entry_t *
lookup_trace_head_entry(void *drcontext, void *tag)
{
trace_head_entry_t **table = htable;
trace_head_entry_t *e;
uint hindex;
hindex = (uint) HASH_FUNC_BITS((ptr_uint_t)tag, HASH_BITS);
for (e = table[hindex]; e; e = e->next) {
if (e->tag == tag)
return e;
}
return NULL;
}
/* Lookup an entry by tag and index and delete it.
* Returns false if no such entry exists.
* Caller must hold htable_mutex if drcontext == NULL.
*/
static bool
remove_trace_head_entry(void *drcontext, void *tag)
{
trace_head_entry_t **table = htable;
trace_head_entry_t *e, *prev;
uint hindex;
hindex = (uint) HASH_FUNC_BITS((ptr_uint_t)tag, HASH_BITS);
for (prev = NULL, e = table[hindex]; e; prev = e, e = e->next) {
if (e->tag == tag) {
if (prev)
prev->next = e->next;
else
table[hindex] = e->next;
dr_global_free(e, sizeof(trace_head_entry_t));
return true;
}
}
return false;
}
/****************************************************************************/
DR_EXPORT void
dr_init(client_id_t id)
{
htable_mutex = dr_mutex_create();
/* global HASH_BITS-bit addressed hash table */
htable = htable_create(NULL/*global*/);
dr_register_exit_event(event_exit);
dr_register_bb_event(event_basic_block);
dr_register_delete_event(event_fragment_deleted);
dr_register_end_trace_event(query_end_trace);
/* make it easy to tell, by looking at log file, which client executed */
dr_log(NULL, LOG_ALL, 1, "Client 'inline' initializing\n");
num_complete_inlines = 0;
}
static void
event_exit(void)
{
/* On WOW64xpsp2 I see 440+, but only 230+ on 2k3 */
if (num_complete_inlines > 100)
dr_fprintf(STDERR, "Inlined callees in >100 traces\n");
else
dr_fprintf(STDERR, "Inlined callees in %d traces: < 100!!!\n", num_complete_inlines);
htable_free(NULL/*global*/, htable);
dr_mutex_destroy(htable_mutex);
}
/****************************************************************************/
/* the work itself */
static dr_emit_flags_t
event_basic_block(void *drcontext, void *tag, instrlist_t *bb, bool for_trace, bool translating)
{
instr_t *instr;
trace_head_entry_t *e = NULL;
if (translating)
return DR_EMIT_DEFAULT;
for (instr = instrlist_first(bb); instr != NULL; instr = instr_get_next(instr)) {
/* blocks containing calls are trace heads */
if (instr_is_call(instr)) {
dr_mark_trace_head(drcontext, tag);
dr_mutex_lock(htable_mutex);
e = add_trace_head_entry(NULL, tag);
e->is_trace_head = true;
dr_mutex_unlock(htable_mutex);
#ifdef VERBOSE
dr_log(drcontext, LOG_ALL, 3,
"inline: marking bb "PFX" as trace head\n", tag);
#endif
/* doesn't matter what's in rest of bb */
return DR_EMIT_DEFAULT;
} else if (instr_is_return(instr)) {
dr_mutex_lock(htable_mutex);
e = add_trace_head_entry(NULL, tag);
e->has_ret = true;
dr_mutex_unlock(htable_mutex);
}
}
return DR_EMIT_DEFAULT;
}
/* to keep the size of our hashtable down */
static void
event_fragment_deleted(void *drcontext, void *tag)
{
dr_mutex_lock(htable_mutex);
remove_trace_head_entry(NULL, tag);
dr_mutex_unlock(htable_mutex);
}
/* Ask whether to end trace prior to adding next_tag fragment.
* Return values:
* CUSTOM_TRACE_DR_DECIDES = use standard termination criteria
* CUSTOM_TRACE_END_NOW = end trace
* CUSTOM_TRACE_CONTINUE = do not end trace
*/
static dr_custom_trace_action_t
query_end_trace(void *drcontext, void *trace_tag, void *next_tag)
{
/* if this is a call trace, only end on the block AFTER a return
* (need to get the return inlined!)
* if this is a standard back branch trace, end it if we see a
* block with a call (so that we'll go into the call trace).
* otherwise return 0 and let DynamoRIO determine whether to
* terminate the trace now.
*/
trace_head_entry_t *e;
dr_mutex_lock(htable_mutex);
e = lookup_trace_head_entry(NULL, trace_tag);
if (e == NULL || !e->is_trace_head) {
e = lookup_trace_head_entry(NULL, next_tag);
if (e == NULL || !e->is_trace_head) {
dr_mutex_unlock(htable_mutex);
return CUSTOM_TRACE_DR_DECIDES;
} else {
/* we've found a call, end this trace now so it won't keep going and
* end up never entering the call trace
*/
#ifdef VERBOSE
dr_log(drcontext, LOG_ALL, 3,
"inline: ending trace "PFX" before block "PFX" containing call\n",
trace_tag, next_tag);
#endif
dr_mutex_unlock(htable_mutex);
return CUSTOM_TRACE_END_NOW;
}
} else if (e->end_next > 0) {
e->end_next--;
if (e->end_next == 0) {
#ifdef VERBOSE
dr_log(drcontext, LOG_ALL, 3,
"inline: ending trace "PFX" before "PFX"\n",
trace_tag, next_tag);
#endif
num_complete_inlines++;
dr_mutex_unlock(htable_mutex);
return CUSTOM_TRACE_END_NOW;
}
} else {
trace_head_entry_t *nxte = lookup_trace_head_entry(NULL, next_tag);
uint size = dr_fragment_size(drcontext, next_tag);
e->size += size;
if (e->size > INLINE_SIZE_LIMIT) {
#ifdef VERBOSE
dr_log(drcontext, LOG_ALL, 3,
"inline: ending trace "PFX" before "PFX" because reached size limit\n",
trace_tag, next_tag);
#endif
dr_mutex_unlock(htable_mutex);
return CUSTOM_TRACE_END_NOW;
}
if (nxte != NULL && nxte->has_ret && !nxte->is_trace_head) {
/* end trace after NEXT block */
e->end_next = 2;
#ifdef VERBOSE
dr_log(drcontext, LOG_ALL, 3,
"inline: going to be ending trace "PFX" after "PFX"\n",
trace_tag, next_tag);
#endif
dr_mutex_unlock(htable_mutex);
return CUSTOM_TRACE_CONTINUE;
}
}
/* do not end trace */
#ifdef VERBOSE
dr_log(drcontext, LOG_ALL, 3,
"inline: NOT ending trace "PFX" after "PFX"\n", trace_tag, next_tag);
#endif
dr_mutex_unlock(htable_mutex);
return CUSTOM_TRACE_CONTINUE;
}