Files
raven/scripts/generate-system-exports.py

133 lines
9.5 KiB
Python

#!/usr/bin/env python3
"""Generate native/nt/system_exports.inc: the public export names of Windows system libraries.
Names come from the import libraries shipped with mingw-w64 (public-domain/ZPL headers and
definition files), so Raven never copies a Windows binary or another runtime's source.
usage: generate-system-exports.py OUTPUT.inc [ARGUMENTS.inc]
With a second path it also writes the stdcall argument words of every function, read from the decorated symbol names (_Name@bytes) of the
32-bit import libraries.
"""
from pathlib import Path
import re
import subprocess
import sys
LIBRARIES = """advapi32 bcrypt comctl32 comdlg32 crypt32 dbghelp dwmapi gdi32 imm32 kernel32 ole32 oleaut32 psapi
sensapi setupapi shell32 shlwapi user32 uxtheme version wininet winmm wintrust ws2_32 wsock32
bcryptprimitives cfgmgr32 dhcpcsvc dnsapi hid imagehlp iphlpapi ntdll ktmw32 msimg32 mswsock ncrypt netapi32 opengl32 glu32 powrprof pdh runtimeobject winspool ravenhost secur32 shcore userenv wevtapi winhttp wtsapi32
atlthunk oleacc odbccp32 usp10 sfc dsound combase wer uiautomationcore lz32 loadperf mpr shfolder wldap32""".split()
# Real exports whose names begin with an underscore, which the generic filter drops.
# The interlocked functions are compiler intrinsics in the import libraries, yet kernel32 exports them and programs import them by name.
INTERLOCKED = {"InterlockedIncrement": 1, "InterlockedDecrement": 1, "InterlockedExchange": 2, "InterlockedExchangeAdd": 2,
"InterlockedCompareExchange": 3, "InterlockedCompareExchange64": 5}
EXTRA = {"runtimeobject": ["RoActivateInstance", "RoGetActivationFactory", "RoInitialize", "RoUninitialize", "RoOriginateError", "RoOriginateErrorW",
"RoGetAgileReference", "RoRegisterActivationFactories", "RoRevokeActivationFactories", "RoGetApartmentIdentifier"], "ws2_32": ["__WSAFDIsSet", "AcceptEx", "ConnectEx", "DisconnectEx", "GetAcceptExSockaddrs", "WSARecvMsg"], "wsock32": ["__WSAFDIsSet"], "comctl32": ["_TrackMouseEvent"], "kernel32": ["QueryUnbiasedInterruptTimePrecise", "QueryInterruptTimePrecise", "_hread", "_hwrite", "_lclose", "_lcreat", "_llseek", "_lopen", "_lread", "_lwrite", "FlsGetValue2", "__C_specific_handler", "_local_unwind", *INTERLOCKED]}
EXTRA_WORDS = {"kernel32": {**INTERLOCKED, "FlsGetValue2": 1, "QueryUnbiasedInterruptTimePrecise": 1, "QueryInterruptTimePrecise": 1}, "ws2_32": {"__WSAFDIsSet": 2, "AcceptEx": 8, "ConnectEx": 7, "DisconnectEx": 4, "GetAcceptExSockaddrs": 8, "WSARecvMsg": 5}, "wsock32": {"__WSAFDIsSet": 2}}
# sfc.dll has no 32-bit import library in mingw-w64: its argument words are listed here, with the catalog functions it publishes by number
# only.
EXTRA["sfc"] = ["SfpDeleteCatalog", "SfpInstallCatalog"]
EXTRA_WORDS["sfc"] = {"SfpDeleteCatalog": 1, "SfpInstallCatalog": 2, "SRSetRestorePoint": 2, "SRSetRestorePointA": 2, "SRSetRestorePointW": 2, "SfcGetNextProtectedFile": 2, "SfcIsFileProtected": 2,
"SfcWLEventLogoff": 1, "SfcWLEventLogon": 1, "SfpVerifyFile": 3}
# loadperf.dll has no 32-bit import library in mingw-w64 either.
EXTRA_WORDS["loadperf"] = {"LoadPerfCounterTextStringsA": 2, "LoadPerfCounterTextStringsW": 2, "UnloadPerfCounterTextStringsA": 2,
"UnloadPerfCounterTextStringsW": 2, "InstallPerfDllA": 3, "InstallPerfDllW": 3, "UpdatePerfNameFilesA": 4,
"UpdatePerfNameFilesW": 4, "SetServiceAsTrustedA": 2, "SetServiceAsTrustedW": 2, "BackupPerfRegistryToFileW": 2,
"RestorePerfRegistryFromFileW": 2}
# Libraries whose file does not end in .dll.
FILE_NAMES = {"winspool": "winspool.drv"}
DIRECTORY = Path("/usr/x86_64-w64-mingw32/lib")
DIRECTORY32 = Path("/usr/i686-w64-mingw32/lib")
# Libraries whose names are listed here rather than read from an import library: the host services Raven's own components import, and
# Windows libraries mingw-w64 ships no import library for.
NAMES_ONLY = {"ravenhost": ["RavenFontClose", "RavenFontGlyphs", "RavenFontInfo", "RavenFontMetrics", "RavenFontName", "RavenFontOpen",
"RavenFontOutline", "RavenFontRender", "RavenFontScan", "RavenFontTable", "RavenTrace", "RavenCanvasCreate",
"RavenCanvasDestroy", "RavenCanvasDraw", "RavenCanvasPixels", "RavenCanvasSize", "RavenPathCombine", "RavenPathWiden"]}
# atlthunk.dll, the window procedure thunks of Windows 10, has no import library in mingw-w64; its four functions are listed here.
NAMES_ONLY["atlthunk"] = ["AtlThunk_AllocateData", "AtlThunk_DataToCode", "AtlThunk_FreeData", "AtlThunk_InitData"]
# combase.dll, Windows' COM and Windows Runtime base, has no import library in mingw-w64: the functions programs and the .NET runtime take from it.
COMBASE_WORDS = {"CLSIDFromString": 2, "CoCreateGuid": 1, "CoCreateInstance": 5, "CoDecrementMTAUsage": 1, "CoGetApartmentType": 2,
"CoGetMalloc": 2, "CoIncrementMTAUsage": 1, "CoInitializeEx": 2, "CoTaskMemAlloc": 1, "CoTaskMemFree": 1, "CoTaskMemRealloc": 2,
"CoUninitialize": 0, "GetErrorInfo": 2, "RoActivateInstance": 2, "RoGetActivationFactory": 3,
"RoGetParameterizedTypeInstanceIID": 5, "RoInitialize": 1, "RoOriginateError": 2, "RoOriginateErrorW": 3,
"RoOriginateLanguageException": 3, "RoParseTypeName": 3, "RoResolveNamespace": 7, "RoUninitialize": 0, "SetErrorInfo": 2,
"StringFromGUID2": 3, "WindowsCompareStringOrdinal": 3, "WindowsCreateString": 3, "WindowsCreateStringReference": 4,
"WindowsDeleteString": 1, "WindowsDuplicateString": 2, "WindowsGetStringLen": 1, "WindowsGetStringRawBuffer": 2,
"WindowsIsStringEmpty": 1}
NAMES_ONLY["combase"] = sorted(COMBASE_WORDS)
NAMES_ONLY_WORDS = {**COMBASE_WORDS, "AtlThunk_AllocateData": 0, "AtlThunk_DataToCode": 1, "AtlThunk_FreeData": 1, "AtlThunk_InitData": 3, "RavenFontClose": 1, "RavenFontGlyphs": 4, "RavenFontInfo": 2, "RavenFontMetrics": 3, "RavenFontName": 4, "RavenFontOpen": 3,
"RavenFontOutline": 4, "RavenFontRender": 10, "RavenFontScan": 2, "RavenFontTable": 5, "RavenTrace": 1,
"RavenCanvasCreate": 2, "RavenCanvasDestroy": 1, "RavenCanvasDraw": 4, "RavenCanvasPixels": 8, "RavenCanvasSize": 2, "RavenPathCombine": 8, "RavenPathWiden": 7}
def exports(library):
if library in NAMES_ONLY:
return sorted(NAMES_ONLY[library])
output = subprocess.run(["x86_64-w64-mingw32-nm", "-g", "--defined-only", str(DIRECTORY / f"lib{library}.a")],
check=True, capture_output=True, text=True).stdout
names = set()
for line in output.splitlines():
parts = line.split()
if len(parts) == 3 and parts[1] in "TD" and not parts[2].startswith(("_", ".")) and "@" not in parts[2]:
names.add(parts[2])
return sorted(names | set(EXTRA.get(library, [])))
def stdcall_words(library):
"""Argument words by function name from the decorated symbols of the 32-bit import library (_Name@bytes)."""
if library in NAMES_ONLY:
return {name: NAMES_ONLY_WORDS[name] for name in NAMES_ONLY[library]}
archive = DIRECTORY32 / f"lib{library}.a"
output = subprocess.run(["i686-w64-mingw32-nm", "-g", "--defined-only", str(archive)], check=True, capture_output=True,
text=True).stdout if archive.exists() else ""
words = {}
for line in output.splitlines():
parts = line.split()
if len(parts) == 3 and parts[1] in "TD":
match = re.fullmatch(r"_(\w+)@(\d+)", parts[2])
if match and int(match.group(2)) % 4 == 0:
words[match.group(1)] = int(match.group(2)) // 4
for name, count in EXTRA_WORDS.get(library, {}).items():
words.setdefault(name, count)
return words
def main():
out = ["// Generated by scripts/generate-system-exports.py from mingw-w64 import libraries. Do not edit.\n"]
for library in LIBRARIES:
names = exports(library)
out.append(f'static constexpr std::string_view {library}_exports[]{{\n')
for start in range(0, len(names), 6):
out.append("\t" + ",".join(f'"{name}"' for name in names[start:start + 6]) + ",\n")
out.append("};\n")
out.append("static constexpr std::pair<std::string_view, std::span<const std::string_view>> libraries[]{\n")
for library in LIBRARIES:
out.append(f'\t{{"{FILE_NAMES.get(library, library + ".dll")}", {library}_exports}},\n')
out.append("};\n")
Path(sys.argv[1]).write_text("".join(out))
if len(sys.argv) > 2:
arguments = ["// Generated by scripts/generate-system-exports.py from the mingw-w64 32-bit import libraries. Do not edit.\n"]
tables = []
for library in LIBRARIES:
words = stdcall_words(library)
if not words:
continue
tables.append(library)
arguments.append(f'static constexpr std::pair<std::string_view, uint8_t> {library}_words[]{{\n')
items = sorted(words.items())
for start in range(0, len(items), 6):
arguments.append("\t" + ",".join(f'{{"{name}",{count}}}' for name, count in items[start:start + 6]) + ",\n")
arguments.append("};\n")
arguments.append("static constexpr std::pair<std::string_view, std::span<const std::pair<std::string_view, uint8_t>>> argument_tables[]{\n")
for library in tables:
arguments.append(f'\t{{"{FILE_NAMES.get(library, library + ".dll")}", {library}_words}},\n')
arguments.append("};\n")
Path(sys.argv[2]).write_text("".join(arguments))
main()