telnetDebugger.cpp
Engine/source/console/telnetDebugger.cpp
Public Variables
Public Functions
debuggerConsumer(U32 level, const char * line)
DefineConsoleFunction(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" )
DefineConsoleFunction(dbgIsConnected , bool , () , "()" "Returns true <a href="/coding/file/tsmeshintrinsics_8cpp/#tsmeshintrinsics_8cpp_1a2594a51175f310ed96ad6cd7d6514878">if</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" )
DefineConsoleFunction(dbgSetParameters , void , (S32 port, const char *password, bool waitForClient) , (false) , "( int port, string password, bool waitForClient )" "Open 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)
DefineConsoleFunction(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" )
DefineConsoleFunction(dbgIsConnected , bool , () , "()" "Returns true <a href="/coding/file/tsmeshintrinsics_8cpp/#tsmeshintrinsics_8cpp_1a2594a51175f310ed96ad6cd7d6514878">if</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" )
DefineConsoleFunction(dbgSetParameters , void , (S32 port, const char *password, bool waitForClient) , (false) , "( int port, string password, bool waitForClient )" "Open 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 117DefineConsoleFunction( 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 128DefineConsoleFunction( 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 135DefineConsoleFunction( 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 // Add the version number in a global so that 168 // scripts can detect the presence of the 169 // "enhanced" debugger features. 170 Con::evaluatef( "$dbgVersion = %d;", Version ); 171} 172 173TelnetDebugger::Breakpoint **TelnetDebugger::findBreakpoint(StringTableEntry fileName, S32 lineNumber) 174{ 175 Breakpoint **walk = &mBreakpoints; 176 Breakpoint *cur; 177 while((cur = *walk) != NULL) 178 { 179 // TODO: This assumes that the OS file names are case 180 // insensitive... Torque needs a dFilenameCmp() function. 181 if( dStricmp( cur->fileName, fileName ) == 0 && cur->lineNumber == U32(lineNumber)) 182 return walk; 183 walk = &cur->next; 184 } 185 return NULL; 186} 187 188 189TelnetDebugger::~TelnetDebugger() 190{ 191 Con::removeConsumer(debuggerConsumer); 192 193 if(mAcceptSocket != NetSocket::INVALID) 194 Net::closeSocket(mAcceptSocket); 195 if(mDebugSocket != NetSocket::INVALID) 196 Net::closeSocket(mDebugSocket); 197} 198 199TelnetDebugger *TelDebugger = NULL; 200 201void TelnetDebugger::create() 202{ 203 TelDebugger = new TelnetDebugger; 204 Process::notify(TelDebugger, &TelnetDebugger::process, PROCESS_FIRST_ORDER); 205} 206 207void TelnetDebugger::destroy() 208{ 209 Process::remove(TelDebugger, &TelnetDebugger::process); 210 delete TelDebugger; 211 TelDebugger = NULL; 212} 213 214void TelnetDebugger::send(const char *str) 215{ 216 Net::send(mDebugSocket, (const unsigned char*)str, dStrlen(str)); 217} 218 219void TelnetDebugger::disconnect() 220{ 221 if ( mDebugSocket != NetSocket::INVALID ) 222 { 223 Net::closeSocket(mDebugSocket); 224 mDebugSocket = NetSocket::INVALID; 225 } 226 227 removeAllBreakpoints(); 228 229 mState = NotConnected; 230 mProgramPaused = false; 231} 232 233void TelnetDebugger::setDebugParameters(S32 port, const char *password, bool waitForClient) 234{ 235 // Don't bail if same port... we might just be wanting to change 236 // the password. 237// if(port == mAcceptPort) 238// return; 239 240 if(mAcceptSocket != NetSocket::INVALID) 241 { 242 Net::closeSocket(mAcceptSocket); 243 mAcceptSocket = NetSocket::INVALID; 244 } 245 mAcceptPort = port; 246 if(mAcceptPort != -1 && mAcceptPort != 0) 247 { 248 NetAddress address; 249 Net::getIdealListenAddress(&address); 250 address.port = mAcceptPort; 251 252 mAcceptSocket = Net::openSocket(); 253 Net::bindAddress(address, mAcceptSocket); 254 Net::listen(mAcceptSocket, 4); 255 256 Net::setBlocking(mAcceptSocket, false); 257 } 258 dStrncpy(mDebuggerPassword, password, PasswordMaxLength); 259 260 mWaitForClient = waitForClient; 261 if ( !mWaitForClient ) 262 return; 263 264 // Wait for the client to fully connect. 265 while ( mState != Connected ) 266 { 267 Platform::sleep(10); 268 process(); 269 } 270 271} 272 273void TelnetDebugger::processConsoleLine(const char *consoleLine) 274{ 275 if(mState != NotConnected) 276 { 277 send("COUT "); 278 send(consoleLine); 279 send("\r\n"); 280 } 281} 282 283void TelnetDebugger::process() 284{ 285 NetAddress address; 286 287 if(mAcceptSocket != NetSocket::INVALID) 288 { 289 // ok, see if we have any new connections: 290 NetSocket newConnection; 291 newConnection = Net::accept(mAcceptSocket, &address); 292 293 if(newConnection != NetSocket::INVALID && mDebugSocket == NetSocket::INVALID) 294 { 295 char buffer[256]; 296 Net::addressToString(&address, buffer); 297 Con::printf("Debugger connection from %s", buffer); 298 299 mState = PasswordTry; 300 mDebugSocket = newConnection; 301 302 Net::setBlocking(newConnection, false); 303 } 304 else if(newConnection != NetSocket::INVALID) 305 Net::closeSocket(newConnection); 306 } 307 // see if we have any input to process... 308 309 if(mDebugSocket == NetSocket::INVALID) 310 return; 311 312 checkDebugRecv(); 313 if(mDebugSocket == NetSocket::INVALID) 314 removeAllBreakpoints(); 315} 316 317void TelnetDebugger::checkDebugRecv() 318{ 319 for (;;) 320 { 321 // Process all the complete commands in the buffer. 322 while ( mCurPos > 0 ) 323 { 324 // Remove leading whitespace. 325 while ( mCurPos > 0 && ( mLineBuffer[0] == 0 || mLineBuffer[0] == '\r' || mLineBuffer[0] == '\n' ) ) 326 { 327 mCurPos--; 328 dMemmove(mLineBuffer, mLineBuffer + 1, mCurPos); 329 } 330 331 // Look for a complete command. 332 bool gotCmd = false; 333 for(S32 i = 0; i < mCurPos; i++) 334 { 335 if( mLineBuffer[i] == 0 ) 336 mLineBuffer[i] = '_'; 337 338 else if ( mLineBuffer[i] == '\r' || mLineBuffer[i] == '\n' ) 339 { 340 // Send this command to be processed. 341 mLineBuffer[i] = '\n'; 342 processLineBuffer(i+1); 343 344 // Remove the command from the buffer. 345 mCurPos -= i + 1; 346 dMemmove(mLineBuffer, mLineBuffer + i + 1, mCurPos); 347 348 gotCmd = true; 349 break; 350 } 351 } 352 353 // If we didn't find a command in this pass 354 // then we have an incomplete buffer. 355 if ( !gotCmd ) 356 break; 357 } 358 359 // found no <CR> or <LF> 360 if(mCurPos == MaxCommandSize) // this shouldn't happen 361 { 362 disconnect(); 363 return; 364 } 365 366 S32 numBytes; 367 Net::Error err = Net::recv(mDebugSocket, (unsigned char*)(mLineBuffer + mCurPos), MaxCommandSize - mCurPos, &numBytes); 368 369 if((err != Net::NoError && err != Net::WouldBlock) || numBytes == 0) 370 { 371 disconnect(); 372 return; 373 } 374 if(err == Net::WouldBlock) 375 return; 376 377 mCurPos += numBytes; 378 } 379} 380 381void TelnetDebugger::executionStopped(CodeBlock *code, U32 lineNumber) 382{ 383 if(mProgramPaused) 384 return; 385 386 if(mBreakOnNextStatement) 387 { 388 setBreakOnNextStatement( false ); 389 breakProcess(); 390 return; 391 } 392 393 Breakpoint **bp = findBreakpoint(code->name, lineNumber); 394 if(!bp) 395 return; 396 397 Breakpoint *brk = *bp; 398 mProgramPaused = true; 399 Con::evaluatef("$Debug::result = %s;", brk->testExpression); 400 if(Con::getBoolVariable("$Debug::result")) 401 { 402 brk->curCount++; 403 if(brk->curCount >= brk->passCount) 404 { 405 brk->curCount = 0; 406 if(brk->clearOnHit) 407 removeBreakpoint(code->name, lineNumber); 408 breakProcess(); 409 } 410 } 411 mProgramPaused = false; 412} 413 414void TelnetDebugger::pushStackFrame() 415{ 416 if(mState == NotConnected) 417 return; 418 419 if(mBreakOnNextStatement && mStackPopBreakIndex > -1 && 420 gEvalState.getStackDepth() > mStackPopBreakIndex) 421 setBreakOnNextStatement( false ); 422} 423 424void TelnetDebugger::popStackFrame() 425{ 426 if(mState == NotConnected) 427 return; 428 429 if(mStackPopBreakIndex > -1 && gEvalState.getStackDepth()-1 <= mStackPopBreakIndex) 430 setBreakOnNextStatement( true ); 431} 432 433void TelnetDebugger::breakProcess() 434{ 435 // Send out a break with the full stack. 436 sendBreak(); 437 438 mProgramPaused = true; 439 while(mProgramPaused) 440 { 441 Platform::sleep(10); 442 checkDebugRecv(); 443 if(mDebugSocket == NetSocket::INVALID) 444 { 445 mProgramPaused = false; 446 removeAllBreakpoints(); 447 debugContinue(); 448 return; 449 } 450 } 451} 452 453void TelnetDebugger::sendBreak() 454{ 455 // echo out the break 456 send("BREAK"); 457 char buffer[MaxCommandSize]; 458 char scope[MaxCommandSize]; 459 460 S32 last = 0; 461 462 for(S32 i = (S32) gEvalState.getStackDepth() - 1; i >= last; i--) 463 { 464 CodeBlock *code = gEvalState.stack[i]->code; 465 const char *file = "<none>"; 466 if (code && code->name && code->name[0]) 467 file = code->name; 468 469 Namespace *ns = gEvalState.stack[i]->scopeNamespace; 470 scope[0] = 0; 471 if ( ns ) { 472 473 if ( ns->mParent && ns->mParent->mPackage && ns->mParent->mPackage[0] ) { 474 dStrcat( scope, ns->mParent->mPackage ); 475 dStrcat( scope, "::" ); 476 } 477 if ( ns->mName && ns->mName[0] ) { 478 dStrcat( scope, ns->mName ); 479 dStrcat( scope, "::" ); 480 } 481 } 482 483 const char *function = gEvalState.stack[i]->scopeName; 484 if ((!function) || (!function[0])) 485 function = "<none>"; 486 dStrcat( scope, function ); 487 488 U32 line=0, inst; 489 U32 ip = gEvalState.stack[i]->ip; 490 if (code) 491 code->findBreakLine(ip, line, inst); 492 dSprintf(buffer, MaxCommandSize, " %s %d %s", file, line, scope); 493 send(buffer); 494 } 495 496 send("\r\n"); 497} 498 499void TelnetDebugger::processLineBuffer(S32 cmdLen) 500{ 501 if (mState == PasswordTry) 502 { 503 if(dStrncmp(mLineBuffer, mDebuggerPassword, cmdLen-1)) 504 { 505 // failed password: 506 send("PASS WrongPassword.\r\n"); 507 disconnect(); 508 } 509 else 510 { 511 send("PASS Connected.\r\n"); 512 mState = mWaitForClient ? Initialize : Connected; 513 } 514 515 return; 516 } 517 else 518 { 519 char evalBuffer[MaxCommandSize]; 520 char varBuffer[MaxCommandSize]; 521 char fileBuffer[MaxCommandSize]; 522 char clear[MaxCommandSize]; 523 S32 passCount, line, frame; 524 525 if(dSscanf(mLineBuffer, "CEVAL %[^\n]", evalBuffer) == 1) 526 { 527 RawData rd; 528 rd.size = dStrlen(evalBuffer) + 1; 529 rd.data = ( S8* ) evalBuffer; 530 Con::smConsoleInput.trigger(rd); 531 } 532 else if(dSscanf(mLineBuffer, "BRKVARSET %s %d %[^\n]", varBuffer, &passCount, evalBuffer) == 3) 533 addVariableBreakpoint(varBuffer, passCount, evalBuffer); 534 else if(dSscanf(mLineBuffer, "BRKVARCLR %s", varBuffer) == 1) 535 removeVariableBreakpoint(varBuffer); 536 else if(dSscanf(mLineBuffer, "BRKSET %s %d %s %d %[^\n]", fileBuffer,&line,&clear,&passCount,evalBuffer) == 5) 537 addBreakpoint(fileBuffer, line, dAtob(clear), passCount, evalBuffer); 538 else if(dSscanf(mLineBuffer, "BRKCLR %s %d", fileBuffer, &line) == 2) 539 removeBreakpoint(fileBuffer, line); 540 else if(!dStrncmp(mLineBuffer, "BRKCLRALL\n", cmdLen)) 541 removeAllBreakpoints(); 542 else if(!dStrncmp(mLineBuffer, "BRKNEXT\n", cmdLen)) 543 debugBreakNext(); 544 else if(!dStrncmp(mLineBuffer, "CONTINUE\n", cmdLen)) 545 debugContinue(); 546 else if(!dStrncmp(mLineBuffer, "STEPIN\n", cmdLen)) 547 debugStepIn(); 548 else if(!dStrncmp(mLineBuffer, "STEPOVER\n", cmdLen)) 549 debugStepOver(); 550 else if(!dStrncmp(mLineBuffer, "STEPOUT\n", cmdLen)) 551 debugStepOut(); 552 else if(dSscanf(mLineBuffer, "EVAL %s %d %[^\n]", varBuffer, &frame, evalBuffer) == 3) 553 evaluateExpression(varBuffer, frame, evalBuffer); 554 else if(!dStrncmp(mLineBuffer, "FILELIST\n", cmdLen)) 555 dumpFileList(); 556 else if(dSscanf(mLineBuffer, "BREAKLIST %s", fileBuffer) == 1) 557 dumpBreakableList(fileBuffer); 558 else 559 { 560 S32 errorLen = dStrlen(mLineBuffer) + 32; // ~25 in error message, plus buffer 561 FrameTemp<char> errorBuffer(errorLen); 562 563 dSprintf( errorBuffer, errorLen, "DBGERR Invalid command(%s)!\r\n", mLineBuffer ); 564 // invalid stuff. 565 send( errorBuffer ); 566 } 567 } 568} 569 570void TelnetDebugger::addVariableBreakpoint(const char*, S32, const char*) 571{ 572 send("addVariableBreakpoint\r\n"); 573} 574 575void TelnetDebugger::removeVariableBreakpoint(const char*) 576{ 577 send("removeVariableBreakpoint\r\n"); 578} 579 580void TelnetDebugger::addAllBreakpoints(CodeBlock *code) 581{ 582 if(mState == NotConnected) 583 return; 584 585 // Find the breakpoints for this code block and attach them. 586 Breakpoint *cur = mBreakpoints; 587 while( cur != NULL ) 588 { 589 // TODO: This assumes that the OS file names are case 590 // insensitive... Torque needs a dFilenameCmp() function. 591 if( dStricmp( cur->fileName, code->name ) == 0 ) 592 { 593 cur->code = code; 594 595 // Find the fist breakline starting from and 596 // including the requested breakline. 597 S32 newLine = code->findFirstBreakLine(cur->lineNumber); 598 if (newLine <= 0) 599 { 600 char buffer[MaxCommandSize]; 601 dSprintf(buffer, MaxCommandSize, "BRKCLR %s %d\r\n", cur->fileName, cur->lineNumber); 602 send(buffer); 603 604 Breakpoint *next = cur->next; 605 removeBreakpoint(cur->fileName, cur->lineNumber); 606 cur = next; 607 608 continue; 609 } 610 611 // If the requested breakline does not match 612 // the actual break line we need to inform 613 // the client. 614 if (newLine != cur->lineNumber) 615 { 616 char buffer[MaxCommandSize]; 617 618 // If we already have a line at this breapoint then 619 // tell the client to clear the breakpoint. 620 if ( findBreakpoint(cur->fileName, newLine) ) { 621 622 dSprintf(buffer, MaxCommandSize, "BRKCLR %s %d\r\n", cur->fileName, cur->lineNumber); 623 send(buffer); 624 625 Breakpoint *next = cur->next; 626 removeBreakpoint(cur->fileName, cur->lineNumber); 627 cur = next; 628 629 continue; 630 } 631 632 // We're moving the breakpoint to new line... inform the 633 // client so it can update it's view. 634 dSprintf(buffer, MaxCommandSize, "BRKMOV %s %d %d\r\n", cur->fileName, cur->lineNumber, newLine); 635 send(buffer); 636 cur->lineNumber = newLine; 637 } 638 639 code->setBreakpoint(cur->lineNumber); 640 } 641 642 cur = cur->next; 643 } 644 645 // Enable all breaks if a break next was set. 646 if (mBreakOnNextStatement) 647 code->setAllBreaks(); 648} 649 650void TelnetDebugger::addBreakpoint(const char *fileName, S32 line, bool clear, S32 passCount, const char *evalString) 651{ 652 fileName = StringTable->insert(fileName); 653 Breakpoint **bp = findBreakpoint(fileName, line); 654 655 if(bp) 656 { 657 // trying to add the same breakpoint... 658 Breakpoint *brk = *bp; 659 dFree(brk->testExpression); 660 brk->testExpression = dStrdup(evalString); 661 brk->passCount = passCount; 662 brk->clearOnHit = clear; 663 brk->curCount = 0; 664 } 665 else 666 { 667 // Note that if the code block is not already 668 // loaded it is handled by addAllBreakpoints. 669 CodeBlock* code = CodeBlock::find(fileName); 670 if (code) 671 { 672 // Find the fist breakline starting from and 673 // including the requested breakline. 674 S32 newLine = code->findFirstBreakLine(line); 675 if (newLine <= 0) 676 { 677 char buffer[MaxCommandSize]; 678 dSprintf(buffer, MaxCommandSize, "BRKCLR %s %d\r\n", fileName, line); 679 send(buffer); 680 return; 681 } 682 683 // If the requested breakline does not match 684 // the actual break line we need to inform 685 // the client. 686 if (newLine != line) 687 { 688 char buffer[MaxCommandSize]; 689 690 // If we already have a line at this breapoint then 691 // tell the client to clear the breakpoint. 692 if ( findBreakpoint(fileName, newLine) ) { 693 dSprintf(buffer, MaxCommandSize, "BRKCLR %s %d\r\n", fileName, line); 694 send(buffer); 695 return; 696 } 697 698 // We're moving the breakpoint to new line... inform the client. 699 dSprintf(buffer, MaxCommandSize, "BRKMOV %s %d %d\r\n", fileName, line, newLine); 700 send(buffer); 701 line = newLine; 702 } 703 704 code->setBreakpoint(line); 705 } 706 707 Breakpoint *brk = new Breakpoint; 708 brk->code = code; 709 brk->fileName = fileName; 710 brk->lineNumber = line; 711 brk->passCount = passCount; 712 brk->clearOnHit = clear; 713 brk->curCount = 0; 714 brk->testExpression = dStrdup(evalString); 715 brk->next = mBreakpoints; 716 mBreakpoints = brk; 717 } 718} 719 720void TelnetDebugger::removeBreakpointsFromCode(CodeBlock *code) 721{ 722 Breakpoint **walk = &mBreakpoints; 723 Breakpoint *cur; 724 while((cur = *walk) != NULL) 725 { 726 if(cur->code == code) 727 { 728 dFree(cur->testExpression); 729 *walk = cur->next; 730 delete walk; 731 } 732 else 733 walk = &cur->next; 734 } 735} 736 737void TelnetDebugger::removeBreakpoint(const char *fileName, S32 line) 738{ 739 fileName = StringTable->insert(fileName); 740 Breakpoint **bp = findBreakpoint(fileName, line); 741 if(bp) 742 { 743 Breakpoint *brk = *bp; 744 *bp = brk->next; 745 if ( brk->code ) 746 brk->code->clearBreakpoint(brk->lineNumber); 747 dFree(brk->testExpression); 748 delete brk; 749 } 750} 751 752void TelnetDebugger::removeAllBreakpoints() 753{ 754 Breakpoint *walk = mBreakpoints; 755 while(walk) 756 { 757 Breakpoint *temp = walk->next; 758 if ( walk->code ) 759 walk->code->clearBreakpoint(walk->lineNumber); 760 dFree(walk->testExpression); 761 delete walk; 762 walk = temp; 763 } 764 mBreakpoints = NULL; 765} 766 767void TelnetDebugger::debugContinue() 768{ 769 if (mState == Initialize) { 770 mState = Connected; 771 return; 772 } 773 774 setBreakOnNextStatement( false ); 775 mStackPopBreakIndex = -1; 776 mProgramPaused = false; 777 send("RUNNING\r\n"); 778} 779 780void TelnetDebugger::setBreakOnNextStatement( bool enabled ) 781{ 782 if ( enabled ) 783 { 784 // Apply breaks on all the code blocks. 785 for(CodeBlock *walk = CodeBlock::getCodeBlockList(); walk; walk = walk->nextFile) 786 walk->setAllBreaks(); 787 mBreakOnNextStatement = true; 788 } 789 else if ( !enabled ) 790 { 791 // Clear all the breaks on the codeblocks 792 // then go reapply the breakpoints. 793 for(CodeBlock *walk = CodeBlock::getCodeBlockList(); walk; walk = walk->nextFile) 794 walk->clearAllBreaks(); 795 for(Breakpoint *w = mBreakpoints; w; w = w->next) 796 { 797 if ( w->code ) 798 w->code->setBreakpoint(w->lineNumber); 799 } 800 mBreakOnNextStatement = false; 801 } 802} 803 804void TelnetDebugger::debugBreakNext() 805{ 806 if (mState != Connected) 807 return; 808 809 if ( !mProgramPaused ) 810 setBreakOnNextStatement( true ); 811} 812 813void TelnetDebugger::debugStepIn() 814{ 815 // Note that step in is allowed during 816 // the initialize state, so that we can 817 // break on the first script line executed. 818 819 setBreakOnNextStatement( true ); 820 mStackPopBreakIndex = -1; 821 mProgramPaused = false; 822 823 // Don't bother sending this to the client 824 // if it's in the initialize state. It will 825 // just be ignored as the client knows it 826 // is in a running state when it connects. 827 if (mState != Initialize) 828 send("RUNNING\r\n"); 829 else 830 mState = Connected; 831} 832 833void TelnetDebugger::debugStepOver() 834{ 835 if (mState != Connected) 836 return; 837 838 setBreakOnNextStatement( true ); 839 mStackPopBreakIndex = gEvalState.getStackDepth(); 840 mProgramPaused = false; 841 send("RUNNING\r\n"); 842} 843 844void TelnetDebugger::debugStepOut() 845{ 846 if (mState != Connected) 847 return; 848 849 setBreakOnNextStatement( false ); 850 mStackPopBreakIndex = gEvalState.getStackDepth() - 1; 851 if ( mStackPopBreakIndex == 0 ) 852 mStackPopBreakIndex = -1; 853 mProgramPaused = false; 854 send("RUNNING\r\n"); 855} 856 857void TelnetDebugger::evaluateExpression(const char *tag, S32 frame, const char *evalBuffer) 858{ 859 // Make sure we're passing a valid frame to the eval. 860 if ( frame > gEvalState.getStackDepth() ) 861 frame = gEvalState.getStackDepth() - 1; 862 if ( frame < 0 ) 863 frame = 0; 864 865 // Build a buffer just big enough for this eval. 866 const char* format = "return %s;"; 867 dsize_t len = dStrlen( format ) + dStrlen( evalBuffer ); 868 char* buffer = new char[ len ]; 869 dSprintf( buffer, len, format, evalBuffer ); 870 871 // Execute the eval. 872 CodeBlock *newCodeBlock = new CodeBlock(); 873 const char* result = newCodeBlock->compileExec( NULL, buffer, false, frame ); 874 delete [] buffer; 875 876 // Create a new buffer that fits the result. 877 format = "EVALOUT %s %s\r\n"; 878 len = dStrlen( format ) + dStrlen( tag ) + dStrlen( result ); 879 buffer = new char[ len ]; 880 dSprintf( buffer, len, format, tag, result[0] ? result : "\"\"" ); 881 882 send( buffer ); 883 delete [] buffer; 884} 885 886void TelnetDebugger::dumpFileList() 887{ 888 send("FILELISTOUT "); 889 for(CodeBlock *walk = CodeBlock::getCodeBlockList(); walk; walk = walk->nextFile) 890 { 891 send(walk->name); 892 if(walk->nextFile) 893 send(" "); 894 } 895 send("\r\n"); 896} 897 898void TelnetDebugger::dumpBreakableList(const char *fileName) 899{ 900 fileName = StringTable->insert(fileName); 901 CodeBlock *file = CodeBlock::find(fileName); 902 char buffer[MaxCommandSize]; 903 if(file) 904 { 905 dSprintf(buffer, MaxCommandSize, "BREAKLISTOUT %s %d", fileName, file->breakListSize >> 1); 906 send(buffer); 907 for(U32 i = 0; i < file->breakListSize; i += 2) 908 { 909 dSprintf(buffer, MaxCommandSize, " %d %d", file->breakList[i], file->breakList[i+1]); 910 send(buffer); 911 } 912 send("\r\n"); 913 } 914 else 915 send("DBGERR No such file!\r\n"); 916} 917 918 919void TelnetDebugger::clearCodeBlockPointers(CodeBlock *code) 920{ 921 Breakpoint **walk = &mBreakpoints; 922 Breakpoint *cur; 923 while((cur = *walk) != NULL) 924 { 925 if(cur->code == code) 926 cur->code = NULL; 927 928 walk = &cur->next; 929 } 930} 931
