[CREATESPEC]
[reactos.git] / rosapps / applications / devutils / createspec / createspec.c
index b9fbbef..b793b6d 100644 (file)
@@ -8,18 +8,31 @@
     - Resolve forwarders
 
 */
+#define MINGW_HAS_SECURE_API
 #include <stdio.h>
 #include <stdlib.h>
 #include <windows.h>
 
+#ifdef __REACTOS__
+#include <dbghelp.h>
+#include <cvconst.h>
+
+// dirty hacks!
+#define sprintf_s(dst, size, format, ...) sprintf(dst, format, __VA_ARGS__)
+#define vsprintf_s(dst, size, format, ap) vsprintf(dst, format, ap)
+#define fopen_s(pfile, name, mode) ((*pfile = fopen(name, mode)), (*pfile != 0) ? 0 : -1)
+#define strcpy_s(dst, size, src) strncpy(dst, src, size)
+#define strcat_s(dst, size, src) strncat(dst, src, size)
+
+#else
 #ifdef _MSC_VER
 #pragma warning(disable:4091)
 #endif
 #define _NO_CVCONST_H
 #include <dbghelp.h>
 
-// doesn't seem to be defined anywhere
-enum BasicType { 
+// This is from cvconst.h, but win sdk lacks this file
+enum BasicType {
    btNoType   = 0,
    btVoid     = 1,
    btChar     = 2,
@@ -40,7 +53,7 @@ enum BasicType {
    btHresult  = 31
 };
 
-typedef enum CV_call_e { 
+typedef enum CV_call_e {
    CV_CALL_NEAR_C    = 0x00,
    CV_CALL_NEAR_FAST = 0x04,
    CV_CALL_NEAR_STD  = 0x07,
@@ -49,13 +62,21 @@ typedef enum CV_call_e {
    CV_CALL_CLRCALL   = 0x16
 } CV_call_e;
 
-#define MAX_SYMBOL_NAME                1024
+#endif // __REACTOS__
+
+#define MAX_SYMBOL_NAME        1024
 typedef struct _SYMINFO_EX
 {
     SYMBOL_INFO si;
     CHAR achName[MAX_SYMBOL_NAME];
 } SYMINFO_EX;
 
+typedef struct _SYMBOL64_EX
+{
+    IMAGEHLP_SYMBOL64 sym64;
+    CHAR achName[MAX_SYMBOL_NAME];
+} SYMBOL64_EX, *PSYMBOL64_EX;
+
 typedef enum _PARAM_TYPES
 {
     TYPE_NONE,
@@ -99,57 +120,203 @@ typedef struct _EXPORT_DATA
 } EXPORT_DATA, *PEXPORT_DATA;
 
 HANDLE ghProcess;
-CHAR gszModuleFileName[MAX_PATH+1];
 
-HRESULT
-OpenFileFromName(
-    _In_ PCSTR pszDllName,
-    _Out_ PHANDLE phFile)
+void
+error(
+    _In_ const char* pszFormat,
+    ...)
+{
+    CHAR szBuffer[512];
+    SIZE_T cchBuffer;
+    DWORD dwLastError;
+    va_list argptr;
+
+    /* Get last error */
+    dwLastError = GetLastError();
+
+    va_start(argptr, pszFormat);
+    cchBuffer = vsprintf_s(szBuffer, sizeof(szBuffer), pszFormat, argptr);
+    va_end(argptr);
+
+    /* Strip trailing newlines */
+    _Analysis_assume_(cchBuffer < sizeof(szBuffer));
+    while ((cchBuffer >= 1) &&
+           ((szBuffer[cchBuffer - 1] == '\r') ||
+            (szBuffer[cchBuffer - 1] == '\n')))
+    {
+        szBuffer[cchBuffer - 1] = '\0';
+        cchBuffer--;
+    }
+
+    /* Check if we have an error */
+    if (dwLastError != ERROR_SUCCESS)
+    {
+        /* Append error code */
+        cchBuffer += sprintf_s(szBuffer + cchBuffer,
+                               sizeof(szBuffer) - cchBuffer,
+                               " [error %lu: ", dwLastError);
+
+        /* Format the last error code */
+        cchBuffer += FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM,
+                                    NULL,
+                                    dwLastError,
+                                    0,
+                                    szBuffer + cchBuffer,
+                                    (DWORD)(sizeof(szBuffer) - cchBuffer),
+                                    NULL);
+
+        /* Strip trailing newlines */
+        _Analysis_assume_(cchBuffer < sizeof(szBuffer));
+        while ((cchBuffer >= 1) &&
+               ((szBuffer[cchBuffer - 1] == '\r') ||
+                (szBuffer[cchBuffer - 1] == '\n')))
+        {
+            szBuffer[cchBuffer - 1] = '\0';
+            cchBuffer--;
+        }
+
+        fprintf(stderr, "%s]\n", szBuffer);
+    }
+    else
+    {
+        fprintf(stderr, "%s\n", szBuffer);
+    }
+}
+
+BOOL
+InitDbgHelp(
+    VOID)
+{
+    static const char *pszMsSymbolServer = "srv**symbols*http://msdl.microsoft.com/download/symbols";
+    DWORD Options;
+
+    /* Save current process ;-) */
+    ghProcess = GetCurrentProcess();
+
+    /* Initialize dbghelp */
+    if (!SymInitialize(ghProcess, 0, FALSE))
+    {
+        error("SymInitialize() failed.");
+        return FALSE;
+    }
+
+    /* Set options */
+    Options = SymGetOptions();
+    Options |= SYMOPT_ALLOW_ABSOLUTE_SYMBOLS | SYMOPT_INCLUDE_32BIT_MODULES | SYMOPT_DEBUG;// | SYMOPT_NO_PROMPTS;
+    Options &= ~SYMOPT_DEFERRED_LOADS;
+    SymSetOptions(Options);
+
+    /* Test if we can reach the MS symbol server */
+    if (!SymSrvIsStore(ghProcess, pszMsSymbolServer))
+    {
+        error("Failed to connect to symbol server.");
+        return FALSE;
+    }
+
+    /* Set MS symbol server as symbol search path */
+    SymSetSearchPath(ghProcess, pszMsSymbolServer);
+
+    return TRUE;
+}
+
+HMODULE
+LoadModuleWithSymbolsFullPath(
+    _In_ PSTR pszFullModuleFileName)
 {
-       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());
+    HMODULE hmod;
+    DWORD64 dwModuleBase;
+
+    /* Load the DLL */
+    hmod = LoadLibraryExA(pszFullModuleFileName,
+                          NULL,
+                          LOAD_IGNORE_CODE_AUTHZ_LEVEL |
+                          DONT_RESOLVE_DLL_REFERENCES |
+                          LOAD_WITH_ALTERED_SEARCH_PATH);
+    if (hmod == NULL)
+    {
+        return NULL;
+    }
+
+    /* Load symbols for this module */
+    dwModuleBase = SymLoadModule64(ghProcess,
+                                   NULL,
+                                   pszFullModuleFileName,
+                                   NULL,
+                                   (DWORD_PTR)hmod,
+                                   0);
+    if (dwModuleBase == 0)
+    {
+        /* ERROR_SUCCESS means, we have symbols already */
+        if (GetLastError() != ERROR_SUCCESS)
+        {
+            return NULL;
+        }
+    }
+    else
+    {
+        printf("Successfully loaded symbols for '%s'\n",
+               pszFullModuleFileName);
+    }
+
+    return hmod;
+}
+
+HMODULE
+LoadModuleWithSymbols(
+    _In_ PSTR pszModuleName)
+{
+    CHAR szFullFileName[MAX_PATH];
+    HMODULE hmod;
+
+    /* Check if the file name has a path */
+    if (strchr(pszModuleName, '\\') != NULL)
+    {
+        /* Try as it is */
+        hmod = LoadModuleWithSymbolsFullPath(pszModuleName);
+        if (hmod != NULL)
+        {
+            return hmod;
+        }
+    }
+
+    /* Try current directory */
+    GetCurrentDirectoryA(MAX_PATH, szFullFileName);
+    strcat_s(szFullFileName, sizeof(szFullFileName), "\\");
+    strcat_s(szFullFileName, sizeof(szFullFileName), pszModuleName);
+    hmod = LoadModuleWithSymbolsFullPath(szFullFileName);
+    if (hmod != NULL)
+    {
+        return hmod;
+    }
+
+    /* Try system32 */
+    strcpy_s(szFullFileName, sizeof(szFullFileName), "%systemroot%\\system32");
+    strcat_s(szFullFileName, sizeof(szFullFileName), pszModuleName);
+    hmod = LoadModuleWithSymbolsFullPath(szFullFileName);
+    if (hmod != NULL)
+    {
+        return hmod;
+    }
+
+#ifdef _WIN64
+    /* Try SysWoW64 */
+    strcpy_s(szFullFileName, sizeof(szFullFileName), "%systemroot%\\system32");
+    strcat_s(szFullFileName, sizeof(szFullFileName), pszModuleName);
+    hmod = LoadModuleWithSymbolsFullPath(szFullFileName);
+    if (hmod != NULL)
+    {
+        return hmod;
+    }
+#endif // _WIN64
+
+    return NULL;
 }
 
 HRESULT
 GetExportsFromFile(
-    _In_ HANDLE hFile,
+    _In_ HMODULE hmod,
     _Out_ PEXPORT_DATA* ppExportData)
 {
-    HANDLE hMap;
     PBYTE pjImageBase;
     PIMAGE_EXPORT_DIRECTORY pExportDir;
     ULONG i, cjExportSize, cFunctions, cjTableSize;
@@ -157,28 +324,18 @@ GetExportsFromFile(
     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,
+    pjImageBase = (PBYTE)hmod;
+
+    /* Get the export directory */
+    pExportDir = ImageDirectoryEntryToData(pjImageBase,
                                            TRUE,
                                            IMAGE_DIRECTORY_ENTRY_EXPORT,
                                            &cjExportSize);
+    if (pExportDir == NULL)
+    {
+        fprintf(stderr, "Failed to get export directory\n");
+        return E_FAIL;
+    }
 
     cFunctions = pExportDir->NumberOfFunctions;
     cjTableSize = FIELD_OFFSET(EXPORT_DATA, aExports[cFunctions]);
@@ -186,6 +343,7 @@ GetExportsFromFile(
     pExportData = malloc(cjTableSize);
     if (pExportData == NULL)
     {
+        error("Failed to allocate %u bytes of memory for export table\n", cjTableSize);
         return E_OUTOFMEMORY;
     }
 
@@ -199,7 +357,8 @@ GetExportsFromFile(
     for (i = 0; i < cFunctions; i++)
     {
         PVOID pvFunction = (pjImageBase + pulAddressTable[i]);
-        
+
+        /* Check if this is a forwarder */
         if ((ULONG_PTR)((PUCHAR)pvFunction - (PUCHAR)pExportDir) < cjExportSize)
         {
             pExportData->aExports[i].pszForwarder = _strdup(pvFunction);
@@ -223,8 +382,6 @@ GetExportsFromFile(
     }
 
     *ppExportData = pExportData;
-    UnmapViewOfFile(pjImageBase);
-    CloseHandle(hMap);
     return S_OK;
 }
 
@@ -233,7 +390,7 @@ CALLBACK
 EnumParametersCallback(
     _In_ PSYMBOL_INFO pSymInfo,
     _In_ ULONG SymbolSize,
-    _In_opt_ PVOID UserContext)
+    _In_ PVOID UserContext)
 {
     PEXPORT pExport = (PEXPORT)UserContext;
     enum SymTagEnum eSymTag;
@@ -282,7 +439,8 @@ EnumParametersCallback(
                     break;
                 }
             }
-            /* 'long' type */
+
+            /* Default to 'long' type */
             pExport->aeParameters[pExport->cParameters - 1] = TYPE_LONG;
             break;
 
@@ -309,12 +467,15 @@ EnumParametersCallback(
                                     TI_GET_BASETYPE,
                                     &eBaseType))
                 {
+                    /* Check for string types */
                     if (eBaseType == btChar)
                     {
+                        /* 'str' type */
                         pExport->aeParameters[pExport->cParameters - 1] = TYPE_STR;
                     }
                     else if (eBaseType == btWChar)
                     {
+                        /* 'wstr' type */
                         pExport->aeParameters[pExport->cParameters - 1] = TYPE_WSTR;
                     }
                 }
@@ -335,46 +496,92 @@ EnumParametersCallback(
     return TRUE;
 }
 
+ULONG64
+GetFunctionFromForwarder(
+    _In_ PCSTR pszForwarder)
+{
+    CHAR szDllName[MAX_SYMBOL_NAME];
+    PCH pchDot, pszName;
+    ULONG64 ullFunction;
+    HMODULE hmod;
+
+    /* Copy the forwarder name */
+    strcpy_s(szDllName, sizeof(szDllName), pszForwarder);
+
+    /* Find the '.' */
+    pchDot = strchr(szDllName, '.');
+    if (pchDot == NULL)
+    {
+        error("Invalid name for forwarder '%s'!", pszForwarder);
+        return 0;
+    }
+
+    /* Terminate DLL name */
+    *pchDot = '\0';
+
+    /* Load the DLL */
+    hmod = LoadModuleWithSymbols(szDllName);
+    if (hmod == NULL)
+    {
+        error("Failed to load module for forwarder '%s'!", pszForwarder);
+        return 0;
+    }
+
+    /* Get the function name and check for ordinal */
+    pszName = pchDot + 1;
+    if (pszName[0] == '#')
+    {
+        ULONG iOrdinal = strtoul(pszName + 1, NULL, 10);
+        if ((iOrdinal == 0) || (iOrdinal > 0xFFFF))
+        {
+            error("Got invalid ordinal %u for ''", iOrdinal, pszForwarder);
+            return 0;
+        }
+
+        pszName = (PSTR)(ULONG_PTR)iOrdinal;
+    }
+
+    /* Get the function address */
+    ullFunction = (ULONG_PTR)GetProcAddress(hmod, pszName);
+    if (ullFunction == 0)
+    {
+        error("Failed to resolve '%s' in '%s'.", pchDot + 1, szDllName);
+        return 0;
+    }
+
+    return ullFunction;
+}
+
 HRESULT
-ParseExportSymbols(
-    _In_ HANDLE hFile,
+ParseImageSymbols(
+    _In_ HMODULE hmod,
     _Inout_ PEXPORT_DATA pExportData)
 {
-    DWORD Options;
     DWORD64 dwModuleBase;
     ULONG i;
     IMAGEHLP_STACK_FRAME StackFrame;
     SYMINFO_EX sym;
 
+    dwModuleBase = (DWORD_PTR)hmod;
 
-    /* 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;
-       }
-
-
+    /* Loop through all exports */
     for (i = 0; i < pExportData->cNumberOfExports; i++)
     {
         PEXPORT pExport = &pExportData->aExports[i];
         ULONG64 ullFunction = dwModuleBase + pExportData->aExports[i].ulRva;
         ULONG64 ullDisplacement;
 
-        /* Skip forwarder */
+        /* Check if this is a forwarder */
         if (pExport->pszForwarder != NULL)
-            continue;
+        {
+            /* Load the module and get the function address */
+            ullFunction = GetFunctionFromForwarder(pExport->pszForwarder);
+            if (ullFunction == 0)
+            {
+                printf("Failed to get function for forwarder '%s'. Skipping.\n", pExport->pszForwarder);
+                continue;
+            }
+        }
 
         RtlZeroMemory(&sym, sizeof(sym));
         sym.si.SizeOfStruct = sizeof(SYMBOL_INFO);
@@ -383,16 +590,16 @@ ParseExportSymbols(
         /* Try to find the symbol */
         if (!SymFromAddr(ghProcess, ullFunction, &ullDisplacement, &sym.si))
         {
-            printf("Error: SymFromAddr() failed. Error code: %u \n", GetLastError());
+            error("Error: SymFromAddr() failed.");
             continue;
         }
 
-        /* Symbol found. Check if it is a function */
+        /* Get the symbol name */
+        pExport->pszSymbol = _strdup(sym.si.Name);
+
+        /* 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,
@@ -401,7 +608,7 @@ ParseExportSymbols(
                                 &pExport->dwCallingConvention))
             {
                 /* Fall back to __stdcall */
-                pExport->dwCallingConvention = 0x07; // CV_CALL_NEAR_STD
+                pExport->dwCallingConvention = CV_CALL_NEAR_STD;
             }
 
             /* Set the context to the function address */
@@ -409,8 +616,7 @@ ParseExportSymbols(
             StackFrame.InstructionOffset = ullFunction;
             if (!SymSetContext(ghProcess, &StackFrame, NULL))
             {
-                DWORD dwLastError = GetLastError();
-                __debugbreak();
+                error("SymSetContext failed for i = %u.", i);
                 continue;
             }
 
@@ -421,11 +627,14 @@ ParseExportSymbols(
                                 EnumParametersCallback,
                                 pExport))
             {
-                DWORD dwLastError = GetLastError();
-                __debugbreak();
+                error("SymEnumSymbols failed for i = %u.", i);
                 continue;
             }
         }
+        else if (sym.si.Tag == SymTagPublicSymbol)
+        {
+            pExport->dwCallingConvention = CV_CALL_NEAR_STD;
+        }
         else if (sym.si.Tag == SymTagData)
         {
             pExport->fData = TRUE;
@@ -457,8 +666,6 @@ GetCallingConvention(
             return "syscall";
         case CV_CALL_THISCALL:
             return "thiscall";
-        case CV_CALL_CLRCALL:
-            return "clrcall";
         default:
             __debugbreak();
     }
@@ -478,7 +685,7 @@ CreateSpecFile(
     /* Create the spec file */
     if (fopen_s(&file, pszSpecFile, "w") != 0)
     {
-        fprintf(stderr, "Failed to open spec file: '%s'\n", pszSpecFile);
+        error("Failed to open spec file: '%s'\n", pszSpecFile);
         return E_FAIL;
     }
 
@@ -487,7 +694,7 @@ CreateSpecFile(
     {
         pExport = &pExportData->aExports[i];
 
-        fprintf(file, "%u %s ", i + 1, GetCallingConvention(pExport));
+        fprintf(file, "%lu %s ", i + 1, GetCallingConvention(pExport));
         //if (pExport->fNoName)
         if (pExport->pszName == NULL)
         {
@@ -504,7 +711,7 @@ CreateSpecFile(
         }
         else
         {
-            fprintf(file, "NamelessExport_%u", i);
+            fprintf(file, "NamelessExport_%lu", i);
         }
 
         if (!pExport->fData)
@@ -539,43 +746,24 @@ int main(int argc, char* argv[])
     HRESULT hr;
     CHAR szSpecFile[MAX_PATH];
     PSTR pszSpecFile;
-    HANDLE hFile;
     PEXPORT_DATA pExportData;
+    HMODULE hmod;
 
-    // check params
-        // help
-
-       ghProcess = GetCurrentProcess();
-
-       /* Open the file */
-       hr = OpenFileFromName(argv[1], &hFile);
-    if (!SUCCEEDED(hr))
+    /* Check parameters */
+    if ((argc < 2) || !strcmp(argv[1], "/?"))
     {
-        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);
+        printf("syntax: createspec <image file> [<spec file>]\n");
+        return 0;
     }
 
+    /* Check if we have a spec file name */
     if (argc > 2)
     {
         pszSpecFile = argv[2];
     }
     else
     {
+        /* Create spec file name from image file name */
         PSTR pszStart = strrchr(argv[1], '\\');
         if (pszStart == 0)
             pszStart = argv[1];
@@ -585,9 +773,40 @@ int main(int argc, char* argv[])
         pszSpecFile = szSpecFile;
     }
 
-    hr = CreateSpecFile(pszSpecFile, pExportData);
+    /* Initialize dbghelp.dll */
+    if (!InitDbgHelp())
+    {
+        error("Failed to init dbghelp!\n"
+              "Make sure you have dbghelp.dll and symsrv.dll in the same folder.\n");
+        return E_FAIL;
+    }
+
+    /* Load the file including symbols */
+    printf("Loading symbols for '%s', please wait...\n", argv[1]);
+    hmod = LoadModuleWithSymbols(argv[1]);
+    if (hmod == NULL)
+    {
+        error("Failed to load module '%s'!", argv[1]);
+        return E_FAIL;
+    }
 
-    CloseHandle(hFile);
+    /* Get the exports */
+    hr = GetExportsFromFile(hmod, &pExportData);
+    if (!SUCCEEDED(hr))
+    {
+        error("Failed to get exports: %lx\n", hr);
+        return hr;
+    }
+
+    /* Get additional info from symbols */
+    hr = ParseImageSymbols(hmod, pExportData);
+    if (!SUCCEEDED(hr))
+    {
+        error("Failed to get symbol information: hr=%lx\n", hr);
+    }
+
+    /* Write the spec file */
+    hr = CreateSpecFile(pszSpecFile, pExportData);
 
     printf("Spec file '%s' was successfully written.\n", szSpecFile);