Debugger: Refactor non-interactive report handling.

- Add dedicated ReportDebugger application class for the case where we're
  asked to do nothing more than save a report. Also add a corresponding
  UserInterface subclass whose sole purpose is to take those necessary
  actions and then exit.
- When the debugger is invoked via the --save-report option, we now start
  via the aforementioned report/interface rather than piggybacking on the
  CLI.
- Clean up CommandLineUserInterface/CliContext to remove handling for the
  report saving option.

Should hopefully resolve #12155.
This commit is contained in:
Rene Gollent
2015-08-22 00:21:07 -04:00
parent 3667f6efdb
commit 2d9d01e2e8
8 changed files with 416 additions and 114 deletions
+104 -4
View File
@@ -1,6 +1,6 @@
/* /*
* Copyright 2009-2012, Ingo Weinhold, [email protected]. * Copyright 2009-2012, Ingo Weinhold, [email protected].
* Copyright 2011-2014, Rene Gollent, [email protected]. * Copyright 2011-2015, Rene Gollent, [email protected].
* Distributed under the terms of the MIT License. * Distributed under the terms of the MIT License.
*/ */
@@ -26,6 +26,7 @@
#include "GraphicalUserInterface.h" #include "GraphicalUserInterface.h"
#include "ImageDebugLoadingStateHandlerRoster.h" #include "ImageDebugLoadingStateHandlerRoster.h"
#include "MessageCodes.h" #include "MessageCodes.h"
#include "ReportUserInterface.h"
#include "SettingsManager.h" #include "SettingsManager.h"
#include "SignalSet.h" #include "SignalSet.h"
#include "StartTeamWindow.h" #include "StartTeamWindow.h"
@@ -143,7 +144,6 @@ parse_arguments(int argc, const char* const* argv, bool noOutput,
case 's': case 's':
{ {
options.useCLI = true;
options.saveReport = true; options.saveReport = true;
options.reportPath = optarg; options.reportPath = optarg;
break; break;
@@ -393,6 +393,21 @@ private:
}; };
class ReportDebugger : private TeamDebugger::Listener {
public:
ReportDebugger();
~ReportDebugger();
bool Run(const Options& options);
private:
// TeamDebugger::Listener
virtual void TeamDebuggerStarted(TeamDebugger* debugger);
virtual void TeamDebuggerRestartRequested(
TeamDebugger* debugger);
virtual void TeamDebuggerQuit(TeamDebugger* debugger);
};
// #pragma mark - Debugger application class // #pragma mark - Debugger application class
@@ -723,8 +738,7 @@ CliDebugger::Run(const Options& options)
// create the command line UI // create the command line UI
CommandLineUserInterface* userInterface CommandLineUserInterface* userInterface
= new(std::nothrow) CommandLineUserInterface(options.saveReport, = new(std::nothrow) CommandLineUserInterface();
options.reportPath, options.thread);
if (userInterface == NULL) { if (userInterface == NULL) {
fprintf(stderr, "Error: Out of memory!\n"); fprintf(stderr, "Error: Out of memory!\n");
return false; return false;
@@ -774,6 +788,89 @@ CliDebugger::TeamDebuggerQuit(TeamDebugger* debugger)
} }
// #pragma mark - ReportDebugger
ReportDebugger::ReportDebugger()
{
}
ReportDebugger::~ReportDebugger()
{
}
bool
ReportDebugger::Run(const Options& options)
{
// initialize global objects and settings manager
status_t error = global_init();
if (error != B_OK) {
fprintf(stderr, "Error: Global initialization failed: %s\n",
strerror(error));
return false;
}
SettingsManager settingsManager;
error = settingsManager.Init();
if (error != B_OK) {
fprintf(stderr, "Error: Settings manager initialization failed: "
"%s\n", strerror(error));
return false;
}
// create the report UI
ReportUserInterface* userInterface
= new(std::nothrow) ReportUserInterface(options.thread, options.reportPath);
if (userInterface == NULL) {
fprintf(stderr, "Error: Out of memory!\n");
return false;
}
BReference<UserInterface> userInterfaceReference(userInterface, true);
// get/run the program to be debugged and start the team debugger
DebuggedProgramInfo programInfo;
if (!get_debugged_program(options, programInfo))
return false;
TeamDebugger* teamDebugger = start_team_debugger(programInfo.team,
&settingsManager, this, programInfo.thread,
programInfo.commandLineArgc, programInfo.commandLineArgv,
programInfo.stopInMain, userInterface);
if (teamDebugger == NULL)
return false;
thread_id teamDebuggerThread = teamDebugger->Thread();
// run the input loop
userInterface->Run();
// wait for the team debugger thread to terminate
wait_for_thread(teamDebuggerThread, NULL);
return true;
}
void
ReportDebugger::TeamDebuggerStarted(TeamDebugger* debugger)
{
}
void
ReportDebugger::TeamDebuggerRestartRequested(TeamDebugger* debugger)
{
}
void
ReportDebugger::TeamDebuggerQuit(TeamDebugger* debugger)
{
}
// #pragma mark - // #pragma mark -
@@ -792,6 +889,9 @@ main(int argc, const char* const* argv)
if (options.useCLI) { if (options.useCLI) {
CliDebugger debugger; CliDebugger debugger;
return debugger.Run(options) ? 0 : 1; return debugger.Run(options) ? 0 : 1;
} else if (options.saveReport) {
ReportDebugger debugger;
return debugger.Run(options) ? 0 : 1;
} }
Debugger app; Debugger app;
+4
View File
@@ -46,6 +46,7 @@ SEARCH_SOURCE += [ FDirName $(SUBDIR) user_interface gui teams_window ] ;
SEARCH_SOURCE += [ FDirName $(SUBDIR) user_interface gui utility_windows ] ; SEARCH_SOURCE += [ FDirName $(SUBDIR) user_interface gui utility_windows ] ;
SEARCH_SOURCE += [ FDirName $(SUBDIR) user_interface gui util ] ; SEARCH_SOURCE += [ FDirName $(SUBDIR) user_interface gui util ] ;
SEARCH_SOURCE += [ FDirName $(SUBDIR) user_interface gui value ] ; SEARCH_SOURCE += [ FDirName $(SUBDIR) user_interface gui value ] ;
SEARCH_SOURCE += [ FDirName $(SUBDIR) user_interface report ] ;
SEARCH_SOURCE += [ FDirName $(SUBDIR) user_interface util ] ; SEARCH_SOURCE += [ FDirName $(SUBDIR) user_interface util ] ;
SEARCH_SOURCE += [ FDirName $(SUBDIR) util ] ; SEARCH_SOURCE += [ FDirName $(SUBDIR) util ] ;
SEARCH_SOURCE += [ FDirName $(SUBDIR) value ] ; SEARCH_SOURCE += [ FDirName $(SUBDIR) value ] ;
@@ -321,6 +322,9 @@ local sources =
TableCellValueRenderer.cpp TableCellValueRenderer.cpp
TableCellValueRendererUtils.cpp TableCellValueRendererUtils.cpp
# user_interface/report
ReportUserInterface.cpp
# user_interface/util # user_interface/util
UiUtils.cpp UiUtils.cpp
@@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2014, Rene Gollent, [email protected]. * Copyright 2012-2015, Rene Gollent, [email protected].
* Copyright 2012, Ingo Weinhold, [email protected]. * Copyright 2012, Ingo Weinhold, [email protected].
* Distributed under the terms of the MIT License. * Distributed under the terms of the MIT License.
*/ */
@@ -98,7 +98,6 @@ CliContext::CliContext()
fInputLoopWaitingForEvents(0), fInputLoopWaitingForEvents(0),
fEventsOccurred(0), fEventsOccurred(0),
fInputLoopWaiting(false), fInputLoopWaiting(false),
fInteractive(true),
fTerminating(false), fTerminating(false),
fCurrentThread(NULL), fCurrentThread(NULL),
fCurrentStackTrace(NULL), fCurrentStackTrace(NULL),
@@ -219,13 +218,6 @@ CliContext::Terminating()
} }
void
CliContext::SetInteractive(bool interactive)
{
fInteractive = interactive;
}
thread_id thread_id
CliContext::CurrentThreadID() const CliContext::CurrentThreadID() const
{ {
@@ -415,6 +407,7 @@ CliContext::ProcessPendingEvents()
switch (event->Type()) { switch (event->Type()) {
case EVENT_QUIT: case EVENT_QUIT:
case EVENT_DEBUG_REPORT_CHANGED:
case EVENT_USER_INTERRUPT: case EVENT_USER_INTERRUPT:
break; break;
case EVENT_THREAD_ADDED: case EVENT_THREAD_ADDED:
@@ -453,13 +446,6 @@ CliContext::ProcessPendingEvents()
if (fExpressionValue != NULL) if (fExpressionValue != NULL)
fExpressionValue->AcquireReference(); fExpressionValue->AcquireReference();
break; break;
case EVENT_DEBUG_REPORT_CHANGED:
if (!IsInteractive()) {
Terminating();
QuitSession(true);
}
break;
} }
} }
} }
@@ -1,6 +1,6 @@
/* /*
* Copyright 2012, Ingo Weinhold, ingo_weinhold@gmx.de. * Copyright 2012, Ingo Weinhold, ingo_weinhold@gmx.de.
* Copyright 2014, Rene Gollent, rene@gollent.com. * Copyright 2014-2015, Rene Gollent, rene@gollent.com.
* Distributed under the terms of the MIT License. * Distributed under the terms of the MIT License.
*/ */
#ifndef CLI_CONTEXT_H #ifndef CLI_CONTEXT_H
@@ -57,9 +57,6 @@ public:
bool IsTerminating() const { return fTerminating; } bool IsTerminating() const { return fTerminating; }
bool IsInteractive() const { return fInteractive; }
void SetInteractive(bool interactive);
// service methods for the input loop thread follow // service methods for the input loop thread follow
Team* GetTeam() const { return fTeam; } Team* GetTeam() const { return fTeam; }
@@ -150,7 +147,6 @@ private:
uint32 fInputLoopWaitingForEvents; uint32 fInputLoopWaitingForEvents;
uint32 fEventsOccurred; uint32 fEventsOccurred;
bool fInputLoopWaiting; bool fInputLoopWaiting;
bool fInteractive;
volatile bool fTerminating; volatile bool fTerminating;
Thread* fCurrentThread; Thread* fCurrentThread;
@@ -1,5 +1,5 @@
/* /*
* Copyright 2011-2014, Rene Gollent, rene@gollent.com. * Copyright 2011-2015, Rene Gollent, rene@gollent.com.
* Copyright 2012, Ingo Weinhold, ingo_weinhold@gmx.de. * Copyright 2012, Ingo Weinhold, ingo_weinhold@gmx.de.
* Distributed under the terms of the MIT License. * Distributed under the terms of the MIT License.
*/ */
@@ -93,13 +93,9 @@ private:
// #pragma mark - CommandLineUserInterface // #pragma mark - CommandLineUserInterface
CommandLineUserInterface::CommandLineUserInterface(bool saveReport, CommandLineUserInterface::CommandLineUserInterface()
const char* reportPath, thread_id reportTargetThread)
: :
fCommands(20, true), fCommands(20, true),
fReportPath(reportPath),
fSaveReport(saveReport),
fReportTargetThread(reportTargetThread),
fShowSemaphore(-1), fShowSemaphore(-1),
fShown(false), fShown(false),
fTerminating(false) fTerminating(false)
@@ -128,8 +124,6 @@ CommandLineUserInterface::Init(Team* team, UserInterfaceListener* listener)
if (error != B_OK) if (error != B_OK)
return error; return error;
fContext.SetInteractive(!fSaveReport);
error = _RegisterCommands(); error = _RegisterCommands();
if (error != B_OK) if (error != B_OK)
return error; return error;
@@ -138,8 +132,6 @@ CommandLineUserInterface::Init(Team* team, UserInterfaceListener* listener)
if (fShowSemaphore < 0) if (fShowSemaphore < 0)
return fShowSemaphore; return fShowSemaphore;
team->AddListener(this);
return B_OK; return B_OK;
} }
@@ -176,9 +168,7 @@ CommandLineUserInterface::Terminate()
bool bool
CommandLineUserInterface::IsInteractive() const CommandLineUserInterface::IsInteractive() const
{ {
// if we were invoked solely for the purpose of saving a crash report, return true;
// then we're not taking user input into account.
return !fSaveReport;
} }
@@ -237,43 +227,12 @@ CommandLineUserInterface::Run()
if (error != B_OK) if (error != B_OK)
return; return;
if (fSaveReport) {
ArgumentVector args;
char buffer[256];
const char* parseErrorLocation;
if (_ReportTargetThreadStopNeeded()) {
snprintf(buffer, sizeof(buffer), "stop %" B_PRId32,
fReportTargetThread);
args.Parse(buffer, &parseErrorLocation);
_ExecuteCommand(args.ArgumentCount(), args.Arguments());
} else
_SubmitSaveReport();
}
_InputLoop(); _InputLoop();
// Release the Show() semaphore to signal Terminate(). // Release the Show() semaphore to signal Terminate().
release_sem(fShowSemaphore); release_sem(fShowSemaphore);
} }
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();
}
}
}
/*static*/ status_t /*static*/ status_t
CommandLineUserInterface::_InputLoopEntry(void* data) CommandLineUserInterface::_InputLoopEntry(void* data)
{ {
@@ -473,32 +432,3 @@ CommandLineUserInterface::_CompareCommandEntries(const CommandEntry* command1,
{ {
return ::Compare(command1->Name(), command2->Name()); return ::Compare(command1->Name(), command2->Name());
} }
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;
}
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());
}
@@ -1,5 +1,5 @@
/* /*
* Copyright 2011-2014, Rene Gollent, rene@gollent.com. * Copyright 2011-2015, Rene Gollent, rene@gollent.com.
* Copyright 2012, Ingo Weinhold, ingo_weinhold@gmx.de. * Copyright 2012, Ingo Weinhold, ingo_weinhold@gmx.de.
* Distributed under the terms of the MIT License. * Distributed under the terms of the MIT License.
*/ */
@@ -17,12 +17,9 @@
class CliCommand; class CliCommand;
class CommandLineUserInterface : public UserInterface, class CommandLineUserInterface : public UserInterface {
public ::Team::Listener {
public: public:
CommandLineUserInterface(bool saveReport, CommandLineUserInterface();
const char* reportPath,
thread_id reportTargetThread);
virtual ~CommandLineUserInterface(); virtual ~CommandLineUserInterface();
virtual const char* ID() const; virtual const char* ID() const;
@@ -54,10 +51,6 @@ public:
// everything has been set up. Enters the // everything has been set up. Enters the
// input loop. // input loop.
// Team::Listener
virtual void ThreadStateChanged(
const Team::ThreadEvent& event);
private: private:
struct CommandEntry; struct CommandEntry;
typedef BObjectList<CommandEntry> CommandList; typedef BObjectList<CommandEntry> CommandList;
@@ -82,15 +75,9 @@ private:
const CommandEntry* command1, const CommandEntry* command1,
const CommandEntry* command2); const CommandEntry* command2);
bool _ReportTargetThreadStopNeeded() const;
void _SubmitSaveReport();
private: private:
CliContext fContext; CliContext fContext;
CommandList fCommands; CommandList fCommands;
const char* fReportPath;
bool fSaveReport;
thread_id fReportTargetThread;
sem_id fShowSemaphore; sem_id fShowSemaphore;
bool fShown; bool fShown;
volatile bool fTerminating; volatile bool fTerminating;
@@ -0,0 +1,233 @@
/*
* Copyright 2015, Rene Gollent, rene@gollent.com.
* Distributed under the terms of the MIT License.
*/
#include "ReportUserInterface.h"
#include <stdio.h>
#include <Entry.h>
#include <FindDirectory.h>
#include <Path.h>
#include <AutoLocker.h>
#include "MessageCodes.h"
#include "UiUtils.h"
ReportUserInterface::ReportUserInterface(thread_id targetThread,
const char* reportPath)
:
fTeam(NULL),
fListener(NULL),
fTargetThread(targetThread),
fReportPath(reportPath),
fShowSemaphore(-1),
fReportSemaphore(-1),
fShown(false),
fTerminating(false)
{
}
ReportUserInterface::~ReportUserInterface()
{
if (fShowSemaphore >= 0)
delete_sem(fShowSemaphore);
fTeam->RemoveListener(this);
}
const char*
ReportUserInterface::ID() const
{
return "ReportUserInterface";
}
status_t
ReportUserInterface::Init(Team* team, UserInterfaceListener* listener)
{
fShowSemaphore = create_sem(0, "show report");
if (fShowSemaphore < 0)
return fShowSemaphore;
fReportSemaphore = create_sem(0, "report generator wait");
if (fReportSemaphore < 0)
return fReportSemaphore;
fTeam = team;
fListener = listener;
fTeam->AddListener(this);
return B_OK;
}
void
ReportUserInterface::Show()
{
fShown = true;
release_sem(fShowSemaphore);
}
void
ReportUserInterface::Terminate()
{
fTerminating = true;
}
bool
ReportUserInterface::IsInteractive() const
{
return false;
}
status_t
ReportUserInterface::LoadSettings(const TeamUiSettings* settings)
{
return B_OK;
}
status_t
ReportUserInterface::SaveSettings(TeamUiSettings*& settings) const
{
return B_OK;
}
void
ReportUserInterface::NotifyUser(const char* title, const char* message,
user_notification_type type)
{
}
void
ReportUserInterface::NotifyBackgroundWorkStatus(const char* message)
{
}
int32
ReportUserInterface::SynchronouslyAskUser(const char* title,
const char* message, const char* choice1, const char* choice2,
const char* choice3)
{
return -1;
}
status_t
ReportUserInterface::SynchronouslyAskUserForFile(entry_ref* _ref)
{
return B_UNSUPPORTED;
}
void
ReportUserInterface::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;
bool waitNeeded = false;
if (fTargetThread > 0) {
AutoLocker< ::Team> teamLocker(fTeam);
::Thread* thread = fTeam->ThreadByID(fTargetThread);
if (thread == NULL)
waitNeeded = true;
else if (thread->State() != THREAD_STATE_STOPPED) {
waitNeeded = true;
fListener->ThreadActionRequested(fTargetThread, MSG_THREAD_STOP);
}
}
if (waitNeeded) {
do {
error = acquire_sem(fShowSemaphore);
} while (error == B_INTERRUPTED);
if (error != B_OK)
return;
}
entry_ref ref;
if (fReportPath != NULL && fReportPath[0] == '/') {
error = get_ref_for_path(fReportPath, &ref);
} else {
char filename[B_FILE_NAME_LENGTH];
if (fReportPath != NULL)
strlcpy(filename, fReportPath, sizeof(filename));
else
UiUtils::ReportNameForTeam(fTeam, filename, sizeof(filename));
BPath path;
error = find_directory(B_DESKTOP_DIRECTORY, &path);
if (error == B_OK)
error = path.Append(filename);
if (error == B_OK)
error = get_ref_for_path(path.Path(), &ref);
}
if (error != B_OK)
printf("Unable to get ref for report path %s\n", strerror(error));
else {
fListener->DebugReportRequested(&ref);
do {
error = acquire_sem(fReportSemaphore);
} while (error == B_INTERRUPTED);
}
fListener->UserInterfaceQuitRequested(
UserInterfaceListener::QUIT_OPTION_ASK_KILL_TEAM);
}
void
ReportUserInterface::ThreadAdded(const Team::ThreadEvent& event)
{
::Thread* thread = event.GetThread();
if (thread->ID() != fTargetThread)
return;
if (thread->State() != THREAD_STATE_STOPPED)
fListener->ThreadActionRequested(thread->ID(), MSG_THREAD_STOP);
else
release_sem(fShowSemaphore);
}
void
ReportUserInterface::ThreadStateChanged(const Team::ThreadEvent& event)
{
::Thread* thread = event.GetThread();
if (thread->ID() != fTargetThread)
return;
else if (thread->State() == THREAD_STATE_STOPPED)
release_sem(fShowSemaphore);
}
void
ReportUserInterface::DebugReportChanged(const Team::DebugReportEvent& event)
{
printf("Debug report saved to %s\n", event.GetReportPath());
release_sem(fReportSemaphore);
}
@@ -0,0 +1,66 @@
/*
* Copyright 2015, Rene Gollent, rene@gollent.com.
* Distributed under the terms of the MIT License.
*/
#ifndef REPORT_USER_INTERFACE_H
#define REPORT_USER_INTERFACE_H
#include <ObjectList.h>
#include <String.h>
#include "UserInterface.h"
#include "Team.h"
class ReportUserInterface : public UserInterface,
private Team::Listener {
public:
ReportUserInterface(thread_id targetThread,
const char* reportPath);
virtual ~ReportUserInterface();
virtual const char* ID() const;
virtual status_t Init(Team* team,
UserInterfaceListener* listener);
virtual void Show();
virtual void Terminate();
virtual bool IsInteractive() const;
virtual status_t LoadSettings(const TeamUiSettings* settings);
virtual status_t SaveSettings(TeamUiSettings*& settings) const;
virtual void NotifyUser(const char* title,
const char* message,
user_notification_type type);
virtual void NotifyBackgroundWorkStatus(
const char* message);
virtual int32 SynchronouslyAskUser(const char* title,
const char* message, const char* choice1,
const char* choice2, const char* choice3);
virtual status_t SynchronouslyAskUserForFile(entry_ref* _ref);
void Run();
// Team::Listener
virtual void ThreadAdded(const Team::ThreadEvent& event);
virtual void ThreadStateChanged(
const Team::ThreadEvent& event);
virtual void DebugReportChanged(
const Team::DebugReportEvent& event);
private:
::Team* fTeam;
UserInterfaceListener* fListener;
thread_id fTargetThread;
const char* fReportPath;
sem_id fShowSemaphore;
sem_id fReportSemaphore;
bool fShown;
volatile bool fTerminating;
};
#endif // REPORT_USER_INTERFACE_H