From d755de849cae10e62941f370a80004d855012834 Mon Sep 17 00:00:00 2001 From: rdb Date: Fri, 24 Nov 2017 21:48:18 +0100 Subject: [PATCH] deploy-ng: new extensible blob format with PRC configurability --- direct/src/showutil/FreezeTool.py | 361 +++++++++++++++++++++--- direct/src/showutil/dist.py | 81 ++++-- direct/src/showutil/pefile.py | 41 ++- dtool/src/prc/configPageManager.cxx | 77 ++++- makepanda/makepanda.py | 2 +- pandatool/src/deploy-stub/deploy-stub.c | 150 +++++++--- 6 files changed, 610 insertions(+), 102 deletions(-) diff --git a/direct/src/showutil/FreezeTool.py b/direct/src/showutil/FreezeTool.py index a8a967f93a..78645acae9 100644 --- a/direct/src/showutil/FreezeTool.py +++ b/direct/src/showutil/FreezeTool.py @@ -8,10 +8,12 @@ import marshal import imp import platform import struct -from io import StringIO, TextIOWrapper +from io import StringIO, BytesIO, TextIOWrapper import distutils.sysconfig as sysconf import zipfile +from . import pefile + # Temporary (?) try..except to protect against unbuilt p3extend_frozen. try: import p3extend_frozen @@ -1589,7 +1591,7 @@ class Freezer: 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. if not self.__writingModule('__main__'): message = "Can't generate an executable without a __main__ module." @@ -1602,15 +1604,18 @@ class Freezer: target = basename modext = '.so' - address_offset = 0 - - # First gather up the strings for all the module names. - blob = b"" + # First gather up the strings and code for all the module names, and + # put those in a string pool. + pool = b"" strings = set() for moduleName, mdef in self.getModuleDefs(): 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. strings = sorted(strings, key=lambda str:-len(str)) string_offsets = {} @@ -1618,13 +1623,15 @@ class Freezer: for string in strings: # First check whether it's already in there; it could be part of # a longer string. - offset = blob.find(string + b'\0') + offset = pool.find(string + b'\0') if offset < 0: - offset = len(blob) - blob += string + b'\0' + offset = len(pool) + pool += string + b'\0' 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 = [] for moduleName, mdef in self.getModuleDefs(): @@ -1635,9 +1642,9 @@ class Freezer: continue # For whatever it's worth, align the code blocks. - if len(blob) & 3 != 0: - pad = (4 - (len(blob) & 3)) - blob += b'\0' * pad + if len(pool) & 3 != 0: + pad = (4 - (len(pool) & 3)) + pool += b'\0' * pad assert not mdef.exclude # Allow importing this module. @@ -1649,8 +1656,8 @@ class Freezer: if getattr(module, "__path__", None): # Indicate package by negative size size = -size - moduleList.append((moduleName, len(blob), size)) - blob += code + moduleList.append((moduleName, len(pool), size)) + pool += code continue # 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 = compile(code, moduleName, 'exec') code = marshal.dumps(code) - moduleList.append((moduleName, len(blob), len(code))) - blob += code + moduleList.append((moduleName, len(pool), len(code))) + pool += code - # Now compose the final blob. - if '64' in self.platform: - layout = '= 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): result = '' result += 'static unsigned char %s[] = {' % (mangledName) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 6b908a0716..4b37f88ab4 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -225,7 +225,17 @@ class build_apps(distutils.core.Command): stub_path = os.path.join(os.path.dirname(dtool_path), '..', 'bin', stub_name) 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() freezer_extras.update(freezer.extras) @@ -492,16 +502,22 @@ class build_apps(distutils.core.Command): # Elf magic. Used on (among others) Linux and FreeBSD. 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', - b'\xFE\xED\xFA\xCF', b'\xCF\xFA\xED\xFE'): + elif magic in (b'\xCE\xFA\xED\xFE', b'\xCF\xFA\xED\xFE'): # 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, # we may want to extract the one containing the architecture we # 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 deps: @@ -573,23 +589,23 @@ class build_apps(distutils.core.Command): search_path += rpath 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 dependent libraries and returns those as a list. """ cputype, cpusubtype, filetype, ncmds, sizeofcmds, flags = \ - struct.unpack('I', fp.read(4))[0] - if num_fat == 0: - return [] + def _read_dependencies_fat(self, fp, is_64bit): + num_fat, = struct.unpack('>I', fp.read(4)) # After the header we get a table of executables in this fat file, # each one with a corresponding offset into the file. - # We are just interested in the first one for now. - cputype, cpusubtype, offset, size, align = \ - struct.unpack('>IIIII', fp.read(20)) + offsets = [] + for i in range(num_fat): + 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. - fp.seek(offset + 4) - return self._read_dependencies_macho(fp) + # Go through each of the binaries in the fat file. + deps = [] + 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): diff --git a/direct/src/showutil/pefile.py b/direct/src/showutil/pefile.py index 20af71c4b7..03714b82fb 100755 --- a/direct/src/showutil/pefile.py +++ b/direct/src/showutil/pefile.py @@ -19,6 +19,7 @@ if sys.version_info >= (3, 0): # Define some internally used structures. RVASize = namedtuple('RVASize', ('addr', 'size')) 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): @@ -571,10 +572,11 @@ class PEFile(object): # Read the sections into some kind of virtual memory. self.vmem = bytearray(self.sections[-1].vaddr + self.sections[-1].size) + memview = memoryview(self.vmem) for section in self.sections: 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. start = self.imp_rva.addr @@ -596,6 +598,43 @@ class PEFile(object): if self.res_rva.addr and self.res_rva.size: 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('= 0 and ordinal < expdir.nentries + start = expdir.entries + 8 * ordinal + addr, = unpack('= section.vaddr and addr < section.vaddr + section.size: + return section + def add_icon(self, icon, ordinal=2): """ Adds an icon resource from the given Icon object. Requires calling add_resource_section() afterwards. """ diff --git a/dtool/src/prc/configPageManager.cxx b/dtool/src/prc/configPageManager.cxx index f7868aa394..7416e0fcbe 100644 --- a/dtool/src/prc/configPageManager.cxx +++ b/dtool/src/prc/configPageManager.cxx @@ -37,6 +37,10 @@ #include #include +#ifndef _MSC_VER +#include +#endif + ConfigPageManager *ConfigPageManager::_global_ptr = NULL; /** @@ -90,11 +94,44 @@ reload_implicit_pages() { } _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. // Pull them out and store them in _prc_patterns. _prc_patterns.clear(); string prc_patterns = PRC_PATTERNS; + if (blobinfo != nullptr && blobinfo->prc_patterns != nullptr) { + prc_patterns = blobinfo->prc_patterns; + } if (!prc_patterns.empty()) { vector_string pat_list; ConfigDeclaration::extract_words(prc_patterns, pat_list); @@ -114,6 +151,9 @@ reload_implicit_pages() { _prc_encrypted_patterns.clear(); 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()) { vector_string pat_list; ConfigDeclaration::extract_words(prc_encrypted_patterns, pat_list); @@ -131,6 +171,9 @@ reload_implicit_pages() { _prc_executable_patterns.clear(); 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()) { vector_string 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 // directory to search. Add it to the search path. 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()) { vector_string 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 // directories to search. Add each of those to the search path. 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()) { vector_string prc_path_envvar_list; ConfigDeclaration::extract_words(prc_path_envvars, prc_path_envvar_list); @@ -201,7 +250,7 @@ reload_implicit_pages() { * DEFAULT_PATHSEP. */ 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; ConfigDeclaration::extract_words(prc_path2_envvars, prc_path_envvar_list); 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 // defined), then use the 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()) { // It's already from-os-specific by ppremake. 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 // important. Walk through the list in reverse order to load their // contents, because we want the first file in the list (the most important) // to be on the top of the stack. ConfigFiles::reverse_iterator ci; - int i = 1; for (ci = config_files.rbegin(); ci != config_files.rend(); ++ci) { const ConfigFile &file = (*ci); Filename filename = file._filename; @@ -313,6 +377,9 @@ reload_implicit_pages() { string command = filename.to_os_specific(); string envvar = PRC_EXECUTABLE_ARGS_ENVVAR; + if (blobinfo != nullptr && blobinfo->prc_executable_args_envvar != nullptr) { + envvar = blobinfo->prc_executable_args_envvar; + } if (!envvar.empty()) { string args = ExecutionEnvironment::get_environment_variable(envvar); if (!args.empty()) { @@ -343,7 +410,11 @@ reload_implicit_pages() { _implicit_pages.push_back(page); _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) { diff --git a/makepanda/makepanda.py b/makepanda/makepanda.py index d9e4755412..383e9b652c 100755 --- a/makepanda/makepanda.py +++ b/makepanda/makepanda.py @@ -6583,7 +6583,7 @@ if PkgSkip("PYTHON") == 0: if GetTarget() == 'windows': 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 LibName('DEPLOYSTUB', "-Wl,-rpath,\$ORIGIN") LibName('DEPLOYSTUB', "-Wl,-z,origin") diff --git a/pandatool/src/deploy-stub/deploy-stub.c b/pandatool/src/deploy-stub/deploy-stub.c index 51d7da88a9..99a0e361ba 100644 --- a/pandatool/src/deploy-stub/deploy-stub.c +++ b/pandatool/src/deploy-stub/deploy-stub.c @@ -22,6 +22,29 @@ #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 #define WIN32_LEAN_AND_MEAN #include @@ -221,15 +244,12 @@ error: return sts; } - -#if defined(_WIN32) && PY_MAJOR_VERSION >= 3 -int wmain(int argc, wchar_t *argv[]) { -#else -int main(int argc, char *argv[]) { -#endif - struct _frozen *blob, *moddef; - uint64_t begin, end, size; - int retval; +/** + * Maps the binary blob at the given memory address to memory, and returns the + * pointer to the beginning of it. + */ +static void *map_blob(off_t offset, size_t size) { + void *blob; FILE *runtime; #ifdef _WIN32 @@ -243,7 +263,7 @@ int main(int argc, char *argv[]) { mib[3] = getpid(); if (sysctl(mib, 4, (void *)buffer, &bufsize, NULL, 0) == -1) { perror("sysctl"); - return 1; + return NULL; } runtime = fopen(buffer, "rb"); #else @@ -251,46 +271,108 @@ int main(int argc, char *argv[]) { runtime = fopen(argv[0], "rb"); #endif - // Get offsets - fseek(runtime, -8, SEEK_END); - end = ftell(runtime); - fread(&begin, 8, 1, runtime); - size = end - begin; + // Get offsets. In version 0, we read it from the end of the file. + if (blobinfo.version == 0) { + uint64_t end, begin; + fseek(runtime, -8, SEEK_END); + 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) #ifdef _WIN32 - blob = (struct _frozen *)malloc(size); + blob = (void *)malloc(size); assert(blob != NULL); - fseek(runtime, (long)begin, SEEK_SET); + fseek(runtime, (long)offset, SEEK_SET); fread(blob, size, 1, runtime); #else - blob = (struct _frozen *)mmap(0, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fileno(runtime), begin); - assert(blob != NULL); + blob = (void *)mmap(0, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fileno(runtime), offset); + assert(blob != MAP_FAILED); #endif fclose(runtime); + return blob; +} - // Offset the pointers in the table using the base mmap address. - moddef = blob; - 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); +/** + * The inverse of map_blob. + */ +static void unmap_blob(void *blob) { + if (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 - PyImport_FrozenModules = blob; + PyImport_FrozenModules = blobinfo.pointers[0]; retval = Py_FrozenMain(argc, argv); - // Free resources -#ifdef _WIN32 - free(blob); -#else - munmap(blob, size); -#endif + unmap_blob(blob); return retval; }