2019-05-27 09:55:05 +03:00
// SPDX-License-Identifier: GPL-2.0-or-later
2009-10-09 01:17:38 +04:00
/*
* builtin - probe . c
*
* Builtin probe command : Set up probe events by C expression
*
* Written by Masami Hiramatsu < mhiramat @ redhat . com >
*/
# include <sys/utsname.h>
# include <sys/types.h>
# include <sys/stat.h>
# include <fcntl.h>
# include <errno.h>
# include <stdio.h>
# include <unistd.h>
# include <stdlib.h>
# include <string.h>
# include "builtin.h"
2019-01-22 16:24:34 +03:00
# include "namespaces.h"
2019-08-30 16:26:37 +03:00
# include "util/build-id.h"
2009-12-09 01:03:23 +03:00
# include "util/strlist.h"
2011-01-20 17:15:39 +03:00
# include "util/strfilter.h"
2019-08-30 17:56:47 +03:00
# include "util/symbol_conf.h"
2009-10-17 04:08:10 +04:00
# include "util/debug.h"
2015-12-15 18:39:39 +03:00
# include <subcmd/parse-options.h>
2009-10-09 01:17:38 +04:00
# include "util/probe-finder.h"
2009-12-01 03:19:58 +03:00
# include "util/probe-event.h"
2015-09-04 15:16:03 +03:00
# include "util/probe-file.h"
2019-08-29 22:18:59 +03:00
# include <linux/string.h>
2019-07-04 17:32:27 +03:00
# include <linux/zalloc.h>
2009-10-09 01:17:38 +04:00
2011-01-20 17:15:39 +03:00
# define DEFAULT_VAR_FILTER "!__k???tab_* & !__crc_*"
2011-01-20 17:15:45 +03:00
# define DEFAULT_FUNC_FILTER "!_*"
2016-07-01 11:03:26 +03:00
# define DEFAULT_LIST_FILTER "*"
2009-10-09 01:17:38 +04:00
/* Session management structure */
static struct {
2015-05-05 18:22:57 +03:00
int command ; /* Command short_name */
2009-12-15 18:31:14 +03:00
bool list_events ;
2012-04-16 16:09:09 +04:00
bool uprobes ;
2014-10-27 23:31:31 +03:00
bool quiet ;
2015-04-01 13:25:42 +03:00
bool target_used ;
2010-03-17 01:06:12 +03:00
int nevents ;
struct perf_probe_event events [ MAX_PROBES ] ;
2010-01-06 17:45:34 +03:00
struct line_range line_range ;
2014-01-16 13:39:47 +04:00
char * target ;
2011-01-20 17:15:39 +03:00
struct strfilter * filter ;
2017-07-06 04:48:10 +03:00
struct nsinfo * nsi ;
2010-03-17 01:05:44 +03:00
} params ;
2009-10-09 01:17:38 +04:00
2009-10-27 23:43:10 +03:00
/* Parse an event definition. Note that any error must die. */
2010-04-12 21:17:42 +04:00
static int parse_probe_event ( const char * str )
2009-10-09 01:17:38 +04:00
{
2010-03-17 01:06:12 +03:00
struct perf_probe_event * pev = & params . events [ params . nevents ] ;
2010-04-12 21:17:42 +04:00
int ret ;
2009-10-09 01:17:38 +04:00
2010-03-17 01:06:12 +03:00
pr_debug ( " probe-definition(%d): %s \n " , params . nevents , str ) ;
2010-05-19 05:57:27 +04:00
if ( + + params . nevents = = MAX_PROBES ) {
pr_err ( " Too many probes (> %d) were specified. " , MAX_PROBES ) ;
return - 1 ;
}
2009-10-09 01:17:38 +04:00
2012-04-16 16:09:09 +04:00
pev - > uprobes = params . uprobes ;
2015-04-01 13:25:39 +03:00
if ( params . target ) {
pev - > target = strdup ( params . target ) ;
if ( ! pev - > target )
return - ENOMEM ;
2015-04-01 13:25:42 +03:00
params . target_used = true ;
2015-04-01 13:25:39 +03:00
}
2012-04-16 16:09:09 +04:00
2018-05-24 17:20:39 +03:00
pev - > nsi = nsinfo__get ( params . nsi ) ;
2017-07-06 04:48:10 +03:00
2010-03-17 01:06:12 +03:00
/* Parse a perf-probe command into event */
2010-04-12 21:17:42 +04:00
ret = parse_perf_probe_command ( str , pev ) ;
2010-03-17 01:06:12 +03:00
pr_debug ( " %d arguments \n " , pev - > nargs ) ;
2010-04-12 21:17:42 +04:00
return ret ;
2009-10-27 23:43:02 +03:00
}
2015-04-24 12:47:50 +03:00
static int params_add_filter ( const char * str )
{
const char * err = NULL ;
int ret = 0 ;
pr_debug2 ( " Add filter: %s \n " , str ) ;
if ( ! params . filter ) {
params . filter = strfilter__new ( str , & err ) ;
if ( ! params . filter )
ret = err ? - EINVAL : - ENOMEM ;
} else
ret = strfilter__or ( params . filter , str , & err ) ;
if ( ret = = - EINVAL ) {
pr_err ( " Filter parse error at %td. \n " , err - str + 1 ) ;
pr_err ( " Source: \" %s \" \n " , str ) ;
pr_err ( " %*c \n " , ( int ) ( err - str + 1 ) , ' ^ ' ) ;
}
return ret ;
}
2012-04-16 16:09:25 +04:00
static int set_target ( const char * ptr )
{
int found = 0 ;
const char * buf ;
/*
* The first argument after options can be an absolute path
* to an executable / library or kernel module .
*
* TODO : Support relative path , and $ PATH , $ LD_LIBRARY_PATH ,
* short module name .
*/
if ( ! params . target & & ptr & & * ptr = = ' / ' ) {
2014-01-16 13:39:47 +04:00
params . target = strdup ( ptr ) ;
if ( ! params . target )
return - ENOMEM ;
2015-04-01 13:25:42 +03:00
params . target_used = false ;
2014-01-16 13:39:47 +04:00
2012-04-16 16:09:25 +04:00
found = 1 ;
buf = ptr + ( strlen ( ptr ) - 3 ) ;
if ( strcmp ( buf , " .ko " ) )
params . uprobes = true ;
}
return found ;
}
2010-04-12 21:17:42 +04:00
static int parse_probe_event_argv ( int argc , const char * * argv )
2009-12-09 01:02:54 +03:00
{
2012-04-16 16:09:25 +04:00
int i , len , ret , found_target ;
2009-12-09 01:02:54 +03:00
char * buf ;
2012-04-16 16:09:25 +04:00
found_target = set_target ( argv [ 0 ] ) ;
2014-01-16 13:39:47 +04:00
if ( found_target < 0 )
return found_target ;
2012-04-16 16:09:25 +04:00
if ( found_target & & argc = = 1 )
return 0 ;
2009-12-09 01:02:54 +03:00
/* Bind up rest arguments */
len = 0 ;
2012-04-16 16:09:25 +04:00
for ( i = 0 ; i < argc ; i + + ) {
if ( i = = 0 & & found_target )
continue ;
2009-12-09 01:02:54 +03:00
len + = strlen ( argv [ i ] ) + 1 ;
2012-04-16 16:09:25 +04:00
}
2010-05-19 05:57:27 +04:00
buf = zalloc ( len + 1 ) ;
if ( buf = = NULL )
return - ENOMEM ;
2009-12-09 01:02:54 +03:00
len = 0 ;
2012-04-16 16:09:25 +04:00
for ( i = 0 ; i < argc ; i + + ) {
if ( i = = 0 & & found_target )
continue ;
2009-12-09 01:02:54 +03:00
len + = sprintf ( & buf [ len ] , " %s " , argv [ i ] ) ;
2012-04-16 16:09:25 +04:00
}
2010-04-12 21:17:42 +04:00
ret = parse_probe_event ( buf ) ;
2009-12-09 01:02:54 +03:00
free ( buf ) ;
2010-04-12 21:17:42 +04:00
return ret ;
2009-12-09 01:02:54 +03:00
}
2012-04-16 16:09:09 +04:00
static int opt_set_target ( const struct option * opt , const char * str ,
2012-09-11 02:15:03 +04:00
int unset __maybe_unused )
2012-04-16 16:09:09 +04:00
{
int ret = - ENOENT ;
2013-12-26 09:41:50 +04:00
char * tmp ;
2012-04-16 16:09:09 +04:00
2015-04-01 13:25:39 +03:00
if ( str ) {
2012-04-16 16:09:09 +04:00
if ( ! strcmp ( opt - > long_name , " exec " ) )
params . uprobes = true ;
else if ( ! strcmp ( opt - > long_name , " module " ) )
params . uprobes = false ;
else
return ret ;
2013-12-26 09:41:50 +04:00
/* Expand given path to absolute path, except for modulename */
if ( params . uprobes | | strchr ( str , ' / ' ) ) {
2017-07-06 04:48:10 +03:00
tmp = nsinfo__realpath ( str , params . nsi ) ;
2013-12-26 09:41:50 +04:00
if ( ! tmp ) {
pr_warning ( " Failed to get the absolute path of %s: %m \n " , str ) ;
return ret ;
}
} else {
tmp = strdup ( str ) ;
if ( ! tmp )
return - ENOMEM ;
}
2015-04-01 13:25:39 +03:00
free ( params . target ) ;
2013-12-26 09:41:50 +04:00
params . target = tmp ;
2015-04-01 13:25:42 +03:00
params . target_used = false ;
2012-04-16 16:09:09 +04:00
ret = 0 ;
}
return ret ;
}
2017-07-06 04:48:10 +03:00
static int opt_set_target_ns ( const struct option * opt __maybe_unused ,
const char * str , int unset __maybe_unused )
{
int ret = - ENOENT ;
pid_t ns_pid ;
struct nsinfo * nsip ;
if ( str ) {
errno = 0 ;
ns_pid = ( pid_t ) strtol ( str , NULL , 10 ) ;
if ( errno ! = 0 ) {
ret = - errno ;
pr_warning ( " Failed to parse %s as a pid: %s \n " , str ,
strerror ( errno ) ) ;
return ret ;
}
nsip = nsinfo__new ( ns_pid ) ;
if ( nsip & & nsip - > need_setns )
params . nsi = nsinfo__get ( nsip ) ;
nsinfo__put ( nsip ) ;
ret = 0 ;
}
return ret ;
}
2015-05-05 18:22:57 +03:00
/* Command option callbacks */
2013-09-30 14:07:11 +04:00
# ifdef HAVE_DWARF_SUPPORT
2015-05-05 18:22:57 +03:00
static int opt_show_lines ( const struct option * opt ,
2012-09-11 02:15:03 +04:00
const char * str , int unset __maybe_unused )
2010-01-16 15:31:16 +03:00
{
2010-04-12 21:17:42 +04:00
int ret = 0 ;
2011-08-11 15:02:53 +04:00
if ( ! str )
return 0 ;
2015-05-05 18:22:57 +03:00
if ( params . command = = ' L ' ) {
2011-08-11 15:02:53 +04:00
pr_warning ( " Warning: more than one --line options are "
" detected. Only the first one is valid. \n " ) ;
return 0 ;
}
2015-05-05 18:22:57 +03:00
params . command = opt - > short_name ;
2011-08-11 15:02:53 +04:00
ret = parse_line_range_desc ( str , & params . line_range ) ;
2010-04-12 21:17:42 +04:00
return ret ;
2010-01-16 15:31:16 +03:00
}
2010-10-21 14:13:23 +04:00
2015-05-05 18:22:57 +03:00
static int opt_show_vars ( const struct option * opt ,
2012-09-11 02:15:03 +04:00
const char * str , int unset __maybe_unused )
2010-10-21 14:13:23 +04:00
{
struct perf_probe_event * pev = & params . events [ params . nevents ] ;
int ret ;
if ( ! str )
return 0 ;
ret = parse_probe_event ( str ) ;
if ( ! ret & & pev - > nargs ! = 0 ) {
pr_err ( " Error: '--vars' doesn't accept arguments. \n " ) ;
return - EINVAL ;
}
2015-05-05 18:22:57 +03:00
params . command = opt - > short_name ;
2010-10-21 14:13:23 +04:00
return ret ;
}
perf tools: Make options always available, even if required libs not linked
This patch keeps options of perf builtins same in all conditions. If
one option is disabled because of compiling options, users should be
notified.
Masami suggested another implementation in [1] that, by adding a
OPTION_NEXT_DEPENDS option before those options in the 'struct option'
array, options parser knows an option is disabled. However, in some
cases this array is reordered (options__order()). In addition, in
parse-option.c that array is const, so we can't simply merge
information in decorator option into the affacted option.
This patch chooses a simpler implementation that, introducing a
set_option_nobuild() function and two option parsing flags. Builtins
with such options should call set_option_nobuild() before option
parsing. The complexity of this patch is because we want some of options
can be skipped safely. In this case their arguments should also be
consumed.
Options in 'perf record' and 'perf probe' are fixed in this patch.
[1] http://lkml.kernel.org/g/50399556C9727B4D88A595C8584AAB3752627CD4@GSjpTKYDCembx32.service.hitachi.net
Test result:
Normal case:
# ./perf probe --vmlinux /tmp/vmlinux sys_write
Added new event:
probe:sys_write (on sys_write)
You can now use it in all perf tools, such as:
perf record -e probe:sys_write -aR sleep 1
Build with NO_DWARF=1:
# ./perf probe -L sys_write
Error: switch `L' is not available because NO_DWARF=1
Usage: perf probe [<options>] 'PROBEDEF' ['PROBEDEF' ...]
or: perf probe [<options>] --add 'PROBEDEF' [--add 'PROBEDEF' ...]
or: perf probe [<options>] --del '[GROUP:]EVENT' ...
or: perf probe --list [GROUP:]EVENT ...
or: perf probe [<options>] --funcs
-L, --line <FUNC[:RLN[+NUM|-RLN2]]|SRC:ALN[+NUM|-ALN2]>
Show source code lines.
(not built-in because NO_DWARF=1)
# ./perf probe -k /tmp/vmlinux sys_write
Warning: switch `k' is being ignored because NO_DWARF=1
Added new event:
probe:sys_write (on sys_write)
You can now use it in all perf tools, such as:
perf record -e probe:sys_write -aR sleep 1
# ./perf probe --vmlinux /tmp/vmlinux sys_write
Warning: option `vmlinux' is being ignored because NO_DWARF=1
Added new event:
[SNIP]
# ./perf probe -l
Usage: perf probe [<options>] 'PROBEDEF' ['PROBEDEF' ...]
or: perf probe [<options>] --add 'PROBEDEF' [--add 'PROBEDEF' ...]
...
-k, --vmlinux <file> vmlinux pathname
(not built-in because NO_DWARF=1)
-L, --line <FUNC[:RLN[+NUM|-RLN2]]|SRC:ALN[+NUM|-ALN2]>
Show source code lines.
(not built-in because NO_DWARF=1)
...
-V, --vars <FUNC[@SRC][+OFF|%return|:RL|;PT]|SRC:AL|SRC;PT>
Show accessible variables on PROBEDEF
(not built-in because NO_DWARF=1)
--externs Show external variables too (with --vars only)
(not built-in because NO_DWARF=1)
--no-inlines Don't search inlined functions
(not built-in because NO_DWARF=1)
--range Show variables location range in scope (with --vars only)
(not built-in because NO_DWARF=1)
Signed-off-by: Wang Nan <wangnan0@huawei.com>
Tested-by: Arnaldo Carvalho de Melo <acme@redhat.com>
Cc: Alexei Starovoitov <ast@kernel.org>
Cc: Jiri Olsa <jolsa@kernel.org>
Cc: Masami Hiramatsu <masami.hiramatsu.pt@hitachi.com>
Cc: Namhyung Kim <namhyung@kernel.org>
Cc: Zefan Li <lizefan@huawei.com>
Cc: pi3orama@163.com
Link: http://lkml.kernel.org/r/1450089563-122430-14-git-send-email-wangnan0@huawei.com
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
2015-12-14 13:39:22 +03:00
# else
# define opt_show_lines NULL
# define opt_show_vars NULL
2011-01-20 17:15:45 +03:00
# endif
2015-05-05 18:22:57 +03:00
static int opt_add_probe_event ( const struct option * opt ,
const char * str , int unset __maybe_unused )
{
if ( str ) {
params . command = opt - > short_name ;
return parse_probe_event ( str ) ;
}
return 0 ;
}
static int opt_set_filter_with_command ( const struct option * opt ,
const char * str , int unset )
2015-05-05 05:29:50 +03:00
{
if ( ! unset )
2015-05-05 18:22:57 +03:00
params . command = opt - > short_name ;
2015-05-05 05:29:50 +03:00
if ( str )
return params_add_filter ( str ) ;
return 0 ;
}
2011-01-20 17:15:39 +03:00
2012-09-11 02:15:03 +04:00
static int opt_set_filter ( const struct option * opt __maybe_unused ,
const char * str , int unset __maybe_unused )
2011-01-20 17:15:39 +03:00
{
2015-04-24 12:47:50 +03:00
if ( str )
return params_add_filter ( str ) ;
2011-01-20 17:15:39 +03:00
2015-04-24 12:47:50 +03:00
return 0 ;
2011-01-20 17:15:39 +03:00
}
2009-10-09 01:17:38 +04:00
2014-02-06 09:32:09 +04:00
static int init_params ( void )
2014-01-16 13:39:47 +04:00
{
2014-02-06 09:32:09 +04:00
return line_range__init ( & params . line_range ) ;
2014-01-16 13:39:47 +04:00
}
static void cleanup_params ( void )
{
int i ;
for ( i = 0 ; i < params . nevents ; i + + )
clear_perf_probe_event ( params . events + i ) ;
line_range__clear ( & params . line_range ) ;
free ( params . target ) ;
2015-07-04 08:44:22 +03:00
strfilter__delete ( params . filter ) ;
2017-07-06 04:48:10 +03:00
nsinfo__put ( params . nsi ) ;
2014-01-16 13:39:47 +04:00
memset ( & params , 0 , sizeof ( params ) ) ;
}
2014-06-06 11:13:52 +04:00
static void pr_err_with_code ( const char * msg , int err )
{
2014-08-14 06:22:34 +04:00
char sbuf [ STRERR_BUFSIZE ] ;
2014-06-06 11:13:52 +04:00
pr_err ( " %s " , msg ) ;
2014-08-14 06:22:34 +04:00
pr_debug ( " Reason: %s (Code: %d) " ,
tools: Introduce str_error_r()
The tools so far have been using the strerror_r() GNU variant, that
returns a string, be it the buffer passed or something else.
But that, besides being tricky in cases where we expect that the
function using strerror_r() returns the error formatted in a provided
buffer (we have to check if it returned something else and copy that
instead), breaks the build on systems not using glibc, like Alpine
Linux, where musl libc is used.
So, introduce yet another wrapper, str_error_r(), that has the GNU
interface, but uses the portable XSI variant of strerror_r(), so that
users rest asured that the provided buffer is used and it is what is
returned.
Cc: Adrian Hunter <adrian.hunter@intel.com>
Cc: David Ahern <dsahern@gmail.com>
Cc: Jiri Olsa <jolsa@kernel.org>
Cc: Namhyung Kim <namhyung@kernel.org>
Cc: Wang Nan <wangnan0@huawei.com>
Link: http://lkml.kernel.org/n/tip-d4t42fnf48ytlk8rjxs822tf@git.kernel.org
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
2016-07-06 17:56:20 +03:00
str_error_r ( - err , sbuf , sizeof ( sbuf ) ) , err ) ;
2014-06-06 11:13:52 +04:00
pr_err ( " \n " ) ;
}
2015-09-04 15:16:01 +03:00
static int perf_add_probe_events ( struct perf_probe_event * pevs , int npevs )
{
int ret ;
int i , k ;
const char * event = NULL , * group = NULL ;
2015-09-10 05:27:05 +03:00
ret = init_probe_symbol_maps ( pevs - > uprobes ) ;
if ( ret < 0 )
return ret ;
2015-09-04 15:16:01 +03:00
ret = convert_perf_probe_events ( pevs , npevs ) ;
if ( ret < 0 )
goto out_cleanup ;
2016-08-25 19:24:27 +03:00
if ( params . command = = ' D ' ) { /* it shows definition */
ret = show_probe_trace_events ( pevs , npevs ) ;
goto out_cleanup ;
}
2015-09-04 15:16:01 +03:00
ret = apply_perf_probe_events ( pevs , npevs ) ;
if ( ret < 0 )
goto out_cleanup ;
for ( i = k = 0 ; i < npevs ; i + + )
k + = pevs [ i ] . ntevs ;
pr_info ( " Added new event%s \n " , ( k > 1 ) ? " s: " : " : " ) ;
for ( i = 0 ; i < npevs ; i + + ) {
struct perf_probe_event * pev = & pevs [ i ] ;
for ( k = 0 ; k < pev - > ntevs ; k + + ) {
struct probe_trace_event * tev = & pev - > tevs [ k ] ;
/* We use tev's name for showing new events */
show_perf_probe_event ( tev - > group , tev - > event , pev ,
tev - > point . module , false ) ;
/* Save the last valid name */
event = tev - > event ;
group = tev - > group ;
}
}
/* Note that it is possible to skip all events because of blacklist */
if ( event ) {
/* Show how to use the event. */
pr_info ( " \n You can now use it in all perf tools, such as: \n \n " ) ;
pr_info ( " \t perf record -e %s:%s -aR sleep 1 \n \n " , group , event ) ;
}
out_cleanup :
cleanup_perf_probe_events ( pevs , npevs ) ;
2015-09-10 05:27:05 +03:00
exit_probe_symbol_maps ( ) ;
2015-09-04 15:16:01 +03:00
return ret ;
}
2016-07-01 11:03:36 +03:00
static int del_perf_probe_caches ( struct strfilter * filter )
{
struct probe_cache * cache ;
struct strlist * bidlist ;
struct str_node * nd ;
int ret ;
2016-07-12 13:04:54 +03:00
bidlist = build_id_cache__list_all ( false ) ;
2016-07-01 11:03:36 +03:00
if ( ! bidlist ) {
ret = - errno ;
pr_debug ( " Failed to get buildids: %d \n " , ret ) ;
return ret ? : - ENOMEM ;
}
strlist__for_each_entry ( nd , bidlist ) {
2017-07-06 04:48:11 +03:00
cache = probe_cache__new ( nd - > s , NULL ) ;
2016-07-01 11:03:36 +03:00
if ( ! cache )
continue ;
if ( probe_cache__filter_purge ( cache , filter ) < 0 | |
probe_cache__commit ( cache ) < 0 )
pr_warning ( " Failed to remove entries for %s \n " , nd - > s ) ;
probe_cache__delete ( cache ) ;
}
return 0 ;
}
2015-09-04 15:16:03 +03:00
static int perf_del_probe_events ( struct strfilter * filter )
{
int ret , ret2 , ufd = - 1 , kfd = - 1 ;
char * str = strfilter__string ( filter ) ;
struct strlist * klist = NULL , * ulist = NULL ;
struct str_node * ent ;
if ( ! str )
return - EINVAL ;
pr_debug ( " Delete filter: \' %s \' \n " , str ) ;
2016-07-01 11:03:36 +03:00
if ( probe_conf . cache )
return del_perf_probe_caches ( filter ) ;
2015-09-04 15:16:03 +03:00
/* Get current event names */
ret = probe_file__open_both ( & kfd , & ufd , PF_FL_RW ) ;
if ( ret < 0 )
goto out ;
klist = strlist__new ( NULL , NULL ) ;
2015-09-16 15:52:42 +03:00
ulist = strlist__new ( NULL , NULL ) ;
if ( ! klist | | ! ulist ) {
ret = - ENOMEM ;
goto out ;
}
2015-09-04 15:16:03 +03:00
ret = probe_file__get_events ( kfd , filter , klist ) ;
if ( ret = = 0 ) {
2016-06-23 17:31:20 +03:00
strlist__for_each_entry ( ent , klist )
2015-09-04 15:16:03 +03:00
pr_info ( " Removed event: %s \n " , ent - > s ) ;
ret = probe_file__del_strlist ( kfd , klist ) ;
if ( ret < 0 )
goto error ;
}
ret2 = probe_file__get_events ( ufd , filter , ulist ) ;
if ( ret2 = = 0 ) {
2016-06-23 17:31:20 +03:00
strlist__for_each_entry ( ent , ulist )
2015-09-04 15:16:03 +03:00
pr_info ( " Removed event: %s \n " , ent - > s ) ;
ret2 = probe_file__del_strlist ( ufd , ulist ) ;
if ( ret2 < 0 )
goto error ;
}
if ( ret = = - ENOENT & & ret2 = = - ENOENT )
2017-03-17 11:16:32 +03:00
pr_warning ( " \" %s \" does not hit any event. \n " , str ) ;
else
ret = 0 ;
2015-09-04 15:16:03 +03:00
error :
if ( kfd > = 0 )
close ( kfd ) ;
if ( ufd > = 0 )
close ( ufd ) ;
out :
strlist__delete ( klist ) ;
strlist__delete ( ulist ) ;
free ( str ) ;
return ret ;
}
2016-08-25 19:24:27 +03:00
# ifdef HAVE_DWARF_SUPPORT
# define PROBEDEF_STR \
" [EVENT=]FUNC[@SRC][+OFF|%return|:RL|;PT]|SRC:AL|SRC;PT [[NAME=]ARG ...] "
# else
# define PROBEDEF_STR "[EVENT=]FUNC[+OFF|%return] [[NAME=]ARG ...]"
# endif
2014-01-16 13:39:47 +04:00
static int
2017-03-27 17:47:20 +03:00
__cmd_probe ( int argc , const char * * argv )
2012-10-01 22:20:58 +04:00
{
const char * const probe_usage [ ] = {
" perf probe [<options>] 'PROBEDEF' ['PROBEDEF' ...] " ,
" perf probe [<options>] --add 'PROBEDEF' [--add 'PROBEDEF' ...] " ,
" perf probe [<options>] --del '[GROUP:]EVENT' ... " ,
2015-04-24 12:47:50 +03:00
" perf probe --list [GROUP:]EVENT ... " ,
2013-09-30 14:07:11 +04:00
# ifdef HAVE_DWARF_SUPPORT
2012-10-01 22:20:58 +04:00
" perf probe [<options>] --line 'LINEDESC' " ,
" perf probe [<options>] --vars 'PROBEPOINT' " ,
2010-02-25 16:35:12 +03:00
# endif
2015-04-23 16:46:12 +03:00
" perf probe [<options>] --funcs " ,
2012-10-01 22:20:58 +04:00
NULL
2015-04-24 12:47:50 +03:00
} ;
2014-10-22 19:15:49 +04:00
struct option options [ ] = {
2010-04-13 12:37:33 +04:00
OPT_INCR ( ' v ' , " verbose " , & verbose ,
2009-10-17 04:08:10 +04:00
" be more verbose (show parsed arguments, etc) " ) ,
2014-10-27 23:31:31 +03:00
OPT_BOOLEAN ( ' q ' , " quiet " , & params . quiet ,
2016-11-23 16:44:47 +03:00
" be quiet (do not show any messages) " ) ,
2015-04-24 12:47:50 +03:00
OPT_CALLBACK_DEFAULT ( ' l ' , " list " , NULL , " [GROUP:]EVENT " ,
2015-05-05 18:22:57 +03:00
" list up probe events " ,
opt_set_filter_with_command , DEFAULT_LIST_FILTER ) ,
2009-12-09 01:03:23 +03:00
OPT_CALLBACK ( ' d ' , " del " , NULL , " [GROUP:]EVENT " , " delete a probe event. " ,
2015-05-05 18:22:57 +03:00
opt_set_filter_with_command ) ,
2016-08-25 19:24:27 +03:00
OPT_CALLBACK ( ' a ' , " add " , NULL , PROBEDEF_STR ,
2009-10-09 01:17:38 +04:00
" probe point definition, where \n "
2009-12-15 18:32:18 +03:00
" \t \t GROUP: \t Group name (optional) \n "
" \t \t EVENT: \t Event name \n "
2009-10-09 01:17:38 +04:00
" \t \t FUNC: \t Function name \n "
2010-02-25 16:36:12 +03:00
" \t \t OFF: \t Offset from function entry (in byte) \n "
2009-10-27 23:43:10 +03:00
" \t \t %return: \t Put the probe at function return \n "
2013-09-30 14:07:11 +04:00
# ifdef HAVE_DWARF_SUPPORT
2009-10-09 01:17:38 +04:00
" \t \t SRC: \t Source code path \n "
2010-02-25 16:36:12 +03:00
" \t \t RL: \t Relative line number from function entry. \n "
" \t \t AL: \t Absolute line number in file. \n "
" \t \t PT: \t Lazy expression of line code. \n "
2009-10-09 01:17:38 +04:00
" \t \t ARG: \t Probe argument (local variable name or \n "
2009-12-01 03:19:58 +03:00
" \t \t \t kprobe-tracer argument format.) \n " ,
2010-03-22 19:10:26 +03:00
# else
" \t \t ARG: \t Probe argument (kprobe-tracer argument format.) \n " ,
# endif
2009-10-27 23:43:10 +03:00
opt_add_probe_event ) ,
2016-08-25 19:24:27 +03:00
OPT_CALLBACK ( ' D ' , " definition " , NULL , PROBEDEF_STR ,
" Show trace event definition of given traceevent for k/uprobe_events. " ,
opt_add_probe_event ) ,
2015-05-08 04:03:31 +03:00
OPT_BOOLEAN ( ' f ' , " force " , & probe_conf . force_add , " forcibly add events "
2009-12-15 18:32:25 +03:00
" with existing name " ) ,
2010-01-06 17:45:34 +03:00
OPT_CALLBACK ( ' L ' , " line " , NULL ,
2010-04-02 20:50:39 +04:00
" FUNC[:RLN[+NUM|-RLN2]]|SRC:ALN[+NUM|-ALN2] " ,
2010-01-06 17:45:34 +03:00
" Show source code lines. " , opt_show_lines ) ,
2010-10-21 14:13:23 +04:00
OPT_CALLBACK ( ' V ' , " vars " , NULL ,
" FUNC[@SRC][+OFF|%return|:RL|;PT]|SRC:AL|SRC;PT " ,
" Show accessible variables on PROBEDEF " , opt_show_vars ) ,
2015-05-08 04:03:31 +03:00
OPT_BOOLEAN ( ' \0 ' , " externs " , & probe_conf . show_ext_vars ,
2010-10-21 14:13:35 +04:00
" Show external variables too (with --vars only) " ) ,
perf probe: Add --range option to show a variable's location range
It is not easy for users to get the accurate byte offset or the line
number where a local variable can be probed.
With '--range' option, local variables in the scope of the probe point
are showed with a byte offset range, and can be added according to this
range information.
For example, there are some variables in the function
generic_perform_write():
<generic_perform_write@mm/filemap.c:0>
0 ssize_t generic_perform_write(struct file *file,
1 struct iov_iter *i, loff_t pos)
2 {
3 struct address_space *mapping = file->f_mapping;
4 const struct address_space_operations *a_ops = mapping->a_ops;
...
42 status = a_ops->write_begin(file, mapping, pos, bytes, flags,
&page, &fsdata);
44 if (unlikely(status < 0))
But we fail when we try to probe the variable 'a_ops' at line 42 or 44.
$ perf probe --add 'generic_perform_write:42 a_ops'
Failed to find the location of a_ops at this address.
Perhaps, it has been optimized out.
This is because the source code do not match the assembly, so a variable
may not be available in the source code line where it appears.
After this patch, we can lookup the accurate byte offset range of a
variable, 'INV' indicates that this variable is not valid at the given
point, but available in the scope:
$ perf probe --vars 'generic_perform_write:42' --range
Available variables at generic_perform_write:42
@<generic_perform_write+141>
[INV] ssize_t written @<generic_perform_write+[324-331]>
[INV] struct address_space_operations* a_ops @<generic_perform_write+[55-61,170-176,223-246]>
[VAL] (unknown_type) fsdata @<generic_perform_write+[70-307,346-411]>
[VAL] loff_t pos @<generic_perform_write+[0-286,286-336,346-411]>
[VAL] long int status @<generic_perform_write+[83-342,346-411]>
[VAL] long unsigned int bytes @<generic_perform_write+[122-311,320-338,346-403,403-411]>
[VAL] struct address_space* mapping @<generic_perform_write+[35-344,346-411]>
[VAL] struct iov_iter* i @<generic_perform_write+[0-340,346-411]>
[VAL] struct page* page @<generic_perform_write+[70-307,346-411]>
Then it is more clear for us to add a probe with this variable:
$ perf probe --add 'generic_perform_write+170 a_ops'
Added new event:
probe:generic_perform_write (on generic_perform_write+170 with a_ops)
Signed-off-by: He Kuang <hekuang@huawei.com>
Acked-by: Masami Hiramatsu <masami.hiramatsu.pt@hitachi.com>
Cc: Jiri Olsa <jolsa@kernel.org>
Cc: Peter Zijlstra <a.p.zijlstra@chello.nl>
Cc: Wang Nan <wangnan0@huawei.com>
Link: http://lkml.kernel.org/r/1431336304-16863-2-git-send-email-hekuang@huawei.com
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
2015-05-11 12:25:03 +03:00
OPT_BOOLEAN ( ' \0 ' , " range " , & probe_conf . show_location_range ,
" Show variables location range in scope (with --vars only) " ) ,
2010-03-22 19:10:26 +03:00
OPT_STRING ( ' k ' , " vmlinux " , & symbol_conf . vmlinux_name ,
" file " , " vmlinux pathname " ) ,
2010-06-14 23:26:30 +04:00
OPT_STRING ( ' s ' , " source " , & symbol_conf . source_prefix ,
" directory " , " path to kernel source " ) ,
2015-05-08 04:03:33 +03:00
OPT_BOOLEAN ( ' \0 ' , " no-inlines " , & probe_conf . no_inlines ,
" Don't search inlined functions " ) ,
2010-03-17 01:06:05 +03:00
OPT__DRY_RUN ( & probe_event_dry_run ) ,
2015-05-08 04:03:31 +03:00
OPT_INTEGER ( ' \0 ' , " max-probes " , & probe_conf . max_probes ,
2010-04-21 23:56:40 +04:00
" Set how many probe points can be found for a probe. " ) ,
2015-05-05 05:29:50 +03:00
OPT_CALLBACK_DEFAULT ( ' F ' , " funcs " , NULL , " [FILTER] " ,
" Show potential probe-able functions. " ,
2015-05-05 18:22:57 +03:00
opt_set_filter_with_command , DEFAULT_FUNC_FILTER ) ,
2011-01-20 17:15:45 +03:00
OPT_CALLBACK ( ' \0 ' , " filter " , NULL ,
" [!]FILTER " , " Set a filter (with --vars/funcs only) \n "
" \t \t \t (default: \" " DEFAULT_VAR_FILTER " \" for --vars, \n "
" \t \t \t \" " DEFAULT_FUNC_FILTER " \" for --funcs) " ,
opt_set_filter ) ,
2012-04-16 16:09:09 +04:00
OPT_CALLBACK ( ' x ' , " exec " , NULL , " executable|path " ,
" target executable name or path " , opt_set_target ) ,
2015-10-02 15:58:32 +03:00
OPT_CALLBACK ( ' m ' , " module " , NULL , " modname|path " ,
" target module name (for online) or path (for offline) " ,
opt_set_target ) ,
2013-10-28 12:04:24 +04:00
OPT_BOOLEAN ( 0 , " demangle " , & symbol_conf . demangle ,
2014-10-27 23:31:24 +03:00
" Enable symbol demangling " ) ,
2014-09-13 08:15:05 +04:00
OPT_BOOLEAN ( 0 , " demangle-kernel " , & symbol_conf . demangle_kernel ,
" Enable kernel symbol demangling " ) ,
2016-06-15 06:28:40 +03:00
OPT_BOOLEAN ( 0 , " cache " , & probe_conf . cache , " Manipulate probe cache " ) ,
2016-07-21 12:48:32 +03:00
OPT_STRING ( 0 , " symfs " , & symbol_conf . symfs , " directory " ,
" Look for files with symbols relative to this directory " ) ,
2017-07-06 04:48:10 +03:00
OPT_CALLBACK ( 0 , " target-ns " , NULL , " pid " ,
" target pid for namespace contexts " , opt_set_target_ns ) ,
2009-10-09 01:17:38 +04:00
OPT_END ( )
2012-10-01 22:20:58 +04:00
} ;
2010-04-12 21:17:42 +04:00
int ret ;
2014-10-22 19:15:49 +04:00
set_option_flag ( options , ' a ' , " add " , PARSE_OPT_EXCLUSIVE ) ;
set_option_flag ( options , ' d ' , " del " , PARSE_OPT_EXCLUSIVE ) ;
2016-08-25 19:24:27 +03:00
set_option_flag ( options , ' D ' , " definition " , PARSE_OPT_EXCLUSIVE ) ;
2014-10-22 19:15:49 +04:00
set_option_flag ( options , ' l ' , " list " , PARSE_OPT_EXCLUSIVE ) ;
# ifdef HAVE_DWARF_SUPPORT
set_option_flag ( options , ' L ' , " line " , PARSE_OPT_EXCLUSIVE ) ;
set_option_flag ( options , ' V ' , " vars " , PARSE_OPT_EXCLUSIVE ) ;
perf tools: Make options always available, even if required libs not linked
This patch keeps options of perf builtins same in all conditions. If
one option is disabled because of compiling options, users should be
notified.
Masami suggested another implementation in [1] that, by adding a
OPTION_NEXT_DEPENDS option before those options in the 'struct option'
array, options parser knows an option is disabled. However, in some
cases this array is reordered (options__order()). In addition, in
parse-option.c that array is const, so we can't simply merge
information in decorator option into the affacted option.
This patch chooses a simpler implementation that, introducing a
set_option_nobuild() function and two option parsing flags. Builtins
with such options should call set_option_nobuild() before option
parsing. The complexity of this patch is because we want some of options
can be skipped safely. In this case their arguments should also be
consumed.
Options in 'perf record' and 'perf probe' are fixed in this patch.
[1] http://lkml.kernel.org/g/50399556C9727B4D88A595C8584AAB3752627CD4@GSjpTKYDCembx32.service.hitachi.net
Test result:
Normal case:
# ./perf probe --vmlinux /tmp/vmlinux sys_write
Added new event:
probe:sys_write (on sys_write)
You can now use it in all perf tools, such as:
perf record -e probe:sys_write -aR sleep 1
Build with NO_DWARF=1:
# ./perf probe -L sys_write
Error: switch `L' is not available because NO_DWARF=1
Usage: perf probe [<options>] 'PROBEDEF' ['PROBEDEF' ...]
or: perf probe [<options>] --add 'PROBEDEF' [--add 'PROBEDEF' ...]
or: perf probe [<options>] --del '[GROUP:]EVENT' ...
or: perf probe --list [GROUP:]EVENT ...
or: perf probe [<options>] --funcs
-L, --line <FUNC[:RLN[+NUM|-RLN2]]|SRC:ALN[+NUM|-ALN2]>
Show source code lines.
(not built-in because NO_DWARF=1)
# ./perf probe -k /tmp/vmlinux sys_write
Warning: switch `k' is being ignored because NO_DWARF=1
Added new event:
probe:sys_write (on sys_write)
You can now use it in all perf tools, such as:
perf record -e probe:sys_write -aR sleep 1
# ./perf probe --vmlinux /tmp/vmlinux sys_write
Warning: option `vmlinux' is being ignored because NO_DWARF=1
Added new event:
[SNIP]
# ./perf probe -l
Usage: perf probe [<options>] 'PROBEDEF' ['PROBEDEF' ...]
or: perf probe [<options>] --add 'PROBEDEF' [--add 'PROBEDEF' ...]
...
-k, --vmlinux <file> vmlinux pathname
(not built-in because NO_DWARF=1)
-L, --line <FUNC[:RLN[+NUM|-RLN2]]|SRC:ALN[+NUM|-ALN2]>
Show source code lines.
(not built-in because NO_DWARF=1)
...
-V, --vars <FUNC[@SRC][+OFF|%return|:RL|;PT]|SRC:AL|SRC;PT>
Show accessible variables on PROBEDEF
(not built-in because NO_DWARF=1)
--externs Show external variables too (with --vars only)
(not built-in because NO_DWARF=1)
--no-inlines Don't search inlined functions
(not built-in because NO_DWARF=1)
--range Show variables location range in scope (with --vars only)
(not built-in because NO_DWARF=1)
Signed-off-by: Wang Nan <wangnan0@huawei.com>
Tested-by: Arnaldo Carvalho de Melo <acme@redhat.com>
Cc: Alexei Starovoitov <ast@kernel.org>
Cc: Jiri Olsa <jolsa@kernel.org>
Cc: Masami Hiramatsu <masami.hiramatsu.pt@hitachi.com>
Cc: Namhyung Kim <namhyung@kernel.org>
Cc: Zefan Li <lizefan@huawei.com>
Cc: pi3orama@163.com
Link: http://lkml.kernel.org/r/1450089563-122430-14-git-send-email-wangnan0@huawei.com
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
2015-12-14 13:39:22 +03:00
# else
# define set_nobuild(s, l, c) set_option_nobuild(options, s, l, "NO_DWARF=1", c)
set_nobuild ( ' L ' , " line " , false ) ;
set_nobuild ( ' V ' , " vars " , false ) ;
set_nobuild ( ' \0 ' , " externs " , false ) ;
set_nobuild ( ' \0 ' , " range " , false ) ;
set_nobuild ( ' k ' , " vmlinux " , true ) ;
set_nobuild ( ' s ' , " source " , true ) ;
set_nobuild ( ' \0 ' , " no-inlines " , true ) ;
# undef set_nobuild
2014-10-22 19:15:49 +04:00
# endif
2015-04-23 16:46:12 +03:00
set_option_flag ( options , ' F ' , " funcs " , PARSE_OPT_EXCLUSIVE ) ;
2014-10-22 19:15:49 +04:00
2009-10-09 01:17:38 +04:00
argc = parse_options ( argc , argv , options , probe_usage ,
2009-10-27 23:43:02 +03:00
PARSE_OPT_STOP_AT_NON_OPTION ) ;
2009-12-15 18:31:28 +03:00
if ( argc > 0 ) {
if ( strcmp ( argv [ 0 ] , " - " ) = = 0 ) {
2015-10-24 18:49:27 +03:00
usage_with_options_msg ( probe_usage , options ,
" '-' is not supported. \n " ) ;
2009-12-15 18:31:28 +03:00
}
2015-05-05 18:22:57 +03:00
if ( params . command & & params . command ! = ' a ' ) {
2015-10-24 18:49:27 +03:00
usage_with_options_msg ( probe_usage , options ,
" another command except --add is set. \n " ) ;
2015-05-05 18:22:57 +03:00
}
2010-04-12 21:17:42 +04:00
ret = parse_probe_event_argv ( argc , argv ) ;
if ( ret < 0 ) {
2014-06-06 11:13:52 +04:00
pr_err_with_code ( " Error: Command Parse Error. " , ret ) ;
2010-04-12 21:17:42 +04:00
return ret ;
}
2015-05-05 18:22:57 +03:00
params . command = ' a ' ;
2009-12-15 18:31:28 +03:00
}
2009-10-27 23:43:02 +03:00
2014-10-27 23:31:31 +03:00
if ( params . quiet ) {
if ( verbose ! = 0 ) {
pr_err ( " Error: -v and -q are exclusive. \n " ) ;
return - EINVAL ;
}
verbose = - 1 ;
}
2015-05-08 04:03:31 +03:00
if ( probe_conf . max_probes = = 0 )
probe_conf . max_probes = MAX_PROBES ;
2010-04-21 23:56:40 +04:00
2010-12-10 16:06:03 +03:00
/*
* Only consider the user ' s kernel image path if given .
*/
symbol_conf . try_vmlinux_path = ( symbol_conf . vmlinux_name = = NULL ) ;
2016-08-26 17:57:58 +03:00
/*
* Except for - - list , - - del and - - add , other command doesn ' t depend
* nor change running kernel . So if user gives offline vmlinux ,
* ignore its buildid .
*/
if ( ! strchr ( " lda " , params . command ) & & symbol_conf . vmlinux_name )
symbol_conf . ignore_vmlinux_buildid = true ;
2015-05-05 18:22:57 +03:00
switch ( params . command ) {
case ' l ' :
2012-04-16 16:09:09 +04:00
if ( params . uprobes ) {
2015-10-24 18:49:27 +03:00
pr_err ( " Error: Don't use --list with --exec. \n " ) ;
parse_options_usage ( probe_usage , options , " l " , true ) ;
parse_options_usage ( NULL , options , " x " , true ) ;
return - EINVAL ;
2012-04-16 16:09:09 +04:00
}
2015-04-24 12:47:50 +03:00
ret = show_perf_probe_events ( params . filter ) ;
2010-04-12 21:17:42 +04:00
if ( ret < 0 )
2014-06-06 11:13:52 +04:00
pr_err_with_code ( " Error: Failed to show event list. " , ret ) ;
2010-04-12 21:17:42 +04:00
return ret ;
2015-05-05 18:22:57 +03:00
case ' F ' :
2017-07-06 04:48:10 +03:00
ret = show_available_funcs ( params . target , params . nsi ,
params . filter , params . uprobes ) ;
2011-01-13 15:46:11 +03:00
if ( ret < 0 )
2014-06-06 11:13:52 +04:00
pr_err_with_code ( " Error: Failed to show functions. " , ret ) ;
2011-01-13 15:46:11 +03:00
return ret ;
2013-09-30 14:07:11 +04:00
# ifdef HAVE_DWARF_SUPPORT
2015-05-05 18:22:57 +03:00
case ' L ' :
2014-09-17 12:40:54 +04:00
ret = show_line_range ( & params . line_range , params . target ,
2017-07-06 04:48:10 +03:00
params . nsi , params . uprobes ) ;
2010-04-12 21:17:42 +04:00
if ( ret < 0 )
2014-06-06 11:13:52 +04:00
pr_err_with_code ( " Error: Failed to show lines. " , ret ) ;
2010-04-12 21:17:42 +04:00
return ret ;
2015-05-05 18:22:57 +03:00
case ' V ' :
2011-01-20 17:15:39 +03:00
if ( ! params . filter )
params . filter = strfilter__new ( DEFAULT_VAR_FILTER ,
NULL ) ;
2010-10-21 14:13:23 +04:00
ret = show_available_vars ( params . events , params . nevents ,
2015-05-08 04:03:31 +03:00
params . filter ) ;
2010-10-21 14:13:23 +04:00
if ( ret < 0 )
2014-06-06 11:13:52 +04:00
pr_err_with_code ( " Error: Failed to show vars. " , ret ) ;
2010-10-21 14:13:23 +04:00
return ret ;
2010-01-06 17:45:34 +03:00
# endif
2015-05-05 18:22:57 +03:00
case ' d ' :
2015-09-04 15:16:03 +03:00
ret = perf_del_probe_events ( params . filter ) ;
2010-04-12 21:17:42 +04:00
if ( ret < 0 ) {
2014-06-06 11:13:52 +04:00
pr_err_with_code ( " Error: Failed to delete events. " , ret ) ;
2010-04-12 21:17:42 +04:00
return ret ;
}
2015-05-05 18:22:57 +03:00
break ;
2016-08-25 19:24:27 +03:00
case ' D ' :
2016-08-25 19:24:42 +03:00
case ' a ' :
2015-04-01 13:25:42 +03:00
/* Ensure the last given target is used */
if ( params . target & & ! params . target_used ) {
2015-10-24 18:49:27 +03:00
pr_err ( " Error: -x/-m must follow the probe definitions. \n " ) ;
parse_options_usage ( probe_usage , options , " m " , true ) ;
parse_options_usage ( NULL , options , " x " , true ) ;
return - EINVAL ;
2015-04-01 13:25:42 +03:00
}
2015-09-04 15:16:01 +03:00
ret = perf_add_probe_events ( params . events , params . nevents ) ;
2010-04-12 21:17:42 +04:00
if ( ret < 0 ) {
2019-07-18 17:28:37 +03:00
/*
* When perf_add_probe_events ( ) fails it calls
* cleanup_perf_probe_events ( pevs , npevs ) , i . e .
* cleanup_perf_probe_events ( params . events , params . nevents ) , which
* will call clear_perf_probe_event ( ) , so set nevents to zero
* to avoid cleanup_params ( ) to call clear_perf_probe_event ( ) again
* on the same pevs .
*/
params . nevents = 0 ;
2014-06-06 11:13:52 +04:00
pr_err_with_code ( " Error: Failed to add events. " , ret ) ;
2010-04-12 21:17:42 +04:00
return ret ;
}
2015-05-05 18:22:57 +03:00
break ;
default :
usage_with_options ( probe_usage , options ) ;
2010-04-12 21:17:42 +04:00
}
2009-10-09 01:17:38 +04:00
return 0 ;
}
2014-01-16 13:39:47 +04:00
2017-03-27 17:47:20 +03:00
int cmd_probe ( int argc , const char * * argv )
2014-01-16 13:39:47 +04:00
{
int ret ;
2014-02-06 09:32:09 +04:00
ret = init_params ( ) ;
if ( ! ret ) {
2017-03-27 17:47:20 +03:00
ret = __cmd_probe ( argc , argv ) ;
2014-02-06 09:32:09 +04:00
cleanup_params ( ) ;
}
2014-01-16 13:39:47 +04:00
2015-05-06 15:46:45 +03:00
return ret < 0 ? ret : 0 ;
2014-01-16 13:39:47 +04:00
}