telnetDebugger.cpp 26 KB

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