From c7a3fec5be90414c8aa52f43cbe518c791b11c57 Mon Sep 17 00:00:00 2001 From: Jon Griffiths Date: Thu, 4 Jan 2001 19:45:49 +0000 Subject: [PATCH] Added spec generation tool specmaker. --- configure | 2 + configure.in | 1 + tools/Makefile.in | 4 +- tools/specmaker/.cvsignore | 2 + tools/specmaker/Makefile.in | 35 +++ tools/specmaker/README | 560 +++++++++++++++++++++++++++++++++++++++ tools/specmaker/dll.c | 213 +++++++++++++++ tools/specmaker/function_grep.pl | 270 +++++++++++++++++++ tools/specmaker/main.c | 257 ++++++++++++++++++ tools/specmaker/misc.c | 210 +++++++++++++++ tools/specmaker/msmangle.c | 560 +++++++++++++++++++++++++++++++++++++++ tools/specmaker/output.c | 548 ++++++++++++++++++++++++++++++++++++++ tools/specmaker/search.c | 318 ++++++++++++++++++++++ tools/specmaker/specmaker.h | 172 ++++++++++++ tools/specmaker/symbol.c | 286 ++++++++++++++++++++ 15 files changed, 3437 insertions(+), 1 deletion(-) create mode 100644 tools/specmaker/.cvsignore create mode 100644 tools/specmaker/Makefile.in create mode 100644 tools/specmaker/README create mode 100644 tools/specmaker/dll.c create mode 100755 tools/specmaker/function_grep.pl create mode 100644 tools/specmaker/main.c create mode 100644 tools/specmaker/misc.c create mode 100644 tools/specmaker/msmangle.c create mode 100644 tools/specmaker/output.c create mode 100644 tools/specmaker/search.c create mode 100644 tools/specmaker/specmaker.h create mode 100644 tools/specmaker/symbol.c diff --git a/configure b/configure index 650e96111ec..993c9e93b77 100755 --- a/configure +++ b/configure @@ -6966,6 +6966,7 @@ scheduler/Makefile server/Makefile tools/Makefile tools/cvdump/Makefile +tools/specmaker/Makefile tools/winebuild/Makefile tools/winelauncher tools/wmc/Makefile @@ -7209,6 +7210,7 @@ scheduler/Makefile server/Makefile tools/Makefile tools/cvdump/Makefile +tools/specmaker/Makefile tools/winebuild/Makefile tools/winelauncher tools/wmc/Makefile diff --git a/configure.in b/configure.in index d7e4fe4e947..7fcd9e8cc1a 100644 --- a/configure.in +++ b/configure.in @@ -1284,6 +1284,7 @@ scheduler/Makefile server/Makefile tools/Makefile tools/cvdump/Makefile +tools/specmaker/Makefile tools/winebuild/Makefile tools/winelauncher tools/wmc/Makefile diff --git a/tools/Makefile.in b/tools/Makefile.in index 4e5384bd46a..234fad8f4be 100644 --- a/tools/Makefile.in +++ b/tools/Makefile.in @@ -11,11 +11,13 @@ C_SRCS = makedep.c fnt2bdf.c bin2res.c SUBDIRS = \ cvdump \ + specmaker \ winebuild \ wmc \ wrc INSTALLSUBDIRS = \ + specmaker \ winebuild \ wmc \ wrc @@ -26,7 +28,7 @@ EXTRASUBDIRS = \ winapi_check/win32 \ wineconf.libs -all: $(PROGRAMS) winebuild wmc wrc +all: $(PROGRAMS) specmaker winebuild wmc wrc @MAKE_RULES@ diff --git a/tools/specmaker/.cvsignore b/tools/specmaker/.cvsignore new file mode 100644 index 00000000000..3f5e3b853e4 --- /dev/null +++ b/tools/specmaker/.cvsignore @@ -0,0 +1,2 @@ +Makefile +specmaker diff --git a/tools/specmaker/Makefile.in b/tools/specmaker/Makefile.in new file mode 100644 index 00000000000..e3c7bfdf173 --- /dev/null +++ b/tools/specmaker/Makefile.in @@ -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: diff --git a/tools/specmaker/README b/tools/specmaker/README new file mode 100644 index 00000000000..e720b7922c9 --- /dev/null +++ b/tools/specmaker/README @@ -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 + cd + 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. + diff --git a/tools/specmaker/dll.c b/tools/specmaker/dll.c new file mode 100644 index 00000000000..52ef299c2ce --- /dev/null +++ b/tools/specmaker/dll.c @@ -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; + } +} diff --git a/tools/specmaker/function_grep.pl b/tools/specmaker/function_grep.pl new file mode 100755 index 00000000000..894c4e30972 --- /dev/null +++ b/tools/specmaker/function_grep.pl @@ -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 = )) { + 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); +} diff --git a/tools/specmaker/main.c b/tools/specmaker/main.c new file mode 100644 index 00000000000..2b99e5d25f5 --- /dev/null +++ b/tools/specmaker/main.c @@ -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; +} diff --git a/tools/specmaker/misc.c b/tools/specmaker/misc.c new file mode 100644 index 00000000000..cdb3ae771f4 --- /dev/null +++ b/tools/specmaker/misc.c @@ -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 (); +} diff --git a/tools/specmaker/msmangle.c b/tools/specmaker/msmangle.c new file mode 100644 index 00000000000..b9b68764e98 --- /dev/null +++ b/tools/specmaker/msmangle.c @@ -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 ? " **" : " *"); + +} diff --git a/tools/specmaker/output.c b/tools/specmaker/output.c new file mode 100644 index 00000000000..b966a24dfb6 --- /dev/null +++ b/tools/specmaker/output.c @@ -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 ""; +} \ No newline at end of file diff --git a/tools/specmaker/search.c b/tools/specmaker/search.c new file mode 100644 index 00000000000..289b8721230 --- /dev/null +++ b/tools/specmaker/search.c @@ -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 + diff --git a/tools/specmaker/specmaker.h b/tools/specmaker/specmaker.h new file mode 100644 index 00000000000..0fc636771a6 --- /dev/null +++ b/tools/specmaker/specmaker.h @@ -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 +#include +#include +#include +#include +#include +#include + +/* 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 */ diff --git a/tools/specmaker/symbol.c b/tools/specmaker/symbol.c new file mode 100644 index 00000000000..225312878ca --- /dev/null +++ b/tools/specmaker/symbol.c @@ -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 */ +} -- 2.11.4.GIT