blob: f2c546b0a0afead8de14ddd7ad9894bfa4f050f1 [file] [log] [blame]
/* ***************************************************************************
* Copyright (c) 2012-2014 Google, 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 Google, 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 GOOGLE, 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:
* drcov.c
*
* Collects information about basic blocks that have been executed.
* It simply stores the information of basic blocks seen in bb callback event
* into a table without any instrumentation, and dumps the buffer into log files
* on thread/process exit.
* To collect per-thread basic block execution information, run DR with
* a thread private code cache (i.e., -thread_private).
* The information can be used in cases like code coverage.
*
* The runtime options for this client include:
* -dump_text Dumps the log file in text format
* -dump_binary Dumps the log file in binary format
* -[no_]nudge_kills On by default.
* Uses nudge to notify a child process being terminated
* by its parent, so that the exit event will be called.
* -logdir <dir> Sets log directory, which by default is ".".
*
* The two options below can only be used when the client is compiled with
* CBR_COVERAGE being defined.
*
* -check_cbr Performs simple online conditional branch coverage checks.
* Checks how many conditional branches are seen and how
* many branches/fallthroughs are not excercised.
* The result are printed to drcov.*.res file.
* -summary_only Prints only the summary of check results. Must be used
* with -check_cbr option.
*/
#include "dr_api.h"
#include "drmgr.h"
#include "drx.h"
#include "drcov.h"
#include "../common/modules.h"
#include "../common/utils.h"
#include "hashtable.h"
#include "drtable.h"
#include "limits.h"
#include <string.h>
#ifdef CBR_COVERAGE
# define IF_CBR_COVERAGE_ELSE(x, y) x
#else
# define IF_CBR_COVERAGE_ELSE(x, y) y
#endif
#define UNKNOWN_MODULE_ID USHRT_MAX
static uint verbose;
#define NOTIFY(level, fmt, ...) do { \
if (verbose >= (level)) \
dr_fprintf(STDERR, fmt, __VA_ARGS__); \
} while (0)
#define OPTION_MAX_LENGTH MAXIMUM_PATH
typedef struct _drcov_option_t {
bool dump_text;
bool dump_binary;
/* Use nudge to notify the process for termination so that
* event_exit will be called.
*/
bool nudge_kills;
char logdir[MAXIMUM_PATH];
int native_until_thread;
#ifdef CBR_COVERAGE
bool check;
bool summary;
#endif
} drcov_option_t;
static drcov_option_t options;
#define NUM_THREAD_MODULE_CACHE 4
typedef struct _per_thread_t {
void *bb_table;
/* for quick per-thread query without lock */
module_entry_t *cache[NUM_THREAD_MODULE_CACHE];
file_t log;
#ifdef CBR_COVERAGE
file_t res;
#endif
} per_thread_t;
static per_thread_t *global_data;
static bool drcov_per_thread = false;
static module_table_t *module_table;
static client_id_t client_id;
#ifndef WINDOWS
static int sysnum_execve = IF_X64_ELSE(59, 11);
#endif
static volatile bool go_native;
static int tls_idx = -1;
static void
event_exit(void);
static void
event_thread_exit(void *drcontext);
/****************************************************************************
* Utility Functions
*/
static file_t
log_file_create_helper(void *drcontext, const char *suffix)
{
char buf[MAXIMUM_PATH];
file_t log =
drx_open_unique_appid_file(options.logdir, drcontext == NULL ?
dr_get_process_id() : dr_get_thread_id(drcontext),
"drcov", suffix,
#ifndef WINDOWS
DR_FILE_CLOSE_ON_FORK |
#endif
DR_FILE_ALLOW_LARGE,
buf, BUFFER_SIZE_ELEMENTS(buf));
if (log != INVALID_FILE) {
dr_log(drcontext, LOG_ALL, 1, "drcov: log file is %s\n", buf);
NOTIFY(1, "<created log file %s>\n", buf);
}
return log;
}
static void
log_file_create(void *drcontext, per_thread_t *data)
{
if (options.dump_text || options.dump_binary) {
data->log = log_file_create_helper(drcontext, drcontext == NULL ?
"proc.log" : "thd.log");
} else {
data->log = INVALID_FILE;
}
#ifdef CBR_COVERAGE
if (options.check) {
data->res = log_file_create_helper(drcontext, drcontext == NULL ?
"proc.res" : "thd.res");
} else
data->res = INVALID_FILE;
#endif
}
/****************************************************************************
* BB Table Functions
*/
#ifdef CBR_COVERAGE
/* iterate data passed for branch coverage check iteration */
typedef struct _check_iter_data_t {
per_thread_t *data;
int num_mods;
/* arrays below are indexed by module id, #modules-1 for bb w/ no-module */
ptr_uint_t *num_bbs;
ptr_uint_t *num_cbr_tgts;
ptr_uint_t *num_cbr_falls;
ptr_uint_t *num_cbr_tgt_misses;
ptr_uint_t *num_cbr_fall_misses;
/* stores all the bbs seen for each module */
hashtable_t *bb_htables;
/* stores all the cbr targets/fallthroughs seen for each module */
hashtable_t *cbr_htables;
} check_iter_data_t;
static bool
bb_table_entry_check(ptr_uint_t idx, void *entry, void *iter_data)
{
check_iter_data_t *data = (check_iter_data_t *)iter_data;
bb_entry_t *bb_entry = (bb_entry_t *)entry;
hashtable_t *bb_htable;
hashtable_t *cbr_htable;
int mod_id = (bb_entry->mod_id == UNKNOWN_MODULE_ID) ?
data->num_mods-1 : bb_entry->mod_id;
bb_htable = &data->bb_htables[mod_id];
cbr_htable = &data->cbr_htables[mod_id];
if (bb_entry->cbr_tgt != 0) {
if (hashtable_add
(cbr_htable, (void *)(ptr_uint_t)bb_entry->cbr_tgt, entry)) {
data->num_cbr_tgts[mod_id]++;
if (hashtable_lookup
(bb_htable, (void *)(ptr_uint_t)bb_entry->cbr_tgt) == NULL) {
data->num_cbr_tgt_misses[mod_id]++;
if (!options.summary) {
dr_fprintf(data->data->res, "module[%3d]: "PFX" to "PFX"\n",
mod_id,
(void *)(ptr_uint_t)bb_entry->start,
(void *)(ptr_uint_t)bb_entry->cbr_tgt);
}
}
}
if (hashtable_add(cbr_htable,
(void *)(ptr_uint_t)(bb_entry->start+bb_entry->size),
entry)) {
data->num_cbr_falls[mod_id]++;
if (hashtable_lookup(bb_htable,
(void *)(ptr_uint_t)
(bb_entry->start + bb_entry->size))
== NULL) {
data->num_cbr_fall_misses[mod_id]++;
if (!options.summary) {
dr_fprintf(data->data->res, "module[%3d]: "PFX" to "PFX"\n",
mod_id,
bb_entry->start,
bb_entry->start + bb_entry->size);
}
}
}
}
return true;
}
static bool
bb_table_entry_fill_htable(ptr_uint_t idx, void *entry, void *iter_data)
{
check_iter_data_t *data = (check_iter_data_t *)iter_data;
bb_entry_t *bb_entry = (bb_entry_t *)entry;
int mod_id = (bb_entry->mod_id == UNKNOWN_MODULE_ID) ?
data->num_mods - 1 : bb_entry->mod_id;
hashtable_t *htable = &data->bb_htables[mod_id];
if (hashtable_add(htable, (void *)(ptr_uint_t)bb_entry->start, entry))
data->num_bbs[mod_id]++;
return true;
}
static void
bb_table_check_print_result(per_thread_t *data,
check_iter_data_t *iter_data,
int mod_id)
{
dr_fprintf(data->res,
"\tunique basic blocks seen: "SZFMT",\n"
"\tunique conditional branch targets: "SZFMT
", not excercised: "SZFMT",\n"
"\tunique conditional branch fallthroughs: "SZFMT
", not excercised: "SZFMT",\n",
iter_data->num_bbs[mod_id],
iter_data->num_cbr_tgts[mod_id],
iter_data->num_cbr_tgt_misses[mod_id],
iter_data->num_cbr_falls[mod_id],
iter_data->num_cbr_fall_misses[mod_id]);
}
/* Checks each conditional branch target and fall-through with whether
* it was executed.
*
* This is done by iterating the bb_table twice:
* - Iteration 1 scans the bb table to find all unique bbs and put them
* into hashtables (bb_htables) of each module.
* - Iteration 2 scans the bb table to find all unique cbr targets and
* fall-throughs, which are stored in hashtables (cbr_htables), and check
* whether they are in bb_htables.
*/
static void
bb_table_check_cbr(module_table_t *table, per_thread_t *data)
{
check_iter_data_t iter_data;
int i;
/* one additional mod for bb w/o module */
int num_mods = table->vector.entries + 1;
ASSERT(data->res != INVALID_FILE, "result file is invalid");
/* create hashtable for each module */
iter_data.data = data;
iter_data.num_mods = num_mods;
iter_data.bb_htables = dr_global_alloc(sizeof(hashtable_t)*num_mods);
iter_data.cbr_htables = dr_global_alloc(sizeof(hashtable_t)*num_mods);
iter_data.num_bbs = dr_global_alloc(sizeof(ptr_uint_t)*num_mods);
iter_data.num_cbr_tgts = dr_global_alloc(sizeof(ptr_uint_t)*num_mods);
iter_data.num_cbr_falls = dr_global_alloc(sizeof(ptr_uint_t)*num_mods);
iter_data.num_cbr_tgt_misses =
dr_global_alloc(sizeof(ptr_uint_t)*num_mods);
iter_data.num_cbr_fall_misses =
dr_global_alloc(sizeof(ptr_uint_t)*num_mods);
memset(iter_data.num_bbs, 0, sizeof(ptr_uint_t)*num_mods);
memset(iter_data.num_cbr_tgts, 0, sizeof(ptr_uint_t)*num_mods);
memset(iter_data.num_cbr_falls, 0, sizeof(ptr_uint_t)*num_mods);
memset(iter_data.num_cbr_tgt_misses, 0, sizeof(ptr_uint_t)*num_mods);
memset(iter_data.num_cbr_fall_misses, 0, sizeof(ptr_uint_t)*num_mods);
for (i = 0; i < num_mods; i++) {
hashtable_init_ex(&iter_data.bb_htables[i], 6, HASH_INTPTR,
false/*!strdup*/, false/*!sync*/, NULL, NULL, NULL);
hashtable_init_ex(&iter_data.cbr_htables[i], 6, HASH_INTPTR,
false/*!strdup*/, false/*!sync*/, NULL, NULL, NULL);
}
/* first iteration to fill the hashtable */
drtable_iterate(data->bb_table, &iter_data, bb_table_entry_fill_htable);
/* second iteration to check if any cbr tgt is there */
if (!options.summary)
dr_fprintf(data->res, "conditional branch not excercised:\n");
drtable_iterate(data->bb_table, &iter_data, bb_table_entry_check);
/* check result */
dr_fprintf(data->res, "Summary:\n");
dr_fprintf(data->res, "module id, base, end, entry, unload, name, path");
#ifdef WINDOWS
dr_fprintf(data->res, ", checksum, timestamp");
#endif
dr_fprintf(data->res, "\n");
drvector_lock(&module_table->vector);
for (i = 0; i < num_mods-1; i++) {
module_entry_t *entry = drvector_get_entry(&module_table->vector, i);
ASSERT(entry != NULL, "fail to get a module entry");
module_table_entry_print(entry, data->res, true);
bb_table_check_print_result(data, &iter_data, i);
}
drvector_unlock(&module_table->vector);
if (iter_data.num_bbs[i] != 0) {
dr_fprintf(data->res, "basic blocks from unknown module\n");
bb_table_check_print_result(data, &iter_data, i);
}
/* destroy the hashtable for each modules */
for (i = 0; i < num_mods; i++) {
hashtable_delete(&iter_data.bb_htables[i]);
hashtable_delete(&iter_data.cbr_htables[i]);
}
dr_global_free(iter_data.bb_htables, sizeof(hashtable_t)*num_mods);
dr_global_free(iter_data.cbr_htables, sizeof(hashtable_t)*num_mods);
dr_global_free(iter_data.num_bbs, sizeof(ptr_uint_t)*num_mods);
dr_global_free(iter_data.num_cbr_tgts, sizeof(ptr_uint_t)*num_mods);
dr_global_free(iter_data.num_cbr_falls, sizeof(ptr_uint_t)*num_mods);
dr_global_free(iter_data.num_cbr_tgt_misses, sizeof(ptr_uint_t)*num_mods);
dr_global_free(iter_data.num_cbr_fall_misses, sizeof(ptr_uint_t)*num_mods);
}
#endif /* CBR_COVERAGE */
static bool
bb_table_entry_print(ptr_uint_t idx, void *entry, void *iter_data)
{
per_thread_t *data = iter_data;
bb_entry_t *bb_entry = (bb_entry_t *)entry;
dr_fprintf(data->log, "module[%3u]: "PFX", %3u",
bb_entry->mod_id, bb_entry->start, bb_entry->size);
#ifdef CBR_COVERAGE
dr_fprintf(data->log, ", "PFX", %2u, %3u",
bb_entry->cbr_tgt, bb_entry->trace ? 1:0, bb_entry->num_instrs);
#endif
dr_fprintf(data->log, "\n");
return true; /* continue iteration */
}
static void
bb_table_print(void *drcontext, per_thread_t *data)
{
ASSERT(data != NULL, "data must not be NULL");
if (data->log == INVALID_FILE) {
ASSERT(false, "invalid log file");
return;
}
dr_fprintf(data->log, "BB Table: %u bbs\n",
drtable_num_entries(data->bb_table));
if (options.dump_text) {
dr_fprintf(data->log, "module id, start, size");
#ifdef CBR_COVERAGE
dr_fprintf(data->log, ", cbr tgt, trace, #instr");
#endif
dr_fprintf(data->log, ":\n");
drtable_iterate(data->bb_table, data, bb_table_entry_print);
} else
drtable_dump_entries(data->bb_table, data->log);
}
static void
bb_table_entry_add(void *drcontext, per_thread_t *data, app_pc start,
#ifdef CBR_COVERAGE
app_pc cbr_tgt, ushort num_instrs, bool trace,
#endif
uint size)
{
bb_entry_t *bb_entry = drtable_alloc(data->bb_table, 1, NULL);
module_entry_t **mod_entry_cache = data != NULL ? data->cache : NULL;
module_entry_t *mod_entry = module_table_lookup(mod_entry_cache,
NUM_THREAD_MODULE_CACHE,
module_table, start);
/* we do not de-duplicate repeated bbs */
ASSERT(size < USHRT_MAX, "size overflow");
bb_entry->size = (ushort)size;
if (mod_entry != NULL && mod_entry->data != NULL) {
ASSERT(bb_entry->mod_id < USHRT_MAX, "module id overflow");
bb_entry->mod_id = (ushort)mod_entry->id;
ASSERT(start > mod_entry->data->start, "wrong module");
bb_entry->start = (uint)(start - mod_entry->data->start);
#ifdef CBR_COVERAGE
ASSERT(cbr_tgt == NULL || cbr_tgt > mod_entry->data->start,
"cbr target should be withing module");
bb_entry->cbr_tgt = (cbr_tgt == NULL) ?
0 : (uint)(cbr_tgt - mod_entry->data->start);
#endif
} else {
/* XXX: we just truncate the address, which may have wrong value
* in x64 arch. It should be ok now since it is an unknown module,
* which will be ignored in the post-processing.
* Should be handled for JIT code in the future.
*/
bb_entry->mod_id = UNKNOWN_MODULE_ID;
bb_entry->start = (uint)(ptr_uint_t)start;
#ifdef CBR_COVERAGE
bb_entry->cbr_tgt = (uint)(ptr_uint_t)cbr_tgt;
#endif
}
#ifdef CBR_COVERAGE
bb_entry->trace = trace;
bb_entry->num_instrs = num_instrs;
#endif
}
#define INIT_BB_TABLE_ENTRIES 4096
static void *
bb_table_create(bool synch)
{
return drtable_create(INIT_BB_TABLE_ENTRIES,
sizeof(bb_entry_t), 0 /* flags */, synch, NULL);
}
static void
bb_table_destroy(void *table, void *data)
{
drtable_destroy(table, data);
}
static void
version_print(file_t log)
{
if (log == INVALID_FILE) {
/* It is possible that failure on log file creation is caused by the
* running process not having enough privilege, so this is not a
* release-build fatal error
*/
ASSERT(false, "invalid log file");
return;
}
dr_fprintf(log, "DRCOV VERSION: %d\n", DRCOV_VERSION);
dr_fprintf(log, "DRCOV FLAVOR: %s\n", DRCOV_FLAVOR);
}
static void
dump_drcov_data(void *drcontext, per_thread_t *data)
{
if (options.dump_text || options.dump_binary) {
version_print(data->log);
module_table_print(module_table, data->log,
IF_CBR_COVERAGE_ELSE(true, false));
bb_table_print(drcontext, data);
}
# ifdef CBR_COVERAGE
if (options.check)
bb_table_check_cbr(module_table, data);
# endif
}
/****************************************************************************
* Thread/Global Data Creation/Destroy
*/
/* make a copy of global data for pre-thread cache */
static per_thread_t *
thread_data_copy(void *drcontext)
{
per_thread_t *data;
ASSERT(drcontext != NULL, "drcontext must not be NULL");
data = dr_thread_alloc(drcontext, sizeof(*data));
*data = *global_data;
return data;
}
static per_thread_t *
thread_data_create(void *drcontext)
{
per_thread_t *data;
if (drcontext == NULL) {
ASSERT(!drcov_per_thread, "drcov_per_thread should not be set");
data = dr_global_alloc(sizeof(*data));
} else {
ASSERT(drcov_per_thread, "drcov_per_thread should be set");
data = dr_thread_alloc(drcontext, sizeof(*data));
}
/* XXX: can we assume bb create event is serialized,
* if so, no lock is required for bb_table operation.
*/
data->bb_table = bb_table_create(drcontext == NULL ? true : false);
memset(data->cache, 0, sizeof(data->cache));
log_file_create(drcontext, data);
return data;
}
static void
thread_data_destroy(void *drcontext, per_thread_t *data)
{
/* destroy the bb table */
bb_table_destroy(data->bb_table, data);
dr_close_file(data->log);
/* free thread data */
if (drcontext == NULL) {
ASSERT(!drcov_per_thread, "drcov_per_thread should not be set");
dr_global_free(data, sizeof(*data));
} else {
ASSERT(drcov_per_thread, "drcov_per_thread is not set");
dr_thread_free(drcontext, data, sizeof(*data));
}
}
static void *
global_data_create(void)
{
return thread_data_create(NULL);
}
static void
global_data_destroy(per_thread_t *data)
{
thread_data_destroy(NULL, data);
}
/****************************************************************************
* Nudges
*/
enum {
NUDGE_TERMINATE_PROCESS = 1,
};
static void
event_nudge(void *drcontext, uint64 argument)
{
int nudge_arg = (int)argument;
int exit_arg = (int)(argument >> 32);
if (nudge_arg == NUDGE_TERMINATE_PROCESS) {
static int nudge_term_count;
/* handle multiple from both NtTerminateProcess and NtTerminateJobObject */
uint count = dr_atomic_add32_return_sum(&nudge_term_count, 1);
if (count == 1) {
dr_exit_process(exit_arg);
}
}
ASSERT(nudge_arg == NUDGE_TERMINATE_PROCESS, "unsupported nudge");
ASSERT(false, "should not reach"); /* should not reach */
}
static bool
event_soft_kill(process_id_t pid, int exit_code)
{
/* we pass [exit_code, NUDGE_TERMINATE_PROCESS] to target process */
dr_config_status_t res;
res = dr_nudge_client_ex(pid, client_id,
NUDGE_TERMINATE_PROCESS | (uint64)exit_code << 32,
0);
if (res == DR_SUCCESS) {
/* skip syscall since target will terminate itself */
return true;
}
/* else failed b/c target not under DR control or maybe some other
* error: let syscall go through
*/
return false;
}
/****************************************************************************
* Event Callbacks
*/
static bool
event_filter_syscall(void *drcontext, int sysnum)
{
#ifdef WINDOWS
return false;
#else
return sysnum == sysnum_execve;
#endif
}
static bool
event_pre_syscall(void *drcontext, int sysnum)
{
#ifdef UNIX
if (sysnum == sysnum_execve) {
/* for !drcov_per_thread, the per-thread data is a copy of global data */
per_thread_t *data = (per_thread_t *)drmgr_get_tls_field(drcontext, tls_idx);
ASSERT(data != NULL, "data must not be NULL");
if (!drcov_per_thread)
drcontext = NULL;
/* We only dump the data but do not free any memory.
* XXX: for drcov_per_thread, we only dump the current thread.
*/
dump_drcov_data(drcontext, data);
/* TODO: add execve test.
* i#1390-c#8: iterate over all the other threads using DR API and dump data.
* i#1390-c#9: update drcov2lcov to handle multiple dumps in the same file.
*/
}
#endif
return true;
}
/* We collect the basic block information including offset from module base,
* size, and num of instructions, and add it into a basic block table without
* instrumentation.
*/
static dr_emit_flags_t
event_basic_block_analysis(void *drcontext, void *tag, instrlist_t *bb,
bool for_trace, bool translating, OUT void **user_data)
{
per_thread_t *data;
instr_t *instr;
app_pc start_pc, end_pc;
#ifdef CBR_COVERAGE
ushort num_instrs = 0;
app_pc cbr_tgt = NULL;
#endif
/* do nothing for translation */
if (translating)
return DR_EMIT_DEFAULT;
data = (per_thread_t *)drmgr_get_tls_field(drcontext, tls_idx);
/* Collect the number of instructions and the basic block size,
* assuming the basic block does not have any elision on control
* transfer instructions, which is true for default options passed
* to DR but not for -opt_speed.
*/
start_pc = dr_fragment_app_pc(tag);
end_pc = start_pc; /* for finding the size */
for (instr = instrlist_first_app(bb);
instr != NULL;
instr = instr_get_next_app(instr)) {
app_pc pc = instr_get_app_pc(instr);
int len = instr_length(drcontext, instr);
/* -opt_speed (elision) is not supported */
ASSERT(pc != NULL && pc >= start_pc, "-opt_speed is not supported");
if (pc + len > end_pc)
end_pc = pc + len;
#ifdef CBR_COVERAGE
num_instrs++;
if (instr_opcode_valid(instr) && instr_is_cbr(instr))
cbr_tgt = opnd_get_pc(instr_get_target(instr));
#endif
}
/* We allow duplicated basic blocks for the following reasons:
* 1. Avoids handling issues like code cache consistency, e.g.,
* module load/unload, self-modifying code, etc.
* 2. Avoids the overhead on duplication check.
* 3. Stores more information on code cache events, e.g., trace building,
* repeated bb building, etc.
* 4. The duplication can be easily handled in a post-processing step,
* which is required anyway.
*/
bb_table_entry_add(drcontext, data, start_pc,
#ifdef CBR_COVERAGE
cbr_tgt, num_instrs, for_trace,
#endif
(uint)(end_pc - start_pc));
if (go_native)
return DR_EMIT_GO_NATIVE;
else
return DR_EMIT_DEFAULT;
}
static void
event_module_unload(void *drcontext, const module_data_t *info)
{
/* we do not delete the module entry but clean the cache only. */
module_table_unload(module_table, info);
}
static void
event_module_load(void *drcontext, const module_data_t *info, bool loaded)
{
module_table_load(module_table, info);
}
static void
event_thread_exit(void *drcontext)
{
per_thread_t *data;
data = (per_thread_t *)drmgr_get_tls_field(drcontext, tls_idx);
ASSERT(data != NULL, "data must not be NULL");
if (drcov_per_thread) {
dump_drcov_data(drcontext, data);
thread_data_destroy(drcontext, data);
} else {
/* the per-thread data is a copy of global data */
dr_thread_free(drcontext, data, sizeof(*data));
}
}
static void
event_thread_init(void *drcontext)
{
per_thread_t *data;
static volatile int thread_count;
if (options.native_until_thread > 0) {
int local_count = dr_atomic_add32_return_sum(&thread_count, 1);
NOTIFY(1, "@@@@@@@@@@@@@ new thread #%d "TIDFMT"\n",
local_count, dr_get_thread_id(drcontext));
if (go_native && local_count == options.native_until_thread) {
void **drcontexts = NULL;
uint num_threads, i;
go_native = false;
NOTIFY(1, "thread "TIDFMT" suspending all threads\n", dr_get_thread_id(drcontext));
if (dr_suspend_all_other_threads_ex(&drcontexts, &num_threads, NULL,
DR_SUSPEND_NATIVE)) {
NOTIFY(1, "suspended %d threads\n", num_threads);
for (i = 0; i < num_threads; i++) {
if (dr_is_thread_native(drcontexts[i])) {
NOTIFY(2, "\txxx taking over thread #%d "TIDFMT"\n",
i, dr_get_thread_id(drcontexts[i]));
dr_retakeover_suspended_native_thread(drcontexts[i]);
} else {
NOTIFY(2, "\tthread #%d "TIDFMT" under DR\n",
i, dr_get_thread_id(drcontexts[i]));
}
}
if (!dr_resume_all_other_threads(drcontexts, num_threads)) {
ASSERT(false, "failed to resume threads");
}
} else {
ASSERT(false, "failed to suspend threads");
}
}
}
/* allocate thread private data for per-thread cache */
if (drcov_per_thread)
data = thread_data_create(drcontext);
else
data = thread_data_copy(drcontext);
drmgr_set_tls_field(drcontext, tls_idx, data);
}
#ifndef WINDOWS
static void
event_fork(void *drcontext)
{
if (!drcov_per_thread) {
log_file_create(NULL, global_data);
} else {
per_thread_t *data = drmgr_get_tls_field(drcontext, tls_idx);
if (data != NULL) {
thread_data_destroy(drcontext, data);
}
event_thread_init(drcontext);
}
}
#endif
static void
event_exit(void)
{
if (!drcov_per_thread) {
dump_drcov_data(NULL, global_data);
global_data_destroy(global_data);
}
/* destroy module table */
module_table_destroy(module_table);
drmgr_unregister_tls_field(tls_idx);
drx_exit();
drmgr_exit();
}
static void
event_init(void)
{
#ifdef DEBUG
uint64 max_elide_jmp = 0;
uint64 max_elide_call = 0;
/* assuming no elision */
ASSERT(dr_get_integer_option("max_elide_jmp", &max_elide_jmp) &&
dr_get_integer_option("max_elide_call", &max_elide_jmp) &&
max_elide_jmp == 0 && max_elide_call == 0,
"elision is not supported");
#endif
/* create module table */
module_table = module_table_create();
/* create process data if whole process bb coverage. */
if (!drcov_per_thread)
global_data = global_data_create();
}
static void
options_init(client_id_t id)
{
const char *opstr = dr_get_options(id);
const char *s;
char token[OPTION_MAX_LENGTH];
/* default values */
options.nudge_kills = true;
dr_snprintf(options.logdir, BUFFER_SIZE_ELEMENTS(options.logdir), ".");
for (s = dr_get_token(opstr, token, BUFFER_SIZE_ELEMENTS(token));
s != NULL;
s = dr_get_token(s, token, BUFFER_SIZE_ELEMENTS(token))) {
if (strcmp(token, "-dump_text") == 0)
options.dump_text = true;
else if (strcmp(token, "-dump_binary") == 0)
options.dump_binary = true;
else if (strcmp(token, "-no_nudge_kills") == 0)
options.nudge_kills = false;
else if (strcmp(token, "-nudge_kills") == 0)
options.nudge_kills = true;
else if (strcmp(token, "-logdir") == 0) {
s = dr_get_token(s, options.logdir,
BUFFER_SIZE_ELEMENTS(options.logdir));
USAGE_CHECK(s != NULL, "missing logdir path");
}
else if (strcmp(token, "-native_until_thread") == 0) {
s = dr_get_token(s, token, BUFFER_SIZE_ELEMENTS(token));
USAGE_CHECK(s != NULL, "missing -native_until_thread number");
if (s != NULL) {
int res = dr_sscanf(token, "%d", &options.native_until_thread);
if (res == 1 && options.native_until_thread > 0)
go_native = true;
else {
options.native_until_thread = 0;
USAGE_CHECK(false, "invalid -native_until_thread number");
}
}
}
else if (strcmp(token, "-verbose") == 0) {
s = dr_get_token(s, token, BUFFER_SIZE_ELEMENTS(token));
USAGE_CHECK(s != NULL, "missing -verbose number");
if (s != NULL) {
int res = dr_sscanf(token, "%u", &verbose);
USAGE_CHECK(res == 1, "invalid -verbose number");
}
}
#ifdef CBR_COVERAGE
else if (strcmp(token, "-check_cbr") == 0) {
options.check = true;
}
else if (strcmp(token, "-summary_only") == 0) {
USAGE_CHECK(options.check, "check_cbr is not set");
options.summary = true;
}
#endif
else {
NOTIFY(0, "UNRECOGNIZED OPTION: \"%s\"\n", token);
USAGE_CHECK(false, "invalid option");
}
}
/* If both or neither specified, we honor the binary. */
if ((options.dump_text && options.dump_binary) ||
(!options.dump_text && !options.dump_binary)) {
options.dump_text = false;
options.dump_binary = true;
}
}
DR_EXPORT void
dr_init(client_id_t id)
{
dr_set_client_name("DrCov", "http://dynamorio.org/issues");
drmgr_init();
drx_init();
dr_register_exit_event(event_exit);
drmgr_register_thread_init_event(event_thread_init);
drmgr_register_thread_exit_event(event_thread_exit);
drmgr_register_bb_instrumentation_event(event_basic_block_analysis, NULL, NULL);
drmgr_register_module_load_event(event_module_load);
drmgr_register_module_unload_event(event_module_unload);
dr_register_filter_syscall_event(event_filter_syscall);
drmgr_register_pre_syscall_event(event_pre_syscall);
dr_register_nudge_event(event_nudge, id);
#ifdef UNIX
dr_register_fork_init_event(event_fork);
#endif
tls_idx = drmgr_register_tls_field();
ASSERT(tls_idx > -1, "unable to reserve TLS slot");
client_id = id;
if (dr_using_all_private_caches())
drcov_per_thread = true;
options_init(id);
if (options.nudge_kills)
drx_register_soft_kills(event_soft_kill);
event_init();
}