553 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			C#
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			553 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			C#
		
	
	
		
			Executable File
		
	
	
	
	
| //-----------------------------------------------------------------------------
 | |
| // 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);
 | |
| 
 | 
