23 changes: 6 additions & 17 deletions lldb/include/lldb/Target/Process.h
Original file line number Diff line number Diff line change
Expand Up @@ -122,8 +122,6 @@ class ProcessAttachInfo : public ProcessInstanceInfo {
ProcessInfo::operator=(launch_info);
SetProcessPluginName(launch_info.GetProcessPluginName());
SetResumeCount(launch_info.GetResumeCount());
SetListener(launch_info.GetListener());
SetHijackListener(launch_info.GetHijackListener());
m_detach_on_error = launch_info.GetDetachOnError();
}

Expand Down Expand Up @@ -174,28 +172,13 @@ class ProcessAttachInfo : public ProcessInstanceInfo {
return false;
}

lldb::ListenerSP GetHijackListener() const { return m_hijack_listener_sp; }

void SetHijackListener(const lldb::ListenerSP &listener_sp) {
m_hijack_listener_sp = listener_sp;
}

bool GetDetachOnError() const { return m_detach_on_error; }

void SetDetachOnError(bool enable) { m_detach_on_error = enable; }

// Get and set the actual listener that will be used for the process events
lldb::ListenerSP GetListener() const { return m_listener_sp; }

void SetListener(const lldb::ListenerSP &listener_sp) {
m_listener_sp = listener_sp;
}

lldb::ListenerSP GetListenerForProcess(Debugger &debugger);

protected:
lldb::ListenerSP m_listener_sp;
lldb::ListenerSP m_hijack_listener_sp;
std::string m_plugin_name;
uint32_t m_resume_count = 0; // How many times do we resume after launching
bool m_wait_for_launch = false;
Expand Down Expand Up @@ -397,6 +380,10 @@ class Process : public std::enable_shared_from_this<Process>,
return GetStaticBroadcasterClass();
}

void SetShadowListener(lldb::ListenerSP listener_sp) override {
Broadcaster::SetShadowListener(listener_sp);
}

/// A notification structure that can be used by clients to listen
/// for changes in a process's lifetime.
///
Expand Down Expand Up @@ -2565,6 +2552,8 @@ void PruneThreadPlans();

virtual void *GetImplementation() { return nullptr; }

virtual void ForceScriptedState(lldb::StateType state) {}

protected:
friend class Trace;

Expand Down
8 changes: 8 additions & 0 deletions lldb/include/lldb/Utility/Broadcaster.h
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,10 @@ class Broadcaster {

lldb::BroadcasterManagerSP GetManager();

virtual void SetShadowListener(lldb::ListenerSP listener_sp) {
m_broadcaster_sp->m_shadow_listener = listener_sp;
}

protected:
/// BroadcasterImpl contains the actual Broadcaster implementation. The
/// Broadcaster makes a BroadcasterImpl which lives as long as it does. The
Expand Down Expand Up @@ -513,6 +517,10 @@ class Broadcaster {
/// for now this is just for private hijacking.
std::vector<uint32_t> m_hijacking_masks;

/// A optional listener that all private events get also broadcasted to,
/// on top the hijacked / default listeners.
lldb::ListenerSP m_shadow_listener = nullptr;

private:
BroadcasterImpl(const BroadcasterImpl &) = delete;
const BroadcasterImpl &operator=(const BroadcasterImpl &) = delete;
Expand Down
3 changes: 3 additions & 0 deletions lldb/include/lldb/Utility/Listener.h
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ class Listener : public std::enable_shared_from_this<Listener> {

size_t HandleBroadcastEvent(lldb::EventSP &event_sp);

void SetShadow(bool is_shadow) { m_is_shadow = is_shadow; }

private:
// Classes that inherit from Listener can see and modify these
struct BroadcasterInfo {
Expand Down Expand Up @@ -134,6 +136,7 @@ class Listener : public std::enable_shared_from_this<Listener> {
std::mutex m_events_mutex; // Protects m_broadcasters and m_events
std::condition_variable m_events_condition;
broadcaster_manager_collection m_broadcaster_managers;
bool m_is_shadow = false;

void BroadcasterWillDestruct(Broadcaster *);

Expand Down
22 changes: 22 additions & 0 deletions lldb/include/lldb/Utility/ProcessInfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,25 @@ class ProcessInfo {
m_scripted_metadata_sp = metadata_sp;
}

// Get and set the actual listener that will be used for the process events
lldb::ListenerSP GetListener() const { return m_listener_sp; }

void SetListener(const lldb::ListenerSP &listener_sp) {
m_listener_sp = listener_sp;
}

lldb::ListenerSP GetHijackListener() const { return m_hijack_listener_sp; }

void SetHijackListener(const lldb::ListenerSP &listener_sp) {
m_hijack_listener_sp = listener_sp;
}

lldb::ListenerSP GetShadowListener() const { return m_shadow_listener_sp; }

void SetShadowListener(const lldb::ListenerSP &listener_sp) {
m_shadow_listener_sp = listener_sp;
}

protected:
FileSpec m_executable;
std::string m_arg0; // argv[0] if supported. If empty, then use m_executable.
Expand All @@ -109,6 +128,9 @@ class ProcessInfo {
ArchSpec m_arch;
lldb::pid_t m_pid = LLDB_INVALID_PROCESS_ID;
lldb::ScriptedMetadataSP m_scripted_metadata_sp = nullptr;
lldb::ListenerSP m_listener_sp = nullptr;
lldb::ListenerSP m_hijack_listener_sp = nullptr;
lldb::ListenerSP m_shadow_listener_sp = nullptr;
};

// ProcessInstanceInfo
Expand Down
15 changes: 11 additions & 4 deletions lldb/packages/Python/lldbsuite/test/lldbutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -1202,18 +1202,25 @@ def start_listening_from(broadcaster, event_mask):
broadcaster.AddListener(listener, event_mask)
return listener

def fetch_next_event(test, listener, broadcaster, timeout=10):
def fetch_next_event(test, listener, broadcaster, match_class=False, timeout=10):
"""Fetch one event from the listener and return it if it matches the provided broadcaster.
If `match_class` is set to True, this will match an event with an entire broadcaster class.
Fails otherwise."""

event = lldb.SBEvent()

if listener.WaitForEvent(timeout, event):
if event.BroadcasterMatchesRef(broadcaster):
return event
if match_class:
if event.GetBroadcasterClass() == broadcaster:
return event
else:
if event.BroadcasterMatchesRef(broadcaster):
return event

stream = lldb.SBStream()
event.GetDescription(stream)
test.fail("received event '%s' from unexpected broadcaster '%s'." %
(event.GetDescription(), event.GetBroadcaster().GetName()))
(stream.GetData(), event.GetBroadcaster().GetName()))

test.fail("couldn't fetch an event before reaching the timeout.")

Expand Down
21 changes: 21 additions & 0 deletions lldb/source/API/SBAttachInfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,27 @@ void SBAttachInfo::SetListener(SBListener &listener) {
m_opaque_sp->SetListener(listener.GetSP());
}

SBListener SBAttachInfo::GetShadowListener() {
LLDB_INSTRUMENT_VA(this);

lldb::ListenerSP shadow_sp = m_opaque_sp->GetShadowListener();
if (!shadow_sp)
return SBListener();
return SBListener(shadow_sp);
}

void SBAttachInfo::SetShadowListener(SBListener &listener) {
LLDB_INSTRUMENT_VA(this, listener);

ListenerSP listener_sp = listener.GetSP();
if (listener_sp && listener.IsValid())
listener_sp->SetShadow(true);
else
listener_sp = nullptr;

m_opaque_sp->SetShadowListener(listener_sp);
}

const char *SBAttachInfo::GetScriptedProcessClassName() const {
LLDB_INSTRUMENT_VA(this);

Expand Down
6 changes: 6 additions & 0 deletions lldb/source/API/SBError.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ SBError::SBError(const SBError &rhs) {
m_opaque_up = clone(rhs.m_opaque_up);
}

SBError::SBError(const char *message) {
LLDB_INSTRUMENT_VA(this, message);

SetErrorString(message);
}

SBError::SBError(const lldb_private::Status &status)
: m_opaque_up(new Status(status)) {
LLDB_INSTRUMENT_VA(this, status);
Expand Down
22 changes: 22 additions & 0 deletions lldb/source/API/SBLaunchInfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include "lldb/API/SBStructuredData.h"
#include "lldb/Core/StructuredDataImpl.h"
#include "lldb/Host/ProcessLaunchInfo.h"
#include "lldb/Utility/Listener.h"
#include "lldb/Utility/ScriptedMetadata.h"

using namespace lldb;
Expand Down Expand Up @@ -387,3 +388,24 @@ void SBLaunchInfo::SetScriptedProcessDictionary(lldb::SBStructuredData dict) {
metadata_sp = std::make_shared<ScriptedMetadata>(class_name, dict_sp);
m_opaque_sp->SetScriptedMetadata(metadata_sp);
}

SBListener SBLaunchInfo::GetShadowListener() {
LLDB_INSTRUMENT_VA(this);

lldb::ListenerSP shadow_sp = m_opaque_sp->GetShadowListener();
if (!shadow_sp)
return SBListener();
return SBListener(shadow_sp);
}

void SBLaunchInfo::SetShadowListener(SBListener &listener) {
LLDB_INSTRUMENT_VA(this, listener);

ListenerSP listener_sp = listener.GetSP();
if (listener_sp && listener.IsValid())
listener_sp->SetShadow(true);
else
listener_sp = nullptr;

m_opaque_sp->SetShadowListener(listener_sp);
}
10 changes: 10 additions & 0 deletions lldb/source/API/SBProcess.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,16 @@ SBEvent SBProcess::GetStopEventForStopID(uint32_t stop_id) {
return sb_event;
}

void SBProcess::ForceScriptedState(StateType new_state) {
LLDB_INSTRUMENT_VA(this, new_state);

if (ProcessSP process_sp = GetSP()) {
std::lock_guard<std::recursive_mutex> guard(
process_sp->GetTarget().GetAPIMutex());
process_sp->ForceScriptedState(new_state);
}
}

StateType SBProcess::GetState() {
LLDB_INSTRUMENT_VA(this);

Expand Down
4 changes: 2 additions & 2 deletions lldb/source/Host/common/ProcessLaunchInfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ using namespace lldb_private;

ProcessLaunchInfo::ProcessLaunchInfo()
: ProcessInfo(), m_working_dir(), m_plugin_name(), m_flags(0),
m_file_actions(), m_pty(new PseudoTerminal), m_monitor_callback(nullptr),
m_listener_sp(), m_hijack_listener_sp() {}
m_file_actions(), m_pty(new PseudoTerminal), m_monitor_callback(nullptr) {
}

ProcessLaunchInfo::ProcessLaunchInfo(const FileSpec &stdin_file_spec,
const FileSpec &stdout_file_spec,
Expand Down
5 changes: 5 additions & 0 deletions lldb/source/Interpreter/ScriptInterpreter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,11 @@ ScriptInterpreter::GetDataExtractorFromSBData(const lldb::SBData &data) const {
return data.m_opaque_sp;
}

lldb::BreakpointSP ScriptInterpreter::GetOpaqueTypeFromSBBreakpoint(
const lldb::SBBreakpoint &breakpoint) const {
return breakpoint.m_opaque_wp.lock();
}

lldb::ProcessAttachInfoSP ScriptInterpreter::GetOpaqueTypeFromSBAttachInfo(
const lldb::SBAttachInfo &attach_info) const {
return attach_info.m_opaque_sp;
Expand Down
2 changes: 2 additions & 0 deletions lldb/source/Plugins/Platform/POSIX/PlatformPOSIX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,7 @@ lldb::ProcessSP PlatformPOSIX::Attach(ProcessAttachInfo &attach_info,
attach_info.SetHijackListener(listener_sp);
}
process_sp->HijackProcessEvents(listener_sp);
process_sp->SetShadowListener(attach_info.GetShadowListener());
error = process_sp->Attach(attach_info);
}
}
Expand Down Expand Up @@ -458,6 +459,7 @@ lldb::ProcessSP PlatformPOSIX::DebugProcess(ProcessLaunchInfo &launch_info,
LLDB_LOG(log, "successfully created process");

process_sp->HijackProcessEvents(launch_info.GetHijackListener());
process_sp->SetShadowListener(launch_info.GetShadowListener());

// Log file actions.
if (log) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,7 @@ PlatformRemoteGDBServer::DebugProcess(ProcessLaunchInfo &launch_info,

if (process_sp) {
process_sp->HijackProcessEvents(launch_info.GetHijackListener());
process_sp->SetShadowListener(launch_info.GetShadowListener());

error = process_sp->ConnectRemote(connect_url.c_str());
// Retry the connect remote one time...
Expand Down Expand Up @@ -515,6 +516,7 @@ lldb::ProcessSP PlatformRemoteGDBServer::Attach(
ListenerSP listener_sp = attach_info.GetHijackListener();
if (listener_sp)
process_sp->HijackProcessEvents(listener_sp);
process_sp->SetShadowListener(attach_info.GetShadowListener());
error = process_sp->Attach(attach_info);
}

Expand Down
71 changes: 26 additions & 45 deletions lldb/source/Plugins/Process/scripted/ScriptedProcess.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -150,22 +150,15 @@ Status ScriptedProcess::DoLoadCore() {

Status ScriptedProcess::DoLaunch(Module *exe_module,
ProcessLaunchInfo &launch_info) {
/* FIXME: This doesn't reflect how lldb actually launches a process.
In reality, it attaches to debugserver, then resume the process. */
LLDB_LOGF(GetLog(LLDBLog::Process), "ScriptedProcess::%s launching process", __FUNCTION__);

/* MARK: This doesn't reflect how lldb actually launches a process.
In reality, it attaches to debugserver, then resume the process.
That's not true in all cases. If debugserver is remote, lldb
asks debugserver to launch the process for it. */
Status error = GetInterface().Launch();
SetPrivateState(eStateRunning);

if (error.Fail())
return error;

// TODO: Fetch next state from stopped event queue then send stop event
// const StateType state = SetThreadStopInfo(response);
// if (state != eStateInvalid) {
// SetPrivateState(state);

SetPrivateState(eStateStopped);

return {};
return error;
}

void ScriptedProcess::DidLaunch() { m_pid = GetInterface().GetProcessID(); }
Expand All @@ -177,25 +170,9 @@ void ScriptedProcess::DidResume() {
}

Status ScriptedProcess::DoResume() {
Log *log = GetLog(LLDBLog::Process);
// FIXME: Fetch data from thread.
const StateType thread_resume_state = eStateRunning;
LLDB_LOGF(log, "ScriptedProcess::%s thread_resume_state = %s", __FUNCTION__,
StateAsCString(thread_resume_state));

bool resume = (thread_resume_state == eStateRunning);
assert(thread_resume_state == eStateRunning && "invalid thread resume state");

Status error;
if (resume) {
LLDB_LOGF(log, "ScriptedProcess::%s sending resume", __FUNCTION__);
LLDB_LOGF(GetLog(LLDBLog::Process), "ScriptedProcess::%s resuming process", __FUNCTION__);

SetPrivateState(eStateRunning);
SetPrivateState(eStateStopped);
error = GetInterface().Resume();
}

return error;
return GetInterface().Resume();
}

Status ScriptedProcess::DoAttach(const ProcessAttachInfo &attach_info) {
Expand Down Expand Up @@ -226,19 +203,6 @@ void ScriptedProcess::DidAttach(ArchSpec &process_arch) {
process_arch = GetArchitecture();
}

Status ScriptedProcess::DoStop() {
Log *log = GetLog(LLDBLog::Process);

if (GetInterface().ShouldStop()) {
SetPrivateState(eStateStopped);
LLDB_LOGF(log, "ScriptedProcess::%s Immediate stop", __FUNCTION__);
return {};
}

LLDB_LOGF(log, "ScriptedProcess::%s Delayed stop", __FUNCTION__);
return GetInterface().Stop();
}

Status ScriptedProcess::DoDestroy() { return Status(); }

bool ScriptedProcess::IsAlive() { return GetInterface().IsAlive(); }
Expand Down Expand Up @@ -285,6 +249,23 @@ size_t ScriptedProcess::DoWriteMemory(lldb::addr_t vm_addr, const void *buf,
return bytes_written;
}

Status ScriptedProcess::EnableBreakpointSite(BreakpointSite *bp_site) {
assert(bp_site != nullptr);

if (bp_site->IsEnabled()) {
return {};
}

if (bp_site->HardwareRequired()) {
return Status("Scripted Processes don't support hardware breakpoints");
}

Status error;
GetInterface().CreateBreakpoint(bp_site->GetLoadAddress(), error);

return error;
}

ArchSpec ScriptedProcess::GetArchitecture() {
return GetTarget().GetArchitecture();
}
Expand Down
8 changes: 6 additions & 2 deletions lldb/source/Plugins/Process/scripted/ScriptedProcess.h
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ class ScriptedProcess : public Process {
size_t DoWriteMemory(lldb::addr_t vm_addr, const void *buf, size_t size,
Status &error) override;

Status EnableBreakpointSite(BreakpointSite *bp_site) override;

ArchSpec GetArchitecture();

Status
Expand All @@ -90,12 +92,14 @@ class ScriptedProcess : public Process {

void *GetImplementation() override;

void ForceScriptedState(lldb::StateType state) override {
SetPrivateState(state);
}

protected:
ScriptedProcess(lldb::TargetSP target_sp, lldb::ListenerSP listener_sp,
const ScriptedMetadata &scripted_metadata, Status &error);

Status DoStop();

void Clear();

bool DoUpdateThreadList(ThreadList &old_thread_list,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ template <> struct PythonFormat<short> : PassthroughFormat<short, 'h'> {};
template <>
struct PythonFormat<unsigned short> : PassthroughFormat<unsigned short, 'H'> {};
template <> struct PythonFormat<int> : PassthroughFormat<int, 'i'> {};
template <> struct PythonFormat<bool> : PassthroughFormat<bool, 'p'> {};
template <>
struct PythonFormat<unsigned int> : PassthroughFormat<unsigned int, 'I'> {};
template <> struct PythonFormat<long> : PassthroughFormat<long, 'l'> {};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ python::ScopedPythonObject<lldb::SBEvent> ToSWIGWrapper(Event *event);
} // namespace python

void *LLDBSWIGPython_CastPyObjectToSBData(PyObject *data);
void *LLDBSWIGPython_CastPyObjectToSBBreakpoint(PyObject *data);
void *LLDBSWIGPython_CastPyObjectToSBAttachInfo(PyObject *data);
void *LLDBSWIGPython_CastPyObjectToSBLaunchInfo(PyObject *data);
void *LLDBSWIGPython_CastPyObjectToSBError(PyObject *data);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,21 +81,8 @@ Status ScriptedProcessPythonInterface::Launch() {
}

Status ScriptedProcessPythonInterface::Resume() {
return GetStatusFromMethod("resume");
}

bool ScriptedProcessPythonInterface::ShouldStop() {
Status error;
StructuredData::ObjectSP obj = Dispatch("is_alive", error);

if (!CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, obj, error))
return {};

return obj->GetBooleanValue();
}

Status ScriptedProcessPythonInterface::Stop() {
return GetStatusFromMethod("stop");
// When calling ScriptedProcess.Resume from lldb we should always stop.
return GetStatusFromMethod("resume", /*should_stop=*/true);
}

std::optional<MemoryRegionInfo>
Expand Down Expand Up @@ -123,6 +110,22 @@ StructuredData::DictionarySP ScriptedProcessPythonInterface::GetThreadsInfo() {
return dict;
}

bool ScriptedProcessPythonInterface::CreateBreakpoint(lldb::addr_t addr,
Status &error) {
Status py_error;
StructuredData::ObjectSP obj =
Dispatch("create_breakpoint", py_error, addr, error);

// If there was an error on the python call, surface it to the user.
if (py_error.Fail())
error = py_error;

if (!CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, obj, error))
return {};

return obj->GetBooleanValue();
}

lldb::DataExtractorSP ScriptedProcessPythonInterface::ReadMemoryAtAddress(
lldb::addr_t address, size_t size, Status &error) {
Status py_error;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,14 @@ class ScriptedProcessPythonInterface : public ScriptedProcessInterface,

Status Resume() override;

bool ShouldStop() override;

Status Stop() override;

std::optional<MemoryRegionInfo>
GetMemoryRegionContainingAddress(lldb::addr_t address,
Status &error) override;

StructuredData::DictionarySP GetThreadsInfo() override;

bool CreateBreakpoint(lldb::addr_t addr, Status &error) override;

lldb::DataExtractorSP ReadMemoryAtAddress(lldb::addr_t address, size_t size,
Status &error) override;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,22 @@ ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::DataExtractorSP>(
return m_interpreter.GetDataExtractorFromSBData(*sb_data);
}

template <>
lldb::BreakpointSP
ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::BreakpointSP>(
python::PythonObject &p, Status &error) {
lldb::SBBreakpoint *sb_breakpoint = reinterpret_cast<lldb::SBBreakpoint *>(
LLDBSWIGPython_CastPyObjectToSBBreakpoint(p.get()));

if (!sb_breakpoint) {
error.SetErrorString(
"Couldn't cast lldb::SBBreakpoint to lldb::BreakpointSP.");
return nullptr;
}

return m_interpreter.GetOpaqueTypeFromSBBreakpoint(*sb_breakpoint);
}

template <>
lldb::ProcessAttachInfoSP ScriptedPythonInterface::ExtractValueFromPythonObject<
lldb::ProcessAttachInfoSP>(python::PythonObject &p, Status &error) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,11 @@ class ScriptedPythonInterface : virtual public ScriptedInterface {
return {object};
}

python::PythonObject Transform(bool arg) {
// Boolean arguments need to be turned into python objects.
return python::PythonBoolean(arg);
}

python::PythonObject Transform(Status arg) {
return python::ToSWIGWrapper(arg);
}
Expand Down Expand Up @@ -141,6 +146,19 @@ class ScriptedPythonInterface : virtual public ScriptedInterface {
original_arg = ExtractValueFromPythonObject<T>(transformed_arg, error);
}

template <>
void ReverseTransform(bool &original_arg,
python::PythonObject transformed_arg, Status &error) {
python::PythonBoolean boolean_arg = python::PythonBoolean(
python::PyRefType::Borrowed, transformed_arg.get());
if (boolean_arg.IsValid())
original_arg = boolean_arg.GetValue();
else
error.SetErrorString(
llvm::formatv("{}: Invalid boolean argument.", LLVM_PRETTY_FUNCTION)
.str());
}

template <std::size_t... I, typename... Args>
auto TransformTuple(const std::tuple<Args...> &args,
std::index_sequence<I...>) {
Expand Down Expand Up @@ -210,6 +228,11 @@ template <>
Status ScriptedPythonInterface::ExtractValueFromPythonObject<Status>(
python::PythonObject &p, Status &error);

template <>
lldb::BreakpointSP
ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::BreakpointSP>(
python::PythonObject &p, Status &error);

template <>
lldb::ProcessAttachInfoSP ScriptedPythonInterface::ExtractValueFromPythonObject<
lldb::ProcessAttachInfoSP>(python::PythonObject &p, Status &error);
Expand Down
44 changes: 24 additions & 20 deletions lldb/source/Target/Process.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1055,14 +1055,16 @@ bool Process::SetExitStatus(int status, const char *cstr) {
std::lock_guard<std::mutex> guard(m_exit_status_mutex);

Log *log(GetLog(LLDBLog::State | LLDBLog::Process));
LLDB_LOGF(
log, "Process::SetExitStatus (status=%i (0x%8.8x), description=%s%s%s)",
status, status, cstr ? "\"" : "", cstr ? cstr : "NULL", cstr ? "\"" : "");
LLDB_LOG(log, "(plugin = %s status=%i (0x%8.8x), description=%s%s%s)",
GetPluginName().data(), status, status, cstr ? "\"" : "",
cstr ? cstr : "NULL", cstr ? "\"" : "");

// We were already in the exited state
if (m_private_state.GetValue() == eStateExited) {
LLDB_LOGF(log, "Process::SetExitStatus () ignoring exit status because "
"state was already set to eStateExited");
LLDB_LOG(log,
"(plugin = %s) ignoring exit status because state was already set "
"to eStateExited",
GetPluginName().data());
return false;
}

Expand Down Expand Up @@ -1314,8 +1316,8 @@ void Process::SetPublicState(StateType new_state, bool restarted) {
}

Log *log(GetLog(LLDBLog::State | LLDBLog::Process));
LLDB_LOGF(log, "Process::SetPublicState (state = %s, restarted = %i)",
StateAsCString(new_state), restarted);
LLDB_LOG(log, "(plugin = %s, state = %s, restarted = %i)",
GetPluginName().data(), StateAsCString(new_state), restarted);
const StateType old_state = m_public_state.GetValue();
m_public_state.SetValue(new_state);

Expand All @@ -1324,16 +1326,16 @@ void Process::SetPublicState(StateType new_state, bool restarted) {
// program to run.
if (!StateChangedIsExternallyHijacked()) {
if (new_state == eStateDetached) {
LLDB_LOGF(log,
"Process::SetPublicState (%s) -- unlocking run lock for detach",
StateAsCString(new_state));
LLDB_LOG(log,
"(plugin = %s, state = %s) -- unlocking run lock for detach",
GetPluginName().data(), StateAsCString(new_state));
m_public_run_lock.SetStopped();
} else {
const bool old_state_is_stopped = StateIsStoppedState(old_state, false);
if ((old_state_is_stopped != new_state_is_stopped)) {
if (new_state_is_stopped && !restarted) {
LLDB_LOGF(log, "Process::SetPublicState (%s) -- unlocking run lock",
StateAsCString(new_state));
LLDB_LOG(log, "(plugin = %s, state = %s) -- unlocking run lock",
GetPluginName().data(), StateAsCString(new_state));
m_public_run_lock.SetStopped();
}
}
Expand All @@ -1343,10 +1345,11 @@ void Process::SetPublicState(StateType new_state, bool restarted) {

Status Process::Resume() {
Log *log(GetLog(LLDBLog::State | LLDBLog::Process));
LLDB_LOGF(log, "Process::Resume -- locking run lock");
LLDB_LOG(log, "(plugin = %s) -- locking run lock", GetPluginName().data());
if (!m_public_run_lock.TrySetRunning()) {
Status error("Resume request failed - process still running.");
LLDB_LOGF(log, "Process::Resume: -- TrySetRunning failed, not resuming.");
LLDB_LOG(log, "(plugin = %s) -- TrySetRunning failed, not resuming.",
GetPluginName().data());
return error;
}
Status error = PrivateResume();
Expand Down Expand Up @@ -1419,7 +1422,8 @@ void Process::SetPrivateState(StateType new_state) {
Log *log(GetLog(LLDBLog::State | LLDBLog::Process | LLDBLog::Unwind));
bool state_changed = false;

LLDB_LOGF(log, "Process::SetPrivateState (%s)", StateAsCString(new_state));
LLDB_LOG(log, "(plugin = %s, state = %s)", GetPluginName().data(),
StateAsCString(new_state));

std::lock_guard<std::recursive_mutex> thread_guard(m_thread_list.GetMutex());
std::lock_guard<std::recursive_mutex> guard(m_private_state.GetMutex());
Expand Down Expand Up @@ -1460,15 +1464,15 @@ void Process::SetPrivateState(StateType new_state) {
if (!m_mod_id.IsLastResumeForUserExpression())
m_mod_id.SetStopEventForLastNaturalStopID(event_sp);
m_memory_cache.Clear();
LLDB_LOGF(log, "Process::SetPrivateState (%s) stop_id = %u",
StateAsCString(new_state), m_mod_id.GetStopID());
LLDB_LOG(log, "(plugin = %s, state = %s, stop_id = %u",
GetPluginName().data(), StateAsCString(new_state),
m_mod_id.GetStopID());
}

m_private_state_broadcaster.BroadcastEvent(event_sp);
} else {
LLDB_LOGF(log,
"Process::SetPrivateState (%s) state didn't change. Ignoring...",
StateAsCString(new_state));
LLDB_LOG(log, "(plugin = %s, state = %s) state didn't change. Ignoring...",
GetPluginName().data(), StateAsCString(new_state));
}
}

Expand Down
1 change: 1 addition & 0 deletions lldb/source/Target/Target.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3194,6 +3194,7 @@ Status Target::Launch(ProcessLaunchInfo &launch_info, Stream *stream) {
// Since we didn't have a platform launch the process, launch it here.
if (m_process_sp) {
m_process_sp->HijackProcessEvents(launch_info.GetHijackListener());
m_process_sp->SetShadowListener(launch_info.GetShadowListener());
error = m_process_sp->Launch(launch_info);
}
}
Expand Down
4 changes: 4 additions & 0 deletions lldb/source/Utility/Broadcaster.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,8 @@ void Broadcaster::BroadcasterImpl::PrivateBroadcastEvent(EventSP &event_sp,
&m_broadcaster, event_type))
return;
hijacking_listener_sp->AddEvent(event_sp);
if (m_shadow_listener)
m_shadow_listener->AddEvent(event_sp);
} else {
for (auto &pair : GetListeners()) {
if (!(pair.second & event_type))
Expand All @@ -237,6 +239,8 @@ void Broadcaster::BroadcasterImpl::PrivateBroadcastEvent(EventSP &event_sp,
continue;

pair.first->AddEvent(event_sp);
if (m_shadow_listener)
m_shadow_listener->AddEvent(event_sp);
}
}
}
Expand Down
5 changes: 3 additions & 2 deletions lldb/source/Utility/Listener.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ class BroadcasterManagerWPMatcher {

Listener::Listener(const char *name)
: m_name(name), m_broadcasters(), m_broadcasters_mutex(), m_events(),
m_events_mutex() {
m_events_mutex(), m_is_shadow() {
Log *log = GetLog(LLDBLog::Object);
if (log != nullptr)
LLDB_LOGF(log, "%p Listener::Listener('%s')", static_cast<void *>(this),
Expand Down Expand Up @@ -302,7 +302,8 @@ bool Listener::FindNextEventInternal(
// to return it so it should be okay to get the next event off the queue
// here - and it might be useful to do that in the "DoOnRemoval".
lock.unlock();
event_sp->DoOnRemoval();
if (!m_is_shadow)
event_sp->DoOnRemoval();
}
return true;
}
Expand Down
6 changes: 4 additions & 2 deletions lldb/source/Utility/ProcessInfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,14 @@ using namespace lldb;
using namespace lldb_private;

ProcessInfo::ProcessInfo()
: m_executable(), m_arguments(), m_environment(), m_arch() {}
: m_executable(), m_arguments(), m_environment(), m_arch(), m_listener_sp(),
m_hijack_listener_sp(), m_shadow_listener_sp() {}

ProcessInfo::ProcessInfo(const char *name, const ArchSpec &arch,
lldb::pid_t pid)
: m_executable(name), m_arguments(), m_environment(), m_arch(arch),
m_pid(pid) {}
m_pid(pid), m_listener_sp(), m_hijack_listener_sp(),
m_shadow_listener_sp() {}

void ProcessInfo::Clear() {
m_executable.Clear();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
CXX_SOURCES := main.cpp
CXXFLAGS=--std=c++17 -g
ARCH=$(shell uname -m)

include Makefile.rules

Original file line number Diff line number Diff line change
@@ -0,0 +1,285 @@
"""
Test the functionality of interactive scripted processes
"""

import lldb
import lldbsuite.test.lldbutil as lldbutil
from lldbsuite.test.lldbtest import *
import json, os


class TestInteractiveScriptedProcess(TestBase):

NO_DEBUG_INFO_TESTCASE = True

def setUp(self):
# Call super's setUp().
TestBase.setUp(self)
# Build and load test program
self.build()
self.runCmd("file " + self.getBuildArtifact("a.out"), CURRENT_EXECUTABLE_SET)
self.main_source_file = lldb.SBFileSpec("main.cpp")
self.script_module = "interactive_scripted_process"
self.script_file = self.script_module + ".py"

def test_passthrough_launch(self):
"""Test a simple pass-through process launch"""
self.passthrough_launch()

lldbutil.run_break_set_by_source_regexp(self, "also break here")
self.assertEqual(self.mux_target.GetNumBreakpoints(), 2)
error = self.mux_process.Continue()
self.assertSuccess(error, "Resuming multiplexer scripted process")
self.assertTrue(self.mux_process.IsValid(), "Got a valid process")

event = lldbutil.fetch_next_event(
self, self.mux_process_listener, self.mux_process.GetBroadcaster()
)
self.assertState(lldb.SBProcess.GetStateFromEvent(event), lldb.eStateRunning)
event = lldbutil.fetch_next_event(
self, self.mux_process_listener, self.mux_process.GetBroadcaster()
)
self.assertState(lldb.SBProcess.GetStateFromEvent(event), lldb.eStateStopped)

def test_multiplexed_launch(self):
"""Test a multiple interactive scripted process debugging"""
self.passthrough_launch()
self.assertEqual(self.dbg.GetNumTargets(), 2)

driving_target = self.mux_process.GetScriptedImplementation().driving_target
self.assertTrue(driving_target.IsValid(), "Driving target is invalid")

# Create a target for the multiplexed even scripted process
even_target = self.duplicate_target(driving_target)
self.assertTrue(
even_target.IsValid(),
"Couldn't duplicate driving target to launch multiplexed even scripted process",
)

class_name = f"{self.script_module}.MultiplexedScriptedProcess"
dictionary = {"driving_target_idx": self.dbg.GetIndexOfTarget(self.mux_target)}

dictionary["parity"] = 0
muxed_launch_info = self.get_launch_info(class_name, dictionary)

# Launch Even Child Scripted Process
error = lldb.SBError()
even_process = even_target.Launch(muxed_launch_info, error)
self.assertTrue(
even_process, "Couldn't launch multiplexed even scripted process"
)
self.multiplex(even_process)

# Check that the even process started running
event = lldbutil.fetch_next_event(
self, self.dbg.GetListener(), even_process.GetBroadcaster()
)
self.assertState(lldb.SBProcess.GetStateFromEvent(event), lldb.eStateRunning)
# Check that the even process stopped
event = lldbutil.fetch_next_event(
self, self.dbg.GetListener(), even_process.GetBroadcaster()
)
self.assertState(lldb.SBProcess.GetStateFromEvent(event), lldb.eStateStopped)

self.assertTrue(even_process.IsValid(), "Got a valid process")
self.assertState(
even_process.GetState(), lldb.eStateStopped, "Process is stopped"
)

# Create a target for the multiplexed odd scripted process
odd_target = self.duplicate_target(driving_target)
self.assertTrue(
odd_target.IsValid(),
"Couldn't duplicate driving target to launch multiplexed odd scripted process",
)

dictionary["parity"] = 1
muxed_launch_info = self.get_launch_info(class_name, dictionary)

# Launch Odd Child Scripted Process
error = lldb.SBError()
odd_process = odd_target.Launch(muxed_launch_info, error)
self.assertTrue(odd_process, "Couldn't launch multiplexed odd scripted process")
self.multiplex(odd_process)

# Check that the odd process started running
event = lldbutil.fetch_next_event(
self, self.dbg.GetListener(), odd_process.GetBroadcaster()
)
self.assertState(lldb.SBProcess.GetStateFromEvent(event), lldb.eStateRunning)
# Check that the odd process stopped
event = lldbutil.fetch_next_event(
self, self.dbg.GetListener(), odd_process.GetBroadcaster()
)
self.assertState(lldb.SBProcess.GetStateFromEvent(event), lldb.eStateStopped)

self.assertTrue(odd_process.IsValid(), "Got a valid process")
self.assertState(
odd_process.GetState(), lldb.eStateStopped, "Process is stopped"
)

# Set a breakpoint on the odd child process
bkpt = odd_target.BreakpointCreateBySourceRegex(
"also break here", self.main_source_file
)
self.assertEqual(odd_target.GetNumBreakpoints(), 1)
self.assertTrue(bkpt, "Second breakpoint set on child scripted process")
self.assertEqual(bkpt.GetNumLocations(), 1, "Second breakpoint has 1 location")

# Verify that the breakpoint was also set on the multiplexer & real target
self.assertEqual(self.mux_target.GetNumBreakpoints(), 2)
bkpt = self.mux_target.GetBreakpointAtIndex(1)
self.assertEqual(
bkpt.GetNumLocations(), 1, "Second breakpoint set on mux scripted process"
)
self.assertTrue(bkpt.MatchesName("multiplexed_scripted_process_421"))

self.assertGreater(driving_target.GetNumBreakpoints(), 1)

# Resume execution on child process
error = odd_process.Continue()
self.assertSuccess(error, "Resuming odd child scripted process")
self.assertTrue(odd_process.IsValid(), "Got a valid process")

# Since all the execution is asynchronous, the order in which events
# arrive is non-deterministic, so we need a data structure to make sure
# we received both the running and stopped event for each target.

# Initialize the execution event "bingo book", that maps a process index
# to a dictionary that contains flags that are not set for the process
# events that we care about (running & stopped)

execution_events = {
1: {lldb.eStateRunning: False, lldb.eStateStopped: False},
2: {lldb.eStateRunning: False, lldb.eStateStopped: False},
3: {lldb.eStateRunning: False, lldb.eStateStopped: False},
}

def fetch_process_event(self, execution_events):
event = lldbutil.fetch_next_event(
self,
self.dbg.GetListener(),
lldb.SBProcess.GetBroadcasterClass(),
match_class=True,
)
state = lldb.SBProcess.GetStateFromEvent(event)
self.assertIn(state, [lldb.eStateRunning, lldb.eStateStopped])
event_process = lldb.SBProcess.GetProcessFromEvent(event)
self.assertTrue(event_process.IsValid())
event_target = event_process.GetTarget()
event_target_idx = self.dbg.GetIndexOfTarget(event_target)
self.assertFalse(
execution_events[event_target_idx][state],
"Event already received for this process",
)
execution_events[event_target_idx][state] = True

event = lldbutil.fetch_next_event(
self, self.mux_process_listener, self.mux_process.GetBroadcaster()
)
self.assertState(lldb.SBProcess.GetStateFromEvent(event), lldb.eStateRunning)

event = lldbutil.fetch_next_event(
self, self.mux_process_listener, self.mux_process.GetBroadcaster()
)
self.assertState(lldb.SBProcess.GetStateFromEvent(event), lldb.eStateStopped)

for _ in range((self.dbg.GetNumTargets() - 1) * 2):
fetch_process_event(self, execution_events)

for target_index, event_states in execution_events.items():
for state, is_set in event_states.items():
self.assertTrue(is_set, f"Target {target_index} has state {state} set")

def duplicate_target(self, driving_target):
exe = driving_target.executable.fullpath
triple = driving_target.triple
return self.dbg.CreateTargetWithFileAndTargetTriple(exe, triple)

def get_launch_info(self, class_name, script_dict):
structured_data = lldb.SBStructuredData()
structured_data.SetFromJSON(json.dumps(script_dict))

launch_info = lldb.SBLaunchInfo(None)
launch_info.SetProcessPluginName("ScriptedProcess")
launch_info.SetScriptedProcessClassName(class_name)
launch_info.SetScriptedProcessDictionary(structured_data)
return launch_info

def multiplex(self, muxed_process):
muxed_process.GetScriptedImplementation().multiplexer = (
self.mux_process.GetScriptedImplementation()
)
self.mux_process.GetScriptedImplementation().multiplexed_processes[
muxed_process.GetProcessID()
] = muxed_process

def passthrough_launch(self):
"""Test that a simple passthrough wrapper functions correctly"""
# First build the real target:
self.assertEqual(self.dbg.GetNumTargets(), 1)
real_target_id = 0
real_target = self.dbg.GetTargetAtIndex(real_target_id)
lldbutil.run_break_set_by_source_regexp(self, "Break here")
self.assertEqual(real_target.GetNumBreakpoints(), 1)

# Now source in the scripted module:
script_path = os.path.join(self.getSourceDir(), self.script_file)
self.runCmd(f"command script import '{script_path}'")

self.mux_target = self.duplicate_target(real_target)
self.assertTrue(self.mux_target.IsValid(), "duplicate target succeeded")

mux_class = f"{self.script_module}.MultiplexerScriptedProcess"
script_dict = {"driving_target_idx": real_target_id}
mux_launch_info = self.get_launch_info(mux_class, script_dict)
self.mux_process_listener = lldb.SBListener(
"lldb.test.interactive-scripted-process.listener"
)
mux_launch_info.SetShadowListener(self.mux_process_listener)

self.dbg.SetAsync(True)
error = lldb.SBError()
self.mux_process = self.mux_target.Launch(mux_launch_info, error)
self.assertSuccess(error, "Launched multiplexer scripted process")
self.assertTrue(self.mux_process.IsValid(), "Got a valid process")

# Check that the mux process started running
event = lldbutil.fetch_next_event(
self, self.mux_process_listener, self.mux_process.GetBroadcaster()
)
self.assertState(lldb.SBProcess.GetStateFromEvent(event), lldb.eStateRunning)
# Check that the real process started running
event = lldbutil.fetch_next_event(
self, self.dbg.GetListener(), self.mux_process.GetBroadcaster()
)
self.assertState(lldb.SBProcess.GetStateFromEvent(event), lldb.eStateRunning)

# Check that the real process stopped
event = lldbutil.fetch_next_event(
self, self.dbg.GetListener(), self.mux_process.GetBroadcaster()
)
self.assertState(lldb.SBProcess.GetStateFromEvent(event), lldb.eStateStopped)
# Check that the mux process stopped
event = lldbutil.fetch_next_event(
self, self.mux_process_listener, self.mux_process.GetBroadcaster()
)
self.assertState(lldb.SBProcess.GetStateFromEvent(event), lldb.eStateStopped)

real_process = real_target.GetProcess()
self.assertTrue(real_process.IsValid(), "Got a valid process")
self.assertState(
real_process.GetState(), lldb.eStateStopped, "Process is stopped"
)

# This is a passthrough, so the two processes should have the same state:
# Check that we got the right threads:
self.assertEqual(
len(real_process.threads),
len(self.mux_process.threads),
"Same number of threads",
)
for id in range(len(real_process.threads)):
real_pc = real_process.threads[id].frame[0].pc
mux_pc = self.mux_process.threads[id].frame[0].pc
self.assertEqual(real_pc, mux_pc, f"PC's equal for {id}")

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#include <iostream>
#include <mutex>
#include <string>
#include <thread>
#include <vector>

void spawn_thread(int index) {
std::string name = "I'm thread " + std::to_string(index) + " !";
bool done = false;
std::string state = "Started execution!";
while (true) {
if (done) // also break here
break;
}

state = "Stopped execution!";
}

int main() {
constexpr size_t num_threads = 10;
std::vector<std::thread> threads;

for (size_t i = 0; i < num_threads; i++) {
threads.push_back(std::thread(spawn_thread, i));
}

std::cout << "Spawned " << threads.size() << " threads!" << std::endl; // Break here

for (auto &t : threads) {
if (t.joinable())
t.join();
}

return 0;
}
4 changes: 4 additions & 0 deletions lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,10 @@ void *lldb_private::LLDBSWIGPython_CastPyObjectToSBData(PyObject *data) {
return nullptr;
}

void *lldb_private::LLDBSWIGPython_CastPyObjectToSBBreakpoint(PyObject *data) {
return nullptr;
}

void *lldb_private::LLDBSWIGPython_CastPyObjectToSBAttachInfo(PyObject *data) {
return nullptr;
}
Expand Down