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