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