1 /*
2 - Info:
3     - http://stackoverflow.com/questions/32251638/dbghelp-get-full-symbol-signature-function-name-parameters-types
4     - http://www.debuginfo.com/articles/dbghelptypeinfo.html
5 - TODO:
6     - Dump usage
7     - Test for dbghelp + symsrv and warn if not working
8     - Resolve forwarders
9 
10 */
11 #include <stdio.h>
12 #include <stdlib.h>
13 #include <windows.h>
14 
15 #ifdef __REACTOS__
16 #include <dbghelp.h>
17 #include <cvconst.h>
18 
19 // dirty hacks!
20 #define sprintf_s(dst, size, format, ...) sprintf(dst, format, __VA_ARGS__)
21 #define vsprintf_s(dst, size, format, ap) vsprintf(dst, format, ap)
22 #define fopen_s(pfile, name, mode) ((*pfile = fopen(name, mode)), (*pfile != 0) ? 0 : -1)
23 #define strcpy_s(dst, size, src) strncpy(dst, src, size)
24 #define strcat_s(dst, size, src) strncat(dst, src, size)
25 
26 #else
27 #ifdef _MSC_VER
28 #pragma warning(disable:4091)
29 #endif
30 #define _NO_CVCONST_H
31 #include <dbghelp.h>
32 
33 // This is from cvconst.h, but win sdk lacks this file
34 enum BasicType {
35    btNoType   = 0,
36    btVoid     = 1,
37    btChar     = 2,
38    btWChar    = 3,
39    btInt      = 6,
40    btUInt     = 7,
41    btFloat    = 8,
42    btBCD      = 9,
43    btBool     = 10,
44    btLong     = 13,
45    btULong    = 14,
46    btCurrency = 25,
47    btDate     = 26,
48    btVariant  = 27,
49    btComplex  = 28,
50    btBit      = 29,
51    btBSTR     = 30,
52    btHresult  = 31
53 };
54 
55 typedef enum CV_call_e {
56    CV_CALL_NEAR_C    = 0x00,
57    CV_CALL_NEAR_FAST = 0x04,
58    CV_CALL_NEAR_STD  = 0x07,
59    CV_CALL_NEAR_SYS  = 0x09,
60    CV_CALL_THISCALL  = 0x0b,
61    CV_CALL_CLRCALL   = 0x16
62 } CV_call_e;
63 
64 #endif // __REACTOS__
65 
66 
67 typedef enum _PARAM_TYPES
68 {
69     TYPE_NONE,
70     TYPE_LONG,
71     TYPE_DOUBLE,
72     TYPE_PTR,
73     TYPE_STR,
74     TYPE_WSTR
75 } PARAM_TYPES, *PPARAM_TYPES;
76 
77 const char*
78 gapszTypeStrings[] =
79 {
80     "???",
81     "long",
82     "double",
83     "ptr",
84     "str",
85     "wstr"
86 };
87 
88 #define MAX_PARAMETERS 64
89 typedef struct _EXPORT
90 {
91     PSTR pszName;
92     PSTR pszSymbol;
93     PSTR pszForwarder;
94     ULONG ulRva;
95     DWORD dwCallingConvention;
96     ULONG fForwarder : 1;
97     ULONG fNoName : 1;
98     ULONG fData : 1;
99     ULONG cParameters;
100     PARAM_TYPES aeParameters[MAX_PARAMETERS];
101 } EXPORT, *PEXPORT;
102 
103 typedef struct _EXPORT_DATA
104 {
105     ULONG cNumberOfExports;
106     EXPORT aExports[1];
107 } EXPORT_DATA, *PEXPORT_DATA;
108 
109 HANDLE ghProcess;
110 
111 void
112 error(
113     _In_ const char* pszFormat,
114     ...)
115 {
116     CHAR szBuffer[512];
117     SIZE_T cchBuffer;
118     DWORD dwLastError;
119     va_list argptr;
120 
121     /* Get last error */
122     dwLastError = GetLastError();
123 
124     va_start(argptr, pszFormat);
125     cchBuffer = vsprintf_s(szBuffer, sizeof(szBuffer), pszFormat, argptr);
126     va_end(argptr);
127 
128     /* Strip trailing newlines */
129     _Analysis_assume_(cchBuffer < sizeof(szBuffer));
130     while ((cchBuffer >= 1) &&
131            ((szBuffer[cchBuffer - 1] == '\r') ||
132             (szBuffer[cchBuffer - 1] == '\n')))
133     {
134         szBuffer[cchBuffer - 1] = '\0';
135         cchBuffer--;
136     }
137 
138     /* Check if we have an error */
139     if (dwLastError != ERROR_SUCCESS)
140     {
141         /* Append error code */
142         cchBuffer += sprintf_s(szBuffer + cchBuffer,
143                                sizeof(szBuffer) - cchBuffer,
144                                " [error %lu: ", dwLastError);
145 
146         /* Format the last error code */
147         cchBuffer += FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM,
148                                     NULL,
149                                     dwLastError,
150                                     0,
151                                     szBuffer + cchBuffer,
152                                     (DWORD)(sizeof(szBuffer) - cchBuffer),
153                                     NULL);
154 
155         /* Strip trailing newlines */
156         _Analysis_assume_(cchBuffer < sizeof(szBuffer));
157         while ((cchBuffer >= 1) &&
158                ((szBuffer[cchBuffer - 1] == '\r') ||
159                 (szBuffer[cchBuffer - 1] == '\n')))
160         {
161             szBuffer[cchBuffer - 1] = '\0';
162             cchBuffer--;
163         }
164 
165         fprintf(stderr, "%s]\n", szBuffer);
166     }
167     else
168     {
169         fprintf(stderr, "%s\n", szBuffer);
170     }
171 }
172 
173 BOOL
174 InitDbgHelp(
175     VOID)
176 {
177     static const char *pszMsSymbolServer = "srv**symbols*http://msdl.microsoft.com/download/symbols";
178     DWORD Options;
179 
180     /* Save current process ;-) */
181     ghProcess = GetCurrentProcess();
182 
183     /* Initialize dbghelp */
184     if (!SymInitialize(ghProcess, 0, FALSE))
185     {
186         error("SymInitialize() failed.");
187         return FALSE;
188     }
189 
190     /* Set options */
191     Options = SymGetOptions();
192     Options |= SYMOPT_ALLOW_ABSOLUTE_SYMBOLS | SYMOPT_INCLUDE_32BIT_MODULES | SYMOPT_DEBUG;// | SYMOPT_NO_PROMPTS;
193     Options &= ~SYMOPT_DEFERRED_LOADS;
194     SymSetOptions(Options);
195 
196     /* Test if we can reach the MS symbol server */
197     if (!SymSrvIsStore(ghProcess, pszMsSymbolServer))
198     {
199         error("Failed to connect to symbol server.");
200         return FALSE;
201     }
202 
203     /* Set MS symbol server as symbol search path */
204     SymSetSearchPath(ghProcess, pszMsSymbolServer);
205 
206     return TRUE;
207 }
208 
209 HMODULE
210 LoadModuleWithSymbolsFullPath(
211     _In_ PSTR pszFullModuleFileName)
212 {
213     HMODULE hmod;
214     DWORD64 dwModuleBase;
215 
216     /* Load the DLL */
217     hmod = LoadLibraryExA(pszFullModuleFileName,
218                           NULL,
219                           LOAD_IGNORE_CODE_AUTHZ_LEVEL |
220                           DONT_RESOLVE_DLL_REFERENCES |
221                           LOAD_WITH_ALTERED_SEARCH_PATH);
222     if (hmod == NULL)
223     {
224         return NULL;
225     }
226 
227     /* Load symbols for this module */
228     dwModuleBase = SymLoadModule64(ghProcess,
229                                    NULL,
230                                    pszFullModuleFileName,
231                                    NULL,
232                                    (DWORD_PTR)hmod,
233                                    0);
234     if (dwModuleBase == 0)
235     {
236         /* ERROR_SUCCESS means, we have symbols already */
237         if (GetLastError() != ERROR_SUCCESS)
238         {
239             return NULL;
240         }
241     }
242     else
243     {
244         printf("Successfully loaded symbols for '%s'\n",
245                pszFullModuleFileName);
246     }
247 
248     return hmod;
249 }
250 
251 HMODULE
252 LoadModuleWithSymbols(
253     _In_ PSTR pszModuleName)
254 {
255     CHAR szFullFileName[MAX_PATH];
256     HMODULE hmod;
257 
258     /* Check if the file name has a path */
259     if (strchr(pszModuleName, '\\') != NULL)
260     {
261         /* Try as it is */
262         hmod = LoadModuleWithSymbolsFullPath(pszModuleName);
263         if (hmod != NULL)
264         {
265             return hmod;
266         }
267     }
268 
269     /* Try current directory */
270     GetCurrentDirectoryA(MAX_PATH, szFullFileName);
271     strcat_s(szFullFileName, sizeof(szFullFileName), "\\");
272     strcat_s(szFullFileName, sizeof(szFullFileName), pszModuleName);
273     hmod = LoadModuleWithSymbolsFullPath(szFullFileName);
274     if (hmod != NULL)
275     {
276         return hmod;
277     }
278 
279     /* Try system32 */
280     strcpy_s(szFullFileName, sizeof(szFullFileName), "%systemroot%\\system32\\");
281     strcat_s(szFullFileName, sizeof(szFullFileName), pszModuleName);
282     hmod = LoadModuleWithSymbolsFullPath(szFullFileName);
283     if (hmod != NULL)
284     {
285         return hmod;
286     }
287 
288 #ifdef _WIN64
289     /* Try SysWOW64 */
290     strcpy_s(szFullFileName, sizeof(szFullFileName), "%systemroot%\\SysWOW64\\");
291     strcat_s(szFullFileName, sizeof(szFullFileName), pszModuleName);
292     hmod = LoadModuleWithSymbolsFullPath(szFullFileName);
293     if (hmod != NULL)
294     {
295         return hmod;
296     }
297 #endif // _WIN64
298 
299     return NULL;
300 }
301 
302 HRESULT
303 GetExportsFromFile(
304     _In_ HMODULE hmod,
305     _Out_ PEXPORT_DATA* ppExportData)
306 {
307     PBYTE pjImageBase;
308     PIMAGE_EXPORT_DIRECTORY pExportDir;
309     ULONG i, cjExportSize, cFunctions, cjTableSize;
310     PEXPORT_DATA pExportData;
311     PULONG pulAddressTable, pulNameTable;
312     PUSHORT pusOrdinalTable;
313 
314     pjImageBase = (PBYTE)hmod;
315 
316     /* Get the export directory */
317     pExportDir = ImageDirectoryEntryToData(pjImageBase,
318                                            TRUE,
319                                            IMAGE_DIRECTORY_ENTRY_EXPORT,
320                                            &cjExportSize);
321     if (pExportDir == NULL)
322     {
323         fprintf(stderr, "Failed to get export directory\n");
324         return E_FAIL;
325     }
326 
327     cFunctions = pExportDir->NumberOfFunctions;
328     cjTableSize = FIELD_OFFSET(EXPORT_DATA, aExports[cFunctions]);
329 
330     pExportData = malloc(cjTableSize);
331     if (pExportData == NULL)
332     {
333         error("Failed to allocate %u bytes of memory for export table\n", cjTableSize);
334         return E_OUTOFMEMORY;
335     }
336 
337     RtlZeroMemory(pExportData, cjTableSize);
338 
339     pulAddressTable = (PULONG)(pjImageBase + pExportDir->AddressOfFunctions);
340 
341     pExportData->cNumberOfExports = cFunctions;
342 
343     /* Loop through the function table */
344     for (i = 0; i < cFunctions; i++)
345     {
346         PVOID pvFunction = (pjImageBase + pulAddressTable[i]);
347 
348         /* Check if this is a forwarder */
349         if ((ULONG_PTR)((PUCHAR)pvFunction - (PUCHAR)pExportDir) < cjExportSize)
350         {
351             pExportData->aExports[i].pszForwarder = _strdup(pvFunction);
352         }
353         else
354         {
355             pExportData->aExports[i].ulRva = pulAddressTable[i];
356         }
357     }
358 
359     pulNameTable = (PULONG)(pjImageBase + pExportDir->AddressOfNames);
360     pusOrdinalTable = (PUSHORT)(pjImageBase + pExportDir->AddressOfNameOrdinals);
361 
362     /* Loop through the name table */
363     for (i = 0; i < pExportDir->NumberOfNames; i++)
364     {
365         ULONG iIndex = pusOrdinalTable[i];
366         PSTR pszName = (PSTR)(pjImageBase + pulNameTable[i]);
367 
368         pExportData->aExports[iIndex].pszName = _strdup(pszName);
369     }
370 
371     *ppExportData = pExportData;
372     return S_OK;
373 }
374 
375 BOOL
376 CALLBACK
377 EnumParametersCallback(
378     _In_ PSYMBOL_INFO pSymInfo,
379     _In_ ULONG SymbolSize,
380     _In_ PVOID UserContext)
381 {
382     PEXPORT pExport = (PEXPORT)UserContext;
383     enum SymTagEnum eSymTag;
384     enum BasicType eBaseType;
385     DWORD dwTypeIndex;
386     ULONG64 ullLength;
387 
388     /* If it's not a parameter, skip it */
389     if (!(pSymInfo->Flags & SYMFLAG_PARAMETER))
390     {
391         return TRUE;
392     }
393 
394     /* Count this parameter */
395     pExport->cParameters++;
396 
397     /* Get the type for the parameter */
398     if (SymGetTypeInfo(ghProcess,
399                        pSymInfo->ModBase,
400                        pSymInfo->TypeIndex,
401                        TI_GET_SYMTAG,
402                        &eSymTag))
403     {
404         switch (eSymTag)
405         {
406         case SymTagUDT:
407         case SymTagBaseType:
408 
409             /* Try to get the size */
410             if (SymGetTypeInfo(ghProcess,
411                                 pSymInfo->ModBase,
412                                 pSymInfo->TypeIndex,
413                                 TI_GET_LENGTH,
414                                 &ullLength))
415             {
416                 if (ullLength > 8)
417                 {
418                     /* That is probably not possible */
419                     __debugbreak();
420                 }
421 
422                 if (ullLength > 4)
423                 {
424                     /* 'double' type */
425                     pExport->aeParameters[pExport->cParameters - 1] = TYPE_DOUBLE;
426                     break;
427                 }
428             }
429 
430             /* Default to 'long' type */
431             pExport->aeParameters[pExport->cParameters - 1] = TYPE_LONG;
432             break;
433 
434         case SymTagEnum:
435             /* 'long' type */
436             pExport->aeParameters[pExport->cParameters - 1] = TYPE_LONG;
437             break;
438 
439         case SymTagPointerType:
440             /* 'ptr' type */
441             pExport->aeParameters[pExport->cParameters - 1] = TYPE_PTR;
442 
443             /* Try to get the underlying type */
444             if (SymGetTypeInfo(ghProcess,
445                                 pSymInfo->ModBase,
446                                 pSymInfo->TypeIndex,
447                                 TI_GET_TYPEID,
448                                 &dwTypeIndex))
449             {
450                 /* Try to get the base type */
451                 if (SymGetTypeInfo(ghProcess,
452                                     pSymInfo->ModBase,
453                                     dwTypeIndex,
454                                     TI_GET_BASETYPE,
455                                     &eBaseType))
456                 {
457                     /* Check for string types */
458                     if (eBaseType == btChar)
459                     {
460                         /* 'str' type */
461                         pExport->aeParameters[pExport->cParameters - 1] = TYPE_STR;
462                     }
463                     else if (eBaseType == btWChar)
464                     {
465                         /* 'wstr' type */
466                         pExport->aeParameters[pExport->cParameters - 1] = TYPE_WSTR;
467                     }
468                 }
469             }
470             break;
471 
472         default:
473             printf("Unhandled eSymTag: %u\n", eSymTag);
474             return FALSE;
475         }
476     }
477     else
478     {
479         printf("Could not get type info. Fallig back to ptr\n");
480         pExport->aeParameters[pExport->cParameters - 1] = TYPE_PTR;
481     }
482 
483     return TRUE;
484 }
485 
486 ULONG64
487 GetFunctionFromForwarder(
488     _In_ PCSTR pszForwarder)
489 {
490     CHAR szDllName[MAX_SYM_NAME];
491     PCH pchDot, pszName;
492     ULONG64 ullFunction;
493     HMODULE hmod;
494 
495     /* Copy the forwarder name */
496     strcpy_s(szDllName, sizeof(szDllName), pszForwarder);
497 
498     /* Find the '.' */
499     pchDot = strchr(szDllName, '.');
500     if (pchDot == NULL)
501     {
502         error("Invalid name for forwarder '%s'!", pszForwarder);
503         return 0;
504     }
505 
506     /* Terminate DLL name */
507     *pchDot = '\0';
508 
509     /* Load the DLL */
510     hmod = LoadModuleWithSymbols(szDllName);
511     if (hmod == NULL)
512     {
513         error("Failed to load module for forwarder '%s'!", pszForwarder);
514         return 0;
515     }
516 
517     /* Get the function name and check for ordinal */
518     pszName = pchDot + 1;
519     if (pszName[0] == '#')
520     {
521         ULONG iOrdinal = strtoul(pszName + 1, NULL, 10);
522         if ((iOrdinal == 0) || (iOrdinal > 0xFFFF))
523         {
524             error("Got invalid ordinal %u for ''", iOrdinal, pszForwarder);
525             return 0;
526         }
527 
528         pszName = (PSTR)(ULONG_PTR)iOrdinal;
529     }
530 
531     /* Get the function address */
532     ullFunction = (ULONG_PTR)GetProcAddress(hmod, pszName);
533     if (ullFunction == 0)
534     {
535         error("Failed to resolve '%s' in '%s'.", pchDot + 1, szDllName);
536         return 0;
537     }
538 
539     return ullFunction;
540 }
541 
542 HRESULT
543 ParseImageSymbols(
544     _In_ HMODULE hmod,
545     _Inout_ PEXPORT_DATA pExportData)
546 {
547     DWORD64 dwModuleBase;
548     ULONG i;
549     IMAGEHLP_STACK_FRAME StackFrame;
550     SYMBOL_INFO_PACKAGE sym;
551 
552     dwModuleBase = (DWORD_PTR)hmod;
553 
554     /* Loop through all exports */
555     for (i = 0; i < pExportData->cNumberOfExports; i++)
556     {
557         PEXPORT pExport = &pExportData->aExports[i];
558         ULONG64 ullFunction = dwModuleBase + pExportData->aExports[i].ulRva;
559         ULONG64 ullDisplacement;
560 
561         /* Check if this is a forwarder */
562         if (pExport->pszForwarder != NULL)
563         {
564             /* Load the module and get the function address */
565             ullFunction = GetFunctionFromForwarder(pExport->pszForwarder);
566             if (ullFunction == 0)
567             {
568                 printf("Failed to get function for forwarder '%s'. Skipping.\n", pExport->pszForwarder);
569                 continue;
570             }
571         }
572 
573         RtlZeroMemory(&sym, sizeof(sym));
574         sym.si.SizeOfStruct = sizeof(SYMBOL_INFO);
575         sym.si.MaxNameLen = MAX_SYM_NAME;
576 
577         /* Try to find the symbol */
578         if (!SymFromAddr(ghProcess, ullFunction, &ullDisplacement, &sym.si))
579         {
580             error("Error: SymFromAddr() failed.");
581             continue;
582         }
583 
584         /* Get the symbol name */
585         pExport->pszSymbol = _strdup(sym.si.Name);
586 
587         /* Check if it is a function */
588         if (sym.si.Tag == SymTagFunction)
589         {
590             /* Get the calling convention */
591             if (!SymGetTypeInfo(ghProcess,
592                                 dwModuleBase,
593                                 sym.si.TypeIndex,
594                                 TI_GET_CALLING_CONVENTION,
595                                 &pExport->dwCallingConvention))
596             {
597                 /* Fall back to __stdcall */
598                 pExport->dwCallingConvention = CV_CALL_NEAR_STD;
599             }
600 
601             /* Set the context to the function address */
602             RtlZeroMemory(&StackFrame, sizeof(StackFrame));
603             StackFrame.InstructionOffset = ullFunction;
604             if (!SymSetContext(ghProcess, &StackFrame, NULL))
605             {
606                 error("SymSetContext failed for i = %u.", i);
607                 continue;
608             }
609 
610             /* Enumerate all symbols for this function */
611             if (!SymEnumSymbols(ghProcess,
612                                 0, // use SymSetContext
613                                 NULL,
614                                 EnumParametersCallback,
615                                 pExport))
616             {
617                 error("SymEnumSymbols failed for i = %u.", i);
618                 continue;
619             }
620         }
621         else if (sym.si.Tag == SymTagPublicSymbol)
622         {
623             pExport->dwCallingConvention = CV_CALL_NEAR_STD;
624         }
625         else if (sym.si.Tag == SymTagData)
626         {
627             pExport->fData = TRUE;
628         }
629     }
630 
631     return S_OK;
632 }
633 
634 const CHAR*
635 GetCallingConvention(
636     _In_ PEXPORT pExport)
637 {
638     if (pExport->fData)
639     {
640         return "extern";
641     }
642 
643 #ifndef _M_AMD64
644     switch (pExport->dwCallingConvention)
645     {
646         case CV_CALL_NEAR_C:
647             return "cdecl";
648         case CV_CALL_NEAR_FAST:
649             return "fastcall";
650         case CV_CALL_NEAR_STD:
651             return "stdcall";
652         case CV_CALL_NEAR_SYS:
653             return "syscall";
654         case CV_CALL_THISCALL:
655             return "thiscall";
656         default:
657             __debugbreak();
658     }
659 #endif
660     return "stdcall";
661 }
662 
663 HRESULT
664 CreateSpecFile(
665     _In_ PCSTR pszSpecFile,
666     _In_ PEXPORT_DATA pExportData)
667 {
668     FILE *file;
669     ULONG i, p;
670     PEXPORT pExport;
671 
672     /* Create the spec file */
673     if (fopen_s(&file, pszSpecFile, "w") != 0)
674     {
675         error("Failed to open spec file: '%s'\n", pszSpecFile);
676         return E_FAIL;
677     }
678 
679     /* Loop all exports */
680     for (i = 0; i < pExportData->cNumberOfExports; i++)
681     {
682         pExport = &pExportData->aExports[i];
683 
684         fprintf(file, "%lu %s ", i + 1, GetCallingConvention(pExport));
685         //if (pExport->fNoName)
686         if (pExport->pszName == NULL)
687         {
688             fprintf(file, "-noname ");
689         }
690 
691         if (pExport->pszName != NULL)
692         {
693             fprintf(file, "%s", pExport->pszName);
694         }
695         else if (pExport->pszSymbol != NULL)
696         {
697             fprintf(file, "%s", pExport->pszSymbol);
698         }
699         else
700         {
701             fprintf(file, "NamelessExport_%lu", i);
702         }
703 
704         if (!pExport->fData)
705         {
706             fprintf(file, "(");
707             for (p = 0; p < pExport->cParameters; p++)
708             {
709                 fprintf(file, "%s", gapszTypeStrings[pExport->aeParameters[p]]);
710                 if ((p + 1) < pExport->cParameters)
711                 {
712                     fprintf(file, " ");
713                 }
714             }
715             fprintf(file, ")");
716         }
717 
718         if (pExport->pszForwarder != NULL)
719         {
720             fprintf(file, " %s", pExport->pszForwarder);
721         }
722 
723         fprintf(file, "\n");
724     }
725 
726     fclose(file);
727 
728     return S_OK;
729 }
730 
731 int main(int argc, char* argv[])
732 {
733     HRESULT hr;
734     CHAR szSpecFile[MAX_PATH];
735     PSTR pszSpecFile;
736     PEXPORT_DATA pExportData;
737     HMODULE hmod;
738 
739     /* Check parameters */
740     if ((argc < 2) || !strcmp(argv[1], "/?"))
741     {
742         printf("syntax: createspec <image file> [<spec file>]\n");
743         return 0;
744     }
745 
746     /* Check if we have a spec file name */
747     if (argc > 2)
748     {
749         pszSpecFile = argv[2];
750     }
751     else
752     {
753         /* Create spec file name from image file name */
754         PSTR pszStart = strrchr(argv[1], '\\');
755         if (pszStart == 0)
756             pszStart = argv[1];
757 
758         strcpy_s(szSpecFile, sizeof(szSpecFile), pszStart);
759         strcat_s(szSpecFile, sizeof(szSpecFile), ".spec");
760         pszSpecFile = szSpecFile;
761     }
762 
763     /* Initialize dbghelp.dll */
764     if (!InitDbgHelp())
765     {
766         error("Failed to init dbghelp!\n"
767               "Make sure you have dbghelp.dll and symsrv.dll in the same folder.\n");
768         return E_FAIL;
769     }
770 
771     /* Load the file including symbols */
772     printf("Loading symbols for '%s', please wait...\n", argv[1]);
773     hmod = LoadModuleWithSymbols(argv[1]);
774     if (hmod == NULL)
775     {
776         error("Failed to load module '%s'!", argv[1]);
777         return E_FAIL;
778     }
779 
780     /* Get the exports */
781     hr = GetExportsFromFile(hmod, &pExportData);
782     if (!SUCCEEDED(hr))
783     {
784         error("Failed to get exports: %lx\n", hr);
785         return hr;
786     }
787 
788     /* Get additional info from symbols */
789     hr = ParseImageSymbols(hmod, pExportData);
790     if (!SUCCEEDED(hr))
791     {
792         error("Failed to get symbol information: hr=%lx\n", hr);
793     }
794 
795     /* Write the spec file */
796     hr = CreateSpecFile(pszSpecFile, pExportData);
797 
798     printf("Spec file '%s' was successfully written.\n", szSpecFile);
799 
800     return hr;
801 }
802