0c0600d387dd652161fcca377fafeef0a82d157a
[reactos.git] / modules / rosapps / applications / devutils / createspec / createspec.c
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 }