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