mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-11-04 17:17:39 +00:00
185 lines
8.7 KiB
Python
185 lines
8.7 KiB
Python
"""LLDB command for the unchanged compiler-produced ghc-boot Setup binary."""
|
|
|
|
import hashlib
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
import shlex
|
|
|
|
import lldb
|
|
|
|
|
|
def nul_entries(path):
|
|
data = path.read_bytes()
|
|
if not data.endswith(b"\0"):
|
|
raise ValueError("incomplete NUL-delimited capture")
|
|
return [os.fsdecode(item) for item in data[:-1].split(b"\0")]
|
|
|
|
|
|
def drain(process):
|
|
for reader in (process.GetSTDOUT, process.GetSTDERR):
|
|
while True:
|
|
text = reader(65536)
|
|
if not text:
|
|
break
|
|
print(text, end="", flush=True)
|
|
|
|
|
|
def describe_stop(process):
|
|
stops = []
|
|
for thread in process:
|
|
data = [thread.GetStopReasonDataAtIndex(i)
|
|
for i in range(thread.GetStopReasonDataCount())]
|
|
stops.append({"thread": thread.GetThreadID(),
|
|
"reason": thread.GetStopReason(), "data": data,
|
|
"description": thread.GetStopDescription(1024)})
|
|
print("Intel Setup stop: " + json.dumps(stops), flush=True)
|
|
return stops
|
|
|
|
|
|
def dump_stop(target, process):
|
|
# Use SB objects directly: a Python command's nested CLI execution context
|
|
# can still refer to the pre-launch process even after SBTarget.Launch.
|
|
for thread in process:
|
|
print("Thread " + str(thread.GetThreadID()), flush=True)
|
|
for index in range(min(thread.GetNumFrames(), 80)):
|
|
print(str(thread.GetFrameAtIndex(index)), flush=True)
|
|
if thread.GetNumFrames() > 80:
|
|
print("Backtrace truncated after 80 frames", flush=True)
|
|
stopped = next((thread for thread in process
|
|
if thread.GetStopReason() not in (lldb.eStopReasonNone,
|
|
lldb.eStopReasonInvalid)), None)
|
|
if stopped is not None and stopped.GetNumFrames():
|
|
frame = stopped.GetFrameAtIndex(0)
|
|
for group in frame.GetRegisters():
|
|
print("Registers: " + str(group.GetName()), flush=True)
|
|
for register in group:
|
|
print(str(register.GetName()) + " = " + str(register.GetValue()), flush=True)
|
|
print("Instructions at stopped PC:", flush=True)
|
|
for instruction in target.ReadInstructions(frame.GetPCAddress(), 12):
|
|
print(str(instruction), flush=True)
|
|
print("Loaded images:", flush=True)
|
|
for index in range(target.GetNumModules()):
|
|
module = target.GetModuleAtIndex(index)
|
|
print(str(module.GetFileSpec()) + " UUID=" + str(module.GetUUIDString()), flush=True)
|
|
|
|
|
|
def stopped_signals(process):
|
|
reasons = [(thread.GetStopReason(),
|
|
thread.GetStopReasonDataAtIndex(0) if thread.GetStopReasonDataCount() else None)
|
|
for thread in process
|
|
if thread.GetStopReason() not in (lldb.eStopReasonNone, lldb.eStopReasonInvalid)]
|
|
return [number for reason, number in reasons
|
|
if reason == lldb.eStopReasonSignal and number is not None], reasons
|
|
|
|
|
|
def run(debugger, command, _result, _internal_dict):
|
|
process = None
|
|
code = 1
|
|
try:
|
|
arguments = shlex.split(command)
|
|
if len(arguments) != 1:
|
|
raise ValueError("expected one argv-capture pathname")
|
|
capture = Path(arguments[0])
|
|
argv = nul_entries(capture)
|
|
environment_file = Path(str(capture) + ".env")
|
|
environment_bytes = environment_file.read_bytes()
|
|
environment = nul_entries(environment_file)
|
|
environment_file.unlink()
|
|
target = debugger.GetSelectedTarget()
|
|
executable = target.GetExecutable()
|
|
executable_path = Path(executable.fullpath)
|
|
print("Intel Setup launch: " + json.dumps({
|
|
"executable": str(executable_path), "argv": argv,
|
|
"cwd": os.getcwd(),
|
|
"executable_sha256": hashlib.sha256(executable_path.read_bytes()).hexdigest(),
|
|
"environment_sha256": hashlib.sha256(environment_bytes).hexdigest(),
|
|
"environment_entries": len(environment),
|
|
}), flush=True)
|
|
|
|
# Supplying the executable separately leaves argv[0] equal to Cabal's
|
|
# original Setup pathname, rather than the retained sibling's name.
|
|
launch = lldb.SBLaunchInfo(argv)
|
|
launch.SetExecutableFile(executable, False)
|
|
launch.SetArguments(argv, False)
|
|
launch.SetWorkingDirectory(os.getcwd())
|
|
launch.SetEnvironmentEntries(environment, False)
|
|
launch.SetLaunchFlags(launch.GetLaunchFlags()
|
|
& ~lldb.eLaunchFlagDisableASLR
|
|
| lldb.eLaunchFlagStopAtEntry)
|
|
# Synchronous launch waits past LLDB's transient initial loader stop.
|
|
# The shell wrapper bounds this call and the debugger at 300 seconds.
|
|
debugger.SetAsync(False)
|
|
error = lldb.SBError()
|
|
process = target.Launch(launch, error)
|
|
if error.Fail() or not process.IsValid():
|
|
raise RuntimeError("LLDB launch failed: " + str(error))
|
|
|
|
unix_signals = process.GetUnixSignals()
|
|
benign = set()
|
|
for name in ("SIGPIPE", "SIGALRM", "SIGVTALRM", "SIGPROF"):
|
|
number = unix_signals.GetSignalNumberFromName(name)
|
|
if number < 0 or not all((unix_signals.SetShouldStop(number, False),
|
|
unix_signals.SetShouldNotify(number, False),
|
|
unix_signals.SetShouldSuppress(number, False))):
|
|
raise RuntimeError("cannot configure LLDB signal policy for " + name)
|
|
benign.add(number)
|
|
print("Intel Setup signals pass/nostop/nonotify: SIGPIPE SIGALRM SIGVTALRM SIGPROF", flush=True)
|
|
for name in ("SIGABRT", "SIGSEGV"):
|
|
number = unix_signals.GetSignalNumberFromName(name)
|
|
if number < 0 or not all((unix_signals.SetShouldStop(number, True),
|
|
unix_signals.SetShouldNotify(number, True),
|
|
unix_signals.SetShouldSuppress(number, False))):
|
|
raise RuntimeError("cannot preserve LLDB fault policy for " + name)
|
|
_, first_reasons = stopped_signals(process)
|
|
print("Intel Setup entry stop: " + json.dumps(first_reasons), flush=True)
|
|
initial_stop = unix_signals.GetSignalNumberFromName("SIGSTOP")
|
|
entry_reasons = (lldb.eStopReasonExec, lldb.eStopReasonTrace,
|
|
lldb.eStopReasonBreakpoint, lldb.eStopReasonPlanComplete)
|
|
# Continue the deliberate entry stop, but preserve any genuine fault
|
|
# occurring before entry. Apply policy before GHC starts its ticker.
|
|
if process.GetState() == lldb.eStateStopped and first_reasons and all(
|
|
reason in entry_reasons
|
|
or (reason == lldb.eStopReasonSignal and (number in benign or number == initial_stop))
|
|
for reason, number in first_reasons):
|
|
error = process.Continue()
|
|
if error.Fail():
|
|
raise RuntimeError("LLDB continue failed: " + str(error))
|
|
# A benign signal may already have been queued when policy changed.
|
|
while process.GetState() == lldb.eStateStopped:
|
|
numbers, reasons = stopped_signals(process)
|
|
if not numbers or len(numbers) != len(reasons) or not set(numbers) <= benign:
|
|
break
|
|
print("Intel Setup resuming already-stopped benign signal: " + str(numbers), flush=True)
|
|
error = process.Continue()
|
|
if error.Fail():
|
|
raise RuntimeError("LLDB continue failed: " + str(error))
|
|
|
|
drain(process)
|
|
state = process.GetState()
|
|
if state == lldb.eStateExited:
|
|
code = process.GetExitStatus()
|
|
print("Intel Setup exited: " + str(code), flush=True)
|
|
elif state in (lldb.eStateStopped, lldb.eStateCrashed, lldb.eStateSuspended):
|
|
stops = describe_stop(process)
|
|
dump_stop(target, process)
|
|
signals = [stop["data"][0] for stop in stops
|
|
if stop["reason"] == lldb.eStopReasonSignal and stop["data"]]
|
|
code = 128 + int(signals[0]) if signals else 1
|
|
else:
|
|
raise RuntimeError("unexpected LLDB process state: " + str(state))
|
|
except Exception as error:
|
|
code = 1
|
|
print("Intel Setup diagnostic error: " + str(error), flush=True)
|
|
finally:
|
|
if process is not None and process.IsValid():
|
|
if process.GetState() not in (lldb.eStateExited, lldb.eStateDetached):
|
|
process.Kill()
|
|
drain(process)
|
|
# LLDB's own successful batch execution must not mask a failed child.
|
|
debugger.HandleCommand("quit " + str(code if 0 <= code <= 255 else 1))
|
|
|
|
|
|
def __lldb_init_module(debugger, _internal_dict):
|
|
debugger.HandleCommand("command script add -f trace_intel_setup_lldb.run intel-setup-run")
|