mirror of
https://github.com/reactos/reactos.git
synced 2024-11-01 12:26:32 +00:00
7dcfda815c
This prevents MSVC from reordering the exports in a way that doesn't match our spec file. Also improve the algorithm to apply ordinals, by starting with the first specified ordinal, possibly reserving ordinals for anything declared without an ordinal before.
1757 lines
50 KiB
C
1757 lines
50 KiB
C
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <ctype.h>
|
|
#include <string.h>
|
|
#include <stdarg.h>
|
|
|
|
#ifdef _MSC_VER
|
|
#define strcasecmp(_String1, _String2) _stricmp(_String1, _String2)
|
|
#define strncasecmp(_String1, _String2, _MaxCount) _strnicmp(_String1, _String2, _MaxCount)
|
|
#endif
|
|
|
|
#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;
|
|
|
|
#if 0 // Debug helper function
|
|
void
|
|
PrintExport(EXPORT *pexp)
|
|
{
|
|
fprintf(stderr, "strName='%.*s'\n", pexp->strName.len, pexp->strName.buf);
|
|
fprintf(stderr, "strName='%.*s'\n", pexp->strTarget.len, pexp->strTarget.buf);
|
|
fprintf(stderr, "nCallingConvention=%u\n", pexp->nCallingConvention);
|
|
fprintf(stderr, "nOrdinal=%u\n", pexp->nOrdinal);
|
|
fprintf(stderr, "nStackBytes=%u\n", pexp->nStackBytes);
|
|
fprintf(stderr, "nArgCount=%u\n", pexp->nArgCount);
|
|
fprintf(stderr, "uFlags=0x%x\n", pexp->uFlags);
|
|
fprintf(stderr, "nNumber=%u\n", pexp->nNumber);
|
|
fprintf(stderr, "nStartVersion=%u\n", pexp->nStartVersion);
|
|
fprintf(stderr, "nEndVersion=%u\n", pexp->nEndVersion);
|
|
fprintf(stderr, "bVersionIncluded=%u\n", pexp->bVersionIncluded);
|
|
}
|
|
#endif
|
|
|
|
enum _ARCH
|
|
{
|
|
ARCH_X86,
|
|
ARCH_AMD64,
|
|
ARCH_IA64,
|
|
ARCH_ARM,
|
|
ARCH_ARM64,
|
|
ARCH_PPC
|
|
};
|
|
|
|
typedef int (*PFNOUTLINE)(FILE *, EXPORT *);
|
|
int gbMSComp = 0;
|
|
int gbImportLib = 0;
|
|
int gbNotPrivateNoWarn = 0;
|
|
int gbTracing = 0;
|
|
int giArch = ARCH_X86;
|
|
char *pszArchString = "i386";
|
|
char *pszArchString2;
|
|
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,
|
|
FL_IMPSYM = 128,
|
|
};
|
|
|
|
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 autogenerated, do not edit. */\n\n"
|
|
"#include <stubs.h>\n");
|
|
|
|
if (gbTracing)
|
|
{
|
|
fprintf(file, "#include <wine/debug.h>\n");
|
|
fprintf(file, "#include <inttypes.h>\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;
|
|
|
|
/* Workaround for forwarded externs. See here for an explanation:
|
|
* https://stackoverflow.com/questions/4060143/forwarding-data-in-a-dll */
|
|
if (gbMSComp &&
|
|
(pexp->nCallingConvention == CC_EXTERN) &&
|
|
(pexp->strTarget.buf != NULL) &&
|
|
(!!ScanToken(pexp->strTarget.buf, '.')))
|
|
{
|
|
fprintf(file, "#pragma comment(linker,\"/export:%s%.*s=%.*s,DATA\")\n\n",
|
|
gpszUnderscore, pexp->strName.len, pexp->strName.buf, pexp->strTarget.len, pexp->strTarget.buf);
|
|
return 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, so use a custom type */
|
|
case ARG_INT128: fprintf(file, "MyInt128"); 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, "0x%%\"PRIx64\""); break;
|
|
case ARG_INT128: fprintf(file, "0x%%\"PRIx64\"-0x%%\"PRIx64\""); 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: case ARG_PTR: case ARG_STR:
|
|
case ARG_WSTR: case ARG_DBL: case ARG_INT64:
|
|
fprintf(file, "a%d", i); break;
|
|
case ARG_INT128:
|
|
fprintf(file, "a%d.lower, a%d.upper", i, i); break;
|
|
case ARG_FLOAT:
|
|
fprintf(file, "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 = 0x%%\"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
|
|
OutputHeader_asmstub(FILE *file, char *libname)
|
|
{
|
|
fprintf(file, "; File generated automatically, do not edit!\n\n");
|
|
|
|
if (giArch == ARCH_X86)
|
|
{
|
|
fprintf(file, ".586\n.model flat\n.code\n");
|
|
}
|
|
else if (giArch == ARCH_AMD64)
|
|
{
|
|
fprintf(file, ".code\n");
|
|
}
|
|
else if (giArch == ARCH_ARM || giArch == ARCH_ARM64)
|
|
{
|
|
fprintf(file, " AREA |.text|,ALIGN=2,CODE,READONLY\n\n");
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
int
|
|
OutputLine_asmstub(FILE *fileDest, EXPORT *pexp)
|
|
{
|
|
char szNameBuffer[128];
|
|
|
|
/* Handle autoname */
|
|
if (pexp->strName.len == 1 && pexp->strName.buf[0] == '@')
|
|
{
|
|
sprintf(szNameBuffer, "%s_stub_ordinal%d",
|
|
gpszUnderscore, pexp->nOrdinal);
|
|
}
|
|
else if (giArch != ARCH_X86)
|
|
{
|
|
/* Does the string already have stdcall decoration? */
|
|
const char *pcAt = ScanToken(pexp->strName.buf, '@');
|
|
if (pcAt && (pcAt < (pexp->strName.buf + pexp->strName.len)) &&
|
|
(pexp->strName.buf[0] == '_'))
|
|
{
|
|
/* Skip leading underscore and remove trailing decoration */
|
|
sprintf(szNameBuffer, "_stub_%.*s",
|
|
(int)(pcAt - pexp->strName.buf - 1),
|
|
pexp->strName.buf + 1);
|
|
}
|
|
else
|
|
{
|
|
sprintf(szNameBuffer, "_stub_%.*s",
|
|
pexp->strName.len, pexp->strName.buf);
|
|
}
|
|
}
|
|
else if (pexp->nCallingConvention == CC_STDCALL)
|
|
{
|
|
sprintf(szNameBuffer, "__stub_%.*s@%d",
|
|
pexp->strName.len, pexp->strName.buf, pexp->nStackBytes);
|
|
}
|
|
else if (pexp->nCallingConvention == CC_FASTCALL)
|
|
{
|
|
sprintf(szNameBuffer, "@_stub_%.*s@%d",
|
|
pexp->strName.len, pexp->strName.buf, pexp->nStackBytes);
|
|
}
|
|
else if ((pexp->nCallingConvention == CC_CDECL) ||
|
|
(pexp->nCallingConvention == CC_THISCALL) ||
|
|
(pexp->nCallingConvention == CC_EXTERN) ||
|
|
(pexp->nCallingConvention == CC_STUB))
|
|
{
|
|
sprintf(szNameBuffer, "__stub_%.*s",
|
|
pexp->strName.len, pexp->strName.buf);
|
|
}
|
|
else
|
|
{
|
|
fprintf(stderr, "Invalid calling convention");
|
|
return 0;
|
|
}
|
|
|
|
Output_stublabel(fileDest, szNameBuffer);
|
|
|
|
return 1;
|
|
}
|
|
|
|
void
|
|
OutputHeader_def(FILE *file, char *libname)
|
|
{
|
|
fprintf(file,
|
|
"; File generated automatically, do not edit!\n\n"
|
|
"NAME %s\n\n"
|
|
"EXPORTS\n",
|
|
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)))
|
|
{
|
|
/* Beware with C++ exports */
|
|
int is_cpp = pcName[0] == '?';
|
|
|
|
/* 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, but not for C++ exports */
|
|
if (!gbMSComp && !is_cpp && (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 if (fDeco && (pexp->nCallingConvention == CC_CDECL) && gbMSComp)
|
|
{
|
|
/* Print with cdecl decoration */
|
|
fprintf(fileDest, "_%.*s", nNameLength, pcName);
|
|
}
|
|
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, pexp->uFlags & FL_IMPSYM ? "==" : "=");
|
|
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, but not C++ exports*/
|
|
if ((giArch == ARCH_X86) &&
|
|
(pexp->strName.buf[0] != '?') &&
|
|
((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)
|
|
{
|
|
/* Don't add private exports to the import lib */
|
|
if (gbImportLib && (pexp->uFlags & FL_PRIVATE))
|
|
{
|
|
DbgPrint("OutputLine_def: skipping private export '%.*s'...\n", pexp->strName.len, pexp->strName.buf);
|
|
return 1;
|
|
}
|
|
|
|
/* Handle import symbols */
|
|
if (pexp->uFlags & FL_IMPSYM)
|
|
{
|
|
/* Skip these, if we are not creating an import lib, or if this is MS */
|
|
if (!gbImportLib || gbMSComp)
|
|
{
|
|
DbgPrint("OutputLine_def: skipping import symbol '%.*s'...\n", pexp->strName.len, pexp->strName.buf);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
/* For MS linker, forwarded externs are managed via #pragma comment(linker,"/export:_data=org.data,DATA") */
|
|
if (gbMSComp && !gbImportLib && (pexp->nCallingConvention == CC_EXTERN) &&
|
|
(pexp->strTarget.buf != NULL) && !!ScanToken(pexp->strTarget.buf, '.'))
|
|
{
|
|
DbgPrint("OutputLine_def: skipping forwarded extern export '%.*s' ->'%.*s'...\n",
|
|
pexp->strName.len, pexp->strName.buf, pexp->strTarget.len, pexp->strTarget.buf);
|
|
return 1;
|
|
}
|
|
|
|
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);
|
|
|
|
/* If it is not an import lib, we force ordinals */
|
|
if ((pexp->uFlags & FL_ORDINAL) || !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
|
|
PrintNameOrImpName(FILE *fileDest, EXPORT *pexp, PSTRING pstr, int fDeco, int fImp)
|
|
{
|
|
if (fImp)
|
|
{
|
|
fprintf(fileDest, "__imp_");
|
|
}
|
|
|
|
PrintName(fileDest, pexp, pstr, fDeco);
|
|
}
|
|
|
|
void
|
|
OutputAlias(FILE *fileDest, EXPORT *pexp, int fImp)
|
|
{
|
|
if ((giArch == ARCH_ARM) || (giArch == ARCH_ARM64))
|
|
{
|
|
fprintf(fileDest, " IMPORT ");
|
|
PrintNameOrImpName(fileDest, pexp, &pexp->strName, 1, fImp);
|
|
fprintf(fileDest, ", WEAK ");
|
|
PrintNameOrImpName(fileDest, pexp, &pexp->strTarget, 1, fImp);
|
|
fprintf(fileDest, "\n");
|
|
}
|
|
else
|
|
{
|
|
fprintf(fileDest, " EXTERN ");
|
|
PrintNameOrImpName(fileDest, pexp, &pexp->strTarget, 1, fImp);
|
|
fprintf(fileDest, ":PROC\n ALIAS <");
|
|
PrintNameOrImpName(fileDest, pexp, &pexp->strName, 1, fImp);
|
|
fprintf(fileDest, "> = <");
|
|
PrintNameOrImpName(fileDest, pexp, &pexp->strTarget, 1, fImp);
|
|
fprintf(fileDest, ">\n");
|
|
}
|
|
}
|
|
|
|
int
|
|
OutputLine_implib_asm(FILE *fileDest, EXPORT *pexp)
|
|
{
|
|
if ((pexp->uFlags & FL_IMPSYM) == 0)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
OutputAlias(fileDest, pexp, 0);
|
|
OutputAlias(fileDest, pexp, 1);
|
|
|
|
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, "-impsym"))
|
|
{
|
|
exp.uFlags |= FL_IMPSYM;
|
|
}
|
|
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);
|
|
}
|
|
|
|
//fprintf(stderr, "info: Name:'%.10s'\n", pc);
|
|
|
|
/* If arch didn't match ours, skip this entry */
|
|
if (!included) continue;
|
|
|
|
/* Get name */
|
|
exp.strName.buf = pc;
|
|
exp.strName.len = TokenLength(pc);
|
|
//DbgPrint("Got name: '%.*s'\n", exp.strName.len, exp.strName.buf);
|
|
|
|
/* Check for autoname */
|
|
if ((exp.strName.len == 1) && (exp.strName.buf[0] == '@'))
|
|
{
|
|
exp.uFlags |= FL_ORDINAL | FL_NONAME;
|
|
}
|
|
|
|
/* Handle parameters */
|
|
exp.nStackBytes = 0;
|
|
pc = NextToken(pc);
|
|
/* Extern can't have parameters, and it's optional to provide ones for stubs. All other exports must have them */
|
|
if (!pc && (exp.nCallingConvention != CC_EXTERN && exp.nCallingConvention != CC_STUB))
|
|
{
|
|
Fatal(pszSourceFileName, nLine, pcLine, pc, 1, "Unexpected end of line");
|
|
}
|
|
|
|
if (pc && (exp.nCallingConvention != CC_EXTERN))
|
|
{
|
|
/* 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 ')'");
|
|
}
|
|
|
|
/* Go to next token */
|
|
pc = NextToken(pc);
|
|
}
|
|
|
|
/* Handle special stub cases */
|
|
if (exp.nCallingConvention == CC_STUB)
|
|
{
|
|
/* If we got parameters, assume STDCALL */
|
|
if (exp.nArgCount != 0)
|
|
{
|
|
exp.nCallingConvention = CC_STDCALL;
|
|
exp.uFlags |= FL_STUB;
|
|
}
|
|
|
|
/* Check for c++ mangled name */
|
|
if (exp.strName.buf[0] == '?')
|
|
{
|
|
//printf("Found c++ mangled name...\n");
|
|
//
|
|
}
|
|
else
|
|
{
|
|
/* Check for stdcall name */
|
|
const char *p = ScanToken(exp.strName.buf, '@');
|
|
if (p && (p - exp.strName.buf < exp.strName.len))
|
|
{
|
|
int i;
|
|
|
|
/* Truncate the name to before the @ */
|
|
exp.strName.len = (int)(p - exp.strName.buf);
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Check optional redirection */
|
|
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 import symbol without target */
|
|
if ((exp.uFlags & FL_IMPSYM) && (exp.strTarget.buf == NULL))
|
|
{
|
|
Fatal(pszSourceFileName, nLine, pcLine, pc, 1, "Import symbol without target");
|
|
}
|
|
|
|
/*
|
|
* 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;
|
|
unsigned short firstOrdinal = 0xFFFF, firstIndex = 0;
|
|
|
|
/* 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;
|
|
if (pexports[i].nOrdinal < firstOrdinal)
|
|
{
|
|
firstOrdinal = pexports[i].nOrdinal;
|
|
firstIndex = i;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Check if we found an ordinal and it's larger than it's index */
|
|
if ((firstOrdinal != 0xFFFF) && (firstOrdinal > firstIndex))
|
|
{
|
|
/* We did. Calculate an appropriate starting ordinal. */
|
|
firstOrdinal -= firstIndex;
|
|
}
|
|
else
|
|
{
|
|
/* We didn't, so start with 1 */
|
|
firstOrdinal = 1;
|
|
}
|
|
|
|
/* Pass 2: apply available ordinals */
|
|
for (i = 0, j = firstOrdinal; 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("syntax: spec2def [<options> ...] <spec file>\n"
|
|
"Possible options:\n"
|
|
" -h --help print this help screen\n"
|
|
" -l=<file> generate an asm lib stub\n"
|
|
" -d=<file> generate a def file\n"
|
|
" -s=<file> generate a stub file\n"
|
|
" -i=<file> generate an import alias file\n"
|
|
" --ms MSVC compatibility\n"
|
|
" -n=<name> name of the dll\n"
|
|
" --version=<version> Sets the version to create exports for\n"
|
|
" --implib generate a def file for an import library\n"
|
|
" --no-private-warnings suppress warnings about symbols that should be -private\n"
|
|
" -a=<arch> set architecture to <arch> (i386, x86_64, arm, arm64)\n"
|
|
" --with-tracing generate wine-like \"+relay\" trace trampolines (needs -s)\n");
|
|
}
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
size_t nFileSize;
|
|
char *pszSource, *pszDefFileName = NULL, *pszStubFileName = NULL, *pszLibStubName = NULL;
|
|
char *pszImpLibAliasFileName = NULL;
|
|
const char* pszVersionOption = "--version=0x";
|
|
char achDllName[40];
|
|
FILE *file;
|
|
unsigned cExports = 0, i;
|
|
EXPORT *pexports;
|
|
|
|
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] == 'l' && argv[i][2] == '=')
|
|
{
|
|
pszLibStubName = argv[i] + 3;
|
|
}
|
|
else if (argv[i][1] == 's' && argv[i][2] == '=')
|
|
{
|
|
pszStubFileName = argv[i] + 3;
|
|
}
|
|
else if (argv[i][1] == 'i' && argv[i][2] == '=')
|
|
{
|
|
pszImpLibAliasFileName = argv[i] + 3;
|
|
}
|
|
else if (argv[i][1] == 'n' && argv[i][2] == '=')
|
|
{
|
|
pszDllName = 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], "--ms") == 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 if (argv[i][1] == 'a' && argv[i][2] == '=')
|
|
{
|
|
pszArchString = argv[i] + 3;
|
|
}
|
|
else
|
|
{
|
|
fprintf(stderr, "Unrecognized option: %s\n", argv[i]);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (strcasecmp(pszArchString, "i386") == 0)
|
|
{
|
|
giArch = ARCH_X86;
|
|
gpszUnderscore = "_";
|
|
}
|
|
else if (strcasecmp(pszArchString, "x86_64") == 0) giArch = ARCH_AMD64;
|
|
else if (strcasecmp(pszArchString, "ia64") == 0) giArch = ARCH_IA64;
|
|
else if (strcasecmp(pszArchString, "arm") == 0) giArch = ARCH_ARM;
|
|
else if (strcasecmp(pszArchString, "arm64") == 0) giArch = ARCH_ARM64;
|
|
else if (strcasecmp(pszArchString, "ppc") == 0) giArch = ARCH_PPC;
|
|
|
|
if ((giArch == ARCH_AMD64) || (giArch == ARCH_IA64))
|
|
{
|
|
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 (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", pszDefFileName);
|
|
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", pszStubFileName);
|
|
return -5;
|
|
}
|
|
|
|
OutputHeader_stub(file);
|
|
|
|
for (i = 0; i < cExports; i++)
|
|
{
|
|
if (pexports[i].bVersionIncluded)
|
|
OutputLine_stub(file, &pexports[i]);
|
|
}
|
|
|
|
fclose(file);
|
|
}
|
|
|
|
if (pszLibStubName)
|
|
{
|
|
/* Open output file */
|
|
file = fopen(pszLibStubName, "w");
|
|
if (!file)
|
|
{
|
|
fprintf(stderr, "error: could not open output file %s\n", pszLibStubName);
|
|
return -5;
|
|
}
|
|
|
|
OutputHeader_asmstub(file, pszDllName);
|
|
|
|
for (i = 0; i < cExports; i++)
|
|
{
|
|
if (pexports[i].bVersionIncluded)
|
|
OutputLine_asmstub(file, &pexports[i]);
|
|
}
|
|
|
|
fprintf(file, "\n END\n");
|
|
fclose(file);
|
|
}
|
|
|
|
if (pszImpLibAliasFileName)
|
|
{
|
|
/* Open output file */
|
|
file = fopen(pszImpLibAliasFileName, "w");
|
|
if (!file)
|
|
{
|
|
fprintf(stderr, "error: could not open output file %s\n", pszImpLibAliasFileName);
|
|
return -5;
|
|
}
|
|
|
|
OutputHeader_asmstub(file, pszDllName);
|
|
|
|
for (i = 0; i < cExports; i++)
|
|
{
|
|
if (pexports[i].bVersionIncluded)
|
|
OutputLine_implib_asm(file, &pexports[i]);
|
|
}
|
|
|
|
fprintf(file, "\n END\n");
|
|
fclose(file);
|
|
}
|
|
|
|
free(pexports);
|
|
|
|
return 0;
|
|
}
|