telnetDebugger.cpp 27 KB

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