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