Fix remote connection lifetime handling in LLDB (#2763)

* Add iOS support

* Rewrite it to match modern Pwndbg

* Forgot to break on healthy targets

* Lint
pull/2798/head
Matt. 9 months ago committed by GitHub
parent 96d85fbc83
commit a229b298da
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -936,8 +936,8 @@ def process_connect(driver: ProcessDriver, relay: EventRelay, args: List[str], d
)
return
if driver.has_process():
print(message.error("error: a process is already being debugged"))
if driver.has_connection():
print(message.error("error: debugger is already connected"))
return
target = dbg.debugger.GetSelectedTarget()
@ -991,8 +991,9 @@ def process_connect(driver: ProcessDriver, relay: EventRelay, args: List[str], d
), "Could not delete the target we've just created. What?"
return
# Tell the debugger that the process was suspended.
dbg._trigger_event(EventType.STOP)
# Tell the debugger that the process was suspended, if there is a process.
if driver.has_process():
dbg._trigger_event(EventType.STOP)
gdb_remote_ap = argparse.ArgumentParser(add_help=False)

@ -7,6 +7,7 @@ from typing import Any
from typing import BinaryIO
from typing import Coroutine
from typing import List
from typing import Tuple
import lldb
@ -14,6 +15,7 @@ import pwndbg
from pwndbg.dbg.lldb import YieldContinue
from pwndbg.dbg.lldb import YieldSingleStep
from pwndbg.dbg.lldb.repl.io import IODriver
from pwndbg.dbg.lldb.repl.io import IODriverPlainText
class EventHandler:
@ -81,6 +83,13 @@ class ProcessDriver:
"""
Whether there's an active process in this driver.
"""
return self.process is not None and self.process.GetState() != lldb.eStateConnected
def has_connection(self) -> bool:
"""
Whether this driver's connected to a target. All drivers that have an
active process also must necessarily be connected.
"""
return self.process is not None
def interrupt(self) -> None:
@ -94,7 +103,7 @@ class ProcessDriver:
first_timeout: int = 1,
only_if_started: bool = False,
fire_events: bool = True,
) -> lldb.SBEvent | None:
) -> Tuple[bool, lldb.SBEvent | None]:
"""
Runs the event loop of the process until the next stop event is hit, with
a configurable timeouts for the first and subsequent timeouts.
@ -121,7 +130,8 @@ class ProcessDriver:
# started by a previous action and is running.
running = not only_if_started
reason: lldb.SBEvent | None = None
last: lldb.SBEvent | None = None
expected: bool = False
while True:
event = lldb.SBEvent()
if not self.listener.WaitForEvent(timeout_time, event):
@ -138,6 +148,7 @@ class ProcessDriver:
break
continue
last = event
if self.debug:
descr = lldb.SBStream()
@ -170,7 +181,7 @@ class ProcessDriver:
# for the time being. Trigger the stopped event and return.
if fire_events:
self.eh.suspended()
reason = event
expected = True
break
if new_state == lldb.eStateRunning or new_state == lldb.eStateStepping:
@ -195,13 +206,12 @@ class ProcessDriver:
if fire_events:
self.eh.exited()
reason = event
break
if io_started:
self.io.stop()
return reason
return expected, last
def cont(self) -> None:
"""
@ -275,6 +285,7 @@ class ProcessDriver:
process in this driver. Returns `True` if the coroutine ran to completion,
and `False` if it was cancelled.
"""
assert self.has_process(), "called run_coroutine() on a driver with no process"
exception: Exception | None = None
while True:
try:
@ -314,14 +325,20 @@ class ProcessDriver:
)
continue
self._run_until_next_stop()
healthy, event = self._run_until_next_stop()
if not healthy:
# The process exited. Cancel the execution controller.
exception = CancelledError()
continue
elif isinstance(step, YieldContinue):
# Continue the process and wait for the next stop-like event.
self.process.Continue()
event = self._run_until_next_stop()
assert (
event is not None
), "None should only be returned by _run_until_next_stop unless start timeouts are enabled"
healthy, event = self._run_until_next_stop()
if not healthy:
# The process exited, Cancel the execution controller.
exception = CancelledError()
continue
# Check whether this stop event is the one we expect.
stop: lldb.SBBreakpoint | lldb.SBWatchpoint = step.target.inner
@ -380,40 +397,64 @@ class ProcessDriver:
Fires the created() event.
"""
stdin, stdout, stderr = io.stdio()
error = lldb.SBError()
self.listener = lldb.SBListener("pwndbg.dbg.lldb.repl.proc.ProcessDriver")
assert self.listener.IsValid()
assert not self.has_process(), "called launch() on a driver with a live process"
# We are interested in handling certain target events synchronously, so
# set them up here, before LLDB has had any chance to do anything to the
# process.
self.listener.StartListeningForEventClass(
target.GetDebugger(),
lldb.SBTarget.GetBroadcasterClassName(),
lldb.SBTarget.eBroadcastBitModulesLoaded,
)
error = lldb.SBError()
# Do the launch, proper. We always stop the target, and let the upper
# layers deal with the user wanting the program to not stop at entry by
# calling `cont()`.
self.process = target.Launch(
self.listener,
args,
env,
stdin,
stdout,
stderr,
os.getcwd(),
lldb.eLaunchFlagStopAtEntry,
True,
error,
)
if self.has_connection():
# This is a remote launch.
#
# We ignore the IODriver we were given, and use the plain text
# driver, as we can't guarantee that anything else would work.
io = IODriverPlainText()
stdin, stdout, stderr = io.stdio()
self.process.RemoteLaunch(
args,
env,
stdin,
stdout,
stderr,
None,
lldb.eLaunchFlagStopAtEntry,
True,
error,
)
else:
# This is a local launch.
self.listener = lldb.SBListener("pwndbg.dbg.lldb.repl.proc.ProcessDriver")
assert self.listener.IsValid()
# We are interested in handling certain target events synchronously, so
# set them up here, before LLDB has had any chance to do anything to the
# process.
self.listener.StartListeningForEventClass(
target.GetDebugger(),
lldb.SBTarget.GetBroadcasterClassName(),
lldb.SBTarget.eBroadcastBitModulesLoaded,
)
stdin, stdout, stderr = io.stdio()
self.process = target.Launch(
self.listener,
args,
env,
stdin,
stdout,
stderr,
os.getcwd(),
lldb.eLaunchFlagStopAtEntry,
True,
error,
)
if not error.success:
# Undo any initialization Launch() might've done.
self.process = None
self.listener = None
if not error.success:
# Undo any initialization Launch() might've done.
self.process = None
self.listener = None
return error
assert self.listener.IsValid()
@ -473,13 +514,14 @@ class ProcessDriver:
def connect(self, target: lldb.SBTarget, io: IODriver, url: str, plugin: str) -> lldb.SBError:
"""
Connects to a remote proces with the given URL using the plugin with the
given name, and attaches to the process until LLDB issues a start event
to us.
given name. This might cause the process to launch in some implementations,
or it might require a call to `launch()`, in implementations that
require a further call to `SBProcess::RemoteLaunch()`.
Potentially fires all types of events, as it is not known when LLDB will
return control of the process to us.
Fires the created() event if a process is automatically attached to
or launched when a connection succeeds.
"""
assert not self.has_connection(), "called connect() on a driver with an active connection"
stdin, stdout, stderr = io.stdio()
error = lldb.SBError()
self.listener = lldb.SBListener("pwndbg.dbg.lldb.repl.proc.ProcessDriver")
@ -506,10 +548,27 @@ class ProcessDriver:
self.io = io
# Unlike in `launch()`, it's not guaranteed that the process will not be
# running at this point, so we have to attach the I/O and wait until we
# get a stop event.
self._run_until_next_stop(fire_events=False)
self.eh.created()
# Unlike in `launch()`, it's not guaranteed that the process is actually
# alive, as it might be in the Connected state, which indicates that
# the connection was successful, but that we still need to launch it
# manually in the remote target.
while True:
healthy, event = self._run_until_next_stop(
with_io=False, fire_events=False, only_if_started=True
)
if healthy:
# The process has startarted. We can fire off the created event
# just fine.
self.eh.created()
break
if (
event is not None
and lldb.SBProcess.GetStateFromEvent(event) == lldb.eStateConnected
):
# This indicates that on this implementation, connecting isn't
# enough to have the process launch or attach, and that we have
# to do that manually.
break
return error

Loading…
Cancel
Save