to the debugger from GUI windows. Previously there was one global debugger
instance that could be accessed that had its own command interpreter and
current state (current target/process/thread/frame). When a GUI debugger
was attached, if it opened more than one window that each had a console
window, there were issues where the last one to setup the global debugger
object won and got control of the debugger.
To avoid this we now create instances of the lldb_private::Debugger that each
has its own state:
- target list for targets the debugger instance owns
- current process/thread/frame
- its own command interpreter
- its own input, output and error file handles to avoid conflicts
- its own input reader stack
So now clients should call:
SBDebugger::Initialize(); // (static function)
SBDebugger debugger (SBDebugger::Create());
// Use which ever file handles you wish
debugger.SetErrorFileHandle (stderr, false);
debugger.SetOutputFileHandle (stdout, false);
debugger.SetInputFileHandle (stdin, true);
// main loop
SBDebugger::Terminate(); // (static function)
SBDebugger::Initialize() and SBDebugger::Terminate() are ref counted to
ensure nothing gets destroyed too early when multiple clients might be
attached.
Cleaned up the command interpreter and the CommandObject and all subclasses
to take more appropriate arguments.
llvm-svn: 106615
494 lines
12 KiB
C++
494 lines
12 KiB
C++
//===-- Debugger.cpp --------------------------------------------*- C++ -*-===//
|
|
//
|
|
// The LLVM Compiler Infrastructure
|
|
//
|
|
// This file is distributed under the University of Illinois Open Source
|
|
// License. See LICENSE.TXT for details.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "lldb/lldb-private.h"
|
|
#include "lldb/Core/ConnectionFileDescriptor.h"
|
|
#include "lldb/Core/Debugger.h"
|
|
#include "lldb/Core/InputReader.h"
|
|
#include "lldb/Core/State.h"
|
|
#include "lldb/Core/Timer.h"
|
|
#include "lldb/Interpreter/CommandInterpreter.h"
|
|
#include "lldb/Target/TargetList.h"
|
|
#include "lldb/Target/Process.h"
|
|
#include "lldb/Target/Thread.h"
|
|
|
|
|
|
using namespace lldb;
|
|
using namespace lldb_private;
|
|
|
|
static uint32_t g_shared_debugger_refcount = 0;
|
|
|
|
void
|
|
Debugger::Initialize ()
|
|
{
|
|
if (g_shared_debugger_refcount == 0)
|
|
lldb_private::Initialize();
|
|
g_shared_debugger_refcount++;
|
|
}
|
|
|
|
void
|
|
Debugger::Terminate ()
|
|
{
|
|
if (g_shared_debugger_refcount > 0)
|
|
{
|
|
g_shared_debugger_refcount--;
|
|
if (g_shared_debugger_refcount == 0)
|
|
{
|
|
lldb_private::WillTerminate();
|
|
lldb_private::Terminate();
|
|
}
|
|
}
|
|
}
|
|
|
|
typedef std::vector<DebuggerSP> DebuggerList;
|
|
|
|
static Mutex &
|
|
GetDebuggerListMutex ()
|
|
{
|
|
static Mutex g_mutex(Mutex::eMutexTypeRecursive);
|
|
return g_mutex;
|
|
}
|
|
|
|
static DebuggerList &
|
|
GetDebuggerList()
|
|
{
|
|
// hide the static debugger list inside a singleton accessor to avoid
|
|
// global init contructors
|
|
static DebuggerList g_list;
|
|
return g_list;
|
|
}
|
|
|
|
|
|
DebuggerSP
|
|
Debugger::CreateInstance ()
|
|
{
|
|
DebuggerSP debugger_sp (new Debugger);
|
|
// Scope for locker
|
|
{
|
|
Mutex::Locker locker (GetDebuggerListMutex ());
|
|
GetDebuggerList().push_back(debugger_sp);
|
|
}
|
|
return debugger_sp;
|
|
}
|
|
|
|
lldb::DebuggerSP
|
|
Debugger::GetSP ()
|
|
{
|
|
lldb::DebuggerSP debugger_sp;
|
|
|
|
Mutex::Locker locker (GetDebuggerListMutex ());
|
|
DebuggerList &debugger_list = GetDebuggerList();
|
|
DebuggerList::iterator pos, end = debugger_list.end();
|
|
for (pos = debugger_list.begin(); pos != end; ++pos)
|
|
{
|
|
if ((*pos).get() == this)
|
|
{
|
|
debugger_sp = *pos;
|
|
break;
|
|
}
|
|
}
|
|
return debugger_sp;
|
|
}
|
|
|
|
|
|
TargetSP
|
|
Debugger::FindTargetWithProcessID (lldb::pid_t pid)
|
|
{
|
|
lldb::TargetSP target_sp;
|
|
Mutex::Locker locker (GetDebuggerListMutex ());
|
|
DebuggerList &debugger_list = GetDebuggerList();
|
|
DebuggerList::iterator pos, end = debugger_list.end();
|
|
for (pos = debugger_list.begin(); pos != end; ++pos)
|
|
{
|
|
target_sp = (*pos)->GetTargetList().FindTargetWithProcessID (pid);
|
|
if (target_sp)
|
|
break;
|
|
}
|
|
return target_sp;
|
|
}
|
|
|
|
|
|
Debugger::Debugger () :
|
|
m_input_comm("debugger.input"),
|
|
m_input_file (),
|
|
m_output_file (),
|
|
m_error_file (),
|
|
m_target_list (),
|
|
m_listener ("lldb.Debugger"),
|
|
m_source_manager (),
|
|
m_command_interpreter_ap (new CommandInterpreter (*this, eScriptLanguageDefault, false)),
|
|
m_exe_ctx (),
|
|
m_input_readers (),
|
|
m_input_reader_data ()
|
|
{
|
|
m_command_interpreter_ap->Initialize ();
|
|
}
|
|
|
|
Debugger::~Debugger ()
|
|
{
|
|
int num_targets = m_target_list.GetNumTargets();
|
|
for (int i = 0; i < num_targets; i++)
|
|
{
|
|
ProcessSP process_sp (m_target_list.GetTargetAtIndex (i)->GetProcessSP());
|
|
if (process_sp)
|
|
process_sp->Destroy();
|
|
}
|
|
DisconnectInput();
|
|
}
|
|
|
|
|
|
bool
|
|
Debugger::GetAsyncExecution ()
|
|
{
|
|
return !m_command_interpreter_ap->GetSynchronous();
|
|
}
|
|
|
|
void
|
|
Debugger::SetAsyncExecution (bool async_execution)
|
|
{
|
|
m_command_interpreter_ap->SetSynchronous (!async_execution);
|
|
}
|
|
|
|
void
|
|
Debugger::DisconnectInput()
|
|
{
|
|
m_input_comm.Clear ();
|
|
}
|
|
|
|
void
|
|
Debugger::SetInputFileHandle (FILE *fh, bool tranfer_ownership)
|
|
{
|
|
m_input_file.SetFileHandle (fh, tranfer_ownership);
|
|
if (m_input_file.GetFileHandle() == NULL)
|
|
m_input_file.SetFileHandle (stdin, false);
|
|
|
|
// Disconnect from any old connection if we had one
|
|
m_input_comm.Disconnect ();
|
|
m_input_comm.SetConnection (new ConnectionFileDescriptor (::fileno (GetInputFileHandle()), true));
|
|
m_input_comm.SetReadThreadBytesReceivedCallback (Debugger::DispatchInputCallback, this);
|
|
|
|
Error error;
|
|
if (m_input_comm.StartReadThread (&error) == false)
|
|
{
|
|
FILE *err_fh = GetErrorFileHandle();
|
|
if (err_fh)
|
|
{
|
|
::fprintf (err_fh, "error: failed to main input read thread: %s", error.AsCString() ? error.AsCString() : "unkown error");
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
FILE *
|
|
Debugger::GetInputFileHandle ()
|
|
{
|
|
return m_input_file.GetFileHandle();
|
|
}
|
|
|
|
|
|
void
|
|
Debugger::SetOutputFileHandle (FILE *fh, bool tranfer_ownership)
|
|
{
|
|
m_output_file.SetFileHandle (fh, tranfer_ownership);
|
|
if (m_output_file.GetFileHandle() == NULL)
|
|
m_output_file.SetFileHandle (stdin, false);
|
|
}
|
|
|
|
FILE *
|
|
Debugger::GetOutputFileHandle ()
|
|
{
|
|
return m_output_file.GetFileHandle();
|
|
}
|
|
|
|
void
|
|
Debugger::SetErrorFileHandle (FILE *fh, bool tranfer_ownership)
|
|
{
|
|
m_error_file.SetFileHandle (fh, tranfer_ownership);
|
|
if (m_error_file.GetFileHandle() == NULL)
|
|
m_error_file.SetFileHandle (stdin, false);
|
|
}
|
|
|
|
|
|
FILE *
|
|
Debugger::GetErrorFileHandle ()
|
|
{
|
|
return m_error_file.GetFileHandle();
|
|
}
|
|
|
|
CommandInterpreter &
|
|
Debugger::GetCommandInterpreter ()
|
|
{
|
|
assert (m_command_interpreter_ap.get());
|
|
return *m_command_interpreter_ap;
|
|
}
|
|
|
|
Listener &
|
|
Debugger::GetListener ()
|
|
{
|
|
return m_listener;
|
|
}
|
|
|
|
|
|
TargetSP
|
|
Debugger::GetCurrentTarget ()
|
|
{
|
|
return m_target_list.GetCurrentTarget ();
|
|
}
|
|
|
|
ExecutionContext
|
|
Debugger::GetCurrentExecutionContext ()
|
|
{
|
|
ExecutionContext exe_ctx;
|
|
exe_ctx.Clear();
|
|
|
|
lldb::TargetSP target_sp = GetCurrentTarget();
|
|
exe_ctx.target = target_sp.get();
|
|
|
|
if (target_sp)
|
|
{
|
|
exe_ctx.process = target_sp->GetProcessSP().get();
|
|
if (exe_ctx.process && exe_ctx.process->IsRunning() == false)
|
|
{
|
|
exe_ctx.thread = exe_ctx.process->GetThreadList().GetCurrentThread().get();
|
|
if (exe_ctx.thread == NULL)
|
|
exe_ctx.thread = exe_ctx.process->GetThreadList().GetThreadAtIndex(0).get();
|
|
if (exe_ctx.thread)
|
|
{
|
|
exe_ctx.frame = exe_ctx.thread->GetCurrentFrame().get();
|
|
if (exe_ctx.frame == NULL)
|
|
exe_ctx.frame = exe_ctx.thread->GetStackFrameAtIndex (0).get();
|
|
}
|
|
}
|
|
}
|
|
return exe_ctx;
|
|
|
|
}
|
|
|
|
SourceManager &
|
|
Debugger::GetSourceManager ()
|
|
{
|
|
return m_source_manager;
|
|
}
|
|
|
|
|
|
TargetList&
|
|
Debugger::GetTargetList ()
|
|
{
|
|
return m_target_list;
|
|
}
|
|
|
|
void
|
|
Debugger::DispatchInputCallback (void *baton, const void *bytes, size_t bytes_len)
|
|
{
|
|
((Debugger *)baton)->DispatchInput ((char *)bytes, bytes_len);
|
|
}
|
|
|
|
|
|
void
|
|
Debugger::DispatchInput (const char *bytes, size_t bytes_len)
|
|
{
|
|
if (bytes == NULL || bytes_len == 0)
|
|
return;
|
|
|
|
// TODO: implement the STDIO to the process as an input reader...
|
|
TargetSP target = GetCurrentTarget();
|
|
if (target.get() != NULL)
|
|
{
|
|
ProcessSP process_sp = target->GetProcessSP();
|
|
if (process_sp.get() != NULL
|
|
&& StateIsRunningState (process_sp->GetState()))
|
|
{
|
|
Error error;
|
|
if (process_sp->PutSTDIN (bytes, bytes_len, error) == bytes_len)
|
|
return;
|
|
}
|
|
}
|
|
|
|
WriteToDefaultReader (bytes, bytes_len);
|
|
}
|
|
|
|
void
|
|
Debugger::WriteToDefaultReader (const char *bytes, size_t bytes_len)
|
|
{
|
|
if (bytes && bytes_len)
|
|
m_input_reader_data.append (bytes, bytes_len);
|
|
|
|
if (m_input_reader_data.empty())
|
|
return;
|
|
|
|
while (!m_input_readers.empty() && !m_input_reader_data.empty())
|
|
{
|
|
while (CheckIfTopInputReaderIsDone ())
|
|
/* Do nothing. */;
|
|
|
|
// Get the input reader from the top of the stack
|
|
InputReaderSP reader_sp(m_input_readers.top());
|
|
|
|
if (!reader_sp)
|
|
break;
|
|
|
|
size_t bytes_handled = reader_sp->HandleRawBytes (m_input_reader_data.data(),
|
|
m_input_reader_data.size());
|
|
if (bytes_handled)
|
|
{
|
|
m_input_reader_data.erase (0, bytes_handled);
|
|
}
|
|
else
|
|
{
|
|
// No bytes were handled, we might not have reached our
|
|
// granularity, just return and wait for more data
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Flush out any input readers that are donesvn
|
|
while (CheckIfTopInputReaderIsDone ())
|
|
/* Do nothing. */;
|
|
|
|
}
|
|
|
|
void
|
|
Debugger::PushInputReader (const InputReaderSP& reader_sp)
|
|
{
|
|
if (!reader_sp)
|
|
return;
|
|
if (!m_input_readers.empty())
|
|
{
|
|
// Deactivate the old top reader
|
|
InputReaderSP top_reader_sp (m_input_readers.top());
|
|
if (top_reader_sp)
|
|
top_reader_sp->Notify (eInputReaderDeactivate);
|
|
}
|
|
m_input_readers.push (reader_sp);
|
|
reader_sp->Notify (eInputReaderActivate);
|
|
ActivateInputReader (reader_sp);
|
|
}
|
|
|
|
bool
|
|
Debugger::PopInputReader (const lldb::InputReaderSP& pop_reader_sp)
|
|
{
|
|
bool result = false;
|
|
|
|
// The reader on the stop of the stack is done, so let the next
|
|
// read on the stack referesh its prompt and if there is one...
|
|
if (!m_input_readers.empty())
|
|
{
|
|
InputReaderSP reader_sp(m_input_readers.top());
|
|
|
|
if (!pop_reader_sp || pop_reader_sp.get() == reader_sp.get())
|
|
{
|
|
m_input_readers.pop ();
|
|
reader_sp->Notify (eInputReaderDeactivate);
|
|
reader_sp->Notify (eInputReaderDone);
|
|
result = true;
|
|
|
|
if (!m_input_readers.empty())
|
|
{
|
|
reader_sp = m_input_readers.top();
|
|
if (reader_sp)
|
|
{
|
|
ActivateInputReader (reader_sp);
|
|
reader_sp->Notify (eInputReaderReactivate);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
bool
|
|
Debugger::CheckIfTopInputReaderIsDone ()
|
|
{
|
|
bool result = false;
|
|
if (!m_input_readers.empty())
|
|
{
|
|
InputReaderSP reader_sp(m_input_readers.top());
|
|
|
|
if (reader_sp && reader_sp->IsDone())
|
|
{
|
|
result = true;
|
|
PopInputReader (reader_sp);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void
|
|
Debugger::ActivateInputReader (const InputReaderSP &reader_sp)
|
|
{
|
|
FILE *in_fh = GetInputFileHandle();
|
|
|
|
if (in_fh)
|
|
{
|
|
struct termios in_fh_termios;
|
|
int in_fd = fileno (in_fh);
|
|
if (::tcgetattr(in_fd, &in_fh_termios) == 0)
|
|
{
|
|
if (reader_sp->GetEcho())
|
|
in_fh_termios.c_lflag |= ECHO; // Turn on echoing
|
|
else
|
|
in_fh_termios.c_lflag &= ~ECHO; // Turn off echoing
|
|
|
|
switch (reader_sp->GetGranularity())
|
|
{
|
|
case eInputReaderGranularityByte:
|
|
case eInputReaderGranularityWord:
|
|
in_fh_termios.c_lflag &= ~ICANON; // Get one char at a time
|
|
break;
|
|
|
|
case eInputReaderGranularityLine:
|
|
case eInputReaderGranularityAll:
|
|
in_fh_termios.c_lflag |= ICANON; // Get lines at a time
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
::tcsetattr (in_fd, TCSANOW, &in_fh_termios);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
Debugger::UpdateExecutionContext (ExecutionContext *override_context)
|
|
{
|
|
m_exe_ctx.Clear();
|
|
|
|
if (override_context != NULL)
|
|
{
|
|
m_exe_ctx.target = override_context->target;
|
|
m_exe_ctx.process = override_context->process;
|
|
m_exe_ctx.thread = override_context->thread;
|
|
m_exe_ctx.frame = override_context->frame;
|
|
}
|
|
else
|
|
{
|
|
TargetSP target_sp (GetCurrentTarget());
|
|
if (target_sp)
|
|
{
|
|
m_exe_ctx.target = target_sp.get();
|
|
m_exe_ctx.process = target_sp->GetProcessSP().get();
|
|
if (m_exe_ctx.process && m_exe_ctx.process->IsRunning() == false)
|
|
{
|
|
m_exe_ctx.thread = m_exe_ctx.process->GetThreadList().GetCurrentThread().get();
|
|
if (m_exe_ctx.thread == NULL)
|
|
m_exe_ctx.thread = m_exe_ctx.process->GetThreadList().GetThreadAtIndex(0).get();
|
|
if (m_exe_ctx.thread)
|
|
{
|
|
m_exe_ctx.frame = m_exe_ctx.thread->GetCurrentFrame().get();
|
|
if (m_exe_ctx.frame == NULL)
|
|
m_exe_ctx.frame = m_exe_ctx.thread->GetStackFrameAtIndex (0).get();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|