//===-- BreakpointOptions.cpp ---------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "lldb/Breakpoint/BreakpointOptions.h" #include "lldb/Breakpoint/StoppointCallbackContext.h" #include "lldb/Core/Value.h" #include "lldb/Interpreter/CommandInterpreter.h" #include "lldb/Interpreter/CommandReturnObject.h" #include "lldb/Target/Process.h" #include "lldb/Target/Target.h" #include "lldb/Target/ThreadSpec.h" #include "lldb/Utility/Stream.h" #include "lldb/Utility/StringList.h" #include "llvm/ADT/STLExtras.h" using namespace lldb; using namespace lldb_private; const char *BreakpointOptions::CommandData::g_option_names[static_cast( BreakpointOptions::CommandData::OptionNames::LastOptionName)]{ "UserSource", "ScriptSource", "StopOnError"}; StructuredData::ObjectSP BreakpointOptions::CommandData::SerializeToStructuredData() { size_t num_strings = user_source.GetSize(); if (num_strings == 0 && script_source.empty()) { // We shouldn't serialize commands if there aren't any, return an empty sp // to indicate this. return StructuredData::ObjectSP(); } StructuredData::DictionarySP options_dict_sp( new StructuredData::Dictionary()); options_dict_sp->AddBooleanItem(GetKey(OptionNames::StopOnError), stop_on_error); StructuredData::ArraySP user_source_sp(new StructuredData::Array()); for (size_t i = 0; i < num_strings; i++) { StructuredData::StringSP item_sp( new StructuredData::String(user_source[i])); user_source_sp->AddItem(item_sp); options_dict_sp->AddItem(GetKey(OptionNames::UserSource), user_source_sp); } options_dict_sp->AddStringItem( GetKey(OptionNames::Interpreter), ScriptInterpreter::LanguageToString(interpreter)); return options_dict_sp; } std::unique_ptr BreakpointOptions::CommandData::CreateFromStructuredData( const StructuredData::Dictionary &options_dict, Status &error) { std::unique_ptr data_up(new CommandData()); bool success = options_dict.GetValueForKeyAsBoolean( GetKey(OptionNames::StopOnError), data_up->stop_on_error); llvm::StringRef interpreter_str; ScriptLanguage interp_language; success = options_dict.GetValueForKeyAsString( GetKey(OptionNames::Interpreter), interpreter_str); if (!success) { error.SetErrorString("Missing command language value."); return data_up; } interp_language = ScriptInterpreter::StringToLanguage(interpreter_str); if (interp_language == eScriptLanguageUnknown) { error.SetErrorStringWithFormatv("Unknown breakpoint command language: {0}.", interpreter_str); return data_up; } data_up->interpreter = interp_language; StructuredData::Array *user_source; success = options_dict.GetValueForKeyAsArray(GetKey(OptionNames::UserSource), user_source); if (success) { size_t num_elems = user_source->GetSize(); for (size_t i = 0; i < num_elems; i++) { if (std::optional maybe_elem_string = user_source->GetItemAtIndexAsString(i)) data_up->user_source.AppendString(*maybe_elem_string); } } return data_up; } const char *BreakpointOptions::g_option_names[( size_t)BreakpointOptions::OptionNames::LastOptionName]{ "ConditionText", "IgnoreCount", "EnabledState", "OneShotState", "AutoContinue"}; // BreakpointOptions constructor BreakpointOptions::BreakpointOptions(bool all_flags_set) : m_callback(nullptr), m_baton_is_command_baton(false), m_callback_is_synchronous(false), m_enabled(true), m_one_shot(false), m_ignore_count(0), m_condition_text_hash(0), m_inject_condition(false), m_auto_continue(false), m_set_flags(0) { if (all_flags_set) m_set_flags.Set(~((Flags::ValueType)0)); } BreakpointOptions::BreakpointOptions(const char *condition, bool enabled, int32_t ignore, bool one_shot, bool auto_continue) : m_callback(nullptr), m_baton_is_command_baton(false), m_callback_is_synchronous(false), m_enabled(enabled), m_one_shot(one_shot), m_ignore_count(ignore), m_condition_text_hash(0), m_inject_condition(false), m_auto_continue(auto_continue) { m_set_flags.Set(eEnabled | eIgnoreCount | eOneShot | eAutoContinue); if (condition && *condition != '\0') { SetCondition(condition); } } // BreakpointOptions copy constructor BreakpointOptions::BreakpointOptions(const BreakpointOptions &rhs) : m_callback(rhs.m_callback), m_callback_baton_sp(rhs.m_callback_baton_sp), m_baton_is_command_baton(rhs.m_baton_is_command_baton), m_callback_is_synchronous(rhs.m_callback_is_synchronous), m_enabled(rhs.m_enabled), m_one_shot(rhs.m_one_shot), m_ignore_count(rhs.m_ignore_count), m_inject_condition(false), m_auto_continue(rhs.m_auto_continue), m_set_flags(rhs.m_set_flags) { if (rhs.m_thread_spec_up != nullptr) m_thread_spec_up = std::make_unique(*rhs.m_thread_spec_up); m_condition_text = rhs.m_condition_text; m_condition_text_hash = rhs.m_condition_text_hash; } // BreakpointOptions assignment operator const BreakpointOptions &BreakpointOptions:: operator=(const BreakpointOptions &rhs) { m_callback = rhs.m_callback; m_callback_baton_sp = rhs.m_callback_baton_sp; m_baton_is_command_baton = rhs.m_baton_is_command_baton; m_callback_is_synchronous = rhs.m_callback_is_synchronous; m_enabled = rhs.m_enabled; m_one_shot = rhs.m_one_shot; m_ignore_count = rhs.m_ignore_count; if (rhs.m_thread_spec_up != nullptr) m_thread_spec_up = std::make_unique(*rhs.m_thread_spec_up); m_condition_text = rhs.m_condition_text; m_condition_text_hash = rhs.m_condition_text_hash; m_inject_condition = rhs.m_inject_condition; m_auto_continue = rhs.m_auto_continue; m_set_flags = rhs.m_set_flags; return *this; } void BreakpointOptions::CopyOverSetOptions(const BreakpointOptions &incoming) { if (incoming.m_set_flags.Test(eEnabled)) { m_enabled = incoming.m_enabled; m_set_flags.Set(eEnabled); } if (incoming.m_set_flags.Test(eOneShot)) { m_one_shot = incoming.m_one_shot; m_set_flags.Set(eOneShot); } if (incoming.m_set_flags.Test(eCallback)) { m_callback = incoming.m_callback; m_callback_baton_sp = incoming.m_callback_baton_sp; m_callback_is_synchronous = incoming.m_callback_is_synchronous; m_baton_is_command_baton = incoming.m_baton_is_command_baton; m_set_flags.Set(eCallback); } if (incoming.m_set_flags.Test(eIgnoreCount)) { m_ignore_count = incoming.m_ignore_count; m_set_flags.Set(eIgnoreCount); } if (incoming.m_set_flags.Test(eCondition)) { // If we're copying over an empty condition, mark it as unset. if (incoming.m_condition_text.empty()) { m_condition_text.clear(); m_condition_text_hash = 0; m_set_flags.Clear(eCondition); } else { m_condition_text = incoming.m_condition_text; m_condition_text_hash = incoming.m_condition_text_hash; m_set_flags.Set(eCondition); } } if (incoming.m_set_flags.Test(eAutoContinue)) { m_auto_continue = incoming.m_auto_continue; m_set_flags.Set(eAutoContinue); } if (incoming.m_set_flags.Test(eThreadSpec) && incoming.m_thread_spec_up) { if (!m_thread_spec_up) m_thread_spec_up = std::make_unique(*incoming.m_thread_spec_up); else *m_thread_spec_up = *incoming.m_thread_spec_up; m_set_flags.Set(eThreadSpec); } } // Destructor BreakpointOptions::~BreakpointOptions() = default; std::unique_ptr BreakpointOptions::CreateFromStructuredData( Target &target, const StructuredData::Dictionary &options_dict, Status &error) { bool enabled = true; bool one_shot = false; bool auto_continue = false; uint32_t ignore_count = 0; llvm::StringRef condition_ref(""); Flags set_options; const char *key = GetKey(OptionNames::EnabledState); bool success; if (key && options_dict.HasKey(key)) { success = options_dict.GetValueForKeyAsBoolean(key, enabled); if (!success) { error.SetErrorStringWithFormat("%s key is not a boolean.", key); return nullptr; } set_options.Set(eEnabled); } key = GetKey(OptionNames::OneShotState); if (key && options_dict.HasKey(key)) { success = options_dict.GetValueForKeyAsBoolean(key, one_shot); if (!success) { error.SetErrorStringWithFormat("%s key is not a boolean.", key); return nullptr; } set_options.Set(eOneShot); } key = GetKey(OptionNames::AutoContinue); if (key && options_dict.HasKey(key)) { success = options_dict.GetValueForKeyAsBoolean(key, auto_continue); if (!success) { error.SetErrorStringWithFormat("%s key is not a boolean.", key); return nullptr; } set_options.Set(eAutoContinue); } key = GetKey(OptionNames::IgnoreCount); if (key && options_dict.HasKey(key)) { success = options_dict.GetValueForKeyAsInteger(key, ignore_count); if (!success) { error.SetErrorStringWithFormat("%s key is not an integer.", key); return nullptr; } set_options.Set(eIgnoreCount); } key = GetKey(OptionNames::ConditionText); if (key && options_dict.HasKey(key)) { success = options_dict.GetValueForKeyAsString(key, condition_ref); if (!success) { error.SetErrorStringWithFormat("%s key is not an string.", key); return nullptr; } set_options.Set(eCondition); } std::unique_ptr cmd_data_up; StructuredData::Dictionary *cmds_dict; success = options_dict.GetValueForKeyAsDictionary( CommandData::GetSerializationKey(), cmds_dict); if (success && cmds_dict) { Status cmds_error; cmd_data_up = CommandData::CreateFromStructuredData(*cmds_dict, cmds_error); if (cmds_error.Fail()) { error.SetErrorStringWithFormat( "Failed to deserialize breakpoint command options: %s.", cmds_error.AsCString()); return nullptr; } } auto bp_options = std::make_unique( condition_ref.str().c_str(), enabled, ignore_count, one_shot, auto_continue); if (cmd_data_up) { if (cmd_data_up->interpreter == eScriptLanguageNone) bp_options->SetCommandDataCallback(cmd_data_up); else { ScriptInterpreter *interp = target.GetDebugger().GetScriptInterpreter(); if (!interp) { error.SetErrorString( "Can't set script commands - no script interpreter"); return nullptr; } if (interp->GetLanguage() != cmd_data_up->interpreter) { error.SetErrorStringWithFormat( "Current script language doesn't match breakpoint's language: %s", ScriptInterpreter::LanguageToString(cmd_data_up->interpreter) .c_str()); return nullptr; } Status script_error; script_error = interp->SetBreakpointCommandCallback(*bp_options, cmd_data_up); if (script_error.Fail()) { error.SetErrorStringWithFormat("Error generating script callback: %s.", error.AsCString()); return nullptr; } } } StructuredData::Dictionary *thread_spec_dict; success = options_dict.GetValueForKeyAsDictionary( ThreadSpec::GetSerializationKey(), thread_spec_dict); if (success) { Status thread_spec_error; std::unique_ptr thread_spec_up = ThreadSpec::CreateFromStructuredData(*thread_spec_dict, thread_spec_error); if (thread_spec_error.Fail()) { error.SetErrorStringWithFormat( "Failed to deserialize breakpoint thread spec options: %s.", thread_spec_error.AsCString()); return nullptr; } bp_options->SetThreadSpec(thread_spec_up); } return bp_options; } StructuredData::ObjectSP BreakpointOptions::SerializeToStructuredData() { StructuredData::DictionarySP options_dict_sp( new StructuredData::Dictionary()); if (m_set_flags.Test(eEnabled)) options_dict_sp->AddBooleanItem(GetKey(OptionNames::EnabledState), m_enabled); if (m_set_flags.Test(eOneShot)) options_dict_sp->AddBooleanItem(GetKey(OptionNames::OneShotState), m_one_shot); if (m_set_flags.Test(eAutoContinue)) options_dict_sp->AddBooleanItem(GetKey(OptionNames::AutoContinue), m_auto_continue); if (m_set_flags.Test(eIgnoreCount)) options_dict_sp->AddIntegerItem(GetKey(OptionNames::IgnoreCount), m_ignore_count); if (m_set_flags.Test(eCondition)) options_dict_sp->AddStringItem(GetKey(OptionNames::ConditionText), m_condition_text); if (m_set_flags.Test(eCallback) && m_baton_is_command_baton) { auto cmd_baton = std::static_pointer_cast(m_callback_baton_sp); StructuredData::ObjectSP commands_sp = cmd_baton->getItem()->SerializeToStructuredData(); if (commands_sp) { options_dict_sp->AddItem( BreakpointOptions::CommandData::GetSerializationKey(), commands_sp); } } if (m_set_flags.Test(eThreadSpec) && m_thread_spec_up) { StructuredData::ObjectSP thread_spec_sp = m_thread_spec_up->SerializeToStructuredData(); options_dict_sp->AddItem(ThreadSpec::GetSerializationKey(), thread_spec_sp); } return options_dict_sp; } // Callbacks void BreakpointOptions::SetCallback(BreakpointHitCallback callback, const lldb::BatonSP &callback_baton_sp, bool callback_is_synchronous) { // FIXME: This seems unsafe. If BatonSP actually *is* a CommandBaton, but // in a shared_ptr instead of a shared_ptr, then we will // set m_baton_is_command_baton to false, which is incorrect. One possible // solution is to make the base Baton class provide a method such as: // virtual StringRef getBatonId() const { return ""; } // and have CommandBaton override this to return something unique, and then // check for it here. Another option might be to make Baton using the llvm // casting infrastructure, so that we could write something like: // if (llvm::isa(callback_baton_sp)) // at relevant callsites instead of storing a boolean. m_callback_is_synchronous = callback_is_synchronous; m_callback = callback; m_callback_baton_sp = callback_baton_sp; m_baton_is_command_baton = false; m_set_flags.Set(eCallback); } void BreakpointOptions::SetCallback( BreakpointHitCallback callback, const BreakpointOptions::CommandBatonSP &callback_baton_sp, bool callback_is_synchronous) { m_callback_is_synchronous = callback_is_synchronous; m_callback = callback; m_callback_baton_sp = callback_baton_sp; m_baton_is_command_baton = true; m_set_flags.Set(eCallback); } void BreakpointOptions::ClearCallback() { m_callback = nullptr; m_callback_is_synchronous = false; m_callback_baton_sp.reset(); m_baton_is_command_baton = false; m_set_flags.Clear(eCallback); } Baton *BreakpointOptions::GetBaton() { return m_callback_baton_sp.get(); } const Baton *BreakpointOptions::GetBaton() const { return m_callback_baton_sp.get(); } bool BreakpointOptions::InvokeCallback(StoppointCallbackContext *context, lldb::user_id_t break_id, lldb::user_id_t break_loc_id) { if (m_callback) { if (context->is_synchronous == IsCallbackSynchronous()) { return m_callback(m_callback_baton_sp ? m_callback_baton_sp->data() : nullptr, context, break_id, break_loc_id); } else if (IsCallbackSynchronous()) { return false; } } return true; } bool BreakpointOptions::HasCallback() const { return static_cast(m_callback); } bool BreakpointOptions::GetCommandLineCallbacks(StringList &command_list) { if (!HasCallback()) return false; if (!m_baton_is_command_baton) return false; auto cmd_baton = std::static_pointer_cast(m_callback_baton_sp); CommandData *data = cmd_baton->getItem(); if (!data) return false; command_list = data->user_source; return true; } void BreakpointOptions::SetCondition(const char *condition) { if (!condition || condition[0] == '\0') { condition = ""; m_set_flags.Clear(eCondition); } else m_set_flags.Set(eCondition); m_condition_text.assign(condition); std::hash hasher; m_condition_text_hash = hasher(m_condition_text); } const char *BreakpointOptions::GetConditionText(size_t *hash) const { if (!m_condition_text.empty()) { if (hash) *hash = m_condition_text_hash; return m_condition_text.c_str(); } else { return nullptr; } } const ThreadSpec *BreakpointOptions::GetThreadSpecNoCreate() const { return m_thread_spec_up.get(); } ThreadSpec *BreakpointOptions::GetThreadSpec() { if (m_thread_spec_up == nullptr) { m_set_flags.Set(eThreadSpec); m_thread_spec_up = std::make_unique(); } return m_thread_spec_up.get(); } void BreakpointOptions::SetThreadID(lldb::tid_t thread_id) { GetThreadSpec()->SetTID(thread_id); m_set_flags.Set(eThreadSpec); } void BreakpointOptions::SetThreadSpec( std::unique_ptr &thread_spec_up) { m_thread_spec_up = std::move(thread_spec_up); m_set_flags.Set(eThreadSpec); } void BreakpointOptions::GetDescription(Stream *s, lldb::DescriptionLevel level) const { // Figure out if there are any options not at their default value, and only // print anything if there are: if (m_ignore_count != 0 || !m_enabled || m_one_shot || m_auto_continue || (GetThreadSpecNoCreate() != nullptr && GetThreadSpecNoCreate()->HasSpecification())) { if (level == lldb::eDescriptionLevelVerbose) { s->EOL(); s->IndentMore(); s->Indent(); s->PutCString("Breakpoint Options:\n"); s->IndentMore(); s->Indent(); } else s->PutCString(" Options: "); if (m_ignore_count > 0) s->Printf("ignore: %d ", m_ignore_count); s->Printf("%sabled ", m_enabled ? "en" : "dis"); if (m_one_shot) s->Printf("one-shot "); if (m_auto_continue) s->Printf("auto-continue "); if (m_thread_spec_up) m_thread_spec_up->GetDescription(s, level); if (level == lldb::eDescriptionLevelFull) { s->IndentLess(); s->IndentMore(); } } if (m_callback_baton_sp.get()) { if (level != eDescriptionLevelBrief) { s->EOL(); m_callback_baton_sp->GetDescription(s->AsRawOstream(), level, s->GetIndentLevel()); } } if (!m_condition_text.empty()) { if (level != eDescriptionLevelBrief) { s->EOL(); s->Printf("Condition: %s\n", m_condition_text.c_str()); } } } void BreakpointOptions::CommandBaton::GetDescription( llvm::raw_ostream &s, lldb::DescriptionLevel level, unsigned indentation) const { const CommandData *data = getItem(); if (level == eDescriptionLevelBrief) { s << ", commands = " << ((data && data->user_source.GetSize() > 0) ? "yes" : "no"); return; } indentation += 2; s.indent(indentation); s << "Breakpoint commands"; if (data->interpreter != eScriptLanguageNone) s << llvm::formatv(" ({0}):\n", ScriptInterpreter::LanguageToString(data->interpreter)); else s << ":\n"; indentation += 2; if (data && data->user_source.GetSize() > 0) { for (llvm::StringRef str : data->user_source) { s.indent(indentation); s << str << "\n"; } } else s << "No commands.\n"; } void BreakpointOptions::SetCommandDataCallback( std::unique_ptr &cmd_data) { cmd_data->interpreter = eScriptLanguageNone; auto baton_sp = std::make_shared(std::move(cmd_data)); SetCallback(BreakpointOptions::BreakpointOptionsCallbackFunction, baton_sp); m_set_flags.Set(eCallback); } bool BreakpointOptions::BreakpointOptionsCallbackFunction( void *baton, StoppointCallbackContext *context, lldb::user_id_t break_id, lldb::user_id_t break_loc_id) { bool ret_value = true; if (baton == nullptr) return true; CommandData *data = (CommandData *)baton; StringList &commands = data->user_source; if (commands.GetSize() > 0) { ExecutionContext exe_ctx(context->exe_ctx_ref); Target *target = exe_ctx.GetTargetPtr(); if (target) { Debugger &debugger = target->GetDebugger(); CommandReturnObject result(debugger.GetUseColor()); // Rig up the results secondary output stream to the debugger's, so the // output will come out synchronously if the debugger is set up that way. StreamSP output_stream(debugger.GetAsyncOutputStream()); StreamSP error_stream(debugger.GetAsyncErrorStream()); result.SetImmediateOutputStream(output_stream); result.SetImmediateErrorStream(error_stream); CommandInterpreterRunOptions options; options.SetStopOnContinue(true); options.SetStopOnError(data->stop_on_error); options.SetEchoCommands(true); options.SetPrintResults(true); options.SetPrintErrors(true); options.SetAddToHistory(false); debugger.GetCommandInterpreter().HandleCommands(commands, exe_ctx, options, result); result.GetImmediateOutputStream()->Flush(); result.GetImmediateErrorStream()->Flush(); } } return ret_value; } void BreakpointOptions::Clear() { m_set_flags.Clear(); m_thread_spec_up.release(); m_one_shot = false; m_ignore_count = 0; m_auto_continue = false; m_callback = nullptr; m_callback_baton_sp.reset(); m_baton_is_command_baton = false; m_callback_is_synchronous = false; m_enabled = false; m_condition_text.clear(); }