mirror of
https://github.com/openai/codex.git
synced 2026-09-09 15:58:47 +00:00
## What changed - Add an x64 and ARM64 MSVC runtime preparer that uses `dumpbin` to validate PE32+ DLL metadata, selects the declared GStreamer plugins and dependency closure, and copies them unchanged into a private `bin/` directory. - Reject malformed or unsupported PE metadata, path-bearing imports, delayed imports, managed DLLs, forwarded exports, undeclared dependencies, and case-insensitive DLL identity conflicts. - Keep `third_party/voice/sources.json` line endings stable across Windows checkouts so native build receipts remain valid. ## Testing Add native Windows tests for relocated DLL loading with a restricted search path, receipt and digest failures, duplicate identities, malformed PE headers, unsupported loader features, and cleanup after failed preparation. GitOrigin-RevId: 5375f21fc54f5d597f3d4e5f8fbca3df307fa2a8
146 lines
4.9 KiB
Python
146 lines
4.9 KiB
Python
"""Prepare verified Windows audio DLLs in one private development runtime directory."""
|
|
|
|
import argparse
|
|
from pathlib import Path
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
|
|
# Import only this script's siblings, including under PYTHONSAFEPATH.
|
|
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
from runtime import Binary, PLUGINS, RuntimeFormat, prepare
|
|
|
|
# VCRUNTIME140 is a development prerequisite, not a guaranteed Windows component.
|
|
EXTERNAL_IMPORTS = frozenset(
|
|
{
|
|
"advapi32.dll",
|
|
"dnsapi.dll",
|
|
"iphlpapi.dll",
|
|
"kernel32.dll",
|
|
"ole32.dll",
|
|
"shell32.dll",
|
|
"shlwapi.dll",
|
|
"user32.dll",
|
|
"ws2_32.dll",
|
|
"vcruntime140.dll",
|
|
*(
|
|
f"api-ms-win-crt-{part}-l1-1-0.dll"
|
|
for part in (
|
|
"conio",
|
|
"convert",
|
|
"environment",
|
|
"filesystem",
|
|
"heap",
|
|
"locale",
|
|
"math",
|
|
"process",
|
|
"runtime",
|
|
"stdio",
|
|
"string",
|
|
"time",
|
|
"utility",
|
|
)
|
|
),
|
|
}
|
|
)
|
|
|
|
|
|
def inspect(path, target):
|
|
machine = {"x86_64-pc-windows-msvc": "8664", "aarch64-pc-windows-msvc": "AA64"}[
|
|
target
|
|
]
|
|
if not 64 <= path.stat().st_size <= 64 * 1024 * 1024:
|
|
raise ValueError("invalid PE file size")
|
|
result = subprocess.run(
|
|
["dumpbin", "/nologo", "/headers", "/dependents", "/exports", str(path)],
|
|
capture_output=True,
|
|
timeout=30,
|
|
)
|
|
output = result.stdout.decode("ascii", errors="backslashreplace").replace(
|
|
"\r\n", "\n"
|
|
)
|
|
if (
|
|
result.returncode
|
|
or result.stderr
|
|
or re.search(r"(?:fatal error|warning) LNK[0-9]+", output)
|
|
):
|
|
raise ValueError("dumpbin rejected the PE library")
|
|
headers = output.split("SECTION HEADER #", 1)[0]
|
|
if (
|
|
"File Type: DLL\n" not in headers
|
|
or not re.search(r"^ +" + machine + r" machine \(", headers, re.M)
|
|
or not re.search(r"^ +20B magic # \(PE32\+\)$", headers, re.M)
|
|
):
|
|
raise ValueError(f"expected a {target} PE32+ DLL")
|
|
count = re.search(r"^ +([0-9A-F]+) number of sections$", headers, re.M)
|
|
if not count or not 1 <= int(count[1], 16) <= 96:
|
|
raise ValueError("invalid PE section table")
|
|
directories = {
|
|
name: (int(address, 16), int(size, 16))
|
|
for address, size, name in re.findall(
|
|
r"^ +([0-9A-F]+) \[ *([0-9A-F]+)\] RVA \[size\] of ([^\n]+ Directory)$",
|
|
headers,
|
|
re.M,
|
|
)
|
|
}
|
|
if len(directories) != 16:
|
|
raise ValueError("unsupported PE data directories")
|
|
if any(
|
|
directories.get(name) != (0, 0)
|
|
for name in ("Delay Import Directory", "COM Descriptor Directory")
|
|
):
|
|
raise ValueError("delay-load and managed DLL dependencies are unsupported")
|
|
if "(forwarded to " in output:
|
|
raise ValueError("forwarded DLL exports are unsupported loader dependencies")
|
|
groups = re.findall(
|
|
r"\n Image has the following dependencies:\n\n(.*?)(?:\n\n|\Z)", output, re.S
|
|
)
|
|
if len(groups) > 1:
|
|
raise ValueError("ambiguous dumpbin dependencies")
|
|
imports = (
|
|
tuple(line.strip().lower() for line in groups[0].splitlines()) if groups else ()
|
|
)
|
|
if any(not re.fullmatch(r"[a-z0-9_+.-]+\.dll", name) for name in imports):
|
|
raise ValueError("PE dependencies must be plain DLL names")
|
|
address, size = directories["Import Directory"]
|
|
# A descriptor per DLL plus its null terminator; reject truncated/injected output.
|
|
if (address, size) != (0, 0) and (
|
|
not address or size != 20 * (len(imports) + 1) or len(imports) > 128
|
|
):
|
|
raise ValueError("inconsistent PE import directory")
|
|
return Binary(path.name.lower(), imports, ())
|
|
|
|
|
|
def finalize_copy(destination, metadata, dependency_paths):
|
|
# DLL import names already resolve among siblings; do not alter signed bytes.
|
|
return metadata
|
|
|
|
|
|
def project(prefix, receipts, target, output):
|
|
if sys.platform != "win32" or target not in (
|
|
"x86_64-pc-windows-msvc",
|
|
"aarch64-pc-windows-msvc",
|
|
):
|
|
raise ValueError(
|
|
"runtime preparation requires Windows and an explicit MSVC target"
|
|
)
|
|
format = RuntimeFormat(
|
|
tuple(Path(f"lib/gstreamer-1.0/gst{name}.dll") for name in sorted(PLUGINS)),
|
|
EXTERNAL_IMPORTS,
|
|
inspect,
|
|
finalize_copy,
|
|
library_dir="bin",
|
|
plugin_dir="bin",
|
|
)
|
|
prepare(prefix, receipts, target, output, format)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("--prefix", type=Path, required=True)
|
|
parser.add_argument("--receipts", type=Path, required=True)
|
|
parser.add_argument("--target", required=True)
|
|
parser.add_argument("--output", type=Path, required=True)
|
|
args = parser.parse_args()
|
|
project(args.prefix, args.receipts, args.target, args.output)
|