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