llvm-project/lldb/source/Interpreter/ScriptInterpreter.cpp
Med Ismail Bennani 9a9fce1fed [lldb] Fix {break,watch}point command function stopping behaviour
In order to run a {break,watch}point command, lldb can resolve to the
script interpreter to run an arbitrary piece of code or call into a
user-provided function. To do so, we will generate a wrapping function,
where we first copy lldb's internal dictionary keys into the
interpreter's global dictionary, copied inline the user code before
resetting the global dictionary to its previous state.

However, {break,watch}point commands can optionally return a value that
would tell lldb whether we should stop or not. This feature was
only implemented for breakpoint commands and since we inlined the user
code directly into the wrapping function, introducing an early return,
that caused lldb to let the interpreter global dictionary tinted with the
internal dictionary keys.

This patch fixes that issue while also adding the stopping behaviour to
watchpoint commands.

To do so, this patch refactors the {break,watch}point command creation
method, to let the lldb wrapper function generator know if the user code is
a function call or a arbitrary expression.

Then the wrapper generator, if the user input was a function call, the
wrapper function will call the user function and save the return value into
a variable. If the user input was an arbitrary expression, the wrapper  will
inline it into a nested function, call the nested function and save the
return value into the same variable. After resetting the interpreter global
dictionary to its previous state, the generated wrapper function will return
the varible containing the return value.

rdar://105461140

Differential Revision: https://reviews.llvm.org/D144688

Signed-off-by: Med Ismail Bennani <medismail.bennani@gmail.com>
2023-02-28 11:39:58 -08:00

248 lines
8.5 KiB
C++

//===-- ScriptInterpreter.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/Interpreter/ScriptInterpreter.h"
#include "lldb/Core/Debugger.h"
#include "lldb/Host/ConnectionFileDescriptor.h"
#include "lldb/Host/Pipe.h"
#include "lldb/Host/PseudoTerminal.h"
#include "lldb/Interpreter/CommandReturnObject.h"
#include "lldb/Utility/Status.h"
#include "lldb/Utility/Stream.h"
#include "lldb/Utility/StringList.h"
#if defined(_WIN32)
#include "lldb/Host/windows/ConnectionGenericFileWindows.h"
#endif
#include <cstdio>
#include <cstdlib>
#include <memory>
#include <optional>
#include <string>
using namespace lldb;
using namespace lldb_private;
ScriptInterpreter::ScriptInterpreter(
Debugger &debugger, lldb::ScriptLanguage script_lang,
lldb::ScriptedPlatformInterfaceUP scripted_platform_interface_up)
: m_debugger(debugger), m_script_lang(script_lang),
m_scripted_platform_interface_up(
std::move(scripted_platform_interface_up)) {}
void ScriptInterpreter::CollectDataForBreakpointCommandCallback(
std::vector<std::reference_wrapper<BreakpointOptions>> &bp_options_vec,
CommandReturnObject &result) {
result.AppendError(
"This script interpreter does not support breakpoint callbacks.");
}
void ScriptInterpreter::CollectDataForWatchpointCommandCallback(
WatchpointOptions *bp_options, CommandReturnObject &result) {
result.AppendError(
"This script interpreter does not support watchpoint callbacks.");
}
StructuredData::DictionarySP ScriptInterpreter::GetInterpreterInfo() {
return nullptr;
}
bool ScriptInterpreter::LoadScriptingModule(const char *filename,
const LoadScriptOptions &options,
lldb_private::Status &error,
StructuredData::ObjectSP *module_sp,
FileSpec extra_search_dir) {
error.SetErrorString(
"This script interpreter does not support importing modules.");
return false;
}
std::string ScriptInterpreter::LanguageToString(lldb::ScriptLanguage language) {
switch (language) {
case eScriptLanguageNone:
return "None";
case eScriptLanguagePython:
return "Python";
case eScriptLanguageLua:
return "Lua";
case eScriptLanguageUnknown:
return "Unknown";
}
llvm_unreachable("Unhandled ScriptInterpreter!");
}
lldb::DataExtractorSP
ScriptInterpreter::GetDataExtractorFromSBData(const lldb::SBData &data) const {
return data.m_opaque_sp;
}
Status
ScriptInterpreter::GetStatusFromSBError(const lldb::SBError &error) const {
if (error.m_opaque_up)
return *error.m_opaque_up;
return Status();
}
std::optional<MemoryRegionInfo>
ScriptInterpreter::GetOpaqueTypeFromSBMemoryRegionInfo(
const lldb::SBMemoryRegionInfo &mem_region) const {
if (!mem_region.m_opaque_up)
return std::nullopt;
return *mem_region.m_opaque_up.get();
}
lldb::ScriptLanguage
ScriptInterpreter::StringToLanguage(const llvm::StringRef &language) {
if (language.equals_insensitive(LanguageToString(eScriptLanguageNone)))
return eScriptLanguageNone;
if (language.equals_insensitive(LanguageToString(eScriptLanguagePython)))
return eScriptLanguagePython;
if (language.equals_insensitive(LanguageToString(eScriptLanguageLua)))
return eScriptLanguageLua;
return eScriptLanguageUnknown;
}
Status ScriptInterpreter::SetBreakpointCommandCallback(
std::vector<std::reference_wrapper<BreakpointOptions>> &bp_options_vec,
const char *callback_text) {
Status error;
for (BreakpointOptions &bp_options : bp_options_vec) {
error = SetBreakpointCommandCallback(bp_options, callback_text,
/*is_callback=*/false);
if (!error.Success())
break;
}
return error;
}
Status ScriptInterpreter::SetBreakpointCommandCallbackFunction(
std::vector<std::reference_wrapper<BreakpointOptions>> &bp_options_vec,
const char *function_name, StructuredData::ObjectSP extra_args_sp) {
Status error;
for (BreakpointOptions &bp_options : bp_options_vec) {
error = SetBreakpointCommandCallbackFunction(bp_options, function_name,
extra_args_sp);
if (!error.Success())
return error;
}
return error;
}
std::unique_ptr<ScriptInterpreterLocker>
ScriptInterpreter::AcquireInterpreterLock() {
return std::make_unique<ScriptInterpreterLocker>();
}
static void ReadThreadBytesReceived(void *baton, const void *src,
size_t src_len) {
if (src && src_len) {
Stream *strm = (Stream *)baton;
strm->Write(src, src_len);
strm->Flush();
}
}
llvm::Expected<std::unique_ptr<ScriptInterpreterIORedirect>>
ScriptInterpreterIORedirect::Create(bool enable_io, Debugger &debugger,
CommandReturnObject *result) {
if (enable_io)
return std::unique_ptr<ScriptInterpreterIORedirect>(
new ScriptInterpreterIORedirect(debugger, result));
auto nullin = FileSystem::Instance().Open(FileSpec(FileSystem::DEV_NULL),
File::eOpenOptionReadOnly);
if (!nullin)
return nullin.takeError();
auto nullout = FileSystem::Instance().Open(FileSpec(FileSystem::DEV_NULL),
File::eOpenOptionWriteOnly);
if (!nullout)
return nullin.takeError();
return std::unique_ptr<ScriptInterpreterIORedirect>(
new ScriptInterpreterIORedirect(std::move(*nullin), std::move(*nullout)));
}
ScriptInterpreterIORedirect::ScriptInterpreterIORedirect(
std::unique_ptr<File> input, std::unique_ptr<File> output)
: m_input_file_sp(std::move(input)),
m_output_file_sp(std::make_shared<StreamFile>(std::move(output))),
m_error_file_sp(m_output_file_sp),
m_communication("lldb.ScriptInterpreterIORedirect.comm"),
m_disconnect(false) {}
ScriptInterpreterIORedirect::ScriptInterpreterIORedirect(
Debugger &debugger, CommandReturnObject *result)
: m_communication("lldb.ScriptInterpreterIORedirect.comm"),
m_disconnect(false) {
if (result) {
m_input_file_sp = debugger.GetInputFileSP();
Pipe pipe;
Status pipe_result = pipe.CreateNew(false);
#if defined(_WIN32)
lldb::file_t read_file = pipe.GetReadNativeHandle();
pipe.ReleaseReadFileDescriptor();
std::unique_ptr<ConnectionGenericFile> conn_up =
std::make_unique<ConnectionGenericFile>(read_file, true);
#else
std::unique_ptr<ConnectionFileDescriptor> conn_up =
std::make_unique<ConnectionFileDescriptor>(
pipe.ReleaseReadFileDescriptor(), true);
#endif
if (conn_up->IsConnected()) {
m_communication.SetConnection(std::move(conn_up));
m_communication.SetReadThreadBytesReceivedCallback(
ReadThreadBytesReceived, &result->GetOutputStream());
m_communication.StartReadThread();
m_disconnect = true;
FILE *outfile_handle = fdopen(pipe.ReleaseWriteFileDescriptor(), "w");
m_output_file_sp = std::make_shared<StreamFile>(outfile_handle, true);
m_error_file_sp = m_output_file_sp;
if (outfile_handle)
::setbuf(outfile_handle, nullptr);
result->SetImmediateOutputFile(debugger.GetOutputStream().GetFileSP());
result->SetImmediateErrorFile(debugger.GetErrorStream().GetFileSP());
}
}
if (!m_input_file_sp || !m_output_file_sp || !m_error_file_sp)
debugger.AdoptTopIOHandlerFilesIfInvalid(m_input_file_sp, m_output_file_sp,
m_error_file_sp);
}
void ScriptInterpreterIORedirect::Flush() {
if (m_output_file_sp)
m_output_file_sp->Flush();
if (m_error_file_sp)
m_error_file_sp->Flush();
}
ScriptInterpreterIORedirect::~ScriptInterpreterIORedirect() {
if (!m_disconnect)
return;
assert(m_output_file_sp);
assert(m_error_file_sp);
assert(m_output_file_sp == m_error_file_sp);
// Close the write end of the pipe since we are done with our one line
// script. This should cause the read thread that output_comm is using to
// exit.
m_output_file_sp->GetFile().Close();
// The close above should cause this thread to exit when it gets to the end
// of file, so let it get all its data.
m_communication.JoinReadThread();
// Now we can close the read end of the pipe.
m_communication.Disconnect();
}