/* - 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 */ #define MINGW_HAS_SECURE_API #include #include #include #ifdef __REACTOS__ #include #include // 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 // This is from cvconst.h, but win sdk lacks this file 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; #endif // __REACTOS__ 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; 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) { 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%\\SysWOW64\\"); strcat_s(szFullFileName, sizeof(szFullFileName), pszModuleName); hmod = LoadModuleWithSymbolsFullPath(szFullFileName); if (hmod != NULL) { return hmod; } #endif // _WIN64 return NULL; } HRESULT GetExportsFromFile( _In_ HMODULE hmod, _Out_ PEXPORT_DATA* ppExportData) { PBYTE pjImageBase; PIMAGE_EXPORT_DIRECTORY pExportDir; ULONG i, cjExportSize, cFunctions, cjTableSize; PEXPORT_DATA pExportData; PULONG pulAddressTable, pulNameTable; PUSHORT pusOrdinalTable; 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]); pExportData = malloc(cjTableSize); if (pExportData == NULL) { error("Failed to allocate %u bytes of memory for export table\n", cjTableSize); 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]); /* Check if this is a forwarder */ 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; return S_OK; } BOOL CALLBACK EnumParametersCallback( _In_ PSYMBOL_INFO pSymInfo, _In_ ULONG SymbolSize, _In_ 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; } } /* Default to '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)) { /* 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; } } } 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; } ULONG64 GetFunctionFromForwarder( _In_ PCSTR pszForwarder) { CHAR szDllName[MAX_SYM_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 ParseImageSymbols( _In_ HMODULE hmod, _Inout_ PEXPORT_DATA pExportData) { DWORD64 dwModuleBase; ULONG i; IMAGEHLP_STACK_FRAME StackFrame; SYMBOL_INFO_PACKAGE sym; dwModuleBase = (DWORD_PTR)hmod; /* 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; /* Check if this is a forwarder */ if (pExport->pszForwarder != NULL) { /* 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); sym.si.MaxNameLen = MAX_SYM_NAME; /* Try to find the symbol */ if (!SymFromAddr(ghProcess, ullFunction, &ullDisplacement, &sym.si)) { error("Error: SymFromAddr() failed."); continue; } /* Get the symbol name */ pExport->pszSymbol = _strdup(sym.si.Name); /* Check if it is a function */ if (sym.si.Tag == SymTagFunction) { /* Get the calling convention */ if (!SymGetTypeInfo(ghProcess, dwModuleBase, sym.si.TypeIndex, TI_GET_CALLING_CONVENTION, &pExport->dwCallingConvention)) { /* Fall back to __stdcall */ pExport->dwCallingConvention = CV_CALL_NEAR_STD; } /* Set the context to the function address */ RtlZeroMemory(&StackFrame, sizeof(StackFrame)); StackFrame.InstructionOffset = ullFunction; if (!SymSetContext(ghProcess, &StackFrame, NULL)) { error("SymSetContext failed for i = %u.", i); continue; } /* Enumerate all symbols for this function */ if (!SymEnumSymbols(ghProcess, 0, // use SymSetContext NULL, EnumParametersCallback, pExport)) { 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; } } 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"; 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) { error("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, "%lu %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_%lu", 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; PEXPORT_DATA pExportData; HMODULE hmod; /* Check parameters */ if ((argc < 2) || !strcmp(argv[1], "/?")) { printf("syntax: createspec []\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]; strcpy_s(szSpecFile, sizeof(szSpecFile), pszStart); strcat_s(szSpecFile, sizeof(szSpecFile), ".spec"); pszSpecFile = szSpecFile; } /* 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; } /* 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); return hr; }