diff options
author | Anas Nashif <anas.nashif@intel.com> | 2013-01-15 08:32:18 -0800 |
---|---|---|
committer | Anas Nashif <anas.nashif@intel.com> | 2013-01-15 08:32:18 -0800 |
commit | 689b9dbb8d7f88ab91e7741932ed000b6e49be9a (patch) | |
tree | 463f5a1df8b2d35644c260e7bf6c8e0a26198af1 /options.c | |
parent | 59749d048d9e452f049f9151735b5256756919c3 (diff) | |
download | ltrace-689b9dbb8d7f88ab91e7741932ed000b6e49be9a.tar.gz ltrace-689b9dbb8d7f88ab91e7741932ed000b6e49be9a.tar.bz2 ltrace-689b9dbb8d7f88ab91e7741932ed000b6e49be9a.zip |
Imported Upstream version 0.7.2upstream/0.7.2
Diffstat (limited to 'options.c')
-rw-r--r-- | options.c | 486 |
1 files changed, 373 insertions, 113 deletions
@@ -1,16 +1,44 @@ +/* + * This file is part of ltrace. + * Copyright (C) 2012 Petr Machata, Red Hat Inc. + * Copyright (C) 2009,2010 Joe Damato + * Copyright (C) 1998,1999,2002,2003,2004,2007,2008,2009 Juan Cespedes + * Copyright (C) 2006 Ian Wienand + * Copyright (C) 2006 Steve Fink + * Copyright (C) 2006 Paul Gilliam, IBM Corporation + * + * This program 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 2 of the + * License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + #include "config.h" -#include <string.h> -#include <stdlib.h> -#include <unistd.h> -#include <fcntl.h> -#include <errno.h> -#include <limits.h> #include <sys/ioctl.h> - +#include <assert.h> +#include <errno.h> +#include <fcntl.h> #include <getopt.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> #include "common.h" +#include "filter.h" +#include "glob.h" #ifndef SYSCONFDIR #define SYSCONFDIR "/etc" @@ -23,7 +51,6 @@ struct options_t options = { .align = DEFAULT_ALIGN, /* alignment column for results */ .user = NULL, /* username to run command as */ .syscalls = 0, /* display syscalls */ - .libcalls = 1, /* display library calls */ #ifdef USE_DEMANGLE .demangle = 0, /* Demangle low-level symbol names */ #endif @@ -36,8 +63,6 @@ struct options_t options = { .follow = 0, /* trace child processes */ }; -char *library[MAX_LIBRARIES]; -int library_num = 0; static char *progname; /* Program name (`ltrace') */ int opt_i = 0; /* instruction pointer */ int opt_r = 0; /* print relative timestamp */ @@ -47,25 +72,12 @@ int opt_T = 0; /* show the time spent inside each call */ /* List of pids given to option -p: */ struct opt_p_t *opt_p = NULL; /* attach to process with a given pid */ -/* List of function names given to option -e: */ -struct opt_e_t *opt_e = NULL; -int opt_e_enable = 1; - -/* List of global function names given to -x: */ -struct opt_x_t *opt_x = NULL; - /* List of filenames give to option -F: */ struct opt_F_t *opt_F = NULL; /* alternate configuration file(s) */ -#ifdef PLT_REINITALISATION_BP -/* Set a break on the routine named here in order to re-initialize breakpoints - after all the PLTs have been initialzed */ -char *PLTs_initialized_by_here = PLT_REINITALISATION_BP; -#endif - static void err_usage(void) { - fprintf(stderr, "Try `%s --help' for more information\n", progname); + fprintf(stderr, "Try `%s --help' for more information.\n", progname); exit(1); } @@ -75,6 +87,7 @@ usage(void) { "Trace library calls of a given program.\n\n" " -a, --align=COLUMN align return values in a secific column.\n" " -A ARRAYLEN maximum number of array elements to print.\n" + " -b, --no-signals don't print signals.\n" " -c count time and calls, and report a summary on exit.\n" # ifdef USE_DEMANGLE " -C, --demangle decode low-level symbol names into user-level names.\n" @@ -86,7 +99,7 @@ usage(void) { " -F, --config=FILE load alternate configuration file (may be repeated).\n" " -h, --help display this help and exit.\n" " -i print instruction pointer at time of library call.\n" - " -l, --library=FILE print library calls from this library only.\n" + " -l, --library=FILE only trace symbols implemented by this library.\n" " -L do NOT display library calls.\n" " -n, --indent=NR indent output by NR spaces for each call level nesting.\n" " -o, --output=FILE write the trace output to that file.\n" @@ -98,10 +111,10 @@ usage(void) { " -T show the time spent inside each call.\n" " -u USERNAME run command with the userid, groupid of username.\n" " -V, --version output version information and exit.\n" +#if defined(HAVE_LIBUNWIND) + " -w, --where=NR print backtrace showing NR stack frames at most.\n" +#endif /* defined(HAVE_LIBUNWIND) */ " -x NAME treat the global NAME like a library subroutine.\n" -#ifdef PLT_REINITALISATION_BP - " -X NAME same as -x; and PLT's will be initialized by here.\n" -#endif "\nReport bugs to ltrace-devel@lists.alioth.debian.org\n", progname); } @@ -139,7 +152,7 @@ search_for_command(char *filename) { m = n = strlen(path); } if (n + strlen(filename) + 1 >= PATH_MAX) { - fprintf(stderr, "Error: filename too long\n"); + fprintf(stderr, "Error: filename too long.\n"); exit(1); } strncpy(pathname, path, n); @@ -175,13 +188,274 @@ guess_cols(void) { } } +static int +compile_libname(const char *expr, const char *a_lib, int lib_re_p, + struct filter_lib_matcher *matcher) +{ + if (strcmp(a_lib, "MAIN") == 0) { + filter_lib_matcher_main_init(matcher); + } else { + /* Add ^ and $ to the library expression as well. */ + char lib[strlen(a_lib) + 3]; + sprintf(lib, "^%s$", a_lib); + + enum filter_lib_matcher_type type + = lib[0] == '/' ? FLM_PATHNAME : FLM_SONAME; + + regex_t lib_re; + int status = (lib_re_p ? regcomp : globcomp)(&lib_re, lib, 0); + if (status != REG_NOERROR) { + char buf[100]; + regerror(status, &lib_re, buf, sizeof buf); + fprintf(stderr, "Rule near '%s' will be ignored: %s.\n", + expr, buf); + return -1; + } + filter_lib_matcher_name_init(matcher, type, lib_re); + } + return 0; +} + +static void +add_filter_rule(struct filter *filt, const char *expr, + enum filter_rule_type type, + const char *a_sym, int sym_re_p, + const char *a_lib, int lib_re_p) +{ + struct filter_rule *rule = malloc(sizeof(*rule)); + struct filter_lib_matcher *matcher = malloc(sizeof(*matcher)); + + if (rule == NULL || matcher == NULL) { + fprintf(stderr, "Rule near '%s' will be ignored: %s.\n", + expr, strerror(errno)); + fail: + free(rule); + free(matcher); + return; + } + + regex_t symbol_re; + { + /* Add ^ to the start of expression and $ to the end, so that + * we match the whole symbol name. Let the user write the "*" + * explicitly if they wish. */ + char sym[strlen(a_sym) + 3]; + sprintf(sym, "^%s$", a_sym); + int status = (sym_re_p ? regcomp : globcomp) + (&symbol_re, sym, 0); + if (status != 0) { + char buf[100]; + regerror(status, &symbol_re, buf, sizeof buf); + fprintf(stderr, "Rule near '%s' will be ignored: %s.\n", + expr, buf); + goto fail; + } + } + + if (compile_libname(expr, a_lib, lib_re_p, matcher) < 0) { + regfree(&symbol_re); + goto fail; + } + + filter_rule_init(rule, type, matcher, symbol_re); + filter_add_rule(filt, rule); +} + +static int +grok_libname_pattern(char **libnamep, char **libendp) +{ + char *libname = *libnamep; + char *libend = *libendp; + + if (libend[0] != '/') + return 0; + + *libend-- = 0; + if (libname != libend && libname[0] == '/') + ++libname; + else + fprintf(stderr, "Unmatched '/' in library name.\n"); + + *libendp = libend; + *libnamep = libname; + return 1; +} + +static int +parse_filter(struct filter *filt, char *expr, int operators) +{ + /* Filter is a chain of sym@lib rules separated by '-' or '+'. + * If the filter expression starts with '-', the missing + * initial rule is implicitly *@*. */ + + enum filter_rule_type type = FR_ADD; + + while (*expr != 0) { + size_t s = strcspn(expr, "-+@" + (operators ? 0 : 2)); + char *symname = expr; + char *libname; + char *next = expr + s + 1; + enum filter_rule_type this_type = type; + + if (expr[s] == 0) { + libname = "*"; + expr = next - 1; + + } else if (expr[s] == '-' || expr[s] == '+') { + type = expr[s] == '-' ? FR_SUBTRACT : FR_ADD; + expr[s] = 0; + libname = "*"; + expr = next; + + } else { + assert(expr[s] == '@'); + expr[s] = 0; + s = strcspn(next, "-+" + (operators ? 0 : 2)); + if (s == 0) { + libname = "*"; + expr = next; + } else if (next[s] == 0) { + expr = next + s; + libname = next; + } else { + assert(next[s] == '-' || next[s] == '+'); + type = next[s] == '-' ? FR_SUBTRACT : FR_ADD; + next[s] = 0; + expr = next + s + 1; + libname = next; + } + } + + assert(*libname != 0); + char *symend = symname + strlen(symname) - 1; + char *libend = libname + strlen(libname) - 1; + int sym_is_re = 0; + int lib_is_re = 0; + + /* + * /xxx/@... and ...@/xxx/ means that xxx are regular + * expressions. They are globs otherwise. + * + * /xxx@yyy/ is the same as /xxx/@/yyy/ + * + * @/xxx matches library path name + * @.xxx matches library relative path name + */ + if (symname[0] == '/') { + if (symname != symend && symend[0] == '/') { + ++symname; + *symend-- = 0; + sym_is_re = 1; + + } else { + sym_is_re = 1; + lib_is_re = 1; + ++symname; + + /* /XXX@YYY/ is the same as + * /XXX/@/YYY/. */ + if (libend[0] != '/') + fprintf(stderr, "Unmatched '/'" + " in symbol name.\n"); + else + *libend-- = 0; + } + } + + /* If libname ends in '/', then we expect '/' in the + * beginning too. Otherwise the initial '/' is part + * of absolute file name. */ + if (!lib_is_re) + lib_is_re = grok_libname_pattern(&libname, &libend); + + if (*symname == 0) /* /@AA/ */ + symname = "*"; + if (*libname == 0) /* /aa@/ */ + libname = "*"; + + add_filter_rule(filt, expr, this_type, + symname, sym_is_re, + libname, lib_is_re); + } + + return 0; +} + +static struct filter * +recursive_parse_chain(char *expr, int operators) +{ + struct filter *filt = malloc(sizeof(*filt)); + if (filt == NULL) { + fprintf(stderr, "(Part of) filter will be ignored: '%s': %s.\n", + expr, strerror(errno)); + return NULL; + } + + filter_init(filt); + if (parse_filter(filt, expr, operators) < 0) { + fprintf(stderr, "Filter '%s' will be ignored.\n", expr); + free(filt); + filt = NULL; + } + + return filt; +} + +static struct filter ** +slist_chase_end(struct filter **begin) +{ + for (; *begin != NULL; begin = &(*begin)->next) + ; + return begin; +} + +static void +parse_filter_chain(const char *expr, struct filter **retp) +{ + char *str = strdup(expr); + if (str == NULL) { + fprintf(stderr, "Filter '%s' will be ignored: %s.\n", + expr, strerror(errno)); + return; + } + /* Support initial '!' for backward compatibility. */ + if (str[0] == '!') + str[0] = '-'; + + *slist_chase_end(retp) = recursive_parse_chain(str, 1); + free(str); +} + +static int +parse_int(const char *optarg, char opt, int min, int max) +{ + char *endptr; + long int l = strtol(optarg, &endptr, 0); + if (l < min || (max != 0 && l > max) + || *optarg == 0 || *endptr != 0) { + const char *fmt = max != 0 + ? "Invalid argument to -%c: '%s'. Use integer %d..%d.\n" + : "Invalid argument to -%c: '%s'. Use integer >=%d.\n"; + fprintf(stderr, fmt, opt, optarg, min, max); + exit(1); + } + return (int)l; +} + char ** -process_options(int argc, char **argv) { +process_options(int argc, char **argv) +{ progname = argv[0]; options.output = stderr; + options.no_signals = 0; +#if defined(HAVE_LIBUNWIND) + options.bt_depth = -1; +#endif /* defined(HAVE_LIBUNWIND) */ guess_cols(); + int libcalls = 1; + while (1) { int c; char *p; @@ -198,23 +472,34 @@ process_options(int argc, char **argv) { {"library", 1, 0, 'l'}, {"output", 1, 0, 'o'}, {"version", 0, 0, 'V'}, + {"no-signals", 0, 0, 'b'}, +#if defined(HAVE_LIBUNWIND) + {"where", 1, 0, 'w'}, +#endif /* defined(HAVE_LIBUNWIND) */ {0, 0, 0, 0} }; - c = getopt_long(argc, argv, "+cfhiLrStTV" + c = getopt_long(argc, argv, "+cfhiLrStTVb" # ifdef USE_DEMANGLE "C" # endif +#if defined(HAVE_LIBUNWIND) + "a:A:D:e:F:l:n:o:p:s:u:x:X:w:", long_options, +#else /* !defined(HAVE_LIBUNWIND) */ "a:A:D:e:F:l:n:o:p:s:u:x:X:", long_options, +#endif &option_index); if (c == -1) { break; } switch (c) { case 'a': - options.align = atoi(optarg); + options.align = parse_int(optarg, 'a', 0, 0); break; case 'A': - options.arraylen = atoi(optarg); + options.arraylen = parse_int(optarg, 'A', 0, 0); + break; + case 'b': + options.no_signals = 1; break; case 'c': options.summary++; @@ -235,50 +520,28 @@ process_options(int argc, char **argv) { err_usage(); } break; + case 'e': - { - char *str_e = strdup(optarg); - if (!str_e) { - perror("ltrace: strdup"); - exit(1); - } - if (str_e[0] == '!') { - opt_e_enable = 0; - str_e++; - } - while (*str_e) { - struct opt_e_t *tmp; - char *str2 = strchr(str_e, ','); - if (str2) { - *str2 = '\0'; - } - tmp = malloc(sizeof(struct opt_e_t)); - if (!tmp) { - perror("ltrace: malloc"); - exit(1); - } - tmp->name = str_e; - tmp->next = opt_e; - opt_e = tmp; - if (str2) { - str_e = str2 + 1; - } else { - break; - } - } - break; - } + parse_filter_chain(optarg, &options.plt_filter); + break; + case 'f': options.follow = 1; break; case 'F': { - struct opt_F_t *tmp = malloc(sizeof(struct opt_F_t)); - if (!tmp) { - perror("ltrace: malloc"); + struct opt_F_t *tmp = malloc(sizeof(*tmp)); + if (tmp == NULL) { + fail: + fprintf(stderr, "%s\n", + strerror(errno)); + free(tmp); exit(1); } tmp->filename = strdup(optarg); + if (tmp->filename == NULL) + goto fail; + tmp->own_filename = 1; tmp->next = opt_F; opt_F = tmp; break; @@ -289,26 +552,27 @@ process_options(int argc, char **argv) { case 'i': opt_i++; break; - case 'l': - if (library_num == MAX_LIBRARIES) { - fprintf(stderr, - "Too many libraries. Maximum is %i.\n", - MAX_LIBRARIES); - exit(1); - } - library[library_num++] = optarg; + + case 'l': { + size_t patlen = strlen(optarg); + char buf[patlen + 2]; + sprintf(buf, "@%s", optarg); + *slist_chase_end(&options.export_filter) + = recursive_parse_chain(buf, 0); break; + } + case 'L': - options.libcalls = 0; + libcalls = 0; break; case 'n': - options.indent = atoi(optarg); + options.indent = parse_int(optarg, 'n', 0, 20); break; case 'o': options.output = fopen(optarg, "w"); if (!options.output) { fprintf(stderr, - "Can't open %s for output: %s\n", + "can't open %s for writing: %s\n", optarg, strerror(errno)); exit(1); } @@ -322,7 +586,7 @@ process_options(int argc, char **argv) { perror("ltrace: malloc"); exit(1); } - tmp->pid = atoi(optarg); + tmp->pid = parse_int(optarg, 'p', 1, 0); tmp->next = opt_p; opt_p = tmp; break; @@ -331,7 +595,7 @@ process_options(int argc, char **argv) { opt_r++; break; case 's': - options.strlen = atoi(optarg); + options.strlen = parse_int(optarg, 's', 0, 0); break; case 'S': options.syscalls = 1; @@ -351,39 +615,16 @@ process_options(int argc, char **argv) { "This is free software; see the GNU General Public Licence\n" "version 2 or later for copying conditions. There is NO warranty.\n"); exit(0); - case 'X': -#ifdef PLT_REINITALISATION_BP - PLTs_initialized_by_here = optarg; -#else - fprintf(stderr, "WARNING: \"-X\" not used for this " - "architecture: assuming you meant \"-x\"\n"); -#endif - /* Fall Thru */ + break; +#if defined(HAVE_LIBUNWIND) + case 'w': + options.bt_depth = parse_int(optarg, 'w', 1, 0); + break; +#endif /* defined(HAVE_LIBUNWIND) */ case 'x': - { - struct opt_x_t *p = opt_x; - - /* First, check for duplicate. */ - while (p && strcmp(p->name, optarg)) { - p = p->next; - } - if (p) { - break; - } - - /* If not duplicate, add to list. */ - p = malloc(sizeof(struct opt_x_t)); - if (!p) { - perror("ltrace: malloc"); - exit(1); - } - p->name = optarg; - p->found = 0; - p->next = opt_x; - opt_x = p; - break; - } + parse_filter_chain(optarg, &options.static_filter); + break; default: err_usage(); @@ -397,7 +638,9 @@ process_options(int argc, char **argv) { opt_F->next = malloc(sizeof(struct opt_F_t)); opt_F->next->next = NULL; opt_F->filename = USER_CONFIG_FILE; + opt_F->own_filename = 0; opt_F->next->filename = SYSTEM_CONFIG_FILE; + opt_F->next->own_filename = 0; } /* Reverse the config file list since it was built by * prepending, and it would make more sense to process the @@ -415,12 +658,29 @@ process_options(int argc, char **argv) { opt_F = egg; } + /* If neither -e, nor -l, nor -L are used, set default -e. + * Use @MAIN for now, as that's what ltrace used to have in + * the past. XXX Maybe we should make this "*" instead. */ + if (libcalls + && options.plt_filter == NULL + && options.export_filter == NULL) { + parse_filter_chain("@MAIN", &options.plt_filter); + options.hide_caller = 1; + } + if (!libcalls && options.plt_filter != NULL) { + fprintf(stderr, + "%s: Option -L can't be used with -e or -l.\n", + progname); + err_usage(); + } + if (!opt_p && argc < 1) { fprintf(stderr, "%s: too few arguments\n", progname); err_usage(); } if (opt_r && opt_t) { - fprintf(stderr, "%s: Incompatible options -r and -t\n", + fprintf(stderr, + "%s: Options -r and -t can't be used together\n", progname); err_usage(); } |