Added spec generation tool specmaker.

This commit is contained in:
Jon Griffiths 2001-01-04 19:45:49 +00:00 committed by Alexandre Julliard
parent 8d91b501dd
commit c7a3fec5be
15 changed files with 3437 additions and 1 deletions

2
configure vendored
View File

@ -6966,6 +6966,7 @@ scheduler/Makefile
server/Makefile server/Makefile
tools/Makefile tools/Makefile
tools/cvdump/Makefile tools/cvdump/Makefile
tools/specmaker/Makefile
tools/winebuild/Makefile tools/winebuild/Makefile
tools/winelauncher tools/winelauncher
tools/wmc/Makefile tools/wmc/Makefile
@ -7209,6 +7210,7 @@ scheduler/Makefile
server/Makefile server/Makefile
tools/Makefile tools/Makefile
tools/cvdump/Makefile tools/cvdump/Makefile
tools/specmaker/Makefile
tools/winebuild/Makefile tools/winebuild/Makefile
tools/winelauncher tools/winelauncher
tools/wmc/Makefile tools/wmc/Makefile

View File

@ -1284,6 +1284,7 @@ scheduler/Makefile
server/Makefile server/Makefile
tools/Makefile tools/Makefile
tools/cvdump/Makefile tools/cvdump/Makefile
tools/specmaker/Makefile
tools/winebuild/Makefile tools/winebuild/Makefile
tools/winelauncher tools/winelauncher
tools/wmc/Makefile tools/wmc/Makefile

View File

@ -11,11 +11,13 @@ C_SRCS = makedep.c fnt2bdf.c bin2res.c
SUBDIRS = \ SUBDIRS = \
cvdump \ cvdump \
specmaker \
winebuild \ winebuild \
wmc \ wmc \
wrc wrc
INSTALLSUBDIRS = \ INSTALLSUBDIRS = \
specmaker \
winebuild \ winebuild \
wmc \ wmc \
wrc wrc
@ -26,7 +28,7 @@ EXTRASUBDIRS = \
winapi_check/win32 \ winapi_check/win32 \
wineconf.libs wineconf.libs
all: $(PROGRAMS) winebuild wmc wrc all: $(PROGRAMS) specmaker winebuild wmc wrc
@MAKE_RULES@ @MAKE_RULES@

View File

@ -0,0 +1,2 @@
Makefile
specmaker

View File

@ -0,0 +1,35 @@
DEFS = -D__WINE__
TOPSRCDIR = @top_srcdir@
TOPOBJDIR = ../..
SRCDIR = @srcdir@
VPATH = @srcdir@
PROGRAMS = specmaker
MODULE = none
C_SRCS = \
dll.c \
main.c \
misc.c \
msmangle.c \
output.c \
search.c \
symbol.c
all: $(PROGRAMS)
@MAKE_RULES@
specmaker: $(OBJS)
$(CC) $(CFLAGS) -o specmaker $(OBJS) $(LDFLAGS)
install:: $(PROGRAMS)
[ -d $(bindir) ] || $(MKDIR) $(bindir)
$(INSTALL_PROGRAM) specmaker $(bindir)/specmaker
$(INSTALL_PROGRAM) function_grep.pl $(bindir)/function_grep.pl
uninstall::
$(RM) $(bindir)/specmaker
$(RM) $(bindir)/function_grep.pl
### Dependencies:

560
tools/specmaker/README Normal file
View File

@ -0,0 +1,560 @@
Specmaker - A Wine DLL tool
---------------------------
Background
----------
Most of the functions available in Windows, and in Windows applications, are
made available to applications from DLL's. Wine implements the Win32 API by
providing replacement's for the essential Windows DLLs in the form of Unix
shared library (.so) files, and provides a tool, winebuild, to allow Winelib
applications to link to functions exported from shared libraries/DLLs.
The first thing to note is that there are many DLLs that aren't yet
implemented in Wine. Mostly this doesn't present a problem because the native
Win32 versions of lots of DLLs can be used without problems, at least on
x86 platforms. However, one of Wine's goals is the eventual replacement of
every essential O/S DLL so that the whole API is implemented. This not only
means that a copy of the real O/S is not needed, but also that non-x86
platforms can run most Win32 programs after recompiling.
The second thing to note is that applications commonly use their own or 3rd
party DLLs to provide functionality. In order to call these functions with
a Winelib program, some 'glue' is needed. This 'glue' comes in the form of
a .spec file. The .spec file, along with some dummy code, is used to create
a Wine .so corresponding to the Windows DLL. The winebuild program can then
resolve calls made to DLL functions to call your dummy DLL. You then tell
Wine to only use the native Win32 version of the DLL, and at runtime your
calls will be made to the Win32 DLL. If you want to reimplement the dll,
you simply add the code for the DLL calls to your stub .so, and then tell
Wine to use the .so version instead [1].
These two factors mean that if you are:
A: Reimplementing a Win32 DLL for use within Wine, or
B: Compiling a Win32 application with Winelib that uses x86 DLLs
Then you will need to create a .spec file (amongst other things). If you
won't be doing either of the above, then you won't need specmaker.
Creating a .spec file is a labour intensive task during which it is easy
to make a mistake. The idea of specmaker is to automate this task and create
the majority of the support code needed for your DLL. In addition you can
have specmaker create code to help you reimplement a DLL, by providing
tracing of calls to the DLL, and (in some cases) automatically determining
the parameters, calling conventions, and return values of the DLLs functions.
You can think of specmaker as somewhat similar to the IMPLIB tool when
only its basic functionality is used.
Usage
-----
Specmaker is a command line tool. Running it with no arguments or passing
it '-h' on the command line lists the available options:
Usage: specmaker [options] -d dll
Options:
-d dll Use dll for input file (mandatory)
-h Display this help message
-I dir Look for prototypes in 'dir' (implies -c)
-o name Set the output dll name (default: dll)
-c Generate skeleton code (requires -I)
-t TRACE arguments (implies -c)
-f dll Forward calls to 'dll' (implies -t)
-D Generate documentation
-C Assume __cdecl calls (default: __stdcall)
-s num Start prototype search after symbol 'num'
-e num End prototype search after symbol 'num'
-q Don't show progress (quiet).
-v Show lots of detail while working (verbose).
Basic options
-------------
OPTION: -d dll Use dll for input file (mandatory)
The -d option tells specmaker which DLL you want to create a .spec file
for. You *must* give this option.
16 bit DLL's are not currently supported (Note that Winelib is intended
only for Win32 programs).
OPTION: -o name Set the output dll name (default: dll)
By default, if specmaker is run on DLL 'foo', it creates files called
'foo.spec', 'foo_main.c' etc, and prefixes any functions generated
with 'FOO_'. If '-o bar' is given, these will become 'bar.spec',
'bar_main.c' and 'BAR_' respectively.
This option is mostly useful when generating a forwarding DLL. See below
for more information.
OPTION: -q Don't show progress (quiet).
-v Show lots of detail while working (verbose).
There are 3 levels of output while specmaker is running. The default level,
when neither -q or -v are given, prints the number of exported functions
found in the dll, followed by the name of each function as it is processed,
and a status indication of whether it was processed OK. With -v given, a
lot of information is dumped while specmaker works: this is intended to help
debug any problems. Giving -q means nothing will be printed unless a fatal
error occurs, and could be used when calling specmaker from a script.
OPTION: -C Assume __cdecl calls (default: __stdcall)
This option determines the default calling convention used by the functions
in the DLL. If specbuild cannot determine the convention, __stdcall is
used by default, unless this option has been given.
Unless -q is given, a warning will be printed for every function that
specmaker determines the calling convention for and which does not match
the assumed calling convention.
Generating stub DLLS
--------------------
If all you want to do is generate a stub DLL to allow you to link your
Winelib application to an x86 DLL, the above options are all you need.
As an example, lets assume the application you are porting uses functions
from a 3rd party dll called 'zipextra.dll', and the functions in the DLL
use the __stdcall calling convention. Copy zipextra.dll to an empty directory,
change to it, and run specmaker as follows:
specmaker -d zipextra (Note: this assumes specmaker is in your path)
The output will look something like the following:
22 exported symbols in DLL ...
Export 1 - '_OpenZipFile' ... [Ignoring]
Export 2 - '_UnZipFile' ... [Ignoring]
...
"[Ignoring]" Just tells you that specmaker isn't trying to determine the
parameters or return types of the functions, its just creating stubs.
The following files are created:
zipextra.spec
This is the .spec file. Each exported function is listed as a stub:
@ stub _OpenZipFile
@ stub _UnZipFile
...
This means that winebuild will generate dummy code for this function. That
doesn't concern us, because all we want is for winebuild to allow the
symbols to be resolved. At run-time, the functions in the native DLL will
be called; this just allows us to link.
zipextra_dll.h zipextra_main.c
These are source code files containing the minimum set of code to build
a stub DLL. The C file contains one function, ZIPEXTRA_Init, which does
nothing.
Makefile.in
This is a template for 'configure' to produce a makefile. It is designed
for a DLL that will be inserted into the Wine source tree. If your DLL
will not be part of Wine, or you don't wish to build it this way,
you should look at the Wine tool 'winemaker' to generate a DLL project.
FIXME: winemaker could run this tool automatically when generating projects
that use extra DLL's (*.lib in the "ADD LINK32" line in .dsp) ....
zipextra_install
A shell script for adding zipextra to the Wine source tree (see below).
Inserting a stub DLL into the Wine tree
---------------------------------------
To build your stub DLL as part of Wine, do the following:
chmod a+x ./zipextra_install
./zipextra_install <wine-path>
cd <wine-path>
autoconf
./configure
make depend && make
make install
Your application can now link with the DLL.
NOTE: **DO NOT** submit patches to Wine for 3rd party DLLs! Building DLLs
into your copy of the tree is just a simple way for you to link. When
you release your application you won't be distributing the Unix .so
anyway, just the Win32 DLL. As you update your version of Wine
you can simply re-run the procedure above (Since no patches are
involved, it should be pretty resiliant to changes).
Advanced Options
----------------
This section discusses features of specmaker that are useful to Wine Hackers
or developers looking to reimplement a Win32 DLL for Unix. Using these
features means you will need to be able to resolve compilation problems and
have a general understanding of Wine programming.
OPTION: -I dir Look for prototypes in 'dir' (implies -c)
For all advanced functionality, you must give specmaker a directoryor file that
contains prototypes for the DLL. In the case of Windows DLLs, this could be
either the standard include directory from your compiler, or an SDK include
directory. If you have a text document with prototypes (such as documentation)
that can be used also, however you may need to delete some non-code lines to
ensure that prototypes are parsed correctly.
The 'dir' argument can also be a file specification (e.g. "include/*"). If
it contains wildcards you must quote it to prevent the shell from expanding it.
If you have no prototypes, specify /dev/null for 'dir'. Specmaker may still
be able to generate some working stub code for you.
Once you have created your DLL, if you generated code (see below), you can
backup the DLL header file created and use it for rebuilding the DLL (you
should remove the DLLNAME_ prefix from the prototypes to make this work). This
allows you to add names to the function arguments, for example, so that the
comments and prototype in the regenerated DLL will be clearer.
Specmaker searches for prototypes using 'grep', and then retrieves each
prototype by calling 'function_grep.pl', a Perl script. When you pass the -v
option on the command line, the calls to both of these programs are logged.
This allows you to see where each function definition has come from. Should
specmaker take an excessively long time to locate a prototype, you can check
that it is searching the right files; you may want to limit the number of files
searched if locating the prototype takes too long.
You can compile function_grep.pl for a slight increase in performance; see
'man perlcc' for details.
OPTION: -s num Start prototype search after symbol 'num'
-e num End prototype search after symbol 'num'
By passing the -s or -e options you can have specmaker try to generate code
for only some functions in your DLL. This may be used to generate a single
function, for example, if you wanted to add functionality to an existing DLL.
They is also useful for debugging problems, in conjunction with -v.
OPTION: -D Generate documentation
By default, specmaker generates a standard comment at the header of each
function it generates. Passing this option makes specmaker output a full
header template for standard Wine documentation, listing the parameters
and return value of the function.
OPTION: -c Generate skeleton code (requires -I)
This option tells specmaker that you want to create function stubs for
each function in the DLL. This is the most basic level of code generation.
As specmaker reads each exported symbol from the source DLL, it first tries
to demangle the name. If the name is a C++ symbol, the arguments, class and
return value are all encoded into the symbol name. Specmaker converts this
information into a C function prototype. If this fails, the file(s) specified
in the -I argument are scanned for a function prototype. If one is found it
is used for the next step of the process, code generation.
Note: C++ name demangling is currently under development. Since the algorithm
used is not documented, it must be decoded. Many simple prototypes are already
working however.
If specmaker does not find a prototype, it emits code like the following:
In the .spec file:
@stub _OpenZipFile
in the header file:
/* __cdecl ZIPEXTRA__OpenZipFile() */
in the C source file:
/*********************************************************************
* _OpenZipFile (ZIPEXTRA.@)
*
*/
#if 0
__stdcall ZIPEXTRA__OpenZipFile()
{
/* '@Stubbed'ed in .spec */
}
#endif
If a prototype is found, or correctly demangled, the following is emitted:
.spec:
@ stdcall _OpenZipFile ZIPEXTRA__OpenZipFile
.h:
BOOL __stdcall ZIPEXTRA__OpenZipFile(LPCSTR pszFileName);
.c:
BOOL __stdcall ZIPEXTRA__OpenZipFile(LPCSTR pszFileName)
{
TRACE("stub");
return 0;
}
Note that if the prototype does not contain argument names, specmaker will
add them following the convention arg0, arg1 ... argN. If the function is
demangled C++, the first argument will be called '_this' if an implicit this
pointer is passed (i.e. the function is a non-static class member function).
OPTION: -t TRACE arguments (implies -c)
This option produces the same code as -c, except that arguments are printed
out when the function is called, so the FIXME in the above example becomes:
FIXME("(%s) stub", pszFileName);
Structs that are passed by value are printed as "struct", and functions
that take variable argument lists print "...".
OPTION: -f dll Forward calls to 'dll' (implies -t)
This is the most complicated level of code generation. The same code is
generated as -t, however support is added for forwarding calls to another
DLL. The DLL to forward to is given as 'dll'. Lets suppose we built the
examples above using "-f real_zipextra". The code generated will look like
the following:
.spec
As for -c, except if a function prototype was not found:
@ forward _OpenZipFile real_zipextra._OpenZipFile
In this case the function is forwarded to the destination DLL rather
than stubbed.
.h
As for -c.
.c
A variable "hDLL" is added to hold a pointer to the DLL to forward to, and
the initialisation code in ZIPEXTRA_Init is changed to load and free the
forward DLL automatically:
HMODULE hDLL = 0; /* DLL to call through to */
BOOL WINAPI ZIPEXTRA_Init(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
TRACE("(0x%08x, %ld, %p)\n", hinstDLL, fdwReason, lpvReserved);
if (fdwReason == DLL_PROCESS_ATTACH)
{
hDLL = LoadLibraryA( "real_zipextra" );
TRACE ("Forwarding DLL (real_zipextra) loaded\n" );
}
else if (fdwReason == DLL_PROCESS_DETACH)
{
FreeLibrary( hDLL );
TRACE ("Forwarding DLL (real_zipextra) freed\n" );
}
return TRUE;
}
The stub function is changed to call the forwarding DLL and return that value.
BOOL __stdcall ZIPEXTRA__OpenZipFile(LPCSTR pszFileName)
{
BOOL (__stdcall *pFunc)(LPCSTR) = (void*)GetProcAddress(hDLL,"_OpenZipFile");
BOOL retVal;
TRACE("((LPCSTR)%s) stub", pszFileName);
retVal = pFunc(pszFileName);
TRACE("returned (%ld)\n",(LONG)retVal));
return retVal;
}
This allows you to investigate the workings of a DLL without interfering in
its operation in any way (unless you want to).
In the example I have been using, we probably should have used the -o option
to change the ouput name of our DLL to something else, and used the -f
option to forward to the real zipextra DLL:
specmaker -d zipextra -f zipextra -o myzipextra -I "~/zipextra/include/*h"
Then in the .spec file for our Winelib application, we add the line:
import myzipextra
When we build our application, winebuild resolves the calls to our Unix .so.
As our application runs we can see the values of all parameters passed to
the DLL, and any values returned, without having to write code to dump
them ourselves (see below for a better way to wrap a DLL for forwarding).
This isn't a very realistic example of the usefulness of this feature,
however, since we could print out the results anyway, because it is our
application making the calls to the DLL. Where DLL forwarding is most useful
is where an application or DLL we didn't write calls functions in the DLL.
In this case we can capture the sequence of calls made, and the values passed
around. This is an aid in reimplementing the DLL, since we can add code for a
function, print the results, and then call the real DLL and compare. Only
when our code is the same do we need to remove the function pointer and the
call to the real DLL. A similar feature in wine is +relay debugging. Using a
fowarding DLL allows more granular reporting of arguments, because you can
write code to dump out the contents of types/structures rather than just
their address in memory. A future version of specmaker may generate this
code automatically for common Win32 types.
See below for more information on setting up a forwarding DLL.
Problems compiling a DLL containing generated code
--------------------------------------------------
Unless you are very lucky, you will need to do a small amount of work to
get a DLL generated with -c, -t or -f to compile. The reason for this is
that most DLLs will use custom types such as structs whose definition
is not known to the code in the DLL.
Heres an example prototype from crtdll:
double __cdecl _cabs(struct _complex arg0)
The definition for the _complex struct needs to be given. Since it is passed
by value, its size also needs to be correct in order to forward the call
correctly to a native DLL. In this case the structure is 8 bytes in size, which
means that the gcc compile flag -freg-struct-return must be given when
compiling the function in order to be compatable with the native DLL. (In
general this is not an issue, but you need to be aware of such issues if you
encounter problems with your forwarding DLL).
For third party (non C++) DLL's, the header(s) supplied with the DLL can
normally be added as an include to the generated DLL header. For other DLLs
I suggest creating a seperate header in the DLL directory and adding any
needed types to that. This allows you to rebuild the DLL at whim, for example
if a new version of specmaker brings increased functionality, then you
only have to overwrite the generated files and re-include the header to take
advantage of it.
Usually there isn't much work to do to get the DLL to compile if you have
headers. As an example, building a forwarded crtdll, which contains 520
functions, required 20 types to be defined before it compiled. Of these,
about half were structures, so about 35 lines of code were needed. The only
change to the generated code was one line in the header to include the type
definitions.
To save some typing in case you don't have headers for your DLL type, specmaker
will dump dummy declarations for unknown classes and types it encounters,
if you use the -v option. These can be piped directly into a fix-up header
file for use in compiling your DLL. For example, if specmaker encounters the
(C++ ) symbol:
??0foobar@@QAE@ABV0@@Z (Which is a constructor for a foobar object)
It will emit the following with -v set:
struct foobar { int _FIXME; };
(Classes are mapped to C structs when generating code).
The output should be piped through 'sort' and 'uniq' to remove multiple
declarations, e.g:
specmaker -d foo -c -I "inc/*.h" -v | grep FIXME | sort | uniq > fixup.h
By adding '#include "fixup.h"' to foobar_dll.h your compile errors will be
greatly reduced.
If specmaker encounters a type it doesnt know that is passed by value (as in
the _cabs example above), it also prints a FIXME message like:
/* FIXME: By value type: Assumed 'int' */ typedef int ldiv_t;
If the type is not an int, you will need to change the code and possibly
the .spec entry in order to forward correctly. Otherwise, include the typedef
in your fixup header to avoid compile errors.
Using a forwarding DLL
----------------------
To create and use a forwarding DLL to trace DLL calls, you need to first
create a DLL using the -f option as outlined above, and get it to compile.
In order to forward calls the following procedure can be used (for this
example we are going to build a forwarding msvcrt.dll for the purpose
of reimplementing it).
First we create the forwarding DLL. We will rename the real msvcrt.dll on our
system to ms_msvcrt.dll, and our msvcrt implementation will call it:
specmaker -d msvcrt -C -f ms_msvcrt -I "inc/*.h"
We then install this DLL into the Wine tree and add the types we need to
make it compile. Once the DLL compiles, we create a dummy ms_msvcrt DLL so
winebuild will resolve our forward calls to it (for the cases where specmaker
couldn't generate code and has placed an '@forward' line in the .spec file):
specmaker -d msvcrt -C -o ms_msvcrt
Install this DLL into the wine tree (since its a stub DLL, no changes are
needed to the code).
Now uncomment the line that specmaker inserted into msvcrt.spec:
#inport ms_msvcrt.dll
And recompile Wine.
Finally, we must tell Wine to only use the builtin msvcrt.dll and to only use
the native (Win32) ms_msvcrt.dll. Add the following two lines to ~/.wine/config
under the [DllOverrides] section:
;Use our implmentation of msvcrt
"msvcrt" = "builtin, so"
;Use only the Win32 ms_msvcrt
"ms_msvcrt" = "native"
At this point, when any call is made to msvcrt.dll, Our libmsvcrt.so recieves
the call. It then forwards or calls ms_msvcrt.dll, which is the native dll. We
recieve a return value and pass it back to our caller, having TRACEd the
arguments on the way.
At this point you are ready to start reimplementing the calls.
Final comments
--------------
If you have any suggestions for improving this tool, please let me know.
If anyone can help answer the FIXME questions in msmangle.c or can fill me in
on any aspect of the C++ mangling scheme, I would appreciate it. In particular
I want to know what _E and _G represent.
If you encounter a C++ symbol that doesn't demangle **AND** you have the
prototype for it, please send me the symbol as reported by specmaker and the
prototype. The more examples I have the easier it is to decypher the scheme,
and generating them myself is very slow.
Finally, although it is easy to generate a DLL, I _very strongly_ suggest that
you dont submit a generated DLL for inclusion into Wine unless you have
actually implemented a fairly reasonable portion of it. Even then, you should
only send the portions of the DLL you have implemented. Thousands of lines of
stub code don't help the project at all.
Please send questions and bug reports to jon_p_griffiths@yahoo.com.
References
----------
[1] See the Wine and Wine.conf man pages for details on how to tell Wine
whether to use native (Win32) or internal DLLs.

213
tools/specmaker/dll.c Normal file
View File

@ -0,0 +1,213 @@
/*
* DLL symbol extraction
*
* Copyright 2000 Jon Griffiths
*/
#include "specmaker.h"
/* DOS/PE Header details */
#define DOS_HEADER_LEN 64
#define DOS_MAGIC 0x5a4d
#define DOS_PE_OFFSET 60
#define PE_HEADER_LEN 248
#define PE_MAGIC 0x4550
#define PE_COUNT_OFFSET 6
#define PE_EXPORTS_OFFSET 120
#define PE_EXPORTS_SIZE PE_EXPORTS_OFFSET + 4
#define SECTION_HEADER_LEN 40
#define SECTION_ADDR_OFFSET 12
#define SECTION_ADDR_SIZE SECTION_ADDR_OFFSET + 4
#define SECTION_POS_OFFSET SECTION_ADDR_SIZE + 4
#define EXPORT_COUNT_OFFSET 24
#define EXPORT_NAME_OFFSET EXPORT_COUNT_OFFSET + 8
/* Minimum memory needed to read both headers into a buffer */
#define MIN_HEADER_LEN (PE_HEADER_LEN * sizeof (unsigned char))
/* Normalise a pointer in the exports section */
#define REBASE(x) ((x) - exports)
/* Module globals */
static FILE *dll_file = NULL;
static char **dll_symbols = NULL;
static size_t dll_num_exports = 0;
/* Get a short from a memory block */
static inline size_t get_short (const char *mem)
{
return *((const unsigned char *)mem) +
(*((const unsigned char *)mem + 1) << 8);
}
/* Get an integer from a memory block */
static inline size_t get_int (const char *mem)
{
assert (sizeof (char) == (size_t)1);
return get_short (mem) + (get_short (mem + 2) << 16);
}
static void dll_close (void);
/*******************************************************************
* dll_open
*
* Open a DLL and read in exported symbols
*/
void dll_open (const char *dll_name)
{
size_t code = 0, code_len = 0, exports, exports_len, count, symbol_data;
char *buff = NULL;
dll_file = open_file (dll_name, ".dll", "r");
atexit (dll_close);
/* Read in the required DOS and PE Headers */
if (!(buff = (char *) malloc (MIN_HEADER_LEN)))
fatal ("Out of memory");
if (fread (buff, DOS_HEADER_LEN, 1, dll_file) != 1 ||
get_short (buff) != DOS_MAGIC)
fatal ("Error reading DOS header");
if (fseek (dll_file, get_int (buff + DOS_PE_OFFSET), SEEK_SET) == -1)
fatal ("Error seeking PE header");
if (fread (buff, PE_HEADER_LEN, 1, dll_file) != 1 ||
get_int (buff) != PE_MAGIC)
fatal ("Error reading PE header");
exports = get_int (buff + PE_EXPORTS_OFFSET);
exports_len = get_int (buff + PE_EXPORTS_SIZE);
if (!exports || !exports_len)
fatal ("No exports in DLL");
if (!(count = get_short (buff + PE_COUNT_OFFSET)))
fatal ("No sections in DLL");
if (VERBOSE)
printf ("DLL has %d sections\n", count);
/* Iterate through sections until we find exports */
while (count--)
{
if (fread (buff, SECTION_HEADER_LEN, 1, dll_file) != 1)
fatal ("Section read error");
code = get_int (buff + SECTION_ADDR_OFFSET);
code_len = get_int (buff + SECTION_ADDR_SIZE);
if (code <= exports && code + code_len > exports)
break;
}
if (!count)
fatal ("No export section");
code_len -= (exports - code);
if (code_len < exports_len)
fatal ("Corrupt exports");
/* Load exports section */
if (fseek (dll_file, get_int (buff + SECTION_POS_OFFSET)
+ exports - code, SEEK_SET) == -1)
fatal ("Export section seek error");
if (VERBOSE)
printf ("Export data size = %d bytes\n", code_len);
if (!(buff = (char *) realloc (buff, code_len)))
fatal ("Out of memory");
if (fread (buff, code_len, 1, dll_file) != 1)
fatal ("Read error");
dll_close();
/* Locate symbol names */
symbol_data = REBASE( get_int (buff + EXPORT_NAME_OFFSET));
if (symbol_data > code_len)
fatal ("Corrupt exports section");
if (!(dll_num_exports = get_int (buff + EXPORT_COUNT_OFFSET)))
fatal ("No export count");
if (!(dll_symbols = (char **) malloc (dll_num_exports * sizeof (char *))))
fatal ("Out of memory");
/* Read symbol names into 'dll_symbols' */
count = 0;
while (count < dll_num_exports)
{
const int symbol_offset = get_int (buff + symbol_data + count * 4);
const char *symbol_name_ptr = REBASE (buff + symbol_offset);
assert(symbol_name_ptr);
dll_symbols[count] = strdup (symbol_name_ptr);
assert(dll_symbols[count]);
count++;
}
if (NORMAL)
printf ("%d exported symbols in DLL\n", dll_num_exports);
free (buff);
/* Set DLL output names */
if ((buff = strrchr (globals.input_name, '/')))
globals.input_name = buff + 1; /* Strip path */
OUTPUT_UC_DLL_NAME = str_toupper( strdup (OUTPUT_DLL_NAME));
}
/*******************************************************************
* dll_next_symbol
*
* Get next exported symbol from dll
*/
char* dll_next_symbol ()
{
static unsigned int current_export = 0;
assert (current_export <= dll_num_exports);
if (current_export == dll_num_exports)
return NULL;
assert (dll_symbols);
assert (dll_symbols [current_export]);
return strdup (dll_symbols [current_export++]);
}
/*******************************************************************
* dll_close
*
* Free resources used by DLL
*/
static void dll_close (void)
{
size_t i;
if (dll_file)
{
fclose (dll_file);
dll_file = NULL;
}
if (dll_symbols)
{
for (i = 0; i < dll_num_exports; i++)
if (dll_symbols [i])
free (dll_symbols [i]);
free (dll_symbols);
dll_symbols = NULL;
}
}

270
tools/specmaker/function_grep.pl Executable file
View File

@ -0,0 +1,270 @@
#! /usr/bin/perl
# Copyright 2000 Patrik Stridvall
use strict;
my $invert = 0;
my $pattern;
my @files = ();
while(defined($_ = shift)) {
if(/^-/) {
if(/^-v$/) {
$invert = 1;
}
} else {
if(!defined($pattern)) {
$pattern = $_;
} else {
push @files, $_;
}
}
}
foreach my $file (@files) {
open(IN, "< $file");
my $level = 0;
my $extern_c = 0;
my $again = 0;
my $lookahead = 0;
while($again || defined(my $line = <IN>)) {
if(!$again) {
chomp $line;
if($lookahead) {
$lookahead = 0;
$_ .= "\n" . $line;
} else {
$_ = $line;
}
} else {
$again = 0;
}
# remove C comments
if(s/^(.*?)(\/\*.*?\*\/)(.*)$/$1 $3/s) {
$again = 1;
next;
} elsif(/^(.*?)\/\*/s) {
$lookahead = 1;
next;
}
# remove C++ comments
while(s/^(.*?)\/\/.*?$/$1\n/s) { $again = 1; }
if($again) { next; }
# remove empty rows
if(/^\s*$/) { next; }
# remove preprocessor directives
if(s/^\s*\#/\#/m) {
if(/^\#.*?\\$/m) {
$lookahead = 1;
next;
} elsif(s/^\#\s*(.*?)(\s+(.*?))?\s*$//m) {
next;
}
}
# Remove extern "C"
if(s/^\s*extern\s+"C"\s+\{//m) {
$extern_c = 1;
$again = 1;
next;
}
if($level > 0)
{
my $line = "";
while(/^[^\{\}]/) {
s/^([^\{\}\'\"]*)//s;
$line .= $1;
if(s/^\'//) {
$line .= "\'";
while(/^./ && !s/^\'//) {
s/^([^\'\\]*)//s;
$line .= $1;
if(s/^\\//) {
$line .= "\\";
if(s/^(.)//s) {
$line .= $1;
if($1 eq "0") {
s/^(\d{0,3})//s;
$line .= $1;
}
}
}
}
$line .= "\'";
} elsif(s/^\"//) {
$line .= "\"";
while(/^./ && !s/^\"//) {
s/^([^\"\\]*)//s;
$line .= $1;
if(s/^\\//) {
$line .= "\\";
if(s/^(.)//s) {
$line .= $1;
if($1 eq "0") {
s/^(\d{0,3})//s;
$line .= $1;
}
}
}
}
$line .= "\"";
}
}
if(s/^\{//) {
$_ = $'; $again = 1;
$line .= "{";
$level++;
} elsif(s/^\}//) {
$_ = $'; $again = 1;
$line .= "}" if $level > 1;
$level--;
if($level == -1 && $extern_c) {
$extern_c = 0;
$level = 0;
}
}
next;
} elsif(/^class[^\}]*{/) {
$_ = $'; $again = 1;
$level++;
next;
} elsif(/^class[^\}]*$/) {
$lookahead = 1;
next;
} elsif(/^typedef[^\}]*;/) {
next;
} elsif(/(extern\s+|static\s+)?
(?:__inline__\s+|__inline\s+|inline\s+)?
((struct\s+|union\s+|enum\s+)?(?:\w+(?:\:\:(?:\s*operator\s*[^\)\s]+)?)?)+((\s*(?:\*|\&))+\s*|\s+))
((__cdecl|__stdcall|CDECL|VFWAPIV|VFWAPI|WINAPIV|WINAPI|CALLBACK)\s+)?
((?:\w+(?:\:\:)?)+(\(\w+\))?)\s*\(([^\)]*)\)\s*
(?:\w+(?:\s*\([^\)]*\))?\s*)*\s*
(\{|\;)/sx)
{
$_ = $'; $again = 1;
if($11 eq "{") {
$level++;
}
my $linkage = $1;
my $return_type = $2;
my $calling_convention = $7;
my $name = $8;
my $arguments = $10;
if(!defined($linkage)) {
$linkage = "";
}
if(!defined($calling_convention)) {
$calling_convention = "";
}
$linkage =~ s/\s*$//;
$return_type =~ s/\s*$//;
$return_type =~ s/\s*\*\s*/*/g;
$return_type =~ s/(\*+)/ $1/g;
$arguments =~ y/\t\n/ /;
$arguments =~ s/^\s*(.*?)\s*$/$1/;
if($arguments eq "") { $arguments = "void" }
my @argument_types;
my @argument_names;
my @arguments = split(/,/, $arguments);
foreach my $n (0..$#arguments) {
my $argument_type = "";
my $argument_name = "";
my $argument = $arguments[$n];
$argument =~ s/^\s*(.*?)\s*$/$1/;
# print " " . ($n + 1) . ": '$argument'\n";
$argument =~ s/^(IN OUT(?=\s)|IN(?=\s)|OUT(?=\s)|\s*)\s*//;
$argument =~ s/^(const(?=\s)|CONST(?=\s)|__const(?=\s)|__restrict(?=\s)|\s*)\s*//;
if($argument =~ /^\.\.\.$/) {
$argument_type = "...";
$argument_name = "...";
} elsif($argument =~ /^
((?:struct\s+|union\s+|enum\s+|(?:signed\s+|unsigned\s+)
(?:short\s+(?=int)|long\s+(?=int))?)?(?:\w+(?:\:\:)?)+)\s*
((?:const(?=\s)|CONST(?=\s)|__const(?=\s)|__restrict(?=\s))?\s*(?:\*\s*?)*)\s*
(?:const(?=\s)|CONST(?=\s)|__const(?=\s)|__restrict(?=\s))?\s*
(\w*)\s*
(?:\[\]|\s+OPTIONAL)?/x)
{
$argument_type = "$1";
if($2 ne "") {
$argument_type .= " $2";
}
$argument_name = $3;
$argument_type =~ s/\s*const\s*/ /;
$argument_type =~ s/^\s*(.*?)\s*$/$1/;
$argument_name =~ s/^\s*(.*?)\s*$/$1/;
} else {
die "$file: $.: syntax error: '$argument'\n";
}
$argument_types[$n] = $argument_type;
$argument_names[$n] = $argument_name;
# print " " . ($n + 1) . ": '$argument_type': '$argument_name'\n";
}
if($#argument_types == 0 && $argument_types[0] =~ /^void$/i) {
$#argument_types = -1;
$#argument_names = -1;
}
@arguments = ();
foreach my $n (0..$#argument_types) {
if($argument_names[$n] && $argument_names[$n] ne "...") {
if($argument_types[$n] !~ /\*$/) {
$arguments[$n] = $argument_types[$n] . " " . $argument_names[$n];
} else {
$arguments[$n] = $argument_types[$n] . $argument_names[$n];
}
} else {
$arguments[$n] = $argument_types[$n];
}
}
$arguments = join(", ", @arguments);
if(!$arguments) { $arguments = "void"; }
if((!$invert && $name =~ /$pattern/) || ($invert && $name !~ /$pattern/)) {
if($calling_convention) {
print "$return_type $calling_convention $name($arguments)\n";
} else {
if($return_type =~ /\*$/) {
print "$return_type$name($arguments)\n";
} else {
print "$return_type $name($arguments)\n";
}
}
}
} elsif(/\'[^\']*\'/s) {
$_ = $'; $again = 1;
} elsif(/\"[^\"]*\"/s) {
$_ = $'; $again = 1;
} elsif(/;/s) {
$_ = $'; $again = 1;
} elsif(/extern\s+"C"\s+{/s) {
$_ = $'; $again = 1;
} elsif(/\{/s) {
$_ = $'; $again = 1;
$level++;
} else {
$lookahead = 1;
}
}
close(IN);
}

257
tools/specmaker/main.c Normal file
View File

@ -0,0 +1,257 @@
/*
* Option processing and main()
*
* Copyright 2000 Jon Griffiths
*/
#include "specmaker.h"
_globals globals; /* All global variables */
static void do_include (const char *arg)
{
globals.directory = arg;
globals.do_code = 1;
}
static inline const char* strip_ext (const char *str)
{
char *ext = strstr(str, ".dll");
if (ext)
return str_substring (str, ext);
else
return strdup (str);
}
static void do_name (const char *arg)
{
globals.dll_name = strip_ext (arg);
}
static void do_input (const char *arg)
{
globals.input_name = strip_ext (arg);
}
static void do_code (void)
{
globals.do_code = 1;
}
static void do_trace (void)
{
globals.do_trace = 1;
globals.do_code = 1;
}
static void do_forward (const char *arg)
{
globals.forward_dll = arg;
globals.do_trace = 1;
globals.do_code = 1;
}
static void do_document (void)
{
globals.do_documentation = 1;
}
static void do_cdecl (void)
{
globals.do_cdecl = 1;
}
static void do_quiet (void)
{
globals.do_quiet = 1;
}
static void do_start (const char *arg)
{
globals.start_ordinal = atoi (arg);
if (!globals.start_ordinal)
fatal ("Invalid -s option (must be numeric)");
}
static void do_end (const char *arg)
{
globals.end_ordinal = atoi (arg);
if (!globals.end_ordinal)
fatal ("Invalid -e option (must be numeric)");
}
static void do_verbose (void)
{
globals.do_verbose = 1;
}
struct option
{
const char *name;
int has_arg;
void (*func) ();
const char *usage;
};
static const struct option option_table[] = {
{"-d", 1, do_input, "-d dll Use dll for input file (mandatory)"},
{"-h", 0, do_usage, "-h Display this help message"},
{"-I", 1, do_include, "-I dir Look for prototypes in 'dir' (implies -c)"},
{"-o", 1, do_name, "-o name Set the output dll name (default: dll)"},
{"-c", 0, do_code, "-c Generate skeleton code (requires -I)"},
{"-t", 0, do_trace, "-t TRACE arguments (implies -c)"},
{"-f", 1, do_forward, "-f dll Forward calls to 'dll' (implies -t)"},
{"-D", 0, do_document, "-D Generate documentation"},
{"-C", 0, do_cdecl, "-C Assume __cdecl calls (default: __stdcall)"},
{"-s", 1, do_start, "-s num Start prototype search after symbol 'num'"},
{"-e", 1, do_end, "-e num End prototype search after symbol 'num'"},
{"-q", 0, do_quiet, "-q Don't show progress (quiet)."},
{"-v", 0, do_verbose, "-v Show lots of detail while working (verbose)."},
{NULL, 0, NULL, NULL}
};
void do_usage (void)
{
const struct option *opt;
printf ("Usage: specmaker [options] -d dll\n\nOptions:\n");
for (opt = option_table; opt->name; opt++)
printf (" %s\n", opt->usage);
puts ("\n");
exit (1);
}
/*******************************************************************
* parse_options
*
* Parse options from the argv array
*/
static void parse_options (char *argv[])
{
const struct option *opt;
char *const *ptr;
const char *arg = NULL;
ptr = argv + 1;
while (*ptr != NULL)
{
for (opt = option_table; opt->name; opt++)
{
if (opt->has_arg && !strncmp (*ptr, opt->name, strlen (opt->name)))
{
arg = *ptr + strlen (opt->name);
if (*arg == '\0')
{
ptr++;
arg = *ptr;
}
break;
}
if (!strcmp (*ptr, opt->name))
{
arg = NULL;
break;
}
}
if (!opt->name)
fatal ("Unrecognized option");
if (opt->has_arg && arg != NULL)
opt->func (arg);
else
opt->func ("");
ptr++;
}
if (globals.do_code && !globals.directory)
fatal ("-I must be used if generating code");
if (!globals.input_name)
fatal ("Option -d is mandatory");
if (VERBOSE && QUIET)
fatal ("Options -v and -q are mutually exclusive");
}
/*******************************************************************
* main
*/
#ifdef __GNUC__
int main (int argc __attribute__((unused)), char *argv[])
#else
int main (int argc, char *argv[])
#endif
{
parsed_symbol symbol;
int count = 0;
parse_options (argv);
dll_open (globals.input_name);
output_spec_preamble ();
output_header_preamble ();
output_c_preamble ();
memset (&symbol, 0, sizeof (parsed_symbol));
while ((symbol.symbol = dll_next_symbol ()))
{
count++;
if (NORMAL)
printf ("Export %3d - '%s' ...%c", count, symbol.symbol,
VERBOSE ? '\n' : ' ');
if (globals.do_code && count >= globals.start_ordinal
&& (!globals.end_ordinal || count <= globals.end_ordinal))
{
/* Attempt to get information about the symbol */
int result = symbol_demangle (&symbol);
if (result)
result = symbol_search (&symbol);
if (!result)
/* Clean up the prototype */
symbol_clean_string (symbol.function_name);
if (NORMAL)
puts (result ? "[Not Found]" : "[OK]");
}
else if (NORMAL)
puts ("[Ignoring]");
output_spec_symbol (&symbol);
output_header_symbol (&symbol);
output_c_symbol (&symbol);
symbol_clear (&symbol);
}
output_makefile ();
output_install_script ();
if (VERBOSE)
puts ("Finished, Cleaning up...");
return 0;
}

210
tools/specmaker/misc.c Normal file
View File

@ -0,0 +1,210 @@
/*
* Misc functions
*
* Copyright 2000 Jon Griffiths
*/
#include "specmaker.h"
/*******************************************************************
* str_create
*
* Create a single string from many substrings
*/
char *str_create(size_t num_str, ...)
{
va_list args;
size_t len = 1, i = 0;
char *tmp, *t;
va_start (args, num_str);
for (i = 0; i < num_str; i++)
if ((t = va_arg(args, char *)))
len += strlen (t);
va_end (args);
if (!(tmp = (char *) malloc (len)))
fatal ("Out of memory");
tmp[0] = '\0';
va_start (args, num_str);
for (i = 0; i < num_str; i++)
if ((t = va_arg(args, char *)))
strcat (tmp, t);
va_end (args);
return tmp;
}
/*******************************************************************
* str_create_num
*
* Create a single string from many substrings, terminating in a number
*/
char *str_create_num(size_t num_str, int num, ...)
{
va_list args;
size_t len = 8, i = 0;
char *tmp, *t;
va_start (args, num);
for (i = 0; i < num_str; i++)
if ((t = va_arg(args, char *)))
len += strlen (t);
va_end (args);
if (!(tmp = (char *) malloc (len)))
fatal ("Out of memory");
tmp[0] = '\0';
va_start (args, num);
for (i = 0; i < num_str; i++)
if ((t = va_arg(args, char *)))
strcat (tmp, t);
va_end (args);
sprintf (tmp + len - 8, "%d", num);
return tmp;
}
/*******************************************************************
* str_substring
*
* Create a new substring from a string
*/
char *str_substring(const char *start, const char *end)
{
char *newstr;
assert (start && end && end > start);
if (!(newstr = (char *) malloc (end - start + 1)))
fatal ("Out of memory");
memcpy (newstr, start, end - start);
newstr [end - start] = '\0';
return newstr;
}
/*******************************************************************
* str_replace
*
* Swap two strings in another string, in place
* Modified PD code from 'snippets'
*/
char *str_replace (char *str, const char *oldstr, const char *newstr)
{
int oldlen, newlen;
char *p, *q;
if (!(p = strstr(str, oldstr)))
return p;
oldlen = strlen (oldstr);
newlen = strlen (newstr);
memmove (q = p + newlen, p + oldlen, strlen (p + oldlen) + 1);
memcpy (p, newstr, newlen);
return q;
}
/*******************************************************************
* str_match
*
* Locate one string in another, ignoring spaces
*/
const char *str_match (const char *str, const char *match, int *found)
{
assert(str && match && found);
for (; *str == ' '; str++);
if (!strncmp (str, match, strlen (match)))
{
*found = 1;
str += strlen (match);
for (; *str == ' '; str++);
}
else
*found = 0;
return str;
}
/*******************************************************************
* str_find_set
*
* Locate the first occurence of a set of characters in a string
*/
const char *str_find_set (const char *str, const char *findset)
{
assert(str && findset);
while (*str)
{
const char *p = findset;
while (*p)
if (*p++ == *str)
return str;
str++;
}
return NULL;
}
/*******************************************************************
* str_toupper
*
* Uppercase a string
*/
char *str_toupper (char *str)
{
char *save = str;
while (*str)
{
*str = toupper (*str);
str++;
}
return save;
}
/*******************************************************************
* open_file
*
* Open a file returning only on success
*/
FILE *open_file (const char *name, const char *ext, const char *mode)
{
char fname[128];
FILE *fp;
if (((unsigned)snprintf (fname, sizeof (fname), "%s%s%s",
*mode == 'w' ? "./" : "", name, ext) > sizeof (fname)))
fatal ("File name too long");
if (VERBOSE)
printf ("Open file %s\n", fname);
fp = fopen (fname, mode);
if (!fp)
fatal ("Cant open file");
return fp;
}
/*******************************************************************
* fatal
*
* Fatal error handling
*/
void fatal (const char *message)
{
if (errno)
perror (message);
else
puts (message);
do_usage ();
}

560
tools/specmaker/msmangle.c Normal file
View File

@ -0,0 +1,560 @@
/*
* Demangle VC++ symbols into C function prototypes
*
* Copyright 2000 Jon Griffiths
*/
#include "specmaker.h"
/* Type for parsing mangled types */
typedef struct _compound_type
{
char dest_type;
int flags;
int have_qualifiers;
char *expression;
} compound_type;
/* Initialise a compound type structure */
#define INIT_CT(ct) do { memset (&ct, 0, sizeof (ct)); } while (0)
/* free the memory used by a compound structure */
#define FREE_CT(ct) do { if (ct.expression) free (ct.expression); } while (0)
/* Internal functions */
static char *demangle_datatype (char **str, compound_type *ct,
parsed_symbol* sym);
static char *get_constraints_convention_1 (char **str, compound_type *ct);
static char *get_constraints_convention_2 (char **str, compound_type *ct);
static char *get_type_string (const char c, const int constraints);
static int get_type_constant (const char c, const int constraints);
static char *get_pointer_type_string (compound_type *ct,
const char *expression);
/*******************************************************************
* demangle_symbol
*
* Demangle a C++ linker symbol into a C prototype
*/
int symbol_demangle (parsed_symbol *sym)
{
compound_type ct;
int is_static = 0, is_const = 0;
char *function_name = NULL;
char *class_name = NULL;
char *name;
static unsigned int hash = 0; /* In case of overloaded functions */
assert (globals.do_code);
assert (sym && sym->symbol);
hash++;
/* MS mangled names always begin with '?' */
name = sym->symbol;
if (*name++ != '?')
return -1;
if (VERBOSE)
puts ("Attempting to demangle symbol");
/* Then function name or operator code */
if (*name == '?')
{
/* C++ operator code (one character, or two if the first is '_') */
switch (*++name)
{
case '0': function_name = strdup ("ctor"); break;
case '1': function_name = strdup ("dtor"); break;
case '2': function_name = strdup ("operator_new"); break;
case '3': function_name = strdup ("operator_delete"); break;
case '4': function_name = strdup ("operator_equals"); break;
case '5': function_name = strdup ("operator_5"); break;
case '6': function_name = strdup ("operator_6"); break;
case '7': function_name = strdup ("operator_7"); break;
case '8': function_name = strdup ("operator_equals_equals"); break;
case '9': function_name = strdup ("operator_not_equals"); break;
case 'E': function_name = strdup ("operator_plus_plus"); break;
case 'H': function_name = strdup ("operator_plus"); break;
case '_':
/* FIXME: Seems to be some kind of escape character - overloads? */
switch (*++name)
{
case '7': /* FIXME: Compiler generated default copy/assignment ctor? */
return -1;
case 'E': function_name = strdup ("_unknown_E"); break;
case 'G': function_name = strdup ("_unknown_G"); break;
default:
return -1;
}
break;
default:
/* FIXME: Other operators */
return -1;
}
name++;
}
else
{
/* Type or function name terminated by '@' */
function_name = name;
while (*name && *name++ != '@') ;
if (!*name)
return -1;
function_name = str_substring (function_name, name - 1);
}
/* Either a class name, or '@' if the symbol is not a class member */
if (*name == '@')
{
class_name = strdup ("global"); /* Non member function (or a datatype) */
name++;
}
else
{
/* Class the function is associated with, terminated by '@@' */
class_name = name;
while (*name && *name++ != '@') ;
if (*name++ != '@')
return -1;
class_name = str_substring (class_name, name - 2);
}
/* Note: This is guesswork on my part, but it seems to work:
* 'Q' Means the function is passed an implicit 'this' pointer.
* 'S' Means static member function, i.e. no implicit 'this' pointer.
* 'Y' Is used for datatypes and functions, so there is no 'this' pointer.
* This character also implies some other things:
* 'Y','S' = The character after the calling convention is always the
* start of the return type code.
* 'Q' Character after the calling convention is 'const'ness code
* (only non static member functions can be const).
* 'U' also occurs, it seems to behave like Q, but probably implies
* something else.
*/
switch(*name++)
{
case 'U' :
case 'Q' :
/* Implicit 'this' pointer */
sym->arg_text [sym->argc] = str_create (3, "struct ", class_name, " *");
sym->arg_type [sym->argc] = ARG_POINTER;
sym->arg_flag [sym->argc] = 0;
sym->arg_name [sym->argc++] = strdup ("_this");
/* New struct definitions can be 'grep'ed out for making a fixup header */
if (VERBOSE)
printf ("struct %s { int _FIXME; };\n", class_name);
break;
case 'S' :
is_static = 1;
break;
case 'Y' :
break;
default:
return -1;
}
/* Next is the calling convention */
switch (*name++)
{
case 'A':
sym->calling_convention = strdup ("__cdecl");
break;
case 'B': /* FIXME: Something to do with __declspec(dllexport)? */
case 'I': /* __fastcall */
case 'G':
sym->calling_convention = strdup ("__stdcall");
break;
default:
return -1;
}
/* If the symbol is associated with a class, its 'const' status follows */
if (sym->argc)
{
if (*name == 'B')
is_const = 1;
else if (*name != 'E')
return -1;
name++;
}
/* Return type, or @ if 'void' */
if (*name == '@')
{
sym->return_text = strdup ("void");
sym->return_type = ARG_VOID;
name++;
}
else
{
INIT_CT (ct);
if (!demangle_datatype (&name, &ct, sym))
return -1;
sym->return_text = ct.expression;
sym->return_type = get_type_constant(ct.dest_type, ct.flags);
ct.expression = NULL;
FREE_CT (ct);
}
/* Now come the function arguments */
while (*name && *name != 'Z')
{
/* Decode each data type and append it to the argument list */
if (*name != '@')
{
INIT_CT (ct);
if (!demangle_datatype(&name, &ct, sym))
return -1;
if (strcmp (ct.expression, "void"))
{
sym->arg_text [sym->argc] = ct.expression;
ct.expression = NULL;
sym->arg_type [sym->argc] = get_type_constant (ct.dest_type, ct.flags);
sym->arg_flag [sym->argc] = ct.flags;
sym->arg_name[sym->argc] = str_create_num (1, sym->argc, "arg");
sym->argc++;
}
else
break; /* 'void' terminates an argument list */
FREE_CT (ct);
}
else
name++;
}
while (*name == '@')
name++;
/* Functions are always terminated by 'Z'. If we made it this far and
* Don't find it, we have incorrectly identified a data type.
*/
if (*name != 'Z')
return -1;
/* Note: '()' after 'Z' means 'throws', but we don't care here */
/* Create the function name. Include a unique number because otherwise
* overloaded functions could have the same c signature.
*/
sym->function_name = str_create_num (4, hash, class_name, "_",
function_name, is_static ? "_static" : is_const ? "_const" : "_");
assert (sym->return_text);
assert (sym->calling_convention);
assert (sym->function_name);
free (class_name);
free (function_name);
if (VERBOSE)
puts ("Demangled symbol OK");
return 0;
}
/*******************************************************************
* demangle_datatype
*
* Attempt to demangle a C++ data type, which may be compound.
* a compound type is made up of a number of simple types. e.g:
* char** = (pointer to (pointer to (char)))
*
* Uses a simple recursive descent algorithm that is broken
* and/or incomplete, without a doubt ;-)
*/
static char *demangle_datatype (char **str, compound_type *ct,
parsed_symbol* sym)
{
char *iter;
assert (str && *str);
assert (ct);
iter = *str;
if (!get_constraints_convention_1 (&iter, ct))
return NULL;
switch (*iter)
{
case 'C': case 'D': case 'E': case 'F': case 'G':
case 'H': case 'I': case 'J': case 'K': case 'M':
case 'N': case 'O': case 'X': case 'Z':
/* Simple data types */
ct->dest_type = *iter++;
if (!get_constraints_convention_2 (&iter, ct))
return NULL;
ct->expression = get_type_string (ct->dest_type, ct->flags);
break;
case 'U':
case 'V':
/* Class/struct/union */
ct->dest_type = *iter++;
if (*iter == '0' || *iter == '1')
{
/* Referring to class type (implicit 'this') */
char *stripped;
if (!sym->argc)
return NULL;
iter++;
/* Apply our constraints to the base type (struct xxx *) */
stripped = strdup (sym->arg_text [0]);
if (!stripped)
fatal ("Out of Memory");
/* If we're a reference, re-use the pointer already in the type */
if (!ct->flags & CT_BY_REFERENCE)
stripped[ strlen (stripped) - 2] = '\0'; /* otherwise, strip it */
ct->expression = str_create (2, ct->flags & CT_CONST ? "const " :
ct->flags & CT_VOLATILE ? "volatile " : "", stripped);
free (stripped);
}
else if (*iter == '_')
{
/* The name of the class/struct, followed by '@@' */
char *struct_name = ++iter;
while (*iter && *iter++ != '@') ;
if (*iter++ != '@')
return NULL;
struct_name = str_substring (struct_name, iter - 2);
ct->expression = str_create (4, ct->flags & CT_CONST ? "const " :
ct->flags & CT_VOLATILE ? "volatile " : "", "struct ",
struct_name, ct->flags & CT_BY_REFERENCE ? " *" : "");
free (struct_name);
}
break;
case 'Q': /* FIXME: Array Just treated as pointer currently */
case 'P': /* Pointer */
{
compound_type sub_ct;
INIT_CT (sub_ct);
ct->dest_type = *iter++;
if (!get_constraints_convention_2 (&iter, ct))
return NULL;
/* FIXME: P6 = Function pointer, others who knows.. */
if (isdigit (*iter))
return NULL;
/* Recurse to get the pointed-to type */
if (!demangle_datatype (&iter, &sub_ct, sym))
return NULL;
ct->expression = get_pointer_type_string (ct, sub_ct.expression);
FREE_CT (sub_ct);
}
break;
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
/* Referring back to previously parsed type */
if (sym->argc >= (size_t)('0' - *iter))
return NULL;
ct->dest_type = sym->arg_type ['0' - *iter];
ct->expression = strdup (sym->arg_text ['0' - *iter]);
iter++;
break;
default :
return NULL;
}
if (!ct->expression)
return NULL;
return (char *)(*str = iter);
}
/* Constraints:
* There are two conventions for specifying data type constaints. I
* don't know how the compiler chooses between them, but I suspect it
* is based on ensuring that linker names are unique.
* Convention 1. The data type modifier is given first, followed
* by the data type it operates on. '?' means passed by value,
* 'A' means passed by reference. Note neither of these characters
* is a valid base data type. This is then followed by a character
* specifying constness or volatilty.
* Convention 2. The base data type (which is never '?' or 'A') is
* given first. The character modifier is optionally given after
* the base type character. If a valid character mofifier is present,
* then it only applies to the current data type if the character
* after that is not 'A' 'B' or 'C' (Because this makes a convention 1
* constraint for the next data type).
*
* The conventions are usually mixed within the same symbol.
* Since 'C' is both a qualifier and a data type, I suspect that
* convention 1 allows specifying e.g. 'volatile signed char*'. In
* convention 2 this would be 'CC' which is ambigious (i.e. Is it two
* pointers, or a single pointer + modifier?). In convention 1 it
* is encoded as '?CC' which is not ambigious. This probably
* holds true for some other types as well.
*/
/*******************************************************************
* get_constraints_convention_1
*
* Get type constraint information for a data type
*/
static char *get_constraints_convention_1 (char **str, compound_type *ct)
{
char *iter = *str, **retval = str;
if (ct->have_qualifiers)
return (char *)*str; /* Previously got constraints for this type */
if (*iter == '?' || *iter == 'A')
{
ct->have_qualifiers = 1;
ct->flags |= (*iter++ == '?' ? 0 : CT_BY_REFERENCE);
switch (*iter++)
{
case 'A' :
break; /* non-const, non-volatile */
case 'B' :
ct->flags |= CT_CONST;
break;
case 'C' :
ct->flags |= CT_VOLATILE;
break;
default :
return NULL;
}
}
return (char *)(*retval = iter);
}
/*******************************************************************
* get_constraints_convention_2
*
* Get type constraint information for a data type
*/
static char *get_constraints_convention_2 (char **str, compound_type *ct)
{
char *iter = *str, **retval = str;
/* FIXME: Why do arrays have both convention 1 & 2 constraints? */
if (ct->have_qualifiers && ct->dest_type != 'Q')
return (char *)*str; /* Previously got constraints for this type */
ct->have_qualifiers = 1; /* Even if none, we've got all we're getting */
switch (*iter)
{
case 'A' :
if (iter[1] != 'A' && iter[1] != 'B' && iter[1] != 'C')
iter++;
break;
case 'B' :
ct->flags |= CT_CONST;
iter++;
break;
case 'C' :
/* See note above, if we find 'C' it is _not_ a signed char */
ct->flags |= CT_VOLATILE;
iter++;
break;
}
return (char *)(*retval = iter);
}
/*******************************************************************
* get_type_string
*
* Return a string containing the name of a data type
*/
static char *get_type_string (const char c, const int constraints)
{
char *type_string;
switch (c)
{
case 'C': /* Signed char, fall through */
case 'D': type_string = "char"; break;
case 'E': type_string = "unsigned char"; break;
case 'F': type_string = "short int"; break;
case 'G': type_string = "unsigned short int"; break;
case 'H': type_string = "int"; break;
case 'I': type_string = "unsigned int"; break;
case 'J': type_string = "long"; break;
case 'K': type_string = "unsigned long"; break;
case 'M': type_string = "float"; break;
case 'N': type_string = "double"; break;
case 'O': type_string = "long double"; break;
case 'U':
case 'V': type_string = "struct"; break;
case 'X': return strdup ("void");
case 'Z': return strdup ("...");
default:
return NULL;
}
return str_create (3, constraints & CT_CONST ? "const " :
constraints & CT_VOLATILE ? "volatile " : "", type_string,
constraints & CT_BY_REFERENCE ? " *" : "");
}
/*******************************************************************
* get_type_constant
*
* Get the ARG_* constant for this data type
*/
static int get_type_constant (const char c, const int constraints)
{
/* Any reference type is really a pointer */
if (constraints & CT_BY_REFERENCE)
return ARG_POINTER;
switch (c)
{
case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': case 'I':
case 'J': case 'K':
return ARG_LONG;
case 'M':
return -1; /* FIXME */
case 'N': case 'O':
return ARG_DOUBLE;
case 'P': case 'Q':
return ARG_POINTER;
case 'U': case 'V':
return ARG_STRUCT;
case 'X':
return ARG_VOID;
case 'Z':
default:
return -1;
}
}
/*******************************************************************
* get_pointer_type_string
*
* Return a string containing 'pointer to expression'
*/
static char *get_pointer_type_string (compound_type *ct,
const char *expression)
{
/* FIXME: set a compound flag for bracketing expression if needed */
return str_create (3, ct->flags & CT_CONST ? "const " :
ct->flags & CT_VOLATILE ? "volatile " : "", expression,
ct->flags & CT_BY_REFERENCE ? " **" : " *");
}

548
tools/specmaker/output.c Normal file
View File

@ -0,0 +1,548 @@
/*
* Code generation functions
*
* Copyright 2000 Jon Griffiths
*/
#include "specmaker.h"
/* Output files */
static FILE *specfile = NULL;
static FILE *hfile = NULL;
static FILE *cfile = NULL;
static void output_spec_postamble (void);
static void output_header_postamble (void);
static void output_c_postamble (void);
static void output_prototype (FILE *file, const parsed_symbol *sym);
static void output_c_banner (const parsed_symbol *sym);
static const char *get_format_str (int type);
static const char *get_in_or_out (const parsed_symbol *sym, size_t arg);
/*******************************************************************
* output_spec_preamble
*
* Write the first part of the .spec file
*/
void output_spec_preamble (void)
{
specfile = open_file (OUTPUT_DLL_NAME, ".spec", "w");
atexit (output_spec_postamble);
if (VERBOSE)
puts ("Creating .spec preamble");
fprintf (specfile,
"# Generated from %s.dll by specmaker\nname %s\n"
"type win32\ninit %s_Init\n\nimport kernel32.dll\n"
"import ntdll.dll\n", globals.input_name, OUTPUT_DLL_NAME,
OUTPUT_UC_DLL_NAME);
if (globals.forward_dll)
fprintf (specfile,"#import %s.dll\n", globals.forward_dll);
fprintf (specfile, "\n\ndebug_channels (%s)\n\n", OUTPUT_DLL_NAME);
}
/*******************************************************************
* output_spec_symbol
*
* Write a symbol to the .spec file
*/
void output_spec_symbol (const parsed_symbol *sym)
{
assert (specfile);
assert (sym && sym->symbol);
if (!globals.do_code || !sym->function_name)
{
if (globals.forward_dll)
fprintf (specfile, "@ forward %s %s.%s\n", sym->symbol,
globals.forward_dll, sym->symbol);
else
{
if (!symbol_is_valid_c (sym))
fputc ('#', specfile);
fprintf (specfile, "@ stub %s\n", sym->symbol);
}
}
else
{
unsigned int i;
fprintf (specfile, "@ %s %s(", sym->varargs ? "varargs" :
symbol_is_cdecl (sym) ? "cdecl" : "stdcall", sym->symbol);
for (i = 0; i < sym->argc; i++)
fprintf (specfile, " %s", symbol_get_spec_type(sym, i));
if (sym->argc)
fputc (' ', specfile);
fprintf (specfile, ") %s_%s\n", OUTPUT_UC_DLL_NAME, sym->function_name);
}
}
/*******************************************************************
* output_spec_postamble
*
* Write the last part of the .spec file
*/
static void output_spec_postamble (void)
{
if (specfile)
fclose (specfile);
specfile = NULL;
}
/*******************************************************************
* output_header_preamble
*
* Write the first part of the .h file
*/
void output_header_preamble (void)
{
hfile = open_file (OUTPUT_DLL_NAME, "_dll.h", "w");
atexit (output_header_postamble);
fprintf (hfile,
"/*\n * %s.dll\n *\n * Generated from %s.dll by specmaker.\n *\n"
" * DO NOT SEND GENERATED DLLS FOR INCLUSION INTO WINE !\n * \n */"
"\n#ifndef __WINE_%s_DLL_H\n#define __WINE_%s_DLL_H\n\n#include "
"\"config.h\"\n#include \"windef.h\"\n#include \"debugtools.h\"\n"
"#include \"winbase.h\"\n#include \"winnt.h\"\n\n\n",
OUTPUT_DLL_NAME, OUTPUT_DLL_NAME, OUTPUT_UC_DLL_NAME,
OUTPUT_UC_DLL_NAME);
}
/*******************************************************************
* output_header_symbol
*
* Write a symbol to the .h file
*/
void output_header_symbol (const parsed_symbol *sym)
{
assert (hfile);
assert (sym && sym->symbol);
if (!globals.do_code)
return;
if (!sym->function_name)
fprintf (hfile, "/* %s %s_%s(); */\n", CALLING_CONVENTION,
OUTPUT_UC_DLL_NAME, sym->symbol);
else
{
output_prototype (hfile, sym);
fputs (";\n", hfile);
}
}
/*******************************************************************
* output_header_postamble
*
* Write the last part of the .h file
*/
static void output_header_postamble (void)
{
if (hfile)
{
fprintf (hfile, "\n\n\n#endif\t/* __WINE_%s_DLL_H */\n",
OUTPUT_UC_DLL_NAME);
fclose (hfile);
hfile = NULL;
}
}
/*******************************************************************
* output_c_preamble
*
* Write the first part of the .c file
*/
void output_c_preamble (void)
{
cfile = open_file (OUTPUT_DLL_NAME, "_main.c", "w");
atexit (output_c_postamble);
fprintf (cfile,
"/*\n * %s.dll\n *\n * Generated from %s.dll by specmaker.\n *\n"
" * DO NOT SUBMIT GENERATED DLLS FOR INCLUSION INTO WINE!\n * \n */"
"\n\n#include \"%s_dll.h\"\n\nDEFAULT_DEBUG_CHANNEL(%s);\n\n",
OUTPUT_DLL_NAME, globals.input_name, OUTPUT_DLL_NAME,
OUTPUT_DLL_NAME);
if (globals.forward_dll)
{
if (VERBOSE)
puts ("Creating a forwarding DLL");
fputs ("\nHMODULE hDLL=0;\t/* DLL to call */\n\n\n", cfile);
}
fprintf (cfile,
"BOOL WINAPI %s_Init(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID "
"lpvReserved)\n{\n\tTRACE(\"(0x%%08x, %%ld, %%p)\\n\",hinstDLL,"
"fdwReason,lpvReserved);\n\n\t"
"if (fdwReason == DLL_PROCESS_ATTACH)\n\t{\n\t\t",
OUTPUT_UC_DLL_NAME);
if (globals.forward_dll)
{
fprintf (cfile,
"hDLL = LoadLibraryA( \"%s\" );\n\t\t"
"TRACE(\":Forwarding DLL (%s) loaded (%%ld)\\n\",(LONG)hDLL);",
globals.forward_dll, globals.forward_dll);
}
else
fputs ("/* FIXME: Initialisation */", cfile);
fputs ("\n\t}\n\telse if (fdwReason == DLL_PROCESS_DETACH)\n\t{\n\t\t",
cfile);
if (globals.forward_dll)
{
fprintf (cfile,
"FreeLibrary( hDLL );\n\t\tTRACE(\":Forwarding DLL (%s)"
" freed\\n\");", globals.forward_dll);
}
else
fputs ("/* FIXME: Cleanup */", cfile);
fputs ("\n\t}\n\n\treturn TRUE;\n}\n\n\n", cfile);
}
/*******************************************************************
* output_c_symbol
*
* Write a symbol to the .c file
*/
void output_c_symbol (const parsed_symbol *sym)
{
unsigned int i;
int is_void;
assert (cfile);
assert (sym && sym->symbol);
if (!globals.do_code)
return;
output_c_banner(sym);
if (!sym->function_name)
{
/* #ifdef'd dummy */
fprintf (cfile, "#if 0\n%s %s_%s()\n{\n\t%s in .spec */\n}\n#endif\n\n\n",
CALLING_CONVENTION, OUTPUT_UC_DLL_NAME, sym->symbol,
globals.forward_dll ? "/* @forward" : "/* @stub");
return;
}
is_void = !strcmp (sym->return_text, "void");
output_prototype (cfile, sym);
fputs ("\n{\n", cfile);
if (!globals.do_trace)
{
fputs ("\tFIXME(\":stub\\n\");\n", cfile);
if (!is_void)
fprintf (cfile, "\treturn (%s) 0;\n", sym->return_text);
fputs ("}\n\n\n", cfile);
return;
}
/* Tracing, maybe forwarding as well */
if (globals.forward_dll)
{
/* Write variables for calling */
fprintf (cfile, "\t%s (%s *pFunc)(", sym->return_text,
sym->calling_convention);
for (i = 0; i < sym->argc; i++)
fprintf (cfile, "%s%s", i ? ", " : "", sym->arg_text [i]);
fprintf (cfile, "%s)=(void*)GetProcAddress(hDLL,\"%s\");\n%s",
sym->varargs ? ",..." : sym->argc ? "" : "void", sym->symbol,
sym->varargs ? "\tva_list valist;\n" : "");
if (!is_void)
fprintf (cfile, "\t%s retVal;\n", sym->return_text);
}
/* TRACE input arguments */
fprintf (cfile, "\tTRACE(\"(%s", !sym->argc ? "void" : "");
for (i = 0; i < sym->argc; i++)
fprintf (cfile, "%s(%s)%s", i ? "," : "", sym->arg_text [i],
get_format_str (sym->arg_type [i]));
fprintf (cfile, "%s): %s\\n\"", sym->varargs ? ",..." : "",
globals.forward_dll ? "forward" : "stub");
for (i = 0; i < sym->argc; i++)
if (sym->arg_type[i] != ARG_STRUCT)
fprintf(cfile, ",%s%s%s%s", sym->arg_type[i] == ARG_LONG ? "(LONG)" : "",
sym->arg_type[i] == ARG_WIDE_STRING ? "debugstr_w(" : "",
sym->arg_name[i],
sym->arg_type[i] == ARG_WIDE_STRING ? ")" : "");
fputs (");\n", cfile);
if (!globals.forward_dll)
{
if (!is_void)
fprintf (cfile, "\treturn (%s) 0;\n", sym->return_text);
fputs ("}\n\n\n", cfile);
return;
}
/* Call the DLL */
if (sym->varargs)
fprintf (cfile, "\tva_start(valist,%s);\n", sym->arg_name[sym->argc-1]);
fprintf (cfile, "\t%spFunc(", !is_void ? "retVal = " : "");
for (i = 0; i < sym->argc; i++)
fprintf (cfile, "%s%s", i ? "," : "", sym->arg_name [i]);
fputs (sym->varargs ? ",valist);\n\tva_end(valist);" : ");", cfile);
/* TRACE return value */
fprintf (cfile, "\n\tTRACE(\"Returned (%s)\\n\"",
get_format_str (sym->return_type));
if (!is_void)
{
if (sym->return_type == ARG_WIDE_STRING)
fputs (",debugstr_w(retVal)", cfile);
else
fprintf (cfile, ",%s%s", sym->return_type == ARG_LONG ? "(LONG)" : "",
sym->return_type == ARG_STRUCT ? "" : "retVal");
fputs (");\n\treturn retVal;\n", cfile);
}
else
fputs (");\n", cfile);
fputs ("}\n\n\n", cfile);
}
/*******************************************************************
* output_c_postamble
*
* Write the last part of the .c file
*/
static void output_c_postamble (void)
{
if (cfile)
fclose (cfile);
cfile = NULL;
}
/*******************************************************************
* output_makefile
*
* Write a Wine compatable makefile.in
*/
void output_makefile (void)
{
FILE *makefile = open_file ("Makefile", ".in", "w");
if (VERBOSE)
puts ("Creating makefile");
fprintf (makefile,
"# Generated from %s.dll by specmaker.\nTOPSRCDIR = @top_srcdir@\n"
"TOPOBJDIR = ../..\nSRCDIR = @srcdir@\nVPATH = @srcdir@\n"
"MODULE = %s\nEXTRALIBS = $(LIBUNICODE)\n\n"
"LDDLLFLAGS = @LDDLLFLAGS@\nSYMBOLFILE = $(MODULE).tmp.o\n\n"
"C_SRCS = \\\n\t%s_main.c\n\n@MAKE_DLL_RULES@\n\n### Dependencies:",
globals.input_name, OUTPUT_DLL_NAME, OUTPUT_DLL_NAME);
fclose (makefile);
}
/*******************************************************************
* output_install_script
*
* Write a script to insert the DLL into Wine
*
* Rather than using diff/patch, several sed calls are generated
* so the script can be re-run at any time without breaking.
*/
void output_install_script (void)
{
char cmd[128];
FILE *install_file = open_file (OUTPUT_DLL_NAME, "_install", "w");
if (VERBOSE)
puts ("Creating install script");
fprintf (install_file,
"#!/bin/bash\n# Generated from %s.dll by specmaker.\n\n"
"if [ $# -ne 1 ] || [ ! -d $1 ] || [ ! -f"
" $1/AUTHORS ]; then\n\t[ $# -eq 1 ] && echo \"Invalid path\"\n"
"\techo \"Usage: $0 wine-base-dir\"\n\texit 1\nfi\n\n"
"if [ -d $1/dlls/%s ]; then\n\techo \"DLL is already present\"\n"
"\texit 1\nfi\n\necho Adding DLL %s to Wine build tree...\n"
"echo\n\nmkdir $1/dlls/%s\ncp %s.spec $1/dlls/%s\n"
"cp %s_main.c $1/dlls/%s\ncp %s_dll.h $1/dlls/%s\n"
"cp Makefile.in $1/dlls/%s\necho Copied DLL files\n\n"
"cd $1\n\nsed '/dlls\\/"
"x11drv\\/Makefile/{G;s/$/dlls\\/%s\\/Makefile/;}' configure.in"
" >t.tmp\nmv -f t.tmp configure.in\necho Patched configure.in\n\n"
"sed '/ws2_32/{G;s/$/\\^%s \\\\/;}' Make.rules.in | tr ^ \\\\t"
" >t.tmp\nmv -f t.tmp Make.rules.in\necho Patched Make.rules.in"
"\n\nsed '/DLLFILES =/{G;s/$/\\^%s\\/lib%s.so \\\\/;}'"
" dlls/Makefile.in| tr ^ \\\\t >t.tmp\n"
"sed '/SUBDIRS =/{G;s/$/\\^%s \\\\/;}' t.tmp | tr ^ \\\\t >t.tmp2"
"\nsed '/Map library name /{G;s/$/^\\$(RM) \\$\\@ \\&\\& \\$\\"
"(LN_S\\) %s\\/lib%s.\\@LIBEXT\\@ \\$\\@/;}' t.tmp2 | tr ^ \\\\t"
" > t.tmp\nsed '/Map library name /{G;s/$/lib%s.\\@LIBEXT\\@: "
"%s\\/lib%s.\\@LIBEXT\\@/;}' t.tmp > t.tmp2\nsed '/dll "
"dependencies /{G;s/$/%s\\/lib%s.\\@LIBEXT\\@\\: libkernel32."
"\\@LIBEXT\\@ libntdll.\\@LIBEXT\\@/;}' t.tmp2 > t.tmp\n"
"mv -f t.tmp dlls/Makefile.in\nrm -f t.tmp2\necho Patched dlls/"
"Makefile.in\n\necho\necho ...done.\necho Run \\'autoconf\\', "
"\\'./configure\\' then \\'make\\' to rebuild Wine\n\n",
OUTPUT_DLL_NAME, OUTPUT_DLL_NAME, OUTPUT_DLL_NAME, OUTPUT_DLL_NAME,
OUTPUT_DLL_NAME, OUTPUT_DLL_NAME, OUTPUT_DLL_NAME, OUTPUT_DLL_NAME,
OUTPUT_DLL_NAME, OUTPUT_DLL_NAME, OUTPUT_DLL_NAME, OUTPUT_DLL_NAME,
OUTPUT_DLL_NAME, OUTPUT_DLL_NAME, OUTPUT_DLL_NAME, OUTPUT_DLL_NAME,
OUTPUT_DLL_NAME, OUTPUT_DLL_NAME, OUTPUT_DLL_NAME, OUTPUT_DLL_NAME,
OUTPUT_DLL_NAME, OUTPUT_DLL_NAME, OUTPUT_DLL_NAME);
fclose (install_file);
snprintf (cmd, sizeof (cmd), "chmod a+x %s_install", OUTPUT_DLL_NAME);
system (cmd);
}
/*******************************************************************
* output_prototype
*
* Write a C prototype for a parsed symbol
*/
static void output_prototype (FILE *file, const parsed_symbol *sym)
{
unsigned int i;
fprintf (file, "%s %s %s_%s(", sym->return_text, sym->calling_convention,
OUTPUT_UC_DLL_NAME, sym->function_name);
if (!sym->argc)
fputs ("void", file);
else
for (i = 0; i < sym->argc; i++)
fprintf (file, "%s%s %s", i ? ", " : "", sym->arg_text [i],
sym->arg_name [i]);
if (sym->varargs)
fputs (", ...", file);
fputc (')', file);
}
/*******************************************************************
* output_c_banner
*
* Write a function banner to the .c file
*/
void output_c_banner (const parsed_symbol *sym)
{
size_t i;
fprintf (cfile, "/*********************************************************"
"*********\n *\t\t%s (%s.@)\n *\n", sym->symbol,
OUTPUT_UC_DLL_NAME);
if (globals.do_documentation && sym->function_name)
{
fputs (" *\n * PARAMS\n *\n", cfile);
if (!sym->argc)
fputs (" * None.\n *\n", cfile);
else
{
for (i = 0; i < sym->argc; i++)
fprintf (cfile, " * %s [%s]%s\n", sym->arg_name [i],
get_in_or_out(sym, i),
strcmp (sym->arg_name [i], "_this") ? "" :
" Pointer to the class object");
if (sym->varargs)
fputs (" * ...[I]\n", cfile);
fputs (" *\n", cfile);
}
fputs (" * RETURNS\n *\n", cfile);
if (sym->return_text && !strcmp (sym->return_text, "void"))
fputs (" * Nothing.\n", cfile);
else
fprintf (cfile, " * %s\n", sym->return_text);
}
fputs (" *\n */\n", cfile);
}
/*******************************************************************
* get_format_str
*
* Get a string containing the correct format string for a type
*/
static const char *get_format_str (int type)
{
switch (type)
{
case ARG_VOID: return "void";
case ARG_FLOAT: return "%f";
case ARG_DOUBLE: return "%g";
case ARG_POINTER: return "%p";
case ARG_WIDE_STRING:
case ARG_STRING: return "%s";
case ARG_LONG: return "%ld";
case ARG_STRUCT: return "struct";
}
assert (0);
return "";
}
/*******************************************************************
* get_in_or_out
*
* Determin if a parameter is In or In/Out
*/
static const char *get_in_or_out (const parsed_symbol *sym, size_t arg)
{
assert (sym && arg < sym->argc);
assert (globals.do_documentation);
if (sym->arg_flag [arg] & CT_CONST)
return "In";
switch (sym->arg_type [arg])
{
case ARG_FLOAT:
case ARG_DOUBLE:
case ARG_LONG:
case ARG_STRUCT: return "In";
case ARG_POINTER:
case ARG_WIDE_STRING:
case ARG_STRING: return "In/Out";
}
assert (0);
return "";
}

318
tools/specmaker/search.c Normal file
View File

@ -0,0 +1,318 @@
/*
* Prototype search and parsing functions
*
* Copyright 2000 Jon Griffiths
*/
#include "specmaker.h"
static char *grep_buff = NULL;
static char *fgrep_buff = NULL;
static int symbol_from_prototype (parsed_symbol *sym, const char *prototype);
static const char *get_type (parsed_symbol *sym, const char *proto, int arg);
/*******************************************************************
* symbol_search
*
* Call Patrik Stridvall's 'function_grep.pl' script to retrieve a
* function prototype from include file(s)
*/
int symbol_search (parsed_symbol *sym)
{
static const size_t MAX_RESULT_LEN = 1024;
FILE *grep;
int attempt = 0;
assert (globals.do_code);
assert (globals.directory);
assert (sym && sym->symbol);
if (!symbol_is_valid_c (sym))
return - 1;
if (!grep_buff)
grep_buff = (char *) malloc (MAX_RESULT_LEN);
if (!fgrep_buff)
fgrep_buff = (char *) malloc (MAX_RESULT_LEN);
if (!grep_buff || !fgrep_buff)
fatal ("Out of Memory");
/* Use 'grep' to tell us which possible files the function is in,
* then use 'function_grep.pl' to get the prototype. If this fails the
* first time then give grep a more general query (that doesn't
* require an opening argument brace on the line with the function name).
*/
while (attempt < 2)
{
FILE *f_grep;
char *cmd = str_create (4, "grep -d recurse -l \"", sym->symbol,
!attempt ? "[:blank:]*(\" " : "\" ", globals.directory);
if (VERBOSE)
puts (cmd);
fflush (NULL); /* See 'man popen' */
if (!(grep = popen (cmd, "r")))
fatal ("Cannot execute grep -l");
free (cmd);
while (fgets (grep_buff, MAX_RESULT_LEN, grep))
{
int i;
for (i = 0; grep_buff[i] && grep_buff[i] != '\n' ; i++)
;
grep_buff[i] = '\0';
if (VERBOSE)
puts (grep_buff);
cmd = str_create (5, "function_grep.pl ", sym->symbol,
" \"", grep_buff, "\"");
if (VERBOSE)
puts (cmd);
fflush (NULL); /* See 'man popen' */
if (!(f_grep = popen (cmd, "r")))
fatal ("Cannot execute function_grep.pl");
free (cmd);
while (fgets (grep_buff, MAX_RESULT_LEN, f_grep))
{
char *iter = grep_buff;
/* Keep only the first line */
symbol_clean_string(grep_buff);
for (i = 0; grep_buff[i] && grep_buff[i] != '\n' ; i++)
;
grep_buff[i] = '\0';
if (VERBOSE)
puts (grep_buff);
while ((iter = strstr (iter, sym->symbol)))
{
if (iter > grep_buff && iter[-1] == ' ' &&
(iter[strlen (sym->symbol)] == ' ' ||
iter[strlen (sym->symbol)] == '('))
{
if (VERBOSE)
puts ("Prototype looks OK, processing");
if (!symbol_from_prototype (sym, grep_buff))
{
pclose (f_grep);
pclose (grep);
return 0; /* OK */
}
if (VERBOSE)
puts ("Failed, trying next");
}
else
iter += strlen (sym->symbol);
}
}
pclose (f_grep);
}
pclose (grep);
attempt++;
}
return -1; /* Not found */
}
/*******************************************************************
* symbol_from_prototype
*
* Convert a C prototype into a symbol
*/
static int symbol_from_prototype (parsed_symbol *sym, const char *proto)
{
char *iter;
int found;
proto = get_type (sym, proto, -1); /* Get return type */
if (!proto)
return -1;
iter = (char *)str_match (proto, sym->symbol, &found);
if (!found)
{
/* Calling Convention */
iter = strchr (iter, ' ');
if (!iter)
return -1;
sym->calling_convention = str_substring (proto, iter);
iter = (char *)str_match (iter, sym->symbol, &found);
if (!found)
return -1;
}
else
sym->calling_convention = strdup (CALLING_CONVENTION);
sym->function_name = strdup (sym->symbol);
proto = iter;
/* Now should be the arguments */
if (*proto++ != '(')
return -1;
for (; *proto == ' '; proto++);
if (!strncmp (proto, "void", 4))
return 0;
do
{
/* Process next argument */
str_match (proto, "...", &sym->varargs);
if (sym->varargs)
return 0;
if (!(proto = get_type (sym, proto, sym->argc)))
return -1;
sym->argc++;
if (*proto == ',')
proto++;
else if (*proto != ')')
return -1;
} while (*proto != ')');
return 0;
}
/*******************************************************************
* get_type
*
* Read a type from a prototype
*/
static const char *get_type (parsed_symbol *sym, const char *proto, int arg)
{
int is_const, is_volatile, is_struct, is_signed, is_unsigned, ptrs = 0;
char *iter, *type_str, *base_type, *catch_unsigned, dest_type;
assert (sym && sym->symbol);
assert (proto && *proto);
assert (arg < 0 || (unsigned)arg == sym->argc);
type_str = (char *)proto;
proto = str_match (proto, "const", &is_const);
proto = str_match (proto, "volatile", &is_volatile);
proto = str_match (proto, "struct", &is_struct);
if (!is_struct)
proto = str_match (proto, "union", &is_struct);
catch_unsigned = (char *)proto;
proto = str_match (proto, "unsigned", &is_unsigned);
proto = str_match (proto, "signed", &is_signed);
/* Can have 'unsigned const' or 'const unsigned' etc */
if (!is_const)
proto = str_match (proto, "const", &is_const);
if (!is_volatile)
proto = str_match (proto, "volatile", &is_volatile);
base_type = (char *)proto;
iter = (char *)str_find_set (proto, " ,*)");
if (!iter)
return NULL;
if (arg < 0 && (is_signed || is_unsigned))
{
/* Prevent calling convention from being swallowed by 'un/signed' alone */
if (strncmp (base_type, "int", 3) && strncmp (base_type, "long", 4) &&
strncmp (base_type, "short", 5) && strncmp (base_type, "char", 4))
{
iter = (char *)proto;
base_type = catch_unsigned;
}
}
else
catch_unsigned = NULL;
/* FIXME: skip const/volatile here too */
for (proto = iter; *proto; proto++)
if (*proto == '*')
ptrs++;
else if (*proto != ' ')
break;
if (!*proto)
return NULL;
type_str = str_substring (type_str, proto);
if (iter == base_type || catch_unsigned)
{
/* 'unsigned' with no type */
char *tmp = str_create (2, type_str, " int");
free (type_str);
type_str = tmp;
}
symbol_clean_string (type_str);
dest_type = symbol_get_type (type_str);
if (arg < 0)
{
sym->return_text = type_str;
sym->return_type = dest_type;
}
else
{
sym->arg_type [arg] = dest_type;
sym->arg_flag [arg] = is_const ? CT_CONST : is_volatile ? CT_VOLATILE : 0;
if (*proto == ',' || *proto == ')')
sym->arg_name [arg] = str_create_num (1, arg, "arg");
else
{
iter = (char *)str_find_set (proto, " ,)");
if (!iter)
{
free (type_str);
return NULL;
}
sym->arg_name [arg] = str_substring (proto, iter);
proto = iter;
}
sym->arg_text [arg] = type_str;
}
return proto;
}
#ifdef __GNUC__
/*******************************************************************
* search_cleanup
*
* Free memory used while searching (a niceity)
*/
void search_cleanup (void) __attribute__ ((destructor));
void search_cleanup (void)
{
if (grep_buff)
free (grep_buff);
if (fgrep_buff)
free (fgrep_buff);
}
#endif

172
tools/specmaker/specmaker.h Normal file
View File

@ -0,0 +1,172 @@
/*
* Specmaker - A Wine DLL tool
*
* Copyright 2000 Jon Griffiths
*
* References:
* DLL symbol extraction based on file format from alib (anthonyw.cjb.net).
*
* Option processing shamelessly cadged from winebuild (www.winehq.com).
*
* All the cool functionality (prototyping, call tracing, forwarding)
* relies on Patrik Stridvall's 'function_grep.pl' script to work.
*
* http://msdn.microsoft.com/library/periodic/period96/msj/S330.htm
* This article provides both a description and freely downloadble
* implementation, in source code form, of how to extract symbols
* from Win32 PE executables/DLL's.
*
* http://www.kegel.com/mangle.html
* Gives information on the name mangling scheme used by MS compilers,
* used as the starting point for the code here. Contains a few
* mistakes and some incorrect assumptions, but the lists of types
* are pure gold.
*/
#ifndef __WINE_SPECMAKER_H
#define __WINE_SPECMAKER_H
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <assert.h>
#include <stdarg.h>
/* Argument type constants */
#define MAX_FUNCTION_ARGS 32
#define ARG_VOID 0x0
#define ARG_STRING 0x1
#define ARG_WIDE_STRING 0x2
#define ARG_POINTER 0x3
#define ARG_LONG 0x4
#define ARG_DOUBLE 0x5
#define ARG_STRUCT 0x6 /* By value */
#define ARG_FLOAT 0x7
#define ARG_VARARGS 0x8
/* Compound type flags */
#define CT_BY_REFERENCE 0x1
#define CT_VOLATILE 0x2
#define CT_CONST 0x4
/* Structure holding a parsed symbol */
typedef struct __parsed_symbol
{
char *symbol;
char *return_text;
char return_type;
char *calling_convention;
char *function_name;
unsigned int varargs;
unsigned int argc;
char arg_type [MAX_FUNCTION_ARGS];
char arg_flag [MAX_FUNCTION_ARGS];
char *arg_text [MAX_FUNCTION_ARGS];
char *arg_name [MAX_FUNCTION_ARGS];
} parsed_symbol;
/* All globals */
typedef struct __globals
{
/* Options */
int do_code; /* -c, -t, -f */
int do_trace; /* -t, -f */
int do_cdecl; /* -C */
int do_quiet; /* -q */
int do_verbose; /* -v */
int do_documentation; /* -D */
/* Option arguments */
int start_ordinal; /* -s */
int end_ordinal; /* -e */
const char *directory; /* -I */
const char *input_name; /* -d */
const char *forward_dll; /* -f */
const char *dll_name; /* -o */
char *uc_dll_name; /* -o */
} _globals;
extern _globals globals;
/* Names to use for output DLL */
#define OUTPUT_DLL_NAME \
(globals.dll_name ? globals.dll_name : globals.input_name)
#define OUTPUT_UC_DLL_NAME globals.uc_dll_name
/* Verbosity levels */
#define QUIET (globals.do_quiet)
#define NORMAL (!QUIET)
#define VERBOSE (globals.do_verbose)
/* Default calling convention */
#define CALLING_CONVENTION (globals.do_cdecl ? "__cdecl" : "__stdcall")
/* DLL functions */
void dll_open (const char *dll_name);
char *dll_next_symbol (void);
/* Symbol functions */
int symbol_demangle (parsed_symbol *symbol);
int symbol_search (parsed_symbol *symbol);
void symbol_clear(parsed_symbol *sym);
int symbol_is_valid_c(const parsed_symbol *sym);
int symbol_is_cdecl(const parsed_symbol *sym);
const char *symbol_get_spec_type (const parsed_symbol *sym, size_t arg);
void symbol_clean_string (const char *string);
int symbol_get_type (const char *string);
/* Output functions */
void output_spec_preamble (void);
void output_spec_symbol (const parsed_symbol *sym);
void output_header_preamble (void);
void output_header_symbol (const parsed_symbol *sym);
void output_c_preamble (void);
void output_c_symbol (const parsed_symbol *sym);
void output_makefile (void);
void output_install_script (void);
/* Misc functions */
char *str_create (size_t num_str, ...);
char *str_create_num (size_t num_str, int num, ...);
char *str_substring(const char *start, const char *end);
char *str_replace (char *str, const char *oldstr, const char *newstr);
const char *str_match (const char *str, const char *match, int *found);
const char *str_find_set (const char *str, const char *findset);
char *str_toupper (char *str);
FILE *open_file (const char *name, const char *ext, const char *mode);
#ifdef __GNUC__
void do_usage (void) __attribute__ ((noreturn));
#else
void do_usage (void);
#endif
void fatal (const char *message);
#endif /* __WINE_SPECMAKER_H */

286
tools/specmaker/symbol.c Normal file
View File

@ -0,0 +1,286 @@
/*
* Symbol functions
*
* Copyright 2000 Jon Griffiths
*/
#include "specmaker.h"
/* Items that are swapped in arguments after the symbol structure
* has been populated
*/
static const char *swap_after[] =
{
"\r", " ", /* Remove whitespace, normalise pointers and brackets */
"\t", " ",
" ", " ",
" * ", " *",
"* *", "**",
"* ", "*",
" ,", ",",
"( ", "(",
" )", ")",
"wchar_t", "WCHAR", /* Help with Unicode compliles */
"wctype_t", "WCHAR",
"wint_t", "WCHAR",
"unsigned __int64", "__uint64", /* Wine doesn't cope with unsigned i64's */
NULL, NULL
};
/* Items containing these substrings are assumed to be wide character
* strings, unless they contain more that one '*'. A preceeding 'LP'
* counts as a '*', so 'LPWCSTR *' is a pointer, not a string
*/
static const char *wide_strings[] =
{
"WSTR", "WCSTR", NULL
};
/* Items containing these substrings are assumed to be wide characters,
* unless they contain one '*'. A preceeding 'LP' counts as a '*',
* so 'WCHAR *' is string, while 'LPWCHAR *' is a pointer
*/
static const char *wide_chars[] =
{
"WCHAR", NULL
};
/* Items containing these substrings are assumed to be ASCII character
* strings, as above
*/
static const char *ascii_strings[] =
{
"STR", "CSTR", NULL
};
/* Items containing these substrings are assumed to be ASCII characters,
* as above
*/
static const char *ascii_chars[] =
{
"CHAR", "char", NULL
};
/* Any type other than the following will produce a FIXME warning with -v
* when mapped to a long, to allow fixups
*/
static const char *known_longs[] =
{
"char", "CHAR", "float", "int", "INT", "short", "SHORT", "long", "LONG",
"WCHAR", "BOOL", "bool", "INT16", NULL
};
/*******************************************************************
* symbol_clear
*
* Free the memory used by a symbol and initialise it
*/
void symbol_clear(parsed_symbol *sym)
{
int i;
assert (sym);
assert (sym->symbol);
free (sym->symbol);
if (sym->return_text)
free (sym->return_text);
if (sym->calling_convention)
free (sym->calling_convention);
if (sym->function_name)
free (sym->function_name);
for (i = sym->argc - 1; i >= 0; i--)
{
if (sym->arg_text [i])
free (sym->arg_text [i]);
if (sym->arg_name [i])
free (sym->arg_name [i]);
}
memset (sym, 0, sizeof (parsed_symbol));
}
/*******************************************************************
* symbol_is_valid_c
*
* Check if a symbol is a valid C identifier
*/
int symbol_is_valid_c(const parsed_symbol *sym)
{
char *name;
assert (sym);
assert (sym->symbol);
name = sym->symbol;
while (*name)
{
if (!isalnum (*name) && *name != '_')
return 0;
name++;
}
return 1;
}
/*******************************************************************
* symbol_is_cdecl
*
* Check if a symbol is cdecl
*/
int symbol_is_cdecl(const parsed_symbol *sym)
{
assert (sym);
assert (sym->symbol);
if (sym->calling_convention && (strstr (sym->calling_convention, "cdecl")
|| strstr (sym->calling_convention, "CDECL")))
return 1;
else if (!sym->calling_convention)
return globals.do_cdecl;
return 0;
}
/*******************************************************************
* symbol_get_spec_type
*
* Get the .spec file text for a symbols argument
*/
const char *symbol_get_spec_type (const parsed_symbol *sym, size_t arg)
{
assert (arg < sym->argc);
switch (sym->arg_type [arg])
{
case ARG_STRING: return "str";
case ARG_WIDE_STRING: return "wstr";
case ARG_POINTER: return "ptr";
case ARG_DOUBLE: return "double";
case ARG_STRUCT:
case ARG_FLOAT:
case ARG_LONG: return "long";
}
assert (0);
return NULL;
}
/*******************************************************************
* symbol_get_type
*
* Get the ARG_ constant for a type string
*/
int symbol_get_type (const char *string)
{
const char *iter = string;
const char **tab;
int ptrs = 0;
while (*iter)
{
if (*iter == '*' || (*iter == 'L' && iter[1] == 'P')
|| (*iter == '[' && iter[1] == ']'))
ptrs++;
if (ptrs > 1)
return ARG_POINTER;
iter++;
}
/* 0 or 1 pointer */
tab = wide_strings;
while (*tab++)
if (strstr (string, tab[-1]))
{
if (!ptrs) return ARG_WIDE_STRING;
else return ARG_POINTER;
}
tab = wide_chars;
while (*tab++)
if (strstr (string, tab[-1]))
{
if (!ptrs) return ARG_LONG;
else return ARG_WIDE_STRING;
}
tab = ascii_strings;
while (*tab++)
if (strstr (string, tab[-1]))
{
if (!ptrs) return ARG_STRING;
else return ARG_POINTER;
}
tab = ascii_chars;
while (*tab++)
if (strstr (string, tab[-1]))
{
if (!ptrs) return ARG_LONG;
else {
if (!strstr (string, "unsigned")) /* unsigned char * => ptr */
return ARG_STRING;
}
}
if (ptrs)
return ARG_POINTER; /* Pointer to some other type */
/* No pointers */
if (strstr (string, "double"))
return ARG_DOUBLE;
if (strstr (string, "void"))
return ARG_VOID;
if (strstr (string, "struct") || strstr (string, "union"))
return ARG_STRUCT; /* Struct by value, ugh */
if (VERBOSE)
{
int known = 0;
tab = known_longs;
while (*tab++)
if (strstr (string, tab[-1]))
{
known = 1;
break;
}
/* Unknown types passed by value can be 'grep'ed out for fixup later */
if (!known)
printf ("/* FIXME: By value type: Assumed 'int' */ typedef int %s;\n",
string);
}
return ARG_LONG;
}
/*******************************************************************
* symbol_clean_string
*
* Make a type string more Wine-friendly. Logically const :-)
*/
void symbol_clean_string (const char *string)
{
const char **tab = swap_after;
char *str = (char *)string;
#define SWAP(i, p, x, y) do { i = p; while ((i = str_replace (i, x, y))); } while(0)
while (tab [0])
{
char *p;
SWAP (p, str, tab [0], tab [1]);
tab += 2;
}
if (str [strlen (str) - 1] == ' ')
str [strlen (str) - 1] = '\0'; /* no trailing space */
if (*str == ' ')
memmove (str, str + 1, strlen (str)); /* No leading spaces */
}