////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    public CLangDebuggeeModule (DebugEngine engine, MiAsyncRecord asyncRecord)
      : base (engine)
    {
      try
      {
        if (asyncRecord == null)
        {
          throw new ArgumentNullException ("asyncRecord");
        }

        Name = asyncRecord ["id"] [0].GetString ();

        RemotePath = asyncRecord ["target-name"] [0].GetString ();

        RemoteLoadAddress = 0;

        SymbolsPath = asyncRecord ["host-name"] [0].GetString ();

        // 
        // The 'symbols-loaded' field is emitted only for backward compatibility and should not be relied on to convey any useful information.
        // 

        if ((!string.IsNullOrEmpty (SymbolsPath)) && File.Exists (SymbolsPath))
        {
          SymbolsLoaded = true;
        }
      }
      catch (Exception e)
      {
        LoggingUtils.HandleException (e);

        throw;
      }
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    public CLangDebuggeeModule AddModule (string moduleName, MiAsyncRecord asyncRecord)
    {
      LoggingUtils.PrintFunction ();

      if (string.IsNullOrWhiteSpace (moduleName))
      {
        throw new ArgumentNullException ("moduleName");
      }

      DebuggeeModule module = null;

      lock (m_debugModules)
      {
        if (!m_debugModules.TryGetValue (moduleName, out module))
        {
          module = new CLangDebuggeeModule (m_debugger.Engine, asyncRecord);

          m_debugModules.Add (moduleName, module);
        }
      }

      if (module != null)
      {
        m_debugger.Engine.Broadcast (new DebugEngineEvent.ModuleLoad (module as IDebugModule2, true), DebugProgram, null);

        if (module.SymbolsLoaded)
        {
          m_debugger.Engine.Broadcast (new DebugEngineEvent.BeforeSymbolSearch (module as IDebugModule3), DebugProgram, null);

          m_debugger.Engine.Broadcast (new DebugEngineEvent.SymbolSearch (module as IDebugModule3, module.Name), DebugProgram, null);
        }
      }

      return (CLangDebuggeeModule) module;
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    private void OnClientAsyncRecord (MiAsyncRecord asyncRecord)
    {
      LoggingUtils.PrintFunction ();

      switch (asyncRecord.Type)
      {
        case MiAsyncRecord.AsyncType.Exec: 
        {
          // 
          // Records prefixed '*'.
          // 

          switch (asyncRecord.Class)
          {
            case "running":
            {
              // 
              // The target is now running. The thread field tells which specific thread is now running, can be 'all' if every thread is running.
              // 

              lock (NativeProgram)
              {
                NativeProgram.SetRunning (true);

                string threadId = asyncRecord ["thread-id"] [0].GetString ();

                if (threadId.Equals ("all"))
                {
                  Dictionary<uint, DebuggeeThread> programThreads = NativeProgram.GetThreads ();

                  lock (programThreads)
                  {
                    foreach (DebuggeeThread thread in programThreads.Values)
                    {
                      thread.SetRunning (true);
                    }
                  }
                }
                else
                {
                  uint numericThreadId = uint.Parse (threadId);

                  NativeProgram.CurrentThreadId = numericThreadId;

                  CLangDebuggeeThread thread = NativeProgram.GetThread (numericThreadId);

                  if (thread != null)
                  {
                    thread.SetRunning (true);
                  }
                }
              }

              break;
            }

            case "stopped":
            {
              // 
              // The target has stopped.
              // 

              CLangDebuggeeThread stoppedThread = null;

              lock (NativeProgram)
              {
                NativeProgram.SetRunning (false);

                if (asyncRecord.HasField ("thread-id"))
                {
                  uint threadId = asyncRecord ["thread-id"] [0].GetUnsignedInt ();

                  NativeProgram.CurrentThreadId = threadId;
                }

                if (stoppedThread == null)
                {
                  stoppedThread = NativeProgram.GetThread (NativeProgram.CurrentThreadId);
                }

                if (stoppedThread != null)
                {
                  stoppedThread.SetRunning (false);
                }
                else
                {
                  throw new InvalidOperationException ("Could not evaluate a thread on which we stopped");
                }

                // 
                // Flag some or all of the program's threads as stopped, directed by 'stopped-threads' field.
                // 

                bool hasStoppedThreads = asyncRecord.HasField ("stopped-threads");

                if (hasStoppedThreads)
                {
                  // 
                  // If all threads are stopped, the stopped field will have the value of "all". 
                  // Otherwise, the value of the stopped field will be a list of thread identifiers.
                  // 

                  MiResultValue stoppedThreadsRecord = asyncRecord ["stopped-threads"] [0];

                  if (stoppedThreadsRecord is MiResultValueList)
                  {
                    MiResultValueList stoppedThreads = stoppedThreadsRecord as MiResultValueList;

                    foreach (MiResultValue stoppedThreadValue in stoppedThreads.List)
                    {
                      uint stoppedThreadId = stoppedThreadValue.GetUnsignedInt ();

                      CLangDebuggeeThread thread = NativeProgram.GetThread (stoppedThreadId);

                      if (thread != null)
                      {
                        thread.SetRunning (false);
                      }
                    }
                  }
                  else
                  {
                    Dictionary<uint, DebuggeeThread> programThreads = NativeProgram.GetThreads ();

                    lock (programThreads)
                    {
                      foreach (DebuggeeThread thread in programThreads.Values)
                      {
                        thread.SetRunning (false);
                      }
                    }
                  }
                }
              }

              // 
              // Unblocks waiting for 'stopped' to be processed. Skipping event handling during interrupt requests as it confuses VS debugger flow.
              // 

              bool ignoreInterruptSignal = false;

              if (m_interruptOperationCompleted != null)
              {
                m_interruptOperationCompleted.Set ();

                ignoreInterruptSignal = true;
              }

              // 
              // Process any pending requests to refresh registered breakpoints.
              // 

#if false
              RefreshSharedLibraries ();
#endif


#if false
              NativeProgram.RefreshAllThreads ();
#endif

              if (!GdbClient.GetClientFeatureSupported ("breakpoint-notifications"))
              {
                Engine.BreakpointManager.RefreshBreakpoints ();
              }

              // 
              // This behaviour seems at odds with the GDB/MI spec, but a *stopped event can contain
              // multiple 'reason' fields. This seems to occur mainly when signals have been ignored prior 
              // to a non-ignored triggering, i.e:
              // 
              //   Signal        Stop\tPrint\tPass to program\tDescription\n
              //   SIGSEGV       No\tYes\tYes\t\tSegmentation fault\n
              // 
              // *stopped,reason="signal-received",signal-name="SIGSEGV",signal-meaning="Segmentation fault",reason="signal-received",signal-name="SIGSEGV",signal-meaning="Segmentation fault",reason="exited-signalled",signal-name="SIGSEGV",signal-meaning="Segmentation fault"
              // 

              if (asyncRecord.HasField ("reason"))
              {
                // 
                // Here we pick the most recent (unhandled) signal.
                // 

                int stoppedIndex = asyncRecord ["reason"].Count - 1;

                MiResultValue stoppedReason = asyncRecord ["reason"] [stoppedIndex];

                // 
                // The reason field can have one of the following values:
                // 

                switch (stoppedReason.GetString ())
                {
                  case "breakpoint-hit":
                  case "watchpoint-trigger":
                  {
                    bool canContinue = true;

                    uint breakpointId = asyncRecord ["bkptno"] [0].GetUnsignedInt ();

                    string breakpointMode = asyncRecord ["disp"] [0].GetString ();

                    if (breakpointMode.Equals ("del"))
                    {
                      // 
                      // For temporary breakpoints, we won't have a valid managed object - so will just enforce a break event.
                      // 

                      //Engine.Broadcast (new DebugEngineEvent.Break (), NativeProgram.DebugProgram, stoppedThread);

                      Engine.Broadcast (new DebugEngineEvent.BreakpointHit (null), NativeProgram.DebugProgram, stoppedThread);
                    }
                    else
                    {
                      DebuggeeBreakpointBound boundBreakpoint = Engine.BreakpointManager.FindBoundBreakpoint (breakpointId);

                      if (boundBreakpoint == null)
                      {
                        // 
                        // Could not find the breakpoint we're looking for. Refresh everything and try again.
                        // 

                        Engine.BreakpointManager.SetDirty (true);

                        Engine.BreakpointManager.RefreshBreakpoints ();

                        boundBreakpoint = Engine.BreakpointManager.FindBoundBreakpoint (breakpointId);
                      }

                      if (boundBreakpoint == null)
                      {
                        // 
                        // Could not locate a registered breakpoint with matching id.
                        // 

                        DebugEngineEvent.Exception exception = new DebugEngineEvent.Exception (NativeProgram.DebugProgram, stoppedReason.GetString (), "Breakpoint #" + breakpointId + "hit", 0x00000000, canContinue);

                        Engine.Broadcast (exception, NativeProgram.DebugProgram, stoppedThread);
                      }
                      else
                      {
                        enum_BP_STATE [] breakpointState = new enum_BP_STATE [1];

                        LoggingUtils.RequireOk (boundBreakpoint.GetState (breakpointState));

                        if (breakpointState [0] == enum_BP_STATE.BPS_DELETED)
                        {
                          // 
                          // Hit a breakpoint which internally is flagged as deleted. Oh noes!
                          // 

                          DebugEngineEvent.Exception exception = new DebugEngineEvent.Exception (NativeProgram.DebugProgram, stoppedReason.GetString (), "Breakpoint #" + breakpointId + " hit [deleted]", 0x00000000, canContinue);

                          Engine.Broadcast (exception, NativeProgram.DebugProgram, stoppedThread);
                        }
                        else
                        {
                          // 
                          // Hit a breakpoint which is known about. Issue break event.
                          // 

                          IDebugBoundBreakpoint2 [] boundBreakpoints = new IDebugBoundBreakpoint2 [] { boundBreakpoint };

                          IEnumDebugBoundBreakpoints2 enumeratedBoundBreakpoint = new DebuggeeBreakpointBound.Enumerator (boundBreakpoints);

                          Engine.Broadcast (new DebugEngineEvent.BreakpointHit (enumeratedBoundBreakpoint), NativeProgram.DebugProgram, stoppedThread);
                        }
                      }
                    }

                    break;
                  }

                  case "end-stepping-range":
                  case "function-finished":
                  {
                    Engine.Broadcast (new DebugEngineEvent.StepComplete (), NativeProgram.DebugProgram, stoppedThread);

                    break;
                  }

                  case "signal-received":
                  {
                    string signalName = asyncRecord ["signal-name"] [stoppedIndex].GetString ();

                    string signalMeaning = asyncRecord ["signal-meaning"] [stoppedIndex].GetString ();

                    switch (signalName)
                    {
                      case null:
                      case "SIGINT":
                      {
                        if (!ignoreInterruptSignal)
                        {
                          Engine.Broadcast (new DebugEngineEvent.Break (), NativeProgram.DebugProgram, stoppedThread);
                        }

                        break;
                      }

                      default:
                      {
                        StringBuilder signalDescription = new StringBuilder ();

                        signalDescription.AppendFormat ("{0} ({1})", signalName, signalMeaning);

                        if (asyncRecord.HasField ("frame"))
                        {
                          MiResultValueTuple frameTuple = asyncRecord ["frame"] [0] as MiResultValueTuple;

                          if (frameTuple.HasField ("addr"))
                          {
                            string address = frameTuple ["addr"] [0].GetString ();

                            signalDescription.AppendFormat (" at {0}", address);
                          }

                          if (frameTuple.HasField ("func"))
                          {
                            string function = frameTuple ["func"] [0].GetString ();

                            signalDescription.AppendFormat (" ({0})", function);
                          }
                        }

                        bool canContinue = true;

                        DebugEngineEvent.Exception exception = new DebugEngineEvent.Exception (NativeProgram.DebugProgram, signalName, signalDescription.ToString (), 0x80000000, canContinue);

                        Engine.Broadcast (exception, NativeProgram.DebugProgram, stoppedThread);

                        break;
                      }
                    }

                    break;
                  }

                  case "read-watchpoint-trigger":
                  case "access-watchpoint-trigger":
                  case "location-reached":
                  case "watchpoint-scope":
                  case "solib-event":
                  case "fork":
                  case "vfork":
                  case "syscall-entry":
                  case "exec":
                  {
                    Engine.Broadcast (new DebugEngineEvent.Break (), NativeProgram.DebugProgram, stoppedThread);

                    break;
                  }

                  case "exited":
                  case "exited-normally":
                  case "exited-signalled":
                  {
                    // 
                    // React to program termination, but defer this so it doesn't consume the async output thread.
                    // 

                    ThreadPool.QueueUserWorkItem (delegate (object state)
                    {
                      try
                      {
                        LoggingUtils.RequireOk (Engine.Detach (NativeProgram.DebugProgram));
                      }
                      catch (Exception e)
                      {
                        LoggingUtils.HandleException (e);
                      }
                    });

                    break;
                  }
                }
              }

              break;
            }
          }

          break;
        }

        case MiAsyncRecord.AsyncType.Status:
        {
          // 
          // Records prefixed '+'.
          // 

          break;
        }


        case MiAsyncRecord.AsyncType.Notify:
        {
          // 
          // Records prefixed '='.
          // 

          switch (asyncRecord.Class)
          {
            case "thread-group-added":
            case "thread-group-started":
            {
              // 
              // A thread group became associated with a running program, either because the program was just started or the thread group was attached to a program.
              // 

              try
              {
                string threadGroupId = asyncRecord ["id"] [0].GetString ();

                m_threadGroupStatus [threadGroupId] = 0;
              }
              catch (Exception e)
              {
                LoggingUtils.HandleException (e);
              }

              break;
            }

            case "thread-group-removed":
            case "thread-group-exited":
            {
              // 
              // A thread group is no longer associated with a running program, either because the program has exited, or because it was detached from.
              // 

              try
              {
                string threadGroupId = asyncRecord ["id"] [0].GetString ();

                if (asyncRecord.HasField ("exit-code"))
                {
                  m_threadGroupStatus [threadGroupId] = asyncRecord ["exit-code"] [0].GetUnsignedInt ();
                }
              }
              catch (Exception e)
              {
                LoggingUtils.HandleException (e);
              }

              break;
            }

            case "thread-created":
            {
              // 
              // A thread either was created. The id field contains the gdb identifier of the thread. The gid field identifies the thread group this thread belongs to. 
              // 

              try
              {
                uint threadId = asyncRecord ["id"] [0].GetUnsignedInt ();

                string threadGroupId = asyncRecord ["group-id"] [0].GetString ();

                CLangDebuggeeThread thread = NativeProgram.GetThread (threadId);

                if (thread == null)
                {
                  NativeProgram.AddThread (threadId);
                }
              }
              catch (Exception e)
              {
                LoggingUtils.HandleException (e);
              }

              break;
            }

            case "thread-exited":
            {
              // 
              // A thread has exited. The 'id' field contains the GDB identifier of the thread. The 'group-id' field identifies the thread group this thread belongs to. 
              // 

              try
              {
                uint threadId = asyncRecord ["id"] [0].GetUnsignedInt ();

                string threadGroupId = asyncRecord ["group-id"] [0].GetString ();

                uint exitCode = m_threadGroupStatus [threadGroupId];

                NativeProgram.RemoveThread (threadId, exitCode);
              }
              catch (Exception e)
              {
                LoggingUtils.HandleException (e);
              }

              break;
            }

            case "thread-selected":
            {
              // 
              // Informs that the selected thread was changed as result of the last command.
              // 

              try
              {
                uint threadId = asyncRecord ["id"] [0].GetUnsignedInt ();

                NativeProgram.CurrentThreadId = threadId;
              }
              catch (Exception e)
              {
                LoggingUtils.HandleException (e);
              }

              break;
            }

            case "library-loaded":
            {
              // 
              // Reports that a new library file was loaded by the program.
              // 

              try
              {
                string moduleName = asyncRecord ["id"] [0].GetString ();

                CLangDebuggeeModule module = NativeProgram.GetModule (moduleName);

                if (module == null)
                {
                  module = NativeProgram.AddModule (moduleName, asyncRecord);
                }

                if (!GdbClient.GetClientFeatureSupported ("breakpoint-notifications"))
                {
                  Engine.BreakpointManager.SetDirty (true);
                }
              }
              catch (Exception e)
              {
                LoggingUtils.HandleException (e);
              }

              break;
            }

            case "library-unloaded":
            {
              // 
              // Reports that a library was unloaded by the program.
              // 

              try
              {
                string moduleName = asyncRecord ["id"] [0].GetString ();

                NativeProgram.RemoveModule (moduleName);

                if (!GdbClient.GetClientFeatureSupported ("breakpoint-notifications"))
                {
                  Engine.BreakpointManager.SetDirty (true);
                }
              }
              catch (Exception e)
              {
                LoggingUtils.HandleException (e);
              }

              break;
            }

            case "breakpoint-created":
            case "breakpoint-modified":
            case "breakpoint-deleted":
            {
              try
              {
                IDebugPendingBreakpoint2 pendingBreakpoint = null;

                if (asyncRecord.HasField ("bkpt"))
                {
                  MiResultValue breakpointData = asyncRecord ["bkpt"] [0];

                  MiBreakpoint currentGdbBreakpoint = new MiBreakpoint (breakpointData.Values);

                  pendingBreakpoint = Engine.BreakpointManager.FindPendingBreakpoint (currentGdbBreakpoint.ID);

                  // If the breakpoint is unknown, this usually means it was bound externally to the IDE.
                  /*if (pendingBreakpoint == null)
                  {
                    // 
                    // CreatePendingBreakpoint always sets the dirty flag, so we need to reset this if it's handled immediately.
                    // 

                    DebugBreakpointRequest breakpointRequest = new DebugBreakpointRequest (currentGdbBreakpoint.Address);

                    LoggingUtils.RequireOk (Engine.BreakpointManager.CreatePendingBreakpoint (breakpointRequest, out pendingBreakpoint));
                  }*/
                }
                else if (asyncRecord.HasField ("id"))
                {
                  pendingBreakpoint = Engine.BreakpointManager.FindPendingBreakpoint (asyncRecord ["id"] [0].GetUnsignedInt ());
                }

                bool wasDirty = Engine.BreakpointManager.IsDirty ();

                if (pendingBreakpoint != null)
                {
                  DebuggeeBreakpointPending thisBreakpoint = pendingBreakpoint as DebuggeeBreakpointPending;

                  thisBreakpoint.RefreshBoundBreakpoints ();

                  thisBreakpoint.RefreshErrorBreakpoints ();
                }

                if (wasDirty)
                {
                  Engine.BreakpointManager.SetDirty (true);
                }
              }
              catch (Exception e)
              {
                LoggingUtils.HandleException (e);
              }

              break;
            }
          }

          break;
        }
      }
    }
Example #4
0
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        public static MiRecord ParseGdbOutputRecord(string streamOutput)
        {
            if (string.IsNullOrEmpty(streamOutput))
            {
                return(null);
            }

            //
            // Process any leading 'async-record' or 'result-record' token.
            //

            int streamIndex = 0;

            if (streamOutput.StartsWith("(gdb)"))
            {
                //
                // GDB prompt. Waiting for input.
                //

                return(new MiPromptRecord());
            }
            else if (streamOutput [streamIndex] == '~')
            {
                //
                // Console stream record. Clears leading '~" and trailing '\\n"' characters.
                //

                ++streamIndex;

                StringBuilder consoleStreamBuilder = new StringBuilder(streamOutput.Trim(new char [] { '~', '\"' }));

                //consoleStreamBuilder.Replace ("\\n", "\n");

                return(new MiStreamRecord(MiStreamRecord.StreamType.Console, consoleStreamBuilder.ToString()));
            }
            else if (streamOutput [streamIndex] == '@')
            {
                //
                // Target stream record. Clears leading '@" and trailing '\\n"' characters.
                //

                ++streamIndex;

                StringBuilder targetStreamBuilder = new StringBuilder(streamOutput.Trim(new char [] { '@', '\"' }));

                //targetStreamBuilder.Replace ("\\n", "\n");

                return(new MiStreamRecord(MiStreamRecord.StreamType.Target, targetStreamBuilder.ToString()));
            }
            else if (streamOutput [streamIndex] == '&')
            {
                //
                // Log stream record. Clears leading '&" and trailing '\\n"' characters.
                //

                ++streamIndex;

                StringBuilder logStreamBuilder = new StringBuilder(streamOutput.Trim(new char [] { '&', '\"' }));

                //logStreamBuilder.Replace ("\\n", "\n");

                return(new MiStreamRecord(MiStreamRecord.StreamType.Log, logStreamBuilder.ToString()));
            }
            else
            {
                //
                // The following record types have associated key-pair data; identify the type and build a result collection.
                //

                string recordData = streamOutput.Substring(streamIndex);

                int bufferStartPos = 0;

                int bufferCurrentPos = bufferStartPos;

                char type = '^';

                uint token = 0;

                while (bufferCurrentPos < streamOutput.Length)
                {
                    if (((bufferCurrentPos + 1) >= streamOutput.Length) || (streamOutput [bufferCurrentPos + 1] == ','))
                    {
                        string clazz = recordData.Substring(bufferStartPos, (bufferCurrentPos + 1) - bufferStartPos);

                        string data = string.Empty;

                        if (((bufferCurrentPos + 1) < streamOutput.Length) && (streamOutput [bufferCurrentPos + 1] == ','))
                        {
                            data = recordData.Substring(bufferCurrentPos + 2);
                        }

                        MiRecord resultRecord = null;

                        List <MiResultValue> values = new List <MiResultValue> ();

                        try
                        {
                            ParseAllResults(data, ref values);
                        }
                        catch (Exception e)
                        {
                            LoggingUtils.HandleException(e);
                        }
                        finally
                        {
                            switch (type)
                            {
                            case '^': resultRecord = new MiResultRecord(token, clazz, values); break;

                            case '*': resultRecord = new MiAsyncRecord(MiAsyncRecord.AsyncType.Exec, token, clazz, values); break;

                            case '+': resultRecord = new MiAsyncRecord(MiAsyncRecord.AsyncType.Status, token, clazz, values); break;

                            case '=': resultRecord = new MiAsyncRecord(MiAsyncRecord.AsyncType.Notify, token, clazz, values); break;
                            }
                        }

                        return(resultRecord);
                    }
                    else if ((recordData [bufferCurrentPos] == '^') || (recordData [bufferCurrentPos] == '*') || (recordData [bufferCurrentPos] == '+') || (recordData [bufferCurrentPos] == '='))
                    {
                        type = recordData [bufferCurrentPos];

                        string stringToken = recordData.Substring(bufferStartPos, bufferCurrentPos);

                        if (!string.IsNullOrWhiteSpace(stringToken))
                        {
                            uint.TryParse(stringToken, out token);
                        }

                        bufferStartPos = ++bufferCurrentPos;
                    }

                    ++bufferCurrentPos;
                }

                return(null);
            }
        }
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    public static MiRecord ParseGdbOutputRecord (string streamOutput)
    {
      if (string.IsNullOrEmpty (streamOutput))
      {
        return null;
      }

      // 
      // Process any leading 'async-record' or 'result-record' token.
      // 

      int streamIndex = 0;

      if (streamOutput.StartsWith ("(gdb)"))
      {
        // 
        // GDB prompt. Waiting for input.
        // 

        return new MiPromptRecord ();
      }
      else if (streamOutput [streamIndex] == '~')
      {
        // 
        // Console stream record. Clears leading '~" and trailing '\\n"' characters.
        // 

        ++streamIndex;

        StringBuilder consoleStreamBuilder = new StringBuilder (streamOutput.Trim (new char [] { '~', '\"' }));

        //consoleStreamBuilder.Replace ("\\n", "\n");

        return new MiStreamRecord (MiStreamRecord.StreamType.Console, consoleStreamBuilder.ToString ());
      }
      else if (streamOutput [streamIndex] == '@')
      {
        // 
        // Target stream record. Clears leading '@" and trailing '\\n"' characters.
        // 

        ++streamIndex;

        StringBuilder targetStreamBuilder = new StringBuilder (streamOutput.Trim (new char [] { '@', '\"' }));

        //targetStreamBuilder.Replace ("\\n", "\n");

        return new MiStreamRecord (MiStreamRecord.StreamType.Target, targetStreamBuilder.ToString ());
      }
      else if (streamOutput [streamIndex] == '&')
      {
        // 
        // Log stream record. Clears leading '&" and trailing '\\n"' characters.
        // 

        ++streamIndex;

        StringBuilder logStreamBuilder = new StringBuilder (streamOutput.Trim (new char [] { '&', '\"' }));

        //logStreamBuilder.Replace ("\\n", "\n");

        return new MiStreamRecord (MiStreamRecord.StreamType.Log, logStreamBuilder.ToString ());
      }
      else
      {
        // 
        // The following record types have associated key-pair data; identify the type and build a result collection.
        // 

        string recordData = streamOutput.Substring (streamIndex);

        int bufferStartPos = 0;

        int bufferCurrentPos = bufferStartPos;

        char type = '^';

        uint token = 0;

        while (bufferCurrentPos < streamOutput.Length)
        {
          if (((bufferCurrentPos + 1) >= streamOutput.Length) || (streamOutput [bufferCurrentPos + 1] == ','))
          {
            string clazz = recordData.Substring (bufferStartPos, (bufferCurrentPos + 1) - bufferStartPos);

            string data = string.Empty;

            if (((bufferCurrentPos + 1) < streamOutput.Length) && (streamOutput [bufferCurrentPos + 1] == ','))
            {
              data = recordData.Substring (bufferCurrentPos + 2);
            }

            MiRecord resultRecord = null;

            List<MiResultValue> values = new List<MiResultValue> ();

            try
            {
              ParseAllResults (data, ref values);
            }
            catch (Exception e)
            {
              LoggingUtils.HandleException (e);
            }
            finally
            {
              switch (type)
              {
                case '^': resultRecord = new MiResultRecord (token, clazz, values); break;
                case '*': resultRecord = new MiAsyncRecord (MiAsyncRecord.AsyncType.Exec, token, clazz, values); break;
                case '+': resultRecord = new MiAsyncRecord (MiAsyncRecord.AsyncType.Status, token, clazz, values); break;
                case '=': resultRecord = new MiAsyncRecord (MiAsyncRecord.AsyncType.Notify, token, clazz, values); break;
              }
            }

            return resultRecord;
          }
          else if ((recordData [bufferCurrentPos] == '^') || (recordData [bufferCurrentPos] == '*') || (recordData [bufferCurrentPos] == '+') || (recordData [bufferCurrentPos] == '='))
          {
            type = recordData [bufferCurrentPos];

            string stringToken = recordData.Substring (bufferStartPos, bufferCurrentPos);

            if (!string.IsNullOrWhiteSpace (stringToken))
            {
              uint.TryParse (stringToken, out token);
            }

            bufferStartPos = ++bufferCurrentPos;
          }

          ++bufferCurrentPos;
        }

        return null;
      }
    }