
debugging optimized code. Adds new methods on Function/SBFunction to query whether a given function is optimized. Adds a new function.is-optimized format entity and changes the default frame-format to append "[opt]" if the function was built with optimization. The only indication that a binary was built with optimization that we have right now is the presence of the DW_AT_APPLE_optimized attribute (DW_FORM_flag value 1) in the DW_TAG_compile_unit. The absence of this flag may mean that the compile_unit was not compiled with optimization, or it may mean that the producer does not generate this attribute. Currently this only works for dSYM debugging. When we create the CompileUnit with dwarf-in-.o-file debugging we don't have the attribute value yet so it's not set. I need to find the flag value when we do start to read the .o file DWARF and set the CompileUnit's status at that point - but haven't done it yet. I'm also going to add a mechanism for issuing warnings to users such that they're only issued once in a debug session and there is away for users to suppress these warnings altogether via .lldbinit file settings. But I want to get this changeset committed now that it's at a useful state. <rdar://problem/19281172> llvm-svn: 243508
1729 lines
57 KiB
C++
1729 lines
57 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/Core/Debugger.h"
|
|
|
|
#include <map>
|
|
|
|
#include "clang/AST/DeclCXX.h"
|
|
#include "clang/AST/Type.h"
|
|
#include "llvm/ADT/StringRef.h"
|
|
|
|
#include "lldb/lldb-private.h"
|
|
#include "lldb/Core/FormatEntity.h"
|
|
#include "lldb/Core/Module.h"
|
|
#include "lldb/Core/PluginManager.h"
|
|
#include "lldb/Core/RegisterValue.h"
|
|
#include "lldb/Core/State.h"
|
|
#include "lldb/Core/StreamAsynchronousIO.h"
|
|
#include "lldb/Core/StreamCallback.h"
|
|
#include "lldb/Core/StreamFile.h"
|
|
#include "lldb/Core/StreamString.h"
|
|
#include "lldb/Core/StructuredData.h"
|
|
#include "lldb/Core/Timer.h"
|
|
#include "lldb/Core/ValueObject.h"
|
|
#include "lldb/Core/ValueObjectVariable.h"
|
|
#include "lldb/DataFormatters/DataVisualization.h"
|
|
#include "lldb/DataFormatters/FormatManager.h"
|
|
#include "lldb/DataFormatters/TypeSummary.h"
|
|
#include "lldb/Host/ConnectionFileDescriptor.h"
|
|
#include "lldb/Host/HostInfo.h"
|
|
#include "lldb/Host/Terminal.h"
|
|
#include "lldb/Host/ThreadLauncher.h"
|
|
#include "lldb/Interpreter/CommandInterpreter.h"
|
|
#include "lldb/Interpreter/OptionValueProperties.h"
|
|
#include "lldb/Interpreter/OptionValueSInt64.h"
|
|
#include "lldb/Interpreter/OptionValueString.h"
|
|
#include "lldb/Symbol/ClangASTContext.h"
|
|
#include "lldb/Symbol/CompileUnit.h"
|
|
#include "lldb/Symbol/Function.h"
|
|
#include "lldb/Symbol/Symbol.h"
|
|
#include "lldb/Symbol/VariableList.h"
|
|
#include "lldb/Target/CPPLanguageRuntime.h"
|
|
#include "lldb/Target/ObjCLanguageRuntime.h"
|
|
#include "lldb/Target/TargetList.h"
|
|
#include "lldb/Target/Process.h"
|
|
#include "lldb/Target/RegisterContext.h"
|
|
#include "lldb/Target/SectionLoadList.h"
|
|
#include "lldb/Target/StopInfo.h"
|
|
#include "lldb/Target/Target.h"
|
|
#include "lldb/Target/Thread.h"
|
|
#include "lldb/Utility/AnsiTerminal.h"
|
|
|
|
#include "llvm/Support/DynamicLibrary.h"
|
|
|
|
using namespace lldb;
|
|
using namespace lldb_private;
|
|
|
|
|
|
static lldb::user_id_t g_unique_id = 1;
|
|
static size_t g_debugger_event_thread_stack_bytes = 8 * 1024 * 1024;
|
|
|
|
#pragma mark Static Functions
|
|
|
|
static Mutex &
|
|
GetDebuggerListMutex ()
|
|
{
|
|
static Mutex g_mutex(Mutex::eMutexTypeRecursive);
|
|
return g_mutex;
|
|
}
|
|
|
|
typedef std::vector<DebuggerSP> DebuggerList;
|
|
|
|
static DebuggerList &
|
|
GetDebuggerList()
|
|
{
|
|
// hide the static debugger list inside a singleton accessor to avoid
|
|
// global init constructors
|
|
static DebuggerList g_list;
|
|
return g_list;
|
|
}
|
|
|
|
OptionEnumValueElement
|
|
g_show_disassembly_enum_values[] =
|
|
{
|
|
{ Debugger::eStopDisassemblyTypeNever, "never", "Never show disassembly when displaying a stop context."},
|
|
{ Debugger::eStopDisassemblyTypeNoSource, "no-source", "Show disassembly when there is no source information, or the source file is missing when displaying a stop context."},
|
|
{ Debugger::eStopDisassemblyTypeAlways, "always", "Always show disassembly when displaying a stop context."},
|
|
{ 0, NULL, NULL }
|
|
};
|
|
|
|
OptionEnumValueElement
|
|
g_language_enumerators[] =
|
|
{
|
|
{ eScriptLanguageNone, "none", "Disable scripting languages."},
|
|
{ eScriptLanguagePython, "python", "Select python as the default scripting language."},
|
|
{ eScriptLanguageDefault, "default", "Select the lldb default as the default scripting language."},
|
|
{ 0, NULL, NULL }
|
|
};
|
|
|
|
#define MODULE_WITH_FUNC "{ ${module.file.basename}{`${function.name-with-args}${function.pc-offset}}}"
|
|
#define FILE_AND_LINE "{ at ${line.file.basename}:${line.number}}"
|
|
#define IS_OPTIMIZED "{${function.is-optimized} [opt]}"
|
|
|
|
#define DEFAULT_THREAD_FORMAT "thread #${thread.index}: tid = ${thread.id%tid}"\
|
|
"{, ${frame.pc}}"\
|
|
MODULE_WITH_FUNC\
|
|
FILE_AND_LINE\
|
|
"{, name = '${thread.name}'}"\
|
|
"{, queue = '${thread.queue}'}"\
|
|
"{, activity = '${thread.info.activity.name}'}" \
|
|
"{, ${thread.info.trace_messages} messages}" \
|
|
"{, stop reason = ${thread.stop-reason}}"\
|
|
"{\\nReturn value: ${thread.return-value}}"\
|
|
"{\\nCompleted expression: ${thread.completed-expression}}"\
|
|
"\\n"
|
|
|
|
#define DEFAULT_FRAME_FORMAT "frame #${frame.index}: ${frame.pc}"\
|
|
MODULE_WITH_FUNC\
|
|
FILE_AND_LINE\
|
|
IS_OPTIMIZED\
|
|
"\\n"
|
|
|
|
// Three parts to this disassembly format specification:
|
|
// 1. If this is a new function/symbol (no previous symbol/function), print
|
|
// dylib`funcname:\n
|
|
// 2. If this is a symbol context change (different from previous symbol/function), print
|
|
// dylib`funcname:\n
|
|
// 3. print
|
|
// address <+offset>:
|
|
#define DEFAULT_DISASSEMBLY_FORMAT "{${function.initial-function}{${module.file.basename}`}{${function.name-without-args}}:\n}{${function.changed}\n{${module.file.basename}`}{${function.name-without-args}}:\n}{${current-pc-arrow} }${addr-file-or-load}{ <${function.concrete-only-addr-offset-no-padding}>}: "
|
|
|
|
// gdb's disassembly format can be emulated with
|
|
// ${current-pc-arrow}${addr-file-or-load}{ <${function.name-without-args}${function.concrete-only-addr-offset-no-padding}>}:
|
|
|
|
// lldb's original format for disassembly would look like this format string -
|
|
// {${function.initial-function}{${module.file.basename}`}{${function.name-without-args}}:\n}{${function.changed}\n{${module.file.basename}`}{${function.name-without-args}}:\n}{${current-pc-arrow} }{${addr-file-or-load}}:
|
|
|
|
|
|
static PropertyDefinition
|
|
g_properties[] =
|
|
{
|
|
{ "auto-confirm", OptionValue::eTypeBoolean , true, false, NULL, NULL, "If true all confirmation prompts will receive their default reply." },
|
|
{ "disassembly-format", OptionValue::eTypeFormatEntity, true, 0 , DEFAULT_DISASSEMBLY_FORMAT, NULL, "The default disassembly format string to use when disassembling instruction sequences." },
|
|
{ "frame-format", OptionValue::eTypeFormatEntity, true, 0 , DEFAULT_FRAME_FORMAT, NULL, "The default frame format string to use when displaying stack frame information for threads." },
|
|
{ "notify-void", OptionValue::eTypeBoolean , true, false, NULL, NULL, "Notify the user explicitly if an expression returns void (default: false)." },
|
|
{ "prompt", OptionValue::eTypeString , true, OptionValueString::eOptionEncodeCharacterEscapeSequences, "(lldb) ", NULL, "The debugger command line prompt displayed for the user." },
|
|
{ "script-lang", OptionValue::eTypeEnum , true, eScriptLanguagePython, NULL, g_language_enumerators, "The script language to be used for evaluating user-written scripts." },
|
|
{ "stop-disassembly-count", OptionValue::eTypeSInt64 , true, 4 , NULL, NULL, "The number of disassembly lines to show when displaying a stopped context." },
|
|
{ "stop-disassembly-display", OptionValue::eTypeEnum , true, Debugger::eStopDisassemblyTypeNoSource, NULL, g_show_disassembly_enum_values, "Control when to display disassembly when displaying a stopped context." },
|
|
{ "stop-line-count-after", OptionValue::eTypeSInt64 , true, 3 , NULL, NULL, "The number of sources lines to display that come after the current source line when displaying a stopped context." },
|
|
{ "stop-line-count-before", OptionValue::eTypeSInt64 , true, 3 , NULL, NULL, "The number of sources lines to display that come before the current source line when displaying a stopped context." },
|
|
{ "term-width", OptionValue::eTypeSInt64 , true, 80 , NULL, NULL, "The maximum number of columns to use for displaying text." },
|
|
{ "thread-format", OptionValue::eTypeFormatEntity, true, 0 , DEFAULT_THREAD_FORMAT, NULL, "The default thread format string to use when displaying thread information." },
|
|
{ "use-external-editor", OptionValue::eTypeBoolean , true, false, NULL, NULL, "Whether to use an external editor or not." },
|
|
{ "use-color", OptionValue::eTypeBoolean , true, true , NULL, NULL, "Whether to use Ansi color codes or not." },
|
|
{ "auto-one-line-summaries", OptionValue::eTypeBoolean , true, true, NULL, NULL, "If true, LLDB will automatically display small structs in one-liner format (default: true)." },
|
|
{ "escape-non-printables", OptionValue::eTypeBoolean , true, true, NULL, NULL, "If true, LLDB will automatically escape non-printable and escape characters when formatting strings." },
|
|
{ NULL, OptionValue::eTypeInvalid , true, 0 , NULL, NULL, NULL }
|
|
};
|
|
|
|
enum
|
|
{
|
|
ePropertyAutoConfirm = 0,
|
|
ePropertyDisassemblyFormat,
|
|
ePropertyFrameFormat,
|
|
ePropertyNotiftVoid,
|
|
ePropertyPrompt,
|
|
ePropertyScriptLanguage,
|
|
ePropertyStopDisassemblyCount,
|
|
ePropertyStopDisassemblyDisplay,
|
|
ePropertyStopLineCountAfter,
|
|
ePropertyStopLineCountBefore,
|
|
ePropertyTerminalWidth,
|
|
ePropertyThreadFormat,
|
|
ePropertyUseExternalEditor,
|
|
ePropertyUseColor,
|
|
ePropertyAutoOneLineSummaries,
|
|
ePropertyEscapeNonPrintables
|
|
};
|
|
|
|
LoadPluginCallbackType Debugger::g_load_plugin_callback = NULL;
|
|
|
|
Error
|
|
Debugger::SetPropertyValue (const ExecutionContext *exe_ctx,
|
|
VarSetOperationType op,
|
|
const char *property_path,
|
|
const char *value)
|
|
{
|
|
bool is_load_script = strcmp(property_path,"target.load-script-from-symbol-file") == 0;
|
|
bool is_escape_non_printables = strcmp(property_path, "escape-non-printables") == 0;
|
|
TargetSP target_sp;
|
|
LoadScriptFromSymFile load_script_old_value;
|
|
if (is_load_script && exe_ctx->GetTargetSP())
|
|
{
|
|
target_sp = exe_ctx->GetTargetSP();
|
|
load_script_old_value = target_sp->TargetProperties::GetLoadScriptFromSymbolFile();
|
|
}
|
|
Error error (Properties::SetPropertyValue (exe_ctx, op, property_path, value));
|
|
if (error.Success())
|
|
{
|
|
// FIXME it would be nice to have "on-change" callbacks for properties
|
|
if (strcmp(property_path, g_properties[ePropertyPrompt].name) == 0)
|
|
{
|
|
const char *new_prompt = GetPrompt();
|
|
std::string str = lldb_utility::ansi::FormatAnsiTerminalCodes (new_prompt, GetUseColor());
|
|
if (str.length())
|
|
new_prompt = str.c_str();
|
|
GetCommandInterpreter().UpdatePrompt(new_prompt);
|
|
EventSP prompt_change_event_sp (new Event(CommandInterpreter::eBroadcastBitResetPrompt, new EventDataBytes (new_prompt)));
|
|
GetCommandInterpreter().BroadcastEvent (prompt_change_event_sp);
|
|
}
|
|
else if (strcmp(property_path, g_properties[ePropertyUseColor].name) == 0)
|
|
{
|
|
// use-color changed. Ping the prompt so it can reset the ansi terminal codes.
|
|
SetPrompt (GetPrompt());
|
|
}
|
|
else if (is_load_script && target_sp && load_script_old_value == eLoadScriptFromSymFileWarn)
|
|
{
|
|
if (target_sp->TargetProperties::GetLoadScriptFromSymbolFile() == eLoadScriptFromSymFileTrue)
|
|
{
|
|
std::list<Error> errors;
|
|
StreamString feedback_stream;
|
|
if (!target_sp->LoadScriptingResources(errors,&feedback_stream))
|
|
{
|
|
StreamFileSP stream_sp (GetErrorFile());
|
|
if (stream_sp)
|
|
{
|
|
for (auto error : errors)
|
|
{
|
|
stream_sp->Printf("%s\n",error.AsCString());
|
|
}
|
|
if (feedback_stream.GetSize())
|
|
stream_sp->Printf("%s",feedback_stream.GetData());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (is_escape_non_printables)
|
|
{
|
|
DataVisualization::ForceUpdate();
|
|
}
|
|
}
|
|
return error;
|
|
}
|
|
|
|
bool
|
|
Debugger::GetAutoConfirm () const
|
|
{
|
|
const uint32_t idx = ePropertyAutoConfirm;
|
|
return m_collection_sp->GetPropertyAtIndexAsBoolean (NULL, idx, g_properties[idx].default_uint_value != 0);
|
|
}
|
|
|
|
const FormatEntity::Entry *
|
|
Debugger::GetDisassemblyFormat() const
|
|
{
|
|
const uint32_t idx = ePropertyDisassemblyFormat;
|
|
return m_collection_sp->GetPropertyAtIndexAsFormatEntity(NULL, idx);
|
|
}
|
|
|
|
const FormatEntity::Entry *
|
|
Debugger::GetFrameFormat() const
|
|
{
|
|
const uint32_t idx = ePropertyFrameFormat;
|
|
return m_collection_sp->GetPropertyAtIndexAsFormatEntity(NULL, idx);
|
|
}
|
|
|
|
bool
|
|
Debugger::GetNotifyVoid () const
|
|
{
|
|
const uint32_t idx = ePropertyNotiftVoid;
|
|
return m_collection_sp->GetPropertyAtIndexAsBoolean (NULL, idx, g_properties[idx].default_uint_value != 0);
|
|
}
|
|
|
|
const char *
|
|
Debugger::GetPrompt() const
|
|
{
|
|
const uint32_t idx = ePropertyPrompt;
|
|
return m_collection_sp->GetPropertyAtIndexAsString (NULL, idx, g_properties[idx].default_cstr_value);
|
|
}
|
|
|
|
void
|
|
Debugger::SetPrompt(const char *p)
|
|
{
|
|
const uint32_t idx = ePropertyPrompt;
|
|
m_collection_sp->SetPropertyAtIndexAsString (NULL, idx, p);
|
|
const char *new_prompt = GetPrompt();
|
|
std::string str = lldb_utility::ansi::FormatAnsiTerminalCodes (new_prompt, GetUseColor());
|
|
if (str.length())
|
|
new_prompt = str.c_str();
|
|
GetCommandInterpreter().UpdatePrompt(new_prompt);
|
|
}
|
|
|
|
const FormatEntity::Entry *
|
|
Debugger::GetThreadFormat() const
|
|
{
|
|
const uint32_t idx = ePropertyThreadFormat;
|
|
return m_collection_sp->GetPropertyAtIndexAsFormatEntity(NULL, idx);
|
|
}
|
|
|
|
lldb::ScriptLanguage
|
|
Debugger::GetScriptLanguage() const
|
|
{
|
|
const uint32_t idx = ePropertyScriptLanguage;
|
|
return (lldb::ScriptLanguage)m_collection_sp->GetPropertyAtIndexAsEnumeration (NULL, idx, g_properties[idx].default_uint_value);
|
|
}
|
|
|
|
bool
|
|
Debugger::SetScriptLanguage (lldb::ScriptLanguage script_lang)
|
|
{
|
|
const uint32_t idx = ePropertyScriptLanguage;
|
|
return m_collection_sp->SetPropertyAtIndexAsEnumeration (NULL, idx, script_lang);
|
|
}
|
|
|
|
uint32_t
|
|
Debugger::GetTerminalWidth () const
|
|
{
|
|
const uint32_t idx = ePropertyTerminalWidth;
|
|
return m_collection_sp->GetPropertyAtIndexAsSInt64 (NULL, idx, g_properties[idx].default_uint_value);
|
|
}
|
|
|
|
bool
|
|
Debugger::SetTerminalWidth (uint32_t term_width)
|
|
{
|
|
const uint32_t idx = ePropertyTerminalWidth;
|
|
return m_collection_sp->SetPropertyAtIndexAsSInt64 (NULL, idx, term_width);
|
|
}
|
|
|
|
bool
|
|
Debugger::GetUseExternalEditor () const
|
|
{
|
|
const uint32_t idx = ePropertyUseExternalEditor;
|
|
return m_collection_sp->GetPropertyAtIndexAsBoolean (NULL, idx, g_properties[idx].default_uint_value != 0);
|
|
}
|
|
|
|
bool
|
|
Debugger::SetUseExternalEditor (bool b)
|
|
{
|
|
const uint32_t idx = ePropertyUseExternalEditor;
|
|
return m_collection_sp->SetPropertyAtIndexAsBoolean (NULL, idx, b);
|
|
}
|
|
|
|
bool
|
|
Debugger::GetUseColor () const
|
|
{
|
|
const uint32_t idx = ePropertyUseColor;
|
|
return m_collection_sp->GetPropertyAtIndexAsBoolean (NULL, idx, g_properties[idx].default_uint_value != 0);
|
|
}
|
|
|
|
bool
|
|
Debugger::SetUseColor (bool b)
|
|
{
|
|
const uint32_t idx = ePropertyUseColor;
|
|
bool ret = m_collection_sp->SetPropertyAtIndexAsBoolean (NULL, idx, b);
|
|
SetPrompt (GetPrompt());
|
|
return ret;
|
|
}
|
|
|
|
uint32_t
|
|
Debugger::GetStopSourceLineCount (bool before) const
|
|
{
|
|
const uint32_t idx = before ? ePropertyStopLineCountBefore : ePropertyStopLineCountAfter;
|
|
return m_collection_sp->GetPropertyAtIndexAsSInt64 (NULL, idx, g_properties[idx].default_uint_value);
|
|
}
|
|
|
|
Debugger::StopDisassemblyType
|
|
Debugger::GetStopDisassemblyDisplay () const
|
|
{
|
|
const uint32_t idx = ePropertyStopDisassemblyDisplay;
|
|
return (Debugger::StopDisassemblyType)m_collection_sp->GetPropertyAtIndexAsEnumeration (NULL, idx, g_properties[idx].default_uint_value);
|
|
}
|
|
|
|
uint32_t
|
|
Debugger::GetDisassemblyLineCount () const
|
|
{
|
|
const uint32_t idx = ePropertyStopDisassemblyCount;
|
|
return m_collection_sp->GetPropertyAtIndexAsSInt64 (NULL, idx, g_properties[idx].default_uint_value);
|
|
}
|
|
|
|
bool
|
|
Debugger::GetAutoOneLineSummaries () const
|
|
{
|
|
const uint32_t idx = ePropertyAutoOneLineSummaries;
|
|
return m_collection_sp->GetPropertyAtIndexAsBoolean (NULL, idx, true);
|
|
}
|
|
|
|
bool
|
|
Debugger::GetEscapeNonPrintables () const
|
|
{
|
|
const uint32_t idx = ePropertyEscapeNonPrintables;
|
|
return m_collection_sp->GetPropertyAtIndexAsBoolean (NULL, idx, true);
|
|
}
|
|
|
|
#pragma mark Debugger
|
|
|
|
//const DebuggerPropertiesSP &
|
|
//Debugger::GetSettings() const
|
|
//{
|
|
// return m_properties_sp;
|
|
//}
|
|
//
|
|
|
|
static bool lldb_initialized = false;
|
|
void
|
|
Debugger::Initialize(LoadPluginCallbackType load_plugin_callback)
|
|
{
|
|
assert(!lldb_initialized && "Debugger::Initialize called more than once!");
|
|
|
|
lldb_initialized = true;
|
|
g_load_plugin_callback = load_plugin_callback;
|
|
}
|
|
|
|
void
|
|
Debugger::Terminate ()
|
|
{
|
|
assert(lldb_initialized && "Debugger::Terminate called without a matching Debugger::Initialize!");
|
|
|
|
// Clear our master list of debugger objects
|
|
Mutex::Locker locker (GetDebuggerListMutex ());
|
|
GetDebuggerList().clear();
|
|
}
|
|
|
|
void
|
|
Debugger::SettingsInitialize ()
|
|
{
|
|
Target::SettingsInitialize ();
|
|
}
|
|
|
|
void
|
|
Debugger::SettingsTerminate ()
|
|
{
|
|
Target::SettingsTerminate ();
|
|
}
|
|
|
|
bool
|
|
Debugger::LoadPlugin (const FileSpec& spec, Error& error)
|
|
{
|
|
if (g_load_plugin_callback)
|
|
{
|
|
llvm::sys::DynamicLibrary dynlib = g_load_plugin_callback (shared_from_this(), spec, error);
|
|
if (dynlib.isValid())
|
|
{
|
|
m_loaded_plugins.push_back(dynlib);
|
|
return true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// The g_load_plugin_callback is registered in SBDebugger::Initialize()
|
|
// and if the public API layer isn't available (code is linking against
|
|
// all of the internal LLDB static libraries), then we can't load plugins
|
|
error.SetErrorString("Public API layer is not available");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static FileSpec::EnumerateDirectoryResult
|
|
LoadPluginCallback
|
|
(
|
|
void *baton,
|
|
FileSpec::FileType file_type,
|
|
const FileSpec &file_spec
|
|
)
|
|
{
|
|
Error error;
|
|
|
|
static ConstString g_dylibext("dylib");
|
|
static ConstString g_solibext("so");
|
|
|
|
if (!baton)
|
|
return FileSpec::eEnumerateDirectoryResultQuit;
|
|
|
|
Debugger *debugger = (Debugger*)baton;
|
|
|
|
// If we have a regular file, a symbolic link or unknown file type, try
|
|
// and process the file. We must handle unknown as sometimes the directory
|
|
// enumeration might be enumerating a file system that doesn't have correct
|
|
// file type information.
|
|
if (file_type == FileSpec::eFileTypeRegular ||
|
|
file_type == FileSpec::eFileTypeSymbolicLink ||
|
|
file_type == FileSpec::eFileTypeUnknown )
|
|
{
|
|
FileSpec plugin_file_spec (file_spec);
|
|
plugin_file_spec.ResolvePath ();
|
|
|
|
if (plugin_file_spec.GetFileNameExtension() != g_dylibext &&
|
|
plugin_file_spec.GetFileNameExtension() != g_solibext)
|
|
{
|
|
return FileSpec::eEnumerateDirectoryResultNext;
|
|
}
|
|
|
|
Error plugin_load_error;
|
|
debugger->LoadPlugin (plugin_file_spec, plugin_load_error);
|
|
|
|
return FileSpec::eEnumerateDirectoryResultNext;
|
|
}
|
|
|
|
else if (file_type == FileSpec::eFileTypeUnknown ||
|
|
file_type == FileSpec::eFileTypeDirectory ||
|
|
file_type == FileSpec::eFileTypeSymbolicLink )
|
|
{
|
|
// Try and recurse into anything that a directory or symbolic link.
|
|
// We must also do this for unknown as sometimes the directory enumeration
|
|
// might be enumerating a file system that doesn't have correct file type
|
|
// information.
|
|
return FileSpec::eEnumerateDirectoryResultEnter;
|
|
}
|
|
|
|
return FileSpec::eEnumerateDirectoryResultNext;
|
|
}
|
|
|
|
void
|
|
Debugger::InstanceInitialize ()
|
|
{
|
|
FileSpec dir_spec;
|
|
const bool find_directories = true;
|
|
const bool find_files = true;
|
|
const bool find_other = true;
|
|
char dir_path[PATH_MAX];
|
|
if (HostInfo::GetLLDBPath(ePathTypeLLDBSystemPlugins, dir_spec))
|
|
{
|
|
if (dir_spec.Exists() && dir_spec.GetPath(dir_path, sizeof(dir_path)))
|
|
{
|
|
FileSpec::EnumerateDirectory (dir_path,
|
|
find_directories,
|
|
find_files,
|
|
find_other,
|
|
LoadPluginCallback,
|
|
this);
|
|
}
|
|
}
|
|
|
|
if (HostInfo::GetLLDBPath(ePathTypeLLDBUserPlugins, dir_spec))
|
|
{
|
|
if (dir_spec.Exists() && dir_spec.GetPath(dir_path, sizeof(dir_path)))
|
|
{
|
|
FileSpec::EnumerateDirectory (dir_path,
|
|
find_directories,
|
|
find_files,
|
|
find_other,
|
|
LoadPluginCallback,
|
|
this);
|
|
}
|
|
}
|
|
|
|
PluginManager::DebuggerInitialize (*this);
|
|
}
|
|
|
|
DebuggerSP
|
|
Debugger::CreateInstance (lldb::LogOutputCallback log_callback, void *baton)
|
|
{
|
|
DebuggerSP debugger_sp (new Debugger(log_callback, baton));
|
|
if (lldb_initialized)
|
|
{
|
|
Mutex::Locker locker (GetDebuggerListMutex ());
|
|
GetDebuggerList().push_back(debugger_sp);
|
|
}
|
|
debugger_sp->InstanceInitialize ();
|
|
return debugger_sp;
|
|
}
|
|
|
|
void
|
|
Debugger::Destroy (DebuggerSP &debugger_sp)
|
|
{
|
|
if (debugger_sp.get() == NULL)
|
|
return;
|
|
|
|
debugger_sp->Clear();
|
|
|
|
if (lldb_initialized)
|
|
{
|
|
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() == debugger_sp.get())
|
|
{
|
|
debugger_list.erase (pos);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
DebuggerSP
|
|
Debugger::FindDebuggerWithInstanceName (const ConstString &instance_name)
|
|
{
|
|
DebuggerSP debugger_sp;
|
|
if (lldb_initialized)
|
|
{
|
|
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()->m_instance_name == instance_name)
|
|
{
|
|
debugger_sp = *pos;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return debugger_sp;
|
|
}
|
|
|
|
TargetSP
|
|
Debugger::FindTargetWithProcessID (lldb::pid_t pid)
|
|
{
|
|
TargetSP target_sp;
|
|
if (lldb_initialized)
|
|
{
|
|
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;
|
|
}
|
|
|
|
TargetSP
|
|
Debugger::FindTargetWithProcess (Process *process)
|
|
{
|
|
TargetSP target_sp;
|
|
if (lldb_initialized)
|
|
{
|
|
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().FindTargetWithProcess (process);
|
|
if (target_sp)
|
|
break;
|
|
}
|
|
}
|
|
return target_sp;
|
|
}
|
|
|
|
Debugger::Debugger(lldb::LogOutputCallback log_callback, void *baton) :
|
|
UserID(g_unique_id++),
|
|
Properties(OptionValuePropertiesSP(new OptionValueProperties())),
|
|
m_input_file_sp(new StreamFile(stdin, false)),
|
|
m_output_file_sp(new StreamFile(stdout, false)),
|
|
m_error_file_sp(new StreamFile(stderr, false)),
|
|
m_terminal_state(),
|
|
m_target_list(*this),
|
|
m_platform_list(),
|
|
m_listener("lldb.Debugger"),
|
|
m_source_manager_ap(),
|
|
m_source_file_cache(),
|
|
m_command_interpreter_ap(new CommandInterpreter(*this, eScriptLanguageDefault, false)),
|
|
m_input_reader_stack(),
|
|
m_instance_name(),
|
|
m_loaded_plugins(),
|
|
m_event_handler_thread (),
|
|
m_io_handler_thread (),
|
|
m_sync_broadcaster (NULL, "lldb.debugger.sync")
|
|
{
|
|
char instance_cstr[256];
|
|
snprintf(instance_cstr, sizeof(instance_cstr), "debugger_%d", (int)GetID());
|
|
m_instance_name.SetCString(instance_cstr);
|
|
if (log_callback)
|
|
m_log_callback_stream_sp.reset (new StreamCallback (log_callback, baton));
|
|
m_command_interpreter_ap->Initialize ();
|
|
// Always add our default platform to the platform list
|
|
PlatformSP default_platform_sp (Platform::GetHostPlatform());
|
|
assert (default_platform_sp.get());
|
|
m_platform_list.Append (default_platform_sp, true);
|
|
|
|
m_collection_sp->Initialize (g_properties);
|
|
m_collection_sp->AppendProperty (ConstString("target"),
|
|
ConstString("Settings specify to debugging targets."),
|
|
true,
|
|
Target::GetGlobalProperties()->GetValueProperties());
|
|
m_collection_sp->AppendProperty (ConstString("platform"),
|
|
ConstString("Platform settings."),
|
|
true,
|
|
Platform::GetGlobalPlatformProperties()->GetValueProperties());
|
|
if (m_command_interpreter_ap.get())
|
|
{
|
|
m_collection_sp->AppendProperty (ConstString("interpreter"),
|
|
ConstString("Settings specify to the debugger's command interpreter."),
|
|
true,
|
|
m_command_interpreter_ap->GetValueProperties());
|
|
}
|
|
OptionValueSInt64 *term_width = m_collection_sp->GetPropertyAtIndexAsOptionValueSInt64 (NULL, ePropertyTerminalWidth);
|
|
term_width->SetMinimumValue(10);
|
|
term_width->SetMaximumValue(1024);
|
|
|
|
// Turn off use-color if this is a dumb terminal.
|
|
const char *term = getenv ("TERM");
|
|
if (term && !strcmp (term, "dumb"))
|
|
SetUseColor (false);
|
|
}
|
|
|
|
Debugger::~Debugger ()
|
|
{
|
|
Clear();
|
|
}
|
|
|
|
void
|
|
Debugger::Clear()
|
|
{
|
|
ClearIOHandlers();
|
|
StopIOHandlerThread();
|
|
StopEventHandlerThread();
|
|
m_listener.Clear();
|
|
int num_targets = m_target_list.GetNumTargets();
|
|
for (int i = 0; i < num_targets; i++)
|
|
{
|
|
TargetSP target_sp (m_target_list.GetTargetAtIndex (i));
|
|
if (target_sp)
|
|
{
|
|
ProcessSP process_sp (target_sp->GetProcessSP());
|
|
if (process_sp)
|
|
process_sp->Finalize();
|
|
target_sp->Destroy();
|
|
}
|
|
}
|
|
BroadcasterManager::Clear ();
|
|
|
|
// Close the input file _before_ we close the input read communications class
|
|
// as it does NOT own the input file, our m_input_file does.
|
|
m_terminal_state.Clear();
|
|
if (m_input_file_sp)
|
|
m_input_file_sp->GetFile().Close ();
|
|
|
|
m_command_interpreter_ap->Clear();
|
|
}
|
|
|
|
bool
|
|
Debugger::GetCloseInputOnEOF () const
|
|
{
|
|
// return m_input_comm.GetCloseOnEOF();
|
|
return false;
|
|
}
|
|
|
|
void
|
|
Debugger::SetCloseInputOnEOF (bool b)
|
|
{
|
|
// m_input_comm.SetCloseOnEOF(b);
|
|
}
|
|
|
|
bool
|
|
Debugger::GetAsyncExecution ()
|
|
{
|
|
return !m_command_interpreter_ap->GetSynchronous();
|
|
}
|
|
|
|
void
|
|
Debugger::SetAsyncExecution (bool async_execution)
|
|
{
|
|
m_command_interpreter_ap->SetSynchronous (!async_execution);
|
|
}
|
|
|
|
|
|
void
|
|
Debugger::SetInputFileHandle (FILE *fh, bool tranfer_ownership)
|
|
{
|
|
if (m_input_file_sp)
|
|
m_input_file_sp->GetFile().SetStream (fh, tranfer_ownership);
|
|
else
|
|
m_input_file_sp.reset (new StreamFile (fh, tranfer_ownership));
|
|
|
|
File &in_file = m_input_file_sp->GetFile();
|
|
if (in_file.IsValid() == false)
|
|
in_file.SetStream (stdin, true);
|
|
|
|
// Save away the terminal state if that is relevant, so that we can restore it in RestoreInputState.
|
|
SaveInputTerminalState ();
|
|
}
|
|
|
|
void
|
|
Debugger::SetOutputFileHandle (FILE *fh, bool tranfer_ownership)
|
|
{
|
|
if (m_output_file_sp)
|
|
m_output_file_sp->GetFile().SetStream (fh, tranfer_ownership);
|
|
else
|
|
m_output_file_sp.reset (new StreamFile (fh, tranfer_ownership));
|
|
|
|
File &out_file = m_output_file_sp->GetFile();
|
|
if (out_file.IsValid() == false)
|
|
out_file.SetStream (stdout, false);
|
|
|
|
// do not create the ScriptInterpreter just for setting the output file handle
|
|
// as the constructor will know how to do the right thing on its own
|
|
const bool can_create = false;
|
|
ScriptInterpreter* script_interpreter = GetCommandInterpreter().GetScriptInterpreter(can_create);
|
|
if (script_interpreter)
|
|
script_interpreter->ResetOutputFileHandle (fh);
|
|
}
|
|
|
|
void
|
|
Debugger::SetErrorFileHandle (FILE *fh, bool tranfer_ownership)
|
|
{
|
|
if (m_error_file_sp)
|
|
m_error_file_sp->GetFile().SetStream (fh, tranfer_ownership);
|
|
else
|
|
m_error_file_sp.reset (new StreamFile (fh, tranfer_ownership));
|
|
|
|
File &err_file = m_error_file_sp->GetFile();
|
|
if (err_file.IsValid() == false)
|
|
err_file.SetStream (stderr, false);
|
|
}
|
|
|
|
void
|
|
Debugger::SaveInputTerminalState ()
|
|
{
|
|
if (m_input_file_sp)
|
|
{
|
|
File &in_file = m_input_file_sp->GetFile();
|
|
if (in_file.GetDescriptor() != File::kInvalidDescriptor)
|
|
m_terminal_state.Save(in_file.GetDescriptor(), true);
|
|
}
|
|
}
|
|
|
|
void
|
|
Debugger::RestoreInputTerminalState ()
|
|
{
|
|
m_terminal_state.Restore();
|
|
}
|
|
|
|
ExecutionContext
|
|
Debugger::GetSelectedExecutionContext ()
|
|
{
|
|
ExecutionContext exe_ctx;
|
|
TargetSP target_sp(GetSelectedTarget());
|
|
exe_ctx.SetTargetSP (target_sp);
|
|
|
|
if (target_sp)
|
|
{
|
|
ProcessSP process_sp (target_sp->GetProcessSP());
|
|
exe_ctx.SetProcessSP (process_sp);
|
|
if (process_sp && process_sp->IsRunning() == false)
|
|
{
|
|
ThreadSP thread_sp (process_sp->GetThreadList().GetSelectedThread());
|
|
if (thread_sp)
|
|
{
|
|
exe_ctx.SetThreadSP (thread_sp);
|
|
exe_ctx.SetFrameSP (thread_sp->GetSelectedFrame());
|
|
if (exe_ctx.GetFramePtr() == NULL)
|
|
exe_ctx.SetFrameSP (thread_sp->GetStackFrameAtIndex (0));
|
|
}
|
|
}
|
|
}
|
|
return exe_ctx;
|
|
}
|
|
|
|
void
|
|
Debugger::DispatchInputInterrupt ()
|
|
{
|
|
Mutex::Locker locker (m_input_reader_stack.GetMutex());
|
|
IOHandlerSP reader_sp (m_input_reader_stack.Top());
|
|
if (reader_sp)
|
|
reader_sp->Interrupt();
|
|
}
|
|
|
|
void
|
|
Debugger::DispatchInputEndOfFile ()
|
|
{
|
|
Mutex::Locker locker (m_input_reader_stack.GetMutex());
|
|
IOHandlerSP reader_sp (m_input_reader_stack.Top());
|
|
if (reader_sp)
|
|
reader_sp->GotEOF();
|
|
}
|
|
|
|
void
|
|
Debugger::ClearIOHandlers ()
|
|
{
|
|
// The bottom input reader should be the main debugger input reader. We do not want to close that one here.
|
|
Mutex::Locker locker (m_input_reader_stack.GetMutex());
|
|
while (m_input_reader_stack.GetSize() > 1)
|
|
{
|
|
IOHandlerSP reader_sp (m_input_reader_stack.Top());
|
|
if (reader_sp)
|
|
PopIOHandler (reader_sp);
|
|
}
|
|
}
|
|
|
|
void
|
|
Debugger::ExecuteIOHandlers()
|
|
{
|
|
while (1)
|
|
{
|
|
IOHandlerSP reader_sp(m_input_reader_stack.Top());
|
|
if (!reader_sp)
|
|
break;
|
|
|
|
reader_sp->Run();
|
|
|
|
// Remove all input readers that are done from the top of the stack
|
|
while (1)
|
|
{
|
|
IOHandlerSP top_reader_sp = m_input_reader_stack.Top();
|
|
if (top_reader_sp && top_reader_sp->GetIsDone())
|
|
PopIOHandler (top_reader_sp);
|
|
else
|
|
break;
|
|
}
|
|
}
|
|
ClearIOHandlers();
|
|
}
|
|
|
|
bool
|
|
Debugger::IsTopIOHandler (const lldb::IOHandlerSP& reader_sp)
|
|
{
|
|
return m_input_reader_stack.IsTop (reader_sp);
|
|
}
|
|
|
|
void
|
|
Debugger::PrintAsync (const char *s, size_t len, bool is_stdout)
|
|
{
|
|
lldb::StreamFileSP stream = is_stdout ? GetOutputFile() : GetErrorFile();
|
|
m_input_reader_stack.PrintAsync(stream.get(), s, len);
|
|
}
|
|
|
|
ConstString
|
|
Debugger::GetTopIOHandlerControlSequence(char ch)
|
|
{
|
|
return m_input_reader_stack.GetTopIOHandlerControlSequence (ch);
|
|
}
|
|
|
|
const char *
|
|
Debugger::GetIOHandlerCommandPrefix()
|
|
{
|
|
return m_input_reader_stack.GetTopIOHandlerCommandPrefix();
|
|
}
|
|
|
|
const char *
|
|
Debugger::GetIOHandlerHelpPrologue()
|
|
{
|
|
return m_input_reader_stack.GetTopIOHandlerHelpPrologue();
|
|
}
|
|
|
|
void
|
|
Debugger::RunIOHandler (const IOHandlerSP& reader_sp)
|
|
{
|
|
PushIOHandler (reader_sp);
|
|
|
|
IOHandlerSP top_reader_sp = reader_sp;
|
|
while (top_reader_sp)
|
|
{
|
|
top_reader_sp->Run();
|
|
|
|
if (top_reader_sp.get() == reader_sp.get())
|
|
{
|
|
if (PopIOHandler (reader_sp))
|
|
break;
|
|
}
|
|
|
|
while (1)
|
|
{
|
|
top_reader_sp = m_input_reader_stack.Top();
|
|
if (top_reader_sp && top_reader_sp->GetIsDone())
|
|
PopIOHandler (top_reader_sp);
|
|
else
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
Debugger::AdoptTopIOHandlerFilesIfInvalid (StreamFileSP &in, StreamFileSP &out, StreamFileSP &err)
|
|
{
|
|
// Before an IOHandler runs, it must have in/out/err streams.
|
|
// This function is called when one ore more of the streams
|
|
// are NULL. We use the top input reader's in/out/err streams,
|
|
// or fall back to the debugger file handles, or we fall back
|
|
// onto stdin/stdout/stderr as a last resort.
|
|
|
|
Mutex::Locker locker (m_input_reader_stack.GetMutex());
|
|
IOHandlerSP top_reader_sp (m_input_reader_stack.Top());
|
|
// If no STDIN has been set, then set it appropriately
|
|
if (!in)
|
|
{
|
|
if (top_reader_sp)
|
|
in = top_reader_sp->GetInputStreamFile();
|
|
else
|
|
in = GetInputFile();
|
|
|
|
// If there is nothing, use stdin
|
|
if (!in)
|
|
in = StreamFileSP(new StreamFile(stdin, false));
|
|
}
|
|
// If no STDOUT has been set, then set it appropriately
|
|
if (!out)
|
|
{
|
|
if (top_reader_sp)
|
|
out = top_reader_sp->GetOutputStreamFile();
|
|
else
|
|
out = GetOutputFile();
|
|
|
|
// If there is nothing, use stdout
|
|
if (!out)
|
|
out = StreamFileSP(new StreamFile(stdout, false));
|
|
}
|
|
// If no STDERR has been set, then set it appropriately
|
|
if (!err)
|
|
{
|
|
if (top_reader_sp)
|
|
err = top_reader_sp->GetErrorStreamFile();
|
|
else
|
|
err = GetErrorFile();
|
|
|
|
// If there is nothing, use stderr
|
|
if (!err)
|
|
err = StreamFileSP(new StreamFile(stdout, false));
|
|
|
|
}
|
|
}
|
|
|
|
void
|
|
Debugger::PushIOHandler (const IOHandlerSP& reader_sp)
|
|
{
|
|
if (!reader_sp)
|
|
return;
|
|
|
|
Mutex::Locker locker (m_input_reader_stack.GetMutex());
|
|
|
|
// Get the current top input reader...
|
|
IOHandlerSP top_reader_sp (m_input_reader_stack.Top());
|
|
|
|
// Don't push the same IO handler twice...
|
|
if (reader_sp == top_reader_sp)
|
|
return;
|
|
|
|
// Push our new input reader
|
|
m_input_reader_stack.Push (reader_sp);
|
|
reader_sp->Activate();
|
|
|
|
// Interrupt the top input reader to it will exit its Run() function
|
|
// and let this new input reader take over
|
|
if (top_reader_sp)
|
|
{
|
|
top_reader_sp->Deactivate();
|
|
top_reader_sp->Cancel();
|
|
}
|
|
}
|
|
|
|
bool
|
|
Debugger::PopIOHandler (const IOHandlerSP& pop_reader_sp)
|
|
{
|
|
if (! pop_reader_sp)
|
|
return false;
|
|
|
|
Mutex::Locker locker (m_input_reader_stack.GetMutex());
|
|
|
|
// The reader on the stop of the stack is done, so let the next
|
|
// read on the stack refresh its prompt and if there is one...
|
|
if (m_input_reader_stack.IsEmpty())
|
|
return false;
|
|
|
|
IOHandlerSP reader_sp(m_input_reader_stack.Top());
|
|
|
|
if (pop_reader_sp != reader_sp)
|
|
return false;
|
|
|
|
reader_sp->Deactivate();
|
|
reader_sp->Cancel();
|
|
m_input_reader_stack.Pop ();
|
|
|
|
reader_sp = m_input_reader_stack.Top();
|
|
if (reader_sp)
|
|
reader_sp->Activate();
|
|
|
|
return true;
|
|
}
|
|
|
|
StreamSP
|
|
Debugger::GetAsyncOutputStream ()
|
|
{
|
|
return StreamSP (new StreamAsynchronousIO (*this, true));
|
|
}
|
|
|
|
StreamSP
|
|
Debugger::GetAsyncErrorStream ()
|
|
{
|
|
return StreamSP (new StreamAsynchronousIO (*this, false));
|
|
}
|
|
|
|
size_t
|
|
Debugger::GetNumDebuggers()
|
|
{
|
|
if (lldb_initialized)
|
|
{
|
|
Mutex::Locker locker (GetDebuggerListMutex ());
|
|
return GetDebuggerList().size();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
lldb::DebuggerSP
|
|
Debugger::GetDebuggerAtIndex (size_t index)
|
|
{
|
|
DebuggerSP debugger_sp;
|
|
|
|
if (lldb_initialized)
|
|
{
|
|
Mutex::Locker locker (GetDebuggerListMutex ());
|
|
DebuggerList &debugger_list = GetDebuggerList();
|
|
|
|
if (index < debugger_list.size())
|
|
debugger_sp = debugger_list[index];
|
|
}
|
|
|
|
return debugger_sp;
|
|
}
|
|
|
|
DebuggerSP
|
|
Debugger::FindDebuggerWithID (lldb::user_id_t id)
|
|
{
|
|
DebuggerSP debugger_sp;
|
|
|
|
if (lldb_initialized)
|
|
{
|
|
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()->GetID() == id)
|
|
{
|
|
debugger_sp = *pos;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return debugger_sp;
|
|
}
|
|
|
|
#if 0
|
|
static void
|
|
TestPromptFormats (StackFrame *frame)
|
|
{
|
|
if (frame == NULL)
|
|
return;
|
|
|
|
StreamString s;
|
|
const char *prompt_format =
|
|
"{addr = '${addr}'\n}"
|
|
"{addr-file-or-load = '${addr-file-or-load}'\n}"
|
|
"{current-pc-arrow = '${current-pc-arrow}'\n}"
|
|
"{process.id = '${process.id}'\n}"
|
|
"{process.name = '${process.name}'\n}"
|
|
"{process.file.basename = '${process.file.basename}'\n}"
|
|
"{process.file.fullpath = '${process.file.fullpath}'\n}"
|
|
"{thread.id = '${thread.id}'\n}"
|
|
"{thread.index = '${thread.index}'\n}"
|
|
"{thread.name = '${thread.name}'\n}"
|
|
"{thread.queue = '${thread.queue}'\n}"
|
|
"{thread.stop-reason = '${thread.stop-reason}'\n}"
|
|
"{target.arch = '${target.arch}'\n}"
|
|
"{module.file.basename = '${module.file.basename}'\n}"
|
|
"{module.file.fullpath = '${module.file.fullpath}'\n}"
|
|
"{file.basename = '${file.basename}'\n}"
|
|
"{file.fullpath = '${file.fullpath}'\n}"
|
|
"{frame.index = '${frame.index}'\n}"
|
|
"{frame.pc = '${frame.pc}'\n}"
|
|
"{frame.sp = '${frame.sp}'\n}"
|
|
"{frame.fp = '${frame.fp}'\n}"
|
|
"{frame.flags = '${frame.flags}'\n}"
|
|
"{frame.reg.rdi = '${frame.reg.rdi}'\n}"
|
|
"{frame.reg.rip = '${frame.reg.rip}'\n}"
|
|
"{frame.reg.rsp = '${frame.reg.rsp}'\n}"
|
|
"{frame.reg.rbp = '${frame.reg.rbp}'\n}"
|
|
"{frame.reg.rflags = '${frame.reg.rflags}'\n}"
|
|
"{frame.reg.xmm0 = '${frame.reg.xmm0}'\n}"
|
|
"{frame.reg.carp = '${frame.reg.carp}'\n}"
|
|
"{function.id = '${function.id}'\n}"
|
|
"{function.changed = '${function.changed}'\n}"
|
|
"{function.initial-function = '${function.initial-function}'\n}"
|
|
"{function.name = '${function.name}'\n}"
|
|
"{function.name-without-args = '${function.name-without-args}'\n}"
|
|
"{function.name-with-args = '${function.name-with-args}'\n}"
|
|
"{function.addr-offset = '${function.addr-offset}'\n}"
|
|
"{function.concrete-only-addr-offset-no-padding = '${function.concrete-only-addr-offset-no-padding}'\n}"
|
|
"{function.line-offset = '${function.line-offset}'\n}"
|
|
"{function.pc-offset = '${function.pc-offset}'\n}"
|
|
"{line.file.basename = '${line.file.basename}'\n}"
|
|
"{line.file.fullpath = '${line.file.fullpath}'\n}"
|
|
"{line.number = '${line.number}'\n}"
|
|
"{line.start-addr = '${line.start-addr}'\n}"
|
|
"{line.end-addr = '${line.end-addr}'\n}"
|
|
;
|
|
|
|
SymbolContext sc (frame->GetSymbolContext(eSymbolContextEverything));
|
|
ExecutionContext exe_ctx;
|
|
frame->CalculateExecutionContext(exe_ctx);
|
|
if (Debugger::FormatPrompt (prompt_format, &sc, &exe_ctx, &sc.line_entry.range.GetBaseAddress(), s))
|
|
{
|
|
printf("%s\n", s.GetData());
|
|
}
|
|
else
|
|
{
|
|
printf ("what we got: %s\n", s.GetData());
|
|
}
|
|
}
|
|
#endif
|
|
|
|
bool
|
|
Debugger::FormatDisassemblerAddress (const FormatEntity::Entry *format,
|
|
const SymbolContext *sc,
|
|
const SymbolContext *prev_sc,
|
|
const ExecutionContext *exe_ctx,
|
|
const Address *addr,
|
|
Stream &s)
|
|
{
|
|
FormatEntity::Entry format_entry;
|
|
|
|
if (format == NULL)
|
|
{
|
|
if (exe_ctx != NULL && exe_ctx->HasTargetScope())
|
|
format = exe_ctx->GetTargetRef().GetDebugger().GetDisassemblyFormat();
|
|
if (format == NULL)
|
|
{
|
|
FormatEntity::Parse("${addr}: ", format_entry);
|
|
format = &format_entry;
|
|
}
|
|
}
|
|
bool function_changed = false;
|
|
bool initial_function = false;
|
|
if (prev_sc && (prev_sc->function || prev_sc->symbol))
|
|
{
|
|
if (sc && (sc->function || sc->symbol))
|
|
{
|
|
if (prev_sc->symbol && sc->symbol)
|
|
{
|
|
if (!sc->symbol->Compare (prev_sc->symbol->GetName(), prev_sc->symbol->GetType()))
|
|
{
|
|
function_changed = true;
|
|
}
|
|
}
|
|
else if (prev_sc->function && sc->function)
|
|
{
|
|
if (prev_sc->function->GetMangled() != sc->function->GetMangled())
|
|
{
|
|
function_changed = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// The first context on a list of instructions will have a prev_sc that
|
|
// has no Function or Symbol -- if SymbolContext had an IsValid() method, it
|
|
// would return false. But we do get a prev_sc pointer.
|
|
if ((sc && (sc->function || sc->symbol))
|
|
&& prev_sc && (prev_sc->function == NULL && prev_sc->symbol == NULL))
|
|
{
|
|
initial_function = true;
|
|
}
|
|
return FormatEntity::Format(*format, s, sc, exe_ctx, addr, NULL, function_changed, initial_function);
|
|
}
|
|
|
|
|
|
void
|
|
Debugger::SetLoggingCallback (lldb::LogOutputCallback log_callback, void *baton)
|
|
{
|
|
// For simplicity's sake, I am not going to deal with how to close down any
|
|
// open logging streams, I just redirect everything from here on out to the
|
|
// callback.
|
|
m_log_callback_stream_sp.reset (new StreamCallback (log_callback, baton));
|
|
}
|
|
|
|
bool
|
|
Debugger::EnableLog (const char *channel, const char **categories, const char *log_file, uint32_t log_options, Stream &error_stream)
|
|
{
|
|
StreamSP log_stream_sp;
|
|
if (m_log_callback_stream_sp)
|
|
{
|
|
log_stream_sp = m_log_callback_stream_sp;
|
|
// For now when using the callback mode you always get thread & timestamp.
|
|
log_options |= LLDB_LOG_OPTION_PREPEND_TIMESTAMP | LLDB_LOG_OPTION_PREPEND_THREAD_NAME;
|
|
}
|
|
else if (log_file == NULL || *log_file == '\0')
|
|
{
|
|
log_stream_sp = GetOutputFile();
|
|
}
|
|
else
|
|
{
|
|
LogStreamMap::iterator pos = m_log_streams.find(log_file);
|
|
if (pos != m_log_streams.end())
|
|
log_stream_sp = pos->second.lock();
|
|
if (!log_stream_sp)
|
|
{
|
|
uint32_t options = File::eOpenOptionWrite | File::eOpenOptionCanCreate
|
|
| File::eOpenOptionCloseOnExec | File::eOpenOptionAppend;
|
|
if (! (log_options & LLDB_LOG_OPTION_APPEND))
|
|
options |= File::eOpenOptionTruncate;
|
|
|
|
log_stream_sp.reset (new StreamFile (log_file, options));
|
|
m_log_streams[log_file] = log_stream_sp;
|
|
}
|
|
}
|
|
assert (log_stream_sp.get());
|
|
|
|
if (log_options == 0)
|
|
log_options = LLDB_LOG_OPTION_PREPEND_THREAD_NAME | LLDB_LOG_OPTION_THREADSAFE;
|
|
|
|
return Log::EnableLogChannel(log_stream_sp, log_options, channel, categories, error_stream);
|
|
}
|
|
|
|
SourceManager &
|
|
Debugger::GetSourceManager ()
|
|
{
|
|
if (m_source_manager_ap.get() == NULL)
|
|
m_source_manager_ap.reset (new SourceManager (shared_from_this()));
|
|
return *m_source_manager_ap;
|
|
}
|
|
|
|
|
|
|
|
// This function handles events that were broadcast by the process.
|
|
void
|
|
Debugger::HandleBreakpointEvent (const EventSP &event_sp)
|
|
{
|
|
using namespace lldb;
|
|
const uint32_t event_type = Breakpoint::BreakpointEventData::GetBreakpointEventTypeFromEvent (event_sp);
|
|
|
|
// if (event_type & eBreakpointEventTypeAdded
|
|
// || event_type & eBreakpointEventTypeRemoved
|
|
// || event_type & eBreakpointEventTypeEnabled
|
|
// || event_type & eBreakpointEventTypeDisabled
|
|
// || event_type & eBreakpointEventTypeCommandChanged
|
|
// || event_type & eBreakpointEventTypeConditionChanged
|
|
// || event_type & eBreakpointEventTypeIgnoreChanged
|
|
// || event_type & eBreakpointEventTypeLocationsResolved)
|
|
// {
|
|
// // Don't do anything about these events, since the breakpoint commands already echo these actions.
|
|
// }
|
|
//
|
|
if (event_type & eBreakpointEventTypeLocationsAdded)
|
|
{
|
|
uint32_t num_new_locations = Breakpoint::BreakpointEventData::GetNumBreakpointLocationsFromEvent(event_sp);
|
|
if (num_new_locations > 0)
|
|
{
|
|
BreakpointSP breakpoint = Breakpoint::BreakpointEventData::GetBreakpointFromEvent(event_sp);
|
|
StreamSP output_sp (GetAsyncOutputStream());
|
|
if (output_sp)
|
|
{
|
|
output_sp->Printf("%d location%s added to breakpoint %d\n",
|
|
num_new_locations,
|
|
num_new_locations == 1 ? "" : "s",
|
|
breakpoint->GetID());
|
|
output_sp->Flush();
|
|
}
|
|
}
|
|
}
|
|
// else if (event_type & eBreakpointEventTypeLocationsRemoved)
|
|
// {
|
|
// // These locations just get disabled, not sure it is worth spamming folks about this on the command line.
|
|
// }
|
|
// else if (event_type & eBreakpointEventTypeLocationsResolved)
|
|
// {
|
|
// // This might be an interesting thing to note, but I'm going to leave it quiet for now, it just looked noisy.
|
|
// }
|
|
}
|
|
|
|
size_t
|
|
Debugger::GetProcessSTDOUT (Process *process, Stream *stream)
|
|
{
|
|
size_t total_bytes = 0;
|
|
if (stream == NULL)
|
|
stream = GetOutputFile().get();
|
|
|
|
if (stream)
|
|
{
|
|
// The process has stuff waiting for stdout; get it and write it out to the appropriate place.
|
|
if (process == NULL)
|
|
{
|
|
TargetSP target_sp = GetTargetList().GetSelectedTarget();
|
|
if (target_sp)
|
|
process = target_sp->GetProcessSP().get();
|
|
}
|
|
if (process)
|
|
{
|
|
Error error;
|
|
size_t len;
|
|
char stdio_buffer[1024];
|
|
while ((len = process->GetSTDOUT (stdio_buffer, sizeof (stdio_buffer), error)) > 0)
|
|
{
|
|
stream->Write(stdio_buffer, len);
|
|
total_bytes += len;
|
|
}
|
|
}
|
|
stream->Flush();
|
|
}
|
|
return total_bytes;
|
|
}
|
|
|
|
size_t
|
|
Debugger::GetProcessSTDERR (Process *process, Stream *stream)
|
|
{
|
|
size_t total_bytes = 0;
|
|
if (stream == NULL)
|
|
stream = GetOutputFile().get();
|
|
|
|
if (stream)
|
|
{
|
|
// The process has stuff waiting for stderr; get it and write it out to the appropriate place.
|
|
if (process == NULL)
|
|
{
|
|
TargetSP target_sp = GetTargetList().GetSelectedTarget();
|
|
if (target_sp)
|
|
process = target_sp->GetProcessSP().get();
|
|
}
|
|
if (process)
|
|
{
|
|
Error error;
|
|
size_t len;
|
|
char stdio_buffer[1024];
|
|
while ((len = process->GetSTDERR (stdio_buffer, sizeof (stdio_buffer), error)) > 0)
|
|
{
|
|
stream->Write(stdio_buffer, len);
|
|
total_bytes += len;
|
|
}
|
|
}
|
|
stream->Flush();
|
|
}
|
|
return total_bytes;
|
|
}
|
|
|
|
|
|
// This function handles events that were broadcast by the process.
|
|
void
|
|
Debugger::HandleProcessEvent (const EventSP &event_sp)
|
|
{
|
|
using namespace lldb;
|
|
const uint32_t event_type = event_sp->GetType();
|
|
ProcessSP process_sp = Process::ProcessEventData::GetProcessFromEvent(event_sp.get());
|
|
|
|
StreamSP output_stream_sp = GetAsyncOutputStream();
|
|
StreamSP error_stream_sp = GetAsyncErrorStream();
|
|
const bool gui_enabled = IsForwardingEvents();
|
|
|
|
if (!gui_enabled)
|
|
{
|
|
bool pop_process_io_handler = false;
|
|
assert (process_sp);
|
|
|
|
bool state_is_stopped = false;
|
|
const bool got_state_changed = (event_type & Process::eBroadcastBitStateChanged) != 0;
|
|
const bool got_stdout = (event_type & Process::eBroadcastBitSTDOUT) != 0;
|
|
const bool got_stderr = (event_type & Process::eBroadcastBitSTDERR) != 0;
|
|
if (got_state_changed)
|
|
{
|
|
StateType event_state = Process::ProcessEventData::GetStateFromEvent (event_sp.get());
|
|
state_is_stopped = StateIsStoppedState(event_state, false);
|
|
}
|
|
|
|
// Display running state changes first before any STDIO
|
|
if (got_state_changed && !state_is_stopped)
|
|
{
|
|
Process::HandleProcessStateChangedEvent (event_sp, output_stream_sp.get(), pop_process_io_handler);
|
|
}
|
|
|
|
// Now display and STDOUT
|
|
if (got_stdout || got_state_changed)
|
|
{
|
|
GetProcessSTDOUT (process_sp.get(), output_stream_sp.get());
|
|
}
|
|
|
|
// Now display and STDERR
|
|
if (got_stderr || got_state_changed)
|
|
{
|
|
GetProcessSTDERR (process_sp.get(), error_stream_sp.get());
|
|
}
|
|
|
|
// Now display any stopped state changes after any STDIO
|
|
if (got_state_changed && state_is_stopped)
|
|
{
|
|
Process::HandleProcessStateChangedEvent (event_sp, output_stream_sp.get(), pop_process_io_handler);
|
|
}
|
|
|
|
output_stream_sp->Flush();
|
|
error_stream_sp->Flush();
|
|
|
|
if (pop_process_io_handler)
|
|
process_sp->PopProcessIOHandler();
|
|
}
|
|
}
|
|
|
|
void
|
|
Debugger::HandleThreadEvent (const EventSP &event_sp)
|
|
{
|
|
// At present the only thread event we handle is the Frame Changed event,
|
|
// and all we do for that is just reprint the thread status for that thread.
|
|
using namespace lldb;
|
|
const uint32_t event_type = event_sp->GetType();
|
|
if (event_type == Thread::eBroadcastBitStackChanged ||
|
|
event_type == Thread::eBroadcastBitThreadSelected )
|
|
{
|
|
ThreadSP thread_sp (Thread::ThreadEventData::GetThreadFromEvent (event_sp.get()));
|
|
if (thread_sp)
|
|
{
|
|
thread_sp->GetStatus(*GetAsyncOutputStream(), 0, 1, 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool
|
|
Debugger::IsForwardingEvents ()
|
|
{
|
|
return (bool)m_forward_listener_sp;
|
|
}
|
|
|
|
void
|
|
Debugger::EnableForwardEvents (const ListenerSP &listener_sp)
|
|
{
|
|
m_forward_listener_sp = listener_sp;
|
|
}
|
|
|
|
void
|
|
Debugger::CancelForwardEvents (const ListenerSP &listener_sp)
|
|
{
|
|
m_forward_listener_sp.reset();
|
|
}
|
|
|
|
|
|
void
|
|
Debugger::DefaultEventHandler()
|
|
{
|
|
Listener& listener(GetListener());
|
|
ConstString broadcaster_class_target(Target::GetStaticBroadcasterClass());
|
|
ConstString broadcaster_class_process(Process::GetStaticBroadcasterClass());
|
|
ConstString broadcaster_class_thread(Thread::GetStaticBroadcasterClass());
|
|
BroadcastEventSpec target_event_spec (broadcaster_class_target,
|
|
Target::eBroadcastBitBreakpointChanged);
|
|
|
|
BroadcastEventSpec process_event_spec (broadcaster_class_process,
|
|
Process::eBroadcastBitStateChanged |
|
|
Process::eBroadcastBitSTDOUT |
|
|
Process::eBroadcastBitSTDERR);
|
|
|
|
BroadcastEventSpec thread_event_spec (broadcaster_class_thread,
|
|
Thread::eBroadcastBitStackChanged |
|
|
Thread::eBroadcastBitThreadSelected );
|
|
|
|
listener.StartListeningForEventSpec (*this, target_event_spec);
|
|
listener.StartListeningForEventSpec (*this, process_event_spec);
|
|
listener.StartListeningForEventSpec (*this, thread_event_spec);
|
|
listener.StartListeningForEvents (m_command_interpreter_ap.get(),
|
|
CommandInterpreter::eBroadcastBitQuitCommandReceived |
|
|
CommandInterpreter::eBroadcastBitAsynchronousOutputData |
|
|
CommandInterpreter::eBroadcastBitAsynchronousErrorData );
|
|
|
|
// Let the thread that spawned us know that we have started up and
|
|
// that we are now listening to all required events so no events get missed
|
|
m_sync_broadcaster.BroadcastEvent(eBroadcastBitEventThreadIsListening);
|
|
|
|
bool done = false;
|
|
while (!done)
|
|
{
|
|
EventSP event_sp;
|
|
if (listener.WaitForEvent(NULL, event_sp))
|
|
{
|
|
if (event_sp)
|
|
{
|
|
Broadcaster *broadcaster = event_sp->GetBroadcaster();
|
|
if (broadcaster)
|
|
{
|
|
uint32_t event_type = event_sp->GetType();
|
|
ConstString broadcaster_class (broadcaster->GetBroadcasterClass());
|
|
if (broadcaster_class == broadcaster_class_process)
|
|
{
|
|
HandleProcessEvent (event_sp);
|
|
}
|
|
else if (broadcaster_class == broadcaster_class_target)
|
|
{
|
|
if (Breakpoint::BreakpointEventData::GetEventDataFromEvent(event_sp.get()))
|
|
{
|
|
HandleBreakpointEvent (event_sp);
|
|
}
|
|
}
|
|
else if (broadcaster_class == broadcaster_class_thread)
|
|
{
|
|
HandleThreadEvent (event_sp);
|
|
}
|
|
else if (broadcaster == m_command_interpreter_ap.get())
|
|
{
|
|
if (event_type & CommandInterpreter::eBroadcastBitQuitCommandReceived)
|
|
{
|
|
done = true;
|
|
}
|
|
else if (event_type & CommandInterpreter::eBroadcastBitAsynchronousErrorData)
|
|
{
|
|
const char *data = reinterpret_cast<const char *>(EventDataBytes::GetBytesFromEvent (event_sp.get()));
|
|
if (data && data[0])
|
|
{
|
|
StreamSP error_sp (GetAsyncErrorStream());
|
|
if (error_sp)
|
|
{
|
|
error_sp->PutCString(data);
|
|
error_sp->Flush();
|
|
}
|
|
}
|
|
}
|
|
else if (event_type & CommandInterpreter::eBroadcastBitAsynchronousOutputData)
|
|
{
|
|
const char *data = reinterpret_cast<const char *>(EventDataBytes::GetBytesFromEvent (event_sp.get()));
|
|
if (data && data[0])
|
|
{
|
|
StreamSP output_sp (GetAsyncOutputStream());
|
|
if (output_sp)
|
|
{
|
|
output_sp->PutCString(data);
|
|
output_sp->Flush();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (m_forward_listener_sp)
|
|
m_forward_listener_sp->AddEvent(event_sp);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
lldb::thread_result_t
|
|
Debugger::EventHandlerThread (lldb::thread_arg_t arg)
|
|
{
|
|
((Debugger *)arg)->DefaultEventHandler();
|
|
return NULL;
|
|
}
|
|
|
|
bool
|
|
Debugger::StartEventHandlerThread()
|
|
{
|
|
if (!m_event_handler_thread.IsJoinable())
|
|
{
|
|
// We must synchronize with the DefaultEventHandler() thread to ensure
|
|
// it is up and running and listening to events before we return from
|
|
// this function. We do this by listening to events for the
|
|
// eBroadcastBitEventThreadIsListening from the m_sync_broadcaster
|
|
Listener listener("lldb.debugger.event-handler");
|
|
listener.StartListeningForEvents(&m_sync_broadcaster, eBroadcastBitEventThreadIsListening);
|
|
|
|
// Use larger 8MB stack for this thread
|
|
m_event_handler_thread = ThreadLauncher::LaunchThread("lldb.debugger.event-handler", EventHandlerThread,
|
|
this,
|
|
NULL,
|
|
g_debugger_event_thread_stack_bytes);
|
|
|
|
// Make sure DefaultEventHandler() is running and listening to events before we return
|
|
// from this function. We are only listening for events of type
|
|
// eBroadcastBitEventThreadIsListening so we don't need to check the event, we just need
|
|
// to wait an infinite amount of time for it (NULL timeout as the first parameter)
|
|
lldb::EventSP event_sp;
|
|
listener.WaitForEvent(NULL, event_sp);
|
|
}
|
|
return m_event_handler_thread.IsJoinable();
|
|
}
|
|
|
|
void
|
|
Debugger::StopEventHandlerThread()
|
|
{
|
|
if (m_event_handler_thread.IsJoinable())
|
|
{
|
|
GetCommandInterpreter().BroadcastEvent(CommandInterpreter::eBroadcastBitQuitCommandReceived);
|
|
m_event_handler_thread.Join(nullptr);
|
|
}
|
|
}
|
|
|
|
|
|
lldb::thread_result_t
|
|
Debugger::IOHandlerThread (lldb::thread_arg_t arg)
|
|
{
|
|
Debugger *debugger = (Debugger *)arg;
|
|
debugger->ExecuteIOHandlers();
|
|
debugger->StopEventHandlerThread();
|
|
return NULL;
|
|
}
|
|
|
|
bool
|
|
Debugger::StartIOHandlerThread()
|
|
{
|
|
if (!m_io_handler_thread.IsJoinable())
|
|
m_io_handler_thread = ThreadLauncher::LaunchThread ("lldb.debugger.io-handler",
|
|
IOHandlerThread,
|
|
this,
|
|
NULL,
|
|
8*1024*1024); // Use larger 8MB stack for this thread
|
|
return m_io_handler_thread.IsJoinable();
|
|
}
|
|
|
|
void
|
|
Debugger::StopIOHandlerThread()
|
|
{
|
|
if (m_io_handler_thread.IsJoinable())
|
|
{
|
|
if (m_input_file_sp)
|
|
m_input_file_sp->GetFile().Close();
|
|
m_io_handler_thread.Join(nullptr);
|
|
}
|
|
}
|
|
|
|
Target *
|
|
Debugger::GetDummyTarget()
|
|
{
|
|
return m_target_list.GetDummyTarget (*this).get();
|
|
}
|
|
|
|
Target *
|
|
Debugger::GetSelectedOrDummyTarget(bool prefer_dummy)
|
|
{
|
|
Target *target = nullptr;
|
|
if (!prefer_dummy)
|
|
{
|
|
target = m_target_list.GetSelectedTarget().get();
|
|
if (target)
|
|
return target;
|
|
}
|
|
|
|
return GetDummyTarget();
|
|
}
|
|
|