telnetDebugger.cpp
Engine/source/console/telnetDebugger.cpp
Public Variables
Public Functions
debuggerConsumer(U32 level, const char * line)
DefineEngineFunction(dbgDisconnect , void , () , "()" "Forcibly disconnects any attached script debugging <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">client.\n</a>" "@internal Primarily used <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> Torsion and other debugging tools" )
DefineEngineFunction(dbgIsConnected , bool , () , "()" "Returns true <a href="/coding/file/tsmeshintrinsics_8cpp/#tsmeshintrinsics_8cpp_1a4e4fa7e3399708e0777b5308db01278c">if</a> <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> script debugging client is connected else return <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">false.\n</a>" "@internal Primarily used <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> Torsion and other debugging tools" )
DefineEngineFunction(dbgSetParameters , void , (S32 port, const char *password, bool waitForClient) , (false) , "( int port, string password, bool waitForClient )" "Open <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> debug server port on the specified port, requiring the specified password, " "and optionally waiting <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> the debug client <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">connect.\n</a>" " @internal Primarily used <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> Torsion and other debugging tools" )
Detailed Description
Public Variables
MODULE_END
MODULE_INIT
MODULE_SHUTDOWN
TelnetDebugger * TelDebugger
Public Functions
debuggerConsumer(U32 level, const char * line)
DefineEngineFunction(dbgDisconnect , void , () , "()" "Forcibly disconnects any attached script debugging <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">client.\n</a>" "@internal Primarily used <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> Torsion and other debugging tools" )
DefineEngineFunction(dbgIsConnected , bool , () , "()" "Returns true <a href="/coding/file/tsmeshintrinsics_8cpp/#tsmeshintrinsics_8cpp_1a4e4fa7e3399708e0777b5308db01278c">if</a> <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> script debugging client is connected else return <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">false.\n</a>" "@internal Primarily used <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> Torsion and other debugging tools" )
DefineEngineFunction(dbgSetParameters , void , (S32 port, const char *password, bool waitForClient) , (false) , "( int port, string password, bool waitForClient )" "Open <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> debug server port on the specified port, requiring the specified password, " "and optionally waiting <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> the debug client <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">connect.\n</a>" " @internal Primarily used <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> Torsion and other debugging tools" )
1 2//----------------------------------------------------------------------------- 3// Copyright (c) 2012 GarageGames, LLC 4// 5// Permission is hereby granted, free of charge, to any person obtaining a copy 6// of this software and associated documentation files (the "Software"), to 7// deal in the Software without restriction, including without limitation the 8// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 9// sell copies of the Software, and to permit persons to whom the Software is 10// furnished to do so, subject to the following conditions: 11// 12// The above copyright notice and this permission notice shall be included in 13// all copies or substantial portions of the Software. 14// 15// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 20// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 21// IN THE SOFTWARE. 22//----------------------------------------------------------------------------- 23 24#include "platform/platform.h" 25#include "console/telnetDebugger.h" 26 27#include "core/frameAllocator.h" 28#include "console/console.h" 29#include "console/engineAPI.h" 30#include "core/stringTable.h" 31#include "console/consoleInternal.h" 32#include "console/ast.h" 33#include "console/compiler.h" 34#include "core/util/journal/process.h" 35#include "core/module.h" 36 37 38MODULE_BEGIN( TelnetDebugger ) 39 40 MODULE_INIT 41 { 42 TelnetDebugger::create(); 43 } 44 45 MODULE_SHUTDOWN 46 { 47 TelnetDebugger::destroy(); 48 } 49 50MODULE_END; 51 52// 53// Enhanced TelnetDebugger for Torsion 54// http://www.sickheadgames.com/torsion 55// 56// 57// Debugger commands: 58// 59// CEVAL console line - evaluate the console line 60// output: none 61// 62// BRKVARSET varName passct expr - NOT IMPLEMENTED! 63// output: none 64// 65// BRKVARCLR varName - NOT IMPLEMENTED! 66// output: none 67// 68// BRKSET file line clear passct expr - set a breakpoint on the file,line 69// it must pass passct times for it to break and if clear is true, it 70// clears when hit 71// output: 72// 73// BRKNEXT - stop execution at the next breakable line. 74// output: none 75// 76// BRKCLR file line - clear a breakpoint on the file,line 77// output: none 78// 79// BRKCLRALL - clear all breakpoints 80// output: none 81// 82// CONTINUE - continue execution 83// output: RUNNING 84// 85// STEPIN - run until next statement 86// output: RUNNING 87// 88// STEPOVER - run until next break <= current frame 89// output: RUNNING 90// 91// STEPOUT - run until next break <= current frame - 1 92// output: RUNNING 93// 94// EVAL tag frame expr - evaluate the expr in the console, on the frame'th stack frame 95// output: EVALOUT tag exprResult 96// 97// FILELIST - list script files loaded 98// output: FILELISTOUT file1 file2 file3 file4 ... 99// 100// BREAKLIST file - get a list of breakpoint-able lines in the file 101// output: BREAKLISTOUT file skipBreakPairs skiplinecount breaklinecount skiplinecount breaklinecount ... 102// 103// 104// Other output: 105// 106// BREAK file1 line1 function1 file2 line2 function2 ... - Sent when the debugger hits a 107// breakpoint. It lists out one file/line/function triplet for each stack level. 108// The first one is the top of the stack. 109// 110// COUT console-output - echo of console output from engine 111// 112// BRKMOV file line newline - sent when a breakpoint is moved to a breakable line. 113// 114// BRKCLR file line - sent when a breakpoint cannot be moved to a breakable line on the client. 115// 116 117DefineEngineFunction( dbgSetParameters, void, (S32 port, const char * password, bool waitForClient ), (false), "( int port, string password, bool waitForClient )" 118 "Open a debug server port on the specified port, requiring the specified password, " 119 "and optionally waiting for the debug client to connect.\n" 120 "@internal Primarily used for Torsion and other debugging tools") 121{ 122 if (TelDebugger) 123 { 124 TelDebugger->setDebugParameters(port, password, waitForClient ); 125 } 126} 127 128DefineEngineFunction( dbgIsConnected, bool, (), , "()" 129 "Returns true if a script debugging client is connected else return false.\n" 130 "@internal Primarily used for Torsion and other debugging tools") 131{ 132 return TelDebugger && TelDebugger->isConnected(); 133} 134 135DefineEngineFunction( dbgDisconnect, void, (), , "()" 136 "Forcibly disconnects any attached script debugging client.\n" 137 "@internal Primarily used for Torsion and other debugging tools") 138{ 139 if (TelDebugger) 140 TelDebugger->disconnect(); 141} 142 143static void debuggerConsumer(U32 level, const char *line) 144{ 145 TORQUE_UNUSED(level); 146 if (TelDebugger) 147 TelDebugger->processConsoleLine(line); 148} 149 150TelnetDebugger::TelnetDebugger() 151{ 152 Con::addConsumer(debuggerConsumer); 153 154 mAcceptPort = -1; 155 mAcceptSocket = NetSocket::INVALID; 156 mDebugSocket = NetSocket::INVALID; 157 158 mState = NotConnected; 159 mCurPos = 0; 160 161 mBreakpoints = NULL; 162 mBreakOnNextStatement = false; 163 mStackPopBreakIndex = -1; 164 mProgramPaused = false; 165 mWaitForClient = false; 166 167 dStrncpy(mDebuggerPassword, "", PasswordMaxLength); 168 dStrncpy(mLineBuffer, "", sizeof(mLineBuffer)); 169 170 // Add the version number in a global so that 171 // scripts can detect the presence of the 172 // "enhanced" debugger features. 173 Con::evaluatef( "$dbgVersion = %d;", Version ); 174} 175 176TelnetDebugger::Breakpoint **TelnetDebugger::findBreakpoint(StringTableEntry fileName, S32 lineNumber) 177{ 178 Breakpoint **walk = &mBreakpoints; 179 Breakpoint *cur; 180 while((cur = *walk) != NULL) 181 { 182 // TODO: This assumes that the OS file names are case 183 // insensitive... Torque needs a dFilenameCmp() function. 184 if( dStricmp( cur->fileName, fileName ) == 0 && cur->lineNumber == U32(lineNumber)) 185 return walk; 186 walk = &cur->next; 187 } 188 return NULL; 189} 190 191 192TelnetDebugger::~TelnetDebugger() 193{ 194 Con::removeConsumer(debuggerConsumer); 195 196 if(mAcceptSocket != NetSocket::INVALID) 197 Net::closeSocket(mAcceptSocket); 198 if(mDebugSocket != NetSocket::INVALID) 199 Net::closeSocket(mDebugSocket); 200} 201 202TelnetDebugger *TelDebugger = NULL; 203 204void TelnetDebugger::create() 205{ 206 TelDebugger = new TelnetDebugger; 207 Process::notify(TelDebugger, &TelnetDebugger::process, PROCESS_FIRST_ORDER); 208} 209 210void TelnetDebugger::destroy() 211{ 212 Process::remove(TelDebugger, &TelnetDebugger::process); 213 delete TelDebugger; 214 TelDebugger = NULL; 215} 216 217void TelnetDebugger::send(const char *str) 218{ 219 Net::send(mDebugSocket, (const unsigned char*)str, dStrlen(str)); 220} 221 222void TelnetDebugger::disconnect() 223{ 224 if ( mDebugSocket != NetSocket::INVALID ) 225 { 226 Net::closeSocket(mDebugSocket); 227 mDebugSocket = NetSocket::INVALID; 228 } 229 230 removeAllBreakpoints(); 231 232 mState = NotConnected; 233 mProgramPaused = false; 234} 235 236void TelnetDebugger::setDebugParameters(S32 port, const char *password, bool waitForClient) 237{ 238 // Don't bail if same port... we might just be wanting to change 239 // the password. 240// if(port == mAcceptPort) 241// return; 242 243 if(mAcceptSocket != NetSocket::INVALID) 244 { 245 Net::closeSocket(mAcceptSocket); 246 mAcceptSocket = NetSocket::INVALID; 247 } 248 mAcceptPort = port; 249 if(mAcceptPort != -1 && mAcceptPort != 0) 250 { 251 NetAddress address; 252 Net::getIdealListenAddress(&address); 253 address.port = mAcceptPort; 254 255 mAcceptSocket = Net::openSocket(); 256 Net::bindAddress(address, mAcceptSocket); 257 Net::listen(mAcceptSocket, 4); 258 259 Net::setBlocking(mAcceptSocket, false); 260 } 261 dStrncpy(mDebuggerPassword, password, PasswordMaxLength); 262 263 mWaitForClient = waitForClient; 264 if ( !mWaitForClient ) 265 return; 266 267 // Wait for the client to fully connect. 268 while ( mState != Connected ) 269 { 270 Platform::sleep(10); 271 process(); 272 } 273 274} 275 276void TelnetDebugger::processConsoleLine(const char *consoleLine) 277{ 278 if(mState != NotConnected) 279 { 280 send("COUT "); 281 send(consoleLine); 282 send("\r\n"); 283 } 284} 285 286void TelnetDebugger::process() 287{ 288 NetAddress address; 289 290 if(mAcceptSocket != NetSocket::INVALID) 291 { 292 // ok, see if we have any new connections: 293 NetSocket newConnection; 294 newConnection = Net::accept(mAcceptSocket, &address); 295 296 if(newConnection != NetSocket::INVALID && mDebugSocket == NetSocket::INVALID) 297 { 298 char buffer[256]; 299 Net::addressToString(&address, buffer); 300 Con::printf("Debugger connection from %s", buffer); 301 302 mState = PasswordTry; 303 mDebugSocket = newConnection; 304 305 Net::setBlocking(newConnection, false); 306 } 307 else if(newConnection != NetSocket::INVALID) 308 Net::closeSocket(newConnection); 309 } 310 // see if we have any input to process... 311 312 if(mDebugSocket == NetSocket::INVALID) 313 return; 314 315 checkDebugRecv(); 316 if(mDebugSocket == NetSocket::INVALID) 317 removeAllBreakpoints(); 318} 319 320void TelnetDebugger::checkDebugRecv() 321{ 322 for (;;) 323 { 324 // Process all the complete commands in the buffer. 325 while ( mCurPos > 0 ) 326 { 327 // Remove leading whitespace. 328 while ( mCurPos > 0 && ( mLineBuffer[0] == 0 || mLineBuffer[0] == '\r' || mLineBuffer[0] == '\n' ) ) 329 { 330 mCurPos--; 331 dMemmove(mLineBuffer, mLineBuffer + 1, mCurPos); 332 } 333 334 // Look for a complete command. 335 bool gotCmd = false; 336 for(S32 i = 0; i < mCurPos; i++) 337 { 338 if( mLineBuffer[i] == 0 ) 339 mLineBuffer[i] = '_'; 340 341 else if ( mLineBuffer[i] == '\r' || mLineBuffer[i] == '\n' ) 342 { 343 // Send this command to be processed. 344 mLineBuffer[i] = '\n'; 345 processLineBuffer(i+1); 346 347 // Remove the command from the buffer. 348 mCurPos -= i + 1; 349 dMemmove(mLineBuffer, mLineBuffer + i + 1, mCurPos); 350 351 gotCmd = true; 352 break; 353 } 354 } 355 356 // If we didn't find a command in this pass 357 // then we have an incomplete buffer. 358 if ( !gotCmd ) 359 break; 360 } 361 362 // found no <CR> or <LF> 363 if(mCurPos == MaxCommandSize) // this shouldn't happen 364 { 365 disconnect(); 366 return; 367 } 368 369 S32 numBytes; 370 Net::Error err = Net::recv(mDebugSocket, (unsigned char*)(mLineBuffer + mCurPos), MaxCommandSize - mCurPos, &numBytes); 371 372 if((err != Net::NoError && err != Net::WouldBlock) || numBytes == 0) 373 { 374 disconnect(); 375 return; 376 } 377 if(err == Net::WouldBlock) 378 return; 379 380 mCurPos += numBytes; 381 } 382} 383 384void TelnetDebugger::executionStopped(CodeBlock *code, U32 lineNumber) 385{ 386 if(mProgramPaused) 387 return; 388 389 if(mBreakOnNextStatement) 390 { 391 setBreakOnNextStatement( false ); 392 breakProcess(); 393 return; 394 } 395 396 Breakpoint **bp = findBreakpoint(code->name, lineNumber); 397 if(!bp) 398 return; 399 400 Breakpoint *brk = *bp; 401 mProgramPaused = true; 402 Con::evaluatef("$Debug::result = %s;", brk->testExpression); 403 if(Con::getBoolVariable("$Debug::result")) 404 { 405 brk->curCount++; 406 if(brk->curCount >= brk->passCount) 407 { 408 brk->curCount = 0; 409 if(brk->clearOnHit) 410 removeBreakpoint(code->name, lineNumber); 411 breakProcess(); 412 } 413 } 414 mProgramPaused = false; 415} 416 417void TelnetDebugger::pushStackFrame() 418{ 419 if(mState == NotConnected) 420 return; 421 422 if(mBreakOnNextStatement && mStackPopBreakIndex > -1 && 423 gEvalState.getStackDepth() > mStackPopBreakIndex) 424 setBreakOnNextStatement( false ); 425} 426 427void TelnetDebugger::popStackFrame() 428{ 429 if(mState == NotConnected) 430 return; 431 432 if(mStackPopBreakIndex > -1 && gEvalState.getStackDepth()-1 <= mStackPopBreakIndex) 433 setBreakOnNextStatement( true ); 434} 435 436void TelnetDebugger::breakProcess() 437{ 438 // Send out a break with the full stack. 439 sendBreak(); 440 441 mProgramPaused = true; 442 while(mProgramPaused) 443 { 444 Platform::sleep(10); 445 checkDebugRecv(); 446 if(mDebugSocket == NetSocket::INVALID) 447 { 448 mProgramPaused = false; 449 removeAllBreakpoints(); 450 debugContinue(); 451 return; 452 } 453 } 454} 455 456void TelnetDebugger::sendBreak() 457{ 458 // echo out the break 459 send("BREAK"); 460 char buffer[MaxCommandSize]; 461 char scope[MaxCommandSize]; 462 463 S32 last = 0; 464 465 for(S32 i = (S32) gEvalState.getStackDepth() - 1; i >= last; i--) 466 { 467 CodeBlock *code = gEvalState.stack[i]->code; 468 const char *file = "<none>"; 469 if (code && code->name && code->name[0]) 470 file = code->name; 471 472 Namespace *ns = gEvalState.stack[i]->scopeNamespace; 473 scope[0] = 0; 474 if ( ns ) { 475 476 if ( ns->mParent && ns->mParent->mPackage && ns->mParent->mPackage[0] ) { 477 dStrcat( scope, ns->mParent->mPackage, MaxCommandSize ); 478 dStrcat( scope, "::", MaxCommandSize ); 479 } 480 if ( ns->mName && ns->mName[0] ) { 481 dStrcat( scope, ns->mName, MaxCommandSize ); 482 dStrcat( scope, "::", MaxCommandSize ); 483 } 484 } 485 486 const char *function = gEvalState.stack[i]->scopeName; 487 if ((!function) || (!function[0])) 488 function = "<none>"; 489 dStrcat( scope, function, MaxCommandSize ); 490 491 U32 line=0, inst; 492 U32 ip = gEvalState.stack[i]->ip; 493 if (code) 494 code->findBreakLine(ip, line, inst); 495 dSprintf(buffer, MaxCommandSize, " %s %d %s", file, line, scope); 496 send(buffer); 497 } 498 499 send("\r\n"); 500} 501 502void TelnetDebugger::processLineBuffer(S32 cmdLen) 503{ 504 if (mState == PasswordTry) 505 { 506 if(dStrncmp(mLineBuffer, mDebuggerPassword, cmdLen-1)) 507 { 508 // failed password: 509 send("PASS WrongPassword.\r\n"); 510 disconnect(); 511 } 512 else 513 { 514 send("PASS Connected.\r\n"); 515 mState = mWaitForClient ? Initialize : Connected; 516 } 517 518 return; 519 } 520 else 521 { 522 char evalBuffer[MaxCommandSize]; 523 char varBuffer[MaxCommandSize]; 524 char fileBuffer[MaxCommandSize]; 525 char clear[MaxCommandSize]; 526 S32 passCount, line, frame; 527 528 if(dSscanf(mLineBuffer, "CEVAL %[^\n]", evalBuffer) == 1) 529 { 530 RawData rd; 531 rd.size = dStrlen(evalBuffer) + 1; 532 rd.data = ( S8* ) evalBuffer; 533 Con::smConsoleInput.trigger(rd); 534 } 535 else if(dSscanf(mLineBuffer, "BRKVARSET %s %d %[^\n]", varBuffer, &passCount, evalBuffer) == 3) 536 addVariableBreakpoint(varBuffer, passCount, evalBuffer); 537 else if(dSscanf(mLineBuffer, "BRKVARCLR %s", varBuffer) == 1) 538 removeVariableBreakpoint(varBuffer); 539 else if(dSscanf(mLineBuffer, "BRKSET %s %d %s %d %[^\n]", fileBuffer,&line,&clear,&passCount,evalBuffer) == 5) 540 addBreakpoint(fileBuffer, line, dAtob(clear), passCount, evalBuffer); 541 else if(dSscanf(mLineBuffer, "BRKCLR %s %d", fileBuffer, &line) == 2) 542 removeBreakpoint(fileBuffer, line); 543 else if(!dStrncmp(mLineBuffer, "BRKCLRALL\n", cmdLen)) 544 removeAllBreakpoints(); 545 else if(!dStrncmp(mLineBuffer, "BRKNEXT\n", cmdLen)) 546 debugBreakNext(); 547 else if(!dStrncmp(mLineBuffer, "CONTINUE\n", cmdLen)) 548 debugContinue(); 549 else if(!dStrncmp(mLineBuffer, "STEPIN\n", cmdLen)) 550 debugStepIn(); 551 else if(!dStrncmp(mLineBuffer, "STEPOVER\n", cmdLen)) 552 debugStepOver(); 553 else if(!dStrncmp(mLineBuffer, "STEPOUT\n", cmdLen)) 554 debugStepOut(); 555 else if(dSscanf(mLineBuffer, "EVAL %s %d %[^\n]", varBuffer, &frame, evalBuffer) == 3) 556 evaluateExpression(varBuffer, frame, evalBuffer); 557 else if(!dStrncmp(mLineBuffer, "FILELIST\n", cmdLen)) 558 dumpFileList(); 559 else if(dSscanf(mLineBuffer, "BREAKLIST %s", fileBuffer) == 1) 560 dumpBreakableList(fileBuffer); 561 else 562 { 563 S32 errorLen = dStrlen(mLineBuffer) + 32; // ~25 in error message, plus buffer 564 FrameTemp<char> errorBuffer(errorLen); 565 566 dSprintf( errorBuffer, errorLen, "DBGERR Invalid command(%s)!\r\n", mLineBuffer ); 567 // invalid stuff. 568 send( errorBuffer ); 569 } 570 } 571} 572 573void TelnetDebugger::addVariableBreakpoint(const char*, S32, const char*) 574{ 575 send("addVariableBreakpoint\r\n"); 576} 577 578void TelnetDebugger::removeVariableBreakpoint(const char*) 579{ 580 send("removeVariableBreakpoint\r\n"); 581} 582 583void TelnetDebugger::addAllBreakpoints(CodeBlock *code) 584{ 585 if(mState == NotConnected) 586 return; 587 588 // Find the breakpoints for this code block and attach them. 589 Breakpoint *cur = mBreakpoints; 590 while( cur != NULL ) 591 { 592 // TODO: This assumes that the OS file names are case 593 // insensitive... Torque needs a dFilenameCmp() function. 594 if( dStricmp( cur->fileName, code->name ) == 0 ) 595 { 596 cur->code = code; 597 598 // Find the fist breakline starting from and 599 // including the requested breakline. 600 S32 newLine = code->findFirstBreakLine(cur->lineNumber); 601 if (newLine <= 0) 602 { 603 char buffer[MaxCommandSize]; 604 dSprintf(buffer, MaxCommandSize, "BRKCLR %s %d\r\n", cur->fileName, cur->lineNumber); 605 send(buffer); 606 607 Breakpoint *next = cur->next; 608 removeBreakpoint(cur->fileName, cur->lineNumber); 609 cur = next; 610 611 continue; 612 } 613 614 // If the requested breakline does not match 615 // the actual break line we need to inform 616 // the client. 617 if (newLine != cur->lineNumber) 618 { 619 char buffer[MaxCommandSize]; 620 621 // If we already have a line at this breapoint then 622 // tell the client to clear the breakpoint. 623 if ( findBreakpoint(cur->fileName, newLine) ) { 624 625 dSprintf(buffer, MaxCommandSize, "BRKCLR %s %d\r\n", cur->fileName, cur->lineNumber); 626 send(buffer); 627 628 Breakpoint *next = cur->next; 629 removeBreakpoint(cur->fileName, cur->lineNumber); 630 cur = next; 631 632 continue; 633 } 634 635 // We're moving the breakpoint to new line... inform the 636 // client so it can update it's view. 637 dSprintf(buffer, MaxCommandSize, "BRKMOV %s %d %d\r\n", cur->fileName, cur->lineNumber, newLine); 638 send(buffer); 639 cur->lineNumber = newLine; 640 } 641 642 code->setBreakpoint(cur->lineNumber); 643 } 644 645 cur = cur->next; 646 } 647 648 // Enable all breaks if a break next was set. 649 if (mBreakOnNextStatement) 650 code->setAllBreaks(); 651} 652 653void TelnetDebugger::addBreakpoint(const char *fileName, S32 line, bool clear, S32 passCount, const char *evalString) 654{ 655 fileName = StringTable->insert(fileName); 656 Breakpoint **bp = findBreakpoint(fileName, line); 657 658 if(bp) 659 { 660 // trying to add the same breakpoint... 661 Breakpoint *brk = *bp; 662 dFree(brk->testExpression); 663 brk->testExpression = dStrdup(evalString); 664 brk->passCount = passCount; 665 brk->clearOnHit = clear; 666 brk->curCount = 0; 667 } 668 else 669 { 670 // Note that if the code block is not already 671 // loaded it is handled by addAllBreakpoints. 672 CodeBlock* code = CodeBlock::find(fileName); 673 if (code) 674 { 675 // Find the fist breakline starting from and 676 // including the requested breakline. 677 S32 newLine = code->findFirstBreakLine(line); 678 if (newLine <= 0) 679 { 680 char buffer[MaxCommandSize]; 681 dSprintf(buffer, MaxCommandSize, "BRKCLR %s %d\r\n", fileName, line); 682 send(buffer); 683 return; 684 } 685 686 // If the requested breakline does not match 687 // the actual break line we need to inform 688 // the client. 689 if (newLine != line) 690 { 691 char buffer[MaxCommandSize]; 692 693 // If we already have a line at this breapoint then 694 // tell the client to clear the breakpoint. 695 if ( findBreakpoint(fileName, newLine) ) { 696 dSprintf(buffer, MaxCommandSize, "BRKCLR %s %d\r\n", fileName, line); 697 send(buffer); 698 return; 699 } 700 701 // We're moving the breakpoint to new line... inform the client. 702 dSprintf(buffer, MaxCommandSize, "BRKMOV %s %d %d\r\n", fileName, line, newLine); 703 send(buffer); 704 line = newLine; 705 } 706 707 code->setBreakpoint(line); 708 } 709 710 Breakpoint *brk = new Breakpoint; 711 brk->code = code; 712 brk->fileName = fileName; 713 brk->lineNumber = line; 714 brk->passCount = passCount; 715 brk->clearOnHit = clear; 716 brk->curCount = 0; 717 brk->testExpression = dStrdup(evalString); 718 brk->next = mBreakpoints; 719 mBreakpoints = brk; 720 } 721} 722 723void TelnetDebugger::removeBreakpointsFromCode(CodeBlock *code) 724{ 725 Breakpoint **walk = &mBreakpoints; 726 Breakpoint *cur; 727 while((cur = *walk) != NULL) 728 { 729 if(cur->code == code) 730 { 731 dFree(cur->testExpression); 732 *walk = cur->next; 733 delete walk; 734 } 735 else 736 walk = &cur->next; 737 } 738} 739 740void TelnetDebugger::removeBreakpoint(const char *fileName, S32 line) 741{ 742 fileName = StringTable->insert(fileName); 743 Breakpoint **bp = findBreakpoint(fileName, line); 744 if(bp) 745 { 746 Breakpoint *brk = *bp; 747 *bp = brk->next; 748 if ( brk->code ) 749 brk->code->clearBreakpoint(brk->lineNumber); 750 dFree(brk->testExpression); 751 delete brk; 752 } 753} 754 755void TelnetDebugger::removeAllBreakpoints() 756{ 757 Breakpoint *walk = mBreakpoints; 758 while(walk) 759 { 760 Breakpoint *temp = walk->next; 761 if ( walk->code ) 762 walk->code->clearBreakpoint(walk->lineNumber); 763 dFree(walk->testExpression); 764 delete walk; 765 walk = temp; 766 } 767 mBreakpoints = NULL; 768} 769 770void TelnetDebugger::debugContinue() 771{ 772 if (mState == Initialize) { 773 mState = Connected; 774 return; 775 } 776 777 setBreakOnNextStatement( false ); 778 mStackPopBreakIndex = -1; 779 mProgramPaused = false; 780 send("RUNNING\r\n"); 781} 782 783void TelnetDebugger::setBreakOnNextStatement( bool enabled ) 784{ 785 if ( enabled ) 786 { 787 // Apply breaks on all the code blocks. 788 for(CodeBlock *walk = CodeBlock::getCodeBlockList(); walk; walk = walk->nextFile) 789 walk->setAllBreaks(); 790 mBreakOnNextStatement = true; 791 } 792 else if ( !enabled ) 793 { 794 // Clear all the breaks on the codeblocks 795 // then go reapply the breakpoints. 796 for(CodeBlock *walk = CodeBlock::getCodeBlockList(); walk; walk = walk->nextFile) 797 walk->clearAllBreaks(); 798 for(Breakpoint *w = mBreakpoints; w; w = w->next) 799 { 800 if ( w->code ) 801 w->code->setBreakpoint(w->lineNumber); 802 } 803 mBreakOnNextStatement = false; 804 } 805} 806 807void TelnetDebugger::debugBreakNext() 808{ 809 if (mState != Connected) 810 return; 811 812 if ( !mProgramPaused ) 813 setBreakOnNextStatement( true ); 814} 815 816void TelnetDebugger::debugStepIn() 817{ 818 // Note that step in is allowed during 819 // the initialize state, so that we can 820 // break on the first script line executed. 821 822 setBreakOnNextStatement( true ); 823 mStackPopBreakIndex = -1; 824 mProgramPaused = false; 825 826 // Don't bother sending this to the client 827 // if it's in the initialize state. It will 828 // just be ignored as the client knows it 829 // is in a running state when it connects. 830 if (mState != Initialize) 831 send("RUNNING\r\n"); 832 else 833 mState = Connected; 834} 835 836void TelnetDebugger::debugStepOver() 837{ 838 if (mState != Connected) 839 return; 840 841 setBreakOnNextStatement( true ); 842 mStackPopBreakIndex = gEvalState.getStackDepth(); 843 mProgramPaused = false; 844 send("RUNNING\r\n"); 845} 846 847void TelnetDebugger::debugStepOut() 848{ 849 if (mState != Connected) 850 return; 851 852 setBreakOnNextStatement( false ); 853 mStackPopBreakIndex = gEvalState.getStackDepth() - 1; 854 if ( mStackPopBreakIndex == 0 ) 855 mStackPopBreakIndex = -1; 856 mProgramPaused = false; 857 send("RUNNING\r\n"); 858} 859 860void TelnetDebugger::evaluateExpression(const char *tag, S32 frame, const char *evalBuffer) 861{ 862 // Make sure we're passing a valid frame to the eval. 863 if ( frame > gEvalState.getStackDepth() ) 864 frame = gEvalState.getStackDepth() - 1; 865 if ( frame < 0 ) 866 frame = 0; 867 868 // Build a buffer just big enough for this eval. 869 const char* format = "return %s;"; 870 dsize_t len = dStrlen( format ) + dStrlen( evalBuffer ); 871 char* buffer = new char[ len ]; 872 dSprintf( buffer, len, format, evalBuffer ); 873 874 // Execute the eval. 875 CodeBlock *newCodeBlock = new CodeBlock(); 876 const char* result = newCodeBlock->compileExec( NULL, buffer, false, frame ); 877 delete [] buffer; 878 879 // Create a new buffer that fits the result. 880 format = "EVALOUT %s %s\r\n"; 881 len = dStrlen( format ) + dStrlen( tag ) + dStrlen( result ); 882 buffer = new char[ len ]; 883 dSprintf( buffer, len, format, tag, result[0] ? result : "\"\"" ); 884 885 send( buffer ); 886 delete [] buffer; 887} 888 889void TelnetDebugger::dumpFileList() 890{ 891 send("FILELISTOUT "); 892 for(CodeBlock *walk = CodeBlock::getCodeBlockList(); walk; walk = walk->nextFile) 893 { 894 send(walk->name); 895 if(walk->nextFile) 896 send(" "); 897 } 898 send("\r\n"); 899} 900 901void TelnetDebugger::dumpBreakableList(const char *fileName) 902{ 903 fileName = StringTable->insert(fileName); 904 CodeBlock *file = CodeBlock::find(fileName); 905 char buffer[MaxCommandSize]; 906 if(file) 907 { 908 dSprintf(buffer, MaxCommandSize, "BREAKLISTOUT %s %d", fileName, file->breakListSize >> 1); 909 send(buffer); 910 for(U32 i = 0; i < file->breakListSize; i += 2) 911 { 912 dSprintf(buffer, MaxCommandSize, " %d %d", file->breakList[i], file->breakList[i+1]); 913 send(buffer); 914 } 915 send("\r\n"); 916 } 917 else 918 send("DBGERR No such file!\r\n"); 919} 920 921 922void TelnetDebugger::clearCodeBlockPointers(CodeBlock *code) 923{ 924 Breakpoint **walk = &mBreakpoints; 925 Breakpoint *cur; 926 while((cur = *walk) != NULL) 927 { 928 if(cur->code == code) 929 cur->code = NULL; 930 931 walk = &cur->next; 932 } 933} 934