diff --git a/pwndbg/dbg/lldb/repl/proc.py b/pwndbg/dbg/lldb/repl/proc.py index 25ea24c0f..6909e865a 100644 --- a/pwndbg/dbg/lldb/repl/proc.py +++ b/pwndbg/dbg/lldb/repl/proc.py @@ -160,20 +160,11 @@ def _updates_scope_counter(target: str) -> Callable[[Callable[..., Any]], Any]: def sub1(self: ProcessDriver, *args, **kwargs): setattr(self, target, getattr(self, target) + 1) try: - if self.debug: - print( - f"[*] ProcessDriver: self.{target} += 1 ({getattr(self, target)})", - file=sys.__stdout__, - ) - + self.debug_print(f"self.{target} += 1 ({getattr(self, target)})") return fn(self, *args, **kwargs) finally: setattr(self, target, getattr(self, target) - 1) - if self.debug: - print( - f"[*] ProcessDriver: self.{target} -= 1 ({getattr(self, target)})", - file=sys.__stdout__, - ) + self.debug_print(f"self.{target} -= 1 ({getattr(self, target)})") return sub1 @@ -215,12 +206,24 @@ class ProcessDriver: self._in_run_until_next_stop = 0 self._in_run_coroutine = 0 + def debug_print(self, *args, **kwargs) -> None: + if self.debug: + try: + print("[*] ProcessDriver: ", end="") + print(*args, **kwargs) + except BlockingIOError as e: + try: + # Try to inform the user of the error. + print( + f"[-] ProcessDriver: Error after printing {e.characters_written} characters in the previous debug message. Information may be missing." + ) + except BlockingIOError: + pass + def has_process(self) -> bool: """ Whether there's an active process in this driver. """ - if self.debug: - print(f"[-] ProcessDriver: has_process() for {self.process}") return self.process is not None and self.process.GetState() != lldb.eStateConnected def has_connection(self) -> bool: @@ -238,29 +241,23 @@ class ProcessDriver: return if self._in_run_until_next_stop > 0: - if self.debug: - print("[*] ProcessDriver: Sending Interrupt", file=sys.__stdout__) + if self._in_run_coroutine > 0: + self.debug_print("Sending interrupt and requesting current coroutine stop") + else: + self.debug_print("Sending interrupt") # If we're in a coroutine, we should tell it to stop as soon as it gets out of _run_until_next_stop() self._pending_cancellation = self._in_run_coroutine > 0 self.process.SendAsyncInterrupt() elif self._hold_cancellation > 0 and not in_lldb_command_handler: - if self.debug: - print("[*] ProcessDriver: Pushing pending cancellation", file=sys.__stdout__) - + self.debug_print("Pushing pending cancellation") self._pending_cancellation = True else: - if self.debug: - print( - "[*] ProcessDriver: Requesting cancellation immediately", - file=sys.__stdout__, - end="", - ) - if self._hold_cancellation > 0: - print(" (forced by being in a command handler)", file=sys.__stdout__) - else: - print(file=sys.__stdout__) + self.debug_print( + "Requesting cancellation immediately", + "(forced by being in a command handler)" if self._hold_cancellation > 0 else "", + ) # This happens even if interrupts are suspended, if we're inside a # command handler. We shouldn't interrupt LLDB until it starts @@ -280,22 +277,17 @@ class ProcessDriver: self._hold_cancellation += 1 try: - if self.debug: - print( - "[*] ProcessDriver: Temporarily suspending cancellations", file=sys.__stdout__ - ) + self.debug_print("Temporarily suspending cancellations") yield None finally: - if self.debug: - print("[*] ProcessDriver: Resuming cancellations", file=sys.__stdout__) + self.debug_print("Resuming cancellations") self._hold_cancellation -= 1 if self._hold_cancellation == 0 and self._pending_cancellation: - if self.debug: - print( - "[*] ProcessDriver: Executing pending cancellation", file=sys.__stdout__ - ) + self.debug_print( + "Executing pending cancellation", + ) self._pending_cancellation = False if interrupt is None: # The default action is to raise an exception in place. @@ -344,16 +336,16 @@ class ProcessDriver: while True: event = lldb.SBEvent() if not self.listener.WaitForEvent(timeout_time, event): - if self.debug: - print(f"[-] ProcessDriver: Timed out after {timeout_time}s") + self.debug_print( + f"Timed out after {timeout_time}s", + ) timeout_time = timeout # If the process isn't running, we should stop. if not running: - if self.debug: - print( - "[-] ProcessDriver: Waited too long for process to start running, giving up" - ) + self.debug_print( + "Waited too long for process to start running, giving up", + ) result = _PollResultTimedOut(last_event) break @@ -363,9 +355,9 @@ class ProcessDriver: if self.debug: descr = lldb.SBStream() if event.GetDescription(descr): - print(f"[-] ProcessDriver: {descr.GetData()}") + self.debug_print(descr.GetData()) else: - print(f"[!] ProcessDriver: No description for {event}") + self.debug_print(f"No description for {event}") if lldb.SBTarget.EventIsTargetEvent(event): if event.GetType() == lldb.SBTarget.eBroadcastBitModulesLoaded: @@ -409,8 +401,9 @@ class ProcessDriver: ): # Nothing else for us to do here. Clear our internal # references to the process, fire the exit event, and leave. - if self.debug: - print(f"[-] ProcessDriver: Process exited with state {new_state}") + self.debug_print( + f"Process exited with state {new_state}", + ) self.process = None self.listener = None @@ -470,8 +463,7 @@ class ProcessDriver: target.write(out.encode(sys.stdout.encoding, errors="backslashreplace")) target.write(b"\n") - if self.debug: - print(f"[-] ProcessDriver: LLDB Command Status: {ret.GetStatus():#x}") + self.debug_print(f"LLDB Command finished with status: {ret.GetStatus():#x}") # Only call _run_until_next_stop() if the command started the process. start_expected = True @@ -524,32 +516,40 @@ class ProcessDriver: """ assert self.has_process(), "called run_coroutine() on a driver with no process" + + self.debug_print("Coroutine: Starting") exceptions: List[BaseException] = [] def queue_cancel(): + self.debug_print("Coroutine: Queueing up user cancellation exception") exceptions.append(UserCancelledError()) while True: try: if len(exceptions) == 0: + self.debug_print("Coroutine: Resuming") step = coroutine.send(None) else: + self.debug_print(f"Coroutine: Raising {type(exceptions[-1])}") step = coroutine.throw(exceptions[-1]) # The coroutine has caught the exception. Continue running # it as if nothing happened. exceptions.pop() except StopIteration: # We got to the end of the coroutine. We're done. + self.debug_print("Coroutine: Ran to completion") break except CancelledError: # We requested that the coroutine be cancelled, and it didn't # override our decision. We're done. + self.debug_print("Coroutine: Cancelled") break # Being interrupted here would be bad for keeping the state of the # process consistent. with self.suspend_interrupts(interrupt=queue_cancel): if isinstance(step, YieldSingleStep): + self.debug_print("Coroutine: Performing ExecutionController.single_step()") # Pick the currently selected thread and step it forward by one # instruction. # @@ -577,6 +577,7 @@ class ProcessDriver: continue elif isinstance(step, YieldContinue): + self.debug_print("Coroutine: Performing ExecutionController.cont()") threads_suspended = [] if step.selected_thread: thread = self.process.GetSelectedThread()