introduce new features

- disable hardware breakpoints
  useful for running mtrace inside a virtual machine
- sanity check
  check new/new[] against mismatch operation
  and all c allocation functions against c++ allocation functions and
  visa versa.
This commit is contained in:
Stefani Seibold 2015-10-28 07:57:53 +01:00
parent 96cd4a6ce8
commit 802a1a3b22
10 changed files with 236 additions and 52 deletions

View File

@ -329,6 +329,9 @@ struct breakpoint *breakpoint_new_ext(struct task *task, arch_addr_t addr, struc
if (bp == NULL)
goto fail1;
if (options.nohwbp)
bp_type = BP_SW;
bp->on_hit = NULL;
bp->libsym = libsym;
bp->addr = addr;

View File

@ -427,10 +427,14 @@ static int client_func(void)
case MT_PVALLOC:
case MT_MMAP:
case MT_MMAP64:
case MT_NEW:
case MT_NEW_ARRAY:
process_alloc(process, &mt_msg, payload);
break;
case MT_REALLOC_ENTER:
case MT_FREE:
case MT_DELETE:
case MT_DELETE_ARRAY:
process_free(process, &mt_msg, payload);
break;
case MT_MUNMAP:

View File

@ -70,6 +70,7 @@ struct rb_stack {
unsigned long long bytes_used;
unsigned long long bytes_leaked;
unsigned long long tsc;
unsigned long long n_mismatched;
};
struct map {
@ -139,6 +140,14 @@ static const char *str_operation(enum mt_operation operation)
return "free";
case MT_MUNMAP:
return "munmap";
case MT_NEW:
return "new";
case MT_NEW_ARRAY:
return "new[]";
case MT_DELETE:
return "delete";
case MT_DELETE_ARRAY:
return "delete[]";
default:
break;
}
@ -422,6 +431,7 @@ static struct rb_stack *stack_clone(struct process *process, struct rb_stack *st
this->leaks = stack_node->leaks;
this->n_allocations = stack_node->n_allocations;
this->n_mismatched = stack_node->n_mismatched;
this->total_allocations = stack_node->total_allocations;
this->bytes_used = stack_node->bytes_used;
this->bytes_leaked = stack_node->bytes_leaked;
@ -486,6 +496,7 @@ static struct rb_stack *stack_add(struct process *process, pid_t pid, void *addr
memcpy(stack->addrs, addrs, stack_size);
this->n_allocations = 0;
this->n_mismatched = 0;
this->total_allocations = 0;
this->bytes_used = 0;
this->leaks = 0;
@ -832,6 +843,15 @@ static int sort_tsc(const struct rb_stack **p, const struct rb_stack **q)
return 0;
}
static int sort_mismatched(const struct rb_stack **p, const struct rb_stack **q)
{
if ((*p)->n_mismatched > (*q)->n_mismatched)
return -1;
if ((*p)->n_mismatched < (*q)->n_mismatched)
return 1;
return 0;
}
static int sort_usage(const struct rb_stack **p, const struct rb_stack **q)
{
if ((*p)->bytes_used > (*q)->bytes_used)
@ -939,6 +959,7 @@ static void _process_dump(struct process *process, int (*sortby)(const struct rb
struct rb_stack *stack = arr[i];
if (!skipfunc(stack)) {
if (!stack->n_mismatched) {
if (dump_printf(
"Stack (%s):\n"
" bytes used: %llu\n"
@ -955,6 +976,16 @@ static void _process_dump(struct process *process, int (*sortby)(const struct rb
if (dump_printf( " leaked allocations: %lu (%llu bytes)\n", stack->leaks, stack->bytes_leaked) == -1)
break;
}
}
else {
if (dump_printf(
"Stack (%s):\n"
" total number of mismatches: %llu\n",
str_operation(stack->stack->operation),
stack->n_mismatched
) == -1)
break;
}
if (dump_printf(" tsc: %llu\n", stack->tsc) == -1)
break;
@ -995,6 +1026,11 @@ static int skip_zero_allocations(struct rb_stack *stack)
return !stack->n_allocations;
}
static int skip_non_mismatched(struct rb_stack *stack)
{
return !stack->n_mismatched;
}
static int skip_zero_leaks(struct rb_stack *stack)
{
return !stack->leaks;
@ -1035,6 +1071,11 @@ void process_dump_sort_tsc(struct process *process, const char *outfile)
process_dump(process, sort_tsc, skip_zero_allocations, outfile);
}
void process_dump_sort_mismatched(struct process *process, const char *outfile)
{
process_dump(process, sort_mismatched, skip_non_mismatched, outfile);
}
void process_dump_stacks(struct process *process, const char *outfile)
{
process_dump(process, sort_allocations, skip_none, outfile);
@ -1188,10 +1229,43 @@ void process_munmap(struct process *process, struct mt_msg *mt_msg, void *payloa
} while(size);
}
static int is_sane(struct rb_block *block, enum mt_operation op)
{
switch(block->stack_node->stack->operation) {
case MT_MALLOC:
case MT_REALLOC:
case MT_REALLOC_FAILED:
case MT_MEMALIGN:
case MT_POSIX_MEMALIGN:
case MT_ALIGNED_ALLOC:
case MT_VALLOC:
case MT_PVALLOC:
if (op != MT_FREE && op != MT_REALLOC_ENTER)
return 0;
break;
case MT_NEW:
if (op != MT_DELETE)
return 0;
break;
case MT_NEW_ARRAY:
if (op != MT_DELETE_ARRAY)
return 0;
break;
case MT_MMAP:
case MT_MMAP64:
default:
return 0;
}
return 1;
}
void process_free(struct process *process, struct mt_msg *mt_msg, void *payload)
{
struct rb_block *block = NULL;
uint32_t payload_len = mt_msg->payload_len;
unsigned long ptr;
void *stack_data;
unsigned long stack_size;
if (!process->tracing)
return;
@ -1200,11 +1274,17 @@ void process_free(struct process *process, struct mt_msg *mt_msg, void *payload)
struct mt_alloc_payload_64 *mt_alloc = payload;
ptr = process->get_ulong(&mt_alloc->ptr);
stack_data = payload + sizeof(*mt_alloc);
stack_size = (payload_len - sizeof(*mt_alloc));
}
else {
struct mt_alloc_payload_32 *mt_alloc = payload;
ptr = process->get_ulong(&mt_alloc->ptr);
stack_data = payload + sizeof(*mt_alloc);
stack_size = (payload_len - sizeof(*mt_alloc));
}
debug(DEBUG_FUNCTION, "ptr=%#lx", ptr);
@ -1219,6 +1299,14 @@ void process_free(struct process *process, struct mt_msg *mt_msg, void *payload)
abort();
}
}
if (!is_sane(block, mt_msg->operation)) {
struct rb_stack *stack = stack_add(process, process->pid, stack_data, stack_size, mt_msg->operation);
stack->n_mismatched++;
stack->tsc = process->tsc++;
}
process_rb_delete_block(process, block);
}
else {
@ -1338,6 +1426,9 @@ void process_dump_sortby(struct process *process)
case OPT_SORT_USAGE:
_process_dump(process, sort_usage, skip_zero_allocations, options.output);
break;
case OPT_SORT_MISMATCHED:
_process_dump(process, sort_mismatched, skip_non_mismatched, options.output);
break;
default:
_process_dump(process, sort_allocations, skip_zero_allocations, options.output);
break;

View File

@ -109,6 +109,7 @@ void process_dump_sort_bytes_leaked(struct process *process, const char *outfile
void process_dump_sort_allocations(struct process *process, const char *outfile);
void process_dump_sort_total(struct process *process, const char *outfile);
void process_dump_sort_tsc(struct process *process, const char *outfile);
void process_dump_sort_mismatched(struct process *process, const char *outfile);
void process_dump_stacks(struct process *process, const char *outfile);
void add_ignore_regex(regex_t *re);

View File

@ -93,6 +93,7 @@ static struct cmd_opt dump_opts[] = {
{ "average", 2, process_dump_sort_average, "sort by average allocation of bytes (usage / allocations)" },
{ "bytes-leaked", 1, process_dump_sort_bytes_leaked, "sort by number of leaked bytes" },
{ "leaks", 1, process_dump_sort_leaks, "sort by number of detected leaks" },
{ "mismatched", 1, process_dump_sort_mismatched, "sort by number of mismatched releases" },
{ "stacks", 1, process_dump_stacks, "dump all stack sort by number of total allocations" },
{ "total", 2, process_dump_sort_total, "sort by number of total allocations" },
{ "tsc", 2, process_dump_sort_tsc, "sort by time stamp counter" },

View File

@ -65,6 +65,10 @@ enum mt_operation {
MT_STOP,
MT_START,
MT_DETACH,
MT_NEW,
MT_NEW_ARRAY,
MT_DELETE,
MT_DELETE_ARRAY,
};
struct mt_msg {

View File

@ -42,6 +42,8 @@ mtrace \- A dynamic memory allocation tracer
[\-i|\-\-interactive]
[\-l|\-\-logfile \fIfilename\fR]
[\-n|\-\-nocpp]
[\-N|\-\-nohwbp]
[\-S|\-\-sanity]
[\-O|\-\-omit \fIfilename\fR]
[\-u|\-\-user \fIusername\fR]
[\-v|\-\-verbose]
@ -70,6 +72,8 @@ mtrace \- A dynamic memory allocation tracer
[\-D|\-\-debug \fImask\fR]
[\-l|\-\-logfile \fIfilename\fR]
[\-n|\-\-nocpp]
[\-N|\-\-nohwbp]
[\-S|\-\-sanity]
[\-O|\-\-omit \fIfilename\fR]
[\-u|\-\-user \fIusername\fR]
[\-v|\-\-verbose]
@ -266,6 +270,9 @@ since the trace will be split into to different actions.
.IP "\-n, \-\-nocpp"
Disable the trace of C++ allocation operators. This is safe and faster for libstdc++,
since this library does call malloc() and free() inside the allocation operators.
.IP "\-N, \-\-nohwbp"
Disable the usage of hardware breakpoints. This options is intended for some
virtual machines, where hardware breakpoints not working.
.IP "\-p, \-\-pid \fIpid"
Attach to the process with the process ID \fIpid\fR and begin tracing.
This option can be used together with passing a command to execute.
@ -301,6 +308,12 @@ Sort by number of bytes leaked (only useful with \-a option).
.RE
.RE
.RS
\fImismatched\fR
.RS
Sort by number of mismatched releases (only useful with \-S option).
.RE
.RE
.RS
\fIleaks\fR
.RS
Sort by number of leaked allocations (only useful with \-a option).
@ -330,6 +343,9 @@ Sort by the pseudo time stamp counter. Each stack backtrace will get an increme
Sort by number of bytes in use of all open allocations.
.RE
.RE
.IP "\-S, \-\-sanity"
Check mismatching operations against new/new[] allocations. This options also
sets the sort-by options to mismatched.
.IP "\-t, \-\-trace"
Run \fBmtrace\fR in trace mode. In this mode all attached processes will run under
the control of \fBmtrace\fR and all dynamic memory function calls will be traced.
@ -380,9 +396,9 @@ at any time. It accepts a maximum of three parameters:
.RS
Sort the output of dump by the keyword. The keyword is the same as for the
\-S option (\fIallocations, \fIaverage\fR, \fIbytes-leaked\fR, \fIleaks\fR,
\fIstacks\fR, \fItotal\fR, \fItsc\fR and \fIusage\fR). See \-S option for
more details about the sortby keywords. The default sort order is
\fIallocations\fR when no sortby parameter is used.
\fImismatched\fR, \fIstacks\fR, \fItotal\fR, \fItsc\fR and \fIusage\fR). See
\-S option for more details about the sortby keywords. The default sort order
is \fIallocations\fR when no sortby parameter is used.
.RE
.RE
.RS

View File

@ -48,7 +48,7 @@
#define MIN_STACK 4
#define MAX_STACK 64
#define DEFAULT_STACK 15
#define DEFAULT_STACK 16
#define DEFAULT_PORT 4576
static char *sockdef;
@ -89,15 +89,17 @@ static void usage(void)
" -h, --help display this help and exit\n"
" -i, --interactive interactive client mode\n"
" -O, --omit=FILE do not place breakpoint in this file\n"
" -k, --kill abort mtrace on unexpected error conditon\n"
" -k, --kill abort mtrace due unexpected error conditon\n"
" -l, --logfile use log file instead of socket connection\n"
" -n, --nocpp disable trace of c++ allocation operators (faster for libstdc++)\n"
" -N, --nohwbp disable hardware breakpoint support\n"
" -o, --output=FILE write the trace output to file with given name\n"
" -p, --pid=PID attach to the process with the process ID pid (may be repeated)\n"
" -P, --port=PORT socket port (default: " STR(DEFAULT_PORT) ")\n"
" -r, --remote=addr remote use address (path, address or host)\n"
" -s, --sort-by=type sort dump by type:\n"
" allocations, average, bytes-leaked, leaks, stacks, total, tsc, usage\n"
" -S, --sanity check mismatching operations against new/new[] allocations\n"
" -t, --trace trace mode\n"
" -u, --user=USERNAME run command with the userid, groupid of username\n"
" -V, --version output version information and exit\n"
@ -276,9 +278,11 @@ char **process_options(int argc, char **argv)
options.opt_b = NULL;
options.opt_O = NULL;
options.sort_by = -1;
options.sanity = 0;
options.debug = 0;
options.kill = 0;
options.nocpp = 0;
options.nohwbp = 0;
for(;;) {
int c;
@ -296,13 +300,15 @@ char **process_options(int argc, char **argv)
{ "interactive", 0, 0, 'i' },
{ "kill", 0, 0, 'k' },
{ "logfile", 1, 0, 'l' },
{ "nocpp", 1, 0, 'n' },
{ "nocpp", 0, 0, 'n' },
{ "nohwbp", 0, 0, 'N' },
{ "output", 1, 0, 'o' },
{ "omit", 1, 0, 'O' },
{ "pid", 1, 0, 'p' },
{ "port", 1, 0, 'P' },
{ "remote", 1, 0, 'r' },
{ "sort-by", 1, 0, 's' },
{ "sanity", 0, 0, 'S' },
{ "trace", 0, 0, 't' },
{ "user", 1, 0, 'u' },
{ "version", 0, 0, 'V' },
@ -312,7 +318,7 @@ char **process_options(int argc, char **argv)
};
c = getopt_long(argc, argv,
"+aefhikLntVvw"
"+aefhikLnNStVvw"
"b:c:d:D:F:l:o:O:p:P:r:s:u:",
long_options,
&option_index);
@ -414,6 +420,9 @@ char **process_options(int argc, char **argv)
case 'n':
options.nocpp = 1;
break;
case 'N':
options.nohwbp = 1;
break;
case 'p':
{
struct opt_p_t *tmp = malloc(sizeof(*tmp));
@ -452,6 +461,9 @@ char **process_options(int argc, char **argv)
if (!strncmp(optarg, "leaks", 1))
options.sort_by = OPT_SORT_LEAKS;
else
if (!strncmp(optarg, "mismatched", 1))
options.sort_by = OPT_SORT_MISMATCHED;
else
if (!strncmp(optarg, "stacks", 1))
options.sort_by = OPT_SORT_STACKS;
else
@ -468,6 +480,9 @@ char **process_options(int argc, char **argv)
exit(1);
}
break;
case 'S':
options.sanity = 1;
break;
case 't':
options.trace = 1;
break;
@ -545,6 +560,16 @@ char **process_options(int argc, char **argv)
err_usage();
}
if (options.nohwbp) {
fprintf(stderr, "%s: client mode does not require -N\n", progname);
err_usage();
}
if (options.sanity) {
fprintf(stderr, "%s: client mode does not require -S\n", progname);
err_usage();
}
if (options.user) {
fprintf(stderr, "%s: user can only passed in trace mode\n", progname);
err_usage();
@ -583,6 +608,10 @@ char **process_options(int argc, char **argv)
options.sort_by = -1;
}
}
else {
if (options.sanity)
options.sort_by = OPT_SORT_MISMATCHED;
}
if (output) {
if (options.interactive) {

View File

@ -37,6 +37,7 @@
#define OPT_SORT_TOTAL 5
#define OPT_SORT_TSC 6
#define OPT_SORT_USAGE 7
#define OPT_SORT_MISMATCHED 8
struct options_t options;
@ -83,8 +84,10 @@ struct options_t {
struct opt_b_t *opt_b; /* binary search path(s) */
struct opt_O_t *opt_O; /* omits path list */
int sort_by; /* sort dump in non interative and non server mode */
int sanity; /* check mismatching operations against new/new[] allocations */
int debug; /* debug */
int nocpp; /* disable trace of c++ allocation operators */
int nohwbp; /* disable hardware breakpoint support */
};
char **process_options(int argc, char **argv);

View File

@ -50,6 +50,7 @@ static int report_alloc64(struct task *task, enum mt_operation op, unsigned long
alloc->size = (uint64_t)size;
if (depth) {
if (libsym)
alloc->data[i++] = libsym->addr;
if (backtrace_init_unwind(task) >= 0) {
@ -83,6 +84,7 @@ static int report_alloc32(struct task *task, enum mt_operation op, unsigned long
alloc->size = (uint32_t)size;
if (depth) {
if (libsym)
alloc->data[i++] = libsym->addr;
if (backtrace_init_unwind(task) >= 0) {
@ -128,7 +130,7 @@ static int _null(struct task *task, struct library_symbol *libsym)
return 0;
}
static int _report_malloc(struct task *task, struct library_symbol *libsym)
static int _report_alloc_op(struct task *task, struct library_symbol *libsym, enum mt_operation op)
{
if (!server_connected())
return -1;
@ -136,17 +138,47 @@ static int _report_malloc(struct task *task, struct library_symbol *libsym)
unsigned long size = fetch_param(task, 0);
unsigned long ret = fetch_retval(task);
return report_alloc(task, MT_MALLOC, ret, size, options.bt_depth, libsym);
return report_alloc(task, op, ret, size, options.bt_depth, libsym);
}
static int report_free(struct task *task, struct library_symbol *libsym)
static int _report_malloc(struct task *task, struct library_symbol *libsym)
{
return _report_alloc_op(task, libsym, MT_MALLOC);
}
static int _report_new(struct task *task, struct library_symbol *libsym)
{
return _report_alloc_op(task, libsym, options.sanity ? MT_NEW : MT_MALLOC);
}
static int _report_new_array(struct task *task, struct library_symbol *libsym)
{
return _report_alloc_op(task, libsym, options.sanity ? MT_NEW_ARRAY : MT_MALLOC);
}
static int _report_free_op(struct task *task, struct library_symbol *libsym, enum mt_operation op)
{
if (!server_connected())
return -1;
unsigned long addr = fetch_param(task, 0);
return report_alloc(task, MT_FREE, addr, 0, 0, libsym);
return report_alloc(task, op, addr, 0, options.sanity ? options.bt_depth : 0, NULL);
}
static int report_free(struct task *task, struct library_symbol *libsym)
{
return _report_free_op(task, libsym, MT_FREE);
}
static int report_delete(struct task *task, struct library_symbol *libsym)
{
return _report_free_op(task, libsym, options.sanity ? MT_DELETE : MT_FREE);
}
static int report_delete_array(struct task *task, struct library_symbol *libsym)
{
return _report_free_op(task, libsym, options.sanity ? MT_DELETE_ARRAY : MT_FREE);
}
static int _report_realloc(struct task *task, struct library_symbol *libsym)
@ -345,20 +377,20 @@ static const struct function flist[] = {
{ "mremap", "mremap", 0, report_mremap, _report_mremap },
{ "cfree", "cfree", 1, report_free, NULL },
{ "new(unsigned int)", "_Znwj", 1, NULL, _report_malloc },
{ "new[](unsigned int)", "_Znaj", 1, NULL, _report_malloc },
{ "new(unsigned int, std::nothrow_t const&)", "_ZnwjRKSt9nothrow_t", 1, NULL, _report_malloc },
{ "new[](unsigned int, std::nothrow_t const&)", "_ZnajRKSt9nothrow_t", 1, NULL, _report_malloc },
{ "new(unsigned int)", "_Znwj", 1, NULL, _report_new },
{ "new[](unsigned int)", "_Znaj", 1, NULL, _report_new_array },
{ "new(unsigned int, std::nothrow_t const&)", "_ZnwjRKSt9nothrow_t", 1, NULL, _report_new },
{ "new[](unsigned int, std::nothrow_t const&)", "_ZnajRKSt9nothrow_t", 1, NULL, _report_new_array },
{ "new(unsigned long)", "_Znwm", 1, NULL, _report_malloc },
{ "new[](unsigned long)", "_Znam", 1, NULL, _report_malloc },
{ "new(unsigned long, std::nothrow_t const&)", "_ZnwmRKSt9nothrow_t", 1, NULL, _report_malloc },
{ "new[](unsigned long, std::nothrow_t const&)", "_ZnamRKSt9nothrow_t", 1, NULL, _report_malloc },
{ "new(unsigned long)", "_Znwm", 1, NULL, _report_new },
{ "new[](unsigned long)", "_Znam", 1, NULL, _report_new_array },
{ "new(unsigned long, std::nothrow_t const&)", "_ZnwmRKSt9nothrow_t", 1, NULL, _report_new },
{ "new[](unsigned long, std::nothrow_t const&)", "_ZnamRKSt9nothrow_t", 1, NULL, _report_new_array },
{ "delete(void*)", "_ZdlPv", 1, report_free, NULL },
{ "delete[](void*)", "_ZdaPv", 1, report_free, NULL },
{ "delete(void*, std::nothrow_t const&)", "_ZdlPvRKSt9nothrow_t", 1, report_free, NULL },
{ "delete[](void*, std::nothrow_t const&)", "_ZdaPvRKSt9nothrow_t", 1, report_free, NULL },
{ "delete(void*)", "_ZdlPv", 1, report_delete, NULL },
{ "delete[](void*)", "_ZdaPv", 1, report_delete_array, NULL },
{ "delete(void*, std::nothrow_t const&)", "_ZdlPvRKSt9nothrow_t", 1, report_delete, NULL },
{ "delete[](void*, std::nothrow_t const&)", "_ZdaPvRKSt9nothrow_t", 1, report_delete_array, NULL },
};
const struct function *flist_matches_symbol(const char *sym_name)