Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 31 additions & 12 deletions src/etc/debugger_tester/lldb/batchmode.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import _thread as thread
import os
import re
import signal
import sys
import threading
import time
Expand Down Expand Up @@ -184,6 +185,27 @@ def dispatch_repr(var_name: str, breakpoint_index: int, frame: lldb.SBFrame) ->
return check(var_name, breakpoint_index, frame) == Result.Ok


def quit_with_error(debugger: lldb.SBDebugger):
"""Kills the parent LLDB process with a non-0 exit code"""

# file handles aren't guaranteed to be flushed when python doesn't return control back to LLDB,
# so we need to do it manually
debugger.GetOutputFile().Flush()
debugger.GetErrorFile().Flush()
sys.stdout.flush()
sys.stderr.flush()

# When using a debugger created from the python script (e.g. `lldb.SBDebugger.Create()`), this
# doesn't actually work, but it doesn't hurt to try =)
debugger.HandleCommand("quit 1")

# Returning status codes using `sys.exit` doesn't work since we're in an LLDB managed python
# instance. Instead, we kill the PID, which happens to be the parent LLDB process.
# Note: We use SIGTERM because it works on linux and windows, unlike SIGKILL, and doesn't cause
# LLDB to spit out a backtrace like SIGABRT.
os.kill(os.getpid(), signal.SIGTERM)


####################################################################################################
# ~main
####################################################################################################
Expand All @@ -203,12 +225,12 @@ def main():
# Start the timeout watchdog
start_watchdog()

# This is the debugger instance of the lldb executable that imported and ran this python script.
# There is some weird behavior around LLDB reassigning, clearing, or not updating their own
# references (like `lldb.debugger`) while a python function is actively running (i.e. if control
# is not given back to the REPL). To prevent LLDB from changing things out from under us, we
# store this reference locally.
debugger = lldb.debugger
# We use a new `SBDebugger` instance, since LLDB often doesn't update internal state/python
# state while a command is being run. Since the entirety of `batchmode` is executed via a
# `script` command, the parent LLDB never gets a chance to update.
# In LLDB <23 this was less of an issue, but a change in LLDB 23 made it difficult to create
# targets from the parent debugger instance.
debugger = lldb.SBDebugger.Create()
Comment thread
Kobzol marked this conversation as resolved.

# When we step or continue, don't return from the function until the process
# stops. We do this by setting the async mode to false.
Expand Down Expand Up @@ -283,13 +305,10 @@ def main():
print(f"Could not read debugging script '{script_path}'.")
traceback.print_exception(type(e), e, e.__traceback__, file=sys.stdout)
print("Aborting.")
# Returning status codes using `sys.exit` doesn't work since we're in an LLDB managed python
# instance. This command sets the exit code but *does not* kill LLDB, the debugee process,
# or the SBDebugger object.
debugger.HandleCommand("quit 1")
quit_with_error(debugger)
except Exception as e:
traceback.print_exception(type(e), e, e.__traceback__, file=sys.stdout)
debugger.HandleCommand("quit 1")
quit_with_error(debugger)
else: # Executes if the `try` block throws no exceptions.
if repr_cmd_run:
# We save importing these until we actually see a repr command. This prevents us
Expand Down Expand Up @@ -329,7 +348,7 @@ def main():
)

if not tested_all_types() or not tested_all_variables():
debugger.HandleCommand("quit 1")
quit_with_error(debugger)
elif BLESS:
from lldb_providers import FEATURE_FLAGS

Expand Down
Loading