blob: 98a0d7dcd420534db41b721b116e8528b1583566 [file]
/* **********************************************************
* Copyright (c) 2013-2014 Google, Inc. All rights reserved.
* 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:
* stats.c
*
* Serves as an example of how to create custom statistics and export
* them in shared memory. It uses the Windows API, which will be redirected
* by DynamoRIO in order to maintain isolation and transparency.
* The current version only supports viewing statistics from processes
* in the same session and by the same user. Look at older versions of
* the sources for statistics that can be read across sessions (but only
* when the target process and the viewer are run as administrator).
*
* These statistics can be viewed using the provided statistics
* viewer. This code also documents the official shared memory layout
* required by the statistics viewer.
*/
#define _CRT_SECURE_NO_DEPRECATE 1
#include "dr_api.h"
#include "utils.h"
#include <stddef.h> /* for offsetof */
#include <wchar.h> /* _snwprintf */
#ifndef WINDOWS
# error WINDOWS-only!
#endif
#define ALIGNED(x, alignment) ((((ptr_uint_t)x) & ((alignment)-1)) == 0)
/* We export a set of stats in shared memory.
* drstats.exe reads and displays them.
*/
const char *stat_names[] = {
/* drstats.exe displays CLIENTSTAT_NAME_MAX_LEN chars for each name */
"Instructions",
"Floating point instrs",
"System calls",
};
/* We do not prefix "Global\", so these stats are NOT visible across sessions
* (that requires running as admiministrator, on Vista+).
* On NT these prefixes are not supported.
*/
#define CLIENT_SHMEM_KEY_NT_L L"DynamoRIO_Client_Statistics"
#define CLIENT_SHMEM_KEY_L L"Local\\DynamoRIO_Client_Statistics"
#define CLIENTSTAT_NAME_MAX_LEN 47
#define NUM_STATS (sizeof(stat_names)/sizeof(char*))
/* Statistics are all 64-bit for x64. At some point we'll add per-stat
* typing, but for now we have identical types dependent on the platform.
*/
#ifdef X86_64
typedef int64 stats_int_t;
# define STAT_FORMAT_CODE UINT64_FORMAT_CODE
#else
typedef int stats_int_t;
# define STAT_FORMAT_CODE "d"
#endif
/* we allocate this struct in the shared memory: */
typedef struct _client_stats {
uint num_stats;
bool exited;
process_id_t pid;
/* we need a copy of all the names here */
char names[NUM_STATS][CLIENTSTAT_NAME_MAX_LEN];
stats_int_t num_instrs;
stats_int_t num_flops;
stats_int_t num_syscalls;
} client_stats;
/* we directly increment the global counters using a lock prefix */
static client_stats *stats;
/***************************************************************************/
/* shared memory setup */
/* we have multiple shared memories: one that holds the count of
* statistics instances, and then one per statistics struct */
static HANDLE shared_map_count;
static PVOID shared_view_count;
static int * shared_count;
static HANDLE shared_map;
static PVOID shared_view;
#define KEYNAME_MAXLEN 128
static wchar_t shared_keyname[KEYNAME_MAXLEN];
/* returns current contents of addr and replaces contents with value */
static uint
atomic_swap(void *addr, uint value)
{
return _InterlockedExchange((long *)addr, value);
}
static bool
is_windows_NT(void)
{
dr_os_version_info_t ver;
return (dr_get_os_version(&ver) && ver.version == DR_WINDOWS_VERSION_NT);
}
static client_stats *
shared_memory_init(void)
{
bool is_NT = is_windows_NT();
int num;
int pos;
/* We do not want to rely on the registry.
* Instead, a piece of shared memory with the key base name holds the
* total number of stats instances.
*/
shared_map_count =
CreateFileMappingW(INVALID_HANDLE_VALUE, NULL,
PAGE_READWRITE, 0, sizeof(client_stats),
is_NT ? CLIENT_SHMEM_KEY_NT_L : CLIENT_SHMEM_KEY_L);
DR_ASSERT(shared_map_count != NULL);
shared_view_count =
MapViewOfFile(shared_map_count, FILE_MAP_READ|FILE_MAP_WRITE, 0, 0, 0);
DR_ASSERT(shared_view_count != NULL);
shared_count = (int *) shared_view_count;
/* ASSUMPTION: memory is initialized to 0!
* otherwise our protocol won't work
* it's hard to build a protocol to initialize it to 0 -- if you want
* to add one, feel free, but make sure it's correct
*/
do {
pos = (int) atomic_swap(shared_count, (uint) -1);
/* if get -1 back, someone else is looking at it */
} while (pos == -1);
/* now increment it */
atomic_swap(shared_count, pos+1);
num = 0;
while (1) {
_snwprintf(shared_keyname, KEYNAME_MAXLEN, L"%s.%03d",
is_NT ? CLIENT_SHMEM_KEY_NT_L : CLIENT_SHMEM_KEY_L, num);
shared_map = CreateFileMappingW(INVALID_HANDLE_VALUE, NULL,
PAGE_READWRITE, 0,
sizeof(client_stats),
shared_keyname);
if (shared_map != NULL && GetLastError() == ERROR_ALREADY_EXISTS) {
dr_close_file(shared_map);
shared_map = NULL;
}
if (shared_map != NULL)
break;
num++;
}
dr_log(NULL, LOG_ALL, 1, "Shared memory key is: \"%S\"\n", shared_keyname);
#ifdef SHOW_RESULTS
dr_fprintf(STDERR, "Shared memory key is: \"%S\"\n", shared_keyname);
#endif
shared_view = MapViewOfFile(shared_map, FILE_MAP_READ|FILE_MAP_WRITE, 0, 0, 0);
DR_ASSERT(shared_view != NULL);
return (client_stats *) shared_view;
}
static void
shared_memory_exit(void)
{
int pos;
stats->exited = true;
/* close down statistics */
UnmapViewOfFile(shared_view);
dr_close_file(shared_map);
/* decrement count, then unmap */
do {
pos = atomic_swap(shared_count, (uint) -1);
/* if get -1 back, someone else is looking at it */
} while (pos == -1);
/* now increment it */
atomic_swap(shared_count, pos-1);
UnmapViewOfFile(shared_view_count);
CloseHandle(shared_map_count);
}
/***************************************************************************/
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 client_id_t my_id;
DR_EXPORT void
dr_init(client_id_t id)
{
uint i;
dr_set_client_name("DynamoRIO Sample Client 'stats'", "http://dynamorio.org/issues");
my_id = id;
/* make it easy to tell, by looking at log file, which client executed */
dr_log(NULL, LOG_ALL, 1, "Client 'stats' initializing\n");
stats = shared_memory_init();
memset(stats, 0, sizeof(stats));
stats->num_stats = NUM_STATS;
stats->pid = dr_get_process_id();
for (i=0; i<NUM_STATS; i++) {
strncpy(stats->names[i], stat_names[i], CLIENTSTAT_NAME_MAX_LEN);
stats->names[i][CLIENTSTAT_NAME_MAX_LEN-1] = '\0';
}
dr_register_bb_event(event_basic_block);
dr_register_exit_event(event_exit);
}
#ifdef WINDOWS
# define IF_WINDOWS(x) x
#else
# define IF_WINDOWS(x) /* nothing */
#endif
static void
event_exit(void)
{
file_t f;
/* display the results */
char msg[512];
int len;
len = dr_snprintf(msg, sizeof(msg)/sizeof(msg[0]),
"Instrumentation results:\n"
" saw %" STAT_FORMAT_CODE " flops\n", stats->num_flops);
DR_ASSERT(len > 0);
msg[sizeof(msg)/sizeof(msg[0])-1] = '\0';
#ifdef SHOW_RESULTS
DISPLAY_STRING(msg);
#endif /* SHOW_RESULTS */
/* On Windows we need an absolute path so we place it in
* the same directory as our library.
*/
f = log_file_open(my_id, NULL, NULL /* client lib path */, "stats", 0);
DR_ASSERT(f != INVALID_FILE);
dr_fprintf(f, "%s\n", msg);
dr_close_file(f);
shared_memory_exit();
}
static void
insert_inc(void *drcontext, instrlist_t *bb, instr_t *where,
stats_int_t *addr, uint incby)
{
instr_t *inc;
opnd_t immed;
if (incby <= 0x7f)
immed = OPND_CREATE_INT8(incby);
else /* unlikely but possible */
immed = OPND_CREATE_INT32(incby);
inc = INSTR_CREATE_add
(drcontext, OPND_CREATE_ABSMEM(addr, opnd_size_from_bytes(sizeof(stats_int_t))),
immed);
/* make it thread-safe (only works if it doesn't straddle a cache line) */
instr_set_prefix_flag(inc, PREFIX_LOCK);
DR_ASSERT(ALIGNED(addr, sizeof(stats_int_t))); /* aligned => single cache line */
instrlist_meta_preinsert(bb, where, inc);
}
static dr_emit_flags_t
event_basic_block(void *drcontext, void *tag, instrlist_t *bb,
bool for_trace, bool translating)
{
instr_t *instr;
dr_fp_type_t fp_type;
int num_instrs = 0;
int num_flops = 0;
int num_syscalls = 0;
#ifdef VERBOSE
dr_printf("in dr_basic_block(tag="PFX")\n", tag);
# ifdef VERBOSE_VERBOSE
instrlist_disassemble(drcontext, tag, bb, STDOUT);
# endif
#endif
/* count up # flops, then do single increment at end */
for (instr = instrlist_first_app(bb);
instr != NULL;
instr = instr_get_next_app(instr)) {
num_instrs++;
if (instr_is_floating_ex(instr, &fp_type) &&
/* We exclude loads and stores (and reg-reg moves) and state preservation */
(fp_type == DR_FP_CONVERT || fp_type == DR_FP_MATH)) {
#ifdef VERBOSE
dr_print_instr(drcontext, STDOUT, instr, "Found flop: ");
#endif
num_flops++;
}
if (instr_is_syscall(instr)) {
num_syscalls++;
}
}
if (num_instrs > 0 || num_flops > 0 || num_syscalls > 0) {
uint eflags;
bool need_to_save = true;
/* insert increment at start, for maximum prob. of not needing
* to save flags
*/
for (instr = instrlist_first(bb); instr != NULL;
instr = instr_get_next(instr)) {
eflags = instr_get_eflags(instr, DR_QUERY_DEFAULT);
/* could be more sophisticated and look beyond exit */
if (instr_is_exit_cti(instr) || (eflags & EFLAGS_READ_6) != 0)
break;
if ((eflags & EFLAGS_WRITE_6) == EFLAGS_WRITE_6) {
need_to_save = false;
break;
}
}
instr = instrlist_first(bb);
if (need_to_save)
dr_save_arith_flags(drcontext, bb, instr, SPILL_SLOT_1);
if (num_instrs > 0)
insert_inc(drcontext, bb, instr, &stats->num_instrs, num_instrs);
if (num_flops > 0)
insert_inc(drcontext, bb, instr, &stats->num_flops, num_flops);
if (num_syscalls > 0)
insert_inc(drcontext, bb, instr, &stats->num_syscalls, num_syscalls);
if (need_to_save)
dr_restore_arith_flags(drcontext, bb, instr, SPILL_SLOT_1);
}
return DR_EMIT_DEFAULT;
}