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