diff options
author | Jiri Olsa <jolsa@shell.devel.redhat.com> | 2009-09-04 02:29:08 -0400 |
---|---|---|
committer | Jiri Olsa <jolsa@shell.devel.redhat.com> | 2009-09-04 02:29:08 -0400 |
commit | 04f3fbbfdb3a5dd197dbc25ca18ad244f1fbf6a5 (patch) | |
tree | 8e53039a4f5d1a4571000bc06214053261aebf8d /src | |
download | latrace-04f3fbbfdb3a5dd197dbc25ca18ad244f1fbf6a5.tar.gz latrace-04f3fbbfdb3a5dd197dbc25ca18ad244f1fbf6a5.tar.xz latrace-04f3fbbfdb3a5dd197dbc25ca18ad244f1fbf6a5.zip |
initial commit - 0.5.7
Diffstat (limited to 'src')
-rw-r--r-- | src/Makefile | 67 | ||||
-rw-r--r-- | src/args-bison.y | 302 | ||||
-rw-r--r-- | src/args-flex.l | 89 | ||||
-rw-r--r-- | src/args.c | 1066 | ||||
-rw-r--r-- | src/audit-init.c | 196 | ||||
-rw-r--r-- | src/audit.c | 287 | ||||
-rw-r--r-- | src/audit.h | 102 | ||||
-rw-r--r-- | src/autoconf.h.in | 36 | ||||
-rw-r--r-- | src/autoconf.make.in | 42 | ||||
-rw-r--r-- | src/config.c | 293 | ||||
-rw-r--r-- | src/config.h | 417 | ||||
-rw-r--r-- | src/fifo.c | 150 | ||||
-rw-r--r-- | src/latrace.c | 45 | ||||
-rw-r--r-- | src/list.h | 103 | ||||
-rw-r--r-- | src/objsearch.c | 192 | ||||
-rw-r--r-- | src/output.c | 125 | ||||
-rw-r--r-- | src/run.c | 319 | ||||
-rw-r--r-- | src/stats.c | 328 | ||||
-rw-r--r-- | src/sysdeps/arm/stack.c | 269 | ||||
-rw-r--r-- | src/sysdeps/i686/stack.c | 131 | ||||
-rw-r--r-- | src/sysdeps/i686/stack.h | 27 | ||||
-rw-r--r-- | src/sysdeps/x86_64/stack.c | 615 | ||||
-rw-r--r-- | src/sysdeps/x86_64/stack.h | 90 | ||||
-rw-r--r-- | src/thread.c | 61 |
24 files changed, 5352 insertions, 0 deletions
diff --git a/src/Makefile b/src/Makefile new file mode 100644 index 0000000..93f01ad --- /dev/null +++ b/src/Makefile @@ -0,0 +1,67 @@ +# Copyright (C) 2008, 2009 Jiri Olsa <olsajiri@gmail.com> +# +# This file is part of the latrace. +# +# The latrace is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# The latrace 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with the latrace (file COPYING). If not, see +# <http://www.gnu.org/licenses/>. + + +# libltaudit.so +AUDIT_BIN=libltaudit.so.$(LT_VER) +AUDIT_LDFLAGS="-Wl,-init=audit_init" "-Wl,-soname,$(AUDIT_BIN)" -fPIC -shared +AUDIT_LIBS=-liberty +AUDIT_OBJS=\ + src/audit.o \ + src/audit-init.o \ + src/fifo.o \ + src/args-bison.o \ + src/args-flex.o \ + src/args.o \ + src/output.o \ + src/objsearch.o \ + src/sysdeps/$(CONFIG_SYSDEP_DIR)/stack.o + +OBJS+=$(AUDIT_OBJS) +PROGRAMS+= $(AUDIT_BIN) + +$(AUDIT_BIN): $(AUDIT_OBJS) + $(QUIET_LD)$(CC) $(AUDIT_LDFLAGS) -o $@ $(AUDIT_OBJS) $(AUDIT_LIBS) + +install:: + $(call install,$(AUDIT_BIN),$(libdir),755) + +# latrace binary +LATRACE_BIN=latrace +LATRACE_LIB=-liberty +LATRACE_OBJS=\ + src/latrace.o \ + src/config.o \ + src/run.o \ + src/stats.o \ + src/fifo.o \ + src/thread.o \ + src/output.o + +OBJS+=$(LATRACE_OBJS) +PROGRAMS+=$(LATRACE_BIN) +CPPFLAGS+=-DCONFIG_LIBDIR=\"$(libdir)\" +CPPFLAGS+=-DLT_ARGS_DEF_DIR=\"$(confdir)\" +CPPFLAGS+=-DLT_ARGS_DEF_CONF=\"$(sysconfdir)/latrace.conf\" + +$(LATRACE_BIN): $(LATRACE_OBJS) + $(QUIET_LD)$(CC) $(LDFLAGS) -o $@ $(LATRACE_OBJS) $(LATRACE_LIBS) $(LATRACE_LIB) + +install:: + $(call install,$(LATRACE_BIN),$(bindir),755) + diff --git a/src/args-bison.y b/src/args-bison.y new file mode 100644 index 0000000..64c88be --- /dev/null +++ b/src/args-bison.y @@ -0,0 +1,302 @@ +/* + Copyright (C) 2008, 2009 Jiri Olsa <olsajiri@gmail.com> + + This file is part of the latrace. + + The latrace is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + The latrace 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with the latrace (file COPYING). If not, see + <http://www.gnu.org/licenses/>. +*/ + + +%{ + +#define YYERROR_VERBOSE 1 +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "config.h" + +int yylex (void); +void yyerror(const char *m); + +static struct lt_config_shared *scfg; +static int struct_alive = 0; + +#define ERROR(fmt, args...) \ +do { \ + char ebuf[1024]; \ + sprintf(ebuf, fmt, ## args); \ + yyerror(ebuf); \ + YYERROR; \ +} while(0) + +#define CHK_TYPEDEF(ret, base, new, pointer) \ +do { \ + switch(ret) { \ + case -1: \ + ERROR("unknown typedef - %s%s%s\n", base, (pointer ? "* " : " "), new); \ + break; \ + case 1: \ + ERROR("typedef alrady defined - %s%s%s\n", base, (pointer ? "* " : " "), new); \ + break; \ + case 2: \ + ERROR("typedef limit reached(%d) - %s%s%s\n", \ + LT_ARGS_DEF_TYPEDEF_NUM, base, (pointer ? "* " : " "), new); \ + break; \ + }; \ +} while(0) + +#define GET_LIST_HEAD(head) \ +do { \ + if (NULL == (head = (struct lt_list_head*) malloc(sizeof(*head)))) \ + ERROR("failed to allocate list head"); \ + lt_init_list_head(head); \ +} while(0) + +%} + +%token NAME FILENAME STRUCT ENUM TYPEDEF INCLUDE END POINTER + +%union +{ + char *s; + struct lt_arg *arg; + struct lt_enum_elem *enum_elem; + struct lt_list_head *head; +} + +%type <s> NAME +%type <s> FILENAME +%type <head> STRUCT_DEF +%type <head> ENUM_DEF +%type <s> ENUM_REF +%type <enum_elem> ENUM_ELEM +%type <head> ARGS +%type <arg> DEF + +%% +entry: +entry struct_def +| +entry enum_def +| +entry func_def +| +entry type_def +| +entry include_def +| +entry END +{ + if (lt_args_buf_close(scfg)) + return 0; +} +| +/* left blank intentionally */ + +/* struct definitions */ +struct_def: +STRUCT NAME '{' STRUCT_DEF '}' ';' +{ + switch(lt_args_add_struct(scfg, $2, $4)) { + case -1: + ERROR("failed to add struct %s\n", $2); + case 1: + ERROR("struct limit reached(%d) - %s\n", LT_ARGS_DEF_STRUCT_NUM, $2); + }; + + /* force creation of the new list head */ + struct_alive = 0; +} + +STRUCT_DEF: +STRUCT_DEF DEF ';' +{ + struct lt_arg *def = $2; + struct lt_list_head *h = $1; + + if (!struct_alive++) + GET_LIST_HEAD(h); + + lt_list_add_tail(&def->args_list, h); + $$ = h; +} +| /* left blank intentionally, + XXX this could be done like the args_def, but user needs to be + able to create an empty structure, so thats why we play + with the global struct_alive thingie... + there could be better way probably */ +{ +} + +/* enum definitions */ +enum_def: +ENUM NAME '{' ENUM_DEF '}' ';' +{ + switch(lt_args_add_enum(scfg, $2, $4)) { + case -1: + ERROR("failed to add enum %s\n", $2); + case 1: + ERROR("enum limit reached(%d) - %s\n", LT_ARGS_DEF_STRUCT_NUM, $2); + }; +} + +ENUM_DEF: +ENUM_DEF ',' ENUM_ELEM +{ + struct lt_enum_elem *enum_elem = $3; + struct lt_list_head *h = $1; + + lt_list_add_tail(&enum_elem->list, h); + $$ = h; +} +| ENUM_ELEM +{ + struct lt_list_head *h; + struct lt_enum_elem *enum_elem = $1; + + GET_LIST_HEAD(h); + lt_list_add_tail(&enum_elem->list, h); + $$ = h; +} + +ENUM_ELEM: +NAME '=' NAME +{ + if (NULL == ($$ = lt_args_get_enum(scfg, $1, $3))) + ERROR("failed to add enum '%s = %s'\n", $1, $3); +} +| +NAME +{ + if (NULL == ($$ = lt_args_get_enum(scfg, $1, NULL))) + ERROR("failed to add enum '%s = undef'\n", $1); +} + +type_def: +TYPEDEF NAME NAME ';' +{ + int ret = lt_args_add_typedef(scfg, $2, $3, 0); + CHK_TYPEDEF(ret, $2, $3, 0); +} +| +TYPEDEF NAME POINTER NAME ';' +{ + int ret = lt_args_add_typedef(scfg, $2, $4, 1); + CHK_TYPEDEF(ret, $2, $4, 1); +} + +/* function definitions */ +func_def: +DEF '(' ARGS ')' ';' +{ + struct lt_arg *arg = $1; + + if (lt_args_add_sym(scfg, arg, $3)) + ERROR("failed to add symbol %s\n", arg->name); + + /* force cration of the new list head */ + $3 = NULL; +} + +ARGS: +ARGS ',' DEF +{ + struct lt_arg *def = $3; + struct lt_list_head *h = $1; + + lt_list_add_tail(&def->args_list, h); + $$ = h; +} +| DEF +{ + struct lt_list_head *h; + struct lt_arg *def = $1; + + GET_LIST_HEAD(h); + lt_list_add_tail(&def->args_list, h); + $$ = h; +} +| NAME +{ + GET_LIST_HEAD($$); +} +| /* left intentionaly blank */ +{ + GET_LIST_HEAD($$); +} + +DEF: +NAME NAME ENUM_REF +{ + struct lt_arg *arg; + + if (NULL == (arg = lt_args_getarg(scfg, $1, $2, 0, 1, $3))) + ERROR("unknown argument type - %s\n", $1); + + $$ = arg; +} +| +NAME POINTER NAME ENUM_REF +{ + struct lt_arg *arg; + if (NULL == (arg = lt_args_getarg(scfg, $1, $3, 1, 1, $4))) + ERROR("unknown argument type - %s\n", $1); + + $$ = arg; +} +| +STRUCT NAME NAME +{ + struct lt_arg *arg; + if (NULL == (arg = lt_args_getarg(scfg, $2, $3, 0, 1, NULL))) + ERROR("unknown argument type - %s\n", $2); + + $$ = arg; +} +| +STRUCT NAME POINTER NAME ENUM_REF +{ + struct lt_arg *arg; + if (NULL == (arg = lt_args_getarg(scfg, $2, $4, 1, 1, $5))) + ERROR("unknown argument type - %s\n", $2); + + $$ = arg; +} + +ENUM_REF: +'=' NAME +{ + $$ = $2; +} +| +{ + $$ = NULL; +} + +/* include definitions */ +include_def: INCLUDE '"' FILENAME '"' +{ + if (lt_args_buf_open(scfg, $3)) + ERROR("failed to process include"); +} + +%% + +int lt_args_parse_init(struct lt_config_shared *cfg) +{ + scfg = cfg; + return 0; +} diff --git a/src/args-flex.l b/src/args-flex.l new file mode 100644 index 0000000..e00b619 --- /dev/null +++ b/src/args-flex.l @@ -0,0 +1,89 @@ +/* + Copyright (C) 2008, 2009 Jiri Olsa <olsajiri@gmail.com> + + This file is part of the latrace. + + The latrace is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + The latrace 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with the latrace (file COPYING). If not, see + <http://www.gnu.org/licenses/>. +*/ + + +%{ + +#include <string.h> + +#include "config.h" +#include "args-bison.h" + +struct lt_args_include* lt_args_buf_get(void); + +%} + +alphnum [-0-9a-zA-Z_] +name ({alphnum})+ +filename ([-0-9a-zA-Z\./_])+ + +%x comment include +%% + +"/*" BEGIN(comment); +<comment>[^*\n]* /* eat anything that's not a '*' */ +<comment>"*"+[^*/\n]* /* eat up '*'s not followed by '/'s */ +<comment>\n { lt_args_buf_get()->lineno++; } +<comment>"*"+"/" BEGIN(INITIAL); + +"#include" { BEGIN(include); return INCLUDE; } +<include>{filename} { yylval.s = strdup(yytext); return FILENAME; } +<include>"\"" { return '"'; } +<include>\n { BEGIN(INITIAL); } +<include>. { ; } + +"extern" { ; } +"const" { ; } +<<EOF>> { return END; } +"struct" { return STRUCT; } +"enum" { return ENUM; } +"typedef" { return TYPEDEF; } +{name} { yylval.s = strdup(yytext); return NAME; } +"\*"+ { return POINTER; } +")" { return ')'; } +"(" { return '('; } +"}" { return '}'; } +"{" { return '{'; } +";" { return ';'; } +"," { return ','; } +"=" { return '='; } +\ { ; } +\n { lt_args_buf_get()->lineno++; } +. { ; } + +%% + +#ifndef yywrap +int yywrap() +{ + return 1; + /* XXX not to get the compiler 'not used' warning */ + yyunput(0, NULL); + input(); +} +#endif + +void yyerror(const char *m) +{ + printf("latrace config file [%s] line %d: %s\n", + lt_args_buf_get()->file, + lt_args_buf_get()->lineno, + m); +} diff --git a/src/args.c b/src/args.c new file mode 100644 index 0000000..4bb1252 --- /dev/null +++ b/src/args.c @@ -0,0 +1,1066 @@ +/* + Copyright (C) 2008, 2009 Jiri Olsa <olsajiri@gmail.com> + + This file is part of the latrace. + + The latrace is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + The latrace 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with the latrace (file COPYING). If not, see + <http://www.gnu.org/licenses/>. +*/ + + +#include <stdlib.h> +#include <string.h> +#include <search.h> +#include <setjmp.h> +#include <errno.h> +#include <limits.h> +#include <stdio.h> + +#include "config.h" + + +#define YY_BUF_SIZE 16384 +#define MAX_INCLUDE_DEPTH 10 +#define LT_EQUAL " = " + + +extern int errno; + +typedef struct yy_buffer_state *YY_BUFFER_STATE; +YY_BUFFER_STATE yy_create_buffer(FILE *file, int size); +extern FILE *yyin; + +int yyparse(); +void yy_switch_to_buffer (YY_BUFFER_STATE new_buffer); +void yy_delete_buffer(YY_BUFFER_STATE b); + +int lt_args_parse_init(struct lt_config_shared *cfg); +static struct lt_args_include include_stack[MAX_INCLUDE_DEPTH]; +static int include_stack_ptr = 0; +static int enum_init = 0; + + +/* hardcoded POD types */ +static struct lt_arg args_def_pod[] = { + { + .dtype = LT_ARGS_DTYPE_POD, + .type_id = LT_ARGS_TYPEID_VOID, + .type_len = sizeof(void), + .type_name = "void", + .pointer = 0, + .name = "", + .mmbcnt = 0, + .arch = NULL, + .en = NULL, + .args_head = NULL, + .args_list = { NULL, NULL } + }, + { + .dtype = LT_ARGS_DTYPE_POD, + .type_id = LT_ARGS_TYPEID_SHORT, + .type_len = sizeof(short), + .type_name = "short", + .pointer = 0, + .name = "", + .mmbcnt = 0, + .arch = NULL, + .en = NULL, + .args_head = NULL, + .args_list = { NULL, NULL } + }, + { + .dtype = LT_ARGS_DTYPE_POD, + .type_id = LT_ARGS_TYPEID_USHORT, + .type_len = sizeof(unsigned short), + .type_name = "u_short", + .pointer = 0, + .name = "", + .mmbcnt = 0, + .arch = NULL, + .en = NULL, + .args_head = NULL, + .args_list = { NULL, NULL } + }, + { + .dtype = LT_ARGS_DTYPE_POD, + .type_id = LT_ARGS_TYPEID_INT, + .type_len = sizeof(int), + .type_name = "int", + .pointer = 0, + .name = "", + .mmbcnt = 0, + .arch = NULL, + .en = NULL, + .args_head = NULL, + .args_list = { NULL, NULL } + }, + { + .dtype = LT_ARGS_DTYPE_POD, + .type_id = LT_ARGS_TYPEID_UINT, + .type_len = sizeof(unsigned int), + .type_name = "u_int", + .pointer = 0, + .name = "", + .mmbcnt = 0, + .arch = NULL, + .en = NULL, + .args_head = NULL, + .args_list = { NULL, NULL } + }, + { + .dtype = LT_ARGS_DTYPE_POD, + .type_id = LT_ARGS_TYPEID_LONG, + .type_len = sizeof(long), + .type_name = "long", + .pointer = 0, + .name = "", + .mmbcnt = 0, + .arch = NULL, + .en = NULL, + .args_head = NULL, + .args_list = { NULL, NULL } + }, + { + .dtype = LT_ARGS_DTYPE_POD, + .type_id = LT_ARGS_TYPEID_ULONG, + .type_len = sizeof(unsigned long), + .type_name = "u_long", + .pointer = 0, + .name = "", + .mmbcnt = 0, + .arch = NULL, + .en = NULL, + .args_head = NULL, + .args_list = { NULL, NULL } + }, + { + .dtype = LT_ARGS_DTYPE_POD, + .type_id = LT_ARGS_TYPEID_CHAR, + .type_len = sizeof(char), + .type_name = "char", + .pointer = 0, + .name = "", + .mmbcnt = 0, + .arch = NULL, + .en = NULL, + .args_head = NULL, + .args_list = { NULL, NULL } + }, + { + .dtype = LT_ARGS_DTYPE_POD, + .type_id = LT_ARGS_TYPEID_UCHAR, + .type_len = sizeof(unsigned char), + .type_name = "u_char", + .pointer = 0, + .name = "", + .mmbcnt = 0, + .arch = NULL, + .en = NULL, + .args_head = NULL, + .args_list = { NULL, NULL } + }, + { + .dtype = LT_ARGS_DTYPE_POD, + .type_id = LT_ARGS_TYPEID_LLONG, + .type_len = sizeof(long long), + .type_name = "llong", + .pointer = 0, + .name = "", + .mmbcnt = 0, + .arch = NULL, + .en = NULL, + .args_head = NULL, + .args_list = { NULL, NULL } + }, + { + .dtype = LT_ARGS_DTYPE_POD, + .type_id = LT_ARGS_TYPEID_ULLONG, + .type_len = sizeof(unsigned long long), + .type_name = "u_llong", + .pointer = 0, + .name = "", + .mmbcnt = 0, + .arch = NULL, + .en = NULL, + .args_head = NULL, + .args_list = { NULL, NULL } + }, + { + .dtype = LT_ARGS_DTYPE_POD, + .type_id = LT_ARGS_TYPEID_DOUBLE, + .type_len = sizeof(double), + .type_name = "double", + .pointer = 0, + .name = "", + .mmbcnt = 0, + .arch = NULL, + .en = NULL, + .args_head = NULL, + .args_list = { NULL, NULL } + }, + { + .dtype = LT_ARGS_DTYPE_POD, + .type_id = LT_ARGS_TYPEID_FLOAT, + .type_len = sizeof(float), + .type_name = "float", + .pointer = 0, + .name = "", + .mmbcnt = 0, + .arch = NULL, + .en = NULL, + .args_head = NULL, + .args_list = { NULL, NULL } + }, +}; + +#define LT_ARGS_DEF_POD_NUM (sizeof(args_def_pod)/sizeof(struct lt_arg)) + +/* struct, typedef, enum */ +static struct lt_arg args_def_struct[LT_ARGS_DEF_STRUCT_NUM]; +static struct lt_arg args_def_typedef[LT_ARGS_DEF_TYPEDEF_NUM]; +static int args_def_struct_cnt = 0; +static int args_def_typedef_cnt = 0; +static struct hsearch_data args_enum_tab; + +static struct lt_enum* getenum(struct lt_config_shared *cfg, char *name) +{ + struct lt_enum *en; + ENTRY e, *ep; + + PRINT_VERBOSE(cfg->verbose, 1, "request for <%s>\n", name); + + if (!enum_init) { + PRINT_VERBOSE(cfg->verbose, 1, "no enum added so far\n", name); + return NULL; + } + + e.key = name; + hsearch_r(e, FIND, &ep, &args_enum_tab); + + if (!ep) { + PRINT_VERBOSE(cfg->verbose, 1, "failed to find enum <%s>\n", name); + return NULL; + } + + en = (struct lt_enum*) ep->data; + + PRINT_VERBOSE(cfg->verbose, 1, "found %p <%s>\n", en, en->name); + return en; +} + +static int enum_comp(const void *ep1, const void *ep2) +{ + struct lt_enum_elem *e1 = (struct lt_enum_elem*) ep1; + struct lt_enum_elem *e2 = (struct lt_enum_elem*) ep2; + + return e1->val - e2->val; +} + +static struct lt_enum_elem* get_enumelem(struct lt_config_shared *cfg, + long val, struct lt_enum *en) +{ + struct lt_enum_elem key; + key.val = val; + + PRINT_VERBOSE(cfg->verbose, 1, "looking for %p <%s> value %ld\n", + en, en->name, val); + + return bsearch(&key, en->elems, en->cnt, + sizeof(struct lt_enum_elem), enum_comp); +} + +int lt_args_add_enum(struct lt_config_shared *cfg, char *name, + struct lt_list_head *h) +{ + ENTRY e, *ep; + struct lt_enum_elem *elem, *last = NULL; + struct lt_enum *en; + int i = 0; + + if (NULL == (en = malloc(sizeof(*en)))) + return -1; + + memset(en, 0x0, sizeof(*en)); + en->name = name; + + /* Initialize the hash table holding enum names */ + if (!enum_init) { + if (!hcreate_r(LT_ARGS_DEF_ENUM_NUM, &args_enum_tab)) { + perror("failed to create has table:"); + return -1; + } + enum_init = 1; + } + + e.key = en->name; + e.data = en; + + if (!hsearch_r(e, ENTER, &ep, &args_enum_tab)) { + perror("hsearch_r failed"); + free(en); + /* we dont want to exit just because + we ran out of our symbol limit */ + PRINT_VERBOSE(cfg->verbose, 3, + "reach the enum number limit %u\n", + LT_ARGS_DEF_ENUM_NUM); + } + + /* We've got enum inside the hash, let's prepare the enum itself. + The 'elems' field is going to be the qsorted list of + 'struct enum_elem's */ + lt_list_for_each_entry(elem, h, list) + en->cnt++; + + if (NULL == (en->elems = malloc(sizeof(struct lt_enum_elem) * en->cnt))) + return -1; + + PRINT_VERBOSE(cfg->verbose, 3, "enum %s (%d elems)\n", + en->name, en->cnt); + + lt_list_for_each_entry(elem, h, list) { + + if (elem->undef) { + if (!last) + elem->val = 0; + else + elem->val = last->val + 1; + elem->undef = 0; + } + + PRINT_VERBOSE(cfg->verbose, 3, "\t %s = %d\n", + elem->name, elem->val); + + en->elems[i++] = *elem; + last = elem; + } + + qsort(en->elems, en->cnt, sizeof(struct lt_enum_elem), enum_comp); + return 0; +} + +struct lt_enum_elem* lt_args_get_enum(struct lt_config_shared *cfg, + char *name, char *val) +{ + struct lt_enum_elem* elem; + + if (NULL == (elem = malloc(sizeof(*elem)))) + return NULL; + + memset(elem, 0x0, sizeof(*elem)); + elem->undef = 1; + + if (val) { + long num = strtol(val, (char **) NULL, 10); + if ((errno == ERANGE && (num == LONG_MAX || num == LONG_MIN)) || + (errno != 0 && num == 0)) + return NULL; + + elem->val = num; + elem->undef = 0; + } + + elem->name = strdup(name); + + PRINT_VERBOSE(cfg->verbose, 3, "enum elem %s = %d, undef = %d\n", + elem->name, elem->val, elem->undef); + return elem; +} + +int lt_args_add_struct(struct lt_config_shared *cfg, char *type_name, + struct lt_list_head *h) +{ + struct lt_arg *arg, sarg; + + if ((args_def_struct_cnt + 1) == LT_ARGS_DEF_STRUCT_NUM) + return 1; + + /* check if the struct name is already + defined as a type */ + if (lt_args_getarg(cfg, type_name, NULL, 0, 1, NULL)) + return -1; + + memset(&sarg, 0, sizeof(sarg)); + sarg.dtype = LT_ARGS_DTYPE_STRUCT; + sarg.type_id = LT_ARGS_TYPEID_CUSTOM + args_def_struct_cnt; + sarg.type_name = type_name; + sarg.args_head = h; + + PRINT_VERBOSE(cfg->verbose, 3, "struct [%s] type %d\n", + sarg.type_name, sarg.type_id); + + lt_list_for_each_entry(arg, sarg.args_head, args_list) { + + PRINT_VERBOSE(cfg->verbose, 3, "\t %s %s %u\n", + arg->type_name, arg->name, arg->type_len); + + /* This is not what sizeof would return on the structure. + The sizeof is arch dependent, this is pure sum. */ + sarg.type_len += arg->type_len; + sarg.mmbcnt++; + } + + args_def_struct[args_def_struct_cnt++] = sarg; + + PRINT_VERBOSE(cfg->verbose, 3, "%d.struct - final len = %u\n", + args_def_struct_cnt, sarg.type_len); + return 0; +} + +int lt_args_add_sym(struct lt_config_shared *cfg, struct lt_arg *ret, + struct lt_list_head *h) +{ + ENTRY e, *ep; + struct lt_args_sym *sym; + struct lt_arg *arg; + int i = 0; + + PRINT_VERBOSE(cfg->verbose, 3, "got symbol '%s %s'\n", + ret->type_name, ret->name); + + if (NULL == (sym = (struct lt_args_sym*) malloc(sizeof(*sym)))) + return -1; + + memset(sym, 0, sizeof(*sym)); + sym->name = ret->name; + + sym->argcnt = 1; + lt_list_for_each_entry(arg, h, args_list) + sym->argcnt++; + + sym->args = (struct lt_arg**) malloc(sym->argcnt * sizeof(struct lt_arg**)); + if (!sym->args) + /* no need to fre sym, since we are going + to exit the program anyway */ + return -1; + + PRINT_VERBOSE(cfg->verbose, 3, "got return %s, ptr %d\n", + ret->type_name, ret->pointer); + + sym->args[i++] = ret; + lt_list_for_each_entry(arg, h, args_list) { + PRINT_VERBOSE(cfg->verbose, 3, "\t '%s %s'\n", + arg->type_name, arg->name); + sym->args[i++] = arg; + } + + e.key = sym->name; + e.data = sym; + + if (!hsearch_r(e, ENTER, &ep, &cfg->args_tab)) { + perror("hsearch_r failed"); + free(sym); + /* we dont want to exit just because + we ran out of our symbol limit */ + PRINT_VERBOSE(cfg->verbose, 3, "reach the symbol number limit %u\n", + LT_ARGS_TAB); + } else + PRINT_VERBOSE(cfg->verbose, 3, "got symbol %s (%d args)\n", + sym->name, sym->argcnt); + + return 0; +} + +static struct lt_arg* argdup(struct lt_config_shared *cfg, struct lt_arg *asrc) +{ + struct lt_arg *arg, *a; + struct lt_list_head *h; + + PRINT_VERBOSE(cfg->verbose, 2, "got arg '%s %s', dtype %d\n", + asrc->type_name, asrc->name, asrc->dtype); + + if (NULL == (arg = malloc(sizeof(*arg)))) { + perror("malloc failed"); + return NULL; + } + + *arg = *asrc; + + if (arg->dtype != LT_ARGS_DTYPE_STRUCT) + return arg; + + /* For structures we need also to copy all its arguments. */ + + if (NULL == (h = (struct lt_list_head*) malloc(sizeof(*h)))) { + perror("malloc failed"); + return NULL; + } + + lt_init_list_head(h); + + lt_list_for_each_entry(a, asrc->args_head, args_list) { + struct lt_arg *aa; + + /* XXX Not sure how safe is this one... + might need some attention in future :) */ + if (NULL == (aa = argdup(cfg, a))) + return NULL; + + lt_list_add_tail(&aa->args_list, h); + } + + arg->args_head = h; + return arg; +} + +static struct lt_arg* find_arg(struct lt_config_shared *cfg, char *type, + struct lt_arg argsdef[], int size, int create) +{ + int i; + + for(i = 0; i < size; i++) { + struct lt_arg *arg; + struct lt_arg adef = argsdef[i]; + + PRINT_VERBOSE(cfg->verbose, 3, "%d. looking for [%s] - [%s]\n", + i, type, adef.type_name); + + if (strcmp(type, adef.type_name)) + continue; + + if (!create) + return &argsdef[i]; + + arg = argdup(cfg, &adef); + + PRINT_VERBOSE(cfg->verbose, 3, "found %d\n", arg->type_id); + return arg; + } + + return NULL; +} + +struct lt_arg* lt_args_getarg(struct lt_config_shared *cfg, char *type, + char *name, int pointer, int create, char *enum_name) +{ + struct lt_arg *arg; + + do { + if ((arg = find_arg(cfg, type, + args_def_pod, LT_ARGS_DEF_POD_NUM, create))) + break; + + if ((arg = find_arg(cfg, type, + args_def_struct, args_def_struct_cnt, create))) + break; + + if ((arg = find_arg(cfg, type, + args_def_typedef, args_def_typedef_cnt, create))) + break; + + return NULL; + + } while(0); + + if (!create) + return arg; + + /* Find out the enum definition if the enum + name is provided. */ + if (enum_name) + arg->en = getenum(cfg, enum_name); + + arg->name = strdup(name); + + /* If the type is already a pointer (could be for typedef), + give it a chance to show up. There's only one pointer for + the arg, since there's no reason to go dreper. */ + if (!arg->pointer) + arg->pointer = pointer; + + return arg; +} + +int lt_args_add_typedef(struct lt_config_shared *cfg, char *base, + char *new, int pointer) +{ + struct lt_arg *arg; + int i; + + if ((args_def_typedef_cnt + 1) == LT_ARGS_DEF_TYPEDEF_NUM) + return 2; + + /* check if the typedef name is already + defined as a type */ + if (lt_args_getarg(cfg, new, NULL, 0, 0, NULL)) + return 1; + + do { + if ((arg = find_arg(cfg, base, + args_def_pod, LT_ARGS_DEF_POD_NUM, 0))) + break; + + if ((arg = find_arg(cfg, base, + args_def_typedef, args_def_typedef_cnt, 0))) + break; + + PRINT_VERBOSE(cfg->verbose, 3, "%s not found\n", base); + return -1; + + } while(0); + + PRINT_VERBOSE(cfg->verbose, 3, "got [%s]\n", new); + + args_def_typedef[i = args_def_typedef_cnt++] = *arg; + + arg = &args_def_typedef[i]; + arg->type_name = strdup(new); + arg->pointer = pointer; + + lt_init_list_head(&arg->args_list); + + PRINT_VERBOSE(cfg->verbose, 3, "%d.typedef - got [%s] [%s]\n", + args_def_typedef_cnt, base, arg->type_name); + return 0; +} + +int lt_args_init(struct lt_config_shared *cfg) +{ + char *file = LT_ARGS_DEF_CONF; + int ret = 0; + + if (!hcreate_r(LT_ARGS_TAB, &cfg->args_tab)) { + perror("failed to create has table:"); + return -1; + } + + lt_args_parse_init(cfg); + + if (*cfg->args_def) + file = cfg->args_def; + + PRINT_VERBOSE(cfg->verbose, 1, "arguments definition file %s\n", file); + + if (lt_args_buf_open(cfg, file)) + return -1; + + if (yyparse()) { + printf("failed to parse config file %s\n", file); + ret = -1; + } + + if (fclose(yyin)) { + perror("failed to close " LT_ARGS_DEF_CONF); + return -1; + } + + return ret; +} + +static struct lt_args_sym* getsym(struct lt_config_shared *cfg, char *sym) +{ + struct lt_args_sym *a; + ENTRY e, *ep; + + PRINT_VERBOSE(cfg->verbose, 1, "request for <%s>\n", sym); + + e.key = sym; + hsearch_r(e, FIND, &ep, &cfg->args_tab); + + if (!ep) + return NULL; + + a = (struct lt_args_sym*) ep->data; + + PRINT_VERBOSE(cfg->verbose, 1, "found %p <%s>\n", a, a->name); + return a; +} + +static int getstr_addenum(struct lt_config_shared *cfg, struct lt_arg *arg, + char *argbuf, int alen, long val) +{ + char *enstr = NULL; + struct lt_enum_elem *elem; + + if (!arg->en) + return 0; + + if (NULL != (elem = get_enumelem(cfg, val, arg->en))) + enstr = elem->name; + + if (enstr) + return snprintf(argbuf, alen, "%s", enstr); + + return 0; +} + +static int getstr_pod(struct lt_config_shared *cfg, int dspname, struct lt_arg *arg, + void *pval, char *argbuf, int *arglen) +{ + int len = 0, alen = *arglen; + int namelen = strlen(arg->name); + + PRINT_VERBOSE(cfg->verbose, 1, "\t arg '%s %s', pval %p, len %d, pointer %d, dtype %d, type_id %d\n", + arg->type_name, arg->name, pval, alen, arg->pointer, arg->dtype, arg->type_id); + + if (alen < 5) + return 0; + + *arglen = 0; + + if ((dspname) && + (namelen < (alen - 5 - sizeof(LT_EQUAL)))) { + *arglen = sprintf(argbuf, "%s"LT_EQUAL, arg->name); + argbuf += *arglen; + alen -= *arglen; + } + + /* Get enum resolve for pointers now, the rest + POD is done in ARGS_SPRINTF macro. The char + pointers need special handling later. */ + if ((arg->pointer) && + (arg->type_id != LT_ARGS_TYPEID_CHAR)) { + + void *ptr = *((void**) pval); + + /* Try to get enumed value first. */ + len = getstr_addenum(cfg, arg, argbuf, alen, (long)ptr); + + /* If there's no enum resolved, + just display the ptr value */ + if (!len) { + if (ptr) + len = snprintf(argbuf, alen, "%p", ptr); + else + len = snprintf(argbuf, alen, "NULL"); + } + + goto out; + } + +#define ARGS_SPRINTF(FMT, TYPE) \ +do { \ + if (!(len = getstr_addenum(cfg, arg, argbuf, alen, \ + (long) *((TYPE*) pval)))) \ + len = snprintf(argbuf, alen, FMT, *((TYPE*) pval)); \ +} while(0) + + switch(arg->type_id) { + case LT_ARGS_TYPEID_SHORT: ARGS_SPRINTF("%hd", short); break; + case LT_ARGS_TYPEID_USHORT: ARGS_SPRINTF("%hu", unsigned short); break; + case LT_ARGS_TYPEID_INT: ARGS_SPRINTF("%d", int); break; + case LT_ARGS_TYPEID_UINT: ARGS_SPRINTF("%u", unsigned int); break; + case LT_ARGS_TYPEID_LONG: ARGS_SPRINTF("%ld", long); break; + case LT_ARGS_TYPEID_ULONG: ARGS_SPRINTF("%lu", unsigned long); break; + case LT_ARGS_TYPEID_LLONG: ARGS_SPRINTF("%lld", long long); break; + case LT_ARGS_TYPEID_ULLONG: ARGS_SPRINTF("%llu", unsigned long long); break; + case LT_ARGS_TYPEID_DOUBLE: ARGS_SPRINTF("%lf", double); break; + case LT_ARGS_TYPEID_FLOAT: ARGS_SPRINTF("%f", float); break; +#undef ARGS_SPRINTF + case LT_ARGS_TYPEID_CHAR: + if (arg->pointer) { + + void *val = *((void**) pval); + + if (val) { + char *s = val; + int slen = strlen(s); + int left = alen; + + if ((slen + 2) > left) { + snprintf(argbuf, left, "\"%s", s); + strncpy(argbuf + left - sizeof("...\"") + 1, "...\"", sizeof("...\"")); + } else { + strcpy(argbuf, "\""); + strcat(argbuf, s); + strcat(argbuf, "\""); + } + } else + len = snprintf(argbuf, alen, "NULL"); + } else { + + if (*((char*) pval) <= ' ') + len = snprintf(argbuf, alen, "0x%02x", + *((char*) pval)); + else + len = snprintf(argbuf, alen, "0x%02x \'%c\'", + *((char*) pval), *((char*) pval)); + } + break; + + case LT_ARGS_TYPEID_VOID: + len = snprintf(argbuf, alen, "void"); + break; + } + + if (LT_ARGS_DTYPE_STRUCT == arg->dtype) { + if (pval) + len = snprintf(argbuf, alen, "v(%p)", pval); + else + len = snprintf(argbuf, alen, "v(REG)"); + + } + +out: + *arglen += strlen(argbuf); + + PRINT_VERBOSE(cfg->verbose, 1, "\t arg out len %d - [%s]\n", + *arglen, argbuf); + return 0; +} + +int lt_args_cb_arg(struct lt_config_shared *cfg, struct lt_arg *arg, void *pval, + struct lt_args_data *data, int last, int dspname) +{ + int len = data->arglen; + + PRINT_VERBOSE(cfg->verbose, 1, "arg '%s %s', pval %p, last %d\n", + arg->type_name, arg->name, pval, last); + + getstr_pod(cfg, dspname, arg, pval, + data->args_buf + data->args_totlen, &len); + data->args_totlen += len; + + if (!last) { + strcat(data->args_buf, ", "); + data->args_totlen += 2; + } + + return 0; +} + +int lt_args_cb_struct(struct lt_config_shared *cfg, int type, struct lt_arg *arg, + void *pval, struct lt_args_data *data, int last) +{ + PRINT_VERBOSE(cfg->verbose, 1, + "type %d, arg '%s %s', pval %p, last %d, pointer %d\n", + type, arg->type_name, arg->name, pval, last, arg->pointer); + + /* initiall call for the structure argument */ + if (type == LT_ARGS_STRUCT_ITSELF) { + + data->argsd_totlen += sprintf(data->argsd_buf + data->argsd_totlen, + "struct %s %s = { ", + arg->type_name, arg->name); + return 0; + + /* subsequent calls for all structure arguments */ + } else if (type == LT_ARGS_STRUCT_ARG) { + + int len = cfg->args_detail_maxlen - data->argsd_totlen; + + getstr_pod(cfg, 1, arg, pval, data->argsd_buf + data->argsd_totlen, &len); + data->argsd_totlen += len; + + if (!last) { + strcat(data->argsd_buf, ", "); + data->argsd_totlen += 2; + } else + data->argsd_totlen += sprintf(data->argsd_buf + + data->argsd_totlen, " }\n"); + } + + return 0; +} + +static int getargs(struct lt_config_shared *cfg, struct lt_args_sym *asym, + La_regs *regs, char **abuf, char **adbuf) +{ + struct lt_args_data data; + int arglen; + char *buf, *bufd; + + if (NULL == (buf = malloc(cfg->args_maxlen))) + return -1; + + memset(&data, 0, sizeof(data)); + + *buf = 0; + *abuf = buf; + + if (cfg->args_detailed) { + if (NULL == (bufd = malloc(cfg->args_detail_maxlen))) + return -1; + + *bufd = 0; + *adbuf = bufd; + data.argsd_buf = bufd; + data.argsd_len = cfg->args_detail_maxlen; + } + + + /* makeup the final space for each + argument textual representation */ + arglen = (cfg->args_maxlen + - ((asym->argcnt - 1) * 2) /* args separating commas */ + )/ asym->argcnt; + + + data.arglen = arglen; + data.args_buf = buf; + data.args_len = cfg->args_maxlen; + + return lt_stack_process(cfg, asym, regs, &data); +} + +static FILE* open_include(struct lt_config_shared *cfg, char *file) +{ + FILE *f; + char fn[LT_MAXFILE]; + + /* we got an absolute path */ + if ((NULL != (f = fopen(file, "r")))) { + PRINT_VERBOSE(cfg->verbose, 1, "open ok [%s]\n", file); + return f; + } + + PRINT_VERBOSE(cfg->verbose, 1, "open failed [%s]: %s\n", + file, strerror(errno)); + + /* give up if there was already the absolute name */ + if (*file == '/') { + printf("open failed [%s]: %s\n", file, strerror(errno)); + return NULL; + } + + /* not an absolute name, give it a chance + inside of the /etc config directory */ + if (strlen(file) > (LT_MAXFILE - sizeof(LT_ARGS_DEF_DIR))) { + printf("file name length crossed the max %u: %s\n", + (u_int) (LT_MAXFILE - sizeof(LT_ARGS_DEF_DIR)), file); + return NULL; + } + + sprintf(fn, "%s/%s", LT_ARGS_DEF_DIR, file); + + if ((NULL == (f = fopen(fn, "r")))) { + PRINT_VERBOSE(cfg->verbose, 1, "open failed [%s]: %s\n", + fn, strerror(errno)); + printf("open failed [%s]: %s\n", file, strerror(errno)); + return NULL; + } + + PRINT_VERBOSE(cfg->verbose, 1, "open ok [%s]\n", fn); + return f; +} + +int lt_args_buf_open(struct lt_config_shared *cfg, char *file) +{ + struct lt_args_include *inc; + + PRINT_VERBOSE(cfg->verbose, 1, "opening buffer for [%s] depth %d\n", + file, include_stack_ptr); + + if ((include_stack_ptr + 1) == MAX_INCLUDE_DEPTH) { + printf("include depth overstep"); + return -1; + } + + if (NULL == (yyin = open_include(cfg, file))) + return -1; + + inc = &include_stack[include_stack_ptr++]; + memset(inc, 0, sizeof(*inc)); + + inc->yyin = yyin; + inc->file = strdup(file); + inc->lineno = 1; + inc->yybuf = yy_create_buffer(yyin, YY_BUF_SIZE); + + yy_switch_to_buffer(inc->yybuf); + + PRINT_VERBOSE(cfg->verbose, 1, "opened buffer for [%s] depth %d\n", + file, include_stack_ptr); + return 0; +} + +int lt_args_buf_close(struct lt_config_shared *cfg) +{ + struct lt_args_include *inc = &include_stack[--include_stack_ptr]; + + PRINT_VERBOSE(cfg->verbose, 1, "buffer closed [%s], depth [%d]\n", + inc->file, include_stack_ptr); + + free(inc->file); + + /* EOF with no other includes on stack */ + if (!include_stack_ptr) + return -1; + + /* looks like the base buffer is cleaned up by the + flex itself, so we do the actual cleaning + only for includes */ + yy_delete_buffer(inc->yybuf); + fclose(inc->yyin); + + inc = &include_stack[include_stack_ptr - 1]; + yy_switch_to_buffer(inc->yybuf); + return 0; +} + +struct lt_args_include* lt_args_buf_get(void) +{ + struct lt_args_include *inc = &include_stack[include_stack_ptr - 1]; + return inc; +} + +int lt_args_sym_entry(struct lt_config_shared *cfg, char *sym, La_regs *regs, + char **argbuf, char **argdbuf) +{ + struct lt_args_sym *asym; + + if (NULL == (asym = getsym(cfg, sym))) + return -1; + + return getargs(cfg, asym, regs, argbuf, argdbuf); +} + +static int getargs_ret(struct lt_config_shared *cfg, struct lt_args_sym *asym, + La_retval *regs, char **abuf, char **adbuf) +{ + struct lt_args_data data; + int arglen, totlen; + char *buf, *bufd; + + if (NULL == (buf = malloc(cfg->args_maxlen))) + return -1; + + memset(&data, 0, sizeof(data)); + + *buf = 0; + *abuf = buf; + + /* TODO get together with getargs function somehow... */ + if (cfg->args_detailed) { + + if (NULL == (bufd = malloc(cfg->args_detail_maxlen))) + return -1; + + *bufd = 0; + *adbuf = bufd; + data.argsd_buf = bufd; + data.argsd_len = cfg->args_detail_maxlen; + } + + arglen = cfg->args_maxlen - sizeof(LT_EQUAL); + totlen = sizeof(LT_EQUAL) - 1; + strcat(buf, LT_EQUAL); + + data.arglen = arglen; + data.args_buf = buf; + data.args_len = cfg->args_maxlen; + data.args_totlen = totlen; + + return lt_stack_process_ret(cfg, asym, regs, &data); +} + +int lt_args_sym_exit(struct lt_config_shared *cfg, char *sym, La_regs *inregs, La_retval *outregs, + char **argbuf, char **argdbuf) +{ + struct lt_args_sym *asym; + + if (NULL == (asym = getsym(cfg, sym))) + return -1; + + return getargs_ret(cfg, asym, outregs, argbuf, argdbuf); +} diff --git a/src/audit-init.c b/src/audit-init.c new file mode 100644 index 0000000..5ab978c --- /dev/null +++ b/src/audit-init.c @@ -0,0 +1,196 @@ +/* + Copyright (C) 2008, 2009 Jiri Olsa <olsajiri@gmail.com> + + This file is part of the latrace. + + The latrace is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + The latrace 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with the latrace (file COPYING). If not, see + <http://www.gnu.org/licenses/>. +*/ + + +#include <stdio.h> +#include <stdlib.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> +#include <string.h> + +#include "config.h" + +struct lt_config_audit cfg; + +static int read_config(char *dir) +{ + int fd; + off_t len; + char file[LT_MAXFILE]; + + memset(&cfg, 0, sizeof(cfg)); + + cfg.dir = dir; + sprintf(file, "%s/config", dir); + + if (-1 == (fd = open(file, O_RDONLY))) { + perror("open failed"); + return -1; + } + + if (-1 == read(fd, &cfg.sh, sizeof(cfg.sh))) { + perror("read failed"); + return -1; + } + + if (-1 == (len = lseek(fd, 0, SEEK_END))) { + perror("lseek failed"); + return -1; + } + + if (len != sizeof(cfg.sh)) { + printf("config file size differs\n"); + return -1; + } + + if (LT_MAGIC != cfg.sh.magic) { + printf("config file magic check failed\n"); + return -1; + } + + return 0; +} + +static int get_names(struct lt_config_audit *cfg, char *names, char **ptr) +{ + char* s; + int cnt = 0; + + PRINT_VERBOSE(cfg->sh.verbose, 1, "names: [%s] max: %d\n", + names, LT_NAMES_MAX); + + s = strchr(names, LT_NAMES_SEP); + while(NULL != (s = strchr(names, LT_NAMES_SEP)) && (cnt < LT_NAMES_MAX)) { + *s = 0x0; + PRINT_VERBOSE(cfg->sh.verbose, 1, "got: %s", names); + ptr[cnt++] = names; + names = ++s; + } + + if (cnt) { + ptr[cnt++] = names; + PRINT_VERBOSE(cfg->sh.verbose, 1, "got: %s\n", names); + } + + if (!cnt && *names) { + ptr[0] = names; + cnt = 1; + PRINT_VERBOSE(cfg->sh.verbose, 1, "got: %s\n", names); + } + + ptr[cnt] = NULL; + + if (!cnt) + return -1; + + PRINT_VERBOSE(cfg->sh.verbose, 1, "got %d entries\n", cnt); + return cnt; +} + +int audit_init(int argc, char **argv, char **env) +{ + if (-1 == read_config(getenv("LT_DIR"))) + return -1; + + /* -Aa */ + if (cfg.sh.args_enabled && lt_args_init(&cfg.sh)) + return -1; + + /* -t */ + if ((*cfg.sh.libs_to) && + (-1 == (cfg.libs_to_cnt = get_names(&cfg, cfg.sh.libs_to, cfg.libs_to)))) { + printf("latrace failed to parse libs to\n"); + return -1; + } + + /* -f */ + if ((*cfg.sh.libs_from) && + (-1 == (cfg.libs_from_cnt = get_names(&cfg, cfg.sh.libs_from, cfg.libs_from)))) { + printf("latrace failed to parse libs from\n"); + return -1; + } + + /* -l */ + if ((*cfg.sh.libs_both) && + (-1 == (cfg.libs_both_cnt = get_names(&cfg, cfg.sh.libs_both, cfg.libs_both)))) { + printf("latrace failed to parse libs from\n"); + return -1; + } + + /* -s */ + if ((*cfg.sh.symbols) && + (-1 == (cfg.symbols_cnt = get_names(&cfg, cfg.sh.symbols, cfg.symbols)))) { + printf("latrace failed to parse symbols\n"); + return -1; + } + + /* -b */ + if ((*cfg.sh.flow_below) && + (-1 == (cfg.flow_below_cnt = get_names(&cfg, cfg.sh.flow_below, cfg.flow_below)))) { + printf("latrace failed to parse symbols in flow-below option\n"); + return -1; + } + + /* -L */ + if (*cfg.sh.libs_subst) { + + char *ptr[LT_NAMES_MAX]; + int cnt; + + if (-1 == (cnt = get_names(&cfg, cfg.sh.libs_subst, ptr))) { + printf("latrace failed to parse input for subst option\n"); + return -1; + } + + if (-1 == lt_objsearch_init(&cfg, ptr, cnt)) { + printf("latrace failed to nitialize subst option\n"); + return -1; + } + } + + /* -o */ + cfg.sh.fout = stdout; + if ((*cfg.sh.output) && (NULL == (cfg.sh.fout = fopen(cfg.sh.output, "w")))) { + printf("latrace failed to open output file %s\n", cfg.sh.output); + return -1; + } + + /* -E */ + if (cfg.sh.not_follow_exec) + unsetenv("LD_AUDIT"); + + /* -F */ + if (cfg.sh.not_follow_fork) + cfg.sh.pid = getpid(); + + cfg.init_ok = 1; + return 0; +} + +void finalize(void) __attribute__((destructor)); + +void +finalize(void) +{ + if ((!cfg.sh.pipe) && (*cfg.sh.output)) + fclose(cfg.sh.fout); +} diff --git a/src/audit.c b/src/audit.c new file mode 100644 index 0000000..d50a01f --- /dev/null +++ b/src/audit.c @@ -0,0 +1,287 @@ +/* + Copyright (C) 2008, 2009 Jiri Olsa <olsajiri@gmail.com> + + This file is part of the latrace. + + The latrace is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + The latrace 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with the latrace (file COPYING). If not, see + <http://www.gnu.org/licenses/>. +*/ + + +#include <link.h> +#include <stdio.h> +#include <string.h> +#include <sys/types.h> +#include <unistd.h> +#include <sys/syscall.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <bits/wordsize.h> +#include <gnu/lib-names.h> +#include <stdlib.h> + +#include "config.h" + + +extern struct lt_config_audit cfg; + +static __thread int pipe_fd = 0; +static __thread int flow_below_stack = 0; + + +static int check_names(char *name, char **ptr) +{ + char *n; + + for(n = *ptr; n; n = *(++ptr)) { + if (strstr(name, n)) { + PRINT_VERBOSE(cfg.sh.verbose, 2, + "return %d for name %s\n", 1, name); + return 1; + } + } + + PRINT_VERBOSE(cfg.sh.verbose, 2, "return %d for name %s\n", + 0, name); + return 0; +} + +static int check_flow_below(const char *symname, int in) +{ + int ret = flow_below_stack; + + if (check_names((char*) symname, cfg.flow_below)) + in ? ret = ++flow_below_stack : flow_below_stack--; + + return ret; +} + +static int sym_entry(const char *symname, char *lib_from, char *lib_to, + La_regs *regs) +{ + int argret; + char *argbuf = "", *argdbuf = ""; + + PRINT_VERBOSE(cfg.sh.verbose, 2, "%s@%s\n", symname, lib_to); + + if (cfg.flow_below_cnt && !check_flow_below(symname, 1)) + return 0; + + argret = cfg.sh.args_enabled ? + lt_args_sym_entry(&cfg.sh, (char*) symname, regs, &argbuf, &argdbuf) : -1; + + if (cfg.sh.pipe) { + char buf[FIFO_MSG_MAXLEN]; + int len; + + if (!pipe_fd) + pipe_fd = lt_fifo_create(&cfg, cfg.dir); + + len = lt_fifo_msym_get(&cfg, buf, FIFO_MSG_TYPE_ENTRY, + (char*) symname, lib_to, argbuf, argdbuf); + + return lt_fifo_send(&cfg, pipe_fd, buf, len); + } + + cfg.sh.indent_depth++; + + lt_out_entry(&cfg.sh, symname, lib_to, + argbuf, argdbuf); + + if (!argret) { + free(argbuf); + if (cfg.sh.args_detailed && (*argdbuf)) + free(argdbuf); + } + + return 0; +} + +static int sym_exit(const char *symname, char *lib_from, char *lib_to, + const La_regs *inregs, La_retval *outregs) +{ + int argret; + char *argbuf = "", *argdbuf = ""; + + PRINT_VERBOSE(cfg.sh.verbose, 2, "%s@%s\n", symname, lib_to); + + if (cfg.flow_below_cnt && !check_flow_below(symname, 0)) + return 0; + + argret = cfg.sh.args_enabled ? + lt_args_sym_exit(&cfg.sh, (char*) symname, + (La_regs*) inregs, outregs, &argbuf, &argdbuf) : -1; + + if (cfg.sh.pipe) { + char buf[FIFO_MSG_MAXLEN]; + int len; + + len = lt_fifo_msym_get(&cfg, buf, FIFO_MSG_TYPE_EXIT, + (char*) symname, lib_to, argbuf, argdbuf); + + return lt_fifo_send(&cfg, pipe_fd, buf, len); + } + + lt_out_exit(&cfg.sh, symname, lib_from, + argbuf, argdbuf); + + cfg.sh.indent_depth--; + + if (!argret) { + free(argbuf); + + if (cfg.sh.args_detailed && (*argdbuf)) + free(argdbuf); + } + + return 0; +} + +static int check_pid() +{ + pid_t pid = getpid(); + + PRINT_VERBOSE(cfg.sh.verbose, 1, "tid = %d, cfg tid = %d\n", + pid, cfg.sh.pid); + + if (pid != cfg.sh.pid) + return -1; + + return 0; +} + +#define CHECK_PID(ret) \ +do { \ + if (cfg.sh.not_follow_fork && \ + check_pid()) \ + return ret; \ +} while(0) + +unsigned int la_version(unsigned int v) +{ + return v; +} + +unsigned int la_objopen(struct link_map *l, Lmid_t a, uintptr_t *cookie) +{ + char *name = l->l_name; + + if (!cfg.init_ok) + return 0; + + if (!name) + return 0; + + /* executable itself */ + if (!(*name)) + return LA_FLG_BINDTO | LA_FLG_BINDFROM; + + /* audit all as default */ + if ((!cfg.libs_to_cnt) && + (!cfg.libs_from_cnt) && + (!cfg.libs_both_cnt)) + return LA_FLG_BINDTO | LA_FLG_BINDFROM; + + if (check_names(name, cfg.libs_to)) + return LA_FLG_BINDTO; + + if (check_names(name, cfg.libs_from)) + return LA_FLG_BINDFROM; + + if (check_names(name, cfg.libs_both)) + return LA_FLG_BINDTO | LA_FLG_BINDFROM; + + /* wrong library name specified ? */ + return 0; +} + +static unsigned int la_symbind(const char *symname) +{ + unsigned int flags = 0; + + if (cfg.symbols_cnt) { + flags = LA_SYMB_NOPLTENTER; + if (check_names((char*) symname, cfg.symbols)) + flags = 0; + } + + return flags; +} + +void la_activity(uintptr_t *cookie, unsigned int act) +{ + PRINT_VERBOSE(cfg.sh.verbose, 2, "entry\n"); +} + +char* la_objsearch(const char *name, uintptr_t *cookie, unsigned int flag) +{ + if (flag == LA_SER_ORIG) + return (char*) name; + + return lt_objsearch(&cfg, name, cookie, flag); +} + +void la_preinit(uintptr_t *__cookie) +{ + PRINT_VERBOSE(cfg.sh.verbose, 2, "entry\n"); +} + +unsigned int la_objclose(uintptr_t *__cookie) +{ + PRINT_VERBOSE(cfg.sh.verbose, 2, "entry\n"); + return 0; +} + +uintptr_t la_symbind32(Elf32_Sym *sym, unsigned int ndx, uintptr_t *refcook, + uintptr_t *defcook, unsigned int *flags, const char *symname) +{ + *flags = la_symbind(symname); + return sym->st_value; +} + +uintptr_t la_symbind64(Elf64_Sym *sym, unsigned int ndx, uintptr_t *refcook, + uintptr_t *defcook, unsigned int *flags, const char *symname) +{ + *flags = la_symbind(symname); + return sym->st_value; +} + +ElfW(Addr) +pltenter(ElfW(Sym) *sym, unsigned int ndx, uintptr_t *refcook, + uintptr_t *defcook, La_regs *regs, unsigned int *flags, + const char *symname, long int *framesizep) +{ + struct link_map *lr = (struct link_map*) *refcook; + struct link_map *ld = (struct link_map*) *defcook; + + CHECK_PID(sym->st_value); + + sym_entry(symname, lr ? lr->l_name : NULL, ld ? ld->l_name : NULL, regs); + *framesizep = cfg.sh.framesize; + return sym->st_value; +} + +unsigned int pltexit(ElfW(Sym) *sym, unsigned int ndx, uintptr_t *refcook, + uintptr_t *defcook, const La_regs *inregs, La_retval *outregs, + const char *symname) +{ + struct link_map *lr = (struct link_map*) *refcook; + struct link_map *ld = (struct link_map*) *defcook; + + CHECK_PID(0); + + sym_exit(symname, lr ? lr->l_name : NULL, ld ? ld->l_name : NULL, inregs, outregs); + return 0; +} diff --git a/src/audit.h b/src/audit.h new file mode 100644 index 0000000..edb72d3 --- /dev/null +++ b/src/audit.h @@ -0,0 +1,102 @@ +/* + Copyright (C) 2008, 2009 Jiri Olsa <olsajiri@gmail.com> + + This file is part of the latrace. + + The latrace is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + The latrace 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with the latrace (file COPYING). If not, see + <http://www.gnu.org/licenses/>. +*/ + + +#ifndef AUDIT_H +#define AUDIT_H + +#include <link.h> + +/* stolen from glibc :) */ +#ifdef __i386__ +# define pltenter la_i86_gnu_pltenter +# define pltexit la_i86_gnu_pltexit +# define La_regs La_i86_regs +# define La_retval La_i86_retval +# define int_retval lrv_eax +#elif defined __x86_64__ +# define pltenter la_x86_64_gnu_pltenter +# define pltexit la_x86_64_gnu_pltexit +# define La_regs La_x86_64_regs +# define La_retval La_x86_64_retval +# define int_retval lrv_rax +#elif defined __arm__ +# define pltenter la_arm_gnu_pltenter +# define pltexit la_arm_gnu_pltexit +# define La_regs La_arm_regs +# define La_retval La_arm_retval +# define int_retval lrv_reg[0] +#elif defined __powerpc__ && __WORDSIZE == 32 +# define pltenter la_ppc32_gnu_pltenter +# define pltexit la_ppc32_gnu_pltexit +# define La_regs La_ppc32_regs +# define La_retval La_ppc32_retval +# define int_retval lrv_r3 +#elif defined __powerpc__ && __WORDSIZE == 64 +# define pltenter la_ppc64_gnu_pltenter +# define pltexit la_ppc64_gnu_pltexit +# define La_regs La_ppc64_regs +# define La_retval La_ppc64_retval +# define int_retval lrv_r3 +#elif defined __sh__ +# define pltenter la_sh_gnu_pltenter +# define pltexit la_sh_gnu_pltexit +# define La_regs La_sh_regs +# define La_retval La_sh_retval +# define int_retval lrv_r0 +#elif defined __alpha__ +# define pltenter la_alpha_gnu_pltenter +# define pltexit la_alpha_gnu_pltexit +# define La_regs La_alpha_regs +# define La_retval La_alpha_retval +# define int_retval lrv_r0 +#elif defined __s390__ && __WORDSIZE == 32 +# define pltenter la_s390_32_gnu_pltenter +# define pltexit la_s390_32_gnu_pltexit +# define La_regs La_s390_32_regs +# define La_retval La_s390_32_retval +# define int_retval lrv_r2 +#elif defined __s390__ && __WORDSIZE == 64 +# define pltenter la_s390_64_gnu_pltenter +# define pltexit la_s390_64_gnu_pltexit +# define La_regs La_s390_64_regs +# define La_retval La_s390_64_retval +# define int_retval lrv_r2 +#elif defined __ia64__ +# define pltenter la_ia64_gnu_pltenter +# define pltexit la_ia64_gnu_pltexit +# define La_regs La_ia64_regs +# define La_retval La_ia64_retval +# define int_retval lrv_r8 +#elif defined __sparc__ && __WORDSIZE == 32 +# define pltenter la_sparc32_gnu_pltenter +# define pltexit la_sparc32_gnu_pltexit +# define La_regs La_sparc32_regs +# define La_retval La_sparc32_retval +# define int_retval lrv_reg[0] +#elif defined __sparc__ && __WORDSIZE == 64 +# define pltenter la_sparc64_gnu_pltenter +# define pltexit la_sparc64_gnu_pltexit +# define La_regs La_sparc64_regs +# define La_retval La_sparc64_retval +# define int_retval lrv_reg[0] +#endif + +#endif // !AUDIT_H diff --git a/src/autoconf.h.in b/src/autoconf.h.in new file mode 100644 index 0000000..efcc490 --- /dev/null +++ b/src/autoconf.h.in @@ -0,0 +1,36 @@ +/* + Copyright (C) 2008, 2009 Jiri Olsa <olsajiri@gmail.com> + + This file is part of the latrace. + + The latrace is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + The latrace 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with the latrace (file COPYING). If not, see + <http://www.gnu.org/licenses/>. +*/ + + +#ifndef AUTOCONF_H + +/* Temporary directory prefix. */ +#undef CONFIG_LT_CONFIG + +/* Version define. */ +#undef LT_VER + +/* x86 define. */ +#undef LT_ARCH_X86 + +/* x86_64 define. */ +#undef LT_ARCH_X86_64 + +#endif diff --git a/src/autoconf.make.in b/src/autoconf.make.in new file mode 100644 index 0000000..c162165 --- /dev/null +++ b/src/autoconf.make.in @@ -0,0 +1,42 @@ +# Copyright (C) 2008, 2009 Jiri Olsa <olsajiri@gmail.com> +# +# This file is part of the latrace. +# +# The latrace is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# The latrace 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with the latrace (file COPYING). If not, see +# <http://www.gnu.org/licenses/>. + + +# @configure_input@ + +prefix = @prefix@ +exec_prefix = @exec_prefix@ +bindir = @bindir@ +libdir = @libdir@ +datadir = @datadir@ +mandir = @mandir@ +datarootdir = @datarootdir@ +sysconfdir = @sysconfdir@ + +RM = rm +FIND = find +CC = @CC@ +LEX = @LEX@ +YACC = @YACC@ +CPPFLAGS = @CPPFLAGS@ +CFLAGS = @CFLAGS@ +LDFLAGS = @LDFLAGS@ +LIBS = @LIBS@ + +CONFIG_SYSDEP_DIR = @CONFIG_SYSDEP_DIR@ +LT_VER = @LT_VER@ diff --git a/src/config.c b/src/config.c new file mode 100644 index 0000000..42d28b2 --- /dev/null +++ b/src/config.c @@ -0,0 +1,293 @@ +/* + Copyright (C) 2008, 2009 Jiri Olsa <olsajiri@gmail.com> + + This file is part of the latrace. + + The latrace is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + The latrace 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with the latrace (file COPYING). If not, see + <http://www.gnu.org/licenses/>. +*/ + + +#include <string.h> +#include <getopt.h> +#include <stdio.h> +#include <stdlib.h> + +#include "config.h" + +static void usage() NORETURN; +static void usage() +{ + printf("usage: latrace [-ltfsbcCpADaoyIiBdvTFELVh] command [arg ... ]\n\n"); + printf(" -l, --libs lib1,lib2... audit from and to lib1, lib2 ...\n"); + printf(" -t, --libs-to lib1,lib2... audit to lib1, lib2 ...\n"); + printf(" -f, --libs-from lib1,lib2... audit from lib1, lib2 ...\n"); + printf(" -s, --sym sym1,sym2... audit symbols sym1, sym2 ... \n"); + printf(" -L, --lib-subst s1,s2... objsearch LD_AUDIT interface (see man page)\n"); + printf("\n"); + printf(" -c, --counts display statistics counts of symbols\n"); + printf(" -C, --sort-counts stat implies -c, plus sort the statistics by 'stat':\n"); + printf(" time,per,call,ucall,lib,sym (default is call)\n"); + printf(" -p, --pipe use pipe to latrace process to send audit data\n"); + printf(" latrace app is then the one displaying the output\n"); + printf("\n"); + printf(" -A, --enable-args enable arguments output (definitions from /etc/latrace.conf)\n"); + printf(" -D, --detail-args display struct arguments in more detail\n"); + printf(" -a, --args file arguments definition file, implies \'-A\'\n"); + printf("\n"); + printf(" -y, --framesize number framesize for storing the stack before pltexit (default 1000)\n"); + printf(" -F, --not-follow-fork dont follow fork calls - childs\n"); + printf(" -E, --not-follow-exec dont follow exec calls\n"); + printf("\n"); + printf(" -b, --flow-below sym1,sym2... display flow only for sym1, sym2 ... \n"); + printf(" -I, --no-indent-sym do not indent symbols based on the their stack depth\n"); + printf(" -i, --indent-sym indent_size specify indent size specification\n"); + printf(" -B, --braces allways display braces {}\n"); + printf(" -d, --demangle run the symbol name throught the C++ demangler\n"); + printf(" -T, --hide-tid dont display thread id\n"); + printf(" -o, --output file store output to file\n"); + printf("\n"); + printf(" -v, --verbose verbose output\n"); + printf(" -V, --version display version\n"); + printf(" -h, --help display help\n"); + printf("\n"); + + exit(1); +} + +static void version() NORETURN; +static void version() +{ + printf("latrace version " LT_VER "\n"); + exit(0); +} + +static struct lt_config_tv counts_sort[] = { + { .type = LT_CSORT_TIME, .name = "time" }, + { .type = LT_CSORT_PERCENT, .name = "per" }, + { .type = LT_CSORT_CALL, .name = "call" }, + { .type = LT_CSORT_UCALL, .name = "ucall" }, + { .type = LT_CSORT_LIB, .name = "lib" }, + { .type = LT_CSORT_SYM, .name = "sym" } +}; + +#define COUNTS_SORT_NUM (sizeof(counts_sort)/sizeof(struct lt_config_tv)) + +static int get_type(struct lt_config_app *cfg, struct lt_config_tv *tv, + int num, char *name) +{ + int i; + + for(i = 0; i < num; i++) + if (!strcmp(name, tv[i].name)) + return tv[i].type; + + printf("failed to find name: %s\n", name); + return -1; +} + +int lt_config(struct lt_config_app *cfg, int argc, char **argv) +{ + memset(cfg, 0, sizeof(*cfg)); + + /* default values settings */ + cfg->sh.magic = LT_MAGIC; + cfg->sh.framesize = 1000; + cfg->sh.fout = stdout; + cfg->sh.indent_sym = 1; + cfg->sh.indent_size = 2; + cfg->sh.args_maxlen = LR_ARGS_MAXLEN; + cfg->sh.args_detail_maxlen = LR_ARGS_DETAIL_MAXLEN; + cfg->csort = LT_CSORT_CALL; + + while (1) { + int c; + int option_index = 0; + static struct option long_options[] = { + {"sym", required_argument, 0, 's'}, + {"libs", required_argument, 0, 'l'}, + {"libs-to", required_argument, 0, 't'}, + {"libs-from", required_argument, 0, 'f'}, + {"no-indent-sym", no_argument, 0, 'I'}, + {"indent-sym", required_argument, 0, 'i'}, + {"braces", no_argument, 0, 'B'}, + {"demangle", no_argument, 0, 'd'}, + {"flow-below", required_argument, 0, 'b'}, + {"counts", no_argument, 0, 'c'}, + {"sort-counts", required_argument, 0, 'C'}, + {"pipe", no_argument, 0, 'p'}, + {"output", required_argument, 0, 'o'}, + {"args", required_argument, 0, 'a'}, + {"enable-args", required_argument, 0, 'A'}, + {"detail-args", required_argument, 0, 'D'}, + {"framesize", required_argument, 0, 'y'}, + {"lib-subst", required_argument, 0, 'L'}, + {"verbose", no_argument, 0, 'v'}, + {"hide-tid", no_argument, 0, 'T'}, + {"not-follow-fork", no_argument, 0, 'F'}, + {"not-follow-exec", no_argument, 0, 'E'}, + {"version", no_argument, 0, 'V'}, + {"help", no_argument, 0, 'h'}, + {0, 0, 0, 0} + }; + + c = getopt_long(argc, argv, "+s:l:t:f:vhi:BdIb:cC:y:L:po:a:ADVTFE", + long_options, &option_index); + + if (c == -1) + break; + + switch (c) { + case 'l': + if (strlen(optarg) > LT_LIBS_MAXSIZE) + return -1; + + strncpy(cfg->sh.libs_both, optarg, strlen(optarg)); + break; + + case 't': + if (strlen(optarg) > LT_LIBS_MAXSIZE) + return -1; + + strncpy(cfg->sh.libs_to, optarg, strlen(optarg)); + break; + + case 'f': + if (strlen(optarg) > LT_LIBS_MAXSIZE) + return -1; + + strncpy(cfg->sh.libs_from, optarg, strlen(optarg)); + break; + + case 's': + if (strlen(optarg) > LT_SYMBOLS_MAXSIZE) + return -1; + + strncpy(cfg->sh.symbols, optarg, strlen(optarg)); + break; + + case 'b': + if (strlen(optarg) > LT_SYMBOLS_MAXSIZE) + return -1; + + strncpy(cfg->sh.flow_below, optarg, strlen(optarg)); + break; + + case 'v': + cfg->sh.verbose++; + break; + + case 'T': + cfg->sh.hide_tid = 1; + break; + + case 'F': + cfg->sh.not_follow_fork = 1; + break; + + case 'E': + cfg->sh.not_follow_exec = 1; + break; + + case 'i': + cfg->sh.indent_size = atoi(optarg); + break; + + case 'B': + cfg->sh.braces = 1; + break; + + case 'd': + cfg->sh.demangle = 1; + break; + + case 'I': + cfg->sh.indent_sym = 0; + break; + + case 'y': + cfg->sh.framesize = atoi(optarg); + break; + + case 'L': + if (strlen(optarg) > LT_SYMBOLS_MAXSIZE) + return -1; + + strncpy(cfg->sh.libs_subst, optarg, strlen(optarg)); + break; + + case 'C': + + if (-1 == (cfg->csort = get_type(cfg, counts_sort, COUNTS_SORT_NUM, optarg))) + usage(); + /* falling through */ + case 'c': + cfg->sh.counts = 1; + /* falling through */ + case 'p': + cfg->sh.pipe = 1; + break; + + case 'a': + strcpy(cfg->sh.args_def, optarg); + /* falling through */ + case 'A': + cfg->sh.args_enabled = 1; + break; + + case 'D': + cfg->sh.args_detailed = 1; + break; + + case 'o': + strcpy(cfg->sh.output, optarg); + break; + + case 'V': + version(); + break; + + case 'h': + usage(); + break; + + default: + printf("unknown option '%c'", c); + } // switch (c) + } // while(1) + + if (optind < argc) { + int i_arg = 1; + cfg->prog = argv[optind++]; + cfg->arg[0] = cfg->prog; + + while ((optind < argc) && (i_arg < LT_NUM_ARG)) { + cfg->arg[i_arg++] = argv[optind++]; + } + cfg->arg_num = i_arg; + } + + if (!cfg->prog) { + printf("failed: no program specified\n"); + usage(); + } + + if ((cfg->sh.pipe) && (*cfg->sh.output) && + (NULL == (cfg->sh.fout = fopen(cfg->sh.output, "w")))) { + printf("failed to fopen output file %s\n", cfg->sh.output); + usage(); + } + + return 0; +} diff --git a/src/config.h b/src/config.h new file mode 100644 index 0000000..680ab07 --- /dev/null +++ b/src/config.h @@ -0,0 +1,417 @@ +/* + Copyright (C) 2008, 2009 Jiri Olsa <olsajiri@gmail.com> + + This file is part of the latrace. + + The latrace is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + The latrace 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with the latrace (file COPYING). If not, see + <http://www.gnu.org/licenses/>. +*/ + + +#ifndef CONFIG_H +#define CONFIG_H + +#include <stdio.h> +#include <search.h> +#include <sys/time.h> +#include <sys/syscall.h> +#include <stdint.h> +#include <unistd.h> + +#include "audit.h" +#include "list.h" +#include "autoconf.h" + +#ifdef __GNUC__ +#define NORETURN __attribute__((__noreturn__)) +#else +#define NORETURN +#ifndef __attribute__ +#define __attribute__(x) +#endif +#endif + +/* TODO put this to autoconf.h */ +#define LT_VERSION_MINOR 1 +#define LT_VERSION_MAJOR 0 +#define LT_VERSION (LT_VERSION_MAJOR*256 + LT_VERSION_MINOR) + +#define LT_NAMES_MAX 50 +#define LT_NAMES_SEP ',' + +#define LT_SYM_HMAX 1000 + +#define LT_ARGS_DEF_STRUCT_NUM 1000 +#define LT_ARGS_DEF_TYPEDEF_NUM 1000 +#define LT_ARGS_DEF_ENUM_NUM 1000 + + +enum { + LT_CSORT_TIME = 0, + LT_CSORT_PERCENT, + LT_CSORT_CALL, + LT_CSORT_UCALL, + LT_CSORT_LIB, + LT_CSORT_SYM +}; + +struct lt_config_tv { + int type; + char *name; +}; + +struct lt_config_shared { +#define LT_MAGIC ((LT_VERSION << 16) + 0xdead) + unsigned int magic; + +#define LT_LIBS_MAXSIZE 200 + char libs_to[LT_LIBS_MAXSIZE]; + char libs_both[LT_LIBS_MAXSIZE]; + char libs_from[LT_LIBS_MAXSIZE]; + + char libs_subst[LT_LIBS_MAXSIZE]; + +#define LT_SYMBOLS_MAXSIZE 200 + char symbols[LT_SYMBOLS_MAXSIZE]; + + char flow_below[LT_SYMBOLS_MAXSIZE]; + +#define LT_MAXFILE 200 + char output[LT_MAXFILE]; + FILE *fout; + + char args_def[LT_MAXFILE]; + char args_enabled; + char args_detailed; +#define LR_ARGS_MAXLEN 1000 + int args_maxlen; +#define LR_ARGS_DETAIL_MAXLEN 1000 + int args_detail_maxlen; +#define LT_ARGS_TAB 10000 + struct hsearch_data args_tab; + + int verbose; + int debug; + int indent_sym; + int indent_size; + int braces; + int demangle; + int counts; + int pipe; + int hide_tid; + int not_follow_exec; + int not_follow_fork; + unsigned int framesize; + + /* for 'not_follow_fork' */ + pid_t pid; + + /* used by both app and lib */ + unsigned int indent_depth; +}; + +struct lt_config_app { + struct lt_config_shared sh; + + char *prog; +#define LT_NUM_ARG 500 + char *arg[LT_NUM_ARG]; + int arg_num; + + int csort; + + struct lt_thread *threads; + struct lt_thread *iter; +}; + +enum { + LT_OS_PATH = 1, /* '=' */ + LT_OS_PTN, /* '%' */ + LT_OS_PTN2PATH, /* '~' */ +}; + +struct lt_objsearch { + int type; + char *src; + char *dst; +}; + +struct lt_config_audit { + struct lt_config_shared sh; + + char *libs_to[LT_NAMES_MAX]; + int libs_to_cnt; + + char *libs_from[LT_NAMES_MAX]; + int libs_from_cnt; + + char *libs_both[LT_NAMES_MAX]; + int libs_both_cnt; + + char *symbols[LT_NAMES_MAX]; + int symbols_cnt; + + char *flow_below[LT_NAMES_MAX]; + int flow_below_cnt; + + struct lt_objsearch subst[LT_NAMES_MAX]; + int subst_cnt; + + char *dir; + int init_ok; +}; + +#define FIFO_MSG_MAXLEN 2000 + +/* common message data */ +struct lt_fifo_mbase { +#define FIFO_MSG_TYPE_ENTRY 1 +#define FIFO_MSG_TYPE_EXIT 2 + uint32_t type; + struct timeval tv; + pid_t tid; + int len; /* the rest of the message size */ +}; + +/* symbol message */ +struct lt_fifo_msym { + struct lt_fifo_mbase h; + + int sym; + int lib; + int arg; + int argd; + char data[0]; +}; + +struct lt_stats_sym { + char *name; + char *sym; + char *lib; + + struct timeval tv_cur; + struct timeval tv_all; + + unsigned int call; + + /* post mortem statistics */ + float percent; + unsigned int usec_call; +}; + +struct lt_thread { + /* global */ + int fifo_fd; + pid_t tid; + + /* start/stop time */ + struct timeval tv_start; + struct timeval tv_stop; + + /* symbol statistics */ + struct lt_stats_sym **sym_array; + struct hsearch_data sym_htab; + unsigned int sym_cnt; + unsigned int sym_max; + + struct lt_thread *next; +}; + +enum { + LT_ARGS_DTYPE_POD = 1, + LT_ARGS_DTYPE_STRUCT, +}; + +enum { + LT_ARGS_TYPEID_VOID = 1, + LT_ARGS_TYPEID_SHORT, + LT_ARGS_TYPEID_USHORT, + LT_ARGS_TYPEID_INT, + LT_ARGS_TYPEID_UINT, + LT_ARGS_TYPEID_LONG, + LT_ARGS_TYPEID_ULONG, + LT_ARGS_TYPEID_CHAR, + LT_ARGS_TYPEID_UCHAR, + LT_ARGS_TYPEID_LLONG, + LT_ARGS_TYPEID_ULLONG, + LT_ARGS_TYPEID_DOUBLE, + LT_ARGS_TYPEID_FLOAT, + + LT_ARGS_TYPEID_CUSTOM = 1000 +}; + +struct lt_enum_elem { + char *name; + long val; + int undef; + struct lt_list_head list; +}; + +struct lt_enum { + char *name; + int cnt; + struct lt_enum_elem *elems; +}; + +struct lt_arg { + /* argument type */ + int dtype; + + /* argument type properties */ + int type_id; + u_int type_len; + char *type_name; + + /* argument value properties */ + int pointer; + char *name; + + /* for structures only */ + int mmbcnt; + + /* architecture dependent */ + void *arch; + + /* enum record */ + struct lt_enum *en; + + /* struct arguments head */ + struct lt_list_head *args_head; + /* nested arguments list if present */ + struct lt_list_head args_list; +}; + +struct lt_args_sym { + char *name; + + int argcnt; +#define LT_ARGS_RET 0 + struct lt_arg **args; +}; + +struct lt_args_include { + FILE *yyin; + void *yybuf; + char *file; + int lineno; +}; + +/* used in lt_args_cb_struct for argument type */ +enum { + LT_ARGS_STRUCT_ITSELF = 0, + LT_ARGS_STRUCT_ARG +}; + +struct lt_args_data { + int arglen; + + /* function arguments */ + char *args_buf; + int args_len; + int args_totlen; + + /* detailed structs */ + char *argsd_buf; + int argsd_len; + int argsd_totlen; +}; + +/* global */ +int lt_config(struct lt_config_app *cfg, int argc, char **argv); +int lt_run(struct lt_config_app *cfg); + +/* fifo */ +int lt_fifo_create(struct lt_config_audit *cfg, char *dir); +int lt_fifo_open(struct lt_config_app *cfg, char *name); +int lt_fifo_send(struct lt_config_audit *cfg, int fd, char *buf, int len); +int lt_fifo_recv(struct lt_config_app *cfg, struct lt_thread *t, + void *buf, int bufsize); +int lt_fifo_msym_get(struct lt_config_audit *cfg, char *buf, int type, + char *symname, char *libto, char *arg, char *argd); + +/* counts */ +int lt_stats_init(struct lt_config_app *cfg); +int lt_stats_sym(struct lt_config_app *cfg, struct lt_thread *t, + struct lt_fifo_msym* m); +int lt_stats_alloc(struct lt_config_app *cfg, struct lt_thread *t); +int lt_stats_show(struct lt_config_app *cfg); + +/* thread */ +struct lt_thread *lt_thread_add(struct lt_config_app *cfg, int fd, pid_t pid); +struct lt_thread *lt_thread_first(struct lt_config_app *cfg); +struct lt_thread *lt_thread_next(struct lt_config_app *cfg); + +/* arguments */ +int lt_args_init(struct lt_config_shared *cfg); +int lt_args_sym_entry(struct lt_config_shared *cfg, char *sym, La_regs *regs, + char **argbuf, char **argdbuf); +int lt_args_sym_exit(struct lt_config_shared *cfg, char *sym, La_regs *inregs, + La_retval *outregs, char **argbuf, char **argdbuf); +int lt_args_add_enum(struct lt_config_shared *cfg, char *name, + struct lt_list_head *h); +struct lt_enum_elem* lt_args_get_enum(struct lt_config_shared *cfg, char *name, char *val); +int lt_args_add_struct(struct lt_config_shared *cfg, char *type_name, + struct lt_list_head *h); +int lt_args_add_sym(struct lt_config_shared *cfg, struct lt_arg *sym, + struct lt_list_head *h); +int lt_args_add_typedef(struct lt_config_shared *cfg, char *base, + char *new, int pointer); +int lt_args_buf_open(struct lt_config_shared *cfg, char *file); +int lt_args_buf_close(struct lt_config_shared *cfg); +struct lt_arg* lt_args_getarg(struct lt_config_shared *cfg, char *type, + char *name, int pointer, int create, char *enum_name); +int lt_args_cb_arg(struct lt_config_shared *cfg, struct lt_arg *arg, + void *pval, struct lt_args_data *data, int last, + int dspname); +int lt_args_cb_struct(struct lt_config_shared *cfg, int type, + struct lt_arg *arg, void *pval, + struct lt_args_data *data, int last); + +/* output */ +int lt_out_entry(struct lt_config_shared *cfg, const char *symname, + char *lib_to, char *argbuf, char *argdbuf); +int lt_out_exit(struct lt_config_shared *cfg, const char *symname, + char *lib_to, char *argbuf, char *argdbuf); + +/* stack handling */ +int lt_stack_process(struct lt_config_shared *cfg, struct lt_args_sym *asym, + La_regs *regs, struct lt_args_data *data); +int lt_stack_process_ret(struct lt_config_shared *cfg, struct lt_args_sym *asym, + La_retval *regs, struct lt_args_data *data); + +/* la_objsearch */ +int lt_objsearch_init(struct lt_config_audit *cfg, char **ptr, int cnt); +char* lt_objsearch(struct lt_config_audit *cfg, const char *name, + uintptr_t *cookie, unsigned int flag); + +#define PRINT(fmt, args...) \ +do { \ + char lpbuf[1024]; \ + sprintf(lpbuf, "[%d %s:%05d] %s", \ + (pid_t) syscall(SYS_gettid), \ + __FUNCTION__, \ + __LINE__, \ + fmt); \ + printf(lpbuf, ## args); \ +} while(0) + +#define PRINT_VERBOSE(verbose, cond, fmt, args...) \ +do { \ + if (cond > verbose) \ + break; \ + PRINT(fmt, ## args); \ +} while(0) + + +#endif // !CONFIG_H diff --git a/src/fifo.c b/src/fifo.c new file mode 100644 index 0000000..08aad32 --- /dev/null +++ b/src/fifo.c @@ -0,0 +1,150 @@ +/* + Copyright (C) 2008, 2009 Jiri Olsa <olsajiri@gmail.com> + + This file is part of the latrace. + + The latrace is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + The latrace 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with the latrace (file COPYING). If not, see + <http://www.gnu.org/licenses/>. +*/ + + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <unistd.h> +#include <sys/syscall.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <string.h> + +#include "config.h" + + +int lt_fifo_create(struct lt_config_audit *cfg, char *dir) +{ + int fd; + char fifo[100]; + + sprintf(fifo, "%s/fifo-%d", dir, (pid_t) syscall(SYS_gettid)); + + if (-1 == mkfifo(fifo, 0666)) { + perror("mkfifo failed"); + return -1; + } + + if (-1 == (fd = open(fifo, O_WRONLY))) { + perror("open fifo failed"); + return -1; + } + + return fd; +} + +int lt_fifo_open(struct lt_config_app *cfg, char *name) +{ + int fd; + + if (-1 == (fd = open(name, O_RDONLY))) + perror("open fifo failed"); + + PRINT_VERBOSE(cfg->sh.verbose, 1, "pipe openned fd: %d\n", fd); + return fd; +} + +int lt_fifo_send(struct lt_config_audit *cfg, int fd, char *buf, int len) +{ + static unsigned int written = 0; + + if (-1 == write(fd, buf, len)) { + perror("write failed"); + return -1; + } + + written += len; + PRINT_VERBOSE(cfg->sh.verbose, 1, "sending %d, total %u\n", + len, written); + return 0; +} + +int lt_fifo_recv(struct lt_config_app *cfg, struct lt_thread *t, void *buf, + int bufsize) +{ + static unsigned int red = 0; + ssize_t size; + struct lt_fifo_mbase *h = buf; + + if (-1 == (size = read(t->fifo_fd, h, sizeof(struct lt_fifo_mbase)))) { + perror("read failed"); + return -1; + } + + if (size == 0) + return -1; + + red += size; + + PRINT_VERBOSE(cfg->sh.verbose, 1, "received %d\n", h->len); + + if ((size + h->len) > bufsize) { + printf("thread %d - buffer max size reached\n", t->tid); + return -1; + } + + if (-1 == (size = read(t->fifo_fd, buf + sizeof(*h), h->len))) { + perror("read failed"); + return -1; + } + + red += size; + + PRINT_VERBOSE(cfg->sh.verbose, 1, "received %d, total %u\n", + size + sizeof(*h), red); + return 0; +} + +int lt_fifo_msym_get(struct lt_config_audit *cfg, char *buf, int type, + char *symname, char *libto, char *arg, char *argd) +{ + struct lt_fifo_msym *m = (struct lt_fifo_msym*) buf; + int len_data, len = sizeof(struct lt_fifo_msym); + + /* TODO need proper buf size checking */ + m->h.type = type; + m->h.tid = (pid_t) syscall(SYS_gettid); + gettimeofday(&m->h.tv, NULL); + + m->sym = 0; + m->lib = strlen(symname); + m->arg = m->lib + strlen(libto) + 1; + m->argd = m->arg + strlen(arg) + 1; + + len_data = sprintf(m->data, "%s %s %s %s", symname, libto, arg, argd); + + m->data[m->lib++] = 0x0; + m->data[m->arg++] = 0x0; + m->data[m->argd++] = 0x0; + m->data[len_data++] = 0x0; + + len += len_data; + m->h.len = len_data + (sizeof(*m) - sizeof(struct lt_fifo_mbase)); + + PRINT_VERBOSE(cfg->sh.verbose, 1, "sending data %d <%s> <%s> <%s> <%s>\n", + m->h.len, + m->data + m->sym, + m->data + m->lib, + m->data + m->arg, + m->data + m->argd); + return len; +} diff --git a/src/latrace.c b/src/latrace.c new file mode 100644 index 0000000..9527fcf --- /dev/null +++ b/src/latrace.c @@ -0,0 +1,45 @@ +/* + Copyright (C) 2008, 2009 Jiri Olsa <olsajiri@gmail.com> + + This file is part of the latrace. + + The latrace is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + The latrace 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with the latrace (file COPYING). If not, see + <http://www.gnu.org/licenses/>. +*/ + + +#include <stdio.h> + +#include "config.h" + + +static struct lt_config_app cfg; + + +int main(int argc, char **argv) +{ + if (-1 == lt_config(&cfg, argc, argv)) + return -1; + + if (-1 == lt_run(&cfg)) + return -1; + + if ((cfg.sh.pipe) && (*cfg.sh.output)) + fclose(cfg.sh.fout); + + if (cfg.sh.counts) + lt_stats_show(&cfg); + + return 0; +} diff --git a/src/list.h b/src/list.h new file mode 100644 index 0000000..d54f67b --- /dev/null +++ b/src/list.h @@ -0,0 +1,103 @@ +/* + Copyright (c) 2003-2006 Thomas Graf <tgraf@suug.ch> + + Taken from the libnl project and customized to fit for latrace. + Released undder following license notice. + + Copyright (C) 2008, 2009 Jiri Olsa <olsajiri@gmail.com> + + This file is part of the latrace. + + The latrace is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + The latrace 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with the latrace (file COPYING). If not, see + <http://www.gnu.org/licenses/>. +*/ + + +#ifndef LIST_H +#define LIST_H + +struct lt_list_head +{ + struct lt_list_head * next; + struct lt_list_head * prev; +}; + + +static inline void __lt_list_add(struct lt_list_head *obj, + struct lt_list_head *prev, + struct lt_list_head *next) +{ + prev->next = obj; + obj->prev = prev; + next->prev = obj; + obj->next = next; +} + +static inline void lt_list_add_tail(struct lt_list_head *obj, + struct lt_list_head *head) +{ + __lt_list_add(obj, head->prev, head); +} + +static inline void lt_list_add_head(struct lt_list_head *obj, + struct lt_list_head *head) +{ + __lt_list_add(obj, head, head->next); +} + +static inline void lt_list_del(struct lt_list_head *obj) +{ + obj->next->prev = obj->prev; + obj->prev->next = obj->next; +} + +static inline int lt_list_empty(struct lt_list_head *head) +{ + return head->next == head; +} + +#define lt_container_of(ptr, type, member) ({ \ + const typeof( ((type *)0)->member ) *__mptr = (ptr); \ + (type *)( (char *)__mptr - ((size_t) &((type *)0)->member));}) + +#define lt_list_entry(ptr, type, member) \ + lt_container_of(ptr, type, member) + +#define lt_list_at_tail(pos, head, member) \ + ((pos)->member.next == (head)) + +#define lt_list_at_head(pos, head, member) \ + ((pos)->member.prev == (head)) + +#define LT_LIST_HEAD(name) \ + struct lt_list_head name = { &(name), &(name) } + +#define lt_list_first_entry(head, type, member) \ + lt_list_entry((head)->next, type, member) + +#define lt_list_for_each_entry(pos, head, member) \ + for (pos = lt_list_entry((head)->next, typeof(*pos), member); \ + &(pos)->member != (head); \ + (pos) = lt_list_entry((pos)->member.next, typeof(*(pos)), member)) + +#define lt_list_for_each_entry_safe(pos, n, head, member) \ + for (pos = lt_list_entry((head)->next, typeof(*pos), member), \ + n = lt_list_entry(pos->member.next, typeof(*pos), member); \ + &(pos)->member != (head); \ + pos = n, n = lt_list_entry(n->member.next, typeof(*n), member)) + +#define lt_init_list_head(head) \ + do { (head)->next = (head); (head)->prev = (head); } while (0) + +#endif diff --git a/src/objsearch.c b/src/objsearch.c new file mode 100644 index 0000000..ae0823f --- /dev/null +++ b/src/objsearch.c @@ -0,0 +1,192 @@ +/* + Copyright (C) 2008, 2009 Jiri Olsa <olsajiri@gmail.com> + + This file is part of the latrace. + + The latrace is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + The latrace 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with the latrace (file COPYING). If not, see + <http://www.gnu.org/licenses/>. +*/ + + +#include <string.h> + +#include "config.h" + + +static int add_subst(struct lt_config_audit *cfg, char *subst) +{ + struct lt_objsearch *s; + char *dst, *src = subst; + int type; + + if (cfg->subst_cnt == LT_NAMES_MAX) + return -1; + + do { + if ((dst = strchr(subst, '='))) { + type = LT_OS_PATH; + break; + } + + if ((dst = strchr(subst, '%'))) { + type = LT_OS_PTN; + break; + } + + if ((dst = strchr(subst, '~'))) { + type = LT_OS_PTN2PATH; + break; + } + + } while(0); + + if (!dst) + return -1; + + *dst++ = 0x0; + + PRINT_VERBOSE(cfg->sh.verbose, 2, "adding subst type %d, src [%s], dst [%s]\n", + type, src, dst); + + s = &cfg->subst[cfg->subst_cnt++]; + s->type = type; + s->src = src; + s->dst = dst; + + return 0; +} + +/* + Comparing src with the name, if matched + replace the name with dst. + + name -> /lib/krava.so + src -> /lib/krava.so + dst -> /lib/debil.so + + ret -> /lib/debil.so +*/ +static int match_path(struct lt_config_audit *cfg, const char *name, + struct lt_objsearch *s, char **ret) +{ + PRINT_VERBOSE(cfg->sh.verbose, 2, "name [%s], src [%s], dst [%s]\n", + name, s->src, s->dst); + + *ret = s->dst; + + if (!strcmp(name, s->src)) + return 1; + + return 0; +} + +/* + Matching src in the name, if found, + replace the src with dst part. + + name -> /lib/krava.so + src -> krava + dst -> debil + + ret -> /lib/debil.so +*/ +static int match_ptn(struct lt_config_audit *cfg, const char *name, + struct lt_objsearch *s, char **ret) +{ + char *pat, *r; + + PRINT_VERBOSE(cfg->sh.verbose, 2, "name [%s], src [%s], dst [%s]\n", + name, s->src, s->dst); + + pat = strstr(name, s->src); + if (!pat) + return 0; + + r = *ret = malloc(strlen(name) + strlen(s->dst)); + if (!r) { + perror("malloc failed"); + return 0; + } + + strncpy(r, name, pat - name); + r += (pat - name); + strcpy(r, s->dst); + strcat(r, pat + strlen(s->src)); + + PRINT_VERBOSE(cfg->sh.verbose, 2, "return %s\n", *ret); + + return 1; +} + +/* + Looking for src in the name, if found, + replace the name with dst. + + name -> /lib/krava.so + src -> krava + dst -> /lib/krava1.so + + ret -> /lib/krava1.so +*/ +static int match_ptn2path(struct lt_config_audit *cfg, const char *name, + struct lt_objsearch *s, char **ret) +{ + PRINT_VERBOSE(cfg->sh.verbose, 2, "name [%s], src [%s], dst [%s]\n", + name, s->src, s->dst); + + *ret = s->dst; + if (strstr(name, s->src)) + return 1; + + return 0; +} + +static int match(struct lt_config_audit *cfg, const char *name, + struct lt_objsearch *s, char **ret) +{ + switch(s->type) { + case LT_OS_PATH: + return match_path(cfg, name, s, ret); + case LT_OS_PTN: + return match_ptn(cfg, name, s, ret); + case LT_OS_PTN2PATH: + return match_ptn2path(cfg, name, s, ret); + }; + + return 0; +} + +int lt_objsearch_init(struct lt_config_audit *cfg, char **ptr, int cnt) +{ + int i; + + for(i = 0; i < cnt; i++) + if (-1 == add_subst(cfg, ptr[i])) + return -1; + + return 0; +} + +char* lt_objsearch(struct lt_config_audit *cfg, const char *name, + uintptr_t *cookie, unsigned int flag) +{ + int i; + char *ret = NULL; + + for(i = 0; i < cfg->subst_cnt; i++) + if (match(cfg, name, &cfg->subst[i], &ret)) + return ret; + + return (char*) name; +} diff --git a/src/output.c b/src/output.c new file mode 100644 index 0000000..b8e8bed --- /dev/null +++ b/src/output.c @@ -0,0 +1,125 @@ +/* + Copyright (C) 2008, 2009 Jiri Olsa <olsajiri@gmail.com> + + This file is part of the latrace. + + The latrace is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + The latrace 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with the latrace (file COPYING). If not, see + <http://www.gnu.org/licenses/>. +*/ + +#include <stdlib.h> + +#include "config.h" + +static char spaces[] = " "; + +static int print_details(struct lt_config_shared *cfg, char *argdbuf) +{ + fprintf(cfg->fout, "%s\n", argdbuf); + return 0; +} + +#define PRINT_TID() \ +do { \ + fprintf(cfg->fout, "%5d ", (pid_t) syscall(SYS_gettid)); \ +} while(0) + +/* libiberty external */ +extern char* cplus_demangle(const char *mangled, int options); + +#define DEMANGLE(sym, d) \ +do { \ + char *dem; \ + dem = cplus_demangle(sym, 0); \ + if (dem) { \ + sym = dem; \ + d = 1; \ + } \ +} while(0) + +int lt_out_entry(struct lt_config_shared *cfg, const char *symname, char *lib_to, + char *argbuf, char *argdbuf) +{ + int demangled = 0; + + /* Print thread ID */ + if (!cfg->hide_tid) + PRINT_TID(); + + /* Print indentation. */ + if (cfg->indent_depth && cfg->indent_sym) + fprintf(cfg->fout, "%.*s", cfg->indent_depth * cfg->indent_size, spaces); + + /* Demangle the symbol if needed */ + if (cfg->demangle) + DEMANGLE(symname, demangled); + + /* Print the symbol and arguments. */ + if (*argbuf) + fprintf(cfg->fout, "%s(%s) [%s] {\n", symname, argbuf, lib_to); + else + fprintf(cfg->fout, "%s [%s] %c\n", + symname, lib_to, + cfg->braces ? '{' : ' '); + + if (demangled) + free((char*) symname); + + /* Print arguments' details. */ + if (cfg->args_detailed && *argdbuf) + print_details(cfg, argdbuf); + + fflush(NULL); + return 0; +} + +int lt_out_exit(struct lt_config_shared *cfg, const char *symname, char *lib_to, + char *argbuf, char *argdbuf) +{ + int demangled = 0; + + if (!*argbuf && (!cfg->braces)) + return 0; + + /* Print thread ID */ + if (!cfg->hide_tid) + PRINT_TID(); + + /* Print indentation. */ + if (cfg->indent_depth && cfg->indent_sym) + fprintf(cfg->fout, "%.*s", cfg->indent_depth * cfg->indent_size, spaces); + + /* We got here, because we have '-B' option enabled. */ + if (!*argbuf && (cfg->braces)) { + fprintf(cfg->fout, "}\n"); + return 0; + } + + /* Demangle the symbol if needed */ + if (cfg->demangle) + DEMANGLE(symname, demangled); + + /* Print the symbol and arguments. */ + fprintf(cfg->fout, "} %s%s\n", symname, argbuf); + + if (demangled) + free((char*) symname); + + /* Print arguments' details. */ + if (cfg->args_detailed && *argdbuf) + print_details(cfg, argdbuf); + + fflush(NULL); + return 0; +} diff --git a/src/run.c b/src/run.c new file mode 100644 index 0000000..23eb983 --- /dev/null +++ b/src/run.c @@ -0,0 +1,319 @@ +/* + Copyright (C) 2008, 2009 Jiri Olsa <olsajiri@gmail.com> + + This file is part of the latrace. + + The latrace is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + The latrace 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with the latrace (file COPYING). If not, see + <http://www.gnu.org/licenses/>. +*/ + + +#include <sys/types.h> +#include <unistd.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <sys/select.h> +#include <sys/inotify.h> +#include <sys/types.h> +#include <dirent.h> +#include <string.h> +#include <errno.h> + +#include "config.h" + +extern char **environ; +extern struct timeval tv_program_start; +extern struct timeval tv_program_stop; + + +static int store_config(struct lt_config_app *cfg, char *file) +{ + int fd; + + if (-1 == (fd = open(file, O_CREAT | O_TRUNC | O_RDWR, + S_IRUSR))) { + perror("open failed"); + return -1; + } + + if (-1 == write(fd, &cfg->sh, sizeof(cfg->sh))) { + perror("read failed"); + return -1; + } + + close(fd); + return 0; +} + +static int set_dir_notify(char *dir) +{ + int fd; + + if (-1 == (fd = inotify_init())) { + perror("inotify_init failed"); + return -1; + } + + if (-1 == inotify_add_watch(fd, dir, IN_CREATE)) { + perror("inotify_add_watch failed"); + return -1; + } + + return fd; +} + +static int get_fifo_dir(char *buf, int len) +{ + snprintf(buf, len , "%s-XXXXXX", CONFIG_LT_CONFIG); + if (NULL == mkdtemp(buf)) { + perror("mkdtemp failed"); + return -1; + } + + return 0; +} + +static int get_fifo(struct lt_config_app *cfg, int notify_fd, + char *dir, pid_t *pid) +{ + char str_fifo[LT_MAXFILE]; + unsigned char buf[1000]; + struct inotify_event *event = (struct inotify_event*) buf; + + if (-1 == read(notify_fd, event, 1000)) { + perror("read notify failed"); + return -1; + } + + sscanf(event->name, "fifo-%d", pid); + sprintf(str_fifo, "%s/%s", dir, event->name); + + PRINT_VERBOSE(cfg->sh.verbose, 1, "thread id %d, got fifo: %s\n", + *pid, str_fifo); + + return lt_fifo_open(cfg, str_fifo); +} + +static int process_fifo(struct lt_config_app *cfg, struct lt_thread *t) +{ + static char buf[FIFO_MSG_MAXLEN]; + + struct lt_fifo_mbase *mbase = (struct lt_fifo_mbase*) buf; + + if (-1 == lt_fifo_recv(cfg, t, mbase, FIFO_MSG_MAXLEN)) + return -1; + + if ((FIFO_MSG_TYPE_ENTRY != mbase->type) && + (FIFO_MSG_TYPE_EXIT != mbase->type)) { + PRINT_VERBOSE(cfg->sh.verbose, 1, "unexpected message type %d\n", + mbase->type); + return -1; + } + + struct lt_fifo_msym *msym = (struct lt_fifo_msym*) buf; + + if (cfg->sh.counts) + return lt_stats_sym(cfg, t, msym); + + if (FIFO_MSG_TYPE_ENTRY == msym->h.type) { + + cfg->sh.indent_depth++; + lt_out_entry(&cfg->sh, + msym->data + msym->sym, + msym->data + msym->lib, + msym->data + msym->arg, + msym->data + msym->argd); + + } else if (FIFO_MSG_TYPE_EXIT == msym->h.type) { + + lt_out_exit(&cfg->sh, + msym->data + msym->sym, + msym->data + msym->lib, + msym->data + msym->arg, + msym->data + msym->argd); + + cfg->sh.indent_depth--; + } + + return 0; +} + +static int process(struct lt_config_app *cfg, pid_t pid, char *dir, int notify_fd) +{ + int finish = 0, getin = 1; + int max_fd = notify_fd; + fd_set cfg_set, wrk_set; + int status; + + FD_ZERO(&cfg_set); + FD_SET(notify_fd, &cfg_set); + + while(!waitpid(pid, &status, WNOHANG) || + /* let all the thread fifo close */ + (finish) || + /* Get inside at least once, in case the traced program + finished before we got here. Another case is if there's + an input on notify descriptor, we want to try another + select to be sure we dont miss any other event. */ + (getin)) + { + + struct timeval tv = { 0, 100 }; + struct lt_thread *t; + int ret; + + getin = 0; + + wrk_set = cfg_set; + if (-1 == (ret = select(max_fd + 1, &wrk_set, NULL, NULL, &tv))) { + perror("select failed"); + return -1; + } + + if (!ret) + continue; + + /* process notify */ + if (FD_ISSET(notify_fd, &wrk_set)) { + int fd; + pid_t pid; + + /* try to get any event at least once again */ + getin = 1; + + if (-1 == (fd = get_fifo(cfg, notify_fd, dir, &pid))) + continue; + + if (NULL == (t = lt_thread_add(cfg, fd, pid))) { + close(fd); + continue; + } + + finish++; + + FD_SET(fd, &cfg_set); + max_fd = (max_fd < fd ? fd : max_fd); + ret--; + } + + /* process fifo */ + for(t = cfg->threads; t ; t = t->next) { + if (FD_ISSET(t->fifo_fd, &wrk_set)) { + if (-1 == process_fifo(cfg, t)) { + FD_CLR(t->fifo_fd, &cfg_set); + gettimeofday(&t->tv_stop, NULL); + finish--; + } + ret--; + } + } + } + + return status; +} + +static int remove_dir(struct lt_config_app *cfg, char *name) +{ + DIR *dir; + struct dirent *d; + + if (NULL == (dir = opendir(name))) { + perror("opendir failed"); + return -1; + } + + while((d = readdir(dir))) { + char file[LT_MAXFILE]; + + if (!strcmp(".", d->d_name) || + (!strcmp("..", d->d_name))) + continue; + + sprintf(file, "%s/%s", name, d->d_name); + if (-1 == (unlink(file))) + perror("unlink failed"); + } + + closedir(dir); + + if (-1 == remove(name)) { + perror("remove failed"); + return -1; + } + + return 0; +} + +int lt_run(struct lt_config_app *cfg) +{ + pid_t child_pid; + char str_dir[LT_MAXFILE]; + char str_cfg[LT_MAXFILE]; + int status; + int notify_fd = -1; + + if (-1 == get_fifo_dir(str_dir, LT_MAXFILE)) + return -1; + + sprintf(str_cfg, "%s/config", str_dir); + if (-1 == store_config(cfg, str_cfg)) + return -1; + + if (cfg->sh.pipe && + (-1 == (notify_fd = set_dir_notify(str_dir)))) + return -1; + + gettimeofday(&tv_program_start, NULL); + + if (0 == (child_pid = fork())) { + char str_audit[100]; + + sprintf(str_audit, "%s/libltaudit.so.%s", CONFIG_LIBDIR, LT_VER); + setenv("LD_AUDIT", str_audit, 1); + setenv("LT_DIR", str_dir, 1); + + PRINT_VERBOSE(cfg->sh.verbose, 1, "executing %s\n", cfg->prog); + + if (-1 == execvp(cfg->prog, cfg->arg)) { + printf("execve failed for \"%s\" : %s\n", + cfg->prog, strerror(errno)); + return -1; + } + } else if (child_pid < 0) { + perror("fork failed"); + return -1; + } + + if (cfg->sh.pipe) + status = process(cfg, child_pid, str_dir, notify_fd); + else + waitpid(child_pid, &status, 0); + + gettimeofday(&tv_program_stop, NULL); + + printf("\n%s finished - ", cfg->prog); + + if (WIFEXITED(status)) { + printf("exited, status=%d\n", WEXITSTATUS(status)); + } else if (WIFSIGNALED(status)) { + printf("killed by signal %d\n", WTERMSIG(status)); + } + + remove_dir(cfg, str_dir); + return 0; +} diff --git a/src/stats.c b/src/stats.c new file mode 100644 index 0000000..59e0d9c --- /dev/null +++ b/src/stats.c @@ -0,0 +1,328 @@ +/* + Copyright (C) 2008, 2009 Jiri Olsa <olsajiri@gmail.com> + + This file is part of the latrace. + + The latrace is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + The latrace 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with the latrace (file COPYING). If not, see + <http://www.gnu.org/licenses/>. +*/ + + +#include <search.h> +#include <string.h> +#include <stdio.h> +#include <stdlib.h> +#include <setjmp.h> + +#include "config.h" + + +struct timeval tv_program_start; +struct timeval tv_program_stop; + +/* res = a - b*/ +static int tv_sub(struct timeval *res, struct timeval *a, struct timeval *b) +{ + res->tv_sec = a->tv_sec - b->tv_sec; + + if (a->tv_usec > b->tv_usec) + res->tv_usec = a->tv_usec - b->tv_usec; + else { + res->tv_usec = a->tv_usec + (1000000 - b->tv_usec); + if (res->tv_sec) + res->tv_sec--; + } + + return 0; +} + +/* res = a + b*/ +static int tv_add(struct timeval *res, struct timeval *a, struct timeval *b) +{ + struct timeval tv; + tv.tv_sec = a->tv_sec + b->tv_sec; + tv.tv_usec = a->tv_usec + b->tv_usec; + if (tv.tv_usec > 1000000) { + tv.tv_usec -= 1000000; + tv.tv_sec++; + } + + *res = tv; + return 0; +} + +int lt_stats_alloc(struct lt_config_app *cfg, struct lt_thread *t) +{ + int i; + + if (!t->sym_array) { + if (!hcreate_r(t->sym_max = LT_SYM_HMAX, &t->sym_htab)) { + perror("hcreate_r failed"); + return -1; + } + + t->sym_array = (struct lt_stats_sym**) malloc( + t->sym_max * sizeof(struct lt_stats_sym*)); + + if (!t->sym_array) { + perror("malloc failed"); + return -1; + } + + return 0; + } + + + PRINT_VERBOSE(cfg->sh.verbose, 1, + "too bad, out of symbols, reallocation... %p - %d\n", + t->sym_array, t->sym_cnt); + + /* hash table reallocation */ + t->sym_max += LT_SYM_HMAX; + hdestroy_r(&t->sym_htab); + + PRINT_VERBOSE(cfg->sh.verbose, 1, "creating new hash table\n"); + + if (!hcreate_r(t->sym_max, &t->sym_htab)) { + perror("hcreate_r failed"); + return -1; + } + + t->sym_array = (struct lt_stats_sym**) realloc(t->sym_array, + t->sym_max * sizeof(struct lt_stats_sym*)); + + if (!t->sym_array) { + perror("realloc failed"); + return -1; + } + + PRINT_VERBOSE(cfg->sh.verbose, 1, + "adding symbols to new hash table %p\n", + t->sym_array); + + for(i = 0; i < t->sym_cnt; i++) { + ENTRY e, *ep; + struct lt_stats_sym *sym; + + sym = t->sym_array[i]; + e.key = sym->name; + e.data = sym; + + PRINT_VERBOSE(cfg->sh.verbose, 1, + "adding symbol %s\n", sym->name); + + if (0 == hsearch_r(e, ENTER, &ep, &t->sym_htab)) { + printf("failed to add hash item during reallocation\n"); + return -1; + } + } + + PRINT_VERBOSE(cfg->sh.verbose, 1, "reallocation ok\n"); + return 0; +} + +int lt_stats_sym(struct lt_config_app *cfg, struct lt_thread *t, + struct lt_fifo_msym* m) +{ + ENTRY e, *ep; + char buf[1000]; + struct lt_stats_sym *sym; + jmp_buf env; + int realloc = 0; + + sprintf(buf, "%s%s", m->data + m->sym, m->data + m->lib); + e.key = strdup(buf); + e.data = 0; + + /* array got out of space */ + if ((t->sym_cnt == t->sym_max) && (-1 == lt_stats_alloc(cfg, t))) + return -1; + + /* hash table got out of space */ + if (setjmp(env)) + realloc = 1; + + if (0 == hsearch_r(e, ENTER, &ep, &t->sym_htab)) { + if (!realloc) { + if (-1 == lt_stats_alloc(cfg, t)) + return -1; + longjmp(env, 1); + } + return -1; + } + + if (!ep->data) { + sym = malloc(sizeof(struct lt_stats_sym)); + memset(sym, 0, sizeof(struct lt_stats_sym)); + + ep->data = sym; + + sym->name = e.key; + sym->sym = strdup(m->data + m->sym); + sym->lib = strdup(m->data + m->lib); + + t->sym_array[t->sym_cnt] = sym; + t->sym_cnt++; + + PRINT_VERBOSE(cfg->sh.verbose, 1, + "adding symbol %d %s\n", t->sym_cnt, sym->name); + } else + free(e.key); + + sym = ep->data; + if (FIFO_MSG_TYPE_ENTRY == m->h.type) { + sym->call++; + sym->tv_cur = m->h.tv; + } + + if (FIFO_MSG_TYPE_EXIT == m->h.type) { + struct timeval tv; + tv_sub(&tv, &m->h.tv, &sym->tv_cur); + tv_add(&sym->tv_all, &sym->tv_all, &tv); + } + + return 0; +} + +static int csort; + +static int qsort_compar(const void *a, const void *b) +{ + struct lt_stats_sym *data_a = *((struct lt_stats_sym**) a); + struct lt_stats_sym *data_b = *((struct lt_stats_sym**) b); + + if (csort == LT_CSORT_TIME) { + if (data_a->tv_all.tv_sec != data_b->tv_all.tv_sec) + return data_b->tv_all.tv_sec - data_a->tv_all.tv_sec; + return data_b->tv_all.tv_usec - data_a->tv_all.tv_usec; + } + + if (csort == LT_CSORT_PERCENT) { + int percent_a = data_a->percent * 100000; + int percent_b = data_b->percent * 100000; + return percent_b - percent_a; + } + + if (csort == LT_CSORT_CALL) { + return data_b->call - data_a->call; + } + + if (csort == LT_CSORT_UCALL) { + return data_b->usec_call - data_a->usec_call; + } + + if (csort == LT_CSORT_LIB) { + int ret = strcmp(data_a->lib, data_b->lib); + if (ret) + return ret; + return strcmp(data_a->sym, data_b->sym); + } + + if (csort == LT_CSORT_SYM) { + int ret = strcmp(data_a->sym, data_b->sym); + if (ret) + return ret; + return strcmp(data_a->lib, data_b->lib); + } + + return 0; +} + +static int lt_stats_show_thread(struct lt_config_app *cfg, struct lt_thread *t) +{ + int i; + struct timeval tv_thread_real; + struct timeval tv_thread_accu = { 0, 0}; + float time_global; + + PRINT_VERBOSE(cfg->sh.verbose, 1, "counting total time\n"); + for(i = 0; i < t->sym_cnt; i++) { + struct lt_stats_sym *sym = t->sym_array[i]; + tv_add(&tv_thread_accu, &tv_thread_accu, &sym->tv_all); + } + + time_global = tv_thread_accu.tv_sec * 1000000 + tv_thread_accu.tv_usec; + + PRINT_VERBOSE(cfg->sh.verbose, 1, "counting post mortem statistics\n"); + + for(i = 0; i < t->sym_cnt; i++) { + struct lt_stats_sym *sym = t->sym_array[i]; + u_int time_sym = sym->tv_all.tv_sec*1000000 + sym->tv_all.tv_usec; + + sym->percent = time_sym / (time_global/100); + sym->usec_call = time_sym/sym->call; + } + + PRINT_VERBOSE(cfg->sh.verbose, 1, "sorting\n"); + + csort = cfg->csort; + qsort(t->sym_array, t->sym_cnt, sizeof(struct lt_stats_sym*), qsort_compar); + + PRINT_VERBOSE(cfg->sh.verbose, 1, "printing\n"); + + tv_sub(&tv_thread_real, &t->tv_stop, &t->tv_start); + printf("\nThread %d (runtime %u.%06u sec)\n", + t->tid, (u_int) tv_thread_real.tv_sec, (u_int) tv_thread_real.tv_usec); + printf("%3s %-6s %10s %10s %10s %-30s\n", + "sec", "usec", "%", "usec/call", "calls", "symbol"); + printf("%3s %-6s %10s %10s %10s %-30s\n", + "---", "------", "----------", "----------", "----------", "------------------------------"); + + for(i = 0; i < t->sym_cnt; i++) { + struct lt_stats_sym *sym = t->sym_array[i]; + + /* sec:usec */ + printf("%3u:%06u ", (u_int) sym->tv_all.tv_sec, (u_int) sym->tv_all.tv_usec); + + /* % */ + printf("%10f ", sym->percent); + + /* usec/call */ + printf("%10u ", sym->usec_call); + + /* call */ + printf("%10u ", sym->call); + + if (LT_CSORT_LIB == cfg->csort) { + /* calls symbol */ + printf("%s ", sym->lib); + printf("%s\n", sym->sym); + } else { + printf("%s@", sym->sym); + printf("%s\n", sym->lib); + } + } + + return 0; +} + +int lt_stats_show(struct lt_config_app *cfg) +{ + struct lt_thread *t; + + struct timeval tv_program_real; + tv_sub(&tv_program_real, &tv_program_stop, &tv_program_start); + + printf("\n--------------------------------------------------------------------------\n"); + printf("Statistics for [%s] total runtime: %u.%06u sec\n", + cfg->prog, + (u_int) tv_program_real.tv_sec, + (u_int) tv_program_real.tv_usec); + + for(t = lt_thread_first(cfg); t; t = lt_thread_next(cfg)) + lt_stats_show_thread(cfg, t); + + return 0; +} + diff --git a/src/sysdeps/arm/stack.c b/src/sysdeps/arm/stack.c new file mode 100644 index 0000000..d1388e0 --- /dev/null +++ b/src/sysdeps/arm/stack.c @@ -0,0 +1,269 @@ +/* + ARM EABI argument extraction + + Copyright (C) 2009 Akos Pasztory <akos.pasztory@gmail.com> + + This file is part of the latrace. + + The latrace is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + The latrace 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with the latrace (file COPYING). If not, see + <http://www.gnu.org/licenses/>. +*/ + + +#include "config.h" + +static inline unsigned long align_up(unsigned long v, unsigned long a) +{ + return (v + a-1) & ~(a-1); +} + +/* + * The .arch field is split the following way: + * -- valid :1 + * -- size :15 + * -- offset :16 + */ + +static inline void arch_set(struct lt_arg *arg, + unsigned int size, unsigned int offs) +{ + arg->arch = (void *)((size << 16) | offs | 0x80000000); +} + +static inline int arch_valid(struct lt_arg *arg) +{ + return (unsigned int)arg->arch & 0x80000000; +} + +static inline unsigned int arch_get_size(struct lt_arg *arg) +{ + return ((unsigned int)arg->arch & ~0x80000000) >> 16; +} + +static inline unsigned int arch_get_offs(struct lt_arg *arg) +{ + return (unsigned int)arg->arch & 0xffff; +} + +/* Processes a struct entirely in memory. */ +static int process_struct_mem(struct lt_config_shared *cfg, struct lt_arg *arg, + void *pval, struct lt_args_data *data) +{ + int i; + unsigned long npval; + struct lt_arg *a; + + lt_args_cb_struct(cfg, LT_ARGS_STRUCT_ITSELF, arg, 0, data, 0); + npval = (unsigned long)pval; + i = 0; + lt_list_for_each_entry(a, arg->args_head, args_list) { + lt_args_cb_struct(cfg, LT_ARGS_STRUCT_ARG, a, + (void *)(npval + arch_get_offs(a)), + data, i+1 == arg->mmbcnt); + ++i; + } + return 0; +} + +/* Returns a bytearray slice from OFFS with SIZE length, treating the + * registers and the stack consecutively. Return value is static, + * only valid until the next get() call. */ +static void *get(unsigned long offs, int size, La_regs *regs) +{ + /* NB: size of buf is fixed. But if we only copy POD types, hopefully + * it won't become a problem. */ + static __thread char buf[64]; + int i; + + for (i = 0; i < size; ++i) { + if (offs+i < 16) + buf[i] = ((char *)regs->lr_reg)[offs+i]; + else + buf[i] = *(char *)(regs->lr_sp + (offs+i-16)); + } + return buf; +} + +/* Calculate member sizes and offsets of a structure. Returns the + * size of the entire structure. */ +static unsigned int calcstruct(struct lt_arg *arg) +{ + unsigned int maxl, moffs; + struct lt_arg *a; + + /* XXX: this is incorrect for struct-in-struct... */ + maxl = moffs = 0; + lt_list_for_each_entry(a, arg->args_head, args_list) { + /* We need to know the strictest aligned member to + * determine the size of the entire struct. */ + if (a->type_len > maxl) + maxl = a->type_len; + /* Align members naturally. */ + moffs = align_up(moffs, a->type_len); + arch_set(a, a->type_len, moffs); + moffs += a->type_len; + } + return align_up(moffs, maxl); +} + +/* Calculates all offsets and `real' (padded) sizes of arguments + * (including struct members). Stores them in ->arch. */ +static void calcofs(struct lt_args_sym *asym) +{ + int i; + unsigned int offs; + struct lt_arg *arg; + + /* If function returns a struct by value larger than a word, + * it is stored in callee-allocated memory and r0 contains the + * address. */ + arg = asym->args[LT_ARGS_RET]; + offs = 0; + if (arg->dtype == LT_ARGS_DTYPE_STRUCT) { + if (!arg->pointer) + offs = 4; + arch_set(arg, calcstruct(arg), 0); + } + for (i = 1; i < asym->argcnt; ++i) { + unsigned int asize; + + arg = asym->args[i]; + /* Struct sizes/offsets should be calculated at all times. */ + if (arg->dtype == LT_ARGS_DTYPE_STRUCT) + calcstruct(arg); + if (arg->dtype != LT_ARGS_DTYPE_STRUCT || arg->pointer) { + /* Non-composite type. */ + /* Size is 4 if arg is smaller than a word. */ + asize = arg->type_len; + if (asize < 4) + asize = 4; + /* Types are aligned to their sizes. */ + offs = align_up(offs, asize); + arch_set(arg, asize, offs); + } else { + /* Composite type. */ + asize = calcstruct(arg); + arch_set(arg, asize, offs); + } + offs += asize; + /* If we are allocating from registers (4*4 bytes), + * each arg must start in a new one. */ + if (offs < 16) + offs = align_up(offs, 4); + } +} + +/* + * -- args which fit are in r0..r3, rest is on the stack + * -- if return type is struct, r0 contains the address for it + * -- dword sized args must be aligned at even registers + * -- one argument may be split between registers and stack + * -- on stack, 8byte values are aligned % 8 == 0 + */ +int lt_stack_process(struct lt_config_shared *cfg, struct lt_args_sym *asym, + La_regs *regs, struct lt_args_data *data) +{ + int i; + + for (i = 1; i < asym->argcnt; ++i) { + struct lt_arg *arg; + void *p; + unsigned int o, lastp; + + lastp = i+1 == asym->argcnt; + arg = asym->args[i]; + if (!arch_valid(arg)) + calcofs(asym); + /* POD */ + if (arg->dtype != LT_ARGS_DTYPE_STRUCT) { + p = get(arch_get_offs(arg), arch_get_size(arg), regs); + lt_args_cb_arg(cfg, arg, p, data, lastp, 1); + continue; + } + /* struct pointer */ + if (arg->pointer) { + p = get(arch_get_offs(arg), sizeof(void *), regs); + p = *(void **)p; + lt_args_cb_arg(cfg, arg, p, data, lastp, 1); + if (cfg->args_detailed) + process_struct_mem(cfg, arg, p, data); + continue; + } + /* struct by value */ + o = arch_get_offs(arg); + /* If part of the struct is in registers this outputs 'REG', + * otherwise, the stack address is printed. */ + lt_args_cb_arg(cfg, arg, + (void *)(o < 16 ? 0 : regs->lr_sp + o-16), + data, lastp, 1); + if (cfg->args_detailed) { + int j; + struct lt_arg *m; + + j = 0; + lt_args_cb_struct(cfg, LT_ARGS_STRUCT_ITSELF, arg, + 0, data, 0); + lt_list_for_each_entry(m, arg->args_head, args_list) { + unsigned int o; + + o = arch_get_offs(arg) + arch_get_offs(m); + p = get(o, arch_get_size(m), regs); + lt_args_cb_struct(cfg, LT_ARGS_STRUCT_ARG, m, + p, data, j+1 == arg->mmbcnt); + ++j; + } + } + } + return 0; +} + +/* + * -- 32bit return values in r0 + * -- 64bit in {r0, r1} + * -- composite < 4bytes in r0 + * -- composite > 4bytes in memory (address is passed as the first parameter + * of the call) + */ +int lt_stack_process_ret(struct lt_config_shared *cfg, + struct lt_args_sym *asym, La_retval *regs, + struct lt_args_data *data) +{ + struct lt_arg *arg; + void *pval; + + /* I think this works for both 32 and 64 bit values (r0 and r1 + * are consecutive in lrv_reg[]). */ + pval = &(regs->lrv_reg[0]); + arg = asym->args[LT_ARGS_RET]; + lt_args_cb_arg(cfg, arg, pval, data, 1, 0); + if (cfg->args_detailed && arg->dtype == LT_ARGS_DTYPE_STRUCT) { + unsigned int asize; + /* AAPCS says: a composite type less than 4 bytes is + * returned in r0. Larger types have memory allocated + * by caller and its address passed as the (hidden) + * 1st argument. + * + * NB: Though I, personally, haven't found it in the + * AAPCS, it's both logical (and apparently gcc does + * so) to return this address in r0. */ + asize = arch_get_size(arg); + if (arg->pointer || asize > 4) + pval = (void *)regs->lrv_reg[0]; + else if (asize > 4) + pval = ®s->lrv_reg[0]; + process_struct_mem(cfg, arg, pval, data); + } + return 0; +} +/* End of stack.c */ diff --git a/src/sysdeps/i686/stack.c b/src/sysdeps/i686/stack.c new file mode 100644 index 0000000..5d2c539 --- /dev/null +++ b/src/sysdeps/i686/stack.c @@ -0,0 +1,131 @@ +/* + Copyright (C) 2008, 2009 Jiri Olsa <olsajiri@gmail.com> + + This file is part of the latrace. + + The latrace is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + The latrace 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with the latrace (file COPYING). If not, see + <http://www.gnu.org/licenses/>. +*/ + + +#include "config.h" +#include "stack.h" + +static int process_struct(struct lt_config_shared *cfg, struct lt_arg *arg, + void *pval, struct lt_args_data *data) +{ + struct lt_arg *a; + int i = 0; + + lt_args_cb_struct(cfg, LT_ARGS_STRUCT_ITSELF, arg, pval, data, 0); + + if (arg->pointer) + pval = *((void**) pval); + + lt_list_for_each_entry(a, arg->args_head, args_list) { + int last = (i + 1) == arg->mmbcnt; + + /* For the type size up to word (4), the offset of the member + is aligned to its size. For the type size over the word, + the offset of the member is aligned to word. */ + + int nsize = a->type_len > 4 ? 4 : a->type_len; + int naligned = (u_int) pval % nsize; + + if (naligned) + pval += nsize - naligned; + + lt_args_cb_struct(cfg, LT_ARGS_STRUCT_ARG, a, pval, data, last); + + pval += a->type_len; + i++; + } + + return 0; +} + +/* + the traced program stack (in regs) + should look like this: + + ... + esp + 12 2nd argument + esp + 8 1st argument + esp + 4 possible return structure/union address + esp return function address + +*/ +int lt_stack_process(struct lt_config_shared *cfg, struct lt_args_sym *asym, + La_regs *regs, struct lt_args_data *data) +{ + int i; + void *pval; + struct lt_arg *argret; + + /* get the esp reg and skip the return address */ + pval = (void*) regs->lr_esp; + pval += sizeof(void*); + + + /* if the function returns structure by value, + there's a hidden first argument we need to skip */ + argret = asym->args[LT_ARGS_RET]; + if ((!argret->pointer) && + (argret->dtype == LT_ARGS_DTYPE_STRUCT)) + pval += sizeof(void*); + + + for(i = 1; i < asym->argcnt; i++) { + + struct lt_arg *arg = asym->args[i]; + int last = (i + 1) == asym->argcnt; + + lt_args_cb_arg(cfg, arg, pval, data, last, 1); + + if ((cfg->args_detailed) && + (LT_ARGS_DTYPE_STRUCT == arg->dtype)) + process_struct(cfg, arg, pval, data); + + pval += arg->pointer ? sizeof(void*) : LT_STACK_ALIGN(arg->type_len); + } + + return 0; +} + +/* x86 is easy, everything is in eax register */ +int lt_stack_process_ret(struct lt_config_shared *cfg, struct lt_args_sym *asym, + La_retval *regs, struct lt_args_data *data) +{ + struct lt_arg *arg; + void *pval; + + pval = (void*) &(regs->lrv_eax); + arg = asym->args[LT_ARGS_RET]; + + lt_args_cb_arg(cfg, arg, pval, data, 1, 0); + + if ((cfg->args_detailed) && + (LT_ARGS_DTYPE_STRUCT == arg->dtype)) { + + /* The process_struct function does its own + dereference of pval value in case of pointer + argument, so we need to prepare pval correctly. */ + if (!arg->pointer) + pval = (void*) regs->lrv_eax; + + process_struct(cfg, arg, pval, data); + } + + return 0; +} diff --git a/src/sysdeps/i686/stack.h b/src/sysdeps/i686/stack.h new file mode 100644 index 0000000..0060132 --- /dev/null +++ b/src/sysdeps/i686/stack.h @@ -0,0 +1,27 @@ +/* + Copyright (C) 2008, 2009 Jiri Olsa <olsajiri@gmail.com> + + This file is part of the latrace. + + The latrace is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + The latrace 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with the latrace (file COPYING). If not, see + <http://www.gnu.org/licenses/>. +*/ + + +#ifndef STACK_H +#define STACK_H + +#define LT_STACK_ALIGN(arg) ((arg + 3) & ~3) + +#endif // !STACK_H diff --git a/src/sysdeps/x86_64/stack.c b/src/sysdeps/x86_64/stack.c new file mode 100644 index 0000000..2273506 --- /dev/null +++ b/src/sysdeps/x86_64/stack.c @@ -0,0 +1,615 @@ +/* + Copyright (C) 2008, 2009 Jiri Olsa <olsajiri@gmail.com> + + This file is part of the latrace. + + The latrace is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + The latrace 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with the latrace (file COPYING). If not, see + <http://www.gnu.org/licenses/>. +*/ + + +#include "config.h" +#include "stack.h" + +static __thread int ass_integer; +static __thread int ass_sse; +static __thread int ass_memory; + +#define ASS_CLEANUP() \ +do { \ + ass_integer = ass_sse = ass_memory = 0; \ +} while(0) + +/* INTEGER registers used for function arguments. */ +static long ass_regs_integer[] = { + LT_64_REG_RDI, + LT_64_REG_RSI, + LT_64_REG_RDX, + LT_64_REG_RCX, + LT_64_REG_R8, + LT_64_REG_R9, +}; +#define ASS_REGS_INTEGER_CNT (sizeof(ass_regs_integer)/sizeof(long)) +#define ASS_REGS_INTEGER_SIZE (sizeof(ass_regs_integer)) + +/* INTEGER registers used for function return arguments. */ +static long ass_regs_integer_ret[] = { + LT_64_REG_RAX, + LT_64_REG_RDX, +}; +#define ASS_REGS_INTEGER_RET_CNT (sizeof(ass_regs_integer_ret)/sizeof(long)) +#define ASS_REGS_INTEGER_RET_SIZE (sizeof(ass_regs_integer_ret)) + +/* SSE registers used for function arguments. */ +static long ass_regs_sse[] = { + LT_64_REG_XMM0, + LT_64_REG_XMM1, + LT_64_REG_XMM2, + LT_64_REG_XMM3, + LT_64_REG_XMM4, + LT_64_REG_XMM5, + LT_64_REG_XMM6, + LT_64_REG_XMM7, +}; +#define ASS_REGS_SSE_CNT (sizeof(ass_regs_sse)/sizeof(long)) + +/* SSE registers used for function return arguments. They + are xmm0 and xmm1, so we reuse the ass_regs_sse array.*/ +#define ASS_REGS_SSE_RET_CNT (2) + +static int classificate_arg(struct lt_config_shared *cfg, struct lt_arg *arg, + int ret, int align); + +static int classificate_memory(struct lt_config_shared *cfg, + struct lt_arg *arg, int align) +{ + if (align) + ass_memory = LT_STACK_ALIGN(ass_memory); + else { + int naligned = ass_memory % arg->type_len; + + if (naligned) + ass_memory += arg->type_len - naligned; + } + + PRINT_VERBOSE(cfg->verbose, 2, "%s - ass %d\n", + arg->name, ass_memory); + + ARCH_SET(arg, ARCH_FLAG_MEM, ass_memory); + + if (arg->pointer) + ass_memory += sizeof(void*); + else + ass_memory += arg->type_len; + + return 0; +} + +static int classificate_integer(struct lt_config_shared *cfg, + struct lt_arg *arg, int align, int regs_size) +{ + if (!align) { + int ass = LT_STACK_ALIGN(ass_integer); + int naligned = ass_integer % arg->type_len; + + if (naligned) + ass_integer += arg->type_len - naligned; + + if ((ass_integer + arg->type_len) > ass) + ass_integer = LT_STACK_ALIGN(ass_integer); + } + + PRINT_VERBOSE(cfg->verbose, 2, + "ass %s - reg size %d - ass_integer %d\n", + arg->name, regs_size, ass_integer); + + if (ass_integer >= regs_size) + return -1; + + ARCH_SET(arg, ARCH_FLAG_REG_INTEGER, ass_integer); + + if (arg->pointer) + ass_integer += sizeof(void*); + else + ass_integer += align ? sizeof(void*) : arg->type_len; + + return 0; +} + +static int classificate_sse(struct lt_config_shared *cfg, struct lt_arg *arg, + int sse_cnt) +{ + PRINT_VERBOSE(cfg->verbose, 2, "ass %s %d %d\n", + arg->name, ASS_REGS_SSE_CNT, ass_sse); + + if (sse_cnt == ass_sse) + return -1; + + ARCH_SET(arg, ARCH_FLAG_REG_SSE, ass_sse++); + return 0; +} + +static void struct_arch(struct lt_config_shared *cfg, struct lt_arg *sarg, + struct lt_arg *farg) +{ + if (sarg->pointer) + return; + + /* Structures passed by value dont have the arch assigned by default. + If the first argument is returned in memory, the structure takes + this pointer, if it is register, we keep it NULL, and lt_args_cb_arg + will print out the REG kw for value.*/ + PRINT_VERBOSE(cfg->verbose, 2, + "first argument for struct %s has flag: %d\n", + sarg->name, ARCH_GET_FLAG(farg)); + + if (ARCH_GET_FLAG(farg) == ARCH_FLAG_MEM) + sarg->arch = farg->arch; + else + ARCH_SET(sarg, ARCH_FLAG_SVREG, 0); +} + + +static int classificate_struct_try(struct lt_config_shared *cfg, + struct lt_arg *arg, int allmem, int ret) +{ + struct lt_arg *a; + int first = 1; + int saved_ass_integer = ass_integer; + int saved_ass_sse = ass_sse; + int saved_ass_memory = ass_memory; + + lt_list_for_each_entry(a, arg->args_head, args_list) { + + if (allmem) + classificate_memory(cfg, a, first); + else { + if (-1 == classificate_arg(cfg, a, ret, 0)) + return -1; + + if (ARCH_GET_FLAG(arg) == ARCH_FLAG_MEM) { + /* There is not enough registers, reset to + have the structure in memory only. */ + ass_integer = saved_ass_integer; + ass_sse = saved_ass_sse; + ass_memory = saved_ass_memory; + return -1; + } + + } + + if (first) { + struct_arch(cfg, arg, a); + first = 0; + } + } + + return 0; +} + +static int get_sizeof(struct lt_config_shared *cfg, struct lt_arg *arg) +{ + struct lt_arg *a; + int size = 0; + + lt_list_for_each_entry(a, arg->args_head, args_list) { + + int naligned = (u_int) size % a->type_len; + + if (naligned) + size += a->type_len - naligned; + + size += a->type_len; + } + + PRINT_VERBOSE(cfg->verbose, 2, "sizeof(struct %s) = %d\n", + arg->type_name, size); + + return size; +} + +static int classificate_struct(struct lt_config_shared *cfg, struct lt_arg *arg, + int ret) +{ + int allmem = 0; + int size = get_sizeof(cfg, arg); + + /* If the structure is bigger than 16B or passed as pointer then + it is in the memory (no registers). If the structure is up to + 16B and passed by value, we might need t classificate its members, + because they could fit in registers. */ + if ((size > 16) || (arg->pointer)) + allmem = 1; + + PRINT_VERBOSE(cfg->verbose, 2, + "struct %s - length sum %d - allmem %d\n", + arg->type_name, arg->type_len, allmem); + + if (-1 == classificate_struct_try(cfg, arg, allmem, ret)) + classificate_struct_try(cfg, arg, 1, ret); + + return 0; +} + +static int classificate_arg_type(struct lt_config_shared *cfg, + struct lt_arg *arg) +{ + int class; + + do { + /* pointers are INTEGER class by default */ + if (arg->pointer) { + class = LT_CLASS_INTEGER; + break; + } + + switch(arg->type_id) { + case LT_ARGS_TYPEID_CHAR: + case LT_ARGS_TYPEID_UCHAR: + case LT_ARGS_TYPEID_SHORT: + case LT_ARGS_TYPEID_USHORT: + case LT_ARGS_TYPEID_INT: + case LT_ARGS_TYPEID_UINT: + case LT_ARGS_TYPEID_LONG: + case LT_ARGS_TYPEID_ULONG: + case LT_ARGS_TYPEID_LLONG: + case LT_ARGS_TYPEID_ULLONG: + class = LT_CLASS_INTEGER; + break; + + case LT_ARGS_TYPEID_DOUBLE: + case LT_ARGS_TYPEID_FLOAT: + class = LT_CLASS_SSE; + break; + + default: + class = LT_CLASS_NONE; + break; + } + + } while(0); + + PRINT_VERBOSE(cfg->verbose, 2, + "argument %s dtype %d - type %s(%d) - class %d\n", + arg->name, arg->dtype, arg->type_name, arg->type_id, + class); + return class; +} + +static int classificate_arg(struct lt_config_shared *cfg, struct lt_arg *arg, + int ret, int align) +{ + int class; + int class_failed = 0; + + PRINT_VERBOSE(cfg->verbose, 2, "got arg \"%s\"\n", + arg->name); + + ARCH_ZERO(arg); + + if (arg->dtype != LT_ARGS_DTYPE_POD) { + + if (-1 == classificate_struct(cfg, arg, ret)) + return -1; + + /* If the structure is passed by pointer, we + still need the pointer classification. */ + if (!arg->pointer) + return 0; + } + + class = classificate_arg_type(cfg, arg); + if (-1 == class) + return -1; + + /* Nothing to be done for NONE class (void type) */ + if (LT_CLASS_NONE == class) + return 0; + + switch(class) { + case LT_CLASS_SSE: + class_failed = classificate_sse(cfg, arg, + ret ? ASS_REGS_SSE_RET_CNT : + ASS_REGS_SSE_CNT); + break; + + case LT_CLASS_INTEGER: + class_failed = classificate_integer(cfg, arg, align, + ret ? ASS_REGS_INTEGER_RET_SIZE : + ASS_REGS_INTEGER_SIZE); + break; + + case LT_CLASS_MEMORY: + classificate_memory(cfg, arg, 1); + break; + } + + /* If class INTEGER or SSE ran out of registers, + then arg is in memory. */ + if (class_failed) + classificate_memory(cfg, arg, 1); + + return 0; +} + +static int classificate(struct lt_config_shared *cfg, struct lt_args_sym *sym) +{ + int i; + struct lt_arg *arg = sym->args[LT_ARGS_RET]; + + PRINT_VERBOSE(cfg->verbose, 2, "got symbol \"%s\"\n", + sym->name); + + ASS_CLEANUP(); + + /* Classificate the return value first. */ + if (-1 == classificate_arg(cfg, arg, 1, 1)) + return -1; + + ASS_CLEANUP(); + + /* If the return value is memory class, + then the edi register is used as a first hidden arg.*/ + if (ARCH_GET_FLAG(arg) == ARCH_FLAG_MEM) + ass_integer += 8; + + for(i = 1; i < sym->argcnt; i++) { + arg = sym->args[i]; + + if (-1 == classificate_arg(cfg, arg, 0, 1)) + return -1; + } + + return 0; +} + +static void *get_value_mem(struct lt_config_shared *cfg, struct lt_arg *arg, + void *regs, int ret) +{ + long offset = ARCH_GET_OFFSET(arg); + void *pval; + + if (ret) { + void *base = (void*) ((struct La_x86_64_retval*) regs)->lrv_rax; + pval = base + offset; + } else { + void *base = (void*) ((struct La_x86_64_regs*) regs)->lr_rsp; + pval = base + /* current stack pointer */ + sizeof(void*) + /* return function address */ + offset; + } + + PRINT_VERBOSE(cfg->verbose, 2, "offset = %ld, %s = %p, ret = %d\n", + offset, arg->name, pval, ret); + return pval; +} + +static void *get_value_reg_integer(struct lt_config_shared *cfg, + struct lt_arg *arg, void *regs, int ret) +{ + struct La_x86_64_retval *regs_ret = regs; + struct La_x86_64_regs *regs_in = regs; + void *pval = NULL; + long offset = ARCH_GET_OFFSET(arg); + long qoff = offset % sizeof(long); + long reg = ret ? ass_regs_integer_ret[offset / sizeof(long)] : + ass_regs_integer[offset / sizeof(long)]; + + + PRINT_VERBOSE(cfg->verbose, 2, + "offset %ld - reg %ld - qoff %ld - ASS_REGS_CNT %ld - ret %d\n", + offset, reg, qoff, ASS_REGS_INTEGER_CNT, ret); + + switch(reg) { + case LT_64_REG_RAX: + pval = ®s_ret->lrv_rax; + break; + case LT_64_REG_RDX: + pval = ret ? ®s_ret->lrv_rdx : ®s_in->lr_rdx; + break; + case LT_64_REG_RDI: + pval = ®s_in->lr_rdi; + break; + case LT_64_REG_RSI: + pval = ®s_in->lr_rsi; + break; + case LT_64_REG_RCX: + pval = ®s_in->lr_rcx; + break; + case LT_64_REG_R8: + pval = ®s_in->lr_r8; + break; + case LT_64_REG_R9: + pval = ®s_in->lr_r9; + break; + } + + pval += qoff; + + PRINT_VERBOSE(cfg->verbose, 2, "argument %s = %p (%lx)\n", + arg->name, pval, *((u_long*)pval)); + return pval; +} + +static void *get_value_reg_sse(struct lt_config_shared *cfg, + struct lt_arg *arg, void *regs, int ret) +{ + int i = ARCH_GET_OFFSET(arg); + void *pval = NULL; + + if (ret) { + struct La_x86_64_retval *regs_ret = regs; + + switch(i) { + case 0: + pval = ®s_ret->lrv_xmm0; + break; + case 1: + pval = ®s_ret->lrv_xmm1; + break; + } + } else { + struct La_x86_64_regs *regs_in = regs; + + pval = ®s_in->lr_xmm[i]; + } + + + PRINT_VERBOSE(cfg->verbose, 2, "argument %s = %p (%lx), ret %d\n", + arg->name, pval, *((u_long*)pval), ret); + return pval; +} + +static void* get_value(struct lt_config_shared *cfg, struct lt_arg *arg, + void *regs, int ret) +{ + void *pval = NULL; + + PRINT_VERBOSE(cfg->verbose, 2, "get value for %s - arch = %lx, flag = %d\n", + arg->name, ARCH_GET(arg), ARCH_GET_FLAG(arg)); + + switch(ARCH_GET_FLAG(arg)) { + case ARCH_FLAG_MEM: + pval = get_value_mem(cfg, arg, regs, ret); + break; + + case ARCH_FLAG_REG_INTEGER: + pval = get_value_reg_integer(cfg, arg, regs, ret); + break; + + case ARCH_FLAG_REG_SSE: + pval = get_value_reg_sse(cfg, arg, regs, ret); + break; + } + + return pval; +} + +/* Process structure stored completelly in the + memory - pointed to by 'pval' arg. */ +static int process_struct_mem(struct lt_config_shared *cfg, struct lt_arg *arg, + void *pval, struct lt_args_data *data) +{ + struct lt_arg *a; + int i = 0; + + PRINT_VERBOSE(cfg->verbose, 2, "for %s - arch = %llx\n", + arg->name, ARCH_GET(arg)); + + lt_args_cb_struct(cfg, LT_ARGS_STRUCT_ITSELF, arg, NULL, data, 0); + + lt_list_for_each_entry(a, arg->args_head, args_list) { + int last = (i + 1) == arg->mmbcnt; + void *pv = pval + ARCH_GET_OFFSET(a); + + lt_args_cb_struct(cfg, LT_ARGS_STRUCT_ARG, a, pv, data, last); + + i++; + } + + return 0; +} + +static int process_struct_arg(struct lt_config_shared *cfg, struct lt_arg *arg, + void *regs, struct lt_args_data *data, int ret) +{ + struct lt_arg *a; + int i = 0; + + PRINT_VERBOSE(cfg->verbose, 2, "for %s - arch = %llx\n", + arg->name, ARCH_GET(arg)); + + lt_args_cb_struct(cfg, LT_ARGS_STRUCT_ITSELF, arg, NULL, data, 0); + + lt_list_for_each_entry(a, arg->args_head, args_list) { + int last = (i + 1) == arg->mmbcnt; + void *pval = NULL; + + pval = get_value(cfg, a, regs, ret); + + lt_args_cb_struct(cfg, LT_ARGS_STRUCT_ARG, a, pval, data, last); + i++; + } + + return 0; +} + +static void process_detailed_struct(struct lt_config_shared *cfg, + struct lt_arg *arg, void *pval, struct lt_args_data *data, + void *regs, int ret) +{ + if (arg->pointer) + pval = *((void**) pval); + + if (ARCH_GET_FLAG(arg) == ARCH_FLAG_MEM) { + process_struct_mem(cfg, arg, pval, data); + } else { + if (arg->pointer) + process_struct_mem(cfg, arg, pval, data); + else + process_struct_arg(cfg, arg, regs, data, ret); + } +} + + +int lt_stack_process(struct lt_config_shared *cfg, struct lt_args_sym *asym, + La_regs *regs, struct lt_args_data *data) +{ + int i; + + if (!ARCH_GET(asym->args[LT_ARGS_RET]) && + (-1 == classificate(cfg, asym))) + return -1; + + for(i = 1; i < asym->argcnt; i++) { + void *pval = NULL; + struct lt_arg *arg = asym->args[i]; + int last = (i + 1) == asym->argcnt; + + pval = get_value(cfg, arg, regs, 0); + if ((!pval) && + (arg->pointer || (LT_ARGS_DTYPE_STRUCT != arg->dtype))) { + PRINT_VERBOSE(cfg->verbose, 2, + "THIS SHOULD NEVER HAPPEN - arg '%s %s'\n", + arg->type_name, arg->name); + continue; + } + + lt_args_cb_arg(cfg, arg, pval, data, last, 1); + + if ((cfg->args_detailed) && + (LT_ARGS_DTYPE_STRUCT == arg->dtype)) + process_detailed_struct(cfg, arg, pval, data, regs, 0); + } + + return 0; +} + +int lt_stack_process_ret(struct lt_config_shared *cfg, struct lt_args_sym *asym, + La_retval *regs, struct lt_args_data *data) +{ + struct lt_arg *arg; + void *pval; + + arg = asym->args[LT_ARGS_RET]; + pval = get_value(cfg, arg, regs, 1); + + lt_args_cb_arg(cfg, arg, pval, data, 1, 0); + + if ((cfg->args_detailed) && + (LT_ARGS_DTYPE_STRUCT == arg->dtype)) + process_detailed_struct(cfg, arg, pval, data, regs, 0); + + return 0; +} diff --git a/src/sysdeps/x86_64/stack.h b/src/sysdeps/x86_64/stack.h new file mode 100644 index 0000000..7a24617 --- /dev/null +++ b/src/sysdeps/x86_64/stack.h @@ -0,0 +1,90 @@ +/* + Copyright (C) 2008, 2009 Jiri Olsa <olsajiri@gmail.com> + + This file is part of the latrace. + + The latrace is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + The latrace 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with the latrace (file COPYING). If not, see + <http://www.gnu.org/licenses/>. +*/ + + +#ifndef STACK_H +#define STACK_H + +/* + TODO Describe the implementation in more details TODO + + Architecture specific data inside lt_arg. + + bits 64 32|31 0 + --------------------|-------------------- + fields flags | offset + + + offset + - offset or memory or registers + flags + - memory/registers switch +*/ + +#define LT_STACK_ALIGN(arg) ((arg + 7) & ~7) + +#define ARCH_POS_FLAGS 32 + +#define ARCH_FLAG_MEM 1L +#define ARCH_FLAG_REG_INTEGER 2L +#define ARCH_FLAG_REG_SSE 3L +#define ARCH_FLAG_SVREG 4L + +#define ARCH_GET(arg) ((int64_t) ((arg)->arch)) +#define ARCH_GET_FLAG(arg) ((int) ((long) arg->arch >> ARCH_POS_FLAGS)) +#define ARCH_GET_OFFSET(arg) ((long) ((long) arg->arch & 0xFFFFFFFFL)) + +#define ARCH_ZERO(arg) \ +do {\ + arg->arch = NULL; \ +} while(0) + +#define ARCH_SET(arg, flags, offset) \ +do { \ + arg->arch = (void*) (offset + \ + (flags << ARCH_POS_FLAGS)); \ +} while(0) + +/* registers index */ +#define LT_64_REG_RAX 1 +#define LT_64_REG_RCX 2 +#define LT_64_REG_RDX 3 +#define LT_64_REG_RDI 4 +#define LT_64_REG_RSI 5 +#define LT_64_REG_R8 6 +#define LT_64_REG_R9 7 +#define LT_64_REG_RBP 8 +#define LT_64_REG_RSP 9 +#define LT_64_REG_XMM0 10 +#define LT_64_REG_XMM1 11 +#define LT_64_REG_XMM2 12 +#define LT_64_REG_XMM3 13 +#define LT_64_REG_XMM4 14 +#define LT_64_REG_XMM5 15 +#define LT_64_REG_XMM6 16 +#define LT_64_REG_XMM7 17 + +/* argument classes */ +#define LT_CLASS_NONE 1 +#define LT_CLASS_INTEGER 2 +#define LT_CLASS_SSE 3 +#define LT_CLASS_MEMORY 4096 + +#endif // !STACK_H diff --git a/src/thread.c b/src/thread.c new file mode 100644 index 0000000..70ff1ef --- /dev/null +++ b/src/thread.c @@ -0,0 +1,61 @@ +/* + Copyright (C) 2008, 2009 Jiri Olsa <olsajiri@gmail.com> + + This file is part of the latrace. + + The latrace is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + The latrace 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with the latrace (file COPYING). If not, see + <http://www.gnu.org/licenses/>. +*/ + + +#include <stdlib.h> +#include <string.h> + +#include "config.h" + + +struct lt_thread *lt_thread_add(struct lt_config_app *cfg, int fd, pid_t pid) +{ + struct lt_thread *t; + + if (NULL == (t = (struct lt_thread*) malloc(sizeof(struct lt_thread)))) { + perror("malloc failed"); + return NULL; + } + + memset(t, 0x0, sizeof(*t)); + + if (-1 == lt_stats_alloc(cfg, t)) { + free(t); + return NULL; + } + + t->fifo_fd = fd; + t->tid = pid; + gettimeofday(&t->tv_start, NULL); + + t->next = cfg->threads; + cfg->threads = t; + return t; +} + +struct lt_thread *lt_thread_first(struct lt_config_app *cfg) +{ + return cfg->iter = cfg->threads; +} + +struct lt_thread *lt_thread_next(struct lt_config_app *cfg) +{ + return cfg->iter = (cfg->iter ? cfg->iter->next : NULL); +} |