[TASKMGR] Process page: Allow using "Open File Location" functionality without runnin...
[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 #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 }