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