1132 lines
40 KiB
Python
Executable File
1132 lines
40 KiB
Python
Executable File
#!/usr/bin/env python
|
|
# Copyright (c) 2012 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.
|
|
|
|
"""Creates windows and posix stub files for a given set of signatures.
|
|
|
|
For libraries that need to be loaded outside of the standard executable startup
|
|
path mechanism, stub files need to be generated for the wanted functions. In
|
|
windows, this is done via "def" files and the delay load mechanism. On a posix
|
|
system, a set of stub functions need to be generated that dispatch to functions
|
|
found via dlsym.
|
|
|
|
This script takes a set of files, where each file is a list of C-style
|
|
signatures (one signature per line). The output is either a windows def file,
|
|
or a header + implementation file of stubs suitable for use in a posix system.
|
|
|
|
This script also handles varidiac functions, e.g.
|
|
void printf(const char* s, ...);
|
|
|
|
TODO(hclam): Fix the situation for varidiac functions.
|
|
Stub for the above function will be generated and inside the stub function it
|
|
is translated to:
|
|
void printf(const char* s, ...) {
|
|
printf_ptr(s, (void*)arg1);
|
|
}
|
|
|
|
Only one argument from the varidiac arguments is used and it will be used as
|
|
type void*.
|
|
"""
|
|
|
|
__author__ = 'ajwong@chromium.org (Albert J. Wong)'
|
|
|
|
import optparse
|
|
import os
|
|
import re
|
|
import string
|
|
import subprocess
|
|
import sys
|
|
|
|
|
|
class Error(Exception):
|
|
pass
|
|
|
|
|
|
class BadSignatureError(Error):
|
|
pass
|
|
|
|
|
|
class SubprocessError(Error):
|
|
def __init__(self, message, error_code):
|
|
Error.__init__(self)
|
|
self.message = message
|
|
self.error_code = error_code
|
|
|
|
def __str__(self):
|
|
return 'Failed with code %s: %s' % (self.message, repr(self.error_code))
|
|
|
|
|
|
# Regular expression used to parse function signatures in the input files.
|
|
# The regex is built around identifying the "identifier" for the function name.
|
|
# We consider the identifier to be the string that follows these constraints:
|
|
#
|
|
# 1) Starts with [_a-ZA-Z] (C++ spec 2.10).
|
|
# 2) Continues with [_a-ZA-Z0-9] (C++ spec 2.10).
|
|
# 3) Preceeds an opening parenthesis by 0 or more whitespace chars.
|
|
#
|
|
# From that, all preceeding characters are considered the return value.
|
|
# Trailing characters should have a substring matching the form (.*). That
|
|
# is considered the arguments.
|
|
SIGNATURE_REGEX = re.compile('(?P<return_type>.+?)'
|
|
'(?P<name>[_a-zA-Z][_a-zA-Z0-9]+)\s*'
|
|
'\((?P<params>.*?)\)')
|
|
|
|
# Used for generating C++ identifiers.
|
|
INVALID_C_IDENT_CHARS = re.compile('[^_a-zA-Z0-9]')
|
|
|
|
# Constants defning the supported file types options.
|
|
FILE_TYPE_WIN_X86 = 'windows_lib'
|
|
FILE_TYPE_WIN_X64 = 'windows_lib_x64'
|
|
FILE_TYPE_POSIX_STUB = 'posix_stubs'
|
|
FILE_TYPE_WIN_DEF = 'windows_def'
|
|
|
|
# Template for generating a stub function definition. Includes a forward
|
|
# declaration marking the symbol as weak. This template takes the following
|
|
# named parameters.
|
|
# return_type: The return type.
|
|
# name: The name of the function.
|
|
# params: The parameters to the function.
|
|
# return_prefix: 'return ' if this function is not void. '' otherwise.
|
|
# arg_list: The arguments used to call the stub function.
|
|
STUB_FUNCTION_DEFINITION = (
|
|
"""extern %(return_type)s %(name)s(%(params)s) __attribute__((weak));
|
|
%(return_type)s %(name)s(%(params)s) {
|
|
%(return_prefix)s%(name)s_ptr(%(arg_list)s);
|
|
}""")
|
|
|
|
# Template for generating a variadic stub function definition with return
|
|
# value.
|
|
# Includes a forward declaration marking the symbol as weak.
|
|
# This template takes the following named parameters.
|
|
# return_type: The return type.
|
|
# name: The name of the function.
|
|
# params: The parameters to the function.
|
|
# arg_list: The arguments used to call the stub function without the
|
|
# variadic argument.
|
|
# last_named_arg: Name of the last named argument before the variadic
|
|
# argument.
|
|
VARIADIC_STUB_FUNCTION_DEFINITION = (
|
|
"""extern %(return_type)s %(name)s(%(params)s) __attribute__((weak));
|
|
%(return_type)s %(name)s(%(params)s) {
|
|
va_list args___;
|
|
va_start(args___, %(last_named_arg)s);
|
|
%(return_type)s ret___ = %(name)s_ptr(%(arg_list)s, va_arg(args___, void*));
|
|
va_end(args___);
|
|
return ret___;
|
|
}""")
|
|
|
|
# Template for generating a variadic stub function definition without
|
|
# return value.
|
|
# Includes a forward declaration marking the symbol as weak.
|
|
# This template takes the following named parameters.
|
|
# name: The name of the function.
|
|
# params: The parameters to the function.
|
|
# arg_list: The arguments used to call the stub function without the
|
|
# variadic argument.
|
|
# last_named_arg: Name of the last named argument before the variadic
|
|
# argument.
|
|
VOID_VARIADIC_STUB_FUNCTION_DEFINITION = (
|
|
"""extern void %(name)s(%(params)s) __attribute__((weak));
|
|
void %(name)s(%(params)s) {
|
|
va_list args___;
|
|
va_start(args___, %(last_named_arg)s);
|
|
%(name)s_ptr(%(arg_list)s, va_arg(args___, void*));
|
|
va_end(args___);
|
|
}""")
|
|
|
|
# Template for the preamble for the stub header file with the header guards,
|
|
# standard set of includes, and namespace opener. This template takes the
|
|
# following named parameters:
|
|
# guard_name: The macro to use as the header guard.
|
|
# namespace: The namespace for the stub functions.
|
|
STUB_HEADER_PREAMBLE = """// This is generated file. Do not modify directly.
|
|
|
|
#ifndef %(guard_name)s
|
|
#define %(guard_name)s
|
|
|
|
#include <map>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include "base/logging.h"
|
|
|
|
namespace %(namespace)s {
|
|
"""
|
|
|
|
# Template for the end of the stub header. This closes the namespace and the
|
|
# header guards. This template takes the following named parameters:
|
|
# guard_name: The macro to use as the header guard.
|
|
# namespace: The namespace for the stub functions.
|
|
STUB_HEADER_CLOSER = """} // namespace %(namespace)s
|
|
|
|
#endif // %(guard_name)s
|
|
"""
|
|
|
|
# The standard includes needed for the stub implementation file. Takes one
|
|
# string substition with the path to the associated stub header file.
|
|
IMPLEMENTATION_PREAMBLE = """// This is generated file. Do not modify directly.
|
|
|
|
#include "%s"
|
|
|
|
#include <stdlib.h> // For NULL.
|
|
#include <dlfcn.h> // For dysym, dlopen.
|
|
|
|
#include <map>
|
|
#include <vector>
|
|
"""
|
|
|
|
# The start and end templates for the enum definitions used by the Umbrella
|
|
# initializer.
|
|
UMBRELLA_ENUM_START = """// Enum and typedef for umbrella initializer.
|
|
enum StubModules {
|
|
"""
|
|
UMBRELLA_ENUM_END = """ kNumStubModules
|
|
};
|
|
|
|
"""
|
|
|
|
# Start and end of the extern "C" section for the implementation contents.
|
|
IMPLEMENTATION_CONTENTS_C_START = """extern "C" {
|
|
|
|
"""
|
|
IMPLEMENTATION_CONTENTS_C_END = """
|
|
} // extern "C"
|
|
|
|
|
|
"""
|
|
|
|
# Templates for the start and end of a namespace. Takes one parameter, the
|
|
# namespace name.
|
|
NAMESPACE_START = """namespace %s {
|
|
|
|
"""
|
|
NAMESPACE_END = """} // namespace %s
|
|
|
|
"""
|
|
|
|
# Comment to include before the section declaring all the function pointers
|
|
# used by the stub functions.
|
|
FUNCTION_POINTER_SECTION_COMMENT = (
|
|
"""// Static pointers that will hold the location of the real function
|
|
// implementations after the module has been loaded.
|
|
""")
|
|
|
|
# Template for the module initialization check function. This template
|
|
# takes two parameteres: the function name, and the conditional used to
|
|
# verify the module's initialization.
|
|
MODULE_INITIALIZATION_CHECK_FUNCTION = (
|
|
"""// Returns true if all stubs have been properly initialized.
|
|
bool %s() {
|
|
if (%s) {
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
""")
|
|
|
|
# Template for the line that initialize the stub pointer. This template takes
|
|
# the following named parameters:
|
|
# name: The name of the function.
|
|
# return_type: The return type.
|
|
# params: The parameters to the function.
|
|
STUB_POINTER_INITIALIZER = """ %(name)s_ptr =
|
|
reinterpret_cast<%(return_type)s (*)(%(parameters)s)>(
|
|
dlsym(module, "%(name)s"));
|
|
VLOG_IF(1, !%(name)s_ptr) << "Couldn't load %(name)s, dlerror() says:\\n"
|
|
<< dlerror();
|
|
"""
|
|
|
|
# Template for module initializer function start and end. This template takes
|
|
# one parameter which is the initializer function name.
|
|
MODULE_INITIALIZE_START = """// Initializes the module stubs.
|
|
void %s(void* module) {
|
|
"""
|
|
MODULE_INITIALIZE_END = """}
|
|
|
|
"""
|
|
|
|
# Template for module uninitializer function start and end. This template
|
|
# takes one parameter which is the initializer function name.
|
|
MODULE_UNINITIALIZE_START = (
|
|
"""// Uninitialize the module stubs. Reset pointers to NULL.
|
|
void %s() {
|
|
""")
|
|
MODULE_UNINITIALIZE_END = """}
|
|
|
|
"""
|
|
|
|
|
|
# Open namespace and add typedef for internal data structures used by the
|
|
# umbrella initializer.
|
|
UMBRELLA_INITIALIZER_START = """namespace %s {
|
|
typedef std::map<StubModules, void*> StubHandleMap;
|
|
"""
|
|
|
|
# Function close DSOs on error and clean up dangling references.
|
|
UMBRELLA_INITIALIZER_CLEANUP_FUNCTION = (
|
|
"""static void CloseLibraries(StubHandleMap* stub_handles) {
|
|
for (StubHandleMap::const_iterator it = stub_handles->begin();
|
|
it != stub_handles->end();
|
|
++it) {
|
|
dlclose(it->second);
|
|
}
|
|
|
|
stub_handles->clear();
|
|
}
|
|
""")
|
|
|
|
# Function to initialize each DSO for the given paths.
|
|
UMBRELLA_INITIALIZER_INITIALIZE_FUNCTION_START = (
|
|
"""bool InitializeStubs(const StubPathMap& path_map) {
|
|
StubHandleMap opened_libraries;
|
|
for (int i = 0; i < kNumStubModules; ++i) {
|
|
StubModules cur_module = static_cast<StubModules>(i);
|
|
// If a module is missing, we fail.
|
|
StubPathMap::const_iterator it = path_map.find(cur_module);
|
|
if (it == path_map.end()) {
|
|
CloseLibraries(&opened_libraries);
|
|
return false;
|
|
}
|
|
|
|
// Otherwise, attempt to dlopen the library.
|
|
const std::vector<std::string>& paths = it->second;
|
|
bool module_opened = false;
|
|
for (std::vector<std::string>::const_iterator dso_path = paths.begin();
|
|
!module_opened && dso_path != paths.end();
|
|
++dso_path) {
|
|
void* handle = dlopen(dso_path->c_str(), RTLD_LAZY);
|
|
if (handle != NULL) {
|
|
module_opened = true;
|
|
opened_libraries[cur_module] = handle;
|
|
} else {
|
|
VLOG(1) << "dlopen(" << dso_path->c_str() << ") failed, "
|
|
<< "dlerror() says:\\n" << dlerror();
|
|
}
|
|
}
|
|
|
|
if (!module_opened) {
|
|
CloseLibraries(&opened_libraries);
|
|
return false;
|
|
}
|
|
}
|
|
""")
|
|
|
|
# Template to generate code to check if each module initializer correctly
|
|
# completed, and cleanup on failures. This template takes the following
|
|
# named parameters.
|
|
# conditional: The conditional expression for successful initialization.
|
|
# uninitializers: The statements needed to uninitialize the modules.
|
|
UMBRELLA_INITIALIZER_CHECK_AND_CLEANUP = (
|
|
""" // Check that each module is initialized correctly.
|
|
// Close all previously opened libraries on failure.
|
|
if (%(conditional)s) {
|
|
%(uninitializers)s;
|
|
CloseLibraries(&opened_libraries);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
""")
|
|
|
|
# Template for Initialize, Unininitialize, and IsInitialized functions for each
|
|
# module. This template takes the following named parameters:
|
|
# initialize: Name of the Initialize function.
|
|
# uninitialize: Name of the Uninitialize function.
|
|
# is_initialized: Name of the IsInitialized function.
|
|
MODULE_FUNCTION_PROTOTYPES = """bool %(is_initialized)s();
|
|
void %(initialize)s(void* module);
|
|
void %(uninitialize)s();
|
|
|
|
"""
|
|
|
|
# Template for umbrella initializer declaration and associated datatypes.
|
|
UMBRELLA_INITIALIZER_PROTOTYPE = (
|
|
"""typedef std::map<StubModules, std::vector<std::string> > StubPathMap;
|
|
|
|
// Umbrella initializer for all the modules in this stub file.
|
|
bool InitializeStubs(const StubPathMap& path_map);
|
|
""")
|
|
|
|
|
|
def ExtractModuleName(infile_path):
|
|
"""Infers the module name from the input file path.
|
|
|
|
The input filename is supposed to be in the form "ModuleName.sigs".
|
|
This function splits the filename from the extention on that basename of
|
|
the path and returns that as the module name.
|
|
|
|
Args:
|
|
infile_path: String holding the path to the input file.
|
|
|
|
Returns:
|
|
The module name as a string.
|
|
"""
|
|
basename = os.path.basename(infile_path)
|
|
|
|
# This loop continously removes suffixes of the filename separated by a "."
|
|
# character.
|
|
while 1:
|
|
new_basename = os.path.splitext(basename)[0]
|
|
if basename == new_basename:
|
|
break
|
|
else:
|
|
basename = new_basename
|
|
return basename
|
|
|
|
|
|
def ParseSignatures(infile):
|
|
"""Parses function signatures in the input file.
|
|
|
|
This function parses a file of signatures into a list of dictionaries that
|
|
represent the function signatures in the input file. Each dictionary has
|
|
the following keys:
|
|
return_type: A string with the return type.
|
|
name: A string with the name of the function.
|
|
params: A list of each function parameter declaration (type + name)
|
|
|
|
The format of the input file is one C-style function signature per line, no
|
|
trailing semicolon. Empty lines are allowed. An empty line is a line that
|
|
consists purely of whitespace. Lines that begin with a # are considered
|
|
comment lines and are ignored.
|
|
|
|
We assume that "int foo(void)" is the same as "int foo()", which is not
|
|
true in C where "int foo()" is equivalent to "int foo(...)". Our generated
|
|
code is C++, and we do not handle varargs, so this is a case that can be
|
|
ignored for now.
|
|
|
|
Args:
|
|
infile: File object holding a text file of function signatures.
|
|
|
|
Returns:
|
|
A list of dictionaries, where each dictionary represents one function
|
|
signature.
|
|
|
|
Raises:
|
|
BadSignatureError: A line could not be parsed as a signature.
|
|
"""
|
|
signatures = []
|
|
for line in infile:
|
|
line = line.strip()
|
|
if line and line[0] != '#':
|
|
m = SIGNATURE_REGEX.match(line)
|
|
if m is None:
|
|
raise BadSignatureError('Unparsable line: %s' % line)
|
|
signatures.append(
|
|
{'return_type': m.group('return_type').strip(),
|
|
'name': m.group('name').strip(),
|
|
'params': [arg.strip() for arg in m.group('params').split(',')]})
|
|
return signatures
|
|
|
|
|
|
def WriteWindowsDefFile(module_name, signatures, outfile):
|
|
"""Writes a windows def file to the given output file object.
|
|
|
|
The def file format is basically a list of function names. Generation is
|
|
simple. After outputting the LIBRARY and EXPORTS lines, print out each
|
|
function name, one to a line, preceeded by 2 spaces.
|
|
|
|
Args:
|
|
module_name: The name of the module we are writing a stub for.
|
|
signatures: The list of signature hashes, as produced by ParseSignatures,
|
|
to create stubs for.
|
|
outfile: File handle to populate with definitions.
|
|
"""
|
|
outfile.write('LIBRARY %s\n' % module_name)
|
|
outfile.write('EXPORTS\n')
|
|
|
|
for sig in signatures:
|
|
outfile.write(' %s\n' % sig['name'])
|
|
|
|
|
|
def QuietRun(args, filter=None, write_to=sys.stdout):
|
|
"""Invoke |args| as command via subprocess.Popen, filtering lines starting
|
|
with |filter|."""
|
|
popen = subprocess.Popen(args, stdout=subprocess.PIPE)
|
|
out, _ = popen.communicate()
|
|
for line in out.splitlines():
|
|
if not filter or not line.startswith(filter):
|
|
write_to.write(line + '\n')
|
|
return popen.returncode
|
|
|
|
|
|
def CreateWindowsLib(module_name, signatures, intermediate_dir, outdir_path,
|
|
machine):
|
|
"""Creates a windows library file.
|
|
|
|
Calling this function will create a lib file in the outdir_path that exports
|
|
the signatures passed into the object. A temporary def file will be created
|
|
in the intermediate_dir.
|
|
|
|
Args:
|
|
module_name: The name of the module we are writing a stub for.
|
|
signatures: The list of signature hashes, as produced by ParseSignatures,
|
|
to create stubs for.
|
|
intermediate_dir: The directory where the generated .def files should go.
|
|
outdir_path: The directory where generated .lib files should go.
|
|
machine: String holding the machine type, 'X86' or 'X64'.
|
|
|
|
Raises:
|
|
SubprocessError: If invoking the windows "lib" tool fails, this is raised
|
|
with the error code.
|
|
"""
|
|
def_file_path = os.path.join(intermediate_dir,
|
|
module_name + '.def')
|
|
lib_file_path = os.path.join(outdir_path,
|
|
module_name + '.lib')
|
|
outfile = open(def_file_path, 'w')
|
|
try:
|
|
WriteWindowsDefFile(module_name, signatures, outfile)
|
|
finally:
|
|
outfile.close()
|
|
|
|
# Invoke the "lib" program on Windows to create stub .lib files for the
|
|
# generated definitions. These .lib files can then be used during
|
|
# delayloading of the dynamic libraries.
|
|
ret = QuietRun(['lib', '/nologo',
|
|
'/machine:' + machine,
|
|
'/def:' + def_file_path,
|
|
'/out:' + lib_file_path],
|
|
filter=' Creating library')
|
|
if ret != 0:
|
|
raise SubprocessError(
|
|
'Failed creating %s for %s' % (lib_file_path, def_file_path),
|
|
ret)
|
|
|
|
|
|
class PosixStubWriter(object):
|
|
"""Creates a file of stub functions for a library that is opened via dlopen.
|
|
|
|
Windows provides a function in their compiler known as delay loading, which
|
|
effectively generates a set of stub functions for a dynamic library that
|
|
delays loading of the dynamic library/resolution of the symbols until one of
|
|
the needed functions are accessed.
|
|
|
|
In posix, RTLD_LAZY does something similar with DSOs. This is the default
|
|
link mode for DSOs. However, even though the symbol is not resolved until
|
|
first usage, the DSO must be present at load time of the main binary.
|
|
|
|
To simulate the windows delay load procedure, we need to create a set of
|
|
stub functions that allow for correct linkage of the main binary, but
|
|
dispatch to the dynamically resolved symbol when the module is initialized.
|
|
|
|
This class takes a list of function signatures, and generates a set of stub
|
|
functions plus initialization code for them.
|
|
"""
|
|
|
|
def __init__(self, module_name, signatures):
|
|
"""Initializes PosixStubWriter for this set of signatures and module_name.
|
|
|
|
Args:
|
|
module_name: The name of the module we are writing a stub for.
|
|
signatures: The list of signature hashes, as produced by ParseSignatures,
|
|
to create stubs for.
|
|
"""
|
|
self.signatures = signatures
|
|
self.module_name = module_name
|
|
|
|
@classmethod
|
|
def CStyleIdentifier(cls, identifier):
|
|
"""Generates a C style identifier.
|
|
|
|
The module_name has all invalid identifier characters removed (anything
|
|
that's not [_a-zA-Z0-9]) and is run through string.capwords to try
|
|
and approximate camel case.
|
|
|
|
Args:
|
|
identifier: The string with the module name to turn to C-style.
|
|
|
|
Returns:
|
|
A string that can be used as part of a C identifier.
|
|
"""
|
|
return string.capwords(re.sub(INVALID_C_IDENT_CHARS, '', identifier))
|
|
|
|
@classmethod
|
|
def EnumName(cls, module_name):
|
|
"""Gets the enum name for the module.
|
|
|
|
Takes the module name and creates a suitable enum name. The module_name
|
|
is munged to be a valid C identifier then prefixed with the string
|
|
"kModule" to generate a Google style enum name.
|
|
|
|
Args:
|
|
module_name: The name of the module to generate an enum name for.
|
|
|
|
Returns:
|
|
A string with the name of the enum value representing this module.
|
|
"""
|
|
return 'kModule%s' % PosixStubWriter.CStyleIdentifier(module_name)
|
|
|
|
@classmethod
|
|
def IsInitializedName(cls, module_name):
|
|
"""Gets the name of function that checks initialization of this module.
|
|
|
|
The name is in the format IsModuleInitialized. Where "Module" is replaced
|
|
with the module name, munged to be a valid C identifier.
|
|
|
|
Args:
|
|
module_name: The name of the module to generate the function name for.
|
|
|
|
Returns:
|
|
A string with the name of the initialization check function.
|
|
"""
|
|
return 'Is%sInitialized' % PosixStubWriter.CStyleIdentifier(module_name)
|
|
|
|
@classmethod
|
|
def InitializeModuleName(cls, module_name):
|
|
"""Gets the name of the function that initializes this module.
|
|
|
|
The name is in the format InitializeModule. Where "Module" is replaced
|
|
with the module name, munged to be a valid C identifier.
|
|
|
|
Args:
|
|
module_name: The name of the module to generate the function name for.
|
|
|
|
Returns:
|
|
A string with the name of the initialization function.
|
|
"""
|
|
return 'Initialize%s' % PosixStubWriter.CStyleIdentifier(module_name)
|
|
|
|
@classmethod
|
|
def UninitializeModuleName(cls, module_name):
|
|
"""Gets the name of the function that uninitializes this module.
|
|
|
|
The name is in the format UninitializeModule. Where "Module" is replaced
|
|
with the module name, munged to be a valid C identifier.
|
|
|
|
Args:
|
|
module_name: The name of the module to generate the function name for.
|
|
|
|
Returns:
|
|
A string with the name of the uninitialization function.
|
|
"""
|
|
return 'Uninitialize%s' % PosixStubWriter.CStyleIdentifier(module_name)
|
|
|
|
@classmethod
|
|
def StubFunctionPointer(cls, signature):
|
|
"""Generates a function pointer declaration for the given signature.
|
|
|
|
Args:
|
|
signature: A signature hash, as produced by ParseSignatures,
|
|
representating the function signature.
|
|
|
|
Returns:
|
|
A string with the declaration of the function pointer for the signature.
|
|
"""
|
|
return 'static %s (*%s_ptr)(%s) = NULL;' % (signature['return_type'],
|
|
signature['name'],
|
|
', '.join(signature['params']))
|
|
|
|
@classmethod
|
|
def StubFunction(cls, signature):
|
|
"""Generates a stub function definition for the given signature.
|
|
|
|
The function definitions are created with __attribute__((weak)) so that
|
|
they may be overridden by a real static link or mock versions to be used
|
|
when testing.
|
|
|
|
Args:
|
|
signature: A signature hash, as produced by ParseSignatures,
|
|
representating the function signature.
|
|
|
|
Returns:
|
|
A string with the stub function definition.
|
|
"""
|
|
return_prefix = ''
|
|
if signature['return_type'] != 'void':
|
|
return_prefix = 'return '
|
|
|
|
# Generate the argument list.
|
|
arguments = [re.split('[\*& ]', arg)[-1].strip() for arg in
|
|
signature['params']]
|
|
arg_list = ', '.join(arguments)
|
|
if arg_list == 'void':
|
|
arg_list = ''
|
|
|
|
if arg_list != '' and len(arguments) > 1 and arguments[-1] == '...':
|
|
# If the last argment is ... then this is a variadic function.
|
|
if return_prefix != '':
|
|
return VARIADIC_STUB_FUNCTION_DEFINITION % {
|
|
'return_type': signature['return_type'],
|
|
'name': signature['name'],
|
|
'params': ', '.join(signature['params']),
|
|
'arg_list': ', '.join(arguments[0:-1]),
|
|
'last_named_arg': arguments[-2]}
|
|
else:
|
|
return VOID_VARIADIC_STUB_FUNCTION_DEFINITION % {
|
|
'name': signature['name'],
|
|
'params': ', '.join(signature['params']),
|
|
'arg_list': ', '.join(arguments[0:-1]),
|
|
'last_named_arg': arguments[-2]}
|
|
else:
|
|
# This is a regular function.
|
|
return STUB_FUNCTION_DEFINITION % {
|
|
'return_type': signature['return_type'],
|
|
'name': signature['name'],
|
|
'params': ', '.join(signature['params']),
|
|
'return_prefix': return_prefix,
|
|
'arg_list': arg_list}
|
|
|
|
@classmethod
|
|
def WriteImplementationPreamble(cls, header_path, outfile):
|
|
"""Write the necessary includes for the implementation file.
|
|
|
|
Args:
|
|
header_path: The path to the header file.
|
|
outfile: The file handle to populate.
|
|
"""
|
|
outfile.write(IMPLEMENTATION_PREAMBLE % header_path)
|
|
|
|
@classmethod
|
|
def WriteUmbrellaInitializer(cls, module_names, namespace, outfile):
|
|
"""Writes a single function that will open + initialize each module.
|
|
|
|
This intializer will take in an stl map of that lists the correct
|
|
dlopen target for each module. The map type is
|
|
std::map<enum StubModules, vector<std::string>> which matches one module
|
|
to a list of paths to try in dlopen.
|
|
|
|
This function is an all-or-nothing function. If any module fails to load,
|
|
all other modules are dlclosed, and the function returns. Though it is
|
|
not enforced, this function should only be called once.
|
|
|
|
Args:
|
|
module_names: A list with the names of the modules in this stub file.
|
|
namespace: The namespace these functions should be in.
|
|
outfile: The file handle to populate with pointer definitions.
|
|
"""
|
|
outfile.write(UMBRELLA_INITIALIZER_START % namespace)
|
|
outfile.write(UMBRELLA_INITIALIZER_CLEANUP_FUNCTION)
|
|
|
|
# Create the initializaiton function that calls all module initializers,
|
|
# checks if they succeeded, and backs out module loads on an error.
|
|
outfile.write(UMBRELLA_INITIALIZER_INITIALIZE_FUNCTION_START)
|
|
outfile.write(
|
|
'\n // Initialize each module if we have not already failed.\n')
|
|
for module in module_names:
|
|
outfile.write(' %s(opened_libraries[%s]);\n' %
|
|
(PosixStubWriter.InitializeModuleName(module),
|
|
PosixStubWriter.EnumName(module)))
|
|
outfile.write('\n')
|
|
|
|
# Output code to check the initialization status, clean up on error.
|
|
initializer_checks = ['!%s()' % PosixStubWriter.IsInitializedName(name)
|
|
for name in module_names]
|
|
uninitializers = ['%s()' % PosixStubWriter.UninitializeModuleName(name)
|
|
for name in module_names]
|
|
outfile.write(UMBRELLA_INITIALIZER_CHECK_AND_CLEANUP % {
|
|
'conditional': ' ||\n '.join(initializer_checks),
|
|
'uninitializers': ';\n '.join(uninitializers)})
|
|
outfile.write('\n} // namespace %s\n' % namespace)
|
|
|
|
@classmethod
|
|
def WriteHeaderContents(cls, module_names, namespace, header_guard, outfile):
|
|
"""Writes a header file for the stub file generated for module_names.
|
|
|
|
The header file exposes the following:
|
|
1) An enum, StubModules, listing with an entry for each enum.
|
|
2) A typedef for a StubPathMap allowing for specification of paths to
|
|
search for each module.
|
|
3) The IsInitialized/Initialize/Uninitialize functions for each module.
|
|
4) An umbrella initialize function for all modules.
|
|
|
|
Args:
|
|
module_names: A list with the names of each module in this stub file.
|
|
namespace: The namespace these functions should be in.
|
|
header_guard: The macro to use as our header guard.
|
|
outfile: The output handle to populate.
|
|
"""
|
|
outfile.write(STUB_HEADER_PREAMBLE %
|
|
{'guard_name': header_guard, 'namespace': namespace})
|
|
|
|
# Generate the Initializer protoypes for each module.
|
|
outfile.write('// Individual module initializer functions.\n')
|
|
for name in module_names:
|
|
outfile.write(MODULE_FUNCTION_PROTOTYPES % {
|
|
'is_initialized': PosixStubWriter.IsInitializedName(name),
|
|
'initialize': PosixStubWriter.InitializeModuleName(name),
|
|
'uninitialize': PosixStubWriter.UninitializeModuleName(name)})
|
|
|
|
# Generate the enum for umbrella initializer.
|
|
outfile.write(UMBRELLA_ENUM_START)
|
|
outfile.write(' %s = 0,\n' % PosixStubWriter.EnumName(module_names[0]))
|
|
for name in module_names[1:]:
|
|
outfile.write(' %s,\n' % PosixStubWriter.EnumName(name))
|
|
outfile.write(UMBRELLA_ENUM_END)
|
|
|
|
outfile.write(UMBRELLA_INITIALIZER_PROTOTYPE)
|
|
outfile.write(STUB_HEADER_CLOSER % {
|
|
'namespace': namespace, 'guard_name':
|
|
header_guard})
|
|
|
|
def WriteImplementationContents(self, namespace, outfile):
|
|
"""Given a file handle, write out the stub definitions for this module.
|
|
|
|
Args:
|
|
namespace: The namespace these functions should be in.
|
|
outfile: The file handle to populate.
|
|
"""
|
|
outfile.write(IMPLEMENTATION_CONTENTS_C_START)
|
|
self.WriteFunctionPointers(outfile)
|
|
self.WriteStubFunctions(outfile)
|
|
outfile.write(IMPLEMENTATION_CONTENTS_C_END)
|
|
|
|
outfile.write(NAMESPACE_START % namespace)
|
|
self.WriteModuleInitializeFunctions(outfile)
|
|
outfile.write(NAMESPACE_END % namespace)
|
|
|
|
def WriteFunctionPointers(self, outfile):
|
|
"""Write the function pointer declarations needed by the stubs.
|
|
|
|
We need function pointers to hold the actual location of the function
|
|
implementation returned by dlsym. This function outputs a pointer
|
|
definition for each signature in the module.
|
|
|
|
Pointers will be named with the following pattern "FuntionName_ptr".
|
|
|
|
Args:
|
|
outfile: The file handle to populate with pointer definitions.
|
|
"""
|
|
outfile.write(FUNCTION_POINTER_SECTION_COMMENT)
|
|
|
|
for sig in self.signatures:
|
|
outfile.write('%s\n' % PosixStubWriter.StubFunctionPointer(sig))
|
|
outfile.write('\n')
|
|
|
|
def WriteStubFunctions(self, outfile):
|
|
"""Write the function stubs to handle dispatching to real implementations.
|
|
|
|
Functions that have a return type other than void will look as follows:
|
|
|
|
ReturnType FunctionName(A a) {
|
|
return FunctionName_ptr(a);
|
|
}
|
|
|
|
Functions with a return type of void will look as follows:
|
|
|
|
void FunctionName(A a) {
|
|
FunctionName_ptr(a);
|
|
}
|
|
|
|
Args:
|
|
outfile: The file handle to populate.
|
|
"""
|
|
outfile.write('// Stubs that dispatch to the real implementations.\n')
|
|
for sig in self.signatures:
|
|
outfile.write('%s\n' % PosixStubWriter.StubFunction(sig))
|
|
|
|
def WriteModuleInitializeFunctions(self, outfile):
|
|
"""Write functions to initialize/query initlialization of the module.
|
|
|
|
This creates 2 functions IsModuleInitialized and InitializeModule where
|
|
"Module" is replaced with the module name, first letter capitalized.
|
|
|
|
The InitializeModule function takes a handle that is retrieved from dlopen
|
|
and attempts to assign each function pointer above via dlsym.
|
|
|
|
The IsModuleInitialized returns true if none of the required functions
|
|
pointers are NULL.
|
|
|
|
Args:
|
|
outfile: The file handle to populate.
|
|
"""
|
|
ptr_names = ['%s_ptr' % sig['name'] for sig in self.signatures]
|
|
|
|
# Construct the conditional expression to check the initialization of
|
|
# all the function pointers above. It should generate a conjuntion
|
|
# with each pointer on its own line, indented by six spaces to match
|
|
# the indentation level of MODULE_INITIALIZATION_CHECK_FUNCTION.
|
|
initialization_conditional = ' &&\n '.join(ptr_names)
|
|
|
|
outfile.write(MODULE_INITIALIZATION_CHECK_FUNCTION % (
|
|
PosixStubWriter.IsInitializedName(self.module_name),
|
|
initialization_conditional))
|
|
|
|
# Create function that initializes the module.
|
|
outfile.write(MODULE_INITIALIZE_START %
|
|
PosixStubWriter.InitializeModuleName(self.module_name))
|
|
for sig in self.signatures:
|
|
outfile.write(STUB_POINTER_INITIALIZER % {
|
|
'name': sig['name'],
|
|
'return_type': sig['return_type'],
|
|
'parameters': ', '.join(sig['params'])})
|
|
outfile.write(MODULE_INITIALIZE_END)
|
|
|
|
# Create function that uninitializes the module (sets all pointers to
|
|
# NULL).
|
|
outfile.write(MODULE_UNINITIALIZE_START %
|
|
PosixStubWriter.UninitializeModuleName(self.module_name))
|
|
for sig in self.signatures:
|
|
outfile.write(' %s_ptr = NULL;\n' % sig['name'])
|
|
outfile.write(MODULE_UNINITIALIZE_END)
|
|
|
|
|
|
def CreateOptionParser():
|
|
"""Creates an OptionParser for the configuration options of script.
|
|
|
|
Returns:
|
|
A OptionParser object.
|
|
"""
|
|
parser = optparse.OptionParser(usage='usage: %prog [options] input')
|
|
parser.add_option('-o',
|
|
'--output',
|
|
dest='out_dir',
|
|
default=None,
|
|
help='Output location.')
|
|
parser.add_option('-i',
|
|
'--intermediate_dir',
|
|
dest='intermediate_dir',
|
|
default=None,
|
|
help=('Location of intermediate files. Ignored for %s type'
|
|
% FILE_TYPE_WIN_DEF))
|
|
parser.add_option('-t',
|
|
'--type',
|
|
dest='type',
|
|
default=None,
|
|
help=('Type of file. Valid types are "%s" or "%s" or "%s" '
|
|
'or "%s"' %
|
|
(FILE_TYPE_POSIX_STUB, FILE_TYPE_WIN_X86,
|
|
FILE_TYPE_WIN_X64, FILE_TYPE_WIN_DEF)))
|
|
parser.add_option('-s',
|
|
'--stubfile_name',
|
|
dest='stubfile_name',
|
|
default=None,
|
|
help=('Name of posix_stubs output file. Only valid with '
|
|
'%s type.' % FILE_TYPE_POSIX_STUB))
|
|
parser.add_option('-p',
|
|
'--path_from_source',
|
|
dest='path_from_source',
|
|
default=None,
|
|
help=('The relative path from the project root that the '
|
|
'generated file should consider itself part of (eg. '
|
|
'third_party/ffmpeg). This is used to generate the '
|
|
'header guard and namespace for our initializer '
|
|
'functions and does NOT affect the physical output '
|
|
'location of the file like -o does. Ignored for '
|
|
'%s and %s types.' %
|
|
(FILE_TYPE_WIN_X86, FILE_TYPE_WIN_X64)))
|
|
parser.add_option('-e',
|
|
'--extra_stub_header',
|
|
dest='extra_stub_header',
|
|
default=None,
|
|
help=('File to insert after the system includes in the '
|
|
'generated stub implemenation file. Ignored for '
|
|
'%s and %s types.' %
|
|
(FILE_TYPE_WIN_X86, FILE_TYPE_WIN_X64)))
|
|
parser.add_option('-m',
|
|
'--module_name',
|
|
dest='module_name',
|
|
default=None,
|
|
help=('Name of output DLL or LIB for DEF creation using '
|
|
'%s type.' % FILE_TYPE_WIN_DEF))
|
|
|
|
return parser
|
|
|
|
|
|
def ParseOptions():
|
|
"""Parses the options and terminates program if they are not sane.
|
|
|
|
Returns:
|
|
The pair (optparse.OptionValues, [string]), that is the output of
|
|
a successful call to parser.parse_args().
|
|
"""
|
|
parser = CreateOptionParser()
|
|
options, args = parser.parse_args()
|
|
|
|
if not args:
|
|
parser.error('No inputs specified')
|
|
|
|
if options.out_dir is None:
|
|
parser.error('Output location not specified')
|
|
|
|
if (options.type not in
|
|
[FILE_TYPE_WIN_X86, FILE_TYPE_WIN_X64, FILE_TYPE_POSIX_STUB,
|
|
FILE_TYPE_WIN_DEF]):
|
|
parser.error('Invalid output file type: %s' % options.type)
|
|
|
|
if options.type == FILE_TYPE_POSIX_STUB:
|
|
if options.stubfile_name is None:
|
|
parser.error('Output file name needed for %s' % FILE_TYPE_POSIX_STUB)
|
|
if options.path_from_source is None:
|
|
parser.error('Path from source needed for %s' % FILE_TYPE_POSIX_STUB)
|
|
|
|
if options.type == FILE_TYPE_WIN_DEF:
|
|
if options.module_name is None:
|
|
parser.error('Module name needed for %s' % FILE_TYPE_WIN_DEF)
|
|
|
|
return options, args
|
|
|
|
|
|
def EnsureDirExists(dir):
|
|
"""Creates a directory. Does not use the more obvious 'if not exists: create'
|
|
to avoid race with other invocations of the same code, which will error out
|
|
on makedirs if another invocation has succeeded in creating the directory
|
|
since the existence check."""
|
|
try:
|
|
os.makedirs(dir)
|
|
except:
|
|
if not os.path.isdir(dir):
|
|
raise
|
|
|
|
|
|
def CreateOutputDirectories(options):
|
|
"""Creates the intermediate and final output directories.
|
|
|
|
Given the parsed options, create the intermediate and final output
|
|
directories if they do not exist. Returns the paths to both directories
|
|
as a pair.
|
|
|
|
Args:
|
|
options: An OptionParser.OptionValues object with the parsed options.
|
|
|
|
Returns:
|
|
The pair (out_dir, intermediate_dir), both of which are strings.
|
|
"""
|
|
out_dir = os.path.normpath(options.out_dir)
|
|
intermediate_dir = os.path.normpath(options.intermediate_dir)
|
|
if intermediate_dir is None:
|
|
intermediate_dir = out_dir
|
|
|
|
EnsureDirExists(out_dir)
|
|
EnsureDirExists(intermediate_dir)
|
|
|
|
return out_dir, intermediate_dir
|
|
|
|
|
|
def CreateWindowsLibForSigFiles(sig_files, out_dir, intermediate_dir, machine):
|
|
"""For each signature file, create a windows lib.
|
|
|
|
Args:
|
|
sig_files: Array of strings with the paths to each signature file.
|
|
out_dir: String holding path to directory where the generated libs go.
|
|
intermediate_dir: String holding path to directory generated intermdiate
|
|
artifacts.
|
|
machine: String holding the machine type, 'X86' or 'X64'.
|
|
"""
|
|
for input_path in sig_files:
|
|
infile = open(input_path, 'r')
|
|
try:
|
|
signatures = ParseSignatures(infile)
|
|
module_name = ExtractModuleName(os.path.basename(input_path))
|
|
CreateWindowsLib(module_name, signatures, intermediate_dir, out_dir,
|
|
machine)
|
|
finally:
|
|
infile.close()
|
|
|
|
|
|
def CreateWindowsDefForSigFiles(sig_files, out_dir, module_name):
|
|
"""For all signature files, create a single windows def file.
|
|
|
|
Args:
|
|
sig_files: Array of strings with the paths to each signature file.
|
|
out_dir: String holding path to directory where the generated def goes.
|
|
module_name: Name of the output DLL or LIB which will link in the def file.
|
|
"""
|
|
signatures = []
|
|
for input_path in sig_files:
|
|
infile = open(input_path, 'r')
|
|
try:
|
|
signatures += ParseSignatures(infile)
|
|
finally:
|
|
infile.close()
|
|
|
|
def_file_path = os.path.join(
|
|
out_dir, os.path.splitext(os.path.basename(module_name))[0] + '.def')
|
|
outfile = open(def_file_path, 'w')
|
|
|
|
try:
|
|
WriteWindowsDefFile(module_name, signatures, outfile)
|
|
finally:
|
|
outfile.close()
|
|
|
|
|
|
def CreatePosixStubsForSigFiles(sig_files, stub_name, out_dir,
|
|
intermediate_dir, path_from_source,
|
|
extra_stub_header):
|
|
"""Create a posix stub library with a module for each signature file.
|
|
|
|
Args:
|
|
sig_files: Array of strings with the paths to each signature file.
|
|
stub_name: String with the basename of the generated stub file.
|
|
out_dir: String holding path to directory for the .h files.
|
|
intermediate_dir: String holding path to directory for the .cc files.
|
|
path_from_source: String with relative path of generated files from the
|
|
project root.
|
|
extra_stub_header: String with path to file of extra lines to insert
|
|
into the generated header for the stub library.
|
|
"""
|
|
header_base_name = stub_name + '.h'
|
|
header_path = os.path.join(out_dir, header_base_name)
|
|
impl_path = os.path.join(intermediate_dir, stub_name + '.cc')
|
|
|
|
module_names = [ExtractModuleName(path) for path in sig_files]
|
|
namespace = path_from_source.replace('/', '_').lower()
|
|
header_guard = '%s_' % namespace.upper()
|
|
header_include_path = os.path.join(path_from_source, header_base_name)
|
|
|
|
# First create the implementation file.
|
|
impl_file = open(impl_path, 'w')
|
|
try:
|
|
# Open the file, and create the preamble which consists of a file
|
|
# header plus any necessary includes.
|
|
PosixStubWriter.WriteImplementationPreamble(header_include_path,
|
|
impl_file)
|
|
if extra_stub_header is not None:
|
|
extra_header_file = open(extra_stub_header, 'r')
|
|
try:
|
|
impl_file.write('\n')
|
|
for line in extra_header_file:
|
|
impl_file.write(line)
|
|
impl_file.write('\n')
|
|
finally:
|
|
extra_header_file.close()
|
|
|
|
# For each signature file, generate the stub population functions
|
|
# for that file. Each file represents one module.
|
|
for input_path in sig_files:
|
|
name = ExtractModuleName(input_path)
|
|
infile = open(input_path, 'r')
|
|
try:
|
|
signatures = ParseSignatures(infile)
|
|
finally:
|
|
infile.close()
|
|
writer = PosixStubWriter(name, signatures)
|
|
writer.WriteImplementationContents(namespace, impl_file)
|
|
|
|
# Lastly, output the umbrella function for the file.
|
|
PosixStubWriter.WriteUmbrellaInitializer(module_names, namespace,
|
|
impl_file)
|
|
finally:
|
|
impl_file.close()
|
|
|
|
# Then create the associated header file.
|
|
header_file = open(header_path, 'w')
|
|
try:
|
|
PosixStubWriter.WriteHeaderContents(module_names, namespace,
|
|
header_guard, header_file)
|
|
finally:
|
|
header_file.close()
|
|
|
|
|
|
def main():
|
|
options, args = ParseOptions()
|
|
out_dir, intermediate_dir = CreateOutputDirectories(options)
|
|
|
|
if options.type == FILE_TYPE_WIN_X86:
|
|
CreateWindowsLibForSigFiles(args, out_dir, intermediate_dir, 'X86')
|
|
elif options.type == FILE_TYPE_WIN_X64:
|
|
CreateWindowsLibForSigFiles(args, out_dir, intermediate_dir, 'X64')
|
|
elif options.type == FILE_TYPE_POSIX_STUB:
|
|
CreatePosixStubsForSigFiles(args, options.stubfile_name, out_dir,
|
|
intermediate_dir, options.path_from_source,
|
|
options.extra_stub_header)
|
|
elif options.type == FILE_TYPE_WIN_DEF:
|
|
CreateWindowsDefForSigFiles(args, out_dir, options.module_name)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|