//----------------------------------------------------------------------------- // Torque Game Engine // Copyright (C) GarageGames.com, Inc. //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- // Remote scripting debugger // To use the debugger, use dbgSetParameters(port, password); in the console // of the server to enable debugger connections. Then on some other system, // start up the app (you don't have to start a game or connect to the // server) and exec("common/debugger/debugger.cs"); in the console. Then use // the debugger GUI to connect to the server with the right port and password. // Create the GUIs. exec("./DebuggerBreakConditionDlg.gui"); exec("./DebuggerConnectDlg.gui"); exec("./DebuggerEditWatchDlg.gui"); exec("./DebuggerFindDlg.gui"); exec("./DebuggerGui.gui"); exec("./DebuggerWatchDlg.gui"); // Create a TCP object named TCPDebugger. new TCPObject(TCPDebugger); // Used to get unique IDs for breakpoints and watch expressions. $DbgBreakId = 0; $DbgWatchSeq = 1; // Functions for the TCPDebugger object: // onLine is invoked whenever the TCP object receives a line from the server. // Treat the first word as a "command" and dispatch to an appropriate // handler. function TCPDebugger::onLine(%this, %line) { echo("Got line=>" @ %line); %cmd = firstWord(%line); %rest = restWords(%line); if (%cmd $= "PASS") { %this.handlePass(%rest); } else if(%cmd $= "COUT") { %this.handleLineOut(%rest); } else if(%cmd $= "FILELISTOUT") { %this.handleFileList(%rest); } else if(%cmd $= "BREAKLISTOUT") { %this.handleBreakList(%rest); } else if(%cmd $= "BREAK") { %this.handleBreak(%rest); } else if(%cmd $= "RUNNING") { %this.handleRunning(); } else if(%cmd $= "EVALOUT") { %this.handleEvalOut(%rest); } else { %this.handleError(%line); } } // Handler for PASS response. function TCPDebugger::handlePass(%this, %message) { if (%message $= "WrongPass") { DebuggerConsoleView.print("Disconnected - wrong password."); %this.disconnect(); } else if(%message $= "Connected.") { DebuggerConsoleView.print("Connected."); DebuggerStatus.setValue("CONNECTED"); %this.send("FILELIST\r\n"); } } // Handler for COUT response. function TCPDebugger::handleLineOut(%this, %line) { DebuggerConsoleView.print(%line); } // Handler for FILELISTOUT response. function TCPDebugger::handleFileList(%this, %line) { DebuggerFilePopup.clear(); %word = 0; while ((%file = getWord(%line, %word)) !$= "") { %word++; DebuggerFilePopup.add(%file, %word); } } // Handler for BREAKLISTOUT response. function TCPDebugger::handleBreakList(%this, %line) { %file = getWord(%line, 0); if (%file != $DebuggerFile) { return; } %pairs = getWord(%line, 1); %curLine = 1; DebuggerFileView.clearBreakPositions(); // Set the possible break positions. for (%i = 0; %i < %pairs; %i++) { %skip = getWord(%line, %i * 2 + 2); %breaks = getWord(%line, %i * 2 + 3); %curLine += %skip; for (%j = 0; %j < %breaks; %j++) { DebuggerFileView.setBreakPosition(%curLine); %curLine++; } } // Now set the actual break points. for (%i = 0; %i < DebuggerBreakPoints.rowCount(); %i++) { %breakText = DebuggerBreakPoints.getRowText(%i); %breakLine = getField(%breakText, 0); %breakFile = getField(%breakText, 1); if (%breakFile == $DebuggerFile) { DebuggerFileView.setBreak(%breakLine); } } } // Handler for BREAK response. function TCPDebugger::handleBreak(%this, %line) { DebuggerStatus.setValue("BREAK"); // Query all the watches. for (%i = 0; %i < DebuggerWatchView.rowCount(); %i++) { %id = DebuggerWatchView.getRowId(%i); %row = DebuggerWatchView.getRowTextById(%id); %expr = getField(%row, 0); %this.send("EVAL " @ %id @ " 0 " @ %expr @ "\r\n"); } // Update the call stack window. DebuggerCallStack.clear(); %file = getWord(%line, 0); %lineNumber = getWord(%line, 1); %funcName = getWord(%line, 2); DbgOpenFile(%file, %lineNumber, true); %nextWord = 3; %rowId = 0; %id = 0; while(1) { DebuggerCallStack.setRowById(%id, %file @ "\t" @ %lineNumber @ "\t" @ %funcName); %id++; %file = getWord(%line, %nextWord); %lineNumber = getWord(%line, %nextWord + 1); %funcName = getWord(%line, %nextWord + 2); %nextWord += 3; if (%file $= "") { break; } } } // Handler for RUNNING response. function TCPDebugger::handleRunning(%this) { DebuggerFileView.setCurrentLine(-1, true); DebuggerCallStack.clear(); DebuggerStatus.setValue("RUNNING..."); } // Handler for EVALOUT response. function TCPDebugger::handleEvalOut(%this, %line) { %id = firstWord(%line); %value = restWords(%line); // See if it's the cursor watch, or from the watch window. if (%id < 0) { DebuggerCursorWatch.setText(DebuggerCursorWatch.expr SPC "=" SPC %value); } else { %row = DebuggerWatchView.getRowTextById(%id); if (%row $= "") { return; } %expr = getField(%row, 0); DebuggerWatchView.setRowById(%id, %expr @ "\t" @ %value); } } // Handler for unrecognized response. function TCPDebugger::handleError(%this, %line) { DebuggerConsoleView.print("ERROR - bogus message: " @ %line); } // XXX Do we want/need to define any of these? function TCPDebugger::onDNSResolve(%this) { } function TCPDebugger::onConnecting(%this) { } function TCPDebugger::onConnected(%this) { } function TCPDebugger::onConnectFailed(%this) { } function TCPDebugger::onDisconnect(%this) { } // Functions for the GUI objects: // Print a line of response from the server. function DebuggerConsoleView::print(%this, %line) { %row = %this.addRow(0, %line); %this.scrollVisible(%row); } // When entry in file list selected, open the file. function DebuggerFilePopup::onSelect(%this, %id, %text) { DbgOpenFile(%text, 0, false); } // When entry on call stack selected, open the file and go to the line. function DebuggerCallStack::onAction(%this) { %id = %this.getSelectedId(); if (%id == -1) { return; } %text = %this.getRowTextById(%id); %file = getField(%text, 0); %line = getField(%text, 1); DbgOpenFile(%file, %line, %id == 0); } // Add a breakpoint at the selected spot, if it doesn't already exist. function DebuggerBreakPoints::addBreak(%this, %file, %line, %clear, %passct, %expr) { // columns 0 = line, 1 = file, 2 = expr %textLine = %line @ "\t" @ %file @ "\t" @ %expr @ "\t" @ %passct @ "\t" @ %clear; %selId = %this.getSelectedId(); %selText = %this.getRowTextById(%selId); if ((getField(%selText, 0) $= %line) && (getField(%selText, 1) $= %file)) { %this.setRowById(%selId, %textLine); } else { %this.addRow($DbgBreakId, %textLine); $DbgBreakId++; } } // Remove the selected breakpoint. function DebuggerBreakPoints::removeBreak(%this, %file, %line) { for (%i = 0; %i < %this.rowCount(); %i++) { %id = %this.getRowId(%i); %text = %this.getRowTextById(%id); if ((getField(%text, 0) $= %line) && (getField(%text, 1) $= %file)) { %this.removeRowById(%id); return; } } } // Remove all breakpoints. function DebuggerBreakPoints::clearBreaks(%this) { while (%this.rowCount()) { %id = %this.getRowId(0); %text = %this.getRowTextById(%id); %file = getField(%text, 1); %line = getField(%text, 0); DbgRemoveBreakPoint(%file, %line); } } // Go to file & line for the selected breakpoint. function DebuggerBreakPoints::onAction(%this) { %id = %this.getSelectedId(); if (%id == -1) { return; } %text = %this.getRowTextById(%id); %line = getField(%text, 0); %file = getField(%text, 1); DbgOpenFile(%file, %line, false); } // Handle breakpoint removal executed from the file-view GUI. function DebuggerFileView::onRemoveBreakPoint(%this, %line) { %file = $DebuggerFile; DbgRemoveBreakPoint(%file, %line); } // Handle breakpoint addition executed from the file-view GUI. function DebuggerFileView::onSetBreakPoint(%this, %line) { %file = $DebuggerFile; DbgSetBreakPoint(%file, %line, 0, 0, true); } // Support functions: // Add a watch expression. function DbgWatchDialogAdd() { %expr = WatchDialogExpression.getValue(); if (%expr !$= "") { DebuggerWatchView.setRowById($DbgWatchSeq, %expr @"\t(unknown)"); TCPDebugger.send("EVAL " @ $DbgWatchSeq @ " 0 " @ %expr @ "\r\n"); $DbgWatchSeq++; } Canvas.popDialog(DebuggerWatchDlg); } // Edit a watch expression. function DbgWatchDialogEdit() { %newValue = EditWatchDialogValue.getValue(); %id = DebuggerWatchView.getSelectedId(); if (%id >= 0) { %row = DebuggerWatchView.getRowTextById(%id); %expr = getField(%row, 0); if (%newValue $= "") { %assignment = %expr @ " = \"\""; } else { %assignment = %expr @ " = " @ %newValue; } TCPDebugger.send("EVAL " @ %id @ " 0 " @ %assignment @ "\r\n"); } Canvas.popDialog(DebuggerEditWatchDlg); } // Set/change the singular "cursor watch" expression. function DbgSetCursorWatch(%expr) { DebuggerCursorWatch.expr = %expr; if (DebuggerCursorWatch.expr $= "") { DebuggerCursorWatch.setText(""); } else { TCPDebugger.send("EVAL -1 0 " @ DebuggerCursorWatch.expr @ "\r\n"); } } // Connect to the server with the given addr/port/password. function DbgConnect() { %address = DebuggerConnectAddress.getValue(); %port = DebuggerConnectPort.getValue(); %password = DebuggerConnectPassword.getValue(); if ((%address !$= "" ) && (%port !$= "" ) && (%password !$= "" )) { TCPDebugger.connect(%address @ ":" @ %port); TCPDebugger.schedule(5000, send, %password @ "\r\n"); TCPDebugger.password = %password; } Canvas.popDialog(DebuggerConnectDlg); } // Put a condition on a breakpoint. function DbgBreakConditionSet() { // Read the condition. %condition = BreakCondition.getValue(); %passct = BreakPassCount.getValue(); %clear = BreakClear.getValue(); if (%condition $= "") { %condition = "true"; } if (%passct $= "") { %passct = "0"; } if (%clear $= "") { %clear = "false"; } // Set the condition. %id = DebuggerBreakPoints.getSelectedId(); if (%id != -1) { %bkp = DebuggerBreakPoints.getRowTextById(%id); DbgSetBreakPoint(getField(%bkp, 1), getField(%bkp, 0), %clear, %passct, %condition); } Canvas.popDialog(DebuggerBreakConditionDlg); } // Open a file, go to the indicated line, and optionally select the line. function DbgOpenFile(%file, %line, %selectLine) { if (%file !$= "") { // Open the file in the file view. if (DebuggerFileView.open(%file)) { // Go to the line. DebuggerFileView.setCurrentLine(%line, %selectLine); // Get the breakpoints for this file. if (%file !$= $DebuggerFile) { TCPDebugger.send("BREAKLIST " @ %file @ "\r\n"); $DebuggerFile = %file; } } } } // Search in the fileview GUI. function DbgFileViewFind() { %searchString = DebuggerFindStringText.getValue(); DebuggerFileView.findString(%searchString); Canvas.popDialog(DebuggerFindDlg); } // Set a breakpoint, optionally with condition. function DbgSetBreakPoint(%file, %line, %clear, %passct, %expr) { if (!%clear) { if (%file == $DebuggerFile) { DebuggerFileView.setBreak(%line); } } DebuggerBreakPoints.addBreak(%file, %line, %clear, %passct, %expr); TCPDebugger.send("BRKSET " @ %file @ " " @ %line @ " " @ %clear @ " " @ %passct @ " " @ %expr @ "\r\n"); } // Remove a breakpoint. function DbgRemoveBreakPoint(%file, %line) { if (%file == $DebuggerFile) { DebuggerFileView.removeBreak(%line); } TCPDebugger.send("BRKCLR " @ %file @ " " @ %line @ "\r\n"); DebuggerBreakPoints.removeBreak(%file, %line); } // Remove whatever breakpoint is selected in the breakpoints GUI. function DbgDeleteSelectedBreak() { %selectedBreak = DebuggerBreakPoints.getSelectedId(); %rowNum = DebuggerBreakPoints.getRowNumById(%selectedWatch); if (%rowNum >= 0) { %breakText = DebuggerBreakPoints.getRowText(%rowNum); %breakLine = getField(%breakText, 0); %breakFile = getField(%breakText, 1); DbgRemoveBreakPoint(%breakFile, %breakLine); } } // Send an expression to the server for evaluation. function DbgConsoleEntryReturn() { %msg = DbgConsoleEntry.getValue(); if (%msg !$= "") { DebuggerConsoleView.print("%" @ %msg); if (DebuggerStatus.getValue() $= "NOT CONNECTED") { DebuggerConsoleView.print("*** Not connected."); } else if (DebuggerStatus.getValue() $= "BREAK") { DebuggerConsoleView.print("*** Target is in BREAK mode."); } else { TCPDebugger.send("CEVAL " @ %msg @ "\r\n"); } } DbgConsoleEntry.setValue(""); } // Print a line from the server. function DbgConsolePrint(%status) { DebuggerConsoleView.print(%status); } // Delete the currently selected watch expression. function DbgDeleteSelectedWatch() { %selectedWatch = DebuggerWatchView.getSelectedId(); %rowNum = DebuggerWatchView.getRowNumById(%selectedWatch); DebuggerWatchView.removeRow(%rowNum); } // Evaluate all the watch expressions. function DbgRefreshWatches() { for (%i = 0; %i < DebuggerWatchView.rowCount(); %i++) { %id = DebuggerWatchView.getRowId(%i); %row = DebuggerWatchView.getRowTextById(%id); %expr = getField(%row, 0); TCPDebugger.send("EVAL " @ %id @ " 0 " @ %expr @ "\r\n"); } } // Various functions for doing incremental execution. function dbgStepIn() { TCPDebugger.send("STEPIN\r\n"); } function dbgStepOut() { TCPDebugger.send("STEPOUT\r\n"); } function dbgStepOver() { TCPDebugger.send("STEPOVER\r\n"); } function dbgContinue() { TCPDebugger.send("CONTINUE\r\n"); } // Start up the GUI. DebuggerConsoleView.setActive(false); Canvas.pushDialog(DebuggerGui);