telnetDebugger.cpp 25 KB

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