Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Bug] GDB provider will crash with Socket Exception 0x80004005 #1931

Open
1 task
jared52005 opened this issue Oct 5, 2024 · 0 comments
Open
1 task

[Bug] GDB provider will crash with Socket Exception 0x80004005 #1931

jared52005 opened this issue Oct 5, 2024 · 0 comments
Labels
bug Something isn't working

Comments

@jared52005
Copy link

Operating System

Windows

What's the issue you encountered?

I was using ImHex v1.31.0 where local GDB connection below worked without a problem and I was able to read/write bytes using ImHex via GDB provider on emulated GDB server (see code below). After updating on v1.35.4 (downloaded msi from release page on GitHub) I am having a following error:

Creating server in .NET using TcpListener expecting connections on 127.0.0.1:3333, which will create a TcpClient after connection is established from ImHex. The moment I will try to send data back, connection will crash on

SocketException: System.Net.Sockets.SocketException (0x80004005): An established connection was aborted by the software in your host machine

On ImHex side I will get unspecified error in notification area in the bottom right corner

I have tried to open a port 3333 in Windows Firewall, but the connection will crash anyway

I have replicated behavior on Windows 11 and Windows 10

Edition	Windows 11 Pro
Version	23H2
OS build	22631.4169
Experience	Windows Feature Experience Pack 1000.22700.1034.0
Edition	Windows 10 Pro
Version	22H2
OS build	19045.4894
Experience	Windows Feature Experience Pack 1000.19060.1000.0

It seems that it is some regression issue. I have downgraded back to v1.31.0 and everything works fine.

How can the issue be reproduced?

Below is a code I am using for emulated GDB server which works in v1.31.0. Code is compiled in Visual Studio. Should be possible to compile in .NET Core (dotnet) on Linux too. After I will create the server and try to connect to it via ImHex -> Other Providers -> GDB -> 127.0.0.1 / 3333 / 0xFFFFFFFF, connection will get established. The moment I will try to send data back via _tcp.Client.Send(frame); connection will crash and ImHex throws Unspecified Error

GDB server

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Net.Sockets;
using System.Net;

namespace GDBServer
{
    /// <summary>
    /// Implementation of GDB server to be used as a provider for ImHex
    /// </summary>
    public class GdbServer : IDisposable
    {
        TcpClient _tcp;
        Queue<string> _receivedFrames;
        AutoResetEvent _frameReceiveComplete;
        Thread _serverThread;
        bool _serverThreadStop;
        int _port;

        bool _sendAck;

        public event ReadDataEventHandler OnRead;
        public event WriteDataEventHandler OnWrite;

        public GdbServer(int port = 3333)
        {
            _receivedFrames = new Queue<string>();
            _frameReceiveComplete = new AutoResetEvent(false);
            _port = port;
            _serverThread = new Thread(ServerListenAndTalk);
            _serverThread.Start();
        }

        void ServerListenAndTalk()
        {
            _sendAck = true;
            GdbServerState sm = GdbServerState.WaitConnection;
            GdbFrame parsedFrame = null;
            _serverThreadStop = false;
            TcpListener server = null;
            string strFrame = string.Empty;
            try
            {
                IPAddress localAddr = IPAddress.Parse("127.0.0.1");
                server = new TcpListener(localAddr, _port);

                // Start listening for client requests.
                server.Start();

                // Enter the listening loop.
                while (!_serverThreadStop)
                {
                    switch (sm)
                    {
                        case GdbServerState.WaitConnection:
                            Console.WriteLine($"[GDB] Waiting for a connection @ {localAddr}:{_port}... ");
                            _tcp = server.AcceptTcpClient();
                            Console.WriteLine("[GDB] Connected!");
                            sm = GdbServerState.ReceiveFrame;
                            break;
                        case GdbServerState.ReceiveFrame:
                            strFrame = ReceiveViaTcp();
                            //Did we received something?
                            if(!string.IsNullOrEmpty(strFrame))
                            {
                                //Is ACK frame?
                                if (strFrame == "+")
                                {
                                    //Do nothing
                                    continue;
                                }
                                //Is command frame?
                                else if (strFrame.StartsWith("$"))
                                {
                                    //Log.LogDebug(strFrame);
                                    sm = GdbServerState.ParseFrame;
                                }
                                else
                                {
                                    Console.WriteLine("Error [GDB] Unknown frame - " + strFrame);
                                }
                            }
                            break;
                        case GdbServerState.AckFrame:
                            if (_sendAck)
                            {
                                SendViaTcp("+");
                            }
                            sm = GdbServerState.ProcessRequest;
                            break;
                        case GdbServerState.ParseFrame:
                            parsedFrame = ParseFrame(strFrame);
                            if(parsedFrame == null)
                            {
                                //NACK should be here
                                sm = GdbServerState.ReceiveFrame;
                            }
                            else
                            {
                                sm = GdbServerState.AckFrame; 
                            }
                            break;
                        case GdbServerState.ProcessRequest:
                            string response = ProcessRequest(parsedFrame);
                            SendViaTcp(parsedFrame.ConstructFrame(response));
                            sm = GdbServerState.ReceiveFrame;
                            break;
                        default:
                            break;
                    }
                }
            }
            catch (SocketException e)
            {
                Console.WriteLine("SocketException: {0}", e);
            }
            finally
            {
                // Stop listening for new clients.
                server.Stop();
            }
        }

        private string ReceiveViaTcp()
        {
            //Dequeue and send all the frames
            if (_receivedFrames.Count != 0)
            {
                return _receivedFrames.Dequeue();
            }

            //Prepare for receiving new frames
            var args = new SocketAsyncEventArgs();
            int bufferSize = 0x1000;
            args.SetBuffer(new byte[bufferSize], 0, bufferSize);
            args.Completed += Receive_Completed;
            _frameReceiveComplete.Reset();
            _tcp.Client.ReceiveAsync(args);
            _frameReceiveComplete.WaitOne(100); //Wait timeout
            if (_receivedFrames.Count != 0)
            {
                args.Dispose();
                return _receivedFrames.Dequeue();
            }
            else
            {
                return null;
            }
        }

        private void Receive_Completed(object sender, SocketAsyncEventArgs e)
        {
            if (e.BytesTransferred > 1)
            {
                byte[] received = e.Buffer.Take(e.BytesTransferred).ToArray();
                string fr = Encoding.ASCII.GetString(received);
                //Store just one GDB command, ignore stuffed rest of commands
                int sonp = fr.IndexOf('$', 1);
                if (sonp > 0)
                {
                    _receivedFrames.Enqueue(fr.Substring(0, sonp));
                    _receivedFrames.Enqueue(fr.Substring(sonp));
                }
                else
                {
                    _receivedFrames.Enqueue(fr);
                }
                _frameReceiveComplete.Set();
            }
        }

        private void SendViaTcp(string data)
        {
            //Log.LogDebug(data);
            var frame = Encoding.ASCII.GetBytes(data);
            if (!_tcp.Connected)
            {
                throw new Exception("[GDB] Disconnected");
            }
            _tcp.Client.Send(frame);
        }

        public void Dispose()
        {
            _serverThreadStop = true;
        }

        private GdbFrame ParseFrame(string frame)
        {
            try
            {
                GdbFrame gdb = new GdbFrame();
                gdb.ParseFrame(frame);
                return gdb;
            }
            catch(Exception ex)
            {
                Console.WriteLine("Parse Error: " + ex.Message);
                return null;
            }
        }

        private string ProcessRequest(GdbFrame frame)
        {
            if (frame.Data.StartsWith("QStartNoAckMode"))
            {
                if (_sendAck)
                {
                    Console.WriteLine("ACK disabled");
                    _sendAck = false;
                }
                return "OK";
            }
            else if (frame.Data[0] == 'm')
            {
                //Read memory
                return ReadMemory(frame);
            }
            else if (frame.Data[0] == 'M')
            {
                //Read memory
                return WriteMemory(frame);
            }
            else
            {
                //Some random error
                Console.WriteLine("[GDB] Unknown Request: " + frame.Data);
                return string.Empty;
            }
        }

        private string ReadMemory(GdbFrame frame)
        {
            var d = frame.Data.Split(',');
            string strAddress = d[0].Substring(1);
            string strLength = d[1];

            uint address = Convert.ToUInt32(strAddress, 16);
            int length = Convert.ToInt32(strLength, 16);

            //Log.LogDebug($"Mocked reading {length:X} byte @ {address:X8}");

            byte[] data = OnRead?.Invoke(address, length);
            return frame.Encode16(data);
        }

        private string WriteMemory(GdbFrame frame)
        {
            //MA00000C9,1:AA
            //Console.WriteLine(frame.Data);
            var d = frame.Data.Split(',');
            string strAddress = d[0].Substring(1);
            var e = d[1].Split(':');
            string strLen = e[0];
            string strData = e[1];

            uint address = Convert.ToUInt32(strAddress, 16);
            int length = Convert.ToByte(strLen, 16);
            byte[] data = frame.Decode16(length, strData);
            OnWrite?.Invoke(address, data);
            return string.Empty;
        }

        enum GdbServerState
        {
            /// <summary>
            /// Wait on TCP connection
            /// </summary>
            WaitConnection,
            /// <summary>
            /// Receive data from TCP connection as parsed string $data#cs
            /// </summary>
            ReceiveFrame,
            /// <summary>
            /// Parse frame
            /// </summary>
            ParseFrame,
            /// <summary>
            /// Send + if received properly and if ACK was not disabled
            /// </summary>
            AckFrame,
            /// <summary>
            /// When frame was successfully received, process request
            /// </summary>
            ProcessRequest,
        }
    }

    public delegate byte[] ReadDataEventHandler(uint address, int length);
    public delegate void WriteDataEventHandler(uint address, byte[] data);
}

GDB Frame

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace GDBServer
{
    public class GdbFrame
    {
        public string Data { get; set; }

        /// <summary>
        /// $data#cs
        /// </summary>
        /// <param name="data"></param>
        /// <returns></returns>
        public string ConstructFrame(string data)
        {
            return $"${data}#{Checksum(data):x2}";
        }

        public string ParseFrame(string frame)
        {
            if(string.IsNullOrEmpty(frame))
            {
                return string.Empty;
            }
            if(!frame.StartsWith("$"))
            {
                throw new ArgumentException("GDB frame must start with $");
            }
            if (frame[frame.Length - 3] != '#')
            {
                throw new ArgumentException("GDB frame is missing CS #");
            }

            Data = frame.Substring(1, frame.Length - 4);
            
            //Verify CS
            string expCsStr = frame.Substring(frame.Length - 2, 2);
            byte cs = Checksum(Data);
            byte expCs = Convert.ToByte(expCsStr, 16);
            if (cs == expCs)
            {
                return Data;
            }
            else
            {
                throw new ArgumentException("GDB frame has invalid CS");
            }
        }

        public byte[] Decode16(int length, string s)
        {
            byte[] datagram = new byte[length];
            s = s.Replace(" ", "");
            int pos = 0;
            for (int i = 0; i < s.Length; i += 2)
            {
                string sByte = s.Substring(i, 2);
                datagram[pos++] = Convert.ToByte(sByte, 16);

                //Prevent writing outside the field
                if(pos == length)
                {
                    break;
                }
            }
            return datagram;
        }

        public string Encode16(byte[] input)
        {
            StringBuilder output = new StringBuilder(input.Length * 2);
            for (int i = 0; i < input.Length; i++)
            {
                output.Append(input[i].ToString("X2"));
            }

            return output.ToString();
        }
        private byte Checksum(string data)
        {
            uint checksum = 0;

            for (int i = 0; i < data.Length; i++)
            {
                checksum += (byte)data[i];
            }

            return (byte)(checksum & 0xFF);
        }

        public override string ToString()
        {
            return Data;
        }
    }
}

ImHex Version

1.35.4

ImHex Build Type

  • Nightly or built from sources

Installation type

msi

Additional context?

No response

@jared52005 jared52005 added the bug Something isn't working label Oct 5, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

1 participant