From 16f504a9dca3fe3b70568f67b7d41241ae485288 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 18:49:04 +0200 Subject: Adding upstream version 7.0.6-dfsg. Signed-off-by: Daniel Baumann --- src/bldprogs/Makefile.kmk | 117 + src/bldprogs/VBoxCPP.cpp | 5542 ++++++++++++++++++++++++++++ src/bldprogs/VBoxCheckImports.cpp | 381 ++ src/bldprogs/VBoxCmp.cpp | 141 + src/bldprogs/VBoxCompilerPlugIns.h | 137 + src/bldprogs/VBoxCompilerPlugInsCommon.cpp | 448 +++ src/bldprogs/VBoxCompilerPlugInsGcc.cpp | 1012 +++++ src/bldprogs/VBoxDef2LazyLoad.cpp | 1624 ++++++++ src/bldprogs/VBoxEditCoffLib.cpp | 476 +++ src/bldprogs/VBoxPeSetVersion.cpp | 404 ++ src/bldprogs/VBoxTpG.cpp | 2665 +++++++++++++ src/bldprogs/bin2c.c | 274 ++ src/bldprogs/biossums.c | 255 ++ src/bldprogs/checkUndefined.sh | 102 + src/bldprogs/deftoimp.sed | 66 + src/bldprogs/filesplitter.cpp | 388 ++ src/bldprogs/genalias.cpp | 500 +++ src/bldprogs/preload.cpp | 222 ++ src/bldprogs/scm.cpp | 3232 ++++++++++++++++ src/bldprogs/scm.h | 490 +++ src/bldprogs/scmdiff.cpp | 450 +++ src/bldprogs/scmdiff.h | 74 + src/bldprogs/scmparser.cpp | 1199 ++++++ src/bldprogs/scmrw.cpp | 3611 ++++++++++++++++++ src/bldprogs/scmstream.cpp | 1447 ++++++++ src/bldprogs/scmstream.h | 150 + src/bldprogs/scmsubversion.cpp | 1690 +++++++++ src/bldprogs/solgcc/.scm-settings | 29 + src/bldprogs/solgcc/config/i386/sol2-gas.h | 0 src/bldprogs/solgcc/config/usegas.h | 0 src/bldprogs/test-gccplugin-2.c | 57 + src/bldprogs/test-gccplugin-3.c | 47 + src/bldprogs/test-gccplugin.c | 36 + 33 files changed, 27266 insertions(+) create mode 100644 src/bldprogs/Makefile.kmk create mode 100644 src/bldprogs/VBoxCPP.cpp create mode 100644 src/bldprogs/VBoxCheckImports.cpp create mode 100644 src/bldprogs/VBoxCmp.cpp create mode 100644 src/bldprogs/VBoxCompilerPlugIns.h create mode 100644 src/bldprogs/VBoxCompilerPlugInsCommon.cpp create mode 100644 src/bldprogs/VBoxCompilerPlugInsGcc.cpp create mode 100644 src/bldprogs/VBoxDef2LazyLoad.cpp create mode 100644 src/bldprogs/VBoxEditCoffLib.cpp create mode 100644 src/bldprogs/VBoxPeSetVersion.cpp create mode 100644 src/bldprogs/VBoxTpG.cpp create mode 100644 src/bldprogs/bin2c.c create mode 100644 src/bldprogs/biossums.c create mode 100755 src/bldprogs/checkUndefined.sh create mode 100644 src/bldprogs/deftoimp.sed create mode 100644 src/bldprogs/filesplitter.cpp create mode 100644 src/bldprogs/genalias.cpp create mode 100644 src/bldprogs/preload.cpp create mode 100644 src/bldprogs/scm.cpp create mode 100644 src/bldprogs/scm.h create mode 100644 src/bldprogs/scmdiff.cpp create mode 100644 src/bldprogs/scmdiff.h create mode 100644 src/bldprogs/scmparser.cpp create mode 100644 src/bldprogs/scmrw.cpp create mode 100644 src/bldprogs/scmstream.cpp create mode 100644 src/bldprogs/scmstream.h create mode 100644 src/bldprogs/scmsubversion.cpp create mode 100644 src/bldprogs/solgcc/.scm-settings create mode 100644 src/bldprogs/solgcc/config/i386/sol2-gas.h create mode 100644 src/bldprogs/solgcc/config/usegas.h create mode 100644 src/bldprogs/test-gccplugin-2.c create mode 100644 src/bldprogs/test-gccplugin-3.c create mode 100644 src/bldprogs/test-gccplugin.c (limited to 'src/bldprogs') diff --git a/src/bldprogs/Makefile.kmk b/src/bldprogs/Makefile.kmk new file mode 100644 index 00000000..613685fd --- /dev/null +++ b/src/bldprogs/Makefile.kmk @@ -0,0 +1,117 @@ +# $Id: Makefile.kmk $ +## @file +# Sub-Makefile for various generic build tools (there is currently only one of them). +# + +# +# Copyright (C) 2006-2022 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see . +# +# SPDX-License-Identifier: GPL-3.0-only +# + +SUB_DEPTH = ../.. +include $(KBUILD_PATH)/subheader.kmk + +BLDPROGS += bin2c +ifn1of ($(KBUILD_TARGET).$(KBUILD_TARGET_ARCH), solaris.sparc64) + BLDPROGS += biossums filesplitter genalias VBoxCmp +endif + +bin2c_TEMPLATE = VBoxBldProg +bin2c_SOURCES = bin2c.c + +biossums_TEMPLATE = VBoxBldProg +biossums_SOURCES = biossums.c + +filesplitter_TEMPLATE = VBoxBldProg +filesplitter_SOURCES = filesplitter.cpp + +genalias_TEMPLATE = VBoxBldProg +genalias_SOURCES = genalias.cpp + +VBoxCmp_TEMPLATE = VBoxBldProg +VBoxCmp_SOURCES = VBoxCmp.cpp + +ifndef VBOX_ONLY_BUILD + PROGRAMS += scm + scm_TEMPLATE = VBoxR3Tool + scm_SOURCES = \ + scm.cpp \ + scmdiff.cpp \ + scmrw.cpp \ + scmparser.cpp \ + scmstream.cpp \ + scmsubversion.cpp + ifdef VBOX_PATH_SUBVERSION_INCS + scm_INCS += $(VBOX_PATH_SUBVERSION_INCS) $(VBOX_PATH_APACHE_RUNTIME_INCS) + scm_DEFS += SCM_WITH_SVN_HEADERS + endif + + BLDPROGS += VBoxCPP + VBoxCPP_TEMPLATE = VBoxAdvBldProg + VBoxCPP_SOURCES = \ + VBoxCPP.cpp \ + scmstream.cpp +endif + +if !defined(VBOX_ONLY_BUILD) || defined(VBOX_ONLY_EXTPACKS) + BLDPROGS += VBoxTpG + VBoxTpG_TEMPLATE = VBoxAdvBldProg + VBoxTpG_SOURCES = \ + VBoxTpG.cpp \ + scmstream.cpp +endif + +ifeq ($(KBUILD_TARGET),win) + BLDPROGS += VBoxPeSetVersion +endif +VBoxPeSetVersion_TEMPLATE = VBoxBldProg +VBoxPeSetVersion_SOURCES = VBoxPeSetVersion.cpp + +BLDPROGS.win += VBoxCheckImports +VBoxCheckImports_TEMPLATE = VBoxBldProg +VBoxCheckImports_SOURCES = VBoxCheckImports.cpp + +ifneq ($(KBUILD_TARGET).$(KBUILD_TARGET_ARCH),solaris.sparc64) + BLDPROGS += VBoxDef2LazyLoad +endif +VBoxDef2LazyLoad_TEMPLATE = VBoxBldProg +VBoxDef2LazyLoad_SOURCES = VBoxDef2LazyLoad.cpp + +ifeq ($(KBUILD_TARGET),win) + BLDPROGS += VBoxEditCoffLib +endif +VBoxEditCoffLib_TEMPLATE = VBoxBldProg +VBoxEditCoffLib_SOURCES = VBoxEditCoffLib.cpp + +# temp hack. +VBoxCompilerPlugInsGcc.o VBoxCompilerPlugInsCommon.o VBoxCompilerPlugIns.o gccplugin: gccplugin$(SUFF_DLL) +gccplugin$(SUFF_DLL): VBoxCompilerPlugInsGcc.cpp VBoxCompilerPlugInsCommon.cpp VBoxCompilerPlugIns.h + $(TOOL_GXX3_CXX) -shared -fPIC -fno-rtti -g \ + -DIN_RING3 \ + $(if-expr "$(KBUILD_TYPE)" != "release",-DDEBUG,) \ + -I$(shell $(TOOL_GXX3_CXX) -print-file-name=plugin)/include \ + -I$(PATH_ROOT)/include \ + $(if-expr "$(KBUILD_HOST)" == "solaris", -I/usr/include/gmp -I$(PATH_ROOT)/src/bldprogs/solgcc/,) \ + -o $@ \ + VBoxCompilerPlugInsGcc.cpp \ + VBoxCompilerPlugInsCommon.cpp + +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/bldprogs/VBoxCPP.cpp b/src/bldprogs/VBoxCPP.cpp new file mode 100644 index 00000000..11380e4c --- /dev/null +++ b/src/bldprogs/VBoxCPP.cpp @@ -0,0 +1,5542 @@ +/* $Id: VBoxCPP.cpp $ */ +/** @file + * VBox Build Tool - A mini C Preprocessor. + * + * Purposes to which this preprocessor will be put: + * - Preprocessig vm.h into dtrace/lib/vm.d so we can access the VM + * structure (as well as substructures) from DTrace without having + * to handcraft it all. + * - Removing \#ifdefs relating to a new feature that has become + * stable and no longer needs \#ifdef'ing. + * - Pretty printing preprocessor directives. This will be used by + * SCM. + */ + +/* + * Copyright (C) 2012-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "scmstream.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** The bitmap type. */ +#define VBCPP_BITMAP_TYPE uint64_t +/** The bitmap size as a multiple of VBCPP_BITMAP_TYPE. */ +#define VBCPP_BITMAP_SIZE (128 / 64) +/** Checks if a bit is set. */ +#define VBCPP_BITMAP_IS_SET(a_bm, a_ch) ASMBitTest(a_bm, (a_ch) & 0x7f) +/** Sets a bit. */ +#define VBCPP_BITMAP_SET(a_bm, a_ch) ASMBitSet(a_bm, (a_ch) & 0x7f) +/** Empties the bitmap. */ +#define VBCPP_BITMAP_EMPTY(a_bm) do { (a_bm)[0] = 0; (a_bm)[1] = 0; } while (0) +/** Joins to bitmaps by OR'ing their values.. */ +#define VBCPP_BITMAP_OR(a_bm1, a_bm2) do { (a_bm1)[0] |= (a_bm2)[0]; (a_bm1)[1] |= (a_bm2)[1]; } while (0) + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** Pointer to the C preprocessor instance data. */ +typedef struct VBCPP *PVBCPP; + + +/** + * Variable string buffer (very simple version of SCMSTREAM). + */ +typedef struct VBCPPSTRBUF +{ + /** The preprocessor instance (for error reporting). */ + struct VBCPP *pThis; + /** The length of the string in the buffer. */ + size_t cchBuf; + /** The string storage. */ + char *pszBuf; + /** Allocated buffer space. */ + size_t cbBufAllocated; +} VBCPPSTRBUF; +/** Pointer to a variable string buffer. */ +typedef VBCPPSTRBUF *PVBCPPSTRBUF; + + +/** + * The preprocessor mode. + */ +typedef enum VBCPPMODE +{ + kVBCppMode_Invalid = 0, + kVBCppMode_Standard, + kVBCppMode_Selective, + kVBCppMode_SelectiveD, + kVBCppMode_End +} VBCPPMODE; + + +/** + * A macro (aka define). + */ +typedef struct VBCPPMACRO +{ + /** The string space core. */ + RTSTRSPACECORE Core; +#if 0 + /** For linking macros that have the fExpanding flag set. */ + struct VBCPPMACRO *pUpExpanding; +#endif + /** Whether it's a function. */ + bool fFunction; + /** Variable argument count. */ + bool fVarArg; + /** Set if originating on the command line. */ + bool fCmdLine; + /** Set if this macro is currently being expanded and should not be + * recursively applied. */ + bool fExpanding; + /** The number of known arguments. */ + uint32_t cArgs; + /** Pointer to a list of argument names. */ + const char **papszArgs; + /** Lead character bitmap for the argument names. */ + VBCPP_BITMAP_TYPE bmArgs[VBCPP_BITMAP_SIZE]; + /** The value length. */ + size_t cchValue; + /** The define value. (This is followed by the name and arguments.) */ + char szValue[1]; +} VBCPPMACRO; +/** Pointer to a macro. */ +typedef VBCPPMACRO *PVBCPPMACRO; + + +/** + * Macro expansion data. + */ +typedef struct VBCPPMACROEXP +{ + /** The expansion buffer. */ + VBCPPSTRBUF StrBuf; +#if 0 + /** List of expanding macros (Stack). */ + PVBCPPMACRO pMacroStack; +#endif + /** The input stream (in case we want to look for parameter lists). */ + PSCMSTREAM pStrmInput; + /** Array of argument values. Used when expanding function style macros. */ + char **papszArgs; + /** The number of argument values current in papszArgs. */ + uint32_t cArgs; + /** The number of argument values papszArgs can currently hold */ + uint32_t cArgsAlloced; +} VBCPPMACROEXP; +/** Pointer to macro expansion data. */ +typedef VBCPPMACROEXP *PVBCPPMACROEXP; + + +/** + * The vbcppMacroExpandReScan mode of operation. + */ +typedef enum VBCPPMACRORESCANMODE +{ + /** Invalid mode. */ + kMacroReScanMode_Invalid = 0, + /** Normal expansion mode. */ + kMacroReScanMode_Normal, + /** Replaces known macros and heeds the 'defined' operator. */ + kMacroReScanMode_Expression, + /** End of valid modes. */ + kMacroReScanMode_End +} VBCPPMACRORESCANMODE; + + +/** + * Expression node type. + */ +typedef enum VBCPPEXPRKIND +{ + kVBCppExprKind_Invalid = 0, + kVBCppExprKind_Unary, + kVBCppExprKind_Binary, + kVBCppExprKind_Ternary, + kVBCppExprKind_SignedValue, + kVBCppExprKind_UnsignedValue, + kVBCppExprKind_End +} VBCPPEXPRKIND; + + +/** Macro used for the precedence field. */ +#define VBCPPOP_PRECEDENCE(a_iPrecedence) ((a_iPrecedence) << 8) +/** Mask for getting the precedence field value. */ +#define VBCPPOP_PRECEDENCE_MASK 0xff00 +/** Operator associativity - Left to right. */ +#define VBCPPOP_L2R (1 << 16) +/** Operator associativity - Right to left. */ +#define VBCPPOP_R2L (2 << 16) + +/** + * Unary operators. + */ +typedef enum VBCPPUNARYOP +{ + kVBCppUnaryOp_Invalid = 0, + kVBCppUnaryOp_Pluss = VBCPPOP_R2L | VBCPPOP_PRECEDENCE( 3) | 5, + kVBCppUnaryOp_Minus = VBCPPOP_R2L | VBCPPOP_PRECEDENCE( 3) | 6, + kVBCppUnaryOp_LogicalNot = VBCPPOP_R2L | VBCPPOP_PRECEDENCE( 3) | 7, + kVBCppUnaryOp_BitwiseNot = VBCPPOP_R2L | VBCPPOP_PRECEDENCE( 3) | 8, + kVBCppUnaryOp_Parenthesis = VBCPPOP_R2L | VBCPPOP_PRECEDENCE(15) | 9, + kVBCppUnaryOp_End +} VBCPPUNARYOP; + +/** + * Binary operators. + */ +typedef enum VBCPPBINARYOP +{ + kVBCppBinary_Invalid = 0, + kVBCppBinary_Multiplication = VBCPPOP_L2R | VBCPPOP_PRECEDENCE( 5) | 2, + kVBCppBinary_Division = VBCPPOP_L2R | VBCPPOP_PRECEDENCE( 5) | 4, + kVBCppBinary_Modulo = VBCPPOP_L2R | VBCPPOP_PRECEDENCE( 5) | 5, + kVBCppBinary_Addition = VBCPPOP_L2R | VBCPPOP_PRECEDENCE( 6) | 6, + kVBCppBinary_Subtraction = VBCPPOP_L2R | VBCPPOP_PRECEDENCE( 6) | 7, + kVBCppBinary_LeftShift = VBCPPOP_L2R | VBCPPOP_PRECEDENCE( 7) | 8, + kVBCppBinary_RightShift = VBCPPOP_L2R | VBCPPOP_PRECEDENCE( 7) | 9, + kVBCppBinary_LessThan = VBCPPOP_L2R | VBCPPOP_PRECEDENCE( 8) | 10, + kVBCppBinary_LessThanOrEqual = VBCPPOP_L2R | VBCPPOP_PRECEDENCE( 8) | 11, + kVBCppBinary_GreaterThan = VBCPPOP_L2R | VBCPPOP_PRECEDENCE( 8) | 12, + kVBCppBinary_GreaterThanOrEqual = VBCPPOP_L2R | VBCPPOP_PRECEDENCE( 8) | 13, + kVBCppBinary_EqualTo = VBCPPOP_L2R | VBCPPOP_PRECEDENCE( 9) | 14, + kVBCppBinary_NotEqualTo = VBCPPOP_L2R | VBCPPOP_PRECEDENCE( 9) | 15, + kVBCppBinary_BitwiseAnd = VBCPPOP_L2R | VBCPPOP_PRECEDENCE(10) | 16, + kVBCppBinary_BitwiseXor = VBCPPOP_L2R | VBCPPOP_PRECEDENCE(11) | 17, + kVBCppBinary_BitwiseOr = VBCPPOP_L2R | VBCPPOP_PRECEDENCE(12) | 18, + kVBCppBinary_LogicalAnd = VBCPPOP_L2R | VBCPPOP_PRECEDENCE(13) | 19, + kVBCppBinary_LogicalOr = VBCPPOP_L2R | VBCPPOP_PRECEDENCE(14) | 20, + kVBCppBinary_End +} VBCPPBINARYOP; + +/** The precedence of the ternary operator (expr ? true : false). */ +#define VBCPPTERNAROP_PRECEDENCE VBCPPOP_PRECEDENCE(16) + + +/** Pointer to an expression parsing node. */ +typedef struct VBCPPEXPR *PVBCPPEXPR; +/** + * Expression parsing node. + */ +typedef struct VBCPPEXPR +{ + /** Parent expression. */ + PVBCPPEXPR pParent; + /** Whether the expression is complete or not. */ + bool fComplete; + /** The kind of expression. */ + VBCPPEXPRKIND enmKind; + /** Kind specific content. */ + union + { + /** kVBCppExprKind_Unary */ + struct + { + VBCPPUNARYOP enmOperator; + PVBCPPEXPR pArg; + } Unary; + + /** kVBCppExprKind_Binary */ + struct + { + VBCPPBINARYOP enmOperator; + PVBCPPEXPR pLeft; + PVBCPPEXPR pRight; + } Binary; + + /** kVBCppExprKind_Ternary */ + struct + { + PVBCPPEXPR pExpr; + PVBCPPEXPR pTrue; + PVBCPPEXPR pFalse; + } Ternary; + + /** kVBCppExprKind_SignedValue */ + struct + { + int64_t s64; + } SignedValue; + + /** kVBCppExprKind_UnsignedValue */ + struct + { + uint64_t u64; + } UnsignedValue; + } u; +} VBCPPEXPR; + + +/** + * Operator return statuses. + */ +typedef enum VBCPPEXPRRET +{ + kExprRet_Error = -1, + kExprRet_Ok = 0, + kExprRet_UnaryOperator, + kExprRet_Value, + kExprRet_EndOfExpr, + kExprRet_End +} VBCPPEXPRRET; + +/** + * Expression parser context. + */ +typedef struct VBCPPEXPRPARSER +{ + /** The current expression posistion. */ + const char *pszCur; + /** The root node. */ + PVBCPPEXPR pRoot; + /** The current expression node. */ + PVBCPPEXPR pCur; + /** Where to insert the next expression. */ + PVBCPPEXPR *ppCur; + /** The expression. */ + const char *pszExpr; + /** The number of undefined macros we've encountered while parsing. */ + size_t cUndefined; + /** Pointer to the C preprocessor instance. */ + PVBCPP pThis; +} VBCPPEXPRPARSER; +/** Pointer to an expression parser context. */ +typedef VBCPPEXPRPARSER *PVBCPPEXPRPARSER; + + +/** + * Evaluation result. + */ +typedef enum VBCPPEVAL +{ + kVBCppEval_Invalid = 0, + kVBCppEval_True, + kVBCppEval_False, + kVBCppEval_Undecided, + kVBCppEval_End +} VBCPPEVAL; + + +/** + * The condition kind. + */ +typedef enum VBCPPCONDKIND +{ + kVBCppCondKind_Invalid = 0, + /** \#if expr */ + kVBCppCondKind_If, + /** \#ifdef define */ + kVBCppCondKind_IfDef, + /** \#ifndef define */ + kVBCppCondKind_IfNDef, + /** \#elif expr */ + kVBCppCondKind_ElIf, + /** The end of valid values. */ + kVBCppCondKind_End +} VBCPPCONDKIND; + + +/** + * Conditional stack entry. + */ +typedef struct VBCPPCOND +{ + /** The next conditional on the stack. */ + struct VBCPPCOND *pUp; + /** The kind of conditional. This changes on encountering \#elif. */ + VBCPPCONDKIND enmKind; + /** Evaluation result. */ + VBCPPEVAL enmResult; + /** The evaluation result of the whole stack. */ + VBCPPEVAL enmStackResult; + + /** Whether we've seen the last else. */ + bool fSeenElse; + /** Set if we have an else if which has already been decided. */ + bool fElIfDecided; + /** The nesting level of this condition. */ + uint16_t iLevel; + /** The nesting level of this condition wrt the ones we keep. */ + uint16_t iKeepLevel; + + /** The condition string. (Points within the stream buffer.) */ + const char *pchCond; + /** The condition length. */ + size_t cchCond; +} VBCPPCOND; +/** Pointer to a conditional stack entry. */ +typedef VBCPPCOND *PVBCPPCOND; + + +/** + * Input buffer stack entry. + */ +typedef struct VBCPPINPUT +{ + /** Pointer to the next input on the stack. */ + struct VBCPPINPUT *pUp; + /** The input stream. */ + SCMSTREAM StrmInput; + /** Pointer into szName to the part which was specified. */ + const char *pszSpecified; + /** The input file name with include path. */ + char szName[1]; +} VBCPPINPUT; +/** Pointer to a input buffer stack entry */ +typedef VBCPPINPUT *PVBCPPINPUT; + + +/** + * The action to take with \#include. + */ +typedef enum VBCPPINCLUDEACTION +{ + kVBCppIncludeAction_Invalid = 0, + kVBCppIncludeAction_Include, + kVBCppIncludeAction_PassThru, + kVBCppIncludeAction_Drop, + kVBCppIncludeAction_End +} VBCPPINCLUDEACTION; + + +/** + * C Preprocessor instance data. + */ +typedef struct VBCPP +{ + /** @name Options + * @{ */ + /** The preprocessing mode. */ + VBCPPMODE enmMode; + /** Whether to keep comments. */ + bool fKeepComments; + /** Whether to respect source defines. */ + bool fRespectSourceDefines; + /** Whether to let source defines overrides the ones on the command + * line. */ + bool fAllowRedefiningCmdLineDefines; + /** Whether to pass thru defines. */ + bool fPassThruDefines; + /** Whether to allow undecided conditionals. */ + bool fUndecidedConditionals; + /** Whether to pass thru D pragmas. */ + bool fPassThruPragmaD; + /** Whether to pass thru STD pragmas. */ + bool fPassThruPragmaSTD; + /** Whether to pass thru other pragmas. */ + bool fPassThruPragmaOther; + /** Whether to remove dropped lines from the output. */ + bool fRemoveDroppedLines; + /** Whether to preforme line splicing. + * @todo implement line splicing */ + bool fLineSplicing; + /** What to do about include files. */ + VBCPPINCLUDEACTION enmIncludeAction; + + /** The number of include directories. */ + uint32_t cIncludes; + /** Array of directories to search for include files. */ + char **papszIncludes; + + /** The name of the input file. */ + const char *pszInput; + /** The name of the output file. NULL if stdout. */ + const char *pszOutput; + /** @} */ + + /** The define string space. */ + RTSTRSPACE StrSpace; + /** The string space holding explicitly undefined macros for selective + * preprocessing runs. */ + RTSTRSPACE UndefStrSpace; + /** Indicates whether a C-word might need expansion. + * The bitmap is indexed by C-word lead character. Bits that are set + * indicates that the lead character is used in a \#define that we know and + * should expand. */ + VBCPP_BITMAP_TYPE bmDefined[VBCPP_BITMAP_SIZE]; + + /** The current depth of the conditional stack. */ + uint32_t cCondStackDepth; + /** Conditional stack. */ + PVBCPPCOND pCondStack; + /** The current condition evaluates to kVBCppEval_False, don't output. */ + bool fIf0Mode; + /** Just dropped a line and should maybe drop the current line. */ + bool fJustDroppedLine; + + /** Whether the current line could be a preprocessor line. + * This is set when EOL is encountered and cleared again when a + * non-comment-or-space character is encountered. See vbcppPreprocess. */ + bool fMaybePreprocessorLine; + + /** The input stack depth */ + uint32_t cInputStackDepth; + /** The input buffer stack. */ + PVBCPPINPUT pInputStack; + + /** The output stream. */ + SCMSTREAM StrmOutput; + + /** The status of the whole job, as far as we know. */ + RTEXITCODE rcExit; + /** Whether StrmOutput is valid (for vbcppTerm). */ + bool fStrmOutputValid; +} VBCPP; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static PVBCPPMACRO vbcppMacroLookup(PVBCPP pThis, const char *pszDefine, size_t cchDefine); +static RTEXITCODE vbcppMacroExpandIt(PVBCPP pThis, PVBCPPMACROEXP pExp, size_t offMacro, PVBCPPMACRO pMacro, size_t offParameters); +static RTEXITCODE vbcppMacroExpandReScan(PVBCPP pThis, PVBCPPMACROEXP pExp, VBCPPMACRORESCANMODE enmMode, size_t *pcReplacements); +static void vbcppMacroExpandCleanup(PVBCPPMACROEXP pExp); + + + +/* + * + * + * Message Handling. + * Message Handling. + * Message Handling. + * Message Handling. + * Message Handling. + * + * + */ + + +/** + * Displays an error message. + * + * @returns RTEXITCODE_FAILURE + * @param pThis The C preprocessor instance. + * @param pszMsg The message. + * @param va Message arguments. + */ +static RTEXITCODE vbcppErrorV(PVBCPP pThis, const char *pszMsg, va_list va) +{ + NOREF(pThis); + if (pThis->pInputStack) + { + PSCMSTREAM pStrm = &pThis->pInputStack->StrmInput; + + size_t const off = ScmStreamTell(pStrm); + size_t const iLine = ScmStreamTellLine(pStrm); + ScmStreamSeekByLine(pStrm, iLine); + size_t const offLine = ScmStreamTell(pStrm); + + RTPrintf("%s:%d:%zd: error: %N.\n", pThis->pInputStack->szName, iLine + 1, off - offLine + 1, pszMsg, va); + + size_t cchLine; + SCMEOL enmEof; + const char *pszLine = ScmStreamGetLineByNo(pStrm, iLine, &cchLine, &enmEof); + if (pszLine) + RTPrintf(" %.*s\n" + " %*s^\n", + cchLine, pszLine, off - offLine, ""); + + ScmStreamSeekAbsolute(pStrm, off); + } + else + RTMsgErrorV(pszMsg, va); + return pThis->rcExit = RTEXITCODE_FAILURE; +} + + +/** + * Displays an error message. + * + * @returns RTEXITCODE_FAILURE + * @param pThis The C preprocessor instance. + * @param pszMsg The message. + * @param ... Message arguments. + */ +static RTEXITCODE vbcppError(PVBCPP pThis, const char *pszMsg, ...) +{ + va_list va; + va_start(va, pszMsg); + RTEXITCODE rcExit = vbcppErrorV(pThis, pszMsg, va); + va_end(va); + return rcExit; +} + + +/** + * Displays an error message. + * + * @returns RTEXITCODE_FAILURE + * @param pThis The C preprocessor instance. + * @param pszPos Pointer to the offending character. + * @param pszMsg The message. + * @param ... Message arguments. + */ +static RTEXITCODE vbcppErrorPos(PVBCPP pThis, const char *pszPos, const char *pszMsg, ...) +{ + NOREF(pszPos); NOREF(pThis); + va_list va; + va_start(va, pszMsg); + RTMsgErrorV(pszMsg, va); + va_end(va); + return pThis->rcExit = RTEXITCODE_FAILURE; +} + + + + + + + +/* + * + * + * Variable String Buffers. + * Variable String Buffers. + * Variable String Buffers. + * Variable String Buffers. + * Variable String Buffers. + * + * + */ + + +/** + * Initializes a string buffer. + * + * @param pStrBuf The buffer structure to initialize. + * @param pThis The C preprocessor instance. + */ +static void vbcppStrBufInit(PVBCPPSTRBUF pStrBuf, PVBCPP pThis) +{ + pStrBuf->pThis = pThis; + pStrBuf->cchBuf = 0; + pStrBuf->cbBufAllocated = 0; + pStrBuf->pszBuf = NULL; +} + + +/** + * Deletes a string buffer. + * + * @param pStrBuf Pointer to the string buffer. + */ +static void vbcppStrBufDelete(PVBCPPSTRBUF pStrBuf) +{ + RTMemFree(pStrBuf->pszBuf); + pStrBuf->pszBuf = NULL; +} + + +/** + * Ensures that sufficient bufferspace is available, growing the buffer if + * necessary. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pStrBuf Pointer to the string buffer. + * @param cbMin The minimum buffer size. + */ +static RTEXITCODE vbcppStrBufGrow(PVBCPPSTRBUF pStrBuf, size_t cbMin) +{ + if (pStrBuf->cbBufAllocated >= cbMin) + return RTEXITCODE_SUCCESS; + + size_t cbNew = pStrBuf->cbBufAllocated * 2; + if (cbNew < cbMin) + cbNew = RT_ALIGN_Z(cbMin, _1K); + void *pv = RTMemRealloc(pStrBuf->pszBuf, cbNew); + if (!pv) + return vbcppError(pStrBuf->pThis, "out of memory (%zu bytes)", cbNew); + + pStrBuf->pszBuf = (char *)pv; + pStrBuf->cbBufAllocated = cbNew; + return RTEXITCODE_SUCCESS; +} + + +/** + * Appends a substring. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pStrBuf Pointer to the string buffer. + * @param pchSrc Pointer to the first character in the substring. + * @param cchSrc The length of the substring. + */ +static RTEXITCODE vbcppStrBufAppendN(PVBCPPSTRBUF pStrBuf, const char *pchSrc, size_t cchSrc) +{ + size_t cchBuf = pStrBuf->cchBuf; + if (cchBuf + cchSrc + 1 > pStrBuf->cbBufAllocated) + { + RTEXITCODE rcExit = vbcppStrBufGrow(pStrBuf, cchBuf + cchSrc + 1); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + } + + memcpy(&pStrBuf->pszBuf[cchBuf], pchSrc, cchSrc); + cchBuf += cchSrc; + pStrBuf->pszBuf[cchBuf] = '\0'; + pStrBuf->cchBuf = cchBuf; + + return RTEXITCODE_SUCCESS; +} + + +/** + * Appends a character. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pStrBuf Pointer to the string buffer. + * @param ch The charater to append. + */ +static RTEXITCODE vbcppStrBufAppendCh(PVBCPPSTRBUF pStrBuf, char ch) +{ + size_t cchBuf = pStrBuf->cchBuf; + if (cchBuf + 2 > pStrBuf->cbBufAllocated) + { + RTEXITCODE rcExit = vbcppStrBufGrow(pStrBuf, cchBuf + 2); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + } + + pStrBuf->pszBuf[cchBuf++] = ch; + pStrBuf->pszBuf[cchBuf] = '\0'; + pStrBuf->cchBuf = cchBuf; + + return RTEXITCODE_SUCCESS; +} + + +/** + * Appends a string to the buffer. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pStrBuf Pointer to the string buffer. + * @param psz The string to append. + */ +static RTEXITCODE vbcppStrBufAppend(PVBCPPSTRBUF pStrBuf, const char *psz) +{ + return vbcppStrBufAppendN(pStrBuf, psz, strlen(psz)); +} + + +/** + * Gets the last char in the buffer. + * + * @returns Last character, 0 if empty. + * @param pStrBuf Pointer to the string buffer. + */ +static char vbcppStrBufLastCh(PVBCPPSTRBUF pStrBuf) +{ + if (!pStrBuf->cchBuf) + return '\0'; + return pStrBuf->pszBuf[pStrBuf->cchBuf - 1]; +} + + + + + + + +/* + * + * + * C Identifier/Word Parsing. + * C Identifier/Word Parsing. + * C Identifier/Word Parsing. + * C Identifier/Word Parsing. + * C Identifier/Word Parsing. + * + * + */ + + +/** + * Checks if the given character is a valid C identifier lead character. + * + * @returns true / false. + * @param ch The character to inspect. + */ +DECLINLINE(bool) vbcppIsCIdentifierLeadChar(char ch) +{ + return RT_C_IS_ALPHA(ch) + || ch == '_'; +} + + +/** + * Checks if the given character is a valid C identifier character. + * + * @returns true / false. + * @param ch The character to inspect. + */ +DECLINLINE(bool) vbcppIsCIdentifierChar(char ch) +{ + return RT_C_IS_ALNUM(ch) + || ch == '_'; +} + + + +/** + * + * @returns @c true if valid, @c false if not. Error message already displayed + * on failure. + * @param pThis The C preprocessor instance. + * @param pchIdentifier The start of the identifier to validate. + * @param cchIdentifier The length of the identifier. RTSTR_MAX if not + * known. + */ +static bool vbcppValidateCIdentifier(PVBCPP pThis, const char *pchIdentifier, size_t cchIdentifier) +{ + if (cchIdentifier == RTSTR_MAX) + cchIdentifier = strlen(pchIdentifier); + + if (cchIdentifier == 0) + { + vbcppErrorPos(pThis, pchIdentifier, "Zero length identifier"); + return false; + } + + if (!vbcppIsCIdentifierLeadChar(*pchIdentifier)) + { + vbcppErrorPos(pThis, pchIdentifier, "Bad lead chararacter in identifier: '%.*s'", cchIdentifier, pchIdentifier); + return false; + } + + for (size_t off = 1; off < cchIdentifier; off++) + { + if (!vbcppIsCIdentifierChar(pchIdentifier[off])) + { + vbcppErrorPos(pThis, pchIdentifier + off, "Illegal chararacter in identifier: '%.*s' (#%zu)", cchIdentifier, pchIdentifier, off + 1); + return false; + } + } + + return true; +} + +#if 0 + +/** + * Checks if the given character is valid C punctuation. + * + * @returns true / false. + * @param ch The character to inspect. + */ +DECLINLINE(bool) vbcppIsCPunctuationLeadChar(char ch) +{ + switch (ch) + { + case '!': + case '#': + case '%': + case '&': + case '(': + case ')': + case '*': + case '+': + case ',': + case '-': + case '.': + case '/': + case ':': + case ';': + case '<': + case '=': + case '>': + case '?': + case '[': + case ']': + case '^': + case '{': + case '|': + case '}': + case '~': + return true; + default: + return false; + } +} + + +/** + * Checks if the given string start with valid C punctuation. + * + * @returns 0 if not, otherwise the length of the punctuation. + * @param pch The which start we should evaluate. + * @param cchMax The maximum string length. + */ +static size_t vbcppIsCPunctuationLeadChar(const char *psz, size_t cchMax) +{ + if (!cchMax) + return 0; + + switch (psz[0]) + { + case '!': + case '*': + case '/': + case '=': + case '^': + if (cchMax >= 2 && psz[1] == '=') + return 2; + return 1; + + case '#': + if (cchMax >= 2 && psz[1] == '#') + return 2; + return 1; + + case '%': + if (cchMax >= 2 && (psz[1] == '=' || psz[1] == '>')) + return 2; + if (cchMax >= 2 && psz[1] == ':') + { + if (cchMax >= 4 && psz[2] == '%' && psz[3] == ':') + return 4; + return 2; + } + return 1; + + case '&': + if (cchMax >= 2 && (psz[1] == '=' || psz[1] == '&')) + return 2; + return 1; + + case '(': + case ')': + case ',': + case '?': + case '[': + case ']': + case '{': + case '}': + return 1; + + case '+': + if (cchMax >= 2 && (psz[1] == '=' || psz[1] == '+')) + return 2; + return 1; + + case '-': + if (cchMax >= 2 && (psz[1] == '=' || psz[1] == '-' || psz[1] == '>')) + return 2; + return 1; + + case ':': + if (cchMax >= 2 && psz[1] == '>') + return 2; + return 1; + + case ';': + return 1; + + case '<': + if (cchMax >= 2 && psz[1] == '<') + { + if (cchMax >= 3 && psz[2] == '=') + return 3; + return 2; + } + if (cchMax >= 2 && (psz[1] == '=' || psz[1] == ':' || psz[1] == '%')) + return 2; + return 1; + + case '.': + if (cchMax >= 3 && psz[1] == '.' && psz[2] == '.') + return 3; + return 1; + + case '>': + if (cchMax >= 2 && psz[1] == '>') + { + if (cchMax >= 3 && psz[2] == '=') + return 3; + return 2; + } + if (cchMax >= 2 && psz[1] == '=') + return 2; + return 1; + + case '|': + if (cchMax >= 2 && (psz[1] == '=' || psz[1] == '|')) + return 2; + return 1; + + case '~': + return 1; + + default: + return 0; + } +} + +#endif + + + + + +/* + * + * + * Output + * Output + * Output + * Output + * Output + * + * + */ + + +/** + * Outputs a character. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pThis The C preprocessor instance. + * @param ch The character to output. + */ +static RTEXITCODE vbcppOutputCh(PVBCPP pThis, char ch) +{ + int rc = ScmStreamPutCh(&pThis->StrmOutput, ch); + if (RT_SUCCESS(rc)) + return RTEXITCODE_SUCCESS; + return vbcppError(pThis, "Output error: %Rrc", rc); +} + + +/** + * Outputs a string. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pThis The C preprocessor instance. + * @param pch The string. + * @param cch The number of characters to write. + */ +static RTEXITCODE vbcppOutputWrite(PVBCPP pThis, const char *pch, size_t cch) +{ + int rc = ScmStreamWrite(&pThis->StrmOutput, pch, cch); + if (RT_SUCCESS(rc)) + return RTEXITCODE_SUCCESS; + return vbcppError(pThis, "Output error: %Rrc", rc); +} + + +static RTEXITCODE vbcppOutputComment(PVBCPP pThis, PSCMSTREAM pStrmInput, size_t offStart, size_t cchOutputted, + size_t cchMinIndent) +{ + RT_NOREF_PV(cchMinIndent); /** @todo cchMinIndent */ + + size_t offCur = ScmStreamTell(pStrmInput); + if (offStart < offCur) + { + int rc = ScmStreamSeekAbsolute(pStrmInput, offStart); + AssertRCReturn(rc, vbcppError(pThis, "Input seek error: %Rrc", rc)); + + /* + * Use the same indent, if possible. + */ + size_t cchIndent = offStart - ScmStreamTellOffsetOfLine(pStrmInput, ScmStreamTellLine(pStrmInput)); + if (cchOutputted < cchIndent) + rc = ScmStreamPrintf(&pThis->StrmOutput, "%*s", cchIndent - cchOutputted, ""); + else + rc = ScmStreamPutCh(&pThis->StrmOutput, ' '); + if (RT_FAILURE(rc)) + return vbcppError(pThis, "Output error: %Rrc", rc); + + /* + * Copy the bytes. + */ + while (ScmStreamTell(pStrmInput) < offCur) + { + unsigned ch = ScmStreamGetCh(pStrmInput); + if (ch == ~(unsigned)0) + return vbcppError(pThis, "Input error: %Rrc", rc); + rc = ScmStreamPutCh(&pThis->StrmOutput, ch); + if (RT_FAILURE(rc)) + return vbcppError(pThis, "Output error: %Rrc", rc); + } + } + + return RTEXITCODE_SUCCESS; +} + + + + + +/* + * + * + * Input + * Input + * Input + * Input + * Input + * + * + */ + + +#if 0 /* unused */ +/** + * Skips white spaces, including escaped new-lines. + * + * @param pStrmInput The input stream. + */ +static void vbcppProcessSkipWhiteAndEscapedEol(PSCMSTREAM pStrmInput) +{ + unsigned chPrev = ~(unsigned)0; + unsigned ch; + while ((ch = ScmStreamPeekCh(pStrmInput)) != ~(unsigned)0) + { + if (ch == '\r' || ch == '\n') + { + if (chPrev != '\\') + break; + chPrev = ch; + ScmStreamSeekByLine(pStrmInput, ScmStreamTellLine(pStrmInput) + 1); + } + else if (RT_C_IS_SPACE(ch)) + { + chPrev = ch; + ch = ScmStreamGetCh(pStrmInput); + Assert(ch == chPrev); + } + else + break; + } +} +#endif + + +/** + * Skips white spaces, escaped new-lines and multi line comments. + * + * @param pThis The C preprocessor instance. + * @param pStrmInput The input stream. + */ +static RTEXITCODE vbcppProcessSkipWhiteEscapedEolAndComments(PVBCPP pThis, PSCMSTREAM pStrmInput) +{ + unsigned chPrev = ~(unsigned)0; + unsigned ch; + while ((ch = ScmStreamPeekCh(pStrmInput)) != ~(unsigned)0) + { + if (!RT_C_IS_SPACE(ch)) + { + /* Multi-line Comment? */ + if (ch != '/') + break; /* most definitely, not. */ + + size_t offSaved = ScmStreamTell(pStrmInput); + ScmStreamGetCh(pStrmInput); + if (ScmStreamPeekCh(pStrmInput) != '*') + { + ScmStreamSeekAbsolute(pStrmInput, offSaved); + break; /* no */ + } + + /* Skip to the end of the comment. */ + while ((ch = ScmStreamGetCh(pStrmInput)) != ~(unsigned)0) + { + if (ch == '*') + { + ch = ScmStreamGetCh(pStrmInput); + if (ch == '/') + break; + if (ch == ~(unsigned)0) + break; + } + } + if (ch == ~(unsigned)0) + return vbcppError(pThis, "unterminated multi-line comment"); + chPrev = '/'; + } + /* New line (also matched by RT_C_IS_SPACE). */ + else if (ch == '\r' || ch == '\n') + { + /* Stop if not escaped. */ + if (chPrev != '\\') + break; + chPrev = ch; + ScmStreamSeekByLine(pStrmInput, ScmStreamTellLine(pStrmInput) + 1); + } + /* Real space char. */ + else + { + chPrev = ch; + ch = ScmStreamGetCh(pStrmInput); + Assert(ch == chPrev); + } + } + return RTEXITCODE_SUCCESS; +} + + +/** + * Skips white spaces, escaped new-lines, and multi line comments, then checking + * that we're at the end of a line. + * + * @param pThis The C preprocessor instance. + * @param pStrmInput The input stream. + */ +static RTEXITCODE vbcppProcessSkipWhiteEscapedEolAndCommentsCheckEol(PVBCPP pThis, PSCMSTREAM pStrmInput) +{ + RTEXITCODE rcExit = vbcppProcessSkipWhiteEscapedEolAndComments(pThis, pStrmInput); + if (rcExit == RTEXITCODE_SUCCESS) + { + unsigned ch = ScmStreamPeekCh(pStrmInput); + if ( ch != ~(unsigned)0 + && ch != '\r' + && ch != '\n') + rcExit = vbcppError(pThis, "Did not expected anything more on this line"); + } + return rcExit; +} + + +/** + * Skips white spaces. + * + * @returns The current location upon return. + * @param pStrmInput The input stream. + */ +static size_t vbcppProcessSkipWhite(PSCMSTREAM pStrmInput) +{ + unsigned ch; + while ((ch = ScmStreamPeekCh(pStrmInput)) != ~(unsigned)0) + { + if (!RT_C_IS_SPACE(ch) || ch == '\r' || ch == '\n') + break; + unsigned chCheck = ScmStreamGetCh(pStrmInput); + AssertBreak(chCheck == ch); + } + return ScmStreamTell(pStrmInput); +} + + +/** + * Looks for a left parenthesis in the input stream. + * + * Used during macro expansion. Will ignore comments, newlines and other + * whitespace. + * + * @retval true if found. The stream position at opening parenthesis. + * @retval false if not found. The stream position is unchanged. + * + * @param pThis The C preprocessor instance. + * @param pStrmInput The input stream. + */ +static bool vbcppInputLookForLeftParenthesis(PVBCPP pThis, PSCMSTREAM pStrmInput) +{ + size_t offSaved = ScmStreamTell(pStrmInput); + /*RTEXITCODE rcExit =*/ vbcppProcessSkipWhiteEscapedEolAndComments(pThis, pStrmInput); + unsigned ch = ScmStreamPeekCh(pStrmInput); + if (ch == '(') + return true; + + int rc = ScmStreamSeekAbsolute(pStrmInput, offSaved); + AssertFatalRC(rc); + return false; +} + + +/** + * Skips input until the real end of the current directive line has been + * reached. + * + * This includes multiline comments starting on the same line + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pThis The C preprocessor instance. + * @param pStrmInput The input stream. + * @param poffComment Where to note down the position of the final + * comment. Optional. + */ +static RTEXITCODE vbcppInputSkipToEndOfDirectiveLine(PVBCPP pThis, PSCMSTREAM pStrmInput, size_t *poffComment) +{ + if (poffComment) + *poffComment = ~(size_t)0; + + RTEXITCODE rcExit = RTEXITCODE_SUCCESS; + bool fInComment = false; + unsigned chPrev = 0; + unsigned ch; + while ((ch = ScmStreamPeekCh(pStrmInput)) != ~(unsigned)0) + { + if (ch == '\r' || ch == '\n') + { + if (chPrev == '\\') + { + ScmStreamSeekByLine(pStrmInput, ScmStreamTellLine(pStrmInput) + 1); + continue; + } + if (!fInComment) + break; + /* The expression continues after multi-line comments. Cool. :-) */ + } + else if (!fInComment) + { + if (chPrev == '/' && ch == '*' ) + { + fInComment = true; + if (poffComment) + *poffComment = ScmStreamTell(pStrmInput) - 1; + } + else if (chPrev == '/' && ch == '/') + { + if (poffComment) + *poffComment = ScmStreamTell(pStrmInput) - 1; + rcExit = vbcppProcessSkipWhiteEscapedEolAndComments(pThis, pStrmInput); + break; /* done */ + } + } + else if (ch == '/' && chPrev == '*') + fInComment = false; + + /* advance */ + chPrev = ch; + ch = ScmStreamGetCh(pStrmInput); Assert(ch == chPrev); + } + return rcExit; +} + + +/** + * Processes a multi-line comment. + * + * Must either string the comment or keep it. If the latter, we must refrain + * from replacing C-words in it. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pThis The C preprocessor instance. + * @param pStrmInput The input stream. + */ +static RTEXITCODE vbcppProcessMultiLineComment(PVBCPP pThis, PSCMSTREAM pStrmInput) +{ + /* The open comment sequence. */ + ScmStreamGetCh(pStrmInput); /* '*' */ + RTEXITCODE rcExit = RTEXITCODE_SUCCESS; + if ( pThis->fKeepComments + && !pThis->fIf0Mode) + rcExit = vbcppOutputWrite(pThis, "/*", 2); + + /* The comment.*/ + unsigned ch; + while ( rcExit == RTEXITCODE_SUCCESS + && (ch = ScmStreamGetCh(pStrmInput)) != ~(unsigned)0 ) + { + if (ch == '*') + { + /* Closing sequence? */ + unsigned ch2 = ScmStreamPeekCh(pStrmInput); + if (ch2 == '/') + { + ScmStreamGetCh(pStrmInput); + if ( pThis->fKeepComments + && !pThis->fIf0Mode) + rcExit = vbcppOutputWrite(pThis, "*/", 2); + break; + } + } + + if (ch == '\r' || ch == '\n') + { + if ( ( pThis->fKeepComments + && !pThis->fIf0Mode) + || !pThis->fRemoveDroppedLines + || !ScmStreamIsAtStartOfLine(&pThis->StrmOutput)) + rcExit = vbcppOutputCh(pThis, ch); + pThis->fJustDroppedLine = false; + pThis->fMaybePreprocessorLine = true; + } + else if ( pThis->fKeepComments + && !pThis->fIf0Mode) + rcExit = vbcppOutputCh(pThis, ch); + + if (rcExit != RTEXITCODE_SUCCESS) + break; + } + return rcExit; +} + + +/** + * Processes a single line comment. + * + * Must either string the comment or keep it. If the latter, we must refrain + * from replacing C-words in it. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pThis The C preprocessor instance. + * @param pStrmInput The input stream. + */ +static RTEXITCODE vbcppProcessOneLineComment(PVBCPP pThis, PSCMSTREAM pStrmInput) +{ + RTEXITCODE rcExit = RTEXITCODE_SUCCESS; + SCMEOL enmEol; + size_t cchLine; + const char *pszLine = ScmStreamGetLine(pStrmInput, &cchLine, &enmEol); Assert(pszLine); + pszLine--; cchLine++; /* unfetching the first slash. */ + for (;;) + { + if ( pThis->fKeepComments + && !pThis->fIf0Mode) + rcExit = vbcppOutputWrite(pThis, pszLine, cchLine + enmEol); + else if ( !pThis->fIf0Mode + || !pThis->fRemoveDroppedLines + || !ScmStreamIsAtStartOfLine(&pThis->StrmOutput) ) + rcExit = vbcppOutputWrite(pThis, pszLine + cchLine, enmEol); + if (rcExit != RTEXITCODE_SUCCESS) + break; + if ( cchLine == 0 + || pszLine[cchLine - 1] != '\\') + break; + + pszLine = ScmStreamGetLine(pStrmInput, &cchLine, &enmEol); + if (!pszLine) + break; + } + pThis->fJustDroppedLine = false; + pThis->fMaybePreprocessorLine = true; + return rcExit; +} + + +/** + * Processes a double quoted string. + * + * Must not replace any C-words in strings. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pThis The C preprocessor instance. + * @param pStrmInput The input stream. + */ +static RTEXITCODE vbcppProcessStringLitteral(PVBCPP pThis, PSCMSTREAM pStrmInput) +{ + RTEXITCODE rcExit = vbcppOutputCh(pThis, '"'); + if (rcExit == RTEXITCODE_SUCCESS) + { + bool fEscaped = false; + for (;;) + { + unsigned ch = ScmStreamGetCh(pStrmInput); + if (ch == ~(unsigned)0) + { + rcExit = vbcppError(pThis, "Unterminated double quoted string"); + break; + } + + rcExit = vbcppOutputCh(pThis, ch); + if (rcExit != RTEXITCODE_SUCCESS) + break; + + if (ch == '"' && !fEscaped) + break; + fEscaped = !fEscaped && ch == '\\'; + } + } + return rcExit; +} + + +/** + * Processes a single quoted constant. + * + * Must not replace any C-words in character constants. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pThis The C preprocessor instance. + * @param pStrmInput The input stream. + */ +static RTEXITCODE vbcppProcessCharacterConstant(PVBCPP pThis, PSCMSTREAM pStrmInput) +{ + RTEXITCODE rcExit = vbcppOutputCh(pThis, '\''); + if (rcExit == RTEXITCODE_SUCCESS) + { + bool fEscaped = false; + for (;;) + { + unsigned ch = ScmStreamGetCh(pStrmInput); + if (ch == ~(unsigned)0) + { + rcExit = vbcppError(pThis, "Unterminated singled quoted string"); + break; + } + + rcExit = vbcppOutputCh(pThis, ch); + if (rcExit != RTEXITCODE_SUCCESS) + break; + + if (ch == '\'' && !fEscaped) + break; + fEscaped = !fEscaped && ch == '\\'; + } + } + return rcExit; +} + + +/** + * Processes a integer or floating point number constant. + * + * Must not replace the type suffix. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pThis The C preprocessor instance. + * @param pStrmInput The input stream. + * @param chFirst The first character. + */ +static RTEXITCODE vbcppProcessNumber(PVBCPP pThis, PSCMSTREAM pStrmInput, char chFirst) +{ + RTEXITCODE rcExit = vbcppOutputCh(pThis, chFirst); + + unsigned ch; + while ( rcExit == RTEXITCODE_SUCCESS + && (ch = ScmStreamPeekCh(pStrmInput)) != ~(unsigned)0) + { + if ( !vbcppIsCIdentifierChar(ch) + && ch != '.') + break; + + unsigned ch2 = ScmStreamGetCh(pStrmInput); + AssertBreakStmt(ch2 == ch, rcExit = vbcppError(pThis, "internal error")); + rcExit = vbcppOutputCh(pThis, ch); + } + + return rcExit; +} + + +/** + * Processes a identifier, possibly replacing it with a definition. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pThis The C preprocessor instance. + * @param pStrmInput The input stream. + */ +static RTEXITCODE vbcppProcessIdentifier(PVBCPP pThis, PSCMSTREAM pStrmInput) +{ + RTEXITCODE rcExit; + size_t cchDefine; + const char *pchDefine = ScmStreamCGetWordM1(pStrmInput, &cchDefine); + AssertReturn(pchDefine, vbcppError(pThis, "Internal error in ScmStreamCGetWordM1")); + + /* + * Does this look like a define we know? + */ + PVBCPPMACRO pMacro = vbcppMacroLookup(pThis, pchDefine, cchDefine); + if ( pMacro + && ( !pMacro->fFunction + || vbcppInputLookForLeftParenthesis(pThis, pStrmInput)) ) + { + /* + * Expand it. + */ + VBCPPMACROEXP ExpCtx; +#if 0 + ExpCtx.pMacroStack = NULL; +#endif + ExpCtx.pStrmInput = pStrmInput; + ExpCtx.papszArgs = NULL; + ExpCtx.cArgs = 0; + ExpCtx.cArgsAlloced = 0; + vbcppStrBufInit(&ExpCtx.StrBuf, pThis); + rcExit = vbcppStrBufAppendN(&ExpCtx.StrBuf, pchDefine, cchDefine); + if (rcExit == RTEXITCODE_SUCCESS) + rcExit = vbcppMacroExpandIt(pThis, &ExpCtx, 0 /* offset */, pMacro, cchDefine); + if (rcExit == RTEXITCODE_SUCCESS) + rcExit = vbcppMacroExpandReScan(pThis, &ExpCtx, kMacroReScanMode_Normal, NULL); + if (rcExit == RTEXITCODE_SUCCESS) + { + /* + * Insert it into the output stream. Make sure there is a + * whitespace following it. + */ + int rc = ScmStreamWrite(&pThis->StrmOutput, ExpCtx.StrBuf.pszBuf, ExpCtx.StrBuf.cchBuf); + if (RT_SUCCESS(rc)) + { + unsigned chAfter = ScmStreamPeekCh(pStrmInput); + if (chAfter != ~(unsigned)0 && !RT_C_IS_SPACE(chAfter)) + rcExit = vbcppOutputCh(pThis, ' '); + } + else + rcExit = vbcppError(pThis, "Output error: %Rrc", rc); + } + vbcppMacroExpandCleanup(&ExpCtx); + } + else + { + /* + * Not a macro or a function-macro name match but no invocation, just + * output the text unchanged. + */ + int rc = ScmStreamWrite(&pThis->StrmOutput, pchDefine, cchDefine); + if (RT_SUCCESS(rc)) + rcExit = RTEXITCODE_SUCCESS; + else + rcExit = vbcppError(pThis, "Output error: %Rrc", rc); + } + return rcExit; +} + + + + + + + +/* + * + * + * D E F I N E S / M A C R O S + * D E F I N E S / M A C R O S + * D E F I N E S / M A C R O S + * D E F I N E S / M A C R O S + * D E F I N E S / M A C R O S + * + * + */ + + +/** + * Checks if a define exists. + * + * @returns true or false. + * @param pThis The C preprocessor instance. + * @param pszDefine The define name and optionally the argument + * list. + * @param cchDefine The length of the name. RTSTR_MAX is ok. + */ +static bool vbcppMacroExists(PVBCPP pThis, const char *pszDefine, size_t cchDefine) +{ + return cchDefine > 0 + && VBCPP_BITMAP_IS_SET(pThis->bmDefined, *pszDefine) + && RTStrSpaceGetN(&pThis->StrSpace, pszDefine, cchDefine) != NULL; +} + + +/** + * Looks up a define. + * + * @returns Pointer to the define if found, NULL if not. + * @param pThis The C preprocessor instance. + * @param pszDefine The define name and optionally the argument + * list. + * @param cchDefine The length of the name. RTSTR_MAX is ok. + */ +static PVBCPPMACRO vbcppMacroLookup(PVBCPP pThis, const char *pszDefine, size_t cchDefine) +{ + if (!cchDefine) + return NULL; + if (!VBCPP_BITMAP_IS_SET(pThis->bmDefined, *pszDefine)) + return NULL; + return (PVBCPPMACRO)RTStrSpaceGetN(&pThis->StrSpace, pszDefine, cchDefine); +} + + +static uint32_t vbcppMacroLookupArg(PVBCPPMACRO pMacro, const char *pchName, size_t cchName) +{ + Assert(cchName > 0); + + char const ch = *pchName; + for (uint32_t i = 0; i < pMacro->cArgs; i++) + if ( pMacro->papszArgs[i][0] == ch + && !strncmp(pMacro->papszArgs[i], pchName, cchName) + && pMacro->papszArgs[i][cchName] == '\0') + return i; + + if ( pMacro->fVarArg + && cchName == sizeof("__VA_ARGS__") - 1 + && !strncmp(pchName, "__VA_ARGS__", sizeof("__VA_ARGS__") - 1) ) + return pMacro->cArgs; + + return UINT32_MAX; +} + + +static RTEXITCODE vbcppMacroExpandReplace(PVBCPP pThis, PVBCPPMACROEXP pExp, size_t off, size_t cchToReplace, + const char *pchReplacement, size_t cchReplacement) +{ + RT_NOREF_PV(pThis); + + /* + * Figure how much space we actually need. + * (Hope this whitespace stuff is correct...) + */ + bool const fLeadingSpace = off > 0 + && !RT_C_IS_SPACE(pExp->StrBuf.pszBuf[off - 1]); + bool const fTrailingSpace = off + cchToReplace < pExp->StrBuf.cchBuf + && !RT_C_IS_SPACE(pExp->StrBuf.pszBuf[off + cchToReplace]); + size_t const cchActualReplacement = fLeadingSpace + cchReplacement + fTrailingSpace; + + /* + * Adjust the buffer size and contents. + */ + if (cchActualReplacement > cchToReplace) + { + size_t const offMore = cchActualReplacement - cchToReplace; + + /* Ensure enough buffer space. */ + size_t cbMinBuf = offMore + pExp->StrBuf.cchBuf + 1; + RTEXITCODE rcExit = vbcppStrBufGrow(&pExp->StrBuf, cbMinBuf); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + + /* Push the chars following the replacement area down to make room. */ + memmove(&pExp->StrBuf.pszBuf[off + cchToReplace + offMore], + &pExp->StrBuf.pszBuf[off + cchToReplace], + pExp->StrBuf.cchBuf - off - cchToReplace + 1); + pExp->StrBuf.cchBuf += offMore; + + } + else if (cchActualReplacement < cchToReplace) + { + size_t const offLess = cchToReplace - cchActualReplacement; + + /* Pull the chars following the replacement area up. */ + memmove(&pExp->StrBuf.pszBuf[off + cchToReplace - offLess], + &pExp->StrBuf.pszBuf[off + cchToReplace], + pExp->StrBuf.cchBuf - off - cchToReplace + 1); + pExp->StrBuf.cchBuf -= offLess; + } + + /* + * Insert the replacement string. + */ + char *pszCur = &pExp->StrBuf.pszBuf[off]; + if (fLeadingSpace) + *pszCur++ = ' '; + memcpy(pszCur, pchReplacement, cchReplacement); + if (fTrailingSpace) + *pszCur++ = ' '; + + Assert(strlen(pExp->StrBuf.pszBuf) == pExp->StrBuf.cchBuf); + + return RTEXITCODE_SUCCESS; +} + + +static unsigned vbcppMacroExpandPeekCh(PVBCPPMACROEXP pExp, size_t *poff) +{ + size_t off = *poff; + if (off >= pExp->StrBuf.cchBuf) + return pExp->pStrmInput ? ScmStreamPeekCh(pExp->pStrmInput) : ~(unsigned)0; + return pExp->StrBuf.pszBuf[off]; +} + + +static unsigned vbcppMacroExpandGetCh(PVBCPPMACROEXP pExp, size_t *poff) +{ + size_t off = *poff; + if (off >= pExp->StrBuf.cchBuf) + return pExp->pStrmInput ? ScmStreamGetCh(pExp->pStrmInput) : ~(unsigned)0; + *poff = off + 1; + return pExp->StrBuf.pszBuf[off]; +} + + +static RTEXITCODE vbcppMacroExpandSkipEolEx(PVBCPP pThis, PVBCPPMACROEXP pExp, size_t *poff, unsigned chFirst) +{ + if (chFirst == '\r') + { + unsigned ch2 = vbcppMacroExpandPeekCh(pExp, poff); + if (ch2 == '\n') + { + ch2 = ScmStreamGetCh(pExp->pStrmInput); + AssertReturn(ch2 == '\n', vbcppError(pThis, "internal error")); + } + } + return RTEXITCODE_SUCCESS; +} + + +static RTEXITCODE vbcppMacroExpandSkipEol(PVBCPP pThis, PVBCPPMACROEXP pExp, size_t *poff) +{ + unsigned ch = vbcppMacroExpandGetCh(pExp, poff); + AssertReturn(ch == '\r' || ch == '\n', vbcppError(pThis, "internal error")); + return vbcppMacroExpandSkipEolEx(pThis, pExp, poff, ch); +} + + +static RTEXITCODE vbcppMacroExpandSkipCommentLine(PVBCPP pThis, PVBCPPMACROEXP pExp, size_t *poff) +{ + unsigned ch = vbcppMacroExpandGetCh(pExp, poff); + AssertReturn(ch == '/', vbcppError(pThis, "Internal error - expected '/' got '%c'", ch)); + + unsigned chPrev = 0; + while ((ch = vbcppMacroExpandGetCh(pExp, poff)) != ~(unsigned)0) + { + if (ch == '\r' || ch == '\n') + { + RTEXITCODE rcExit = vbcppMacroExpandSkipEolEx(pThis, pExp, poff, ch); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + if (chPrev != '\\') + break; + } + + chPrev = ch; + } + return RTEXITCODE_SUCCESS; +} + + +static RTEXITCODE vbcppMacroExpandSkipComment(PVBCPP pThis, PVBCPPMACROEXP pExp, size_t *poff) +{ + unsigned ch = vbcppMacroExpandGetCh(pExp, poff); + AssertReturn(ch == '*', vbcppError(pThis, "Internal error - expected '*' got '%c'", ch)); + + unsigned chPrev2 = 0; + unsigned chPrev = 0; + while ((ch = vbcppMacroExpandGetCh(pExp, poff)) != ~(unsigned)0) + { + if (ch == '/' && chPrev == '*') + break; + + if (ch == '\r' || ch == '\n') + { + RTEXITCODE rcExit = vbcppMacroExpandSkipEolEx(pThis, pExp, poff, ch); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + if (chPrev == '\\') + { + chPrev = chPrev2; /* for line splicing */ + continue; + } + } + + chPrev2 = chPrev; + chPrev = ch; + } + return RTEXITCODE_SUCCESS; +} + + +static RTEXITCODE vbcppMacroExpandGrowArgArray(PVBCPP pThis, PVBCPPMACROEXP pExp, uint32_t cMinArgs) +{ + if (cMinArgs > pExp->cArgsAlloced) + { + void *pv = RTMemRealloc(pExp->papszArgs, cMinArgs * sizeof(char *)); + if (!pv) + return vbcppError(pThis, "out of memory"); + pExp->papszArgs = (char **)pv; + pExp->cArgsAlloced = cMinArgs; + } + return RTEXITCODE_SUCCESS; +} + + +static RTEXITCODE vbcppMacroExpandAddEmptyParameter(PVBCPP pThis, PVBCPPMACROEXP pExp) +{ + RTEXITCODE rcExit = vbcppMacroExpandGrowArgArray(pThis, pExp, pExp->cArgs + 1); + if (rcExit == RTEXITCODE_SUCCESS) + { + char *pszArg = (char *)RTMemAllocZ(1); + if (pszArg) + pExp->papszArgs[pExp->cArgs++] = pszArg; + else + rcExit = vbcppError(pThis, "out of memory"); + } + return rcExit; +} + + +static RTEXITCODE vbcppMacroExpandGatherParameters(PVBCPP pThis, PVBCPPMACROEXP pExp, size_t *poff, uint32_t cArgsHint) +{ + RTEXITCODE rcExit = RTEXITCODE_SUCCESS; + + /* + * Free previous argument values. + */ + while (pExp->cArgs > 0) + { + RTMemFree(pExp->papszArgs[--pExp->cArgs]); + pExp->papszArgs[pExp->cArgs] = NULL; + } + + /* + * The current character should be an opening parenthsis. + */ + unsigned ch = vbcppMacroExpandGetCh(pExp, poff); + if (ch != '(') + return vbcppError(pThis, "Internal error - expected '(', found '%c' (#x)", ch, ch); + + /* + * Parse the argument list. + */ + char chQuote = 0; + size_t cbArgAlloc = 0; + size_t cchArg = 0; + char *pszArg = NULL; + size_t cParentheses = 1; + unsigned chPrev = 0; + while ((ch = vbcppMacroExpandGetCh(pExp, poff)) != ~(unsigned)0) + { +/** @todo check for '#directives'! */ + if (ch == ')' && !chQuote) + { + Assert(cParentheses >= 1); + cParentheses--; + + /* The end? */ + if (!cParentheses) + { + if (cchArg) + while (cchArg > 0 && RT_C_IS_SPACE(pszArg[cchArg - 1])) + pszArg[--cchArg] = '\0'; + else if (pExp->cArgs || cArgsHint > 0) + rcExit = vbcppMacroExpandAddEmptyParameter(pThis, pExp); + break; + } + } + else if (ch == '(' && !chQuote) + cParentheses++; + else if (ch == ',' && cParentheses == 1 && !chQuote) + { + /* End of one argument, start of the next. */ + if (cchArg) + while (cchArg > 0 && RT_C_IS_SPACE(pszArg[cchArg - 1])) + pszArg[--cchArg] = '\0'; + else + { + rcExit = vbcppMacroExpandAddEmptyParameter(pThis, pExp); + if (rcExit != RTEXITCODE_SUCCESS) + break; + } + + cbArgAlloc = 0; + cchArg = 0; + pszArg = NULL; + continue; + } + else if (ch == '/' && !chQuote) + { + /* Comment? */ + unsigned ch2 = vbcppMacroExpandPeekCh(pExp, poff); + /** @todo This ain't right wrt line splicing. */ + if (ch2 == '/' || ch == '*') + { + if (ch2 == '/') + rcExit = vbcppMacroExpandSkipCommentLine(pThis, pExp, poff); + else + rcExit = vbcppMacroExpandSkipComment(pThis, pExp, poff); + if (rcExit != RTEXITCODE_SUCCESS) + break; + continue; + } + } + else if (ch == '"') + { + if (!chQuote) + chQuote = '"'; + else if (chPrev != '\\') + chQuote = 0; + } + else if (ch == '\'') + { + if (!chQuote) + chQuote = '\''; + else if (chPrev != '\\') + chQuote = 0; + } + else if (ch == '\\') + { + /* Splice lines? */ + unsigned ch2 = vbcppMacroExpandPeekCh(pExp, poff); + if (ch2 == '\r' || ch2 == '\n') + { + rcExit = vbcppMacroExpandSkipEol(pThis, pExp, poff); + if (rcExit != RTEXITCODE_SUCCESS) + break; + continue; + } + } + else if (cchArg == 0 && RT_C_IS_SPACE(ch)) + continue; /* ignore spaces leading up to an argument value */ + + /* Append the character to the argument value, adding the argument + to the output array if it's first character in it. */ + if (cchArg + 1 >= cbArgAlloc) + { + /* Add argument to the vector. */ + if (!cchArg) + { + rcExit = vbcppMacroExpandGrowArgArray(pThis, pExp, RT_MAX(pExp->cArgs + 1, cArgsHint)); + if (rcExit != RTEXITCODE_SUCCESS) + break; + pExp->papszArgs[pExp->cArgs++] = pszArg; + } + + /* Resize the argument value buffer. */ + cbArgAlloc = cbArgAlloc ? cbArgAlloc * 2 : 16; + pszArg = (char *)RTMemRealloc(pszArg, cbArgAlloc); + if (!pszArg) + { + rcExit = vbcppError(pThis, "out of memory"); + break; + } + pExp->papszArgs[pExp->cArgs - 1] = pszArg; + } + + pszArg[cchArg++] = ch; + pszArg[cchArg] = '\0'; + } + + /* + * Check that we're leaving on good terms. + */ + if (rcExit == RTEXITCODE_SUCCESS) + { + if (cParentheses) + rcExit = vbcppError(pThis, "Missing ')'"); + } + + return rcExit; +} + + +/** + * Expands the arguments referenced in the macro value. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE + msg. + * @param pThis The C preprocessor instance. + * @param pExp The expansion context. + * @param pMacro The macro. Must be a function macro. + * @param pStrBuf String buffer containing the result. The caller + * should initialize and destroy this! + */ +static RTEXITCODE vbcppMacroExpandValueWithArguments(PVBCPP pThis, PVBCPPMACROEXP pExp, PVBCPPMACRO pMacro, + PVBCPPSTRBUF pStrBuf) +{ + Assert(pMacro->fFunction); + + /* + * Empty? + */ + if ( !pMacro->cchValue + || (pMacro->cchValue == 1 && pMacro->szValue[0] == '#')) + return RTEXITCODE_SUCCESS; + + /* + * Parse the value. + */ + RTEXITCODE rcExit = RTEXITCODE_SUCCESS; + const char *pszSrc = pMacro->szValue; + const char *pszSrcSeq; + char ch; + while ((ch = *pszSrc++) != '\0') + { + Assert(ch != '\r'); Assert(ch != '\n'); /* probably not true atm. */ + if (ch == '#') + { + if (*pszSrc == '#') + { + /* Concatenate operator. */ + rcExit = vbcppError(pThis, "The '##' operatore is not yet implemented"); + } + else + { + /* Stringify macro argument. */ + rcExit = vbcppError(pThis, "The '#' operatore is not yet implemented"); + } + return rcExit; + } + else if (ch == '"') + { + /* String litteral. */ + pszSrcSeq = pszSrc - 1; + while ((ch = *pszSrc++) != '"') + { + if (ch == '\\') + ch = *pszSrc++; + if (ch == '\0') + { + rcExit = vbcppError(pThis, "String litteral is missing closing quote (\")."); + break; + } + } + rcExit = vbcppStrBufAppendN(pStrBuf, pszSrcSeq, pszSrc - pszSrcSeq); + } + else if (ch == '\'') + { + /* Character constant. */ + pszSrcSeq = pszSrc - 1; + while ((ch = *pszSrc++) != '\'') + { + if (ch == '\\') + ch = *pszSrc++; + if (ch == '\0') + { + rcExit = vbcppError(pThis, "Character constant is missing closing quote (')."); + break; + } + } + rcExit = vbcppStrBufAppendN(pStrBuf, pszSrcSeq, pszSrc - pszSrcSeq); + } + else if (RT_C_IS_DIGIT(ch)) + { + /* Process numerical constants correctly (i.e. don't mess with the suffix). */ + pszSrcSeq = pszSrc - 1; + while ( (ch = *pszSrc) != '\0' + && ( vbcppIsCIdentifierChar(ch) + || ch == '.') ) + pszSrc++; + rcExit = vbcppStrBufAppendN(pStrBuf, pszSrcSeq, pszSrc - pszSrcSeq); + } + else if (RT_C_IS_SPACE(ch)) + { + /* join spaces */ + if (RT_C_IS_SPACE(vbcppStrBufLastCh(pStrBuf))) + continue; + rcExit = vbcppStrBufAppendCh(pStrBuf, ch); + } + else if (vbcppIsCIdentifierLeadChar(ch)) + { + /* Something we should replace? */ + pszSrcSeq = pszSrc - 1; + while ( (ch = *pszSrc) != '\0' + && vbcppIsCIdentifierChar(ch)) + pszSrc++; + size_t cchDefine = pszSrc - pszSrcSeq; + uint32_t iArg; + if ( VBCPP_BITMAP_IS_SET(pMacro->bmArgs, *pszSrcSeq) + && (iArg = vbcppMacroLookupArg(pMacro, pszSrcSeq, cchDefine)) != UINT32_MAX) + { + /** @todo check out spaces here! */ + if (iArg < pMacro->cArgs) + { + Assert(iArg < pExp->cArgs); + rcExit = vbcppStrBufAppend(pStrBuf, pExp->papszArgs[iArg]); + if (*pExp->papszArgs[iArg] != '\0' && rcExit == RTEXITCODE_SUCCESS) + rcExit = vbcppStrBufAppendCh(pStrBuf, ' '); + } + else + { + /* __VA_ARGS__ */ + if (iArg < pExp->cArgs) + { + for (;;) + { + rcExit = vbcppStrBufAppend(pStrBuf, pExp->papszArgs[iArg]); + if (rcExit != RTEXITCODE_SUCCESS) + break; + iArg++; + if (iArg >= pExp->cArgs) + break; + rcExit = vbcppStrBufAppendCh(pStrBuf, ','); + if (rcExit != RTEXITCODE_SUCCESS) + break; + } + } + if (rcExit == RTEXITCODE_SUCCESS) + rcExit = vbcppStrBufAppendCh(pStrBuf, ' '); + } + } + /* Not an argument needing replacing. */ + else + rcExit = vbcppStrBufAppendN(pStrBuf, pszSrcSeq, cchDefine); + } + else + { + rcExit = vbcppStrBufAppendCh(pStrBuf, ch); + } + } + + return rcExit; +} + + + +/** + * Expands the given macro. + * + * Caller already checked if a function macro should be expanded, i.e. whether + * there is a parameter list. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE + msg. + * @param pThis The C preprocessor instance. + * @param pExp The expansion context. + * @param offMacro Offset into the expansion buffer of the macro + * invocation. + * @param pMacro The macro. + * @param offParameters The start of the parameter list if applicable. + * Ignored if not function macro. If the + * parameter list starts at the current stream + * position shall be at the end of the expansion + * buffer. + */ +static RTEXITCODE vbcppMacroExpandIt(PVBCPP pThis, PVBCPPMACROEXP pExp, size_t offMacro, PVBCPPMACRO pMacro, + size_t offParameters) +{ + RTEXITCODE rcExit; + Assert(offMacro + pMacro->Core.cchString <= pExp->StrBuf.cchBuf); + Assert(!pMacro->fExpanding); + + /* + * Function macros are kind of difficult... + */ + if (pMacro->fFunction) + { + rcExit = vbcppMacroExpandGatherParameters(pThis, pExp, &offParameters, pMacro->cArgs + pMacro->fVarArg); + if (rcExit == RTEXITCODE_SUCCESS) + { + if (pExp->cArgs > pMacro->cArgs && !pMacro->fVarArg) + rcExit = vbcppError(pThis, "Too many arguments to macro '%s' - found %u, expected %u", + pMacro->Core.pszString, pExp->cArgs, pMacro->cArgs); + else if (pExp->cArgs < pMacro->cArgs) + rcExit = vbcppError(pThis, "Too few arguments to macro '%s' - found %u, expected %u", + pMacro->Core.pszString, pExp->cArgs, pMacro->cArgs); + } + if (rcExit == RTEXITCODE_SUCCESS) + { + VBCPPSTRBUF ValueBuf; + vbcppStrBufInit(&ValueBuf, pThis); + rcExit = vbcppMacroExpandValueWithArguments(pThis, pExp, pMacro, &ValueBuf); + if (rcExit == RTEXITCODE_SUCCESS) + rcExit = vbcppMacroExpandReplace(pThis, pExp, offMacro, offParameters - offMacro, + ValueBuf.pszBuf, ValueBuf.cchBuf); + vbcppStrBufDelete(&ValueBuf); + } + } + /* + * Object-like macros are easy. :-) + */ + else + rcExit = vbcppMacroExpandReplace(pThis, pExp, offMacro, pMacro->Core.cchString, pMacro->szValue, pMacro->cchValue); + if (rcExit == RTEXITCODE_SUCCESS) + { +#if 0 /* wrong */ + /* + * Push the macro onto the stack. + */ + pMacro->fExpanding = true; + pMacro->pUpExpanding = pExp->pMacroStack; + pExp->pMacroStack = pMacro; +#endif + } + + return rcExit; +} + + +/** + * Looks for a left parenthesis in the macro expansion buffer and the input + * stream. + * + * @retval true if found. The stream position at opening parenthesis. + * @retval false if not found. The stream position is unchanged. + * + * @param pThis The C preprocessor instance. + * @param pExp The expansion context. + * @param poff The current offset in the expansion context. + * Will be updated on success. + * + * @sa vbcppInputLookForLeftParenthesis + */ +static bool vbcppMacroExpandLookForLeftParenthesis(PVBCPP pThis, PVBCPPMACROEXP pExp, size_t *poff) +{ + /* + * Search the buffer first. (No comments there.) + */ + size_t off = *poff; + while (off < pExp->StrBuf.cchBuf) + { + char ch = pExp->StrBuf.pszBuf[off]; + if (!RT_C_IS_SPACE(ch)) + { + if (ch == '(') + { + *poff = off; + return true; + } + return false; + } + off++; + } + + /* + * Reached the end of the buffer, continue searching in the stream. + */ + PSCMSTREAM pStrmInput = pExp->pStrmInput; + size_t offSaved = ScmStreamTell(pStrmInput); + /*RTEXITCODE rcExit = */ vbcppProcessSkipWhiteEscapedEolAndComments(pThis, pStrmInput); + unsigned ch = ScmStreamPeekCh(pStrmInput); + if (ch == '(') + { + *poff = pExp->StrBuf.cchBuf; + return true; + } + + int rc = ScmStreamSeekAbsolute(pStrmInput, offSaved); + AssertFatalRC(rc); + return false; +} + + +/** + * Implements the 'defined' unary operator for \#if and \#elif expressions. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE + msg. + * @param pThis The C preprocessor instance. + * @param pExp The expansion context. + * @param offStart The expansion buffer offset where the 'defined' + * occurs. + * @param poff Where to store the offset at which the re-scan + * shall resume upon return. + */ +static RTEXITCODE vbcppMacroExpandDefinedOperator(PVBCPP pThis, PVBCPPMACROEXP pExp, size_t offStart, size_t *poff) +{ + Assert(!pExp->pStrmInput); /* offset usage below. */ + + /* + * Skip white space. + */ + unsigned ch; + while ((ch = vbcppMacroExpandGetCh(pExp, poff)) != ~(unsigned)0) + if (!RT_C_IS_SPACE(ch)) + break; + bool const fWithParenthesis = ch == '('; + if (fWithParenthesis) + while ((ch = vbcppMacroExpandGetCh(pExp, poff)) != ~(unsigned)0) + if (!RT_C_IS_SPACE(ch)) + break; + + /* + * Macro identifier. + */ + if (!vbcppIsCIdentifierLeadChar(ch)) + return vbcppError(pThis, "Expected macro name after 'defined' operator"); + + size_t const offDefine = *poff - 1; + while ((ch = vbcppMacroExpandGetCh(pExp, poff)) != ~(unsigned)0) + if (!vbcppIsCIdentifierChar(ch)) + break; + size_t const cchDefine = *poff - offDefine - 1; + + /* + * Check for closing parenthesis. + */ + if (fWithParenthesis) + { + while (RT_C_IS_SPACE(ch)) + ch = vbcppMacroExpandGetCh(pExp, poff); + if (ch != ')') + return vbcppError(pThis, "Expected closing parenthesis after macro name"); + } + + /* + * Do the job. + */ + const char *pszResult = vbcppMacroExists(pThis, &pExp->StrBuf.pszBuf[offDefine], cchDefine) + ? "1" : "0"; + RTEXITCODE rcExit = vbcppMacroExpandReplace(pThis, pExp, offStart, *poff - offStart, pszResult, 1); + *poff = offStart + 1; + return rcExit; +} + + +/** + * Re-scan the expanded macro. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE + msg. + * @param pThis The C preprocessor instance. + * @param pExp The expansion context. + * @param enmMode The re-scan mode. + * @param pcReplacements Where to return the number of replacements + * performed. Optional. + */ +static RTEXITCODE vbcppMacroExpandReScan(PVBCPP pThis, PVBCPPMACROEXP pExp, VBCPPMACRORESCANMODE enmMode, size_t *pcReplacements) +{ + RTEXITCODE rcExit = RTEXITCODE_SUCCESS; + size_t cReplacements = 0; + size_t off = 0; + unsigned ch; + while ( off < pExp->StrBuf.cchBuf + && (ch = vbcppMacroExpandGetCh(pExp, &off)) != ~(unsigned)0) + { + /* + * String litteral or character constant. + */ + if (ch == '\'' || ch == '"') + { + unsigned const chEndQuote = ch; + while ( off < pExp->StrBuf.cchBuf + && (ch = vbcppMacroExpandGetCh(pExp, &off)) != ~(unsigned)0) + { + if (ch == '\\') + { + ch = vbcppMacroExpandGetCh(pExp, &off); + if (ch == ~(unsigned)0) + break; + } + else if (ch == chEndQuote) + break; + } + if (ch == ~(unsigned)0) + return vbcppError(pThis, "Missing end quote (%c)", chEndQuote); + } + /* + * Number constant. + */ + else if ( RT_C_IS_DIGIT(ch) + || ( ch == '.' + && off + 1 < pExp->StrBuf.cchBuf + && RT_C_IS_DIGIT(vbcppMacroExpandPeekCh(pExp, &off)) + ) + ) + { + while ( off < pExp->StrBuf.cchBuf + && (ch = vbcppMacroExpandPeekCh(pExp, &off)) != ~(unsigned)0 + && vbcppIsCIdentifierChar(ch) ) + vbcppMacroExpandGetCh(pExp, &off); + } + /* + * Something that can be replaced? + */ + else if (vbcppIsCIdentifierLeadChar(ch)) + { + size_t offDefine = off - 1; + while ( off < pExp->StrBuf.cchBuf + && (ch = vbcppMacroExpandPeekCh(pExp, &off)) != ~(unsigned)0 + && vbcppIsCIdentifierChar(ch) ) + vbcppMacroExpandGetCh(pExp, &off); + size_t cchDefine = off - offDefine; + + PVBCPPMACRO pMacro = vbcppMacroLookup(pThis, &pExp->StrBuf.pszBuf[offDefine], cchDefine); + if ( pMacro + && ( !pMacro->fFunction + || vbcppMacroExpandLookForLeftParenthesis(pThis, pExp, &off)) ) + { + cReplacements++; + rcExit = vbcppMacroExpandIt(pThis, pExp, offDefine, pMacro, off); + off = offDefine; + } + else + { + if ( !pMacro + && enmMode == kMacroReScanMode_Expression + && cchDefine == sizeof("defined") - 1 + && !strncmp(&pExp->StrBuf.pszBuf[offDefine], "defined", cchDefine)) + { + cReplacements++; + rcExit = vbcppMacroExpandDefinedOperator(pThis, pExp, offDefine, &off); + } + else + off = offDefine + cchDefine; + } + } + else + { + Assert(RT_C_IS_SPACE(ch) || RT_C_IS_PUNCT(ch)); + Assert(ch != '\r' && ch != '\n'); + } + } + + if (pcReplacements) + *pcReplacements = cReplacements; + return rcExit; +} + + +/** + * Cleans up the expansion context. + * + * This involves clearing VBCPPMACRO::fExpanding and VBCPPMACRO::pUpExpanding, + * and freeing the memory resources associated with the expansion context. + * + * @param pExp The expansion context. + */ +static void vbcppMacroExpandCleanup(PVBCPPMACROEXP pExp) +{ +#if 0 + while (pExp->pMacroStack) + { + PVBCPPMACRO pMacro = pExp->pMacroStack; + pExp->pMacroStack = pMacro->pUpExpanding; + + pMacro->fExpanding = false; + pMacro->pUpExpanding = NULL; + } +#endif + + while (pExp->cArgs > 0) + { + RTMemFree(pExp->papszArgs[--pExp->cArgs]); + pExp->papszArgs[pExp->cArgs] = NULL; + } + + RTMemFree(pExp->papszArgs); + pExp->papszArgs = NULL; + + vbcppStrBufDelete(&pExp->StrBuf); +} + + + +/** + * Frees a define. + * + * @returns VINF_SUCCESS (used when called by RTStrSpaceDestroy) + * @param pStr Pointer to the VBCPPMACRO::Core member. + * @param pvUser Unused. + */ +static DECLCALLBACK(int) vbcppMacroFree(PRTSTRSPACECORE pStr, void *pvUser) +{ + RTMemFree(pStr); + NOREF(pvUser); + return VINF_SUCCESS; +} + + +/** + * Removes a define. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE + msg. + * @param pThis The C preprocessor instance. + * @param pszDefine The define name, no argument list or anything. + * @param cchDefine The length of the name. RTSTR_MAX is ok. + * @param fExplicitUndef Explicit undefinition, that is, in a selective + * preprocessing run it will evaluate to undefined. + */ +static RTEXITCODE vbcppMacroUndef(PVBCPP pThis, const char *pszDefine, size_t cchDefine, bool fExplicitUndef) +{ + PRTSTRSPACECORE pHit = RTStrSpaceGetN(&pThis->StrSpace, pszDefine, cchDefine); + if (pHit) + { + RTStrSpaceRemove(&pThis->StrSpace, pHit->pszString); + vbcppMacroFree(pHit, NULL); + } + + if (fExplicitUndef) + { + if (cchDefine == RTSTR_MAX) + cchDefine = strlen(pszDefine); + + PRTSTRSPACECORE pStr = (PRTSTRSPACECORE)RTMemAlloc(sizeof(*pStr) + cchDefine + 1); + if (!pStr) + return vbcppError(pThis, "out of memory"); + char *pszDst = (char *)(pStr + 1); + pStr->pszString = pszDst; + memcpy(pszDst, pszDefine, cchDefine); + pszDst[cchDefine] = '\0'; + if (!RTStrSpaceInsert(&pThis->UndefStrSpace, pStr)) + RTMemFree(pStr); + } + + return RTEXITCODE_SUCCESS; +} + + +/** + * Inserts a define (rejecting and freeing it in some case). + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE + msg. + * @param pThis The C preprocessor instance. + * @param pMacro The define to insert. + */ +static RTEXITCODE vbcppMacroInsert(PVBCPP pThis, PVBCPPMACRO pMacro) +{ + /* + * Reject illegal macro names. + */ + if (!strcmp(pMacro->Core.pszString, "defined")) + { + RTEXITCODE rcExit = vbcppError(pThis, "Cannot use '%s' as a macro name", pMacro->Core.pszString); + vbcppMacroFree(&pMacro->Core, NULL); + return rcExit; + } + + /* + * Ignore in source-file defines when doing selective preprocessing. + */ + if ( !pThis->fRespectSourceDefines + && !pMacro->fCmdLine) + { + /* Ignore*/ + vbcppMacroFree(&pMacro->Core, NULL); + return RTEXITCODE_SUCCESS; + } + + /* + * Insert it and update the lead character hint bitmap. + */ + if (RTStrSpaceInsert(&pThis->StrSpace, &pMacro->Core)) + VBCPP_BITMAP_SET(pThis->bmDefined, *pMacro->Core.pszString); + else + { + /* + * Duplicate. When doing selective D preprocessing, let the command + * line take precendece. + */ + PVBCPPMACRO pOld = (PVBCPPMACRO)RTStrSpaceGet(&pThis->StrSpace, pMacro->Core.pszString); Assert(pOld); + if ( pThis->fAllowRedefiningCmdLineDefines + || pMacro->fCmdLine == pOld->fCmdLine) + { + if (pMacro->fCmdLine) + RTMsgWarning("Redefining '%s'", pMacro->Core.pszString); + + RTStrSpaceRemove(&pThis->StrSpace, pOld->Core.pszString); + vbcppMacroFree(&pOld->Core, NULL); + + bool fRc = RTStrSpaceInsert(&pThis->StrSpace, &pMacro->Core); + Assert(fRc); NOREF(fRc); + } + else + { + RTMsgWarning("Ignoring redefinition of '%s'", pMacro->Core.pszString); + vbcppMacroFree(&pMacro->Core, NULL); + } + } + + return RTEXITCODE_SUCCESS; +} + + +/** + * Adds a define. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE + msg. + * @param pThis The C preprocessor instance. + * @param pszDefine The define name, no parameter list. + * @param cchDefine The length of the name. + * @param pszParams The parameter list. + * @param cchParams The length of the parameter list. + * @param pszValue The value. + * @param cchDefine The length of the value. + * @param fCmdLine Set if originating on the command line. + */ +static RTEXITCODE vbcppMacroAddFn(PVBCPP pThis, const char *pszDefine, size_t cchDefine, + const char *pszParams, size_t cchParams, + const char *pszValue, size_t cchValue, + bool fCmdLine) + +{ + Assert(RTStrNLen(pszDefine, cchDefine) == cchDefine); + Assert(RTStrNLen(pszParams, cchParams) == cchParams); + Assert(RTStrNLen(pszValue, cchValue) == cchValue); + + /* + * Determin the number of arguments and how much space their names + * requires. Performing syntax validation while parsing. + */ + uint32_t cchArgNames = 0; + uint32_t cArgs = 0; + for (size_t off = 0; off < cchParams; off++) + { + /* Skip blanks and maybe one comma. */ + bool fIgnoreComma = cArgs != 0; + while (off < cchParams) + { + if (!RT_C_IS_SPACE(pszParams[off])) + { + if (pszParams[off] != ',' || !fIgnoreComma) + { + if (vbcppIsCIdentifierLeadChar(pszParams[off])) + break; + /** @todo variadic macros. */ + return vbcppErrorPos(pThis, &pszParams[off], "Unexpected character"); + } + fIgnoreComma = false; + } + off++; + } + if (off >= cchParams) + break; + + /* Found and argument. First character is already validated. */ + cArgs++; + cchArgNames += 2; + off++; + while ( off < cchParams + && vbcppIsCIdentifierChar(pszParams[off])) + off++, cchArgNames++; + } + + /* + * Allocate a structure. + */ + size_t cbDef = RT_UOFFSETOF_DYN(VBCPPMACRO, szValue[cchValue + 1 + cchDefine + 1 + cchArgNames]) + + sizeof(const char *) * cArgs; + cbDef = RT_ALIGN_Z(cbDef, sizeof(const char *)); + PVBCPPMACRO pMacro = (PVBCPPMACRO)RTMemAlloc(cbDef); + if (!pMacro) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "out of memory"); + + char *pszDst = &pMacro->szValue[cchValue + 1]; + pMacro->Core.pszString = pszDst; + memcpy(pszDst, pszDefine, cchDefine); + pszDst += cchDefine; + *pszDst++ = '\0'; + pMacro->fFunction = true; + pMacro->fVarArg = false; + pMacro->fCmdLine = fCmdLine; + pMacro->fExpanding = false; + pMacro->cArgs = cArgs; + pMacro->papszArgs = (const char **)((uintptr_t)pMacro + cbDef - sizeof(const char *) * cArgs); + VBCPP_BITMAP_EMPTY(pMacro->bmArgs); + pMacro->cchValue = cchValue; + memcpy(pMacro->szValue, pszValue, cchValue); + pMacro->szValue[cchValue] = '\0'; + + /* + * Set up the arguments. + */ + uint32_t iArg = 0; + for (size_t off = 0; off < cchParams; off++) + { + /* Skip blanks and maybe one comma. */ + bool fIgnoreComma = cArgs != 0; + while (off < cchParams) + { + if (!RT_C_IS_SPACE(pszParams[off])) + { + if (pszParams[off] != ',' || !fIgnoreComma) + break; + fIgnoreComma = false; + } + off++; + } + if (off >= cchParams) + break; + + /* Found and argument. First character is already validated. */ + VBCPP_BITMAP_SET(pMacro->bmArgs, pszParams[off]); + pMacro->papszArgs[iArg] = pszDst; + do + { + *pszDst++ = pszParams[off++]; + } while ( off < cchParams + && vbcppIsCIdentifierChar(pszParams[off])); + *pszDst++ = '\0'; + iArg++; + } + Assert((uintptr_t)pszDst <= (uintptr_t)pMacro->papszArgs); + + return vbcppMacroInsert(pThis, pMacro); +} + + +/** + * Adds a define. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE + msg. + * @param pThis The C preprocessor instance. + * @param pszDefine The define name and optionally the argument + * list. + * @param cchDefine The length of the name. RTSTR_MAX is ok. + * @param pszValue The value. + * @param cchDefine The length of the value. RTSTR_MAX is ok. + * @param fCmdLine Set if originating on the command line. + */ +static RTEXITCODE vbcppMacroAdd(PVBCPP pThis, const char *pszDefine, size_t cchDefine, + const char *pszValue, size_t cchValue, bool fCmdLine) +{ + /* + * We need the lengths. Trim the input. + */ + if (cchDefine == RTSTR_MAX) + cchDefine = strlen(pszDefine); + while (cchDefine > 0 && RT_C_IS_SPACE(*pszDefine)) + pszDefine++, cchDefine--; + while (cchDefine > 0 && RT_C_IS_SPACE(pszDefine[cchDefine - 1])) + cchDefine--; + if (!cchDefine) + return vbcppErrorPos(pThis, pszDefine, "The define has no name"); + + if (cchValue == RTSTR_MAX) + cchValue = strlen(pszValue); + while (cchValue > 0 && RT_C_IS_SPACE(*pszValue)) + pszValue++, cchValue--; + while (cchValue > 0 && RT_C_IS_SPACE(pszValue[cchValue - 1])) + cchValue--; + + /* + * Arguments make the job a bit more annoying. Handle that elsewhere + */ + const char *pszParams = (const char *)memchr(pszDefine, '(', cchDefine); + if (pszParams) + { + size_t cchParams = pszDefine + cchDefine - pszParams; + cchDefine -= cchParams; + if (!vbcppValidateCIdentifier(pThis, pszDefine, cchDefine)) + return RTEXITCODE_FAILURE; + if (pszParams[cchParams - 1] != ')') + return vbcppErrorPos(pThis, pszParams + cchParams - 1, "Missing closing parenthesis"); + pszParams++; + cchParams -= 2; + return vbcppMacroAddFn(pThis, pszDefine, cchDefine, pszParams, cchParams, pszValue, cchValue, fCmdLine); + } + + /* + * Simple define, no arguments. + */ + if (!vbcppValidateCIdentifier(pThis, pszDefine, cchDefine)) + return RTEXITCODE_FAILURE; + + PVBCPPMACRO pMacro = (PVBCPPMACRO)RTMemAlloc(RT_UOFFSETOF_DYN(VBCPPMACRO, szValue[cchValue + 1 + cchDefine + 1])); + if (!pMacro) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "out of memory"); + + pMacro->Core.pszString = &pMacro->szValue[cchValue + 1]; + memcpy((char *)pMacro->Core.pszString, pszDefine, cchDefine); + ((char *)pMacro->Core.pszString)[cchDefine] = '\0'; + pMacro->fFunction = false; + pMacro->fVarArg = false; + pMacro->fCmdLine = fCmdLine; + pMacro->fExpanding = false; + pMacro->cArgs = 0; + pMacro->papszArgs = NULL; + VBCPP_BITMAP_EMPTY(pMacro->bmArgs); + pMacro->cchValue = cchValue; + memcpy(pMacro->szValue, pszValue, cchValue); + pMacro->szValue[cchValue] = '\0'; + + return vbcppMacroInsert(pThis, pMacro); +} + + +/** + * Tries to convert a define into an inline D constant. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pThis The C preprocessor instance. + * @param pMacro The macro. + */ +static RTEXITCODE vbcppMacroTryConvertToInlineD(PVBCPP pThis, PVBCPPMACRO pMacro) +{ + AssertReturn(pMacro, vbcppError(pThis, "Internal error")); + if (pMacro->fFunction) + return RTEXITCODE_SUCCESS; + + /* + * Do some simple macro resolving. (Mostly to make x86.h work.) + */ + const char *pszDefine = pMacro->Core.pszString; + const char *pszValue = pMacro->szValue; + size_t cchValue = pMacro->cchValue; + + unsigned i = 0; + PVBCPPMACRO pMacro2; + while ( i < 10 + && cchValue > 0 + && vbcppIsCIdentifierLeadChar(*pszValue) + && (pMacro2 = vbcppMacroLookup(pThis, pszValue, cchValue)) != NULL + && !pMacro2->fFunction ) + { + pszValue = pMacro2->szValue; + cchValue = pMacro2->cchValue; + i++; + } + + if (!pMacro->cchValue) + return RTEXITCODE_SUCCESS; + + + /* + * A lone value? + */ + ssize_t cch = 0; + uint64_t u64; + char *pszNext; + int rc = RTStrToUInt64Ex(pszValue, &pszNext, 0, &u64); + if (RT_SUCCESS(rc)) + { + if ( rc == VWRN_TRAILING_SPACES + || rc == VWRN_NEGATIVE_UNSIGNED + || rc == VWRN_NUMBER_TOO_BIG) + return RTEXITCODE_SUCCESS; + const char *pszType; + if (rc == VWRN_TRAILING_CHARS) + { + if (!strcmp(pszNext, "u") || !strcmp(pszNext, "U")) + pszType = "uint32_t"; + else if (!strcmp(pszNext, "ul") || !strcmp(pszNext, "UL")) + pszType = "uintptr_t"; + else if (!strcmp(pszNext, "ull") || !strcmp(pszNext, "ULL")) + pszType = "uint64_t"; + else + pszType = NULL; + } + else if (u64 <= UINT8_MAX) + pszType = "uint8_t"; + else if (u64 <= UINT16_MAX) + pszType = "uint16_t"; + else if (u64 <= UINT32_MAX) + pszType = "uint32_t"; + else + pszType = "uint64_t"; + if (!pszType) + return RTEXITCODE_SUCCESS; + cch = ScmStreamPrintf(&pThis->StrmOutput, "inline %s %s = %.*s;\n", + pszType, pszDefine, pszNext - pszValue, pszValue); + } + /* + * A value wrapped in a constant macro? + */ + else if ( (pszNext = (char *)strchr(pszValue, '(')) != NULL + && pszValue[cchValue - 1] == ')' ) + { + size_t cchPrefix = pszNext - pszValue; + size_t cchInnerValue = cchValue - cchPrefix - 2; + const char *pchInnerValue = &pszValue[cchPrefix + 1]; + while (cchInnerValue > 0 && RT_C_IS_SPACE(*pchInnerValue)) + cchInnerValue--, pchInnerValue++; + while (cchInnerValue > 0 && RT_C_IS_SPACE(pchInnerValue[cchInnerValue - 1])) + cchInnerValue--; + if (!cchInnerValue || !RT_C_IS_XDIGIT(*pchInnerValue)) + return RTEXITCODE_SUCCESS; + + rc = RTStrToUInt64Ex(pchInnerValue, &pszNext, 0, &u64); + if ( RT_FAILURE(rc) + || rc == VWRN_TRAILING_SPACES + || rc == VWRN_NEGATIVE_UNSIGNED + || rc == VWRN_NUMBER_TOO_BIG) + return RTEXITCODE_SUCCESS; + + const char *pszType; +#define MY_MATCH_STR(a_sz) (sizeof(a_sz) - 1 == cchPrefix && !strncmp(pszValue, a_sz, sizeof(a_sz) - 1)) + if (MY_MATCH_STR("UINT8_C")) + pszType = "uint8_t"; + else if (MY_MATCH_STR("UINT16_C")) + pszType = "uint16_t"; + else if (MY_MATCH_STR("UINT32_C")) + pszType = "uint32_t"; + else if (MY_MATCH_STR("UINT64_C")) + pszType = "uint64_t"; + else + pszType = NULL; + if (pszType) + cch = ScmStreamPrintf(&pThis->StrmOutput, "inline %s %s = %.*s;\n", + pszType, pszDefine, cchInnerValue, pchInnerValue); + else if (MY_MATCH_STR("RT_BIT") || MY_MATCH_STR("RT_BIT_32")) + cch = ScmStreamPrintf(&pThis->StrmOutput, "inline uint32_t %s = 1U << %llu;\n", + pszDefine, u64); + else if (MY_MATCH_STR("RT_BIT_64")) + cch = ScmStreamPrintf(&pThis->StrmOutput, "inline uint64_t %s = 1ULL << %llu;\n", + pszDefine, u64); + else + return RTEXITCODE_SUCCESS; +#undef MY_MATCH_STR + } + /* Dunno what this is... */ + else + return RTEXITCODE_SUCCESS; + + /* + * Check for output error and clear the output suppression indicator. + */ + if (cch < 0) + return vbcppError(pThis, "Output error"); + + pThis->fJustDroppedLine = false; + return RTEXITCODE_SUCCESS; +} + + + +/** + * Processes a abbreviated line number directive. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pThis The C preprocessor instance. + * @param pStrmInput The input stream. + * @param offStart The stream position where the directive + * started (for pass thru). + */ +static RTEXITCODE vbcppDirectiveDefine(PVBCPP pThis, PSCMSTREAM pStrmInput, size_t offStart) +{ + RT_NOREF_PV(offStart); + + /* + * Parse it. + */ + RTEXITCODE rcExit = vbcppProcessSkipWhiteEscapedEolAndComments(pThis, pStrmInput); + if (rcExit == RTEXITCODE_SUCCESS) + { + size_t cchDefine; + const char *pchDefine = ScmStreamCGetWord(pStrmInput, &cchDefine); + if (pchDefine) + { + /* If it's a function style define, parse out the parameter list. */ + size_t cchParams = 0; + const char *pchParams = NULL; + unsigned ch = ScmStreamPeekCh(pStrmInput); + if (ch == '(') + { + ScmStreamGetCh(pStrmInput); + pchParams = ScmStreamGetCur(pStrmInput); + + unsigned chPrev = ch; + while ((ch = ScmStreamPeekCh(pStrmInput)) != ~(unsigned)0) + { + if (ch == '\r' || ch == '\n') + { + if (chPrev != '\\') + { + rcExit = vbcppError(pThis, "Missing ')'"); + break; + } + ScmStreamSeekByLine(pStrmInput, ScmStreamTellLine(pStrmInput) + 1); + } + if (ch == ')') + { + cchParams = ScmStreamGetCur(pStrmInput) - pchParams; + ScmStreamGetCh(pStrmInput); + break; + } + ScmStreamGetCh(pStrmInput); + } + } + /* The simple kind. */ + else if (!RT_C_IS_SPACE(ch) && ch != ~(unsigned)0) + rcExit = vbcppError(pThis, "Expected whitespace after macro name"); + + /* Parse out the value. */ + if (rcExit == RTEXITCODE_SUCCESS) + rcExit = vbcppProcessSkipWhiteEscapedEolAndComments(pThis, pStrmInput); + if (rcExit == RTEXITCODE_SUCCESS) + { + size_t offValue = ScmStreamTell(pStrmInput); + const char *pchValue = ScmStreamGetCur(pStrmInput); + unsigned chPrev = ch; + while ((ch = ScmStreamPeekCh(pStrmInput)) != ~(unsigned)0) + { + if (ch == '\r' || ch == '\n') + { + if (chPrev != '\\') + break; + ScmStreamSeekByLine(pStrmInput, ScmStreamTellLine(pStrmInput) + 1); + } + chPrev = ScmStreamGetCh(pStrmInput); + } + size_t cchValue = ScmStreamGetCur(pStrmInput) - pchValue; + + /* + * Execute. + */ + if (pchParams) + rcExit = vbcppMacroAddFn(pThis, pchDefine, cchDefine, pchParams, cchParams, pchValue, cchValue, false); + else + rcExit = vbcppMacroAdd(pThis, pchDefine, cchDefine, pchValue, cchValue, false); + + /* + * Pass thru? + */ + if ( rcExit == RTEXITCODE_SUCCESS + && pThis->fPassThruDefines) + { + unsigned cchIndent = pThis->pCondStack ? pThis->pCondStack->iKeepLevel : 0; + ssize_t cch; + if (pchParams) + cch = ScmStreamPrintf(&pThis->StrmOutput, "#%*sdefine %.*s(%.*s)", + cchIndent, "", cchDefine, pchDefine, cchParams, pchParams); + else + cch = ScmStreamPrintf(&pThis->StrmOutput, "#%*sdefine %.*s", + cchIndent, "", cchDefine, pchDefine); + if (cch > 0) + vbcppOutputComment(pThis, pStrmInput, offValue, cch, 1); + else + rcExit = vbcppError(pThis, "output error"); + } + else if ( rcExit == RTEXITCODE_SUCCESS + && pThis->enmMode == kVBCppMode_SelectiveD) + rcExit = vbcppMacroTryConvertToInlineD(pThis, vbcppMacroLookup(pThis, pchDefine, cchDefine)); + else + pThis->fJustDroppedLine = true; + } + } + } + return rcExit; +} + + +/** + * Processes a abbreviated line number directive. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pThis The C preprocessor instance. + * @param pStrmInput The input stream. + * @param offStart The stream position where the directive + * started (for pass thru). + */ +static RTEXITCODE vbcppDirectiveUndef(PVBCPP pThis, PSCMSTREAM pStrmInput, size_t offStart) +{ + RT_NOREF_PV(offStart); + + /* + * Parse it. + */ + RTEXITCODE rcExit = vbcppProcessSkipWhiteEscapedEolAndComments(pThis, pStrmInput); + if (rcExit == RTEXITCODE_SUCCESS) + { + size_t cchDefine; + const char *pchDefine = ScmStreamCGetWord(pStrmInput, &cchDefine); + if (pchDefine) + { + size_t offMaybeComment = vbcppProcessSkipWhite(pStrmInput); + rcExit = vbcppProcessSkipWhiteEscapedEolAndCommentsCheckEol(pThis, pStrmInput); + if (rcExit == RTEXITCODE_SUCCESS) + { + /* + * Take action. + */ + PVBCPPMACRO pMacro = vbcppMacroLookup(pThis, pchDefine, cchDefine); + if ( pMacro + && pThis->fRespectSourceDefines + && ( !pMacro->fCmdLine + || pThis->fAllowRedefiningCmdLineDefines ) ) + { + RTStrSpaceRemove(&pThis->StrSpace, pMacro->Core.pszString); + vbcppMacroFree(&pMacro->Core, NULL); + } + + /* + * Pass thru. + */ + if ( rcExit == RTEXITCODE_SUCCESS + && pThis->fPassThruDefines) + { + unsigned cchIndent = pThis->pCondStack ? pThis->pCondStack->iKeepLevel : 0; + ssize_t cch = ScmStreamPrintf(&pThis->StrmOutput, "#%*sundef %.*s", + cchIndent, "", cchDefine, pchDefine); + if (cch > 0) + vbcppOutputComment(pThis, pStrmInput, offMaybeComment, cch, 1); + else + rcExit = vbcppError(pThis, "output error"); + } + + } + } + else + rcExit = vbcppError(pThis, "Malformed #ifndef"); + } + return rcExit; + +} + + + + + +/* + * + * + * C O N D I T I O N A L S + * C O N D I T I O N A L S + * C O N D I T I O N A L S + * C O N D I T I O N A L S + * C O N D I T I O N A L S + * + * + */ + + +/** + * Combines current stack result with the one being pushed. + * + * @returns Combined result. + * @param enmEvalPush The result of the condition being pushed. + * @param enmEvalStack The current stack result. + */ +static VBCPPEVAL vbcppCondCombine(VBCPPEVAL enmEvalPush, VBCPPEVAL enmEvalStack) +{ + if (enmEvalStack == kVBCppEval_False) + return kVBCppEval_False; + return enmEvalPush; +} + + +/** + * Pushes an conditional onto the stack. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pThis The C preprocessor instance. + * @param pStrmInput The current input stream. + * @param offStart Not currently used, using @a pchCondition and + * @a cchCondition instead. + * @param enmKind The kind of conditional. + * @param enmResult The result of the evaluation. + * @param pchCondition The raw condition. + * @param cchCondition The length of @a pchCondition. + */ +static RTEXITCODE vbcppCondPush(PVBCPP pThis, PSCMSTREAM pStrmInput, size_t offStart, + VBCPPCONDKIND enmKind, VBCPPEVAL enmResult, + const char *pchCondition, size_t cchCondition) +{ + RT_NOREF_PV(offStart); RT_NOREF_PV(pStrmInput); + + + if (pThis->cCondStackDepth >= _64K) + return vbcppError(pThis, "Too many nested #if/#ifdef/#ifndef statements"); + + /* + * Allocate a new entry and push it. + */ + PVBCPPCOND pCond = (PVBCPPCOND)RTMemAlloc(sizeof(*pCond)); + if (!pCond) + return vbcppError(pThis, "out of memory"); + + PVBCPPCOND pUp = pThis->pCondStack; + pCond->enmKind = enmKind; + pCond->enmResult = enmResult; + pCond->enmStackResult = pUp ? vbcppCondCombine(enmResult, pUp->enmStackResult) : enmResult; + pCond->fSeenElse = false; + pCond->fElIfDecided = enmResult == kVBCppEval_True; + pCond->iLevel = pThis->cCondStackDepth; + pCond->iKeepLevel = (pUp ? pUp->iKeepLevel : 0) + enmResult == kVBCppEval_Undecided; + pCond->pchCond = pchCondition; + pCond->cchCond = cchCondition; + + pCond->pUp = pThis->pCondStack; + pThis->pCondStack = pCond; + pThis->fIf0Mode = pCond->enmStackResult == kVBCppEval_False; + + /* + * Do pass thru. + */ + if ( !pThis->fIf0Mode + && enmResult == kVBCppEval_Undecided) + { + /** @todo this is stripping comments of \#ifdef and \#ifndef atm. */ + const char *pszDirective; + switch (enmKind) + { + case kVBCppCondKind_If: pszDirective = "if"; break; + case kVBCppCondKind_IfDef: pszDirective = "ifdef"; break; + case kVBCppCondKind_IfNDef: pszDirective = "ifndef"; break; + case kVBCppCondKind_ElIf: pszDirective = "elif"; break; + default: AssertFailedReturn(RTEXITCODE_FAILURE); + } + ssize_t cch = ScmStreamPrintf(&pThis->StrmOutput, "#%*s%s %.*s", + pCond->iKeepLevel - 1, "", pszDirective, cchCondition, pchCondition); + if (cch < 0) + return vbcppError(pThis, "Output error %Rrc", (int)cch); + } + else + pThis->fJustDroppedLine = true; + + return RTEXITCODE_SUCCESS; +} + + +/** + * Recursively destroys the expression tree. + * + * @param pExpr The root of the expression tree to destroy. + */ +static void vbcppExprDestoryTree(PVBCPPEXPR pExpr) +{ + if (!pExpr) + return; + + switch (pExpr->enmKind) + { + case kVBCppExprKind_Unary: + vbcppExprDestoryTree(pExpr->u.Unary.pArg); + break; + case kVBCppExprKind_Binary: + vbcppExprDestoryTree(pExpr->u.Binary.pLeft); + vbcppExprDestoryTree(pExpr->u.Binary.pRight); + break; + case kVBCppExprKind_Ternary: + vbcppExprDestoryTree(pExpr->u.Ternary.pExpr); + vbcppExprDestoryTree(pExpr->u.Ternary.pExpr); + vbcppExprDestoryTree(pExpr->u.Ternary.pFalse); + break; + case kVBCppExprKind_SignedValue: + case kVBCppExprKind_UnsignedValue: + break; + default: + AssertFailed(); + return; + } + RTMemFree(pExpr); +} + + +/** + * Report error during expression parsing. + * + * @returns kExprRet_Error + * @param pParser The parser instance. + * @param pszMsg The error message. + * @param ... Format arguments. + */ +static VBCPPEXPRRET vbcppExprParseError(PVBCPPEXPRPARSER pParser, const char *pszMsg, ...) +{ + va_list va; + va_start(va, pszMsg); + vbcppErrorV(pParser->pThis, pszMsg, va); + va_end(va); + return kExprRet_Error; +} + + +/** + * Skip white space. + * + * @param pParser The parser instance. + */ +static void vbcppExprParseSkipWhiteSpace(PVBCPPEXPRPARSER pParser) +{ + while (RT_C_IS_SPACE(*pParser->pszCur)) + pParser->pszCur++; +} + + +/** + * Allocate a new + * + * @returns Pointer to the node. NULL+msg on failure. + * @param pParser The parser instance. + */ +static PVBCPPEXPR vbcppExprParseAllocNode(PVBCPPEXPRPARSER pParser) +{ + PVBCPPEXPR pExpr = (PVBCPPEXPR)RTMemAllocZ(sizeof(*pExpr)); + if (!pExpr) + vbcppExprParseError(pParser, "out of memory (expression node)"); + return pExpr; +} + + +/** + * Looks for right parentheses and/or end of expression. + * + * @returns Expression status. + * @retval kExprRet_Ok + * @retval kExprRet_Error with msg. + * @retval kExprRet_EndOfExpr + * @param pParser The parser instance. + */ +static VBCPPEXPRRET vbcppExprParseMaybeRParenOrEoe(PVBCPPEXPRPARSER pParser) +{ + Assert(!pParser->ppCur); + for (;;) + { + vbcppExprParseSkipWhiteSpace(pParser); + char ch = *pParser->pszCur; + if (ch == '\0') + return kExprRet_EndOfExpr; + if (ch != ')') + break; + pParser->pszCur++; + + PVBCPPEXPR pCur = pParser->pCur; + while ( pCur + && ( pCur->enmKind != kVBCppExprKind_Unary + || pCur->u.Unary.enmOperator != kVBCppUnaryOp_Parenthesis)) + { + switch (pCur->enmKind) + { + case kVBCppExprKind_SignedValue: + case kVBCppExprKind_UnsignedValue: + Assert(pCur->fComplete); + break; + case kVBCppExprKind_Unary: + AssertReturn(pCur->u.Unary.pArg, vbcppExprParseError(pParser, "internal error")); + pCur->fComplete = true; + break; + case kVBCppExprKind_Binary: + AssertReturn(pCur->u.Binary.pLeft, vbcppExprParseError(pParser, "internal error")); + AssertReturn(pCur->u.Binary.pRight, vbcppExprParseError(pParser, "internal error")); + pCur->fComplete = true; + break; + case kVBCppExprKind_Ternary: +#if 1 /** @todo Check out the ternary operator implementation. */ + return vbcppExprParseError(pParser, "The ternary operator is not implemented"); +#else + Assert(pCur->u.Ternary.pExpr); + if (!pCur->u.Ternary.pTrue) + return vbcppExprParseError(pParser, "?!?!?"); + if (!pCur->u.Ternary.pFalse) + return vbcppExprParseError(pParser, "?!?!?!?"); + pCur->fComplete = true; +#endif + break; + default: + return vbcppExprParseError(pParser, "Internal error (enmKind=%d)", pCur->enmKind); + } + pCur = pCur->pParent; + } + if (!pCur) + return vbcppExprParseError(pParser, "Right parenthesis without a left one"); + pCur->fComplete = true; + + while ( pCur->enmKind == kVBCppExprKind_Unary + && pCur->u.Unary.enmOperator != kVBCppUnaryOp_Parenthesis + && pCur->pParent) + { + AssertReturn(pCur->u.Unary.pArg, vbcppExprParseError(pParser, "internal error")); + pCur->fComplete = true; + pCur = pCur->pParent; + } + } + + return kExprRet_Ok; +} + + +/** + * Parses an binary operator. + * + * @returns Expression status. + * @retval kExprRet_Ok + * @retval kExprRet_Error with msg. + * @param pParser The parser instance. + */ +static VBCPPEXPRRET vbcppExprParseBinaryOperator(PVBCPPEXPRPARSER pParser) +{ + /* + * Binary or ternary operator should follow now. + */ + VBCPPBINARYOP enmOp; + char ch = *pParser->pszCur; + switch (ch) + { + case '*': + if (pParser->pszCur[1] == '=') + return vbcppExprParseError(pParser, "The assignment by product operator is not valid in a preprocessor expression"); + enmOp = kVBCppBinary_Multiplication; + break; + case '/': + if (pParser->pszCur[1] == '=') + return vbcppExprParseError(pParser, "The assignment by quotient operator is not valid in a preprocessor expression"); + enmOp = kVBCppBinary_Division; + break; + case '%': + if (pParser->pszCur[1] == '=') + return vbcppExprParseError(pParser, "The assignment by remainder operator is not valid in a preprocessor expression"); + enmOp = kVBCppBinary_Modulo; + break; + case '+': + if (pParser->pszCur[1] == '=') + return vbcppExprParseError(pParser, "The assignment by sum operator is not valid in a preprocessor expression"); + enmOp = kVBCppBinary_Addition; + break; + case '-': + if (pParser->pszCur[1] == '=') + return vbcppExprParseError(pParser, "The assignment by difference operator is not valid in a preprocessor expression"); + enmOp = kVBCppBinary_Subtraction; + break; + case '<': + enmOp = kVBCppBinary_LessThan; + if (pParser->pszCur[1] == '=') + { + pParser->pszCur++; + enmOp = kVBCppBinary_LessThanOrEqual; + } + else if (pParser->pszCur[1] == '<') + { + pParser->pszCur++; + if (pParser->pszCur[1] == '=') + return vbcppExprParseError(pParser, "The assignment by bitwise left shift operator is not valid in a preprocessor expression"); + enmOp = kVBCppBinary_LeftShift; + } + break; + case '>': + enmOp = kVBCppBinary_GreaterThan; + if (pParser->pszCur[1] == '=') + { + pParser->pszCur++; + enmOp = kVBCppBinary_GreaterThanOrEqual; + } + else if (pParser->pszCur[1] == '<') + { + pParser->pszCur++; + if (pParser->pszCur[1] == '=') + return vbcppExprParseError(pParser, "The assignment by bitwise right shift operator is not valid in a preprocessor expression"); + enmOp = kVBCppBinary_LeftShift; + } + break; + case '=': + if (pParser->pszCur[1] != '=') + return vbcppExprParseError(pParser, "The assignment operator is not valid in a preprocessor expression"); + pParser->pszCur++; + enmOp = kVBCppBinary_EqualTo; + break; + + case '!': + if (pParser->pszCur[1] != '=') + return vbcppExprParseError(pParser, "Expected binary operator, found the unary operator logical NOT"); + pParser->pszCur++; + enmOp = kVBCppBinary_NotEqualTo; + break; + + case '&': + if (pParser->pszCur[1] == '=') + return vbcppExprParseError(pParser, "The assignment by bitwise AND operator is not valid in a preprocessor expression"); + if (pParser->pszCur[1] == '&') + { + pParser->pszCur++; + enmOp = kVBCppBinary_LogicalAnd; + } + else + enmOp = kVBCppBinary_BitwiseAnd; + break; + case '^': + if (pParser->pszCur[1] == '=') + return vbcppExprParseError(pParser, "The assignment by bitwise XOR operator is not valid in a preprocessor expression"); + enmOp = kVBCppBinary_BitwiseXor; + break; + case '|': + if (pParser->pszCur[1] == '=') + return vbcppExprParseError(pParser, "The assignment by bitwise AND operator is not valid in a preprocessor expression"); + if (pParser->pszCur[1] == '|') + { + pParser->pszCur++; + enmOp = kVBCppBinary_LogicalOr; + } + else + enmOp = kVBCppBinary_BitwiseOr; + break; + case '~': + return vbcppExprParseError(pParser, "Expected binary operator, found the unary operator bitwise NOT"); + + case ':': + case '?': + return vbcppExprParseError(pParser, "The ternary operator is not yet implemented"); + + default: + return vbcppExprParseError(pParser, "Expected binary operator, found '%.20s'", pParser->pszCur); + } + pParser->pszCur++; + + /* + * Create a binary operator node. + */ + PVBCPPEXPR pExpr = vbcppExprParseAllocNode(pParser); + if (!pExpr) + return kExprRet_Error; + pExpr->fComplete = true; + pExpr->enmKind = kVBCppExprKind_Binary; + pExpr->u.Binary.enmOperator = enmOp; + pExpr->u.Binary.pLeft = NULL; + pExpr->u.Binary.pRight = NULL; + + /* + * Back up the tree until we find our spot. + */ + PVBCPPEXPR *ppPlace = NULL; + PVBCPPEXPR pChild = NULL; + PVBCPPEXPR pParent = pParser->pCur; + while (pParent) + { + if (pParent->enmKind == kVBCppExprKind_Unary) + { + if (pParent->u.Unary.enmOperator == kVBCppUnaryOp_Parenthesis) + { + ppPlace = &pParent->u.Unary.pArg; + break; + } + AssertReturn(pParent->u.Unary.pArg, vbcppExprParseError(pParser, "internal error")); + pParent->fComplete = true; + } + else if (pParent->enmKind == kVBCppExprKind_Binary) + { + AssertReturn(pParent->u.Binary.pLeft, vbcppExprParseError(pParser, "internal error")); + AssertReturn(pParent->u.Binary.pRight, vbcppExprParseError(pParser, "internal error")); + if ((pParent->u.Binary.enmOperator & VBCPPOP_PRECEDENCE_MASK) >= (enmOp & VBCPPOP_PRECEDENCE_MASK)) + { + AssertReturn(pChild, vbcppExprParseError(pParser, "internal error")); + + if (pParent->u.Binary.pRight == pChild) + ppPlace = &pParent->u.Binary.pRight; + else + ppPlace = &pParent->u.Binary.pLeft; + AssertReturn(*ppPlace == pChild, vbcppExprParseError(pParser, "internal error")); + break; + } + pParent->fComplete = true; + } + else if (pParent->enmKind == kVBCppExprKind_Ternary) + { + return vbcppExprParseError(pParser, "The ternary operator is not implemented"); + } + else + AssertReturn( pParent->enmKind == kVBCppExprKind_SignedValue + || pParent->enmKind == kVBCppExprKind_UnsignedValue, + vbcppExprParseError(pParser, "internal error")); + + /* Up on level */ + pChild = pParent; + pParent = pParent->pParent; + } + + /* + * Do the rotation. + */ + Assert(pChild); + Assert(pChild->pParent == pParent); + pChild->pParent = pExpr; + + pExpr->u.Binary.pLeft = pChild; + pExpr->pParent = pParent; + + if (!pParent) + pParser->pRoot = pExpr; + else + *ppPlace = pExpr; + + pParser->ppCur = &pExpr->u.Binary.pRight; + pParser->pCur = pExpr; + + return kExprRet_Ok; +} + + +/** + * Deals with right paretheses or/and end of expression, looks for binary + * operators. + * + * @returns Expression status. + * @retval kExprRet_Ok if binary operator was found processed. + * @retval kExprRet_Error with msg. + * @retval kExprRet_EndOfExpr + * @param pParser The parser instance. + */ +static VBCPPEXPRRET vbcppExprParseBinaryOrEoeOrRparen(PVBCPPEXPRPARSER pParser) +{ + VBCPPEXPRRET enmRet = vbcppExprParseMaybeRParenOrEoe(pParser); + if (enmRet != kExprRet_Ok) + return enmRet; + return vbcppExprParseBinaryOperator(pParser); +} + + +/** + * Parses an identifier in the expression, replacing it by 0. + * + * All known identifiers has already been replaced by their macro values, so + * what's left are unknown macros. These are replaced by 0. + * + * @returns Expression status. + * @retval kExprRet_Value + * @retval kExprRet_Error with msg. + * @param pParser The parser instance. + */ +static VBCPPEXPRRET vbcppExprParseIdentifier(PVBCPPEXPRPARSER pParser) +{ +/** @todo don't increment if it's an actively undefined macro. Need to revise + * the expression related code wrt selective preprocessing. */ + pParser->cUndefined++; + + /* Find the end. */ + const char *pszMacro = pParser->pszCur; + const char *pszNext = pszMacro + 1; + while (vbcppIsCIdentifierChar(*pszNext)) + pszNext++; + size_t cchMacro = pszNext - pszMacro; + + /* Create a signed value node. */ + PVBCPPEXPR pExpr = vbcppExprParseAllocNode(pParser); + if (!pExpr) + return kExprRet_Error; + pExpr->fComplete = true; + pExpr->enmKind = kVBCppExprKind_UnsignedValue; + pExpr->u.UnsignedValue.u64 = 0; + + /* Link it. */ + pExpr->pParent = pParser->pCur; + pParser->pCur = pExpr; + *pParser->ppCur = pExpr; + pParser->ppCur = NULL; + + /* Skip spaces and check for parenthesis. */ + pParser->pszCur = pszNext; + vbcppExprParseSkipWhiteSpace(pParser); + if (*pParser->pszCur == '(') + return vbcppExprParseError(pParser, "Unknown unary operator '%.*s'", cchMacro, pszMacro); + + return kExprRet_Value; +} + + +/** + * Parses an numeric constant in the expression. + * + * @returns Expression status. + * @retval kExprRet_Value + * @retval kExprRet_Error with msg. + * @param pParser The parser instance. + */ +static VBCPPEXPRRET vbcppExprParseNumber(PVBCPPEXPRPARSER pParser) +{ + bool fSigned; + char *pszNext; + uint64_t u64; + char ch = *pParser->pszCur++; + char ch2 = *pParser->pszCur; + if ( ch == '0' + && (ch == 'x' || ch == 'X')) + { + ch2 = *++pParser->pszCur; + if (!RT_C_IS_XDIGIT(ch2)) + return vbcppExprParseError(pParser, "Expected hex digit following '0x'"); + int rc = RTStrToUInt64Ex(pParser->pszCur, &pszNext, 16, &u64); + if ( RT_FAILURE(rc) + || rc == VWRN_NUMBER_TOO_BIG) + return vbcppExprParseError(pParser, "Invalid hex value '%.20s...' (%Rrc)", pParser->pszCur, rc); + fSigned = false; + } + else if (ch == '0') + { + int rc = RTStrToUInt64Ex(pParser->pszCur - 1, &pszNext, 8, &u64); + if ( RT_FAILURE(rc) + || rc == VWRN_NUMBER_TOO_BIG) + return vbcppExprParseError(pParser, "Invalid octal value '%.20s...' (%Rrc)", pParser->pszCur, rc); + fSigned = u64 > (uint64_t)INT64_MAX ? false : true; + } + else + { + int rc = RTStrToUInt64Ex(pParser->pszCur - 1, &pszNext, 10, &u64); + if ( RT_FAILURE(rc) + || rc == VWRN_NUMBER_TOO_BIG) + return vbcppExprParseError(pParser, "Invalid decimal value '%.20s...' (%Rrc)", pParser->pszCur, rc); + fSigned = u64 > (uint64_t)INT64_MAX ? false : true; + } + + /* suffix. */ + if (vbcppIsCIdentifierLeadChar(*pszNext)) + { + size_t cchSuffix = 1; + while (vbcppIsCIdentifierLeadChar(pszNext[cchSuffix])) + cchSuffix++; + + if (cchSuffix == '1' && (*pszNext == 'u' || *pszNext == 'U')) + fSigned = false; + else if ( cchSuffix == '1' + && (*pszNext == 'l' || *pszNext == 'L')) + fSigned = true; + else if ( cchSuffix == '2' + && (!strncmp(pszNext, "ul", 2) || !strncmp(pszNext, "UL", 2))) + fSigned = false; + else if ( cchSuffix == '2' + && (!strncmp(pszNext, "ll", 2) || !strncmp(pszNext, "LL", 2))) + fSigned = true; + else if ( cchSuffix == '3' + && (!strncmp(pszNext, "ull", 3) || !strncmp(pszNext, "ULL", 3))) + fSigned = false; + else + return vbcppExprParseError(pParser, "Invalid number suffix '%.*s'", cchSuffix, pszNext); + + pszNext += cchSuffix; + } + pParser->pszCur = pszNext; + + /* Create a signed value node. */ + PVBCPPEXPR pExpr = vbcppExprParseAllocNode(pParser); + if (!pExpr) + return kExprRet_Error; + pExpr->fComplete = true; + if (fSigned) + { + pExpr->enmKind = kVBCppExprKind_SignedValue; + pExpr->u.SignedValue.s64 = (int64_t)u64; + } + else + { + pExpr->enmKind = kVBCppExprKind_UnsignedValue; + pExpr->u.UnsignedValue.u64 = u64; + } + + /* Link it. */ + pExpr->pParent = pParser->pCur; + pParser->pCur = pExpr; + *pParser->ppCur = pExpr; + pParser->ppCur = NULL; + + return kExprRet_Value; +} + + +/** + * Parses an character constant in the expression. + * + * @returns Expression status. + * @retval kExprRet_Value + * @retval kExprRet_Error with msg. + * @param pParser The parser instance. + */ +static VBCPPEXPRRET vbcppExprParseCharacterConstant(PVBCPPEXPRPARSER pParser) +{ + Assert(*pParser->pszCur == '\''); + pParser->pszCur++; + char ch2 = *pParser->pszCur++; + if (ch2 == '\'') + return vbcppExprParseError(pParser, "Empty character constant"); + int64_t s64; + if (ch2 == '\\') + { + ch2 = *pParser->pszCur++; + switch (ch2) + { + case '0': s64 = 0x00; break; + case 'n': s64 = 0x0d; break; + case 'r': s64 = 0x0a; break; + case 't': s64 = 0x09; break; + default: + return vbcppExprParseError(pParser, "Escape character '%c' is not implemented", ch2); + } + } + else + s64 = ch2; + if (*pParser->pszCur != '\'') + return vbcppExprParseError(pParser, "Character constant contains more than one character"); + + /* Create a signed value node. */ + PVBCPPEXPR pExpr = vbcppExprParseAllocNode(pParser); + if (!pExpr) + return kExprRet_Error; + pExpr->fComplete = true; + pExpr->enmKind = kVBCppExprKind_SignedValue; + pExpr->u.SignedValue.s64 = s64; + + /* Link it. */ + pExpr->pParent = pParser->pCur; + pParser->pCur = pExpr; + *pParser->ppCur = pExpr; + pParser->ppCur = NULL; + + return kExprRet_Value; +} + + +/** + * Parses a unary operator or a value. + * + * @returns Expression status. + * @retval kExprRet_Value if value was found and processed. + * @retval kExprRet_UnaryOperator if an unary operator was found and processed. + * @retval kExprRet_Error with msg. + * @param pParser The parser instance. + */ +static VBCPPEXPRRET vbcppExprParseUnaryOrValue(PVBCPPEXPRPARSER pParser) +{ + vbcppExprParseSkipWhiteSpace(pParser); + char ch = *pParser->pszCur; + if (ch == '\0') + return vbcppExprParseError(pParser, "Premature end of expression"); + + /* + * Value? + */ + if (ch == '\'') + return vbcppExprParseCharacterConstant(pParser); + if (RT_C_IS_DIGIT(ch)) + return vbcppExprParseNumber(pParser); + if (ch == '"') + return vbcppExprParseError(pParser, "String litteral"); + if (vbcppIsCIdentifierLeadChar(ch)) + return vbcppExprParseIdentifier(pParser); + + /* + * Operator? + */ + VBCPPUNARYOP enmOperator; + if (ch == '+') + { + enmOperator = kVBCppUnaryOp_Pluss; + if (pParser->pszCur[1] == '+') + return vbcppExprParseError(pParser, "The prefix increment operator is not valid in a preprocessor expression"); + } + else if (ch == '-') + { + enmOperator = kVBCppUnaryOp_Minus; + if (pParser->pszCur[1] == '-') + return vbcppExprParseError(pParser, "The prefix decrement operator is not valid in a preprocessor expression"); + } + else if (ch == '!') + enmOperator = kVBCppUnaryOp_LogicalNot; + else if (ch == '~') + enmOperator = kVBCppUnaryOp_BitwiseNot; + else if (ch == '(') + enmOperator = kVBCppUnaryOp_Parenthesis; + else + return vbcppExprParseError(pParser, "Unknown token '%.*s'", 32, pParser->pszCur - 1); + pParser->pszCur++; + + /* Create an operator node. */ + PVBCPPEXPR pExpr = vbcppExprParseAllocNode(pParser); + if (!pExpr) + return kExprRet_Error; + pExpr->fComplete = false; + pExpr->enmKind = kVBCppExprKind_Unary; + pExpr->u.Unary.enmOperator = enmOperator; + pExpr->u.Unary.pArg = NULL; + + /* Link it into the tree. */ + pExpr->pParent = pParser->pCur; + pParser->pCur = pExpr; + *pParser->ppCur = pExpr; + pParser->ppCur = &pExpr->u.Unary.pArg; + + return kExprRet_UnaryOperator; +} + + +/** + * Parses an expanded preprocessor expression. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pThis The C preprocessor instance. + * @param pszExpr The expression to parse. + * @param cchExpr The length of the expression in case we need it. + * @param ppExprTree Where to return the parse tree. + * @param pcUndefined Where to return the number of unknown undefined + * macros. Optional. + */ +static RTEXITCODE vbcppExprParse(PVBCPP pThis, char *pszExpr, size_t cchExpr, PVBCPPEXPR *ppExprTree, size_t *pcUndefined) +{ + RTEXITCODE rcExit = RTEXITCODE_FAILURE; + NOREF(cchExpr); + + /* + * Initialize the parser context structure. + */ + VBCPPEXPRPARSER Parser; + Parser.pszCur = pszExpr; + Parser.pRoot = NULL; + Parser.pCur = NULL; + Parser.ppCur = &Parser.pRoot; + Parser.pszExpr = pszExpr; + Parser.cUndefined = 0; + Parser.pThis = pThis; + + /* + * Do the parsing. + */ + VBCPPEXPRRET enmRet; + for (;;) + { + /* + * Eat unary operators until we hit a value. + */ + do + enmRet = vbcppExprParseUnaryOrValue(&Parser); + while (enmRet == kExprRet_UnaryOperator); + if (enmRet == kExprRet_Error) + break; + AssertBreakStmt(enmRet == kExprRet_Value, enmRet = vbcppExprParseError(&Parser, "Expected value (enmRet=%d)", enmRet)); + + /* + * Non-unary operator, right parenthesis or end of expression is up next. + */ + enmRet = vbcppExprParseBinaryOrEoeOrRparen(&Parser); + if (enmRet == kExprRet_Error) + break; + if (enmRet == kExprRet_EndOfExpr) + { + /** @todo check if there are any open parentheses. */ + rcExit = RTEXITCODE_SUCCESS; + break; + } + AssertBreakStmt(enmRet == kExprRet_Ok, enmRet = vbcppExprParseError(&Parser, "Expected value (enmRet=%d)", enmRet)); + } + + if (rcExit != RTEXITCODE_SUCCESS) + { + vbcppExprDestoryTree(Parser.pRoot); + return rcExit; + } + + if (pcUndefined) + *pcUndefined = Parser.cUndefined; + *ppExprTree = Parser.pRoot; + return rcExit; +} + + +/** + * Checks if an expression value value is evaluates to @c true or @c false. + * + * @returns @c true or @c false. + * @param pExpr The value expression. + */ +static bool vbcppExprIsExprTrue(PVBCPPEXPR pExpr) +{ + Assert(pExpr->enmKind == kVBCppExprKind_SignedValue || pExpr->enmKind == kVBCppExprKind_UnsignedValue); + + return pExpr->enmKind == kVBCppExprKind_SignedValue + ? pExpr->u.SignedValue.s64 != 0 + : pExpr->u.UnsignedValue.u64 != 0; +} + + +/** + * Evalutes a parse (sub-)tree. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pThis The C preprocessor instance. + * @param pRoot The root of the parse (sub-)tree. + * @param pResult Where to store the result value. + */ +static RTEXITCODE vbcppExprEvaluteTree(PVBCPP pThis, PVBCPPEXPR pRoot, PVBCPPEXPR pResult) +{ + RTEXITCODE rcExit; + switch (pRoot->enmKind) + { + case kVBCppExprKind_SignedValue: + pResult->enmKind = kVBCppExprKind_SignedValue; + pResult->u.SignedValue.s64 = pRoot->u.SignedValue.s64; + return RTEXITCODE_SUCCESS; + + case kVBCppExprKind_UnsignedValue: + pResult->enmKind = kVBCppExprKind_UnsignedValue; + pResult->u.UnsignedValue.u64 = pRoot->u.UnsignedValue.u64; + return RTEXITCODE_SUCCESS; + + case kVBCppExprKind_Unary: + rcExit = vbcppExprEvaluteTree(pThis, pRoot->u.Unary.pArg, pResult); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + + /* Apply the unary operator to the value */ + switch (pRoot->u.Unary.enmOperator) + { + case kVBCppUnaryOp_Minus: + if (pResult->enmKind == kVBCppExprKind_SignedValue) + pResult->u.SignedValue.s64 = -pResult->u.SignedValue.s64; + else + pResult->u.UnsignedValue.u64 = (uint64_t)-(int64_t)pResult->u.UnsignedValue.u64; + break; + + case kVBCppUnaryOp_LogicalNot: + if (pResult->enmKind == kVBCppExprKind_SignedValue) + pResult->u.SignedValue.s64 = !pResult->u.SignedValue.s64; + else + pResult->u.UnsignedValue.u64 = !pResult->u.UnsignedValue.u64; + break; + + case kVBCppUnaryOp_BitwiseNot: + if (pResult->enmKind == kVBCppExprKind_SignedValue) + pResult->u.SignedValue.s64 = ~pResult->u.SignedValue.s64; + else + pResult->u.UnsignedValue.u64 = ~pResult->u.UnsignedValue.u64; + break; + + case kVBCppUnaryOp_Pluss: + case kVBCppUnaryOp_Parenthesis: + /* do nothing. */ + break; + + default: + return vbcppError(pThis, "Internal error: u.Unary.enmOperator=%d", pRoot->u.Unary.enmOperator); + } + return RTEXITCODE_SUCCESS; + + case kVBCppExprKind_Binary: + { + /* Always evalute the left side. */ + rcExit = vbcppExprEvaluteTree(pThis, pRoot->u.Binary.pLeft, pResult); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + + /* If logical AND or OR we can sometimes skip evaluting the right side. */ + if ( pRoot->u.Binary.enmOperator == kVBCppBinary_LogicalAnd + && !vbcppExprIsExprTrue(pResult)) + return RTEXITCODE_SUCCESS; + + if ( pRoot->u.Binary.enmOperator == kVBCppBinary_LogicalOr + && vbcppExprIsExprTrue(pResult)) + return RTEXITCODE_SUCCESS; + + /* Evalute the right side. */ + VBCPPEXPR Result2; + rcExit = vbcppExprEvaluteTree(pThis, pRoot->u.Binary.pRight, &Result2); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + + /* If one of them is unsigned, promote the other to unsigned as well. */ + if ( pResult->enmKind == kVBCppExprKind_UnsignedValue + && Result2.enmKind == kVBCppExprKind_SignedValue) + { + Result2.enmKind = kVBCppExprKind_UnsignedValue; + Result2.u.UnsignedValue.u64 = Result2.u.SignedValue.s64; + } + else if ( pResult->enmKind == kVBCppExprKind_SignedValue + && Result2.enmKind == kVBCppExprKind_UnsignedValue) + { + pResult->enmKind = kVBCppExprKind_UnsignedValue; + pResult->u.UnsignedValue.u64 = pResult->u.SignedValue.s64; + } + + /* Perform the operation. */ + if (pResult->enmKind == kVBCppExprKind_UnsignedValue) + { + switch (pRoot->u.Binary.enmOperator) + { + case kVBCppBinary_Multiplication: + pResult->u.UnsignedValue.u64 *= Result2.u.UnsignedValue.u64; + break; + case kVBCppBinary_Division: + if (!Result2.u.UnsignedValue.u64) + return vbcppError(pThis, "Divide by zero"); + pResult->u.UnsignedValue.u64 /= Result2.u.UnsignedValue.u64; + break; + case kVBCppBinary_Modulo: + if (!Result2.u.UnsignedValue.u64) + return vbcppError(pThis, "Divide by zero"); + pResult->u.UnsignedValue.u64 %= Result2.u.UnsignedValue.u64; + break; + case kVBCppBinary_Addition: + pResult->u.UnsignedValue.u64 += Result2.u.UnsignedValue.u64; + break; + case kVBCppBinary_Subtraction: + pResult->u.UnsignedValue.u64 -= Result2.u.UnsignedValue.u64; + break; + case kVBCppBinary_LeftShift: + pResult->u.UnsignedValue.u64 <<= Result2.u.UnsignedValue.u64; + break; + case kVBCppBinary_RightShift: + pResult->u.UnsignedValue.u64 >>= Result2.u.UnsignedValue.u64; + break; + case kVBCppBinary_LessThan: + pResult->u.UnsignedValue.u64 = pResult->u.UnsignedValue.u64 < Result2.u.UnsignedValue.u64; + break; + case kVBCppBinary_LessThanOrEqual: + pResult->u.UnsignedValue.u64 = pResult->u.UnsignedValue.u64 <= Result2.u.UnsignedValue.u64; + break; + case kVBCppBinary_GreaterThan: + pResult->u.UnsignedValue.u64 = pResult->u.UnsignedValue.u64 > Result2.u.UnsignedValue.u64; + break; + case kVBCppBinary_GreaterThanOrEqual: + pResult->u.UnsignedValue.u64 = pResult->u.UnsignedValue.u64 >= Result2.u.UnsignedValue.u64; + break; + case kVBCppBinary_EqualTo: + pResult->u.UnsignedValue.u64 = pResult->u.UnsignedValue.u64 == Result2.u.UnsignedValue.u64; + break; + case kVBCppBinary_NotEqualTo: + pResult->u.UnsignedValue.u64 = pResult->u.UnsignedValue.u64 != Result2.u.UnsignedValue.u64; + break; + case kVBCppBinary_BitwiseAnd: + pResult->u.UnsignedValue.u64 &= Result2.u.UnsignedValue.u64; + break; + case kVBCppBinary_BitwiseXor: + pResult->u.UnsignedValue.u64 ^= Result2.u.UnsignedValue.u64; + break; + case kVBCppBinary_BitwiseOr: + pResult->u.UnsignedValue.u64 |= Result2.u.UnsignedValue.u64; + break; + case kVBCppBinary_LogicalAnd: + pResult->u.UnsignedValue.u64 = pResult->u.UnsignedValue.u64 && Result2.u.UnsignedValue.u64; + break; + case kVBCppBinary_LogicalOr: + pResult->u.UnsignedValue.u64 = pResult->u.UnsignedValue.u64 || Result2.u.UnsignedValue.u64; + break; + default: + return vbcppError(pThis, "Internal error: u.Binary.enmOperator=%d", pRoot->u.Binary.enmOperator); + } + } + else + { + switch (pRoot->u.Binary.enmOperator) + { + case kVBCppBinary_Multiplication: + pResult->u.SignedValue.s64 *= Result2.u.SignedValue.s64; + break; + case kVBCppBinary_Division: + if (!Result2.u.SignedValue.s64) + return vbcppError(pThis, "Divide by zero"); + pResult->u.SignedValue.s64 /= Result2.u.SignedValue.s64; + break; + case kVBCppBinary_Modulo: + if (!Result2.u.SignedValue.s64) + return vbcppError(pThis, "Divide by zero"); + pResult->u.SignedValue.s64 %= Result2.u.SignedValue.s64; + break; + case kVBCppBinary_Addition: + pResult->u.SignedValue.s64 += Result2.u.SignedValue.s64; + break; + case kVBCppBinary_Subtraction: + pResult->u.SignedValue.s64 -= Result2.u.SignedValue.s64; + break; + case kVBCppBinary_LeftShift: + pResult->u.SignedValue.s64 <<= Result2.u.SignedValue.s64; + break; + case kVBCppBinary_RightShift: + pResult->u.SignedValue.s64 >>= Result2.u.SignedValue.s64; + break; + case kVBCppBinary_LessThan: + pResult->u.SignedValue.s64 = pResult->u.SignedValue.s64 < Result2.u.SignedValue.s64; + break; + case kVBCppBinary_LessThanOrEqual: + pResult->u.SignedValue.s64 = pResult->u.SignedValue.s64 <= Result2.u.SignedValue.s64; + break; + case kVBCppBinary_GreaterThan: + pResult->u.SignedValue.s64 = pResult->u.SignedValue.s64 > Result2.u.SignedValue.s64; + break; + case kVBCppBinary_GreaterThanOrEqual: + pResult->u.SignedValue.s64 = pResult->u.SignedValue.s64 >= Result2.u.SignedValue.s64; + break; + case kVBCppBinary_EqualTo: + pResult->u.SignedValue.s64 = pResult->u.SignedValue.s64 == Result2.u.SignedValue.s64; + break; + case kVBCppBinary_NotEqualTo: + pResult->u.SignedValue.s64 = pResult->u.SignedValue.s64 != Result2.u.SignedValue.s64; + break; + case kVBCppBinary_BitwiseAnd: + pResult->u.SignedValue.s64 &= Result2.u.SignedValue.s64; + break; + case kVBCppBinary_BitwiseXor: + pResult->u.SignedValue.s64 ^= Result2.u.SignedValue.s64; + break; + case kVBCppBinary_BitwiseOr: + pResult->u.SignedValue.s64 |= Result2.u.SignedValue.s64; + break; + case kVBCppBinary_LogicalAnd: + pResult->u.SignedValue.s64 = pResult->u.SignedValue.s64 && Result2.u.SignedValue.s64; + break; + case kVBCppBinary_LogicalOr: + pResult->u.SignedValue.s64 = pResult->u.SignedValue.s64 || Result2.u.SignedValue.s64; + break; + default: + return vbcppError(pThis, "Internal error: u.Binary.enmOperator=%d", pRoot->u.Binary.enmOperator); + } + } + return rcExit; + } + + case kVBCppExprKind_Ternary: + rcExit = vbcppExprEvaluteTree(pThis, pRoot->u.Ternary.pExpr, pResult); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + if (vbcppExprIsExprTrue(pResult)) + return vbcppExprEvaluteTree(pThis, pRoot->u.Ternary.pTrue, pResult); + return vbcppExprEvaluteTree(pThis, pRoot->u.Ternary.pFalse, pResult); + + default: + return vbcppError(pThis, "Internal error: enmKind=%d", pRoot->enmKind); + } +} + + +/** + * Evalutes the expression. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pThis The C preprocessor instance. + * @param pszExpr The expression. + * @param cchExpr The length of the expression. + * @param penmResult Where to store the result. + */ +static RTEXITCODE vbcppExprEval(PVBCPP pThis, char *pszExpr, size_t cchExpr, size_t cReplacements, VBCPPEVAL *penmResult) +{ + Assert(strlen(pszExpr) == cchExpr); + RT_NOREF_PV(cReplacements); + + size_t cUndefined; + PVBCPPEXPR pExprTree; + RTEXITCODE rcExit = vbcppExprParse(pThis, pszExpr, cchExpr, &pExprTree, &cUndefined); + if (rcExit == RTEXITCODE_SUCCESS) + { + if ( !cUndefined + || pThis->enmMode == kVBCppMode_SelectiveD + || pThis->enmMode == kVBCppMode_Standard) + { + VBCPPEXPR Result; + rcExit = vbcppExprEvaluteTree(pThis, pExprTree, &Result); + if (rcExit == RTEXITCODE_SUCCESS) + { + if (vbcppExprIsExprTrue(&Result)) + *penmResult = kVBCppEval_True; + else + *penmResult = kVBCppEval_False; + } + } + else + *penmResult = kVBCppEval_Undecided; + } + return rcExit; +} + + +static RTEXITCODE vbcppExtractSkipCommentLine(PVBCPP pThis, PSCMSTREAM pStrmInput) +{ + RT_NOREF_PV(pThis); + + unsigned chPrev = ScmStreamGetCh(pStrmInput); Assert(chPrev == '/'); + unsigned ch; + while ((ch = ScmStreamPeekCh(pStrmInput)) != ~(unsigned)0) + { + if (ch == '\r' || ch == '\n') + { + if (chPrev != '\\') + break; + ScmStreamSeekByLine(pStrmInput, ScmStreamTellLine(pStrmInput) + 1); + chPrev = ch; + } + else + { + chPrev = ScmStreamGetCh(pStrmInput); + Assert(chPrev == ch); + } + } + return RTEXITCODE_SUCCESS; +} + + +static RTEXITCODE vbcppExtractSkipComment(PVBCPP pThis, PSCMSTREAM pStrmInput) +{ + unsigned ch = ScmStreamGetCh(pStrmInput); Assert(ch == '*'); + while ((ch = ScmStreamGetCh(pStrmInput)) != ~(unsigned)0) + { + if (ch == '*') + { + ch = ScmStreamGetCh(pStrmInput); + if (ch == '/') + return RTEXITCODE_SUCCESS; + } + } + return vbcppError(pThis, "Expected '*/'"); +} + + +static RTEXITCODE vbcppExtractQuotedString(PVBCPP pThis, PSCMSTREAM pStrmInput, PVBCPPSTRBUF pStrBuf, + char chOpen, char chClose) +{ + unsigned ch = ScmStreamGetCh(pStrmInput); + Assert(ch == (unsigned)chOpen); + + RTEXITCODE rcExit = vbcppStrBufAppendCh(pStrBuf, chOpen); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + + for (;;) + { + ch = ScmStreamGetCh(pStrmInput); + if (ch == '\\') + { + ch = ScmStreamGetCh(pStrmInput); + if (ch == ~(unsigned)0) + break; + rcExit = vbcppStrBufAppendCh(pStrBuf, '\\'); + if (rcExit == RTEXITCODE_SUCCESS) + rcExit = vbcppStrBufAppendCh(pStrBuf, ch); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + } + else if (ch != ~(unsigned)0) + { + rcExit = vbcppStrBufAppendCh(pStrBuf, ch); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + if (ch == (unsigned)chClose) + return RTEXITCODE_SUCCESS; + } + else + break; + } + + return vbcppError(pThis, "File ended with an open character constant"); +} + + +/** + * Extracts a line from the stream, stripping it for comments and maybe + * optimzing some of the whitespace. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pThis The C preprocessor instance. + * @param pStrmInput The input stream. + * @param pStrBuf Where to store the extracted line. Caller must + * initialize this prior to the call an delete it + * after use (even on failure). + * @param poffComment Where to note down the position of the final + * comment. Optional. + */ +static RTEXITCODE vbcppExtractDirectiveLine(PVBCPP pThis, PSCMSTREAM pStrmInput, PVBCPPSTRBUF pStrBuf, size_t *poffComment) +{ + size_t offComment = ~(size_t)0; + unsigned ch; + while ((ch = ScmStreamPeekCh(pStrmInput)) != ~(unsigned)0) + { + RTEXITCODE rcExit; + if (ch == '/') + { + /* Comment? */ + unsigned ch2 = ScmStreamGetCh(pStrmInput); Assert(ch == ch2); NOREF(ch2); + ch = ScmStreamPeekCh(pStrmInput); + if (ch == '*') + { + offComment = ScmStreamTell(pStrmInput) - 1; + rcExit = vbcppExtractSkipComment(pThis, pStrmInput); + } + else if (ch == '/') + { + offComment = ScmStreamTell(pStrmInput) - 1; + rcExit = vbcppExtractSkipCommentLine(pThis, pStrmInput); + } + else + rcExit = vbcppStrBufAppendCh(pStrBuf, '/'); + } + else if (ch == '\'') + { + offComment = ~(size_t)0; + rcExit = vbcppExtractQuotedString(pThis, pStrmInput, pStrBuf, '\'', '\''); + } + else if (ch == '"') + { + offComment = ~(size_t)0; + rcExit = vbcppExtractQuotedString(pThis, pStrmInput, pStrBuf, '"', '"'); + } + else if (ch == '\r' || ch == '\n') + break; /* done */ + else if ( RT_C_IS_SPACE(ch) + && ( RT_C_IS_SPACE(vbcppStrBufLastCh(pStrBuf)) + || vbcppStrBufLastCh(pStrBuf) == '\0') ) + { + unsigned ch2 = ScmStreamGetCh(pStrmInput); + Assert(ch == ch2); NOREF(ch2); + rcExit = RTEXITCODE_SUCCESS; + } + else + { + unsigned ch2 = ScmStreamGetCh(pStrmInput); Assert(ch == ch2); + + /* Escaped newline? */ + if ( ch == '\\' + && ( (ch2 = ScmStreamPeekCh(pStrmInput)) == '\r' + || ch2 == '\n')) + { + ScmStreamSeekByLine(pStrmInput, ScmStreamTellLine(pStrmInput) + 1); + rcExit = RTEXITCODE_SUCCESS; + } + else + { + offComment = ~(size_t)0; + rcExit = vbcppStrBufAppendCh(pStrBuf, ch); + } + } + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + } + + if (poffComment) + *poffComment = offComment; + return RTEXITCODE_SUCCESS; +} + + +/** + * Processes a abbreviated line number directive. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pThis The C preprocessor instance. + * @param pStrmInput The input stream. + * @param offStart The stream position where the directive + * started (for pass thru). + * @param enmKind The kind of directive we're processing. + */ +static RTEXITCODE vbcppDirectiveIfOrElif(PVBCPP pThis, PSCMSTREAM pStrmInput, size_t offStart, + VBCPPCONDKIND enmKind) +{ + /* + * Check for missing #if if #elif. + */ + if ( enmKind == kVBCppCondKind_ElIf + && !pThis->pCondStack ) + return vbcppError(pThis, "#elif without #if"); + + /* + * Extract the expression string. + */ + const char *pchCondition = ScmStreamGetCur(pStrmInput); + size_t offComment; + VBCPPMACROEXP ExpCtx; +#if 0 + ExpCtx.pMacroStack = NULL; +#endif + ExpCtx.pStrmInput = NULL; + ExpCtx.papszArgs = NULL; + ExpCtx.cArgs = 0; + ExpCtx.cArgsAlloced = 0; + vbcppStrBufInit(&ExpCtx.StrBuf, pThis); + RTEXITCODE rcExit = vbcppExtractDirectiveLine(pThis, pStrmInput, &ExpCtx.StrBuf, &offComment); + if (rcExit == RTEXITCODE_SUCCESS) + { + size_t const cchCondition = ScmStreamGetCur(pStrmInput) - pchCondition; + + /* + * Expand known macros in it. + */ + size_t cReplacements; + rcExit = vbcppMacroExpandReScan(pThis, &ExpCtx, kMacroReScanMode_Expression, &cReplacements); + if (rcExit == RTEXITCODE_SUCCESS) + { + /* + * Strip it and check that it's not empty. + */ + char *pszExpr = ExpCtx.StrBuf.pszBuf; + size_t cchExpr = ExpCtx.StrBuf.cchBuf; + while (cchExpr > 0 && RT_C_IS_SPACE(*pszExpr)) + pszExpr++, cchExpr--; + + while (cchExpr > 0 && RT_C_IS_SPACE(pszExpr[cchExpr - 1])) + { + pszExpr[--cchExpr] = '\0'; + ExpCtx.StrBuf.cchBuf--; + } + if (cchExpr) + { + /* + * Now, evalute the expression. + */ + VBCPPEVAL enmResult; + rcExit = vbcppExprEval(pThis, pszExpr, cchExpr, cReplacements, &enmResult); + if (rcExit == RTEXITCODE_SUCCESS) + { + /* + * Take action. + */ + if (enmKind != kVBCppCondKind_ElIf) + rcExit = vbcppCondPush(pThis, pStrmInput, offComment, enmKind, enmResult, + pchCondition, cchCondition); + else + { + PVBCPPCOND pCond = pThis->pCondStack; + if ( pCond->enmResult != kVBCppEval_Undecided + && ( !pCond->pUp + || pCond->pUp->enmStackResult == kVBCppEval_True)) + { + Assert(enmResult == kVBCppEval_True || enmResult == kVBCppEval_False); + if ( pCond->enmResult == kVBCppEval_False + && enmResult == kVBCppEval_True + && !pCond->fElIfDecided) + { + pCond->enmStackResult = kVBCppEval_True; + pCond->fElIfDecided = true; + } + else + pCond->enmStackResult = kVBCppEval_False; + pThis->fIf0Mode = pCond->enmStackResult == kVBCppEval_False; + } + pCond->enmKind = kVBCppCondKind_ElIf; + pCond->enmResult = enmResult; + pCond->pchCond = pchCondition; + pCond->cchCond = cchCondition; + + /* + * Do #elif pass thru. + */ + if ( !pThis->fIf0Mode + && pCond->enmResult == kVBCppEval_Undecided) + { + ssize_t cch = ScmStreamPrintf(&pThis->StrmOutput, "#%*selif", pCond->iKeepLevel - 1, ""); + if (cch > 0) + rcExit = vbcppOutputComment(pThis, pStrmInput, offStart, cch, 2); + else + rcExit = vbcppError(pThis, "Output error %Rrc", (int)cch); + } + else + pThis->fJustDroppedLine = true; + } + } + } + else + rcExit = vbcppError(pThis, "Empty #if expression"); + } + } + vbcppMacroExpandCleanup(&ExpCtx); + return rcExit; +} + + +/** + * Processes a abbreviated line number directive. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pThis The C preprocessor instance. + * @param pStrmInput The input stream. + * @param offStart The stream position where the directive + * started (for pass thru). + */ +static RTEXITCODE vbcppDirectiveIfDef(PVBCPP pThis, PSCMSTREAM pStrmInput, size_t offStart) +{ + /* + * Parse it. + */ + RTEXITCODE rcExit = vbcppProcessSkipWhiteEscapedEolAndComments(pThis, pStrmInput); + if (rcExit == RTEXITCODE_SUCCESS) + { + size_t cchDefine; + const char *pchDefine = ScmStreamCGetWord(pStrmInput, &cchDefine); + if (pchDefine) + { + rcExit = vbcppProcessSkipWhiteEscapedEolAndCommentsCheckEol(pThis, pStrmInput); + if (rcExit == RTEXITCODE_SUCCESS) + { + /* + * Evaluate it. + */ + VBCPPEVAL enmEval; + if (vbcppMacroExists(pThis, pchDefine, cchDefine)) + enmEval = kVBCppEval_True; + else if ( !pThis->fUndecidedConditionals + || RTStrSpaceGetN(&pThis->UndefStrSpace, pchDefine, cchDefine) != NULL) + enmEval = kVBCppEval_False; + else + enmEval = kVBCppEval_Undecided; + rcExit = vbcppCondPush(pThis, pStrmInput, offStart, kVBCppCondKind_IfDef, enmEval, + pchDefine, cchDefine); + } + } + else + rcExit = vbcppError(pThis, "Malformed #ifdef"); + } + return rcExit; +} + + +/** + * Processes a abbreviated line number directive. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pThis The C preprocessor instance. + * @param pStrmInput The input stream. + * @param offStart The stream position where the directive + * started (for pass thru). + */ +static RTEXITCODE vbcppDirectiveIfNDef(PVBCPP pThis, PSCMSTREAM pStrmInput, size_t offStart) +{ + /* + * Parse it. + */ + RTEXITCODE rcExit = vbcppProcessSkipWhiteEscapedEolAndComments(pThis, pStrmInput); + if (rcExit == RTEXITCODE_SUCCESS) + { + size_t cchDefine; + const char *pchDefine = ScmStreamCGetWord(pStrmInput, &cchDefine); + if (pchDefine) + { + rcExit = vbcppProcessSkipWhiteEscapedEolAndCommentsCheckEol(pThis, pStrmInput); + if (rcExit == RTEXITCODE_SUCCESS) + { + /* + * Evaluate it. + */ + VBCPPEVAL enmEval; + if (vbcppMacroExists(pThis, pchDefine, cchDefine)) + enmEval = kVBCppEval_False; + else if ( !pThis->fUndecidedConditionals + || RTStrSpaceGetN(&pThis->UndefStrSpace, pchDefine, cchDefine) != NULL) + enmEval = kVBCppEval_True; + else + enmEval = kVBCppEval_Undecided; + rcExit = vbcppCondPush(pThis, pStrmInput, offStart, kVBCppCondKind_IfNDef, enmEval, + pchDefine, cchDefine); + } + } + else + rcExit = vbcppError(pThis, "Malformed #ifndef"); + } + return rcExit; +} + + +/** + * Processes a abbreviated line number directive. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pThis The C preprocessor instance. + * @param pStrmInput The input stream. + * @param offStart The stream position where the directive + * started (for pass thru). + */ +static RTEXITCODE vbcppDirectiveElse(PVBCPP pThis, PSCMSTREAM pStrmInput, size_t offStart) +{ + /* + * Nothing to parse, just comment positions to find and note down. + */ + offStart = vbcppProcessSkipWhite(pStrmInput); + RTEXITCODE rcExit = vbcppProcessSkipWhiteEscapedEolAndCommentsCheckEol(pThis, pStrmInput); + if (rcExit == RTEXITCODE_SUCCESS) + { + /* + * Execute. + */ + PVBCPPCOND pCond = pThis->pCondStack; + if (pCond) + { + if (!pCond->fSeenElse) + { + pCond->fSeenElse = true; + if ( pCond->enmResult != kVBCppEval_Undecided + && ( !pCond->pUp + || pCond->pUp->enmStackResult == kVBCppEval_True)) + { + if ( pCond->enmResult == kVBCppEval_True + || pCond->fElIfDecided) + + pCond->enmStackResult = kVBCppEval_False; + else + pCond->enmStackResult = kVBCppEval_True; + pThis->fIf0Mode = pCond->enmStackResult == kVBCppEval_False; + } + + /* + * Do pass thru. + */ + if ( !pThis->fIf0Mode + && pCond->enmResult == kVBCppEval_Undecided) + { + ssize_t cch = ScmStreamPrintf(&pThis->StrmOutput, "#%*selse", pCond->iKeepLevel - 1, ""); + if (cch > 0) + rcExit = vbcppOutputComment(pThis, pStrmInput, offStart, cch, 2); + else + rcExit = vbcppError(pThis, "Output error %Rrc", (int)cch); + } + else + pThis->fJustDroppedLine = true; + } + else + rcExit = vbcppError(pThis, "Double #else or/and missing #endif"); + } + else + rcExit = vbcppError(pThis, "#else without #if"); + } + return rcExit; +} + + +/** + * Processes a abbreviated line number directive. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pThis The C preprocessor instance. + * @param pStrmInput The input stream. + * @param offStart The stream position where the directive + * started (for pass thru). + */ +static RTEXITCODE vbcppDirectiveEndif(PVBCPP pThis, PSCMSTREAM pStrmInput, size_t offStart) +{ + /* + * Nothing to parse, just comment positions to find and note down. + */ + offStart = vbcppProcessSkipWhite(pStrmInput); + RTEXITCODE rcExit = vbcppProcessSkipWhiteEscapedEolAndCommentsCheckEol(pThis, pStrmInput); + if (rcExit == RTEXITCODE_SUCCESS) + { + /* + * Execute. + */ + PVBCPPCOND pCond = pThis->pCondStack; + if (pCond) + { + pThis->pCondStack = pCond->pUp; + pThis->fIf0Mode = pCond->pUp && pCond->pUp->enmStackResult == kVBCppEval_False; + + /* + * Do pass thru. + */ + if ( !pThis->fIf0Mode + && pCond->enmResult == kVBCppEval_Undecided) + { + ssize_t cch = ScmStreamPrintf(&pThis->StrmOutput, "#%*sendif", pCond->iKeepLevel - 1, ""); + if (cch > 0) + rcExit = vbcppOutputComment(pThis, pStrmInput, offStart, cch, 1); + else + rcExit = vbcppError(pThis, "Output error %Rrc", (int)cch); + } + else + pThis->fJustDroppedLine = true; + } + else + rcExit = vbcppError(pThis, "#endif without #if"); + } + return rcExit; +} + + + + + +/* + * + * + * Misc Directives + * Misc Directives + * Misc Directives + * Misc Directives + * + * + */ + + +/** + * Adds an include directory. + * + * @returns Program exit code, with error message on failure. + * @param pThis The C preprocessor instance. + * @param pszDir The directory to add. + */ +static RTEXITCODE vbcppAddInclude(PVBCPP pThis, const char *pszDir) +{ + uint32_t cIncludes = pThis->cIncludes; + if (cIncludes >= _64K) + return vbcppError(pThis, "Too many include directories"); + + void *pv = RTMemRealloc(pThis->papszIncludes, (cIncludes + 1) * sizeof(char **)); + if (!pv) + return vbcppError(pThis, "No memory for include directories"); + pThis->papszIncludes = (char **)pv; + + int rc = RTStrDupEx(&pThis->papszIncludes[cIncludes], pszDir); + if (RT_FAILURE(rc)) + return vbcppError(pThis, "No string memory for include directories"); + + pThis->cIncludes = cIncludes + 1; + return RTEXITCODE_SUCCESS; +} + + +/** + * Processes a abbreviated line number directive. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pThis The C preprocessor instance. + * @param pStrmInput The input stream. + * @param offStart The stream position where the directive + * started (for pass thru). + */ +static RTEXITCODE vbcppDirectiveInclude(PVBCPP pThis, PSCMSTREAM pStrmInput, size_t offStart) +{ + RT_NOREF_PV(offStart); + + /* + * Parse it. + */ + RTEXITCODE rcExit = vbcppProcessSkipWhiteEscapedEolAndComments(pThis, pStrmInput); + if (rcExit == RTEXITCODE_SUCCESS) + { + size_t cchFileSpec = 0; + const char *pchFileSpec = NULL; + size_t cchFilename = 0; + const char *pchFilename = NULL; + + unsigned ch = ScmStreamPeekCh(pStrmInput); + unsigned chType = ch; + if (ch == '"' || ch == '<') + { + ScmStreamGetCh(pStrmInput); + pchFileSpec = pchFilename = ScmStreamGetCur(pStrmInput); + unsigned chEnd = chType == '<' ? '>' : '"'; + while ( (ch = ScmStreamGetCh(pStrmInput)) != ~(unsigned)0 + && ch != chEnd) + { + if (ch == '\r' || ch == '\n') + { + rcExit = vbcppError(pThis, "Multi-line include file specfications are not supported"); + break; + } + } + + if (rcExit == RTEXITCODE_SUCCESS) + { + if (ch != ~(unsigned)0) + cchFileSpec = cchFilename = ScmStreamGetCur(pStrmInput) - pchFilename - 1; + else + rcExit = vbcppError(pThis, "Expected '%c'", chType); + } + } + else if (vbcppIsCIdentifierLeadChar(ch)) + { + //pchFileSpec = ScmStreamCGetWord(pStrmInput, &cchFileSpec); + rcExit = vbcppError(pThis, "Including via a define is not implemented yet"); + } + else + rcExit = vbcppError(pThis, "Malformed include directive"); + + /* + * Take down the location of the next non-white space, in case we need + * to pass thru the directive further down. Then skip to the end of the + * line. + */ + size_t const offIncEnd = vbcppProcessSkipWhite(pStrmInput); + if (rcExit == RTEXITCODE_SUCCESS) + rcExit = vbcppProcessSkipWhiteEscapedEolAndCommentsCheckEol(pThis, pStrmInput); + + if (rcExit == RTEXITCODE_SUCCESS) + { + /* + * Execute it. + */ + if (pThis->enmIncludeAction == kVBCppIncludeAction_Include) + { + /** @todo Search for the include file and push it onto the input stack. + * Not difficult, just unnecessary rigth now. */ + rcExit = vbcppError(pThis, "Includes are fully implemented"); + } + else if (pThis->enmIncludeAction == kVBCppIncludeAction_PassThru) + { + /* Pretty print the passthru. */ + unsigned cchIndent = pThis->pCondStack ? pThis->pCondStack->iKeepLevel : 0; + ssize_t cch; + if (chType == '<') + cch = ScmStreamPrintf(&pThis->StrmOutput, "#%*sinclude <%.*s>", + cchIndent, "", cchFileSpec, pchFileSpec); + else if (chType == '"') + cch = ScmStreamPrintf(&pThis->StrmOutput, "#%*sinclude \"%.*s\"", + cchIndent, "", cchFileSpec, pchFileSpec); + else + cch = ScmStreamPrintf(&pThis->StrmOutput, "#%*sinclude %.*s", + cchIndent, "", cchFileSpec, pchFileSpec); + if (cch > 0) + rcExit = vbcppOutputComment(pThis, pStrmInput, offIncEnd, cch, 1); + else + rcExit = vbcppError(pThis, "Output error %Rrc", (int)cch); + + } + else + { + Assert(pThis->enmIncludeAction == kVBCppIncludeAction_Drop); + pThis->fJustDroppedLine = true; + } + } + } + return rcExit; +} + + +/** + * Processes a abbreviated line number directive. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pThis The C preprocessor instance. + * @param pStrmInput The input stream. + * @param offStart The stream position where the directive + * started (for pass thru). + */ +static RTEXITCODE vbcppDirectivePragma(PVBCPP pThis, PSCMSTREAM pStrmInput, size_t offStart) +{ + RT_NOREF_PV(offStart); + + /* + * Parse out the first word. + */ + RTEXITCODE rcExit = vbcppProcessSkipWhiteEscapedEolAndComments(pThis, pStrmInput); + if (rcExit == RTEXITCODE_SUCCESS) + { + size_t cchPragma; + const char *pchPragma = ScmStreamCGetWord(pStrmInput, &cchPragma); + if (pchPragma) + { + size_t const off2nd = vbcppProcessSkipWhite(pStrmInput); + size_t offComment; + rcExit = vbcppInputSkipToEndOfDirectiveLine(pThis, pStrmInput, &offComment); + if (rcExit == RTEXITCODE_SUCCESS) + { + /* + * What to do about this + */ + bool fPassThru = false; + if ( cchPragma == 1 + && *pchPragma == 'D') + fPassThru = pThis->fPassThruPragmaD; + else if ( cchPragma == 3 + && !strncmp(pchPragma, "STD", 3)) + fPassThru = pThis->fPassThruPragmaSTD; + else + fPassThru = pThis->fPassThruPragmaOther; + if (fPassThru) + { + unsigned cchIndent = pThis->pCondStack ? pThis->pCondStack->iKeepLevel : 0; + ssize_t cch = ScmStreamPrintf(&pThis->StrmOutput, "#%*spragma %.*s", + cchIndent, "", cchPragma, pchPragma); + if (cch > 0) + rcExit = vbcppOutputComment(pThis, pStrmInput, off2nd, cch, 1); + else + rcExit = vbcppError(pThis, "output error"); + } + else + pThis->fJustDroppedLine = true; + } + } + else + rcExit = vbcppError(pThis, "Malformed #pragma"); + } + + return rcExit; +} + + +/** + * Processes an error directive. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pThis The C preprocessor instance. + * @param pStrmInput The input stream. + * @param offStart The stream position where the directive + * started (for pass thru). + */ +static RTEXITCODE vbcppDirectiveError(PVBCPP pThis, PSCMSTREAM pStrmInput, size_t offStart) +{ + RT_NOREF_PV(offStart); + RT_NOREF_PV(pStrmInput); + return vbcppError(pThis, "Hit an #error"); +} + + +/** + * Processes a abbreviated line number directive. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pThis The C preprocessor instance. + * @param pStrmInput The input stream. + * @param offStart The stream position where the directive + * started (for pass thru). + */ +static RTEXITCODE vbcppDirectiveLineNo(PVBCPP pThis, PSCMSTREAM pStrmInput, size_t offStart) +{ + RT_NOREF_PV(offStart); + RT_NOREF_PV(pStrmInput); + return vbcppError(pThis, "Not implemented: %s", __FUNCTION__); +} + + +/** + * Processes a abbreviated line number directive. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pThis The C preprocessor instance. + * @param pStrmInput The input stream. + */ +static RTEXITCODE vbcppDirectiveLineNoShort(PVBCPP pThis, PSCMSTREAM pStrmInput) +{ + RT_NOREF_PV(pStrmInput); + return vbcppError(pThis, "Not implemented: %s", __FUNCTION__); +} + + +/** + * Handles a preprocessor directive. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pThis The C preprocessor instance. + * @param pStrmInput The input stream. + */ +static RTEXITCODE vbcppProcessDirective(PVBCPP pThis, PSCMSTREAM pStrmInput) +{ + /* + * Get the directive and do a string switch on it. + */ + RTEXITCODE rcExit = vbcppProcessSkipWhiteEscapedEolAndComments(pThis, pStrmInput); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + size_t cchDirective; + const char *pchDirective = ScmStreamCGetWord(pStrmInput, &cchDirective); + if (pchDirective) + { + size_t const offStart = ScmStreamTell(pStrmInput); +#define IS_DIRECTIVE(a_sz) ( sizeof(a_sz) - 1 == cchDirective && strncmp(pchDirective, a_sz, sizeof(a_sz) - 1) == 0) + if (IS_DIRECTIVE("if")) + rcExit = vbcppDirectiveIfOrElif(pThis, pStrmInput, offStart, kVBCppCondKind_If); + else if (IS_DIRECTIVE("elif")) + rcExit = vbcppDirectiveIfOrElif(pThis, pStrmInput, offStart, kVBCppCondKind_ElIf); + else if (IS_DIRECTIVE("ifdef")) + rcExit = vbcppDirectiveIfDef(pThis, pStrmInput, offStart); + else if (IS_DIRECTIVE("ifndef")) + rcExit = vbcppDirectiveIfNDef(pThis, pStrmInput, offStart); + else if (IS_DIRECTIVE("else")) + rcExit = vbcppDirectiveElse(pThis, pStrmInput, offStart); + else if (IS_DIRECTIVE("endif")) + rcExit = vbcppDirectiveEndif(pThis, pStrmInput, offStart); + else if (!pThis->fIf0Mode) + { + if (IS_DIRECTIVE("include")) + rcExit = vbcppDirectiveInclude(pThis, pStrmInput, offStart); + else if (IS_DIRECTIVE("define")) + rcExit = vbcppDirectiveDefine(pThis, pStrmInput, offStart); + else if (IS_DIRECTIVE("undef")) + rcExit = vbcppDirectiveUndef(pThis, pStrmInput, offStart); + else if (IS_DIRECTIVE("pragma")) + rcExit = vbcppDirectivePragma(pThis, pStrmInput, offStart); + else if (IS_DIRECTIVE("error")) + rcExit = vbcppDirectiveError(pThis, pStrmInput, offStart); + else if (IS_DIRECTIVE("line")) + rcExit = vbcppDirectiveLineNo(pThis, pStrmInput, offStart); + else + rcExit = vbcppError(pThis, "Unknown preprocessor directive '#%.*s'", cchDirective, pchDirective); + } +#undef IS_DIRECTIVE + } + else if (!pThis->fIf0Mode) + { + /* Could it be a # "file" directive? */ + unsigned ch = ScmStreamPeekCh(pStrmInput); + if (RT_C_IS_DIGIT(ch)) + rcExit = vbcppDirectiveLineNoShort(pThis, pStrmInput); + else + rcExit = vbcppError(pThis, "Malformed preprocessor directive"); + } + return rcExit; +} + + +/* + * + * + * M a i n b o d y. + * M a i n b o d y. + * M a i n b o d y. + * M a i n b o d y. + * M a i n b o d y. + * + * + */ + + +/** + * Does the actually preprocessing of the input file. + * + * @returns Exit code. + * @param pThis The C preprocessor instance. + */ +static RTEXITCODE vbcppPreprocess(PVBCPP pThis) +{ + RTEXITCODE rcExit = RTEXITCODE_SUCCESS; + + /* + * Parse. + */ + while (pThis->pInputStack) + { + pThis->fMaybePreprocessorLine = true; + + PSCMSTREAM pStrmInput = &pThis->pInputStack->StrmInput; + unsigned ch; + while ((ch = ScmStreamGetCh(pStrmInput)) != ~(unsigned)0) + { + if (ch == '/') + { + ch = ScmStreamPeekCh(pStrmInput); + if (ch == '*') + rcExit = vbcppProcessMultiLineComment(pThis, pStrmInput); + else if (ch == '/') + rcExit = vbcppProcessOneLineComment(pThis, pStrmInput); + else + { + pThis->fMaybePreprocessorLine = false; + if (!pThis->fIf0Mode) + rcExit = vbcppOutputCh(pThis, '/'); + } + } + else if (ch == '#' && pThis->fMaybePreprocessorLine) + { + rcExit = vbcppProcessDirective(pThis, pStrmInput); + pStrmInput = &pThis->pInputStack->StrmInput; + } + else if (ch == '\r' || ch == '\n') + { + if ( ( !pThis->fIf0Mode + && !pThis->fJustDroppedLine) + || !pThis->fRemoveDroppedLines + || !ScmStreamIsAtStartOfLine(&pThis->StrmOutput)) + rcExit = vbcppOutputCh(pThis, ch); + pThis->fJustDroppedLine = false; + pThis->fMaybePreprocessorLine = true; + } + else if (RT_C_IS_SPACE(ch)) + { + if (!pThis->fIf0Mode) + rcExit = vbcppOutputCh(pThis, ch); + } + else + { + pThis->fMaybePreprocessorLine = false; + if (!pThis->fIf0Mode) + { + if (ch == '"') + rcExit = vbcppProcessStringLitteral(pThis, pStrmInput); + else if (ch == '\'') + rcExit = vbcppProcessCharacterConstant(pThis, pStrmInput); + else if (vbcppIsCIdentifierLeadChar(ch)) + rcExit = vbcppProcessIdentifier(pThis, pStrmInput); + else if (RT_C_IS_DIGIT(ch)) + rcExit = vbcppProcessNumber(pThis, pStrmInput, ch); + else + rcExit = vbcppOutputCh(pThis, ch); + } + } + if (rcExit != RTEXITCODE_SUCCESS) + break; + } + + /* + * Check for errors. + */ + if (rcExit != RTEXITCODE_SUCCESS) + break; + + /* + * Pop the input stack. + */ + PVBCPPINPUT pPopped = pThis->pInputStack; + pThis->pInputStack = pPopped->pUp; + RTMemFree(pPopped); + } + + return rcExit; +} + + +/** + * Opens the input and output streams. + * + * @returns Exit code. + * @param pThis The C preprocessor instance. + */ +static RTEXITCODE vbcppOpenStreams(PVBCPP pThis) +{ + if (!pThis->pszInput) + return vbcppError(pThis, "Preprocessing the standard input stream is currently not supported"); + + size_t cchName = strlen(pThis->pszInput); + PVBCPPINPUT pInput = (PVBCPPINPUT)RTMemAlloc(RT_UOFFSETOF_DYN(VBCPPINPUT, szName[cchName + 1])); + if (!pInput) + return vbcppError(pThis, "out of memory"); + pInput->pUp = pThis->pInputStack; + pInput->pszSpecified = pInput->szName; + memcpy(pInput->szName, pThis->pszInput, cchName + 1); + pThis->pInputStack = pInput; + int rc = ScmStreamInitForReading(&pInput->StrmInput, pThis->pszInput); + if (RT_FAILURE(rc)) + return vbcppError(pThis, "ScmStreamInitForReading returned %Rrc when opening input file (%s)", + rc, pThis->pszInput); + + rc = ScmStreamInitForWriting(&pThis->StrmOutput, &pInput->StrmInput); + if (RT_FAILURE(rc)) + return vbcppError(pThis, "ScmStreamInitForWriting returned %Rrc", rc); + + pThis->fStrmOutputValid = true; + return RTEXITCODE_SUCCESS; +} + + +/** + * Changes the preprocessing mode. + * + * @param pThis The C preprocessor instance. + * @param enmMode The new mode. + */ +static void vbcppSetMode(PVBCPP pThis, VBCPPMODE enmMode) +{ + switch (enmMode) + { + case kVBCppMode_Standard: + pThis->fKeepComments = false; + pThis->fRespectSourceDefines = true; + pThis->fAllowRedefiningCmdLineDefines = true; + pThis->fPassThruDefines = false; + pThis->fUndecidedConditionals = false; + pThis->fPassThruPragmaD = false; + pThis->fPassThruPragmaSTD = true; + pThis->fPassThruPragmaOther = true; + pThis->fRemoveDroppedLines = false; + pThis->fLineSplicing = true; + pThis->enmIncludeAction = kVBCppIncludeAction_Include; + break; + + case kVBCppMode_Selective: + pThis->fKeepComments = true; + pThis->fRespectSourceDefines = false; + pThis->fAllowRedefiningCmdLineDefines = false; + pThis->fPassThruDefines = true; + pThis->fUndecidedConditionals = true; + pThis->fPassThruPragmaD = true; + pThis->fPassThruPragmaSTD = true; + pThis->fPassThruPragmaOther = true; + pThis->fRemoveDroppedLines = true; + pThis->fLineSplicing = false; + pThis->enmIncludeAction = kVBCppIncludeAction_PassThru; + break; + + case kVBCppMode_SelectiveD: + pThis->fKeepComments = true; + pThis->fRespectSourceDefines = true; + pThis->fAllowRedefiningCmdLineDefines = false; + pThis->fPassThruDefines = false; + pThis->fUndecidedConditionals = false; + pThis->fPassThruPragmaD = true; + pThis->fPassThruPragmaSTD = false; + pThis->fPassThruPragmaOther = false; + pThis->fRemoveDroppedLines = true; + pThis->fLineSplicing = false; + pThis->enmIncludeAction = kVBCppIncludeAction_Drop; + break; + + default: + AssertFailedReturnVoid(); + } + pThis->enmMode = enmMode; +} + + +/** + * Parses the command line options. + * + * @returns Program exit code. Exit on non-success or if *pfExit is set. + * @param pThis The C preprocessor instance. + * @param argc The argument count. + * @param argv The argument vector. + * @param pfExit Pointer to the exit indicator. + */ +static RTEXITCODE vbcppParseOptions(PVBCPP pThis, int argc, char **argv, bool *pfExit) +{ + RTEXITCODE rcExit; + + *pfExit = false; + + /* + * Option config. + */ + static RTGETOPTDEF const s_aOpts[] = + { + { "--define", 'D', RTGETOPT_REQ_STRING }, + { "--include-dir", 'I', RTGETOPT_REQ_STRING }, + { "--undefine", 'U', RTGETOPT_REQ_STRING }, + { "--keep-comments", 'C', RTGETOPT_REQ_NOTHING }, + { "--strip-comments", 'c', RTGETOPT_REQ_NOTHING }, + { "--D-strip", 'd', RTGETOPT_REQ_NOTHING }, + }; + + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetOptState; + int rc = RTGetOptInit(&GetOptState, argc, argv, &s_aOpts[0], RT_ELEMENTS(s_aOpts), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST); + AssertReleaseRCReturn(rc, RTEXITCODE_FAILURE); + + /* + * Process the options. + */ + while ((rc = RTGetOpt(&GetOptState, &ValueUnion)) != 0) + { + switch (rc) + { + case 'c': + pThis->fKeepComments = false; + break; + + case 'C': + pThis->fKeepComments = false; + break; + + case 'd': + vbcppSetMode(pThis, kVBCppMode_SelectiveD); + break; + + case 'D': + { + const char *pszEqual = strchr(ValueUnion.psz, '='); + if (pszEqual) + rcExit = vbcppMacroAdd(pThis, ValueUnion.psz, pszEqual - ValueUnion.psz, pszEqual + 1, RTSTR_MAX, true); + else + rcExit = vbcppMacroAdd(pThis, ValueUnion.psz, RTSTR_MAX, "1", 1, true); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + break; + } + + case 'I': + rcExit = vbcppAddInclude(pThis, ValueUnion.psz); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + break; + + case 'U': + rcExit = vbcppMacroUndef(pThis, ValueUnion.psz, RTSTR_MAX, true); + break; + + case 'h': + RTPrintf("No help yet, sorry\n"); + *pfExit = true; + return RTEXITCODE_SUCCESS; + + case 'V': + { + /* The following is assuming that svn does it's job here. */ + static const char s_szRev[] = "$Revision: 153224 $"; + const char *psz = RTStrStripL(strchr(s_szRev, ' ')); + RTPrintf("r%.*s\n", strchr(psz, ' ') - psz, psz); + *pfExit = true; + return RTEXITCODE_SUCCESS; + } + + case VINF_GETOPT_NOT_OPTION: + if (!pThis->pszInput) + pThis->pszInput = ValueUnion.psz; + else if (!pThis->pszOutput) + pThis->pszOutput = ValueUnion.psz; + else + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "too many file arguments"); + break; + + + /* + * Errors and bugs. + */ + default: + return RTGetOptPrintError(rc, &ValueUnion); + } + } + + return RTEXITCODE_SUCCESS; +} + + +/** + * Terminates the preprocessor. + * + * This may return failure if an error was delayed. + * + * @returns Exit code. + * @param pThis The C preprocessor instance. + */ +static RTEXITCODE vbcppTerm(PVBCPP pThis) +{ + /* + * Flush the output first. + */ + if (pThis->fStrmOutputValid) + { + if (pThis->pszOutput) + { + int rc = ScmStreamWriteToFile(&pThis->StrmOutput, "%s", pThis->pszOutput); + if (RT_FAILURE(rc)) + vbcppError(pThis, "ScmStreamWriteToFile failed with %Rrc when writing '%s'", rc, pThis->pszOutput); + } + else + { + int rc = ScmStreamWriteToStdOut(&pThis->StrmOutput); + if (RT_FAILURE(rc)) + vbcppError(pThis, "ScmStreamWriteToStdOut failed with %Rrc", rc); + } + } + + /* + * Cleanup. + */ + while (pThis->pInputStack) + { + ScmStreamDelete(&pThis->pInputStack->StrmInput); + void *pvFree = pThis->pInputStack; + pThis->pInputStack = pThis->pInputStack->pUp; + RTMemFree(pvFree); + } + + ScmStreamDelete(&pThis->StrmOutput); + + RTStrSpaceDestroy(&pThis->StrSpace, vbcppMacroFree, NULL); + pThis->StrSpace = NULL; + + uint32_t i = pThis->cIncludes; + while (i-- > 0) + RTStrFree(pThis->papszIncludes[i]); + RTMemFree(pThis->papszIncludes); + pThis->papszIncludes = NULL; + + return pThis->rcExit; +} + + +/** + * Initializes the C preprocessor instance data. + * + * @param pThis The C preprocessor instance data. + */ +static void vbcppInit(PVBCPP pThis) +{ + vbcppSetMode(pThis, kVBCppMode_Selective); + pThis->cIncludes = 0; + pThis->papszIncludes = NULL; + pThis->pszInput = NULL; + pThis->pszOutput = NULL; + pThis->StrSpace = NULL; + pThis->UndefStrSpace = NULL; + pThis->cCondStackDepth = 0; + pThis->pCondStack = NULL; + pThis->fIf0Mode = false; + pThis->fJustDroppedLine = false; + pThis->fMaybePreprocessorLine = true; + VBCPP_BITMAP_EMPTY(pThis->bmDefined); + pThis->cCondStackDepth = 0; + pThis->pInputStack = NULL; + RT_ZERO(pThis->StrmOutput); + pThis->rcExit = RTEXITCODE_SUCCESS; + pThis->fStrmOutputValid = false; +} + + + +int main(int argc, char **argv) +{ + int rc = RTR3InitExe(argc, &argv, 0); + if (RT_FAILURE(rc)) + return RTMsgInitFailure(rc); + + /* + * Do the job. The code says it all. + */ + VBCPP This; + vbcppInit(&This); + bool fExit; + RTEXITCODE rcExit = vbcppParseOptions(&This, argc, argv, &fExit); + if (!fExit && rcExit == RTEXITCODE_SUCCESS) + { + rcExit = vbcppOpenStreams(&This); + if (rcExit == RTEXITCODE_SUCCESS) + rcExit = vbcppPreprocess(&This); + } + + if (rcExit == RTEXITCODE_SUCCESS) + rcExit = vbcppTerm(&This); + else + vbcppTerm(&This); + return rcExit; +} + diff --git a/src/bldprogs/VBoxCheckImports.cpp b/src/bldprogs/VBoxCheckImports.cpp new file mode 100644 index 00000000..ab9c988e --- /dev/null +++ b/src/bldprogs/VBoxCheckImports.cpp @@ -0,0 +1,381 @@ +/* $Id: VBoxCheckImports.cpp $ */ +/** @file + * IPRT - Checks that a windows image only imports from a given set of DLLs. + */ + +/* + * Copyright (C) 2012-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include +#include +#include +#include + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +typedef struct +{ + const char *pszImage; + FILE *pFile; + bool f32Bit; + union + { + IMAGE_NT_HEADERS32 Nt32; + IMAGE_NT_HEADERS64 Nt64; + } Hdrs; + + uint32_t cSections; + PIMAGE_SECTION_HEADER paSections; +} MYIMAGE; + + +static bool Failed(MYIMAGE *pThis, const char *pszFmt, ...) +{ + va_list va; + fprintf(stderr, "error '%s': ", pThis->pszImage); + va_start(va, pszFmt); + vfprintf(stderr, pszFmt, va); + va_end(va); + fprintf(stderr, "\n"); + return false; +} + + +static bool ReadPeHeaders(MYIMAGE *pThis) +{ + /* + * MZ header. + */ + IMAGE_DOS_HEADER MzHdr; + if (fread(&MzHdr, sizeof(MzHdr), 1, pThis->pFile) != 1) + return Failed(pThis, "Reading DOS header"); + + if (MzHdr.e_magic != IMAGE_DOS_SIGNATURE) + return Failed(pThis, "No MZ magic (found %#x)", MzHdr.e_magic); + + if (fseek(pThis->pFile, MzHdr.e_lfanew, SEEK_SET) != 0) + return Failed(pThis, "Seeking to %#lx", (unsigned long)MzHdr.e_lfanew); + + /* + * NT signature + file header. + */ + if (fread(&pThis->Hdrs.Nt32, offsetof(IMAGE_NT_HEADERS32, OptionalHeader), 1, pThis->pFile) != 1) + return Failed(pThis, "Reading NT file header"); + + if (pThis->Hdrs.Nt32.Signature != IMAGE_NT_SIGNATURE) + return Failed(pThis, "No PE magic (found %#x)", pThis->Hdrs.Nt32.Signature); + + if (pThis->Hdrs.Nt32.FileHeader.SizeOfOptionalHeader == sizeof(pThis->Hdrs.Nt32.OptionalHeader)) + pThis->f32Bit = true; + else if (pThis->Hdrs.Nt32.FileHeader.SizeOfOptionalHeader == sizeof(pThis->Hdrs.Nt64.OptionalHeader)) + pThis->f32Bit = false; + else + return Failed(pThis, "Unsupported SizeOfOptionalHeaders value: %#x", + pThis->Hdrs.Nt32.FileHeader.SizeOfOptionalHeader); + + /* + * NT optional header. + */ + if (fread(&pThis->Hdrs.Nt32.OptionalHeader, pThis->Hdrs.Nt32.FileHeader.SizeOfOptionalHeader, 1, pThis->pFile) != 1) + return Failed(pThis, "Reading NT optional header"); + + if ( pThis->Hdrs.Nt32.OptionalHeader.Magic + != (pThis->f32Bit ? IMAGE_NT_OPTIONAL_HDR32_MAGIC : IMAGE_NT_OPTIONAL_HDR64_MAGIC) ) + return Failed(pThis, "Bad optional header magic: %#x", pThis->Hdrs.Nt32.OptionalHeader.Magic); + + uint32_t NumberOfRvaAndSizes = pThis->f32Bit + ? pThis->Hdrs.Nt32.OptionalHeader.NumberOfRvaAndSizes + : pThis->Hdrs.Nt64.OptionalHeader.NumberOfRvaAndSizes; + if (NumberOfRvaAndSizes != IMAGE_NUMBEROF_DIRECTORY_ENTRIES) + return Failed(pThis, "Unsupported NumberOfRvaAndSizes value: %#x", NumberOfRvaAndSizes); + + /* + * Read the section table. + */ + pThis->cSections = pThis->Hdrs.Nt32.FileHeader.NumberOfSections; + if (!pThis->cSections) + return Failed(pThis, "No sections in image!"); + pThis->paSections = (PIMAGE_SECTION_HEADER)calloc(sizeof(pThis->paSections[0]), pThis->cSections); + if (!pThis->paSections) + return Failed(pThis, "Out of memory!"); + if (fread(pThis->paSections, sizeof(pThis->paSections[0]), pThis->cSections, pThis->pFile) != pThis->cSections) + return Failed(pThis, "Reading NT section headers"); + + + return true; +} + + +static bool ReadAtRva(MYIMAGE *pThis, uint32_t uRva, void *pvBuf, size_t cbToRead) +{ + unsigned const uRvaOrg = uRva; + size_t const cbToReadOrg = cbToRead; + + /* + * Header section. + */ + int iSh = -1; + uint32_t uSectRva = 0; + uint32_t offSectRaw = 0; + uint32_t cbSectRaw = pThis->f32Bit + ? pThis->Hdrs.Nt32.OptionalHeader.SizeOfHeaders + : pThis->Hdrs.Nt64.OptionalHeader.SizeOfHeaders; + uint32_t cbSectMax = pThis->paSections[0].VirtualAddress; + + for (;;) + { + /* Read if we've got a match. */ + uint32_t off = uRva - uSectRva; + if (off < cbSectMax) + { + uint32_t cbThis = cbSectMax - off; + if (cbThis > cbToRead) + cbThis = (uint32_t)cbToRead; + + memset(pvBuf, 0, cbThis); + + if (off < cbSectRaw) + { + if (fseek(pThis->pFile, offSectRaw + off, SEEK_SET) != 0) + return Failed(pThis, "Seeking to %#x", offSectRaw + off); + if (fread(pvBuf, RT_MIN(cbThis, cbSectRaw - off), 1, pThis->pFile) != 1) + return Failed(pThis, "Reading %u bytes at %#x", RT_MIN(cbThis, cbSectRaw - off), offSectRaw + off); + } + + cbToRead -= cbThis; + if (!cbToRead) + return true; + uRva += cbThis; + pvBuf = (uint8_t *)pvBuf + cbThis; + } + + /* next section */ + iSh++; + if ((unsigned)iSh >= pThis->cSections) + return Failed(pThis, "RVA %#x LB %u is outside the image", uRvaOrg, cbToReadOrg); + uSectRva = pThis->paSections[iSh].VirtualAddress; + offSectRaw = pThis->paSections[iSh].PointerToRawData; + cbSectRaw = pThis->paSections[iSh].SizeOfRawData; + if ((unsigned)iSh + 1 < pThis->cSections) + cbSectMax = pThis->paSections[iSh + 1].VirtualAddress - uSectRva; + else + cbSectMax = pThis->paSections[iSh].Misc.VirtualSize; + } +} + + +static bool ReadStringAtRva(MYIMAGE *pThis, uint32_t uRva, char *pszBuf, size_t cbMax) +{ + uint32_t const uRvaOrg = uRva; + + /* + * Try read the whole string at once. + */ + uint32_t cbImage = pThis->f32Bit + ? pThis->Hdrs.Nt32.OptionalHeader.SizeOfImage + : pThis->Hdrs.Nt64.OptionalHeader.SizeOfImage; + uint32_t cbThis = uRva < cbImage ? cbImage - uRva : 1; + if (cbThis > cbMax) + cbThis = (uint32_t)cbMax; + if (!ReadAtRva(pThis, uRva, pszBuf, cbThis)) + return false; + if (memchr(pszBuf, 0, cbThis) != NULL) + return true; + + /* + * Read more, byte-by-byte. + */ + for (;;) + { + cbMax -= cbThis; + if (!cbMax) + return Failed(pThis, "String to long at %#x", uRvaOrg); + pszBuf += cbThis; + uRva += cbThis; + + cbThis = 1; + if (!ReadAtRva(pThis, uRva, pszBuf, cbThis)) + return false; + if (!*pszBuf) + return true; + } +} + + +static void *ReadAtRvaAlloc(MYIMAGE *pThis, uint32_t uRva, size_t cbToRead) +{ + void *pvBuf = malloc(cbToRead); + if (pvBuf) + { + if (ReadAtRva(pThis, uRva, pvBuf, cbToRead)) + return pvBuf; + free(pvBuf); + } + else + Failed(pThis, "Out of memory!"); + return NULL; +} + + +static bool ParseAndCheckImports(MYIMAGE *pThis, const char **papszAllowed, unsigned cAllowed) +{ + /* + * Do we have an import directory? If so, read it. + */ + IMAGE_DATA_DIRECTORY ImpDir = pThis->f32Bit + ? pThis->Hdrs.Nt32.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT] + : pThis->Hdrs.Nt64.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]; + if (ImpDir.Size == 0) + return true; + + uint32_t cImps = ImpDir.Size / sizeof(IMAGE_IMPORT_DESCRIPTOR); + if (cImps * sizeof(IMAGE_IMPORT_DESCRIPTOR) != ImpDir.Size) + return Failed(pThis, "Import directory size is not a multiple of IMAGE_IMPORT_DESCRIPTOR: %#x", ImpDir.Size); + + PIMAGE_IMPORT_DESCRIPTOR paImps = (PIMAGE_IMPORT_DESCRIPTOR)ReadAtRvaAlloc(pThis, ImpDir.VirtualAddress, ImpDir.Size); + if (!paImps) + return false; + + /* There is usually an empty entry at the end. */ + if ( paImps[cImps - 1].Name == 0 + || paImps[cImps - 1].FirstThunk == 0) + cImps--; + + /* + * Do the processing. + */ + bool fRc = true; + for (uint32_t i = 0; i < cImps; i++) + { + /* Read the import name string. */ + char szName[128]; + if (!ReadStringAtRva(pThis, paImps[i].Name, szName, sizeof(szName))) + { + fRc = false; + break; + } + + /* Check it against the list of allowed DLLs. */ + bool fFound = false; + unsigned j = cAllowed; + while (j-- > 0) + if (stricmp(papszAllowed[j], szName) == 0) + { + fFound = true; + break; + } + if (!fFound) + fRc = Failed(pThis, "Illegal import: '%s'", szName); + } + + free(paImps); + return fRc; +} + + +static int usage(const char *argv0) +{ + printf("usage: %s --image [allowed-dll [..]]\n", argv0); + return RTEXITCODE_SUCCESS; +} + + +int main(int argc, char **argv) +{ + /* + * Parse arguments. + */ + const char *pszImage = NULL; + const char **papszAllowed = (const char **)calloc(argc, sizeof(const char *)); + unsigned cAllowed = 0; + + for (int i = 1; i < argc; i++) + { + const char *psz = argv[i]; + if (*psz == '-') + { + if (!strcmp(psz, "--image") || !strcmp(psz, "-i")) + { + if (++i >= argc) + { + fprintf(stderr, "syntax error: File name expected after '%s'.\n", psz); + return RTEXITCODE_SYNTAX; + } + pszImage = argv[i]; + } + else if ( !strcmp(psz, "--help") + || !strcmp(psz, "-help") + || !strcmp(psz, "-h") + || !strcmp(psz, "-?") ) + return usage(argv[0]); + else if ( !strcmp(psz, "--version") + || !strcmp(psz, "-V")) + { + printf("$Revision: 153224 $\n"); + return RTEXITCODE_SUCCESS; + } + else + { + fprintf(stderr, "syntax error: Unknown option '%s'.\n", psz); + return RTEXITCODE_SYNTAX; + } + } + else + papszAllowed[cAllowed++] = argv[i]; + } + + if (!pszImage) + { + fprintf(stderr, "syntax error: No input file specified.\n"); + return RTEXITCODE_SYNTAX; + } + + /* + * Open the image and process headers. + */ + RTEXITCODE rcExit = RTEXITCODE_FAILURE; + MYIMAGE MyImage; + memset(&MyImage, 0, sizeof(MyImage)); + MyImage.pszImage = pszImage; + MyImage.pFile = fopen(pszImage, "rb"); + if (MyImage.pFile) + { + if ( ReadPeHeaders(&MyImage) + && ParseAndCheckImports(&MyImage, papszAllowed, cAllowed)) + rcExit = RTEXITCODE_SUCCESS; + + fclose(MyImage.pFile); + free(MyImage.paSections); + } + else + Failed(&MyImage, "Failed to open image for binary reading."); + + return rcExit; +} + diff --git a/src/bldprogs/VBoxCmp.cpp b/src/bldprogs/VBoxCmp.cpp new file mode 100644 index 00000000..dc9ee227 --- /dev/null +++ b/src/bldprogs/VBoxCmp.cpp @@ -0,0 +1,141 @@ +/* $Id: VBoxCmp.cpp $ */ +/** @file + * File Compare - Compares two files byte by byte. + */ + +/* + * Copyright (C) 2006-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include +#include +#include +#include + +#include +#include + + +/** + * Writes an error message. + * + * @returns RTEXITCODE_FAILURE. + * @param pcszFormat Error message. + * @param ... Format argument referenced in the message. + */ +static RTEXITCODE printErr(const char *pcszFormat, ...) +{ + va_list va; + + fprintf(stderr, "VBoxCmp: "); + va_start(va, pcszFormat); + vfprintf(stderr, pcszFormat, va); + va_end(va); + + return RTEXITCODE_FAILURE; +} + + +static FILE *openFile(const char *pszFile) +{ +#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2) + FILE *pFile = fopen(pszFile, "rb"); +#else + FILE *pFile = fopen(pszFile, "r"); +#endif + if (!pFile) + printErr("Failed to open '%s': %s\n", pszFile, strerror(errno)); + return pFile; +} + + +static RTEXITCODE compareFiles(FILE *pFile1, FILE *pFile2) +{ + if (!pFile1 || !pFile2) + return RTEXITCODE_FAILURE; + + uint32_t cMismatches = 1; + RTEXITCODE rcRet = RTEXITCODE_SUCCESS; + uint64_t off = 0; + for (;;) + { + uint8_t b1; + size_t cb1 = fread(&b1, sizeof(b1), 1, pFile1); + uint8_t b2; + size_t cb2 = fread(&b2, sizeof(b2), 1, pFile2); + if (cb1 != 1 || cb2 != 1) + break; + if (b1 != b2) + { + printErr("0x%x%08x: %#04x (%3d) != %#04x (%3d)\n", (uint32_t)(off >> 32), (uint32_t)off, b1, b1, b2, b2); + rcRet = RTEXITCODE_FAILURE; + cMismatches++; + if (cMismatches > 128) + { + printErr("Too many mismatches, giving up\n"); + return rcRet; + } + } + off++; + } + + if (!feof(pFile1) || !feof(pFile2)) + { + if (!feof(pFile1) && ferror(pFile1)) + rcRet = printErr("Read error on file #1.\n"); + else if (!feof(pFile2) && ferror(pFile2)) + rcRet = printErr("Read error on file #2.\n"); + else if (!feof(pFile2)) + rcRet = printErr("0x%x%08x: file #1 ends before file #2\n", (uint32_t)(off >> 32), (uint32_t)off); + else + rcRet = printErr("0x%x%08x: file #2 ends before file #1\n", (uint32_t)(off >> 32), (uint32_t)off); + } + + return rcRet; +} + + +int main(int argc, char *argv[]) +{ + RTEXITCODE rcExit; + + if (argc == 3) + { + const char *pszFile1 = argv[1]; + const char *pszFile2 = argv[2]; + FILE *pFile1 = openFile(pszFile1); + FILE *pFile2 = openFile(pszFile2); + rcExit = compareFiles(pFile1, pFile2); + if (pFile1) + fclose(pFile1); + if (pFile2) + fclose(pFile2); + } + else + rcExit = printErr("Syntax error: usage: VBoxCmp \n"); + return rcExit; +} + diff --git a/src/bldprogs/VBoxCompilerPlugIns.h b/src/bldprogs/VBoxCompilerPlugIns.h new file mode 100644 index 00000000..fb5c339b --- /dev/null +++ b/src/bldprogs/VBoxCompilerPlugIns.h @@ -0,0 +1,137 @@ +/* $Id: VBoxCompilerPlugIns.h $ */ +/** @file + * VBoxCompilerPlugIns - Types, Prototypes and Macros common to the VBox compiler plug-ins. + */ + +/* + * Copyright (C) 2006-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#ifndef VBOX_INCLUDED_SRC_bldprogs_VBoxCompilerPlugIns_h +#define VBOX_INCLUDED_SRC_bldprogs_VBoxCompilerPlugIns_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include +#include + + +/** @def dprintf + * Macro for debug printing using fprintf. Only active when DEBUG is defined. + */ +#ifdef DEBUG +# define dprintf(...) do { fprintf(stderr, __VA_ARGS__); } while (0) +#else +# define dprintf(...) do { } while (0) +#endif + + +/** + * Checker state. + */ +typedef struct VFMTCHKSTATE +{ + long iFmt; + long iArgs; + const char *pszFmt; + bool fMaybeNull; +#if defined(__GNUC__) && !defined(VBOX_COMPILER_PLUG_IN_AGNOSTIC) +# if RT_GNUC_PREREQ(6, 0) + gimple const *hStmt; +# else + gimple hStmt; +# endif + location_t hFmtLoc; +#endif +} VFMTCHKSTATE; +/** Pointer to my checker state. */ +typedef VFMTCHKSTATE *PVFMTCHKSTATE; + +#define MYSTATE_FMT_FILE(a_pState) VFmtChkGetFmtLocFile(a_pState) +#define MYSTATE_FMT_LINE(a_pState) VFmtChkGetFmtLocLine(a_pState) +#define MYSTATE_FMT_COLUMN(a_pState) VFmtChkGetFmtLocColumn(a_pState) + +const char *VFmtChkGetFmtLocFile(PVFMTCHKSTATE pState); + +unsigned int VFmtChkGetFmtLocLine(PVFMTCHKSTATE pState); + +unsigned int VFmtChkGetFmtLocColumn(PVFMTCHKSTATE pState); + +/** + * Implements checking format string replacement (%M). + * + * Caller will have checked all that can be checked. This means that there is a + * string argument present, or it won't make the call. + * + * @param pState The format string checking state. + * @param pszPctM The position of the '%M'. + * @param iArg The next argument number. + */ +void VFmtChkHandleReplacementFormatString(PVFMTCHKSTATE pState, const char *pszPctM, unsigned iArg); + +/** + * Warning. + * + * @returns + * @param pState . + * @param pszLoc . + * @param pszFormat . + * @param ... . + */ +void VFmtChkWarnFmt(PVFMTCHKSTATE pState, const char *pszLoc, const char *pszFormat, ...); + +/** + * Error. + * + * @returns + * @param pState . + * @param pszLoc . + * @param pszFormat . + * @param ... . + */ +void VFmtChkErrFmt(PVFMTCHKSTATE pState, const char *pszLoc, const char *pszFormat, ...); + +/** + * Checks that @a iFmtArg isn't present or a valid final dummy argument. + * + * Will issue warning/error if there are more arguments at @a iFmtArg. + * + * @param pState The format string checking state. + * @param iArg The index of the end of arguments, this is + * relative to VFMTCHKSTATE::iArgs. + */ +void VFmtChkVerifyEndOfArgs(PVFMTCHKSTATE pState, unsigned iArg); + +bool VFmtChkRequirePresentArg(PVFMTCHKSTATE pState, const char *pszLoc, unsigned iArg, const char *pszMessage); + +bool VFmtChkRequireIntArg(PVFMTCHKSTATE pState, const char *pszLoc, unsigned iArg, const char *pszMessage); + +bool VFmtChkRequireStringArg(PVFMTCHKSTATE pState, const char *pszLoc, unsigned iArg, const char *pszMessage); + +bool VFmtChkRequireVaListPtrArg(PVFMTCHKSTATE pState, const char *pszLoc, unsigned iArg, const char *pszMessage); + +/* VBoxCompilerPlugInsCommon.cpp */ +void MyCheckFormatCString(PVFMTCHKSTATE pState, const char *pszFmt); + + +#endif /* !VBOX_INCLUDED_SRC_bldprogs_VBoxCompilerPlugIns_h */ + diff --git a/src/bldprogs/VBoxCompilerPlugInsCommon.cpp b/src/bldprogs/VBoxCompilerPlugInsCommon.cpp new file mode 100644 index 00000000..11473fd6 --- /dev/null +++ b/src/bldprogs/VBoxCompilerPlugInsCommon.cpp @@ -0,0 +1,448 @@ +/* $Id: VBoxCompilerPlugInsCommon.cpp $ */ +/** @file + * VBoxCompilerPlugInsCommon - Code common to the compiler plug-ins. + */ + +/* + * Copyright (C) 2006-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define VBOX_COMPILER_PLUG_IN_AGNOSTIC +#include "VBoxCompilerPlugIns.h" + +#include + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#define MY_ISDIGIT(c) ((c) >= '0' && (c) <= '9') + +/** @name RTSTR_Z_XXX - Size modifiers + * @{ */ +#define RTSTR_Z_DEFAULT UINT16_C(0x0001) +#define RTSTR_Z_LONG UINT16_C(0x0002) /**< l */ +#define RTSTR_Z_LONGLONG UINT16_C(0x0004) /**< ll, L, q. */ +#define RTSTR_Z_HALF UINT16_C(0x0008) /**< h */ +#define RTSTR_Z_HALFHALF UINT16_C(0x0010) /**< hh (internally H) */ +#define RTSTR_Z_SIZE UINT16_C(0x0020) /**< z */ +#define RTSTR_Z_PTRDIFF UINT16_C(0x0040) /**< t */ +#define RTSTR_Z_INTMAX UINT16_C(0x0080) /**< j */ +#define RTSTR_Z_MS_I32 UINT16_C(0x1000) /**< I32 */ +#define RTSTR_Z_MS_I64 UINT16_C(0x2000) /**< I64 */ +#define RTSTR_Z_ALL_INT UINT16_C(0x30fe) /**< short hand for integers. */ +/** @} */ + + +/** @name VFMTCHKTYPE_F_XXX - Type flags. + * @{ */ +/** Pointers type. */ +#define VFMTCHKTYPE_F_PTR UINT8_C(0x01) +/** Both const and non-const pointer types. */ +#define VFMTCHKTYPE_F_CPTR (UINT8_C(0x02) | VFMTCHKTYPE_F_PTR) +/** @} */ + +/** @name VFMTCHKTYPE_Z_XXX - Special type sizes + * @{ */ +#define VFMTCHKTYPE_Z_CHAR UINT8_C(0xe0) +#define VFMTCHKTYPE_Z_SHORT UINT8_C(0xe1) +#define VFMTCHKTYPE_Z_INT UINT8_C(0xe2) +#define VFMTCHKTYPE_Z_LONG UINT8_C(0xe3) +#define VFMTCHKTYPE_Z_LONGLONG UINT8_C(0xe4) +#define VFMTCHKTYPE_Z_PTR UINT8_C(0xe5) /**< ASSUMED to be the same for 'void *', 'size_t' and 'ptrdiff_t'. */ +/** @} */ + +/** @name VFMTCHKTYPE_NM_XXX - Standard C type names. + * @{ */ +#define VFMTCHKTYPE_NM_INT "int" +#define VFMTCHKTYPE_NM_UINT "unsigned int" +#define VFMTCHKTYPE_NM_LONG "long" +#define VFMTCHKTYPE_NM_ULONG "unsigned long" +#define VFMTCHKTYPE_NM_LONGLONG "long long" +#define VFMTCHKTYPE_NM_ULONGLONG "unsigned long long" +#define VFMTCHKTYPE_NM_SHORT "short" +#define VFMTCHKTYPE_NM_USHORT "unsigned short" +#define VFMTCHKTYPE_NM_CHAR "char" +#define VFMTCHKTYPE_NM_SCHAR "signed char" +#define VFMTCHKTYPE_NM_UCHAR "unsigned char" +/** @} */ + + +/** @name VFMTCHKDESC_F_XXX - Format descriptor flags. + * @{ */ +#define VFMTCHKDESC_F_NONE UINT32_C(0) +#define VFMTCHKDESC_F_SIGNED RT_BIT_32(0) +#define VFMTCHKDESC_F_UNSIGNED RT_BIT_32(1) +/** @} */ + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * Format check type entry. + */ +typedef struct VFMTCHKTYPE +{ + /** The format size flag(s). */ + uint16_t fSize; + /** The argument size. */ + uint8_t cbArg; + /** Argument flags (VFMTCHKTYPE_F_XXX). */ + uint8_t fFlags; + /** List of strings with acceptable types, if NULL only check the sizes. */ + const char *pszzTypeNames; +} VFMTCHKTYPE; +/** Pointer to a read only format check type entry. */ +typedef VFMTCHKTYPE const *PCVFMTCHKTYPE; + +/** For use as an initializer in VFMTCHKDESK where it indicates that + * everything is covered by VFMTCHKDESC::paMoreTypes. Useful for repeating + * stuff. */ +#define VFMTCHKTYPE_USE_MORE_TYPES { 0, 0, 0, NULL } + +/** + * Format type descriptor. + */ +typedef struct VFMTCHKDESC +{ + /** The format type. */ + const char *pszType; + /** Recognized format flags (RTSTR_F_XXX). */ + uint16_t fFmtFlags; + /** Recognized format sizes (RTSTR_Z_XXX). */ + uint16_t fFmtSize; + /** Flags (VFMTCHKDESC_F_XXX). */ + uint32_t fFlags; + /** Primary type. */ + VFMTCHKTYPE Type; + /** More recognized types (optional). */ + PCVFMTCHKTYPE paMoreTypes; +} VFMTCHKDESC; +typedef VFMTCHKDESC const *PCVFMTCHKDESC; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Integer type specs for 'x', 'd', 'u', 'i', ++ + * + * @todo RTUINT32U and friends... The whole type matching thing. + */ +static VFMTCHKTYPE const g_aIntTypes[] = +{ + { RTSTR_Z_DEFAULT, VFMTCHKTYPE_Z_INT, 0, VFMTCHKTYPE_NM_INT "\0" VFMTCHKTYPE_NM_UINT "\0" }, + { RTSTR_Z_LONG, VFMTCHKTYPE_Z_LONG, 0, VFMTCHKTYPE_NM_LONG "\0" VFMTCHKTYPE_NM_ULONG "\0" }, + { RTSTR_Z_LONGLONG, VFMTCHKTYPE_Z_LONGLONG, 0, VFMTCHKTYPE_NM_LONGLONG "\0" VFMTCHKTYPE_NM_ULONGLONG "\0" }, + { RTSTR_Z_HALF, VFMTCHKTYPE_Z_SHORT, 0, VFMTCHKTYPE_NM_SHORT "\0" VFMTCHKTYPE_NM_USHORT "\0" }, + { RTSTR_Z_HALFHALF, VFMTCHKTYPE_Z_CHAR, 0, VFMTCHKTYPE_NM_SCHAR "\0" VFMTCHKTYPE_NM_UCHAR "\0" VFMTCHKTYPE_NM_CHAR "\0" }, + { RTSTR_Z_SIZE, VFMTCHKTYPE_Z_PTR, 0, "size_t\0" "RTUINTPTR\0" "RTINTPTR\0" }, + { RTSTR_Z_PTRDIFF, VFMTCHKTYPE_Z_PTR, 0, "ptrdiff_t\0" "RTUINTPTR\0" "RTINTPTR\0" }, + { RTSTR_Z_INTMAX, VFMTCHKTYPE_Z_PTR, 0, "uint64_t\0" "int64_t\0" "RTUINT64U\0" VFMTCHKTYPE_NM_LONGLONG "\0" VFMTCHKTYPE_NM_ULONGLONG "\0" }, + { RTSTR_Z_MS_I32, sizeof(uint32_t), 0, "uint32_t\0" "int32_t\0" "RTUINT32U\0" }, + { RTSTR_Z_MS_I64, sizeof(uint64_t), 0, "uint64_t\0" "int64_t\0" "RTUINT64U\0" }, +}; + +/** String type specs for 's', 'ls' and 'Ls'. + */ +static VFMTCHKTYPE const g_aStringTypes[] = +{ + { RTSTR_Z_DEFAULT, VFMTCHKTYPE_Z_PTR, VFMTCHKTYPE_F_CPTR, VFMTCHKTYPE_NM_CHAR "\0" }, + { RTSTR_Z_LONG, VFMTCHKTYPE_Z_PTR, VFMTCHKTYPE_F_CPTR, "RTUTF16\0" }, + { RTSTR_Z_LONGLONG, VFMTCHKTYPE_Z_PTR, VFMTCHKTYPE_F_CPTR, "RTUNICP\0" }, +}; + +static VFMTCHKDESC const g_aFmtDescs[] = +{ + { "s", + RTSTR_F_LEFT | RTSTR_F_WIDTH | RTSTR_F_PRECISION, + RTSTR_Z_DEFAULT | RTSTR_Z_LONG | RTSTR_Z_LONGLONG, + VFMTCHKDESC_F_UNSIGNED, + VFMTCHKTYPE_USE_MORE_TYPES, + g_aStringTypes + }, + { "x", + RTSTR_F_LEFT | RTSTR_F_ZEROPAD | RTSTR_F_SPECIAL | RTSTR_F_WIDTH | RTSTR_F_PRECISION, + RTSTR_Z_ALL_INT, + VFMTCHKDESC_F_UNSIGNED, + VFMTCHKTYPE_USE_MORE_TYPES, + g_aIntTypes + }, + { "RX32", + RTSTR_F_LEFT | RTSTR_F_ZEROPAD | RTSTR_F_SPECIAL | RTSTR_F_WIDTH | RTSTR_F_PRECISION, + RTSTR_Z_ALL_INT, + VFMTCHKDESC_F_UNSIGNED, + { RTSTR_Z_DEFAULT, sizeof(uint32_t), 0, "uint32_t\0" "int32_t\0" }, + NULL + }, + + + +}; + + +/** + * Does the actual format string checking. + * + * @todo Move this to different file common to both GCC and CLANG later. + * + * @param pState The format string checking state. + * @param pszFmt The format string. + */ +void MyCheckFormatCString(PVFMTCHKSTATE pState, const char *pszFmt) +{ + dprintf("checker2: \"%s\" at %s:%d col %d\n", pszFmt, + MYSTATE_FMT_FILE(pState), MYSTATE_FMT_LINE(pState), MYSTATE_FMT_COLUMN(pState)); + pState->pszFmt = pszFmt; + + unsigned iArg = 0; + for (;;) + { + /* + * Skip to the next argument. + * Quits the loop with the first char following the '%' in 'ch'. + */ + char ch; + for (;;) + { + ch = *pszFmt++; + if (ch == '%') + { + ch = *pszFmt++; + if (ch != '%') + break; + } + else if (ch == '\0') + { + VFmtChkVerifyEndOfArgs(pState, iArg); + return; + } + } + const char * const pszPct = pszFmt - 2; + + /* + * Flags + */ + uint32_t fFmtFlags = 0; + for (;;) + { + uint32_t fFlag; + switch (ch) + { + case '#': fFlag = RTSTR_F_SPECIAL; break; + case '-': fFlag = RTSTR_F_LEFT; break; + case '+': fFlag = RTSTR_F_PLUS; break; + case ' ': fFlag = RTSTR_F_BLANK; break; + case '0': fFlag = RTSTR_F_ZEROPAD; break; + case '\'': fFlag = RTSTR_F_THOUSAND_SEP; break; + default: fFlag = 0; break; + } + if (!fFlag) + break; + if (fFmtFlags & fFlag) + VFmtChkWarnFmt(pState, pszPct, "duplicate flag '%c'", ch); + fFmtFlags |= fFlag; + ch = *pszFmt++; + } + + /* + * Width. + */ + int cchWidth = -1; + if (MY_ISDIGIT(ch)) + { + cchWidth = ch - '0'; + while ( (ch = *pszFmt++) != '\0' + && MY_ISDIGIT(ch)) + { + cchWidth *= 10; + cchWidth += ch - '0'; + } + fFmtFlags |= RTSTR_F_WIDTH; + } + else if (ch == '*') + { + VFmtChkRequireIntArg(pState, pszPct, iArg, "width should be an 'int' sized argument"); + iArg++; + cchWidth = 0; + fFmtFlags |= RTSTR_F_WIDTH; + ch = *pszFmt++; + } + + /* + * Precision + */ + int cchPrecision = -1; + if (ch == '.') + { + ch = *pszFmt++; + if (MY_ISDIGIT(ch)) + { + cchPrecision = ch - '0'; + while ( (ch = *pszFmt++) != '\0' + && MY_ISDIGIT(ch)) + { + cchPrecision *= 10; + cchPrecision += ch - '0'; + } + } + else if (ch == '*') + { + VFmtChkRequireIntArg(pState, pszPct, iArg, "precision should be an 'int' sized argument"); + iArg++; + cchPrecision = 0; + ch = *pszFmt++; + } + else + VFmtChkErrFmt(pState, pszPct, "Missing precision value, only got the '.'"); + if (cchPrecision < 0) + { + VFmtChkErrFmt(pState, pszPct, "Negative precision value: %d", cchPrecision); + cchPrecision = 0; + } + fFmtFlags |= RTSTR_F_PRECISION; + } + + /* + * Argument size. + */ + uint16_t fFmtSize = RTSTR_Z_DEFAULT; + switch (ch) + { + default: + fFmtSize = RTSTR_Z_DEFAULT; + break; + + case 'z': + fFmtSize = RTSTR_Z_SIZE; + ch = *pszFmt++; + break; + case 'j': + fFmtSize = RTSTR_Z_INTMAX; + ch = *pszFmt++; + break; + case 't': + fFmtSize = RTSTR_Z_PTRDIFF; + ch = *pszFmt++; + break; + + case 'l': + fFmtSize = RTSTR_Z_LONG; + ch = *pszFmt++; + if (ch == 'l') + { + fFmtSize = RTSTR_Z_LONGLONG; + ch = *pszFmt++; + } + break; + + case 'q': /* Used on BSD platforms. */ + case 'L': + fFmtSize = RTSTR_Z_LONGLONG; + ch = *pszFmt++; + break; + + case 'h': + fFmtSize = RTSTR_Z_HALF; + ch = *pszFmt++; + if (ch == 'h') + { + fFmtSize = RTSTR_Z_HALFHALF; + ch = *pszFmt++; + } + break; + + case 'I': /* Used by Win32/64 compilers. */ + if ( pszFmt[0] == '6' + && pszFmt[1] == '4') + { + pszFmt += 2; + fFmtSize = RTSTR_Z_MS_I64; + } + else if ( pszFmt[0] == '3' + && pszFmt[1] == '2') + { + pszFmt += 2; + fFmtSize = RTSTR_Z_MS_I32; + } + else + { + VFmtChkErrFmt(pState, pszFmt, "Unknow format type/size/flag 'I%c'", pszFmt[0]); + fFmtSize = RTSTR_Z_INTMAX; + } + ch = *pszFmt++; + break; + } + + /* + * The type. + */ + switch (ch) + { + /* + * Nested extensions. + */ + case 'M': /* replace the format string (not stacked yet). */ + { + if (*pszFmt) + VFmtChkErrFmt(pState, pszFmt, "Characters following '%%M' will be ignored"); + if (fFmtSize != RTSTR_Z_DEFAULT) + VFmtChkWarnFmt(pState, pszFmt, "'%%M' does not support any size flags (%#x)", fFmtSize); + if (fFmtFlags != 0) + VFmtChkWarnFmt(pState, pszFmt, "'%%M' does not support any format flags (%#x)", fFmtFlags); + if (VFmtChkRequireStringArg(pState, pszPct, iArg, "'%M' expects a format string")) + VFmtChkHandleReplacementFormatString(pState, pszPct, iArg); + return; + } + + case 'N': /* real nesting. */ + { + if (fFmtSize != RTSTR_Z_DEFAULT) + VFmtChkWarnFmt(pState, pszFmt, "'%%N' does not support any size flags (%#x)", fFmtSize); + if (fFmtFlags != 0) + VFmtChkWarnFmt(pState, pszFmt, "'%%N' does not support any format flags (%#x)", fFmtFlags); + VFmtChkRequireStringArg(pState, pszPct, iArg, "'%N' expects a string followed by a va_list pointer"); + VFmtChkRequireVaListPtrArg(pState, pszPct, iArg + 1, "'%N' expects a string followed by a va_list pointer"); + iArg += 2; + break; + } + + case 'R': + if ( pszFmt[0] == 'h' + && pszFmt[1] == 'X') + { + VFmtChkRequirePresentArg(pState, pszPct, iArg, "Expected argument"); + iArg++; + } + RT_FALL_THROUGH(); + + default: + VFmtChkRequirePresentArg(pState, pszPct, iArg, "Expected argument"); + iArg++; + break; + } + } +} + diff --git a/src/bldprogs/VBoxCompilerPlugInsGcc.cpp b/src/bldprogs/VBoxCompilerPlugInsGcc.cpp new file mode 100644 index 00000000..b23d2611 --- /dev/null +++ b/src/bldprogs/VBoxCompilerPlugInsGcc.cpp @@ -0,0 +1,1012 @@ +/* $Id: VBoxCompilerPlugInsGcc.cpp $ */ +/** @file + * gccplugin - GCC plugin for checking IPRT format strings. + */ + +/* + * Copyright (C) 2006-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include +#include + +#if RT_GNUC_PREREQ(5, 1) +# include "gcc-plugin.h" +# include "plugin-version.h" +#endif +#if __GNUC__ == 4 && __GNUC_MINOR__ == 5 +# include "gmp.h" +extern "C" { +#endif +#if __GNUC__ == 4 && __GNUC_MINOR__ == 5 +# include "coretypes.h" +#endif +#include "plugin.h" +#include "basic-block.h" +#include "tree.h" +#include "tree-pass.h" +#if __GNUC__ == 5 && __GNUC_MINOR__ == 4 +# include "tree-ssa-alias.h" +# include "gimple-expr.h" +#endif +#include "gimple.h" +#if RT_GNUC_PREREQ(4, 9) +# include "gimple-iterator.h" +# include "context.h" /* for g */ +#endif +#include "cp/cp-tree.h" +#if RT_GNUC_PREREQ(10, 0) +# include "stringpool.h" +# include "attribs.h" +#endif +#if __GNUC__ == 4 && __GNUC_MINOR__ == 5 +} +#endif + +#include "VBoxCompilerPlugIns.h" + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** License indicator. */ +int plugin_is_GPL_compatible; + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** Convencience macro not present in earlier gcc versions. */ +#ifndef VAR_P +# define VAR_P(a_hNode) (TREE_CODE(a_hNode) == VAR_DECL) +#endif +/** Replacement for the 4.9.0 get_tree_code_name function. */ +#if !RT_GNUC_PREREQ(4, 9) +# define get_tree_code_name(a_enmTreeCode) (tree_code_name[a_enmTreeCode]) +#endif + + +/** For use with messages. + * @todo needs some more work... Actually, seems we're a bit handicapped by + * working on gimplified stuff. */ +#define MY_LOC(a_hPreferred, a_pState) EXPR_LOC_OR_LOC(a_hPreferred, (a_pState)->hFmtLoc) + +/** @name Compatibility glue + * @{ */ +#if __GNUC__ == 4 && __GNUC_MINOR__ == 5 +# define linemap_location_from_macro_expansion_p(a, b) false +#endif +#if __GNUC__ == 4 && __GNUC_MINOR__ == 5 +static tree gimple_call_fntype(gimple hStmt) +{ + tree hDecl = gimple_call_fndecl(hStmt); + if (hDecl) + return TREE_TYPE(hDecl); + hDecl = gimple_call_fn(hStmt); + if (TREE_CODE(hDecl) == OBJ_TYPE_REF) + hDecl = OBJ_TYPE_REF_EXPR(hDecl); + if (DECL_P(hDecl)) + { + tree hType = TREE_TYPE(hDecl); + if (POINTER_TYPE_P(hType)) + hType = TREE_TYPE(hType); + return hType; + } + return NULL_TREE; /* caller bitches about this*/ +} +#endif + +///* Integer to HOST_WIDE_INT conversion fun. */ +//#if RT_GNUC_PREREQ(4, 6) +//# define MY_INT_FITS_SHWI(hNode) (hNode).fits_shwi() +//# define MY_INT_TO_SHWI(hNode) (hNode).to_shwi() +//#else +//# define MY_INT_FITS_SHWI(hNode) double_int_fits_in_shwi_p(hNode) +//# define MY_INT_TO_SHWI(hNode) double_int_to_shwi(hNode) +//#endif + +/* Integer to HOST_WIDE_INT conversion fun. */ +#if RT_GNUC_PREREQ(5, 1) +# define MY_DOUBLE_INT_FITS_SHWI(hNode) tree_fits_shwi_p(hNode) +# define MY_DOUBLE_INT_TO_SHWI(hNode) tree_to_shwi(hNode) +#elif RT_GNUC_PREREQ(4, 6) +# define MY_DOUBLE_INT_FITS_SHWI(hNode) (TREE_INT_CST(hNode).fits_shwi()) +# define MY_DOUBLE_INT_TO_SHWI(hNode) (TREE_INT_CST(hNode).to_shwi()) +#else +# define MY_DOUBLE_INT_FITS_SHWI(hNode) double_int_fits_in_shwi_p(TREE_INT_CST(hNode)) +# define MY_DOUBLE_INT_TO_SHWI(hNode) double_int_to_shwi(TREE_INT_CST(hNode)) +#endif + +#ifndef EXPR_LOC_OR_LOC +# define EXPR_LOC_OR_LOC(a,b) (b) +#endif +/** @} */ + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static bool MyPassGateCallback(void); +static unsigned int MyPassExecuteCallback(void); +static unsigned int MyPassExecuteCallbackWithFunction(struct function *pFun); +static tree AttributeHandler(tree *, tree, tree, int, bool *); + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Plug-in info. */ +static const struct plugin_info g_PlugInInfo = +{ + version: "0.0.0-ALPHA", + help : "Implements the __iprt_format__ attribute for checking format strings and arguments." +}; + +#if RT_GNUC_PREREQ(4, 9) + +/** My pass. */ +static const pass_data g_MyPassData = +{ + type : GIMPLE_PASS, + name : "*iprt-format-checks", /* asterisk = no dump */ +# if RT_GNUC_PREREQ(10, 0) + optinfo_flags : OPTGROUP_NONE, +# else + optinfo_flags : 0, +# endif + tv_id : TV_NONE, + properties_required : 0, + properties_provided : 0, + properties_destroyed : 0, + todo_flags_start : 0, + todo_flags_finish : 0, +}; + +class MyPass : public gimple_opt_pass +{ +public: + MyPass(gcc::context *pCtx) : gimple_opt_pass(g_MyPassData, pCtx) + { } + + virtual bool gate(function *pFun) + { + NOREF(pFun); + return MyPassGateCallback(); + } + + virtual unsigned int execute(function *pFun) + { + NOREF(pFun); + return MyPassExecuteCallbackWithFunction(pFun); + } +}; + +#else /* < 4.9.0 */ + +/** My pass. */ +static struct gimple_opt_pass g_MyPass = +{ + pass: + { + type : GIMPLE_PASS, + name : "*iprt-format-checks", /* asterisk = no dump */ +# if RT_GNUC_PREREQ(4, 6) + optinfo_flags : 0, +# endif + gate : MyPassGateCallback, + execute : MyPassExecuteCallback, + sub : NULL, + next : NULL, + static_pass_number : 0, + tv_id : TV_NONE, + properties_required : 0, + properties_provided : 0, + properties_destroyed : 0, + todo_flags_start : 0, + todo_flags_finish : 0, + } +}; + +/** The registration info for my pass. */ +static const struct register_pass_info g_MyPassInfo = +{ + pass : &g_MyPass.pass, + reference_pass_name : "ssa", + ref_pass_instance_number : 1, + pos_op : PASS_POS_INSERT_BEFORE, +}; + +#endif /* < 4.9.0 */ + + +/** Attribute specifications. */ +static const struct attribute_spec g_AttribSpecs[] = +{ + { + name : "iprt_format", + min_length : 2, + max_length : 2, + decl_required : false, + type_required : true, + function_type_required : true, +// gcc 7.3 at least moves this field to after "handler", and with 8.3 it is back +#if RT_GNUC_PREREQ(4, 6) && !(RT_GNUC_PREREQ(7, 0) && !RT_GNUC_PREREQ(8, 0)) + affects_type_identity : false, +#endif + handler : AttributeHandler, +#if RT_GNUC_PREREQ(7, 0) && !RT_GNUC_PREREQ(8, 0) + affects_type_identity : false, +#endif +#if RT_GNUC_PREREQ(8, 0) + exclude : NULL, +#endif + }, + { + name : "iprt_format_maybe_null", + min_length : 2, + max_length : 2, + decl_required : false, + type_required : true, + function_type_required : true, +#if RT_GNUC_PREREQ(4, 6) && !(RT_GNUC_PREREQ(7, 0) && !RT_GNUC_PREREQ(8, 0)) + affects_type_identity : false, +#endif + handler : AttributeHandler, +#if RT_GNUC_PREREQ(7, 0) && !RT_GNUC_PREREQ(8, 0) + affects_type_identity : false, +#endif +#if RT_GNUC_PREREQ(8, 0) + exclude : NULL, +#endif + } +}; + + +#ifdef DEBUG + +/** + * Debug function for printing the scope of a decl. + * @param hDecl Declaration to print scope for. + */ +static void dprintScope(tree hDecl) +{ +# if 0 /* later? */ + tree hScope = CP_DECL_CONTEXT(hDecl); + if (hScope == global_namespace) + return; + if (TREE_CODE(hScope) == RECORD_TYPE) + hScope = TYPE_NAME(hScope); + + /* recurse */ + dprintScope(hScope); + + /* name the scope. */ + dprintf("::%s", DECL_NAME(hScope) ? IDENTIFIER_POINTER(DECL_NAME(hScope)) : ""); +# endif +} + + +/** + * Debug function for printing a declaration. + * @param hDecl The declaration to print. + */ +static void dprintDecl(tree hDecl) +{ + enum tree_code const enmDeclCode = TREE_CODE(hDecl); + tree const hType = TREE_TYPE(hDecl); + enum tree_code const enmTypeCode = hType ? TREE_CODE(hType) : (enum tree_code)-1; +#if 0 + if ( enmTypeCode == RECORD_TYPE + && enmDeclCode == TYPE_DECL + && DECL_ARTIFICIAL(hDecl)) + dprint_class(hType); +#endif + + dprintf("%s ", get_tree_code_name(enmDeclCode)); + dprintScope(hDecl); + dprintf("::%s", DECL_NAME(hDecl) ? IDENTIFIER_POINTER(DECL_NAME(hDecl)) : ""); + if (hType) + dprintf(" type %s", get_tree_code_name(enmTypeCode)); + dprintf(" @%s:%d", DECL_SOURCE_FILE(hDecl), DECL_SOURCE_LINE(hDecl)); +} + +#endif /* DEBUG */ + + +static location_t MyGetLocationPlusColumnOffset(location_t hLoc, unsigned int offColumn) +{ + /* + * Skip NOOPs, reserved locations and macro expansion. + */ + if ( offColumn != 0 + && hLoc >= RESERVED_LOCATION_COUNT + && !linemap_location_from_macro_expansion_p(line_table, hLoc)) + { +#if __GNUC__ >= 5 /** @todo figure this... */ + /* + * There is an API for doing this, nice. + */ + location_t hNewLoc = linemap_position_for_loc_and_offset(line_table, hLoc, offColumn); + if (hNewLoc && hNewLoc != hLoc) + { + dprintf("MyGetLocationPlusColumnOffset: hNewLoc=%#x hLoc=%#x offColumn=%u\n", hNewLoc, hLoc, offColumn); + return hNewLoc; + } + +#elif __GNUC_MINOR__ > 5 + /* + * Have to do the job ourselves, it seems. This is a bit hairy... + */ + line_map const *pMap = NULL; + location_t hLoc2 = linemap_resolve_location(line_table, hLoc, LRK_SPELLING_LOCATION, &pMap); + if (hLoc2) + hLoc = hLoc2; + + /* Guard against wrap arounds and overlaps. */ + if ( hLoc + offColumn > MAP_START_LOCATION(pMap) /** @todo Use MAX_SOURCE_LOCATION? */ + && ( pMap == LINEMAPS_LAST_ORDINARY_MAP(line_table) + || hLoc + offColumn < MAP_START_LOCATION((pMap + 1)))) + { + /* Calc new column and check that it's within the valid range. */ + unsigned int uColumn = SOURCE_COLUMN(pMap, hLoc) + offColumn; + if (uColumn < RT_BIT_32(ORDINARY_MAP_NUMBER_OF_COLUMN_BITS(pMap))) + { + /* Try add the position. If we get a valid result, replace the location. */ + source_location hNewLoc = linemap_position_for_line_and_column((line_map *)pMap, SOURCE_LINE(pMap, hLoc), uColumn); + if ( hNewLoc <= line_table->highest_location + && linemap_lookup(line_table, hNewLoc) != NULL) + { + dprintf("MyGetLocationPlusColumnOffset: hNewLoc=%#x hLoc=%#x offColumn=%u uColumn=%u\n", + hNewLoc, hLoc, offColumn, uColumn); + return hNewLoc; + } + } + } +#endif + } + dprintf("MyGetLocationPlusColumnOffset: taking fallback\n"); + return hLoc; +} + + +#if 0 +DECLINLINE(int) MyGetLineLength(const char *pszLine) +{ + if (pszLine) + { + const char *pszEol = strpbrk(pszLine, "\n\r"); + if (!pszEol) + pszEol = strchr(pszLine, '\0'); + return (int)(pszEol - pszLine); + } + return 0; +} +#endif + +static location_t MyGetFormatStringLocation(PVFMTCHKSTATE pState, const char *pszLoc) +{ + location_t hLoc = pState->hFmtLoc; +#if RT_GNUC_PREREQ(4,6) + intptr_t offString = pszLoc - pState->pszFmt; + if ( offString >= 0 + && !linemap_location_from_macro_expansion_p(line_table, hLoc)) + { + unsigned uCol = 1 + offString; +# if 0 /* apparently not needed */ + expanded_location XLoc = expand_location_to_spelling_point(hLoc); +# if RT_GNUC_PREREQ(10,0) + char_span Span = location_get_source_line(XLoc.file, XLoc.line); + const char *pszLine = Span.m_ptr; /** @todo if enabled */ + int cchLine = (int)Span.m_n_elts; +# elif RT_GNUC_PREREQ(6,0) + int cchLine = 0; + const char *pszLine = location_get_source_line(XLoc.file, XLoc.line, &cchLine); +# elif RT_GNUC_PREREQ(5,0) + int cchLine = 0; + const char *pszLine = location_get_source_line(XLoc, &cchLine); +# else + const char *pszLine = location_get_source_line(XLoc); + int cchLine = MyGetLineLength(pszLine); +# endif + if (pszLine) + { + /** @todo Adjust the position by parsing the source. */ + pszLine += XLoc.column - 1; + cchLine -= XLoc.column - 1; + } +# endif + + hLoc = MyGetLocationPlusColumnOffset(hLoc, uCol); + } +#endif + return hLoc; +} + + +/** + * Non-recursive worker for MyCheckFormatRecursive. + * + * This will attempt to result @a hFmtArg into a string literal which it then + * passes on to MyCheckFormatString for the actual analyzis. + * + * @param pState The format string checking state. + * @param hFmtArg The format string node. + */ +DECL_NO_INLINE(static, void) MyCheckFormatNonRecursive(PVFMTCHKSTATE pState, tree hFmtArg) +{ + dprintf("checker: hFmtArg=%p %s\n", hFmtArg, get_tree_code_name(TREE_CODE(hFmtArg))); + + /* + * Try resolve variables into constant strings. + */ + if (VAR_P(hFmtArg)) + { + hFmtArg = decl_constant_value(hFmtArg); + STRIP_NOPS(hFmtArg); /* Used as argument and assigned call result. */ + dprintf("checker1: variable => hFmtArg=%p %s\n", hFmtArg, get_tree_code_name(TREE_CODE(hFmtArg))); + } + + /* + * Fend off NULLs. + */ + if (integer_zerop(hFmtArg)) + { + if (pState->fMaybeNull) + VFmtChkVerifyEndOfArgs(pState, 0); + else + error_at(MY_LOC(hFmtArg, pState), "Format string should not be NULL"); + } + /* + * Need address expression to get any further. + */ + else if (TREE_CODE(hFmtArg) != ADDR_EXPR) + dprintf("checker1: Not address expression (%s)\n", get_tree_code_name(TREE_CODE(hFmtArg))); + else + { + pState->hFmtLoc = EXPR_LOC_OR_LOC(hFmtArg, pState->hFmtLoc); + hFmtArg = TREE_OPERAND(hFmtArg, 0); + + /* + * Deal with fixed string indexing, if possible. + */ + HOST_WIDE_INT off = 0; + if ( TREE_CODE(hFmtArg) == ARRAY_REF + && MY_DOUBLE_INT_FITS_SHWI(TREE_OPERAND(hFmtArg, 1)) + && MY_DOUBLE_INT_FITS_SHWI(TREE_OPERAND(hFmtArg, 1)) ) + { + off = MY_DOUBLE_INT_TO_SHWI(TREE_OPERAND(hFmtArg, 1)); + if (off < 0) + { + dprintf("checker1: ARRAY_REF, off=%ld\n", off); + return; + } + hFmtArg = TREE_OPERAND(hFmtArg, 0); + dprintf("checker1: ARRAY_REF => hFmtArg=%p %s, off=%ld\n", hFmtArg, get_tree_code_name(TREE_CODE(hFmtArg)), off); + } + + /* + * Deal with static const char g_szFmt[] = "qwerty"; Take care as + * the actual string constant may not necessarily include the terminator. + */ + tree hArraySize = NULL_TREE; + if ( VAR_P(hFmtArg) + && TREE_CODE(TREE_TYPE(hFmtArg)) == ARRAY_TYPE) + { + tree hArrayInitializer = decl_constant_value(hFmtArg); + if ( hArrayInitializer != hFmtArg + && TREE_CODE(hArrayInitializer) == STRING_CST) + { + hArraySize = DECL_SIZE_UNIT(hFmtArg); + hFmtArg = hArrayInitializer; + } + } + + /* + * Are we dealing with a string literal now? + */ + if (TREE_CODE(hFmtArg) != STRING_CST) + dprintf("checker1: Not string literal (%s)\n", get_tree_code_name(TREE_CODE(hFmtArg))); + else if (TYPE_MAIN_VARIANT(TREE_TYPE(TREE_TYPE(hFmtArg))) != char_type_node) + warning_at(pState->hFmtLoc, 0, "expected 'char' type string literal"); + else + { + /* + * Yes we are, so get the pointer to the string and its length. + */ + const char *pszFmt = TREE_STRING_POINTER(hFmtArg); + int cchFmt = TREE_STRING_LENGTH(hFmtArg); + + /* Adjust cchFmt to the initialized array size if appropriate. */ + if (hArraySize != NULL_TREE) + { + if (TREE_CODE(hArraySize) != INTEGER_CST) + warning_at(pState->hFmtLoc, 0, "Expected integer array size (not %s)", get_tree_code_name(TREE_CODE(hArraySize))); + else if (!MY_DOUBLE_INT_FITS_SHWI(hArraySize)) + warning_at(pState->hFmtLoc, 0, "Unexpected integer overflow in array size constant"); + else + { + HOST_WIDE_INT cbArray = MY_DOUBLE_INT_TO_SHWI(hArraySize); + if ( cbArray <= 0 + || cbArray != (int)cbArray) + warning_at(pState->hFmtLoc, 0, "Unexpected integer array size constant value: %ld", cbArray); + else if (cchFmt > cbArray) + { + dprintf("checker1: cchFmt=%d => cchFmt=%ld (=cbArray)\n", cchFmt, cbArray); + cchFmt = (int)cbArray; + } + } + } + + /* Apply the offset, if given. */ + if (off) + { + if (off >= cchFmt) + { + dprintf("checker1: off=%ld >= cchFmt=%d -> skipping\n", off, cchFmt); + return; + } + pszFmt += off; + cchFmt -= (int)off; + } + + /* + * Check for unterminated strings. + */ + if ( cchFmt < 1 + || pszFmt[cchFmt - 1] != '\0') + warning_at(pState->hFmtLoc, 0, "Unterminated format string (cchFmt=%d)", cchFmt); + /* + * Call worker to check the actual string. + */ + else + MyCheckFormatCString(pState, pszFmt); + } + } +} + + +/** + * Deal recursively with special format string constructs. + * + * This will call MyCheckFormatNonRecursive to validate each format string. + * + * @param pState The format string checking state. + * @param hFmtArg The format string node. + */ +static void MyCheckFormatRecursive(PVFMTCHKSTATE pState, tree hFmtArg) +{ + /* + * Catch wrong attribute use. + */ + if (hFmtArg == NULL_TREE) + error_at(pState->hFmtLoc, "IPRT format attribute is probably used incorrectly (hFmtArg is NULL)"); + /* + * NULL format strings may cause crashes. + */ + else if (integer_zerop(hFmtArg)) + { + if (pState->fMaybeNull) + VFmtChkVerifyEndOfArgs(pState, 0); + else + error_at(MY_LOC(hFmtArg, pState), "Format string should not be NULL"); + } + /* + * Check both branches of a ternary operator. + */ + else if (TREE_CODE(hFmtArg) == COND_EXPR) + { + MyCheckFormatRecursive(pState, TREE_OPERAND(hFmtArg, 1)); + MyCheckFormatRecursive(pState, TREE_OPERAND(hFmtArg, 2)); + } + /* + * Strip coercion. + */ + else if ( CONVERT_EXPR_P(hFmtArg) + && TYPE_PRECISION(TREE_TYPE(hFmtArg)) == TYPE_PRECISION(TREE_TYPE(TREE_OPERAND(hFmtArg, 0))) ) + MyCheckFormatRecursive(pState, TREE_OPERAND(hFmtArg, 0)); + /* + * We're good, hand it to the non-recursive worker. + */ + else + MyCheckFormatNonRecursive(pState, hFmtArg); +} + + +#if !RT_GNUC_PREREQ(4, 9) +/** + * Execute my pass. + * @returns Flags indicates stuff todo, we return 0. + */ +static unsigned int MyPassExecuteCallback(void) +{ + return MyPassExecuteCallbackWithFunction(cfun); +} +#endif + +/** + * Execute my pass. + * @returns Flags indicates stuff todo, we return 0. + */ +static unsigned int MyPassExecuteCallbackWithFunction(struct function *pFun) +{ + dprintf("MyPassExecuteCallback:\n"); + + /* + * Enumerate the basic blocks. + */ + basic_block hBasicBlock; + FOR_EACH_BB_FN(hBasicBlock, pFun) + { + dprintf(" hBasicBlock=%p\n", hBasicBlock); + + /* + * Enumerate the statements in the current basic block. + * We're interested in calls to functions with the __iprt_format__ attribute. + */ + for (gimple_stmt_iterator hStmtItr = gsi_start_bb(hBasicBlock); !gsi_end_p(hStmtItr); gsi_next(&hStmtItr)) + { +#if RT_GNUC_PREREQ(6, 0) + const gimple * const hStmt = gsi_stmt(hStmtItr); +#else + gimple const hStmt = gsi_stmt(hStmtItr); +#endif + + enum gimple_code const enmCode = gimple_code(hStmt); +#ifdef DEBUG + unsigned const cOps = gimple_num_ops(hStmt); + dprintf(" hStmt=%p %s (%d) ops=%d\n", hStmt, gimple_code_name[enmCode], enmCode, cOps); + for (unsigned iOp = 0; iOp < cOps; iOp++) + { + tree const hOp = gimple_op(hStmt, iOp); + if (hOp) + dprintf(" %02d: %p, code %s(%d)\n", iOp, hOp, get_tree_code_name(TREE_CODE(hOp)), TREE_CODE(hOp)); + else + dprintf(" %02d: NULL_TREE\n", iOp); + } +#endif + if (enmCode == GIMPLE_CALL) + { + /* + * Check if the function type has the __iprt_format__ attribute. + */ + tree const hFn = gimple_call_fn(hStmt); + dprintf(" hFn =%p %s(%d); args=%d\n", + hFn, hFn ? get_tree_code_name(TREE_CODE(hFn)) : NULL, hFn ? TREE_CODE(hFn) : - 1, + gimple_call_num_args(hStmt)); +#ifdef DEBUG + if (hFn && DECL_P(hFn)) + dprintf(" hFn is decl: %s %s:%d\n", + DECL_NAME(hFn) ? IDENTIFIER_POINTER(DECL_NAME(hFn)) : "", + DECL_SOURCE_FILE(hFn), DECL_SOURCE_LINE(hFn)); +#endif + tree const hFnDecl = gimple_call_fndecl(hStmt); + if (hFnDecl) + dprintf(" hFnDecl=%p %s(%d) %s type=%p %s:%d\n", + hFnDecl, get_tree_code_name(TREE_CODE(hFnDecl)), TREE_CODE(hFnDecl), + DECL_NAME(hFnDecl) ? IDENTIFIER_POINTER(DECL_NAME(hFnDecl)) : "", + TREE_TYPE(hFnDecl), DECL_SOURCE_FILE(hFnDecl), DECL_SOURCE_LINE(hFnDecl)); + tree const hFnType = gimple_call_fntype(hStmt); + if (hFnType == NULL_TREE) + { + if ( hFnDecl == NULL_TREE + && gimple_call_internal_p(hStmt) /* va_arg() kludge */) + continue; + error_at(gimple_location(hStmt), "Failed to resolve function type [fn=%s fndecl=%s]\n", + hFn ? get_tree_code_name(TREE_CODE(hFn)) : "", + hFnDecl ? get_tree_code_name(TREE_CODE(hFnDecl)) : ""); + } + else if (POINTER_TYPE_P(hFnType)) + error_at(gimple_location(hStmt), "Got a POINTER_TYPE when expecting a function type [fn=%s]\n", + get_tree_code_name(TREE_CODE(hFn))); + if (hFnType) + dprintf(" hFnType=%p %s(%d) %s\n", hFnType, get_tree_code_name(TREE_CODE(hFnType)), TREE_CODE(hFnType), + TYPE_NAME(hFnType) && DECL_NAME(TYPE_NAME(hFnType)) + ? IDENTIFIER_POINTER(DECL_NAME(TYPE_NAME(hFnType))) : ""); + + tree const hAttr = hFnType ? lookup_attribute("iprt_format", TYPE_ATTRIBUTES(hFnType)) : NULL_TREE; + tree const hAttrMaybe0 = hFnType ? lookup_attribute("iprt_format_maybe_null", TYPE_ATTRIBUTES(hFnType)) : NULL_TREE; + if (hAttr || hAttrMaybe0) + { + /* + * Yeah, it has the attribute! + */ + tree const hAttrArgs = hAttr ? TREE_VALUE(hAttr) : TREE_VALUE(hAttrMaybe0); + VFMTCHKSTATE State; + State.iFmt = MY_DOUBLE_INT_TO_SHWI(TREE_VALUE(hAttrArgs)); + State.iArgs = MY_DOUBLE_INT_TO_SHWI(TREE_VALUE(TREE_CHAIN(hAttrArgs))); + State.pszFmt = NULL; + State.fMaybeNull = hAttr == NULL_TREE; + State.hStmt = hStmt; + State.hFmtLoc = gimple_location(hStmt); + dprintf(" %s() __iprt_format%s__(iFmt=%ld, iArgs=%ld)\n", + hFnDecl && DECL_NAME(hFnDecl) ? IDENTIFIER_POINTER(DECL_NAME(hFnDecl)) : "", + State.fMaybeNull ? "_maybe_null" : "", State.iFmt, State.iArgs); + + unsigned cCallArgs = gimple_call_num_args(hStmt); + if (cCallArgs >= State.iFmt) + MyCheckFormatRecursive(&State, gimple_call_arg(hStmt, State.iFmt - 1)); + else + error_at(gimple_location(hStmt), + "Call has only %d arguments; %s() format string is argument #%lu (1-based), thus missing\n", + cCallArgs, DECL_NAME(hFnDecl) ? IDENTIFIER_POINTER(DECL_NAME(hFnDecl)) : "", State.iFmt); + } + } + } + } + return 0; +} + + +/** + * Gate callback for my pass that indicates whether it should execute or not. + * @returns true to execute. + */ +static bool MyPassGateCallback(void) +{ + dprintf("MyPassGateCallback:\n"); + return true; +} + + +/** + * Validate the use of an attribute. + * + * @returns ?? + * @param phOnNode The node the attribute is being used on. + * @param hAttrName The attribute name. + * @param hAttrArgs The attribute arguments. + * @param fFlags Some kind of flags... + * @param pfDontAddAttrib Whether to add the attribute to this node or not. + */ +static tree AttributeHandler(tree *phOnNode, tree hAttrName, tree hAttrArgs, int fFlags, bool *pfDontAddAttrib) +{ + dprintf("AttributeHandler: name=%s fFlags=%#x", IDENTIFIER_POINTER(hAttrName), fFlags); + long iFmt = MY_DOUBLE_INT_TO_SHWI(TREE_VALUE(hAttrArgs)); + long iArgs = MY_DOUBLE_INT_TO_SHWI(TREE_VALUE(TREE_CHAIN(hAttrArgs))); + dprintf(" iFmt=%ld iArgs=%ld", iFmt, iArgs); + + tree hType = *phOnNode; + dprintf(" hType=%p %s(%d)\n", hType, get_tree_code_name(TREE_CODE(hType)), TREE_CODE(hType)); + + if (pfDontAddAttrib) + *pfDontAddAttrib = false; + return NULL_TREE; +} + + +/** + * Called when we can register attributes. + * + * @param pvEventData Ignored. + * @param pvUser Ignored. + */ +static void RegisterAttributesEvent(void *pvEventData, void *pvUser) +{ + NOREF(pvEventData); NOREF(pvUser); + dprintf("RegisterAttributesEvent: pvEventData=%p\n", pvEventData); + + register_attribute(&g_AttribSpecs[0]); + register_attribute(&g_AttribSpecs[1]); +} + + +/** + * The plug-in entry point. + * + * @returns 0 to indicate success? + * @param pPlugInInfo Plugin info structure. + * @param pGccVer GCC Version. + */ +int plugin_init(plugin_name_args *pPlugInInfo, plugin_gcc_version *pGccVer) +{ + dprintf("plugin_init: %s\n", pPlugInInfo->full_name); + dprintf("gcc version: basever=%s datestamp=%s devphase=%s revision=%s\n", + pGccVer->basever, pGccVer->datestamp, pGccVer->devphase, pGccVer->revision); + + /* Ask for callback in which we may register the attribute. */ + register_callback(pPlugInInfo->base_name, PLUGIN_ATTRIBUTES, RegisterAttributesEvent, NULL /*pvUser*/); + + /* Register our pass. */ +#if RT_GNUC_PREREQ(4, 9) + /** The registration info for my pass. */ + struct register_pass_info MyPassInfo; + MyPassInfo.pass = new MyPass(g); + MyPassInfo.reference_pass_name = "ssa"; + MyPassInfo.ref_pass_instance_number = 1; + MyPassInfo.pos_op = PASS_POS_INSERT_BEFORE; + register_callback(pPlugInInfo->base_name, PLUGIN_PASS_MANAGER_SETUP, NULL, &MyPassInfo); +#else + register_callback(pPlugInInfo->base_name, PLUGIN_PASS_MANAGER_SETUP, NULL, (void *)&g_MyPassInfo); +#endif + + /* Register plug-in info. */ + register_callback(pPlugInInfo->base_name, PLUGIN_INFO, NULL, (void *)&g_PlugInInfo); + + return 0; +} + + + + +/* + * + * Functions used by the common code. + * Functions used by the common code. + * Functions used by the common code. + * + */ + +void VFmtChkWarnFmt(PVFMTCHKSTATE pState, const char *pszLoc, const char *pszFormat, ...) +{ + char szTmp[1024]; + va_list va; + va_start(va, pszFormat); + vsnprintf(szTmp, sizeof(szTmp), pszFormat, va); + va_end(va); + + /* display the warning. */ + warning_at(MyGetFormatStringLocation(pState, pszLoc), 0, "%s", szTmp); +} + + +void VFmtChkErrFmt(PVFMTCHKSTATE pState, const char *pszLoc, const char *pszFormat, ...) +{ + char szTmp[1024]; + va_list va; + va_start(va, pszFormat); + vsnprintf(szTmp, sizeof(szTmp), pszFormat, va); + va_end(va); + + /* display the warning. */ + error_at(MyGetFormatStringLocation(pState, pszLoc), "%s", szTmp); +} + + + +void VFmtChkVerifyEndOfArgs(PVFMTCHKSTATE pState, unsigned iArg) +{ + dprintf("VFmtChkVerifyEndOfArgs: iArg=%u iArgs=%ld cArgs=%u\n", iArg, pState->iArgs, gimple_call_num_args(pState->hStmt)); + if (pState->iArgs > 0) + { + iArg += pState->iArgs - 1; + unsigned cArgs = gimple_call_num_args(pState->hStmt); + if (iArg == cArgs) + { /* fine */ } + else if (iArg < cArgs) + { + tree hArg = gimple_call_arg(pState->hStmt, iArg); + if (cArgs - iArg > 1) + error_at(MY_LOC(hArg, pState), "%u extra arguments not consumed by format string", cArgs - iArg); + else if ( TREE_CODE(hArg) != INTEGER_CST + || !MY_DOUBLE_INT_FITS_SHWI(hArg) + || MY_DOUBLE_INT_TO_SHWI(hArg) != -99) /* ignore final dummy argument: ..., -99); */ + error_at(MY_LOC(hArg, pState), "one extra argument not consumed by format string"); + } + /* This should be handled elsewhere, but just in case. */ + else if (iArg - 1 == cArgs) + error_at(pState->hFmtLoc, "one argument too few"); + else + error_at(pState->hFmtLoc, "%u arguments too few", iArg - cArgs); + } +} + + +bool VFmtChkRequirePresentArg(PVFMTCHKSTATE pState, const char *pszLoc, unsigned iArg, const char *pszMessage) +{ + if (pState->iArgs > 0) + { + iArg += pState->iArgs - 1; + unsigned cArgs = gimple_call_num_args(pState->hStmt); + if (iArg >= cArgs) + { + VFmtChkErrFmt(pState, pszLoc, "Missing argument! %s", pszMessage); + return false; + } + + tree hArg = gimple_call_arg(pState->hStmt, iArg); + tree hType = TREE_TYPE(hArg); + dprintf("arg%u: hArg=%p [%s] hType=%p [%s] cls=%s\n", iArg, hArg, get_tree_code_name(TREE_CODE(hArg)), + hType, get_tree_code_name(TREE_CODE(hType)), tree_code_class_strings[TREE_CODE_CLASS(TREE_CODE(hType))]); + dprintf(" nm=%p\n", TYPE_NAME(hType)); + dprintf(" cb=%p %s value=%ld\n", TYPE_SIZE(hType), get_tree_code_name(TREE_CODE(TYPE_SIZE(hType))), + MY_DOUBLE_INT_TO_SHWI(TYPE_SIZE(hType)) ); + dprintf(" unit=%p %s value=%ld\n", TYPE_SIZE_UNIT(hType), get_tree_code_name(TREE_CODE(TYPE_SIZE_UNIT(hType))), + MY_DOUBLE_INT_TO_SHWI(TYPE_SIZE_UNIT(hType)) ); + tree hTypeNm = TYPE_NAME(hType); + if (hTypeNm) + dprintf(" typenm=%p %s '%s'\n", hTypeNm, get_tree_code_name(TREE_CODE(hTypeNm)), + IDENTIFIER_POINTER(DECL_NAME(hTypeNm))); + } + return true; +} + + +bool VFmtChkRequireIntArg(PVFMTCHKSTATE pState, const char *pszLoc, unsigned iArg, const char *pszMessage) +{ + if (VFmtChkRequirePresentArg(pState, pszLoc, iArg, pszMessage)) + { + /** @todo type check. */ + return true; + } + return false; +} + + +bool VFmtChkRequireStringArg(PVFMTCHKSTATE pState, const char *pszLoc, unsigned iArg, const char *pszMessage) +{ + if (VFmtChkRequirePresentArg(pState, pszLoc, iArg, pszMessage)) + { + /** @todo type check. */ + return true; + } + return false; +} + + +bool VFmtChkRequireVaListPtrArg(PVFMTCHKSTATE pState, const char *pszLoc, unsigned iArg, const char *pszMessage) +{ + if (VFmtChkRequirePresentArg(pState, pszLoc, iArg, pszMessage)) + { + /** @todo type check. */ + return true; + } + return false; +} + + +void VFmtChkHandleReplacementFormatString(PVFMTCHKSTATE pState, const char *pszPctM, unsigned iArg) +{ + if (pState->iArgs > 0) + { + pState->iFmt = pState->iArgs + iArg; + pState->iArgs = pState->iFmt + 1; + pState->fMaybeNull = false; + MyCheckFormatRecursive(pState, gimple_call_arg(pState->hStmt, pState->iFmt - 1)); + } +} + + +const char *VFmtChkGetFmtLocFile(PVFMTCHKSTATE pState) +{ + return LOCATION_FILE(pState->hFmtLoc); +} + + +unsigned int VFmtChkGetFmtLocLine(PVFMTCHKSTATE pState) +{ + return LOCATION_LINE(pState->hFmtLoc); +} + + +unsigned int VFmtChkGetFmtLocColumn(PVFMTCHKSTATE pState) +{ +#ifdef LOCATION_COLUMN + return LOCATION_COLUMN(pState->hFmtLoc); +#else + return 1; +#endif +} + diff --git a/src/bldprogs/VBoxDef2LazyLoad.cpp b/src/bldprogs/VBoxDef2LazyLoad.cpp new file mode 100644 index 00000000..8cd9b44e --- /dev/null +++ b/src/bldprogs/VBoxDef2LazyLoad.cpp @@ -0,0 +1,1624 @@ +/* $Id: VBoxDef2LazyLoad.cpp $ */ +/** @file + * VBoxDef2LazyLoad - Lazy Library Loader Generator. + * + * @note Only tested on win.amd64 & darwin.amd64. + */ + +/* + * Copyright (C) 2013-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include +#include +#include +#include +#include /* For RTLDRARCH. */ + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +typedef struct MYEXPORT +{ + struct MYEXPORT *pNext; + /** Pointer to unmangled name for stdcall (after szName), NULL if not. */ + char *pszUnstdcallName; + /** Pointer to the exported name. */ + char const *pszExportedNm; + unsigned uOrdinal; + bool fNoName; + char szName[1]; +} MYEXPORT; +typedef MYEXPORT *PMYEXPORT; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** @name Options + * @{ */ +static const char *g_pszOutput = NULL; +static const char *g_pszLibrary = NULL; +static const char *g_apszInputs[8] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; +static unsigned g_cInputs = 0; +static bool g_fIgnoreData = true; +static bool g_fWithExplictLoadFunction = false; +static bool g_fSystemLibrary = false; +#if defined(RT_ARCH_AMD64) +static RTLDRARCH g_enmTarget = RTLDRARCH_AMD64; +#elif defined(RT_ARCH_X86) +static RTLDRARCH g_enmTarget = RTLDRARCH_X86_32; +#elif defined(RT_ARCH_ARM64) +static RTLDRARCH g_enmTarget = RTLDRARCH_ARM64; +#else +# error "Port me!" +#endif +/** @} */ + +/** Pointer to the export name list head. */ +static PMYEXPORT g_pExpHead = NULL; +/** Pointer to the next pointer for insertion. */ +static PMYEXPORT *g_ppExpNext = &g_pExpHead; + + + +#if 0 /* unused */ +static const char *leftStrip(const char *psz) +{ + while (isspace(*psz)) + psz++; + return psz; +} +#endif + + +static char *leftStrip(char *psz) +{ + while (isspace(*psz)) + psz++; + return psz; +} + + +static unsigned wordLength(const char *pszWord) +{ + unsigned off = 0; + char ch; + while ( (ch = pszWord[off]) != '\0' + && ch != '=' + && ch != ',' + && ch != ':' + && !isspace(ch) ) + off++; + return off; +} + + +/** + * Parses the module definition file, collecting export information. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE, in the latter case full + * details has been displayed. + * @param pInput The input stream. + */ +static RTEXITCODE parseInputInner(FILE *pInput, const char *pszInput) +{ + /* + * Process the file line-by-line. + */ + bool fInExports = false; + unsigned iLine = 0; + char szLine[16384]; + while (fgets(szLine, sizeof(szLine), pInput)) + { + iLine++; + + /* + * Strip leading and trailing spaces from the line as well as + * trailing comments. + */ + char *psz = leftStrip(szLine); + if (*psz == ';') + continue; /* comment line. */ + + char *pszComment = strchr(psz, ';'); + if (pszComment) + *pszComment = '\0'; + + unsigned cch = (unsigned)strlen(psz); + while (cch > 0 && (isspace(psz[cch - 1]) || psz[cch - 1] == '\r' || psz[cch - 1] == '\n')) + psz[--cch] = '\0'; + + if (!cch) + continue; + + /* + * Check for known directives. + */ + size_t cchWord0 = wordLength(psz); +#define WORD_CMP(pszWord1, cchWord1, szWord2) \ + ( (cchWord1) == sizeof(szWord2) - 1 && memcmp(pszWord1, szWord2, sizeof(szWord2) - 1) == 0 ) + if (WORD_CMP(psz, cchWord0, "EXPORTS")) + { + fInExports = true; + + /* In case there is an export on the same line. (Really allowed?) */ + psz = leftStrip(psz + sizeof("EXPORTS") - 1); + if (!*psz) + continue; + } + /* Directives that we don't care about, but need to catch in order to + terminate the EXPORTS section in a timely manner. */ + else if ( WORD_CMP(psz, cchWord0, "NAME") + || WORD_CMP(psz, cchWord0, "LIBRARY") + || WORD_CMP(psz, cchWord0, "DESCRIPTION") + || WORD_CMP(psz, cchWord0, "STACKSIZE") + || WORD_CMP(psz, cchWord0, "SECTIONS") + || WORD_CMP(psz, cchWord0, "SEGMENTS") + || WORD_CMP(psz, cchWord0, "VERSION") + ) + { + fInExports = false; + } + + /* + * Process exports: + * entryname[=internalname] [@ordinal[ ][NONAME]] [DATA] [PRIVATE] + */ + if (fInExports) + { + const char *pchName = psz; + unsigned cchName = wordLength(psz); + + psz = leftStrip(psz + cchName); + if (*psz == '=') + { + psz = leftStrip(psz + 1); + psz = leftStrip(psz + wordLength(psz)); + } + + bool fNoName = false; + unsigned uOrdinal = ~0U; + if (*psz == '@') + { + psz++; + if (!isdigit(*psz)) + { + fprintf(stderr, "%s:%u: error: Invalid ordinal spec.\n", pszInput, iLine); + return RTEXITCODE_FAILURE; + } + uOrdinal = *psz++ - '0'; + while (isdigit(*psz)) + { + uOrdinal *= 10; + uOrdinal += *psz++ - '0'; + } + psz = leftStrip(psz); + cch = wordLength(psz); + if (WORD_CMP(psz, cch, "NONAME")) + { + fNoName = true; + psz = leftStrip(psz + cch); + } + } + + while (*psz) + { + cch = wordLength(psz); + if (WORD_CMP(psz, cch, "DATA")) + { + if (!g_fIgnoreData) + { + fprintf(stderr, "%s:%u: error: Cannot wrap up DATA export '%.*s'.\n", + pszInput, iLine, cchName, pchName); + return RTEXITCODE_SUCCESS; + } + } + else if (!WORD_CMP(psz, cch, "PRIVATE")) + { + fprintf(stderr, "%s:%u: error: Cannot wrap up DATA export '%.*s'.\n", + pszInput, iLine, cchName, pchName); + return RTEXITCODE_SUCCESS; + } + psz = leftStrip(psz + cch); + } + + /* + * Check for stdcall mangling. + */ + size_t cbExp = sizeof(MYEXPORT) + cchName; + unsigned cchStdcall = 0; + if (cchName > 3 && *pchName == '_' && isdigit(pchName[cchName - 1])) + { + if (cchName > 3 && pchName[cchName - 2] == '@') + cchStdcall = 2; + else if (cchName > 4 && pchName[cchName - 3] == '@' && isdigit(pchName[cchName - 2])) + cchStdcall = 3; + if (cchStdcall) + cbExp += cchName - 1 - cchStdcall; + } + + /* + * Add the export. + */ + + PMYEXPORT pExp = (PMYEXPORT)malloc(cbExp); + if (!pExp) + { + fprintf(stderr, "%s:%u: error: Out of memory.\n", pszInput, iLine); + return RTEXITCODE_SUCCESS; + } + memcpy(pExp->szName, pchName, cchName); + pExp->szName[cchName] = '\0'; + if (!cchStdcall) + { + pExp->pszUnstdcallName = NULL; + pExp->pszExportedNm = pExp->szName; + } + else + { + pExp->pszUnstdcallName = &pExp->szName[cchName + 1]; + memcpy(pExp->pszUnstdcallName, pchName + 1, cchName - 1 - cchStdcall); + pExp->pszUnstdcallName[cchName - 1 - cchStdcall] = '\0'; + pExp->pszExportedNm = pExp->pszUnstdcallName; + } + pExp->uOrdinal = uOrdinal; + pExp->fNoName = fNoName; + pExp->pNext = NULL; + *g_ppExpNext = pExp; + g_ppExpNext = &pExp->pNext; + } + } + + /* + * Why did we quit the loop, EOF or error? + */ + if (feof(pInput)) + return RTEXITCODE_SUCCESS; + fprintf(stderr, "error: Read while reading '%s' (iLine=%u).\n", pszInput, iLine); + return RTEXITCODE_FAILURE; +} + + +/** + * Parses a_apszInputs, populating the list pointed to by g_pExpHead. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE, in the latter case full + * details has been displayed. + */ +static RTEXITCODE parseInputs(void) +{ + RTEXITCODE rcExit = RTEXITCODE_SUCCESS; + for (unsigned i = 0; i < g_cInputs; i++) + { + FILE *pInput = fopen(g_apszInputs[i], "r"); + if (pInput) + { + RTEXITCODE rcExit2 = parseInputInner(pInput, g_apszInputs[i]); + fclose(pInput); + if (rcExit2 == RTEXITCODE_SUCCESS && !g_pExpHead) + { + fprintf(stderr, "error: Found no exports in '%s'.\n", g_apszInputs[i]); + rcExit2 = RTEXITCODE_FAILURE; + } + if (rcExit2 != RTEXITCODE_SUCCESS) + rcExit = rcExit2; + } + else + { + fprintf(stderr, "error: Failed to open '%s' for reading.\n", g_apszInputs[i]); + rcExit = RTEXITCODE_FAILURE; + } + } + return rcExit; +} + + +/** + * Generates the assembly source code for AMD64 and x86, writing it + * to @a pOutput. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE, in the latter case full + * details has been displayed. + * @param pOutput The output stream (caller checks it for errors + * when closing). + */ +static RTEXITCODE generateOutputInnerX86AndAMD64(FILE *pOutput) +{ + fprintf(pOutput, ";;\n"); + for (unsigned i = 0; i < g_cInputs; i++) + fprintf(pOutput, ";; Autogenerated from '%s'.\n", g_apszInputs[i]); + + fprintf(pOutput, + ";; DO NOT EDIT!\n" + ";;\n" + "\n" + "\n" + "%%include \"iprt/asmdefs.mac\"\n" + "\n" + "\n"); + + /* + * Put the thunks first for alignment and other reasons. It's the hot part of the code. + */ + fprintf(pOutput, + ";\n" + "; Thunks.\n" + ";\n" + "BEGINCODE\n"); + for (PMYEXPORT pExp = g_pExpHead; pExp; pExp = pExp->pNext) + if (!pExp->pszUnstdcallName) + fprintf(pOutput, + "BEGINPROC %s\n" + " jmp RTCCPTR_PRE [g_pfn%s xWrtRIP]\n" + "ENDPROC %s\n", + pExp->szName, pExp->szName, pExp->szName); + else + fprintf(pOutput, + "%%ifdef RT_ARCH_X86\n" + "global %s\n" + "%s:\n" + " jmp RTCCPTR_PRE [g_pfn%s xWrtRIP]\n" + "%%else\n" + "BEGINPROC %s\n" + " jmp RTCCPTR_PRE [g_pfn%s xWrtRIP]\n" + "ENDPROC %s\n" + "%%endif\n", + pExp->szName, pExp->szName, pExp->pszUnstdcallName, + pExp->pszUnstdcallName, pExp->pszUnstdcallName, pExp->pszUnstdcallName); + + fprintf(pOutput, + "\n" + "\n"); + + /* + * Import pointers + */ + fprintf(pOutput, + ";\n" + "; Import pointers. Initialized to point a lazy loading stubs.\n" + ";\n" + "BEGINDATA\n" + "g_apfnImports:\n"); + for (PMYEXPORT pExp = g_pExpHead; pExp; pExp = pExp->pNext) + if (pExp->pszUnstdcallName) + fprintf(pOutput, + "%%ifdef ASM_FORMAT_PE\n" + " %%ifdef RT_ARCH_X86\n" + "global __imp_%s\n" + "__imp_%s:\n" + " %%else\n" + "global __imp_%s\n" + "__imp_%s:\n" + " %%endif\n" + "%%endif\n" + "g_pfn%s RTCCPTR_DEF ___LazyLoad___%s\n" + "\n", + pExp->szName, + pExp->szName, + pExp->pszUnstdcallName, + pExp->pszUnstdcallName, + pExp->pszExportedNm, + pExp->pszExportedNm); + else + fprintf(pOutput, + "%%ifdef ASM_FORMAT_PE\n" + "global __imp_%s\n" + "__imp_%s:\n" + "%%endif\n" + "g_pfn%s RTCCPTR_DEF ___LazyLoad___%s\n" + "\n", + pExp->szName, + pExp->szName, + pExp->pszExportedNm, + pExp->pszExportedNm); + fprintf(pOutput, + "RTCCPTR_DEF 0 ; Terminator entry for traversal.\n" + "\n" + "\n"); + + /* + * Now for the less important stuff, starting with the names. + * + * We keep the names separate so we can traverse them in parallel to + * g_apfnImports in the load-everything routine further down. + */ + fprintf(pOutput, + ";\n" + "; Imported names.\n" + ";\n" + "BEGINCODE\n" + "g_szLibrary: db '%s',0\n" + "\n" + "g_szzNames:\n", + g_pszLibrary); + for (PMYEXPORT pExp = g_pExpHead; pExp; pExp = pExp->pNext) + if (!pExp->fNoName) + fprintf(pOutput, " g_sz%s:\n db '%s',0\n", pExp->pszExportedNm, pExp->pszExportedNm); + else + fprintf(pOutput, " g_sz%s:\n db '#%u',0\n", pExp->pszExportedNm, pExp->uOrdinal); + fprintf(pOutput, + "g_EndOfNames: db 0\n" + "\n" + "g_szFailLoadFmt: db 'Lazy loader failed to load \"%%s\": %%Rrc', 10, 0\n" + "g_szFailResolveFmt: db 'Lazy loader failed to resolve symbol \"%%s\" in \"%%s\": %%Rrc', 10, 0\n" + "\n" + "\n"); + + /* + * The per import lazy load code. + */ + fprintf(pOutput, + ";\n" + "; Lazy load+resolve stubs.\n" + ";\n" + "BEGINCODE\n"); + for (PMYEXPORT pExp = g_pExpHead; pExp; pExp = pExp->pNext) + { + if (!pExp->fNoName) + fprintf(pOutput, + "___LazyLoad___%s:\n" + /* "int3\n" */ + "%%ifdef RT_ARCH_AMD64\n" + " lea rax, [g_sz%s wrt rip]\n" + " lea r10, [g_pfn%s wrt rip]\n" + " call LazyLoadResolver\n" + "%%elifdef RT_ARCH_X86\n" + " push g_sz%s\n" + " push g_pfn%s\n" + " call LazyLoadResolver\n" + " add esp, 8h\n" + "%%else\n" + " %%error \"Unsupported architecture\"\n" + "%%endif\n" + , + pExp->pszExportedNm, + pExp->pszExportedNm, + pExp->pszExportedNm, + pExp->pszExportedNm, + pExp->pszExportedNm); + else + fprintf(pOutput, + "___LazyLoad___%s:\n" + /* "int3\n" */ + "%%ifdef RT_ARCH_AMD64\n" + " mov eax, %u\n" + " lea r10, [g_pfn%s wrt rip]\n" + " call LazyLoadResolver\n" + "%%elifdef RT_ARCH_X86\n" + " push %u\n" + " push g_pfn%s\n" + " call LazyLoadResolver\n" + " add esp, 8h\n" + "%%else\n" + " %%error \"Unsupported architecture\"\n" + "%%endif\n" + , + pExp->pszExportedNm, + pExp->uOrdinal, + pExp->pszExportedNm, + pExp->uOrdinal, + pExp->pszExportedNm); + if (!pExp->pszUnstdcallName) + fprintf(pOutput, " jmp NAME(%s)\n", pExp->szName); + else + fprintf(pOutput, + "%%ifdef RT_ARCH_X86\n" + " jmp %s\n" + "%%else\n" + " jmp NAME(%s)\n" + "%%endif\n" + , + pExp->szName, pExp->pszUnstdcallName); + fprintf(pOutput, "\n"); + } + fprintf(pOutput, + "\n" + "\n" + "\n"); + + /* + * The code that does the loading and resolving. + */ + fprintf(pOutput, + ";\n" + "; The module handle.\n" + ";\n" + "BEGINDATA\n" + "g_hMod RTCCPTR_DEF 0\n" + "\n" + "\n" + "\n"); + + /* + * How we load the module needs to be selectable later on. + * + * The LazyLoading routine returns the module handle in RCX/ECX, caller + * saved all necessary registers. + */ + if (!g_fSystemLibrary) + fprintf(pOutput, + ";\n" + ";SUPR3DECL(int) SUPR3HardenedLdrLoadAppPriv(const char *pszFilename, PRTLDRMOD phLdrMod,\n" + "; uint32_t fFlags, PRTERRINFO pErrInfo);\n" + ";\n" + "EXTERN_IMP2 SUPR3HardenedLdrLoadAppPriv\n" + "%%ifdef IN_RT_R3\n" + "extern NAME(RTAssertMsg2Weak)\n" + "%%else\n" + "EXTERN_IMP2 RTAssertMsg2Weak\n" + "%%endif\n" + "BEGINCODE\n" + "\n" + "LazyLoading:\n" + " mov xCX, [g_hMod xWrtRIP]\n" + " or xCX, xCX\n" + " jnz .return\n" + "\n" + "%%ifdef ASM_CALL64_GCC\n" + " xor rcx, rcx ; pErrInfo\n" + " xor rdx, rdx ; fFlags (local load)\n" + " lea rsi, [g_hMod wrt rip] ; phLdrMod\n" + " lea rdi, [g_szLibrary wrt rip] ; pszFilename\n" + " sub rsp, 08h\n" + " call IMP2(SUPR3HardenedLdrLoadAppPriv)\n" + " add rsp, 08h\n" + "\n" + "%%elifdef ASM_CALL64_MSC\n" + " xor r9, r9 ; pErrInfo\n" + " xor r8, r8 ; fFlags (local load)\n" + " lea rdx, [g_hMod wrt rip] ; phLdrMod\n" + " lea rcx, [g_szLibrary wrt rip] ; pszFilename\n" + " sub rsp, 28h\n" + " call IMP2(SUPR3HardenedLdrLoadAppPriv)\n" + " add rsp, 28h\n" + "\n" + "%%elifdef RT_ARCH_X86\n" + " sub xSP, 0ch\n" + " push 0 ; pErrInfo\n" + " push 0 ; fFlags (local load)\n" + " push g_hMod ; phLdrMod\n" + " push g_szLibrary ; pszFilename\n" + " call IMP2(SUPR3HardenedLdrLoadAppPriv)\n" + " add esp, 1ch\n" + "%%else\n" + " %%error \"Unsupported architecture\"\n" + "%%endif\n"); + else + fprintf(pOutput, + ";\n" + "; RTDECL(int) RTLdrLoadSystem(const char *pszFilename, bool fNoUnload, PRTLDRMOD phLdrMod);\n" + ";\n" + "%%ifdef IN_RT_R3\n" + "extern NAME(RTLdrLoadSystem)\n" + "extern NAME(RTAssertMsg2Weak)\n" + "%%else\n" + "EXTERN_IMP2 RTLdrLoadSystem\n" + "EXTERN_IMP2 RTAssertMsg2Weak\n" + "%%endif\n" + "BEGINCODE\n" + "\n" + "LazyLoading:\n" + " mov xCX, [g_hMod xWrtRIP]\n" + " or xCX, xCX\n" + " jnz .return\n" + "\n" + "%%ifdef ASM_CALL64_GCC\n" + " lea rdx, [g_hMod wrt rip] ; phLdrMod\n" + " mov esi, 1 ; fNoUnload=true\n" + " lea rdi, [g_szLibrary wrt rip] ; pszFilename\n" + " sub rsp, 08h\n" + " %%ifdef IN_RT_R3\n" + " call NAME(RTLdrLoadSystem)\n" + " %%else\n" + " call IMP2(RTLdrLoadSystem)\n" + " %%endif\n" + " add rsp, 08h\n" + "\n" + "%%elifdef ASM_CALL64_MSC\n" + " lea r8, [g_hMod wrt rip] ; phLdrMod\n" + " mov edx, 1 ; fNoUnload=true\n" + " lea rcx, [g_szLibrary wrt rip] ; pszFilename\n" + " sub rsp, 28h\n" + " %%ifdef IN_RT_R3\n" + " call NAME(RTLdrLoadSystem)\n" + " %%else\n" + " call IMP2(RTLdrLoadSystem)\n" + " %%endif\n" + " add rsp, 28h\n" + "\n" + "%%elifdef RT_ARCH_X86\n" + " push g_hMod ; phLdrMod\n" + " push 1 ; fNoUnload=true\n" + " push g_szLibrary ; pszFilename\n" + " %%ifdef IN_RT_R3\n" + " call NAME(RTLdrLoadSystem)\n" + " %%else\n" + " call IMP2(RTLdrLoadSystem)\n" + " %%endif\n" + " add esp, 0ch\n" + "%%else\n" + " %%error \"Unsupported architecture\"\n" + "%%endif\n"); + fprintf(pOutput, + " or eax, eax\n" + " jnz .badload\n" + " mov xCX, [g_hMod xWrtRIP]\n" + ".return:\n" + " ret\n" + "\n" + ".badload:\n" + "%%ifdef ASM_CALL64_GCC\n" + " mov edx, eax\n" + " lea rsi, [g_szLibrary wrt rip]\n" + " lea rdi, [g_szFailLoadFmt wrt rip]\n" + " sub rsp, 08h\n" + "%%elifdef ASM_CALL64_MSC\n" + " mov r8d, eax\n" + " lea rdx, [g_szLibrary wrt rip]\n" + " lea rcx, [g_szFailLoadFmt wrt rip]\n" + " sub rsp, 28h\n" + "%%elifdef RT_ARCH_X86\n" + " push eax\n" + " push g_szLibrary\n" + " push g_szFailLoadFmt\n" + "%%endif\n" + "%%ifdef IN_RT_R3\n" + " call NAME(RTAssertMsg2Weak)\n" + "%%else\n" + " call IMP2(RTAssertMsg2Weak)\n" + "%%endif\n" + ".badloadloop:\n" + " int3\n" + " jmp .badloadloop\n" + "LazyLoading_End:\n" + "\n" + "\n"); + + + fprintf(pOutput, + ";\n" + ";RTDECL(int) RTLdrGetSymbol(RTLDRMOD hLdrMod, const char *pszSymbol, void **ppvValue);\n" + ";\n" + "%%ifdef IN_RT_R3\n" + "extern NAME(RTLdrGetSymbol)\n" + "%%else\n" + "EXTERN_IMP2 RTLdrGetSymbol\n" + "%%endif\n" + "BEGINCODE\n" + "LazyLoadResolver:\n" + "%%ifdef RT_ARCH_AMD64\n" + " push rbp\n" + " mov rbp, rsp\n" + " push r15\n" + " push r14\n" + " mov r15, rax ; name\n" + " mov r14, r10 ; ppfn\n" + " push r9\n" + " push r8\n" + " push rcx\n" + " push rdx\n" + " push r12\n" + " %%ifdef ASM_CALL64_GCC\n" + " push rsi\n" + " push rdi\n" + " mov r12, rsp\n" + " %%else\n" + " mov r12, rsp\n" + " sub rsp, 20h\n" + " %%endif\n" + " and rsp, 0fffffff0h ; Try make sure the stack is aligned\n" + "\n" + " call LazyLoading ; returns handle in rcx\n" + " %%ifdef ASM_CALL64_GCC\n" + " mov rdi, rcx ; hLdrMod\n" + " mov rsi, r15 ; pszSymbol\n" + " mov rdx, r14 ; ppvValue\n" + " %%else\n" + " mov rdx, r15 ; pszSymbol\n" + " mov r8, r14 ; ppvValue\n" + " %%endif\n" + " %%ifdef IN_RT_R3\n" + " call NAME(RTLdrGetSymbol)\n" + " %%else\n" + " call IMP2(RTLdrGetSymbol)\n" + " %%endif\n" + " or eax, eax\n" + " jnz .badsym\n" + "\n" + " mov rsp, r12\n" + " %%ifdef ASM_CALL64_GCC\n" + " pop rdi\n" + " pop rsi\n" + " %%endif\n" + " pop r12\n" + " pop rdx\n" + " pop rcx\n" + " pop r8\n" + " pop r9\n" + " pop r14\n" + " pop r15\n" + " leave\n" + "\n" + "%%elifdef RT_ARCH_X86\n" + " push ebp\n" + " mov ebp, esp\n" + " push eax\n" + " push ecx\n" + " push edx\n" + " and esp, 0fffffff0h\n" + "\n" + ".loaded:\n" + " call LazyLoading ; returns handle in ecx\n" + " push dword [ebp + 8] ; value addr\n" + " push dword [ebp + 12] ; symbol name\n" + " push ecx\n" + " %%ifdef IN_RT_R3\n" + " call NAME(RTLdrGetSymbol)\n" + " %%else\n" + " call IMP2(RTLdrGetSymbol)\n" + " %%endif\n" + " or eax, eax\n" + " jnz .badsym\n" + " lea esp, [ebp - 0ch]\n" + " pop edx\n" + " pop ecx\n" + " pop eax\n" + " leave\n" + "%%else\n" + " %%error \"Unsupported architecture\"\n" + "%%endif\n" + " ret\n" + "\n" + ".badsym:\n" + "%%ifdef ASM_CALL64_GCC\n" + " mov ecx, eax\n" + " lea rdx, [g_szLibrary wrt rip]\n" + " mov rsi, r15\n" + " lea rdi, [g_szFailResolveFmt wrt rip]\n" + " sub rsp, 08h\n" + "%%elifdef ASM_CALL64_MSC\n" + " mov r9d, eax\n" + " mov r8, r15\n" + " lea rdx, [g_szLibrary wrt rip]\n" + " lea rcx, [g_szFailResolveFmt wrt rip]\n" + " sub rsp, 28h\n" + "%%elifdef RT_ARCH_X86\n" + " push eax\n" + " push dword [ebp + 12]\n" + " push g_szLibrary\n" + " push g_szFailResolveFmt\n" + "%%endif\n" + "%%ifdef IN_RT_R3\n" + " call NAME(RTAssertMsg2Weak)\n" + "%%else\n" + " call IMP2(RTAssertMsg2Weak)\n" + "%%endif\n" + ".badsymloop:\n" + " int3\n" + " jmp .badsymloop\n" + "\n" + "LazyLoadResolver_End:\n" + "\n" + "\n" + ); + + + + /* + * C callable method for explicitly loading the library and optionally + * resolving all the imports. + */ + if (g_fWithExplictLoadFunction) + { + if (g_fSystemLibrary) /* Lazy bird. */ + { + fprintf(stderr, "error: cannot use --system with --explicit-load-function, sorry\n"); + return RTEXITCODE_FAILURE; + } + + int cchLibBaseName = (int)(strchr(g_pszLibrary, '.') ? strchr(g_pszLibrary, '.') - g_pszLibrary : strlen(g_pszLibrary)); + fprintf(pOutput, + ";;\n" + "; ExplicitlyLoad%.*s(bool fResolveAllImports, pErrInfo);\n" + ";\n" + "EXTERN_IMP2 RTErrInfoSet\n" + "BEGINCODE\n" + "BEGINPROC ExplicitlyLoad%.*s\n" + " push xBP\n" + " mov xBP, xSP\n" + " push xBX\n" + "%%ifdef ASM_CALL64_GCC\n" + " %%define pszCurStr r14\n" + " push r14\n" + "%%else\n" + " %%define pszCurStr xDI\n" + " push xDI\n" + "%%endif\n" + " sub xSP, 40h\n" + "\n" + " ;\n" + " ; Save parameters on stack (64-bit only).\n" + " ;\n" + "%%ifdef ASM_CALL64_GCC\n" + " mov [xBP - xCB * 3], rdi ; fResolveAllImports\n" + " mov [xBP - xCB * 4], rsi ; pErrInfo\n" + "%%elifdef ASM_CALL64_MSC\n" + " mov [xBP - xCB * 3], rcx ; fResolveAllImports\n" + " mov [xBP - xCB * 4], rdx ; pErrInfo\n" + "%%endif\n" + "\n" + " ;\n" + " ; Is the module already loaded?\n" + " ;\n" + " cmp RTCCPTR_PRE [g_hMod xWrtRIP], 0\n" + " jnz .loaded\n" + "\n" + " ;\n" + " ; Load the module.\n" + " ;\n" + "%%ifdef ASM_CALL64_GCC\n" + " mov rcx, [xBP - xCB * 4] ; pErrInfo\n" + " xor rdx, rdx ; fFlags (local load)\n" + " lea rsi, [g_hMod wrt rip] ; phLdrMod\n" + " lea rdi, [g_szLibrary wrt rip] ; pszFilename\n" + " call IMP2(SUPR3HardenedLdrLoadAppPriv)\n" + "\n" + "%%elifdef ASM_CALL64_MSC\n" + " mov r9, [xBP - xCB * 4] ; pErrInfo\n" + " xor r8, r8 ; fFlags (local load)\n" + " lea rdx, [g_hMod wrt rip] ; phLdrMod\n" + " lea rcx, [g_szLibrary wrt rip] ; pszFilename\n" + " call IMP2(SUPR3HardenedLdrLoadAppPriv)\n" + "\n" + "%%elifdef RT_ARCH_X86\n" + " sub xSP, 0ch\n" + " push dword [xBP + 12] ; pErrInfo\n" + " push 0 ; fFlags (local load)\n" + " push g_hMod ; phLdrMod\n" + " push g_szLibrary ; pszFilename\n" + " call IMP2(SUPR3HardenedLdrLoadAppPriv)\n" + " add esp, 1ch\n" + "%%else\n" + " %%error \"Unsupported architecture\"\n" + "%%endif\n" + " or eax, eax\n" + " jnz .return\n" + "\n" + " ;\n" + " ; Resolve the imports too if requested to do so.\n" + " ;\n" + ".loaded:\n" + "%%ifdef ASM_ARCH_X86\n" + " cmp byte [xBP + 8], 0\n" + "%%else\n" + " cmp byte [xBP - xCB * 3], 0\n" + "%%endif\n" + " je .return\n" + "\n" + " lea pszCurStr, [g_szzNames xWrtRIP]\n" + " lea xBX, [g_apfnImports xWrtRIP]\n" + ".next_import:\n" + " cmp RTCCPTR_PRE [xBX], 0\n" + " je .return\n" + "%%ifdef ASM_CALL64_GCC\n" + " mov rdx, xBX ; ppvValue\n" + " mov rsi, pszCurStr ; pszSymbol\n" + " mov rdi, [g_hMod wrt rip] ; hLdrMod\n" + " call IMP2(RTLdrGetSymbol)\n" + "%%elifdef ASM_CALL64_MSC\n" + " mov r8, xBX ; ppvValue\n" + " mov rdx, pszCurStr ; pszSymbol\n" + " mov rcx, [g_hMod wrt rip] ; pszSymbol\n" + " call IMP2(RTLdrGetSymbol)\n" + "%%else\n" + " push xBX ; ppvValue\n" + " push pszCurStr ; pszSymbol\n" + " push RTCCPTR_PRE [g_hMod] ; hLdrMod\n" + " call IMP2(RTLdrGetSymbol)\n" + " add xSP, 0ch\n" + "%%endif\n" + " or eax, eax\n" + " jnz .symbol_error\n" + "\n" + " ; Advance.\n" + " add xBX, RTCCPTR_CB\n" + " xor eax, eax\n" + " mov xCX, 0ffffffffh\n" + "%%ifdef ASM_CALL64_GCC\n" + " mov xDI, pszCurStr\n" + " repne scasb\n" + " mov pszCurStr, xDI\n" + "%%else\n" + " repne scasb\n" + "%%endif\n" + " jmp .next_import\n" + "\n" + " ;\n" + " ; Error loading a symbol. Call RTErrInfoSet on pErrInfo (preserves eax).\n" + " ;\n" + ".symbol_error:\n" + "%%ifdef ASM_CALL64_GCC\n" + " mov rdx, pszCurStr ; pszMsg\n" + " mov esi, eax ; rc\n" + " mov rdi, [xBP - xCB * 4] ; pErrInfo\n" + " call IMP2(RTErrInfoSet)\n" + "%%elifdef ASM_CALL64_MSC\n" + " mov r8, pszCurStr ; pszMsg\n" + " mov edx, eax ; rc\n" + " mov rcx, [xBP - xCB * 4] ; pErrInfo\n" + " call IMP2(RTErrInfoSet)\n" + "%%else\n" + " push pszCurStr ; pszMsg\n" + " push eax ; pszSymbol\n" + " push dword [xBP + 0ch] ; pErrInfo\n" + " call IMP2(RTErrInfoSet)\n" + " add xSP, 0ch\n" + "%%endif\n" + " " + "\n" + ".return:\n" + " mov pszCurStr, [xBP - xCB * 2]\n" + " mov xBX, [xBP - xCB * 1]\n" + " leave\n" + " ret\n" + "ENDPROC ExplicitlyLoad%.*s\n" + "\n" + "\n" + , + cchLibBaseName, g_pszLibrary, + cchLibBaseName, g_pszLibrary, + cchLibBaseName, g_pszLibrary + ); + } + + + return RTEXITCODE_SUCCESS; +} + + +/** + * Generates the assembly source code for ARM64, writing it + * to @a pOutput. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE, in the latter case full + * details has been displayed. + * @param pOutput The output stream (caller checks it for errors + * when closing). + */ +static RTEXITCODE generateOutputInnerArm64(FILE *pOutput) +{ +// bool fMachO = true; +// bool fDarwin = true; + const char *pszNmPfx = "_"; + + fprintf(pOutput, ";;\n"); + for (unsigned i = 0; i < g_cInputs; i++) + fprintf(pOutput, ";; Autogenerated from '%s'.\n", g_apszInputs[i]); + + fprintf(pOutput, + ";; DO NOT EDIT!\n" + ";;\n" + "\n" + "\n" + /*"%%include \"iprt/asmdefs.mac\"\n"*/ + "\n" + "\n"); + + /* + * Put the thunks first for alignment and other reasons. It's the hot part of the code. + */ + fprintf(pOutput, + ";\n" + "; Thunks.\n" + ";\n" + ".section __TEXT,__text,regular,pure_instructions\n"); + for (PMYEXPORT pExp = g_pExpHead; pExp; pExp = pExp->pNext) + fprintf(pOutput, + ".p2align 3\n" + ".globl %s%s\n" + "%s%s:\n" + " adrp x9, %sg_pfn%s@PAGE\n" + " ldr x9, [x9, %sg_pfn%s@PAGEOFF]\n" + " br x9\n", + pszNmPfx, pExp->szName, pszNmPfx, pExp->szName, pszNmPfx, pExp->szName, pszNmPfx, pExp->szName); + fprintf(pOutput, + "\n" + "\n"); + + /* + * Import pointers + */ + fprintf(pOutput, + ";\n" + "; Import pointers. Initialized to point a lazy loading stubs.\n" + ";\n" + ".section __DATA,__data\n" + ".p2align 3\n" + "g_apfnImports:\n"); + for (PMYEXPORT pExp = g_pExpHead; pExp; pExp = pExp->pNext) + fprintf(pOutput, + ".globl __imp_%s\n" + "__imp_%s:\n" + ".globl %sg_pfn%s\n" + "%sg_pfn%s:\n" + " .quad ___LazyLoad___%s\n" + "\n", + pExp->szName, pExp->szName, + pszNmPfx, pExp->szName, pszNmPfx, pExp->szName, + pExp->pszExportedNm); + fprintf(pOutput, + " .quad 0 ; Terminator entry for traversal.\n" + "\n" + "\n"); + + /* + * Now for the less important stuff, starting with the names. + * + * We keep the names separate so we can traverse them in parallel to + * g_apfnImports in the load-everything routine further down. + */ + fprintf(pOutput, + ";\n" + "; Imported names.\n" + ";\n" + ".section __TEXT,__cstring,cstring_literals\n" + "g_szLibrary:\n" + " .asciz \"%s\"\n" + "\n" + "g_szzNames:\n", + g_pszLibrary); + for (PMYEXPORT pExp = g_pExpHead; pExp; pExp = pExp->pNext) + if (!pExp->fNoName) + fprintf(pOutput, " g_sz%s:\n .asciz \"%s\"\n", pExp->pszExportedNm, pExp->pszExportedNm); + else + fprintf(pOutput, " g_sz%s:\n .asciz \"#%u\"\n", pExp->pszExportedNm, pExp->uOrdinal); + fprintf(pOutput, + "g_EndOfNames: .byte 0\n" + "\n" + "g_szFailLoadFmt: .asciz \"Lazy loader failed to load \\\"%%s\\\": %%Rrc\\n\"\n" + "g_szFailResolveFmt: .asciz \"Lazy loader failed to resolve symbol \\\"%%s\\\" in \\\"%%s\\\": %%Rrc\\n\"\n" + "\n" + "\n"); + + /* + * The per import lazy load code. + */ + fprintf(pOutput, + ";\n" + "; Lazy load+resolve stubs.\n" + ";\n" + ".section __TEXT,__text,regular,pure_instructions\n" + ".p2align 3\n"); + for (PMYEXPORT pExp = g_pExpHead; pExp; pExp = pExp->pNext) + { + if (!pExp->fNoName) + fprintf(pOutput, + "___LazyLoad___%s:\n" + " adrp x9, g_sz%s@PAGE\n" + " add x9, x9, g_sz%s@PAGEOFF\n" + " adrp x10, %sg_pfn%s@PAGE\n" + " add x10, x10, %sg_pfn%s@PAGEOFF\n" + " bl LazyLoadResolver\n" + , pExp->pszExportedNm, + pExp->pszExportedNm, pExp->pszExportedNm, + pszNmPfx, pExp->pszExportedNm, pszNmPfx, pExp->pszExportedNm); + else + fprintf(pOutput, + "___LazyLoad___%s:\n" + " movk w9, #%u\n" + " adrp x10, %sg_pfn%s@PAGE\n" + " add x10, x10, %sg_pfn%s@PAGEOFF\n" + , pExp->pszExportedNm, + pExp->uOrdinal, + pszNmPfx, pExp->pszExportedNm, pszNmPfx, pExp->pszExportedNm); + fprintf(pOutput, " b %s%s\n", pszNmPfx, pExp->szName); + fprintf(pOutput, "\n"); + } + fprintf(pOutput, + "\n" + "\n" + "\n"); + + /* + * The code that does the loading and resolving. + */ + fprintf(pOutput, + ";\n" + "; The module handle.\n" + ";\n" + ".section __DATA,__data\n" + "g_hMod:\n" + " .quad 0\n" + "\n" + "\n" + "\n"); + + /* + * Common lazy loader and resolved. + */ + fprintf(pOutput, + ";\n" + "; The resolver code.\n" + ";\n" + ".section __TEXT,__text,regular,pure_instructions\n" + ".p2align 3\n" + "LazyLoadResolver:\n" + " .cfi_startproc\n" + " ; Create frame.\n" + " sub sp, sp, #(16 + 192)\n" + " stp x29, x30, [sp, #192]\n" + " add x29, sp, #192\n" + " .cfi_def_cfa x29, 16\n" + " .cfi_offset x30, -8\n" + " .cfi_offset x29, -16\n" + " ; Save all argument registers and a handful of preserved ones.\n" + " stp x0, x1, [sp, #(192 - 16)]\n" + " .cfi_offset x0, -32\n" + " .cfi_offset x1, -24\n" + " stp x2, x3, [sp, #(192 - 32)]\n" + " .cfi_offset x3, -40\n" + " .cfi_offset x2, -48\n" + " stp x4, x5, [sp, #(192 - 48)]\n" + " .cfi_offset x6, -56\n" + " .cfi_offset x5, -64\n" + " stp x6, x7, [sp, #(192 - 64)]\n" + " .cfi_offset x7, -72\n" + " .cfi_offset x6, -80\n" + " stp x16, x17, [sp, #(192 - 80)]\n" + " .cfi_offset x17, -88\n" + " .cfi_offset x16, -96\n" + " stp x18, x19, [sp, #(192 - 96)]\n" + " .cfi_offset x19, -104\n" + " .cfi_offset x18, -112\n" + " stp x20, x21, [sp, #(192 - 112)]\n" + " .cfi_offset x21, -120\n" + " .cfi_offset x20, -128\n" + " stp x22, x23, [sp, #(192 - 128)]\n" + " .cfi_offset x23, -136\n" + " .cfi_offset x22, -144\n" + " str x8, [sp, #(192 - 144)]\n" + "\n" + " ; Shift the symbol name to x19 and g_pfnXXXX pointer to x20 as these are preserved registers\n" + " ; (in case we need to call LazyLoadModule/RTLdrLoad)\n" + " mov x19, x9\n" + " mov x20, x10\n" + "\n" + " ; Get the module handle and call RTLdrGetSymbol(RTLDRMOD hLdrMod, const char *pszSymbol, void **ppvValue)\n" + " adrp x0, g_hMod@PAGE\n" + " ldr x0, [x0, g_hMod@PAGEOFF]\n" + " cmp x0, #0\n" + " b.eq LazyLoading\n" + " mov x1, x19\n" + " mov x2, x20\n" + " bl %sRTLdrGetSymbol\n" + "\n" + " cmp w0, #0\n" + " b.eq Lreturn\n" + "\n" + "Lbadsym: ; Call sRTAssertMsg2Weak. Variadic (...) arguments are passed on the stack it seems.\n" + " mov x3, x0\n" + " adrp x2, g_szLibrary@PAGE\n" + " add x2, x2, g_szLibrary@PAGEOFF\n" + " mov x1, x19\n" + " adrp x0, g_szFailLoadFmt@PAGE\n" + " add x0, x0, g_szFailLoadFmt@PAGEOFF\n" + " stp x1, x2, [sp]\n" + " str x3, [sp, #16]\n" + " bl %sRTAssertMsg2Weak\n" + "Lbadsymloop:\n" + " brk #0x1\n" + " b Lbadsymloop\n" + + "Lreturn:\n" + " ; Restore saved register\n" + " ldr x8, [sp, #(192 - 144)]\n" + " .cfi_restore x8\n" + " ldp x22, x23, [sp, #(192 - 128)]\n" + " .cfi_restore x23\n" + " .cfi_restore x22\n" + " ldp x20, x21, [sp, #(192 - 112)]\n" + " .cfi_restore x21\n" + " .cfi_restore x20\n" + " ldp x18, x19, [sp, #(192 - 96)]\n" + " .cfi_restore x19\n" + " .cfi_restore x18\n" + " ldp x16, x17, [sp, #(192 - 80)]\n" + " .cfi_restore x17\n" + " .cfi_restore x18\n" + " ldp x6, x7, [sp, #(192 - 64)]\n" + " .cfi_restore x7\n" + " .cfi_restore x6\n" + " ldp x4, x5, [sp, #(192 - 48)]\n" + " .cfi_restore x5\n" + " .cfi_restore x4\n" + " ldp x2, x3, [sp, #(192 - 32)]\n" + " .cfi_restore x3\n" + " .cfi_restore x2\n" + " ldp x0, x1, [sp, #(192 - 16)]\n" + " .cfi_restore x1\n" + " .cfi_restore x0\n" + "\n" + " ldp x29, x30, [sp, #192]\n" + " .cfi_restore x29\n" + " .cfi_restore x30\n" + " add sp, sp, #(16 + 192)\n" + " ret\n" + " .cfi_endproc\n" + "\n" + "\n" + , pszNmPfx, pszNmPfx); + + fprintf(pOutput, + ";\n" + "; Loads the module.\n" + "; ASSUMES called from LazyLoadResolver where all relevant registers are already saved.\n" + ";\n" + "LazyLoading:\n" + " .cfi_startproc\n" + " ; Create frame.\n" + " sub sp, sp, #(16 + 48)\n" + " stp x29, x30, [sp, #48]\n" + " add x29, sp, #48\n" + " .cfi_def_cfa x29, 16\n" + " .cfi_offset x30, -8\n" + " .cfi_offset x29, -16\n" + "\n"); + + if (!g_fSystemLibrary) + fprintf(pOutput, + " ; Call SUPR3HardenedLdrLoadAppPriv(const char *pszFilename, PRTLDRMOD phLdrMod, uint32_t fFlags, PRTERRINFO pErrInfo);\n" + " mov x3, #0\n" + " mov x2, #0\n" + " adrp x1, g_hMod@PAGE\n" + " add x1, x1, g_hMod@PAGEOFF\n" + " adrp x0, g_szLibrary@PAGE\n" + " add x0, x0, g_szLibrary@PAGEOFF\n" + " bl %sSUPR3HardenedLdrLoadAppPriv\n" + , pszNmPfx); + else + fprintf(pOutput, + " ; Call RTLdrLoadSystem(const char *pszFilename, bool fNoUnload, PRTLDRMOD phLdrMod);\n" + " adrp x2, g_hMod@PAGE\n" + " add x2, x2, g_hMod@PAGEOFF\n" + " mov x1, #1\n" + " adrp x0, g_szLibrary@PAGE\n" + " add x0, x0, g_szLibrary@PAGEOFF\n" + " bl %sRTLdrLoadSystem\n" + , pszNmPfx); + + fprintf(pOutput, + " cmp w0, #0\n" + " b.eq Lload_return\n" + "\n" + "Lbadload: ; Call sRTAssertMsg2Weak. Variadic (...) arguments are passed on the stack it seems.\n" + " mov x2, x0\n" + " adrp x1, g_szLibrary@PAGE\n" + " add x1, x1, g_szLibrary@PAGEOFF\n" + " adrp x0, g_szFailResolveFmt@PAGE\n" + " add x0, x0, g_szFailResolveFmt@PAGEOFF\n" + " stp x1, x2, [sp]\n" + " bl %sRTAssertMsg2Weak\n" + "Lbadloadloop:\n" + " brk #0x1\n" + " b Lbadloadloop\n" + "Lload_return:\n" + " adrp x0, g_hMod@PAGE\n" + " ldr x0, [x0, g_hMod@PAGEOFF]\n" + " ldp x29, x30, [sp, #48]\n" + " .cfi_restore x29\n" + " .cfi_restore x30\n" + " add sp, sp, #(16 + 48)\n" + " ret\n" + " .cfi_endproc\n" + "\n" + "\n" + , pszNmPfx); + + /* + * C callable method for explicitly loading the library and optionally + * resolving all the imports. + */ + if (g_fWithExplictLoadFunction) + { + if (g_fSystemLibrary) /* Lazy bird. */ + { + fprintf(stderr, "error: cannot use --system with --explicit-load-function, sorry\n"); + return RTEXITCODE_FAILURE; + } + + int cchLibBaseName = (int)(strchr(g_pszLibrary, '.') ? strchr(g_pszLibrary, '.') - g_pszLibrary : strlen(g_pszLibrary)); + fprintf(pOutput, + ";;\n" + "; ExplicitlyLoad%.*s(bool fResolveAllImports, pErrInfo);\n" + ";\n" + ".section __TEXT,__text,regular,pure_instructions\n" + ".p2align 3\n" + ".globl %sExplicitlyLoad%.*s\n" + "%sExplicitlyLoad%.*s:\n" + " .cfi_startproc\n" + " ; Create frame.\n" + " sub sp, sp, #(16 + 96)\n" + " stp x29, x30, [sp, #96]\n" + " add x29, sp, #96\n" + " .cfi_def_cfa x29, 16\n" + " .cfi_offset x30, -8\n" + " .cfi_offset x29, -16\n" + "\n" + " stp x20, x21, [sp, #(96 - 16)]\n" + " .cfi_offset x21, -24\n" + " .cfi_offset x20, -32\n" + " stp x22, x23, [sp, #(96 - 32)]\n" + " .cfi_offset x23, -40\n" + " .cfi_offset x22, -48\n" + + " ; Save the input parameters.\n" + " mov x20, x0\n" + " mov x21, x1\n" + "\n" + " ;\n" + " ; Is the module already loaded?\n" + " ;\n" + " adrp x0, g_hMod@PAGE\n" + " ldr x0, [x0, g_hMod@PAGEOFF]\n" + " cmp x0, #0\n" + " b.ne Lexplicit_loaded_module\n" + "\n" + , + cchLibBaseName, g_pszLibrary, + pszNmPfx, cchLibBaseName, g_pszLibrary, + pszNmPfx, cchLibBaseName, g_pszLibrary); + fprintf(pOutput, + "Lexplicit_load_module:\n" + " ; Call SUPR3HardenedLdrLoadAppPriv(const char *pszFilename, PRTLDRMOD phLdrMod, uint32_t fFlags, PRTERRINFO pErrInfo);\n" + " mov x3, #0\n" + " mov x2, #0\n" + " adrp x1, g_hMod@PAGE\n" + " add x1, x1, g_hMod@PAGEOFF\n" + " adrp x0, g_szLibrary@PAGE\n" + " add x0, x0, g_szLibrary@PAGEOFF\n" + " bl %sSUPR3HardenedLdrLoadAppPriv\n" + " cmp x0, #0\n" + " b.ne Lexplicit_load_return\n" + "\n" + , pszNmPfx); + + fprintf(pOutput, + " ;\n" + " ; Resolve the imports too if requested to do so.\n" + " ;\n" + "Lexplicit_loaded_module:\n" + " cmp w20, #0\n" + " b.eq Lexplicit_load_return\n" + "\n" + " adrp x22, g_szzNames@PAGE\n" + " add x22, x22, g_szzNames@PAGEOFF\n" + " adrp x23, g_apfnImports@PAGE\n" + " add x23, x23, g_apfnImports@PAGEOFF\n" + "Lexplicit_load_next_import:\n" + " ldr x0, [x23]\n" + " cmp x0, #0\n" + " b.eq Lexplicit_load_return\n" + "\n" + " ; Get the module handle and call RTLdrGetSymbol(RTLDRMOD hLdrMod, const char *pszSymbol, void **ppvValue)\n" + " adrp x0, g_hMod@PAGE\n" + " ldr x0, [x0, g_hMod@PAGEOFF]\n" + " mov x1, x22\n" + " mov x2, x23\n" + " bl %sRTLdrGetSymbol\n" + " cmp x0, #0\n" + " b.ne Lexplicit_load_symbol_error\n" + "\n" + " ; Advance.\n" + " add x23, x23, #8\n" + "Lexplict_load_advance_string:\n" + " ldrb w0, [x22]\n" + " add x22, x22, #1\n" + " cmp w0, #0\n" + " b.ne Lexplict_load_advance_string\n" + " b Lexplicit_load_next_import\n" + "\n" + " ;\n" + " ; Error loading a symbol. Call RTErrInfoSet(PRTERRINFO pErrInfo, int rc, const char *pszMsg) on pErrInfo (preserves x0).\n" + " ;\n" + "Lexplicit_load_symbol_error:\n" + " mov x2, x22\n" + " mov x1, x0\n" + " mov x0, x21\n" + " bl %sRTErrInfoSet\n" + " b Lexplicit_load_return" + " " + "\n" + "Lexplicit_load_return:\n" + " ldp x22, x23, [sp, #(96 - 32)]\n" + " .cfi_restore x23\n" + " .cfi_restore x22\n" + " ldp x20, x21, [sp, #(96 - 16)]\n" + " .cfi_restore x21\n" + " .cfi_restore x20\n" + "\n" + " ldp x29, x30, [sp, #96]\n" + " .cfi_restore x29\n" + " .cfi_restore x30\n" + " add sp, sp, #(16 + 96)\n" + " ret\n" + " .cfi_endproc\n" + "\n" + "\n" + , pszNmPfx, pszNmPfx); + } + + return RTEXITCODE_SUCCESS; +} + + +/** + * Generates the assembly source code, writing it to g_pszOutput. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE, in the latter case full + * details has been displayed. + */ +static RTEXITCODE generateOutput(void) +{ + RTEXITCODE rcExit = RTEXITCODE_FAILURE; + FILE *pOutput = fopen(g_pszOutput, "w"); + if (pOutput) + { + switch (g_enmTarget) + { + case RTLDRARCH_AMD64: + case RTLDRARCH_X86_32: + rcExit = generateOutputInnerX86AndAMD64(pOutput); + break; + case RTLDRARCH_ARM64: + rcExit = generateOutputInnerArm64(pOutput); + break; + default: + rcExit = RTEXITCODE_FAILURE; + break; + } + if (fclose(pOutput)) + { + fprintf(stderr, "error: Error closing '%s'.\n", g_pszOutput); + rcExit = RTEXITCODE_FAILURE; + } + } + else + fprintf(stderr, "error: Failed to open '%s' for writing.\n", g_pszOutput); + return rcExit; +} + + +/** + * Displays usage information. + * + * @returns RTEXITCODE_SUCCESS. + * @param pszArgv0 The argv[0] string. + */ +static int usage(const char *pszArgv0) +{ + printf("usage: %s [options] --libary --output \n" + "\n" + "Options:\n" + " --explicit-load-function, --no-explicit-load-function\n" + " Whether to include the explicit load function, default is not to.\n" + "\n" + "Copyright (C) 2013-2016 Oracle Corporation\n" + , pszArgv0); + + return RTEXITCODE_SUCCESS; +} + + +int main(int argc, char **argv) +{ + /* + * Parse options. + */ + for (int i = 1; i < argc; i++) + { + const char *psz = argv[i]; + if (*psz == '-') + { + if (!strcmp(psz, "--output") || !strcmp(psz, "-o")) + { + if (++i >= argc) + { + fprintf(stderr, "syntax error: File name expected after '%s'.\n", psz); + return RTEXITCODE_SYNTAX; + } + g_pszOutput = argv[i]; + } + else if (!strcmp(psz, "--library") || !strcmp(psz, "-l")) + { + if (++i >= argc) + { + fprintf(stderr, "syntax error: Library name expected after '%s'.\n", psz); + return RTEXITCODE_SYNTAX; + } + g_pszLibrary = argv[i]; + } + else if (!strcmp(psz, "--explicit-load-function")) + g_fWithExplictLoadFunction = true; + else if (!strcmp(psz, "--no-explicit-load-function")) + g_fWithExplictLoadFunction = false; + else if (!strcmp(psz, "--system")) + g_fSystemLibrary = true; + /** @todo Support different load methods so this can be used on system libs and + * such if we like. */ + else if ( !strcmp(psz, "--help") + || !strcmp(psz, "-help") + || !strcmp(psz, "-h") + || !strcmp(psz, "-?") ) + return usage(argv[0]); + else if ( !strcmp(psz, "--version") + || !strcmp(psz, "-V")) + { + printf("$Revision: 153224 $\n"); + return RTEXITCODE_SUCCESS; + } + else + { + fprintf(stderr, "syntax error: Unknown option '%s'.\n", psz); + return RTEXITCODE_SYNTAX; + } + } + else + { + if (g_cInputs >= RT_ELEMENTS(g_apszInputs)) + { + fprintf(stderr, "syntax error: Too many input files, max is %d.\n", (int)RT_ELEMENTS(g_apszInputs)); + return RTEXITCODE_SYNTAX; + } + g_apszInputs[g_cInputs++] = argv[i]; + } + } + if (g_cInputs == 0) + { + fprintf(stderr, "syntax error: No input file specified.\n"); + return RTEXITCODE_SYNTAX; + } + if (!g_pszOutput) + { + fprintf(stderr, "syntax error: No output file specified.\n"); + return RTEXITCODE_SYNTAX; + } + if (!g_pszLibrary) + { + fprintf(stderr, "syntax error: No library name specified.\n"); + return RTEXITCODE_SYNTAX; + } + + /* + * Do the job. + */ + RTEXITCODE rcExit = parseInputs(); + if (rcExit == RTEXITCODE_SUCCESS) + rcExit = generateOutput(); + return rcExit; +} + diff --git a/src/bldprogs/VBoxEditCoffLib.cpp b/src/bldprogs/VBoxEditCoffLib.cpp new file mode 100644 index 00000000..6342d37a --- /dev/null +++ b/src/bldprogs/VBoxEditCoffLib.cpp @@ -0,0 +1,476 @@ +/* $Id: VBoxEditCoffLib.cpp $ */ +/** @file + * VBoxEditCoffLib - Simple COFF editor for library files. + */ + +/* + * Copyright (C) 2006-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include +#include + +#include +#include +#include +#include + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +typedef struct ARHDR +{ + char achName[16]; + char achDate[12]; + char achUid[6]; + char achGid[6]; + char achMode[8]; + char achSize[10]; + char achMagic[2]; +} ARHDR; +AssertCompileSize(ARHDR, 16+12+6+6+8+10+2); + + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Verbosity level. */ +static int g_cVerbosity = 0; + +/** The binary size. */ +static unsigned g_cbBinary = 0; +/** The binary data we're editing. */ +static uint8_t *g_pbBinary = NULL; + +/** Size of the currently selected member. */ +static unsigned g_cbMember = 0; +/** Pointer to the data for the currently selected member. */ +static uint8_t *g_pbMember = NULL; + + +/** + * File size. + * + * @returns file size in bytes. + * @returns 0 on failure. + * @param pFile File to size. + */ +static unsigned fsize(FILE *pFile) +{ + long cbFile; + off_t Pos = ftell(pFile); + if ( Pos >= 0 + && !fseek(pFile, 0, SEEK_END)) + { + cbFile = ftell(pFile); + if ( cbFile >= 0 + && !fseek(pFile, 0, SEEK_SET)) + return cbFile; + } + return 0; +} + + +/** + * Reports a problem. + * + * @returns RTEXITCODE_FAILURE + */ +static int error(const char *pszFormat, ...) +{ + fprintf(stderr, "error: "); + va_list va; + va_start(va, pszFormat); + vfprintf(stderr, pszFormat, va); + va_end(va); + return RTEXITCODE_FAILURE; +} + + +/** + * Reports a syntax problem. + * + * @returns RTEXITCODE_SYNTAX + */ +static int syntax(const char *pszFormat, ...) +{ + fprintf(stderr, "syntax error: "); + va_list va; + va_start(va, pszFormat); + vfprintf(stderr, pszFormat, va); + va_end(va); + return RTEXITCODE_FAILURE; +} + + +/** + * Display usage + * + * @returns success if stdout, syntax error if stderr. + */ +static int usage(FILE *pOut, const char *argv0) +{ + fprintf(pOut, + "usage: %s --input --output [options and operations]\n" + "\n" + "Operations and Options (processed in place):\n" + " --verbose Noisier.\n" + " --quiet Quiet execution.\n" + " --select \n" + " Selects archive member which name ends in the given string.\n" + " --redefine-sym =\n" + " Redefine the symbol to .\n" + " Note! the length must be the same!\n" + , argv0); + return pOut == stdout ? RTEXITCODE_SUCCESS : RTEXITCODE_SYNTAX; +} + + +/** + * Helper for SelectMember. + */ +static bool AreAllDigits(const char *pch, size_t cch, size_t *puValue) +{ + *puValue = 0; + do + { + if (!RT_C_IS_DIGIT(*pch)) + return false; + *puValue = *puValue * 10 + *pch - '0'; + pch++; + cch--; + } while (cch > 0); + return true; +} + + +/** + * Selects archive member ending with the given name. + * + * Updates g_cbMember and g_pbMember. + */ +static int SelectMember(const char *pszEndsWith) +{ + size_t const cchEndsWith = strlen(pszEndsWith); + + /* + * Check the header. + */ + if (memcmp(g_pbBinary, RT_STR_TUPLE("!\n"))) + return error("Not an AR library!\n"); + + /* + * Work the members. + */ + const char *pszStringTab = NULL; + size_t cbStringTab = 0; + for (size_t off = sizeof("!\n") - 1; off < g_cbBinary;) + { + ARHDR *pHdr = (ARHDR *)&g_pbBinary[off]; + char szTmp[16 + 8]; + size_t uValue; + char *pszIgn; +#define COPY_AND_TRIM(a_pchSrc, a_cbSrc) do { \ + memcpy(szTmp, (a_pchSrc), (a_cbSrc)); \ + size_t offCopy = (a_cbSrc); \ + while (offCopy > 0 && (szTmp[offCopy - 1] == ' ' || szTmp[offCopy - 1] == '\0')) \ + offCopy--; \ + szTmp[offCopy] = '\0'; \ + } while (0) + + /* + * Parse the header. + */ + + /* The size: */ + COPY_AND_TRIM(pHdr->achSize, sizeof(pHdr->achSize)); + size_t cbFile = strtol(szTmp, &pszIgn, 10); + + /* The name: */ + size_t cbExtra = 0; + size_t cchName = sizeof(pHdr->achName); + const char *pchName = pHdr->achName; + if ( pchName[0] == '#' + && pchName[1] == '1' + && pchName[2] == '/') + { + COPY_AND_TRIM(&pchName[3], cchName - 3); + cchName = cbExtra = strtol(szTmp, &pszIgn, 10); + pchName = (char *)(pHdr + 1); + } + else + { + while (cchName > 0 && (pchName[cchName - 1] == ' ' || pchName[cchName - 1] == '\0')) + cchName--; + + /* Long filename string table? */ + if ( (cchName == 2 && pchName[0] == '/' && pchName[1] == '/') + || (cchName == sizeof("ARFILENAMES/") - 1 && memcmp(pchName, RT_STR_TUPLE("ARFILENAMES/")) == 0)) + { + pszStringTab = (char *)(pHdr + 1); + cbStringTab = cbFile; + } + /* Long filename string table reference? */ + else if ( cchName >= 2 + && ( pchName[0] == '/' /* System V */ + || pchName[0] == ' ' /* Other */) + && AreAllDigits(&pchName[1], cchName - 1, &uValue) && uValue < cbStringTab) + { + pchName = &pszStringTab[uValue]; + cchName = strlen(pchName); /** @todo unsafe! */ + } + /* Drop trailing slash in case of System V filename: */ + else if (cchName > 1 && pchName[cchName - 1] == '/') + cchName -= 1; + } + + if (g_cVerbosity > 2) + fprintf(stderr, "debug: %#08x: %#010x %*.*s\n", + (unsigned)off, (unsigned)(cbFile - cbExtra), (int)cchName, (int)cchName, pchName); + + /* + * Do matching. + */ + if ( cchName >= cchEndsWith + && strncmp(&pchName[cchName - cchEndsWith], pszEndsWith, cchEndsWith) == 0) + { + g_pbMember = (uint8_t *)(pHdr + 1) + cbExtra; + g_cbMember = (unsigned)(cbFile - cbExtra); + if (g_cVerbosity > 1) + fprintf(stderr, "debug: selected '%*.*s': %#x LB %#x\n", + (int)cchName, (int)cchName, pchName, (unsigned)(off + sizeof(*pHdr) + cbExtra), g_cbMember); + return 0; + } + + /* + * Advance. + */ + off += sizeof(ARHDR) + cbFile + (cbFile & 1); + } + + return error("No member ending with '%s' was found!\n", pszEndsWith); +} + + +/** + * @note Borrowed from VBoxBs3objConverter.cpp + */ +static const char *coffGetSymbolName(PCIMAGE_SYMBOL pSym, const char *pchStrTab, uint32_t cbStrTab, char pszShortName[16]) +{ + if (pSym->N.Name.Short != 0) + { + memcpy(pszShortName, pSym->N.ShortName, 8); + pszShortName[8] = '\0'; + return pszShortName; + } + if (pSym->N.Name.Long < cbStrTab) + { + uint32_t const cbLeft = cbStrTab - pSym->N.Name.Long; + const char *pszRet = pchStrTab + pSym->N.Name.Long; + if (memchr(pszRet, '\0', cbLeft) != NULL) + return pszRet; + } + error("Invalid string table index %#x!\n", pSym->N.Name.Long); + return "Invalid Symbol Table Entry"; +} + + +/** + * Redefine a symbol with a different name. + */ +static int RedefineSymbol(const char *pszOldEqualNew) +{ + /* + * Check state and split up the input. + */ + if (!g_pbMember) + return error("No selected archive member!\n"); + + const char *pszNew = strchr(pszOldEqualNew, '='); + if (!pszNew || pszNew[1] == '\0') + return error("Malformed 'old=new' argument: %s\n", pszOldEqualNew); + const char *pszOld = pszOldEqualNew; + size_t const cchOld = pszNew - pszOldEqualNew; + pszNew += 1; + size_t const cchNew = strlen(pszNew); + if (cchNew > cchOld) + return error("The new symbol must not be longer than the old symbol: %#x vs %#x (%s)\n", cchNew, cchOld, pszOldEqualNew); + + if (g_cVerbosity > 2) + fprintf(stderr, "debug: redefining symbol '%*.*s' to '%*.*s'...\n", + (int)cchOld, (int)cchOld, pszOld, (int)cchNew, (int)cchNew, pszNew); + + /* + * Parse COFF header. + */ + const IMAGE_FILE_HEADER *pHdr = (const IMAGE_FILE_HEADER *)g_pbMember; + if (sizeof(*pHdr) >= g_cbMember) + return error("member too small for COFF\n"); + if ( pHdr->Machine != IMAGE_FILE_MACHINE_AMD64 + && pHdr->Machine != IMAGE_FILE_MACHINE_I386) + return error("Unsupported COFF machine: %#x\n", pHdr->Machine); + if ( pHdr->PointerToSymbolTable >= g_cbMember + || pHdr->PointerToSymbolTable < sizeof(*pHdr)) + return error("PointerToSymbolTable is out of bounds: %#x, max %#x\n", pHdr->PointerToSymbolTable, g_cbMember); + unsigned const cSymbols = pHdr->NumberOfSymbols; + if ( cSymbols >= g_cbMember - pHdr->PointerToSymbolTable + || cSymbols * sizeof(IMAGE_SYMBOL) > g_cbMember - pHdr->PointerToSymbolTable) + return error("PointerToSymbolTable + NumberOfSymbols is out of bounds: %#x + %#x * %#x (%#x), max %#x\n", + pHdr->PointerToSymbolTable, cSymbols, sizeof(IMAGE_SYMBOL), + pHdr->PointerToSymbolTable + cSymbols * sizeof(IMAGE_SYMBOL), g_cbMember); + + /* + * Work the symbol table. + */ + unsigned cRenames = 0; + PIMAGE_SYMBOL const paSymTab = (PIMAGE_SYMBOL)&g_pbMember[pHdr->PointerToSymbolTable]; + const char * const pchStrTab = (const char *)&paSymTab[pHdr->NumberOfSymbols]; + uint32_t const cbStrTab = (uint32_t)((uintptr_t)&g_pbMember[g_cbMember] - (uintptr_t)pchStrTab); + for (unsigned iSym = 0; iSym < cSymbols; iSym++) + { + char szShort[16]; + const char *pszSymName = coffGetSymbolName(&paSymTab[iSym], pchStrTab, cbStrTab, szShort); + size_t cchSymName = strlen(pszSymName); + if (g_cVerbosity > 3 && cchSymName > 0) + fprintf(stderr, "debug: symbol %u: %s\n", iSym, pszSymName); + if ( cchSymName == cchOld + && memcmp(pszSymName, pszOld, cchSymName) == 0) + { + size_t const offStrTab = (size_t)(pszSymName - pchStrTab); + if (offStrTab < cbStrTab) + { + if (g_cVerbosity > 1) + fprintf(stderr, "debug: Found symbol '%s' in at string table offset %#x, renaming to '%s'.\n", + pszSymName, (uint32_t)offStrTab, pszNew); + if (offStrTab > 0 && pchStrTab[offStrTab - 1] != '\0') + return error("Cannot rename sub-string!\n"); + memset((char *)pszSymName, 0, cchOld); + memcpy((char *)pszSymName, pszNew, cchNew); + } + else + { + if (g_cVerbosity > 1) + fprintf(stderr, "debug: Found symbol '%s' in symbol table, renaming to '%s'.\n", pszSymName, pszNew); + memset(paSymTab[iSym].N.ShortName, 0, sizeof(paSymTab[iSym].N.ShortName)); + memcpy(paSymTab[iSym].N.ShortName, pszNew, cchNew); + } + cRenames++; + } + + /* Skip AUX symbols. */ + uint8_t cAuxSyms = paSymTab[iSym].NumberOfAuxSymbols; + while (cAuxSyms-- > 0) + iSym++; + } + + if (cRenames > 0) + return RTEXITCODE_SUCCESS; + return error("Symbol '%*.*s' was not found!\n", cchOld, cchOld, pszOld); +} + + +int main(int argc, char **argv) +{ + /* + * Parse arguments. + */ + const char *pszIn = NULL; + const char *pszOut = NULL; + for (int i = 1; i < argc; i++) + { + const char *pszArg = argv[i]; + + /* Options without values first: */ + if ( strcmp(pszArg, "--verbose") == 0 + || strcmp(pszArg, "-v") == 0) + g_cVerbosity += 1; + else if ( strcmp(pszArg, "--quiet") == 0 + || strcmp(pszArg, "--q") == 0) + g_cVerbosity = 0; + else if ( strcmp(pszArg, "--help") == 0 + || strcmp(pszArg, "-h") == 0 + || strcmp(pszArg, "-?") == 0) + return usage(stdout, argv[0]); + else if (i + 1 >= argc) + return syntax("Missing argument value or unknown option '%s'!\n", pszArg); + else + { + i++; + const char *pszValue = argv[i]; + int rc = 0; + if (strcmp(pszArg, "--input") == 0) + { + if (pszIn) + return syntax("--input can only be specified once!\n"); + pszIn = pszValue; + + /* Load it into memory: */ + FILE *pIn = fopen(pszIn, "rb"); + if (!pIn) + return error("Failed to open '%s' for reading!\n", pszIn); + g_cbBinary = fsize(pIn); + if (!g_cbBinary) + return error("Failed to determin the size of '%s'!\n", pszIn); + if (g_cbBinary > _128M) + return error("'%s' is too large: %x, max %x\n", g_cbBinary, (size_t)_128M); + g_pbBinary = (uint8_t *)calloc(1, g_cbBinary + 4096); + if (!g_pbBinary) + return error("Out of memory!\n"); + if (fread(g_pbBinary, g_cbBinary, 1, pIn) != 1) + return error("Failed to read '%s' into memory!\n", pszIn); + fclose(pIn); + } + else if (strcmp(pszArg, "--output") == 0) + pszOut = pszValue; + else if (strcmp(pszArg, "--select") == 0) + rc = SelectMember(pszValue); + else if (strcmp(pszArg, "--redefine-sym") == 0) + rc = RedefineSymbol(pszValue); + else + return syntax("Unknown option: %s\n", pszArg); + if (rc != RTEXITCODE_SUCCESS) + return rc; + } + } + + if (!pszIn || !pszOut) + return syntax("No %s specified!\n", pszIn ? "output file" : "intput library file"); + + /* + * Write out the result. + */ + FILE *pOut = fopen(pszOut, "wb"); + if (!pOut) + return error("Failed to open '%s' for writing!\n", pszOut); + if (fwrite(g_pbBinary, g_cbBinary, 1, pOut) != 1) + return error("Error writing %#x bytes to '%s'!\n", g_cbBinary, pszOut); + if (fclose(pOut) != 0) + return error("Error closing '%s'!\n", pszOut); + return RTEXITCODE_SUCCESS; +} + diff --git a/src/bldprogs/VBoxPeSetVersion.cpp b/src/bldprogs/VBoxPeSetVersion.cpp new file mode 100644 index 00000000..9104412c --- /dev/null +++ b/src/bldprogs/VBoxPeSetVersion.cpp @@ -0,0 +1,404 @@ +/* $Id: VBoxPeSetVersion.cpp $ */ +/** @file + * IPRT - Change the OS and SubSystem version to value suitable for NT v3.1. + * + * Also make sure the IAT is writable, since NT v3.1 expects this. These are + * tricks necessary to make binaries created by newer Visual C++ linkers work + * on ancient NT version like W2K, NT4 and NT 3.x. + */ + +/* + * Copyright (C) 2012-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include +#include +#include +#include + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#define MK_VER(a_uHi, a_uLo) ( ((a_uHi) << 8) | (a_uLo)) + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +static const char *g_pszFilename; +static unsigned g_cVerbosity = 0; + + +static int Error(const char *pszFormat, ...) +{ + va_list va; + va_start(va, pszFormat); + char szTmp[1024]; + _vsnprintf(szTmp, sizeof(szTmp), pszFormat, va); + va_end(va); + fprintf(stderr, "VBoxPeSetVersion: %s: error: %s\n", g_pszFilename, szTmp); + return RTEXITCODE_FAILURE; +} + + +static void Info(unsigned iLevel, const char *pszFormat, ...) +{ + if (iLevel <= g_cVerbosity) + { + va_list va; + va_start(va, pszFormat); + char szTmp[1024]; + _vsnprintf(szTmp, sizeof(szTmp), pszFormat, va); + va_end(va); + fprintf(stderr, "VBoxPeSetVersion: %s: info: %s\n", g_pszFilename, szTmp); + } +} + + +static int UpdateFile(FILE *pFile, unsigned uNtVersion, PIMAGE_SECTION_HEADER *ppaShdr) +{ + /* + * Locate and read the PE header. + * + * Note! We'll be reading the 64-bit size even for 32-bit since the difference + * is 16 bytes, which is less than a section header, so it won't be a problem. + */ + unsigned long offNtHdrs; + { + IMAGE_DOS_HEADER MzHdr; + if (fread(&MzHdr, sizeof(MzHdr), 1, pFile) != 1) + return Error("Failed to read MZ header: %s", strerror(errno)); + if (MzHdr.e_magic != IMAGE_DOS_SIGNATURE) + return Error("Invalid MZ magic: %#x", MzHdr.e_magic); + offNtHdrs = MzHdr.e_lfanew; + } + + if (fseek(pFile, offNtHdrs, SEEK_SET) != 0) + return Error("Failed to seek to PE header at %#lx: %s", offNtHdrs, strerror(errno)); + union + { + IMAGE_NT_HEADERS32 x32; + IMAGE_NT_HEADERS64 x64; + } NtHdrs, + NtHdrsNew; + if (fread(&NtHdrs, sizeof(NtHdrs), 1, pFile) != 1) + return Error("Failed to read PE header at %#lx: %s", offNtHdrs, strerror(errno)); + + /* + * Validate it a little bit. + */ + if (NtHdrs.x32.Signature != IMAGE_NT_SIGNATURE) + return Error("Invalid PE signature: %#x", NtHdrs.x32.Signature); + uint32_t cbNewHdrs; + if (NtHdrs.x32.FileHeader.Machine == IMAGE_FILE_MACHINE_AMD64) + { + if (NtHdrs.x64.FileHeader.SizeOfOptionalHeader != sizeof(NtHdrs.x64.OptionalHeader)) + return Error("Invalid optional header size: %#x", NtHdrs.x64.FileHeader.SizeOfOptionalHeader); + if (NtHdrs.x64.OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR64_MAGIC) + return Error("Invalid optional header magic: %#x", NtHdrs.x64.OptionalHeader.Magic); + if (!uNtVersion) + uNtVersion = MK_VER(5, 2); + else if (uNtVersion < MK_VER(5, 2)) + return Error("Selected version is too old for AMD64: %u.%u", uNtVersion >> 8, uNtVersion & 0xff); + cbNewHdrs = sizeof(NtHdrsNew.x64); + } + else if (NtHdrs.x32.FileHeader.Machine != IMAGE_FILE_MACHINE_I386) + return Error("Not I386 or AMD64 machine: %#x", NtHdrs.x32.FileHeader.Machine); + else + { + if (NtHdrs.x32.FileHeader.SizeOfOptionalHeader != sizeof(NtHdrs.x32.OptionalHeader)) + return Error("Invalid optional header size: %#x", NtHdrs.x32.FileHeader.SizeOfOptionalHeader); + if (NtHdrs.x32.OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR32_MAGIC) + return Error("Invalid optional header magic: %#x", NtHdrs.x32.OptionalHeader.Magic); + if (!uNtVersion) + uNtVersion = MK_VER(3, 10); + cbNewHdrs = sizeof(NtHdrsNew.x32); + } + + /* + * Do the header modifications. + */ + memcpy(&NtHdrsNew, &NtHdrs, sizeof(NtHdrsNew)); + NtHdrsNew.x32.OptionalHeader.MajorOperatingSystemVersion = uNtVersion >> 8; + NtHdrsNew.x32.OptionalHeader.MinorOperatingSystemVersion = uNtVersion & 0xff; + NtHdrsNew.x32.OptionalHeader.MajorSubsystemVersion = uNtVersion >> 8; + NtHdrsNew.x32.OptionalHeader.MinorSubsystemVersion = uNtVersion & 0xff; + AssertCompileMembersAtSameOffset(IMAGE_NT_HEADERS32, OptionalHeader.MajorOperatingSystemVersion, IMAGE_NT_HEADERS64, OptionalHeader.MajorOperatingSystemVersion); + AssertCompileMembersAtSameOffset(IMAGE_NT_HEADERS32, OptionalHeader.MinorOperatingSystemVersion, IMAGE_NT_HEADERS64, OptionalHeader.MinorOperatingSystemVersion); + AssertCompileMembersAtSameOffset(IMAGE_NT_HEADERS32, OptionalHeader.MajorSubsystemVersion, IMAGE_NT_HEADERS64, OptionalHeader.MajorSubsystemVersion); + AssertCompileMembersAtSameOffset(IMAGE_NT_HEADERS32, OptionalHeader.MinorSubsystemVersion, IMAGE_NT_HEADERS64, OptionalHeader.MinorSubsystemVersion); + + if (uNtVersion <= MK_VER(3, 50)) + { + NtHdrsNew.x32.OptionalHeader.MajorOperatingSystemVersion = 1; + NtHdrsNew.x32.OptionalHeader.MinorOperatingSystemVersion = 0; + AssertCompileMembersAtSameOffset(IMAGE_NT_HEADERS32, OptionalHeader.MajorOperatingSystemVersion, IMAGE_NT_HEADERS64, OptionalHeader.MajorOperatingSystemVersion); + AssertCompileMembersAtSameOffset(IMAGE_NT_HEADERS32, OptionalHeader.MinorOperatingSystemVersion, IMAGE_NT_HEADERS64, OptionalHeader.MinorOperatingSystemVersion); + } + + if (memcmp(&NtHdrsNew, &NtHdrs, sizeof(NtHdrs))) + { + /** @todo calc checksum. */ + NtHdrsNew.x32.OptionalHeader.CheckSum = 0; + AssertCompileMembersAtSameOffset(IMAGE_NT_HEADERS32, OptionalHeader.MinorOperatingSystemVersion, IMAGE_NT_HEADERS64, OptionalHeader.MinorOperatingSystemVersion); + + if ( NtHdrsNew.x32.OptionalHeader.MajorOperatingSystemVersion != NtHdrs.x32.OptionalHeader.MajorOperatingSystemVersion + || NtHdrsNew.x32.OptionalHeader.MinorOperatingSystemVersion != NtHdrs.x32.OptionalHeader.MinorOperatingSystemVersion) + Info(1,"OperatingSystemVersion %u.%u -> %u.%u", + NtHdrs.x32.OptionalHeader.MajorOperatingSystemVersion, NtHdrs.x32.OptionalHeader.MinorOperatingSystemVersion, + NtHdrsNew.x32.OptionalHeader.MajorOperatingSystemVersion, NtHdrsNew.x32.OptionalHeader.MinorOperatingSystemVersion); + if ( NtHdrsNew.x32.OptionalHeader.MajorSubsystemVersion != NtHdrs.x32.OptionalHeader.MajorSubsystemVersion + || NtHdrsNew.x32.OptionalHeader.MinorSubsystemVersion != NtHdrs.x32.OptionalHeader.MinorSubsystemVersion) + Info(1,"SubsystemVersion %u.%u -> %u.%u", + NtHdrs.x32.OptionalHeader.MajorSubsystemVersion, NtHdrs.x32.OptionalHeader.MinorSubsystemVersion, + NtHdrsNew.x32.OptionalHeader.MajorSubsystemVersion, NtHdrsNew.x32.OptionalHeader.MinorSubsystemVersion); + + if (fseek(pFile, offNtHdrs, SEEK_SET) != 0) + return Error("Failed to seek to PE header at %#lx: %s", offNtHdrs, strerror(errno)); + if (fwrite(&NtHdrsNew, cbNewHdrs, 1, pFile) != 1) + return Error("Failed to write PE header at %#lx: %s", offNtHdrs, strerror(errno)); + } + + /* + * Make the IAT writable for NT 3.1 and drop the non-cachable flag from .bss. + * + * The latter is a trick we use to prevent the linker from merging .data and .bss, + * because NT 3.1 does not honor Misc.VirtualSize and won't zero padd the .bss part + * if it's not zero padded in the file. This seemed simpler than adding zero padding. + */ + if ( uNtVersion <= MK_VER(3, 10) + && NtHdrsNew.x32.FileHeader.NumberOfSections > 0) + { + uint32_t cbShdrs = sizeof(IMAGE_SECTION_HEADER) * NtHdrsNew.x32.FileHeader.NumberOfSections; + PIMAGE_SECTION_HEADER paShdrs = (PIMAGE_SECTION_HEADER)calloc(1, cbShdrs); + if (!paShdrs) + return Error("Out of memory"); + *ppaShdr = paShdrs; + + unsigned long offShdrs = offNtHdrs + + RT_UOFFSETOF_DYN(IMAGE_NT_HEADERS32, + OptionalHeader.DataDirectory[NtHdrsNew.x32.OptionalHeader.NumberOfRvaAndSizes]); + if (fseek(pFile, offShdrs, SEEK_SET) != 0) + return Error("Failed to seek to section headers at %#lx: %s", offShdrs, strerror(errno)); + if (fread(paShdrs, cbShdrs, 1, pFile) != 1) + return Error("Failed to read section headers at %#lx: %s", offShdrs, strerror(errno)); + + bool fFoundBss = false; + uint32_t uRvaEnd = NtHdrsNew.x32.OptionalHeader.SizeOfImage; + uint32_t uRvaIat = NtHdrsNew.x32.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IAT].Size > 0 + ? NtHdrsNew.x32.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IAT].VirtualAddress : UINT32_MAX; + uint32_t i = NtHdrsNew.x32.FileHeader.NumberOfSections; + while (i-- > 0) + if (!(paShdrs[i].Characteristics & IMAGE_SCN_TYPE_NOLOAD)) + { + bool fModified = false; + if (uRvaIat >= paShdrs[i].VirtualAddress && uRvaIat < uRvaEnd) + { + if (!(paShdrs[i].Characteristics & IMAGE_SCN_MEM_WRITE)) + { + paShdrs[i].Characteristics |= IMAGE_SCN_MEM_WRITE; + fModified = true; + } + uRvaIat = UINT32_MAX; + } + + if ( !fFoundBss + && strcmp((const char *)paShdrs[i].Name, ".bss") == 0) + { + if (paShdrs[i].Characteristics & IMAGE_SCN_MEM_NOT_CACHED) + { + paShdrs[i].Characteristics &= ~IMAGE_SCN_MEM_NOT_CACHED; + fModified = true; + } + fFoundBss = true; + } + + if (fModified) + { + unsigned long offShdr = offShdrs + i * sizeof(IMAGE_SECTION_HEADER); + if (fseek(pFile, offShdr, SEEK_SET) != 0) + return Error("Failed to seek to section header #%u at %#lx: %s", i, offShdr, strerror(errno)); + if (fwrite(&paShdrs[i], sizeof(IMAGE_SECTION_HEADER), 1, pFile) != 1) + return Error("Failed to write %8.8s section header header at %#lx: %s", + paShdrs[i].Name, offShdr, strerror(errno)); + if (uRvaIat == UINT32_MAX && fFoundBss) + break; + } + + /* Advance */ + uRvaEnd = paShdrs[i].VirtualAddress; + } + + } + + return RTEXITCODE_SUCCESS; +} + + +static int Usage(FILE *pOutput) +{ + fprintf(pOutput, + "Usage: VBoxPeSetVersion [options] \n" + "Options:\n" + " -v, --verbose\n" + " Increases verbosity.\n" + " -q, --quiet\n" + " Quiet operation (default).\n" + " --nt31, --nt350, --nt351, --nt4, --w2k, --xp, --w2k3, --vista,\n" + " --w7, --w8, --w81, --w10\n" + " Which version to set. Default: --nt31\n" + ); + return RTEXITCODE_SYNTAX; +} + + +/** @todo Rewrite this so it can take options and print out error messages. */ +int main(int argc, char **argv) +{ + /* + * Parse arguments. + * This stucks + */ + unsigned uNtVersion = 0; + const char *pszFilename = NULL; + bool fAcceptOptions = true; + for (int i = 1; i < argc; i++) + { + const char *psz = argv[i]; + if (fAcceptOptions && *psz == '-') + { + char ch = psz[1]; + psz += 2; + if (ch == '-') + { + if (!*psz) + { + fAcceptOptions = false; + continue; + } + + if (strcmp(psz, "verbose") == 0) + ch = 'v'; + else if (strcmp(psz, "quiet") == 0) + ch = 'q'; + else if (strcmp(psz, "help") == 0) + ch = 'h'; + else if (strcmp(psz, "version") == 0) + ch = 'V'; + else + { + if (strcmp(psz, "nt31") == 0) + uNtVersion = MK_VER(3,10); + else if (strcmp(psz, "nt350") == 0) + uNtVersion = MK_VER(3,50); + else if (strcmp(psz, "nt351") == 0) + uNtVersion = MK_VER(3,51); + else if (strcmp(psz, "nt4") == 0) + uNtVersion = MK_VER(4,0); + else if (strcmp(psz, "w2k") == 0) + uNtVersion = MK_VER(5,0); + else if (strcmp(psz, "xp") == 0) + uNtVersion = MK_VER(5,1); + else if (strcmp(psz, "w2k3") == 0) + uNtVersion = MK_VER(5,2); + else if (strcmp(psz, "vista") == 0) + uNtVersion = MK_VER(6,0); + else if (strcmp(psz, "w7") == 0) + uNtVersion = MK_VER(6,1); + else if (strcmp(psz, "w8") == 0) + uNtVersion = MK_VER(6,2); + else if (strcmp(psz, "w81") == 0) + uNtVersion = MK_VER(6,3); + else if (strcmp(psz, "w10") == 0) + uNtVersion = MK_VER(10,0); + else + { + fprintf(stderr, "VBoxPeSetVersion: syntax error: Unknown option: --%s\n", psz); + return RTEXITCODE_SYNTAX; + } + continue; + } + psz = " "; + } + do + { + switch (ch) + { + case 'q': + g_cVerbosity = 0; + break; + case 'v': + g_cVerbosity++; + break; + case 'V': + printf("2.0\n"); + return RTEXITCODE_SUCCESS; + case 'h': + Usage(stdout); + return RTEXITCODE_SUCCESS; + default: + fprintf(stderr, "VBoxPeSetVersion: syntax error: Unknown option: -%c\n", ch ? ch : ' '); + return RTEXITCODE_SYNTAX; + } + } while ((ch = *psz++) != '\0'); + + } + else if (!pszFilename) + pszFilename = psz; + else + { + fprintf(stderr, "VBoxPeSetVersion: syntax error: More than one PE-image specified!\n"); + return RTEXITCODE_SYNTAX; + } + } + + if (!pszFilename) + { + fprintf(stderr, "VBoxPeSetVersion: syntax error: No PE-image specified!\n"); + return RTEXITCODE_SYNTAX; + } + g_pszFilename = pszFilename; + + /* + * Process the file. + */ + int rcExit; + FILE *pFile = fopen(pszFilename, "r+b"); + if (pFile) + { + PIMAGE_SECTION_HEADER paShdrs = NULL; + rcExit = UpdateFile(pFile, uNtVersion, &paShdrs); + if (paShdrs) + free(paShdrs); + if (fclose(pFile) != 0) + rcExit = Error("fclose failed on '%s': %s", pszFilename, strerror(errno)); + } + else + rcExit = Error("Failed to open '%s' for updating: %s", pszFilename, strerror(errno)); + return rcExit; +} + diff --git a/src/bldprogs/VBoxTpG.cpp b/src/bldprogs/VBoxTpG.cpp new file mode 100644 index 00000000..ab734937 --- /dev/null +++ b/src/bldprogs/VBoxTpG.cpp @@ -0,0 +1,2665 @@ +/* $Id: VBoxTpG.cpp $ */ +/** @file + * VBox Build Tool - VBox Tracepoint Generator. + */ + +/* + * Copyright (C) 2012-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "scmstream.h" + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +typedef struct VTGPROBE *PVTGPROBE; + +typedef struct VTGATTRS +{ + kVTGStability enmCode; + kVTGStability enmData; + kVTGClass enmDataDep; +} VTGATTRS; +typedef VTGATTRS *PVTGATTRS; + + +typedef struct VTGARG +{ + RTLISTNODE ListEntry; + /** The argument name. (heap) */ + char *pszName; + /** The type presented to the tracer (in string table). */ + const char *pszTracerType; + /** The argument type used in the probe method in that context. (heap) */ + char *pszCtxType; + /** Argument passing format string. First and only argument is the name. + * (const string) */ + const char *pszArgPassingFmt; + /** The type flags. */ + uint32_t fType; + /** The argument number (0-based) for complaining/whatever. */ + uint16_t iArgNo; + /** The probe the argument belongs to (for complaining/whatever). */ + PVTGPROBE pProbe; + /** The absolute source position. */ + size_t offSrc; +} VTGARG; +typedef VTGARG *PVTGARG; + +typedef struct VTGPROBE +{ + RTLISTNODE ListEntry; + char *pszMangledName; + const char *pszUnmangledName; + RTLISTANCHOR ArgHead; + uint32_t cArgs; + bool fHaveLargeArgs; + uint32_t offArgList; + uint32_t iProbe; + size_t iLine; +} VTGPROBE; + +typedef struct VTGPROVIDER +{ + RTLISTNODE ListEntry; + const char *pszName; + + uint16_t iFirstProbe; + uint16_t cProbes; + + VTGATTRS AttrSelf; + VTGATTRS AttrModules; + VTGATTRS AttrFunctions; + VTGATTRS AttrName; + VTGATTRS AttrArguments; + + RTLISTANCHOR ProbeHead; +} VTGPROVIDER; +typedef VTGPROVIDER *PVTGPROVIDER; + +/** + * A string table string. + */ +typedef struct VTGSTRING +{ + /** The string space core. */ + RTSTRSPACECORE Core; + /** The string table offset. */ + uint32_t offStrTab; + /** The actual string. */ + char szString[1]; +} VTGSTRING; +typedef VTGSTRING *PVTGSTRING; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** The string space organizing the string table strings. Each node is a VTGSTRING. */ +static RTSTRSPACE g_StrSpace = NULL; +/** Used by the string table enumerator to set VTGSTRING::offStrTab. */ +static uint32_t g_offStrTab; +/** List of providers created by the parser. */ +static RTLISTANCHOR g_ProviderHead; +/** The number of type errors. */ +static uint32_t g_cTypeErrors = 0; + + +/** @name Options + * @{ */ +static enum +{ + kVBoxTpGAction_Nothing, + kVBoxTpGAction_GenerateHeader, + kVBoxTpGAction_GenerateWrapperHeader, + kVBoxTpGAction_GenerateObject +} g_enmAction = kVBoxTpGAction_Nothing; +static uint32_t g_cBits = HC_ARCH_BITS; +static uint32_t g_cHostBits = HC_ARCH_BITS; +static uint32_t g_fTypeContext = VTG_TYPE_CTX_R0; +static const char *g_pszContextDefine = "IN_RING0"; +static const char *g_pszContextDefine2 = NULL; +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 = "-f"; +static const char g_szAssemblerFmtVal32[] = "macho32"; +static const char g_szAssemblerFmtVal64[] = "macho64"; +static const char g_szAssemblerOsDef[] = "RT_OS_DARWIN"; +#elif defined(RT_OS_OS2) +static const char *g_pszAssembler = "nasm.exe"; +static const char *g_pszAssemblerFmtOpt = "-f"; +static const char g_szAssemblerFmtVal32[] = "obj"; +static const char g_szAssemblerFmtVal64[] = "elf64"; +static const char g_szAssemblerOsDef[] = "RT_OS_OS2"; +#elif defined(RT_OS_WINDOWS) +static const char *g_pszAssembler = "yasm.exe"; +static const char *g_pszAssemblerFmtOpt = "-f"; +static const char g_szAssemblerFmtVal32[] = "win32"; +static const char g_szAssemblerFmtVal64[] = "win64"; +static const char g_szAssemblerOsDef[] = "RT_OS_WINDOWS"; +#else +static const char *g_pszAssembler = "yasm"; +static const char *g_pszAssemblerFmtOpt = "-f"; +static const char g_szAssemblerFmtVal32[] = "elf32"; +static const char g_szAssemblerFmtVal64[] = "elf64"; +# ifdef RT_OS_FREEBSD +static const char g_szAssemblerOsDef[] = "RT_OS_FREEBSD"; +# elif defined(RT_OS_NETBSD) +static const char g_szAssemblerOsDef[] = "RT_OS_NETBSD"; +# elif defined(RT_OS_OPENBSD) +static const char g_szAssemblerOsDef[] = "RT_OS_OPENBSD"; +# elif defined(RT_OS_LINUX) +static const char g_szAssemblerOsDef[] = "RT_OS_LINUX"; +# elif defined(RT_OS_SOLARIS) +static const char g_szAssemblerOsDef[] = "RT_OS_SOLARIS"; +# else +# error "Port me!" +# endif +#endif +static const char *g_pszAssemblerFmtVal = RT_CONCAT(g_szAssemblerFmtVal, HC_ARCH_BITS); +static const char *g_pszAssemblerDefOpt = "-D"; +static const char *g_pszAssemblerIncOpt = "-I"; +static char g_szAssemblerIncVal[RTPATH_MAX]; +static const char *g_pszAssemblerIncVal = __FILE__ "/../../../include/"; +static const char *g_pszAssemblerOutputOpt = "-o"; +static unsigned g_cAssemblerOptions = 0; +static const char *g_apszAssemblerOptions[32]; +static const char *g_pszProbeFnName = "SUPR0TracerFireProbe"; +static bool g_fProbeFnImported = true; +static bool g_fPic = false; +/** @} */ + + + + +/** + * Inserts a string into the string table, reusing any matching existing string + * if possible. + * + * @returns Read only string. + * @param pch The string to insert (need not be terminated). + * @param cch The length of the string. + */ +static const char *strtabInsertN(const char *pch, size_t cch) +{ + PVTGSTRING pStr = (PVTGSTRING)RTStrSpaceGetN(&g_StrSpace, pch, cch); + if (pStr) + return pStr->szString; + + /* + * Create a new entry. + */ + pStr = (PVTGSTRING)RTMemAlloc(RT_UOFFSETOF_DYN(VTGSTRING, szString[cch + 1])); + if (!pStr) + return NULL; + + pStr->Core.pszString = pStr->szString; + memcpy(pStr->szString, pch, cch); + pStr->szString[cch] = '\0'; + pStr->offStrTab = UINT32_MAX; + + bool fRc = RTStrSpaceInsert(&g_StrSpace, &pStr->Core); + Assert(fRc); NOREF(fRc); + return pStr->szString; +} + + +/** + * Retrieves the string table offset of the given string table string. + * + * @returns String table offset. + * @param pszStrTabString The string table string. + */ +static uint32_t strtabGetOff(const char *pszStrTabString) +{ + PVTGSTRING pStr = RT_FROM_MEMBER(pszStrTabString, VTGSTRING, szString[0]); + Assert(pStr->Core.pszString == pszStrTabString); + return pStr->offStrTab; +} + + +/** + * Invokes the assembler. + * + * @returns Exit code. + * @param pszOutput The output file. + * @param pszTempAsm The source file. + */ +static RTEXITCODE generateInvokeAssembler(const char *pszOutput, const char *pszTempAsm) +{ + const char *apszArgs[64]; + unsigned iArg = 0; + + apszArgs[iArg++] = g_pszAssembler; + apszArgs[iArg++] = g_pszAssemblerFmtOpt; + apszArgs[iArg++] = g_pszAssemblerFmtVal; + apszArgs[iArg++] = g_pszAssemblerDefOpt; + if (!strcmp(g_pszAssemblerFmtVal, "macho32") || !strcmp(g_pszAssemblerFmtVal, "macho64")) + apszArgs[iArg++] = "ASM_FORMAT_MACHO"; + else if (!strcmp(g_pszAssemblerFmtVal, "obj") || !strcmp(g_pszAssemblerFmtVal, "omf")) + apszArgs[iArg++] = "ASM_FORMAT_OMF"; + else if ( !strcmp(g_pszAssemblerFmtVal, "win32") + || !strcmp(g_pszAssemblerFmtVal, "win64") + || !strcmp(g_pszAssemblerFmtVal, "pe32") + || !strcmp(g_pszAssemblerFmtVal, "pe64") + || !strcmp(g_pszAssemblerFmtVal, "pe") ) + apszArgs[iArg++] = "ASM_FORMAT_PE"; + else if ( !strcmp(g_pszAssemblerFmtVal, "elf32") + || !strcmp(g_pszAssemblerFmtVal, "elf64") + || !strcmp(g_pszAssemblerFmtVal, "elf")) + apszArgs[iArg++] = "ASM_FORMAT_ELF"; + else + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Unknown assembler format '%s'", g_pszAssemblerFmtVal); + apszArgs[iArg++] = g_pszAssemblerDefOpt; + if (g_cBits == 32) + apszArgs[iArg++] = "ARCH_BITS=32"; + else + apszArgs[iArg++] = "ARCH_BITS=64"; + apszArgs[iArg++] = g_pszAssemblerDefOpt; + if (g_cHostBits == 32) + apszArgs[iArg++] = "HC_ARCH_BITS=32"; + else + apszArgs[iArg++] = "HC_ARCH_BITS=64"; + apszArgs[iArg++] = g_pszAssemblerDefOpt; + if (g_cBits == 32) + apszArgs[iArg++] = "RT_ARCH_X86"; + else + apszArgs[iArg++] = "RT_ARCH_AMD64"; + apszArgs[iArg++] = g_pszAssemblerDefOpt; + apszArgs[iArg++] = g_pszContextDefine; + if (g_pszContextDefine2) + { + apszArgs[iArg++] = g_pszAssemblerDefOpt; + apszArgs[iArg++] = g_pszContextDefine2; + } + if (g_szAssemblerOsDef[0]) + { + apszArgs[iArg++] = g_pszAssemblerDefOpt; + apszArgs[iArg++] = g_szAssemblerOsDef; + } + apszArgs[iArg++] = g_pszAssemblerIncOpt; + apszArgs[iArg++] = g_pszAssemblerIncVal; + apszArgs[iArg++] = g_pszAssemblerOutputOpt; + apszArgs[iArg++] = pszOutput; + for (unsigned i = 0; i < g_cAssemblerOptions; i++) + apszArgs[iArg++] = g_apszAssemblerOptions[i]; + apszArgs[iArg++] = pszTempAsm; + apszArgs[iArg] = NULL; + Assert(iArg <= RT_ELEMENTS(apszArgs)); + + if (g_cVerbosity > 1) + { + RTMsgInfo("Starting assmbler '%s' with arguments:\n", g_pszAssembler); + for (unsigned i = 0; i < iArg; i++) + RTMsgInfo(" #%02u: '%s'\n", i, apszArgs[i]); + } + + RTPROCESS hProc; + int rc = RTProcCreate(apszArgs[0], apszArgs, RTENV_DEFAULT, RTPROC_FLAGS_SEARCH_PATH, &hProc); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to start '%s' (assembler): %Rrc", apszArgs[0], rc); + + RTPROCSTATUS Status; + rc = RTProcWait(hProc, RTPROCWAIT_FLAGS_BLOCK, &Status); + if (RT_FAILURE(rc)) + { + RTProcTerminate(hProc); + return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTProcWait failed: %Rrc", rc); + } + if (Status.enmReason == RTPROCEXITREASON_SIGNAL) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "The assembler failed: signal %d", Status.iStatus); + if (Status.enmReason != RTPROCEXITREASON_NORMAL) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "The assembler failed: abend"); + if (Status.iStatus != 0) + return RTMsgErrorExit((RTEXITCODE)Status.iStatus, "The assembler failed: exit code %d", Status.iStatus); + + return RTEXITCODE_SUCCESS; +} + + +/** + * Worker that does the boring bits when generating a file. + * + * @returns Exit code. + * @param pszOutput The name of the output file. + * @param pszWhat What kind of file it is. + * @param pfnGenerator The callback function that provides the contents + * of the file. + */ +static RTEXITCODE generateFile(const char *pszOutput, const char *pszWhat, + RTEXITCODE (*pfnGenerator)(PSCMSTREAM)) +{ + SCMSTREAM Strm; + int rc = ScmStreamInitForWriting(&Strm, NULL); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "ScmStreamInitForWriting returned %Rrc when generating the %s file", + rc, pszWhat); + + RTEXITCODE rcExit = pfnGenerator(&Strm); + if (RT_FAILURE(ScmStreamGetStatus(&Strm))) + rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "Stream error %Rrc generating the %s file", + ScmStreamGetStatus(&Strm), pszWhat); + if (rcExit == RTEXITCODE_SUCCESS) + { + rc = ScmStreamWriteToFile(&Strm, "%s", pszOutput); + if (RT_FAILURE(rc)) + rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "ScmStreamWriteToFile returned %Rrc when writing '%s' (%s)", + rc, pszOutput, pszWhat); + if (rcExit == RTEXITCODE_SUCCESS) + { + if (g_cVerbosity > 0) + RTMsgInfo("Successfully generated '%s'.", pszOutput); + if (g_cVerbosity > 1) + { + RTMsgInfo("================ %s - start ================", pszWhat); + ScmStreamRewindForReading(&Strm); + const char *pszLine; + size_t cchLine; + SCMEOL enmEol; + while ((pszLine = ScmStreamGetLine(&Strm, &cchLine, &enmEol)) != NULL) + RTPrintf("%.*s\n", cchLine, pszLine); + RTMsgInfo("================ %s - end ================", pszWhat); + } + } + } + ScmStreamDelete(&Strm); + return rcExit; +} + + +/** + * @callback_method_impl{FNRTSTRSPACECALLBACK, Writes the string table strings.} + */ +static DECLCALLBACK(int) generateAssemblyStrTabCallback(PRTSTRSPACECORE pStr, void *pvUser) +{ + PVTGSTRING pVtgStr = (PVTGSTRING)pStr; + PSCMSTREAM pStrm = (PSCMSTREAM)pvUser; + + pVtgStr->offStrTab = g_offStrTab; + g_offStrTab += (uint32_t)pVtgStr->Core.cchString + 1; + + ScmStreamPrintf(pStrm, + " db '%s', 0 ; off=%u len=%zu\n", + pVtgStr->szString, pVtgStr->offStrTab, pVtgStr->Core.cchString); + return VINF_SUCCESS; +} + + +/** + * Generate assembly source that can be turned into an object file. + * + * (This is a generateFile callback.) + * + * @returns Exit code. + * @param pStrm The output stream. + */ +static RTEXITCODE generateAssembly(PSCMSTREAM pStrm) +{ + PVTGPROVIDER pProvider; + PVTGPROBE pProbe; + PVTGARG pArg; + + + if (g_cVerbosity > 0) + RTMsgInfo("Generating assembly code..."); + + /* + * Write the file header. + */ + ScmStreamPrintf(pStrm, + "; $Id: VBoxTpG.cpp $ \n" + ";; @file\n" + "; Automatically generated from %s. Do NOT edit!\n" + ";\n" + "\n" + "%%include \"iprt/asmdefs.mac\"\n" + "\n" + "\n" + ";" + "; We put all the data in a dedicated section / segment.\n" + ";\n" + "; In order to find the probe location specifiers, we do the necessary\n" + "; trickery here, ASSUMING that this object comes in first in the link\n" + "; editing process.\n" + ";\n" + "%%ifdef ASM_FORMAT_OMF\n" + " %%macro VTG_GLOBAL 2\n" + " global NAME(%%1)\n" + " NAME(%%1):\n" + " %%endmacro\n" + " segment VTG.Obj public CLASS=VTG align=4096 use32\n" + "\n" + "%%elifdef ASM_FORMAT_MACHO\n" + " %%macro VTG_GLOBAL 2\n" + " global NAME(%%1)\n" + " NAME(%%1):\n" + " %%endmacro\n" + " %%ifdef IN_RING3\n" + " %%define VTG_NEW_MACHO_LINKER\n" + " %%elif ARCH_BITS == 64\n" + " %%define VTG_NEW_MACHO_LINKER\n" + " %%elifdef IN_RING0_AGNOSTIC\n" + " %%define VTG_NEW_MACHO_LINKER\n" + " %%endif\n" + " %%ifdef VTG_NEW_MACHO_LINKER\n" + " ; Section order hack!\n" + " ; With the ld64-97.17 linker there was a problem with it determining the section\n" + " ; order based on symbol references. The references to the start and end of the\n" + " ; __VTGPrLc section forced it in front of __VTGObj, we want __VTGObj first.\n" + " extern section$start$__VTG$__VTGObj\n" + " extern section$end$__VTG$__VTGObj\n" + " %%else\n" + " ; Creating 32-bit kext of the type MH_OBJECT. No fancy section end/start symbols handy.\n" + " [section __VTG __VTGObj align=16]\n" + "VTG_GLOBAL g_aVTGObj_LinkerPleaseNoticeMe, data\n" + " [section __VTG __VTGPrLc.Begin align=16]\n" + " dq 0, 0 ; Paranoia, related to the fudge below.\n" + "VTG_GLOBAL g_aVTGPrLc, data\n" + " [section __VTG __VTGPrLc align=16]\n" + "VTG_GLOBAL g_aVTGPrLc_LinkerPleaseNoticeMe, data\n" + " [section __VTG __VTGPrLc.End align=16]\n" + "VTG_GLOBAL g_aVTGPrLc_End, data\n" + " dq 0, 0 ; Fudge to work around unidentified linker where it would otherwise generate\n" + " ; a fix up of the first dword in __VTGPrLc.Begin despite the fact that it were\n" + " ; an empty section with nothing whatsoever to fix up.\n" + " %%endif\n" + " [section __VTG __VTGObj]\n" + "\n" + "%%elifdef ASM_FORMAT_PE\n" + " %%macro VTG_GLOBAL 2\n" + " global NAME(%%1)\n" + " NAME(%%1):\n" + " %%endmacro\n" + " [section VTGPrLc.Begin data align=64]\n" + /*" times 16 db 0xcc\n"*/ + "VTG_GLOBAL g_aVTGPrLc, data\n" + " [section VTGPrLc.Data data align=4]\n" + " [section VTGPrLc.End data align=4]\n" + "VTG_GLOBAL g_aVTGPrLc_End, data\n" + /*" times 16 db 0xcc\n"*/ + " [section VTGObj data align=32]\n" + "\n" + "%%elifdef ASM_FORMAT_ELF\n" + " %%macro VTG_GLOBAL 2\n" + " global NAME(%%1):%%2 hidden\n" + " NAME(%%1):\n" + " %%endmacro\n" + " [section .VTGData progbits alloc noexec write align=4096]\n" + " [section .VTGPrLc.Begin progbits alloc noexec write align=32]\n" + " dd 0,0,0,0, 0,0,0,0\n" + "VTG_GLOBAL g_aVTGPrLc, data\n" + " [section .VTGPrLc progbits alloc noexec write align=1]\n" + " [section .VTGPrLc.End progbits alloc noexec write align=1]\n" + "VTG_GLOBAL g_aVTGPrLc_End, data\n" + " dd 0,0,0,0, 0,0,0,0\n" + " [section .VTGData]\n" + "\n" + "%%else\n" + " %%error \"ASM_FORMAT_XXX is not defined\"\n" + "%%endif\n" + "\n" + "\n" + "VTG_GLOBAL g_VTGObjHeader, data\n" + " ;0 1 2 3\n" + " ;012345678901234567890123456789012\n" + " db 'VTG Object Header v1.7', 0, 0\n" + " dd %u\n" + " dd NAME(g_acVTGProbeEnabled_End) - NAME(g_VTGObjHeader)\n" + " dd NAME(g_achVTGStringTable) - NAME(g_VTGObjHeader)\n" + " dd NAME(g_achVTGStringTable_End) - NAME(g_achVTGStringTable)\n" + " dd NAME(g_aVTGArgLists) - NAME(g_VTGObjHeader)\n" + " dd NAME(g_aVTGArgLists_End) - NAME(g_aVTGArgLists)\n" + " dd NAME(g_aVTGProbes) - NAME(g_VTGObjHeader)\n" + " dd NAME(g_aVTGProbes_End) - NAME(g_aVTGProbes)\n" + " dd NAME(g_aVTGProviders) - NAME(g_VTGObjHeader)\n" + " dd NAME(g_aVTGProviders_End) - NAME(g_aVTGProviders)\n" + " dd NAME(g_acVTGProbeEnabled) - NAME(g_VTGObjHeader)\n" + " dd NAME(g_acVTGProbeEnabled_End) - NAME(g_acVTGProbeEnabled)\n" + " dd 0\n" + " dd 0\n" + "%%ifdef VTG_NEW_MACHO_LINKER\n" + " extern section$start$__VTG$__VTGPrLc\n" + " RTCCPTR_DEF section$start$__VTG$__VTGPrLc\n" + " %%if ARCH_BITS == 32\n" + " dd 0\n" + " %%endif\n" + " extern section$end$__VTG$__VTGPrLc\n" + " RTCCPTR_DEF section$end$__VTG$__VTGPrLc\n" + " %%if ARCH_BITS == 32\n" + " dd 0\n" + " %%endif\n" + "%%else\n" + " RTCCPTR_DEF NAME(g_aVTGPrLc)\n" + " %%if ARCH_BITS == 32\n" + " dd 0\n" + " %%endif\n" + " RTCCPTR_DEF NAME(g_aVTGPrLc_End)\n" + " %%if ARCH_BITS == 32\n" + " dd 0\n" + " %%endif\n" + "%%endif\n" + , + g_pszScript, g_cBits); + RTUUID Uuid; + int rc = RTUuidCreate(&Uuid); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTUuidCreate failed: %Rrc", rc); + ScmStreamPrintf(pStrm, + " dd 0%08xh, 0%08xh, 0%08xh, 0%08xh\n" + "%%ifdef VTG_NEW_MACHO_LINKER\n" + " RTCCPTR_DEF section$start$__VTG$__VTGObj\n" + " %%if ARCH_BITS == 32\n" + " dd 0\n" + " %%endif\n" + "%%else\n" + " dd 0, 0\n" + "%%endif\n" + " dd 0, 0\n" + , Uuid.au32[0], Uuid.au32[1], Uuid.au32[2], Uuid.au32[3]); + + /* + * Dump the string table before we start using the strings. + */ + ScmStreamPrintf(pStrm, + "\n" + ";\n" + "; The string table.\n" + ";\n" + "VTG_GLOBAL g_achVTGStringTable, data\n"); + g_offStrTab = 0; + RTStrSpaceEnumerate(&g_StrSpace, generateAssemblyStrTabCallback, pStrm); + ScmStreamPrintf(pStrm, + "VTG_GLOBAL g_achVTGStringTable_End, data\n"); + + /* + * Write out the argument lists before we use them. + */ + ScmStreamPrintf(pStrm, + "\n" + ";\n" + "; The argument lists.\n" + ";\n" + "ALIGNDATA(16)\n" + "VTG_GLOBAL g_aVTGArgLists, data\n"); + uint32_t off = 0; + RTListForEach(&g_ProviderHead, pProvider, VTGPROVIDER, ListEntry) + { + RTListForEach(&pProvider->ProbeHead, pProbe, VTGPROBE, ListEntry) + { + if (pProbe->offArgList != UINT32_MAX) + continue; + + /* Write it. */ + pProbe->offArgList = off; + ScmStreamPrintf(pStrm, + " ; off=%u\n" + " db %2u ; Argument count\n" + " db %u ; fHaveLargeArgs\n" + " db 0, 0 ; Reserved\n" + , off, pProbe->cArgs, (int)pProbe->fHaveLargeArgs); + off += 4; + RTListForEach(&pProbe->ArgHead, pArg, VTGARG, ListEntry) + { + ScmStreamPrintf(pStrm, + " dd %8u ; type '%s' (name '%s')\n" + " dd 0%08xh ; type flags\n", + strtabGetOff(pArg->pszTracerType), pArg->pszTracerType, pArg->pszName, + pArg->fType); + off += 8; + } + + /* Look for matching argument lists (lazy bird walks the whole list). */ + PVTGPROVIDER pProv2; + RTListForEach(&g_ProviderHead, pProv2, VTGPROVIDER, ListEntry) + { + PVTGPROBE pProbe2; + RTListForEach(&pProvider->ProbeHead, pProbe2, VTGPROBE, ListEntry) + { + if (pProbe2->offArgList != UINT32_MAX) + continue; + if (pProbe2->cArgs != pProbe->cArgs) + continue; + + PVTGARG pArg2; + pArg = RTListNodeGetNext(&pProbe->ArgHead, VTGARG, ListEntry); + pArg2 = RTListNodeGetNext(&pProbe2->ArgHead, VTGARG, ListEntry); + int32_t cArgs = pProbe->cArgs; + while ( cArgs-- > 0 + && pArg2->pszTracerType == pArg->pszTracerType + && pArg2->fType == pArg->fType) + { + pArg = RTListNodeGetNext(&pArg->ListEntry, VTGARG, ListEntry); + pArg2 = RTListNodeGetNext(&pArg2->ListEntry, VTGARG, ListEntry); + } + if (cArgs >= 0) + continue; + pProbe2->offArgList = pProbe->offArgList; + } + } + } + } + ScmStreamPrintf(pStrm, + "VTG_GLOBAL g_aVTGArgLists_End, data\n"); + + + /* + * Probe definitions. + */ + ScmStreamPrintf(pStrm, + "\n" + ";\n" + "; Prob definitions.\n" + ";\n" + "ALIGNDATA(16)\n" + "VTG_GLOBAL g_aVTGProbes, data\n" + "\n"); + uint32_t iProvider = 0; + uint32_t iProbe = 0; + RTListForEach(&g_ProviderHead, pProvider, VTGPROVIDER, ListEntry) + { + pProvider->iFirstProbe = iProbe; + RTListForEach(&pProvider->ProbeHead, pProbe, VTGPROBE, ListEntry) + { + ScmStreamPrintf(pStrm, + "VTG_GLOBAL g_VTGProbeData_%s_%s, data ; idx=#%4u\n" + " dd %6u ; offName\n" + " dd %6u ; offArgList\n" + " dw (NAME(g_cVTGProbeEnabled_%s_%s) - NAME(g_acVTGProbeEnabled)) / 4 ; idxEnabled\n" + " dw %6u ; idxProvider\n" + " dd NAME(g_VTGObjHeader) - NAME(g_VTGProbeData_%s_%s) ; offObjHdr\n" + , + pProvider->pszName, pProbe->pszMangledName, iProbe, + strtabGetOff(pProbe->pszUnmangledName), + pProbe->offArgList, + pProvider->pszName, pProbe->pszMangledName, + iProvider, + pProvider->pszName, pProbe->pszMangledName + ); + pProbe->iProbe = iProbe; + iProbe++; + } + pProvider->cProbes = iProbe - pProvider->iFirstProbe; + iProvider++; + } + ScmStreamPrintf(pStrm, "VTG_GLOBAL g_aVTGProbes_End, data\n"); + + /* + * The provider data. + */ + ScmStreamPrintf(pStrm, + "\n" + ";\n" + "; Provider data.\n" + ";\n" + "ALIGNDATA(16)\n" + "VTG_GLOBAL g_aVTGProviders, data\n"); + iProvider = 0; + RTListForEach(&g_ProviderHead, pProvider, VTGPROVIDER, ListEntry) + { + ScmStreamPrintf(pStrm, + " ; idx=#%4u - %s\n" + " dd %6u ; name\n" + " dw %6u ; index of first probe\n" + " dw %6u ; count of probes\n" + " db %d, %d, %d ; AttrSelf\n" + " db %d, %d, %d ; AttrModules\n" + " db %d, %d, %d ; AttrFunctions\n" + " db %d, %d, %d ; AttrName\n" + " db %d, %d, %d ; AttrArguments\n" + " db 0 ; reserved\n" + "VTG_GLOBAL g_cVTGProviderProbesEnabled_%s, data\n" + " dd 0\n" + "VTG_GLOBAL g_cVTGProviderSettingsSeqNo_%s, data\n" + " dd 0\n" + , + iProvider, pProvider->pszName, + strtabGetOff(pProvider->pszName), + pProvider->iFirstProbe, + pProvider->cProbes, + pProvider->AttrSelf.enmCode, pProvider->AttrSelf.enmData, pProvider->AttrSelf.enmDataDep, + pProvider->AttrModules.enmCode, pProvider->AttrModules.enmData, pProvider->AttrModules.enmDataDep, + pProvider->AttrFunctions.enmCode, pProvider->AttrFunctions.enmData, pProvider->AttrFunctions.enmDataDep, + pProvider->AttrName.enmCode, pProvider->AttrName.enmData, pProvider->AttrName.enmDataDep, + pProvider->AttrArguments.enmCode, pProvider->AttrArguments.enmData, pProvider->AttrArguments.enmDataDep, + pProvider->pszName, + pProvider->pszName); + iProvider++; + } + ScmStreamPrintf(pStrm, "VTG_GLOBAL g_aVTGProviders_End, data\n"); + + /* + * Declare the probe enable flags. + * + * These must be placed at the end so they'll end up adjacent to the probe + * locations. This is important for reducing the amount of memory we need + * to lock down for user mode modules. + */ + ScmStreamPrintf(pStrm, + ";\n" + "; Probe enabled flags.\n" + ";\n" + "ALIGNDATA(16)\n" + "VTG_GLOBAL g_acVTGProbeEnabled, data\n" + ); + uint32_t cProbes = 0; + RTListForEach(&g_ProviderHead, pProvider, VTGPROVIDER, ListEntry) + { + RTListForEach(&pProvider->ProbeHead, pProbe, VTGPROBE, ListEntry) + { + ScmStreamPrintf(pStrm, + "VTG_GLOBAL g_cVTGProbeEnabled_%s_%s, data\n" + " dd 0\n", + pProvider->pszName, pProbe->pszMangledName); + cProbes++; + } + } + ScmStreamPrintf(pStrm, "VTG_GLOBAL g_acVTGProbeEnabled_End, data\n"); + if (cProbes >= _32K) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Too many probes: %u (max %u)", cProbes, _32K - 1); + + + /* + * Emit code for the stub functions. + */ + bool const fWin64 = g_cBits == 64 && (!strcmp(g_pszAssemblerFmtVal, "win64") || !strcmp(g_pszAssemblerFmtVal, "pe64")); + bool const fElf = !strcmp(g_pszAssemblerFmtVal, "elf32") || !strcmp(g_pszAssemblerFmtVal, "elf64"); + ScmStreamPrintf(pStrm, + "\n" + ";\n" + "; Prob stubs.\n" + ";\n" + "BEGINCODE\n" + ); + if (g_fProbeFnImported) + ScmStreamPrintf(pStrm, + "EXTERN_IMP2 %s\n" + "BEGINCODE ; EXTERN_IMP2 changes section\n", + g_pszProbeFnName); + else + ScmStreamPrintf(pStrm, "extern NAME(%s)\n", g_pszProbeFnName); + + RTListForEach(&g_ProviderHead, pProvider, VTGPROVIDER, ListEntry) + { + RTListForEach(&pProvider->ProbeHead, pProbe, VTGPROBE, ListEntry) + { + ScmStreamPrintf(pStrm, + "\n" + "VTG_GLOBAL VTGProbeStub_%s_%s, function; (VBOXTPGPROBELOC pVTGProbeLoc", + pProvider->pszName, pProbe->pszMangledName); + RTListForEach(&pProbe->ArgHead, pArg, VTGARG, ListEntry) + { + ScmStreamPrintf(pStrm, ", %s %s", pArg->pszTracerType, pArg->pszName); + } + ScmStreamPrintf(pStrm, + ");\n"); + + /* + * Check if the probe in question is enabled. + */ + if (g_cBits == 32) + ScmStreamPrintf(pStrm, + " mov eax, [esp + 4]\n" + " test byte [eax+3], 0x80 ; fEnabled == true?\n" + " jz .return ; jump on false\n"); + else if (fWin64) + ScmStreamPrintf(pStrm, + " test byte [rcx+3], 0x80 ; fEnabled == true?\n" + " jz .return ; jump on false\n"); + else + ScmStreamPrintf(pStrm, + " test byte [rdi+3], 0x80 ; fEnabled == true?\n" + " jz .return ; jump on false\n"); + + /* + * Jump to the fire-probe function. + */ + if (g_cBits == 32) + ScmStreamPrintf(pStrm, g_fPic && fElf ? + " jmp %s wrt ..plt\n" + : g_fProbeFnImported ? + " mov ecx, IMP2(%s)\n" + " jmp ecx\n" + : + " jmp NAME(%s)\n" + , g_pszProbeFnName); + else + ScmStreamPrintf(pStrm, g_fPic && fElf ? + " jmp [rel %s wrt ..got]\n" + : g_fProbeFnImported ? + " jmp IMP2(%s)\n" + : + " jmp NAME(%s)\n" + , g_pszProbeFnName); + + ScmStreamPrintf(pStrm, + ".return:\n" + " ret ; The probe was disabled, return\n" + "\n"); + } + } + + return RTEXITCODE_SUCCESS; +} + + +static RTEXITCODE generateObject(const char *pszOutput, const char *pszTempAsm) +{ + if (!pszTempAsm) + { + size_t cch = strlen(pszOutput); + char *psz = (char *)alloca(cch + sizeof(".asm")); + memcpy(psz, pszOutput, cch); + memcpy(psz + cch, ".asm", sizeof(".asm")); + pszTempAsm = psz; + } + + RTEXITCODE rcExit = generateFile(pszTempAsm, "assembly", generateAssembly); + if (rcExit == RTEXITCODE_SUCCESS) + rcExit = generateInvokeAssembler(pszOutput, pszTempAsm); + RTFileDelete(pszTempAsm); + return rcExit; +} + + +static RTEXITCODE generateProbeDefineName(char *pszBuf, size_t cbBuf, const char *pszProvider, const char *pszProbe) +{ + size_t cbMax = strlen(pszProvider) + 1 + strlen(pszProbe) + 1; + if (cbMax > cbBuf || cbMax > 80) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Probe '%s' in provider '%s' ends up with a too long defined\n", pszProbe, pszProvider); + + while (*pszProvider) + *pszBuf++ = RT_C_TO_UPPER(*pszProvider++); + + *pszBuf++ = '_'; + + while (*pszProbe) + { + if (pszProbe[0] == '_' && pszProbe[1] == '_') + pszProbe++; + *pszBuf++ = RT_C_TO_UPPER(*pszProbe++); + } + + *pszBuf = '\0'; + return RTEXITCODE_SUCCESS; +} + + +static RTEXITCODE generateProviderDefineName(char *pszBuf, size_t cbBuf, const char *pszProvider) +{ + size_t cbMax = strlen(pszProvider) + 1; + if (cbMax > cbBuf || cbMax > 80) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Provider '%s' ends up with a too long defined\n", pszProvider); + + while (*pszProvider) + *pszBuf++ = RT_C_TO_UPPER(*pszProvider++); + + *pszBuf = '\0'; + return RTEXITCODE_SUCCESS; +} + + +/** + * Called via generateFile to generate the header file. + * + * @returns Exit code status. + * @param pStrm The output stream. + */ +static RTEXITCODE generateHeader(PSCMSTREAM pStrm) +{ + /* + * Calc the double inclusion blocker define and then write the file header. + */ + char szTmp[4096]; + const char *pszName = RTPathFilename(g_pszScript); + size_t cchName = strlen(pszName); + if (cchName >= sizeof(szTmp) - 64) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "File name is too long '%s'", pszName); + szTmp[0] = '_'; + szTmp[1] = '_'; + szTmp[2] = '_'; + memcpy(&szTmp[3], pszName, cchName); + szTmp[3 + cchName + 0] = '_'; + szTmp[3 + cchName + 1] = '_'; + szTmp[3 + cchName + 2] = '_'; + szTmp[3 + cchName + 3] = '\0'; + char *psz = &szTmp[3]; + while (*psz) + { + if (!RT_C_IS_ALNUM(*psz) && *psz != '_') + *psz = '_'; + psz++; + } + + ScmStreamPrintf(pStrm, + "/* $Id: VBoxTpG.cpp $ */\n" + "/** @file\n" + " * Automatically generated from %s. Do NOT edit!\n" + " */\n" + "\n" + "#ifndef %s\n" + "#define %s\n" + "#ifndef RT_WITHOUT_PRAGMA_ONCE\n" + "# pragma once\n" + "#endif\n" + "\n" + "#include \n" + "\n" + "#ifndef %s\n" + "# error \"Expected '%s' to be defined\"\n" + "#endif\n" + "\n" + "RT_C_DECLS_BEGIN\n" + "\n" + "#ifdef VBOX_WITH_DTRACE\n" + "\n" + "# ifdef _MSC_VER\n" + "# pragma data_seg(VTG_LOC_SECT)\n" + "# pragma data_seg()\n" + "# endif\n" + "\n" + , + g_pszScript, + szTmp, + szTmp, + g_pszContextDefine, + g_pszContextDefine); + + /* + * Declare data, code and macros for each probe. + */ + PVTGPROVIDER pProv; + PVTGPROBE pProbe; + PVTGARG pArg; + RTListForEach(&g_ProviderHead, pProv, VTGPROVIDER, ListEntry) + { + /* This macro is not available in ring-3 because we don't have + anything similar available for native dtrace. */ + ScmStreamPrintf(pStrm, "\n\n"); + if (g_fTypeContext != VTG_TYPE_CTX_R3) + { + generateProviderDefineName(szTmp, sizeof(szTmp), pProv->pszName); + ScmStreamPrintf(pStrm, + "extern uint32_t const volatile g_cVTGProviderProbesEnabled_%s;\n" + "# define %s_ANY_PROBES_ENABLED() \\\n" + " (RT_UNLIKELY(g_cVTGProviderProbesEnabled_%s != 0))\n" + "extern uint32_t const volatile g_cVTGProviderSettingsSeqNo_%s;\n" + "# define %s_GET_SETTINGS_SEQ_NO() (g_cVTGProviderSettingsSeqNo_%s)\n" + "\n", + pProv->pszName, + szTmp, pProv->pszName, + pProv->pszName, + szTmp, pProv->pszName); + } + + RTListForEach(&pProv->ProbeHead, pProbe, VTGPROBE, ListEntry) + { + ScmStreamPrintf(pStrm, + "extern uint32_t const volatile g_cVTGProbeEnabled_%s_%s;\n" + "extern VTGDESCPROBE g_VTGProbeData_%s_%s;\n" + "DECLASM(void) VTGProbeStub_%s_%s(PVTGPROBELOC", + pProv->pszName, pProbe->pszMangledName, + pProv->pszName, pProbe->pszMangledName, + pProv->pszName, pProbe->pszMangledName); + RTListForEach(&pProbe->ArgHead, pArg, VTGARG, ListEntry) + { + ScmStreamPrintf(pStrm, ", %s", pArg->pszCtxType); + } + generateProbeDefineName(szTmp, sizeof(szTmp), pProv->pszName, pProbe->pszMangledName); + ScmStreamPrintf(pStrm, + ");\n" + "# define %s_ENABLED() (RT_UNLIKELY(g_cVTGProbeEnabled_%s_%s != 0))\n" + "# define %s_ENABLED_RAW() (g_cVTGProbeEnabled_%s_%s)\n" + "# define %s(" + , + szTmp, pProv->pszName, pProbe->pszMangledName, + szTmp, pProv->pszName, pProbe->pszMangledName, + szTmp); + RTListForEach(&pProbe->ArgHead, pArg, VTGARG, ListEntry) + { + if (RTListNodeIsFirst(&pProbe->ArgHead, &pArg->ListEntry)) + ScmStreamPrintf(pStrm, "%s", pArg->pszName); + else + ScmStreamPrintf(pStrm, ", %s", pArg->pszName); + } + ScmStreamPrintf(pStrm, + ") \\\n" + " do { \\\n" + " if (RT_UNLIKELY(g_cVTGProbeEnabled_%s_%s)) \\\n" + " { \\\n" + " VTG_DECL_VTGPROBELOC(s_VTGProbeLoc) = \\\n" + " { __LINE__, 0, 0, __FUNCTION__, &g_VTGProbeData_%s_%s }; \\\n" + " VTGProbeStub_%s_%s(&s_VTGProbeLoc", + pProv->pszName, pProbe->pszMangledName, + pProv->pszName, pProbe->pszMangledName, + pProv->pszName, pProbe->pszMangledName); + RTListForEach(&pProbe->ArgHead, pArg, VTGARG, ListEntry) + { + ScmStreamPrintf(pStrm, pArg->pszArgPassingFmt, pArg->pszName); + } + ScmStreamPrintf(pStrm, + "); \\\n" + " } \\\n" + " { \\\n" ); + uint32_t iArg = 0; + RTListForEach(&pProbe->ArgHead, pArg, VTGARG, ListEntry) + { + if ((pArg->fType & (VTG_TYPE_FIXED_SIZED | VTG_TYPE_AUTO_CONV_PTR)) == VTG_TYPE_FIXED_SIZED) + ScmStreamPrintf(pStrm, + " AssertCompile(sizeof(%s) == %u); \\\n" + " AssertCompile(sizeof(%s) <= %u); \\\n", + pArg->pszTracerType, pArg->fType & VTG_TYPE_SIZE_MASK, + pArg->pszName, pArg->fType & VTG_TYPE_SIZE_MASK); + else if (pArg->fType & (VTG_TYPE_POINTER | VTG_TYPE_HC_ARCH_SIZED)) + ScmStreamPrintf(pStrm, + " AssertCompile(sizeof(%s) <= sizeof(uintptr_t)); \\\n" + " AssertCompile(sizeof(%s) <= sizeof(uintptr_t)); \\\n", + pArg->pszName, + pArg->pszTracerType); + iArg++; + } + ScmStreamPrintf(pStrm, + " } \\\n" + " } while (0)\n" + "\n"); + } + } + + ScmStreamPrintf(pStrm, + "\n" + "#else\n" + "\n"); + RTListForEach(&g_ProviderHead, pProv, VTGPROVIDER, ListEntry) + { + if (g_fTypeContext != VTG_TYPE_CTX_R3) + { + generateProviderDefineName(szTmp, sizeof(szTmp), pProv->pszName); + ScmStreamPrintf(pStrm, + "# define %s_ANY_PROBES_ENABLED() (false)\n" + "# define %s_GET_SETTINGS_SEQ_NO() UINT32_C(0)\n" + "\n", + szTmp, szTmp); + } + + RTListForEach(&pProv->ProbeHead, pProbe, VTGPROBE, ListEntry) + { + generateProbeDefineName(szTmp, sizeof(szTmp), pProv->pszName, pProbe->pszMangledName); + ScmStreamPrintf(pStrm, + "# define %s_ENABLED() (false)\n" + "# define %s_ENABLED_RAW() UINT32_C(0)\n" + "# define %s(" + , szTmp, szTmp, szTmp); + RTListForEach(&pProbe->ArgHead, pArg, VTGARG, ListEntry) + { + if (RTListNodeIsFirst(&pProbe->ArgHead, &pArg->ListEntry)) + ScmStreamPrintf(pStrm, "%s", pArg->pszName); + else + ScmStreamPrintf(pStrm, ", %s", pArg->pszName); + } + ScmStreamPrintf(pStrm, + ") do { } while (0)\n"); + } + } + + ScmStreamWrite(pStrm, RT_STR_TUPLE("\n" + "#endif\n" + "\n" + "RT_C_DECLS_END\n" + "#endif\n")); + return RTEXITCODE_SUCCESS; +} + + +/** + * Called via generateFile to generate the wrapper header file. + * + * @returns Exit code status. + * @param pStrm The output stream. + */ +static RTEXITCODE generateWrapperHeader(PSCMSTREAM pStrm) +{ + /* + * Calc the double inclusion blocker define and then write the file header. + */ + char szTmp[4096]; + const char *pszName = RTPathFilename(g_pszScript); + size_t cchName = strlen(pszName); + if (cchName >= sizeof(szTmp) - 64) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "File name is too long '%s'", pszName); + szTmp[0] = '_'; + szTmp[1] = '_'; + szTmp[2] = '_'; + memcpy(&szTmp[3], pszName, cchName); + strcpy(&szTmp[3 + cchName ], "___WRAPPER___"); + char *psz = &szTmp[3]; + while (*psz) + { + if (!RT_C_IS_ALNUM(*psz) && *psz != '_') + *psz = '_'; + psz++; + } + + ScmStreamPrintf(pStrm, + "/* $Id: VBoxTpG.cpp $ */\n" + "/** @file\n" + " * Automatically generated from %s. Do NOT edit!\n" + " */\n" + "\n" + "#ifndef %s\n" + "#define %s\n" + "\n" + "#include \n" + "\n" + "#ifndef %s\n" + "# error \"Expected '%s' to be defined\"\n" + "#endif\n" + "\n" + "#ifdef VBOX_WITH_DTRACE\n" + "\n" + , + g_pszScript, + szTmp, + szTmp, + g_pszContextDefine, + g_pszContextDefine); + + /* + * Declare macros for each probe. + */ + PVTGPROVIDER pProv; + PVTGPROBE pProbe; + PVTGARG pArg; + RTListForEach(&g_ProviderHead, pProv, VTGPROVIDER, ListEntry) + { + RTListForEach(&pProv->ProbeHead, pProbe, VTGPROBE, ListEntry) + { + generateProbeDefineName(szTmp, sizeof(szTmp), pProv->pszName, pProbe->pszMangledName); + ScmStreamPrintf(pStrm, + "# define %s(" + , szTmp); + RTListForEach(&pProbe->ArgHead, pArg, VTGARG, ListEntry) + { + if (RTListNodeIsFirst(&pProbe->ArgHead, &pArg->ListEntry)) + ScmStreamPrintf(pStrm, "%s", pArg->pszName); + else + ScmStreamPrintf(pStrm, ", %s", pArg->pszName); + } + ScmStreamPrintf(pStrm, + ") \\\n" + " do { \\\n" + " if (RT_UNLIKELY(%s_ENABLED())) \\\n" + " { \\\n" + " %s_ORIGINAL(" + , szTmp, szTmp); + RTListForEach(&pProbe->ArgHead, pArg, VTGARG, ListEntry) + { + const char *pszFmt = pArg->pszArgPassingFmt; + if (pArg->fType & VTG_TYPE_AUTO_CONV_PTR) + { + /* Casting is required. ASSUMES sizeof(RTR0PTR) == sizeof(RTR3PTR) - safe! */ + pszFmt += sizeof(", ") - 1; + if (RTListNodeIsFirst(&pProbe->ArgHead, &pArg->ListEntry)) + ScmStreamPrintf(pStrm, "(%s)%M", pArg->pszTracerType, pszFmt, pArg->pszName); + else + ScmStreamPrintf(pStrm, ", (%s)%M", pArg->pszTracerType, pszFmt, pArg->pszName); + } + else if (pArg->fType & VTG_TYPE_CONST_CHAR_PTR) + { + /* Casting from 'const char *' (probe) to 'char *' (dtrace) is required to shut up warnings. */ + pszFmt += sizeof(", ") - 1; + if (RTListNodeIsFirst(&pProbe->ArgHead, &pArg->ListEntry)) + ScmStreamPrintf(pStrm, "(char *)%M", pszFmt, pArg->pszName); + else + ScmStreamPrintf(pStrm, ", (char *)%M", pszFmt, pArg->pszName); + } + else + { + if (RTListNodeIsFirst(&pProbe->ArgHead, &pArg->ListEntry)) + ScmStreamPrintf(pStrm, pArg->pszArgPassingFmt + sizeof(", ") - 1, pArg->pszName); + else + ScmStreamPrintf(pStrm, pArg->pszArgPassingFmt, pArg->pszName); + } + } + ScmStreamPrintf(pStrm, + "); \\\n" + " } \\\n" + " } while (0)\n" + "\n"); + } + } + + ScmStreamPrintf(pStrm, + "\n" + "#else\n" + "\n"); + RTListForEach(&g_ProviderHead, pProv, VTGPROVIDER, ListEntry) + { + RTListForEach(&pProv->ProbeHead, pProbe, VTGPROBE, ListEntry) + { + generateProbeDefineName(szTmp, sizeof(szTmp), pProv->pszName, pProbe->pszMangledName); + ScmStreamPrintf(pStrm, + "# define %s(" + , szTmp); + RTListForEach(&pProbe->ArgHead, pArg, VTGARG, ListEntry) + { + if (RTListNodeIsFirst(&pProbe->ArgHead, &pArg->ListEntry)) + ScmStreamPrintf(pStrm, "%s", pArg->pszName); + else + ScmStreamPrintf(pStrm, ", %s", pArg->pszName); + } + ScmStreamPrintf(pStrm, + ") do { } while (0)\n"); + } + } + + ScmStreamWrite(pStrm, RT_STR_TUPLE("\n" + "#endif\n" + "\n" + "#endif\n")); + return RTEXITCODE_SUCCESS; +} + + +/** + * Parser error with line and position. + * + * @returns RTEXITCODE_FAILURE. + * @param pStrm The stream. + * @param fAbs Absolute or relative offset. + * @param offSeek When @a fAbs is @c false, the offset from the current + * position to the point of failure. When @a fAbs is @c + * true, it's the absolute position. + * @param pszFormat The message format string. + * @param va Format arguments. + */ +static RTEXITCODE parseErrorExV(PSCMSTREAM pStrm, bool fAbs, size_t offSeek, const char *pszFormat, va_list va) +{ + if (fAbs) + ScmStreamSeekAbsolute(pStrm, offSeek); + else if (offSeek != 0) + ScmStreamSeekRelative(pStrm, -(ssize_t)offSeek); + size_t const off = ScmStreamTell(pStrm); + size_t const iLine = ScmStreamTellLine(pStrm); + ScmStreamSeekByLine(pStrm, iLine); + size_t const offLine = ScmStreamTell(pStrm); + + va_list va2; + va_copy(va2, va); + RTPrintf("%s:%d:%zd: error: %N.\n", g_pszScript, iLine + 1, off - offLine + 1, pszFormat, &va2); + va_end(va2); + + size_t cchLine; + SCMEOL enmEof; + const char *pszLine = ScmStreamGetLineByNo(pStrm, iLine, &cchLine, &enmEof); + if (pszLine) + RTPrintf(" %.*s\n" + " %*s^\n", + cchLine, pszLine, off - offLine, ""); + return RTEXITCODE_FAILURE; +} + + +/** + * Parser error with line and position. + * + * @returns RTEXITCODE_FAILURE. + * @param pStrm The stream. + * @param off The offset from the current position to the point of + * failure. + * @param pszFormat The message format string. + * @param ... Format arguments. + */ +static RTEXITCODE parseError(PSCMSTREAM pStrm, size_t off, const char *pszFormat, ...) +{ + va_list va; + va_start(va, pszFormat); + RTEXITCODE rcExit = parseErrorExV(pStrm, false, off, pszFormat, va); + va_end(va); + return rcExit; +} + + +/** + * Parser error with line and position, absolute version. + * + * @returns RTEXITCODE_FAILURE. + * @param pStrm The stream. + * @param off The offset from the current position to the point of + * failure. + * @param pszFormat The message format string. + * @param ... Format arguments. + */ +static RTEXITCODE parseErrorAbs(PSCMSTREAM pStrm, size_t off, const char *pszFormat, ...) +{ + va_list va; + va_start(va, pszFormat); + RTEXITCODE rcExit = parseErrorExV(pStrm, true /*fAbs*/, off, pszFormat, va); + va_end(va); + return rcExit; +} + + +/** + * Parser warning with line and position. + * + * @param pStrm The stream. + * @param fAbs Absolute or relative offset. + * @param offSeek When @a fAbs is @c false, the offset from the current + * position to the point of failure. When @a fAbs is @c + * true, it's the absolute position. + * @param pszFormat The message format string. + * @param va Format arguments. + */ +static void parseWarnExV(PSCMSTREAM pStrm, bool fAbs, size_t offSeek, const char *pszFormat, va_list va) +{ + /* Save the stream position. */ + size_t const offOrg = ScmStreamTell(pStrm); + + if (fAbs) + ScmStreamSeekAbsolute(pStrm, offSeek); + else if (offSeek != 0) + ScmStreamSeekRelative(pStrm, -(ssize_t)offSeek); + size_t const off = ScmStreamTell(pStrm); + size_t const iLine = ScmStreamTellLine(pStrm); + ScmStreamSeekByLine(pStrm, iLine); + size_t const offLine = ScmStreamTell(pStrm); + + va_list va2; + va_copy(va2, va); + RTPrintf("%s:%d:%zd: warning: %N.\n", g_pszScript, iLine + 1, off - offLine + 1, pszFormat, &va2); + va_end(va2); + + size_t cchLine; + SCMEOL enmEof; + const char *pszLine = ScmStreamGetLineByNo(pStrm, iLine, &cchLine, &enmEof); + if (pszLine) + RTPrintf(" %.*s\n" + " %*s^\n", + cchLine, pszLine, off - offLine, ""); + + /* restore the position. */ + int rc = ScmStreamSeekAbsolute(pStrm, offOrg); + AssertRC(rc); +} + +#if 0 /* unused */ +/** + * Parser warning with line and position. + * + * @param pStrm The stream. + * @param off The offset from the current position to the point of + * failure. + * @param pszFormat The message format string. + * @param ... Format arguments. + */ +static void parseWarn(PSCMSTREAM pStrm, size_t off, const char *pszFormat, ...) +{ + va_list va; + va_start(va, pszFormat); + parseWarnExV(pStrm, false, off, pszFormat, va); + va_end(va); +} +#endif /* unused */ + +/** + * Parser warning with line and position, absolute version. + * + * @param pStrm The stream. + * @param off The offset from the current position to the point of + * failure. + * @param pszFormat The message format string. + * @param ... Format arguments. + */ +static void parseWarnAbs(PSCMSTREAM pStrm, size_t off, const char *pszFormat, ...) +{ + va_list va; + va_start(va, pszFormat); + parseWarnExV(pStrm, true /*fAbs*/, off, pszFormat, va); + va_end(va); +} + + +/** + * Handles a C++ one line comment. + * + * @returns Exit code. + * @param pStrm The stream. + */ +static RTEXITCODE parseOneLineComment(PSCMSTREAM pStrm) +{ + ScmStreamSeekByLine(pStrm, ScmStreamTellLine(pStrm) + 1); + return RTEXITCODE_SUCCESS; +} + + +/** + * Handles a multi-line C/C++ comment. + * + * @returns Exit code. + * @param pStrm The stream. + */ +static RTEXITCODE parseMultiLineComment(PSCMSTREAM pStrm) +{ + unsigned ch; + while ((ch = ScmStreamGetCh(pStrm)) != ~(unsigned)0) + { + if (ch == '*') + { + do + ch = ScmStreamGetCh(pStrm); + while (ch == '*'); + if (ch == '/') + return RTEXITCODE_SUCCESS; + } + } + + parseError(pStrm, 1, "Expected end of comment, got end of file"); + return RTEXITCODE_FAILURE; +} + + +/** + * Skips spaces and comments. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE. + * @param pStrm The stream.. + */ +static RTEXITCODE parseSkipSpacesAndComments(PSCMSTREAM pStrm) +{ + unsigned ch; + while ((ch = ScmStreamPeekCh(pStrm)) != ~(unsigned)0) + { + if (!RT_C_IS_SPACE(ch) && ch != '/') + return RTEXITCODE_SUCCESS; + unsigned ch2 = ScmStreamGetCh(pStrm); AssertBreak(ch == ch2); NOREF(ch2); + if (ch == '/') + { + ch = ScmStreamGetCh(pStrm); + RTEXITCODE rcExit; + if (ch == '*') + rcExit = parseMultiLineComment(pStrm); + else if (ch == '/') + rcExit = parseOneLineComment(pStrm); + else + rcExit = parseError(pStrm, 2, "Unexpected character"); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + } + } + + return parseError(pStrm, 0, "Unexpected end of file"); +} + + +/** + * 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. + */ +static unsigned parseGetNextNonSpaceNonCommentCh(PSCMSTREAM pStrm) +{ + unsigned ch; + while ((ch = ScmStreamGetCh(pStrm)) != ~(unsigned)0) + { + if (!RT_C_IS_SPACE(ch) && ch != '/') + return ch; + if (ch == '/') + { + ch = ScmStreamGetCh(pStrm); + RTEXITCODE rcExit; + if (ch == '*') + rcExit = parseMultiLineComment(pStrm); + else if (ch == '/') + rcExit = parseOneLineComment(pStrm); + else + rcExit = parseError(pStrm, 2, "Unexpected character"); + if (rcExit != RTEXITCODE_SUCCESS) + return ~(unsigned)0; + } + } + + parseError(pStrm, 0, "Unexpected end of file"); + 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. + */ +static unsigned parseGetNextNonSpaceNonCommentChOnPpLine(PSCMSTREAM pStrm) +{ + size_t off = ScmStreamTell(pStrm) - 1; + unsigned ch; + while ((ch = ScmStreamGetCh(pStrm)) != ~(unsigned)0) + { + if (RT_C_IS_SPACE(ch)) + { + if (ch == '\n' || ch == '\r') + { + parseErrorAbs(pStrm, off, "Invalid preprocessor statement"); + break; + } + } + else if (ch == '\\') + { + size_t off2 = ScmStreamTell(pStrm) - 1; + ch = ScmStreamGetCh(pStrm); + if (ch == '\r') + ch = ScmStreamGetCh(pStrm); + if (ch != '\n') + { + parseErrorAbs(pStrm, off2, "Expected new line"); + 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. + */ +static const char *parseGetNextCWord(PSCMSTREAM pStrm, size_t *pcchWord) +{ + if (parseSkipSpacesAndComments(pStrm) != RTEXITCODE_SUCCESS) + return NULL; + return ScmStreamCGetWord(pStrm, pcchWord); +} + + + +/** + * 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. + */ +static kVTGStability parseStability(PSCMSTREAM pStrm, unsigned ch) +{ + switch (ch) + { + case 'E': + if (ScmStreamCMatchingWordM1(pStrm, RT_STR_TUPLE("External"))) + return kVTGStability_External; + if (ScmStreamCMatchingWordM1(pStrm, RT_STR_TUPLE("Evolving"))) + return kVTGStability_Evolving; + break; + case 'I': + if (ScmStreamCMatchingWordM1(pStrm, RT_STR_TUPLE("Internal"))) + return kVTGStability_Internal; + break; + case 'O': + if (ScmStreamCMatchingWordM1(pStrm, RT_STR_TUPLE("Obsolete"))) + return kVTGStability_Obsolete; + break; + case 'P': + if (ScmStreamCMatchingWordM1(pStrm, RT_STR_TUPLE("Private"))) + return kVTGStability_Private; + break; + case 'S': + if (ScmStreamCMatchingWordM1(pStrm, RT_STR_TUPLE("Stable"))) + return kVTGStability_Stable; + if (ScmStreamCMatchingWordM1(pStrm, RT_STR_TUPLE("Standard"))) + return kVTGStability_Standard; + break; + case 'U': + if (ScmStreamCMatchingWordM1(pStrm, RT_STR_TUPLE("Unstable"))) + return kVTGStability_Unstable; + break; + } + parseError(pStrm, 1, "Unknown stability specifier"); + 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. + */ +static kVTGClass parseDataDepClass(PSCMSTREAM pStrm, unsigned ch) +{ + switch (ch) + { + case 'C': + if (ScmStreamCMatchingWordM1(pStrm, RT_STR_TUPLE("Common"))) + return kVTGClass_Common; + if (ScmStreamCMatchingWordM1(pStrm, RT_STR_TUPLE("Cpu"))) + return kVTGClass_Cpu; + break; + case 'G': + if (ScmStreamCMatchingWordM1(pStrm, RT_STR_TUPLE("Group"))) + return kVTGClass_Group; + break; + case 'I': + if (ScmStreamCMatchingWordM1(pStrm, RT_STR_TUPLE("Isa"))) + return kVTGClass_Isa; + break; + case 'P': + if (ScmStreamCMatchingWordM1(pStrm, RT_STR_TUPLE("Platform"))) + return kVTGClass_Platform; + break; + case 'U': + if (ScmStreamCMatchingWordM1(pStrm, RT_STR_TUPLE("Unknown"))) + return kVTGClass_Unknown; + break; + } + parseError(pStrm, 1, "Unknown data dependency class specifier"); + return kVTGClass_Invalid; +} + +/** + * Parses a pragma D attributes statement. + * + * @returns Suitable exit code, errors message already written on failure. + * @param pStrm The stream. + */ +static RTEXITCODE parsePragmaDAttributes(PSCMSTREAM pStrm) +{ + /* + * "CodeStability/DataStability/DataDepClass" - no spaces allowed. + */ + unsigned ch = parseGetNextNonSpaceNonCommentChOnPpLine(pStrm); + if (ch == ~(unsigned)0) + return RTEXITCODE_FAILURE; + + kVTGStability enmCode = parseStability(pStrm, ch); + if (enmCode == kVTGStability_Invalid) + return RTEXITCODE_FAILURE; + ch = ScmStreamGetCh(pStrm); + if (ch != '/') + return parseError(pStrm, 1, "Expected '/' following the code stability specifier"); + + kVTGStability enmData = parseStability(pStrm, ScmStreamGetCh(pStrm)); + if (enmData == kVTGStability_Invalid) + return RTEXITCODE_FAILURE; + ch = ScmStreamGetCh(pStrm); + if (ch != '/') + return parseError(pStrm, 1, "Expected '/' following the data stability specifier"); + + kVTGClass enmDataDep = parseDataDepClass(pStrm, ScmStreamGetCh(pStrm)); + if (enmDataDep == kVTGClass_Invalid) + return RTEXITCODE_FAILURE; + + /* + * Expecting 'provider' followed by the name of an provider defined earlier. + */ + ch = parseGetNextNonSpaceNonCommentChOnPpLine(pStrm); + if (ch == ~(unsigned)0) + return RTEXITCODE_FAILURE; + if (ch != 'p' || !ScmStreamCMatchingWordM1(pStrm, RT_STR_TUPLE("provider"))) + return parseError(pStrm, 1, "Expected 'provider'"); + + size_t cchName; + const char *pszName = parseGetNextCWord(pStrm, &cchName); + if (!pszName) + return parseError(pStrm, 1, "Expected provider name"); + + PVTGPROVIDER pProv; + RTListForEach(&g_ProviderHead, pProv, VTGPROVIDER, ListEntry) + { + if ( !strncmp(pProv->pszName, pszName, cchName) + && pProv->pszName[cchName] == '\0') + break; + } + if (RTListNodeIsDummy(&g_ProviderHead, pProv, VTGPROVIDER, ListEntry)) + return parseError(pStrm, cchName, "Provider not found"); + + /* + * Which aspect of the provider? + */ + size_t cchAspect; + const char *pszAspect = parseGetNextCWord(pStrm, &cchAspect); + if (!pszAspect) + return parseError(pStrm, 1, "Expected provider aspect"); + + PVTGATTRS pAttrs; + if (cchAspect == 8 && !memcmp(pszAspect, "provider", 8)) + pAttrs = &pProv->AttrSelf; + else if (cchAspect == 8 && !memcmp(pszAspect, "function", 8)) + pAttrs = &pProv->AttrFunctions; + else if (cchAspect == 6 && !memcmp(pszAspect, "module", 6)) + pAttrs = &pProv->AttrModules; + else if (cchAspect == 4 && !memcmp(pszAspect, "name", 4)) + pAttrs = &pProv->AttrName; + else if (cchAspect == 4 && !memcmp(pszAspect, "args", 4)) + pAttrs = &pProv->AttrArguments; + else + return parseError(pStrm, cchAspect, "Unknown aspect"); + + if (pAttrs->enmCode != kVTGStability_Invalid) + return parseError(pStrm, cchAspect, "You have already specified these attributes"); + + pAttrs->enmCode = enmCode; + pAttrs->enmData = enmData; + pAttrs->enmDataDep = enmDataDep; + return RTEXITCODE_SUCCESS; +} + +/** + * Parses a D pragma statement. + * + * @returns Suitable exit code, errors message already written on failure. + * @param pStrm The stream. + */ +static RTEXITCODE parsePragma(PSCMSTREAM pStrm) +{ + RTEXITCODE rcExit; + unsigned ch = parseGetNextNonSpaceNonCommentChOnPpLine(pStrm); + if (ch == ~(unsigned)0) + rcExit = RTEXITCODE_FAILURE; + else if (ch == 'D' && ScmStreamCMatchingWordM1(pStrm, RT_STR_TUPLE("D"))) + { + ch = parseGetNextNonSpaceNonCommentChOnPpLine(pStrm); + if (ch == ~(unsigned)0) + rcExit = RTEXITCODE_FAILURE; + else if (ch == 'a' && ScmStreamCMatchingWordM1(pStrm, RT_STR_TUPLE("attributes"))) + rcExit = parsePragmaDAttributes(pStrm); + else + rcExit = parseError(pStrm, 1, "Unknown pragma D"); + } + else + rcExit = parseError(pStrm, 1, "Unknown pragma"); + return rcExit; +} + + +/** + * Classifies the given type expression. + * + * @return Type flags. + * @param pszType The type expression. + * @param pStrm The input stream (for errors + warnings). + * @param offSrc The absolute source position of this expression (for + * warnings). + */ +static uint32_t parseTypeExpression(const char *pszType, PSCMSTREAM pStrm, size_t offSrc) +{ + size_t cchType = strlen(pszType); +#define MY_STRMATCH(a_sz) (cchType == sizeof(a_sz) - 1 && !memcmp(a_sz, pszType, sizeof(a_sz) - 1)) + + /* + * Try detect pointers. + */ + if (pszType[cchType - 1] == '*') + { + if (MY_STRMATCH("const char *")) return VTG_TYPE_POINTER | VTG_TYPE_CONST_CHAR_PTR; + return VTG_TYPE_POINTER; + } + if (pszType[cchType - 1] == '&') + { + parseWarnAbs(pStrm, offSrc, "Please avoid using references like '%s' for probe arguments!", pszType); + return VTG_TYPE_POINTER; + } + + /* + * Standard integer types and IPRT variants. + * It's important that we catch all types larger than 32-bit here or we'll + * screw up the probe argument handling. + */ + if (MY_STRMATCH("int")) return VTG_TYPE_FIXED_SIZED | sizeof(int) | VTG_TYPE_SIGNED; + if (MY_STRMATCH("uintptr_t")) return VTG_TYPE_HC_ARCH_SIZED | VTG_TYPE_UNSIGNED; + if (MY_STRMATCH("intptr_t")) return VTG_TYPE_HC_ARCH_SIZED | VTG_TYPE_SIGNED; + + //if (MY_STRMATCH("uint128_t")) return VTG_TYPE_FIXED_SIZED | sizeof(uint128_t) | VTG_TYPE_UNSIGNED; + if (MY_STRMATCH("uint64_t")) return VTG_TYPE_FIXED_SIZED | sizeof(uint64_t) | VTG_TYPE_UNSIGNED; + if (MY_STRMATCH("uint32_t")) return VTG_TYPE_FIXED_SIZED | sizeof(uint32_t) | VTG_TYPE_UNSIGNED; + if (MY_STRMATCH("uint16_t")) return VTG_TYPE_FIXED_SIZED | sizeof(uint16_t) | VTG_TYPE_UNSIGNED; + if (MY_STRMATCH("uint8_t")) return VTG_TYPE_FIXED_SIZED | sizeof(uint8_t) | VTG_TYPE_UNSIGNED; + + //if (MY_STRMATCH("int128_t")) return VTG_TYPE_FIXED_SIZED | sizeof(int128_t) | VTG_TYPE_SIGNED; + if (MY_STRMATCH("int64_t")) return VTG_TYPE_FIXED_SIZED | sizeof(int64_t) | VTG_TYPE_SIGNED; + if (MY_STRMATCH("int32_t")) return VTG_TYPE_FIXED_SIZED | sizeof(int32_t) | VTG_TYPE_SIGNED; + if (MY_STRMATCH("int16_t")) return VTG_TYPE_FIXED_SIZED | sizeof(int16_t) | VTG_TYPE_SIGNED; + if (MY_STRMATCH("int8_t")) return VTG_TYPE_FIXED_SIZED | sizeof(int8_t) | VTG_TYPE_SIGNED; + + if (MY_STRMATCH("RTUINT64U")) return VTG_TYPE_FIXED_SIZED | sizeof(uint64_t) | VTG_TYPE_UNSIGNED; + if (MY_STRMATCH("RTUINT32U")) return VTG_TYPE_FIXED_SIZED | sizeof(uint32_t) | VTG_TYPE_UNSIGNED; + if (MY_STRMATCH("RTUINT16U")) return VTG_TYPE_FIXED_SIZED | sizeof(uint16_t) | VTG_TYPE_UNSIGNED; + + if (MY_STRMATCH("RTMSINTERVAL")) return VTG_TYPE_FIXED_SIZED | sizeof(RTMSINTERVAL) | VTG_TYPE_UNSIGNED; + if (MY_STRMATCH("RTTIMESPEC")) return VTG_TYPE_FIXED_SIZED | sizeof(RTTIMESPEC) | VTG_TYPE_SIGNED; + if (MY_STRMATCH("RTPROCESS")) return VTG_TYPE_FIXED_SIZED | sizeof(RTPROCESS) | VTG_TYPE_UNSIGNED; + if (MY_STRMATCH("RTHCPHYS")) return VTG_TYPE_FIXED_SIZED | sizeof(RTHCPHYS) | VTG_TYPE_UNSIGNED | VTG_TYPE_PHYS; + + if (MY_STRMATCH("RTR3PTR")) return VTG_TYPE_CTX_POINTER | VTG_TYPE_CTX_R3; + if (MY_STRMATCH("RTR0PTR")) return VTG_TYPE_CTX_POINTER | VTG_TYPE_CTX_R0; + if (MY_STRMATCH("RTRCPTR")) return VTG_TYPE_CTX_POINTER | VTG_TYPE_CTX_RC; + if (MY_STRMATCH("RTHCPTR")) return VTG_TYPE_CTX_POINTER | VTG_TYPE_CTX_R3 | VTG_TYPE_CTX_R0; + + if (MY_STRMATCH("RTR3UINTPTR")) return VTG_TYPE_CTX_POINTER | VTG_TYPE_CTX_R3 | VTG_TYPE_UNSIGNED; + if (MY_STRMATCH("RTR0UINTPTR")) return VTG_TYPE_CTX_POINTER | VTG_TYPE_CTX_R0 | VTG_TYPE_UNSIGNED; + if (MY_STRMATCH("RTRCUINTPTR")) return VTG_TYPE_CTX_POINTER | VTG_TYPE_CTX_RC | VTG_TYPE_UNSIGNED; + if (MY_STRMATCH("RTHCUINTPTR")) return VTG_TYPE_CTX_POINTER | VTG_TYPE_CTX_R3 | VTG_TYPE_CTX_R0 | VTG_TYPE_UNSIGNED; + + if (MY_STRMATCH("RTR3INTPTR")) return VTG_TYPE_CTX_POINTER | VTG_TYPE_CTX_R3 | VTG_TYPE_SIGNED; + if (MY_STRMATCH("RTR0INTPTR")) return VTG_TYPE_CTX_POINTER | VTG_TYPE_CTX_R0 | VTG_TYPE_SIGNED; + if (MY_STRMATCH("RTRCINTPTR")) return VTG_TYPE_CTX_POINTER | VTG_TYPE_CTX_RC | VTG_TYPE_SIGNED; + if (MY_STRMATCH("RTHCINTPTR")) return VTG_TYPE_CTX_POINTER | VTG_TYPE_CTX_R3 | VTG_TYPE_CTX_R0 | VTG_TYPE_SIGNED; + + if (MY_STRMATCH("RTUINTPTR")) return VTG_TYPE_CTX_POINTER | VTG_TYPE_CTX_R3 | VTG_TYPE_CTX_R0 | VTG_TYPE_CTX_RC | VTG_TYPE_UNSIGNED; + if (MY_STRMATCH("RTINTPTR")) return VTG_TYPE_CTX_POINTER | VTG_TYPE_CTX_R3 | VTG_TYPE_CTX_R0 | VTG_TYPE_CTX_RC | VTG_TYPE_SIGNED; + + if (MY_STRMATCH("RTHCUINTREG")) return VTG_TYPE_HC_ARCH_SIZED | VTG_TYPE_CTX_R3 | VTG_TYPE_CTX_R0 | VTG_TYPE_UNSIGNED; + if (MY_STRMATCH("RTR3UINTREG")) return VTG_TYPE_HC_ARCH_SIZED | VTG_TYPE_CTX_R3 | VTG_TYPE_UNSIGNED; + if (MY_STRMATCH("RTR0UINTREG")) return VTG_TYPE_HC_ARCH_SIZED | VTG_TYPE_CTX_R3 | VTG_TYPE_UNSIGNED; + + if (MY_STRMATCH("RTGCUINTREG")) return VTG_TYPE_FIXED_SIZED | sizeof(RTGCUINTREG) | VTG_TYPE_UNSIGNED | VTG_TYPE_CTX_GST; + if (MY_STRMATCH("RTGCPTR")) return VTG_TYPE_FIXED_SIZED | sizeof(RTGCPTR) | VTG_TYPE_UNSIGNED | VTG_TYPE_CTX_GST; + if (MY_STRMATCH("RTGCINTPTR")) return VTG_TYPE_FIXED_SIZED | sizeof(RTGCUINTPTR) | VTG_TYPE_SIGNED | VTG_TYPE_CTX_GST; + if (MY_STRMATCH("RTGCPTR32")) return VTG_TYPE_FIXED_SIZED | sizeof(RTGCPTR32) | VTG_TYPE_SIGNED | VTG_TYPE_CTX_GST; + if (MY_STRMATCH("RTGCPTR64")) return VTG_TYPE_FIXED_SIZED | sizeof(RTGCPTR64) | VTG_TYPE_SIGNED | VTG_TYPE_CTX_GST; + if (MY_STRMATCH("RTGCPHYS")) return VTG_TYPE_FIXED_SIZED | sizeof(RTGCPHYS) | VTG_TYPE_UNSIGNED | VTG_TYPE_PHYS | VTG_TYPE_CTX_GST; + if (MY_STRMATCH("RTGCPHYS32")) return VTG_TYPE_FIXED_SIZED | sizeof(RTGCPHYS32) | VTG_TYPE_UNSIGNED | VTG_TYPE_PHYS | VTG_TYPE_CTX_GST; + if (MY_STRMATCH("RTGCPHYS64")) return VTG_TYPE_FIXED_SIZED | sizeof(RTGCPHYS64) | VTG_TYPE_UNSIGNED | VTG_TYPE_PHYS | VTG_TYPE_CTX_GST; + + /* + * The special VBox types. + */ + if (MY_STRMATCH("PVM")) return VTG_TYPE_POINTER; + if (MY_STRMATCH("PVMCPU")) return VTG_TYPE_POINTER; + if (MY_STRMATCH("PCPUMCTX")) return VTG_TYPE_POINTER; + + /* + * Preaching time. + */ + if ( MY_STRMATCH("unsigned long") + || MY_STRMATCH("unsigned long long") + || MY_STRMATCH("signed long") + || MY_STRMATCH("signed long long") + || MY_STRMATCH("long") + || MY_STRMATCH("long long") + || MY_STRMATCH("char") + || MY_STRMATCH("signed char") + || MY_STRMATCH("unsigned char") + || MY_STRMATCH("double") + || MY_STRMATCH("long double") + || MY_STRMATCH("float") + ) + { + RTMsgError("Please do NOT use the type '%s' for probe arguments!", pszType); + g_cTypeErrors++; + return 0; + } + + if ( MY_STRMATCH("unsigned") + || MY_STRMATCH("signed") + || MY_STRMATCH("signed int") + || MY_STRMATCH("unsigned int") + || MY_STRMATCH("short") + || MY_STRMATCH("signed short") + || MY_STRMATCH("unsigned short") + ) + parseWarnAbs(pStrm, offSrc, "Please avoid using the type '%s' for probe arguments!", pszType); + if (MY_STRMATCH("unsigned")) return VTG_TYPE_FIXED_SIZED | sizeof(int) | VTG_TYPE_UNSIGNED; + if (MY_STRMATCH("unsigned int")) return VTG_TYPE_FIXED_SIZED | sizeof(int) | VTG_TYPE_UNSIGNED; + if (MY_STRMATCH("signed")) return VTG_TYPE_FIXED_SIZED | sizeof(int) | VTG_TYPE_SIGNED; + if (MY_STRMATCH("signed int")) return VTG_TYPE_FIXED_SIZED | sizeof(int) | VTG_TYPE_SIGNED; + if (MY_STRMATCH("short")) return VTG_TYPE_FIXED_SIZED | sizeof(short) | VTG_TYPE_SIGNED; + if (MY_STRMATCH("signed short")) return VTG_TYPE_FIXED_SIZED | sizeof(short) | VTG_TYPE_SIGNED; + if (MY_STRMATCH("unsigned short")) return VTG_TYPE_FIXED_SIZED | sizeof(short) | VTG_TYPE_UNSIGNED; + + /* + * What we haven't caught by now is either unknown to us or wrong. + */ + if (pszType[0] == 'P') + { + RTMsgError("Type '%s' looks like a pointer typedef, please do NOT use those " + "but rather the non-pointer typedef or struct with '*'", + pszType); + g_cTypeErrors++; + return VTG_TYPE_POINTER; + } + + RTMsgError("Don't know '%s' - please change or fix VBoxTpG", pszType); + g_cTypeErrors++; + +#undef MY_STRCMP + return 0; +} + + +/** + * Initializes the members of an argument. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg. + * @param pProbe The probe. + * @param pArg The argument. + * @param pStrm The input stream (for errors + warnings). + * @param pchType The type. + * @param cchType The type length. + * @param pchName The name. + * @param cchName The name length. + */ +static RTEXITCODE parseInitArgument(PVTGPROBE pProbe, PVTGARG pArg, PSCMSTREAM pStrm, + char *pchType, size_t cchType, char *pchName, size_t cchName) +{ + Assert(!pArg->pszName); Assert(!pArg->pszTracerType); Assert(!pArg->pszCtxType); Assert(!pArg->fType); + + pArg->pszArgPassingFmt = ", %s"; + pArg->pszName = RTStrDupN(pchName, cchName); + pArg->pszTracerType = strtabInsertN(pchType, cchType); + if (!pArg->pszTracerType || !pArg->pszName) + return parseError(pStrm, 1, "Out of memory"); + pArg->fType = parseTypeExpression(pArg->pszTracerType, pStrm, pArg->offSrc); + + if ( (pArg->fType & VTG_TYPE_POINTER) + && !(g_fTypeContext & VTG_TYPE_CTX_R0) ) + { + pArg->fType &= ~VTG_TYPE_POINTER; + if ( !strcmp(pArg->pszTracerType, "struct VM *") || !strcmp(pArg->pszTracerType, "PVM") + || !strcmp(pArg->pszTracerType, "struct VMCPU *") || !strcmp(pArg->pszTracerType, "PVMCPU") + || !strcmp(pArg->pszTracerType, "struct CPUMCTX *") || !strcmp(pArg->pszTracerType, "PCPUMCTX") + ) + { + pArg->fType |= VTG_TYPE_CTX_POINTER | VTG_TYPE_CTX_R0 + | VTG_TYPE_FIXED_SIZED | (g_cHostBits / 8) + | VTG_TYPE_AUTO_CONV_PTR; + pArg->pszCtxType = RTStrDup("RTR0PTR"); + + if (!strcmp(pArg->pszTracerType, "struct VM *") || !strcmp(pArg->pszTracerType, "PVM")) + pArg->pszArgPassingFmt = ", VTG_VM_TO_R0(%s)"; + else if (!strcmp(pArg->pszTracerType, "struct VMCPU *") || !strcmp(pArg->pszTracerType, "PVMCPU")) + pArg->pszArgPassingFmt = ", VTG_VMCPU_TO_R0(%s)"; + else + { + PVTGARG pFirstArg = RTListGetFirst(&pProbe->ArgHead, VTGARG, ListEntry); + if ( !pFirstArg + || pFirstArg == pArg + || strcmp(pFirstArg->pszName, "a_pVCpu") + || ( strcmp(pFirstArg->pszTracerType, "struct VMCPU *") + && strcmp(pFirstArg->pszTracerType, "PVMCPU *")) ) + return parseError(pStrm, 1, "The automatic ring-0 pointer conversion requires 'a_pVCpu' with type 'struct VMCPU *' as the first argument"); + + if (!strcmp(pArg->pszTracerType, "struct CPUMCTX *")|| !strcmp(pArg->pszTracerType, "PCPUMCTX")) + pArg->pszArgPassingFmt = ", VTG_CPUMCTX_TO_R0(a_pVCpu, %s)"; + else + pArg->pszArgPassingFmt = ", VBoxTpG-Is-Buggy!!"; + } + } + else + { + pArg->fType |= VTG_TYPE_CTX_POINTER | g_fTypeContext | VTG_TYPE_FIXED_SIZED | (g_cBits / 8); + pArg->pszCtxType = RTStrDupN(pchType, cchType); + } + } + else + pArg->pszCtxType = RTStrDupN(pchType, cchType); + if (!pArg->pszCtxType) + return parseError(pStrm, 1, "Out of memory"); + + return RTEXITCODE_SUCCESS; +} + + +/** + * Unmangles the probe name. + * + * This involves translating double underscore to dash. + * + * @returns Pointer to the unmangled name in the string table. + * @param pszMangled The mangled name. + */ +static const char *parseUnmangleProbeName(const char *pszMangled) +{ + size_t cchMangled = strlen(pszMangled); + char *pszTmp = (char *)alloca(cchMangled + 2); + const char *pszSrc = pszMangled; + char *pszDst = pszTmp; + + while (*pszSrc) + { + if (pszSrc[0] == '_' && pszSrc[1] == '_' && pszSrc[2] != '_') + { + *pszDst++ = '-'; + pszSrc += 2; + } + else + *pszDst++ = *pszSrc++; + } + *pszDst = '\0'; + + return strtabInsertN(pszTmp, pszDst - pszTmp); +} + + +/** + * 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. + */ +static RTEXITCODE parseProbe(PSCMSTREAM pStrm, PVTGPROVIDER pProv) +{ + size_t const iProbeLine = ScmStreamTellLine(pStrm); + + /* + * Next up is a name followed by an opening parenthesis. + */ + size_t cchProbe; + const char *pszProbe = parseGetNextCWord(pStrm, &cchProbe); + if (!pszProbe) + return parseError(pStrm, 1, "Expected a probe name starting with an alphabetical character"); + unsigned ch = parseGetNextNonSpaceNonCommentCh(pStrm); + if (ch != '(') + return parseError(pStrm, 1, "Expected '(' after the probe name"); + + /* + * Create a probe instance. + */ + PVTGPROBE pProbe = (PVTGPROBE)RTMemAllocZ(sizeof(*pProbe)); + if (!pProbe) + return parseError(pStrm, 0, "Out of memory"); + RTListInit(&pProbe->ArgHead); + RTListAppend(&pProv->ProbeHead, &pProbe->ListEntry); + pProbe->offArgList = UINT32_MAX; + pProbe->iLine = iProbeLine; + pProbe->pszMangledName = RTStrDupN(pszProbe, cchProbe); + if (!pProbe->pszMangledName) + return parseError(pStrm, 0, "Out of memory"); + pProbe->pszUnmangledName = parseUnmangleProbeName(pProbe->pszMangledName); + if (!pProbe->pszUnmangledName) + return parseError(pStrm, 0, "Out of memory"); + + /* + * Parse loop for the argument. + */ + PVTGARG pArg = NULL; + size_t cchName = 0; + size_t cchArg = 0; + char szArg[4096]; + for (;;) + { + ch = parseGetNextNonSpaceNonCommentCh(pStrm); + switch (ch) + { + case ')': + case ',': + { + /* commit the argument */ + if (pArg) + { + if (!cchName) + return parseError(pStrm, 1, "Argument has no name"); + if (cchArg - cchName - 1 >= 128) + return parseError(pStrm, 1, "Argument type too long"); + RTEXITCODE rcExit = parseInitArgument(pProbe, pArg, pStrm, + szArg, cchArg - cchName - 1, + &szArg[cchArg - cchName], cchName); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + if (VTG_TYPE_IS_LARGE(pArg->fType)) + pProbe->fHaveLargeArgs = true; + pArg = NULL; + cchName = cchArg = 0; + } + if (ch == ')') + { + size_t off = ScmStreamTell(pStrm); + ch = parseGetNextNonSpaceNonCommentCh(pStrm); + if (ch != ';') + return parseErrorAbs(pStrm, off, "Expected ';'"); + return RTEXITCODE_SUCCESS; + } + break; + } + + default: + { + size_t cchWord; + const char *pszWord = ScmStreamCGetWordM1(pStrm, &cchWord); + if (!pszWord) + return parseError(pStrm, 0, "Expected argument"); + if (!pArg) + { + pArg = (PVTGARG)RTMemAllocZ(sizeof(*pArg)); + if (!pArg) + return parseError(pStrm, 1, "Out of memory"); + RTListAppend(&pProbe->ArgHead, &pArg->ListEntry); + pArg->iArgNo = pProbe->cArgs++; + pArg->pProbe = pProbe; + pArg->offSrc = ScmStreamTell(pStrm) - cchWord; + + if (cchWord + 1 > sizeof(szArg)) + return parseError(pStrm, 1, "Too long parameter declaration"); + memcpy(szArg, pszWord, cchWord); + szArg[cchWord] = '\0'; + cchArg = cchWord; + cchName = 0; + } + else + { + if (cchArg + 1 + cchWord + 1 > sizeof(szArg)) + return parseError(pStrm, 1, "Too long parameter declaration"); + + szArg[cchArg++] = ' '; + memcpy(&szArg[cchArg], pszWord, cchWord); + cchArg += cchWord; + szArg[cchArg] = '\0'; + cchName = cchWord; + } + break; + } + + case '*': + { + if (!pArg) + return parseError(pStrm, 1, "A parameter type does not start with an asterix"); + if (cchArg + sizeof(" *") >= sizeof(szArg)) + return parseError(pStrm, 1, "Too long parameter declaration"); + szArg[cchArg++] = ' '; + szArg[cchArg++] = '*'; + szArg[cchArg ] = '\0'; + cchName = 0; + break; + } + + case ~(unsigned)0: + return parseError(pStrm, 0, "Missing closing ')' on probe"); + } + } +} + +/** + * Parses a D provider statement. + * + * @returns Suitable exit code, errors message already written on failure. + * @param pStrm The stream. + */ +static RTEXITCODE parseProvider(PSCMSTREAM pStrm) +{ + /* + * Next up is a name followed by a curly bracket. Ignore comments. + */ + RTEXITCODE rcExit = parseSkipSpacesAndComments(pStrm); + if (rcExit != RTEXITCODE_SUCCESS) + return parseError(pStrm, 1, "Expected a provider name starting with an alphabetical character"); + size_t cchName; + const char *pszName = ScmStreamCGetWord(pStrm, &cchName); + if (!pszName) + return parseError(pStrm, 0, "Bad provider name"); + if (RT_C_IS_DIGIT(pszName[cchName - 1])) + return parseError(pStrm, 1, "A provider name cannot end with digit"); + + unsigned ch = parseGetNextNonSpaceNonCommentCh(pStrm); + if (ch != '{') + return parseError(pStrm, 1, "Expected '{' after the provider name"); + + /* + * Create a provider instance. + */ + PVTGPROVIDER pProv = (PVTGPROVIDER)RTMemAllocZ(sizeof(*pProv)); + if (!pProv) + return parseError(pStrm, 0, "Out of memory"); + RTListInit(&pProv->ProbeHead); + RTListAppend(&g_ProviderHead, &pProv->ListEntry); + pProv->pszName = strtabInsertN(pszName, cchName); + if (!pProv->pszName) + return parseError(pStrm, 0, "Out of memory"); + + /* + * Parse loop. + */ + for (;;) + { + ch = parseGetNextNonSpaceNonCommentCh(pStrm); + switch (ch) + { + case 'p': + if (ScmStreamCMatchingWordM1(pStrm, RT_STR_TUPLE("probe"))) + rcExit = parseProbe(pStrm, pProv); + else + rcExit = parseError(pStrm, 1, "Unexpected character"); + break; + + case '}': + { + size_t off = ScmStreamTell(pStrm); + ch = parseGetNextNonSpaceNonCommentCh(pStrm); + if (ch == ';') + return RTEXITCODE_SUCCESS; + rcExit = parseErrorAbs(pStrm, off, "Expected ';'"); + break; + } + + case ~(unsigned)0: + rcExit = parseError(pStrm, 0, "Missing closing '}' on provider"); + break; + + default: + rcExit = parseError(pStrm, 1, "Unexpected character"); + break; + } + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + } +} + + +static RTEXITCODE parseScript(const char *pszScript) +{ + SCMSTREAM Strm; + int rc = ScmStreamInitForReading(&Strm, pszScript); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to open & read '%s' into memory: %Rrc", pszScript, rc); + if (g_cVerbosity > 0) + RTMsgInfo("Parsing '%s'...", pszScript); + + RTEXITCODE rcExit = RTEXITCODE_SUCCESS; + unsigned ch; + while ((ch = ScmStreamGetCh(&Strm)) != ~(unsigned)0) + { + if (RT_C_IS_SPACE(ch)) + continue; + switch (ch) + { + case '/': + ch = ScmStreamGetCh(&Strm); + if (ch == '*') + rcExit = parseMultiLineComment(&Strm); + else if (ch == '/') + rcExit = parseOneLineComment(&Strm); + else + rcExit = parseError(&Strm, 2, "Unexpected character"); + break; + + case 'p': + if (ScmStreamCMatchingWordM1(&Strm, RT_STR_TUPLE("provider"))) + rcExit = parseProvider(&Strm); + else + rcExit = parseError(&Strm, 1, "Unexpected character"); + break; + + case '#': + { + ch = parseGetNextNonSpaceNonCommentChOnPpLine(&Strm); + if (ch == ~(unsigned)0) + rcExit = RTEXITCODE_FAILURE; + else if (ch == 'p' && ScmStreamCMatchingWordM1(&Strm, RT_STR_TUPLE("pragma"))) + rcExit = parsePragma(&Strm); + else + rcExit = parseError(&Strm, 1, "Unsupported preprocessor directive"); + break; + } + + default: + rcExit = parseError(&Strm, 1, "Unexpected character"); + break; + } + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + } + + ScmStreamDelete(&Strm); + if (g_cVerbosity > 0 && rcExit == RTEXITCODE_SUCCESS) + RTMsgInfo("Successfully parsed '%s'.", pszScript); + return rcExit; +} + + +/** + * Parses the arguments. + */ +static RTEXITCODE parseArguments(int argc, char **argv) +{ + /* + * Set / Adjust defaults. + */ + int rc = RTPathAbs(g_pszAssemblerIncVal, g_szAssemblerIncVal, sizeof(g_szAssemblerIncVal) - 1); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTPathAbs failed: %Rrc", rc); + strcat(g_szAssemblerIncVal, "/"); + g_pszAssemblerIncVal = g_szAssemblerIncVal; + + /* + * Option config. + */ + enum + { + kVBoxTpGOpt_32Bit = 1000, + kVBoxTpGOpt_64Bit, + kVBoxTpGOpt_GenerateWrapperHeader, + kVBoxTpGOpt_Assembler, + kVBoxTpGOpt_AssemblerFmtOpt, + kVBoxTpGOpt_AssemblerFmtVal, + kVBoxTpGOpt_AssemblerOutputOpt, + kVBoxTpGOpt_AssemblerOption, + kVBoxTpGOpt_Pic, + kVBoxTpGOpt_ProbeFnName, + kVBoxTpGOpt_ProbeFnImported, + kVBoxTpGOpt_ProbeFnNotImported, + kVBoxTpGOpt_Host32Bit, + kVBoxTpGOpt_Host64Bit, + kVBoxTpGOpt_RawModeContext, + kVBoxTpGOpt_Ring0Context, + kVBoxTpGOpt_Ring0ContextAgnostic, + kVBoxTpGOpt_Ring3Context, + kVBoxTpGOpt_End + }; + + static RTGETOPTDEF const s_aOpts[] = + { + /* dtrace w/ long options */ + { "-32", kVBoxTpGOpt_32Bit, RTGETOPT_REQ_NOTHING }, + { "-64", kVBoxTpGOpt_64Bit, RTGETOPT_REQ_NOTHING }, + { "--apply-cpp", 'C', RTGETOPT_REQ_NOTHING }, + { "--generate-obj", 'G', RTGETOPT_REQ_NOTHING }, + { "--generate-header", 'h', RTGETOPT_REQ_NOTHING }, + { "--output", 'o', RTGETOPT_REQ_STRING }, + { "--script", 's', RTGETOPT_REQ_STRING }, + { "--verbose", 'v', RTGETOPT_REQ_NOTHING }, + /* our stuff */ + { "--generate-wrapper-header", kVBoxTpGOpt_GenerateWrapperHeader, RTGETOPT_REQ_NOTHING }, + { "--assembler", kVBoxTpGOpt_Assembler, RTGETOPT_REQ_STRING }, + { "--assembler-fmt-opt", kVBoxTpGOpt_AssemblerFmtOpt, RTGETOPT_REQ_STRING }, + { "--assembler-fmt-val", kVBoxTpGOpt_AssemblerFmtVal, RTGETOPT_REQ_STRING }, + { "--assembler-output-opt", kVBoxTpGOpt_AssemblerOutputOpt, RTGETOPT_REQ_STRING }, + { "--assembler-option", kVBoxTpGOpt_AssemblerOption, RTGETOPT_REQ_STRING }, + { "--pic", kVBoxTpGOpt_Pic, RTGETOPT_REQ_NOTHING }, + { "--probe-fn-name", kVBoxTpGOpt_ProbeFnName, RTGETOPT_REQ_STRING }, + { "--probe-fn-imported", kVBoxTpGOpt_ProbeFnImported, RTGETOPT_REQ_NOTHING }, + { "--probe-fn-not-imported", kVBoxTpGOpt_ProbeFnNotImported, RTGETOPT_REQ_NOTHING }, + { "--host-32-bit", kVBoxTpGOpt_Host32Bit, RTGETOPT_REQ_NOTHING }, + { "--host-64-bit", kVBoxTpGOpt_Host64Bit, RTGETOPT_REQ_NOTHING }, + { "--raw-mode-context", kVBoxTpGOpt_RawModeContext, RTGETOPT_REQ_NOTHING }, + { "--ring-0-context", kVBoxTpGOpt_Ring0Context, RTGETOPT_REQ_NOTHING }, + { "--ring-0-context-agnostic", kVBoxTpGOpt_Ring0ContextAgnostic, RTGETOPT_REQ_NOTHING }, + { "--ring-3-context", kVBoxTpGOpt_Ring3Context, RTGETOPT_REQ_NOTHING }, + /** @todo We're missing a bunch of assembler options! */ + }; + + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetOptState; + rc = RTGetOptInit(&GetOptState, argc, argv, &s_aOpts[0], RT_ELEMENTS(s_aOpts), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST); + AssertReleaseRCReturn(rc, RTEXITCODE_FAILURE); + + /* + * Process the options. + */ + while ((rc = RTGetOpt(&GetOptState, &ValueUnion)) != 0) + { + switch (rc) + { + /* + * DTrace compatible options. + */ + case kVBoxTpGOpt_32Bit: + g_cHostBits = g_cBits = 32; + g_pszAssemblerFmtVal = g_szAssemblerFmtVal32; + break; + + case kVBoxTpGOpt_64Bit: + g_cHostBits = g_cBits = 64; + g_pszAssemblerFmtVal = g_szAssemblerFmtVal64; + 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 + && g_enmAction != kVBoxTpGAction_GenerateObject) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "-G does not mix with -h or --generate-wrapper-header"); + g_enmAction = kVBoxTpGAction_GenerateObject; + break; + + case 'h': + if (!strcmp(GetOptState.pDef->pszLong, "--generate-header")) + { + if ( g_enmAction != kVBoxTpGAction_Nothing + && g_enmAction != kVBoxTpGAction_GenerateHeader) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "-h does not mix with -G or --generate-wrapper-header"); + g_enmAction = kVBoxTpGAction_GenerateHeader; + } + else + { + /* --help or similar */ + RTPrintf("VirtualBox Tracepoint Generator\n" + "\n" + "Usage: %s [options]\n" + "\n" + "Options:\n", RTProcShortName()); + for (size_t i = 0; i < RT_ELEMENTS(s_aOpts); i++) + if ((unsigned)s_aOpts[i].iShort < 128) + RTPrintf(" -%c,%s\n", s_aOpts[i].iShort, s_aOpts[i].pszLong); + else + RTPrintf(" %s\n", s_aOpts[i].pszLong); + return RTEXITCODE_SUCCESS; + } + break; + + case 'o': + if (g_pszOutput) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Output file is already set to '%s'", g_pszOutput); + g_pszOutput = ValueUnion.psz; + break; + + case 's': + if (g_pszScript) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Script file is already set to '%s'", g_pszScript); + g_pszScript = ValueUnion.psz; + 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: 154972 $"; + const char *psz = RTStrStripL(strchr(s_szRev, ' ')); + RTPrintf("r%.*s\n", strchr(psz, ' ') - psz, psz); + return RTEXITCODE_SUCCESS; + } + + case VINF_GETOPT_NOT_OPTION: + if (g_enmAction == kVBoxTpGAction_GenerateObject) + break; /* object files, ignore them. */ + return RTGetOptPrintError(rc, &ValueUnion); + + + /* + * Our options. + */ + case kVBoxTpGOpt_GenerateWrapperHeader: + if ( g_enmAction != kVBoxTpGAction_Nothing + && g_enmAction != kVBoxTpGAction_GenerateWrapperHeader) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "--generate-wrapper-header does not mix with -h or -G"); + g_enmAction = kVBoxTpGAction_GenerateWrapperHeader; + break; + + case kVBoxTpGOpt_Assembler: + g_pszAssembler = ValueUnion.psz; + break; + + case kVBoxTpGOpt_AssemblerFmtOpt: + g_pszAssemblerFmtOpt = ValueUnion.psz; + break; + + case kVBoxTpGOpt_AssemblerFmtVal: + g_pszAssemblerFmtVal = ValueUnion.psz; + break; + + case kVBoxTpGOpt_AssemblerOutputOpt: + g_pszAssemblerOutputOpt = ValueUnion.psz; + break; + + case kVBoxTpGOpt_AssemblerOption: + if (g_cAssemblerOptions >= RT_ELEMENTS(g_apszAssemblerOptions)) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Too many assembly options (max %u)", RT_ELEMENTS(g_apszAssemblerOptions)); + g_apszAssemblerOptions[g_cAssemblerOptions] = ValueUnion.psz; + g_cAssemblerOptions++; + break; + + case kVBoxTpGOpt_Pic: + g_fPic = true; + break; + + case kVBoxTpGOpt_ProbeFnName: + g_pszProbeFnName = ValueUnion.psz; + break; + + case kVBoxTpGOpt_ProbeFnImported: + g_fProbeFnImported = true; + break; + + case kVBoxTpGOpt_ProbeFnNotImported: + g_fProbeFnImported = false; + break; + + case kVBoxTpGOpt_Host32Bit: + g_cHostBits = 32; + break; + + case kVBoxTpGOpt_Host64Bit: + g_cHostBits = 64; + break; + + case kVBoxTpGOpt_RawModeContext: + g_fTypeContext = VTG_TYPE_CTX_RC; + g_pszContextDefine = "IN_RC"; + g_pszContextDefine2 = NULL; + break; + + case kVBoxTpGOpt_Ring0Context: + g_fTypeContext = VTG_TYPE_CTX_R0; + g_pszContextDefine = "IN_RING0"; + g_pszContextDefine2 = NULL; + break; + + case kVBoxTpGOpt_Ring0ContextAgnostic: + g_fTypeContext = VTG_TYPE_CTX_R0; + g_pszContextDefine = "IN_RING0_AGNOSTIC"; + g_pszContextDefine2 = "IN_RING0"; + break; + + case kVBoxTpGOpt_Ring3Context: + g_fTypeContext = VTG_TYPE_CTX_R3; + g_pszContextDefine = "IN_RING3"; + g_pszContextDefine2 = NULL; + break; + + + /* + * Errors and bugs. + */ + default: + return RTGetOptPrintError(rc, &ValueUnion); + } + } + + /* + * Check that we've got all we need. + */ + if (g_enmAction == kVBoxTpGAction_Nothing) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "No action specified (-h, -G or --generate-wrapper-header)"); + if (!g_pszScript) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "No script file specified (-s)"); + if (!g_pszOutput) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "No output file specified (-o)"); + + return RTEXITCODE_SUCCESS; +} + + +int main(int argc, char **argv) +{ + int rc = RTR3InitExe(argc, &argv, 0); + if (RT_FAILURE(rc)) + return 1; + + RTEXITCODE rcExit = parseArguments(argc, argv); + if (rcExit == RTEXITCODE_SUCCESS) + { + /* + * Parse the script. + */ + RTListInit(&g_ProviderHead); + rcExit = parseScript(g_pszScript); + if (rcExit == RTEXITCODE_SUCCESS) + { + /* + * Take action. + */ + if (g_enmAction == kVBoxTpGAction_GenerateHeader) + rcExit = generateFile(g_pszOutput, "header", generateHeader); + else if (g_enmAction == kVBoxTpGAction_GenerateWrapperHeader) + rcExit = generateFile(g_pszOutput, "wrapper header", generateWrapperHeader); + else + rcExit = generateObject(g_pszOutput, g_pszTempAsm); + } + } + + if (rcExit == RTEXITCODE_SUCCESS && g_cTypeErrors > 0) + rcExit = RTEXITCODE_FAILURE; + return rcExit; +} + diff --git a/src/bldprogs/bin2c.c b/src/bldprogs/bin2c.c new file mode 100644 index 00000000..1e9a5a4d --- /dev/null +++ b/src/bldprogs/bin2c.c @@ -0,0 +1,274 @@ +/* $Id: bin2c.c $ */ +/** @file + * bin2c - Binary 2 C Structure Converter. + */ + +/* + * Copyright (C) 2006-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include +#include +#include +#include + + +/** + * File size. + * + * @returns file size in bytes. + * @returns 0 on failure. + * @param pFile File to size. + */ +static size_t fsize(FILE *pFile) +{ + long cbFile; + off_t Pos = ftell(pFile); + if ( Pos >= 0 + && !fseek(pFile, 0, SEEK_END)) + { + cbFile = ftell(pFile); + if ( cbFile >= 0 + && !fseek(pFile, 0, SEEK_SET)) + return cbFile; + } + return 0; +} + +static int usage(const char *argv0) +{ + fprintf(stderr, + "Syntax: %s [options] \n" + " --min check if is not smaller than KB\n" + " --max check if is not bigger than KB\n" + " --mask check if size of binaryfile is -aligned\n" + " --width number of bytes per line (default: 16)\n" + " --break break every lines (default: -1)\n" + , argv0); + fprintf(stderr, + " --ascii show ASCII representation of binary as comment\n" + " --export emit DECLEXPORT\n" + " --append append to the output file (default: truncate)\n" + " --no-size Skip the size.\n" + " --static Static data scope.\n"); + + return 1; +} + +int main(int argc, char *argv[]) +{ + FILE *pFileIn; + FILE *pFileOut; + int iArg; + size_t cbMin = 0; + size_t cbMax = ~0U; + size_t uMask = 0; + int fAscii = 0; + int fAppend = 0; + int fExport = 0; + int fNoSize = 0; + int fStatic = 0; + long iBreakEvery = -1; + unsigned char abLine[32]; + size_t cbLine = 16; + size_t off; + size_t cbRead; + size_t cbBin; + int rc = 1; /* assume the worst... */ + + if (argc < 2) + return usage(argv[0]); + + for (iArg = 1; iArg < argc; iArg++) + { + if (!strcmp(argv[iArg], "--min") || !strcmp(argv[iArg], "-min")) + { + if (++iArg >= argc) + return usage(argv[0]); + cbMin = 1024 * strtoul(argv[iArg], NULL, 0); + } + else if (!strcmp(argv[iArg], "--max") || !strcmp(argv[iArg], "-max")) + { + if (++iArg >= argc) + return usage(argv[0]); + cbMax = 1024 * strtoul(argv[iArg], NULL, 0); + } + else if (!strcmp(argv[iArg], "--mask") || !strcmp(argv[iArg], "-mask")) + { + if (++iArg >= argc) + return usage(argv[0]); + uMask = strtoul(argv[iArg], NULL, 0); + } + else if (!strcmp(argv[iArg], "--ascii") || !strcmp(argv[iArg], "-ascii")) + fAscii = 1; + else if (!strcmp(argv[iArg], "--append")) + fAppend = 1; + else if (!strcmp(argv[iArg], "--export") || !strcmp(argv[iArg], "-export")) + fExport = 1; + else if (!strcmp(argv[iArg], "--no-size")) + fNoSize = 1; + else if (!strcmp(argv[iArg], "--static")) + fStatic = 1; + else if (!strcmp(argv[iArg], "--width") || !strcmp(argv[iArg], "-width")) + { + if (++iArg >= argc) + return usage(argv[0]); + cbLine = strtoul(argv[iArg], NULL, 0); + if (cbLine == 0 || cbLine > sizeof(abLine)) + { + fprintf(stderr, "%s: '%s' is too wide, max %u\n", + argv[0], argv[iArg], (unsigned)sizeof(abLine)); + return 1; + } + } + else if (!strcmp(argv[iArg], "--break") || !strcmp(argv[iArg], "-break")) + { + if (++iArg >= argc) + return usage(argv[0]); + iBreakEvery = strtol(argv[iArg], NULL, 0); + if (iBreakEvery <= 0 && iBreakEvery != -1) + { + fprintf(stderr, "%s: -break value '%s' is not >= 1 or -1.\n", + argv[0], argv[iArg]); + return 1; + } + } + else if (iArg == argc - 3) + break; + else + { + fprintf(stderr, "%s: syntax error: Unknown argument '%s'\n", + argv[0], argv[iArg]); + return usage(argv[0]); + } + } + + pFileIn = fopen(argv[iArg+1], "rb"); + if (!pFileIn) + { + fprintf(stderr, "Error: failed to open input file '%s'!\n", argv[iArg+1]); + return 1; + } + + pFileOut = fopen(argv[iArg+2], fAppend ? "a" : "w"); /* no b! */ + if (!pFileOut) + { + fprintf(stderr, "Error: failed to open output file '%s'!\n", argv[iArg+2]); + fclose(pFileIn); + return 1; + } + + cbBin = fsize(pFileIn); + + fprintf(pFileOut, + "/*\n" + " * This file was automatically generated\n" + " * from %s\n" + " * by %s.\n" + " */\n" + "\n" + "#include \n" + "\n" + "%sconst unsigned char%s g_ab%s[] =\n" + "{\n", + argv[iArg+1], argv[0], fStatic ? "static " : fExport ? "DECLEXPORT(" : "", !fStatic && fExport ? ")" : "", argv[iArg]); + + /* check size restrictions */ + if (uMask && (cbBin & uMask)) + fprintf(stderr, "%s: size=%ld - Not aligned!\n", argv[0], (long)cbBin); + else if (cbBin < cbMin || cbBin > cbMax) + fprintf(stderr, "%s: size=%ld - Not %ld-%ldb in size!\n", + argv[0], (long)cbBin, (long)cbMin, (long)cbMax); + else + { + /* the binary data */ + off = 0; + while ((cbRead = fread(&abLine[0], 1, cbLine, pFileIn)) > 0) + { + size_t j; + + if ( iBreakEvery > 0 + && off + && (off / cbLine) % iBreakEvery == 0) + fprintf(pFileOut, "\n"); + + fprintf(pFileOut, " "); + for (j = 0; j < cbRead; j++) + fprintf(pFileOut, " 0x%02x,", abLine[j]); + for (; j < cbLine; j++) + fprintf(pFileOut, " "); + if (fAscii) + { + fprintf(pFileOut, " /* 0x%08lx: ", (long)off); + for (j = 0; j < cbRead; j++) + /* be careful with '/' prefixed/followed by a '*'! */ + fprintf(pFileOut, "%c", + isprint(abLine[j]) && abLine[j] != '/' ? abLine[j] : '.'); + for (; j < cbLine; j++) + fprintf(pFileOut, " "); + fprintf(pFileOut, " */"); + } + fprintf(pFileOut, "\n"); + + off += cbRead; + } + + /* check for errors */ + if (ferror(pFileIn) && !feof(pFileIn)) + fprintf(stderr, "%s: read error\n", argv[0]); + else if (off != cbBin) + fprintf(stderr, "%s: read error off=%ld cbBin=%ld\n", argv[0], (long)off, (long)cbBin); + else + { + /* no errors, finish the structure. */ + fprintf(pFileOut, + "};\n"); + + if (!fNoSize) + fprintf(pFileOut, + "\n" + "%sconst unsigned%s g_cb%s = sizeof(g_ab%s);\n", + fExport ? "DECLEXPORT(" : "", fExport ? ")" : "", argv[iArg], argv[iArg]); + + fprintf(pFileOut, "/* end of file */\n"); + + /* flush output and check for error. */ + fflush(pFileOut); + if (ferror(pFileOut)) + fprintf(stderr, "%s: write error\n", argv[0]); + else + rc = 0; /* success! */ + } + } + + /* cleanup, delete the output file on failure. */ + fclose(pFileOut); + fclose(pFileIn); + if (rc) + remove(argv[iArg+2]); + + return rc; +} diff --git a/src/bldprogs/biossums.c b/src/bldprogs/biossums.c new file mode 100644 index 00000000..871c55ae --- /dev/null +++ b/src/bldprogs/biossums.c @@ -0,0 +1,255 @@ +/* $Id: biossums.c $ */ +/** @file + * Tool for modifying a BIOS image to write the BIOS checksum. + */ + +/* + * Copyright (C) 2006-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#include +#include +#include +#include +#include +#ifndef RT_OS_WINDOWS +# include /* unlink */ +#endif + +typedef unsigned char uint8_t; + +static uint8_t abBios[64*1024]; +static FILE *g_pIn = NULL; +static FILE *g_pOut = NULL; +static const char *g_pszOutFile = NULL; +static const char *g_argv0; + +/** + * Find where the filename starts in the given path. + */ +static const char *name(const char *pszPath) +{ + const char *psz = strrchr(pszPath, '/'); +#if defined(_MSC_VER) || defined(__OS2__) + const char *psz2 = strrchr(pszPath, '\\'); + if (!psz2) + psz2 = strrchr(pszPath, ':'); + if (psz2 && (!psz || psz2 > psz)) + psz = psz2; +#endif + return psz ? psz + 1 : pszPath; +} + +/** + * Report an error. + */ +static int fatal(const char *pszFormat, ...) +{ + va_list va; + + fprintf(stderr, "%s: ", name(g_argv0)); + + va_start(va, pszFormat); + vfprintf(stderr, pszFormat, va); + va_end(va); + + /* clean up */ + if (g_pIn) + fclose(g_pIn); + if (g_pOut) + fclose(g_pOut); + if (g_pszOutFile) + unlink(g_pszOutFile); + + return 1; +} + +/** + * Calculate the checksum. + */ +static uint8_t calculateChecksum(uint8_t *pb, size_t cb, size_t iChecksum) +{ + uint8_t u8Sum = 0; + size_t i; + + for (i = 0; i < cb; i++) + if (i != iChecksum) + u8Sum += pb[i]; + + return -u8Sum; +} + +/** + * Find a header in the binary. + * + * @param pb Where to search for the signature + * @param cb Size of the search area + * @param pbHeader Pointer to the start of the signature + * @returns 0 if signature was not found, 1 if found or + * 2 if more than one signature was found */ +static int searchHeader(uint8_t *pb, size_t cb, const char *pszHeader, uint8_t **pbHeader) +{ + int fFound = 0; + unsigned int i; + size_t cbSignature = strlen(pszHeader); + + for (i = 0; i < cb; i += 16) + if (!memcmp(pb + i, pszHeader, cbSignature)) + { + if (fFound++) + return 2; + *pbHeader = pb + i; + } + + return fFound; +} + +int main(int argc, char **argv) +{ + FILE *pIn, *pOut; + size_t cbIn, cbOut; + int fAdapterBios = 0; + + g_argv0 = argv[0]; + + if (argc != 3) + return fatal("Input file name and output file name required.\n"); + + pIn = g_pIn = fopen(argv[1], "rb"); + if (!pIn) + return fatal("Error opening '%s' for reading (%s).\n", argv[1], strerror(errno)); + + pOut = g_pOut = fopen(argv[2], "wb"); + if (!pOut) + return fatal("Error opening '%s' for writing (%s).\n", argv[2], strerror(errno)); + g_pszOutFile = argv[2]; + + /* safety precaution (aka. complete paranoia :-) */ + memset(abBios, 0, sizeof(abBios)); + + cbIn = fread(abBios, 1, sizeof(abBios), pIn); + if (ferror(pIn)) + return fatal("Error reading from '%s' (%s).\n", argv[1], strerror(errno)); + g_pIn = NULL; + fclose(pIn); + + fAdapterBios = abBios[0] == 0x55 && abBios[1] == 0xaa; + + /* align size to page size */ + if ((cbIn % 4096) != 0) + cbIn = (cbIn + 4095) & ~4095; + + if (!fAdapterBios && cbIn != 64*1024) + return fatal("Size of system BIOS is not 64KB!\n"); + + if (fAdapterBios) + { + /* adapter BIOS */ + + /* set the length indicator */ + abBios[2] = (uint8_t)(cbIn / 512); + } + else + { + /* system BIOS */ + size_t cbChecksum; + uint8_t u8Checksum; + uint8_t *pbHeader; + + /* Set the BIOS32 header checksum. */ + switch (searchHeader(abBios, cbIn, "_32_", &pbHeader)) + { + case 0: + return fatal("No BIOS32 header not found!\n"); + case 2: + return fatal("More than one BIOS32 header found!\n"); + case 1: + cbChecksum = (size_t)pbHeader[9] * 16; + u8Checksum = calculateChecksum(pbHeader, cbChecksum, 10); + pbHeader[10] = u8Checksum; + break; + } + + /* Set the PIR header checksum according to PCI IRQ Routing table + * specification version 1.0, Microsoft Corporation, 1996 */ + switch (searchHeader(abBios, cbIn, "$PIR", &pbHeader)) + { + case 0: + return fatal("No PCI IRQ routing table found!\n"); + case 2: + return fatal("More than one PCI IRQ routing table found!\n"); + case 1: + cbChecksum = (size_t)pbHeader[6] + (size_t)pbHeader[7] * 256; + u8Checksum = calculateChecksum(pbHeader, cbChecksum, 31); + pbHeader[31] = u8Checksum; + break; + } + + /* Set the SMBIOS header checksum according to System Management BIOS + * Reference Specification Version 2.5, DSP0134. */ + switch (searchHeader(abBios, cbIn, "_SM_", &pbHeader)) + { + case 0: + return fatal("No SMBIOS header found!\n"); + case 2: + return fatal("More than one SMBIOS header found!\n"); + case 1: + /* at first fix the DMI header starting at SMBIOS header offset 16 */ + u8Checksum = calculateChecksum(pbHeader+16, 15, 5); + pbHeader[21] = u8Checksum; + + /* now fix the checksum of the whole SMBIOS header */ + cbChecksum = (size_t)pbHeader[5]; + u8Checksum = calculateChecksum(pbHeader, cbChecksum, 4); + pbHeader[4] = u8Checksum; + break; + } + + /* If there is a VPD table, adjust its checksum. */ + switch (searchHeader(abBios, cbIn, "\xAA\x55VPD", &pbHeader)) + { + case 0: + break; /* VPD is optional */ + case 2: + return fatal("More than one VPD header found!\n"); + case 1: + cbChecksum = (size_t)pbHeader[5]; + if (cbChecksum < 0x30) + return fatal("VPD size too small!\n"); + u8Checksum = calculateChecksum(pbHeader, cbChecksum, cbChecksum - 1); + pbHeader[cbChecksum - 1] = u8Checksum; + break; + } + } + + /* set the BIOS checksum */ + abBios[cbIn-1] = calculateChecksum(abBios, cbIn, cbIn - 1); + + cbOut = fwrite(abBios, 1, cbIn, pOut); + if (ferror(pOut)) + return fatal("Error writing to '%s' (%s).\n", g_pszOutFile, strerror(errno)); + g_pOut = NULL; + if (fclose(pOut)) + return fatal("Error closing '%s' (%s).\n", g_pszOutFile, strerror(errno)); + + return 0; +} + diff --git a/src/bldprogs/checkUndefined.sh b/src/bldprogs/checkUndefined.sh new file mode 100755 index 00000000..4ff1f56c --- /dev/null +++ b/src/bldprogs/checkUndefined.sh @@ -0,0 +1,102 @@ +#!/bin/sh + +# +# Copyright (C) 2006-2022 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see . +# +# SPDX-License-Identifier: GPL-3.0-only +# + +# +# Compare undefined symbols in a shared or static object against a new-line +# separated list of grep patterns in a set of text files and complain if +# symbols are found which aren't in the files. +# +# Usage: /bin/sh