1 // SPDX-License-Identifier: GPL-2.0+
3 * Copyright (c) 2013 Google, Inc
6 /* Decode and dump U-Boot profiling information */
17 #include <sys/param.h>
18 #include <sys/types.h>
23 #define MAX_LINE_LEN 500
26 FUNCF_TRACE = 1 << 0, /* Include this function in trace */
30 * struct func_info - information recorded for each function
32 * @offset: Function offset in the image, measured from the text_base
33 * @name: Function name
34 * @code_size: Total code size of the function
35 * @flags: Either 0 or FUNCF_TRACE
36 * @objsection: the section this function is in
41 unsigned long code_size;
43 struct objsection_info *objsection;
47 * enum trace_line_type - whether to include or exclude a function
49 * @TRACE_LINE_INCLUDE: Include the function
50 * @TRACE_LINE_EXCLUDE: Exclude the function
52 enum trace_line_type {
58 * struct trace_configline_info - information about a config-file line
62 * @name: identifier name / wildcard
63 * @regex: Regex to use if name starts with '/'
65 struct trace_configline_info {
66 struct trace_configline_info *next;
67 enum trace_line_type type;
72 /* The contents of the trace config file */
73 struct trace_configline_info *trace_config_head;
75 /* list of all functions in System.map file, sorted by offset in the image */
76 struct func_info *func_list;
78 int func_count; /* number of functions */
79 struct trace_call *call_list; /* list of all calls in the input trace file */
80 int call_count; /* number of calls */
81 int verbose; /* Verbosity level 0=none, 1=warn, 2=notice, 3=info, 4=debug */
82 ulong text_offset; /* text address of first function */
84 /* debugging helpers */
85 static void outf(int level, const char *fmt, ...)
86 __attribute__ ((format (__printf__, 2, 3)));
87 #define error(fmt, b...) outf(0, fmt, ##b)
88 #define warn(fmt, b...) outf(1, fmt, ##b)
89 #define notice(fmt, b...) outf(2, fmt, ##b)
90 #define info(fmt, b...) outf(3, fmt, ##b)
91 #define debug(fmt, b...) outf(4, fmt, ##b)
93 static void outf(int level, const char *fmt, ...)
95 if (verbose >= level) {
99 vfprintf(stderr, fmt, args);
104 static void usage(void)
107 "Usage: proftool [-cmtv] <cmd> <profdata>\n"
110 " dump-ftrace\t\tDump out records in ftrace format for use by trace-cmd\n"
113 " -c <cfg>\tSpecify config file\n"
114 " -m <map>\tSpecify Systen.map file\n"
115 " -o <fname>\tSpecify output file\n"
116 " -t <fname>\tSpecify trace data file (from U-Boot 'trace calls')\n"
117 " -v <0-4>\tSpecify verbosity\n");
122 * h_cmp_offset - bsearch() function to compare two functions bny their offset
124 * @v1: Pointer to first function (struct func_info)
125 * @v2: Pointer to second function (struct func_info)
126 * Returns: < 0 if v1 offset < v2 offset, 0 if equal, > 0 otherwise
128 static int h_cmp_offset(const void *v1, const void *v2)
130 const struct func_info *f1 = v1, *f2 = v2;
132 return (f1->offset / FUNC_SITE_SIZE) - (f2->offset / FUNC_SITE_SIZE);
136 * read_system_map() - read the System.map file to create a list of functions
138 * This also reads the text_offset value, since we assume that the first text
139 * symbol is at that address
142 * Returns: 0 if OK, non-zero on error
144 static int read_system_map(FILE *fin)
146 unsigned long offset, start = 0;
147 struct func_info *func;
148 char buff[MAX_LINE_LEN];
150 char symname[MAX_LINE_LEN + 1];
154 for (linenum = 1, alloced = func_count = 0;; linenum++) {
157 if (fgets(buff, sizeof(buff), fin))
158 fields = sscanf(buff, "%lx %c %100s\n", &offset,
162 } else if (feof(fin)) {
164 } else if (fields < 2) {
165 error("Map file line %d: invalid format\n", linenum);
169 /* Must be a text symbol */
170 symtype = tolower(symtype);
171 if (symtype != 't' && symtype != 'w')
174 if (func_count == alloced) {
176 func_list = realloc(func_list,
177 sizeof(struct func_info) * alloced);
183 func = &func_list[func_count++];
184 memset(func, '\0', sizeof(*func));
185 func->offset = offset - start;
186 func->name = strdup(symname);
187 func->flags = FUNCF_TRACE; /* trace by default */
189 /* Update previous function's code size */
191 func[-1].code_size = func->offset - func[-1].offset;
193 notice("%d functions found in map file\n", func_count);
199 static int read_data(FILE *fin, void *buff, int size)
203 err = fread(buff, 1, size, fin);
207 error("Cannot read trace file at pos %lx\n", ftell(fin));
214 * find_func_by_offset() - Look up a function by its offset
216 * @offset: Offset to search for, from text_base
217 * Returns: function, if found, else NULL
219 * This does a fast search for a function given its offset from text_base
222 static struct func_info *find_func_by_offset(uint offset)
224 struct func_info key, *found;
227 found = bsearch(&key, func_list, func_count, sizeof(struct func_info),
234 * find_caller_by_offset() - finds the function which contains the given offset
236 * @offset: Offset to search for, from text_base
237 * Returns: function, if found, else NULL
239 * If the offset falls between two functions, then it is assumed to belong to
240 * the first function (with the lowest offset). This is a way of figuring out
241 * which function owns code at a particular offset
243 static struct func_info *find_caller_by_offset(uint offset)
245 int low; /* least function that could be a match */
246 int high; /* greated function that could be a match */
247 struct func_info key;
250 high = func_count - 1;
252 while (high > low + 1) {
253 int mid = (low + high) / 2;
256 result = h_cmp_offset(&key, &func_list[mid]);
262 return &func_list[mid];
265 return low >= 0 ? &func_list[low] : NULL;
269 * read_calls() - Read the list of calls from the trace data
271 * The calls are stored consecutively in the trace output produced by U-Boot
273 * @fin: File to read from
274 * @count: Number of calls to read
275 * Returns: 0 if OK, -1 on error
277 static int read_calls(FILE *fin, size_t count)
279 struct trace_call *call_data;
282 notice("call count: %zu\n", count);
283 call_list = (struct trace_call *)calloc(count, sizeof(*call_data));
285 error("Cannot allocate call_list\n");
290 call_data = call_list;
291 for (i = 0; i < count; i++, call_data++) {
292 if (read_data(fin, call_data, sizeof(*call_data)))
299 * read_trace() - Read the U-Boot trace file
301 * Read in the calls from the trace file. The function list is ignored at
305 * Returns 0 if OK, non-zero on error
307 static int read_trace(FILE *fin)
309 struct trace_output_hdr hdr;
314 err = read_data(fin, &hdr, sizeof(hdr));
321 case TRACE_CHUNK_FUNCS:
322 /* Ignored at present */
325 case TRACE_CHUNK_CALLS:
326 if (read_calls(fin, hdr.rec_count))
335 * read_map_file() - Read the System.map file
337 * This reads the file into the func_list array
339 * @fname: Filename to read
340 * Returns 0 if OK, non-zero on error
342 static int read_map_file(const char *fname)
347 fmap = fopen(fname, "r");
349 error("Cannot open map file '%s'\n", fname);
353 err = read_system_map(fmap);
360 * read_trace_file() - Open and read the U-Boot trace file
362 * Read in the calls from the trace file. The function list is ignored at
366 * Returns 0 if OK, non-zero on error
368 static int read_trace_file(const char *fname)
373 fprof = fopen(fname, "rb");
375 error("Cannot open trace data file '%s'\n",
379 err = read_trace(fprof);
387 static int regex_report_error(regex_t *regex, int err, const char *op,
392 regerror(err, regex, buf, sizeof(buf));
393 error("Regex error '%s' in %s '%s'\n", buf, op, name);
397 static void check_trace_config_line(struct trace_configline_info *item)
399 struct func_info *func, *end;
402 debug("Checking trace config line '%s'\n", item->name);
403 for (func = func_list, end = func + func_count; func < end; func++) {
404 err = regexec(&item->regex, func->name, 0, NULL, 0);
405 debug(" - regex '%s', string '%s': %d\n", item->name,
407 if (err == REG_NOMATCH)
411 regex_report_error(&item->regex, err, "match",
416 /* It matches, so perform the action */
417 switch (item->type) {
418 case TRACE_LINE_INCLUDE:
419 info(" include %s at %lx\n", func->name,
420 text_offset + func->offset);
421 func->flags |= FUNCF_TRACE;
424 case TRACE_LINE_EXCLUDE:
425 info(" exclude %s at %lx\n", func->name,
426 text_offset + func->offset);
427 func->flags &= ~FUNCF_TRACE;
433 /** check_trace_config() - Check trace-config file, reporting any problems */
434 static void check_trace_config(void)
436 struct trace_configline_info *line;
438 for (line = trace_config_head; line; line = line->next)
439 check_trace_config_line(line);
443 * Check the functions to see if they each have an objsection. If not, then
444 * the linker must have eliminated them.
446 static void check_functions(void)
448 struct func_info *func, *end;
449 unsigned long removed_code_size = 0;
452 /* Look for missing functions */
453 for (func = func_list, end = func + func_count; func < end; func++) {
454 if (!func->objsection) {
455 removed_code_size += func->code_size;
460 /* Figure out what functions we want to trace */
461 check_trace_config();
463 warn("%d functions removed by linker, %ld code size\n",
464 not_found, removed_code_size);
468 * read_trace_config() - read the trace-config file
470 * This file consists of lines like:
472 * include-func <regex>
473 * exclude-func <regex>
475 * where <regex> is a regular expression matched against function names. It
476 * allows some functions to be dropped from the trace when producing ftrace
479 * @fin: File to process
480 * Returns: 0 if OK, -1 on error
482 static int read_trace_config(FILE *fin)
486 struct trace_configline_info **tailp = &trace_config_head;
488 while (fgets(buff, sizeof(buff), fin)) {
489 int len = strlen(buff);
490 struct trace_configline_info *line;
496 if (len && buff[len - 1] == '\n')
497 buff[len - 1] = '\0';
499 /* skip blank lines and comments */
500 for (s = buff; *s == ' ' || *s == '\t'; s++)
502 if (!*s || *s == '#')
505 line = (struct trace_configline_info *)calloc(1, sizeof(*line));
507 error("Cannot allocate config line\n");
511 tok = strtok_r(s, " \t", &saveptr);
513 error("Invalid trace config data on line %d\n",
517 if (0 == strcmp(tok, "include-func")) {
518 line->type = TRACE_LINE_INCLUDE;
519 } else if (0 == strcmp(tok, "exclude-func")) {
520 line->type = TRACE_LINE_EXCLUDE;
522 error("Unknown command in trace config data line %d\n",
527 tok = strtok_r(NULL, " \t", &saveptr);
529 error("Missing pattern in trace config data line %d\n",
534 err = regcomp(&line->regex, tok, REG_NOSUB);
536 int r = regex_report_error(&line->regex, err,
542 /* link this new one to the end of the list */
543 line->name = strdup(tok);
550 error("Cannot read from trace config file at position %ld\n",
557 static int read_trace_config_file(const char *fname)
562 fin = fopen(fname, "r");
564 error("Cannot open trace_config file '%s'\n", fname);
567 err = read_trace_config(fin);
572 static void out_func(ulong func_offset, int is_caller, const char *suffix)
574 struct func_info *func;
576 func = (is_caller ? find_caller_by_offset : find_func_by_offset)
580 printf("%s%s", func->name, suffix);
582 printf("%lx%s", func_offset, suffix);
588 * # TASK-PID CPU# TIMESTAMP FUNCTION
590 * # bash-4251 [01] 10152.583854: path_put <-path_walk
591 * # bash-4251 [01] 10152.583855: dput <-path_put
592 * # bash-4251 [01] 10152.583855: _atomic_dec_and_lock <-dput
594 static int make_ftrace(void)
596 struct trace_call *call;
597 int missing_count = 0, skip_count = 0;
600 printf("# tracer: function\n"
602 "# entries-in-buffer/entries-written: 140080/250280 #P:4\n"
604 "# _-----=> irqs-off\n"
605 "# / _----=> need-resched\n"
606 "# | / _---=> hardirq/softirq\n"
607 "# || / _--=> preempt-depth\n"
609 "# TASK-PID CPU# |||| TIMESTAMP FUNCTION\n"
610 "# | | | |||| | |\n");
611 for (i = 0, call = call_list; i < call_count; i++, call++) {
612 struct func_info *func = find_func_by_offset(call->func);
613 ulong time = call->flags & FUNCF_TIMESTAMP_MASK;
615 if (TRACE_CALL_TYPE(call) != FUNCF_ENTRY &&
616 TRACE_CALL_TYPE(call) != FUNCF_EXIT)
619 warn("Cannot find function at %lx\n",
620 text_offset + call->func);
625 if (!(func->flags & FUNCF_TRACE)) {
626 debug("Funcion '%s' is excluded from trace\n",
632 printf("%16s-%-5d [000] .... %lu.%06lu: ", "uboot", 1,
633 time / 1000000, time % 1000000);
635 out_func(call->func, 0, " <- ");
636 out_func(call->caller, 1, "\n");
638 info("ftrace: %d functions not found, %d excluded\n", missing_count,
645 * prof_tool() - Performs requested action
647 * @argc: Number of arguments (used to obtain the command
648 * @argv: List of arguments
649 * @trace_fname: Filename of input file (trace data from U-Boot)
650 * @map_fname: Filename of map file (System.map from U-Boot)
651 * @trace_config_fname: Trace-configuration file, or NULL if none
652 * @out_fname: Output filename
654 static int prof_tool(int argc, char *const argv[],
655 const char *trace_fname, const char *map_fname,
656 const char *trace_config_fname, const char *out_fname)
660 if (read_map_file(map_fname))
662 if (trace_fname && read_trace_file(trace_fname))
664 if (trace_config_fname && read_trace_config_file(trace_config_fname))
669 for (; argc; argc--, argv++) {
670 const char *cmd = *argv;
672 if (0 == strcmp(cmd, "dump-ftrace"))
675 warn("Unknown command '%s'\n", cmd);
681 int main(int argc, char *argv[])
683 const char *map_fname = "System.map";
684 const char *trace_fname = NULL;
685 const char *config_fname = NULL;
686 const char *out_fname = NULL;
690 while ((opt = getopt(argc, argv, "c:m:o:t:v:")) != -1) {
693 config_fname = optarg;
702 trace_fname = optarg;
705 verbose = atoi(optarg);
711 argc -= optind; argv += optind;
715 if (!out_fname || !map_fname || !trace_fname) {
717 "Must provide trace data, System.map file and output file\n");
721 debug("Debug enabled\n");
722 return prof_tool(argc, argv, trace_fname, map_fname, config_fname,