"""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")