Create a spec file
Contents
Prerequisites
- Python 2.7 (and at least the ability to read it, and modify the code shown here to suit your needs).
- pefile (Very simple / nice to use PE file library, if you don't have it, install with:
pip install pefile
) - A building checkout of ROS (Example source is in R:/src)
- A dll to base the stubs on (Stored in d:/dll/w2k3/vssapi.dll for this example)
Getting started
As a first step, let's get accustomed to pefile a bit, and examine the exports from our target dll. The usage examples of pefile show how to iterate exports.
Let's apply that (and don't forget to change the path to your local copy of the dll):
import pefile
pe = pefile.PE('d:/dll/w2k3/vssapi.dll')
for entry in pe.DIRECTORY_ENTRY_EXPORT.symbols:
print entry.ordinal, entry.name
This lists all functions exported, and their ordinal. A small snippet of the output:
8 ??0CVssJetWriter@@QAE@XZ
9 ??0CVssWriter@@QAE@XZ
10 ??1CVssJetWriter@@UAE@XZ
11 ??1CVssWriter@@UAE@XZ
12 ?AreComponentsSelected@CVssWriter@@IBG_NXZ
.. snip ..
If you are wondering about the '?' and '@' in the function names: These are so called 'decorated' or 'mangled' exports, which means that the compiler encoded extra info in the function name.
All right, we have a list of all exports, so let's continue to the next step.
Adding a new dll with CMake
- Navigate to
R:/src/win32
- Create a new folder, and name it the same as your target dll (vssapi in the example, substitute with your target).
- Open
R:/src/win32/CMakeLists.txt
with a texteditor, and add a line:add_subdirectory(vssapi)
(and again, substitute it). This tells cmake to also include the directory named vssapi when looking for stuff to include. - Open the new folder (
R:/src/win32/vssapi
), and create a new (empty) file:R:/src/win32/vssapi/vssapi.spec
(where you again substitute vssapi with your target). - Create another file:
R:/src/win32/vssapi/CMakeLists.txt
, with content:
include_directories(${REACTOS_SOURCE_DIR}/include/reactos/wine)
add_definitions(-D__WINESRC__)
spec2def(vssapi.dll vssapi.spec ADD_IMPORTLIB)
list(APPEND SOURCE
${CMAKE_CURRENT_BINARY_DIR}/vssapi_stubs.c)
add_library(vssapi SHARED
${SOURCE}
${CMAKE_CURRENT_BINARY_DIR}/vssapi.def)
set_module_type(vssapi win32dll)
target_link_libraries(vssapi wine)
add_importlibs(vssapi msvcrt kernel32 ntdll)
add_cd_file(TARGET vssapi DESTINATION reactos/system32 FOR all)
And of course, substitute every occurrence of vssapi with your dll name. This file tells cmake to use the vssapi.spec file to create a dll, and that the dll should be stored in reactos/system32. (And some more stuff, but that's a story for another day.)
Now we should be able to build an empty dll, so re-run cmake (just building anything else should work, cmake will detect the change in r:/src/dll/win32/CMakeLists.txt
).
After re-running cmake, there should be a new target: vssapi.dll, so let's build that.
Filling in the blanks
So, to recap: we can build an empty dll, and we can list all exports from a dll. Let's combine this to build a dll with stubbed functions!
Looking at a few .spec files now, the format seems to be very simple:
[ordinal or @] [stub|stdcall] [functionname]
So all we have to do is print 'stub' inbetween the ordinal and name we already printed, and that should be it (see The finished script for the result)!
Now we can paste the output from the script in the .spec file, rebuild the dll and we have a stubbed dll!
The finished script
import pefile
pe = pefile.PE('d:/dll/w2k3/vssapi.dll')
for entry in pe.DIRECTORY_ENTRY_EXPORT.symbols:
print entry.ordinal, 'stub', entry.name
Bonus feature
And since everyone likes neat stuff, how about we order the exports by ordinal?
import pefile
pe = pefile.PE('d:/dll/w2k3/vssapi.dll')
exports = []
for entry in pe.DIRECTORY_ENTRY_EXPORT.symbols:
exports.append(entry)
for export in sorted(exports, key=lambda exp: exp.ordinal):
print export.ordinal, 'stub', export.name
A new do-it all script, thatll output a CMakeLists.txt and spec file (mkspec.py)
import argparse import os import sys try: import pefile except ImportError: print 'You need to install the pefile module from https://github.com/erocarrera/pefile' print 'Or install it with `pip install pefile`' exit(1) # Those functions need to be exported with -private, and without ordinal. PRIVATE_EXPORTS = { 'DllCanUnloadNow': 'DllCanUnloadNow()', 'DllGetClassObject': 'DllGetClassObject(ptr ptr ptr)', 'DllRegisterServer': 'DllRegisterServer()', 'DllUnregisterServer': 'DllUnregisterServer()' } CMAKE_TEMPLATE = """ include_directories(${{REACTOS_SOURCE_DIR}}/include/reactos/wine) add_definitions(-D__WINESRC__) spec2def({dll}.dll {dll}.spec ADD_IMPORTLIB) list(APPEND SOURCE ${{CMAKE_CURRENT_BINARY_DIR}}/{dll}_stubs.c) add_library({dll} SHARED ${{SOURCE}} ${{CMAKE_CURRENT_BINARY_DIR}}/{dll}.def) set_module_type({dll} win32dll) target_link_libraries({dll} wine) add_importlibs({dll} msvcrt kernel32 ntdll) add_cd_file(TARGET {dll} DESTINATION reactos/system32 FOR all) """ EXPORT_DIR_ID = pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_EXPORT'] class SpecGen: def __init__(self, target): self.target = target self.basename = os.path.splitext(os.path.basename(target))[0] self.pe = pe = pefile.PE(target, fast_load = True) self.exports = None def _update(self): if not hasattr(self.pe, 'DIRECTORY_ENTRY_EXPORT'): self.pe.parse_data_directories(EXPORT_DIR_ID) exports = [] for entry in self.pe.DIRECTORY_ENTRY_EXPORT.symbols: exports.append(entry) self.exports = sorted(exports,key=lambda export: export.ordinal) def _write_spec(self, file): if self.exports is None: self._update() for export in self.exports: if not export.name: file.write('{ord} stub -noname Ordinal{ord}\n'.format(ord=export.ordinal)) elif export.name in PRIVATE_EXPORTS: file.write('@ stub -private {}\n'.format(export.name)) else: file.write('{} stub {}\n'.format(export.ordinal, export.name)) def _write_cmake(self, file): file.write(CMAKE_TEMPLATE.format(dll = self.basename)) def write_spec(self, dir): if dir is None: spec = sys.stdout else: print 'Writing', self.basename + '.spec' spec = open(os.path.join(dir, self.basename + '.spec'), 'w') self._write_spec(spec) def write_cmake(self, dir): if dir is None: cmake = sys.stdout else: print 'Writing CMakeLists.txt for', self.basename cmake = open(os.path.join(dir, 'CMakeLists.txt'), 'w') self._write_cmake(cmake) def main(args): parser = argparse.ArgumentParser() parser.add_argument('--out', help='specify the output dir') parser.add_argument('--skip_spec', action='store_true') parser.add_argument('--skip_cmake', action='store_true') args, files = parser.parse_known_args(args) if len(files) == 0: print 'Please specify atleast one file to dump' exit(1) for file in files: gen = SpecGen(file) dir = os.path.join(args.out, gen.basename) if args.out else None if dir and not os.path.isdir(dir): os.makedirs(dir) if not args.skip_spec: gen.write_spec(dir) if not args.skip_cmake: gen.write_cmake(dir) if __name__ == '__main__': main(sys.argv[1:])