xref: /haiku/src/bin/debug/strace/strace.cpp (revision 03187b607b2b5eec7ee059f1ead09bdba14991fb)
1 /*
2  * Copyright 2005-2008, Ingo Weinhold, bonefish@users.sf.net.
3  * Distributed under the terms of the MIT License.
4  */
5 
6 #include <ctype.h>
7 #include <stdio.h>
8 #include <stdlib.h>
9 #include <string.h>
10 #include <errno.h>
11 #include <signal.h>
12 
13 #include <map>
14 #include <string>
15 #include <vector>
16 
17 #include <debugger.h>
18 #include <image.h>
19 #include <syscalls.h>
20 
21 #include "debug_utils.h"
22 
23 #include "Context.h"
24 #include "MemoryReader.h"
25 #include "Syscall.h"
26 #include "TypeHandler.h"
27 
28 using std::map;
29 using std::string;
30 using std::vector;
31 
32 extern void get_syscalls0(vector<Syscall*> &syscalls);
33 extern void get_syscalls1(vector<Syscall*> &syscalls);
34 extern void get_syscalls2(vector<Syscall*> &syscalls);
35 extern void get_syscalls3(vector<Syscall*> &syscalls);
36 extern void get_syscalls4(vector<Syscall*> &syscalls);
37 extern void get_syscalls5(vector<Syscall*> &syscalls);
38 extern void get_syscalls6(vector<Syscall*> &syscalls);
39 extern void get_syscalls7(vector<Syscall*> &syscalls);
40 extern void get_syscalls8(vector<Syscall*> &syscalls);
41 extern void get_syscalls9(vector<Syscall*> &syscalls);
42 extern void get_syscalls10(vector<Syscall*> &syscalls);
43 extern void get_syscalls11(vector<Syscall*> &syscalls);
44 extern void get_syscalls12(vector<Syscall*> &syscalls);
45 extern void get_syscalls13(vector<Syscall*> &syscalls);
46 extern void get_syscalls14(vector<Syscall*> &syscalls);
47 extern void get_syscalls15(vector<Syscall*> &syscalls);
48 extern void get_syscalls16(vector<Syscall*> &syscalls);
49 extern void get_syscalls17(vector<Syscall*> &syscalls);
50 extern void get_syscalls18(vector<Syscall*> &syscalls);
51 extern void get_syscalls19(vector<Syscall*> &syscalls);
52 
53 extern const char *__progname;
54 static const char *kCommandName = __progname;
55 
56 // usage
57 static const char *kUsage =
58 "Usage: %s [ <options> ] [ <thread or team ID> | <executable with args> ]\n"
59 "\n"
60 "Traces the the syscalls of a thread or a team. If an executable with\n"
61 "arguments is supplied, it is loaded and it's main thread traced.\n"
62 "\n"
63 "Options:\n"
64 "  -a             - Don't print syscall arguments.\n"
65 "  -c             - Don't colorize output.\n"
66 "  -d <name>      - Filter the types that have their contents retrieved.\n"
67 "                   <name> is one of: strings, enums, simple, complex or\n"
68 "                                     pointer_values\n"
69 "  -f             - Fast mode. Syscall arguments contents aren't retrieved.\n"
70 "  -h, --help     - Print this text.\n"
71 "  -i             - Print integers in decimal format instead of hexadecimal.\n"
72 "  -l             - Also trace loading the executable. Only considered when\n"
73 "                   an executable is provided.\n"
74 "  -r             - Don't print syscall return values.\n"
75 "  -s             - Also trace all threads spawned by the supplied thread,\n"
76 "                   respectively the loaded executable's main thread.\n"
77 "  -T             - Trace all threads of the supplied or loaded executable's\n"
78 "                   team. If an ID is supplied, it is interpreted as a team\n"
79 "                   ID.\n"
80 "  -o <file>      - directs output into the specified file.\n"
81 "  -S             - prints output to serial debug line.\n"
82 "  -g             - turns off signal tracing.\n"
83 ;
84 
85 // terminal color escape sequences
86 // (http://www.dee.ufcg.edu.br/~rrbrandt/tools/ansi.html)
87 static const char *kTerminalTextNormal	= "\33[0m";
88 static const char *kTerminalTextRed		= "\33[31m";
89 static const char *kTerminalTextMagenta	= "\33[35m";
90 static const char *kTerminalTextBlue	= "\33[34m";
91 
92 static const char *kSignalName[] = {
93 	/*  0 */ "SIG0",
94 	/*  1 */ "SIGHUP",
95 	/*  2 */ "SIGINT",
96 	/*  3 */ "SIGQUIT",
97 	/*  4 */ "SIGILL",
98 	/*  5 */ "SIGCHLD",
99 	/*  6 */ "SIGABRT",
100 	/*  7 */ "SIGPIPE",
101 	/*  8 */ "SIGFPE",
102 	/*  9 */ "SIGKILL",
103 	/* 10 */ "SIGSTOP",
104 	/* 11 */ "SIGSEGV",
105 	/* 12 */ "SIGCONT",
106 	/* 13 */ "SIGTSTP",
107 	/* 14 */ "SIGALRM",
108 	/* 15 */ "SIGTERM",
109 	/* 16 */ "SIGTTIN",
110 	/* 17 */ "SIGTTOU",
111 	/* 18 */ "SIGUSR1",
112 	/* 19 */ "SIGUSR2",
113 	/* 20 */ "SIGWINCH",
114 	/* 21 */ "SIGKILLTHR",
115 	/* 22 */ "SIGTRAP",
116 	/* 23 */ "SIGPOLL",
117 	/* 24 */ "SIGPROF",
118 	/* 25 */ "SIGSYS",
119 	/* 26 */ "SIGURG",
120 	/* 27 */ "SIGVTALRM",
121 	/* 28 */ "SIGXCPU",
122 	/* 29 */ "SIGXFSZ",
123 };
124 
125 // command line args
126 static int sArgc;
127 static const char *const *sArgv;
128 
129 // syscalls
130 static vector<Syscall*>			sSyscallVector;
131 static map<string, Syscall*>	sSyscallMap;
132 
133 // print_usage
134 void
135 print_usage(bool error)
136 {
137 	// print usage
138 	fprintf((error ? stderr : stdout), kUsage, kCommandName);
139 }
140 
141 // print_usage_and_exit
142 static
143 void
144 print_usage_and_exit(bool error)
145 {
146 	print_usage(error);
147 	exit(error ? 1 : 0);
148 }
149 
150 // get_id
151 static
152 bool
153 get_id(const char *str, int32 &id)
154 {
155 	int32 len = strlen(str);
156 	for (int32 i = 0; i < len; i++) {
157 		if (!isdigit(str[i]))
158 			return false;
159 	}
160 
161 	id = atol(str);
162 	return true;
163 }
164 
165 // get_syscall
166 Syscall *
167 get_syscall(const char *name)
168 {
169 	map<string, Syscall *>::const_iterator i = sSyscallMap.find(name);
170 	if (i == sSyscallMap.end())
171 		return NULL;
172 
173 	return i->second;
174 }
175 
176 // patch_syscalls
177 static void
178 patch_syscalls()
179 {
180 	// instead of having this done here manually we should either add the
181 	// patching step to gensyscalls also manually or add metadata to
182 	// kernel/syscalls.h and have it parsed automatically
183 	extern void patch_ioctl();
184 
185 	patch_ioctl();
186 }
187 
188 // init_syscalls
189 static
190 void
191 init_syscalls()
192 {
193 	// init the syscall vector
194 	get_syscalls0(sSyscallVector);
195 	get_syscalls1(sSyscallVector);
196 	get_syscalls2(sSyscallVector);
197 	get_syscalls3(sSyscallVector);
198 	get_syscalls4(sSyscallVector);
199 	get_syscalls5(sSyscallVector);
200 	get_syscalls6(sSyscallVector);
201 	get_syscalls7(sSyscallVector);
202 	get_syscalls8(sSyscallVector);
203 	get_syscalls9(sSyscallVector);
204 	get_syscalls10(sSyscallVector);
205 	get_syscalls11(sSyscallVector);
206 	get_syscalls12(sSyscallVector);
207 	get_syscalls13(sSyscallVector);
208 	get_syscalls14(sSyscallVector);
209 	get_syscalls15(sSyscallVector);
210 	get_syscalls16(sSyscallVector);
211 	get_syscalls17(sSyscallVector);
212 	get_syscalls18(sSyscallVector);
213 	get_syscalls19(sSyscallVector);
214 
215 	// init the syscall map
216 	int32 count = sSyscallVector.size();
217 	for (int32 i = 0; i < count; i++) {
218 		Syscall *syscall = sSyscallVector[i];
219 		sSyscallMap[syscall->Name()] = syscall;
220 	}
221 
222 	patch_syscalls();
223 }
224 
225 // print_to_string
226 static
227 void
228 print_to_string(char **_buffer, int32 *_length, const char *format, ...)
229 {
230 	va_list list;
231 	va_start(list, format);
232 	ssize_t length = vsnprintf(*_buffer, *_length, format, list);
233 	va_end(list);
234 
235 	*_buffer += length;
236 	*_length -= length;
237 }
238 
239 // print_syscall
240 static
241 void
242 print_syscall(FILE *outputFile, debug_post_syscall &message,
243 	MemoryReader &memoryReader, bool printArguments, uint32 contentsFlags,
244 	bool printReturnValue, bool colorize, bool decimal)
245 {
246 	char buffer[4096], *string = buffer;
247 	int32 length = (int32)sizeof(buffer);
248 	int32 syscallNumber = message.syscall;
249 	Syscall *syscall = sSyscallVector[syscallNumber];
250 
251 	Context ctx(syscall, (char *)message.args, memoryReader,
252 		    contentsFlags, decimal);
253 
254 	// print syscall name
255 	if (colorize) {
256 		print_to_string(&string, &length, "[%6ld] %s%s%s(",
257 			message.origin.thread, kTerminalTextBlue, syscall->Name().c_str(),
258 			kTerminalTextNormal);
259 	} else {
260 		print_to_string(&string, &length, "[%6ld] %s(",
261 			message.origin.thread, syscall->Name().c_str());
262 	}
263 
264 	// print arguments
265 	if (printArguments) {
266 		int32 count = syscall->CountParameters();
267 		for (int32 i = 0; i < count; i++) {
268 			// get the value
269 			Parameter *parameter = syscall->ParameterAt(i);
270 			TypeHandler *handler = parameter->Handler();
271 			::string value =
272 				handler->GetParameterValue(ctx, parameter,
273 						ctx.GetValue(parameter));
274 
275 			print_to_string(&string, &length, (i > 0 ? ", %s" : "%s"),
276 				value.c_str());
277 		}
278 	}
279 
280 	print_to_string(&string, &length, ")");
281 
282 	// print return value
283 	if (printReturnValue) {
284 		Type *returnType = syscall->ReturnType();
285 		TypeHandler *handler = returnType->Handler();
286 		::string value = handler->GetReturnValue(ctx, message.return_value);
287 		if (value.length() > 0) {
288 			print_to_string(&string, &length, " = %s", value.c_str());
289 
290 			// if the return type is status_t or ssize_t, print human-readable
291 			// error codes
292 			if (returnType->TypeName() == "status_t"
293 					|| ((returnType->TypeName() == "ssize_t"
294 					 || returnType->TypeName() == "int")
295 					&& message.return_value < 0)) {
296 				print_to_string(&string, &length, " %s", strerror(message.return_value));
297 			}
298 		}
299 	}
300 
301 	if (colorize) {
302 		print_to_string(&string, &length, " %s(%lld us)%s\n", kTerminalTextMagenta,
303 			message.end_time - message.start_time, kTerminalTextNormal);
304 	} else {
305 		print_to_string(&string, &length, " (%lld us)\n",
306 			message.end_time - message.start_time);
307 	}
308 
309 //for (int32 i = 0; i < 16; i++) {
310 //	if (i % 4 == 0) {
311 //		if (i > 0)
312 //			printf("\n");
313 //		printf("  ");
314 //	} else
315 //		printf(" ");
316 //	printf("%08lx", message.args[i]);
317 //}
318 //printf("\n");
319 
320 	// output either to file or serial debug line
321 	if (outputFile != NULL)
322 		fwrite(buffer, sizeof(buffer) - length, 1, outputFile);
323 	else
324 		_kern_debug_output(buffer);
325 }
326 
327 static
328 const char *
329 signal_name(int signal)
330 {
331 	if (signal >= 0 && signal < NSIG)
332 		return kSignalName[signal];
333 
334 	static char buffer[32];
335 	sprintf(buffer, "%d", signal);
336 	return buffer;
337 }
338 
339 // print_signal
340 static
341 void
342 print_signal(FILE *outputFile, debug_signal_received &message,
343 	bool colorize)
344 {
345 	char buffer[4096], *string = buffer;
346 	int32 length = (int32)sizeof(buffer);
347 	int signalNumber = message.signal;
348 
349 	// print signal name
350 	if (colorize) {
351 		print_to_string(&string, &length, "[%6ld] --- %s%s (%s) %s---\n",
352 			message.origin.thread, kTerminalTextRed, signal_name(signalNumber),
353 			strsignal(signalNumber), kTerminalTextNormal);
354 	} else {
355 		print_to_string(&string, &length, "[%6ld] --- %s (%s) ---\n",
356 			message.origin.thread, signal_name(signalNumber),
357 			strsignal(signalNumber));
358 	}
359 
360 	// output either to file or serial debug line
361 	if (outputFile != NULL)
362 		fwrite(buffer, sizeof(buffer) - length, 1, outputFile);
363 	else
364 		_kern_debug_output(buffer);
365 }
366 
367 // main
368 int
369 main(int argc, const char *const *argv)
370 {
371 	sArgc = argc;
372 	sArgv = argv;
373 
374 	// parameters
375 	const char *const *programArgs = NULL;
376 	int32 programArgCount = 0;
377 	bool printArguments = true;
378 	bool colorize = true;
379 	uint32 contentsFlags = 0;
380 	bool decimalFormat = false;
381 	bool fastMode = false;
382 	bool traceLoading = false;
383 	bool printReturnValues = true;
384 	bool traceChildThreads = false;
385 	bool traceTeam = false;
386 	bool traceSignal = true;
387 	bool serialOutput = false;
388 	FILE *outputFile = stdout;
389 
390 	// parse arguments
391 	for (int argi = 1; argi < argc; argi++) {
392 		const char *arg = argv[argi];
393 		if (arg[0] == '-') {
394 			// ToDo: improve option parsing so that ie. "-rsf" would also work
395 			if (strcmp(arg, "-h") == 0 || strcmp(arg, "--help") == 0) {
396 				print_usage_and_exit(false);
397 			} else if (strcmp(arg, "-a") == 0) {
398 				printArguments = false;
399 			} else if (strcmp(arg, "-c") == 0) {
400 				colorize = false;
401 			} else if (strcmp(arg, "-d") == 0) {
402 				const char *what = NULL;
403 
404 				if (arg[2] == '\0'
405 					&& argi + 1 < argc && argv[argi + 1][0] != '-') {
406 					// next arg is what
407 					what = argv[++argi];
408 				} else
409 					print_usage_and_exit(true);
410 
411 				if (strcasecmp(what, "strings") == 0)
412 					contentsFlags |= Context::STRINGS;
413 				else if (strcasecmp(what, "enums") == 0)
414 					contentsFlags |= Context::ENUMERATIONS;
415 				else if (strcasecmp(what, "simple") == 0)
416 					contentsFlags |= Context::SIMPLE_STRUCTS;
417 				else if (strcasecmp(what, "complex") == 0)
418 					contentsFlags |= Context::COMPLEX_STRUCTS;
419 				else if (strcasecmp(what, "pointer_values") == 0)
420 					contentsFlags |= Context::POINTER_VALUES;
421 				else {
422 					fprintf(stderr, "%s: Unknown content filter `%s'\n",
423 						kCommandName, what);
424 					exit(1);
425 				}
426 			} else if (strcmp(arg, "-f") == 0) {
427 				fastMode = true;
428 			} else if (strcmp(arg, "-i") == 0) {
429 				decimalFormat = true;
430 			} else if (strcmp(arg, "-l") == 0) {
431 				traceLoading = true;
432 			} else if (strcmp(arg, "-r") == 0) {
433 				printReturnValues = false;
434 			} else if (strcmp(arg, "-s") == 0) {
435 				traceChildThreads = true;
436 			} else if (strcmp(arg, "-T") == 0) {
437 				traceTeam = true;
438 			} else if (strcmp(arg, "-g") == 0) {
439 				traceSignal = false;
440 			} else if (strcmp(arg, "-S") == 0) {
441 				serialOutput = true;
442 				outputFile = NULL;
443 			} else if (strncmp(arg, "-o", 2) == 0) {
444 				// read filename
445 				const char *filename = NULL;
446 				if (arg[2] == '=') {
447 					// name follows
448 					filename = arg + 3;
449 				} else if (arg[2] == '\0'
450 					&& argi + 1 < argc && argv[argi + 1][0] != '-') {
451 					// next arg is name
452 					filename = argv[++argi];
453 				} else
454 					print_usage_and_exit(true);
455 
456 				outputFile = fopen(filename, "w+");
457 				if (outputFile == NULL) {
458 					fprintf(stderr, "%s: Could not open `%s': %s\n",
459 						kCommandName, filename, strerror(errno));
460 					exit(1);
461 				}
462 			} else {
463 				print_usage_and_exit(true);
464 			}
465 		} else {
466 			programArgs = argv + argi;
467 			programArgCount = argc - argi;
468 			break;
469 		}
470 	}
471 
472 	// check parameters
473 	if (!programArgs)
474 		print_usage_and_exit(true);
475 
476 	if (fastMode)
477 		contentsFlags = 0;
478 	else if (contentsFlags == 0)
479 		contentsFlags = Context::ALL;
480 
481 	// initialize our syscalls vector and map
482 	init_syscalls();
483 
484 	// don't colorize the output, if we don't have a terminal
485 	if (outputFile == stdout)
486 		colorize = colorize && isatty(STDOUT_FILENO);
487 	else if (outputFile)
488 		colorize = false;
489 
490 	// get thread/team to be debugged
491 	thread_id thread = -1;
492 	team_id team = -1;
493 	if (programArgCount > 1
494 		|| !get_id(*programArgs, (traceTeam ? team : thread))) {
495 		// we've been given an executable and need to load it
496 		thread = load_program(programArgs, programArgCount, traceLoading);
497 		if (thread < 0) {
498 			fprintf(stderr, "%s: Failed to start `%s': %s\n", kCommandName,
499 				programArgs[0], strerror(thread));
500 			exit(1);
501 		}
502 	}
503 
504 	// get the team ID, if we have none yet
505 	if (team < 0) {
506 		thread_info threadInfo;
507 		status_t error = get_thread_info(thread, &threadInfo);
508 		if (error != B_OK) {
509 			fprintf(stderr, "%s: Failed to get info for thread %ld: %s\n",
510 				kCommandName, thread, strerror(error));
511 			exit(1);
512 		}
513 		team = threadInfo.team;
514 	}
515 
516 	// create a debugger port
517 	port_id debuggerPort = create_port(10, "debugger port");
518 	if (debuggerPort < 0) {
519 		fprintf(stderr, "%s: Failed to create debugger port: %s\n",
520 			kCommandName, strerror(debuggerPort));
521 		exit(1);
522 	}
523 
524 	// install ourselves as the team debugger
525 	port_id nubPort = install_team_debugger(team, debuggerPort);
526 	if (nubPort < 0) {
527 		fprintf(stderr, "%s: Failed to install team debugger: %s\n",
528 			kCommandName, strerror(nubPort));
529 		exit(1);
530 	}
531 
532 	// set team debugging flags
533 	int32 teamDebugFlags = (traceTeam ? B_TEAM_DEBUG_POST_SYSCALL : 0)
534 		| (traceSignal ? B_TEAM_DEBUG_SIGNALS : 0);
535 	set_team_debugging_flags(nubPort, teamDebugFlags);
536 
537 	// set thread debugging flags
538 	if (thread >= 0) {
539 		int32 threadDebugFlags = 0;
540 		if (!traceTeam) {
541 			threadDebugFlags = B_THREAD_DEBUG_POST_SYSCALL
542 				| (traceChildThreads
543 					? B_THREAD_DEBUG_SYSCALL_TRACE_CHILD_THREADS : 0);
544 		}
545 		set_thread_debugging_flags(nubPort, thread, threadDebugFlags);
546 
547 		// resume the target thread to be sure, it's running
548 		resume_thread(thread);
549 	}
550 
551 	MemoryReader memoryReader(nubPort);
552 
553 	// debug loop
554 	while (true) {
555 		bool quitLoop = false;
556 		int32 code;
557 		debug_debugger_message_data message;
558 		ssize_t messageSize = read_port(debuggerPort, &code, &message,
559 			sizeof(message));
560 
561 		if (messageSize < 0) {
562 			if (messageSize == B_INTERRUPTED)
563 				continue;
564 
565 			fprintf(stderr, "%s: Reading from debugger port failed: %s\n",
566 				kCommandName, strerror(messageSize));
567 			exit(1);
568 		}
569 
570 		switch (code) {
571 			case B_DEBUGGER_MESSAGE_POST_SYSCALL:
572 			{
573 				print_syscall(outputFile, message.post_syscall, memoryReader,
574 					printArguments, contentsFlags, printReturnValues,
575 					colorize, decimalFormat);
576 				break;
577 			}
578 
579 			case B_DEBUGGER_MESSAGE_SIGNAL_RECEIVED:
580 			{
581 				if (traceSignal) {
582 					print_signal(outputFile, message.signal_received,
583 						colorize);
584 				}
585 				break;
586 			}
587 
588 			case B_DEBUGGER_MESSAGE_THREAD_DEBUGGED:
589 			case B_DEBUGGER_MESSAGE_DEBUGGER_CALL:
590 			case B_DEBUGGER_MESSAGE_BREAKPOINT_HIT:
591 			case B_DEBUGGER_MESSAGE_WATCHPOINT_HIT:
592 			case B_DEBUGGER_MESSAGE_SINGLE_STEP:
593 			case B_DEBUGGER_MESSAGE_PRE_SYSCALL:
594 			case B_DEBUGGER_MESSAGE_EXCEPTION_OCCURRED:
595 			case B_DEBUGGER_MESSAGE_TEAM_CREATED:
596 			case B_DEBUGGER_MESSAGE_THREAD_CREATED:
597 			case B_DEBUGGER_MESSAGE_THREAD_DELETED:
598 			case B_DEBUGGER_MESSAGE_IMAGE_CREATED:
599 			case B_DEBUGGER_MESSAGE_IMAGE_DELETED:
600 				break;
601 
602 			case B_DEBUGGER_MESSAGE_TEAM_DELETED:
603 				// the debugged team is gone
604 				quitLoop = true;
605 				break;
606 		}
607 
608 		if (quitLoop)
609 			break;
610 
611 		// tell the thread to continue (only when there is a thread and the
612 		// message was synchronous)
613 		if (message.origin.thread >= 0 && message.origin.nub_port >= 0)
614 			continue_thread(message.origin.nub_port, message.origin.thread);
615 	}
616 
617 	if (outputFile != NULL && outputFile != stdout)
618 		fclose(outputFile);
619 
620 	return 0;
621 }
622