telnetDebugger.cc 25 KB

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