//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 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; } } }
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 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; } }