mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-11-04 17:17:39 +00:00
build: diagnose Intel Darwin ghc-boot Setup failures with LLDB
This commit is contained in:
1 parent
17554a9d17
commit
75f95375f8
2 files changed
+269
No files matched your search
@@ -0,0 +1,85 @@
|
||||
# Failure-only diagnostic for the pinned Intel Darwin compiler's ghc-boot Setup.
|
||||
# Resolve lldbPath with xcrun on the runner before evaluation. Only this
|
||||
# diagnostic build may use `--option sandbox false` to reach Apple's debugger.
|
||||
{ original, lldbPath
|
||||
, expectedDrv ? "/nix/store/vghjgkr64qjigddh3gnf3c103wbvpxmz-ghc-boot-lib-ghc-boot-9.14.drv"
|
||||
}:
|
||||
let
|
||||
shellQuote = value: "'" + builtins.replaceStrings [ "'" ] [ "'\\''" ] value + "'";
|
||||
needle = " printf '#!/bin/sh\\nexec %s \"$@\"\\n' \"$g\" > \"$wrapBin/$gName\"";
|
||||
replacement = ''
|
||||
printf '#!/bin/sh\nrealGhc="%s"\n' "$g" > "$wrapBin/$gName"
|
||||
cat >> "$wrapBin/$gName" <<'SIMPLEX_SETUP_COMPILER'
|
||||
setupOutput=
|
||||
previous=
|
||||
setupSource=
|
||||
setupMake=
|
||||
for arg do
|
||||
[ "$previous" != -o ] || setupOutput=$arg
|
||||
case "$arg" in
|
||||
--make) setupMake=1 ;;
|
||||
*/ghc-boot-9.14/setup/setup.hs) setupSource=1 ;;
|
||||
esac
|
||||
previous=$arg
|
||||
done
|
||||
"$realGhc" "$@"
|
||||
status=$?
|
||||
[ "$status" -eq 0 ] || exit "$status"
|
||||
case "$setupOutput" in
|
||||
*/ghc-boot-9.14/setup/setup)
|
||||
if [ -n "$setupMake" ] && [ -n "$setupSource" ]; then
|
||||
realSetup=$setupOutput.lldb-real
|
||||
cp -p "$setupOutput" "$realSetup" || exit 1
|
||||
cmp "$setupOutput" "$realSetup" || exit 1
|
||||
echo 'Intel Setup diagnostic: preserving compiler-produced executable'
|
||||
sha256sum "$realSetup"
|
||||
printf '#!/bin/sh\nset -eu\nrealSetup="%s"\noriginalSetup="%s"\n' \
|
||||
"$realSetup" "$setupOutput" > "$setupOutput"
|
||||
cat >> "$setupOutput" <<'SIMPLEX_SETUP_EXECUTION'
|
||||
configure=
|
||||
for arg do
|
||||
[ "$arg" != configure ] || configure=1
|
||||
done
|
||||
if [ -z "$configure" ]; then
|
||||
exec "$realSetup" "$@"
|
||||
fi
|
||||
argvFile=$originalSetup.lldb-argv
|
||||
printf '%s\0' "$originalSetup" "$@" > "$argvFile"
|
||||
env -0 > "$argvFile.env"
|
||||
: "''${NIX_BUILD_TOP:?Intel Setup diagnostic requires NIX_BUILD_TOP}"
|
||||
: > "$NIX_BUILD_TOP/intel-setup-lldb-attempted"
|
||||
# LLDB rejects a Python module whose basename contains Nix's hash prefix.
|
||||
driverDir=$originalSetup.lldb-driver
|
||||
mkdir -p "$driverDir"
|
||||
cp -f ${./trace_intel_setup_lldb.py} "$driverDir/trace_intel_setup_lldb.py"
|
||||
echo 'Intel Setup diagnostic: actual configure under host LLDB (diagnostic permits sandbox=false)'
|
||||
${shellQuote lldbPath} --version
|
||||
exec timeout --kill-after=10s 300s ${shellQuote lldbPath} --batch --no-lldbinit \
|
||||
-o "command script import \"$driverDir/trace_intel_setup_lldb.py\"" \
|
||||
-o "intel-setup-run \"$argvFile\"" -- "$realSetup"
|
||||
SIMPLEX_SETUP_EXECUTION
|
||||
chmod +x "$setupOutput"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
exit 0
|
||||
SIMPLEX_SETUP_COMPILER
|
||||
'';
|
||||
occurrences = phase: builtins.div
|
||||
(builtins.stringLength phase - builtins.stringLength
|
||||
(builtins.replaceStrings [ needle ] [ "" ] phase))
|
||||
(builtins.stringLength needle);
|
||||
in
|
||||
assert original.system == "x86_64-darwin";
|
||||
assert original.identifier.name == "ghc-boot";
|
||||
assert original.drvPath == expectedDrv;
|
||||
assert builtins.isString lldbPath && builtins.substring 0 1 lldbPath == "/";
|
||||
original.overrideAttrs (old:
|
||||
assert occurrences old.buildPhase == 1; {
|
||||
buildPhase = builtins.replaceStrings [ needle ] [ replacement ] old.buildPhase + ''
|
||||
if [ ! -f "$NIX_BUILD_TOP/intel-setup-lldb-attempted" ]; then
|
||||
echo 'ERROR: ghc-boot Setup configure was not intercepted by LLDB' >&2
|
||||
exit 1
|
||||
fi
|
||||
'';
|
||||
})
|
||||
@@ -0,0 +1,184 @@
|
||||
"""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")
|
||||
Reference in new issue
Block a user