cb424d7448
verify-patch-sanity.py validates every active recipe .patch has internally- consistent hunk line counts — catching the 'malformed patch at line N' failure at commit/CI/preflight time instead of hours into a cook. This cycle hit that class three times (qtwaylandscanner, sddm, xwayland), each only discovered when cookbook tried to apply the patch. Running it across the repo found 29 latent malformed patches (validated against GNU patch: e.g. relibc/P3-sysv-ipc reproduces 'malformed patch at line 22'). They were harmless only because they sit in vendored recipes (baked, not re- applied) — but would fail on any version-bump re-derivation. --fix recounts the hunk headers (body untouched) and repaired all 29. Wired into build-preflight.sh (Phase 1.0D) and redbear-ci.yml, with a unit test (test-patch-sanity.sh). Skips archived/legacy trees and unvalidatable formats (empty placeholders, bare-@@ git hunks).
241 lines
6.4 KiB
Python
Executable File
241 lines
6.4 KiB
Python
Executable File
#!/usr/bin/env python
|
|
|
|
import argparse
|
|
import datetime
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
import time
|
|
|
|
parser = argparse.ArgumentParser(
|
|
description="Run an exhaustive test of the LLDB disassembler for a specific architecture."
|
|
)
|
|
|
|
parser.add_argument(
|
|
"--arch",
|
|
required=True,
|
|
action="store",
|
|
help="The architecture whose disassembler is to be tested",
|
|
)
|
|
parser.add_argument(
|
|
"--bytes",
|
|
required=True,
|
|
action="store",
|
|
type=int,
|
|
help="The byte width of instructions for that architecture",
|
|
)
|
|
parser.add_argument(
|
|
"--random",
|
|
required=False,
|
|
action="store_true",
|
|
help="Enables non-sequential testing",
|
|
)
|
|
parser.add_argument(
|
|
"--start",
|
|
required=False,
|
|
action="store",
|
|
type=int,
|
|
help="The first instruction value to test",
|
|
)
|
|
parser.add_argument(
|
|
"--skip",
|
|
required=False,
|
|
action="store",
|
|
type=int,
|
|
help="The interval between instructions to test",
|
|
)
|
|
parser.add_argument(
|
|
"--log",
|
|
required=False,
|
|
action="store",
|
|
help="A log file to write the most recent instruction being tested",
|
|
)
|
|
parser.add_argument(
|
|
"--time",
|
|
required=False,
|
|
action="store_true",
|
|
help="Every 100,000 instructions, print an ETA to standard out",
|
|
)
|
|
parser.add_argument(
|
|
"--lldb",
|
|
required=False,
|
|
action="store",
|
|
help="The path to LLDB.framework, if LLDB should be overridden",
|
|
)
|
|
|
|
arguments = sys.argv[1:]
|
|
|
|
arg_ns = parser.parse_args(arguments)
|
|
|
|
|
|
def AddLLDBToSysPathOnMacOSX():
|
|
def GetLLDBFrameworkPath():
|
|
lldb_path = subprocess.check_output(["xcrun", "-find", "lldb"])
|
|
re_result = re.match("(.*)/Developer/usr/bin/lldb", lldb_path)
|
|
if re_result is None:
|
|
return None
|
|
xcode_contents_path = re_result.group(1)
|
|
return xcode_contents_path + "/SharedFrameworks/LLDB.framework"
|
|
|
|
lldb_framework_path = GetLLDBFrameworkPath()
|
|
|
|
if lldb_framework_path is None:
|
|
print("Couldn't find LLDB.framework")
|
|
sys.exit(-1)
|
|
|
|
sys.path.append(lldb_framework_path + "/Resources/Python")
|
|
|
|
|
|
if arg_ns.lldb is None:
|
|
AddLLDBToSysPathOnMacOSX()
|
|
else:
|
|
sys.path.append(arg_ns.lldb + "/Resources/Python")
|
|
|
|
import lldb
|
|
|
|
debugger = lldb.SBDebugger.Create()
|
|
|
|
if not debugger.IsValid():
|
|
print("Couldn't create an SBDebugger")
|
|
sys.exit(-1)
|
|
|
|
target = debugger.CreateTargetWithFileAndArch(None, arg_ns.arch)
|
|
|
|
if not target.IsValid():
|
|
print("Couldn't create an SBTarget for architecture " + arg_ns.arch)
|
|
sys.exit(-1)
|
|
|
|
|
|
def ResetLogFile(log_file):
|
|
if log_file != sys.stdout:
|
|
log_file.seek(0)
|
|
|
|
|
|
def PrintByteArray(log_file, byte_array):
|
|
for byte in byte_array:
|
|
print(hex(byte) + " ", end=" ", file=log_file)
|
|
print(file=log_file)
|
|
|
|
|
|
class SequentialInstructionProvider:
|
|
def __init__(self, byte_width, log_file, start=0, skip=1):
|
|
self.m_byte_width = byte_width
|
|
self.m_log_file = log_file
|
|
self.m_start = start
|
|
self.m_skip = skip
|
|
self.m_value = start
|
|
self.m_last = (1 << (byte_width * 8)) - 1
|
|
|
|
def PrintCurrentState(self, ret):
|
|
ResetLogFile(self.m_log_file)
|
|
print(self.m_value, file=self.m_log_file)
|
|
PrintByteArray(self.m_log_file, ret)
|
|
|
|
def GetNextInstruction(self):
|
|
if self.m_value > self.m_last:
|
|
return None
|
|
ret = bytearray(self.m_byte_width)
|
|
for i in range(self.m_byte_width):
|
|
ret[self.m_byte_width - (i + 1)] = (self.m_value >> (i * 8)) & 255
|
|
self.PrintCurrentState(ret)
|
|
self.m_value += self.m_skip
|
|
return ret
|
|
|
|
def GetNumInstructions(self):
|
|
return (self.m_last - self.m_start) / self.m_skip
|
|
|
|
def __iter__(self):
|
|
return self
|
|
|
|
def next(self):
|
|
ret = self.GetNextInstruction()
|
|
if ret is None:
|
|
raise StopIteration
|
|
return ret
|
|
|
|
|
|
class RandomInstructionProvider:
|
|
def __init__(self, byte_width, log_file):
|
|
self.m_byte_width = byte_width
|
|
self.m_log_file = log_file
|
|
self.m_random_file = open("/dev/random", "r")
|
|
|
|
def PrintCurrentState(self, ret):
|
|
ResetLogFile(self.m_log_file)
|
|
PrintByteArray(self.m_log_file, ret)
|
|
|
|
def GetNextInstruction(self):
|
|
ret = bytearray(self.m_byte_width)
|
|
for i in range(self.m_byte_width):
|
|
ret[i] = self.m_random_file.read(1)
|
|
self.PrintCurrentState(ret)
|
|
return ret
|
|
|
|
def __iter__(self):
|
|
return self
|
|
|
|
def next(self):
|
|
ret = self.GetNextInstruction()
|
|
if ret is None:
|
|
raise StopIteration
|
|
return ret
|
|
|
|
|
|
log_file = None
|
|
|
|
|
|
def GetProviderWithArguments(args):
|
|
global log_file
|
|
if args.log is not None:
|
|
log_file = open(args.log, "w")
|
|
else:
|
|
log_file = sys.stdout
|
|
instruction_provider = None
|
|
if args.random:
|
|
instruction_provider = RandomInstructionProvider(args.bytes, log_file)
|
|
else:
|
|
start = 0
|
|
skip = 1
|
|
if args.start is not None:
|
|
start = args.start
|
|
if args.skip is not None:
|
|
skip = args.skip
|
|
instruction_provider = SequentialInstructionProvider(
|
|
args.bytes, log_file, start, skip
|
|
)
|
|
return instruction_provider
|
|
|
|
|
|
instruction_provider = GetProviderWithArguments(arg_ns)
|
|
|
|
fake_address = lldb.SBAddress()
|
|
|
|
actually_time = arg_ns.time and not arg_ns.random
|
|
|
|
if actually_time:
|
|
num_instructions_logged = 0
|
|
total_num_instructions = instruction_provider.GetNumInstructions()
|
|
start_time = time.time()
|
|
|
|
for inst_bytes in instruction_provider:
|
|
if actually_time:
|
|
if (num_instructions_logged != 0) and (num_instructions_logged % 100000 == 0):
|
|
curr_time = time.time()
|
|
elapsed_time = curr_time - start_time
|
|
remaining_time = float(total_num_instructions - num_instructions_logged) * (
|
|
float(elapsed_time) / float(num_instructions_logged)
|
|
)
|
|
print(str(datetime.timedelta(seconds=remaining_time)))
|
|
num_instructions_logged = num_instructions_logged + 1
|
|
inst_list = target.GetInstructions(fake_address, inst_bytes)
|
|
if not inst_list.IsValid():
|
|
print("Invalid instruction list", file=log_file)
|
|
continue
|
|
inst = inst_list.GetInstructionAtIndex(0)
|
|
if not inst.IsValid():
|
|
print("Invalid instruction", file=log_file)
|
|
continue
|
|
instr_output_stream = lldb.SBStream()
|
|
inst.GetDescription(instr_output_stream)
|
|
print(instr_output_stream.GetData(), file=log_file)
|