/**************************************************************************** * apps/system/trace/trace.c * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. The * ASF licenses this file to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance with the * License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. * ****************************************************************************/ /**************************************************************************** * Included Files ****************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include "trace.h" /**************************************************************************** * Private Data ****************************************************************************/ /**************************************************************************** * Private Functions ****************************************************************************/ /**************************************************************************** * Name: notectl_enable ****************************************************************************/ static bool notectl_enable(int flag, int notectlfd) { struct note_filter_mode_s mode; int oldflag; ioctl(notectlfd, NOTECTL_GETMODE, (unsigned long)&mode); oldflag = (mode.flag & NOTE_FILTER_MODE_FLAG_ENABLE) != 0; if (flag == oldflag) { /* Already set */ return false; } if (flag) { mode.flag |= NOTE_FILTER_MODE_FLAG_ENABLE; } else { mode.flag &= ~NOTE_FILTER_MODE_FLAG_ENABLE; } ioctl(notectlfd, NOTECTL_SETMODE, (unsigned long)&mode); return true; } /**************************************************************************** * Name: trace_cmd_start ****************************************************************************/ static int trace_cmd_start(int index, int argc, FAR char **argv, int notectlfd) { FAR char *endptr; int duration = 0; bool cont = false; /* Usage: trace start [-c][] */ if (index < argc) { if (strcmp(argv[index], "-c") == 0) { cont = true; index++; } } if (index < argc) { duration = strtoul(argv[index], &endptr, 0); if (!duration || endptr == argv[index] || *endptr != '\0') { fprintf(stderr, "trace start: invalid argument '%s'\n", argv[index]); return ERROR; } index++; } /* Clear the trace buffer */ if (!cont) { trace_dump_clear(); } /* Start tracing */ notectl_enable(true, notectlfd); if (duration > 0) { /* If is given, stop tracing after specified seconds. */ sleep(duration); notectl_enable(false, notectlfd); } return index; } /**************************************************************************** * Name: trace_cmd_dump ****************************************************************************/ #ifdef CONFIG_DRIVER_NOTERAM static int trace_cmd_dump(int index, int argc, FAR char **argv, int notectlfd) { FAR FILE *out = stdout; int ret; bool changed = false; bool cont = false; /* Usage: trace dump [-c][] */ if (index < argc) { if (strcmp(argv[index], "-c") == 0) { cont = true; index++; } } /* If is '-' or not given, trace dump is displayed * to stdout. */ if (index < argc) { if (strcmp(argv[index], "-") != 0) { /* If is given, open the file stream for output. */ out = fopen(argv[index], "w"); if (out == NULL) { fprintf(stderr, "trace dump: cannot open '%s'\n", argv[index]); return ERROR; } } index++; } /* Stop the tracing before dump */ if (!cont) { changed = notectl_enable(false, notectlfd); } /* Dump the trace data */ ret = trace_dump(out); if (changed) { notectl_enable(true, notectlfd); } /* If needed, close the file stream for dump. */ if (out != stdout) { fclose(out); } if (ret < 0) { fprintf(stderr, "trace dump: dump failed\n"); return ERROR; } return index; } #endif /**************************************************************************** * Name: trace_cmd_cmd ****************************************************************************/ #ifdef CONFIG_SYSTEM_SYSTEM static int trace_cmd_cmd(int index, int argc, FAR char **argv, int notectlfd) { char command[CONFIG_NSH_LINELEN]; bool changed; bool cont = false; /* Usage: trace cmd [-c] [...] */ if (index < argc) { if (strcmp(argv[index], "-c") == 0) { cont = true; index++; } } if (index >= argc) { /* parameter is mandatory. */ fprintf(stderr, "trace cmd: no argument\n"); return ERROR; } memset(command, 0, sizeof(command)); while (index < argc) { strcat(command, argv[index]); strcat(command, " "); index++; } /* Clear the trace buffer */ if (!cont) { trace_dump_clear(); } /* Execute the command with tracing */ changed = notectl_enable(true, notectlfd); system(command); if (changed) { notectl_enable(false, notectlfd); } return index; } #endif /**************************************************************************** * Name: trace_cmd_mode ****************************************************************************/ static int trace_cmd_mode(int index, int argc, FAR char **argv, int notectlfd) { struct note_filter_mode_s mode; bool owmode; bool enable; bool modified = false; #ifdef CONFIG_SCHED_INSTRUMENTATION_SYSCALL struct note_filter_syscall_s filter_syscall; #endif #ifdef CONFIG_SCHED_INSTRUMENTATION_IRQHANDLER struct note_filter_irq_s filter_irq; #endif int i; int count; /* Usage: trace mode [{+|-}{o|s|a|i}...] */ /* Get current trace mode */ ioctl(notectlfd, NOTECTL_GETMODE, (unsigned long)&mode); owmode = trace_dump_get_overwrite(); /* Parse the mode setting parameters */ while (index < argc) { if (argv[index][0] != '-' && argv[index][0] != '+') { break; } enable = (argv[index][0] == '+'); switch (argv[index][1]) { #ifdef CONFIG_DRIVER_NOTERAM case 'o': /* Overwrite mode */ owmode = enable; break; #endif #ifdef CONFIG_SCHED_INSTRUMENTATION_SYSCALL case 's': /* Syscall trace */ if (enable) { mode.flag |= NOTE_FILTER_MODE_FLAG_SYSCALL; } else { mode.flag &= ~NOTE_FILTER_MODE_FLAG_SYSCALL; } break; case 'a': /* Record syscall arguments */ if (enable) { mode.flag |= NOTE_FILTER_MODE_FLAG_SYSCALL_ARGS; } else { mode.flag &= ~NOTE_FILTER_MODE_FLAG_SYSCALL_ARGS; } break; #endif #ifdef CONFIG_SCHED_INSTRUMENTATION_IRQHANDLER case 'i': /* IRQ trace */ if (enable) { mode.flag |= NOTE_FILTER_MODE_FLAG_IRQ; } else { mode.flag &= ~NOTE_FILTER_MODE_FLAG_IRQ; } break; #endif default: fprintf(stderr, "trace mode: invalid option '%s'\n", argv[index]); return ERROR; } index++; modified = true; } if (modified) { /* Update trace mode */ ioctl(notectlfd, NOTECTL_SETMODE, (unsigned long)&mode); trace_dump_set_overwrite(owmode); return index; } /* If no parameter, display current trace mode setting. */ printf("Task trace mode:\n"); printf(" Trace : %s\n", (mode.flag & NOTE_FILTER_MODE_FLAG_ENABLE) ? "enabled" : "disabled"); #ifdef CONFIG_DRIVER_NOTERAM printf(" Overwrite : %s\n", owmode ? "on (+o)" : "off (-o)"); #endif #ifdef CONFIG_SCHED_INSTRUMENTATION_SYSCALL ioctl(notectlfd, NOTECTL_GETSYSCALLFILTER, (unsigned long)&filter_syscall); for (count = i = 0; i < SYS_nsyscalls; i++) { if (NOTE_FILTER_SYSCALLMASK_ISSET(i, &filter_syscall)) { count++; } } printf(" Syscall trace : %s\n", mode.flag & NOTE_FILTER_MODE_FLAG_SYSCALL ? "on (+s)" : "off (-s)"); if (mode.flag & NOTE_FILTER_MODE_FLAG_SYSCALL) { printf(" Filtered Syscalls : %d\n", count); } printf(" Syscall trace with args : %s\n", mode.flag & NOTE_FILTER_MODE_FLAG_SYSCALL_ARGS ? "on (+a)" : "off (-a)"); #endif #ifdef CONFIG_SCHED_INSTRUMENTATION_IRQHANDLER ioctl(notectlfd, NOTECTL_GETIRQFILTER, (unsigned long)&filter_irq); for (count = i = 0; i < NR_IRQS; i++) { if (NOTE_FILTER_IRQMASK_ISSET(i, &filter_irq)) { count++; } } printf(" IRQ trace : %s\n", mode.flag & NOTE_FILTER_MODE_FLAG_IRQ ? "on (+i)" : "off (-i)"); if (mode.flag & NOTE_FILTER_MODE_FLAG_IRQ) { printf(" Filtered IRQs : %d\n", count); } #endif return index; } /**************************************************************************** * Name: trace_cmd_syscall ****************************************************************************/ #ifdef CONFIG_SCHED_INSTRUMENTATION_SYSCALL static int trace_cmd_syscall(int index, int argc, FAR char **argv, int notectlfd) { bool enable; bool modified = false; int syscallno; FAR struct note_filter_syscall_s filter_syscall; int n; int count; /* Usage: trace syscall [{+|-}...] */ /* Get current syscall filter setting */ ioctl(notectlfd, NOTECTL_GETSYSCALLFILTER, (unsigned long)&filter_syscall); /* Parse the setting parameters */ while (index < argc) { if (argv[index][0] != '-' && argv[index][0] != '+') { break; } enable = (argv[index][0] == '+'); /* Check whether the given pattern matches for each syscall names */ for (syscallno = 0; syscallno < SYS_nsyscalls; syscallno++) { if (!match(&argv[index][1], g_funcnames[syscallno])) { continue; } /* If matches, update the masked syscall number list */ if (enable) { NOTE_FILTER_SYSCALLMASK_SET(syscallno, &filter_syscall); } else { NOTE_FILTER_SYSCALLMASK_CLR(syscallno, &filter_syscall); } } index++; modified = true; } if (modified) { /* Update current syscall filter setting */ ioctl(notectlfd, NOTECTL_SETSYSCALLFILTER, (unsigned long)&filter_syscall); } else { /* If no parameter, display current setting. */ for (count = n = 0; n < SYS_nsyscalls; n++) { if (NOTE_FILTER_SYSCALLMASK_ISSET(n, &filter_syscall)) { count++; } } printf("Filtered Syscalls: %d\n", count); for (n = 0; n < SYS_nsyscalls; n++) { if (NOTE_FILTER_SYSCALLMASK_ISSET(n, &filter_syscall)) { printf(" %s\n", g_funcnames[n]); } } } return index; } #endif /**************************************************************************** * Name: trace_cmd_irq ****************************************************************************/ #ifdef CONFIG_SCHED_INSTRUMENTATION_IRQHANDLER static int trace_cmd_irq(int index, int argc, FAR char **argv, int notectlfd) { bool enable; bool modified = false; int irqno; FAR char *endptr; struct note_filter_irq_s filter_irq; int n; int count; /* Usage: trace irq [{+|-}...] */ /* Get current irq filter setting */ ioctl(notectlfd, NOTECTL_GETIRQFILTER, (unsigned long)&filter_irq); /* Parse the setting parameters */ while (index < argc) { if (argv[index][0] != '-' && argv[index][0] != '+') { break; } enable = (argv[index][0] == '+'); if (argv[index][1] == '*') { /* Mask or unmask all IRQs */ if (enable) { for (n = 0; n < NR_IRQS; n++) { NOTE_FILTER_IRQMASK_SET(n, &filter_irq); } } else { NOTE_FILTER_IRQMASK_ZERO(&filter_irq); } } else { /* Get IRQ number */ irqno = strtoul(&argv[index][1], &endptr, 0); if (endptr == &argv[index][1] || *endptr != '\0' || irqno >= NR_IRQS) { fprintf(stderr, "trace irq: invalid argument '%s'\n", argv[index]); return ERROR; } /* Update the masked IRQ number list */ if (enable) { NOTE_FILTER_IRQMASK_SET(irqno, &filter_irq); } else { NOTE_FILTER_IRQMASK_CLR(irqno, &filter_irq); } } index++; modified = true; } if (modified) { /* Update current irq filter setting */ ioctl(notectlfd, NOTECTL_SETIRQFILTER, (unsigned long)&filter_irq); } else { /* If no parameter, display current setting. */ for (count = n = 0; n < NR_IRQS; n++) { if (NOTE_FILTER_IRQMASK_ISSET(n, &filter_irq)) { count++; } } printf("Filtered IRQs: %d\n", count); for (n = 0; n < NR_IRQS; n++) { if (NOTE_FILTER_IRQMASK_ISSET(n, &filter_irq)) { printf(" %d\n", n); } } } return index; } #endif /**************************************************************************** * Name: show_usage ****************************************************************************/ static void show_usage(void) { fprintf(stderr, "\nUsage: trace ...\n" "Subcommand:\n" " start [-c][] :" " Start task tracing\n" " stop :" " Stop task tracing\n" #ifdef CONFIG_SYSTEM_SYSTEM " cmd [-c] [...] :" " Get the trace while running \n" #endif #ifdef CONFIG_DRIVER_NOTERAM " dump [-c][] :" " Output the trace result\n" #endif " mode [{+|-}{o|s|a|i}...] :" " Set task trace options\n" #ifdef CONFIG_SCHED_INSTRUMENTATION_SYSCALL " syscall [{+|-}...] :" " Configure syscall trace filter\n" #endif #ifdef CONFIG_SCHED_INSTRUMENTATION_IRQHANDLER " irq [{+|-}...] :" " Configure IRQ trace filter\n" #endif ); } /**************************************************************************** * Public Functions ****************************************************************************/ int main(int argc, FAR char *argv[]) { int notectlfd; int exitcode = EXIT_FAILURE; int i; /* Open note control device */ notectlfd = open("/dev/notectl", 0); if (notectlfd < 0) { fprintf(stderr, "trace: cannot open /dev/notectl\n"); goto errout; } if (argc == 1) { /* No arguments - show current mode */ trace_cmd_mode(0, 0, NULL, notectlfd); goto exit_with_close; } /* Parse command line arguments */ i = 1; while (i < argc) { if (strcmp(argv[i], "start") == 0) { i = trace_cmd_start(i + 1, argc, argv, notectlfd); } else if (strcmp(argv[i], "stop") == 0) { i++; notectl_enable(false, notectlfd); } #ifdef CONFIG_DRIVER_NOTERAM else if (strcmp(argv[i], "dump") == 0) { i = trace_cmd_dump(i + 1, argc, argv, notectlfd); } #endif #ifdef CONFIG_SYSTEM_SYSTEM else if (strcmp(argv[i], "cmd") == 0) { i = trace_cmd_cmd(i + 1, argc, argv, notectlfd); } #endif else if (strcmp(argv[i], "mode") == 0) { i = trace_cmd_mode(i + 1, argc, argv, notectlfd); } #ifdef CONFIG_SCHED_INSTRUMENTATION_SYSCALL else if (strcmp(argv[i], "syscall") == 0) { i = trace_cmd_syscall(i + 1, argc, argv, notectlfd); } #endif #ifdef CONFIG_SCHED_INSTRUMENTATION_IRQHANDLER else if (strcmp(argv[i], "irq") == 0) { i = trace_cmd_irq(i + 1, argc, argv, notectlfd); } #endif else { show_usage(); goto errout_with_close; } if (i < 0) { goto errout_with_close; } } exit_with_close: exitcode = EXIT_SUCCESS; /* Close note */ errout_with_close: close(notectlfd); errout: return exitcode; }