diff --git a/tools/xtcspecc.c b/tools/xtcspecc.c new file mode 100644 index 0000000..d65f39f --- /dev/null +++ b/tools/xtcspecc.c @@ -0,0 +1,1573 @@ +/** + * PROJECT: XTchain + * LICENSE: See COPYING.md in the top level directory + * FILE: tools/xtcspecc.c + * DESCRIPTION: SPEC Compiler + * DEVELOPERS: Timo Kreuzer + * Mark Jansen + * Amine Khaldi + * Thomas Faber + * Cameron Gutman + * Jerome Gardou + * Rafal Kupiec + */ + +#include "xtchain.h" + +#define XTCSPECC_VERSION "1.2" + +#define ARRAYSIZE(a) (sizeof(a) / sizeof((a)[0])) + +typedef struct _STRING +{ + const char *buf; + int len; +} STRING, *PSTRING; + +typedef struct +{ + STRING strName; + STRING strTarget; + int nCallingConvention; + int nOrdinal; + int nStackBytes; + int nArgCount; + int anArgs[30]; + unsigned int uFlags; + int nNumber; + unsigned nStartVersion; + unsigned nEndVersion; + int bVersionIncluded; +} EXPORT; + +enum _ARCH +{ + ARCH_X86, + ARCH_AMD64, + ARCH_ARM, + ARCH_ARM64 +}; + +typedef int (*PFNOUTLINE)(FILE *, EXPORT *); +int gbMSComp = 0; +int gbImportLib = 0; +int gbNotPrivateNoWarn = 0; +int gbTracing = 0; +int giArch = ARCH_X86; +char *pszArchString; +char *pszArchString2; +char *pszExecName; +char *pszSourceFileName = NULL; +char *pszDllName = NULL; +char *gpszUnderscore = ""; +int gbDebug; +unsigned guOsVersion = 0x502; + +#define DbgPrint(...) (!gbDebug || fprintf(stderr, __VA_ARGS__)) + +enum +{ + FL_PRIVATE = 1, + FL_STUB = 2, + FL_NONAME = 4, + FL_ORDINAL = 8, + FL_NORELAY = 16, + FL_RET64 = 32, + FL_REGISTER = 64, +}; + +enum +{ + CC_STDCALL, + CC_CDECL, + CC_FASTCALL, + CC_THISCALL, + CC_EXTERN, + CC_STUB, +}; + +enum +{ + ARG_LONG, + ARG_PTR, + ARG_STR, + ARG_WSTR, + ARG_DBL, + ARG_INT64, + ARG_INT128, + ARG_FLOAT +}; + +const char* astrCallingConventions[] = +{ + "STDCALL", + "CDECL", + "FASTCALL", + "THISCALL", + "EXTERN" +}; + +/* + * List of OLE exports that should be PRIVATE and not be assigned an ordinal. + * In case these conditions are not met when linking with MS LINK.EXE, warnings + * LNK4104 and LNK4222 respectively are emitted. + */ +static const char* astrOlePrivateExports[] = +{ + "DllCanUnloadNow", + "DllGetClassObject", + "DllGetClassFactoryFromClassString", + "DllGetDocumentation", + "DllInitialize", + "DllInstall", + "DllRegisterServer", + "DllRegisterServerEx", + "DllRegisterServerExW", + "DllUnload", + "DllUnregisterServer", + "RasCustomDeleteEntryNotify", + "RasCustomDial", + "RasCustomDialDlg", + "RasCustomEntryDlg", +}; + +static +int +IsSeparator(char chr) +{ + return ((chr <= ',' && chr != '$' && chr != '#') || + (chr >= ':' && chr < '?') ); +} + +int +CompareToken(const char *token, + const char *comparand) +{ + while(*comparand) + { + if(*token != *comparand) return 0; + token++; + comparand++; + } + + if(IsSeparator(comparand[-1])) + { + return 1; + } + + if(!IsSeparator(*token)) + { + return 0; + } + + return 1; +} + +const char * +ScanToken(const char *token, + char chr) +{ + while(!IsSeparator(*token)) + { + if(*token == chr) + { + return token; + } + token++; + } + return 0; +} + +const char * +NextLine(const char *pc) +{ + while(*pc != 0) + { + if(pc[0] == '\n' && pc[1] == '\r') return pc + 2; + else if(pc[0] == '\n') return pc + 1; + pc++; + } + return pc; +} + +int +TokenLength(const char *pc) +{ + int length = 0; + + while(!IsSeparator(*pc++)) + { + length++; + } + + return length; +} + +const char * +NextToken(const char *pc) +{ + /* Skip token */ + while(!IsSeparator(*pc)) + { + pc++; + } + + /* Skip white spaces */ + while(*pc == ' ' || *pc == '\t') + { + pc++; + } + + /* Check for end of line */ + if(*pc == '\n' || *pc == '\r' || *pc == 0) + { + return 0; + } + + /* Check for comment */ + if(*pc == '#' || *pc == ';') + { + return 0; + } + + return pc; +} + +void +OutputHeader_stub(FILE *file) +{ + fprintf(file, "/* This file is generated automatically by %s, do not edit! */\n\n" + "#include \n", + pszExecName); + + if(gbTracing) + { + fprintf(file, "#include \n"); + fprintf(file, "#include \n"); + fprintf(file, "WINE_DECLARE_DEBUG_CHANNEL(relay);\n"); + } + + fprintf(file, "\n"); +} + +int +OutputLine_stub(FILE *file, + EXPORT *pexp) +{ + int i; + int bRelay = 0; + int bInPrototype = 0; + + if(pexp->nCallingConvention != CC_STUB && + (pexp->uFlags & FL_STUB) == 0) + { + /* Only relay trace stdcall C functions */ + if(!gbTracing || (pexp->nCallingConvention != CC_STDCALL) || + (pexp->uFlags & FL_NORELAY) || + (pexp->strName.buf[0] == '?')) + { + return 0; + } + bRelay = 1; + } + + /* Declare the "real" function */ + if(bRelay) + { + fprintf(file, "extern "); + bInPrototype = 1; + } + + do + { + if(pexp->uFlags & FL_REGISTER) + { + /* FIXME: Not sure this is right */ + fprintf(file, "void "); + } + else if(pexp->uFlags & FL_RET64) + { + fprintf(file, "__int64 "); + } + else + { + fprintf(file, "int "); + } + + if((giArch == ARCH_X86) && + pexp->nCallingConvention == CC_STDCALL) + { + fprintf(file, "__stdcall "); + } + + /* Check for C++ */ + if(pexp->strName.buf[0] == '?') + { + fprintf(file, "stub_function%d(", pexp->nNumber); + } + else + { + if(!bRelay || bInPrototype) + { + fprintf(file, "%.*s(", pexp->strName.len, pexp->strName.buf); + } + else + { + fprintf(file, "$relaytrace$%.*s(", pexp->strName.len, pexp->strName.buf); + } + } + + for(i = 0; i < pexp->nArgCount; i++) + { + if(i != 0) fprintf(file, ", "); + switch(pexp->anArgs[i]) + { + case ARG_LONG: fprintf(file, "long"); break; + case ARG_PTR: fprintf(file, "void*"); break; + case ARG_STR: fprintf(file, "char*"); break; + case ARG_WSTR: fprintf(file, "wchar_t*"); break; + case ARG_DBL: fprintf(file, "double"); break; + case ARG_INT64 : fprintf(file, "__int64"); break; + /* __int128 is not supported on x86, and int128 in spec files most often represents a GUID */ + case ARG_INT128 : fprintf(file, "GUID"); break; + case ARG_FLOAT: fprintf(file, "float"); break; + } + fprintf(file, " a%d", i); + } + + if(bInPrototype) + { + fprintf(file, ");\n\n"); + } + } while(bInPrototype--); + + if(!bRelay) + { + fprintf(file, ")\n{\n\tDbgPrint(\"WARNING: calling stub %.*s(", + pexp->strName.len, pexp->strName.buf); + } + else + { + fprintf(file, ")\n{\n"); + if(pexp->uFlags & FL_REGISTER) + { + /* No return value */ + } + else if(pexp->uFlags & FL_RET64) + { + fprintf(file, "\t__int64 retval;\n"); + } + else + { + fprintf(file, "\tint retval;\n"); + } + fprintf(file, "\tif(TRACE_ON(relay))\n\t\tDPRINTF(\"%s: %.*s(", + pszDllName, pexp->strName.len, pexp->strName.buf); + } + + for(i = 0; i < pexp->nArgCount; i++) + { + if(i != 0) fprintf(file, ","); + switch(pexp->anArgs[i]) + { + case ARG_LONG: fprintf(file, "0x%%lx"); break; + case ARG_PTR: fprintf(file, "0x%%p"); break; + case ARG_STR: fprintf(file, "'%%s'"); break; + case ARG_WSTR: fprintf(file, "'%%ws'"); break; + case ARG_DBL: fprintf(file, "%%f"); break; + case ARG_INT64: fprintf(file, "%%\"PRIx64\""); break; + case ARG_INT128: fprintf(file, "'%%s'"); break; + case ARG_FLOAT: fprintf(file, "%%f"); break; + } + } + fprintf(file, ")\\n\""); + + for(i = 0; i < pexp->nArgCount; i++) + { + fprintf(file, ", "); + switch(pexp->anArgs[i]) + { + case ARG_LONG: fprintf(file, "(long)a%d", i); break; + case ARG_PTR: fprintf(file, "(void*)a%d", i); break; + case ARG_STR: fprintf(file, "(char*)a%d", i); break; + case ARG_WSTR: fprintf(file, "(wchar_t*)a%d", i); break; + case ARG_DBL: fprintf(file, "(double)a%d", i); break; + case ARG_INT64: fprintf(file, "(__int64)a%d", i); break; + case ARG_INT128: fprintf(file, "wine_dbgstr_guid(&a%d)", i); break; + case ARG_FLOAT: fprintf(file, "(float)a%d", i); break; + } + } + fprintf(file, ");\n"); + + if(pexp->nCallingConvention == CC_STUB) + { + fprintf(file, "\t__wine_spec_unimplemented_stub(\"%s\", __FUNCTION__);\n", pszDllName); + } + else if(bRelay) + { + if(pexp->uFlags & FL_REGISTER) + { + fprintf(file,"\t"); + } + else + { + fprintf(file, "\tretval = "); + } + fprintf(file, "%.*s(", pexp->strName.len, pexp->strName.buf); + + for(i = 0; i < pexp->nArgCount; i++) + { + if(i != 0) fprintf(file, ", "); + fprintf(file, "a%d", i); + } + fprintf(file, ");\n"); + } + + if(!bRelay) + { + fprintf(file, "\treturn 0;\n}\n\n"); + } + else if((pexp->uFlags & FL_REGISTER) == 0) + { + if(pexp->uFlags & FL_RET64) + { + fprintf(file, "\tif(TRACE_ON(relay))\n\t\tDPRINTF(\"%s: %.*s: retval = %%\"PRIx64\"\\n\", retval);\n", + pszDllName, pexp->strName.len, pexp->strName.buf); + } + else + { + fprintf(file, "\tif(TRACE_ON(relay))\n\t\tDPRINTF(\"%s: %.*s: retval = 0x%%lx\\n\", retval);\n", + pszDllName, pexp->strName.len, pexp->strName.buf); + } + fprintf(file, "\treturn retval;\n}\n\n"); + } + + return 1; +} + +void +Output_stublabel(FILE *fileDest, + char* pszSymbolName) +{ + if((giArch == ARCH_ARM) || (giArch == ARCH_ARM64)) + { + fprintf(fileDest, + "\tEXPORT |%s| [FUNC]\n|%s|\n", + pszSymbolName, + pszSymbolName); + } + else + { + fprintf(fileDest, + "PUBLIC %s\n%s: nop\n", + pszSymbolName, + pszSymbolName); + } +} + +void +OutputHeader_def(FILE *file, + char *libname) +{ + fprintf(file, + "; This file is generated automatically by %s, do not edit!\n\n" + "NAME %s\n\n" + "EXPORTS\n", + pszExecName, + libname); +} + +void +PrintName(FILE *fileDest, + EXPORT *pexp, + PSTRING pstr, + int fDeco) +{ + const char *pcName = pstr->buf; + int nNameLength = pstr->len; + const char* pcDot, *pcAt; + char namebuffer[19]; + + if((nNameLength == 1) && (pcName[0] == '@')) + { + sprintf(namebuffer, "ordinal%d", pexp->nOrdinal); + pcName = namebuffer; + nNameLength = strlen(namebuffer); + } + + /* Check for non-x86 first */ + if(giArch != ARCH_X86) + { + /* Does the string already have stdcall decoration? */ + pcAt = ScanToken(pcName, '@'); + if(pcAt && (pcAt < (pcName + nNameLength)) && (pcName[0] == '_')) + { + /* Skip leading underscore and remove trailing decoration */ + pcName++; + nNameLength = (int)(pcAt - pcName); + } + + /* Print the undecorated function name */ + fprintf(fileDest, "%.*s", nNameLength, pcName); + } + else if(fDeco && + ((pexp->nCallingConvention == CC_STDCALL) || + (pexp->nCallingConvention == CC_FASTCALL))) + { + /* Scan for a dll forwarding dot */ + pcDot = ScanToken(pcName, '.'); + if(pcDot) + { + /* First print the dll name, followed by a dot */ + nNameLength = (int)(pcDot - pcName); + fprintf(fileDest, "%.*s.", nNameLength, pcName); + + /* Now the actual function name */ + pcName = pcDot + 1; + nNameLength = pexp->strTarget.len - nNameLength - 1; + } + + /* Does the string already have decoration? */ + pcAt = ScanToken(pcName, '@'); + if(pcAt && (pcAt < (pcName + nNameLength))) + { + /* On GCC, we need to remove the leading stdcall underscore */ + if(!gbMSComp && (pexp->nCallingConvention == CC_STDCALL)) + { + pcName++; + nNameLength--; + } + + /* Print the already decorated function name */ + fprintf(fileDest, "%.*s", nNameLength, pcName); + } + else + { + /* Print the prefix, but skip it for (GCC && stdcall) */ + if(gbMSComp || (pexp->nCallingConvention != CC_STDCALL)) + { + fprintf(fileDest, "%c", pexp->nCallingConvention == CC_FASTCALL ? '@' : '_'); + } + + /* Print the name with trailing decoration */ + fprintf(fileDest, "%.*s@%d", nNameLength, pcName, pexp->nStackBytes); + } + } + else + { + /* Print the undecorated function name */ + fprintf(fileDest, "%.*s", nNameLength, pcName); + } +} + +void +OutputLine_def_MS(FILE *fileDest, + EXPORT *pexp) +{ + PrintName(fileDest, pexp, &pexp->strName, 0); + + if(gbImportLib) + { + /* Redirect to a stub function, to get the right decoration in the lib */ + fprintf(fileDest, "=_stub_"); + PrintName(fileDest, pexp, &pexp->strName, 0); + } + else if(pexp->strTarget.buf) + { + if(pexp->strName.buf[0] == '?') + { + //fprintf(stderr, "warning: ignoring C++ redirection %.*s -> %.*s\n", + // pexp->strName.len, pexp->strName.buf, pexp->strTarget.len, pexp->strTarget.buf); + } + else + { + fprintf(fileDest, "="); + + /* If the original name was decorated, use decoration in the forwarder as well */ + if((giArch == ARCH_X86) && ScanToken(pexp->strName.buf, '@') && + !ScanToken(pexp->strTarget.buf, '@') && + ((pexp->nCallingConvention == CC_STDCALL) || + (pexp->nCallingConvention == CC_FASTCALL)) ) + { + PrintName(fileDest, pexp, &pexp->strTarget, 1); + } + else + { + /* Write the undecorated redirection name */ + fprintf(fileDest, "%.*s", pexp->strTarget.len, pexp->strTarget.buf); + } + } + } + else if(((pexp->uFlags & FL_STUB) || (pexp->nCallingConvention == CC_STUB)) && + (pexp->strName.buf[0] == '?')) + { + /* C++ stubs are forwarded to C stubs */ + fprintf(fileDest, "=stub_function%d", pexp->nNumber); + } + else if(gbTracing && ((pexp->uFlags & FL_NORELAY) == 0) && (pexp->nCallingConvention == CC_STDCALL) && + (pexp->strName.buf[0] != '?')) + { + /* Redirect it to the relay-tracing trampoline */ + fprintf(fileDest, "=$relaytrace$%.*s", pexp->strName.len, pexp->strName.buf); + } +} + +void +OutputLine_def_GCC(FILE *fileDest, + EXPORT *pexp) +{ + int bTracing = 0; + /* Print the function name, with decoration for export libs */ + PrintName(fileDest, pexp, &pexp->strName, gbImportLib); + DbgPrint("Generating def line for '%.*s'\n", pexp->strName.len, pexp->strName.buf); + + /* Check if this is a forwarded export */ + if(pexp->strTarget.buf) + { + int fIsExternal = !!ScanToken(pexp->strTarget.buf, '.'); + DbgPrint("Got redirect '%.*s'\n", pexp->strTarget.len, pexp->strTarget.buf); + + /* print the target name, don't decorate if it is external */ + fprintf(fileDest, "="); + PrintName(fileDest, pexp, &pexp->strTarget, !fIsExternal); + } + else if(((pexp->uFlags & FL_STUB) || (pexp->nCallingConvention == CC_STUB)) && + (pexp->strName.buf[0] == '?')) + { + /* C++ stubs are forwarded to C stubs */ + fprintf(fileDest, "=stub_function%d", pexp->nNumber); + } + else if(gbTracing && ((pexp->uFlags & FL_NORELAY) == 0) && + (pexp->nCallingConvention == CC_STDCALL) && + (pexp->strName.buf[0] != '?')) + { + /* Redirect it to the relay-tracing trampoline */ + char buf[256]; + STRING strTarget; + fprintf(fileDest, "="); + sprintf(buf, "$relaytrace$%.*s", pexp->strName.len, pexp->strName.buf); + strTarget.buf = buf; + strTarget.len = pexp->strName.len + 12; + PrintName(fileDest, pexp, &strTarget, 1); + bTracing = 1; + } + + /* Special handling for stdcall and fastcall */ + if((giArch == ARCH_X86) && + ((pexp->nCallingConvention == CC_STDCALL) || + (pexp->nCallingConvention == CC_FASTCALL))) + { + /* Is this the import lib? */ + if(gbImportLib) + { + /* Is the name in the spec file decorated? */ + const char* pcDeco = ScanToken(pexp->strName.buf, '@'); + if(pcDeco && + (pexp->strName.len > 1) && + (pcDeco < pexp->strName.buf + pexp->strName.len)) + { + /* Write the name including the leading @ */ + fprintf(fileDest, "==%.*s", pexp->strName.len, pexp->strName.buf); + } + } + else if((!pexp->strTarget.buf) && !(bTracing)) + { + /* Write a forwarder to the actual decorated symbol */ + fprintf(fileDest, "="); + PrintName(fileDest, pexp, &pexp->strName, 1); + } + } +} + +int +OutputLine_def(FILE *fileDest, + EXPORT *pexp) +{ + DbgPrint("OutputLine_def: '%.*s'...\n", pexp->strName.len, pexp->strName.buf); + fprintf(fileDest, " "); + + if(gbMSComp) + OutputLine_def_MS(fileDest, pexp); + else + OutputLine_def_GCC(fileDest, pexp); + + /* On GCC builds we force ordinals */ + if((pexp->uFlags & FL_ORDINAL) || (!gbMSComp && !gbImportLib)) + { + fprintf(fileDest, " @%d", pexp->nOrdinal); + } + + if(pexp->uFlags & FL_NONAME) + { + fprintf(fileDest, " NONAME"); + } + + /* Either PRIVATE or DATA */ + if(pexp->uFlags & FL_PRIVATE) + { + fprintf(fileDest, " PRIVATE"); + } + else if(pexp->nCallingConvention == CC_EXTERN) + { + fprintf(fileDest, " DATA"); + } + + fprintf(fileDest, "\n"); + + return 1; +} + +void +Fatalv(const char* filename, + unsigned nLine, + const char *pcLine, + const char *pc, + size_t errorlen, + const char *format, + va_list argptr) +{ + unsigned i, errorpos, len; + const char* pcLineEnd; + + /* Get the length of the line */ + pcLineEnd = strpbrk(pcLine, "\r\n"); + len = (unsigned)(pcLineEnd - pcLine); + + if(pc == NULL) + { + pc = pcLine + len - 1; + errorlen = 1; + } + + errorpos = (unsigned)(pc - pcLine); + + /* Output the error message */ + fprintf(stderr, "ERROR: (%s:%u:%u): ", filename, nLine, errorpos); + vfprintf(stderr, format, argptr); + fprintf(stderr, "\n"); + + /* Output the line with the error */ + fprintf(stderr, "> %.*s\n", len, pcLine); + + if(errorlen == 0) + { + errorlen = TokenLength(pc); + } + + for(i = 0; i < errorpos + 2; i++) + { + fprintf(stderr, " "); + } + for(i = 0; i < errorlen; i++) + { + fprintf(stderr, "~"); + } + fprintf(stderr, "\n"); + exit(-1); +} + +void +Fatal(const char* filename, + unsigned nLine, + const char *pcLine, + const char *pc, + size_t errorlen, + const char *format, + ...) +{ + va_list argptr; + + va_start(argptr, format); + Fatalv(filename, nLine, pcLine, pc, errorlen, format, argptr); + va_end(argptr); +} + +EXPORT * +ParseFile(char* pcStart, + FILE *fileDest, + unsigned *cExports) +{ + EXPORT *pexports; + const char *pc, *pcLine; + int cLines, nLine; + EXPORT exp; + int included; + unsigned int i; + + *cExports = 0; + + //fprintf(stderr, "info: line %d, pcStart:'%.30s'\n", nLine, pcStart); + + /* Count the lines */ + for(cLines = 1, pcLine = pcStart; *pcLine; pcLine = NextLine(pcLine), cLines++) + { + /* Nothing */ + } + + /* Allocate an array of EXPORT structures */ + pexports = malloc(cLines * sizeof(EXPORT)); + if(pexports == NULL) + { + fprintf(stderr, "ERROR: %s: failed to allocate EXPORT array of %u elements\n", pszSourceFileName, cLines); + return NULL; + } + + /* Loop all lines */ + nLine = 1; + exp.nNumber = 0; + for(pcLine = pcStart; *pcLine; pcLine = NextLine(pcLine), nLine++) + { + pc = pcLine; + + exp.strName.buf = NULL; + exp.strName.len = 0; + exp.strTarget.buf = NULL; + exp.strTarget.len = 0; + exp.nArgCount = 0; + exp.uFlags = 0; + exp.nNumber++; + exp.nStartVersion = 0; + exp.nEndVersion = 0xFFFFFFFF; + exp.bVersionIncluded = 1; + + /* Skip white spaces */ + while(*pc == ' ' || *pc == '\t') pc++; + + /* Check for line break or comment */ + if((*pc == '\r') || (*pc == '\n') || + (*pc == ';') || (*pc == '#')) + { + continue; + } + + /* On EOF we are done */ + if(*pc == 0) + { + return pexports; + } + + /* Now we should get either an ordinal or @ */ + if(*pc == '@') + { + exp.nOrdinal = -1; + } + else if((*pc >= '0') && (*pc <= '9')) + { + char* end; + long int number = strtol(pc, &end, 10); + if((*end != ' ') && (*end != '\t')) + { + Fatal(pszSourceFileName, nLine, pcLine, end, 0, "Unexpected character(s) after ordinal"); + } + + if((number < 0) || (number > 0xFFFE)) + { + Fatal(pszSourceFileName, nLine, pcLine, pc, 0, "Invalid value for ordinal"); + } + + exp.nOrdinal = number; + + /* The import lib should contain the ordinal only if -ordinal was specified */ + if(!gbImportLib) + { + exp.uFlags |= FL_ORDINAL; + } + } + else + { + Fatal(pszSourceFileName, nLine, pcLine, pc, 0, "Expected '@' or ordinal"); + } + + /* Go to next token (type) */ + if(!(pc = NextToken(pc))) + { + Fatal(pszSourceFileName, nLine, pcLine, pc, 1, "Unexpected end of line"); + } + + //fprintf(stderr, "info: Token:'%.*s'\n", TokenLength(pc), pc); + + /* Now we should get the type */ + if(CompareToken(pc, "stdcall")) + { + exp.nCallingConvention = CC_STDCALL; + } + else if(CompareToken(pc, "cdecl") || + CompareToken(pc, "varargs")) + { + exp.nCallingConvention = CC_CDECL; + } + else if(CompareToken(pc, "fastcall")) + { + exp.nCallingConvention = CC_FASTCALL; + } + else if(CompareToken(pc, "thiscall")) + { + exp.nCallingConvention = CC_THISCALL; + } + else if(CompareToken(pc, "extern")) + { + exp.nCallingConvention = CC_EXTERN; + } + else if(CompareToken(pc, "stub")) + { + exp.nCallingConvention = CC_STUB; + } + else + { + Fatal(pszSourceFileName, nLine, pcLine, pc, 0, "Invalid calling convention"); + } + + /* Go to next token (options or name) */ + if(!(pc = NextToken(pc))) + { + Fatal(pszSourceFileName, nLine, pcLine, pc, 1, "Unexpected end of line"); + } + + /* Handle options */ + included = 1; + while(*pc == '-') + { + if(CompareToken(pc, "-arch=")) + { + /* Default to not included */ + included = 0; + pc += 5; + + /* Look if we are included */ + do + { + pc++; + if(CompareToken(pc, pszArchString) || + CompareToken(pc, pszArchString2)) + { + included = 1; + } + + /* Skip to next arch or end */ + while(*pc > ',') + { + pc++; + } + } while(*pc == ','); + } + else if(CompareToken(pc, "-i386")) + { + if(giArch != ARCH_X86) + { + included = 0; + } + } + else if(CompareToken(pc, "-version=")) + { + const char *pcVersionStart = pc + 9; + + /* Default to not included */ + exp.bVersionIncluded = 0; + pc += 8; + + /* Look if we are included */ + do + { + unsigned version, endversion; + + /* Optionally skip leading '0x' */ + pc++; + if((pc[0] == '0') && (pc[1] == 'x')) pc += 2; + + /* Now get the version number */ + endversion = version = strtoul(pc, (char**)&pc, 16); + + /* Check if it's a range */ + if(pc[0] == '+') + { + endversion = 0xFFF; + pc++; + } + else if(pc[0] == '-') + { + /* Optionally skip leading '0x' */ + pc++; + if((pc[0] == '0') && (pc[1] == 'x')) pc += 2; + endversion = strtoul(pc, (char**)&pc, 16); + } + + /* Check for degenerate range */ + if(version > endversion) + { + Fatal(pszSourceFileName, + nLine, + pcLine, + pcVersionStart, + pc - pcVersionStart, + "Invalid version range"); + } + + exp.nStartVersion = version; + exp.nEndVersion = endversion; + + /* Now compare the range with our version */ + if((guOsVersion >= version) && + (guOsVersion <= endversion)) + { + exp.bVersionIncluded = 1; + } + + /* Skip to next arch or end */ + while(*pc > ',') pc++; + + } while(*pc == ','); + } + else if(CompareToken(pc, "-private")) + { + exp.uFlags |= FL_PRIVATE; + } + else if(CompareToken(pc, "-noname")) + { + exp.uFlags |= FL_ORDINAL | FL_NONAME; + } + else if(CompareToken(pc, "-ordinal")) + { + exp.uFlags |= FL_ORDINAL; + /* GCC doesn't automatically import by ordinal if an ordinal + * is found in the def file. Force it. */ + if(gbImportLib && !gbMSComp) + exp.uFlags |= FL_NONAME; + } + else if(CompareToken(pc, "-stub")) + { + exp.uFlags |= FL_STUB; + } + else if(CompareToken(pc, "-norelay")) + { + exp.uFlags |= FL_NORELAY; + } + else if(CompareToken(pc, "-ret64")) + { + exp.uFlags |= FL_RET64; + } + else if(CompareToken(pc, "-register")) + { + exp.uFlags |= FL_REGISTER; + } + else + { + fprintf(stdout, + "INFO: %s line %d: Ignored option: '%.*s'\n", + pszSourceFileName, + nLine, + TokenLength(pc), + pc); + } + + /* Go to next token */ + pc = NextToken(pc); + } + + /* If arch didn't match ours, skip this entry */ + if(!included) + { + continue; + } + + /* Get name */ + exp.strName.buf = pc; + exp.strName.len = TokenLength(pc); + + /* Check for autoname */ + if((exp.strName.len == 1) && (exp.strName.buf[0] == '@')) + { + exp.uFlags |= FL_ORDINAL | FL_NONAME; + } + + /* Handle parameters */ + exp.nStackBytes = 0; + if(exp.nCallingConvention != CC_EXTERN && + exp.nCallingConvention != CC_STUB) + { + /* Go to next token */ + if(!(pc = NextToken(pc))) + { + Fatal(pszSourceFileName, nLine, pcLine, pc, 1, "Unexpected end of line"); + } + + /* Verify syntax */ + if(*pc++ != '(') + { + Fatal(pszSourceFileName, nLine, pcLine, pc - 1, 0, "Expected '('"); + } + + /* Skip whitespaces */ + while(*pc == ' ' || *pc == '\t') pc++; + + exp.nStackBytes = 0; + while(*pc >= '0') + { + if(CompareToken(pc, "long")) + { + exp.nStackBytes += 4; + exp.anArgs[exp.nArgCount] = ARG_LONG; + } + else if(CompareToken(pc, "double")) + { + exp.nStackBytes += 8; + exp.anArgs[exp.nArgCount] = ARG_DBL; + } + else if(CompareToken(pc, "ptr")) + { + exp.nStackBytes += 4; // sizeof(void*) on x86 + exp.anArgs[exp.nArgCount] = ARG_PTR; + } + else if(CompareToken(pc, "str")) + { + exp.nStackBytes += 4; // sizeof(void*) on x86 + exp.anArgs[exp.nArgCount] = ARG_STR; + } + else if(CompareToken(pc, "wstr")) + { + exp.nStackBytes += 4; // sizeof(void*) on x86 + exp.anArgs[exp.nArgCount] = ARG_WSTR; + } + else if(CompareToken(pc, "int64")) + { + exp.nStackBytes += 8; + exp.anArgs[exp.nArgCount] = ARG_INT64; + } + else if(CompareToken(pc, "int128")) + { + exp.nStackBytes += 16; + exp.anArgs[exp.nArgCount] = ARG_INT128; + } + else if(CompareToken(pc, "float")) + { + exp.nStackBytes += 4; + exp.anArgs[exp.nArgCount] = ARG_FLOAT; + } + else + { + Fatal(pszSourceFileName, nLine, pcLine, pc, 0, "Unrecognized type"); + } + + exp.nArgCount++; + + /* Go to next parameter */ + if(!(pc = NextToken(pc))) + { + Fatal(pszSourceFileName, nLine, pcLine, pc, 1, "Unexpected end of line"); + } + } + + /* Check syntax */ + if(*pc++ != ')') + { + Fatal(pszSourceFileName, nLine, pcLine, pc - 1, 0, "Expected ')'"); + } + } + + /* Handle special stub cases */ + if(exp.nCallingConvention == CC_STUB) + { + /* Check for c++ mangled name */ + if(pc[0] == '?') + { + //printf("Found c++ mangled name...\n"); + // + } + else + { + /* Check for stdcall name */ + const char *p = ScanToken(pc, '@'); + if(p && (p - pc < exp.strName.len)) + { + int i; + + /* Truncate the name to before the @ */ + exp.strName.len = (int)(p - pc); + if(exp.strName.len < 1) + { + Fatal(pszSourceFileName, nLine, pcLine, p, 1, "Unexpected @"); + } + exp.nStackBytes = atoi(p + 1); + exp.nArgCount = exp.nStackBytes / 4; + exp.nCallingConvention = CC_STDCALL; + exp.uFlags |= FL_STUB; + for(i = 0; i < exp.nArgCount; i++) + { + exp.anArgs[i] = ARG_LONG; + } + } + } + } + + /* Get optional redirection */ + pc = NextToken(pc); + if(pc) + { + exp.strTarget.buf = pc; + exp.strTarget.len = TokenLength(pc); + + /* Check syntax (end of line) */ + if(NextToken(pc)) + { + Fatal(pszSourceFileName, nLine, pcLine, NextToken(pc), 0, "Excess token(s) at end of definition"); + } + + /* Don't relay-trace forwarded functions */ + exp.uFlags |= FL_NORELAY; + } + else + { + exp.strTarget.buf = NULL; + exp.strTarget.len = 0; + } + + /* Check for no-name without ordinal */ + if((exp.uFlags & FL_ORDINAL) && (exp.nOrdinal == -1)) + { + Fatal(pszSourceFileName, nLine, pcLine, pc, 0, "Ordinal export without ordinal"); + } + + /* + * Check for special handling of OLE exports, only when MSVC + * is not used, since otherwise this is handled by MS LINK.EXE. + */ + if(!gbMSComp) + { + /* Check whether the current export is not PRIVATE, or has an ordinal */ + int bIsNotPrivate = (!gbNotPrivateNoWarn && /*gbImportLib &&*/ !(exp.uFlags & FL_PRIVATE)); + int bHasOrdinal = (exp.uFlags & FL_ORDINAL); + + /* Check whether the current export is an OLE export, in case any of these tests pass */ + if(bIsNotPrivate || bHasOrdinal) + { + for(i = 0; i < ARRAYSIZE(astrOlePrivateExports); ++i) + { + if(strlen(astrOlePrivateExports[i]) == exp.strName.len && + strncmp(exp.strName.buf, astrOlePrivateExports[i], exp.strName.len) == 0) + { + /* The current export is an OLE export: display the corresponding warning */ + if(bIsNotPrivate) + { + fprintf(stderr, "WARNING: %s line %d: Exported symbol '%.*s' should be PRIVATE\n", + pszSourceFileName, nLine, exp.strName.len, exp.strName.buf); + } + if(bHasOrdinal) + { + fprintf(stderr, "WARNING: %s line %d: exported symbol '%.*s' should not be assigned an ordinal\n", + pszSourceFileName, nLine, exp.strName.len, exp.strName.buf); + } + break; + } + } + } + } + + pexports[*cExports] = exp; + (*cExports)++; + gbDebug = 0; + } + + return pexports; +} + +int +ApplyOrdinals(EXPORT* pexports, + unsigned cExports) +{ + unsigned short i, j; + char* used; + + /* Allocate a table to mark used ordinals */ + used = malloc(65536); + if(used == NULL) + { + fprintf(stderr, "Failed to allocate memory for ordinal use table\n"); + return -1; + } + memset(used, 0, 65536); + + /* Pass 1: mark the ordinals that are already used */ + for(i = 0; i < cExports; i++) + { + if(pexports[i].uFlags & FL_ORDINAL) + { + if(used[pexports[i].nOrdinal] != 0) + { + fprintf(stderr, "Found duplicate ordinal: %u\n", pexports[i].nOrdinal); + return -1; + } + used[pexports[i].nOrdinal] = 1; + } + } + + /* Pass 2: apply available ordinals */ + for(i = 0, j = 1; i < cExports; i++) + { + if((pexports[i].uFlags & FL_ORDINAL) == 0 && pexports[i].bVersionIncluded) + { + while(used[j] != 0) + { + j++; + } + + pexports[i].nOrdinal = j; + used[j] = 1; + } + } + + free(used); + return 0; +} + +void usage(void) +{ + printf("XTchain SPEC Compiler Version %s\n" + "Syntax: %s [ ...] \n\n" + "Possible options:\n" + " -h --help print this help screen\n" + " -d= generate a def file\n" + " -s= generate a stub file\n" + " -n= name of the dll\n" + " -a= set architecture to one of: aarch64, armv7, i686, x86_64\n" + " --implib generate a def file for an import library\n" + " --msvc MSVC compatibility\n" + " --no-private-warnings suppress warnings about symbols that should be private\n" + " --with-tracing generate wine-like \"+relay\" trace trampolines (needs -s)\n", + XTCSPECC_VERSION, + pszExecName); +} + +int main(int argc, + char *argv[]) +{ + size_t nFileSize; + char *pszSource, *pszDefFileName = NULL, *pszStubFileName = NULL; + const char* pszVersionOption = "--version=0x"; + char achDllName[40]; + FILE *file; + unsigned cExports = 0, i; + EXPORT *pexports; + + /* Read architecture from executable name */ + split_argv(argv[0], NULL, NULL, &pszArchString, &pszExecName); + if(pszArchString) + { + pszArchString = strtok(pszArchString, "-"); + } + + if(argc < 2) + { + usage(); + return -1; + } + + /* Read options */ + for(i = 1; i < (unsigned)argc && *argv[i] == '-'; i++) + { + if((strcasecmp(argv[i], "--help") == 0) || + (strcasecmp(argv[i], "-h") == 0)) + { + usage(); + return 0; + } + else if(argv[i][1] == 'd' && argv[i][2] == '=') + { + pszDefFileName = argv[i] + 3; + } + else if(argv[i][1] == 's' && argv[i][2] == '=') + { + pszStubFileName = argv[i] + 3; + } + else if(argv[i][1] == 'n' && argv[i][2] == '=') + { + pszDllName = argv[i] + 3; + } + else if(argv[i][1] == 'a' && argv[i][2] == '=') + { + pszArchString = argv[i] + 3; + } + else if(strncasecmp(argv[i], pszVersionOption, strlen(pszVersionOption)) == 0) + { + guOsVersion = strtoul(argv[i] + strlen(pszVersionOption), NULL, 16); + } + else if(strcasecmp(argv[i], "--implib") == 0) + { + gbImportLib = 1; + } + else if(strcasecmp(argv[i], "--msvc") == 0) + { + gbMSComp = 1; + } + else if(strcasecmp(argv[i], "--no-private-warnings") == 0) + { + gbNotPrivateNoWarn = 1; + } + else if(strcasecmp(argv[i], "--with-tracing") == 0) + { + if(!pszStubFileName) + { + fprintf(stderr, "Error: cannot use --with-tracing without -s option.\n"); + return -1; + } + gbTracing = 1; + } + else + { + fprintf(stderr, "Unrecognized option: %s\n", argv[i]); + return -1; + } + } + + if(pszArchString) + { + if((strcasecmp(pszArchString, "i386") == 0) || (strcasecmp(pszArchString, "i686") == 0)) + { + giArch = ARCH_X86; + gpszUnderscore = "_"; + } + else if((strcasecmp(pszArchString, "x86_64") == 0) || (strcasecmp(pszArchString, "amd64") == 0)) + { + giArch = ARCH_AMD64; + } + else if((strcasecmp(pszArchString, "arm") == 0) || (strcasecmp(pszArchString, "armv7") == 0)) + { + giArch = ARCH_ARM; + } + else if((strcasecmp(pszArchString, "aarch64") == 0) || (strcasecmp(pszArchString, "arm64") == 0)) + { + giArch = ARCH_ARM64; + } + else + { + printf("Invalid architecture specified.\n"); + exit(-1); + } + } + else + { + printf("No architecture specified.\n"); + exit(-1); + } + + if((giArch == ARCH_AMD64) || (giArch == ARCH_ARM64)) + { + pszArchString2 = "win64"; + } + else + { + pszArchString2 = "win32"; + } + /* Set a default dll name */ + if(!pszDllName) + { + char *p1, *p2; + size_t len; + + p1 = strrchr(argv[i], '\\'); + if(!p1) p1 = strrchr(argv[i], '/'); + p2 = p1 = p1 ? p1 + 1 : argv[i]; + + /* walk up to '.' */ + while(*p2 != '.' && *p2 != 0) p2++; + len = p2 - p1; + if(len >= sizeof(achDllName) - 5) + { + fprintf(stderr, "name too long: %s\n", p1); + return -2; + } + + strncpy(achDllName, p1, len); + strncpy(achDllName + len, ".dll", sizeof(achDllName) - len); + pszDllName = achDllName; + } + + /* Open input file */ + pszSourceFileName = argv[i]; + file = fopen(pszSourceFileName, "r"); + if(!file) + { + fprintf(stderr, "error: could not open file %s\n", pszSourceFileName); + return -3; + } + + /* Get file size */ + fseek(file, 0, SEEK_END); + nFileSize = ftell(file); + rewind(file); + + /* Allocate memory buffer */ + pszSource = malloc(nFileSize + 1); + if(!pszSource) + { + fclose(file); + return -4; + } + + /* Load input file into memory */ + nFileSize = fread(pszSource, 1, nFileSize, file); + fclose(file); + + /* Zero terminate the source */ + pszSource[nFileSize] = '\0'; + + pexports = ParseFile(pszSource, file, &cExports); + if(pexports == NULL) + { + fprintf(stderr, "error: could not parse file!\n"); + return -1; + } + + if(!gbMSComp) + { + if(ApplyOrdinals(pexports, cExports) < 0) + { + fprintf(stderr, "error: could not apply ordinals!\n"); + return -1; + } + } + + if(pszDefFileName) + { + /* Open output file */ + file = fopen(pszDefFileName, "w"); + if(!file) + { + fprintf(stderr, "error: could not open output file %s\n", argv[i + 1]); + return -5; + } + + OutputHeader_def(file, pszDllName); + + for(i = 0; i < cExports; i++) + { + if(pexports[i].bVersionIncluded) + OutputLine_def(file, &pexports[i]); + } + + fclose(file); + } + + if(pszStubFileName) + { + /* Open output file */ + file = fopen(pszStubFileName, "w"); + if(!file) + { + fprintf(stderr, "error: could not open output file %s\n", argv[i + 1]); + return -5; + } + + OutputHeader_stub(file); + + for(i = 0; i < cExports; i++) + { + if(pexports[i].bVersionIncluded) + OutputLine_stub(file, &pexports[i]); + } + + fclose(file); + } + + free(pexports); + + return 0; +}