| # Copyright 2023 The Chromium Authors |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| """ |
| Parses .apiset section from a DLL (e.g. ApiSetSchema.dll) to determine which |
| functions are "exported" on a given Windows version by apisets. Only supports |
| version 6 (Windows 10+) of the schema. |
| |
| This script is not run automatically as we do not ship a specimen ApiSetSchema |
| with the build. |
| |
| vpython3 .\chrome\test\delayload\generate_supported_apisets.py \ |
| --dll c:\Windows\System32\ApisetSchema.dll \ |
| --out-file .\chrome\test\delayload\supported_apisets_10.0.10240.inc |
| |
| See: https://www.geoffchappell.com/studies/windows/win32/apisetschema/index.htm |
| """ |
| |
| import argparse |
| import hashlib |
| import os |
| import sys |
| import struct |
| import ctypes |
| |
| USE_PYTHON_3 = f'This script will only run under python3.' |
| |
| # Assume this script is under chrome\test\delayload |
| _SCRIPT_DIR = os.path.dirname(__file__) |
| _ROOT_DIR = os.path.join(_SCRIPT_DIR, os.pardir, os.pardir, os.pardir) |
| _PEFILE_DIR = os.path.join(_ROOT_DIR, 'third_party', 'pefile_py3') |
| |
| sys.path.insert(1, _PEFILE_DIR) |
| import pefile |
| |
| |
| def from_utf16(data, start, length): |
| return data[start:start + length].decode("utf-16-le") |
| |
| |
| class StructHelper(ctypes.Structure): |
| def __init__(self, data): |
| super(StructHelper, self).__init__() |
| ctypes.memmove(ctypes.addressof(self), data, ctypes.sizeof(self)) |
| |
| |
| class ApiSetNamespaceEntryV6(StructHelper): |
| _fields_ = [ |
| ('Flags', ctypes.c_uint32), |
| ('NameOffset', ctypes.c_uint32), |
| ('NameLength', ctypes.c_uint32), |
| ('HashedLength', ctypes.c_uint32), |
| ('ValueOffset', ctypes.c_uint32), |
| ('ValueCount', ctypes.c_uint32) |
| ] |
| |
| |
| class ApiSetNamespaceV6(StructHelper): |
| _fields_ = [ |
| ('Version', ctypes.c_uint32), |
| ('Size', ctypes.c_uint32), |
| ('Flags', ctypes.c_uint32), |
| ('Count', ctypes.c_uint32), |
| ('EntryOffset', ctypes.c_uint32), |
| ('HashOffset', ctypes.c_uint32), |
| ('HashFactor', ctypes.c_uint32) |
| ] |
| |
| |
| class ApiSetSchemaV6: |
| def __init__(self, data): |
| self._data = data |
| self._apisets = [] |
| header = ApiSetNamespaceV6(self._data) |
| self._flag = header.Flags |
| self._version = header.Version |
| self._load_apisets(header.EntryOffset, header.Count) |
| |
| @property |
| def apisets(self): |
| return self._apisets |
| |
| def _load_apisets(self, offset, count): |
| for _ in range(count): |
| entry = ApiSetNamespaceEntryV6( |
| self._data[offset:offset + ctypes.sizeof(ApiSetNamespaceEntryV6)]) |
| entry_name = from_utf16(self._data, entry.NameOffset, entry.NameLength) |
| self._apisets.append(entry_name) |
| offset += ctypes.sizeof(ApiSetNamespaceEntryV6) |
| |
| |
| def parse_apiset_names(data): |
| version = struct.unpack("B", data[0:1])[0] |
| if version != 6: |
| raise Exception(f'Unsupported schema version: {version}') |
| apiset_schema = ApiSetSchemaV6(data) |
| return apiset_schema.apisets |
| |
| |
| def get_file_version(pe): |
| for fileinfo in pe.FileInfo[0]: |
| if fileinfo.Key.decode() == 'StringFileInfo': |
| for st in fileinfo.StringTable: |
| for entry in st.entries.items(): |
| if entry[0] == b'FileVersion': |
| return entry[1].decode('utf8') |
| raise Exception("FileVersion not found in dll") |
| |
| |
| def read_apiset_section(filename): |
| pe = pefile.PE(filename) |
| product_version = get_file_version(pe) |
| for section in pe.sections: |
| if section.Name == b'.apiset\0': |
| return (product_version, section.get_data()) |
| raise Exception(".apiset section not Found") |
| |
| |
| # apiset_name: api-ms-win-core-synch-l1-2-0.dll |
| # -> (api-ms-win-core-synch-l1-2, 0) |
| def apiset_dll_to_version(apiset_name): |
| last_dash = apiset_name.rindex('-') |
| apiset_maj_min = apiset_name[0:last_dash] |
| apiset_subversion = apiset_name[last_dash+1:] |
| return (apiset_maj_min, apiset_subversion) |
| |
| |
| def main(): |
| parser = argparse.ArgumentParser( |
| description=__doc__, formatter_class=argparse.RawTextHelpFormatter) |
| parser.add_argument('--dll', |
| metavar='FILE_NAME', |
| help='Dll with a .apiset section') |
| parser.add_argument('--out-file', |
| default='apisets.inc', |
| metavar='FILE_NAME', |
| help='path to write .inc file to') |
| args, _ = parser.parse_known_args() |
| |
| shasum = hashlib.sha256(open(args.dll, 'rb').read()).hexdigest() |
| dll_basename = os.path.basename(args.dll) |
| (dll_version, data) = read_apiset_section(args.dll) |
| |
| apiset_names = parse_apiset_names(data) |
| # Only keep api- entries (skip ext- as these are for kernel modules). |
| apiset_names = filter(lambda s: s.startswith("api-"), apiset_names) |
| apiset_entries = [apiset_dll_to_version(s) for s in apiset_names] |
| |
| with open(args.out_file, 'w', encoding='utf8') as f: |
| f.write(f'// Generated from {dll_basename}\n') |
| f.write(f'// FileVersion: {dll_version}\n') |
| f.write(f'// sha256: {shasum}\n') |
| f.write(',\n'.join([f'{{"{e[0]}", {e[1]}}}' for e in apiset_entries])) |
| f.write('\n') |
| |
| |
| if __name__ == '__main__': |
| sys.exit(main()) |