Skip to content

Commit

Permalink
Merge branch 'watch-variable'
Browse files Browse the repository at this point in the history
Support watchpoints (-W option) for global variables.  This time it can
only handle integer types.

Signed-off-by: Namhyung Kim <namhyung@gmail.com>
  • Loading branch information
namhyung committed Jun 11, 2024
2 parents 1fdd8ce + 0096460 commit 3ad5f45
Show file tree
Hide file tree
Showing 13 changed files with 346 additions and 18 deletions.
15 changes: 14 additions & 1 deletion cmds/dump.c
Original file line number Diff line number Diff line change
Expand Up @@ -497,7 +497,20 @@ static void pr_retval(struct uftrace_fstack_args *args)
static void pr_event(struct uftrace_task_reader *task, unsigned evt_id)
{
char *evt_name = event_get_name(task->h, evt_id);
char *evt_data = event_get_data_str(evt_id, task->args.data, false);
struct uftrace_symbol *sym = NULL;
char *evt_data;

if (evt_id == EVENT_ID_WATCH_VAR) {
unsigned long long addr = 0;

if (data_is_lp64(task->h))
memcpy(&addr, task->args.data, 8);
else
memcpy(&addr, task->args.data, 4);

sym = task_find_sym_addr(&task->h->sessions, task, task->ustack.time, addr);
}
evt_data = event_get_data_str(task->h, evt_id, task->args.data, task->args.len, sym, false);

pr_out(" %s", evt_name);
if (evt_data)
Expand Down
23 changes: 20 additions & 3 deletions cmds/replay.c
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,6 @@ static void print_event(struct uftrace_task_reader *task, struct uftrace_record
{
unsigned evt_id = urec->addr;
char *evt_name = event_get_name(task->h, evt_id);
char *evt_data = event_get_data_str(evt_id, task->args.data, true);

if (evt_id == EVENT_ID_EXTERN_DATA) {
pr_color(color, "%s: %s", evt_name, (char *)task->args.data);
Expand All @@ -290,14 +289,32 @@ static void print_event(struct uftrace_task_reader *task, struct uftrace_record
pr_color(color, "%s", evt_name);
}
else {
char *evt_data;
struct uftrace_symbol *sym = NULL;

if (evt_id == EVENT_ID_WATCH_VAR) {
unsigned long long addr = 0;

if (data_is_lp64(task->h))
memcpy(&addr, task->args.data, 8);
else
memcpy(&addr, task->args.data, 4);

sym = task_find_sym_addr(&task->h->sessions, task, task->ustack.time, addr);
}

evt_data = event_get_data_str(task->h, evt_id, task->args.data, task->args.len, sym,
true);

pr_color(color, "%s", evt_name);

if (evt_data)
if (evt_data) {
pr_color(color, " (%s)", evt_data);
free(evt_data);
}
}

free(evt_name);
free(evt_data);
}

static int print_flat_rstack(struct uftrace_data *handle, struct uftrace_task_reader *task,
Expand Down
15 changes: 14 additions & 1 deletion cmds/script.c
Original file line number Diff line number Diff line change
Expand Up @@ -123,9 +123,22 @@ static int run_script_for_rstack(struct uftrace_data *handle, struct uftrace_tas
.timestamp = rstack->time,
.address = rstack->addr,
};
struct uftrace_symbol *watch_sym = NULL;

if (rstack->addr == EVENT_ID_WATCH_VAR) {
unsigned long long addr = 0;

if (data_is_lp64(task->h))
memcpy(&addr, task->args.data, 8);
else
memcpy(&addr, task->args.data, 4);

watch_sym = task_find_sym_addr(sessions, task, rstack->time, addr);
}

sc_ctx.name = event_get_name(handle, rstack->addr);
sc_ctx.argbuf = event_get_data_str(rstack->addr, task->args.data, false);
sc_ctx.argbuf = event_get_data_str(handle, rstack->addr, task->args.data,
task->args.len, watch_sym, false);

script_uftrace_event(&sc_ctx);

Expand Down
16 changes: 16 additions & 0 deletions doc/uftrace-record.md
Original file line number Diff line number Diff line change
Expand Up @@ -889,6 +889,7 @@ so it might miss some updates.
As of now, following watch points are supported:

* "cpu" : cpu number current task is running on
* "var": read value of a global variable given after ":".

Like read triggers, the result is displayed as event (comment):

Expand All @@ -905,6 +906,21 @@ Like read triggers, the result is displayed as event (comment):
9.350 us [ 19060] | } /* a */
12.479 us [ 19060] | } /* main */

This is how to use 'var' watchpoints:

$ uftrace -W var:mydata a.out
# DURATION TID FUNCTION
[239842] | __monstartup() {
[239842] | /* watch:var (mydata=7) */
3.534 us [239842] | } /* __monstartup */
0.191 us [239842] | __cxa_atexit();
[239842] | main() {
[239842] | foo() {
[239842] | /* watch:var (mydata=42) */
0.381 us [239842] | bar();
1.069 us [239842] | } /* foo */
2.698 us [239842] | } /* main */


SEE ALSO
========
Expand Down
14 changes: 13 additions & 1 deletion libmcount/internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,19 @@ struct mcount_event {

#define MAX_EVENT 4

enum mcount_watch_item {
enum mcount_watch_kind {
MCOUNT_WATCH_NONE = 0,
MCOUNT_WATCH_CPU = (1 << 0),
MCOUNT_WATCH_VAR = (1 << 1),
};

struct mcount_watchpoint_item {
struct list_head list;
unsigned long addr;
unsigned short kind;
unsigned char size;
unsigned char inited;
unsigned char data[];
};

struct mcount_watchpoint {
Expand All @@ -104,6 +114,7 @@ struct mcount_watchpoint {
int cpu;

/* global watch points */
struct list_head list;
};

#ifndef DISABLE_MCOUNT_FILTER
Expand Down Expand Up @@ -429,6 +440,7 @@ bool check_mem_region(struct mcount_arg_context *ctx, unsigned long addr);

void save_watchpoint(struct mcount_thread_data *mtdp, struct mcount_ret_stack *rstack,
unsigned long watchpoints);
bool mcount_watch_update(unsigned long addr, void *data, int size);

struct mcount_event_info {
char *module;
Expand Down
98 changes: 96 additions & 2 deletions libmcount/mcount.c
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,9 @@ __weak void dynamic_return(void)
{
}

/* list of watch points (of global variables) */
static LIST_HEAD(mcount_watch_list);

#ifdef DISABLE_MCOUNT_FILTER

static void mcount_filter_init(struct uftrace_filter_setting *filter_setting, bool force)
Expand All @@ -121,6 +124,10 @@ static void mcount_filter_finish(void)
finish_debug_info(&mcount_sym_info);
}

static void mcount_watch_finish(void)
{
}

#else

/* be careful: this can be called from signal handler */
Expand Down Expand Up @@ -471,19 +478,105 @@ static void mcount_watch_init(void)
strv_split(&watch, watch_str, ";");

strv_for_each(&watch, str, i) {
if (!strcasecmp(str, "cpu"))
mcount_watchpoints = MCOUNT_WATCH_CPU;
if (!strcasecmp(str, "cpu")) {
mcount_watchpoints |= MCOUNT_WATCH_CPU;
continue;
}

if (!strncasecmp(str, "var:", 4)) {
struct mcount_watchpoint_item *w;
struct uftrace_mmap *map = mcount_sym_info.exec_map;
struct uftrace_symbol *sym;

w = xmalloc(sizeof(*w));
sym = find_symname(&map->mod->symtab, str + 4);
if (sym == NULL) {
pr_dbg("ignore watchpoint for %s\n", str);
free(w);
continue;
}

w->kind = MCOUNT_WATCH_VAR;
w->addr = map->start + sym->addr;
w->size = sym->size;
if (w->size > 8) {
pr_dbg("symbol is too big, ignored... %s\n", str);
free(w);
continue;
}

list_add_tail(&w->list, &mcount_watch_list);

mcount_watchpoints |= MCOUNT_WATCH_VAR;
continue;
}
}
strv_free(&watch);
}

static void mcount_watch_finish(void)
{
struct mcount_watchpoint_item *w;

while (!list_empty(&mcount_watch_list)) {
w = list_first_entry(&mcount_watch_list, typeof(*w), list);
list_del(&w->list);
free(w);
}
}

bool mcount_watch_update(unsigned long addr, void *data, int size)
{
static pthread_mutex_t watch_mutex = PTHREAD_MUTEX_INITIALIZER;
struct mcount_watchpoint_item *w;
bool updated = false;

pthread_mutex_lock(&watch_mutex);
list_for_each_entry(w, &mcount_watch_list, list) {
if (w->addr != addr)
continue;

/* someone already updated for us? */
if (w->inited && !memcmp(data, w->data, size))
break;

mcount_memcpy1(w->data, data, size);
w->inited = true;
updated = true;
break;
}
pthread_mutex_unlock(&watch_mutex);

return updated;
}

static void mcount_watch_setup(struct mcount_thread_data *mtdp)
{
struct mcount_watchpoint_item *pos, *w;

mtdp->watch.cpu = -1;
INIT_LIST_HEAD(&mtdp->watch.list);

/* each thread gets a copy of the global watch items */
list_for_each_entry(pos, &mcount_watch_list, list) {
w = xmalloc(sizeof(*w) + pos->size);

memcpy(w, pos, sizeof(*w));
memcpy(w->data, (void *)w->addr, w->size);

list_add_tail(&w->list, &mtdp->watch.list);
}
}

static void mcount_watch_release(struct mcount_thread_data *mtdp)
{
struct mcount_watchpoint_item *w;

while (!list_empty(&mtdp->watch.list)) {
w = list_first_entry(&mtdp->watch.list, typeof(*w), list);
list_del(&w->list);
free(w);
}
}

#endif /* DISABLE_MCOUNT_FILTER */
Expand Down Expand Up @@ -1984,6 +2077,7 @@ static void mcount_cleanup(void)
#endif

mcount_filter_finish();
mcount_watch_finish();

if (SCRIPT_ENABLED && script_str)
script_finish();
Expand Down
30 changes: 30 additions & 0 deletions libmcount/record.c
Original file line number Diff line number Diff line change
Expand Up @@ -785,6 +785,36 @@ void save_watchpoint(struct mcount_thread_data *mtdp, struct mcount_ret_stack *r
}
mtdp->watch.cpu = cpu;
}

if (watchpoints & MCOUNT_WATCH_VAR) {
struct mcount_watchpoint_item *w;
unsigned long watch_data = 0;
struct mcount_event *event;

list_for_each_entry(w, &mtdp->watch.list, list) {
if (mtdp->nr_events >= MAX_EVENT)
continue;

/* check the data without lock first */
mcount_memcpy1(&watch_data, (void *)w->addr, w->size);
if (!memcmp(&watch_data, w->data, w->size))
continue;

/* make sure only one thread updates the watch data */
if (!mcount_watch_update(w->addr, &watch_data, w->size))
continue;

event = &mtdp->event[mtdp->nr_events++];

event->id = EVENT_ID_WATCH_VAR;
event->time = timestamp;
event->idx = rstack_idx;
event->dsize = sizeof(long) + w->size;

mcount_memcpy4(event->data, &w->addr, sizeof(long));
mcount_memcpy1(event->data + sizeof(long), &watch_data, w->size);
}
}
}

#else
Expand Down
27 changes: 27 additions & 0 deletions tests/s-watch-global.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#include <stdlib.h>

volatile int mydata;

void bar(int n)
{
if (n)
mydata = -1;
}

void foo(int n)
{
mydata = 1;
bar(n);
mydata = 2;
}

int main(int argc, char *argv[])
{
int n = 0;

if (argc > 1)
n = atoi(argv[1]);

foo(n);
return 0;
}
25 changes: 25 additions & 0 deletions tests/t290_watch_global.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#!/usr/bin/env python3

import re

from runtest import TestBase

class TestCase(TestBase):
def __init__(self):
TestBase.__init__(self, 'watch-global', result="""
# DURATION TID FUNCTION
[243156] | __monstartup() {
[243156] | /* watch:var (mydata=0) */
2.803 us [243156] | } /* __monstartup */
0.621 us [243156] | __cxa_atexit();
[243156] | main() {
[243156] | foo() {
[243156] | /* watch:var (mydata=1) */
0.117 us [243156] | bar();
[243156] | /* watch:var (mydata=2) */
0.643 us [243156] | } /* foo */
0.938 us [243156] | } /* main */
""")

def setup(self):
self.option = '-W var:mydata'
Loading

0 comments on commit 3ad5f45

Please sign in to comment.