deploy-ng: new extensible blob format with PRC configurability

This commit is contained in:
rdb 2017-11-24 21:48:18 +01:00
parent a7e00d0ee6
commit d755de849c
6 changed files with 610 additions and 102 deletions

View File

@ -8,10 +8,12 @@ import marshal
import imp import imp
import platform import platform
import struct import struct
from io import StringIO, TextIOWrapper from io import StringIO, BytesIO, TextIOWrapper
import distutils.sysconfig as sysconf import distutils.sysconfig as sysconf
import zipfile import zipfile
from . import pefile
# Temporary (?) try..except to protect against unbuilt p3extend_frozen. # Temporary (?) try..except to protect against unbuilt p3extend_frozen.
try: try:
import p3extend_frozen import p3extend_frozen
@ -1589,7 +1591,7 @@ class Freezer:
return target return target
def generateRuntimeFromStub(self, basename, stub_file): def generateRuntimeFromStub(self, basename, stub_file, fields={}):
# We must have a __main__ module to make an exe file. # We must have a __main__ module to make an exe file.
if not self.__writingModule('__main__'): if not self.__writingModule('__main__'):
message = "Can't generate an executable without a __main__ module." message = "Can't generate an executable without a __main__ module."
@ -1602,15 +1604,18 @@ class Freezer:
target = basename target = basename
modext = '.so' modext = '.so'
address_offset = 0 # First gather up the strings and code for all the module names, and
# put those in a string pool.
# First gather up the strings for all the module names. pool = b""
blob = b""
strings = set() strings = set()
for moduleName, mdef in self.getModuleDefs(): for moduleName, mdef in self.getModuleDefs():
strings.add(moduleName.encode('ascii')) strings.add(moduleName.encode('ascii'))
for value in fields.values():
if value is not None:
strings.add(value.encode('utf-8'))
# Sort by length descending, allowing reuse of partial strings. # Sort by length descending, allowing reuse of partial strings.
strings = sorted(strings, key=lambda str:-len(str)) strings = sorted(strings, key=lambda str:-len(str))
string_offsets = {} string_offsets = {}
@ -1618,13 +1623,15 @@ class Freezer:
for string in strings: for string in strings:
# First check whether it's already in there; it could be part of # First check whether it's already in there; it could be part of
# a longer string. # a longer string.
offset = blob.find(string + b'\0') offset = pool.find(string + b'\0')
if offset < 0: if offset < 0:
offset = len(blob) offset = len(pool)
blob += string + b'\0' pool += string + b'\0'
string_offsets[string] = offset string_offsets[string] = offset
# Generate export table. # Now go through the modules and add them to the pool as well. These
# are not 0-terminated, but we later record their sizes and names in
# a table after the blob header.
moduleList = [] moduleList = []
for moduleName, mdef in self.getModuleDefs(): for moduleName, mdef in self.getModuleDefs():
@ -1635,9 +1642,9 @@ class Freezer:
continue continue
# For whatever it's worth, align the code blocks. # For whatever it's worth, align the code blocks.
if len(blob) & 3 != 0: if len(pool) & 3 != 0:
pad = (4 - (len(blob) & 3)) pad = (4 - (len(pool) & 3))
blob += b'\0' * pad pool += b'\0' * pad
assert not mdef.exclude assert not mdef.exclude
# Allow importing this module. # Allow importing this module.
@ -1649,8 +1656,8 @@ class Freezer:
if getattr(module, "__path__", None): if getattr(module, "__path__", None):
# Indicate package by negative size # Indicate package by negative size
size = -size size = -size
moduleList.append((moduleName, len(blob), size)) moduleList.append((moduleName, len(pool), size))
blob += code pool += code
continue continue
# This is a module with no associated Python code. It is either # This is a module with no associated Python code. It is either
@ -1668,55 +1675,327 @@ class Freezer:
code = 'import sys;del sys.modules["%s"];import sys,os,imp;imp.load_dynamic("%s",os.path.join(os.path.dirname(sys.executable), "%s%s"))' % (moduleName, moduleName, moduleName, modext) code = 'import sys;del sys.modules["%s"];import sys,os,imp;imp.load_dynamic("%s",os.path.join(os.path.dirname(sys.executable), "%s%s"))' % (moduleName, moduleName, moduleName, modext)
code = compile(code, moduleName, 'exec') code = compile(code, moduleName, 'exec')
code = marshal.dumps(code) code = marshal.dumps(code)
moduleList.append((moduleName, len(blob), len(code))) moduleList.append((moduleName, len(pool), len(code)))
blob += code pool += code
# Now compose the final blob. # Determine the format of the header and module list entries depending
if '64' in self.platform: # on the platform.
layout = '<QQixxxx' num_pointers = 10
stub_data = bytearray(stub_file.read())
if self._is_executable_64bit(stub_data):
header_layout = '<QQHHHH8x%dQQ' % num_pointers
entry_layout = '<QQixxxx'
else: else:
layout = '<IIi' header_layout = '<QQHHHH8x%dII' % num_pointers
entry_layout = '<IIi'
blob2 = bytes() # Calculate the size of the header and module table, so that we can
blobOffset = (len(moduleList) + 1) * struct.calcsize(layout) # determine the proper offset for the string pointers.
blobOffset += address_offset pool_offset = (len(moduleList) + 1) * struct.calcsize(entry_layout)
# The module table is the first thing in the blob.
blob = b""
for moduleName, offset, size in moduleList: for moduleName, offset, size in moduleList:
encoded = moduleName.encode('ascii') encoded = moduleName.encode('ascii')
stringOffset = blobOffset + string_offsets[encoded] string_offset = pool_offset + string_offsets[encoded]
if size != 0: if size != 0:
offset += blobOffset offset += pool_offset
blob2 += struct.pack(layout, stringOffset, offset, size) blob += struct.pack(entry_layout, string_offset, offset, size)
blob2 += struct.pack(layout, 0, 0, 0) blob += struct.pack(entry_layout, 0, 0, 0)
assert len(blob2) + address_offset == blobOffset
blob = blob2 + blob # Add the string pool.
del blob2 assert len(blob) == pool_offset
blob += pool
del pool
# Total blob length should be aligned to 8 bytes. # Total blob length should be aligned to 8 bytes.
if len(blob) & 7 != 0: if len(blob) & 7 != 0:
pad = (8 - (len(blob) & 7)) pad = (8 - (len(blob) & 7))
blob += b'\0' * pad blob += b'\0' * pad
# Build from pre-built binary stub if self.platform.startswith('win'):
with open(target, 'wb') as f: # We don't use mmap on Windows.
f.write(stub_file.read()) blob_align = 32
offset = f.tell() else:
# Align to page size, so that it can be mmapped.
blob_align = 4096
# Determine the blob offset, padding the stub if necessary.
blob_offset = len(stub_data)
if (blob_offset & (blob_align - 1)) != 0:
# Add padding to align to the page size, so it can be mmapped. # Add padding to align to the page size, so it can be mmapped.
if offset % 4095 != 0: pad = (blob_align - (blob_offset & (blob_align - 1)))
pad = (4096 - (offset & 4095)) stub_data += (b'\0' * pad)
f.write(b'\0' * pad) blob_offset += pad
offset += pad assert (blob_offset % blob_align) == 0
assert blob_offset == len(stub_data)
# Calculate the offsets for the variables. These are pointers,
# relative to the beginning of the blob.
field_offsets = {}
for key, value in fields.items():
if value is not None:
encoded = value.encode('utf-8')
field_offsets[key] = pool_offset + string_offsets[encoded]
# Compose the header we will be writing to the stub, to tell it where
# to find the module data blob, as well as other variables.
header = struct.pack(header_layout,
blob_offset,
len(blob),
1, # Version number
num_pointers, # Number of pointers that follow
0, # Codepage, not yet used
0, # Flags, not yet used
0, # Module table pointer, always 0 for now.
# The following variables need to be set before static init
# time. See configPageManager.cxx, where they are read.
field_offsets.get('prc_data', 0),
field_offsets.get('default_prc_dir', 0),
field_offsets.get('prc_dir_envvars', 0),
field_offsets.get('prc_path_envvars', 0),
field_offsets.get('prc_patterns', 0),
field_offsets.get('prc_encrypted_patterns', 0),
field_offsets.get('prc_encryption_key', 0),
field_offsets.get('prc_executable_patterns', 0),
field_offsets.get('prc_executable_args_envvar', 0),
0)
# Now, find the location of the 'blobinfo' symbol in the executable,
# to which we will write our header.
if not self._replace_symbol(stub_data, b'blobinfo', header):
# This must be a legacy deploy-stub, which requires the offset to
# be appended to the end.
blob += struct.pack('<Q', blob_offset)
with open(target, 'wb') as f:
f.write(stub_data)
assert f.tell() == blob_offset
f.write(blob) f.write(blob)
# And tack on the offset of the blob to the end.
f.write(struct.pack('<Q', offset))
os.chmod(target, 0o755) os.chmod(target, 0o755)
return target return target
def _is_executable_64bit(self, data):
"""Returns true if this is a 64-bit executable."""
if data.startswith(b'MZ'):
# A Windows PE file.
offset, = struct.unpack_from('<I', data, 0x3c)
assert data[offset:offset+4] == b'PE\0\0'
magic, = struct.unpack_from('<H', data, offset + 24)
assert magic in (0x010b, 0x020b)
return magic == 0x020b
elif data.startswith(b"\177ELF"):
# A Linux/FreeBSD ELF executable.
elfclass = ord(data[4:5])
assert elfclass in (1, 2)
return elfclass == 2
elif data[:4] in (b'\xFE\xED\xFA\xCE', b'\xCE\xFA\xED\xFE'):
# 32-bit Mach-O file, as used on macOS.
return False
elif data[:4] in (b'\xFE\xED\xFA\xCF', b'\xCF\xFA\xED\xFE'):
# 64-bit Mach-O file, as used on macOS.
return True
elif data[:4] in (b'\xCA\xFE\xBA\xBE', b'\xBE\xBA\xFE\xCA',
b'\xCA\xFE\xBA\xBF', b'\xBF\xBA\xFE\xCA'):
# A universal file, containing one or more Mach-O files.
#TODO: how to handle universal stubs with more than one arch?
pass
def _replace_symbol(self, data, symbol_name, replacement):
"""We store a custom section in the binary file containing a header
containing offsets to the binary data."""
if data.startswith(b'MZ'):
# A Windows PE file.
pe = pefile.PEFile()
pe.read(BytesIO(data))
addr = pe.get_export_address(symbol_name)
if addr is not None:
# We found it, return its offset in the file.
offset = pe.get_address_offset(addr)
if offset is not None:
data[offset:offset+len(replacement)] = replacement
return True
elif data.startswith(b"\177ELF"):
return self._replace_symbol_elf(data, symbol_name, replacement)
elif data[:4] in (b'\xFE\xED\xFA\xCE', b'\xCE\xFA\xED\xFE',
b'\xFE\xED\xFA\xCF', b'\xCF\xFA\xED\xFE'):
off = self._find_symbol_macho(macho_data, symbol_name)
if off is not None:
data[off:off+len(replacement)] = replacement
return True
return False
elif data[:4] in (b'\xCA\xFE\xBA\xBE', b'\xBE\xBA\xFE\xCA'):
# Universal binary with 32-bit offsets.
return self._replace_symbol_fat(data, b'_' + symbol_name, replacement, False)
elif data[:4] in (b'\xCA\xFE\xBA\xBF', b'\xBF\xBA\xFE\xCA'):
# Universal binary with 64-bit offsets.
return self._replace_symbol_fat(data, b'_' + symbol_name, replacement, True)
# We don't know what kind of file this is.
return False
def _replace_symbol_elf(self, elf_data, symbol_name, replacement):
""" The Linux/FreeBSD implementation of _replace_symbol. """
replaced = False
# Make sure we read in the correct endianness and integer size
endian = "<>"[ord(elf_data[5:6]) - 1]
is_64bit = ord(elf_data[4:5]) - 1 # 0 = 32-bits, 1 = 64-bits
header_struct = endian + ("HHIIIIIHHHHHH", "HHIQQQIHHHHHH")[is_64bit]
section_struct = endian + ("4xI4xIIII8xI", "4xI8xQQQI12xQ")[is_64bit]
symbol_struct = endian + ("IIIBBH", "IBBHQQ")[is_64bit]
header_size = struct.calcsize(header_struct)
type, machine, version, entry, phoff, shoff, flags, ehsize, phentsize, phnum, shentsize, shnum, shstrndx \
= struct.unpack_from(header_struct, elf_data, 16)
section_offsets = []
symbol_tables = []
string_tables = {}
# Seek to the section header table and find the symbol tables.
ptr = shoff
for i in range(shnum):
type, addr, offset, size, link, entsize = struct.unpack_from(section_struct, elf_data[ptr:ptr+shentsize])
ptr += shentsize
section_offsets.append(offset - addr)
if type == 0x0B and link != 0: # SHT_DYNSYM, links to string table
symbol_tables.append((offset, size, link, entsize))
string_tables[link] = None
# Read the relevant string tables.
for idx in list(string_tables.keys()):
ptr = shoff + idx * shentsize
type, addr, offset, size, link, entsize = struct.unpack_from(section_struct, elf_data[ptr:ptr+shentsize])
if type == 3:
string_tables[idx] = elf_data[offset:offset+size]
# Loop through to find the offset of the "blobinfo" symbol.
for offset, size, link, entsize in symbol_tables:
entries = size // entsize
for i in range(entries):
ptr = offset + i * entsize
fields = struct.unpack_from(symbol_struct, elf_data[ptr:ptr+entsize])
if is_64bit:
name, info, other, shndx, value, size = fields
else:
name, value, size, info, other, shndx = fields
if not name:
continue
name = string_tables[link][name : string_tables[link].find(b'\0', name)]
if name == symbol_name:
if shndx == 0: # SHN_UNDEF
continue
elif shndx >= 0xff00 and shndx <= 0xffff:
assert False
else:
# Got it. Make the replacement.
off = section_offsets[shndx] + value
elf_data[off:off+len(replacement)] = replacement
replaced = True
return replaced
def _find_symbol_macho(self, macho_data, symbol_name):
""" Returns the offset of the given symbol in the binary file. """
if macho_data[:4] in (b'\xCE\xFA\xED\xFE', b'\xCF\xFA\xED\xFE'):
endian = '<'
else:
endian = '>'
cputype, cpusubtype, filetype, ncmds, sizeofcmds, flags = \
struct.unpack_from(endian + 'IIIIII', macho_data, 4)
is_64bit = (cputype & 0x1000000) != 0
segments = []
cmd_ptr = 28
nlist_struct = endian + 'IBBHI'
if is_64bit:
nlist_struct = endian + 'IBBHQ'
cmd_ptr += 4
nlist_size = struct.calcsize(nlist_struct)
for i in range(ncmds):
cmd, cmd_size = struct.unpack_from(endian + 'II', macho_data, cmd_ptr)
cmd_data = macho_data[cmd_ptr+8:cmd_ptr+cmd_size]
cmd_ptr += cmd_size
cmd &= ~0x80000000
if cmd == 0x01: # LC_SEGMENT
segname, vmaddr, vmsize, fileoff, filesize, maxprot, initprot, nsects, flags = \
struct.unpack_from(endian + '16sIIIIIIII', cmd_data)
segments.append((vmaddr, vmsize, fileoff))
elif cmd == 0x19: # LC_SEGMENT_64
segname, vmaddr, vmsize, fileoff, filesize, maxprot, initprot, nsects, flags = \
struct.unpack_from(endian + '16sQQQQIIII', cmd_data)
segments.append((vmaddr, vmsize, fileoff))
elif cmd == 0x2: # LC_SYMTAB
symoff, nsyms, stroff, strsize = \
struct.unpack_from(endian + 'IIII', cmd_data)
strings = macho_data[stroff:stroff+strsize]
for i in range(nsyms):
strx, type, sect, desc, value = struct.unpack_from(nlist_struct, macho_data, symoff)
symoff += nlist_size
name = strings[strx : strings.find(b'\0', strx)]
if name == symbol_name:
# Find out in which segment this is.
for vmaddr, vmsize, fileoff in segments:
# Is it defined in this segment?
rel = value - vmaddr
if rel >= 0 and rel < vmsize:
# Yes, so return the symbol offset.
return fileoff + rel
def _replace_symbol_fat(self, fat_data, symbol_name, replacement, is_64bit):
""" Implementation of _replace_symbol for universal binaries. """
num_fat, = struct.unpack_from('>I', fat_data, 4)
# After the header we get a table of executables in this fat file,
# each one with a corresponding offset into the file.
replaced = False
ptr = 8
for i in range(num_fat):
if is_64bit:
cputype, cpusubtype, offset, size, align = \
struct.unpack_from('>QQQQQ', fat_data, ptr)
ptr += 40
else:
cputype, cpusubtype, offset, size, align = \
struct.unpack_from('>IIIII', fat_data, ptr)
ptr += 20
macho_data = fat_data[offset:offset+size]
off = self._find_symbol_macho(macho_data, symbol_name)
if off is not None:
off += offset
fat_data[off:off+len(replacement)] = replacement
replaced = True
return replaced
def makeModuleDef(self, mangledName, code): def makeModuleDef(self, mangledName, code):
result = '' result = ''
result += 'static unsigned char %s[] = {' % (mangledName) result += 'static unsigned char %s[] = {' % (mangledName)

View File

@ -225,7 +225,17 @@ class build_apps(distutils.core.Command):
stub_path = os.path.join(os.path.dirname(dtool_path), '..', 'bin', stub_name) stub_path = os.path.join(os.path.dirname(dtool_path), '..', 'bin', stub_name)
stub_file = open(stub_path, 'rb') stub_file = open(stub_path, 'rb')
freezer.generateRuntimeFromStub(os.path.join(builddir, appname), stub_file) freezer.generateRuntimeFromStub(os.path.join(builddir, appname), stub_file, {
'prc_data': None,
'default_prc_dir': None,
'prc_dir_envvars': None,
'prc_path_envvars': None,
'prc_patterns': None,
'prc_encrypted_patterns': None,
'prc_encryption_key': None,
'prc_executable_patterns': None,
'prc_executable_args_envvar': None,
})
stub_file.close() stub_file.close()
freezer_extras.update(freezer.extras) freezer_extras.update(freezer.extras)
@ -492,16 +502,22 @@ class build_apps(distutils.core.Command):
# Elf magic. Used on (among others) Linux and FreeBSD. # Elf magic. Used on (among others) Linux and FreeBSD.
deps = self._read_dependencies_elf(fp, os.path.dirname(source_path), search_path) deps = self._read_dependencies_elf(fp, os.path.dirname(source_path), search_path)
elif magic in (b'\xFE\xED\xFA\xCE', b'\xCE\xFA\xED\xFE', elif magic in (b'\xCE\xFA\xED\xFE', b'\xCF\xFA\xED\xFE'):
b'\xFE\xED\xFA\xCF', b'\xCF\xFA\xED\xFE'):
# A Mach-O file, as used on macOS. # A Mach-O file, as used on macOS.
deps = self._read_dependencies_macho(fp) deps = self._read_dependencies_macho(fp, '<')
elif magic in (b'\xCA\xFE\xBA\xBE', b'\xBE\xBA\xFE\bCA'): elif magic in (b'\xFE\xED\xFA\xCE', b'\xFE\xED\xFA\xCF'):
deps = self._read_dependencies_macho(fp, '>')
elif magic in (b'\xCA\xFE\xBA\xBE', b'\xBE\xBA\xFE\xCA'):
# A fat file, containing multiple Mach-O binaries. In the future, # A fat file, containing multiple Mach-O binaries. In the future,
# we may want to extract the one containing the architecture we # we may want to extract the one containing the architecture we
# are building for. # are building for.
deps = self._read_dependencies_fat(fp) deps = self._read_dependencies_fat(fp, False)
elif magic in (b'\xCA\xFE\xBA\xBF', b'\xBF\xBA\xFE\xCA'):
# A 64-bit fat file.
deps = self._read_dependencies_fat(fp, True)
# If we discovered any dependencies, recursively add those. # If we discovered any dependencies, recursively add those.
if deps: if deps:
@ -573,23 +589,23 @@ class build_apps(distutils.core.Command):
search_path += rpath search_path += rpath
return needed return needed
def _read_dependencies_macho(self, fp): def _read_dependencies_macho(self, fp, endian):
""" Having read the first 4 bytes of the Mach-O file, fetches the """ Having read the first 4 bytes of the Mach-O file, fetches the
dependent libraries and returns those as a list. """ dependent libraries and returns those as a list. """
cputype, cpusubtype, filetype, ncmds, sizeofcmds, flags = \ cputype, cpusubtype, filetype, ncmds, sizeofcmds, flags = \
struct.unpack('<IIIIII', fp.read(24)) struct.unpack(endian + 'IIIIII', fp.read(24))
is_64 = (cputype & 0x1000000) != 0 is_64bit = (cputype & 0x1000000) != 0
if is_64: if is_64bit:
fp.read(4) fp.read(4)
# After the header, we get a series of linker commands. We just # After the header, we get a series of linker commands. We just
# iterate through them and gather up the LC_LOAD_DYLIB commands. # iterate through them and gather up the LC_LOAD_DYLIB commands.
load_dylibs = [] load_dylibs = []
for i in range(ncmds): for i in range(ncmds):
cmd, cmdsize = struct.unpack('<II', fp.read(8)) cmd, cmd_size = struct.unpack(endian + 'II', fp.read(8))
cmd_data = fp.read(cmdsize - 8) cmd_data = fp.read(cmd_size - 8)
cmd &= ~0x80000000 cmd &= ~0x80000000
if cmd == 0x0c: # LC_LOAD_DYLIB if cmd == 0x0c: # LC_LOAD_DYLIB
@ -600,20 +616,41 @@ class build_apps(distutils.core.Command):
return load_dylibs return load_dylibs
def _read_dependencies_fat(self, fp): def _read_dependencies_fat(self, fp, is_64bit):
num_fat = struct.unpack('>I', fp.read(4))[0] num_fat, = struct.unpack('>I', fp.read(4))
if num_fat == 0:
return []
# After the header we get a table of executables in this fat file, # After the header we get a table of executables in this fat file,
# each one with a corresponding offset into the file. # each one with a corresponding offset into the file.
# We are just interested in the first one for now. offsets = []
cputype, cpusubtype, offset, size, align = \ for i in range(num_fat):
struct.unpack('>IIIII', fp.read(20)) if is_64bit:
cputype, cpusubtype, offset, size, align = \
struct.unpack('>QQQQQ', fp.read(40))
else:
cputype, cpusubtype, offset, size, align = \
struct.unpack('>IIIII', fp.read(20))
offsets.append(offset)
# Add 4, since it expects we've already read the magic. # Go through each of the binaries in the fat file.
fp.seek(offset + 4) deps = []
return self._read_dependencies_macho(fp) for offset in offsets:
# Add 4, since it expects we've already read the magic.
fp.seek(offset)
magic = fp.read(4)
if magic in (b'\xCE\xFA\xED\xFE', b'\xCF\xFA\xED\xFE'):
endian = '<'
elif magic in (b'\xFE\xED\xFA\xCE', b'\xFE\xED\xFA\xCF'):
endian = '>'
else:
# Not a Mach-O file we can read.
continue
for dep in self._read_dependencies_macho(fp, endian):
if dep not in deps:
deps.append(dep)
return deps
class bdist_apps(distutils.core.Command): class bdist_apps(distutils.core.Command):

View File

@ -19,6 +19,7 @@ if sys.version_info >= (3, 0):
# Define some internally used structures. # Define some internally used structures.
RVASize = namedtuple('RVASize', ('addr', 'size')) RVASize = namedtuple('RVASize', ('addr', 'size'))
impdirtab = namedtuple('impdirtab', ('lookup', 'timdat', 'forward', 'name', 'impaddr')) impdirtab = namedtuple('impdirtab', ('lookup', 'timdat', 'forward', 'name', 'impaddr'))
expdirtab = namedtuple('expdirtab', ('flags', 'timdat', 'majver', 'minver', 'name', 'ordinal_base', 'nentries', 'nnames', 'entries', 'names', 'ordinals'))
def _unpack_zstring(mem, offs=0): def _unpack_zstring(mem, offs=0):
@ -571,10 +572,11 @@ class PEFile(object):
# Read the sections into some kind of virtual memory. # Read the sections into some kind of virtual memory.
self.vmem = bytearray(self.sections[-1].vaddr + self.sections[-1].size) self.vmem = bytearray(self.sections[-1].vaddr + self.sections[-1].size)
memview = memoryview(self.vmem)
for section in self.sections: for section in self.sections:
fp.seek(section.offset) fp.seek(section.offset)
fp.readinto(memoryview(self.vmem)[section.vaddr:section.vaddr+section.size]) fp.readinto(memview[section.vaddr:section.vaddr+section.size])
# Read the import table. # Read the import table.
start = self.imp_rva.addr start = self.imp_rva.addr
@ -596,6 +598,43 @@ class PEFile(object):
if self.res_rva.addr and self.res_rva.size: if self.res_rva.addr and self.res_rva.size:
self.resources.unpack_from(self.vmem, self.res_rva.addr) self.resources.unpack_from(self.vmem, self.res_rva.addr)
def get_export_address(self, symbol_name):
""" Finds the virtual address for a named export symbol. """
start = self.exp_rva.addr
expdir = expdirtab(*unpack('<IIHHIIIIIII', self.vmem[start:start+40]))
if expdir.nnames == 0 or expdir.ordinals == 0 or expdir.names == 0:
return None
nptr = expdir.names
optr = expdir.ordinals
for i in range(expdir.nnames):
name_rva, = unpack('<I', self.vmem[nptr:nptr+4])
ordinal, = unpack('<H', self.vmem[optr:optr+2])
if name_rva != 0:
name = _unpack_zstring(self.vmem, name_rva)
if name == symbol_name:
assert ordinal >= 0 and ordinal < expdir.nentries
start = expdir.entries + 8 * ordinal
addr, = unpack('<I', self.vmem[start:start+4])
return addr
nptr += 4
optr += 2
def get_address_offset(self, addr):
""" Turns an address into a offset relative to the file beginning. """
section = self.get_address_section(addr)
if section is not None:
return (addr - section.vaddr) + section.offset
def get_address_section(self, addr):
""" Returns the section that this virtual address belongs to. """
for section in self.sections:
if addr >= section.vaddr and addr < section.vaddr + section.size:
return section
def add_icon(self, icon, ordinal=2): def add_icon(self, icon, ordinal=2):
""" Adds an icon resource from the given Icon object. Requires """ Adds an icon resource from the given Icon object. Requires
calling add_resource_section() afterwards. """ calling add_resource_section() afterwards. """

View File

@ -37,6 +37,10 @@
#include <algorithm> #include <algorithm>
#include <ctype.h> #include <ctype.h>
#ifndef _MSC_VER
#include <dlfcn.h>
#endif
ConfigPageManager *ConfigPageManager::_global_ptr = NULL; ConfigPageManager *ConfigPageManager::_global_ptr = NULL;
/** /**
@ -90,11 +94,44 @@ reload_implicit_pages() {
} }
_implicit_pages.clear(); _implicit_pages.clear();
// If we are running inside a deployed application, see if it exposes
// information about how the PRC data should be initialized.
struct BlobInfo {
uint64_t blob_offset;
uint64_t blob_size;
uint16_t version;
uint16_t num_pointers;
uint16_t codepage;
uint16_t flags;
uint64_t reserved;
const void *module_table;
const char *prc_data;
const char *default_prc_dir;
const char *prc_dir_envvars;
const char *prc_path_envvars;
const char *prc_patterns;
const char *prc_encrypted_patterns;
const char *prc_encryption_key;
const char *prc_executable_patterns;
const char *prc_executable_args_envvar;
};
#ifdef _MSC_VER
const BlobInfo *blobinfo = (const BlobInfo *)GetProcAddress(GetModuleHandle(NULL), "blobinfo");
#else
const BlobInfo *blobinfo = (const BlobInfo *)dlsym(RTLD_SELF, "blobinfo");
#endif
if (blobinfo != nullptr && (blobinfo->version == 0 || blobinfo->num_pointers < 10)) {
blobinfo = nullptr;
}
// PRC_PATTERNS lists one or more filename templates separated by spaces. // PRC_PATTERNS lists one or more filename templates separated by spaces.
// Pull them out and store them in _prc_patterns. // Pull them out and store them in _prc_patterns.
_prc_patterns.clear(); _prc_patterns.clear();
string prc_patterns = PRC_PATTERNS; string prc_patterns = PRC_PATTERNS;
if (blobinfo != nullptr && blobinfo->prc_patterns != nullptr) {
prc_patterns = blobinfo->prc_patterns;
}
if (!prc_patterns.empty()) { if (!prc_patterns.empty()) {
vector_string pat_list; vector_string pat_list;
ConfigDeclaration::extract_words(prc_patterns, pat_list); ConfigDeclaration::extract_words(prc_patterns, pat_list);
@ -114,6 +151,9 @@ reload_implicit_pages() {
_prc_encrypted_patterns.clear(); _prc_encrypted_patterns.clear();
string prc_encrypted_patterns = PRC_ENCRYPTED_PATTERNS; string prc_encrypted_patterns = PRC_ENCRYPTED_PATTERNS;
if (blobinfo != nullptr && blobinfo->prc_encrypted_patterns != nullptr) {
prc_encrypted_patterns = blobinfo->prc_encrypted_patterns;
}
if (!prc_encrypted_patterns.empty()) { if (!prc_encrypted_patterns.empty()) {
vector_string pat_list; vector_string pat_list;
ConfigDeclaration::extract_words(prc_encrypted_patterns, pat_list); ConfigDeclaration::extract_words(prc_encrypted_patterns, pat_list);
@ -131,6 +171,9 @@ reload_implicit_pages() {
_prc_executable_patterns.clear(); _prc_executable_patterns.clear();
string prc_executable_patterns = PRC_EXECUTABLE_PATTERNS; string prc_executable_patterns = PRC_EXECUTABLE_PATTERNS;
if (blobinfo != nullptr && blobinfo->prc_executable_patterns != nullptr) {
prc_executable_patterns = blobinfo->prc_executable_patterns;
}
if (!prc_executable_patterns.empty()) { if (!prc_executable_patterns.empty()) {
vector_string pat_list; vector_string pat_list;
ConfigDeclaration::extract_words(prc_executable_patterns, pat_list); ConfigDeclaration::extract_words(prc_executable_patterns, pat_list);
@ -151,6 +194,9 @@ reload_implicit_pages() {
// spaces. Pull them out, and each of those contains the name of a single // spaces. Pull them out, and each of those contains the name of a single
// directory to search. Add it to the search path. // directory to search. Add it to the search path.
string prc_dir_envvars = PRC_DIR_ENVVARS; string prc_dir_envvars = PRC_DIR_ENVVARS;
if (blobinfo != nullptr && blobinfo->prc_dir_envvars != nullptr) {
prc_dir_envvars = blobinfo->prc_dir_envvars;
}
if (!prc_dir_envvars.empty()) { if (!prc_dir_envvars.empty()) {
vector_string prc_dir_envvar_list; vector_string prc_dir_envvar_list;
ConfigDeclaration::extract_words(prc_dir_envvars, prc_dir_envvar_list); ConfigDeclaration::extract_words(prc_dir_envvars, prc_dir_envvar_list);
@ -170,6 +216,9 @@ reload_implicit_pages() {
// spaces. Pull them out, and then each one of those contains a list of // spaces. Pull them out, and then each one of those contains a list of
// directories to search. Add each of those to the search path. // directories to search. Add each of those to the search path.
string prc_path_envvars = PRC_PATH_ENVVARS; string prc_path_envvars = PRC_PATH_ENVVARS;
if (blobinfo != nullptr && blobinfo->prc_path_envvars != nullptr) {
prc_path_envvars = blobinfo->prc_path_envvars;
}
if (!prc_path_envvars.empty()) { if (!prc_path_envvars.empty()) {
vector_string prc_path_envvar_list; vector_string prc_path_envvar_list;
ConfigDeclaration::extract_words(prc_path_envvars, prc_path_envvar_list); ConfigDeclaration::extract_words(prc_path_envvars, prc_path_envvar_list);
@ -201,7 +250,7 @@ reload_implicit_pages() {
* DEFAULT_PATHSEP. * DEFAULT_PATHSEP.
*/ */
string prc_path2_envvars = PRC_PATH2_ENVVARS; string prc_path2_envvars = PRC_PATH2_ENVVARS;
if (!prc_path2_envvars.empty()) { if (!prc_path2_envvars.empty() && blobinfo == nullptr) {
vector_string prc_path_envvar_list; vector_string prc_path_envvar_list;
ConfigDeclaration::extract_words(prc_path2_envvars, prc_path_envvar_list); ConfigDeclaration::extract_words(prc_path2_envvars, prc_path_envvar_list);
for (size_t i = 0; i < prc_path_envvar_list.size(); ++i) { for (size_t i = 0; i < prc_path_envvar_list.size(); ++i) {
@ -225,6 +274,9 @@ reload_implicit_pages() {
// If nothing's on the search path (PRC_DIR and PRC_PATH were not // If nothing's on the search path (PRC_DIR and PRC_PATH were not
// defined), then use the DEFAULT_PRC_DIR. // defined), then use the DEFAULT_PRC_DIR.
string default_prc_dir = DEFAULT_PRC_DIR; string default_prc_dir = DEFAULT_PRC_DIR;
if (blobinfo != nullptr && blobinfo->default_prc_dir != nullptr) {
default_prc_dir = blobinfo->default_prc_dir;
}
if (!default_prc_dir.empty()) { if (!default_prc_dir.empty()) {
// It's already from-os-specific by ppremake. // It's already from-os-specific by ppremake.
Filename prc_dir_filename = default_prc_dir; Filename prc_dir_filename = default_prc_dir;
@ -297,12 +349,24 @@ reload_implicit_pages() {
} }
} }
int i = 1;
// If prc_data is predefined, we load it as an implicit page.
if (blobinfo != nullptr && blobinfo->prc_data != nullptr) {
ConfigPage *page = new ConfigPage("builtin", true, i);
++i;
_implicit_pages.push_back(page);
_pages_sorted = false;
istringstream in(blobinfo->prc_data);
page->read_prc(in);
}
// Now we have a list of filenames in order from most important to least // Now we have a list of filenames in order from most important to least
// important. Walk through the list in reverse order to load their // important. Walk through the list in reverse order to load their
// contents, because we want the first file in the list (the most important) // contents, because we want the first file in the list (the most important)
// to be on the top of the stack. // to be on the top of the stack.
ConfigFiles::reverse_iterator ci; ConfigFiles::reverse_iterator ci;
int i = 1;
for (ci = config_files.rbegin(); ci != config_files.rend(); ++ci) { for (ci = config_files.rbegin(); ci != config_files.rend(); ++ci) {
const ConfigFile &file = (*ci); const ConfigFile &file = (*ci);
Filename filename = file._filename; Filename filename = file._filename;
@ -313,6 +377,9 @@ reload_implicit_pages() {
string command = filename.to_os_specific(); string command = filename.to_os_specific();
string envvar = PRC_EXECUTABLE_ARGS_ENVVAR; string envvar = PRC_EXECUTABLE_ARGS_ENVVAR;
if (blobinfo != nullptr && blobinfo->prc_executable_args_envvar != nullptr) {
envvar = blobinfo->prc_executable_args_envvar;
}
if (!envvar.empty()) { if (!envvar.empty()) {
string args = ExecutionEnvironment::get_environment_variable(envvar); string args = ExecutionEnvironment::get_environment_variable(envvar);
if (!args.empty()) { if (!args.empty()) {
@ -343,7 +410,11 @@ reload_implicit_pages() {
_implicit_pages.push_back(page); _implicit_pages.push_back(page);
_pages_sorted = false; _pages_sorted = false;
page->read_encrypted_prc(in, PRC_ENCRYPTION_KEY); if (blobinfo != nullptr && blobinfo->prc_encryption_key != nullptr) {
page->read_encrypted_prc(in, blobinfo->prc_encryption_key);
} else {
page->read_encrypted_prc(in, PRC_ENCRYPTION_KEY);
}
} }
} else if ((file._file_flags & FF_read) != 0) { } else if ((file._file_flags & FF_read) != 0) {

View File

@ -6583,7 +6583,7 @@ if PkgSkip("PYTHON") == 0:
if GetTarget() == 'windows': if GetTarget() == 'windows':
TargetAdd('frozen_dllmain.obj', opts=OPTS, input='frozen_dllmain.c') TargetAdd('frozen_dllmain.obj', opts=OPTS, input='frozen_dllmain.c')
if GetTarget() == 'linux': if GetTarget() == 'linux' or GetTarget() == 'freebsd':
# Setup rpath so libs can be found in the same directory as the deployed game # Setup rpath so libs can be found in the same directory as the deployed game
LibName('DEPLOYSTUB', "-Wl,-rpath,\$ORIGIN") LibName('DEPLOYSTUB', "-Wl,-rpath,\$ORIGIN")
LibName('DEPLOYSTUB', "-Wl,-z,origin") LibName('DEPLOYSTUB', "-Wl,-z,origin")

View File

@ -22,6 +22,29 @@
#endif #endif
#endif #endif
/* Define an exposed symbol where we store the offset to the module data. */
#ifdef _MSC_VER
__declspec(dllexport)
#else
__attribute__((__visibility__("default"), used))
#endif
volatile struct {
uint64_t blob_offset;
uint64_t blob_size;
uint16_t version;
uint16_t num_pointers;
uint16_t codepage;
uint16_t flags;
uint64_t reserved;
// Leave room for future expansion. We only read pointer 0, but there are
// other pointers that are being read by configPageManager.cxx.
void *pointers[24];
// The reason we initialize it to -1 is because otherwise, smart linkers may
// end up putting it in the .bss section for zero-initialized data.
} blobinfo = {(uint64_t)-1};
#ifdef MS_WINDOWS #ifdef MS_WINDOWS
#define WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN
#include <windows.h> #include <windows.h>
@ -221,15 +244,12 @@ error:
return sts; return sts;
} }
/**
#if defined(_WIN32) && PY_MAJOR_VERSION >= 3 * Maps the binary blob at the given memory address to memory, and returns the
int wmain(int argc, wchar_t *argv[]) { * pointer to the beginning of it.
#else */
int main(int argc, char *argv[]) { static void *map_blob(off_t offset, size_t size) {
#endif void *blob;
struct _frozen *blob, *moddef;
uint64_t begin, end, size;
int retval;
FILE *runtime; FILE *runtime;
#ifdef _WIN32 #ifdef _WIN32
@ -243,7 +263,7 @@ int main(int argc, char *argv[]) {
mib[3] = getpid(); mib[3] = getpid();
if (sysctl(mib, 4, (void *)buffer, &bufsize, NULL, 0) == -1) { if (sysctl(mib, 4, (void *)buffer, &bufsize, NULL, 0) == -1) {
perror("sysctl"); perror("sysctl");
return 1; return NULL;
} }
runtime = fopen(buffer, "rb"); runtime = fopen(buffer, "rb");
#else #else
@ -251,46 +271,108 @@ int main(int argc, char *argv[]) {
runtime = fopen(argv[0], "rb"); runtime = fopen(argv[0], "rb");
#endif #endif
// Get offsets // Get offsets. In version 0, we read it from the end of the file.
fseek(runtime, -8, SEEK_END); if (blobinfo.version == 0) {
end = ftell(runtime); uint64_t end, begin;
fread(&begin, 8, 1, runtime); fseek(runtime, -8, SEEK_END);
size = end - begin; end = ftell(runtime);
fread(&begin, 8, 1, runtime);
offset = (off_t)begin;
size = (size_t)(end - begin);
}
// mmap the section indicated by the offset (or malloc/fread on windows) // mmap the section indicated by the offset (or malloc/fread on windows)
#ifdef _WIN32 #ifdef _WIN32
blob = (struct _frozen *)malloc(size); blob = (void *)malloc(size);
assert(blob != NULL); assert(blob != NULL);
fseek(runtime, (long)begin, SEEK_SET); fseek(runtime, (long)offset, SEEK_SET);
fread(blob, size, 1, runtime); fread(blob, size, 1, runtime);
#else #else
blob = (struct _frozen *)mmap(0, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fileno(runtime), begin); blob = (void *)mmap(0, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fileno(runtime), offset);
assert(blob != NULL); assert(blob != MAP_FAILED);
#endif #endif
fclose(runtime); fclose(runtime);
return blob;
}
// Offset the pointers in the table using the base mmap address. /**
moddef = blob; * The inverse of map_blob.
while (moddef->name) { */
moddef->name = (char *)((uintptr_t)moddef->name + (uintptr_t)blob); static void unmap_blob(void *blob) {
if (moddef->code != 0) { if (blob) {
moddef->code = (unsigned char *)((uintptr_t)moddef->code + (uintptr_t)blob); #ifdef _WIN32
free(blob);
#else
munmap(blob, blobinfo.blob_size);
#endif
}
}
/**
* Main entry point to deploy-stub.
*/
#if defined(_WIN32) && PY_MAJOR_VERSION >= 3
int wmain(int argc, wchar_t *argv[]) {
#else
int main(int argc, char *argv[]) {
#endif
int retval;
struct _frozen *moddef;
void *blob = NULL;
/*
printf("blob_offset: %d\n", (int)blobinfo.blob_offset);
printf("blob_size: %d\n", (int)blobinfo.blob_size);
printf("version: %d\n", (int)blobinfo.version);
printf("num_pointers: %d\n", (int)blobinfo.num_pointers);
printf("codepage: %d\n", (int)blobinfo.codepage);
printf("flags: %d\n", (int)blobinfo.flags);
printf("reserved: %d\n", (int)blobinfo.reserved);
*/
// If we have a blob offset, we have to map the blob to memory.
if (blobinfo.version == 0 || blobinfo.blob_offset != 0) {
void *blob = map_blob((off_t)blobinfo.blob_offset, (size_t)blobinfo.blob_size);
assert(blob != NULL);
// Offset the pointers in the header using the base mmap address.
if (blobinfo.version > 0 && blobinfo.num_pointers > 0) {
uint32_t i;
for (i = 0; i < blobinfo.num_pointers; ++i) {
if (i == 0 || blobinfo.pointers[i] != 0) {
blobinfo.pointers[i] = (void *)((uintptr_t)blobinfo.pointers[i] + (uintptr_t)blob);
}
}
} else {
blobinfo.pointers[0] = blob;
}
// Offset the pointers in the module table using the base mmap address.
moddef = blobinfo.pointers[0];
while (moddef->name) {
moddef->name = (char *)((uintptr_t)moddef->name + (uintptr_t)blob);
if (moddef->code != 0) {
moddef->code = (unsigned char *)((uintptr_t)moddef->code + (uintptr_t)blob);
}
//printf("MOD: %s %p %d\n", moddef->name, (void*)moddef->code, moddef->size);
moddef++;
} }
//printf("MOD: %s %p %d\n", moddef->name, (void*)moddef->code, moddef->size);
moddef++;
} }
#ifdef _WIN32
if (codepage != 0) {
SetConsoleCP(codepage);
SetConsoleOutputCP(codepage);
}
#endif
// Run frozen application // Run frozen application
PyImport_FrozenModules = blob; PyImport_FrozenModules = blobinfo.pointers[0];
retval = Py_FrozenMain(argc, argv); retval = Py_FrozenMain(argc, argv);
// Free resources unmap_blob(blob);
#ifdef _WIN32
free(blob);
#else
munmap(blob, size);
#endif
return retval; return retval;
} }