Cosmos/source2/Debug/Cosmos.Debug.GDB/GDB.cs
kudzu_cp a196ba35c8
2012-07-13 01:35:37 +00:00

240 lines
No EOL
7.2 KiB
C#

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<string> Text = new List<string>();
public override string ToString() {
return Command;
}
}
protected Queue<string> mLastCmd = new Queue<string>();
public System.Diagnostics.Process mGDBProcess;
protected Action<Response> mOnResponse;
protected Action<bool> mOnRunStateChange;
protected List<string> mBuffer = new List<string>();
// 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<string> all = new List<string>();
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<Response> aOnResponse, Action<bool> 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("symbol-file " + Settings.ObjFile);
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 int START_ADDRESS = 0x000ffff0;
if (xAddr <= START_ADDRESS)
{
SendCmd("break Kernel_Start");
SendCmd("continue");
SendCmd("delete 1");
}
}
}
}
}