[CREATESPEC]
authorTimo Kreuzer <timo.kreuzer@reactos.org>
Sat, 9 Jan 2016 19:05:14 +0000 (19:05 +0000)
committerTimo Kreuzer <timo.kreuzer@reactos.org>
Sat, 9 Jan 2016 19:05:14 +0000 (19:05 +0000)
Small utility to auto-create spec files. Uses MS symbol server to evaluate data not available in the export table. Features:
- Parse export table for export names and forwarders
- Find function name for nameless exports
- Recognize calling convention / data exports
- Analyze function parameters

svn path=/trunk/; revision=70559

rosapps/applications/devutils/createspec/CMakeLists.txt [new file with mode: 0644]
rosapps/applications/devutils/createspec/createspec.c [new file with mode: 0644]

diff --git a/rosapps/applications/devutils/createspec/CMakeLists.txt b/rosapps/applications/devutils/createspec/CMakeLists.txt
new file mode 100644 (file)
index 0000000..a517326
--- /dev/null
@@ -0,0 +1,6 @@
+
+add_executable(createspec createspec.c)
+set_module_type(createspec win32cui)
+target_link_libraries(createspec wine)
+add_importlibs(createspec dbghelp msvcrt kernel32)
+add_cd_file(TARGET createspec DESTINATION reactos/system32 FOR all)
diff --git a/rosapps/applications/devutils/createspec/createspec.c b/rosapps/applications/devutils/createspec/createspec.c
new file mode 100644 (file)
index 0000000..b9fbbef
--- /dev/null
@@ -0,0 +1,595 @@
+/*
+- Info:
+    - http://stackoverflow.com/questions/32251638/dbghelp-get-full-symbol-signature-function-name-parameters-types
+    - http://www.debuginfo.com/articles/dbghelptypeinfo.html
+- TODO:
+    - Dump usage
+    - Test for dbghelp + symsrv and warn if not working
+    - Resolve forwarders
+
+*/
+#include <stdio.h>
+#include <stdlib.h>
+#include <windows.h>
+
+#ifdef _MSC_VER
+#pragma warning(disable:4091)
+#endif
+#define _NO_CVCONST_H
+#include <dbghelp.h>
+
+// doesn't seem to be defined anywhere
+enum BasicType { 
+   btNoType   = 0,
+   btVoid     = 1,
+   btChar     = 2,
+   btWChar    = 3,
+   btInt      = 6,
+   btUInt     = 7,
+   btFloat    = 8,
+   btBCD      = 9,
+   btBool     = 10,
+   btLong     = 13,
+   btULong    = 14,
+   btCurrency = 25,
+   btDate     = 26,
+   btVariant  = 27,
+   btComplex  = 28,
+   btBit      = 29,
+   btBSTR     = 30,
+   btHresult  = 31
+};
+
+typedef enum CV_call_e { 
+   CV_CALL_NEAR_C    = 0x00,
+   CV_CALL_NEAR_FAST = 0x04,
+   CV_CALL_NEAR_STD  = 0x07,
+   CV_CALL_NEAR_SYS  = 0x09,
+   CV_CALL_THISCALL  = 0x0b,
+   CV_CALL_CLRCALL   = 0x16
+} CV_call_e;
+
+#define MAX_SYMBOL_NAME                1024
+typedef struct _SYMINFO_EX
+{
+    SYMBOL_INFO si;
+    CHAR achName[MAX_SYMBOL_NAME];
+} SYMINFO_EX;
+
+typedef enum _PARAM_TYPES
+{
+    TYPE_NONE,
+    TYPE_LONG,
+    TYPE_DOUBLE,
+    TYPE_PTR,
+    TYPE_STR,
+    TYPE_WSTR
+} PARAM_TYPES, *PPARAM_TYPES;
+
+const char*
+gapszTypeStrings[] =
+{
+    "???",
+    "long",
+    "double",
+    "ptr",
+    "str",
+    "wstr"
+};
+
+#define MAX_PARAMETERS 64
+typedef struct _EXPORT
+{
+    PSTR pszName;
+    PSTR pszSymbol;
+    PSTR pszForwarder;
+    ULONG ulRva;
+    DWORD dwCallingConvention;
+    ULONG fForwarder : 1;
+    ULONG fNoName : 1;
+    ULONG fData : 1;
+    ULONG cParameters;
+    PARAM_TYPES aeParameters[MAX_PARAMETERS];
+} EXPORT, *PEXPORT;
+
+typedef struct _EXPORT_DATA
+{
+    ULONG cNumberOfExports;
+    EXPORT aExports[1];
+} EXPORT_DATA, *PEXPORT_DATA;
+
+HANDLE ghProcess;
+CHAR gszModuleFileName[MAX_PATH+1];
+
+HRESULT
+OpenFileFromName(
+    _In_ PCSTR pszDllName,
+    _Out_ PHANDLE phFile)
+{
+       HANDLE hFile;
+
+       /* Try current directory */
+       GetCurrentDirectoryA(MAX_PATH, gszModuleFileName);
+       strcat_s(gszModuleFileName, sizeof(gszModuleFileName), "\\");
+       strcat_s(gszModuleFileName, sizeof(gszModuleFileName), pszDllName);
+       hFile = CreateFileA(gszModuleFileName,
+                        FILE_READ_DATA,
+                        FILE_SHARE_READ,
+                        NULL,
+                        OPEN_EXISTING,
+                        FILE_ATTRIBUTE_NORMAL,
+                        NULL);
+       if (hFile != INVALID_HANDLE_VALUE)
+    {
+        *phFile = hFile;
+        return S_OK;
+    }
+
+       /* Try system32 directory */
+    strcat_s(gszModuleFileName, sizeof(gszModuleFileName), "%systemroot%\\system32\\");
+    strcat_s(gszModuleFileName, sizeof(gszModuleFileName), pszDllName);
+       hFile = CreateFileA(gszModuleFileName,
+                        FILE_READ_DATA,
+                        FILE_SHARE_READ,
+                        NULL,
+                        OPEN_EXISTING,
+                        FILE_ATTRIBUTE_NORMAL,
+                        NULL);
+       if (hFile != INVALID_HANDLE_VALUE)
+    {
+        *phFile = hFile;
+        return S_OK;
+    }
+
+    return HRESULT_FROM_WIN32(GetLastError());
+}
+
+HRESULT
+GetExportsFromFile(
+    _In_ HANDLE hFile,
+    _Out_ PEXPORT_DATA* ppExportData)
+{
+    HANDLE hMap;
+    PBYTE pjImageBase;
+    PIMAGE_EXPORT_DIRECTORY pExportDir;
+    ULONG i, cjExportSize, cFunctions, cjTableSize;
+    PEXPORT_DATA pExportData;
+    PULONG pulAddressTable, pulNameTable;
+    PUSHORT pusOrdinalTable;
+
+    /* Create an image file mapping */
+       hMap = CreateFileMappingA(hFile, NULL, PAGE_READONLY | SEC_IMAGE, 0, 0, NULL);
+       if (!hMap)
+       {
+               fprintf(stderr, "CreateFileMapping() failed: %ld\n", GetLastError());
+               return HRESULT_FROM_WIN32(GetLastError());
+       }
+
+    /* Map the file */
+       pjImageBase = MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 0);
+       if(pjImageBase == NULL)
+       {
+               fprintf(stderr, "MapViewOfFile() failed: %ld\n", GetLastError());
+        CloseHandle(hMap);
+               return HRESULT_FROM_WIN32(GetLastError());
+       }
+
+       /* Get the export directory */
+       pExportDir = ImageDirectoryEntryToData(pjImageBase,
+                                           TRUE,
+                                           IMAGE_DIRECTORY_ENTRY_EXPORT,
+                                           &cjExportSize);
+
+    cFunctions = pExportDir->NumberOfFunctions;
+    cjTableSize = FIELD_OFFSET(EXPORT_DATA, aExports[cFunctions]);
+
+    pExportData = malloc(cjTableSize);
+    if (pExportData == NULL)
+    {
+        return E_OUTOFMEMORY;
+    }
+
+    RtlZeroMemory(pExportData, cjTableSize);
+
+    pulAddressTable = (PULONG)(pjImageBase + pExportDir->AddressOfFunctions);
+
+    pExportData->cNumberOfExports = cFunctions;
+
+    /* Loop through the function table */
+    for (i = 0; i < cFunctions; i++)
+    {
+        PVOID pvFunction = (pjImageBase + pulAddressTable[i]);
+        
+        if ((ULONG_PTR)((PUCHAR)pvFunction - (PUCHAR)pExportDir) < cjExportSize)
+        {
+            pExportData->aExports[i].pszForwarder = _strdup(pvFunction);
+        }
+        else
+        {
+            pExportData->aExports[i].ulRva = pulAddressTable[i];
+        }
+    }
+
+    pulNameTable = (PULONG)(pjImageBase + pExportDir->AddressOfNames);
+    pusOrdinalTable = (PUSHORT)(pjImageBase + pExportDir->AddressOfNameOrdinals);
+
+    /* Loop through the name table */
+    for (i = 0; i < pExportDir->NumberOfNames; i++)
+    {
+        ULONG iIndex = pusOrdinalTable[i];
+        PSTR pszName = (PSTR)(pjImageBase + pulNameTable[i]);
+
+        pExportData->aExports[iIndex].pszName = _strdup(pszName);
+    }
+
+    *ppExportData = pExportData;
+    UnmapViewOfFile(pjImageBase);
+    CloseHandle(hMap);
+    return S_OK;
+}
+
+BOOL
+CALLBACK
+EnumParametersCallback(
+    _In_ PSYMBOL_INFO pSymInfo,
+    _In_ ULONG SymbolSize,
+    _In_opt_ PVOID UserContext)
+{
+    PEXPORT pExport = (PEXPORT)UserContext;
+    enum SymTagEnum eSymTag;
+    enum BasicType eBaseType;
+    DWORD dwTypeIndex;
+    ULONG64 ullLength;
+
+    /* If it's not a parameter, skip it */
+    if (!(pSymInfo->Flags & SYMFLAG_PARAMETER))
+    {
+        return TRUE;
+    }
+
+    /* Count this parameter */
+    pExport->cParameters++;
+
+    /* Get the type for the parameter */
+    if (SymGetTypeInfo(ghProcess,
+                       pSymInfo->ModBase,
+                       pSymInfo->TypeIndex,
+                       TI_GET_SYMTAG,
+                       &eSymTag))
+    {
+        switch (eSymTag)
+        {
+        case SymTagUDT:
+        case SymTagBaseType:
+
+            /* Try to get the size */
+            if (SymGetTypeInfo(ghProcess,
+                                pSymInfo->ModBase,
+                                pSymInfo->TypeIndex,
+                                TI_GET_LENGTH,
+                                &ullLength))
+            {
+                if (ullLength > 8)
+                {
+                    /* That is probably not possible */
+                    __debugbreak();
+                }
+
+                if (ullLength > 4)
+                {
+                    /* 'double' type */
+                    pExport->aeParameters[pExport->cParameters - 1] = TYPE_DOUBLE;
+                    break;
+                }
+            }
+            /* 'long' type */
+            pExport->aeParameters[pExport->cParameters - 1] = TYPE_LONG;
+            break;
+
+        case SymTagEnum:
+            /* 'long' type */
+            pExport->aeParameters[pExport->cParameters - 1] = TYPE_LONG;
+            break;
+
+        case SymTagPointerType:
+            /* 'ptr' type */
+            pExport->aeParameters[pExport->cParameters - 1] = TYPE_PTR;
+
+            /* Try to get the underlying type */
+            if (SymGetTypeInfo(ghProcess,
+                                pSymInfo->ModBase,
+                                pSymInfo->TypeIndex,
+                                TI_GET_TYPEID,
+                                &dwTypeIndex))
+            {
+                /* Try to get the base type */
+                if (SymGetTypeInfo(ghProcess,
+                                    pSymInfo->ModBase,
+                                    dwTypeIndex,
+                                    TI_GET_BASETYPE,
+                                    &eBaseType))
+                {
+                    if (eBaseType == btChar)
+                    {
+                        pExport->aeParameters[pExport->cParameters - 1] = TYPE_STR;
+                    }
+                    else if (eBaseType == btWChar)
+                    {
+                        pExport->aeParameters[pExport->cParameters - 1] = TYPE_WSTR;
+                    }
+                }
+            }
+            break;
+
+        default:
+            printf("Unhandled eSymTag: %u\n", eSymTag);
+            return FALSE;
+        }
+    }
+    else
+    {
+        printf("Could not get type info. Fallig back to ptr\n");
+        pExport->aeParameters[pExport->cParameters - 1] = TYPE_PTR;
+    }
+
+    return TRUE;
+}
+
+HRESULT
+ParseExportSymbols(
+    _In_ HANDLE hFile,
+    _Inout_ PEXPORT_DATA pExportData)
+{
+    DWORD Options;
+    DWORD64 dwModuleBase;
+    ULONG i;
+    IMAGEHLP_STACK_FRAME StackFrame;
+    SYMINFO_EX sym;
+
+
+    /* Initialize dbghelp */
+       if (!SymInitialize(ghProcess, 0, FALSE))
+               return E_FAIL;
+
+    Options = SymGetOptions();
+    Options |= SYMOPT_ALLOW_ABSOLUTE_SYMBOLS | SYMOPT_DEBUG;// | SYMOPT_NO_PROMPTS;
+    Options &= ~SYMOPT_DEFERRED_LOADS;
+    SymSetOptions(Options);
+    SymSetSearchPath(ghProcess, "srv**symbols*http://msdl.microsoft.com/download/symbols");
+
+       printf("Loading symbols, please wait...\n");
+       dwModuleBase = SymLoadModule64(ghProcess, 0, gszModuleFileName, 0, 0, 0);
+       if (dwModuleBase == 0)
+       {
+               fprintf(stderr, "SymLoadModule64() failed: %ld\n", GetLastError());
+               return E_FAIL;
+       }
+
+
+    for (i = 0; i < pExportData->cNumberOfExports; i++)
+    {
+        PEXPORT pExport = &pExportData->aExports[i];
+        ULONG64 ullFunction = dwModuleBase + pExportData->aExports[i].ulRva;
+        ULONG64 ullDisplacement;
+
+        /* Skip forwarder */
+        if (pExport->pszForwarder != NULL)
+            continue;
+
+        RtlZeroMemory(&sym, sizeof(sym));
+        sym.si.SizeOfStruct = sizeof(SYMBOL_INFO);
+        sym.si.MaxNameLen = MAX_SYMBOL_NAME;
+
+        /* Try to find the symbol */
+        if (!SymFromAddr(ghProcess, ullFunction, &ullDisplacement, &sym.si))
+        {
+            printf("Error: SymFromAddr() failed. Error code: %u \n", GetLastError());
+            continue;
+        }
+
+        /* Symbol found. Check if it is a function */
+        if (sym.si.Tag == SymTagFunction)
+        {
+            /* If we don't have a name yet, get one */
+            pExport->pszSymbol = _strdup(sym.si.Name);
+
+            /* Get the calling convention */
+            if (!SymGetTypeInfo(ghProcess,
+                                dwModuleBase,
+                                sym.si.TypeIndex,
+                                TI_GET_CALLING_CONVENTION,
+                                &pExport->dwCallingConvention))
+            {
+                /* Fall back to __stdcall */
+                pExport->dwCallingConvention = 0x07; // CV_CALL_NEAR_STD
+            }
+
+            /* Set the context to the function address */
+            RtlZeroMemory(&StackFrame, sizeof(StackFrame));
+            StackFrame.InstructionOffset = ullFunction;
+            if (!SymSetContext(ghProcess, &StackFrame, NULL))
+            {
+                DWORD dwLastError = GetLastError();
+                __debugbreak();
+                continue;
+            }
+
+            /* Enumerate all symbols for this function */
+            if (!SymEnumSymbols(ghProcess,
+                                0, // use SymSetContext
+                                NULL,
+                                EnumParametersCallback,
+                                pExport))
+            {
+                DWORD dwLastError = GetLastError();
+                __debugbreak();
+                continue;
+            }
+        }
+        else if (sym.si.Tag == SymTagData)
+        {
+            pExport->fData = TRUE;
+        }
+    }
+
+    return S_OK;
+}
+
+const CHAR*
+GetCallingConvention(
+    _In_ PEXPORT pExport)
+{
+    if (pExport->fData)
+    {
+        return "extern";
+    }
+
+#ifndef _M_AMD64
+    switch (pExport->dwCallingConvention)
+    {
+        case CV_CALL_NEAR_C:
+            return "cdecl";
+        case CV_CALL_NEAR_FAST:
+            return "fastcall";
+        case CV_CALL_NEAR_STD:
+            return "stdcall";
+        case CV_CALL_NEAR_SYS:
+            return "syscall";
+        case CV_CALL_THISCALL:
+            return "thiscall";
+        case CV_CALL_CLRCALL:
+            return "clrcall";
+        default:
+            __debugbreak();
+    }
+#endif
+    return "stdcall";
+}
+
+HRESULT
+CreateSpecFile(
+    _In_ PCSTR pszSpecFile,
+    _In_ PEXPORT_DATA pExportData)
+{
+    FILE *file;
+    ULONG i, p;
+    PEXPORT pExport;
+
+    /* Create the spec file */
+    if (fopen_s(&file, pszSpecFile, "w") != 0)
+    {
+        fprintf(stderr, "Failed to open spec file: '%s'\n", pszSpecFile);
+        return E_FAIL;
+    }
+
+    /* Loop all exports */
+    for (i = 0; i < pExportData->cNumberOfExports; i++)
+    {
+        pExport = &pExportData->aExports[i];
+
+        fprintf(file, "%u %s ", i + 1, GetCallingConvention(pExport));
+        //if (pExport->fNoName)
+        if (pExport->pszName == NULL)
+        {
+            fprintf(file, "-noname ");
+        }
+
+        if (pExport->pszName != NULL)
+        {
+            fprintf(file, "%s", pExport->pszName);
+        }
+        else if (pExport->pszSymbol != NULL)
+        {
+            fprintf(file, "%s", pExport->pszSymbol);
+        }
+        else
+        {
+            fprintf(file, "NamelessExport_%u", i);
+        }
+
+        if (!pExport->fData)
+        {
+            fprintf(file, "(");
+            for (p = 0; p < pExport->cParameters; p++)
+            {
+                fprintf(file, "%s", gapszTypeStrings[pExport->aeParameters[p]]);
+                if ((p + 1) < pExport->cParameters)
+                {
+                    fprintf(file, " ");
+                }
+            }
+            fprintf(file, ")");
+        }
+
+        if (pExport->pszForwarder != NULL)
+        {
+            fprintf(file, " %s", pExport->pszForwarder);
+        }
+
+        fprintf(file, "\n");
+    }
+
+    fclose(file);
+
+    return S_OK;
+}
+
+int main(int argc, char* argv[])
+{
+    HRESULT hr;
+    CHAR szSpecFile[MAX_PATH];
+    PSTR pszSpecFile;
+    HANDLE hFile;
+    PEXPORT_DATA pExportData;
+
+    // check params
+        // help
+
+       ghProcess = GetCurrentProcess();
+
+       /* Open the file */
+       hr = OpenFileFromName(argv[1], &hFile);
+    if (!SUCCEEDED(hr))
+    {
+        fprintf(stderr, "Failed to open file: %lx\n", hr);
+        return hr;
+    }
+
+    /* Get the exports */
+    hr = GetExportsFromFile(hFile, &pExportData);
+    if (!SUCCEEDED(hr))
+    {
+        fprintf(stderr, "Failed to get exports: %lx\n", hr);
+        return hr;
+    }
+
+    /* Get additional info from symbols */
+    hr = ParseExportSymbols(hFile, pExportData);
+    if (!SUCCEEDED(hr))
+    {
+        fprintf(stderr, "Failed to get symbol information: %lx\n", hr);
+    }
+
+    if (argc > 2)
+    {
+        pszSpecFile = argv[2];
+    }
+    else
+    {
+        PSTR pszStart = strrchr(argv[1], '\\');
+        if (pszStart == 0)
+            pszStart = argv[1];
+
+        strcpy_s(szSpecFile, sizeof(szSpecFile), pszStart);
+        strcat_s(szSpecFile, sizeof(szSpecFile), ".spec");
+        pszSpecFile = szSpecFile;
+    }
+
+    hr = CreateSpecFile(pszSpecFile, pExportData);
+
+    CloseHandle(hFile);
+
+    printf("Spec file '%s' was successfully written.\n", szSpecFile);
+
+    return hr;
+}