xref: /freebsd/contrib/tcpdump/instrument-functions.c (revision b64c5a0ace59af62eff52bfe110a521dc73c937b)
1 /*
2  * Redistribution and use in source and binary forms, with or without
3  * modification, are permitted provided that: (1) source code
4  * distributions retain the above copyright notice and this paragraph
5  * in its entirety, and (2) distributions including binary code include
6  * the above copyright notice and this paragraph in its entirety in
7  * the documentation or other materials provided with the distribution.
8  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND
9  * WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, WITHOUT
10  * LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
11  * FOR A PARTICULAR PURPOSE.
12  */
13 
14 #include <stdio.h>
15 #include <string.h>
16 #include <stdlib.h>
17 #include <unistd.h>
18 #include <bfd.h>
19 
20 /*
21  * Generate instrumentation calls for entry and exit to functions.
22  * Just after function entry and just before function exit, the
23  * following profiling functions are called with the address of the
24  * current function and its call site (currently not use).
25  *
26  * The attribute 'no_instrument_function' causes this instrumentation is
27  * not done.
28  *
29  * These profiling functions call print_debug(). This function prints the
30  * current function name with indentation and call level.
31  * If entering in a function it prints also the calling function name with
32  * file name and line number.
33  *
34  * If the environment variable INSTRUMENT is
35  * unset or set to an empty string, print nothing, like with no instrumentation
36  * set to "all" or "a", print all the functions names
37  * set to "global" or "g", print only the global functions names
38  */
39 
40 #define ND_NO_INSTRUMENT __attribute__((no_instrument_function))
41 
42 /* Store the function call level, used also in pretty_print_packet() */
43 extern int profile_func_level;
44 int profile_func_level = -1;
45 
46 typedef enum {
47 	ENTER,
48 	EXIT
49 } action_type;
50 
51 void __cyg_profile_func_enter(void *this_fn, void *call_site) ND_NO_INSTRUMENT;
52 
53 void __cyg_profile_func_exit(void *this_fn, void *call_site) ND_NO_INSTRUMENT;
54 
55 static void print_debug(void *this_fn, void *call_site, action_type action)
56 	ND_NO_INSTRUMENT;
57 
58 void
59 __cyg_profile_func_enter(void *this_fn, void *call_site)
60 {
61 	print_debug(this_fn, call_site, ENTER);
62 }
63 
64 void
65 __cyg_profile_func_exit(void *this_fn, void *call_site)
66 {
67 	print_debug(this_fn, call_site, EXIT);
68 }
69 
70 static void print_debug(void *this_fn, void *call_site, action_type action)
71 {
72 	static bfd* abfd;
73 	static asymbol **symtab;
74 	static long symcount;
75 	static asection *text;
76 	static bfd_vma vma;
77 	static int instrument_set;
78 	static int instrument_off;
79 	static int instrument_global;
80 
81 	if (!instrument_set) {
82 		static char *instrument_type;
83 
84 		/* Get the configuration environment variable INSTRUMENT value if any */
85 		instrument_type = getenv("INSTRUMENT");
86 		/* unset or set to an empty string ? */
87 		if (instrument_type == NULL ||
88 			!strncmp(instrument_type, "", sizeof(""))) {
89 			instrument_off = 1;
90 		} else {
91 			/* set to "global" or "g" ? */
92 			if (!strncmp(instrument_type, "global", sizeof("global")) ||
93 				!strncmp(instrument_type, "g", sizeof("g")))
94 				instrument_global = 1;
95 			else if (strncmp(instrument_type, "all", sizeof("all")) &&
96 					 strncmp(instrument_type, "a", sizeof("a"))) {
97 				fprintf(stderr, "INSTRUMENT can be only \"\", \"all\", \"a\", "
98 						"\"global\" or \"g\".\n");
99 				exit(1);
100 			}
101 		}
102 		instrument_set = 1;
103 	}
104 
105 	if (instrument_off)
106 			return;
107 
108 	/* If no errors, this block should be executed one time */
109 	if (!abfd) {
110 		char pgm_name[1024];
111 		long symsize;
112 
113 		ssize_t ret = readlink("/proc/self/exe", pgm_name, sizeof(pgm_name));
114 		if (ret == -1) {
115 			perror("failed to find executable");
116 			return;
117 		}
118 		if (ret == sizeof(pgm_name)) {
119 			/* no space for the '\0' */
120 			printf("truncation may have occurred\n");
121 			return;
122 		}
123 		pgm_name[ret] = '\0';
124 
125 		bfd_init();
126 
127 		abfd = bfd_openr(pgm_name, NULL);
128 		if (!abfd) {
129 			bfd_perror("bfd_openr");
130 			return;
131 		}
132 
133 		if (!bfd_check_format(abfd, bfd_object)) {
134 			bfd_perror("bfd_check_format");
135 			return;
136 		}
137 
138 		if((symsize = bfd_get_symtab_upper_bound(abfd)) == -1) {
139 			bfd_perror("bfd_get_symtab_upper_bound");
140 			return;
141 		}
142 
143 		symtab = (asymbol **)malloc((size_t)symsize);
144 		symcount = bfd_canonicalize_symtab(abfd, symtab);
145 		if (symcount < 0) {
146 			free(symtab);
147 			bfd_perror("bfd_canonicalize_symtab");
148 			return;
149 		}
150 
151 		if ((text = bfd_get_section_by_name(abfd, ".text")) == NULL) {
152 			bfd_perror("bfd_get_section_by_name");
153 			return;
154 		}
155 		vma = text->vma;
156 	}
157 
158 	if (instrument_global) {
159 		symbol_info syminfo;
160 		int found;
161 		long i;
162 
163 		i = 0;
164 		found = 0;
165 		while (i < symcount && !found) {
166 			bfd_get_symbol_info(abfd, symtab[i], &syminfo);
167 			if ((void *)syminfo.value == this_fn) {
168 				found = 1;
169 			}
170 			i++;
171 		}
172 		/* type == 'T' for a global function */
173 		if (found == 1 && syminfo.type != 'T')
174 			return;
175 	}
176 
177 	/* Current function */
178 	if ((bfd_vma)this_fn < vma) {
179 		printf("[ERROR address this_fn]");
180 	} else {
181 		const char *file;
182 		const char *func;
183 		unsigned int line;
184 
185 		if (!bfd_find_nearest_line(abfd, text, symtab, (bfd_vma)this_fn - vma,
186 								   &file, &func, &line)) {
187 			printf("[ERROR bfd_find_nearest_line this_fn]");
188 		} else {
189 			int i;
190 
191 			if (action == ENTER)
192 				profile_func_level += 1;
193 			/* Indentation */
194 			for (i = 0 ; i < profile_func_level ; i++)
195 				putchar(' ');
196 			if (action == ENTER)
197 				printf("[>> ");
198 			else
199 				printf("[<< ");
200 			/* Function name */
201 			if (func == NULL || *func == '\0')
202 				printf("???");
203 			else
204 				printf("%s", func);
205 			printf(" (%d)", profile_func_level);
206 			/* Print the "from" part except for the main function) */
207 			if (action == ENTER && func != NULL &&
208 				strncmp(func, "main", sizeof("main"))) {
209 				/* Calling function */
210 				if ((bfd_vma)call_site < vma) {
211 					printf("[ERROR address call_site]");
212 				} else {
213 					if (!bfd_find_nearest_line(abfd, text, symtab,
214 											   (bfd_vma)call_site - vma, &file,
215 											   &func, &line)) {
216 						printf("[ERROR bfd_find_nearest_line call_site]");
217 					} else {
218 						printf(" from ");
219 						/* Function name */
220 						if (func == NULL || *func == '\0')
221 							printf("???");
222 						else
223 							printf("%s", func);
224 						/* File name */
225 						if (file == NULL || *file == '\0')
226 							printf(" ??:");
227 						else {
228 							char *slashp = strrchr(file, '/');
229 							if (slashp != NULL)
230 								file = slashp + 1;
231 							printf(" %s:", file);
232 						}
233 						/* Line number */
234 						if (line == 0)
235 							printf("?");
236 						else
237 							printf("%u", line);
238 						printf("]");
239 					}
240 				}
241 			}
242 			putchar('\n');
243 			if (action == EXIT)
244 				profile_func_level -= 1;
245 		}
246 	}
247 	fflush(stdout);
248 }
249 
250 /* vi: set tabstop=4 softtabstop=0 shiftwidth=4 smarttab autoindent : */
251