VBoxTpG.cpp revision 66b58af085e22ee26be57f98127fb49ee2e91790
/* $Id$ */
/** @file
* IPRT Testcase / Tool - VBox Tracepoint Compiler.
*/
/*
* Copyright (C) 2012 Oracle Corporation
*
* This file is part of VirtualBox Open Source Edition (OSE), as
* available from http://www.virtualbox.org. This file is free software;
* General Public License (GPL) as published by the Free Software
* Foundation, in version 2 as it comes in the "COPYING" file of the
* VirtualBox OSE distribution. VirtualBox OSE is distributed in the
* hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
*/
/*******************************************************************************
* Header Files *
*******************************************************************************/
#include <iprt/initterm.h>
#include <iprt/strcache.h>
#include "scmstream.h"
/*******************************************************************************
* Structures and Typedefs *
*******************************************************************************/
/**
*/
typedef enum kVTGStability
{
/**
* Data dependency.
*/
typedef enum kVTGClass
{
kVTGClass_Invalid = 0,
} kVTGClass;
typedef struct VTGATTRS
{
} VTGATTRS;
typedef struct VTGARG
{
const char *pszName;
char *pszType;
} VTGARG;
typedef struct VTGPROBE
{
const char *pszName;
} VTGPROBE;
typedef struct VTGPROVIDER
{
const char *pszName;
} VTGPROVIDER;
typedef VTGPROVIDER *PVTGPROVIDER;
/*******************************************************************************
* Global Variables *
*******************************************************************************/
/** String cache used for storing strings when parsing. */
/** List of providers created by the parser. */
static RTLISTANCHOR g_ProviderHead;
/** @name Options
* @{ */
static enum
{
static bool g_fApplyCpp = false;
static uint32_t g_cVerbosity = 0;
static const char *g_pszOutput = NULL;
static const char *g_pszScript = NULL;
static const char *g_pszTempAsm = NULL;
#ifdef RT_OS_DARWIN
static const char *g_pszAssembler = "yasm";
static const char *g_pszAssemblerFmtOpt = "--oformat";
static const char g_szAssemblerFmtVal32[] = "macho32";
static const char g_szAssemblerFmtVal64[] = "macho64";
static const char *pszAssembler = "nasm.exe";
static const char *pszAssemblerFmtOpt = "-f";
static const char g_szAssemblerFmtVal32[] = "obj";
static const char g_szAssemblerFmtVal64[] = "elf64";
#elif defined(RT_OS_WINDOWS)
static const char *g_pszAssembler = "yasm.exe";
static const char *g_pszAssemblerFmtOpt = "--oformat";
static const char g_szAssemblerFmtVal32[] = "win32";
static const char g_szAssemblerFmtVal64[] = "win64";
#else
static const char *g_pszAssembler = "yasm";
static const char *g_pszAssemblerFmtOpt = "--oformat";
static const char g_szAssemblerFmtVal32[] = "elf32";
static const char g_szAssemblerFmtVal64[] = "elf64";
#endif
static const char *g_pszAssemblerOutputOpt = "-o";
static unsigned g_cAssemblerOptions = 0;
static const char *g_apszAssemblerOptions[32];
/** @} */
{
RTPrintf("Todo invoke the assembler\n");
return RTEXITCODE_SKIPPED;
}
{
if (RT_FAILURE(rc))
return RTMsgErrorExit(RTEXITCODE_FAILURE, "ScmStreamInitForWriting returned %Rrc when generating the %s file",
if (rcExit == RTEXITCODE_SUCCESS)
{
if (RT_FAILURE(rc))
rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "ScmStreamWriteToFile returned %Rrc when writing '%s' (%s)",
if (rcExit == RTEXITCODE_SUCCESS)
{
if (g_cVerbosity > 0)
if (g_cVerbosity > 1)
{
const char *pszLine;
}
}
}
return rcExit;
}
{
char *psz;
if (cch)
{
if (RT_FAILURE(rc))
}
return cch;
}
{
return cch;
}
{
if (g_cVerbosity > 0)
RTMsgInfo("Generating assembly code...");
/*
* Write the file header.
*/
"; $Id$ \n"
";; @file\n"
"; Automatically generated from %s. Do NOT edit!\n"
";\n"
"\n"
"\n"
,
/*
* Declare the probe enable flags.
*/
";\n"
"; Probe enabled flags. Since these will be accessed all the time\n"
"; they are placed together to get some more cache and TLB hits when\n"
"; the probes are disabled."
";\n"
"BEGINDATA\n"
"ALIGNDATA(16)\n"
"GLOBALNAME g_afVTGProbeEnabled\n"
);
{
{
"GLOBALNAME g_fVTGProbeEnabled_%s_%s\n"
" db 0\n",
}
}
/*
* Declare the probe data.
*/
"\n"
";\n"
"; Prob data.\n"
";\n"
"BEGINDATA\n"
"GLOBALNAME g_aVTGProbeData\n"
"\n");
{
{
/** @todo */
//ScmStreamPrintf(pStrm,
// "GLOBALNAME g_fVTGProbeEnabled_%s_%s\n"
// " db 0\n",
// pProv->pszName, pProbe->pszName);
}
}
/*
* Write the string table.
*/
"\n"
";\n"
"; String table.\n"
";\n"
"BEGINDATA\n"
"GLOBALNAME g_abVTGProbeStrings\n"
"\n");
/** @todo */
return RTEXITCODE_SUCCESS;
}
{
if (!pszTempAsm)
{
pszTempAsm = psz;
}
if (rcExit == RTEXITCODE_SUCCESS)
return rcExit;
}
static RTEXITCODE generateProbeDefineName(char *pszBuf, size_t cbBuf, const char *pszProvider, const char *pszProbe)
{
return RTMsgErrorExit(RTEXITCODE_FAILURE, "Probe '%s' in provider '%s' ends up with a too long defined\n", pszProbe, pszProvider);
while (*pszProvider)
*pszBuf++ = '_';
while (*pszProbe)
{
pszProbe++;
}
*pszBuf = '\0';
return RTEXITCODE_SUCCESS;
}
{
/*
* Calc the double inclusion blocker define and then write the file header.
*/
char szTmp[4096];
szTmp[0] = '_';
while (*psz)
{
*psz = '_';
psz++;
}
"/* $Id$ */\n"
"/** @file\n"
" * Automatically generated from %s. Do NOT edit!\n"
" */\n"
"\n"
"#ifndef %s\n"
"#define %s\n"
"\n"
"\n"
"#ifdef _MSC_VER\n"
"# define DECL_DATA_SECT(scope, type, name, sect) __declspec(allocate(#sect)) scope type name\n"
"#elif defined(__GNUC__)\n"
"# ifdef RT_OS_DARWIN\n"
"# define DECL_DATA_SECT(scope, type, name, sect) scope type __attribute__((section(#sect \",\" #sect))) name\n"
"# else\n"
"# define DECL_DATA_SECT(scope, type, name, sect) scope type __attribute__((section(#sect))) name\n"
"# endif\n"
"#else\n"
"# error portme\n"
"#endif\n"
"\n"
"typedef struct VBOXTPGPROBELOC\n"
"{\n"
" uint32_t uLine : 31;\n"
" uint32_t fEnabled : 1;\n"
" uint32_t idProbe;\n"
" const char *pszFunction;\n"
" const char *pszFile;\n"
" uint8_t *pbProbe;\n"
"} VBOXTPGPROBELOC;\n"
"\n"
"RT_C_DECLS_BEGIN\n"
,
szTmp);
/*
* Declare data, code and macros for each probe.
*/
{
{
"extern bool g_fVTGProbeEnabled_%s_%s;\n"
"extern uint8_t g_VTGProbeData_%s_%s;\n"
"DECLASM(void) VTGProbeStub_%s_%s(VBOXTPGPROBELOC *",
{
}
");\n"
"#define %s_ENABLED() \\\n"
" (RT_UNLIKELY(g_fVTGProbeEnabled_%s_%s)) \n"
"#define %s("
, szTmp,
szTmp);
{
else
}
") \\\n"
" do { \\\n"
" if (RT_UNLIKELY(/*g_fVTGProbeEnabled_%s_%s*/ true)) \\\n"
" { \\\n"
" DECL_DATA_SECT(static, VBOXTPGPROBELOC, s_VTGProbeLoc, VTGPrLc) = \\\n"
" { __LINE__, 0, UINT32_MAX, __PRETTY_FUNCTION__, __FILE__, /*&g_VTGProbeData_%s_%s*/ NULL }; \\\n"
" /*VTGProbeStub_%s_%s(&s_VTGProbeLoc",
{
}
");*/ RTAssertMsg2(\"%p\", &s_VTGProbeLoc); \\\n"
" } \\\n"
" } while (0)\n"
"\n");
}
}
"RT_C_DECLS_END\n"
"#endif\n"));
return RTEXITCODE_SUCCESS;
}
{
}
/**
* If the given C word is at off - 1, return @c true and skip beyond it,
* otherwise return @c false.
*
* @retval true if the given C-word is at the current position minus one char.
* The stream position changes.
* @retval false if not. The stream position is unchanged.
*
* @param pStream The stream.
* @param cchWord The length of the word.
* @param pszWord The word.
*/
{
/* Check stream state. */
/* Sufficient chars left on the line? */
size_t const cchLeft = pStream->paLines[iLine].cch + pStream->paLines[iLine].off - (pStream->off - 1);
return false;
/* Do they match? */
return false;
/* Is it the end of a C word? */
{
return false;
}
/* Skip ahead. */
return true;
}
/**
* Get's the C word starting at the current position.
*
* @returns Pointer to the word on success and the stream position advanced to
* the end of it.
* NULL on failure, stream position normally unchanged.
* @param pStream The stream to get the C word from.
* @param pcchWord Where to return the word length.
*/
{
/* Check stream state. */
/* Get the number of chars left on the line and locate the current char. */
/* Is it a leading C character. */
return NULL;
/* Find the end of the word. */
char ch;
|| RT_C_IS_ALNUM(ch)))
off++;
return psz;
}
/**
* Get's the C word starting at the current position minus one.
*
* @returns Pointer to the word on success and the stream position advanced to
* the end of it.
* NULL on failure, stream position normally unchanged.
* @param pStream The stream to get the C word from.
* @param pcchWord Where to return the word length.
*/
{
/* Check stream state. */
/* Get the number of chars left on the line and locate the current char. */
size_t const cchLeft = pStream->paLines[iLine].cch + pStream->paLines[iLine].off - (pStream->off - 1);
/* Is it a leading C character. */
return NULL;
/* Find the end of the word. */
char ch;
|| RT_C_IS_ALNUM(ch)))
off++;
return psz;
}
/**
* Parser error with line and position.
*
* @returns RTEXITCODE_FAILURE.
* @param pStrm The stream.
* @param cb The offset from the current position to the
* point of failure.
* @param pszMsg The message to display.
*/
{
if (cb)
if (pszLine)
RTPrintf(" %.*s\n"
" %*s^\n",
return RTEXITCODE_FAILURE;
}
/**
* Parser error with line and position.
*
* @returns RTEXITCODE_FAILURE.
* @param pStrm The stream.
* @param cb The offset from the current position to the
* point of failure.
* @param pszMsg The message to display.
*/
{
}
/**
* Handles a C++ one line comment.
*
* @returns Exit code.
* @param pStrm The stream.
*/
{
return RTEXITCODE_SUCCESS;
}
/**
* Handles a multi-line C/C++ comment.
*
* @returns Exit code.
* @param pStrm The stream.
*/
{
unsigned ch;
{
if (ch == '*')
{
do
while (ch == '*');
if (ch == '/')
return RTEXITCODE_SUCCESS;
}
}
return RTEXITCODE_FAILURE;
}
/**
* Skips spaces and comments.
*
* @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE.
* @param pStrm The stream..
*/
{
unsigned ch;
{
return RTEXITCODE_SUCCESS;
if (ch == '/')
{
if (ch == '*')
else if (ch == '/')
else
if (rcExit != RTEXITCODE_SUCCESS)
return rcExit;
}
}
}
/**
* Skips spaces and comments, returning the next character.
*
* @returns Next non-space-non-comment character. ~(unsigned)0 on EOF or
* failure.
* @param pStrm The stream.
*/
{
unsigned ch;
{
return ch;
if (ch == '/')
{
if (ch == '*')
else if (ch == '/')
else
if (rcExit != RTEXITCODE_SUCCESS)
return ~(unsigned)0;
}
}
return ~(unsigned)0;
}
/**
* Get the next non-space-non-comment character on a preprocessor line.
*
* @returns The next character. On error message and ~(unsigned)0.
* @param pStrm The stream.
*/
{
unsigned ch;
{
if (RT_C_IS_SPACE(ch))
{
{
break;
}
}
else if (ch == '\\')
{
if (ch == '\r')
if (ch != '\n')
{
break;
}
}
else
return ch;
}
return ~(unsigned)0;
}
/**
* Skips spaces and comments.
*
* @returns Same as ScmStreamCGetWord
* @param pStrm The stream..
* @param pcchWord Where to return the length.
*/
{
return NULL;
}
/**
* Parses interface stability.
*
* @returns Interface stability if parsed correctly, otherwise error message and
* kVTGStability_Invalid.
* @param pStrm The stream.
* @param ch The first character in the stability spec.
*/
{
switch (ch)
{
case 'E':
return kVTGStability_External;
return kVTGStability_Evolving;
break;
case 'I':
return kVTGStability_Internal;
break;
case 'O':
return kVTGStability_Obsolete;
break;
case 'P':
return kVTGStability_Private;
break;
case 'S':
return kVTGStability_Stable;
return kVTGStability_Standard;
break;
case 'U':
return kVTGStability_Unstable;
break;
}
return kVTGStability_Invalid;
}
/**
* Parses data depndency class.
*
* @returns Data dependency class if parsed correctly, otherwise error message
* and kVTGClass_Invalid.
* @param pStrm The stream.
* @param ch The first character in the stability spec.
*/
{
switch (ch)
{
case 'C':
return kVTGClass_Common;
return kVTGClass_Cpu;
break;
case 'G':
return kVTGClass_Group;
break;
case 'I':
return kVTGClass_Isa;
break;
case 'P':
return kVTGClass_Platform;
break;
case 'U':
return kVTGClass_Unknown;
break;
}
return kVTGClass_Invalid;
}
/**
* Parses a pragma D attributes statement.
*
* @returns Suitable exit code, errors message already written on failure.
* @param pStrm The stream.
*/
{
/*
* "CodeStability/DataStability/DataDepClass" - no spaces allowed.
*/
if (ch == ~(unsigned)0)
return RTEXITCODE_FAILURE;
if (enmCode == kVTGStability_Invalid)
return RTEXITCODE_FAILURE;
if (ch != '/')
if (enmData == kVTGStability_Invalid)
return RTEXITCODE_FAILURE;
if (ch != '/')
if (enmDataDep == kVTGClass_Invalid)
return RTEXITCODE_FAILURE;
/*
* Expecting 'provider' followed by the name of an provider defined earlier.
*/
if (ch == ~(unsigned)0)
return RTEXITCODE_FAILURE;
if (!pszName)
{
break;
}
if (!pProv)
/*
* Which aspect of the provider?
*/
if (!pszAspect)
else
return RTEXITCODE_SUCCESS;
}
/**
* Parses a D pragma statement.
*
* @returns Suitable exit code, errors message already written on failure.
* @param pStrm The stream.
*/
{
if (ch == ~(unsigned)0)
{
if (ch == ~(unsigned)0)
else
}
else
return rcExit;
}
/**
* Parses a D probe statement.
*
* @returns Suitable exit code, errors message already written on failure.
* @param pStrm The stream.
* @param pProv The provider being parsed.
*/
{
/*
* Next up is a name followed by an opening parenthesis.
*/
if (!pszProbe)
if (ch != '(')
/*
* Create a probe instance.
*/
if (!pProbe)
/*
* Parse loop for the argument.
*/
for (;;)
{
switch (ch)
{
case ')':
case ',':
{
/* commit the argument */
if (pArg)
{
if (!cchName)
char *pszName;
}
if (ch == ')')
{
if (ch != ';')
return RTEXITCODE_SUCCESS;
}
break;
}
default:
{
int rc;
if (!pszWord)
if (!pArg)
{
if (!pArg)
cchName = 0;
}
else
{
}
if (RT_FAILURE(rc))
break;
}
case '*':
{
if (!pArg)
if (RT_FAILURE(rc))
cchName = 0;
break;
}
case ~(unsigned)0:
}
}
}
/**
* Parses a D provider statement.
*
* @returns Suitable exit code, errors message already written on failure.
* @param pStrm The stream.
*/
{
/*
* Next up is a name followed by a curly bracket. Ignore comments.
*/
if (rcExit != RTEXITCODE_SUCCESS)
if (!pszName)
if (ch != '{')
/*
* Create a provider instance.
*/
if (!pProv)
/*
* Parse loop.
*/
for (;;)
{
switch (ch)
{
case 'p':
else
break;
case '}':
{
if (ch == ';')
return RTEXITCODE_SUCCESS;
break;
}
case ~(unsigned)0:
break;
default:
break;
}
if (rcExit != RTEXITCODE_SUCCESS)
return rcExit;
}
}
{
if (RT_FAILURE(rc))
return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to open & read '%s' into memory: %Rrc", pszScript, rc);
if (g_cVerbosity > 0)
unsigned ch;
{
if (RT_C_IS_SPACE(ch))
continue;
switch (ch)
{
case '/':
if (ch == '*')
else if (ch == '/')
else
break;
case 'p':
else
break;
case '#':
{
if (ch == ~(unsigned)0)
else
break;
}
default:
break;
}
if (rcExit != RTEXITCODE_SUCCESS)
return rcExit;
}
return rcExit;
}
/**
* Parses the arguments.
*/
{
enum
{
kVBoxTpGOpt_32Bit = 1000,
};
static RTGETOPTDEF const s_aOpts[] =
{
/* dtrace w/ long options */
/* out stuff */
};
int rc = RTGetOptInit(&GetOptState, argc, argv, &s_aOpts[0], RT_ELEMENTS(s_aOpts), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
{
switch (rc)
{
/*
* DTrace compatible options.
*/
case kVBoxTpGOpt_32Bit:
g_cBits = 32;
break;
case kVBoxTpGOpt_64Bit:
g_cBits = 64;
break;
case 'C':
g_fApplyCpp = true;
RTMsgWarning("Ignoring the -C option - no preprocessing of the D script will be performed");
break;
case 'G':
if ( g_enmAction != kVBoxTpGAction_Nothing
break;
case 'h':
{
if ( g_enmAction != kVBoxTpGAction_Nothing
}
else
{
/* --help or similar */
RTPrintf("VirtualBox Tracepoint Generator\n"
"\n"
"Usage: %s [options]\n"
"\n"
"Options:\n", RTProcShortName());
else
return RTEXITCODE_SUCCESS;
}
break;
case 'o':
if (g_pszOutput)
break;
case 's':
if (g_pszScript)
break;
case 'v':
g_cVerbosity++;
break;
case 'V':
{
/* The following is assuming that svn does it's job here. */
static const char s_szRev[] = "$Revision$";
return RTEXITCODE_SUCCESS;
}
case VINF_GETOPT_NOT_OPTION:
break; /* object files, ignore them. */
/*
* Out options.
*/
case kVBoxTpGOpt_Assembler:
break;
break;
break;
break;
return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Too many assembly options (max %u)", RT_ELEMENTS(g_apszAssemblerOptions));
break;
/*
* Errors and bugs.
*/
default:
}
}
/*
* Check that we've got all we need.
*/
if (g_enmAction == kVBoxTpGAction_Nothing)
if (!g_pszScript)
if (!g_pszOutput)
return RTEXITCODE_SUCCESS;
}
{
if (RT_FAILURE(rc))
return 1;
if (rcExit == RTEXITCODE_SUCCESS)
{
/*
* Parse the script.
*/
if (RT_SUCCESS(rc))
{
if (rcExit == RTEXITCODE_SUCCESS)
{
/*
* Take action.
*/
else
}
}
}
return rcExit;
}