Last active
July 28, 2017 02:47
-
-
Save Clcanny/eed2e68d454e7850a1a26ddfc278ae84 to your computer and use it in GitHub Desktop.
DynamoRIO
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* ********************************************************** | |
* 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 */ | |
/***************************************************************************/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* *************************************************************************** | |
* 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(); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* ********************************************************** | |
* 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