xref: /haiku/src/apps/debugger/user_interface/cli/CommandLineUserInterface.cpp (revision 220d04022750f40f8bac8f01fa551211e28d04f2)
1 /*
2  * Copyright 2011-2014, Rene Gollent, rene@gollent.com.
3  * Copyright 2012, Ingo Weinhold, ingo_weinhold@gmx.de.
4  * Distributed under the terms of the MIT License.
5  */
6 
7 
8 #include "CommandLineUserInterface.h"
9 
10 #include <stdio.h>
11 
12 #include <algorithm>
13 
14 #include <ArgumentVector.h>
15 #include <AutoDeleter.h>
16 #include <AutoLocker.h>
17 #include <Referenceable.h>
18 
19 #include "CliContext.h"
20 #include "CliContinueCommand.h"
21 #include "CliDebugReportCommand.h"
22 #include "CliDumpMemoryCommand.h"
23 #include "CliPrintVariableCommand.h"
24 #include "CliQuitCommand.h"
25 #include "CliStackFrameCommand.h"
26 #include "CliStackTraceCommand.h"
27 #include "CliStopCommand.h"
28 #include "CliThreadCommand.h"
29 #include "CliThreadsCommand.h"
30 #include "CliVariablesCommand.h"
31 
32 
33 static const char* kDebuggerPrompt = "debugger> ";
34 
35 
36 // #pragma mark - CommandEntry
37 
38 
39 struct CommandLineUserInterface::CommandEntry {
40 	CommandEntry(const BString& name, CliCommand* command)
41 		:
42 		fName(name),
43 		fCommand(command)
44 	{
45 	}
46 
47 	const BString& Name() const
48 	{
49 		return fName;
50 	}
51 
52 	CliCommand* Command() const
53 	{
54 		return fCommand.Get();
55 	}
56 
57 private:
58 	BString					fName;
59 	BReference<CliCommand>	fCommand;
60 };
61 
62 
63 // #pragma mark - HelpCommand
64 
65 
66 struct CommandLineUserInterface::HelpCommand : CliCommand {
67 	HelpCommand(CommandLineUserInterface* userInterface)
68 		:
69 		CliCommand("print help for a command or a list of all commands",
70 			"%s [ <command> ]\n"
71 			"Prints help for command <command>, if given, or a list of all "
72 				"commands\n"
73 			"otherwise."),
74 		fUserInterface(userInterface)
75 	{
76 	}
77 
78 	virtual void Execute(int argc, const char* const* argv, CliContext& context)
79 	{
80 		if (argc > 2) {
81 			PrintUsage(argv[0]);
82 			return;
83 		}
84 
85 		fUserInterface->_PrintHelp(argc == 2 ? argv[1] : NULL);
86 	}
87 
88 private:
89 	CommandLineUserInterface* fUserInterface;
90 };
91 
92 
93 // #pragma mark - CommandLineUserInterface
94 
95 
96 CommandLineUserInterface::CommandLineUserInterface(bool saveReport,
97 	const char* reportPath, thread_id reportTargetThread)
98 	:
99 	fCommands(20, true),
100 	fReportPath(reportPath),
101 	fSaveReport(saveReport),
102 	fReportTargetThread(reportTargetThread),
103 	fShowSemaphore(-1),
104 	fShown(false),
105 	fTerminating(false)
106 {
107 }
108 
109 
110 CommandLineUserInterface::~CommandLineUserInterface()
111 {
112 	if (fShowSemaphore >= 0)
113 		delete_sem(fShowSemaphore);
114 }
115 
116 
117 const char*
118 CommandLineUserInterface::ID() const
119 {
120 	return "BasicCommandLineUserInterface";
121 }
122 
123 
124 status_t
125 CommandLineUserInterface::Init(Team* team, UserInterfaceListener* listener)
126 {
127 	status_t error = fContext.Init(team, listener);
128 	if (error != B_OK)
129 		return error;
130 
131 	error = _RegisterCommands();
132 	if (error != B_OK)
133 		return error;
134 
135 	fShowSemaphore = create_sem(0, "show CLI");
136 	if (fShowSemaphore < 0)
137 		return fShowSemaphore;
138 
139 	team->AddListener(this);
140 
141 	return B_OK;
142 }
143 
144 
145 void
146 CommandLineUserInterface::Show()
147 {
148 	fShown = true;
149 	release_sem(fShowSemaphore);
150 }
151 
152 
153 void
154 CommandLineUserInterface::Terminate()
155 {
156 	fTerminating = true;
157 
158 	if (fShown) {
159 		fContext.Terminating();
160 
161 		// Wait for input loop to finish.
162 		while (acquire_sem(fShowSemaphore) == B_INTERRUPTED) {
163 		}
164 	} else {
165 		// The main thread will still be blocked in Run(). Unblock it.
166 		delete_sem(fShowSemaphore);
167 		fShowSemaphore = -1;
168 	}
169 
170 	fContext.Cleanup();
171 }
172 
173 
174 bool
175 CommandLineUserInterface::IsInteractive() const
176 {
177 	// if we were invoked solely for the purpose of saving a crash report,
178 	// then we're not taking user input into account.
179 	return !fSaveReport;
180 }
181 
182 
183 status_t
184 CommandLineUserInterface::LoadSettings(const TeamUiSettings* settings)
185 {
186 	return B_OK;
187 }
188 
189 
190 status_t
191 CommandLineUserInterface::SaveSettings(TeamUiSettings*& settings) const
192 {
193 	return B_OK;
194 }
195 
196 
197 void
198 CommandLineUserInterface::NotifyUser(const char* title, const char* message,
199 	user_notification_type type)
200 {
201 }
202 
203 
204 int32
205 CommandLineUserInterface::SynchronouslyAskUser(const char* title,
206 	const char* message, const char* choice1, const char* choice2,
207 	const char* choice3)
208 {
209 	return -1;
210 }
211 
212 
213 status_t
214 CommandLineUserInterface::SynchronouslyAskUserForFile(entry_ref* _ref)
215 {
216 	return B_UNSUPPORTED;
217 }
218 
219 
220 void
221 CommandLineUserInterface::Run()
222 {
223 	// Wait for the Show() semaphore to be released.
224 	status_t error;
225 	do {
226 		error = acquire_sem(fShowSemaphore);
227 	} while (error == B_INTERRUPTED);
228 
229 	if (error != B_OK)
230 		return;
231 
232 	if (!fSaveReport) {
233 		_InputLoop();
234 		// Release the Show() semaphore to signal Terminate().
235 		release_sem(fShowSemaphore);
236 	} else {
237 		ArgumentVector args;
238 		char buffer[256];
239 		const char* parseErrorLocation;
240 		if (_ReportTargetThreadStopNeeded()) {
241 			snprintf(buffer, sizeof(buffer), "stop %" B_PRId32,
242 				fReportTargetThread);
243 			args.Parse(buffer, &parseErrorLocation);
244 			_ExecuteCommand(args.ArgumentCount(), args.Arguments());
245 		} else
246 			_SubmitSaveReport();
247 	}
248 }
249 
250 
251 void
252 CommandLineUserInterface::ThreadStateChanged(const Team::ThreadEvent& event)
253 {
254 	if (fSaveReport) {
255 		Thread* thread = event.GetThread();
256 		// If we were asked to attach/report on a specific thread
257 		// rather than a team, and said thread was still
258 		// running, when we attached, we need to wait for its corresponding
259 		// stop state before generating a report, else we might not get its
260 		// stack trace.
261 		if (thread->ID() == fReportTargetThread
262 			&& thread->State() == THREAD_STATE_STOPPED) {
263 			_SubmitSaveReport();
264 		}
265 	}
266 }
267 
268 
269 void
270 CommandLineUserInterface::DebugReportChanged(
271 	const Team::DebugReportEvent& event)
272 {
273 	printf("Successfully saved debug report to %s\n",
274 		event.GetReportPath());
275 
276 	if (fSaveReport) {
277 		fContext.QuitSession(true);
278 		// Release the Show() semaphore to signal Terminate().
279 		release_sem(fShowSemaphore);
280 	}
281 }
282 
283 
284 /*static*/ status_t
285 CommandLineUserInterface::_InputLoopEntry(void* data)
286 {
287 	return ((CommandLineUserInterface*)data)->_InputLoop();
288 }
289 
290 
291 status_t
292 CommandLineUserInterface::_InputLoop()
293 {
294 	thread_id currentThread = -1;
295 
296 	while (!fTerminating) {
297 		// Wait for a thread or Ctrl-C.
298 		fContext.WaitForThreadOrUser();
299 		if (fContext.IsTerminating())
300 			break;
301 
302 		// Print the active thread, if it changed.
303 		if (fContext.CurrentThreadID() != currentThread) {
304 			fContext.PrintCurrentThread();
305 			currentThread = fContext.CurrentThreadID();
306 		}
307 
308 		// read a command line
309 		const char* line = fContext.PromptUser(kDebuggerPrompt);
310 		if (line == NULL)
311 			break;
312 
313 		// parse the command line
314 		ArgumentVector args;
315 		const char* parseErrorLocation;
316 		switch (args.Parse(line, &parseErrorLocation)) {
317 			case ArgumentVector::NO_ERROR:
318 				break;
319 			case ArgumentVector::NO_MEMORY:
320 				printf("Insufficient memory parsing the command line.\n");
321 				continue;
322 			case ArgumentVector::UNTERMINATED_QUOTED_STRING:
323 				printf("Parse error: Unterminated quoted string starting at "
324 					"character %zu.\n", parseErrorLocation - line + 1);
325 				continue;
326 			case ArgumentVector::TRAILING_BACKSPACE:
327 				printf("Parse error: trailing backspace.\n");
328 				continue;
329 		}
330 
331 		if (args.ArgumentCount() == 0)
332 			continue;
333 
334 		// add line to history
335 		fContext.AddLineToInputHistory(line);
336 
337 		// execute command
338 		_ExecuteCommand(args.ArgumentCount(), args.Arguments());
339 	}
340 
341 	return B_OK;
342 }
343 
344 
345 status_t
346 CommandLineUserInterface::_RegisterCommands()
347 {
348 	if (_RegisterCommand("bt sc", new(std::nothrow) CliStackTraceCommand)
349 		&& _RegisterCommand("continue", new(std::nothrow) CliContinueCommand)
350 		&& _RegisterCommand("db ds dw dl string", new(std::nothrow)
351 			CliDumpMemoryCommand)
352 		&& _RegisterCommand("frame", new(std::nothrow) CliStackFrameCommand)
353 		&& _RegisterCommand("help", new(std::nothrow) HelpCommand(this))
354 		&& _RegisterCommand("print", new(std::nothrow) CliPrintVariableCommand)
355 		&& _RegisterCommand("quit", new(std::nothrow) CliQuitCommand)
356 		&& _RegisterCommand("save-report",
357 			new(std::nothrow) CliDebugReportCommand)
358 		&& _RegisterCommand("stop", new(std::nothrow) CliStopCommand)
359 		&& _RegisterCommand("thread", new(std::nothrow) CliThreadCommand)
360 		&& _RegisterCommand("threads", new(std::nothrow) CliThreadsCommand)
361 		&& _RegisterCommand("variables",
362 			new(std::nothrow) CliVariablesCommand)) {
363 		fCommands.SortItems(&_CompareCommandEntries);
364 		return B_OK;
365 	}
366 
367 	return B_NO_MEMORY;
368 }
369 
370 
371 bool
372 CommandLineUserInterface::_RegisterCommand(const BString& name,
373 	CliCommand* command)
374 {
375 	BReference<CliCommand> commandReference(command, true);
376 	if (name.IsEmpty() || command == NULL)
377 		return false;
378 
379 	BString nextName;
380 	int32 startIndex = 0;
381 	int32 spaceIndex;
382 	do {
383 		spaceIndex = name.FindFirst(' ', startIndex);
384 		if (spaceIndex == B_ERROR)
385 			spaceIndex = name.Length();
386 		name.CopyInto(nextName, startIndex, spaceIndex - startIndex);
387 
388 		CommandEntry* entry = new(std::nothrow) CommandEntry(nextName,
389 			command);
390 		if (entry == NULL || !fCommands.AddItem(entry)) {
391 			delete entry;
392 			return false;
393 		}
394 		startIndex = spaceIndex + 1;
395 	} while (startIndex < name.Length());
396 
397 	return true;
398 }
399 
400 
401 void
402 CommandLineUserInterface::_ExecuteCommand(int argc, const char* const* argv)
403 {
404 	CommandEntry* commandEntry = _FindCommand(argv[0]);
405 	if (commandEntry != NULL)
406 		commandEntry->Command()->Execute(argc, argv, fContext);
407 }
408 
409 
410 CommandLineUserInterface::CommandEntry*
411 CommandLineUserInterface::_FindCommand(const char* commandName)
412 {
413 	size_t commandNameLength = strlen(commandName);
414 
415 	// try to find an exact match first
416 	CommandEntry* commandEntry = NULL;
417 	for (int32 i = 0; CommandEntry* entry = fCommands.ItemAt(i); i++) {
418 		if (entry->Name() == commandName) {
419 			commandEntry = entry;
420 			break;
421 		}
422 	}
423 
424 	// If nothing found yet, try partial matches, but only, if they are
425 	// unambiguous.
426 	if (commandEntry == NULL) {
427 		for (int32 i = 0; CommandEntry* entry = fCommands.ItemAt(i); i++) {
428 			if (entry->Name().Compare(commandName, commandNameLength) == 0) {
429 				if (commandEntry != NULL) {
430 					printf("Error: Ambiguous command \"%s\".\n", commandName);
431 					return NULL;
432 				}
433 
434 				commandEntry = entry;
435 			}
436 		}
437 	}
438 
439 	if (commandEntry == NULL) {
440 		printf("Error: Unknown command \"%s\".\n", commandName);
441 		return NULL;
442 	}
443 
444 	return commandEntry;
445 }
446 
447 
448 void
449 CommandLineUserInterface::_PrintHelp(const char* commandName)
450 {
451 	// If a command name is given, print the usage for that one.
452 	if (commandName != NULL) {
453 		CommandEntry* commandEntry = _FindCommand(commandName);
454 		if (commandEntry != NULL)
455 			commandEntry->Command()->PrintUsage(commandEntry->Name().String());
456 		return;
457 	}
458 
459 	// No command name given -- print a list of all commands.
460 
461 	// determine longest command name
462 	int32 longestCommandName = 0;
463 	for (int32 i = 0; CommandEntry* entry = fCommands.ItemAt(i); i++) {
464 		longestCommandName
465 			= std::max(longestCommandName, entry->Name().Length());
466 	}
467 
468 	// print the command list
469 	for (int32 i = 0; CommandEntry* entry = fCommands.ItemAt(i); i++) {
470 		printf("%*s  -  %s\n", (int)longestCommandName, entry->Name().String(),
471 			entry->Command()->Summary());
472 	}
473 }
474 
475 
476 /*static */
477 int
478 CommandLineUserInterface::_CompareCommandEntries(const CommandEntry* command1,
479 	const CommandEntry* command2)
480 {
481 	return ::Compare(command1->Name(), command2->Name());
482 }
483 
484 
485 bool
486 CommandLineUserInterface::_ReportTargetThreadStopNeeded() const
487 {
488 	if (fReportTargetThread < 0)
489 		return false;
490 
491 	Team* team = fContext.GetTeam();
492 	AutoLocker<Team> teamLocker(team);
493 	Thread* thread = team->ThreadByID(fReportTargetThread);
494 	if (thread == NULL)
495 		return false;
496 
497 	return thread->State() != THREAD_STATE_STOPPED;
498 }
499 
500 
501 void
502 CommandLineUserInterface::_SubmitSaveReport()
503 {
504 	ArgumentVector args;
505 	char buffer[256];
506 	const char* parseErrorLocation;
507 	snprintf(buffer, sizeof(buffer), "save-report %s",
508 		fReportPath != NULL ? fReportPath : "");
509 	args.Parse(buffer, &parseErrorLocation);
510 	_ExecuteCommand(args.ArgumentCount(), args.Arguments());
511 }
512