using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text.RegularExpressions; using System.Text; using System.Threading; using Registry = Microsoft.Win32.Registry; using Path = System.IO.Path; using System.Runtime.InteropServices; namespace Cosmos.Debug.GDB { public class GDB { public class Response { public string Command = string.Empty; public string Reply = string.Empty; public bool Error = false; public string ErrorMsg = string.Empty; public List Text = new List(); public override string ToString() { return Command; } } protected Queue mLastCmd = new Queue(); public System.Diagnostics.Process mGDBProcess; protected Action mOnResponse; protected Action mOnRunStateChange; protected List mBuffer = new List(); // DO NOT change to sync reads.. with process output there are SERIOUS bugs in the StreamReader.. // Unforunately the .NET implementation when no more data exists // sticks forever and there seems to be no way to abort it including closing the stream. // StreamReader in general has other issues on non seekable streams as well and accounts for why even our // implementation looks poor in places. void mGDBProcess_OutputDataReceived(object sender, DataReceivedEventArgs e) { all.Add(e.Data); if (e.Data == null) return; ProcessResponse(e.Data); } bool firstRun = true; List all = new List(); protected bool m_IsStopped = true; void OnRunStateChanged(bool stopped) { m_IsStopped = stopped; mOnRunStateChange(stopped); } protected void ProcessResponse(string line) { switch (line) { case "(gdb) ": if (firstRun) { // remove start things, like versions etc. mBuffer.Clear(); firstRun = false; return; } else if (mBuffer.Count == 0) { // occures if CTRL + C the program breaks return; } break; default: // only 7.x if (line[0] == '*') { // represents start or stopped int xIndex = line.IndexOf(','); string xState = line.Substring(1, xIndex - 1); switch (xState) { case "stopped": OnRunStateChanged(true); break; case "running": OnRunStateChanged(false); break; } } else mBuffer.Add(line); return; } var xResponse = new Response(); string xCmd = mBuffer[0]; if (xCmd[0] == '&') { xResponse.Command = Unescape(xCmd.Substring(1)); } foreach (string xLine in mBuffer) { var xType = xLine[0]; // & echo of a command or reply // ~ text response // ^ done //&target remote :8832 //&:8832: No connection could be made because the target machine actively refused it. //^error,msg=":8832: No connection could be made because the target machine actively refused it." //&target remote :8832 //~Remote debugging using :8832 //~[New Thread 1] //~0x000ffff0 in ?? () //^done string sData = Unescape(xLine.Substring(1)); if (xType == '&') { if (xResponse.Reply.Length == 0) xResponse.Reply = sData; else xResponse.Reply += Environment.NewLine + sData; } else if (xType == '~') { xResponse.Text.Add(Unescape(sData)); } else if (xType == '^') { xResponse.Error = sData.StartsWith("error"); } } mBuffer.Clear(); // detect manual input of cmds var xSplit = xResponse.Command.Split(new char[]{' ', '\t'}, StringSplitOptions.RemoveEmptyEntries); if (xSplit.Length == 1 && xSplit[0] == "detach") { if (false == xResponse.Error) mConnected = false; } else if (xSplit.Length > 2 && xSplit[0] == "target" && xSplit[1] == "remote") { TargetCmdReply(xResponse); } try { Windows.mMainForm.Invoke(mOnResponse, new object[] { xResponse }); } catch (ObjectDisposedException) { // check the propertie IsDisposed didnt solve the problem, so we catch } } static public string Unescape(string aInput) { // Remove surrounding ", /n, then unescape and trim string xResult = aInput; if (xResult.StartsWith("\"")) { xResult = xResult.Substring(1, aInput.Length - 2); xResult = xResult.Replace('\n', ' '); xResult = Regex.Unescape(xResult); } return xResult.Trim(); } public void SendCmd(string aCmd) { all.Add("Cmd: " + aCmd); mGDBProcess.StandardInput.WriteLine(aCmd); } protected bool mConnected = false; public bool Connected { get { return mConnected; } } public bool Stopped { get { return m_IsStopped; } } static readonly string mGDBExePath; static GDB() { mGDBExePath = Path.Combine(Cosmos.Build.Common.CosmosPaths.Tools, "gdb.exe"); } public GDB(Action aOnResponse, Action aOnRunStateChange) { mOnResponse = aOnResponse; mOnRunStateChange = aOnRunStateChange; var xStartInfo = new ProcessStartInfo(); xStartInfo.FileName = mGDBExePath; xStartInfo.Arguments = @"--interpreter=mi2 -silent -nx"; xStartInfo.WorkingDirectory = Settings.OutputPath; xStartInfo.CreateNoWindow = true; xStartInfo.UseShellExecute = false; xStartInfo.RedirectStandardError = true; xStartInfo.RedirectStandardInput = true; xStartInfo.RedirectStandardOutput = true; mGDBProcess = System.Diagnostics.Process.Start(xStartInfo); mGDBProcess.OutputDataReceived += new DataReceivedEventHandler(mGDBProcess_OutputDataReceived); mGDBProcess.BeginOutputReadLine(); SendCmd("set target-async 1"); // doc http://sourceware.org/gdb/onlinedocs/gdb/Asynchronous-and-non_002dstop-modes.html SendCmd("add-symbol-file " + Settings.ObjFile + " 0x02000000"); SendCmd("set architecture i386"); SendCmd("set language asm"); SendCmd("set disassembly-flavor intel"); } public void Connect() { SendCmd("target remote :8832"); } public void Disconnect() { SendCmd("detach"); } protected void TargetCmdReply(Response res) { mConnected = !res.Error; if (false == res.Error) { // get current address var xAddr = Global.FromHexWithLeadingZeroX(res.Text[1].Substring(0, res.Text[1].IndexOf(' '))); // only use breakpoint if not already deep in OS const uint START_ADDRESS = 0xFFFFFFF0; if (xAddr == START_ADDRESS) { SendCmd("break Kernel_Start"); SendCmd("continue"); SendCmd("delete 1"); } } } } }