1 // SPDX-License-Identifier: GPL-2.0-or-later 2 /* 3 * builtin-probe.c 4 * 5 * Builtin probe command: Set up probe events by C expression 6 * 7 * Written by Masami Hiramatsu <mhiramat@redhat.com> 8 */ 9 #include <sys/utsname.h> 10 #include <sys/types.h> 11 #include <sys/stat.h> 12 #include <fcntl.h> 13 #include <errno.h> 14 #include <stdio.h> 15 #include <unistd.h> 16 #include <stdlib.h> 17 #include <string.h> 18 19 #include "perf.h" 20 #include "builtin.h" 21 #include "namespaces.h" 22 #include "util/strlist.h" 23 #include "util/strfilter.h" 24 #include "util/symbol.h" 25 #include "util/debug.h" 26 #include <subcmd/parse-options.h> 27 #include "util/probe-finder.h" 28 #include "util/probe-event.h" 29 #include "util/probe-file.h" 30 #include <linux/zalloc.h> 31 32 #define DEFAULT_VAR_FILTER "!__k???tab_* & !__crc_*" 33 #define DEFAULT_FUNC_FILTER "!_*" 34 #define DEFAULT_LIST_FILTER "*" 35 36 /* Session management structure */ 37 static struct { 38 int command; /* Command short_name */ 39 bool list_events; 40 bool uprobes; 41 bool quiet; 42 bool target_used; 43 int nevents; 44 struct perf_probe_event events[MAX_PROBES]; 45 struct line_range line_range; 46 char *target; 47 struct strfilter *filter; 48 struct nsinfo *nsi; 49 } params; 50 51 /* Parse an event definition. Note that any error must die. */ 52 static int parse_probe_event(const char *str) 53 { 54 struct perf_probe_event *pev = ¶ms.events[params.nevents]; 55 int ret; 56 57 pr_debug("probe-definition(%d): %s\n", params.nevents, str); 58 if (++params.nevents == MAX_PROBES) { 59 pr_err("Too many probes (> %d) were specified.", MAX_PROBES); 60 return -1; 61 } 62 63 pev->uprobes = params.uprobes; 64 if (params.target) { 65 pev->target = strdup(params.target); 66 if (!pev->target) 67 return -ENOMEM; 68 params.target_used = true; 69 } 70 71 pev->nsi = nsinfo__get(params.nsi); 72 73 /* Parse a perf-probe command into event */ 74 ret = parse_perf_probe_command(str, pev); 75 pr_debug("%d arguments\n", pev->nargs); 76 77 return ret; 78 } 79 80 static int params_add_filter(const char *str) 81 { 82 const char *err = NULL; 83 int ret = 0; 84 85 pr_debug2("Add filter: %s\n", str); 86 if (!params.filter) { 87 params.filter = strfilter__new(str, &err); 88 if (!params.filter) 89 ret = err ? -EINVAL : -ENOMEM; 90 } else 91 ret = strfilter__or(params.filter, str, &err); 92 93 if (ret == -EINVAL) { 94 pr_err("Filter parse error at %td.\n", err - str + 1); 95 pr_err("Source: \"%s\"\n", str); 96 pr_err(" %*c\n", (int)(err - str + 1), '^'); 97 } 98 99 return ret; 100 } 101 102 static int set_target(const char *ptr) 103 { 104 int found = 0; 105 const char *buf; 106 107 /* 108 * The first argument after options can be an absolute path 109 * to an executable / library or kernel module. 110 * 111 * TODO: Support relative path, and $PATH, $LD_LIBRARY_PATH, 112 * short module name. 113 */ 114 if (!params.target && ptr && *ptr == '/') { 115 params.target = strdup(ptr); 116 if (!params.target) 117 return -ENOMEM; 118 params.target_used = false; 119 120 found = 1; 121 buf = ptr + (strlen(ptr) - 3); 122 123 if (strcmp(buf, ".ko")) 124 params.uprobes = true; 125 126 } 127 128 return found; 129 } 130 131 static int parse_probe_event_argv(int argc, const char **argv) 132 { 133 int i, len, ret, found_target; 134 char *buf; 135 136 found_target = set_target(argv[0]); 137 if (found_target < 0) 138 return found_target; 139 140 if (found_target && argc == 1) 141 return 0; 142 143 /* Bind up rest arguments */ 144 len = 0; 145 for (i = 0; i < argc; i++) { 146 if (i == 0 && found_target) 147 continue; 148 149 len += strlen(argv[i]) + 1; 150 } 151 buf = zalloc(len + 1); 152 if (buf == NULL) 153 return -ENOMEM; 154 len = 0; 155 for (i = 0; i < argc; i++) { 156 if (i == 0 && found_target) 157 continue; 158 159 len += sprintf(&buf[len], "%s ", argv[i]); 160 } 161 ret = parse_probe_event(buf); 162 free(buf); 163 return ret; 164 } 165 166 static int opt_set_target(const struct option *opt, const char *str, 167 int unset __maybe_unused) 168 { 169 int ret = -ENOENT; 170 char *tmp; 171 172 if (str) { 173 if (!strcmp(opt->long_name, "exec")) 174 params.uprobes = true; 175 else if (!strcmp(opt->long_name, "module")) 176 params.uprobes = false; 177 else 178 return ret; 179 180 /* Expand given path to absolute path, except for modulename */ 181 if (params.uprobes || strchr(str, '/')) { 182 tmp = nsinfo__realpath(str, params.nsi); 183 if (!tmp) { 184 pr_warning("Failed to get the absolute path of %s: %m\n", str); 185 return ret; 186 } 187 } else { 188 tmp = strdup(str); 189 if (!tmp) 190 return -ENOMEM; 191 } 192 free(params.target); 193 params.target = tmp; 194 params.target_used = false; 195 ret = 0; 196 } 197 198 return ret; 199 } 200 201 static int opt_set_target_ns(const struct option *opt __maybe_unused, 202 const char *str, int unset __maybe_unused) 203 { 204 int ret = -ENOENT; 205 pid_t ns_pid; 206 struct nsinfo *nsip; 207 208 if (str) { 209 errno = 0; 210 ns_pid = (pid_t)strtol(str, NULL, 10); 211 if (errno != 0) { 212 ret = -errno; 213 pr_warning("Failed to parse %s as a pid: %s\n", str, 214 strerror(errno)); 215 return ret; 216 } 217 nsip = nsinfo__new(ns_pid); 218 if (nsip && nsip->need_setns) 219 params.nsi = nsinfo__get(nsip); 220 nsinfo__put(nsip); 221 222 ret = 0; 223 } 224 225 return ret; 226 } 227 228 229 /* Command option callbacks */ 230 231 #ifdef HAVE_DWARF_SUPPORT 232 static int opt_show_lines(const struct option *opt, 233 const char *str, int unset __maybe_unused) 234 { 235 int ret = 0; 236 237 if (!str) 238 return 0; 239 240 if (params.command == 'L') { 241 pr_warning("Warning: more than one --line options are" 242 " detected. Only the first one is valid.\n"); 243 return 0; 244 } 245 246 params.command = opt->short_name; 247 ret = parse_line_range_desc(str, ¶ms.line_range); 248 249 return ret; 250 } 251 252 static int opt_show_vars(const struct option *opt, 253 const char *str, int unset __maybe_unused) 254 { 255 struct perf_probe_event *pev = ¶ms.events[params.nevents]; 256 int ret; 257 258 if (!str) 259 return 0; 260 261 ret = parse_probe_event(str); 262 if (!ret && pev->nargs != 0) { 263 pr_err(" Error: '--vars' doesn't accept arguments.\n"); 264 return -EINVAL; 265 } 266 params.command = opt->short_name; 267 268 return ret; 269 } 270 #else 271 # define opt_show_lines NULL 272 # define opt_show_vars NULL 273 #endif 274 static int opt_add_probe_event(const struct option *opt, 275 const char *str, int unset __maybe_unused) 276 { 277 if (str) { 278 params.command = opt->short_name; 279 return parse_probe_event(str); 280 } 281 282 return 0; 283 } 284 285 static int opt_set_filter_with_command(const struct option *opt, 286 const char *str, int unset) 287 { 288 if (!unset) 289 params.command = opt->short_name; 290 291 if (str) 292 return params_add_filter(str); 293 294 return 0; 295 } 296 297 static int opt_set_filter(const struct option *opt __maybe_unused, 298 const char *str, int unset __maybe_unused) 299 { 300 if (str) 301 return params_add_filter(str); 302 303 return 0; 304 } 305 306 static int init_params(void) 307 { 308 return line_range__init(¶ms.line_range); 309 } 310 311 static void cleanup_params(void) 312 { 313 int i; 314 315 for (i = 0; i < params.nevents; i++) 316 clear_perf_probe_event(params.events + i); 317 line_range__clear(¶ms.line_range); 318 free(params.target); 319 strfilter__delete(params.filter); 320 nsinfo__put(params.nsi); 321 memset(¶ms, 0, sizeof(params)); 322 } 323 324 static void pr_err_with_code(const char *msg, int err) 325 { 326 char sbuf[STRERR_BUFSIZE]; 327 328 pr_err("%s", msg); 329 pr_debug(" Reason: %s (Code: %d)", 330 str_error_r(-err, sbuf, sizeof(sbuf)), err); 331 pr_err("\n"); 332 } 333 334 static int perf_add_probe_events(struct perf_probe_event *pevs, int npevs) 335 { 336 int ret; 337 int i, k; 338 const char *event = NULL, *group = NULL; 339 340 ret = init_probe_symbol_maps(pevs->uprobes); 341 if (ret < 0) 342 return ret; 343 344 ret = convert_perf_probe_events(pevs, npevs); 345 if (ret < 0) 346 goto out_cleanup; 347 348 if (params.command == 'D') { /* it shows definition */ 349 ret = show_probe_trace_events(pevs, npevs); 350 goto out_cleanup; 351 } 352 353 ret = apply_perf_probe_events(pevs, npevs); 354 if (ret < 0) 355 goto out_cleanup; 356 357 for (i = k = 0; i < npevs; i++) 358 k += pevs[i].ntevs; 359 360 pr_info("Added new event%s\n", (k > 1) ? "s:" : ":"); 361 for (i = 0; i < npevs; i++) { 362 struct perf_probe_event *pev = &pevs[i]; 363 364 for (k = 0; k < pev->ntevs; k++) { 365 struct probe_trace_event *tev = &pev->tevs[k]; 366 367 /* We use tev's name for showing new events */ 368 show_perf_probe_event(tev->group, tev->event, pev, 369 tev->point.module, false); 370 371 /* Save the last valid name */ 372 event = tev->event; 373 group = tev->group; 374 } 375 } 376 377 /* Note that it is possible to skip all events because of blacklist */ 378 if (event) { 379 /* Show how to use the event. */ 380 pr_info("\nYou can now use it in all perf tools, such as:\n\n"); 381 pr_info("\tperf record -e %s:%s -aR sleep 1\n\n", group, event); 382 } 383 384 out_cleanup: 385 cleanup_perf_probe_events(pevs, npevs); 386 exit_probe_symbol_maps(); 387 return ret; 388 } 389 390 static int del_perf_probe_caches(struct strfilter *filter) 391 { 392 struct probe_cache *cache; 393 struct strlist *bidlist; 394 struct str_node *nd; 395 int ret; 396 397 bidlist = build_id_cache__list_all(false); 398 if (!bidlist) { 399 ret = -errno; 400 pr_debug("Failed to get buildids: %d\n", ret); 401 return ret ?: -ENOMEM; 402 } 403 404 strlist__for_each_entry(nd, bidlist) { 405 cache = probe_cache__new(nd->s, NULL); 406 if (!cache) 407 continue; 408 if (probe_cache__filter_purge(cache, filter) < 0 || 409 probe_cache__commit(cache) < 0) 410 pr_warning("Failed to remove entries for %s\n", nd->s); 411 probe_cache__delete(cache); 412 } 413 return 0; 414 } 415 416 static int perf_del_probe_events(struct strfilter *filter) 417 { 418 int ret, ret2, ufd = -1, kfd = -1; 419 char *str = strfilter__string(filter); 420 struct strlist *klist = NULL, *ulist = NULL; 421 struct str_node *ent; 422 423 if (!str) 424 return -EINVAL; 425 426 pr_debug("Delete filter: \'%s\'\n", str); 427 428 if (probe_conf.cache) 429 return del_perf_probe_caches(filter); 430 431 /* Get current event names */ 432 ret = probe_file__open_both(&kfd, &ufd, PF_FL_RW); 433 if (ret < 0) 434 goto out; 435 436 klist = strlist__new(NULL, NULL); 437 ulist = strlist__new(NULL, NULL); 438 if (!klist || !ulist) { 439 ret = -ENOMEM; 440 goto out; 441 } 442 443 ret = probe_file__get_events(kfd, filter, klist); 444 if (ret == 0) { 445 strlist__for_each_entry(ent, klist) 446 pr_info("Removed event: %s\n", ent->s); 447 448 ret = probe_file__del_strlist(kfd, klist); 449 if (ret < 0) 450 goto error; 451 } 452 453 ret2 = probe_file__get_events(ufd, filter, ulist); 454 if (ret2 == 0) { 455 strlist__for_each_entry(ent, ulist) 456 pr_info("Removed event: %s\n", ent->s); 457 458 ret2 = probe_file__del_strlist(ufd, ulist); 459 if (ret2 < 0) 460 goto error; 461 } 462 463 if (ret == -ENOENT && ret2 == -ENOENT) 464 pr_warning("\"%s\" does not hit any event.\n", str); 465 else 466 ret = 0; 467 468 error: 469 if (kfd >= 0) 470 close(kfd); 471 if (ufd >= 0) 472 close(ufd); 473 out: 474 strlist__delete(klist); 475 strlist__delete(ulist); 476 free(str); 477 478 return ret; 479 } 480 481 #ifdef HAVE_DWARF_SUPPORT 482 #define PROBEDEF_STR \ 483 "[EVENT=]FUNC[@SRC][+OFF|%return|:RL|;PT]|SRC:AL|SRC;PT [[NAME=]ARG ...]" 484 #else 485 #define PROBEDEF_STR "[EVENT=]FUNC[+OFF|%return] [[NAME=]ARG ...]" 486 #endif 487 488 489 static int 490 __cmd_probe(int argc, const char **argv) 491 { 492 const char * const probe_usage[] = { 493 "perf probe [<options>] 'PROBEDEF' ['PROBEDEF' ...]", 494 "perf probe [<options>] --add 'PROBEDEF' [--add 'PROBEDEF' ...]", 495 "perf probe [<options>] --del '[GROUP:]EVENT' ...", 496 "perf probe --list [GROUP:]EVENT ...", 497 #ifdef HAVE_DWARF_SUPPORT 498 "perf probe [<options>] --line 'LINEDESC'", 499 "perf probe [<options>] --vars 'PROBEPOINT'", 500 #endif 501 "perf probe [<options>] --funcs", 502 NULL 503 }; 504 struct option options[] = { 505 OPT_INCR('v', "verbose", &verbose, 506 "be more verbose (show parsed arguments, etc)"), 507 OPT_BOOLEAN('q', "quiet", ¶ms.quiet, 508 "be quiet (do not show any messages)"), 509 OPT_CALLBACK_DEFAULT('l', "list", NULL, "[GROUP:]EVENT", 510 "list up probe events", 511 opt_set_filter_with_command, DEFAULT_LIST_FILTER), 512 OPT_CALLBACK('d', "del", NULL, "[GROUP:]EVENT", "delete a probe event.", 513 opt_set_filter_with_command), 514 OPT_CALLBACK('a', "add", NULL, PROBEDEF_STR, 515 "probe point definition, where\n" 516 "\t\tGROUP:\tGroup name (optional)\n" 517 "\t\tEVENT:\tEvent name\n" 518 "\t\tFUNC:\tFunction name\n" 519 "\t\tOFF:\tOffset from function entry (in byte)\n" 520 "\t\t%return:\tPut the probe at function return\n" 521 #ifdef HAVE_DWARF_SUPPORT 522 "\t\tSRC:\tSource code path\n" 523 "\t\tRL:\tRelative line number from function entry.\n" 524 "\t\tAL:\tAbsolute line number in file.\n" 525 "\t\tPT:\tLazy expression of line code.\n" 526 "\t\tARG:\tProbe argument (local variable name or\n" 527 "\t\t\tkprobe-tracer argument format.)\n", 528 #else 529 "\t\tARG:\tProbe argument (kprobe-tracer argument format.)\n", 530 #endif 531 opt_add_probe_event), 532 OPT_CALLBACK('D', "definition", NULL, PROBEDEF_STR, 533 "Show trace event definition of given traceevent for k/uprobe_events.", 534 opt_add_probe_event), 535 OPT_BOOLEAN('f', "force", &probe_conf.force_add, "forcibly add events" 536 " with existing name"), 537 OPT_CALLBACK('L', "line", NULL, 538 "FUNC[:RLN[+NUM|-RLN2]]|SRC:ALN[+NUM|-ALN2]", 539 "Show source code lines.", opt_show_lines), 540 OPT_CALLBACK('V', "vars", NULL, 541 "FUNC[@SRC][+OFF|%return|:RL|;PT]|SRC:AL|SRC;PT", 542 "Show accessible variables on PROBEDEF", opt_show_vars), 543 OPT_BOOLEAN('\0', "externs", &probe_conf.show_ext_vars, 544 "Show external variables too (with --vars only)"), 545 OPT_BOOLEAN('\0', "range", &probe_conf.show_location_range, 546 "Show variables location range in scope (with --vars only)"), 547 OPT_STRING('k', "vmlinux", &symbol_conf.vmlinux_name, 548 "file", "vmlinux pathname"), 549 OPT_STRING('s', "source", &symbol_conf.source_prefix, 550 "directory", "path to kernel source"), 551 OPT_BOOLEAN('\0', "no-inlines", &probe_conf.no_inlines, 552 "Don't search inlined functions"), 553 OPT__DRY_RUN(&probe_event_dry_run), 554 OPT_INTEGER('\0', "max-probes", &probe_conf.max_probes, 555 "Set how many probe points can be found for a probe."), 556 OPT_CALLBACK_DEFAULT('F', "funcs", NULL, "[FILTER]", 557 "Show potential probe-able functions.", 558 opt_set_filter_with_command, DEFAULT_FUNC_FILTER), 559 OPT_CALLBACK('\0', "filter", NULL, 560 "[!]FILTER", "Set a filter (with --vars/funcs only)\n" 561 "\t\t\t(default: \"" DEFAULT_VAR_FILTER "\" for --vars,\n" 562 "\t\t\t \"" DEFAULT_FUNC_FILTER "\" for --funcs)", 563 opt_set_filter), 564 OPT_CALLBACK('x', "exec", NULL, "executable|path", 565 "target executable name or path", opt_set_target), 566 OPT_CALLBACK('m', "module", NULL, "modname|path", 567 "target module name (for online) or path (for offline)", 568 opt_set_target), 569 OPT_BOOLEAN(0, "demangle", &symbol_conf.demangle, 570 "Enable symbol demangling"), 571 OPT_BOOLEAN(0, "demangle-kernel", &symbol_conf.demangle_kernel, 572 "Enable kernel symbol demangling"), 573 OPT_BOOLEAN(0, "cache", &probe_conf.cache, "Manipulate probe cache"), 574 OPT_STRING(0, "symfs", &symbol_conf.symfs, "directory", 575 "Look for files with symbols relative to this directory"), 576 OPT_CALLBACK(0, "target-ns", NULL, "pid", 577 "target pid for namespace contexts", opt_set_target_ns), 578 OPT_END() 579 }; 580 int ret; 581 582 set_option_flag(options, 'a', "add", PARSE_OPT_EXCLUSIVE); 583 set_option_flag(options, 'd', "del", PARSE_OPT_EXCLUSIVE); 584 set_option_flag(options, 'D', "definition", PARSE_OPT_EXCLUSIVE); 585 set_option_flag(options, 'l', "list", PARSE_OPT_EXCLUSIVE); 586 #ifdef HAVE_DWARF_SUPPORT 587 set_option_flag(options, 'L', "line", PARSE_OPT_EXCLUSIVE); 588 set_option_flag(options, 'V', "vars", PARSE_OPT_EXCLUSIVE); 589 #else 590 # define set_nobuild(s, l, c) set_option_nobuild(options, s, l, "NO_DWARF=1", c) 591 set_nobuild('L', "line", false); 592 set_nobuild('V', "vars", false); 593 set_nobuild('\0', "externs", false); 594 set_nobuild('\0', "range", false); 595 set_nobuild('k', "vmlinux", true); 596 set_nobuild('s', "source", true); 597 set_nobuild('\0', "no-inlines", true); 598 # undef set_nobuild 599 #endif 600 set_option_flag(options, 'F', "funcs", PARSE_OPT_EXCLUSIVE); 601 602 argc = parse_options(argc, argv, options, probe_usage, 603 PARSE_OPT_STOP_AT_NON_OPTION); 604 if (argc > 0) { 605 if (strcmp(argv[0], "-") == 0) { 606 usage_with_options_msg(probe_usage, options, 607 "'-' is not supported.\n"); 608 } 609 if (params.command && params.command != 'a') { 610 usage_with_options_msg(probe_usage, options, 611 "another command except --add is set.\n"); 612 } 613 ret = parse_probe_event_argv(argc, argv); 614 if (ret < 0) { 615 pr_err_with_code(" Error: Command Parse Error.", ret); 616 return ret; 617 } 618 params.command = 'a'; 619 } 620 621 if (params.quiet) { 622 if (verbose != 0) { 623 pr_err(" Error: -v and -q are exclusive.\n"); 624 return -EINVAL; 625 } 626 verbose = -1; 627 } 628 629 if (probe_conf.max_probes == 0) 630 probe_conf.max_probes = MAX_PROBES; 631 632 /* 633 * Only consider the user's kernel image path if given. 634 */ 635 symbol_conf.try_vmlinux_path = (symbol_conf.vmlinux_name == NULL); 636 637 /* 638 * Except for --list, --del and --add, other command doesn't depend 639 * nor change running kernel. So if user gives offline vmlinux, 640 * ignore its buildid. 641 */ 642 if (!strchr("lda", params.command) && symbol_conf.vmlinux_name) 643 symbol_conf.ignore_vmlinux_buildid = true; 644 645 switch (params.command) { 646 case 'l': 647 if (params.uprobes) { 648 pr_err(" Error: Don't use --list with --exec.\n"); 649 parse_options_usage(probe_usage, options, "l", true); 650 parse_options_usage(NULL, options, "x", true); 651 return -EINVAL; 652 } 653 ret = show_perf_probe_events(params.filter); 654 if (ret < 0) 655 pr_err_with_code(" Error: Failed to show event list.", ret); 656 return ret; 657 case 'F': 658 ret = show_available_funcs(params.target, params.nsi, 659 params.filter, params.uprobes); 660 if (ret < 0) 661 pr_err_with_code(" Error: Failed to show functions.", ret); 662 return ret; 663 #ifdef HAVE_DWARF_SUPPORT 664 case 'L': 665 ret = show_line_range(¶ms.line_range, params.target, 666 params.nsi, params.uprobes); 667 if (ret < 0) 668 pr_err_with_code(" Error: Failed to show lines.", ret); 669 return ret; 670 case 'V': 671 if (!params.filter) 672 params.filter = strfilter__new(DEFAULT_VAR_FILTER, 673 NULL); 674 675 ret = show_available_vars(params.events, params.nevents, 676 params.filter); 677 if (ret < 0) 678 pr_err_with_code(" Error: Failed to show vars.", ret); 679 return ret; 680 #endif 681 case 'd': 682 ret = perf_del_probe_events(params.filter); 683 if (ret < 0) { 684 pr_err_with_code(" Error: Failed to delete events.", ret); 685 return ret; 686 } 687 break; 688 case 'D': 689 case 'a': 690 691 /* Ensure the last given target is used */ 692 if (params.target && !params.target_used) { 693 pr_err(" Error: -x/-m must follow the probe definitions.\n"); 694 parse_options_usage(probe_usage, options, "m", true); 695 parse_options_usage(NULL, options, "x", true); 696 return -EINVAL; 697 } 698 699 ret = perf_add_probe_events(params.events, params.nevents); 700 if (ret < 0) { 701 702 /* 703 * When perf_add_probe_events() fails it calls 704 * cleanup_perf_probe_events(pevs, npevs), i.e. 705 * cleanup_perf_probe_events(params.events, params.nevents), which 706 * will call clear_perf_probe_event(), so set nevents to zero 707 * to avoid cleanup_params() to call clear_perf_probe_event() again 708 * on the same pevs. 709 */ 710 params.nevents = 0; 711 pr_err_with_code(" Error: Failed to add events.", ret); 712 return ret; 713 } 714 break; 715 default: 716 usage_with_options(probe_usage, options); 717 } 718 return 0; 719 } 720 721 int cmd_probe(int argc, const char **argv) 722 { 723 int ret; 724 725 ret = init_params(); 726 if (!ret) { 727 ret = __cmd_probe(argc, argv); 728 cleanup_params(); 729 } 730 731 return ret < 0 ? ret : 0; 732 } 733