mirror of
https://github.com/klzgrad/naiveproxy.git
synced 2024-11-28 08:16:09 +03:00
239 lines
11 KiB
Python
239 lines
11 KiB
Python
|
# Copyright 2017 The Chromium Authors. All rights reserved.
|
||
|
# Use of this source code is governed by a BSD-style license that can be
|
||
|
# found in the LICENSE file.
|
||
|
|
||
|
import array
|
||
|
import difflib
|
||
|
import distutils.dir_util
|
||
|
import filecmp
|
||
|
import operator
|
||
|
import os
|
||
|
import re
|
||
|
import shutil
|
||
|
import struct
|
||
|
import subprocess
|
||
|
import sys
|
||
|
import tempfile
|
||
|
import uuid
|
||
|
|
||
|
|
||
|
def ZapTimestamp(filename):
|
||
|
contents = open(filename, 'rb').read()
|
||
|
# midl.exe writes timestamp 2147483647 (2^31 - 1) as creation date into its
|
||
|
# outputs, but using the local timezone. To make the output timezone-
|
||
|
# independent, replace that date with a fixed string of the same length.
|
||
|
# Also blank out the minor version number.
|
||
|
if filename.endswith('.tlb'):
|
||
|
# See https://chromium-review.googlesource.com/c/chromium/src/+/693223 for
|
||
|
# a fairly complete description of the .tlb binary format.
|
||
|
# TLB files start with a 54 byte header. Offset 0x20 stores how many types
|
||
|
# are defined in the file, and the header is followed by that many uint32s.
|
||
|
# After that, 15 section headers appear. Each section header is 16 bytes,
|
||
|
# starting with offset and length uint32s.
|
||
|
# Section 12 in the file contains custom() data. custom() data has a type
|
||
|
# (int, string, etc). Each custom data chunk starts with a uint16_t
|
||
|
# describing its type. Type 8 is string data, consisting of a uint32_t
|
||
|
# len, followed by that many data bytes, followed by 'W' bytes to pad to a
|
||
|
# 4 byte boundary. Type 0x13 is uint32 data, followed by 4 data bytes,
|
||
|
# followed by two 'W' to pad to a 4 byte boundary.
|
||
|
# The custom block always starts with one string containing "Created by
|
||
|
# MIDL version 8...", followed by one uint32 containing 0x7fffffff,
|
||
|
# followed by another uint32 containing the MIDL compiler version (e.g.
|
||
|
# 0x0801026e for v8.1.622 -- 0x26e == 622). These 3 fields take 0x54 bytes.
|
||
|
# There might be more custom data after that, but these 3 blocks are always
|
||
|
# there for file-level metadata.
|
||
|
# All data is little-endian in the file.
|
||
|
assert contents[0:8] == 'MSFT\x02\x00\x01\x00'
|
||
|
ntypes, = struct.unpack_from('<I', contents, 0x20)
|
||
|
custom_off, custom_len = struct.unpack_from(
|
||
|
'<II', contents, 0x54 + 4*ntypes + 11*16)
|
||
|
assert custom_len >= 0x54
|
||
|
# First: Type string (0x8), followed by 0x3e characters.
|
||
|
assert contents[custom_off:custom_off+6] == '\x08\x00\x3e\x00\x00\x00'
|
||
|
assert re.match(
|
||
|
'Created by MIDL version 8\.\d\d\.\d{4} at ... Jan 1. ..:..:.. 2038\n',
|
||
|
contents[custom_off+6:custom_off+6+0x3e])
|
||
|
# Second: Type uint32 (0x13) storing 0x7fffffff (followed by WW / 0x57 pad)
|
||
|
assert contents[custom_off+6+0x3e:custom_off+6+0x3e+8] == \
|
||
|
'\x13\x00\xff\xff\xff\x7f\x57\x57'
|
||
|
# Third: Type uint32 (0x13) storing MIDL compiler version.
|
||
|
assert contents[custom_off+6+0x3e+8:custom_off+6+0x3e+8+2] == '\x13\x00'
|
||
|
# Replace "Created by" string with fixed string, and fixed MIDL version with
|
||
|
# 8.1.622 always.
|
||
|
contents = (contents[0:custom_off+6] +
|
||
|
'Created by MIDL version 8.xx.xxxx at a redacted point in time\n' +
|
||
|
# uint32 (0x13) val 0x7fffffff, WW, uint32 (0x13), val 0x0801026e, WW
|
||
|
'\x13\x00\xff\xff\xff\x7f\x57\x57\x13\x00\x6e\x02\x01\x08\x57\x57' +
|
||
|
contents[custom_off + 0x54:])
|
||
|
else:
|
||
|
contents = re.sub(
|
||
|
'File created by MIDL compiler version 8\.\d\d\.\d{4} \*/\r\n'
|
||
|
'/\* at ... Jan 1. ..:..:.. 2038',
|
||
|
'File created by MIDL compiler version 8.xx.xxxx */\r\n'
|
||
|
'/* at a redacted point in time',
|
||
|
contents)
|
||
|
contents = re.sub(
|
||
|
' Oicf, W1, Zp8, env=(.....) \(32b run\), '
|
||
|
'target_arch=(AMD64|X86) 8\.\d\d\.\d{4}',
|
||
|
' Oicf, W1, Zp8, env=\\1 (32b run), target_arch=\\2 8.xx.xxxx',
|
||
|
contents)
|
||
|
# TODO(thakis): If we need more hacks than these, try to verify checked-in
|
||
|
# outputs when we're using the hermetic toolchain.
|
||
|
# midl.exe older than 8.1.622 omit '//' after #endif, fix that:
|
||
|
contents = contents.replace('#endif !_MIDL_USE_GUIDDEF_',
|
||
|
'#endif // !_MIDL_USE_GUIDDEF_')
|
||
|
# midl.exe puts the midl version into code in one place. To have
|
||
|
# predictable output, lie about the midl version if it's not 8.1.622.
|
||
|
# This is unfortunate, but remember that there's beauty too in imperfection.
|
||
|
contents = contents.replace('0x801026c, /* MIDL Version 8.1.620 */',
|
||
|
'0x801026e, /* MIDL Version 8.1.622 */')
|
||
|
open(filename, 'wb').write(contents)
|
||
|
|
||
|
|
||
|
def overwrite_cls_guid_h(h_file, dynamic_guid):
|
||
|
contents = open(h_file, 'rb').read()
|
||
|
contents = re.sub('class DECLSPEC_UUID\("[^"]*"\)',
|
||
|
'class DECLSPEC_UUID("%s")' % str(dynamic_guid), contents)
|
||
|
open(h_file, 'wb').write(contents)
|
||
|
|
||
|
|
||
|
def overwrite_cls_guid_iid(iid_file, dynamic_guid):
|
||
|
contents = open(iid_file, 'rb').read()
|
||
|
hexuuid = '0x%08x,0x%04x,0x%04x,' % dynamic_guid.fields[0:3]
|
||
|
hexuuid += ','.join('0x%02x' % ord(b) for b in dynamic_guid.bytes[8:])
|
||
|
contents = re.sub(r'MIDL_DEFINE_GUID\(CLSID, ([^,]*),[^)]*\)',
|
||
|
r'MIDL_DEFINE_GUID(CLSID, \1,%s)' % hexuuid, contents)
|
||
|
open(iid_file, 'wb').write(contents)
|
||
|
|
||
|
|
||
|
def overwrite_cls_guid_tlb(tlb_file, dynamic_guid):
|
||
|
# See ZapTimestamp() for a short overview of the .tlb format. The 1st
|
||
|
# section contains type descriptions, and the first type should be our
|
||
|
# coclass. It points to the type's GUID in section 6, the GUID section.
|
||
|
contents = open(tlb_file, 'rb').read()
|
||
|
assert contents[0:8] == 'MSFT\x02\x00\x01\x00'
|
||
|
ntypes, = struct.unpack_from('<I', contents, 0x20)
|
||
|
type_off, type_len = struct.unpack_from('<II', contents, 0x54 + 4*ntypes)
|
||
|
assert ord(contents[type_off]) == 0x25, "expected coclass"
|
||
|
guidind = struct.unpack_from('<I', contents, type_off + 0x2c)[0]
|
||
|
guid_off, guid_len = struct.unpack_from(
|
||
|
'<II', contents, 0x54 + 4*ntypes + 5*16)
|
||
|
assert guidind + 14 <= guid_len
|
||
|
contents = array.array('c', contents)
|
||
|
struct.pack_into('<IHH8s', contents, guid_off + guidind,
|
||
|
*(dynamic_guid.fields[0:3] + (dynamic_guid.bytes[8:],)))
|
||
|
# The GUID is correct now, but there's also a GUID hashtable in section 5.
|
||
|
# Need to recreate that too. Since the hash table uses chaining, it's
|
||
|
# easiest to recompute it from scratch rather than trying to patch it up.
|
||
|
hashtab = [0xffffffff] * (0x80 / 4)
|
||
|
for guidind in range(guid_off, guid_off + guid_len, 24):
|
||
|
guidbytes, typeoff, nextguid = struct.unpack_from(
|
||
|
'<16sII', contents, guidind)
|
||
|
words = struct.unpack('<8H', guidbytes)
|
||
|
# midl seems to use the following simple hash function for GUIDs:
|
||
|
guidhash = reduce(operator.xor, [w for w in words]) % (0x80 / 4)
|
||
|
nextguid = hashtab[guidhash]
|
||
|
struct.pack_into('<I', contents, guidind + 0x14, nextguid)
|
||
|
hashtab[guidhash] = guidind - guid_off
|
||
|
hash_off, hash_len = struct.unpack_from(
|
||
|
'<II', contents, 0x54 + 4*ntypes + 4*16)
|
||
|
for i, hashval in enumerate(hashtab):
|
||
|
struct.pack_into('<I', contents, hash_off + 4*i, hashval)
|
||
|
open(tlb_file, 'wb').write(contents)
|
||
|
|
||
|
|
||
|
def overwrite_cls_guid(h_file, iid_file, tlb_file, dynamic_guid):
|
||
|
# Fix up GUID in .h, _i.c, and .tlb. This currently assumes that there's
|
||
|
# only one coclass in the idl file, and that that's the type with the
|
||
|
# dynamic type.
|
||
|
overwrite_cls_guid_h(h_file, dynamic_guid)
|
||
|
overwrite_cls_guid_iid(iid_file, dynamic_guid)
|
||
|
overwrite_cls_guid_tlb(tlb_file, dynamic_guid)
|
||
|
|
||
|
|
||
|
def main(arch, outdir, dynamic_guid, tlb, h, dlldata, iid, proxy, idl, *flags):
|
||
|
# Copy checked-in outputs to final location.
|
||
|
THIS_DIR = os.path.abspath(os.path.dirname(__file__))
|
||
|
source = os.path.join(THIS_DIR, '..', '..', '..',
|
||
|
'third_party', 'win_build_output', outdir.replace('gen/', 'midl/'))
|
||
|
if os.path.isdir(os.path.join(source, os.path.basename(idl))):
|
||
|
source = os.path.join(source, os.path.basename(idl))
|
||
|
source = os.path.join(source, arch.split('.')[1]) # Append 'x86' or 'x64'.
|
||
|
source = os.path.normpath(source)
|
||
|
distutils.dir_util.copy_tree(source, outdir, preserve_times=False)
|
||
|
if dynamic_guid != 'none':
|
||
|
overwrite_cls_guid(os.path.join(outdir, h),
|
||
|
os.path.join(outdir, iid),
|
||
|
os.path.join(outdir, tlb),
|
||
|
uuid.UUID(dynamic_guid))
|
||
|
|
||
|
# On non-Windows, that's all we can do.
|
||
|
if sys.platform != 'win32':
|
||
|
return 0
|
||
|
|
||
|
# On Windows, run midl.exe on the input and check that its outputs are
|
||
|
# identical to the checked-in outputs (after possibly replacing their main
|
||
|
# class guid).
|
||
|
tmp_dir = tempfile.mkdtemp()
|
||
|
delete_tmp_dir = True
|
||
|
|
||
|
# Read the environment block from the file. This is stored in the format used
|
||
|
# by CreateProcess. Drop last 2 NULs, one for list terminator, one for
|
||
|
# trailing vs. separator.
|
||
|
env_pairs = open(arch).read()[:-2].split('\0')
|
||
|
env_dict = dict([item.split('=', 1) for item in env_pairs])
|
||
|
|
||
|
args = ['midl', '/nologo'] + list(flags) + [
|
||
|
'/out', tmp_dir,
|
||
|
'/tlb', tlb,
|
||
|
'/h', h,
|
||
|
'/dlldata', dlldata,
|
||
|
'/iid', iid,
|
||
|
'/proxy', proxy,
|
||
|
idl]
|
||
|
try:
|
||
|
popen = subprocess.Popen(args, shell=True, env=env_dict,
|
||
|
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
||
|
out, _ = popen.communicate()
|
||
|
# Filter junk out of stdout, and write filtered versions. Output we want
|
||
|
# to filter is pairs of lines that look like this:
|
||
|
# Processing C:\Program Files (x86)\Microsoft SDKs\...\include\objidl.idl
|
||
|
# objidl.idl
|
||
|
lines = out.splitlines()
|
||
|
prefixes = ('Processing ', '64 bit Processing ')
|
||
|
processing = set(os.path.basename(x)
|
||
|
for x in lines if x.startswith(prefixes))
|
||
|
for line in lines:
|
||
|
if not line.startswith(prefixes) and line not in processing:
|
||
|
print line
|
||
|
if popen.returncode != 0:
|
||
|
return popen.returncode
|
||
|
|
||
|
for f in os.listdir(tmp_dir):
|
||
|
ZapTimestamp(os.path.join(tmp_dir, f))
|
||
|
|
||
|
# Now compare the output in tmp_dir to the copied-over outputs.
|
||
|
diff = filecmp.dircmp(tmp_dir, outdir)
|
||
|
if diff.diff_files:
|
||
|
print 'midl.exe output different from files in %s, see %s' \
|
||
|
% (outdir, tmp_dir)
|
||
|
for f in diff.diff_files:
|
||
|
if f.endswith('.tlb'): continue
|
||
|
fromfile = os.path.join(outdir, f)
|
||
|
tofile = os.path.join(tmp_dir, f)
|
||
|
print ''.join(difflib.unified_diff(open(fromfile, 'U').readlines(),
|
||
|
open(tofile, 'U').readlines(),
|
||
|
fromfile, tofile))
|
||
|
delete_tmp_dir = False
|
||
|
print 'To rebaseline:'
|
||
|
print ' copy /y %s\* %s' % (tmp_dir, source)
|
||
|
sys.exit(1)
|
||
|
return 0
|
||
|
finally:
|
||
|
if os.path.exists(tmp_dir) and delete_tmp_dir:
|
||
|
shutil.rmtree(tmp_dir)
|
||
|
|
||
|
|
||
|
if __name__ == '__main__':
|
||
|
sys.exit(main(*sys.argv[1:]))
|