xref: /haiku/src/apps/debugger/user_interface/cli/CommandLineUserInterface.cpp (revision 3b07762c548ec4016dea480d1061577cd15ec614)
1 /*
2  * Copyright 2011-2013, 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 status_t
175 CommandLineUserInterface::LoadSettings(const TeamUiSettings* settings)
176 {
177 	return B_OK;
178 }
179 
180 
181 status_t
182 CommandLineUserInterface::SaveSettings(TeamUiSettings*& settings) const
183 {
184 	return B_OK;
185 }
186 
187 
188 void
189 CommandLineUserInterface::NotifyUser(const char* title, const char* message,
190 	user_notification_type type)
191 {
192 }
193 
194 
195 int32
196 CommandLineUserInterface::SynchronouslyAskUser(const char* title,
197 	const char* message, const char* choice1, const char* choice2,
198 	const char* choice3)
199 {
200 	return -1;
201 }
202 
203 
204 void
205 CommandLineUserInterface::Run()
206 {
207 	// Wait for the Show() semaphore to be released.
208 	status_t error;
209 	do {
210 		error = acquire_sem(fShowSemaphore);
211 	} while (error == B_INTERRUPTED);
212 
213 	if (error != B_OK)
214 		return;
215 
216 	if (!fSaveReport) {
217 		_InputLoop();
218 		// Release the Show() semaphore to signal Terminate().
219 		release_sem(fShowSemaphore);
220 	} else {
221 		ArgumentVector args;
222 		char buffer[256];
223 		const char* parseErrorLocation;
224 		if (_ReportTargetThreadStopNeeded()) {
225 			snprintf(buffer, sizeof(buffer), "stop %" B_PRId32,
226 				fReportTargetThread);
227 			args.Parse(buffer, &parseErrorLocation);
228 			_ExecuteCommand(args.ArgumentCount(), args.Arguments());
229 		} else
230 			_SubmitSaveReport();
231 	}
232 }
233 
234 
235 void
236 CommandLineUserInterface::ThreadStateChanged(const Team::ThreadEvent& event)
237 {
238 	if (fSaveReport) {
239 		Thread* thread = event.GetThread();
240 		// If we were asked to attach/report on a specific thread
241 		// rather than a team, and said thread was still
242 		// running, when we attached, we need to wait for its corresponding
243 		// stop state before generating a report, else we might not get its
244 		// stack trace.
245 		if (thread->ID() == fReportTargetThread
246 			&& thread->State() == THREAD_STATE_STOPPED) {
247 			_SubmitSaveReport();
248 		}
249 	}
250 }
251 
252 
253 void
254 CommandLineUserInterface::DebugReportChanged(
255 	const Team::DebugReportEvent& event)
256 {
257 	printf("Successfully saved debug report to %s\n",
258 		event.GetReportPath());
259 
260 	if (fSaveReport) {
261 		fContext.QuitSession(true);
262 		// Release the Show() semaphore to signal Terminate().
263 		release_sem(fShowSemaphore);
264 	}
265 }
266 
267 
268 /*static*/ status_t
269 CommandLineUserInterface::_InputLoopEntry(void* data)
270 {
271 	return ((CommandLineUserInterface*)data)->_InputLoop();
272 }
273 
274 
275 status_t
276 CommandLineUserInterface::_InputLoop()
277 {
278 	thread_id currentThread = -1;
279 
280 	while (!fTerminating) {
281 		// Wait for a thread or Ctrl-C.
282 		fContext.WaitForThreadOrUser();
283 		if (fContext.IsTerminating())
284 			break;
285 
286 		// Print the active thread, if it changed.
287 		if (fContext.CurrentThreadID() != currentThread) {
288 			fContext.PrintCurrentThread();
289 			currentThread = fContext.CurrentThreadID();
290 		}
291 
292 		// read a command line
293 		const char* line = fContext.PromptUser(kDebuggerPrompt);
294 		if (line == NULL)
295 			break;
296 
297 		// parse the command line
298 		ArgumentVector args;
299 		const char* parseErrorLocation;
300 		switch (args.Parse(line, &parseErrorLocation)) {
301 			case ArgumentVector::NO_ERROR:
302 				break;
303 			case ArgumentVector::NO_MEMORY:
304 				printf("Insufficient memory parsing the command line.\n");
305 				continue;
306 			case ArgumentVector::UNTERMINATED_QUOTED_STRING:
307 				printf("Parse error: Unterminated quoted string starting at "
308 					"character %zu.\n", parseErrorLocation - line + 1);
309 				continue;
310 			case ArgumentVector::TRAILING_BACKSPACE:
311 				printf("Parse error: trailing backspace.\n");
312 				continue;
313 		}
314 
315 		if (args.ArgumentCount() == 0)
316 			continue;
317 
318 		// add line to history
319 		fContext.AddLineToInputHistory(line);
320 
321 		// execute command
322 		_ExecuteCommand(args.ArgumentCount(), args.Arguments());
323 	}
324 
325 	return B_OK;
326 }
327 
328 
329 status_t
330 CommandLineUserInterface::_RegisterCommands()
331 {
332 	if (_RegisterCommand("bt sc", new(std::nothrow) CliStackTraceCommand)
333 		&& _RegisterCommand("continue", new(std::nothrow) CliContinueCommand)
334 		&& _RegisterCommand("db ds dw dl string", new(std::nothrow)
335 			CliDumpMemoryCommand)
336 		&& _RegisterCommand("frame", new(std::nothrow) CliStackFrameCommand)
337 		&& _RegisterCommand("help", new(std::nothrow) HelpCommand(this))
338 		&& _RegisterCommand("print", new(std::nothrow) CliPrintVariableCommand)
339 		&& _RegisterCommand("quit", new(std::nothrow) CliQuitCommand)
340 		&& _RegisterCommand("save-report",
341 			new(std::nothrow) CliDebugReportCommand)
342 		&& _RegisterCommand("stop", new(std::nothrow) CliStopCommand)
343 		&& _RegisterCommand("thread", new(std::nothrow) CliThreadCommand)
344 		&& _RegisterCommand("threads", new(std::nothrow) CliThreadsCommand)
345 		&& _RegisterCommand("variables",
346 			new(std::nothrow) CliVariablesCommand)) {
347 		fCommands.SortItems(&_CompareCommandEntries);
348 		return B_OK;
349 	}
350 
351 	return B_NO_MEMORY;
352 }
353 
354 
355 bool
356 CommandLineUserInterface::_RegisterCommand(const BString& name,
357 	CliCommand* command)
358 {
359 	BReference<CliCommand> commandReference(command, true);
360 	if (name.IsEmpty() || command == NULL)
361 		return false;
362 
363 	BString nextName;
364 	int32 startIndex = 0;
365 	int32 spaceIndex;
366 	do {
367 		spaceIndex = name.FindFirst(' ', startIndex);
368 		if (spaceIndex == B_ERROR)
369 			spaceIndex = name.Length();
370 		name.CopyInto(nextName, startIndex, spaceIndex - startIndex);
371 
372 		CommandEntry* entry = new(std::nothrow) CommandEntry(nextName,
373 			command);
374 		if (entry == NULL || !fCommands.AddItem(entry)) {
375 			delete entry;
376 			return false;
377 		}
378 		startIndex = spaceIndex + 1;
379 	} while (startIndex < name.Length());
380 
381 	return true;
382 }
383 
384 
385 void
386 CommandLineUserInterface::_ExecuteCommand(int argc, const char* const* argv)
387 {
388 	CommandEntry* commandEntry = _FindCommand(argv[0]);
389 	if (commandEntry != NULL)
390 		commandEntry->Command()->Execute(argc, argv, fContext);
391 }
392 
393 
394 CommandLineUserInterface::CommandEntry*
395 CommandLineUserInterface::_FindCommand(const char* commandName)
396 {
397 	size_t commandNameLength = strlen(commandName);
398 
399 	// try to find an exact match first
400 	CommandEntry* commandEntry = NULL;
401 	for (int32 i = 0; CommandEntry* entry = fCommands.ItemAt(i); i++) {
402 		if (entry->Name() == commandName) {
403 			commandEntry = entry;
404 			break;
405 		}
406 	}
407 
408 	// If nothing found yet, try partial matches, but only, if they are
409 	// unambiguous.
410 	if (commandEntry == NULL) {
411 		for (int32 i = 0; CommandEntry* entry = fCommands.ItemAt(i); i++) {
412 			if (entry->Name().Compare(commandName, commandNameLength) == 0) {
413 				if (commandEntry != NULL) {
414 					printf("Error: Ambiguous command \"%s\".\n", commandName);
415 					return NULL;
416 				}
417 
418 				commandEntry = entry;
419 			}
420 		}
421 	}
422 
423 	if (commandEntry == NULL) {
424 		printf("Error: Unknown command \"%s\".\n", commandName);
425 		return NULL;
426 	}
427 
428 	return commandEntry;
429 }
430 
431 
432 void
433 CommandLineUserInterface::_PrintHelp(const char* commandName)
434 {
435 	// If a command name is given, print the usage for that one.
436 	if (commandName != NULL) {
437 		CommandEntry* commandEntry = _FindCommand(commandName);
438 		if (commandEntry != NULL)
439 			commandEntry->Command()->PrintUsage(commandEntry->Name().String());
440 		return;
441 	}
442 
443 	// No command name given -- print a list of all commands.
444 
445 	// determine longest command name
446 	int32 longestCommandName = 0;
447 	for (int32 i = 0; CommandEntry* entry = fCommands.ItemAt(i); i++) {
448 		longestCommandName
449 			= std::max(longestCommandName, entry->Name().Length());
450 	}
451 
452 	// print the command list
453 	for (int32 i = 0; CommandEntry* entry = fCommands.ItemAt(i); i++) {
454 		printf("%*s  -  %s\n", (int)longestCommandName, entry->Name().String(),
455 			entry->Command()->Summary());
456 	}
457 }
458 
459 
460 /*static */
461 int
462 CommandLineUserInterface::_CompareCommandEntries(const CommandEntry* command1,
463 	const CommandEntry* command2)
464 {
465 	return ::Compare(command1->Name(), command2->Name());
466 }
467 
468 
469 bool
470 CommandLineUserInterface::_ReportTargetThreadStopNeeded() const
471 {
472 	if (fReportTargetThread < 0)
473 		return false;
474 
475 	Team* team = fContext.GetTeam();
476 	AutoLocker<Team> teamLocker(team);
477 	Thread* thread = team->ThreadByID(fReportTargetThread);
478 	if (thread == NULL)
479 		return false;
480 
481 	return thread->State() != THREAD_STATE_STOPPED;
482 }
483 
484 
485 void
486 CommandLineUserInterface::_SubmitSaveReport()
487 {
488 	ArgumentVector args;
489 	char buffer[256];
490 	const char* parseErrorLocation;
491 	snprintf(buffer, sizeof(buffer), "save-report %s",
492 		fReportPath != NULL ? fReportPath : "");
493 	args.Parse(buffer, &parseErrorLocation);
494 	_ExecuteCommand(args.ArgumentCount(), args.Arguments());
495 }
496