/* =========================================================================== Doom 3 GPL Source Code Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company. This file is part of the Doom 3 GPL Source Code ("Doom 3 Source Code"). Doom 3 Source Code is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Doom 3 Source Code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Doom 3 Source Code. If not, see . In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code. If not, please request a copy in writing from id Software at the address below. If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. =========================================================================== */ #include "tools/edit_gui_common.h" #include "DebuggerApp.h" /* ================ rvDebuggerClient::rvDebuggerClient ================ */ rvDebuggerClient::rvDebuggerClient ( ) { mConnected = false; mWaitFor = DBMSG_UNKNOWN; } /* ================ rvDebuggerClient::~rvDebuggerClient ================ */ rvDebuggerClient::~rvDebuggerClient ( ) { ClearBreakpoints ( ); ClearCallstack ( ); ClearThreads ( ); } /* ================ rvDebuggerClient::Initialize Initialize the debugger client ================ */ bool rvDebuggerClient::Initialize ( void ) { // Nothing else can run with the debugger com_editors = EDITOR_DEBUGGER; // Initialize the network connection if ( !mPort.InitForPort ( 27981 ) ) { return false; } // Server must be running on the local host on port 28980 Sys_StringToNetAdr ( "localhost", &mServerAdr, true ); mServerAdr.port = 27980; // Attempt to let the server know we are here. The server may not be running so this // message will just get ignored. SendMessage ( DBMSG_CONNECT ); return true; } /* ================ rvDebuggerClient::Shutdown Shutdown the debugger client and let the debugger server know we are shutting down ================ */ void rvDebuggerClient::Shutdown ( void ) { if ( mConnected ) { SendMessage ( DBMSG_DISCONNECT ); mConnected = false; } } /* ================ rvDebuggerClient::ProcessMessages Process all incomding messages from the debugger server ================ */ bool rvDebuggerClient::ProcessMessages ( void ) { netadr_t adrFrom; idBitMsg msg; byte buffer[MAX_MSGLEN]; msg.SetSize(MAX_MSGLEN); msg.BeginReading(); int msgSize; // Check for pending udp packets on the debugger port while ( mPort.GetPacket ( adrFrom, buffer,msgSize, MAX_MSGLEN) ) { short command; msg.Init(buffer, sizeof(buffer)); msg.SetSize(msgSize); msg.BeginReading(); // Only accept packets from the debugger server for security reasons if ( !Sys_CompareNetAdrBase ( adrFrom, mServerAdr ) ) { continue; } command = msg.ReadShort ( ); // Is this what we are waiting for? if ( command == mWaitFor ) { mWaitFor = DBMSG_UNKNOWN; } switch ( command ) { case DBMSG_CONNECT: mConnected = true; SendMessage ( DBMSG_CONNECTED ); SendBreakpoints ( ); break; case DBMSG_CONNECTED: mConnected = true; SendBreakpoints ( ); break; case DBMSG_DISCONNECT: mConnected = false; break; case DBMSG_BREAK: HandleBreak ( &msg ); break; // Callstack being send to the client case DBMSG_INSPECTCALLSTACK: HandleInspectCallstack ( &msg ); break; // Thread list is being sent to the client case DBMSG_INSPECTTHREADS: HandleInspectThreads ( &msg ); break; case DBMSG_INSPECTVARIABLE: HandleInspectVariable ( &msg ); break; case DBMSG_REMOVEBREAKPOINT: HandleRemoveBreakpoint( &msg ); break; case DBMSG_INSPECTSCRIPTS: HandleInspectScripts( &msg ); break; } // Give the window a chance to process the message msg.SetReadCount(0); msg.SetReadBit(0); gDebuggerApp.GetWindow().ProcessNetMessage ( &msg ); } return true; } void rvDebuggerClient::HandleRemoveBreakpoint(idBitMsg* msg) { long lineNumber; char filename[MAX_PATH]; // Read the breakpoint info lineNumber = msg->ReadInt(); msg->ReadString(filename, MAX_PATH); rvDebuggerBreakpoint* bp = FindBreakpoint(filename, lineNumber); if(bp) RemoveBreakpoint(bp->GetID()); } /* ================ rvDebuggerClient::HandleBreak Handle the DBMSG_BREAK message send from the server. This message is handled by caching the file and linenumber where the break occured. ================ */ void rvDebuggerClient::HandleBreak ( idBitMsg* msg ) { char filename[MAX_PATH]; mBreak = true; // Line number mBreakLineNumber = msg->ReadInt ( ); // Filename msg->ReadString ( filename, MAX_PATH ); mBreakFilename = filename; //int64_t ptr64b = msg->ReadInt64(); //mBreakProgram = (idProgram*)ptr64b; // Clear the variables mVariables.Clear ( ); // Request the callstack and threads SendMessage ( DBMSG_INSPECTCALLSTACK ); WaitFor ( DBMSG_INSPECTCALLSTACK, 2000 ); SendMessage ( DBMSG_INSPECTTHREADS ); WaitFor ( DBMSG_INSPECTTHREADS, 2000 ); } /* ================ rvDebuggerClient::InspectScripts Instructs the client to inspect the loaded scripts ================ */ void rvDebuggerClient::InspectScripts ( void ) { idBitMsg msg; byte buffer[MAX_MSGLEN]; msg.Init(buffer, sizeof(buffer)); msg.BeginWriting(); msg.WriteShort((short)DBMSG_INSPECTSCRIPTS); SendPacket(msg.GetData(), msg.GetSize()); } /* ================ rvDebuggerClient::InspectVariable Instructs the client to inspect the given variable at the given callstack depth. The variable is inspected by sending a DBMSG_INSPECTVARIABLE message to the server which will in turn respond back to the client with the variable value ================ */ void rvDebuggerClient::InspectVariable ( const char* name, int callstackDepth ) { idBitMsg msg; byte buffer[MAX_MSGLEN]; msg.Init( buffer, sizeof( buffer ) ); msg.BeginWriting(); msg.WriteShort ( (short)DBMSG_INSPECTVARIABLE ); msg.WriteShort ( (short)(mCallstack.Num()-callstackDepth) ); msg.WriteString ( name ); SendPacket ( msg.GetData(), msg.GetSize()); } /* ================ rvDebuggerClient::HandleInspectScripts Handle the message DBMSG_INSPECTSCRIPTS being sent from the server. This message is handled by adding the script entries to a list for later lookup. ================ */ void rvDebuggerClient::HandleInspectScripts( idBitMsg* msg ) { int totalScripts; mServerScripts.Clear(); // Read all of the callstack entries specfied in the message for (totalScripts = msg->ReadInt(); totalScripts > 0; totalScripts--) { char temp[1024]; // Script Name msg->ReadString(temp, 1024); mServerScripts.Append(temp); } } /* ================ rvDebuggerClient::HandleInspectCallstack Handle the message DBMSG_INSPECTCALLSTACK being sent from the server. This message is handled by adding the callstack entries to a list for later lookup. ================ */ void rvDebuggerClient::HandleInspectCallstack ( idBitMsg* msg ) { int depth; ClearCallstack ( ); // Read all of the callstack entries specfied in the message for ( depth = (short)msg->ReadShort ( ) ; depth > 0; depth -- ) { rvDebuggerCallstack* entry = new rvDebuggerCallstack; char temp[1024]; // Function name msg->ReadString ( temp, 1024 ); entry->mFunction = idStr(temp); // Filename msg->ReadString ( temp, 1024 ); entry->mFilename = idStr(temp); // Line Number entry->mLineNumber = msg->ReadInt ( ); // Add to list mCallstack.Append ( entry ); } } /* ================ rvDebuggerClient::HandleInspectThreads Handle the message DBMSG_INSPECTTHREADS being sent from the server. This message is handled by adding the list of threads to a list for later lookup. ================ */ void rvDebuggerClient::HandleInspectThreads ( idBitMsg* msg ) { int count; ClearThreads ( ); // Loop over the number of threads in the message for ( count = (short)msg->ReadShort ( ) ; count > 0; count -- ) { rvDebuggerThread* entry = new rvDebuggerThread; char temp[1024]; // Thread name msg->ReadString ( temp, 1024 ); entry->mName = temp; // Thread ID entry->mID = msg->ReadInt ( ); // Thread state entry->mCurrent = msg->ReadBits ( 1 ) ? true : false; entry->mDoneProcessing = msg->ReadBits ( 1 ) ? true : false; entry->mWaiting = msg->ReadBits ( 1 ) ? true : false; entry->mDying = msg->ReadBits ( 1 ) ? true : false; // Add thread to list mThreads.Append ( entry ); } } /* ================ rvDebuggerClient::HandleInspectVariable Handle the message DBMSG_INSPECTVARIABLE being sent from the server. This message is handled by adding the inspected variable to a dictionary for later lookup ================ */ void rvDebuggerClient::HandleInspectVariable ( idBitMsg* msg ) { char var[1024]; char value[1024]; int callDepth; callDepth = (short)msg->ReadShort ( ); msg->ReadString ( var, 1024 ); msg->ReadString ( value, 1024 ); mVariables.Set ( va("%d:%s", mCallstack.Num()-callDepth, var), value ); } /* ================ rvDebuggerClient::WaitFor Waits the given amount of time for the specified message to be received by the debugger client. ================ */ bool rvDebuggerClient::WaitFor ( EDebuggerMessage msg, int time ) { int start; // Cant wait if not connected if ( !mConnected ) { return false; } start = Sys_Milliseconds ( ); mWaitFor = msg; while ( mWaitFor != DBMSG_UNKNOWN && Sys_Milliseconds()-start < time ) { ProcessMessages ( ); Sleep ( 0 ); } if ( mWaitFor != DBMSG_UNKNOWN ) { mWaitFor = DBMSG_UNKNOWN; return false; } return true; } /* ================ rvDebuggerClient::FindBreakpoint Searches for a breakpoint that maches the given filename and linenumber ================ */ rvDebuggerBreakpoint* rvDebuggerClient::FindBreakpoint ( const char* filename, int linenumber ) { int i; for ( i = 0; i < mBreakpoints.Num(); i ++ ) { rvDebuggerBreakpoint* bp = mBreakpoints[i]; if ( linenumber == bp->GetLineNumber ( ) && !idStr::Icmp ( bp->GetFilename ( ), filename ) ) { return bp; } } return NULL; } /* ================ rvDebuggerClient::ClearBreakpoints Removes all breakpoints from the client and server ================ */ void rvDebuggerClient::ClearBreakpoints ( void ) { int i; for ( i = 0; i < GetBreakpointCount(); i ++ ) { rvDebuggerBreakpoint* bp = mBreakpoints[i]; assert ( bp ); SendRemoveBreakpoint ( *bp ); delete bp; } mBreakpoints.Clear ( ); } /* ================ rvDebuggerClient::AddBreakpoint Adds a breakpoint to the client and server with the give nfilename and linenumber ================ */ int rvDebuggerClient::AddBreakpoint ( const char* filename, int lineNumber, bool onceOnly ) { int index = mBreakpoints.Append ( new rvDebuggerBreakpoint ( filename, lineNumber ) ); SendAddBreakpoint ( *mBreakpoints[index] ); return index; } /* ================ rvDebuggerClient::RemoveBreakpoint Removes the breakpoint with the given ID from the client and server ================ */ bool rvDebuggerClient::RemoveBreakpoint ( int bpID ) { int index; for ( index = 0; index < GetBreakpointCount(); index ++ ) { if ( mBreakpoints[index]->GetID ( ) == bpID ) { SendRemoveBreakpoint ( *mBreakpoints[index] ); delete mBreakpoints[index]; mBreakpoints.RemoveIndex ( index ); return true; } } return false; } /* ================ rvDebuggerClient::SendMessage Send a message with no data to the debugger server ================ */ void rvDebuggerClient::SendMessage ( EDebuggerMessage dbmsg ) { idBitMsg msg; byte buffer[MAX_MSGLEN]; msg.Init ( buffer, sizeof( buffer ) ); msg.BeginWriting ( ); msg.WriteShort ( (short)dbmsg ); SendPacket ( msg.GetData(), msg.GetSize() ); } /* ================ rvDebuggerClient::SendBreakpoints Send all breakpoints to the debugger server ================ */ void rvDebuggerClient::SendBreakpoints ( void ) { int i; if ( !mConnected ) { return; } // Send all the breakpoints to the server for ( i = 0; i < mBreakpoints.Num(); i ++ ) { SendAddBreakpoint ( *mBreakpoints[i] ); } } /* ================ rvDebuggerClient::SendAddBreakpoint Send an individual breakpoint over to the debugger server ================ */ void rvDebuggerClient::SendAddBreakpoint ( rvDebuggerBreakpoint& bp, bool onceOnly ) { idBitMsg msg; byte buffer[MAX_MSGLEN]; if ( !mConnected ) { return; } msg.Init( buffer, sizeof( buffer ) ); msg.BeginWriting(); msg.WriteShort ( (short)DBMSG_ADDBREAKPOINT ); msg.WriteBits ( onceOnly?1:0, 1 ); msg.WriteInt ( (unsigned long) bp.GetLineNumber ( ) ); msg.WriteInt ( bp.GetID ( ) ); msg.WriteString ( bp.GetFilename() ); SendPacket ( msg.GetData(), msg.GetSize() ); } /* ================ rvDebuggerClient::SendRemoveBreakpoint Sends a remove breakpoint message to the debugger server ================ */ void rvDebuggerClient::SendRemoveBreakpoint ( rvDebuggerBreakpoint& bp ) { idBitMsg msg; byte buffer[MAX_MSGLEN]; if ( !mConnected ) { return; } msg.Init ( buffer, sizeof( buffer ) ); msg.BeginWriting( ); msg.WriteShort ( (short)DBMSG_REMOVEBREAKPOINT ); msg.WriteInt ( bp.GetID() ); SendPacket ( msg.GetData(), msg.GetSize() ); } /* ================ rvDebuggerClient::ClearCallstack Clear all callstack entries ================ */ void rvDebuggerClient::ClearCallstack ( void ) { int depth; for ( depth = 0; depth < mCallstack.Num(); depth ++ ) { delete mCallstack[depth]; } mCallstack.Clear ( ); } /* ================ rvDebuggerClient::ClearThreads Clear all thread entries ================ */ void rvDebuggerClient::ClearThreads ( void ) { int i; for ( i = 0; i < mThreads.Num(); i ++ ) { delete mThreads[i]; } mThreads.Clear ( ); }