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