https://git.reactos.org/?p=reactos.git;a=commitdiff;h=6f87d45e1cc227c84053e…
commit 6f87d45e1cc227c84053e5da112821ea1aebe07e
Author:     Hermès Bélusca-Maïto <hermes.belusca-maito(a)reactos.org>
AuthorDate: Sat Jun 6 20:42:56 2020 +0200
Commit:     Hermès Bélusca-Maïto <hermes.belusca-maito(a)reactos.org>
CommitDate: Wed Aug 19 20:35:55 2020 +0200
    [CMD] Add a command tree dumper, for debugging purposes of the parser code.
    This feature is also present in Windows' CMD, and has been documented
    e.g. at:
https://www.fireeye.com/blog/threat-research/2018/11/cmd-and-conquer-de-dos…
    https://www.real-sec.com/2019/08/cmd-and-conquer-de-dosfuscation-with-flare…
---
 base/shell/cmd/parser.c | 315 +++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 312 insertions(+), 3 deletions(-)
diff --git a/base/shell/cmd/parser.c b/base/shell/cmd/parser.c
index 2a7b653be5d..5661f12024c 100644
--- a/base/shell/cmd/parser.c
+++ b/base/shell/cmd/parser.c
@@ -4,6 +4,15 @@
 #include "precomp.h"
+/*
+ * Parser debugging support. These flags are global so that their values can be
+ * modified at runtime from a debugger. They correspond to the public Windows'
+ * cmd!fDumpTokens and cmd!fDumpParse booleans.
+ * (Same names are used for compatibility as they are documented online.)
+ */
+BOOLEAN fDumpTokens = FALSE;
+BOOLEAN fDumpParse  = FALSE;
+
 #define C_OP_LOWEST C_MULTI
 #define C_OP_HIGHEST C_PIPE
 static const TCHAR OpString[][3] = { _T("&"), _T("||"),
_T("&&"), _T("|") };
@@ -220,7 +229,12 @@ static int ParseToken(TCHAR ExtraEnd, TCHAR *Separators)
         Type = TOK_END;
     }
     *Out = _T('\0');
-    return CurrentTokenType = Type;
+
+    /* Debugging support */
+    if (fDumpTokens)
+        ConOutPrintf(_T("ParseToken: (%d) '%s'\n"), Type,
CurrentToken);
+
+    return (CurrentTokenType = Type);
 }
 static BOOL ParseRedirection(REDIRECTION **List)
@@ -763,6 +777,9 @@ static PARSED_COMMAND *ParseCommandOp(int OpType)
     return Cmd;
 }
+VOID
+DumpCommand(PARSED_COMMAND *Cmd, ULONG SpacePad);
+
 PARSED_COMMAND *
 ParseCommand(LPTSTR Line)
 {
@@ -787,14 +804,19 @@ ParseCommand(LPTSTR Line)
     Cmd = ParseCommandOp(C_OP_LOWEST);
     if (Cmd)
     {
+        bIgnoreEcho = FALSE;
+
         if (CurrentTokenType != TOK_END)
             ParseError();
         if (bParseError)
         {
             FreeCommand(Cmd);
-            Cmd = NULL;
+            return NULL;
         }
-        bIgnoreEcho = FALSE;
+
+        /* Debugging support */
+        if (fDumpParse)
+            DumpCommand(Cmd, 0);
     }
     else
     {
@@ -804,6 +826,293 @@ ParseCommand(LPTSTR Line)
 }
+/*
+ * This function is similar to EchoCommand(), but is used
+ * for dumping the command tree for debugging purposes.
+ */
+static VOID
+DumpRedir(REDIRECTION* Redirections)
+{
+    REDIRECTION* Redir;
+
+    if (Redirections)
+#ifndef MSCMD_ECHO_COMMAND_COMPAT
+        ConOutPuts(_T(" Redir: "));
+#else
+        ConOutPuts(_T("Redir: "));
+#endif
+    for (Redir = Redirections; Redir; Redir = Redir->Next)
+    {
+        ConOutPrintf(_T(" %x %s%s"), Redir->Number,
+                     RedirString[Redir->Mode], Redir->Filename);
+    }
+}
+
+VOID
+DumpCommand(PARSED_COMMAND *Cmd, ULONG SpacePad)
+{
+/*
+ * This macro is like DumpCommand(Cmd, Pad);
+ * but avoids an extra recursive level.
+ * Note that it can be used ONLY for terminating commands!
+ */
+#define DUMP(Command, Pad) \
+do { \
+    Cmd = (Command); \
+    SpacePad = (Pad); \
+    goto dump; \
+} while (0)
+
+    PARSED_COMMAND *Sub;
+
+dump:
+    /* Space padding */
+    ConOutPrintf(_T("%*s"), SpacePad, _T(""));
+
+    switch (Cmd->Type)
+    {
+    case C_COMMAND:
+    {
+        /* Generic command name, and Type */
+#ifndef MSCMD_ECHO_COMMAND_COMPAT
+        ConOutPrintf(_T("Cmd: %s  Type: %x"),
+                     Cmd->Command.First, Cmd->Type);
+#else
+        ConOutPrintf(_T("Cmd: %s  Type: %x "),
+                     Cmd->Command.First, Cmd->Type);
+#endif
+        /* Arguments */
+        if (Cmd->Command.Rest && *(Cmd->Command.Rest))
+#ifndef MSCMD_ECHO_COMMAND_COMPAT
+            ConOutPrintf(_T(" Args: `%s'"), Cmd->Command.Rest);
+#else
+            ConOutPrintf(_T("Args: `%s' "), Cmd->Command.Rest);
+#endif
+        /* Redirections */
+        DumpRedir(Cmd->Redirections);
+
+        ConOutChar(_T('\n'));
+        return;
+    }
+
+    case C_QUIET:
+    {
+#ifndef MSCMD_ECHO_COMMAND_COMPAT
+        ConOutChar(_T('@'));
+#else
+        ConOutPuts(_T("@ "));
+#endif
+        DumpRedir(Cmd->Redirections); // FIXME: Can we have leading redirections??
+        ConOutChar(_T('\n'));
+
+        /*DumpCommand*/DUMP(Cmd->Subcommands, SpacePad + 2);
+        return;
+    }
+
+    case C_BLOCK:
+    {
+#ifndef MSCMD_ECHO_COMMAND_COMPAT
+        ConOutChar(_T('('));
+#else
+        ConOutPuts(_T("( "));
+#endif
+        DumpRedir(Cmd->Redirections);
+        ConOutChar(_T('\n'));
+
+        SpacePad += 2;
+
+        for (Sub = Cmd->Subcommands; Sub; Sub = Sub->Next)
+        {
+#if defined(MSCMD_ECHO_COMMAND_COMPAT) && defined(MSCMD_PARSER_BUGS)
+            /*
+             * We will emulate Windows' CMD handling of "CRLF" and
"&" multi-command
+             * enumeration within parenthesized command blocks.
+             */
+
+            if (!Sub->Next)
+            {
+                DumpCommand(Sub, SpacePad);
+                continue;
+            }
+
+            if (Sub->Type != C_MULTI)
+            {
+                ConOutPrintf(_T("%*s"), SpacePad, _T(""));
+                ConOutPuts(_T("CRLF \n"));
+                DumpCommand(Sub, SpacePad);
+                continue;
+            }
+
+            /* Now, Sub->Type == C_MULTI */
+
+            Cmd = Sub;
+
+            ConOutPrintf(_T("%*s"), SpacePad, _T(""));
+            ConOutPrintf(_T("%s \n"), OpString[Cmd->Type - C_OP_LOWEST]);
+            // FIXME: Can we have redirections on these operator-type commands?
+
+            SpacePad += 2;
+
+            Cmd = Cmd->Subcommands;
+            DumpCommand(Cmd, SpacePad);
+            ConOutPrintf(_T("%*s"), SpacePad, _T(""));
+            ConOutPuts(_T("CRLF \n"));
+            DumpCommand(Cmd->Next, SpacePad);
+
+            // NOTE: Next commands will remain indented.
+
+#else
+
+            /*
+             * If this command is followed by another one, first display
"CRLF".
+             * This also emulates the CRLF placement "bug" of Windows' CMD
+             * for the last two commands.
+             */
+            if (Sub->Next)
+            {
+                ConOutPrintf(_T("%*s"), SpacePad, _T(""));
+#ifndef MSCMD_ECHO_COMMAND_COMPAT
+                ConOutPuts(_T("CRLF\n"));
+#else
+                ConOutPuts(_T("CRLF \n"));
+#endif
+            }
+            DumpCommand(Sub, SpacePad);
+
+#endif // defined(MSCMD_ECHO_COMMAND_COMPAT) && defined(MSCMD_PARSER_BUGS)
+        }
+
+        return;
+    }
+
+    case C_MULTI:
+    case C_OR:
+    case C_AND:
+    case C_PIPE:
+    {
+#ifndef MSCMD_ECHO_COMMAND_COMPAT
+        ConOutPrintf(_T("%s\n"), OpString[Cmd->Type - C_OP_LOWEST]);
+#else
+        ConOutPrintf(_T("%s \n"), OpString[Cmd->Type - C_OP_LOWEST]);
+#endif
+        // FIXME: Can we have redirections on these operator-type commands?
+
+        SpacePad += 2;
+
+        Sub = Cmd->Subcommands;
+        DumpCommand(Sub, SpacePad);
+        /*DumpCommand*/DUMP(Sub->Next, SpacePad);
+        return;
+    }
+
+    case C_IF:
+    {
+        ConOutPuts(_T("if"));
+        /* NOTE: IF cannot have leading redirections */
+
+        if (Cmd->If.Flags & IFFLAG_IGNORECASE)
+            ConOutPuts(_T(" /I"));
+
+        ConOutChar(_T('\n'));
+
+        SpacePad += 2;
+
+        /*
+         * Show the IF command condition as a command.
+         * If it is negated, indent the command more.
+         */
+        if (Cmd->If.Flags & IFFLAG_NEGATE)
+        {
+            ConOutPrintf(_T("%*s"), SpacePad, _T(""));
+            ConOutPuts(_T("not\n"));
+            SpacePad += 2;
+        }
+
+        ConOutPrintf(_T("%*s"), SpacePad, _T(""));
+
+        /*
+         * Command name:
+         * - Unary operator: its name is the command name, and its argument is the
command argument.
+         * - Binary operator: its LHS is the command name, its RHS is the command
argument.
+         *
+         * Type:
+         * Windows' CMD (Win2k3 / Win7-10) values are as follows:
+         *   CMDEXTVERSION  Type: 0x32 / 0x34
+         *   ERRORLEVEL     Type: 0x33 / 0x35
+         *   DEFINED        Type: 0x34 / 0x36
+         *   EXIST          Type: 0x35 / 0x37
+         *   ==             Type: 0x37 / 0x39 (String Comparison)
+         *
+         * For the following command:
+         *   NOT            Type: 0x36 / 0x38
+         * Windows only prints it without any type / redirection.
+         *
+         * For the following command:
+         *   EQU, NEQ, etc. Type: 0x38 / 0x3a (Generic Comparison)
+         * Windows displays it as command of unknown type.
+         */
+#ifndef MSCMD_ECHO_COMMAND_COMPAT
+        ConOutPrintf(_T("Cmd: %s  Type: %x"),
+                     (Cmd->If.Operator <= IF_MAX_UNARY) ?
+                        IfOperatorString[Cmd->If.Operator] :
+                        Cmd->If.LeftArg,
+                     Cmd->If.Operator);
+#else
+        ConOutPrintf(_T("Cmd: %s  Type: %x "),
+                     (Cmd->If.Operator <= IF_MAX_UNARY) ?
+                        IfOperatorString[Cmd->If.Operator] :
+                        Cmd->If.LeftArg,
+                     Cmd->If.Operator);
+#endif
+        /* Arguments */
+#ifndef MSCMD_ECHO_COMMAND_COMPAT
+        ConOutPrintf(_T(" Args: `%s'"), Cmd->If.RightArg);
+#else
+        ConOutPrintf(_T("Args: `%s' "), Cmd->If.RightArg);
+#endif
+
+        ConOutChar(_T('\n'));
+
+        if (Cmd->If.Flags & IFFLAG_NEGATE)
+        {
+            SpacePad -= 2;
+        }
+
+        Sub = Cmd->Subcommands;
+        DumpCommand(Sub, SpacePad);
+        if (Sub->Next)
+        {
+            ConOutPrintf(_T("%*s"), SpacePad - 2, _T(""));
+            ConOutPuts(_T("else\n"));
+            DumpCommand(Sub->Next, SpacePad);
+        }
+        return;
+    }
+
+    case C_FOR:
+    {
+        ConOutPuts(_T("for"));
+        /* NOTE: FOR cannot have leading redirections */
+
+        if (Cmd->For.Switches & FOR_DIRS)      ConOutPuts(_T(" /D"));
+        if (Cmd->For.Switches & FOR_F)         ConOutPuts(_T(" /F"));
+        if (Cmd->For.Switches & FOR_LOOP)      ConOutPuts(_T(" /L"));
+        if (Cmd->For.Switches & FOR_RECURSIVE) ConOutPuts(_T(" /R"));
+        if (Cmd->For.Params)
+            ConOutPrintf(_T(" %s"), Cmd->For.Params);
+        ConOutPrintf(_T(" %%%c in (%s) do\n"), Cmd->For.Variable,
Cmd->For.List);
+        /*DumpCommand*/DUMP(Cmd->Subcommands, SpacePad + 2);
+        return;
+    }
+
+    default:
+        ConOutPrintf(_T("*** Unknown type: %x\n"), Cmd->Type);
+        break;
+    }
+
+#undef DUMP
+}
+
 /*
  * Reconstruct a parse tree into text form; used for echoing
  * batch file commands and FOR instances.