| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Copyright (C) 2009, 2010 Red Hat Inc, Steven Rostedt <[email protected]> |
| * |
| */ |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <limits.h> |
| #include <getopt.h> |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| #include <sys/wait.h> |
| #include <fcntl.h> |
| #include <unistd.h> |
| #include <errno.h> |
| |
| #include "tracefs.h" |
| #include "trace-local.h" |
| |
| #define PROC_FILE "/proc/sys/kernel/stack_tracer_enabled" |
| |
| enum stack_type { |
| STACK_START, |
| STACK_STOP, |
| STACK_RESET, |
| STACK_REPORT |
| }; |
| |
| static void test_available(void) |
| { |
| struct stat buf; |
| int fd; |
| |
| fd = stat(PROC_FILE, &buf); |
| if (fd < 0) |
| die("stack tracer not configured on running kernel"); |
| } |
| |
| /* NOTE: this implementation only accepts new_status in the range [0..9]. */ |
| static void change_stack_tracer_status(unsigned new_status) |
| { |
| char buf[1]; |
| int status; |
| int ret; |
| int fd; |
| int n; |
| |
| if (new_status > 9) { |
| warning("invalid status %d\n", new_status); |
| return; |
| } |
| |
| ret = tracecmd_stack_tracer_status(&status); |
| if (ret < 0) |
| die("error reading %s", PROC_FILE); |
| |
| if (ret > 0 && status == new_status) |
| return; /* nothing to do */ |
| |
| fd = open(PROC_FILE, O_WRONLY); |
| if (fd < 0) |
| die("writing %s", PROC_FILE); |
| |
| buf[0] = new_status + '0'; |
| |
| n = write(fd, buf, 1); |
| if (n < 0) |
| die("writing into %s", PROC_FILE); |
| close(fd); |
| } |
| |
| static void start_trace(void) |
| { |
| change_stack_tracer_status(1); |
| } |
| |
| static void stop_trace(void) |
| { |
| change_stack_tracer_status(0); |
| } |
| |
| static void reset_trace(void) |
| { |
| char *path; |
| char buf[1]; |
| int fd; |
| int n; |
| |
| path = tracefs_get_tracing_file("stack_max_size"); |
| fd = open(path, O_WRONLY); |
| if (fd < 0) |
| die("writing %s", path); |
| |
| buf[0] = '0'; |
| n = write(fd, buf, 1); |
| if (n < 0) |
| die("writing into %s", path); |
| tracefs_put_tracing_file(path); |
| close(fd); |
| } |
| |
| static void read_trace(void) |
| { |
| char *buf = NULL; |
| int status; |
| char *path; |
| FILE *fp; |
| size_t n; |
| int r; |
| |
| if (tracecmd_stack_tracer_status(&status) <= 0) |
| die("Invalid stack tracer state"); |
| |
| if (status > 0) |
| printf("(stack tracer running)\n"); |
| else |
| printf("(stack tracer not running)\n"); |
| |
| path = tracefs_get_tracing_file("stack_trace"); |
| fp = fopen(path, "r"); |
| if (!fp) |
| die("reading to '%s'", path); |
| tracefs_put_tracing_file(path); |
| |
| while ((r = getline(&buf, &n, fp)) >= 0) { |
| /* |
| * Skip any line that starts with a '#'. |
| * Those talk about how to enable stack tracing |
| * within the debugfs system. We don't care about that. |
| */ |
| if (buf[0] != '#') |
| printf("%s", buf); |
| |
| free(buf); |
| buf = NULL; |
| } |
| |
| fclose(fp); |
| } |
| |
| enum { |
| OPT_verbose = 252, |
| OPT_reset = 253, |
| OPT_stop = 254, |
| OPT_start = 255, |
| }; |
| |
| void trace_stack (int argc, char **argv) |
| { |
| enum stack_type trace_type = STACK_REPORT; |
| int c; |
| |
| if (argc < 2) |
| usage(argv); |
| |
| if (strcmp(argv[1], "stack") != 0) |
| usage(argv); |
| |
| for (;;) { |
| int option_index = 0; |
| static struct option long_options[] = { |
| {"start", no_argument, NULL, OPT_start}, |
| {"stop", no_argument, NULL, OPT_stop}, |
| {"reset", no_argument, NULL, OPT_reset}, |
| {"help", no_argument, NULL, '?'}, |
| {"verbose", optional_argument, NULL, OPT_verbose}, |
| {NULL, 0, NULL, 0} |
| }; |
| |
| c = getopt_long (argc-1, argv+1, "+h?", |
| long_options, &option_index); |
| if (c == -1) |
| break; |
| |
| switch (c) { |
| case 'h': |
| usage(argv); |
| break; |
| case OPT_start: |
| trace_type = STACK_START; |
| break; |
| case OPT_stop: |
| trace_type = STACK_STOP; |
| break; |
| case OPT_reset: |
| trace_type = STACK_RESET; |
| break; |
| case OPT_verbose: |
| if (trace_set_verbose(optarg) < 0) |
| die("invalid verbose level %s", optarg); |
| break; |
| default: |
| usage(argv); |
| } |
| } |
| |
| test_available(); |
| |
| switch (trace_type) { |
| case STACK_START: |
| start_trace(); |
| break; |
| case STACK_STOP: |
| stop_trace(); |
| break; |
| case STACK_RESET: |
| reset_trace(); |
| break; |
| default: |
| read_trace(); |
| break; |
| } |
| |
| return; |
| } |