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