Files
haiku-beta6/src/apps/debugger/user_interface/cli/CommandLineUserInterface.cpp
T

496 lines
11 KiB
C++
Raw Normal View History

/*
2013-12-07 11:15:17 -05:00
* Copyright 2011-2013, Rene Gollent, [email protected].
* Copyright 2012, Ingo Weinhold, [email protected].
* Distributed under the terms of the MIT License.
*/
#include "CommandLineUserInterface.h"
#include <stdio.h>
#include <algorithm>
#include <ArgumentVector.h>
2012-07-23 22:44:06 +02:00
#include <AutoDeleter.h>
2013-12-07 11:15:17 -05:00
#include <AutoLocker.h>
#include <Referenceable.h>
#include "CliContext.h"
2012-08-05 01:06:37 +02:00
#include "CliContinueCommand.h"
2012-11-22 23:41:34 -05:00
#include "CliDebugReportCommand.h"
#include "CliDumpMemoryCommand.h"
2012-12-17 20:46:27 -05:00
#include "CliPrintVariableCommand.h"
#include "CliQuitCommand.h"
2012-12-16 21:57:53 -05:00
#include "CliStackFrameCommand.h"
2012-08-05 01:06:37 +02:00
#include "CliStackTraceCommand.h"
#include "CliStopCommand.h"
#include "CliThreadCommand.h"
2012-07-23 23:51:05 +02:00
#include "CliThreadsCommand.h"
2012-12-16 21:57:53 -05:00
#include "CliVariablesCommand.h"
static const char* kDebuggerPrompt = "debugger> ";
2012-07-25 00:11:14 +02:00
// #pragma mark - CommandEntry
struct CommandLineUserInterface::CommandEntry {
CommandEntry(const BString& name, CliCommand* command)
:
fName(name),
fCommand(command)
{
}
const BString& Name() const
{
return fName;
}
CliCommand* Command() const
{
return fCommand.Get();
}
private:
BString fName;
BReference<CliCommand> fCommand;
};
// #pragma mark - HelpCommand
struct CommandLineUserInterface::HelpCommand : CliCommand {
HelpCommand(CommandLineUserInterface* userInterface)
:
2012-08-05 00:57:57 +02:00
CliCommand("print help for a command or a list of all commands",
"%s [ <command> ]\n"
"Prints help for command <command>, if given, or a list of all "
"commands\n"
"otherwise."),
fUserInterface(userInterface)
{
}
virtual void Execute(int argc, const char* const* argv, CliContext& context)
{
2012-08-05 00:57:57 +02:00
if (argc > 2) {
PrintUsage(argv[0]);
return;
}
fUserInterface->_PrintHelp(argc == 2 ? argv[1] : NULL);
}
private:
CommandLineUserInterface* fUserInterface;
};
// #pragma mark - CommandLineUserInterface
CommandLineUserInterface::CommandLineUserInterface(bool saveReport,
2013-12-07 11:15:17 -05:00
const char* reportPath, thread_id reportTargetThread)
:
fCommands(20, true),
fReportPath(reportPath),
fSaveReport(saveReport),
2013-12-07 11:15:17 -05:00
fReportTargetThread(reportTargetThread),
fShowSemaphore(-1),
fShown(false),
fTerminating(false)
{
}
CommandLineUserInterface::~CommandLineUserInterface()
{
if (fShowSemaphore >= 0)
delete_sem(fShowSemaphore);
}
const char*
CommandLineUserInterface::ID() const
{
return "BasicCommandLineUserInterface";
}
status_t
CommandLineUserInterface::Init(Team* team, UserInterfaceListener* listener)
{
status_t error = fContext.Init(team, listener);
if (error != B_OK)
return error;
error = _RegisterCommands();
if (error != B_OK)
return error;
fShowSemaphore = create_sem(0, "show CLI");
if (fShowSemaphore < 0)
return fShowSemaphore;
2012-11-24 00:46:07 -05:00
team->AddListener(this);
return B_OK;
}
void
CommandLineUserInterface::Show()
{
fShown = true;
release_sem(fShowSemaphore);
}
void
CommandLineUserInterface::Terminate()
{
fTerminating = true;
if (fShown) {
fContext.Terminating();
// Wait for input loop to finish.
while (acquire_sem(fShowSemaphore) == B_INTERRUPTED) {
}
} else {
// The main thread will still be blocked in Run(). Unblock it.
delete_sem(fShowSemaphore);
fShowSemaphore = -1;
}
2012-07-25 00:11:14 +02:00
fContext.Cleanup();
}
status_t
CommandLineUserInterface::LoadSettings(const TeamUiSettings* settings)
{
return B_OK;
}
status_t
CommandLineUserInterface::SaveSettings(TeamUiSettings*& settings) const
{
return B_OK;
}
void
CommandLineUserInterface::NotifyUser(const char* title, const char* message,
user_notification_type type)
{
}
int32
CommandLineUserInterface::SynchronouslyAskUser(const char* title,
const char* message, const char* choice1, const char* choice2,
const char* choice3)
{
return -1;
}
void
CommandLineUserInterface::Run()
{
// Wait for the Show() semaphore to be released.
status_t error;
do {
error = acquire_sem(fShowSemaphore);
} while (error == B_INTERRUPTED);
if (error != B_OK)
return;
2012-11-24 00:46:07 -05:00
if (!fSaveReport) {
_InputLoop();
2012-11-24 00:46:07 -05:00
// Release the Show() semaphore to signal Terminate().
release_sem(fShowSemaphore);
} else {
ArgumentVector args;
char buffer[256];
const char* parseErrorLocation;
2013-12-07 11:15:17 -05:00
if (_ReportTargetThreadStopNeeded()) {
snprintf(buffer, sizeof(buffer), "stop %" B_PRId32,
fReportTargetThread);
args.Parse(buffer, &parseErrorLocation);
_ExecuteCommand(args.ArgumentCount(), args.Arguments());
2013-12-09 18:39:50 -05:00
} else
_SubmitSaveReport();
}
}
void
CommandLineUserInterface::ThreadStateChanged(const Team::ThreadEvent& event)
{
if (fSaveReport) {
Thread* thread = event.GetThread();
// If we were asked to attach/report on a specific thread
// rather than a team, and said thread was still
// running, when we attached, we need to wait for its corresponding
// stop state before generating a report, else we might not get its
// stack trace.
if (thread->ID() == fReportTargetThread
&& thread->State() == THREAD_STATE_STOPPED) {
_SubmitSaveReport();
2013-12-07 11:15:17 -05:00
}
}
2012-11-24 00:46:07 -05:00
}
2012-11-24 00:46:07 -05:00
void
CommandLineUserInterface::DebugReportChanged(
const Team::DebugReportEvent& event)
{
printf("Successfully saved debug report to %s\n",
event.GetReportPath());
if (fSaveReport) {
fContext.QuitSession(true);
// Release the Show() semaphore to signal Terminate().
release_sem(fShowSemaphore);
}
}
/*static*/ status_t
CommandLineUserInterface::_InputLoopEntry(void* data)
{
return ((CommandLineUserInterface*)data)->_InputLoop();
}
status_t
CommandLineUserInterface::_InputLoop()
{
thread_id currentThread = -1;
while (!fTerminating) {
// Wait for a thread or Ctrl-C.
fContext.WaitForThreadOrUser();
if (fContext.IsTerminating())
break;
// Print the active thread, if it changed.
if (fContext.CurrentThreadID() != currentThread) {
fContext.PrintCurrentThread();
currentThread = fContext.CurrentThreadID();
}
// read a command line
const char* line = fContext.PromptUser(kDebuggerPrompt);
2012-07-23 22:44:06 +02:00
if (line == NULL)
break;
// parse the command line
ArgumentVector args;
const char* parseErrorLocation;
2012-07-23 22:44:06 +02:00
switch (args.Parse(line, &parseErrorLocation)) {
case ArgumentVector::NO_ERROR:
break;
case ArgumentVector::NO_MEMORY:
printf("Insufficient memory parsing the command line.\n");
continue;
case ArgumentVector::UNTERMINATED_QUOTED_STRING:
printf("Parse error: Unterminated quoted string starting at "
2012-07-23 22:44:06 +02:00
"character %zu.\n", parseErrorLocation - line + 1);
continue;
case ArgumentVector::TRAILING_BACKSPACE:
printf("Parse error: trailing backspace.\n");
continue;
}
if (args.ArgumentCount() == 0)
continue;
2012-07-25 00:11:14 +02:00
// add line to history
fContext.AddLineToInputHistory(line);
2012-07-23 22:44:06 +02:00
2012-07-25 00:11:14 +02:00
// execute command
_ExecuteCommand(args.ArgumentCount(), args.Arguments());
}
return B_OK;
}
status_t
CommandLineUserInterface::_RegisterCommands()
{
2012-12-19 22:27:45 -05:00
if (_RegisterCommand("bt sc", new(std::nothrow) CliStackTraceCommand)
2012-11-24 00:46:07 -05:00
&& _RegisterCommand("continue", new(std::nothrow) CliContinueCommand)
2012-12-19 22:27:45 -05:00
&& _RegisterCommand("db ds dw dl string", new(std::nothrow)
CliDumpMemoryCommand)
2012-12-16 21:57:53 -05:00
&& _RegisterCommand("frame", new(std::nothrow) CliStackFrameCommand)
2012-11-24 00:46:07 -05:00
&& _RegisterCommand("help", new(std::nothrow) HelpCommand(this))
2012-12-17 20:46:27 -05:00
&& _RegisterCommand("print", new(std::nothrow) CliPrintVariableCommand)
2012-11-24 00:46:07 -05:00
&& _RegisterCommand("quit", new(std::nothrow) CliQuitCommand)
&& _RegisterCommand("save-report",
new(std::nothrow) CliDebugReportCommand)
&& _RegisterCommand("stop", new(std::nothrow) CliStopCommand)
&& _RegisterCommand("thread", new(std::nothrow) CliThreadCommand)
2012-12-16 21:57:53 -05:00
&& _RegisterCommand("threads", new(std::nothrow) CliThreadsCommand)
&& _RegisterCommand("variables",
new(std::nothrow) CliVariablesCommand)) {
2012-12-19 22:27:45 -05:00
fCommands.SortItems(&_CompareCommandEntries);
return B_OK;
}
return B_NO_MEMORY;
}
bool
CommandLineUserInterface::_RegisterCommand(const BString& name,
CliCommand* command)
{
BReference<CliCommand> commandReference(command, true);
if (name.IsEmpty() || command == NULL)
return false;
2012-12-19 22:27:45 -05:00
BString nextName;
int32 startIndex = 0;
int32 spaceIndex;
do {
spaceIndex = name.FindFirst(' ', startIndex);
if (spaceIndex == B_ERROR)
spaceIndex = name.Length();
name.CopyInto(nextName, startIndex, spaceIndex - startIndex);
CommandEntry* entry = new(std::nothrow) CommandEntry(nextName,
command);
if (entry == NULL || !fCommands.AddItem(entry)) {
delete entry;
return false;
}
startIndex = spaceIndex + 1;
} while (startIndex < name.Length());
return true;
}
void
CommandLineUserInterface::_ExecuteCommand(int argc, const char* const* argv)
{
2012-08-05 00:57:57 +02:00
CommandEntry* commandEntry = _FindCommand(argv[0]);
if (commandEntry != NULL)
commandEntry->Command()->Execute(argc, argv, fContext);
}
CommandLineUserInterface::CommandEntry*
CommandLineUserInterface::_FindCommand(const char* commandName)
{
size_t commandNameLength = strlen(commandName);
2012-08-05 00:57:57 +02:00
// try to find an exact match first
CommandEntry* commandEntry = NULL;
for (int32 i = 0; CommandEntry* entry = fCommands.ItemAt(i); i++) {
2012-08-05 00:57:57 +02:00
if (entry->Name() == commandName) {
commandEntry = entry;
break;
}
}
2012-08-05 00:57:57 +02:00
// If nothing found yet, try partial matches, but only, if they are
// unambiguous.
if (commandEntry == NULL) {
for (int32 i = 0; CommandEntry* entry = fCommands.ItemAt(i); i++) {
if (entry->Name().Compare(commandName, commandNameLength) == 0) {
if (commandEntry != NULL) {
printf("Error: Ambiguous command \"%s\".\n", commandName);
return NULL;
}
commandEntry = entry;
}
}
}
2012-08-05 00:57:57 +02:00
if (commandEntry == NULL) {
printf("Error: Unknown command \"%s\".\n", commandName);
return NULL;
}
2012-08-05 00:57:57 +02:00
return commandEntry;
}
void
2012-08-05 00:57:57 +02:00
CommandLineUserInterface::_PrintHelp(const char* commandName)
{
2012-08-05 00:57:57 +02:00
// If a command name is given, print the usage for that one.
if (commandName != NULL) {
CommandEntry* commandEntry = _FindCommand(commandName);
if (commandEntry != NULL)
commandEntry->Command()->PrintUsage(commandEntry->Name().String());
return;
}
// No command name given -- print a list of all commands.
// determine longest command name
int32 longestCommandName = 0;
for (int32 i = 0; CommandEntry* entry = fCommands.ItemAt(i); i++) {
longestCommandName
= std::max(longestCommandName, entry->Name().Length());
}
// print the command list
for (int32 i = 0; CommandEntry* entry = fCommands.ItemAt(i); i++) {
printf("%*s - %s\n", (int)longestCommandName, entry->Name().String(),
entry->Command()->Summary());
}
}
2012-12-19 22:27:45 -05:00
/*static */
int
CommandLineUserInterface::_CompareCommandEntries(const CommandEntry* command1,
const CommandEntry* command2)
{
return ::Compare(command1->Name(), command2->Name());
}
2013-12-07 11:15:17 -05:00
bool
CommandLineUserInterface::_ReportTargetThreadStopNeeded() const
{
if (fReportTargetThread < 0)
return false;
Team* team = fContext.GetTeam();
AutoLocker<Team> teamLocker(team);
Thread* thread = team->ThreadByID(fReportTargetThread);
if (thread == NULL)
return false;
return thread->State() != THREAD_STATE_STOPPED;
}
2013-12-09 18:39:50 -05:00
void
CommandLineUserInterface::_SubmitSaveReport()
{
ArgumentVector args;
char buffer[256];
const char* parseErrorLocation;
snprintf(buffer, sizeof(buffer), "save-report %s",
fReportPath != NULL ? fReportPath : "");
args.Parse(buffer, &parseErrorLocation);
_ExecuteCommand(args.ArgumentCount(), args.Arguments());
}