[CREATESPEC]
[reactos.git] / 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 _MSC_VER
16 #pragma warning(disable:4091)
17 #endif
18 #define _NO_CVCONST_H
19 #include <dbghelp.h>
20
21 // doesn't seem to be defined anywhere
22 enum BasicType {
23 btNoType = 0,
24 btVoid = 1,
25 btChar = 2,
26 btWChar = 3,
27 btInt = 6,
28 btUInt = 7,
29 btFloat = 8,
30 btBCD = 9,
31 btBool = 10,
32 btLong = 13,
33 btULong = 14,
34 btCurrency = 25,
35 btDate = 26,
36 btVariant = 27,
37 btComplex = 28,
38 btBit = 29,
39 btBSTR = 30,
40 btHresult = 31
41 };
42
43 typedef enum CV_call_e {
44 CV_CALL_NEAR_C = 0x00,
45 CV_CALL_NEAR_FAST = 0x04,
46 CV_CALL_NEAR_STD = 0x07,
47 CV_CALL_NEAR_SYS = 0x09,
48 CV_CALL_THISCALL = 0x0b,
49 CV_CALL_CLRCALL = 0x16
50 } CV_call_e;
51
52 #define MAX_SYMBOL_NAME 1024
53 typedef struct _SYMINFO_EX
54 {
55 SYMBOL_INFO si;
56 CHAR achName[MAX_SYMBOL_NAME];
57 } SYMINFO_EX;
58
59 typedef enum _PARAM_TYPES
60 {
61 TYPE_NONE,
62 TYPE_LONG,
63 TYPE_DOUBLE,
64 TYPE_PTR,
65 TYPE_STR,
66 TYPE_WSTR
67 } PARAM_TYPES, *PPARAM_TYPES;
68
69 const char*
70 gapszTypeStrings[] =
71 {
72 "???",
73 "long",
74 "double",
75 "ptr",
76 "str",
77 "wstr"
78 };
79
80 #define MAX_PARAMETERS 64
81 typedef struct _EXPORT
82 {
83 PSTR pszName;
84 PSTR pszSymbol;
85 PSTR pszForwarder;
86 ULONG ulRva;
87 DWORD dwCallingConvention;
88 ULONG fForwarder : 1;
89 ULONG fNoName : 1;
90 ULONG fData : 1;
91 ULONG cParameters;
92 PARAM_TYPES aeParameters[MAX_PARAMETERS];
93 } EXPORT, *PEXPORT;
94
95 typedef struct _EXPORT_DATA
96 {
97 ULONG cNumberOfExports;
98 EXPORT aExports[1];
99 } EXPORT_DATA, *PEXPORT_DATA;
100
101 HANDLE ghProcess;
102 CHAR gszModuleFileName[MAX_PATH+1];
103
104 HRESULT
105 OpenFileFromName(
106 _In_ PCSTR pszDllName,
107 _Out_ PHANDLE phFile)
108 {
109 HANDLE hFile;
110
111 /* Try current directory */
112 GetCurrentDirectoryA(MAX_PATH, gszModuleFileName);
113 strcat_s(gszModuleFileName, sizeof(gszModuleFileName), "\\");
114 strcat_s(gszModuleFileName, sizeof(gszModuleFileName), pszDllName);
115 hFile = CreateFileA(gszModuleFileName,
116 FILE_READ_DATA,
117 FILE_SHARE_READ,
118 NULL,
119 OPEN_EXISTING,
120 FILE_ATTRIBUTE_NORMAL,
121 NULL);
122 if (hFile != INVALID_HANDLE_VALUE)
123 {
124 *phFile = hFile;
125 return S_OK;
126 }
127
128 /* Try system32 directory */
129 strcat_s(gszModuleFileName, sizeof(gszModuleFileName), "%systemroot%\\system32\\");
130 strcat_s(gszModuleFileName, sizeof(gszModuleFileName), pszDllName);
131 hFile = CreateFileA(gszModuleFileName,
132 FILE_READ_DATA,
133 FILE_SHARE_READ,
134 NULL,
135 OPEN_EXISTING,
136 FILE_ATTRIBUTE_NORMAL,
137 NULL);
138 if (hFile != INVALID_HANDLE_VALUE)
139 {
140 *phFile = hFile;
141 return S_OK;
142 }
143
144 return HRESULT_FROM_WIN32(GetLastError());
145 }
146
147 HRESULT
148 GetExportsFromFile(
149 _In_ HANDLE hFile,
150 _Out_ PEXPORT_DATA* ppExportData)
151 {
152 HANDLE hMap;
153 PBYTE pjImageBase;
154 PIMAGE_EXPORT_DIRECTORY pExportDir;
155 ULONG i, cjExportSize, cFunctions, cjTableSize;
156 PEXPORT_DATA pExportData;
157 PULONG pulAddressTable, pulNameTable;
158 PUSHORT pusOrdinalTable;
159
160 /* Create an image file mapping */
161 hMap = CreateFileMappingA(hFile, NULL, PAGE_READONLY | SEC_IMAGE, 0, 0, NULL);
162 if (!hMap)
163 {
164 fprintf(stderr, "CreateFileMapping() failed: %ld\n", GetLastError());
165 return HRESULT_FROM_WIN32(GetLastError());
166 }
167
168 /* Map the file */
169 pjImageBase = MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 0);
170 if(pjImageBase == NULL)
171 {
172 fprintf(stderr, "MapViewOfFile() failed: %ld\n", GetLastError());
173 CloseHandle(hMap);
174 return HRESULT_FROM_WIN32(GetLastError());
175 }
176
177 /* Get the export directory */
178 pExportDir = ImageDirectoryEntryToData(pjImageBase,
179 TRUE,
180 IMAGE_DIRECTORY_ENTRY_EXPORT,
181 &cjExportSize);
182
183 cFunctions = pExportDir->NumberOfFunctions;
184 cjTableSize = FIELD_OFFSET(EXPORT_DATA, aExports[cFunctions]);
185
186 pExportData = malloc(cjTableSize);
187 if (pExportData == NULL)
188 {
189 return E_OUTOFMEMORY;
190 }
191
192 RtlZeroMemory(pExportData, cjTableSize);
193
194 pulAddressTable = (PULONG)(pjImageBase + pExportDir->AddressOfFunctions);
195
196 pExportData->cNumberOfExports = cFunctions;
197
198 /* Loop through the function table */
199 for (i = 0; i < cFunctions; i++)
200 {
201 PVOID pvFunction = (pjImageBase + pulAddressTable[i]);
202
203 if ((ULONG_PTR)((PUCHAR)pvFunction - (PUCHAR)pExportDir) < cjExportSize)
204 {
205 pExportData->aExports[i].pszForwarder = _strdup(pvFunction);
206 }
207 else
208 {
209 pExportData->aExports[i].ulRva = pulAddressTable[i];
210 }
211 }
212
213 pulNameTable = (PULONG)(pjImageBase + pExportDir->AddressOfNames);
214 pusOrdinalTable = (PUSHORT)(pjImageBase + pExportDir->AddressOfNameOrdinals);
215
216 /* Loop through the name table */
217 for (i = 0; i < pExportDir->NumberOfNames; i++)
218 {
219 ULONG iIndex = pusOrdinalTable[i];
220 PSTR pszName = (PSTR)(pjImageBase + pulNameTable[i]);
221
222 pExportData->aExports[iIndex].pszName = _strdup(pszName);
223 }
224
225 *ppExportData = pExportData;
226 UnmapViewOfFile(pjImageBase);
227 CloseHandle(hMap);
228 return S_OK;
229 }
230
231 BOOL
232 CALLBACK
233 EnumParametersCallback(
234 _In_ PSYMBOL_INFO pSymInfo,
235 _In_ ULONG SymbolSize,
236 _In_opt_ PVOID UserContext)
237 {
238 PEXPORT pExport = (PEXPORT)UserContext;
239 enum SymTagEnum eSymTag;
240 enum BasicType eBaseType;
241 DWORD dwTypeIndex;
242 ULONG64 ullLength;
243
244 /* If it's not a parameter, skip it */
245 if (!(pSymInfo->Flags & SYMFLAG_PARAMETER))
246 {
247 return TRUE;
248 }
249
250 /* Count this parameter */
251 pExport->cParameters++;
252
253 /* Get the type for the parameter */
254 if (SymGetTypeInfo(ghProcess,
255 pSymInfo->ModBase,
256 pSymInfo->TypeIndex,
257 TI_GET_SYMTAG,
258 &eSymTag))
259 {
260 switch (eSymTag)
261 {
262 case SymTagUDT:
263 case SymTagBaseType:
264
265 /* Try to get the size */
266 if (SymGetTypeInfo(ghProcess,
267 pSymInfo->ModBase,
268 pSymInfo->TypeIndex,
269 TI_GET_LENGTH,
270 &ullLength))
271 {
272 if (ullLength > 8)
273 {
274 /* That is probably not possible */
275 __debugbreak();
276 }
277
278 if (ullLength > 4)
279 {
280 /* 'double' type */
281 pExport->aeParameters[pExport->cParameters - 1] = TYPE_DOUBLE;
282 break;
283 }
284 }
285 /* 'long' type */
286 pExport->aeParameters[pExport->cParameters - 1] = TYPE_LONG;
287 break;
288
289 case SymTagEnum:
290 /* 'long' type */
291 pExport->aeParameters[pExport->cParameters - 1] = TYPE_LONG;
292 break;
293
294 case SymTagPointerType:
295 /* 'ptr' type */
296 pExport->aeParameters[pExport->cParameters - 1] = TYPE_PTR;
297
298 /* Try to get the underlying type */
299 if (SymGetTypeInfo(ghProcess,
300 pSymInfo->ModBase,
301 pSymInfo->TypeIndex,
302 TI_GET_TYPEID,
303 &dwTypeIndex))
304 {
305 /* Try to get the base type */
306 if (SymGetTypeInfo(ghProcess,
307 pSymInfo->ModBase,
308 dwTypeIndex,
309 TI_GET_BASETYPE,
310 &eBaseType))
311 {
312 if (eBaseType == btChar)
313 {
314 pExport->aeParameters[pExport->cParameters - 1] = TYPE_STR;
315 }
316 else if (eBaseType == btWChar)
317 {
318 pExport->aeParameters[pExport->cParameters - 1] = TYPE_WSTR;
319 }
320 }
321 }
322 break;
323
324 default:
325 printf("Unhandled eSymTag: %u\n", eSymTag);
326 return FALSE;
327 }
328 }
329 else
330 {
331 printf("Could not get type info. Fallig back to ptr\n");
332 pExport->aeParameters[pExport->cParameters - 1] = TYPE_PTR;
333 }
334
335 return TRUE;
336 }
337
338 HRESULT
339 ParseExportSymbols(
340 _In_ HANDLE hFile,
341 _Inout_ PEXPORT_DATA pExportData)
342 {
343 DWORD Options;
344 DWORD64 dwModuleBase;
345 ULONG i;
346 IMAGEHLP_STACK_FRAME StackFrame;
347 SYMINFO_EX sym;
348
349
350 /* Initialize dbghelp */
351 if (!SymInitialize(ghProcess, 0, FALSE))
352 return E_FAIL;
353
354 Options = SymGetOptions();
355 Options |= SYMOPT_ALLOW_ABSOLUTE_SYMBOLS | SYMOPT_DEBUG;// | SYMOPT_NO_PROMPTS;
356 Options &= ~SYMOPT_DEFERRED_LOADS;
357 SymSetOptions(Options);
358 SymSetSearchPath(ghProcess, "srv**symbols*http://msdl.microsoft.com/download/symbols");
359
360 printf("Loading symbols, please wait...\n");
361 dwModuleBase = SymLoadModule64(ghProcess, 0, gszModuleFileName, 0, 0, 0);
362 if (dwModuleBase == 0)
363 {
364 fprintf(stderr, "SymLoadModule64() failed: %ld\n", GetLastError());
365 return E_FAIL;
366 }
367
368
369 for (i = 0; i < pExportData->cNumberOfExports; i++)
370 {
371 PEXPORT pExport = &pExportData->aExports[i];
372 ULONG64 ullFunction = dwModuleBase + pExportData->aExports[i].ulRva;
373 ULONG64 ullDisplacement;
374
375 /* Skip forwarder */
376 if (pExport->pszForwarder != NULL)
377 continue;
378
379 RtlZeroMemory(&sym, sizeof(sym));
380 sym.si.SizeOfStruct = sizeof(SYMBOL_INFO);
381 sym.si.MaxNameLen = MAX_SYMBOL_NAME;
382
383 /* Try to find the symbol */
384 if (!SymFromAddr(ghProcess, ullFunction, &ullDisplacement, &sym.si))
385 {
386 printf("Error: SymFromAddr() failed. Error code: %u \n", GetLastError());
387 continue;
388 }
389
390 /* Symbol found. Check if it is a function */
391 if (sym.si.Tag == SymTagFunction)
392 {
393 /* If we don't have a name yet, get one */
394 pExport->pszSymbol = _strdup(sym.si.Name);
395
396 /* Get the calling convention */
397 if (!SymGetTypeInfo(ghProcess,
398 dwModuleBase,
399 sym.si.TypeIndex,
400 TI_GET_CALLING_CONVENTION,
401 &pExport->dwCallingConvention))
402 {
403 /* Fall back to __stdcall */
404 pExport->dwCallingConvention = 0x07; // CV_CALL_NEAR_STD
405 }
406
407 /* Set the context to the function address */
408 RtlZeroMemory(&StackFrame, sizeof(StackFrame));
409 StackFrame.InstructionOffset = ullFunction;
410 if (!SymSetContext(ghProcess, &StackFrame, NULL))
411 {
412 DWORD dwLastError = GetLastError();
413 __debugbreak();
414 continue;
415 }
416
417 /* Enumerate all symbols for this function */
418 if (!SymEnumSymbols(ghProcess,
419 0, // use SymSetContext
420 NULL,
421 EnumParametersCallback,
422 pExport))
423 {
424 DWORD dwLastError = GetLastError();
425 __debugbreak();
426 continue;
427 }
428 }
429 else if (sym.si.Tag == SymTagData)
430 {
431 pExport->fData = TRUE;
432 }
433 }
434
435 return S_OK;
436 }
437
438 const CHAR*
439 GetCallingConvention(
440 _In_ PEXPORT pExport)
441 {
442 if (pExport->fData)
443 {
444 return "extern";
445 }
446
447 #ifndef _M_AMD64
448 switch (pExport->dwCallingConvention)
449 {
450 case CV_CALL_NEAR_C:
451 return "cdecl";
452 case CV_CALL_NEAR_FAST:
453 return "fastcall";
454 case CV_CALL_NEAR_STD:
455 return "stdcall";
456 case CV_CALL_NEAR_SYS:
457 return "syscall";
458 case CV_CALL_THISCALL:
459 return "thiscall";
460 case CV_CALL_CLRCALL:
461 return "clrcall";
462 default:
463 __debugbreak();
464 }
465 #endif
466 return "stdcall";
467 }
468
469 HRESULT
470 CreateSpecFile(
471 _In_ PCSTR pszSpecFile,
472 _In_ PEXPORT_DATA pExportData)
473 {
474 FILE *file;
475 ULONG i, p;
476 PEXPORT pExport;
477
478 /* Create the spec file */
479 if (fopen_s(&file, pszSpecFile, "w") != 0)
480 {
481 fprintf(stderr, "Failed to open spec file: '%s'\n", pszSpecFile);
482 return E_FAIL;
483 }
484
485 /* Loop all exports */
486 for (i = 0; i < pExportData->cNumberOfExports; i++)
487 {
488 pExport = &pExportData->aExports[i];
489
490 fprintf(file, "%u %s ", i + 1, GetCallingConvention(pExport));
491 //if (pExport->fNoName)
492 if (pExport->pszName == NULL)
493 {
494 fprintf(file, "-noname ");
495 }
496
497 if (pExport->pszName != NULL)
498 {
499 fprintf(file, "%s", pExport->pszName);
500 }
501 else if (pExport->pszSymbol != NULL)
502 {
503 fprintf(file, "%s", pExport->pszSymbol);
504 }
505 else
506 {
507 fprintf(file, "NamelessExport_%u", i);
508 }
509
510 if (!pExport->fData)
511 {
512 fprintf(file, "(");
513 for (p = 0; p < pExport->cParameters; p++)
514 {
515 fprintf(file, "%s", gapszTypeStrings[pExport->aeParameters[p]]);
516 if ((p + 1) < pExport->cParameters)
517 {
518 fprintf(file, " ");
519 }
520 }
521 fprintf(file, ")");
522 }
523
524 if (pExport->pszForwarder != NULL)
525 {
526 fprintf(file, " %s", pExport->pszForwarder);
527 }
528
529 fprintf(file, "\n");
530 }
531
532 fclose(file);
533
534 return S_OK;
535 }
536
537 int main(int argc, char* argv[])
538 {
539 HRESULT hr;
540 CHAR szSpecFile[MAX_PATH];
541 PSTR pszSpecFile;
542 HANDLE hFile;
543 PEXPORT_DATA pExportData;
544
545 // check params
546 // help
547
548 ghProcess = GetCurrentProcess();
549
550 /* Open the file */
551 hr = OpenFileFromName(argv[1], &hFile);
552 if (!SUCCEEDED(hr))
553 {
554 fprintf(stderr, "Failed to open file: %lx\n", hr);
555 return hr;
556 }
557
558 /* Get the exports */
559 hr = GetExportsFromFile(hFile, &pExportData);
560 if (!SUCCEEDED(hr))
561 {
562 fprintf(stderr, "Failed to get exports: %lx\n", hr);
563 return hr;
564 }
565
566 /* Get additional info from symbols */
567 hr = ParseExportSymbols(hFile, pExportData);
568 if (!SUCCEEDED(hr))
569 {
570 fprintf(stderr, "Failed to get symbol information: %lx\n", hr);
571 }
572
573 if (argc > 2)
574 {
575 pszSpecFile = argv[2];
576 }
577 else
578 {
579 PSTR pszStart = strrchr(argv[1], '\\');
580 if (pszStart == 0)
581 pszStart = argv[1];
582
583 strcpy_s(szSpecFile, sizeof(szSpecFile), pszStart);
584 strcat_s(szSpecFile, sizeof(szSpecFile), ".spec");
585 pszSpecFile = szSpecFile;
586 }
587
588 hr = CreateSpecFile(pszSpecFile, pExportData);
589
590 CloseHandle(hFile);
591
592 printf("Spec file '%s' was successfully written.\n", szSpecFile);
593
594 return hr;
595 }