Skip to content

Instantly share code, notes, and snippets.

@Clcanny
Last active July 28, 2017 02:47
Show Gist options
  • Save Clcanny/eed2e68d454e7850a1a26ddfc278ae84 to your computer and use it in GitHub Desktop.
Save Clcanny/eed2e68d454e7850a1a26ddfc278ae84 to your computer and use it in GitHub Desktop.
DynamoRIO
/* **********************************************************
* Copyright (c) 2013-2014 Google, Inc. All rights reserved.
* **********************************************************/
/* Dr. Memory: the memory debugger
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License, and no later version.
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
/* strace: system call tracing tool based on the Dr. Syscall Extension.
*
* XXX: add more features, such as:
* + named constants for flags
* + callstacks
* + timestamps
*
* XXX i#1497: port to Linux
* XXX i#1498: port to MacOS
*/
#include "dr_api.h"
#include "drmgr.h"
#include "drx.h"
#include "drsyscall.h"
#include "drstrace_named_consts.h"
#include "utils.h"
#include "dr_tools.h"
#include "drwrap.h"
#include "drcovlib.h"
#include <string.h>
//#ifdef WINDOWS
# include "windefs.h"
# include <windows.h>
# include <Windows.h>
//#endif
static int tcls_idx;
extern size_t get_const_arrays_num(void);
/* Where to write the trace */
static file_t outf;
#ifndef DRSTRACE_UNIT_TESTS
static
#endif
hashtable_t nconsts_table;
/* We buffer the output via a stack-allocated buffer. We flush prior to
* each system call.
*/
#define OUTBUF_SIZE 2048
#define TYPE_OUTPUT_SIZE 2048
#define HASHTABLE_BITSIZE 10 /* 512 < entries # < 1024 */
typedef struct _buf_info_t {
char buf[OUTBUF_SIZE];
size_t sofar;
ssize_t len;
} buf_info_t;
#define OUTPUT(buf_info, fmt, ...) \
BUFFERED_WRITE(outf, (buf_info)->buf, BUFFER_SIZE_ELEMENTS((buf_info)->buf), \
(buf_info)->sofar, (buf_info)->len, fmt, ##__VA_ARGS__)
static uint verbose = 1;
#define ALERT(level, fmt, ...) do { \
if (verbose >= (level)) \
dr_fprintf(STDERR, fmt, ##__VA_ARGS__); \
} while (0)
/* Checks for both debug and release builds: */
#define USAGE_CHECK(x, msg) DR_ASSERT_MSG(x, msg)
#undef ASSERT /* we don't want msgbox */
#define ASSERT(cond, msg) \
((void)((!(cond)) ? \
(dr_fprintf(STDERR, "ASSERT FAILURE: %s:%d: %s (%s)", \
__FILE__, __LINE__, #cond, msg), \
dr_abort(), 0) : 0))
#define OPTION_MAX_LENGTH MAXIMUM_PATH
typedef struct _drstrace_options_t {
char logdir[MAXIMUM_PATH];
char sympath[MAXIMUM_PATH];
} drstrace_options_t;
static drstrace_options_t options;
/* Avoid exe exports, as on Linux many apps have a ton of global symbols. */
//static app_pc exe_start;
static void
print_unicode_string(buf_info_t *buf, UNICODE_STRING *us)
{
if (us == NULL)
OUTPUT(buf, "<null>");
else {
OUTPUT(buf, "%d/%d \"%.*S\"", us->Length, us->MaximumLength,
us->Length/sizeof(wchar_t),
(us->Buffer == NULL) ? L"<null>" : us->Buffer);
}
}
void
print_simple_value(buf_info_t *buf, drsys_arg_t *arg, bool leading_zeroes)
{
bool pointer = !TEST(DRSYS_PARAM_INLINED, arg->mode);
OUTPUT(buf, pointer ? PFX : (leading_zeroes ? PFX : PIFX), arg->value);
if (pointer && ((arg->pre && TEST(DRSYS_PARAM_IN, arg->mode)) ||
(!arg->pre && TEST(DRSYS_PARAM_OUT, arg->mode)))) {
ptr_uint_t deref = 0;
ASSERT(arg->size <= sizeof(deref), "too-big simple type");
/* We assume little-endian */
if (dr_safe_read((void *)arg->value, arg->size, &deref, NULL))
OUTPUT(buf, (leading_zeroes ? " => "PFX : " => "PIFX), deref);
}
}
static bool
drstrace_print_enum_const_name(buf_info_t *buf, drsys_arg_t *arg)
{
/* The routine returns false when can't
* find symbolic name in the hashtable.
*/
int iterator = 0;
const_values_t *named_consts;
const_values_t *named_consts_save;
bool has_out = false;
/* Trying to find enum_name in the hashtable */
named_consts = (const_values_t *)
hashtable_lookup(&nconsts_table, (void *) arg->enum_name);
if (named_consts == NULL) {
OUTPUT(buf, PIFX, arg->value);
return false;
}
/* There are a lot of named constants with incremental values
* (e.g. REG_NONE 0, REG_SZ 1, REG_EXPAND_SZ 2, REG_BINARY 3).
* So, firstly, we're trying to determine such cases.
*/
named_consts_save = named_consts;
while (named_consts_save->const_name != NULL) {
if (arg->value == named_consts_save->value) {
if (has_out)
OUTPUT(buf, " or ");
OUTPUT(buf, "%s", named_consts_save->const_name);
has_out = true;
}
named_consts_save++;
}
if (has_out)
return true;
/* If not, we perform linear search for composite named constants
* (e.g. FILE_SHARE_READ | FILE_SHARE_WRITE). We're using linear
* search instead of random access b/c current table entries may
* contain the same values for different named constants as well as
* combination values, which make it difficult, such as:
* ...
* {0x00800000, "FILE_OPEN_FOR_FREE_SPACE_QUERY"},
* {0x00ffffff, "FILE_VALID_OPTION_FLAGS"},
* ...
*/
has_out = false;
while (named_consts->const_name != NULL) {
if (TESTALL(named_consts->value, arg->value)) {
if (has_out)
OUTPUT(buf, "|");
/* FIXME i#1550: We don't perform additional search to
* include entries with the same values in the output.
* Ideally the tables shouldn't contain such entries.
*/
OUTPUT(buf, "%s", named_consts->const_name);
has_out = true;
}
named_consts++;
}
if (!has_out) {
OUTPUT(buf, PIFX, arg->value);
return false;
}
return true;
}
/* NOTE: the routine returns up to 64 bit memory values */
static int64
safe_read_field(buf_info_t *buf, void *addr_to_resolve, size_t addr_size,
bool print_value)
{
int64 mem_value = 0;
ASSERT(addr_size <= sizeof(mem_value), "too-big mem value to read");
if (!dr_safe_read(addr_to_resolve, addr_size, &mem_value, NULL)) {
OUTPUT(buf, "<field unreadable>");
return 0;
}
if (print_value)
OUTPUT(buf, "0x"HEX64_FORMAT_STRING, mem_value);
return mem_value;
}
static bool
print_known_compound_type(buf_info_t *buf, drsys_param_type_t type, void *start_addr)
{
switch (type) {
case DRSYS_TYPE_UNICODE_STRING: {
print_unicode_string(buf, (UNICODE_STRING *) start_addr);
break;
}
case DRSYS_TYPE_OBJECT_ATTRIBUTES: {
OBJECT_ATTRIBUTES *oa = (OBJECT_ATTRIBUTES *) start_addr;
OUTPUT(buf, "len="PIFX", root="PIFX", name=",
oa->Length, oa->RootDirectory);
print_unicode_string(buf, oa->ObjectName);
OUTPUT(buf, ", att="PIFX", sd="PFX", sqos="PFX,
oa->Attributes, oa->SecurityDescriptor,
oa->SecurityQualityOfService);
break;
}
case DRSYS_TYPE_IO_STATUS_BLOCK: {
IO_STATUS_BLOCK *io = (IO_STATUS_BLOCK *) start_addr;
OUTPUT(buf, "status="PIFX", info="PIFX"", io->StatusPointer.Status,
io->Information);
break;
}
case DRSYS_TYPE_LARGE_INTEGER: {
LARGE_INTEGER *li = (LARGE_INTEGER *) start_addr;
OUTPUT(buf, "0x"HEX64_FORMAT_STRING, li->QuadPart);
break;
}
default: {
/* FIXME i#1089: add the other types */
return false;
}
}
/* XXX: we want KEY_VALUE_PARTIAL_INFORMATION, etc. like in
* syscall_diagnostics. Add drsyscall types for those, or hardcode here?
*/
return true;
}
static bool
identify_known_compound_type(buf_info_t *buf, char *name, void *start_addr)
{
/* XXX i#1607 There are two reasons why we're trying to determine types
* by name here. Firstly, we can't simply parse types with unions in the
* print_structure since this routine increases memory address by field
* size after each field which we don't want to do with unions. We make
* temporarly solution here *only* for LARGE_INTEGER. So we're still need
* to resolve union problem.
* The second one is that we want extra information (e.g. field names)
* for already known common structures.
*/
drsys_param_type_t type = DRSYS_TYPE_UNKNOWN;
if (strcmp(name, "_LARGE_INTEGER") == 0) {
type = DRSYS_TYPE_LARGE_INTEGER;
} else if (strcmp(name, "_UNICODE_STRING") == 0) {
type = DRSYS_TYPE_UNICODE_STRING;
} else if (strcmp(name, "_OBJECT_ATTRIBUTES") == 0) {
type = DRSYS_TYPE_OBJECT_ATTRIBUTES;
} else if (strcmp(name, "_IO_STATUS_BLOCK") == 0) {
type = DRSYS_TYPE_IO_STATUS_BLOCK;
} else {
return false;
}
return print_known_compound_type(buf, type, start_addr);
}
static uint
get_total_size_of_fields(drsym_compound_type_t *compound_type)
{
int i;
uint total_size = 0;
for (i = 0; i < compound_type->num_fields; i++)
total_size += compound_type->field_types[i]->size;
return total_size;
}
static void
print_structure(buf_info_t *buf, drsym_type_t *type, drsys_arg_t *arg, void *addr)
{
int i;
bool type_union = false;
if (type->kind == DRSYM_TYPE_COMPOUND) {
drsym_compound_type_t *compound_type =
(drsym_compound_type_t *)type;
OUTPUT(buf, "%s {", compound_type->name);
if (identify_known_compound_type(buf, compound_type->name, addr)) {
OUTPUT(buf, "}");
return;
}
/* i#1607: We need to print properly parent structures when they are
* actually unions (e.g. LARGE_INTEGER).
*/
if (get_total_size_of_fields(compound_type) > compound_type->type.size)
type_union = true;
for (i = 0; i < compound_type->num_fields; i++) {
print_structure(buf, compound_type->field_types[i], arg, addr);
if (!type_union)
addr = (char *)addr + compound_type->field_types[i]->size;
/* we don't want comma after last field */
if (i+1 != compound_type->num_fields)
OUTPUT(buf, ", ");
}
OUTPUT(buf, "}");
} else {
/* Print type fields */
if (type->kind == DRSYM_TYPE_VOID) {
OUTPUT(buf, "void=");
safe_read_field(buf, addr, type->size, true);
return;
} else if (type->kind == DRSYM_TYPE_PTR) {
drsym_ptr_type_t *ptr_type = (drsym_ptr_type_t *)type;
/* We're expecting an address here. So we truncate int64 to void* */
void *mem_value = (void *)safe_read_field(buf, addr, ptr_type->type.size,
false);
print_structure(buf, ptr_type->elt_type, arg, mem_value);
OUTPUT(buf, "*");
return;
} else if (type->kind == DRSYM_TYPE_ARRAY) {
OUTPUT(buf, "array(%d)={", type->size);
/* only print up to the first 4 bytes of the array */
safe_read_field(buf, addr, 0x1, true);
for (i = 1; i < type->size && i < 4; i++) {
OUTPUT(buf, ", ");
safe_read_field(buf, (byte *)addr + i, 0x1, true);
}
if (i < type->size)
OUTPUT(buf, ", ...");
OUTPUT(buf, "}");
return;
} else {
/* Print integer base types */
switch (type->size) {
case 1:
OUTPUT(buf, "byte|bool=");
break;
case 2:
OUTPUT(buf,"short=");
break;
case 4:
OUTPUT(buf, "int=");
break;
case 8:
OUTPUT(buf, "long long=");
break;
default:
OUTPUT(buf, "unknown type=");
break;
}
safe_read_field(buf, addr, type->size, true);
return;
}
}
return;
}
static bool
type_has_unknown_components(drsym_type_t *type)
{
int i;
if (type->kind == DRSYM_TYPE_COMPOUND) {
drsym_compound_type_t *compound_type = (drsym_compound_type_t *)type;
drsym_type_t **field_types = compound_type->field_types;
for (i = 0; i < compound_type->num_fields; i++) {
if (field_types[i]->kind == DRSYM_TYPE_PTR) {
drsym_ptr_type_t *ptr_type = (drsym_ptr_type_t *)field_types[i];
if (ptr_type->elt_type->size == 0)
return false;
}
/* recursively check type fields */
if (!type_has_unknown_components(field_types[i]))
return false;
}
} else if (type->size == 0) {
return false;
}
return true;
}
static bool
drstrace_print_info_class_struct(buf_info_t *buf, drsys_arg_t *arg)
{
char buf_tmp[TYPE_OUTPUT_SIZE];
drsym_type_t *type;
drsym_type_t *expand_type;
drsym_error_t r;
r = drsym_get_type_by_name(options.sympath, arg->enum_name,
buf_tmp, BUFFER_SIZE_BYTES(buf_tmp),
&type);
if (r != DRSYM_SUCCESS) {
NOTIFY("Value to symbol %s lookup failed", arg->enum_name);
return false;
}
r = drsym_expand_type(options.sympath, type->id, UINT_MAX,
buf_tmp, BUFFER_SIZE_BYTES(buf_tmp),
&expand_type);
if (r != DRSYM_SUCCESS) {
NOTIFY("%s structure expanding failed", arg->enum_name);
return false;
}
if (!type_has_unknown_components(expand_type)) {
NOTIFY("%s structure has unknown types", arg->enum_name);
return false;
}
if (arg->valid && !arg->pre) {
if (arg->value64 == 0) {
OUTPUT(buf, "NULL");
/* We return true since we already printed for this value */
return true;
}
/* We're expecting an address here. So we truncate int64 to void*. */
print_structure(buf, expand_type, arg, (void *)arg->value64);
} else {
return false;
}
return true;
}
static bool
drstrace_get_arg_symname(buf_info_t *buf, drsys_arg_t *arg)
{
if (arg->type >= DRSYS_TYPE_STRUCT) {
if (drstrace_print_info_class_struct(buf, arg)) {
OUTPUT(buf, " (type=<struct>*, size="PIFX")\n",
arg->size);
return true;
} else {
return false;
}
} else if (arg->enum_name != NULL) {
if (drstrace_print_enum_const_name(buf, arg)) {
OUTPUT(buf, " (type=named constant, value="PIFX", size="PIFX")\n",
arg->value,
arg->size);
} else {
OUTPUT(buf, " (type=named constant, size="PIFX")\n",
arg->size);
}
return true;
}
return false;
}
static void
print_arg(buf_info_t *buf, drsys_arg_t *arg)
{
if (arg->ordinal == -1)
OUTPUT(buf, "\tretval: ");
else
OUTPUT(buf, "\targ %d: ", arg->ordinal);
if (arg->enum_name != NULL) {
if (drstrace_get_arg_symname(buf, arg))
return;
}
/* XXX: add return value to dr_fprintf so we can more easily align
* after PFX vs PIFX w/o having to print to buffer
*/
switch (arg->type) {
case DRSYS_TYPE_VOID: print_simple_value(buf, arg, true); break;
case DRSYS_TYPE_POINTER: print_simple_value(buf, arg, true); break;
case DRSYS_TYPE_BOOL: print_simple_value(buf, arg, false); break;
case DRSYS_TYPE_INT: print_simple_value(buf, arg, false); break;
case DRSYS_TYPE_SIGNED_INT: print_simple_value(buf, arg, false); break;
case DRSYS_TYPE_UNSIGNED_INT: print_simple_value(buf, arg, false); break;
case DRSYS_TYPE_HANDLE: print_simple_value(buf, arg, false); break;
case DRSYS_TYPE_NTSTATUS: print_simple_value(buf, arg, false); break;
case DRSYS_TYPE_ATOM: print_simple_value(buf, arg, false); break;
default: {
if (arg->value == 0) {
OUTPUT(buf, "<null>");
} else if (arg->pre && !TEST(DRSYS_PARAM_IN, arg->mode)) {
OUTPUT(buf, PFX, arg->value);
} else {
if (!print_known_compound_type(buf, arg->type, (void *) arg->value))
OUTPUT(buf, "<NYI>");
}
}
}
OUTPUT(buf, " (%s%s%stype=%s%s, size="PIFX")\n",
(arg->arg_name == NULL) ? "" : "name=",
(arg->arg_name == NULL) ? "" : arg->arg_name,
(arg->arg_name == NULL) ? "" : ", ",
(arg->type_name == NULL) ? "\"\"" : arg->type_name,
(arg->type_name == NULL ||
TESTANY(DRSYS_PARAM_INLINED|DRSYS_PARAM_RETVAL, arg->mode)) ? "" : "*",
arg->size);
}
static bool
drsys_iter_arg_cb(drsys_arg_t *arg, void *user_data)
{
buf_info_t *buf = (buf_info_t *) user_data;
ASSERT(arg->valid, "no args should be invalid");
if ((arg->pre && !TEST(DRSYS_PARAM_RETVAL, arg->mode)) ||
(!arg->pre && TESTANY(DRSYS_PARAM_OUT|DRSYS_PARAM_RETVAL, arg->mode)))
{
if (arg->ordinal == 0)
{
ptr_uint_t return_address = 0;
dr_safe_read((void *)((char *)arg->start_addr - 4), 4, &return_address, NULL);
OUTPUT(buf, "return address is "PFX"\n", return_address);
}
print_arg(buf, arg);
}
return true; /* keep going */
}
static bool
event_pre_syscall(void *drcontext, int sysnum)
{
drsys_syscall_t *syscall;
bool known;
drsys_param_type_t ret_type;
const char *name;
drmf_status_t res;
LARGE_INTEGER startTime;
LARGE_INTEGER *data;
buf_info_t buf;
buf.sofar = 0;
if (drsys_cur_syscall(drcontext, &syscall) != DRMF_SUCCESS)
ASSERT(false, "drsys_cur_syscall failed");
if (drsys_syscall_name(syscall, &name) != DRMF_SUCCESS)
ASSERT(false, "drsys_syscall_name failed");
if (drsys_syscall_is_known(syscall, &known) != DRMF_SUCCESS)
ASSERT(false, "failed to find whether known");
OUTPUT(&buf, "%s%s\n", name, known ? "" : " (details not all known)");
//data = (LARGE_INTEGER *)drmgr_get_cls_field(drcontext, tcls_idx);
//QueryPerformanceCounter(data);
QueryPerformanceCounter(&startTime);
OUTPUT(&buf, "start at %016I64x ticks.\n", startTime);
//OUTPUT(&buf, "start at %lu millseconds.\n", dr_get_microseconds());
res = drsys_iterate_args(drcontext, drsys_iter_arg_cb, &buf);
if (res != DRMF_SUCCESS && res != DRMF_ERROR_DETAILS_UNKNOWN)
ASSERT(false, "drsys_iterate_args failed pre-syscall");
/* Flush prior to potentially waiting in the kernel */
FLUSH_BUFFER(outf, buf.buf, buf.sofar);
return true;
}
static void
event_post_syscall(void *drcontext, int sysnum)
{
drsys_syscall_t *syscall;
bool success = false;
uint errno;
drmf_status_t res;
LARGE_INTEGER endTime;
LARGE_INTEGER ntime;
LARGE_INTEGER *data;
double time;
buf_info_t buf;
buf.sofar = 0;
if (drsys_cur_syscall(drcontext, &syscall) != DRMF_SUCCESS)
ASSERT(false, "drsys_cur_syscall failed");
if (drsys_cur_syscall_result(drcontext, &success, NULL, &errno) != DRMF_SUCCESS)
ASSERT(false, "drsys_cur_syscall_result failed");
if (success)
OUTPUT(&buf, " succeeded =>\n");
else
OUTPUT(&buf, " failed (error="IF_WINDOWS_ELSE(PIFX, "%d")") =>\n", errno);
res = drsys_iterate_args(drcontext, drsys_iter_arg_cb, &buf);
if (res != DRMF_SUCCESS && res != DRMF_ERROR_DETAILS_UNKNOWN)
ASSERT(false, "drsys_iterate_args failed post-syscall");
//OUTPUT(&buf, "end at %lu millseconds.\n", dr_get_microseconds());
//data = (LARGE_INTEGER *)drmgr_get_cls_field(drcontext, tcls_idx);
QueryPerformanceCounter(&endTime);
QueryPerformanceFrequency(&ntime);
//time = (double)(endTime.QuadPart - data->QuadPart) / (double)ntime.QuadPart * 1000;
//OUTPUT(&buf, "spent %f millseconds.\n", time);
OUTPUT(&buf, "end at %016I64x ticks.\n", endTime);
FLUSH_BUFFER(outf, buf.buf, buf.sofar);
}
static bool
event_filter_syscall(void *drcontext, int sysnum)
{
return true; /* intercept everything */
}
static void
open_log_file(void)
{
char buf[MAXIMUM_PATH];
if (strcmp(options.logdir, "-") == 0)
outf = STDERR;
else {
outf = drx_open_unique_appid_file(options.logdir, dr_get_process_id(),
"drstrace", "log",
#ifndef WINDOWS
DR_FILE_CLOSE_ON_FORK |
#endif
DR_FILE_ALLOW_LARGE,
buf, BUFFER_SIZE_ELEMENTS(buf));
ASSERT(outf != INVALID_FILE, "failed to open log file");
ALERT(1, "<drstrace log file is %s>\n", buf);
}
}
#ifndef WINDOWS
static void
event_fork(void *drcontext)
{
/* The old file was closed by DR b/c we passed DR_FILE_CLOSE_ON_FORK */
open_log_file();
}
#endif
static
void exit_event(void)
{
if (outf != STDERR)
dr_close_file(outf);
if (drsys_exit() != DRMF_SUCCESS)
ASSERT(false, "drsys failed to exit");
drsym_exit();
drx_exit();
drmgr_exit();
hashtable_delete(&nconsts_table);
}
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 */
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, "-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, "-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");
}
} else if (strcmp(token, "-symcache_path") == 0) {
s = dr_get_token(s, options.sympath,
BUFFER_SIZE_ELEMENTS(options.sympath));
USAGE_CHECK(s != NULL, "missing symcache dir path");
ALERT(2, "<drstrace symbol source is %s>\n", options.sympath);
} else {
ALERT(0, "UNRECOGNIZED OPTION: \"%s\"\n", token);
USAGE_CHECK(false, "invalid option");
}
}
}
/********************************************************************
* code come from drltrace
* print return address
*/
static void
lib_entry(void *wrapcxt, INOUT void **user_data)
{
const char *name = (const char *) *user_data;
const char *modname = NULL;
app_pc func = drwrap_get_func(wrapcxt);
module_data_t *mod;
thread_id_t tid;
uint mod_id;
app_pc mod_start, ret_addr;
drcovlib_status_t res;
void *drcontext = drwrap_get_drcontext(wrapcxt);
/* XXX: it may be better to heap-allocate the "module!func" string and
* pass in, to avoid this lookup.
*/
mod = dr_lookup_module(func);
if (mod != NULL)
modname = dr_module_preferred_name(mod);
tid = dr_get_thread_id(drcontext);
if (tid != INVALID_THREAD_ID)
dr_fprintf(outf, "~~%d~~ ", tid);
else
dr_fprintf(outf, "~~Dr.L~~ ");
dr_fprintf(outf, "%s%s%s", modname == NULL ? "" : modname,
modname == NULL ? "" : "!", name);
/* XXX: We employ three schemes of arguments printing. drsyscall is used
* to get a symbolic representation of arguments for known library calls.
* For the rest of library calls we are looking for prototypes in config file
* specified by user. If there is no info in both sources we employ type-blind
* printing and use -num_unknown_args to get a count of arguments to print.
*/
//print_symbolic_args(name, wrapcxt, func);
ret_addr = drwrap_get_retaddr(wrapcxt);
res = drmodtrack_lookup(drcontext, ret_addr, &mod_id, &mod_start);
if (res == DRCOVLIB_SUCCESS) {
dr_fprintf(outf, "module name is: %s.\n", modname);
dr_fprintf(outf,
" and return to module id:%d, offset:" PIFX,
mod_id, ret_addr - mod_start);
dr_fprintf(outf, "\n");
dr_fprintf(outf,
" and return to(absolute address) " PIFX,
ret_addr);
}
dr_fprintf(outf, "\n");
if (mod != NULL)
dr_free_module_data(mod);
}
static void
iterate_exports(const module_data_t *info, bool add)
{
dr_symbol_export_iterator_t *exp_iter =
dr_symbol_export_iterator_start(info->handle);
while (dr_symbol_export_iterator_hasnext(exp_iter)) {
dr_symbol_export_t *sym = dr_symbol_export_iterator_next(exp_iter);
app_pc func = NULL;
if (sym->is_code)
func = sym->addr;
#ifdef LINUX
else if (sym->is_indirect_code) {
/* Invoke the export to get the real entry: */
app_pc (*indir)(void) = (app_pc (*)(void)) cast_to_func(sym->addr);
void *drcontext = dr_get_current_drcontext();
DR_TRY_EXCEPT(drcontext, {
func = (*indir)();
}, { /* EXCEPT */
func = NULL;
});
VNOTIFY(2, "export %s indirected from " PFX " to " PFX NL,
sym->name, sym->addr, func);
}
#endif
if (func != NULL) {
if (add) {
//IF_DEBUG(bool ok =)
drwrap_wrap_ex(func, lib_entry, NULL, (void *) sym->name, 0);
//ASSERT(ok, "wrap request failed");
//VNOTIFY(2, "wrapping export %s!%s @" PFX NL,
// dr_module_preferred_name(info), sym->name, func);
} else {
//IF_DEBUG(bool ok =)
drwrap_unwrap(func, lib_entry, NULL);
//ASSERT(ok, "unwrap request failed");
}
}
}
dr_symbol_export_iterator_stop(exp_iter);
}
static bool
library_matches_filter(const module_data_t *info)
{
// if (!op_only_to_lib.get_value().empty()) {
// const char *libname = dr_module_preferred_name(info);
//#ifdef WINDOWS
// return (libname != NULL && strcasestr(libname,
// op_only_to_lib.get_value().c_str()) != NULL);
//#else
// return (libname != NULL && strstr(libname,
// op_only_to_lib.get_value().c_str()) != NULL);
//#endif
// }
return true;
}
static void
event_module_load(void *drcontext, const module_data_t *info, bool loaded)
{
//if (info->start != exe_start && library_matches_filter(info))
iterate_exports(info, true/*add*/);
}
static void
event_module_unload(void *drcontext, const module_data_t *info)
{
//if (info->start != exe_start && library_matches_filter(info))
iterate_exports(info, false/*remove*/);
}
/****************************************************************
* code coming from drltrace.cpp
* end
*/
static void
event_thread_context_init(void *drcontext, bool new_depth)
{
LARGE_INTEGER *data = NULL;
if (new_depth) {
//data = (LARGE_INTEGER *)dr_thread_alloc(drcontext, sizeof(LARGE_INTEGER));
//data = (LARGE_INTEGER *)malloc(sizeof(LARGE_INTEGER));
drmgr_set_cls_field(drcontext, tcls_idx, data);
}
else
data = (LARGE_INTEGER *)drmgr_get_cls_field(drcontext, tcls_idx);
//memset(data, 0, sizeof(*data));
}
static void
event_thread_context_exit(void *drcontext, bool thread_exit)
{
if (thread_exit) {
LARGE_INTEGER *data = (LARGE_INTEGER *)drmgr_get_cls_field(drcontext, tcls_idx);
//dr_thread_free(drcontext, data, sizeof(*data));
//free(data);
}
/* else, nothing to do: we leave the struct for re-use on next context */
}
DR_EXPORT
void dr_init(client_id_t id)
{
uint i = 0;
uint const_arrays_num;
drsys_options_t ops = { sizeof(ops), 0, };
dr_set_client_name("Dr. STrace", "http://drmemory.org/issues");
#ifdef WINDOWS
dr_enable_console_printing();
#endif
options_init(id);
drsym_init(0);
drmgr_init();
drx_init();
/* for drltrace.cpp */
//module_data_t *exe;
drwrap_init();
drmodtrack_init();
//exe = dr_get_main_module();
// if (exe != NULL)
// exe_start = exe->start;
// dr_free_module_data(exe);
/* No-frills is safe b/c we're the only module doing wrapping, and
* we're only wrapping at module load and unwrapping at unload.
* Fast cleancalls is safe b/c we're only wrapping func entry and
* we don't care about the app context.
*/
drwrap_set_global_flags((drwrap_global_flags_t)
(DRWRAP_NO_FRILLS | DRWRAP_FAST_CLEANCALLS));
//dr_register_exit_event(event_exit);
#ifdef UNIX
dr_register_fork_init_event(event_fork);
#endif
drmgr_register_module_load_event(event_module_load);
drmgr_register_module_unload_event(event_module_unload);
tcls_idx = drmgr_register_cls_field(event_thread_context_init,
event_thread_context_exit);
DR_ASSERT(tcls_idx != -1);
#ifdef WINDOWS
dr_enable_console_printing();
#endif
/* for drltrace.cpp */
if (drsys_init(id, &ops) != DRMF_SUCCESS)
{
// ASSERT(false, "drsys failed to init");
}
dr_register_exit_event(exit_event);
dr_register_filter_syscall_event(event_filter_syscall);
drmgr_register_pre_syscall_event(event_pre_syscall);
drmgr_register_post_syscall_event(event_post_syscall);
if (drsys_filter_all_syscalls() != DRMF_SUCCESS)
ASSERT(false, "drsys_filter_all_syscalls should never fail");
open_log_file();
const_arrays_num = get_const_arrays_num();
hashtable_init(&nconsts_table, HASHTABLE_BITSIZE, HASH_STRING, false);
while (i < const_arrays_num) {
const_values_t *named_consts = const_struct_array[i];
bool res = hashtable_add(&nconsts_table,
(void *) named_consts[0].const_name,
(void *) named_consts);
if (!res)
ASSERT(false, "drstrace failed to add to hashtable");
i++;
}
}
/****************************************************************************
* Unit tests group of functions
*/
#ifdef DRSTRACE_UNIT_TESTS
bool
drstrace_unit_test_syscall_exit()
{
if (drsym_exit() != DRSYM_SUCCESS)
return false;
hashtable_delete(&nconsts_table);
return true;
}
bool
drstrace_unit_test_syscall_init()
{
uint const_arrays_num;
uint i = 0;
dr_standalone_init();
if (drsym_init(0) != DRSYM_SUCCESS)
return false;
const_arrays_num = get_const_arrays_num();
hashtable_init(&nconsts_table, HASHTABLE_BITSIZE, HASH_STRING, false);
while (i < const_arrays_num) {
const_values_t *named_consts = const_struct_array[i];
bool res = hashtable_add(&nconsts_table,
(void *) named_consts[0].const_name,
(void *) named_consts);
if (!res)
return false;
i++;
}
return true;
}
void
drstrace_set_symbol_path(const char *pdb_dir) {
_snprintf(options.sympath, BUFFER_SIZE_ELEMENTS(options.sympath), "%s", pdb_dir);
}
void
drstrace_unit_test_syscall_arg_iteration(drsys_arg_t arg, void *user_data)
{
drsys_iter_arg_cb(&arg, user_data);
return;
}
#endif /* DRSTRACE_UNIT_TESTS */
/***************************************************************************/
/* ***************************************************************************
* Copyright (c) 2013-2017 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.
*/
/* Library Tracing Tool: drltrace
*
* Records calls to exported library routines.
*
* The runtime options for this client are specified in drltrace_options.h,
* see DROPTION_SCOPE_CLIENT options.
*/
#include "drltrace.h"
/* XXX i#1948: features to add:
*
* + Add filtering of which library routines to trace.
* This would likely be via a configuration file.
* Currently we have a simple -only_to_lib option.
*
* + Add argument values and return values. The number and type of each
* argument and return would likely come from the filter configuration
* file, or from querying debug information.
* Today we have simple type-blind printing via -num_unknown_args and
* usage of drsyscall to print symbolic arguments for known library calls.
*
* + Add 2 more modes, both gathering statistics rather than a full
* trace: one mode that counts total calls, and one that just
* records whether each library routine was ever called. For these,
* we'll probably want to insert custom instrumentation rather than
* a clean call via drwrap, and so we'll want our own hashtable of
* the library entries.
*/
/* Where to write the trace */
static file_t outf;
/* Avoid exe exports, as on Linux many apps have a ton of global symbols. */
static app_pc exe_start;
/****************************************************************************
* Arguments printing
*/
/* XXX i#1978: The functions print_simple_value and print_arg were taken from drstrace.
* It would be better to move them in drsyscall and import in drstrace and here.
*/
static void
print_simple_value(drsys_arg_t *arg, bool leading_zeroes)
{
bool pointer = !TEST(DRSYS_PARAM_INLINED, arg->mode);
dr_fprintf(outf, pointer ? PFX : (leading_zeroes ? PFX : PIFX), arg->value);
if (pointer && ((arg->pre && TEST(DRSYS_PARAM_IN, arg->mode)) ||
(!arg->pre && TEST(DRSYS_PARAM_OUT, arg->mode)))) {
ptr_uint_t deref = 0;
ASSERT(arg->size <= sizeof(deref), "too-big simple type");
/* We assume little-endian */
if (dr_safe_read((void *)arg->value, arg->size, &deref, NULL))
dr_fprintf(outf, (leading_zeroes ? " => " PFX : " => " PIFX), deref);
}
}
static void
print_string(void *drcontext, void *pointer_str, bool is_wide)
{
if (pointer_str == NULL)
dr_fprintf(outf, "<null>");
else {
DR_TRY_EXCEPT(drcontext, {
dr_fprintf(outf, is_wide ? "%S" : "%s", pointer_str);
}, {
dr_fprintf(outf, "<invalid memory>");
});
}
}
static void
print_arg(void *drcontext, drsys_arg_t *arg)
{
dr_fprintf(outf, "\n arg %d: ", arg->ordinal);
switch (arg->type) {
case DRSYS_TYPE_VOID: print_simple_value(arg, true); break;
case DRSYS_TYPE_POINTER: print_simple_value(arg, true); break;
case DRSYS_TYPE_BOOL: print_simple_value(arg, false); break;
case DRSYS_TYPE_INT: print_simple_value(arg, false); break;
case DRSYS_TYPE_SIGNED_INT: print_simple_value(arg, false); break;
case DRSYS_TYPE_UNSIGNED_INT: print_simple_value(arg, false); break;
case DRSYS_TYPE_HANDLE: print_simple_value(arg, false); break;
case DRSYS_TYPE_NTSTATUS: print_simple_value(arg, false); break;
case DRSYS_TYPE_ATOM: print_simple_value(arg, false); break;
case DRSYS_TYPE_CSTRING:
print_string(drcontext, (void *)arg->value, false);
break;
case DRSYS_TYPE_CWSTRING:
print_string(drcontext, (void *)arg->value, true);
break;
default: {
if (arg->value == 0)
dr_fprintf(outf, "<null>");
else
dr_fprintf(outf, PFX, arg->value);
}
}
dr_fprintf(outf, " (%s%s%stype=%s%s, size=" PIFX ")",
(arg->arg_name == NULL) ? "" : "name=",
(arg->arg_name == NULL) ? "" : arg->arg_name,
(arg->arg_name == NULL) ? "" : ", ",
(arg->type_name == NULL) ? "\"\"" : arg->type_name,
(arg->type_name == NULL ||
TESTANY(DRSYS_PARAM_INLINED|DRSYS_PARAM_RETVAL, arg->mode)) ? "" : "*",
arg->size);
}
static bool
drlib_iter_arg_cb(drsys_arg_t *arg, void *wrapcxt)
{
if (arg->ordinal == -1)
return true;
if (arg->ordinal >= op_max_args.get_value())
return false; /* limit number of arguments to be printed */
arg->value = (ptr_uint_t)drwrap_get_arg(wrapcxt, arg->ordinal);
print_arg(drwrap_get_drcontext(wrapcxt), arg);
return true; /* keep going */
}
static void
print_args_unknown_call(app_pc func, void *wrapcxt)
{
uint i;
void *drcontext = drwrap_get_drcontext(wrapcxt);
DR_TRY_EXCEPT(drcontext, {
for (i = 0; i < op_unknown_args.get_value(); i++) {
dr_fprintf(outf, "\n arg %d: " PFX, i,
drwrap_get_arg(wrapcxt, i));
}
}, {
dr_fprintf(outf, "<invalid memory>");
/* Just keep going */
});
/* all args have been sucessfully printed */
dr_fprintf(outf, op_print_ret_addr.get_value() ? "\n ": "");
}
static bool
print_libcall_args(std::vector<drsys_arg_t*> *args_vec, void *wrapcxt)
{
if (args_vec == NULL || args_vec->size() <= 0)
return false;
std::vector<drsys_arg_t*>::iterator it;
for (it = args_vec->begin(); it != args_vec->end(); ++it) {
if (!drlib_iter_arg_cb(*it, wrapcxt))
break;
}
return true;
}
static void
print_symbolic_args(const char *name, void *wrapcxt, app_pc func)
{
drmf_status_t res;
drsys_syscall_t *syscall;
std::vector<drsys_arg_t *> *args_vec;
if (op_max_args.get_value() == 0)
return;
if (op_use_config.get_value()) {
/* looking for libcall in libcalls hashtable */
args_vec = libcalls_search(name);
if (print_libcall_args(args_vec, wrapcxt)) {
dr_fprintf(outf, op_print_ret_addr.get_value() ? "\n ": "");
return; /* we found libcall and sucessfully printed all arguments */
}
}
/* trying to find a prototype of the libcall using drsyscall */
res = drsys_name_to_syscall(name, &syscall);
if (res == DRMF_SUCCESS) {
res = drsys_iterate_arg_types(syscall, drlib_iter_arg_cb, wrapcxt);
if (res != DRMF_SUCCESS && res != DRMF_ERROR_DETAILS_UNKNOWN)
ASSERT(false, "drsys_iterate_arg_types failed in print_symbolic_args");
/* all args have been sucessfully printed */
dr_fprintf(outf, op_print_ret_addr.get_value() ? "\n ": "");
return;
} else {
/* use standard type-blind scheme */
if (op_unknown_args.get_value() > 0)
print_args_unknown_call(func, wrapcxt);
}
}
/****************************************************************************
* Library entry wrapping
*/
static void
lib_entry(void *wrapcxt, INOUT void **user_data)
{
const char *name = (const char *) *user_data;
const char *modname = NULL;
app_pc func = drwrap_get_func(wrapcxt);
module_data_t *mod;
thread_id_t tid;
uint mod_id;
app_pc mod_start, ret_addr;
drcovlib_status_t res;
void *drcontext = drwrap_get_drcontext(wrapcxt);
if (op_only_from_app.get_value()) {
/* For just this option, the modxfer approach might be better */
app_pc retaddr = NULL;
DR_TRY_EXCEPT(drcontext, {
retaddr = drwrap_get_retaddr(wrapcxt);
}, { /* EXCEPT */
retaddr = NULL;
});
if (retaddr != NULL) {
mod = dr_lookup_module(retaddr);
if (mod != NULL) {
bool from_exe = (mod->start == exe_start);
dr_free_module_data(mod);
if (!from_exe)
return;
}
} else {
/* Nearly all of these cases should be things like KiUserCallbackDispatcher
* or other abnormal transitions.
* If the user really wants to see everything they can not pass
* -only_from_app.
*/
return;
}
}
/* XXX: it may be better to heap-allocate the "module!func" string and
* pass in, to avoid this lookup.
*/
mod = dr_lookup_module(func);
if (mod != NULL)
modname = dr_module_preferred_name(mod);
tid = dr_get_thread_id(drcontext);
if (tid != INVALID_THREAD_ID)
dr_fprintf(outf, "~~%d~~ ", tid);
else
dr_fprintf(outf, "~~Dr.L~~ ");
dr_fprintf(outf, "%s%s%s", modname == NULL ? "" : modname,
modname == NULL ? "" : "!", name);
/* XXX: We employ three schemes of arguments printing. drsyscall is used
* to get a symbolic representation of arguments for known library calls.
* For the rest of library calls we are looking for prototypes in config file
* specified by user. If there is no info in both sources we employ type-blind
* printing and use -num_unknown_args to get a count of arguments to print.
*/
print_symbolic_args(name, wrapcxt, func);
if (op_print_ret_addr.get_value()) {
ret_addr = drwrap_get_retaddr(wrapcxt);
res = drmodtrack_lookup(drcontext, ret_addr, &mod_id, &mod_start);
if (res == DRCOVLIB_SUCCESS) {
dr_fprintf(outf,
op_print_ret_addr.get_value() ?
" and return to module id:%d, offset:" PIFX : "",
mod_id, ret_addr - mod_start);
dr_fprintf(outf,
op_print_ret_addr.get_value() ?
" and return to(absolute address) " PIFX : "",
ret_addr);
}
}
dr_fprintf(outf, "\n");
if (mod != NULL)
dr_free_module_data(mod);
}
static void
iterate_exports(const module_data_t *info, bool add)
{
dr_symbol_export_iterator_t *exp_iter =
dr_symbol_export_iterator_start(info->handle);
while (dr_symbol_export_iterator_hasnext(exp_iter)) {
dr_symbol_export_t *sym = dr_symbol_export_iterator_next(exp_iter);
app_pc func = NULL;
if (sym->is_code)
func = sym->addr;
#ifdef LINUX
else if (sym->is_indirect_code) {
/* Invoke the export to get the real entry: */
app_pc (*indir)(void) = (app_pc (*)(void)) cast_to_func(sym->addr);
void *drcontext = dr_get_current_drcontext();
DR_TRY_EXCEPT(drcontext, {
func = (*indir)();
}, { /* EXCEPT */
func = NULL;
});
VNOTIFY(2, "export %s indirected from " PFX " to " PFX NL,
sym->name, sym->addr, func);
}
#endif
if (op_ignore_underscore.get_value() && strstr(sym->name, "_") == sym->name)
func = NULL;
if (func != NULL) {
if (add) {
IF_DEBUG(bool ok =)
drwrap_wrap_ex(func, lib_entry, NULL, (void *) sym->name, 0);
ASSERT(ok, "wrap request failed");
VNOTIFY(2, "wrapping export %s!%s @" PFX NL,
dr_module_preferred_name(info), sym->name, func);
} else {
IF_DEBUG(bool ok =)
drwrap_unwrap(func, lib_entry, NULL);
ASSERT(ok, "unwrap request failed");
}
}
}
dr_symbol_export_iterator_stop(exp_iter);
}
static bool
library_matches_filter(const module_data_t *info)
{
if (!op_only_to_lib.get_value().empty()) {
const char *libname = dr_module_preferred_name(info);
#ifdef WINDOWS
return (libname != NULL && strcasestr(libname,
op_only_to_lib.get_value().c_str()) != NULL);
#else
return (libname != NULL && strstr(libname,
op_only_to_lib.get_value().c_str()) != NULL);
#endif
}
return true;
}
static void
event_module_load(void *drcontext, const module_data_t *info, bool loaded)
{
if (info->start != exe_start && library_matches_filter(info))
iterate_exports(info, true/*add*/);
}
static void
event_module_unload(void *drcontext, const module_data_t *info)
{
if (info->start != exe_start && library_matches_filter(info))
iterate_exports(info, false/*remove*/);
}
/****************************************************************************
* Init and exit
*/
static void
open_log_file(void)
{
char buf[MAXIMUM_PATH];
if (op_logdir.get_value().compare("-") == 0)
outf = STDERR;
else {
outf = drx_open_unique_appid_file(op_logdir.get_value().c_str(),
dr_get_process_id(),
"drltrace", "log",
#ifndef WINDOWS
DR_FILE_CLOSE_ON_FORK |
#endif
DR_FILE_ALLOW_LARGE,
buf, BUFFER_SIZE_ELEMENTS(buf));
ASSERT(outf != INVALID_FILE, "failed to open log file");
VNOTIFY(0, "drltrace log file is %s" NL, buf);
}
}
#ifndef WINDOWS
static void
event_fork(void *drcontext)
{
/* The old file was closed by DR b/c we passed DR_FILE_CLOSE_ON_FORK */
open_log_file();
}
#endif
static void
event_exit(void)
{
if (op_max_args.get_value() > 0)
drsys_exit();
if (op_use_config.get_value())
libcalls_hashtable_delete();
if (outf != STDERR) {
if (op_print_ret_addr.get_value())
drmodtrack_dump(outf);
dr_close_file(outf);
}
drx_exit();
drwrap_exit();
drmgr_exit();
if (op_print_ret_addr.get_value())
drmodtrack_exit();
}
DR_EXPORT void
dr_client_main(client_id_t id, int argc, const char *argv[])
{
module_data_t *exe;
drsys_options_t ops = { sizeof(ops), 0, };
IF_DEBUG(bool ok;)
dr_set_client_name("Dr. LTrace", "http://drmemory.org/issues");
if (!droption_parser_t::parse_argv(DROPTION_SCOPE_CLIENT, argc, argv,
NULL, NULL))
ASSERT(false, "unable to parse options specified for drltracelib");
op_print_stderr = true;
IF_DEBUG(ok = )
drmgr_init();
ASSERT(ok, "drmgr failed to initialize");
IF_DEBUG(ok = )
drwrap_init();
ASSERT(ok, "drwrap failed to initialize");
IF_DEBUG(ok = )
drx_init();
ASSERT(ok, "drx failed to initialize");
if (op_print_ret_addr.get_value()) {
IF_DEBUG(ok = )
drmodtrack_init();
ASSERT(ok == DRCOVLIB_SUCCESS, "drmodtrack failed to initialize");
}
exe = dr_get_main_module();
if (exe != NULL)
exe_start = exe->start;
dr_free_module_data(exe);
/* No-frills is safe b/c we're the only module doing wrapping, and
* we're only wrapping at module load and unwrapping at unload.
* Fast cleancalls is safe b/c we're only wrapping func entry and
* we don't care about the app context.
*/
drwrap_set_global_flags((drwrap_global_flags_t)
(DRWRAP_NO_FRILLS | DRWRAP_FAST_CLEANCALLS));
dr_register_exit_event(event_exit);
#ifdef UNIX
dr_register_fork_init_event(event_fork);
#endif
drmgr_register_module_load_event(event_module_load);
drmgr_register_module_unload_event(event_module_unload);
#ifdef WINDOWS
dr_enable_console_printing();
#endif
if (op_max_args.get_value() > 0) {
drsys_init(id, &ops);
parse_config();
}
open_log_file();
}
/* **********************************************************
* Copyright (c) 2013-2014 Google, Inc. All rights reserved.
* **********************************************************/
/* Dr. Memory: the memory debugger
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License, and no later version.
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
/* strace: system call tracing tool based on the Dr. Syscall Extension.
*
* XXX: add more features, such as:
* + named constants for flags
* + callstacks
* + timestamps
*
* XXX i#1497: port to Linux
* XXX i#1498: port to MacOS
*/
#include "dr_api.h"
#include "drmgr.h"
#include "drx.h"
#include "drsyscall.h"
#include "drstrace_named_consts.h"
#include "utils.h"
#include "drwrap.h"
#include "drcovlib.h"
#include <string.h>
#ifdef WINDOWS
# include "windefs.h"
# include <windows.h>
#endif
extern size_t get_const_arrays_num(void);
/* Where to write the trace */
static file_t outf;
#ifndef DRSTRACE_UNIT_TESTS
static
#endif
hashtable_t nconsts_table;
/* We buffer the output via a stack-allocated buffer. We flush prior to
* each system call.
*/
#define OUTBUF_SIZE 2048
#define TYPE_OUTPUT_SIZE 2048
#define HASHTABLE_BITSIZE 10 /* 512 < entries # < 1024 */
typedef struct _buf_info_t {
char buf[OUTBUF_SIZE];
size_t sofar;
ssize_t len;
} buf_info_t;
#define OUTPUT(buf_info, fmt, ...) \
BUFFERED_WRITE(outf, (buf_info)->buf, BUFFER_SIZE_ELEMENTS((buf_info)->buf), \
(buf_info)->sofar, (buf_info)->len, fmt, ##__VA_ARGS__)
static uint verbose = 1;
#define ALERT(level, fmt, ...) do { \
if (verbose >= (level)) \
dr_fprintf(STDERR, fmt, ##__VA_ARGS__); \
} while (0)
/* Checks for both debug and release builds: */
#define USAGE_CHECK(x, msg) DR_ASSERT_MSG(x, msg)
#undef ASSERT /* we don't want msgbox */
#define ASSERT(cond, msg) \
((void)((!(cond)) ? \
(dr_fprintf(STDERR, "ASSERT FAILURE: %s:%d: %s (%s)", \
__FILE__, __LINE__, #cond, msg), \
dr_abort(), 0) : 0))
#define OPTION_MAX_LENGTH MAXIMUM_PATH
typedef struct _drstrace_options_t {
char logdir[MAXIMUM_PATH];
char sympath[MAXIMUM_PATH];
} drstrace_options_t;
static drstrace_options_t options;
/* Avoid exe exports, as on Linux many apps have a ton of global symbols. */
//static app_pc exe_start;
static void
print_unicode_string(buf_info_t *buf, UNICODE_STRING *us)
{
if (us == NULL)
OUTPUT(buf, "<null>");
else {
OUTPUT(buf, "%d/%d \"%.*S\"", us->Length, us->MaximumLength,
us->Length/sizeof(wchar_t),
(us->Buffer == NULL) ? L"<null>" : us->Buffer);
}
}
void
print_simple_value(buf_info_t *buf, drsys_arg_t *arg, bool leading_zeroes)
{
bool pointer = !TEST(DRSYS_PARAM_INLINED, arg->mode);
OUTPUT(buf, pointer ? PFX : (leading_zeroes ? PFX : PIFX), arg->value);
if (pointer && ((arg->pre && TEST(DRSYS_PARAM_IN, arg->mode)) ||
(!arg->pre && TEST(DRSYS_PARAM_OUT, arg->mode)))) {
ptr_uint_t deref = 0;
ASSERT(arg->size <= sizeof(deref), "too-big simple type");
/* We assume little-endian */
if (dr_safe_read((void *)arg->value, arg->size, &deref, NULL))
OUTPUT(buf, (leading_zeroes ? " => "PFX : " => "PIFX), deref);
}
}
static bool
drstrace_print_enum_const_name(buf_info_t *buf, drsys_arg_t *arg)
{
/* The routine returns false when can't
* find symbolic name in the hashtable.
*/
int iterator = 0;
const_values_t *named_consts;
const_values_t *named_consts_save;
bool has_out = false;
/* Trying to find enum_name in the hashtable */
named_consts = (const_values_t *)
hashtable_lookup(&nconsts_table, (void *) arg->enum_name);
if (named_consts == NULL) {
OUTPUT(buf, PIFX, arg->value);
return false;
}
/* There are a lot of named constants with incremental values
* (e.g. REG_NONE 0, REG_SZ 1, REG_EXPAND_SZ 2, REG_BINARY 3).
* So, firstly, we're trying to determine such cases.
*/
named_consts_save = named_consts;
while (named_consts_save->const_name != NULL) {
if (arg->value == named_consts_save->value) {
if (has_out)
OUTPUT(buf, " or ");
OUTPUT(buf, "%s", named_consts_save->const_name);
has_out = true;
}
named_consts_save++;
}
if (has_out)
return true;
/* If not, we perform linear search for composite named constants
* (e.g. FILE_SHARE_READ | FILE_SHARE_WRITE). We're using linear
* search instead of random access b/c current table entries may
* contain the same values for different named constants as well as
* combination values, which make it difficult, such as:
* ...
* {0x00800000, "FILE_OPEN_FOR_FREE_SPACE_QUERY"},
* {0x00ffffff, "FILE_VALID_OPTION_FLAGS"},
* ...
*/
has_out = false;
while (named_consts->const_name != NULL) {
if (TESTALL(named_consts->value, arg->value)) {
if (has_out)
OUTPUT(buf, "|");
/* FIXME i#1550: We don't perform additional search to
* include entries with the same values in the output.
* Ideally the tables shouldn't contain such entries.
*/
OUTPUT(buf, "%s", named_consts->const_name);
has_out = true;
}
named_consts++;
}
if (!has_out) {
OUTPUT(buf, PIFX, arg->value);
return false;
}
return true;
}
/* NOTE: the routine returns up to 64 bit memory values */
static int64
safe_read_field(buf_info_t *buf, void *addr_to_resolve, size_t addr_size,
bool print_value)
{
int64 mem_value = 0;
ASSERT(addr_size <= sizeof(mem_value), "too-big mem value to read");
if (!dr_safe_read(addr_to_resolve, addr_size, &mem_value, NULL)) {
OUTPUT(buf, "<field unreadable>");
return 0;
}
if (print_value)
OUTPUT(buf, "0x"HEX64_FORMAT_STRING, mem_value);
return mem_value;
}
static bool
print_known_compound_type(buf_info_t *buf, drsys_param_type_t type, void *start_addr)
{
switch (type) {
case DRSYS_TYPE_UNICODE_STRING: {
print_unicode_string(buf, (UNICODE_STRING *) start_addr);
break;
}
case DRSYS_TYPE_OBJECT_ATTRIBUTES: {
OBJECT_ATTRIBUTES *oa = (OBJECT_ATTRIBUTES *) start_addr;
OUTPUT(buf, "len="PIFX", root="PIFX", name=",
oa->Length, oa->RootDirectory);
print_unicode_string(buf, oa->ObjectName);
OUTPUT(buf, ", att="PIFX", sd="PFX", sqos="PFX,
oa->Attributes, oa->SecurityDescriptor,
oa->SecurityQualityOfService);
break;
}
case DRSYS_TYPE_IO_STATUS_BLOCK: {
IO_STATUS_BLOCK *io = (IO_STATUS_BLOCK *) start_addr;
OUTPUT(buf, "status="PIFX", info="PIFX"", io->StatusPointer.Status,
io->Information);
break;
}
case DRSYS_TYPE_LARGE_INTEGER: {
LARGE_INTEGER *li = (LARGE_INTEGER *) start_addr;
OUTPUT(buf, "0x"HEX64_FORMAT_STRING, li->QuadPart);
break;
}
default: {
/* FIXME i#1089: add the other types */
return false;
}
}
/* XXX: we want KEY_VALUE_PARTIAL_INFORMATION, etc. like in
* syscall_diagnostics. Add drsyscall types for those, or hardcode here?
*/
return true;
}
static bool
identify_known_compound_type(buf_info_t *buf, char *name, void *start_addr)
{
/* XXX i#1607 There are two reasons why we're trying to determine types
* by name here. Firstly, we can't simply parse types with unions in the
* print_structure since this routine increases memory address by field
* size after each field which we don't want to do with unions. We make
* temporarly solution here *only* for LARGE_INTEGER. So we're still need
* to resolve union problem.
* The second one is that we want extra information (e.g. field names)
* for already known common structures.
*/
drsys_param_type_t type = DRSYS_TYPE_UNKNOWN;
if (strcmp(name, "_LARGE_INTEGER") == 0) {
type = DRSYS_TYPE_LARGE_INTEGER;
} else if (strcmp(name, "_UNICODE_STRING") == 0) {
type = DRSYS_TYPE_UNICODE_STRING;
} else if (strcmp(name, "_OBJECT_ATTRIBUTES") == 0) {
type = DRSYS_TYPE_OBJECT_ATTRIBUTES;
} else if (strcmp(name, "_IO_STATUS_BLOCK") == 0) {
type = DRSYS_TYPE_IO_STATUS_BLOCK;
} else {
return false;
}
return print_known_compound_type(buf, type, start_addr);
}
static uint
get_total_size_of_fields(drsym_compound_type_t *compound_type)
{
int i;
uint total_size = 0;
for (i = 0; i < compound_type->num_fields; i++)
total_size += compound_type->field_types[i]->size;
return total_size;
}
static void
print_structure(buf_info_t *buf, drsym_type_t *type, drsys_arg_t *arg, void *addr)
{
int i;
bool type_union = false;
if (type->kind == DRSYM_TYPE_COMPOUND) {
drsym_compound_type_t *compound_type =
(drsym_compound_type_t *)type;
OUTPUT(buf, "%s {", compound_type->name);
if (identify_known_compound_type(buf, compound_type->name, addr)) {
OUTPUT(buf, "}");
return;
}
/* i#1607: We need to print properly parent structures when they are
* actually unions (e.g. LARGE_INTEGER).
*/
if (get_total_size_of_fields(compound_type) > compound_type->type.size)
type_union = true;
for (i = 0; i < compound_type->num_fields; i++) {
print_structure(buf, compound_type->field_types[i], arg, addr);
if (!type_union)
addr = (char *)addr + compound_type->field_types[i]->size;
/* we don't want comma after last field */
if (i+1 != compound_type->num_fields)
OUTPUT(buf, ", ");
}
OUTPUT(buf, "}");
} else {
/* Print type fields */
if (type->kind == DRSYM_TYPE_VOID) {
OUTPUT(buf, "void=");
safe_read_field(buf, addr, type->size, true);
return;
} else if (type->kind == DRSYM_TYPE_PTR) {
drsym_ptr_type_t *ptr_type = (drsym_ptr_type_t *)type;
/* We're expecting an address here. So we truncate int64 to void* */
void *mem_value = (void *)safe_read_field(buf, addr, ptr_type->type.size,
false);
print_structure(buf, ptr_type->elt_type, arg, mem_value);
OUTPUT(buf, "*");
return;
} else if (type->kind == DRSYM_TYPE_ARRAY) {
OUTPUT(buf, "array(%d)={", type->size);
/* only print up to the first 4 bytes of the array */
safe_read_field(buf, addr, 0x1, true);
for (i = 1; i < type->size && i < 4; i++) {
OUTPUT(buf, ", ");
safe_read_field(buf, (byte *)addr + i, 0x1, true);
}
if (i < type->size)
OUTPUT(buf, ", ...");
OUTPUT(buf, "}");
return;
} else {
/* Print integer base types */
switch (type->size) {
case 1:
OUTPUT(buf, "byte|bool=");
break;
case 2:
OUTPUT(buf,"short=");
break;
case 4:
OUTPUT(buf, "int=");
break;
case 8:
OUTPUT(buf, "long long=");
break;
default:
OUTPUT(buf, "unknown type=");
break;
}
safe_read_field(buf, addr, type->size, true);
return;
}
}
return;
}
static bool
type_has_unknown_components(drsym_type_t *type)
{
int i;
if (type->kind == DRSYM_TYPE_COMPOUND) {
drsym_compound_type_t *compound_type = (drsym_compound_type_t *)type;
drsym_type_t **field_types = compound_type->field_types;
for (i = 0; i < compound_type->num_fields; i++) {
if (field_types[i]->kind == DRSYM_TYPE_PTR) {
drsym_ptr_type_t *ptr_type = (drsym_ptr_type_t *)field_types[i];
if (ptr_type->elt_type->size == 0)
return false;
}
/* recursively check type fields */
if (!type_has_unknown_components(field_types[i]))
return false;
}
} else if (type->size == 0) {
return false;
}
return true;
}
static bool
drstrace_print_info_class_struct(buf_info_t *buf, drsys_arg_t *arg)
{
char buf_tmp[TYPE_OUTPUT_SIZE];
drsym_type_t *type;
drsym_type_t *expand_type;
drsym_error_t r;
r = drsym_get_type_by_name(options.sympath, arg->enum_name,
buf_tmp, BUFFER_SIZE_BYTES(buf_tmp),
&type);
if (r != DRSYM_SUCCESS) {
NOTIFY("Value to symbol %s lookup failed", arg->enum_name);
return false;
}
r = drsym_expand_type(options.sympath, type->id, UINT_MAX,
buf_tmp, BUFFER_SIZE_BYTES(buf_tmp),
&expand_type);
if (r != DRSYM_SUCCESS) {
NOTIFY("%s structure expanding failed", arg->enum_name);
return false;
}
if (!type_has_unknown_components(expand_type)) {
NOTIFY("%s structure has unknown types", arg->enum_name);
return false;
}
if (arg->valid && !arg->pre) {
if (arg->value64 == 0) {
OUTPUT(buf, "NULL");
/* We return true since we already printed for this value */
return true;
}
/* We're expecting an address here. So we truncate int64 to void*. */
print_structure(buf, expand_type, arg, (void *)arg->value64);
} else {
return false;
}
return true;
}
static bool
drstrace_get_arg_symname(buf_info_t *buf, drsys_arg_t *arg)
{
if (arg->type >= DRSYS_TYPE_STRUCT) {
if (drstrace_print_info_class_struct(buf, arg)) {
OUTPUT(buf, " (type=<struct>*, size="PIFX")\n",
arg->size);
return true;
} else {
return false;
}
} else if (arg->enum_name != NULL) {
if (drstrace_print_enum_const_name(buf, arg)) {
OUTPUT(buf, " (type=named constant, value="PIFX", size="PIFX")\n",
arg->value,
arg->size);
} else {
OUTPUT(buf, " (type=named constant, size="PIFX")\n",
arg->size);
}
return true;
}
return false;
}
static void
print_arg(buf_info_t *buf, drsys_arg_t *arg)
{
if (arg->ordinal == -1)
OUTPUT(buf, "\tretval: ");
else
OUTPUT(buf, "\targ %d: ", arg->ordinal);
if (arg->enum_name != NULL) {
if (drstrace_get_arg_symname(buf, arg))
return;
}
/* XXX: add return value to dr_fprintf so we can more easily align
* after PFX vs PIFX w/o having to print to buffer
*/
switch (arg->type) {
case DRSYS_TYPE_VOID: print_simple_value(buf, arg, true); break;
case DRSYS_TYPE_POINTER: print_simple_value(buf, arg, true); break;
case DRSYS_TYPE_BOOL: print_simple_value(buf, arg, false); break;
case DRSYS_TYPE_INT: print_simple_value(buf, arg, false); break;
case DRSYS_TYPE_SIGNED_INT: print_simple_value(buf, arg, false); break;
case DRSYS_TYPE_UNSIGNED_INT: print_simple_value(buf, arg, false); break;
case DRSYS_TYPE_HANDLE: print_simple_value(buf, arg, false); break;
case DRSYS_TYPE_NTSTATUS: print_simple_value(buf, arg, false); break;
case DRSYS_TYPE_ATOM: print_simple_value(buf, arg, false); break;
default: {
if (arg->value == 0) {
OUTPUT(buf, "<null>");
} else if (arg->pre && !TEST(DRSYS_PARAM_IN, arg->mode)) {
OUTPUT(buf, PFX, arg->value);
} else {
if (!print_known_compound_type(buf, arg->type, (void *) arg->value))
OUTPUT(buf, "<NYI>");
}
}
}
OUTPUT(buf, " (%s%s%stype=%s%s, size="PIFX")\n",
(arg->arg_name == NULL) ? "" : "name=",
(arg->arg_name == NULL) ? "" : arg->arg_name,
(arg->arg_name == NULL) ? "" : ", ",
(arg->type_name == NULL) ? "\"\"" : arg->type_name,
(arg->type_name == NULL ||
TESTANY(DRSYS_PARAM_INLINED|DRSYS_PARAM_RETVAL, arg->mode)) ? "" : "*",
arg->size);
}
static bool
drsys_iter_arg_cb(drsys_arg_t *arg, void *user_data)
{
buf_info_t *buf = (buf_info_t *) user_data;
ASSERT(arg->valid, "no args should be invalid");
if ((arg->pre && !TEST(DRSYS_PARAM_RETVAL, arg->mode)) ||
(!arg->pre && TESTANY(DRSYS_PARAM_OUT|DRSYS_PARAM_RETVAL, arg->mode)))
{
if (arg->ordinal == 0)
{
ptr_uint_t return_address = 0;
dr_safe_read((void *)((char *)arg->start_addr - 4), 4, &return_address, NULL);
OUTPUT(buf, "return address is "PFX"\n", return_address);
}
print_arg(buf, arg);
}
return true; /* keep going */
}
static bool
event_pre_syscall(void *drcontext, int sysnum)
{
drsys_syscall_t *syscall;
bool known;
drsys_param_type_t ret_type;
const char *name;
drmf_status_t res;
buf_info_t buf;
buf.sofar = 0;
if (drsys_cur_syscall(drcontext, &syscall) != DRMF_SUCCESS)
ASSERT(false, "drsys_cur_syscall failed");
if (drsys_syscall_name(syscall, &name) != DRMF_SUCCESS)
ASSERT(false, "drsys_syscall_name failed");
if (drsys_syscall_is_known(syscall, &known) != DRMF_SUCCESS)
ASSERT(false, "failed to find whether known");
OUTPUT(&buf, "%s%s\n", name, known ? "" : " (details not all known)");
res = drsys_iterate_args(drcontext, drsys_iter_arg_cb, &buf);
if (res != DRMF_SUCCESS && res != DRMF_ERROR_DETAILS_UNKNOWN)
ASSERT(false, "drsys_iterate_args failed pre-syscall");
/* Flush prior to potentially waiting in the kernel */
FLUSH_BUFFER(outf, buf.buf, buf.sofar);
return true;
}
static void
event_post_syscall(void *drcontext, int sysnum)
{
drsys_syscall_t *syscall;
bool success = false;
uint errno;
drmf_status_t res;
buf_info_t buf;
buf.sofar = 0;
if (drsys_cur_syscall(drcontext, &syscall) != DRMF_SUCCESS)
ASSERT(false, "drsys_cur_syscall failed");
if (drsys_cur_syscall_result(drcontext, &success, NULL, &errno) != DRMF_SUCCESS)
ASSERT(false, "drsys_cur_syscall_result failed");
if (success)
OUTPUT(&buf, " succeeded =>\n");
else
OUTPUT(&buf, " failed (error="IF_WINDOWS_ELSE(PIFX, "%d")") =>\n", errno);
res = drsys_iterate_args(drcontext, drsys_iter_arg_cb, &buf);
if (res != DRMF_SUCCESS && res != DRMF_ERROR_DETAILS_UNKNOWN)
ASSERT(false, "drsys_iterate_args failed post-syscall");
FLUSH_BUFFER(outf, buf.buf, buf.sofar);
}
static bool
event_filter_syscall(void *drcontext, int sysnum)
{
return true; /* intercept everything */
}
static void
open_log_file(void)
{
char buf[MAXIMUM_PATH];
if (strcmp(options.logdir, "-") == 0)
outf = STDERR;
else {
outf = drx_open_unique_appid_file(options.logdir, dr_get_process_id(),
"drstrace", "log",
#ifndef WINDOWS
DR_FILE_CLOSE_ON_FORK |
#endif
DR_FILE_ALLOW_LARGE,
buf, BUFFER_SIZE_ELEMENTS(buf));
ASSERT(outf != INVALID_FILE, "failed to open log file");
ALERT(1, "<drstrace log file is %s>\n", buf);
}
}
#ifndef WINDOWS
static void
event_fork(void *drcontext)
{
/* The old file was closed by DR b/c we passed DR_FILE_CLOSE_ON_FORK */
open_log_file();
}
#endif
static
void exit_event(void)
{
if (outf != STDERR)
dr_close_file(outf);
if (drsys_exit() != DRMF_SUCCESS)
ASSERT(false, "drsys failed to exit");
drsym_exit();
drx_exit();
drmgr_exit();
hashtable_delete(&nconsts_table);
}
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 */
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, "-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, "-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");
}
} else if (strcmp(token, "-symcache_path") == 0) {
s = dr_get_token(s, options.sympath,
BUFFER_SIZE_ELEMENTS(options.sympath));
USAGE_CHECK(s != NULL, "missing symcache dir path");
ALERT(2, "<drstrace symbol source is %s>\n", options.sympath);
} else {
ALERT(0, "UNRECOGNIZED OPTION: \"%s\"\n", token);
USAGE_CHECK(false, "invalid option");
}
}
}
/********************************************************************
* code come from drltrace
* print return address
*/
static void
lib_entry(void *wrapcxt, INOUT void **user_data)
{
const char *name = (const char *) *user_data;
const char *modname = NULL;
app_pc func = drwrap_get_func(wrapcxt);
module_data_t *mod;
thread_id_t tid;
uint mod_id;
app_pc mod_start, ret_addr;
drcovlib_status_t res;
void *drcontext = drwrap_get_drcontext(wrapcxt);
/* XXX: it may be better to heap-allocate the "module!func" string and
* pass in, to avoid this lookup.
*/
mod = dr_lookup_module(func);
if (mod != NULL)
modname = dr_module_preferred_name(mod);
tid = dr_get_thread_id(drcontext);
if (tid != INVALID_THREAD_ID)
dr_fprintf(outf, "~~%d~~ ", tid);
else
dr_fprintf(outf, "~~Dr.L~~ ");
dr_fprintf(outf, "%s%s%s", modname == NULL ? "" : modname,
modname == NULL ? "" : "!", name);
/* XXX: We employ three schemes of arguments printing. drsyscall is used
* to get a symbolic representation of arguments for known library calls.
* For the rest of library calls we are looking for prototypes in config file
* specified by user. If there is no info in both sources we employ type-blind
* printing and use -num_unknown_args to get a count of arguments to print.
*/
//print_symbolic_args(name, wrapcxt, func);
ret_addr = drwrap_get_retaddr(wrapcxt);
res = drmodtrack_lookup(drcontext, ret_addr, &mod_id, &mod_start);
if (res == DRCOVLIB_SUCCESS) {
dr_fprintf(outf,
" and return to module id:%d, offset:" PIFX,
mod_id, ret_addr - mod_start);
dr_fprintf(outf, "\n");
dr_fprintf(outf,
" and return to(absolute address) " PIFX,
ret_addr);
}
dr_fprintf(outf, "\n");
if (mod != NULL)
dr_free_module_data(mod);
}
static void
iterate_exports(const module_data_t *info, bool add)
{
dr_symbol_export_iterator_t *exp_iter =
dr_symbol_export_iterator_start(info->handle);
while (dr_symbol_export_iterator_hasnext(exp_iter)) {
dr_symbol_export_t *sym = dr_symbol_export_iterator_next(exp_iter);
app_pc func = NULL;
if (sym->is_code)
func = sym->addr;
#ifdef LINUX
else if (sym->is_indirect_code) {
/* Invoke the export to get the real entry: */
app_pc (*indir)(void) = (app_pc (*)(void)) cast_to_func(sym->addr);
void *drcontext = dr_get_current_drcontext();
DR_TRY_EXCEPT(drcontext, {
func = (*indir)();
}, { /* EXCEPT */
func = NULL;
});
VNOTIFY(2, "export %s indirected from " PFX " to " PFX NL,
sym->name, sym->addr, func);
}
#endif
if (func != NULL) {
if (add) {
//IF_DEBUG(bool ok =)
drwrap_wrap_ex(func, lib_entry, NULL, (void *) sym->name, 0);
//ASSERT(ok, "wrap request failed");
//VNOTIFY(2, "wrapping export %s!%s @" PFX NL,
// dr_module_preferred_name(info), sym->name, func);
} else {
//IF_DEBUG(bool ok =)
drwrap_unwrap(func, lib_entry, NULL);
//ASSERT(ok, "unwrap request failed");
}
}
}
dr_symbol_export_iterator_stop(exp_iter);
}
static bool
library_matches_filter(const module_data_t *info)
{
// if (!op_only_to_lib.get_value().empty()) {
// const char *libname = dr_module_preferred_name(info);
//#ifdef WINDOWS
// return (libname != NULL && strcasestr(libname,
// op_only_to_lib.get_value().c_str()) != NULL);
//#else
// return (libname != NULL && strstr(libname,
// op_only_to_lib.get_value().c_str()) != NULL);
//#endif
// }
return true;
}
static void
event_module_load(void *drcontext, const module_data_t *info, bool loaded)
{
//if (info->start != exe_start && library_matches_filter(info))
iterate_exports(info, true/*add*/);
}
static void
event_module_unload(void *drcontext, const module_data_t *info)
{
//if (info->start != exe_start && library_matches_filter(info))
iterate_exports(info, false/*remove*/);
}
/****************************************************************
* code coming from drltrace.cpp
* end
*/
DR_EXPORT
void dr_init(client_id_t id)
{
uint i = 0;
uint const_arrays_num;
drsys_options_t ops = { sizeof(ops), 0, };
dr_set_client_name("Dr. STrace", "http://drmemory.org/issues");
#ifdef WINDOWS
dr_enable_console_printing();
#endif
options_init(id);
drsym_init(0);
drmgr_init();
drx_init();
/* for drltrace.cpp */
//module_data_t *exe;
drwrap_init();
drmodtrack_init();
//exe = dr_get_main_module();
// if (exe != NULL)
// exe_start = exe->start;
// dr_free_module_data(exe);
/* No-frills is safe b/c we're the only module doing wrapping, and
* we're only wrapping at module load and unwrapping at unload.
* Fast cleancalls is safe b/c we're only wrapping func entry and
* we don't care about the app context.
*/
drwrap_set_global_flags((drwrap_global_flags_t)
(DRWRAP_NO_FRILLS | DRWRAP_FAST_CLEANCALLS));
//dr_register_exit_event(event_exit);
#ifdef UNIX
dr_register_fork_init_event(event_fork);
#endif
drmgr_register_module_load_event(event_module_load);
drmgr_register_module_unload_event(event_module_unload);
#ifdef WINDOWS
dr_enable_console_printing();
#endif
/* for drltrace.cpp */
if (drsys_init(id, &ops) != DRMF_SUCCESS)
{
// ASSERT(false, "drsys failed to init");
}
dr_register_exit_event(exit_event);
dr_register_filter_syscall_event(event_filter_syscall);
drmgr_register_pre_syscall_event(event_pre_syscall);
drmgr_register_post_syscall_event(event_post_syscall);
if (drsys_filter_all_syscalls() != DRMF_SUCCESS)
ASSERT(false, "drsys_filter_all_syscalls should never fail");
open_log_file();
const_arrays_num = get_const_arrays_num();
hashtable_init(&nconsts_table, HASHTABLE_BITSIZE, HASH_STRING, false);
while (i < const_arrays_num) {
const_values_t *named_consts = const_struct_array[i];
bool res = hashtable_add(&nconsts_table,
(void *) named_consts[0].const_name,
(void *) named_consts);
if (!res)
ASSERT(false, "drstrace failed to add to hashtable");
i++;
}
}
/****************************************************************************
* Unit tests group of functions
*/
#ifdef DRSTRACE_UNIT_TESTS
bool
drstrace_unit_test_syscall_exit()
{
if (drsym_exit() != DRSYM_SUCCESS)
return false;
hashtable_delete(&nconsts_table);
return true;
}
bool
drstrace_unit_test_syscall_init()
{
uint const_arrays_num;
uint i = 0;
dr_standalone_init();
if (drsym_init(0) != DRSYM_SUCCESS)
return false;
const_arrays_num = get_const_arrays_num();
hashtable_init(&nconsts_table, HASHTABLE_BITSIZE, HASH_STRING, false);
while (i < const_arrays_num) {
const_values_t *named_consts = const_struct_array[i];
bool res = hashtable_add(&nconsts_table,
(void *) named_consts[0].const_name,
(void *) named_consts);
if (!res)
return false;
i++;
}
return true;
}
void
drstrace_set_symbol_path(const char *pdb_dir) {
_snprintf(options.sympath, BUFFER_SIZE_ELEMENTS(options.sympath), "%s", pdb_dir);
}
void
drstrace_unit_test_syscall_arg_iteration(drsys_arg_t arg, void *user_data)
{
drsys_iter_arg_cb(&arg, user_data);
return;
}
#endif /* DRSTRACE_UNIT_TESTS */
/***************************************************************************/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment