├── .gitignore ├── .travis.yml ├── Changes.md ├── LICENSE ├── README.md ├── appveyor.yml ├── debugger ├── CommandLineController.hx ├── DebuggerThread.hx ├── HaxeProtocol.hx ├── HaxeRemote.hx ├── HaxeServer.hx ├── IController.hx ├── Local.hx └── Version.hx ├── haxelib.json └── run-server.hxml /.gitignore: -------------------------------------------------------------------------------- 1 | bin -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: haxe 2 | 3 | haxe: 4 | - development 5 | - 3.4.2 6 | - 3.2.1 7 | 8 | os: 9 | - linux 10 | - osx 11 | 12 | sudo: false 13 | addons: 14 | apt: 15 | packages: 16 | # C++ (for rebuilding hxcpp) 17 | - gcc-multilib 18 | - g++-multilib 19 | 20 | install: 21 | - if [ "${TRAVIS_HAXE_VERSION}" = "development" ]; then 22 | haxelib git hxcpp https://github.com/HaxeFoundation/hxcpp.git; 23 | pushd $(haxelib path hxcpp | head -1); 24 | pushd tools/hxcpp; haxe compile.hxml; popd; 25 | pushd project; neko build.n; popd; 26 | popd; 27 | else 28 | haxelib install hxcpp; 29 | fi 30 | - haxelib dev hxcpp-debugger . 31 | - haxelib list 32 | 33 | script: 34 | # Type check all modules in the debugger package. 35 | - haxe -cpp bin --no-output --macro 'include("debugger")' 36 | # Compile debugger.HaxeServer. 37 | - haxe -cpp bin -main debugger.HaxeServer 38 | -------------------------------------------------------------------------------- /Changes.md: -------------------------------------------------------------------------------- 1 | 1.2.0 2 | --------------- 3 | * Switched to using arrays instead of lists for files, classes and variables. Lists have a problem when they grow too big and they become unusable. 4 | * Bumped protocol version to 0.1.0. 5 | 6 | 1.1.0 7 | --------------- 8 | * Change haxelib.json license to MIT, which is in the same spirit as Apache from source code. 9 | 10 | 1.0 11 | --------------- 12 | Initial Release 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | hxcpp-debugger 2 | ============== 3 | 4 | [![TravisCI Build Status](https://travis-ci.org/HaxeFoundation/hxcpp-debugger.svg?branch=master)](https://travis-ci.org/HaxeFoundation/hxcpp-debugger) 5 | [![AppVeyor Build Status](https://ci.appveyor.com/api/projects/status/github/HaxeFoundation/hxcpp-debugger?branch=master&svg=true)](https://ci.appveyor.com/project/HaxeFoundation/hxcpp-debugger) 6 | 7 | Cross-platform debugger for hxcpp 8 | 9 | See https://github.com/HaxeFoundation/hxcpp-debugger/wiki/Getting-started for an introduction. 10 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: "{build}" 2 | 3 | environment: 4 | global: 5 | HAXELIB_ROOT: C:\projects\haxelib 6 | 7 | install: 8 | # Install the haxe chocolatey package (https://chocolatey.org/packages/haxe) 9 | - cinst haxe -version 3.4.2 -y 10 | - RefreshEnv 11 | # Setup haxelib 12 | - mkdir "%HAXELIB_ROOT%" 13 | - haxelib setup "%HAXELIB_ROOT%" 14 | # Install dependencies 15 | - haxelib install hxcpp > log.txt || type log.txt && cmd /C exit 1 16 | - haxelib dev hxcpp-debugger . 17 | - haxelib list 18 | 19 | build: off 20 | 21 | test_script: 22 | # Type check all modules in the debugger package. 23 | - haxe -cpp bin --no-output --macro "include(\"debugger\")" 24 | # Compile debugger.HaxeServer. 25 | - haxe -cpp bin -main debugger.HaxeServer -------------------------------------------------------------------------------- /debugger/CommandLineController.hx: -------------------------------------------------------------------------------- 1 | /** ************************************************************************** 2 | * CommandLineController.hx 3 | * 4 | * Copyright 2013 TiVo Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | ************************************************************************** **/ 18 | 19 | package debugger; 20 | 21 | import debugger.IController; 22 | 23 | 24 | /** 25 | * This class implements a command line interface to a debugger. It 26 | * implements IController so that it can be used directly by a debugger 27 | * thread, or can be used by a proxy class. This interface reads from stdin 28 | * and writes to stdout. It supports history commands, sourcing files, and 29 | * some other niceties. 30 | **/ 31 | class CommandLineController implements IController 32 | { 33 | /** 34 | * Creates a new command line interface. This interface will read and 35 | * parse Commands from stdin, and emit debugger output to stdout. 36 | **/ 37 | public function new() 38 | { 39 | Sys.println(""); 40 | Sys.println("-=- hxcpp built-in debugger in command line mode -=-"); 41 | Sys.println("-=- Use 'help' for help if you need it. -=-"); 42 | Sys.println("-=- Have fun! -=-"); 43 | mUnsafeMode = false; 44 | mInputs = new Array(); 45 | mInputs.push(Sys.stdin()); 46 | mStoredCommands = new Array(); 47 | mLastCommand = null; 48 | // Command 0 is not valid 49 | mStoredCommands.push(""); 50 | this.setupRegexHandlers(); 51 | } 52 | 53 | // Called when the process being debugged has started again 54 | public function debuggedProcessStarted() 55 | { 56 | Sys.println("Attached to debugged process."); 57 | // xxx todo - re-issue breakpoint commands from history? 58 | // Or maybe at the very least support a command that does this? 59 | } 60 | 61 | // Called when the process being debugged has exited 62 | public function debuggedProcessExited() 63 | { 64 | Sys.println("Debugged process exited."); 65 | } 66 | 67 | public function getNextCommand() : Command 68 | { 69 | var carriedCommandLine : String = ""; 70 | 71 | while (true) { 72 | var input = mInputs[mInputs.length - 1]; 73 | 74 | if (mInputs.length == 1) { 75 | Sys.print("\n" + mStoredCommands.length + "> " + 76 | carriedCommandLine); 77 | } 78 | 79 | var commandLine = null; 80 | 81 | try { 82 | commandLine = StringTools.trim(carriedCommandLine + 83 | input.readLine()); 84 | carriedCommandLine = ""; 85 | } 86 | catch (e : haxe.io.Eof) { 87 | Sys.println("\n"); 88 | input.close(); 89 | mInputs.pop(); 90 | if (mInputs.length == 0) { 91 | return Detach; 92 | } 93 | else { 94 | continue; 95 | } 96 | } 97 | 98 | if (mInputs.length == 1) { 99 | Sys.println(""); 100 | } 101 | 102 | // If the command line ends with "\", don't execute the command, 103 | // just append it to the carriedCommandLine. This is to assist 104 | // when program or thread event output has confused the user and 105 | // they wish to continue the command in progress. 106 | 107 | if (StringTools.endsWith(commandLine, "\\")) { 108 | carriedCommandLine = 109 | commandLine.substr(0, commandLine.length - 1); 110 | continue; 111 | } 112 | 113 | // If the line is empty, the skip it unless the input is 114 | // stdin and the last command was one of the commands that 115 | // is repeated automatically (continue, step, next, finish, up, 116 | // and down) 117 | if (commandLine.length == 0) { 118 | // If reading from a sourced file or this is the first 119 | // command, don't try to repeat 120 | if ((mInputs.length > 1) || (mLastCommand == null)) { 121 | continue; 122 | } 123 | switch (mLastCommand) { 124 | case Continue(n): 125 | case Step(n): 126 | case Next(n): 127 | case Finish(n): 128 | case Up(n): 129 | case Down(n): 130 | default: 131 | // For anything other than the above, read a new command 132 | continue; 133 | } 134 | // For Continue, Step, Next, Finish, Up, or Down, repeat 135 | return mLastCommand; 136 | } 137 | 138 | var charZero = commandLine.charAt(0); 139 | 140 | // If the command is a comment, do nothing 141 | if (charZero == "#") { 142 | continue; 143 | } 144 | 145 | if (mInputs.length != 1) { 146 | Sys.println("\n" + mStoredCommands.length + "> " + commandLine); 147 | } 148 | 149 | // If it's a bang command, replace it with the stored command 150 | if (charZero == "!") { 151 | var number = 152 | Std.parseInt(StringTools.trim(commandLine.substr(1))); 153 | if ((number <= 0) || (number >= mStoredCommands.length)) { 154 | Sys.println("No command " + number + " in history."); 155 | continue; 156 | } 157 | commandLine = mStoredCommands[number]; 158 | // Print out the command line to show what is being run 159 | Sys.println(number + ") " + commandLine); 160 | } 161 | else { 162 | mStoredCommands.push(commandLine); 163 | } 164 | 165 | var command : Command = null; 166 | 167 | var matched = false; 168 | 169 | for (rh in mRegexHandlers) { 170 | if (rh.r.match(commandLine)) { 171 | command = rh.h(rh.r); 172 | matched = true; 173 | break; 174 | } 175 | } 176 | 177 | if (!matched) { 178 | Sys.println("Invalid command."); 179 | continue; 180 | } 181 | 182 | if (command != null) { 183 | // Instruction was not handled locally, so pass it on to the 184 | // debugger 185 | mLastCommand = command; 186 | return command; 187 | } 188 | } 189 | } 190 | 191 | public function acceptMessage(message : Message) 192 | { 193 | // This makes the output of the 'source' command look a little better 194 | if (mInputs.length > 1) { 195 | Sys.println(""); 196 | } 197 | 198 | switch (message) { 199 | case ErrorInternal(details): 200 | Sys.println("Debugged thread reported internal error: " + details); 201 | 202 | case ErrorNoSuchThread(number): 203 | Sys.println("No such thread " + number + "."); 204 | 205 | case ErrorNoSuchFile(fileName): 206 | Sys.println("No such file " + fileName + "."); 207 | 208 | case ErrorNoSuchBreakpoint(number): 209 | Sys.println("No such breakpoint " + number + "."); 210 | 211 | case ErrorBadClassNameRegex(details): 212 | Sys.println("Invalid class name regular expression: " + 213 | details + "."); 214 | 215 | case ErrorBadFunctionNameRegex(details): 216 | Sys.println("Invalid function name regular expression: " + 217 | details + "."); 218 | 219 | case ErrorNoMatchingFunctions(className, functionName, 220 | unresolvableClasses): 221 | Sys.println("No functions matching " + className + "." + 222 | functionName + "."); 223 | printUnresolvableClasses(unresolvableClasses); 224 | 225 | case ErrorBadCount(count): 226 | Sys.println("Bad count " + count + "."); 227 | 228 | case ErrorCurrentThreadNotStopped(threadNumber): 229 | Sys.println("Current thread " + threadNumber + " not stopped."); 230 | 231 | case ErrorEvaluatingExpression(details): 232 | Sys.println("Failed to evaluate expression: " + details); 233 | 234 | case OK: 235 | // This message is just sent as a way to say that commands that 236 | // don't have any status were received 237 | 238 | case Exited: 239 | Sys.println("Debugged process has exited."); 240 | 241 | case Detached: 242 | Sys.println("Debugged process has detached."); 243 | 244 | case Files(list): 245 | printStringList(list, "\n"); 246 | Sys.println(""); 247 | 248 | case AllClasses(list): 249 | printStringList(list, "\n"); 250 | Sys.println(""); 251 | 252 | case Classes(list): 253 | // The command line controller never issues a request that should 254 | // have a Classes response, instead it asks for AllClasses 255 | throw "Internal error: unexpected Classes"; 256 | 257 | case MemBytes(bytes): 258 | Sys.println(bytes + " bytes used."); 259 | 260 | case Compacted(bytesBefore, bytesAfter): 261 | Sys.println(bytesBefore + " bytes used before compaction."); 262 | Sys.println(bytesAfter + " bytes used after compaction."); 263 | 264 | case Collected(bytesBefore, bytesAfter): 265 | Sys.println(bytesBefore + " bytes used before collection."); 266 | Sys.println(bytesAfter + " bytes used after collection."); 267 | 268 | case ThreadLocation(number, frameNumber, className, functionName, 269 | fileName, lineNumber): 270 | Sys.println("* " + frameNumber + " : " + 271 | className + "." + functionName + "() at " + 272 | fileName + ":" + lineNumber); 273 | 274 | case FileLineBreakpointNumber(number): 275 | Sys.println("Breakpoint " + number + " set and enabled."); 276 | 277 | case ClassFunctionBreakpointNumber(number, unresolvableClasses): 278 | Sys.println("Breakpoint " + number + " set and enabled."); 279 | printUnresolvableClasses(unresolvableClasses); 280 | 281 | case Breakpoints(Terminator): 282 | Sys.println("No breakpoints."); 283 | 284 | case Breakpoints(list): 285 | Sys.println("Number | E/d | M | Description"); 286 | while (true) { 287 | switch (list) { 288 | case Terminator: 289 | break; 290 | case Breakpoint(number, description, enabled, multi, next): 291 | Sys.println(padString(Std.string(number), 9) + 292 | (enabled ? "E " : " d ") + 293 | (multi ? "* " : " ") + description); 294 | list = next; 295 | } 296 | } 297 | 298 | case BreakpointDescription(number, Terminator): 299 | Sys.println("Breakpoint " + number + ":"); 300 | Sys.println(" Breaks nowhere!"); 301 | 302 | case BreakpointDescription(number, list): 303 | Sys.println("Breakpoint " + number + ":"); 304 | while (true) { 305 | switch (list) { 306 | case Terminator: 307 | break; 308 | case FileLine(fileName, lineNumber, next): 309 | Sys.println(" Breaks at " + fileName + ":" + 310 | lineNumber + "."); 311 | list = next; 312 | case ClassFunction(className, functionName, next): 313 | Sys.println(" Breaks at " + className + "." + 314 | functionName + "()."); 315 | list = next; 316 | } 317 | } 318 | 319 | case BreakpointStatuses(Terminator): 320 | Sys.println("No breakpoints affected."); 321 | 322 | case BreakpointStatuses(list): 323 | while (true) { 324 | switch (list) { 325 | case Terminator: 326 | break; 327 | case Nonexistent(number, next): 328 | Sys.println("Breakpoint " + number + " does not exist."); 329 | list = next; 330 | case Disabled(number, next): 331 | Sys.println("Breakpoint " + number + " disabled."); 332 | list = next; 333 | case AlreadyDisabled(number, next): 334 | Sys.println("Breakpoint " + number + 335 | " was already disabled."); 336 | list = next; 337 | case Enabled(number, next): 338 | Sys.println("Breakpoint " + number + " enabled."); 339 | list = next; 340 | case AlreadyEnabled(number, next): 341 | Sys.println("Breakpoint " + number + 342 | " was already enabled."); 343 | list = next; 344 | case Deleted(number, next): 345 | Sys.println("Breakpoint " + number + " deleted."); 346 | list = next; 347 | } 348 | } 349 | 350 | case ThreadsWhere(Terminator): 351 | Sys.println("No threads."); 352 | 353 | case ThreadsWhere(list): 354 | var needNewline : Bool = false; 355 | while (true) { 356 | switch (list) { 357 | case Terminator: 358 | break; 359 | case Where(number, status, frameList, next): 360 | if (needNewline) { 361 | Sys.println(""); 362 | } 363 | else { 364 | needNewline = true; 365 | } 366 | Sys.print("Thread " + number + " ("); 367 | var isRunning : Bool = false; 368 | switch (status) { 369 | case Running: 370 | Sys.println("running)"); 371 | list = next; 372 | isRunning = true; 373 | case StoppedImmediate: 374 | Sys.println("stopped):"); 375 | case StoppedBreakpoint(number): 376 | Sys.println("stopped in breakpoint " + number + "):"); 377 | case StoppedUncaughtException: 378 | Sys.println("uncaught exception):"); 379 | case StoppedCriticalError(description): 380 | Sys.println("critical error: " + description + "):"); 381 | } 382 | var hasStack = false; 383 | while (true) { 384 | switch (frameList) { 385 | case Terminator: 386 | break; 387 | case Frame(isCurrent, number, className, functionName, 388 | fileName, lineNumber, next): 389 | Sys.print((isCurrent ? "* " : " ")); 390 | Sys.print(padStringRight(Std.string(number), 5)); 391 | Sys.print(" : " + className + "." + functionName + 392 | "()"); 393 | Sys.println(" at " + fileName + ":" + lineNumber); 394 | hasStack = true; 395 | frameList = next; 396 | } 397 | } 398 | if (!hasStack && !isRunning) { 399 | Sys.println("No stack."); 400 | } 401 | list = next; 402 | } 403 | } 404 | 405 | case Variables(list): 406 | printStringList(list, "\n"); 407 | Sys.println(""); 408 | 409 | case Value(expression, type, value): 410 | Sys.println(expression + " : " + type + " = " + value); 411 | 412 | case Structured(structuredValue): 413 | throw "Internal error: unexpected Structured"; 414 | 415 | case ThreadCreated(number): 416 | Sys.println("\nThread " + number + " created."); 417 | 418 | case ThreadTerminated(number): 419 | Sys.println("\nThread " + number + " terminated."); 420 | 421 | case ThreadStarted(number): 422 | // Don't print anything 423 | 424 | case ThreadStopped(number, frameNumber, className, functionName, 425 | fileName, lineNumber): 426 | Sys.println("\nThread " + number + " stopped in " + 427 | className + "." + functionName + "() at " + 428 | fileName + ":" + lineNumber + "."); 429 | } 430 | } 431 | 432 | private function exit(regex : EReg) : Null 433 | { 434 | Sys.println("Exiting."); 435 | Sys.exit(0); 436 | return null; 437 | } 438 | 439 | private function detach(regex : EReg) : Null 440 | { 441 | return Detach; 442 | } 443 | 444 | private function help(regex : EReg) 445 | { 446 | if (regex.matched(1).length == 0) { 447 | Sys.println("For help on one of the following commands, use " + 448 | "\"help \"."); 449 | Sys.println("For example, \"help break\":\n"); 450 | for (h in gHelp) { 451 | Sys.println(padString(h.c, 10) + " : " + h.s); 452 | } 453 | } 454 | else { 455 | var cmd = regex.matched(1); 456 | for (h in gHelp) { 457 | if (h.c == cmd) { 458 | Sys.println( h.l + "\n"); 459 | return null; 460 | } 461 | } 462 | 463 | Sys.println("No such command '" + cmd + "'"); 464 | } 465 | return null; 466 | } 467 | 468 | private function source(regex : EReg) : Null 469 | { 470 | var line = regex.matched(1); 471 | if (line.length == 0) { 472 | Sys.println("The source command requires one argument."); 473 | return null; 474 | } 475 | if (gRegexQuotes.match(line)) { 476 | source_file(gRegexQuotes.matched(1)); 477 | } 478 | else if (gRegexNoQuotes.match(line)) { 479 | source_file(gRegexNoQuotes.matched(1)); 480 | } 481 | else { 482 | Sys.println("Failed to parse source line at: " + line + "."); 483 | } 484 | 485 | return null; 486 | } 487 | 488 | private function source_file(path : String) : Null 489 | { 490 | try { 491 | mInputs.push(sys.io.File.read(path)); 492 | Sys.println("Executing debugger commands from " + path + " ..."); 493 | } 494 | catch (e : Dynamic) { 495 | Sys.println("Failed to open " + path + " for sourcing."); 496 | } 497 | 498 | return null; 499 | } 500 | 501 | private function history(regex : EReg) : Null 502 | { 503 | this.historyRange(1, mStoredCommands.length - 1); 504 | return null; 505 | } 506 | 507 | private function history_at(regex : EReg) : Null 508 | { 509 | var number = Std.parseInt(regex.matched(1)); 510 | this.historyRange(number, number); 511 | return null; 512 | } 513 | 514 | private function history_upto(regex : EReg) : Null 515 | { 516 | this.historyRange(1, Std.parseInt(regex.matched(1))); 517 | return null; 518 | } 519 | 520 | private function history_from(regex : EReg) : Null 521 | { 522 | this.historyRange(Std.parseInt(regex.matched(1)), 523 | mStoredCommands.length - 1); 524 | return null; 525 | } 526 | 527 | private function history_from_upto(regex : EReg) : Null 528 | { 529 | this.historyRange(Std.parseInt(regex.matched(1)), 530 | Std.parseInt(regex.matched(2))); 531 | return null; 532 | } 533 | 534 | private function historyRange(first : Int, last : Int) 535 | { 536 | if (first < 1) { 537 | first = 1; 538 | } 539 | 540 | if (last >= mStoredCommands.length) { 541 | last = (mStoredCommands.length - 1); 542 | } 543 | 544 | for (i in first ... (last + 1)) { 545 | Sys.println("(" + i + ") " + mStoredCommands[i]); 546 | } 547 | } 548 | 549 | private function files(regex : EReg) : Null 550 | { 551 | return Files; 552 | } 553 | 554 | private function filespath(regex : EReg) : Null 555 | { 556 | return FilesFullPath; 557 | } 558 | 559 | private function classes(regex : EReg) : Null 560 | { 561 | return AllClasses; 562 | } 563 | 564 | private function mem(regex : EReg) : Null 565 | { 566 | return Mem; 567 | } 568 | 569 | private function compact(regex : EReg) : Null 570 | { 571 | return Compact; 572 | } 573 | 574 | private function collect(regex : EReg) : Null 575 | { 576 | return Collect; 577 | } 578 | 579 | private function set_current_thread(regex : EReg) : Null 580 | { 581 | return SetCurrentThread(Std.parseInt(regex.matched(1))); 582 | } 583 | 584 | private function unsafe(regex : EReg) : Null 585 | { 586 | if (mUnsafeMode) { 587 | Sys.println("Already in unsafe mode."); 588 | } 589 | else { 590 | mUnsafeMode = true; 591 | Sys.println("Now in unsafe mode."); 592 | } 593 | 594 | return null; 595 | } 596 | 597 | private function safe(regex : EReg) : Null 598 | { 599 | if (mUnsafeMode) { 600 | mUnsafeMode = false; 601 | Sys.println("Now in safe mode."); 602 | } 603 | else { 604 | Sys.println("Already in safe mode."); 605 | } 606 | 607 | return null; 608 | } 609 | 610 | private function break_now(regex : EReg) : Null 611 | { 612 | return BreakNow; 613 | } 614 | 615 | private function break_file_line(regex : EReg) : Null 616 | { 617 | return AddFileLineBreakpoint(regex.matched(2), 618 | Std.parseInt(regex.matched(3))); 619 | } 620 | 621 | private function break_class_function(regex : EReg) : Null 622 | { 623 | var full = regex.matched(2); 624 | var lastDot = full.lastIndexOf("."); 625 | return AddClassFunctionBreakpoint(full.substring(0, lastDot), 626 | full.substring(lastDot + 1)); 627 | } 628 | 629 | private function break_class_regexp(regex : EReg) : Null 630 | { 631 | var full = regex.matched(2); 632 | var index = full.indexOf("/"); 633 | var className = full.substring(0, index - 1); 634 | 635 | var value = full.substring(index); 636 | 637 | // Value starts with / ... look for end / 638 | index = findSlash(value, 1); 639 | 640 | if (index == -1) { 641 | Sys.println("Invalid command."); 642 | return null; 643 | } 644 | 645 | return AddClassFunctionBreakpoint 646 | (className, value.substr(0, index + 1)); 647 | } 648 | 649 | private function break_possible_regexps(regex : EReg) : Null 650 | { 651 | var value = regex.matched(2); 652 | 653 | // Value starts with / ... look for end / 654 | var index = findSlash(value, 1); 655 | 656 | if (index == -1) { 657 | Sys.println("Invalid command."); 658 | return null; 659 | } 660 | 661 | var className = value.substr(0, index + 1); 662 | 663 | value = value.substr(index + 1); 664 | 665 | var regex = ~/[\s]*\.[\s]*([a-zA-Z_][a-zA-Z0-9_]*)[\s]*$/; 666 | if (regex.match(value)) { 667 | return AddClassFunctionBreakpoint(className, regex.matched(1)); 668 | } 669 | 670 | regex = ~/[\s]*\.[\s]*(\/.*)$/; 671 | if (regex.match(value)) { 672 | value = regex.matched(1); 673 | 674 | // Value starts with / ... look for end / 675 | var index = findSlash(value, 1); 676 | 677 | if (index == -1) { 678 | Sys.println("Invalid command."); 679 | return null; 680 | } 681 | 682 | return AddClassFunctionBreakpoint 683 | (className, value.substr(0, index + 1)); 684 | } 685 | else { 686 | Sys.println("Invalid command."); 687 | return null; 688 | } 689 | } 690 | 691 | private function list_all_breakpoints(regex : EReg) : Null 692 | { 693 | return ListBreakpoints(true, true); 694 | } 695 | 696 | private function list_enabled_breakpoints(regex : EReg) : Null 697 | { 698 | return ListBreakpoints(true, false); 699 | } 700 | 701 | private function list_disabled_breakpoints(regex : EReg) : Null 702 | { 703 | return ListBreakpoints(false, true); 704 | } 705 | 706 | private function describe_breakpoint(regex : EReg) : Null 707 | { 708 | return DescribeBreakpoint(Std.parseInt(regex.matched(2))); 709 | } 710 | 711 | private function disable_all_breakpoints(regex : EReg) : Null 712 | { 713 | return DisableAllBreakpoints; 714 | } 715 | 716 | private function disable_breakpoint(regex : EReg) : Null 717 | { 718 | var number = Std.parseInt(regex.matched(2)); 719 | return DisableBreakpointRange(number, number); 720 | } 721 | 722 | private function disable_ranged_breakpoints(regex : EReg) : Null 723 | { 724 | return DisableBreakpointRange(Std.parseInt(regex.matched(2)), 725 | Std.parseInt(regex.matched(3))); 726 | } 727 | 728 | private function enable_all_breakpoints(regex : EReg) : Null 729 | { 730 | return EnableAllBreakpoints; 731 | } 732 | 733 | private function enable_breakpoint(regex : EReg) : Null 734 | { 735 | var number = Std.parseInt(regex.matched(2)); 736 | return EnableBreakpointRange(number, number); 737 | } 738 | 739 | private function enable_ranged_breakpoints(regex : EReg) : Null 740 | { 741 | return EnableBreakpointRange(Std.parseInt(regex.matched(2)), 742 | Std.parseInt(regex.matched(3))); 743 | } 744 | 745 | private function delete_all_breakpoints(regex : EReg) : Null 746 | { 747 | return DeleteAllBreakpoints; 748 | } 749 | 750 | private function delete_breakpoint(regex : EReg) : Null 751 | { 752 | var number = Std.parseInt(regex.matched(2)); 753 | return DeleteBreakpointRange(number, number); 754 | } 755 | 756 | private function delete_ranged_breakpoints(regex : EReg) : Null 757 | { 758 | return DeleteBreakpointRange(Std.parseInt(regex.matched(2)), 759 | Std.parseInt(regex.matched(3))); 760 | } 761 | 762 | private function clear_file_line(regex : EReg) : Null 763 | { 764 | return DeleteFileLineBreakpoint(regex.matched(1), 765 | Std.parseInt(regex.matched(2))); 766 | } 767 | 768 | private function continue_current(regex : EReg) : Null 769 | { 770 | if (regex.matched(2).length > 0) { 771 | return Continue(Std.parseInt(regex.matched(2))); 772 | } 773 | else { 774 | return Continue(1); 775 | } 776 | } 777 | 778 | private function step_execution(regex : EReg) : Null 779 | { 780 | return Step((regex.matched(2).length > 0) ? 781 | Std.parseInt(regex.matched(2)) : 1); 782 | } 783 | 784 | private function next_execution(regex : EReg) : Null 785 | { 786 | return Next((regex.matched(2).length > 0) ? 787 | Std.parseInt(regex.matched(2)) : 1); 788 | } 789 | 790 | private function finish_execution(regex : EReg) : Null 791 | { 792 | return Finish((regex.matched(2).length > 0) ? 793 | Std.parseInt(regex.matched(2)) : 1); 794 | } 795 | 796 | private function where(regex : EReg) : Null 797 | { 798 | return WhereCurrentThread(mUnsafeMode); 799 | } 800 | 801 | private function where_all(regex : EReg) : Null 802 | { 803 | return WhereAllThreads; 804 | } 805 | 806 | private function up_one(regex : EReg) : Null 807 | { 808 | return Up(1); 809 | } 810 | 811 | private function up_count(regex : EReg) : Null 812 | { 813 | return Up((regex.matched(1).length > 0) ? 814 | Std.parseInt(regex.matched(1)) : 1); 815 | } 816 | 817 | private function down_one(regex : EReg) : Null 818 | { 819 | return Down(1); 820 | } 821 | 822 | private function down_count(regex : EReg) : Null 823 | { 824 | return Down((regex.matched(1).length > 0) ? 825 | Std.parseInt(regex.matched(1)) : 1); 826 | } 827 | 828 | private function frame(regex : EReg) : Null 829 | { 830 | return SetFrame(Std.parseInt(regex.matched(1))); 831 | } 832 | 833 | private function variables(regex : EReg) : Null 834 | { 835 | return Variables(mUnsafeMode); 836 | } 837 | 838 | private function print_expression(regex : EReg) : Null 839 | { 840 | return PrintExpression(mUnsafeMode, regex.matched(2)); 841 | } 842 | 843 | private function set_expression(regex : EReg) : Null 844 | { 845 | var expr = regex.matched(2); 846 | 847 | // Find the = 848 | var index = levelNextIndexOf(expr, 0, "="); 849 | if (index == -1){ 850 | Sys.println("Expected = in set command.\n"); 851 | return null; 852 | } 853 | 854 | return SetExpression(mUnsafeMode, expr.substr(0, index), 855 | expr.substr(index + 1)); 856 | } 857 | 858 | // Utility functions and helpers ----------------------------------------- 859 | 860 | private static function printStringList(list : StringArray, sep : String) 861 | { 862 | var need_sep = false; 863 | 864 | for(string in list) { 865 | if (need_sep) { 866 | Sys.print(sep); 867 | } 868 | else { 869 | need_sep = true; 870 | } 871 | Sys.print(string); 872 | } 873 | } 874 | 875 | private static function printUnresolvableClasses( 876 | unresolvableClasses : StringArray) 877 | { 878 | Sys.print("Unresolvable classes: "); 879 | printStringList(unresolvableClasses, ", "); 880 | Sys.println("."); 881 | } 882 | 883 | private static function findEndQuote(str : String, index : Int) : Int 884 | { 885 | while (index < str.length) { 886 | var quoteIndex = str.indexOf("\"", index); 887 | // Count backslashes before quotes 888 | var slashCount = 0; 889 | var si : Int = quoteIndex - 1; 890 | while ((si >= 0) && (str.charAt(si) == "\\")) { 891 | slashCount += 1; 892 | si -= 1; 893 | } 894 | // If there are an even number of slashes, then the quote 895 | // is not escaped 896 | if ((slashCount % 2) == 0) { 897 | return quoteIndex; 898 | } 899 | // Else the quote is escaped 900 | else { 901 | index = quoteIndex + 1; 902 | } 903 | } 904 | return -1; 905 | } 906 | 907 | private static function levelNextIndexOf(str : String, index : Int, 908 | find : String) : Int 909 | { 910 | var bracketLevel = 0, parenLevel = 0; 911 | 912 | while (index < str.length) { 913 | var char = str.charAt(index); 914 | if ((char == find) && (bracketLevel == 0) && (parenLevel == 0)) { 915 | return index; 916 | } 917 | else if (char == "[") { 918 | bracketLevel += 1; 919 | } 920 | else if (char == "]") { 921 | bracketLevel -= 1; 922 | } 923 | else if (char == "(") { 924 | parenLevel += 1; 925 | } 926 | else if (char == ")") { 927 | parenLevel -= 1; 928 | } 929 | else if (char == "\"") { 930 | var endQuote = findEndQuote(str, index + 1); 931 | if (endQuote == -1) { 932 | throw "Mismatched quotes"; 933 | } 934 | index = endQuote; 935 | } 936 | index += 1; 937 | } 938 | 939 | return -1; 940 | } 941 | 942 | private static function findSlash(str : String, index : Int) : Int 943 | { 944 | while (index < str.length) { 945 | var char = str.charAt(index); 946 | if ((char == "/") && 947 | ((index == 0) || (str.charAt(index - 1) != "\\"))) { 948 | return index; 949 | } 950 | index += 1; 951 | } 952 | return -1; 953 | } 954 | 955 | private static function padString(str : String, width : Int) 956 | { 957 | var spacesNeeded = width - str.length; 958 | 959 | if (spacesNeeded <= 0) { 960 | return str; 961 | } 962 | 963 | if (gEmptySpace[spacesNeeded] == null) { 964 | var str = ""; 965 | for (i in 0...spacesNeeded) { 966 | str += " "; 967 | } 968 | gEmptySpace[spacesNeeded] = str; 969 | } 970 | 971 | return (str + gEmptySpace[spacesNeeded]); 972 | } 973 | 974 | private static function padStringRight(str : String, width : Int) 975 | { 976 | var spacesNeeded = width - str.length; 977 | 978 | if (spacesNeeded <= 0) { 979 | return str; 980 | } 981 | 982 | if (gEmptySpace[spacesNeeded] == null) { 983 | var str = ""; 984 | for (i in 0...spacesNeeded) { 985 | str += " "; 986 | } 987 | gEmptySpace[spacesNeeded] = str; 988 | } 989 | 990 | return (gEmptySpace[spacesNeeded] + str); 991 | } 992 | 993 | private function setupRegexHandlers() 994 | { 995 | mRegexHandlers = [ 996 | { r: ~/^(quit|exit)[\s]*$/, h: exit }, 997 | { r: ~/^detach[\s]*$/, h: detach }, 998 | { r: ~/^help()[\s]*$/, h: help }, 999 | { r: ~/^help[\s]+([^\s]*)$/, h: help }, 1000 | { r: ~/^source[\s]+(.*)$/, h: source }, 1001 | { r: ~/^history[\s]*$/, h: history }, 1002 | { r: ~/^history[\s]+([0-9]+)$/, h: history_at }, 1003 | { r: ~/^history[\s]+-[\s]*([0-9]+)$/, h: history_upto }, 1004 | { r: ~/^history[\s]+([0-9]+)[\s]*-$/, h: history_from }, 1005 | { r: ~/^history[\s]+([0-9]+)[\s]*-[\s]*([0-9]+)$/, h: history_from_upto }, 1006 | { r: ~/^filespath[\s]*$/, h: filespath }, 1007 | { r: ~/^files[\s]*$/, h: files }, 1008 | { r: ~/^classes[\s]*$/, h: classes }, 1009 | { r: ~/^mem[\s]*$/, h: mem }, 1010 | { r: ~/^compact[\s]*$/, h: compact }, 1011 | { r: ~/^collect[\s]*$/, h: collect }, 1012 | { r: ~/^thread[\s]+([0-9]+)[\s]*$/, h: set_current_thread }, 1013 | { r: ~/^unsafe[\s]*$/, h: unsafe }, 1014 | { r: ~/^safe[\s]*$/, h: safe }, 1015 | { r: ~/^(b|break)[\s]*$/, h : break_now }, 1016 | { r: ~/^(b|break)[\s]+([^:]+):[\s]*([0-9]+)[\s]*$/, h : break_file_line }, 1017 | { r: ~/^(b|break)[\s]+(([a-zA-Z0-9_]+\.)+[a-zA-Z0-9_]+)[\s]*$/, h : break_class_function }, 1018 | { r: ~/^(b|break)[\s]+(([a-zA-Z0-9_]+\.)+\/.*)$/, h : break_class_regexp }, 1019 | { r: ~/^(b|break)[\s]+(\/.*)$/, h : break_possible_regexps }, 1020 | { r: ~/^lb[\s]*$/, h : list_all_breakpoints }, 1021 | { r: ~/^(l|list)[\s]+(all[\s]+)?(b|breakpoints)$/, h : list_all_breakpoints }, 1022 | { r: ~/^(l|list)[\s]+(en|enabled)[\s]+(b|breakpoints)$/, 1023 | h : list_enabled_breakpoints }, 1024 | { r: ~/^(l|list)[\s]+(dis|disabled)[\s]+(b|breakpoints)$/, 1025 | h : list_disabled_breakpoints }, 1026 | { r: ~/^(desc|describe)[\s]+([0-9]+)[\s]*$/, h: describe_breakpoint }, 1027 | { r: ~/^(dis|disable)[\s]+all[\s]*$/, h: disable_all_breakpoints }, 1028 | { r: ~/^(dis|disable)[\s]+([0-9]+)[\s]*$/, h: disable_breakpoint }, 1029 | { r: ~/^(dis|disable)[\s]+([0-9]+)[\s]*-[\s]*([0-9]+)[\s]*$/, 1030 | h: disable_ranged_breakpoints }, 1031 | { r: ~/^(en|enable)[\s]+all[\s]*$/, h: enable_all_breakpoints }, 1032 | { r: ~/^(en|enable)[\s]+([0-9]+)[\s]*$/, h: enable_breakpoint }, 1033 | { r: ~/^(en|enable)[\s]+([0-9]+)[\s]*-[\s]*([0-9]+)[\s]*$/, 1034 | h: enable_ranged_breakpoints }, 1035 | { r: ~/^(d|delete)[\s]+all[\s]*$/, h: delete_all_breakpoints }, 1036 | { r: ~/^(d|delete)[\s]+([0-9]+)[\s]*$/, h: delete_breakpoint }, 1037 | { r: ~/^(d|delete)[\s]+([0-9]+)[\s]*-[\s]*([0-9]+)[\s]*$/, 1038 | h: delete_ranged_breakpoints }, 1039 | { r: ~/^clear[\s]+([^:]+):[\s]*([0-9]+)[\s]*$/, h : clear_file_line }, 1040 | { r: ~/^(continue|cont|c)()[\s]*$/, h: continue_current }, 1041 | { r: ~/^(continue|cont|c)([\s]+[0-9]+)[\s]*$/,h: continue_current }, 1042 | { r: ~/^(step|stepi|s)()[\s]*$/, h: step_execution }, 1043 | { r: ~/^(step|stepi|s)([\s]+[0-9]+)[\s]*$/, h: step_execution }, 1044 | { r: ~/^(next|nexti|n)()[\s]*$/, h: next_execution }, 1045 | { r: ~/^(next|nexti|n)([\s]+[0-9]+)[\s]*$/, h: next_execution }, 1046 | { r: ~/^(finish|f)()[\s]*$/, h: finish_execution }, 1047 | { r: ~/^(finish|f)([\s]+[0-9]+)[\s]*$/, h: finish_execution }, 1048 | { r: ~/^(where|w)[\s]*$/, h: where }, 1049 | { r: ~/^(where|w)[\s]+all[\s]*$/, h: where_all }, 1050 | { r: ~/^up[\s]*$/, h: up_one }, 1051 | { r: ~/^up[\s]+([0-9]+)[\s]*$/, h: up_count }, 1052 | { r: ~/^down[\s]*$/, h: down_one }, 1053 | { r: ~/^down[\s]+([0-9]+)[\s]*$/, h: down_count }, 1054 | { r: ~/^frame[\s]+([0-9]+)[\s]*$/, h: frame }, 1055 | { r: ~/^(vars|variables)[\s]*$/, h : variables }, 1056 | { r: ~/^(p|print)[\s]+(.*)$/, h: print_expression }, 1057 | { r: ~/^(s|set)[\s]+(.*)$/, h: set_expression } 1058 | ]; 1059 | } 1060 | 1061 | private var mUnsafeMode : Bool; 1062 | private var mInputs : Array; 1063 | private var mStoredCommands : Array; 1064 | private var mLastCommand : Command; 1065 | private var mRegexHandlers : Array; 1066 | private static var gRegexQuotes = ~/^[\s]*"([^"]+)"[\s]*$/; 1067 | private static var gRegexNoQuotes = ~/^[\s]*([^\s"]+)[\s]*$/; 1068 | private static var gEmptySpace : Array = [ "" ]; 1069 | private static var gHelp : Array = 1070 | [ 1071 | { c : "input", s : "Inputting and repeating commands", 1072 | l : "Every comand prompt is preceded by a number. When a command is\n" + 1073 | "entered, it may be repeated later with the command \"!N\", where N is\n" + 1074 | "that number. For example:\n\n" + 1075 | " 4> mem\n\n" + 1076 | " 844323 bytes used.\n\n" + 1077 | " 5> !4\n\n" + 1078 | " 844323 bytes used.\n\n" + 1079 | "The history of commands that can be repeated is printed by the\n" + 1080 | "'history' command.\n\n" + 1081 | "Some commands are repeated if an empty line is read immediately after\n" + 1082 | "the command. The commands which repeat are: continue, step, next,\n" + 1083 | "finish, up, and down. Commands are only repeated when commands are\n" + 1084 | "being read from the user (not when sourcing files).\n\n" + 1085 | "If at any time an asynchronous threading message interrupts a command\n" + 1086 | "being typed in, ending the command with '\\' will re-print the\n" + 1087 | "current command prompt and command in progress. For example:\n\n" + 1088 | " 8> b Foo.h\n" + 1089 | " Thread 4 terminated.\n" + 1090 | " \\\n" + 1091 | " 8> b Foo.hx:10\n\n" + 1092 | "Here, the user was in the middie of typing a 'b' command when a\n" + 1093 | "thread terminated. The user entered a bare '\\' to cause the command\n" + 1094 | "in progress to be re-printed so that the user could see the command\n" + 1095 | "being typed in and then complete it." }, 1096 | 1097 | { c : "quit", s : "Quits the debugger", 1098 | l : "Syntax: quit/exit\n\n" + 1099 | "The quit (or exit) command exits the debugger and the debugged " + 1100 | "process." }, 1101 | 1102 | { c : "detach", s : "Detaches the debugger", 1103 | l : "Syntax: detach\n\n" + 1104 | "The detach command detaches the debugger from the debugged process.\n" + 1105 | "The debugger exits but the debugged process continues to execute." }, 1106 | 1107 | { c : "help", s : "Displays command help", 1108 | l : "Syntax: help [command]\n\n" + 1109 | "With no arguments, the help command prints out a list of all\n" + 1110 | "commands. With an argument, the help command prints out detailed\n" + 1111 | "help about that command." }, 1112 | 1113 | { c : "source", s : "Runs commands from a file", 1114 | l : "Syntax: source \n\n" + 1115 | "The source command reads in and executes commands from the file\n" + 1116 | " as if they had been typed in at the command prompt.\n" + 1117 | "Comment lines beginning with '#' in the input file are ignored.\n" + 1118 | "After execution of all commands from the file, input resumes at the\n" + 1119 | "normal interactive prompt." }, 1120 | 1121 | { c : "history", s : "Displays command history", 1122 | l : "Syntax: history/h /-/-/-\n\n" + 1123 | "The history (or h) command displays the list of commands previously\n" + 1124 | "entered either at the command prompt or when sourcing a file.\n" + 1125 | "Several variations are supported for specifying the extent of\n" + 1126 | "command history to display:\n\n" + 1127 | " history : Displays all history.\n" + 1128 | " history : Displays the history of command N.\n" + 1129 | " history - : Displays history of all commands starting with " + 1130 | "N.\n" + 1131 | " history - : Displays history in the range N - M, inclusive.\n" + 1132 | " history - : Displays history of in the range 1 - M, " + 1133 | "inclusive." }, 1134 | 1135 | { c : "files", s : "Lists debuggable files", 1136 | l : "Syntax: files\n\n" + 1137 | "The files command lists all files in which file:line breakpoints may\n" + 1138 | "be set." }, 1139 | 1140 | { c : "filespath", s : "Lists full paths of the debuggable files", 1141 | l : "Syntax: files\n\n" + 1142 | "The order of theses paths matches the order of the 'files' command.\n" + 1143 | "Use this to work out which file to edit." }, 1144 | 1145 | 1146 | { c : "classes", s : "Lists debuggable classes", 1147 | l : "Syntax: classes\n\n" + 1148 | "The classes command lists all classes in which class:function\n" + 1149 | "breakpoints may be set. This is all classes known to the compiler\n" + 1150 | "at the time the debugged program was compiled." }, 1151 | 1152 | { c : "mem", s : "Displays memory usage", 1153 | l : "Syntax: mem\n\n" + 1154 | "The mem command displays the amount of bytes currently used by the\n" + 1155 | "debugged process." }, 1156 | 1157 | { c : "compact", s : "Compacts the heap", 1158 | l : "Syntax: compact\n\n" + 1159 | "The compact command compacts the program's heap as much as possible\n" + 1160 | "and prints out the number of bytes used by the program before and\n" + 1161 | "after compaction." }, 1162 | 1163 | { c : "collect", s : "Runs the garbage collector", 1164 | l : "Syntax: compact\n\n" + 1165 | "The compact command compacts the program's heap as much as possible\n" + 1166 | "and prints out the number of bytes used by the program before and\n" + 1167 | "after compaction." }, 1168 | 1169 | { c : "thread", s : "Sets the current thread", 1170 | l : "Syntax: thread \n\n" + 1171 | "The thread command switches the debugger to thread , making\n" + 1172 | "this thread the current thread. The current thread is the thread\n" + 1173 | "which is targeted by the following commands:\n\n" + 1174 | " continue, step, next, finish, where, up, down, frame, print, set" }, 1175 | 1176 | { c : "unsafe", s : "Puts the debugger into unsafe mode", 1177 | l : "Syntax: unsafe\n\n" + 1178 | "The unsafe command puts the debugger into unsafe mode. In unsafe\n" + 1179 | "mode, the debugger will print stack traces and allow the printing\n" + 1180 | "and setting of stack variables for threads which are not stopped.\n" + 1181 | "this is extremely unsafe and could lead to program crashes or other\n" + 1182 | "undefined behavior as threads which are actively running are\n" + 1183 | "manipulated in unsafe mode. However, if a thread is hung and cannot\n" + 1184 | "be induced to break at a breakpoint, unsafe mode can allow the\n" + 1185 | "inspection of the thread's state to determine what the cause of the\n" + 1186 | "hang could be. To leave unsafe mode, use the 'safe' command." }, 1187 | 1188 | { c : "safe", s : "Puts the debugger into safe mode", 1189 | l : "Syntax: safe\n\n" + 1190 | "The safe command puts the debugger back into safe mode from unsafe\n" + 1191 | "mode. In safe mode, the call stack of only stopped threads can be\n" + 1192 | "examined and call stack variables of only stopped threads can be\n" + 1193 | "printed or modified. To leave safe mode, use the 'unsafe' command." }, 1194 | 1195 | { c : "break", s : "Sets a breakpoint", 1196 | l : "Syntax: break :/.\n\n" + 1197 | "The break (or b) command sets a breakpoint. Breakpoints take effect\n" + 1198 | "immediately and all threads will break on all breakpoints whenever\n" + 1199 | "the breakpoint is hit.\n" + 1200 | "Breakpoints may be specified either by file and line number, or by\n" + 1201 | "class name and function name.\n" + 1202 | "In the class.function case, the class name, or function name or\n" + 1203 | "both, may optionally be a regular expression which will cause the\n" + 1204 | "breakpoint to break on all matching functions.\n" + 1205 | "The set of files in which breakpoints may be set can be printed using\n" + 1206 | "the 'files' command. The set of classes in which breakpoints may\n" + 1207 | "be set can be printed using the 'classes' command.\n\nExamples:\n\n" + 1208 | " b Foo.hx:10\n" + 1209 | " Sets a breakpoint in file Foo.hx line 10.\n\n" + 1210 | " b SomeClass.SomeFunction\n " + 1211 | " Sets a breakpoint on entry to the function " + 1212 | "SomeClass.SomeFunction.\n\n" + 1213 | " b SomeClass./get.*/\n" + 1214 | " Sets a breakpoint on entry to all functions whose names begin\n" + 1215 | " with 'get' in the class SomeClass.\n\n" + 1216 | " b /.*/.new\n" + 1217 | " Sets a breakpoint on entry to the constructor of every class.\n\n" + 1218 | " b /.*/./.*/\n" + 1219 | " Sets a breakpoint on entry to every function of every class." }, 1220 | 1221 | { c : "list", s : "Lists breakpoints", 1222 | l : "Syntax: list/l [all/enabled/en/disabled/dis] breakpoints/b\n\n" + 1223 | "The list (or l) command lists all breakpoints that match the given\n" + 1224 | "criteria. The criteria default to 'all' if not specified, and may be\n" + 1225 | "specified as one of:\n\n" + 1226 | " all : to list all breakpoints\n" + 1227 | " enabled (or en) : to list only enabled breakpoints\n" + 1228 | " disabled (or dis) : to list only disabled breakpoints\n\n" + 1229 | "Note that the syntax of the command requires the word 'breakpoints'\n" + 1230 | "(or b) at the end. Examples:\n\n" + 1231 | " list all breakpoints\n" + 1232 | " Lists all breakpoints\n\n" + 1233 | " l en breakpoints\n" + 1234 | " Lists only enabled breakpoints\n\n" + 1235 | " l dis b\n" + 1236 | " Lists only disabled breakpoints\n\n" + 1237 | "The breakpoints are listed with the following columns:\n\n" + 1238 | " Number : The breakpoint number.\n" + 1239 | " E/d : Enabled or disabled. A value of E means that the\n" + 1240 | " breakpoint is enabled, d means that the breakpoint " + 1241 | "is\n" + 1242 | " disabled.\n" + 1243 | " M : Indicates whether or not the breakpoint breaks in\n" + 1244 | " multiple code locations, which can be true for\n" + 1245 | " regex-specified breakpoints. The 'describe' command\n" + 1246 | " is useful for listing the multiple code locations\n" + 1247 | " of a regex-specified Multi breakpoint.\n" + 1248 | " Description : Describes the breakpoint." }, 1249 | 1250 | { c : "describe", s : "Describes a breakpoint", 1251 | l : "Syntax: describe/desc \n\n" + 1252 | "The describe (or desc) command lists the code locations at which\n" + 1253 | "a breakpoint will break. This is especially useful for breakpoints\n" + 1254 | "which were specified using regexps that matched more than one class\n" + 1255 | "and/or function." }, 1256 | 1257 | { c : "disable", s : "Disables breakpoints", 1258 | l : "Syntax: disable/dis all//-\n\n" + 1259 | "The disable (or dis) command disables a set of breakpoints.\n" + 1260 | "Several variations are supported for specifying the range of\n" + 1261 | "breakpoints to disable:\n\n" + 1262 | " disable all : Disables all breakpoints.\n" + 1263 | " disable : Disables breakpoint N.\n" + 1264 | " disable - : Disables breakpoints in the range N - M, " + 1265 | "inclusive.\n" }, 1266 | 1267 | { c : "enable", s : "Enables breakpoints", 1268 | l : "Syntax: enable/en all//-\n\n" + 1269 | "The enable (or en) command enables a set of breakpoints.\n" + 1270 | "Several variations are supported for specifying the range of\n" + 1271 | "breakpoints to enable:\n\n" + 1272 | " enable all : Enables all breakpoints.\n" + 1273 | " enable : Enables breakpoint N.\n" + 1274 | " enable - : Enables breakpoints in the range N - M, " + 1275 | "inclusive.\n" }, 1276 | 1277 | { c : "delete", s : "Deletes breakpoints", 1278 | l : "Syntax: delete/d all//-\n\n" + 1279 | "The delete (or d) command deletes a set of breakpoints.\n" + 1280 | "Several variations are supported for specifying the range of\n" + 1281 | "breakpoints to delete:\n\n" + 1282 | " delete all : Deletes all breakpoints.\n" + 1283 | " delete : Deletes breakpoint N.\n" + 1284 | " delete - : Deletes breakpoints in the range N - M, " + 1285 | "inclusive.\n" }, 1286 | 1287 | { c : "clear", s : "Deletes a breakpoint", 1288 | l : "Syntax: clear :\n\n" + 1289 | "The clear command deletes a single file:line breakpoint." }, 1290 | 1291 | { c : "continue", s : "Continues thread execution", 1292 | l : "Syntax: continue/c \n\n" + 1293 | "The continue (or c) command continues threads until the\n" + 1294 | "next breakpoint occurs. An optional parameter N gives the number of\n" + 1295 | "breakpoints past which to continue just for the current thread (all\n" + 1296 | "other threads continue until the next breapoint). If N is not\n" + 1297 | "specified, it defaults to 1." }, 1298 | 1299 | { c : "step", s : "Single steps a thread", 1300 | l : "Syntax: step/s [N]\n\n" + 1301 | "The step (or s) command steps the current thread. This causes the\n" + 1302 | "current thread to execute the next line of Haxe code and to stop\n" + 1303 | "immediately thereafter. This could include entering a function and\n" + 1304 | "executing the first line of code in that function. The optional\n" + 1305 | "parameter N specifies how many lines to step. If not provided, the\n" + 1306 | " default is 1." }, 1307 | 1308 | { c : "next", s : "Single steps a thread in current frame", 1309 | l : "Syntax: next/n [N]\n\n" + 1310 | "The next (or n) command steps the current thread over a function\n" + 1311 | "call. If the next line of Haxe code to execute is a function call,\n" + 1312 | "the entire function is executed and the thread stops before\n" + 1313 | "executing the line of code after the function call. If the next line\n" + 1314 | "of Haxe code is not a function, the next command behaves exactly like\n" + 1315 | "the step command. The optional parameter N specifies how many\n" + 1316 | "function calls or Haxe lines of code to step. If not provided, the\n" + 1317 | " default is 1." }, 1318 | 1319 | { c : "finish", s : "Continues until return from frame", 1320 | l : "Syntax: finish/f [N]\n\n" + 1321 | "The finish (or f) command causes the current thread to finish\n" + 1322 | "execution of the current function and stop before executing the next\n" + 1323 | "line of code in the calling function. The optional parameter N\n" + 1324 | "specifies how many function calls to finish. If not provided, the\n" + 1325 | "default is 1." }, 1326 | 1327 | { c : "where", s : "Displays thread call stack", 1328 | l : "Syntax: where/w [all]\n\n" + 1329 | "The where (or w) command lists the call stack of a thread or threads,\n" + 1330 | "as well as giving execution status of that thread or those threads.\n" + 1331 | "The call stack is listed with the lowest stack frame (i.e. the one\n" + 1332 | "currently being executed) first and the highest stack frame last,\n" + 1333 | "and these frames are numbered in reverse order so that the numbers\n" + 1334 | "do not change as stack frames are added. The current stack frame\n" + 1335 | "being examined is demarcated with an asterisk. Two variations are\n" + 1336 | "supported for specifying the threads to show call stack and status\n" + 1337 | "of:\n\n" + 1338 | " where : Shows stack frame and status for the current thread.\n" + 1339 | " where all : Shows stack frame and status for all threads." }, 1340 | 1341 | { c : "up", s : "Moves up the call stack", 1342 | l : "Syntax: up [N]\n\n" + 1343 | "The up command moves the current stack frame being examined up a\n" + 1344 | "number of frames. The optional parameter N specifies how many\n" + 1345 | "frames to move up. The default if N is not provided is 1. Note " + 1346 | "that\n" + 1347 | "moving up the call stack means moving to lower call frame numbers." }, 1348 | 1349 | { c : "down", s : "Moves down the call stack", 1350 | l : "Syntax: down [N]\n\n" + 1351 | "The down command moves the current stack frame being examined down a\n" + 1352 | "number of frames. The optional parameter N specifies how many\n" + 1353 | "frames to move down. The default if N is not provided is 1. Note " + 1354 | "that\n" + 1355 | "moving down the call stack means moving to higher call frame numbers." }, 1356 | 1357 | { c : "frame", s : "Moves to a specific call stack frame", 1358 | l : "Syntax: frame \n\n" + 1359 | "The frame command moves the current stack frame being examined to a\n" + 1360 | "specific frame. The parameter N specifies which frame to move to." }, 1361 | 1362 | { c : "variables", s : "Prints available stack variables", 1363 | l : "Syntax: variables/vars\n\n" + 1364 | "The variables (or vars) command lists all stack variables present\n" + 1365 | "in the current stack frame." }, 1366 | 1367 | { c : "print", s : "Prints values from debugged process", 1368 | l : "Syntax: print/p \n\n" + 1369 | "The print (or p) command evaluates and prints the results of Haxe " + 1370 | "expressions.\n" + 1371 | "This is typically used for examining variables on the stack or in\n" + 1372 | "global scope, but can be used for the side effect of executing\n" + 1373 | "Haxe function calls if desired. The expressions that can be\n" + 1374 | "evaluated and the results printed include all syntax necessary to\n" + 1375 | "identify variable values, but does not include syntax for executing\n" + 1376 | "arbitrary Haxe code. For example, math operators are not supported.\n" + 1377 | "Examples:\n\n" + 1378 | " print foo\n" + 1379 | " Prints the value of the variable 'foo' in the current scope.\n" + 1380 | " If foo is a variable on the stack, it is printed; otherwise,\n" + 1381 | " if foo is a member of the 'this' variable, then that is " + 1382 | "printed.\n\n" + 1383 | " print someVariable.mArray[4]\n" + 1384 | " Prints the value of the 5th element of the 'mArray' array\n" + 1385 | " of the someVariable value.\n\n" + 1386 | " print someVariable.doSomething(7)\n" + 1387 | " Prints the result of calling the 'doSomething' function of\n" + 1388 | " 'someVariable' instance, passing in 7 as the argument.\n\n" + 1389 | "Note that class member variables and array member variables of\n" + 1390 | "classes that are printed are ellipsed to prevent the output from\n" + 1391 | "being too large. To see the contents of these values, print them\n" + 1392 | "via a print command targeting them." }, 1393 | 1394 | { c : "set", s : "Sets values in debugged process", 1395 | l : "Syntax: set/s = \n\n" + 1396 | "The set (or s) command evaluates a left hand side and a right hand\n" + 1397 | "side Haxe expression and sets the value referenced by the left hand\n" + 1398 | "side to the value identified by the right hand side. The allowed\n" + 1399 | "expression syntax is identical to that of the print command.\n\n" + 1400 | "There is a special variable name '$', which can be used to store\n" + 1401 | "arbitrary values in the debugger that can be printed later or used\n" + 1402 | "in expressions. An example of its use:\n\n" + 1403 | " 4> set $.foo = 1\n\n" + 1404 | " $.foo : Int = 1\n\n" + 1405 | " 5> set $.bar = \"Hello, world\"\n\n" + 1406 | " $.bar : String = \"Hello, world\"\n\n" + 1407 | " 6> set $.baz = [ 1, 2, 3, 4 ]\n\n" + 1408 | " $.baz : Array[4] = [ 1, 2, 3, 4 ]\n\n" + 1409 | " 7> p $.bar\n\n" + 1410 | " $.bar : String = \"Hello, world\"\n\n" + 1411 | " 8> p $\n\n" + 1412 | " $ : Debugger variables = \n\n" + 1413 | " $.bar : String = \"Hello, world\"\n" + 1414 | " $.baz : Array[4] = [ 1, 2, 3, 4 ]\n" + 1415 | " $.foo : Int = 1\n\n" + 1416 | " 9> set someValue.arr = $.baz\n\n" + 1417 | " someValue.arr : Array[4] = [ 1, 2, 3, 4 ]" } 1418 | ]; 1419 | } 1420 | 1421 | 1422 | private typedef RegexHandler = 1423 | { 1424 | var r : EReg; 1425 | var h : EReg -> Null; 1426 | } 1427 | 1428 | 1429 | private typedef Help = 1430 | { 1431 | var c : String; // command 1432 | var s : String; // short help 1433 | var l : String; // long help 1434 | } 1435 | -------------------------------------------------------------------------------- /debugger/DebuggerThread.hx: -------------------------------------------------------------------------------- 1 | /** ************************************************************************** 2 | * DebuggerThread.hx 3 | * 4 | * Copyright 2013 TiVo Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | ************************************************************************** **/ 18 | 19 | package debugger; 20 | 21 | import cpp.vm.Debugger; 22 | import debugger.IController; 23 | import haxe.CallStack; 24 | 25 | #if cpp 26 | import cpp.vm.Deque; 27 | import cpp.vm.Mutex; 28 | import cpp.vm.Thread; 29 | #elseif neko 30 | import neko.vm.Deque; 31 | import neko.vm.Mutex; 32 | import neko.vm.Thread; 33 | #else 34 | #error "DebuggerThread supported only for cpp and neko targets" 35 | #end 36 | 37 | 38 | /** 39 | * This class actually implements a debugger loop, which runs in a thread 40 | * and executes debugger commands. There can only be one 41 | * DebuggerThread in a program at a time. Its constructor spawns a 42 | * debugging thread that will read commands from its controller, execute them, 43 | * and provide messages describing the results and also describing spurious 44 | * thread events. 45 | **/ 46 | class DebuggerThread 47 | { 48 | /** 49 | * Creates an DebuggerThread which will acquire commands from and 50 | * send messages to the given controller. 51 | * 52 | * @param controller is the controller from which commands will be read 53 | * and to which messages will be written 54 | * @param startStopped if true, all threads except the debugger thread 55 | * itself will be stopped when the debugger thread starts. If 56 | * false, the calling thread will continue to run after the 57 | * debugging thread has been created. 58 | **/ 59 | public function new(controller : IController, startStopped : Bool) 60 | { 61 | #if !HXCPP_DEBUGGER 62 | Sys.println("Warning: This program was not compiled with the " + 63 | "HXCPP_DEBUGGER flag set, debugging not supported."); 64 | return; 65 | #end 66 | if (gStartMutex == null) { 67 | throw "Debug thread cannot be constructed by a static initializer"; 68 | } 69 | 70 | // Ensure that there isn't a debugger already 71 | gStartMutex.acquire(); 72 | if (gStarted) { 73 | gStartMutex.release(); 74 | throw ("Debug thread constructor failed - there is already a " + 75 | "debugger running"); 76 | } 77 | gStarted = true; 78 | gStartMutex.release(); 79 | 80 | // Set up instance state 81 | mStateMutex = new Mutex(); 82 | mController = controller; 83 | mCurrentThreadNumber = Debugger.getCurrentThreadNumber(); 84 | mCurrentThreadInfo = null; 85 | mBreakpoints = new haxe.ds.IntMap(); 86 | mBreakpointsByDescription = new haxe.ds.StringMap(); 87 | mNextBreakpointNumber = 1; 88 | mDebuggerVariables = new DebuggerVariables(); 89 | 90 | // Set gStartStopped so that the debugger thread will know whether to 91 | // stop the program 92 | gStartStopped = startStopped; 93 | 94 | // Start the real debugger thread running. Before doing so, ensure 95 | // that the current thread will not stop until this function is 96 | // exited. 97 | Debugger.enableCurrentThreadDebugging(false); 98 | 99 | Thread.create(debuggerThreadMain); 100 | 101 | if (startStopped) { 102 | // Cannot proceed until the debugger thread has notified us that 103 | // it's OK to proceed. 104 | gStartQueue.pop(true); 105 | } 106 | 107 | // Once this function is called, the current thread can break 108 | Debugger.enableCurrentThreadDebugging(true); 109 | } 110 | 111 | 112 | // ------------------------------------------------------------------------ 113 | // ------------------------------------------------------------------------ 114 | // Private implementation follows 115 | // ------------------------------------------------------------------------ 116 | // ------------------------------------------------------------------------ 117 | 118 | private function debuggerThreadMain() 119 | { 120 | // Set the thread event notification handler 121 | Debugger.setEventNotificationHandler(handleThreadEvent); 122 | 123 | // If the 'started debugger thread' is set, it means that debugging 124 | // was to start with all threads (aside from the debugger thread) 125 | // stopped, so do so now. 126 | if (gStartStopped) { 127 | // Ensure that the main thread will break once it gets past its 128 | // unbreakable region - but don't wait for it to break since it's 129 | // currently blocking on gStartQueue. 130 | Debugger.breakNow(false); 131 | // Signal to the started debugger thread to proceed (which will 132 | // cause it to immediately break at the instruction after its 133 | // readMessage() call in its constructor). 134 | gStartQueue.push(true); 135 | // This call does not return until all threads (except for the 136 | // calling thread) have been broken. 137 | Debugger.breakNow(true); 138 | } 139 | // Else, just let the main thread go now 140 | else { 141 | gStartQueue.push(true); 142 | } 143 | 144 | // Now run the main loop 145 | try { 146 | while (true) { 147 | switch (mController.getNextCommand()) { 148 | case Exit: 149 | emit(Exited); 150 | Sys.exit(0); 151 | 152 | case Detach: 153 | emit(Detached); 154 | return; 155 | 156 | case Files: 157 | emit(this.files()); 158 | 159 | case FilesFullPath: 160 | emit(this.filesFullPath()); 161 | 162 | case AllClasses: 163 | emit(this.allClasses()); 164 | 165 | case Classes(continuation): 166 | emit(this.classes(continuation)); 167 | 168 | case Mem: 169 | emit(this.mem()); 170 | 171 | case Compact: 172 | emit(this.compact()); 173 | 174 | case Collect: 175 | emit(this.collect()); 176 | 177 | case SetCurrentThread(number): 178 | emit(this.setCurrentThread(number)); 179 | 180 | case AddFileLineBreakpoint(fileName, lineNumber): 181 | emit(this.addFileLineBreakpoint(fileName, lineNumber)); 182 | 183 | case AddClassFunctionBreakpoint(className, functionName): 184 | emit(this.addClassFunctionBreakpoint 185 | (className, functionName)); 186 | 187 | case ListBreakpoints(enabled, disabled): 188 | emit(this.listBreakpoints(enabled, disabled)); 189 | 190 | case DescribeBreakpoint(number): 191 | emit(this.describeBreakpoint(number)); 192 | 193 | case DisableAllBreakpoints: 194 | emit(this.disableAllBreakpoints()); 195 | 196 | case DisableBreakpointRange(first, last): 197 | emit(this.disableBreakpointRange(first, last)); 198 | 199 | case EnableAllBreakpoints: 200 | emit(this.enableAllBreakpoints()); 201 | 202 | case EnableBreakpointRange(first, last): 203 | emit(this.enableBreakpointRange(first, last)); 204 | 205 | case DeleteAllBreakpoints: 206 | emit(this.deleteAllBreakpoints()); 207 | 208 | case DeleteBreakpointRange(first, last): 209 | emit(this.deleteBreakpointRange(first, last)); 210 | 211 | case DeleteFileLineBreakpoint(fileName, lineNumber): 212 | emit(this.deleteFileLineBreakpoint(fileName, lineNumber)); 213 | 214 | case BreakNow: 215 | emit(this.breakNow()); 216 | 217 | case Continue(count): 218 | emit(this.continueCurrent(count)); 219 | 220 | case Step(count): 221 | emit(this.step(count)); 222 | 223 | case Next(count): 224 | emit(this.next(count)); 225 | 226 | case Finish(count): 227 | emit(this.finish(count)); 228 | 229 | case WhereCurrentThread(unsafe): 230 | emit(this.whereCurrentThread(unsafe)); 231 | 232 | case WhereAllThreads: 233 | emit(this.whereAllThreads()); 234 | 235 | case Up(count): 236 | emit(this.up(count)); 237 | 238 | case Down(count): 239 | emit(this.down(count)); 240 | 241 | case SetFrame(number): 242 | emit(this.setFrame(number)); 243 | 244 | case Variables(unsafe): 245 | emit(this.variables(unsafe)); 246 | 247 | case PrintExpression(unsafe, expression): 248 | emit(this.printExpression(unsafe, expression)); 249 | 250 | case SetExpression(unsafe, lhs, rhs): 251 | emit(this.setExpression(unsafe, lhs, rhs)); 252 | 253 | case GetStructured(unsafe, expression): 254 | emit(this.getStructured(unsafe, expression)); 255 | } 256 | } 257 | } 258 | catch (e : Dynamic) { 259 | emit(ErrorInternal("Exception in debugger, detaching: " + e)); 260 | } 261 | 262 | // No longer want to know about thread events 263 | Debugger.setEventNotificationHandler(null); 264 | 265 | // Delete all breakpoints from the debugged process 266 | this.deleteAllBreakpoints(); 267 | 268 | emit(Detached); 269 | 270 | // Ensure that all threads are running 271 | Debugger.continueThreads(-1, 1); 272 | 273 | // Another thread might immediately start another debugger and re-set 274 | // gStartedDebuggerThread, which is why it was latched previously. 275 | gStarted = false; 276 | } 277 | 278 | private function handleThreadEvent(threadNumber : Int, event : Int, 279 | stackFrame : Int, 280 | className : String, 281 | functionName : String, 282 | fileName : String, lineNumber : Int) 283 | { 284 | switch (event) { 285 | case Debugger.THREAD_CREATED: 286 | emit(ThreadCreated(threadNumber)); 287 | case Debugger.THREAD_TERMINATED: 288 | mStateMutex.acquire(); 289 | if (threadNumber == mCurrentThreadNumber) { 290 | mCurrentThreadInfo = null; 291 | } 292 | mStateMutex.release(); 293 | emit(ThreadTerminated(threadNumber)); 294 | case Debugger.THREAD_STARTED: 295 | mStateMutex.acquire(); 296 | if (threadNumber == mCurrentThreadNumber) { 297 | mCurrentThreadInfo = null; 298 | } 299 | mStateMutex.release(); 300 | emit(ThreadStarted(threadNumber)); 301 | case Debugger.THREAD_STOPPED: 302 | mStateMutex.acquire(); 303 | if (threadNumber == mCurrentThreadNumber) { 304 | mCurrentThreadInfo = null; 305 | } 306 | mStateMutex.release(); 307 | emit(ThreadStopped(threadNumber, stackFrame, className, 308 | functionName, fileName, lineNumber)); 309 | } 310 | } 311 | 312 | private function emit(message : Message) 313 | { 314 | mController.acceptMessage(message); 315 | } 316 | 317 | private function files() : Message 318 | { 319 | // Preserve order to match filesFullPath 320 | return Files( Debugger.getFiles() ); 321 | } 322 | 323 | private function filesFullPath() : Message 324 | { 325 | return Files( Debugger.getFilesFullPath() ); 326 | } 327 | 328 | private function allClasses() : Message 329 | { 330 | var classes = Debugger.getClasses(); 331 | 332 | var list : StringArray = new StringArray(); 333 | 334 | // Sort the classes in reverse so that the list can be created easily 335 | classes.sort(function (a : String, b : String) { 336 | return Reflect.compare(b, a); 337 | }); 338 | 339 | for (f in classes) { 340 | list.push(f); 341 | } 342 | 343 | return AllClasses(list); 344 | } 345 | 346 | private function classes(continuation : Null) : Message 347 | { 348 | var classes = Debugger.getClasses(); 349 | 350 | var to_skip = (continuation == null) ? 0 : Std.parseInt(continuation); 351 | var initial_to_skip = to_skip; 352 | var total = 0; 353 | var byte_total = 0; 354 | 355 | // Accumulate classes to show 356 | var classes_to_use = new Array(); 357 | 358 | continuation = null; 359 | 360 | for (f in classes) { 361 | if (to_skip > 0) { 362 | to_skip -= 1; 363 | continue; 364 | } 365 | // Allow at most 10K of classes in a message 366 | // Allow at most 100 classes in a message 367 | if ((total == 100) || (f.length + byte_total) > (10 * 1024)) { 368 | continuation = Std.string(initial_to_skip + total); 369 | break; 370 | } 371 | total += 1; 372 | classes_to_use.push(f); 373 | } 374 | 375 | // Sort the classes in reverse so that the list can be created easily 376 | classes_to_use.sort(function (a : String, b : String) { 377 | return Reflect.compare(b, a); 378 | }); 379 | 380 | var ca = new Array(); 381 | 382 | for (f in classes_to_use) { 383 | var ce : ClassEnum = ClassFunction(f, hasStaticValue(f)); 384 | ca.push(ce); 385 | } 386 | 387 | var list : ClassList = ClassFunction(ca, continuation); 388 | 389 | return Classes(list); 390 | } 391 | 392 | private function mem() : Message 393 | { 394 | #if cpp 395 | return MemBytes(cpp.vm.Gc.memUsage()); 396 | #else 397 | return MemBytes(neko.vm.Gc.stats().heap); 398 | #end 399 | } 400 | 401 | private function compact() : Message 402 | { 403 | #if cpp 404 | var pre = cpp.vm.Gc.memUsage(); 405 | cpp.vm.Gc.compact(); 406 | var post = cpp.vm.Gc.memUsage(); 407 | #else 408 | var pre = neko.vm.Gc.stats().heap; 409 | neko.vm.Gc.run(false); 410 | var post = neko.vm.Gc.stats().heap; 411 | #end 412 | return Compacted(pre, post); 413 | } 414 | 415 | private function collect() : Message 416 | { 417 | #if cpp 418 | var pre = cpp.vm.Gc.memUsage(); 419 | cpp.vm.Gc.run(true); 420 | var post = cpp.vm.Gc.memUsage(); 421 | #else 422 | var pre = neko.vm.Gc.stats().heap; 423 | neko.vm.Gc.run(true); 424 | var post = neko.vm.Gc.stats().heap; 425 | #end 426 | return Collected(pre, post); 427 | } 428 | 429 | private function setCurrentThread(number: Int) : Message 430 | { 431 | var threadInfos = Debugger.getThreadInfos(); 432 | 433 | for (ti in threadInfos) { 434 | if (ti.number == number) { 435 | mStateMutex.acquire(); 436 | mCurrentThreadNumber = number; 437 | mCurrentThreadInfo = null; 438 | mStateMutex.release(); 439 | if (ti.status == ThreadInfo.STATUS_RUNNING) { 440 | if (ti.stack.length == 0) { 441 | return OK; 442 | } 443 | } 444 | var frameNumber = ti.stack.length - 1; 445 | var frame = ti.stack[frameNumber]; 446 | return ThreadLocation(number, frameNumber, frame.className, 447 | frame.functionName, frame.fileName, 448 | frame.lineNumber); 449 | } 450 | } 451 | 452 | return ErrorNoSuchThread(number); 453 | } 454 | 455 | private function addFileLineBreakpoint(fileName : String, 456 | lineNumber : Int) : Message 457 | { 458 | var desc = (fileName + ":" + lineNumber); 459 | 460 | if (!mBreakpointsByDescription.exists(desc)) { 461 | var files = Debugger.getFiles(); 462 | for (f in files) { 463 | if (f == fileName) { 464 | var breakpoint = 465 | new Breakpoint(mNextBreakpointNumber++, desc); 466 | mBreakpoints.set(breakpoint.number, breakpoint); 467 | mBreakpointsByDescription.set(desc, breakpoint); 468 | breakpoint.addFileLine(fileName, lineNumber); 469 | break; 470 | } 471 | } 472 | } 473 | 474 | if (mBreakpointsByDescription.exists(desc)) { 475 | var breakpoint = mBreakpointsByDescription.get(desc); 476 | breakpoint.enable(); 477 | return FileLineBreakpointNumber(breakpoint.number); 478 | } 479 | else { 480 | return ErrorNoSuchFile(fileName); 481 | } 482 | } 483 | 484 | private function addClassFunctionBreakpoint(className : String, 485 | functionName : String) : Message 486 | { 487 | var classNameRegex : EReg = null; 488 | var functionNameRegex : EReg = null; 489 | 490 | try { 491 | if (className.charAt(0) == "/") { 492 | classNameRegex = 493 | new EReg(className.substr(1, className.length - 2), ""); 494 | } 495 | } 496 | catch (e : Dynamic) { 497 | return ErrorBadClassNameRegex(e); 498 | } 499 | 500 | try { 501 | if (functionName.charAt(0) == "/") { 502 | functionNameRegex = 503 | new EReg(functionName.substr 504 | (1, functionName.length - 2), ""); 505 | } 506 | } 507 | catch (e : Dynamic) { 508 | return ErrorBadFunctionNameRegex(e); 509 | } 510 | 511 | var desc = className + "." + functionName; 512 | 513 | if (mBreakpointsByDescription.exists(desc)) { 514 | var breakpoint = mBreakpointsByDescription.get(desc); 515 | breakpoint.enable(); 516 | return ClassFunctionBreakpointNumber 517 | (breakpoint.number, null); 518 | } 519 | 520 | var badClasses : StringArray = new StringArray(); 521 | 522 | var classNames = Debugger.getClasses(); 523 | for (cn in classNames) { 524 | var matchesClass = false; 525 | if (classNameRegex == null) { 526 | if (cn == className) { 527 | matchesClass = true; 528 | } 529 | else { 530 | var last = cn.lastIndexOf("."); 531 | if (last != -1) { 532 | if (cn.substr(last + 1) == className) { 533 | matchesClass = true; 534 | } 535 | } 536 | } 537 | } 538 | else if (classNameRegex.match(cn)) { 539 | matchesClass = true; 540 | } 541 | if (!matchesClass) { 542 | continue; 543 | } 544 | var klass = Type.resolveClass(cn); 545 | if (klass == null) { 546 | badClasses.push(cn); 547 | } 548 | else { 549 | this.breakFunction(desc, cn, klass, functionName, 550 | functionNameRegex); 551 | } 552 | } 553 | 554 | if (mBreakpointsByDescription.exists(desc)) { 555 | var breakpoint = mBreakpointsByDescription.get(desc); 556 | breakpoint.enable(); 557 | return ClassFunctionBreakpointNumber 558 | (breakpoint.number, badClasses); 559 | } 560 | else { 561 | return ErrorNoMatchingFunctions 562 | (className, functionName, badClasses); 563 | } 564 | } 565 | 566 | private function breakFunction(desc : String, className : String, 567 | klass : Class, 568 | functionName : String, 569 | functionNameRegex : EReg) 570 | { 571 | // First of all, any new function matches implicitly 572 | if (functionName == "new") { 573 | this.addFunctionBreakpoint(desc, className, functionName); 574 | } 575 | 576 | // Try to match the function within the class 577 | for (f in Type.getClassFields(klass).concat 578 | (Type.getInstanceFields(klass))) { 579 | if (!Reflect.isFunction(Reflect.field(klass, f))) { 580 | continue; 581 | } 582 | var matchesFunction = false; 583 | if (functionNameRegex == null) { 584 | if (f == functionName) { 585 | matchesFunction = true; 586 | } 587 | } 588 | else if (functionNameRegex.match(f)) { 589 | matchesFunction = true; 590 | } 591 | if (matchesFunction) { 592 | this.addFunctionBreakpoint(desc, className, f); 593 | if (functionNameRegex == null) { 594 | return; 595 | } 596 | } 597 | } 598 | } 599 | 600 | private function addFunctionBreakpoint(desc : String, className : String, 601 | functionName : String) 602 | { 603 | var breakpoint : Breakpoint = mBreakpointsByDescription.get(desc); 604 | if (breakpoint == null) { 605 | breakpoint = new Breakpoint(mNextBreakpointNumber++, desc); 606 | mBreakpoints.set(breakpoint.number, breakpoint); 607 | mBreakpointsByDescription.set(desc, breakpoint); 608 | } 609 | 610 | breakpoint.addClassFunction(className, functionName); 611 | } 612 | 613 | private function listBreakpoints(enabled, disabled) : Message 614 | { 615 | var list : BreakpointList = Terminator; 616 | 617 | var breakpoints : Array = new Array(); 618 | 619 | for (k in mBreakpoints.keys()) { 620 | var breakpoint = mBreakpoints.get(k); 621 | if ((enabled && breakpoint.enabled) || 622 | (disabled && !breakpoint.enabled)) { 623 | breakpoints.push(breakpoint); 624 | } 625 | } 626 | 627 | breakpoints.sort(function (a : Breakpoint, b : Breakpoint) { 628 | return b.number - a.number; }); 629 | 630 | for (b in breakpoints) { 631 | list = Breakpoint(b.number, b.description, b.enabled, 632 | (b.bpCount() > 1), list); 633 | } 634 | 635 | return Breakpoints(list); 636 | } 637 | 638 | private function describeBreakpoint(number : Int) : Message 639 | { 640 | var breakpoint = mBreakpoints.get(number); 641 | 642 | if (breakpoint == null) { 643 | return ErrorNoSuchBreakpoint(number); 644 | } 645 | 646 | var list : BreakpointLocationList = Terminator; 647 | 648 | for (b in breakpoint.bps()) { 649 | switch (b) { 650 | case BP.FileLine(bp, fileName, lineNumber): 651 | list = BreakpointLocationList.FileLine 652 | (fileName, lineNumber, list); 653 | case BP.ClassFunction(bp, className, functionName): 654 | list = BreakpointLocationList.ClassFunction 655 | (className, functionName, list); 656 | } 657 | } 658 | 659 | return BreakpointDescription(number, list); 660 | } 661 | 662 | private function disableAllBreakpoints() : Message 663 | { 664 | return this.disableBreakpointRange(-1, -1); 665 | } 666 | 667 | private function disableBreakpointRange(first, last) : Message 668 | { 669 | var list : BreakpointStatusList = Terminator; 670 | 671 | for (i in getBreakpointIds(first, last)) { 672 | var breakpoint = mBreakpoints.get(i); 673 | if (breakpoint == null) { 674 | list = Nonexistent(i, list); 675 | continue; 676 | } 677 | if (breakpoint.enabled) { 678 | breakpoint.disable(); 679 | list = Disabled(i, list); 680 | } 681 | else { 682 | list = AlreadyDisabled(i, list); 683 | } 684 | } 685 | 686 | return BreakpointStatuses(list); 687 | } 688 | 689 | private function enableAllBreakpoints() : Message 690 | { 691 | return this.enableBreakpointRange(-1, -1); 692 | } 693 | 694 | private function enableBreakpointRange(first, last) : Message 695 | { 696 | var list : BreakpointStatusList = Terminator; 697 | 698 | for (i in getBreakpointIds(first, last)) { 699 | var breakpoint = mBreakpoints.get(i); 700 | if (breakpoint == null) { 701 | list = Nonexistent(i, list); 702 | continue; 703 | } 704 | if (breakpoint.enabled) { 705 | list = AlreadyEnabled(i, list); 706 | } 707 | else { 708 | breakpoint.enable(); 709 | list = Enabled(i, list); 710 | } 711 | } 712 | 713 | return BreakpointStatuses(list); 714 | } 715 | 716 | private function deleteAllBreakpoints() : Message 717 | { 718 | return this.deleteBreakpointRange(-1, -1); 719 | } 720 | 721 | private function deleteBreakpointRange(first, last) : Message 722 | { 723 | return deleteBreakpoints(getBreakpointIds(first, last)); 724 | } 725 | 726 | private function deleteFileLineBreakpoint(fileName : String, 727 | lineNumber : Int) : Message 728 | { 729 | var list : BreakpointStatusList = Terminator; 730 | 731 | var description = fileName + ":" + lineNumber; 732 | 733 | var toRemove : Array = new Array(); 734 | 735 | for (i in mBreakpoints.keys()) { 736 | var bp = mBreakpoints.get(i); 737 | if (bp.description == description) { 738 | toRemove.push(i); 739 | } 740 | } 741 | 742 | return deleteBreakpoints(toRemove.iterator()); 743 | } 744 | 745 | private function deleteBreakpoints(bps : Iterator) : Message 746 | { 747 | var list : BreakpointStatusList = Terminator; 748 | 749 | for (i in bps) { 750 | var breakpoint = mBreakpoints.get(i); 751 | if (breakpoint == null) { 752 | list = Nonexistent(i, list); 753 | continue; 754 | } 755 | mBreakpoints.remove(i); 756 | mBreakpointsByDescription.remove(breakpoint.description); 757 | breakpoint.delete(); 758 | list = Deleted(i, list); 759 | } 760 | 761 | return BreakpointStatuses(list); 762 | } 763 | 764 | private function breakNow() : Message 765 | { 766 | Debugger.breakNow(); 767 | return OK; 768 | } 769 | 770 | private function continueCurrent(count : Int) : Message 771 | { 772 | if (count < 1) { 773 | return ErrorBadCount(count); 774 | } 775 | 776 | Debugger.continueThreads(mCurrentThreadNumber, count); 777 | 778 | return OK; 779 | } 780 | 781 | private function step(count) : Message 782 | { 783 | return this.stepExecution(count, Debugger.STEP_INTO); 784 | } 785 | 786 | private function next(count) : Message 787 | { 788 | return this.stepExecution(count, Debugger.STEP_OVER); 789 | } 790 | 791 | private function finish(count) : Message 792 | { 793 | return this.stepExecution(count, Debugger.STEP_OUT); 794 | } 795 | 796 | private function whereCurrentThread(unsafe : Bool) : Message 797 | { 798 | // Latch the current thread number, because it may change, and the 799 | // value used in any print calls should match the value used in the 800 | // getThreadInfo call 801 | mStateMutex.acquire(); 802 | 803 | var threadInfo : ThreadInfo = this.getCurrentThreadInfoLocked(unsafe); 804 | 805 | if (threadInfo == null) { 806 | mStateMutex.release(); 807 | return ErrorCurrentThreadNotStopped(mCurrentThreadNumber); 808 | } 809 | 810 | if (mCurrentStackFrame == -1) { 811 | mCurrentStackFrame = threadInfo.stack.length - 1; 812 | } 813 | 814 | mStateMutex.release(); 815 | 816 | return ThreadsWhere 817 | (threadInfoToThreadWhereLocked(threadInfo, Terminator)); 818 | } 819 | 820 | private function whereAllThreads() : Message 821 | { 822 | var threadInfos = Debugger.getThreadInfos(); 823 | if (threadInfos.length == 0) { 824 | return ThreadsWhere(Terminator); 825 | } 826 | 827 | var list : ThreadWhereList = Terminator; 828 | 829 | for (ti in threadInfos) { 830 | list = threadInfoToThreadWhereLocked(ti, list); 831 | } 832 | 833 | return ThreadsWhere(list); 834 | } 835 | 836 | private function threadInfoToThreadWhereLocked(threadInfo : ThreadInfo, 837 | next : ThreadWhereList) : ThreadWhereList 838 | { 839 | var list : FrameList = Terminator; 840 | 841 | if (threadInfo.stack.length > 0) { 842 | var frameNumber = 0; 843 | 844 | for (sf in threadInfo.stack) { 845 | list = Frame(((threadInfo.number == mCurrentThreadNumber) && 846 | (frameNumber == mCurrentStackFrame)), 847 | frameNumber, sf.className, sf.functionName, 848 | sf.fileName, sf.lineNumber, list); 849 | frameNumber += 1; 850 | } 851 | } 852 | 853 | var threadStatus : ThreadStatus; 854 | 855 | switch (threadInfo.status) { 856 | case ThreadInfo.STATUS_RUNNING: 857 | threadStatus = Running; 858 | case ThreadInfo.STATUS_STOPPED_BREAK_IMMEDIATE: 859 | threadStatus = StoppedImmediate; 860 | case ThreadInfo.STATUS_STOPPED_BREAKPOINT: 861 | threadStatus = StoppedBreakpoint 862 | (this.findBreakpoint(threadInfo.breakpoint)); 863 | case ThreadInfo.STATUS_STOPPED_UNCAUGHT_EXCEPTION: 864 | threadStatus = StoppedUncaughtException; 865 | default: // ThreadInfo.STATUS_STOPPED_CRITICAL_ERROR 866 | threadStatus = StoppedCriticalError 867 | (threadInfo.criticalErrorDescription); 868 | } 869 | 870 | return Where(threadInfo.number, threadStatus, list, next); 871 | } 872 | 873 | private function up(count : Int) : Message 874 | { 875 | if (count < 0) { 876 | return ErrorBadCount(count); 877 | } 878 | 879 | mStateMutex.acquire(); 880 | 881 | var threadInfo : ThreadInfo = this.getCurrentThreadInfoLocked(); 882 | 883 | if (threadInfo == null) { 884 | mStateMutex.release(); 885 | return ErrorCurrentThreadNotStopped(mCurrentThreadNumber); 886 | } 887 | 888 | mCurrentStackFrame -= count; 889 | if (mCurrentStackFrame < 0) { 890 | mCurrentStackFrame = 0; 891 | } 892 | 893 | count = mCurrentStackFrame; 894 | 895 | mStateMutex.release(); 896 | 897 | var frame = threadInfo.stack[mCurrentStackFrame]; 898 | 899 | return ThreadLocation(mCurrentThreadNumber, mCurrentStackFrame, 900 | frame.className, frame.functionName, 901 | frame.fileName, frame.lineNumber); 902 | } 903 | 904 | private function down(count : Int) : Message 905 | { 906 | if (count < 0) { 907 | return ErrorBadCount(count); 908 | } 909 | 910 | mStateMutex.acquire(); 911 | 912 | var threadInfo : ThreadInfo = this.getCurrentThreadInfoLocked(); 913 | 914 | if (threadInfo == null) { 915 | mStateMutex.release(); 916 | return ErrorCurrentThreadNotStopped(mCurrentThreadNumber); 917 | } 918 | 919 | var max_stack = threadInfo.stack.length - 1; 920 | 921 | mCurrentStackFrame += count; 922 | if (mCurrentStackFrame > max_stack) { 923 | mCurrentStackFrame = max_stack; 924 | } 925 | 926 | count = mCurrentStackFrame; 927 | 928 | mStateMutex.release(); 929 | 930 | var frame = threadInfo.stack[mCurrentStackFrame]; 931 | 932 | return ThreadLocation(mCurrentThreadNumber, mCurrentStackFrame, 933 | frame.className, frame.functionName, 934 | frame.fileName, frame.lineNumber); 935 | } 936 | 937 | private function setFrame(number : Int) : Message 938 | { 939 | mStateMutex.acquire(); 940 | 941 | var threadInfo : ThreadInfo = this.getCurrentThreadInfoLocked(); 942 | 943 | if (threadInfo == null) { 944 | mStateMutex.release(); 945 | return ErrorCurrentThreadNotStopped(mCurrentThreadNumber); 946 | } 947 | 948 | mCurrentStackFrame = number; 949 | 950 | if (mCurrentStackFrame < 0) { 951 | mCurrentStackFrame = 0; 952 | } 953 | else { 954 | var max_stack = threadInfo.stack.length - 1; 955 | if (mCurrentStackFrame > max_stack) { 956 | mCurrentStackFrame = max_stack; 957 | } 958 | } 959 | 960 | number = mCurrentStackFrame; 961 | 962 | mStateMutex.release(); 963 | 964 | var frame = threadInfo.stack[mCurrentStackFrame]; 965 | 966 | return ThreadLocation(mCurrentThreadNumber, mCurrentStackFrame, 967 | frame.className, frame.functionName, 968 | frame.fileName, frame.lineNumber); 969 | } 970 | 971 | private function variables(unsafe : Bool) : Message 972 | { 973 | mStateMutex.acquire(); 974 | 975 | // Just to ensure that the current stack frame is known 976 | this.getCurrentThreadInfoLocked(); 977 | 978 | var variables : Array = Debugger.getStackVariables 979 | (mCurrentThreadNumber, mCurrentStackFrame, unsafe); 980 | 981 | if ((variables.length == 1) && 982 | (variables[0] == Debugger.THREAD_NOT_STOPPED)) { 983 | mStateMutex.release(); 984 | return ErrorCurrentThreadNotStopped(mCurrentThreadNumber); 985 | } 986 | 987 | mStateMutex.release(); 988 | 989 | var list : StringArray = new StringArray(); 990 | 991 | // Sort the variables in reverse so that the list can be created easily 992 | variables.sort(function (a : String, b : String) { 993 | return Reflect.compare(b, a); 994 | }); 995 | 996 | for (f in variables) { 997 | list.push(f); 998 | } 999 | 1000 | return Variables(list); 1001 | } 1002 | 1003 | private function printExpression(unsafe : Bool, 1004 | expression : String) : Message 1005 | { 1006 | mStateMutex.acquire(); 1007 | 1008 | // Just to ensure that the current stack frame is known 1009 | this.getCurrentThreadInfoLocked(); 1010 | 1011 | try { 1012 | var value : Dynamic = ExpressionHelper.getValue 1013 | (expression, { threadNumber : mCurrentThreadNumber, 1014 | stackFrame : mCurrentStackFrame, 1015 | dbgVars : mDebuggerVariables, 1016 | unsafe : unsafe }); 1017 | 1018 | mStateMutex.release(); 1019 | 1020 | return Message.Value(StringTools.trim(expression), 1021 | TypeHelpers.getValueTypeName(value), 1022 | TypeHelpers.getValueString(value)); 1023 | } 1024 | catch (e : Dynamic) { 1025 | mStateMutex.release(); 1026 | if (e == Debugger.NONEXISTENT_VALUE) { 1027 | return ErrorEvaluatingExpression("No such value"); 1028 | } 1029 | else if (e == Debugger.THREAD_NOT_STOPPED) { 1030 | return ErrorCurrentThreadNotStopped(mCurrentThreadNumber); 1031 | } 1032 | else { 1033 | return ErrorEvaluatingExpression(e.toString() + 1034 | CallStack.toString(CallStack.exceptionStack())); 1035 | } 1036 | } 1037 | } 1038 | 1039 | private function setExpression(unsafe : Bool, lhs : String, 1040 | rhs : String) : Message 1041 | { 1042 | mStateMutex.acquire(); 1043 | 1044 | // Just to ensure that the current stack frame is known 1045 | this.getCurrentThreadInfoLocked(); 1046 | 1047 | try { 1048 | var value : Dynamic = ExpressionHelper.setValue 1049 | (lhs, rhs, { threadNumber : mCurrentThreadNumber, 1050 | stackFrame : mCurrentStackFrame, 1051 | dbgVars : mDebuggerVariables, 1052 | unsafe : unsafe }); 1053 | 1054 | mStateMutex.release(); 1055 | 1056 | return Message.Value(StringTools.trim(lhs), 1057 | TypeHelpers.getValueTypeName(value), 1058 | TypeHelpers.getValueString(value)); 1059 | } 1060 | catch (e : Dynamic) { 1061 | mStateMutex.release(); 1062 | if (e == Debugger.NONEXISTENT_VALUE) { 1063 | return ErrorEvaluatingExpression("No such value"); 1064 | } 1065 | else if (e == Debugger.THREAD_NOT_STOPPED) { 1066 | return ErrorCurrentThreadNotStopped(mCurrentThreadNumber); 1067 | } 1068 | else { 1069 | return ErrorEvaluatingExpression(e.toString() + 1070 | CallStack.toString(CallStack.exceptionStack())); 1071 | } 1072 | } 1073 | } 1074 | 1075 | private function getStructured(unsafe : Bool, expression : String) : Message 1076 | { 1077 | mStateMutex.acquire(); 1078 | 1079 | // Just to ensure that the current stack frame is known 1080 | this.getCurrentThreadInfoLocked(); 1081 | 1082 | try { 1083 | var value : Dynamic = ExpressionHelper.getValue 1084 | (expression, { threadNumber : mCurrentThreadNumber, 1085 | stackFrame : mCurrentStackFrame, 1086 | dbgVars : mDebuggerVariables, 1087 | unsafe : unsafe }); 1088 | 1089 | mStateMutex.release(); 1090 | 1091 | return Message.Structured(TypeHelpers.getStructuredValue 1092 | (value, false, expression)); 1093 | } 1094 | catch (e : Dynamic) { 1095 | mStateMutex.release(); 1096 | if (e == Debugger.NONEXISTENT_VALUE) { 1097 | return ErrorEvaluatingExpression("No such value"); 1098 | } 1099 | else if (e == Debugger.THREAD_NOT_STOPPED) { 1100 | return ErrorCurrentThreadNotStopped(mCurrentThreadNumber); 1101 | } 1102 | else { 1103 | return ErrorEvaluatingExpression(e.toString() + 1104 | CallStack.toString(CallStack.exceptionStack())); 1105 | } 1106 | } 1107 | } 1108 | 1109 | private function getBreakpointIds(first : Int, last : Int) 1110 | : Iterator 1111 | { 1112 | var sorted = new Array(); 1113 | 1114 | if (first == -1) { 1115 | for (k in mBreakpoints.keys()) { 1116 | sorted.push(k); 1117 | } 1118 | sorted.sort(function (a : Int, b : Int) { return b - a; }); 1119 | } 1120 | else { 1121 | for (i in first ... (last + 1)) { 1122 | sorted.unshift(i); 1123 | } 1124 | } 1125 | 1126 | return sorted.iterator(); 1127 | } 1128 | 1129 | private function stepExecution(count : Int, type : Int) : Message 1130 | { 1131 | if (count < 1) { 1132 | return ErrorBadCount(count); 1133 | } 1134 | 1135 | Debugger.stepThread(mCurrentThreadNumber, type, count); 1136 | 1137 | return OK; 1138 | } 1139 | 1140 | // Find by a debugger breakpoint number given the breakpoint field of a 1141 | // ThreadInfo 1142 | private function findBreakpoint(bpNumber : Int) : Int 1143 | { 1144 | for (breakpoint in mBreakpoints) { 1145 | for (bp in breakpoint.bps()) { 1146 | switch (bp) { 1147 | case FileLine(bp, fileName, lineNumber): 1148 | if (bp == bpNumber) { 1149 | return breakpoint.number; 1150 | } 1151 | case ClassFunction(bp, className, functionName): 1152 | if (bp == bpNumber) { 1153 | return breakpoint.number; 1154 | } 1155 | } 1156 | } 1157 | } 1158 | 1159 | // Hm, no such breakpoint 1160 | return -1; 1161 | } 1162 | 1163 | private function getCurrentThreadInfoLocked 1164 | (unsafe : Bool = false) : ThreadInfo 1165 | { 1166 | if (unsafe) { 1167 | mCurrentThreadInfo = null; 1168 | return Debugger.getThreadInfo(mCurrentThreadNumber, unsafe); 1169 | } 1170 | 1171 | if (mCurrentThreadInfo != null) { 1172 | return mCurrentThreadInfo; 1173 | } 1174 | 1175 | mCurrentThreadInfo = Debugger.getThreadInfo 1176 | (mCurrentThreadNumber, unsafe); 1177 | 1178 | if (mCurrentThreadInfo == null) { 1179 | return null; 1180 | } 1181 | 1182 | mCurrentStackFrame = mCurrentThreadInfo.stack.length - 1; 1183 | 1184 | return mCurrentThreadInfo; 1185 | } 1186 | 1187 | private static function hasStaticValue(className : String) : Bool 1188 | { 1189 | var klass = Type.resolveClass(className); 1190 | if (klass == null) { 1191 | return false; 1192 | } 1193 | for (f in Type.getClassFields(klass)) { 1194 | if (!Reflect.isFunction(Reflect.field(klass, f))) { 1195 | return true; 1196 | } 1197 | } 1198 | return false; 1199 | } 1200 | 1201 | // This is a mutex that prevents simultaneous access of member variables 1202 | // by the event handler thread and the debugger thread. 1203 | private var mStateMutex : Mutex; 1204 | private var mController : IController; 1205 | // mCurrentThreadNumber starts out as the thread that created the debugger 1206 | // instance. 1207 | private var mCurrentThreadNumber : Int; 1208 | private var mCurrentStackFrame : Int; 1209 | private var mCurrentThreadInfo : ThreadInfo; 1210 | private var mBreakpoints : haxe.ds.IntMap; 1211 | private var mBreakpointsByDescription : haxe.ds.StringMap; 1212 | private var mNextBreakpointNumber : Int; 1213 | 1214 | private var mDebuggerVariables : DebuggerVariables; 1215 | 1216 | private static var gStartMutex : Mutex = new Mutex(); 1217 | private static var gStarted : Bool = false; 1218 | private static var gStartStopped : Bool; 1219 | private static var gStartQueue : Deque = new Deque(); 1220 | } 1221 | 1222 | 1223 | private class TypeHelpers 1224 | { 1225 | public static function getValueTypeName(value : Dynamic) : String 1226 | { 1227 | switch (Type.typeof(value)) { 1228 | case TUnknown: 1229 | return "UNKNOWN"; 1230 | case TNull: 1231 | return "NULL"; 1232 | case TObject: 1233 | if (Std.is(value, Class)) { 1234 | return "Class<" + getClassName(cast value) + ">"; 1235 | } 1236 | return "Anonymous"; 1237 | case TInt: 1238 | return "Int"; 1239 | case TFunction: 1240 | return "Function"; 1241 | case TFloat: 1242 | return "Float"; 1243 | case TEnum(e): 1244 | return Type.getEnumName(e); 1245 | case TClass(Array): 1246 | var arr = cast(value, Array); 1247 | if (arr.length == 0) { 1248 | return "Array<>"; 1249 | } 1250 | else { 1251 | return ("Array<" + getValueTypeName(arr[0]) + ">[" + 1252 | arr.length + "]"); 1253 | } 1254 | case TClass(DebuggerVariables): 1255 | return "Debugger variables"; 1256 | case TClass(c): 1257 | return getClassName(c); 1258 | case TBool: 1259 | return "Bool"; 1260 | } 1261 | 1262 | return "INVALID"; 1263 | } 1264 | 1265 | public static function getValueString 1266 | (value : Dynamic, indent : String = "", 1267 | ellipseForObjects : Bool = false) : String 1268 | { 1269 | switch (Type.typeof(value)) { 1270 | case TUnknown: 1271 | case TInt: 1272 | case TBool: 1273 | case TFloat: 1274 | case TEnum(e): 1275 | case TNull: 1276 | case TFunction: 1277 | return Std.string(value); 1278 | case TObject: 1279 | var klass = null; 1280 | try { 1281 | klass = cast(value, Class); 1282 | } catch (e:Dynamic) { 1283 | } 1284 | if (klass != null) { 1285 | return ("Class<" + Std.string(value) + ">" + 1286 | getClassValueString(klass, indent)); 1287 | } 1288 | if (ellipseForObjects) { 1289 | return "..."; 1290 | } 1291 | var ret = "{\n"; 1292 | for (f in Reflect.fields(value)) { 1293 | ret += indent; 1294 | ret += f + ' : '; 1295 | ret += getValueString(Reflect.field(value, f), indent + " ", 1296 | ellipseForObjects); 1297 | ret += "\n"; 1298 | } 1299 | return ret + indent + "}"; 1300 | case TClass(Array): 1301 | var arr : Array = cast value; 1302 | if (arr.length == 0) { 1303 | return "[ ]"; 1304 | } 1305 | if (ellipseForObjects) { 1306 | return "[ ... ]"; 1307 | } 1308 | var ret = "[ "; 1309 | var needComma = false; 1310 | for (a in arr) { 1311 | if (needComma) { 1312 | ret += ", "; 1313 | } 1314 | else { 1315 | needComma = true; 1316 | } 1317 | ret += getValueString(a, indent); 1318 | } 1319 | return ret + " ]"; 1320 | case TClass(String): 1321 | return "\"" + value + "\""; 1322 | case TClass(DebuggerVariables): 1323 | return value.toString(); 1324 | case TClass(c): 1325 | if (ellipseForObjects) { 1326 | return "..."; 1327 | } 1328 | var klass:Class = Type.getClass(value); 1329 | if (klass == null) { 1330 | return "???"; 1331 | } 1332 | return getInstanceValueString(klass, value, indent); 1333 | } 1334 | 1335 | return Std.string(value); 1336 | } 1337 | 1338 | public static function getClassValueString(klass : Class, 1339 | indent : String) : String 1340 | { 1341 | var ret = "\n" + indent + "{\n"; 1342 | 1343 | var fields = new Array(); 1344 | 1345 | for (f in Type.getClassFields(klass)) { 1346 | if (Reflect.isFunction(Reflect.field(klass, f))) { 1347 | continue; 1348 | } 1349 | fields.push(f); 1350 | } 1351 | 1352 | for (f in fields) { 1353 | var fieldValue = Reflect.getProperty(klass, f); 1354 | ret += (indent + " " + f + " : static " + 1355 | getValueTypeName(fieldValue) + " = " + 1356 | getValueString(fieldValue, indent + " ", true) + "\n"); 1357 | } 1358 | 1359 | return ret + indent + "}"; 1360 | } 1361 | 1362 | public static function getInstanceValueString(klass : Class, 1363 | value : Dynamic, 1364 | indent : String) : String 1365 | { 1366 | var ret = "\n" + indent + "{\n"; 1367 | 1368 | // Type seems to return the fields in the reverse order as they 1369 | // are defined in the source ... 1370 | var fields = new Array(); 1371 | 1372 | for (f in Type.getInstanceFields(klass)) { 1373 | if (Reflect.isFunction(Reflect.field(value, f))) { 1374 | continue; 1375 | } 1376 | fields.unshift(f); 1377 | } 1378 | 1379 | for (f in fields) { 1380 | var fieldValue = Reflect.getProperty(value, f); 1381 | 1382 | ret += (indent + " " + f + " : " + 1383 | getValueTypeName(fieldValue) + " = " + 1384 | getValueString(fieldValue, indent + " ", true) + "\n"); 1385 | } 1386 | 1387 | // Although the instance fields returned by Type seem to include super 1388 | // class variables also, class variables do not, so iterate through 1389 | // super classes manually 1390 | while (klass != null) { 1391 | fields = new Array(); 1392 | 1393 | for (f in Type.getClassFields(klass)) { 1394 | if (Reflect.isFunction(Reflect.field(klass, f))) { 1395 | continue; 1396 | } 1397 | fields.push(f); 1398 | } 1399 | 1400 | for (f in fields) { 1401 | var fieldValue = Reflect.getProperty(klass, f); 1402 | ret += (indent + " " + f + " : static " + 1403 | getValueTypeName(fieldValue) + " = " + 1404 | getValueString(fieldValue, indent + " ", true) + 1405 | "\n"); 1406 | } 1407 | 1408 | klass = Type.getSuperClass(klass); 1409 | } 1410 | 1411 | 1412 | return ret + indent + "}"; 1413 | } 1414 | 1415 | public static function getStructuredValueType(value : Dynamic) 1416 | : StructuredValueType 1417 | { 1418 | switch (Type.typeof(value)) { 1419 | case TNull, TUnknown: 1420 | return TypeNull; 1421 | 1422 | case TInt: 1423 | return TypeInt; 1424 | 1425 | case TFloat: 1426 | return TypeFloat; 1427 | 1428 | case TBool: 1429 | return TypeBool; 1430 | 1431 | case TObject: 1432 | if (Std.is(value, Class)) { 1433 | return TypeClass(getClassName(cast value)); 1434 | } 1435 | var list : StructuredValueTypeList = Terminator; 1436 | if (value != null) { 1437 | var arr = [ ]; 1438 | for (f in Reflect.fields(value)) { 1439 | arr.push(f); 1440 | } 1441 | var i = arr.length - 1; 1442 | while (i >= 0) { 1443 | list = _Type(getStructuredValueType 1444 | (Reflect.field(value, arr[i])), list); 1445 | i -= 1; 1446 | } 1447 | } 1448 | return TypeAnonymous(list); 1449 | 1450 | case TFunction: 1451 | return TypeFunction; 1452 | 1453 | case TEnum(e): 1454 | return TypeEnum(Type.getEnumName(e)); 1455 | 1456 | case TClass(String): 1457 | return TypeString; 1458 | 1459 | case TClass(Array): 1460 | return TypeArray; 1461 | 1462 | case TClass(c): 1463 | return TypeInstance(getClassName(c)); 1464 | } 1465 | } 1466 | 1467 | public static function getClassName(klass : Class) : String { 1468 | var className : String = ""; 1469 | if (null != klass) { 1470 | var klassName : String = Type.getClassName(klass); 1471 | if (null != klassName && 0 != klassName.length) { 1472 | className = klassName; 1473 | } 1474 | } 1475 | return className; 1476 | } 1477 | 1478 | private static function getClassFieldNames(value : Dynamic, 1479 | klass : Class) 1480 | : Array 1481 | { 1482 | // We walk the class hierarchy to find all statics. 1483 | var staticNames : Array = new Array(); 1484 | var clazz : Class = klass; 1485 | while (clazz != null) { 1486 | for (f in Type.getClassFields(clazz)) { 1487 | if (Reflect.isFunction(Reflect.field(value, f))) { 1488 | continue; 1489 | } 1490 | staticNames.push(f); 1491 | } 1492 | clazz = Type.getSuperClass(clazz); 1493 | } 1494 | return staticNames; 1495 | } 1496 | 1497 | public static function getStructuredValue(value : Dynamic, 1498 | elideArraysAndObjects : Bool, 1499 | expression : String) 1500 | : StructuredValue 1501 | { 1502 | switch (Type.typeof(value)) { 1503 | case TNull, TInt, TFloat,TBool, TFunction, TUnknown: 1504 | return Single(getStructuredValueType(value), Std.string(value)); 1505 | 1506 | case TEnum(e): 1507 | return Single(getStructuredValueType(value), Std.string(value)); 1508 | 1509 | case TObject: 1510 | var klass = Type.resolveClass(Std.string(value)); 1511 | if (klass != null) { 1512 | if (elideArraysAndObjects) { 1513 | return Single(getStructuredValueType(value), 1514 | Std.string(value)); 1515 | } 1516 | var list : StructuredValueList = Terminator; 1517 | for (f in getClassFieldNames(value, klass)) { 1518 | try { 1519 | var fieldValue : StructuredValue = null; 1520 | var property : Dynamic = Reflect.getProperty(klass, f); 1521 | if (null == property) { 1522 | // Variable was inlined. 1523 | fieldValue = Single 1524 | (getStructuredValueType(null), 1525 | Std.string("No instances (inlined)")); 1526 | } 1527 | else { 1528 | fieldValue = getStructuredValue(property, true, 1529 | expression + "." + f); 1530 | } 1531 | list = Element(f, fieldValue, list); 1532 | } 1533 | catch (e : Dynamic) { 1534 | } 1535 | } 1536 | return List(Class, list); 1537 | } 1538 | if (elideArraysAndObjects) { 1539 | return Elided(getStructuredValueType(value), expression); 1540 | } 1541 | return List(Anonymous, 1542 | getStructuredValueList(value, expression)); 1543 | 1544 | case TClass(String): 1545 | return Single(TypeClass("String"), Std.string(value)); 1546 | 1547 | case TClass(Array): 1548 | if (elideArraysAndObjects) { 1549 | return Elided(TypeArray, expression); 1550 | } 1551 | var list : StructuredValueList = Terminator; 1552 | var arr = cast(value, Array); 1553 | if (arr != null) { 1554 | var i = arr.length - 1; 1555 | while (i >= 0) { 1556 | var val = arr[i]; 1557 | var subexp = expression + "[" + i + "]"; 1558 | list = Element(Std.string(i), 1559 | getStructuredValue(val, true, subexp), list); 1560 | i -= 1; 1561 | } 1562 | } 1563 | return List(_Array, list); 1564 | 1565 | case TClass(c): 1566 | if (elideArraysAndObjects) { 1567 | return Elided(getStructuredValueType(value), expression); 1568 | } 1569 | return List(Instance(getClassName(c)), 1570 | getStructuredValueList(value, expression)); 1571 | } 1572 | } 1573 | 1574 | public static function getStructuredValueList(v : Dynamic, 1575 | expression : String) 1576 | : StructuredValueList 1577 | { 1578 | var list : StructuredValueList = Terminator; 1579 | if (v == null) { 1580 | return list; 1581 | } 1582 | var arr = [ ]; 1583 | for (f in Reflect.fields(v)) { 1584 | arr.push(f); 1585 | } 1586 | var i = arr.length - 1; 1587 | while (i >= 0) { 1588 | var name = arr[i]; 1589 | var dottedExpression = expression + "." + name; 1590 | var structuredValue : StructuredValue; 1591 | // Don't allow a failed access to stop processing for the whole 1592 | // structure. 1593 | try { 1594 | structuredValue = getStructuredValue(Reflect.field(v, name), 1595 | true, 1596 | dottedExpression); 1597 | } 1598 | catch (e : Dynamic) { 1599 | structuredValue = getStructuredValue( 1600 | ErrorEvaluatingExpression(e), 1601 | true, 1602 | dottedExpression); 1603 | } 1604 | list = Element(name, structuredValue, list); 1605 | i -= 1; 1606 | } 1607 | 1608 | // Add static values, if any. 1609 | var klass = Type.getClass(v); 1610 | var staticNames : Array = getClassFieldNames(v, klass); 1611 | if (null != staticNames && staticNames.length > 0) { 1612 | var staticList = getStructuredValue(klass, false, 1613 | getClassName(klass)); 1614 | list = Element("static variables", staticList, list); 1615 | } 1616 | 1617 | return list; 1618 | } 1619 | } 1620 | 1621 | 1622 | private class DebuggerVariables 1623 | { 1624 | public function new() 1625 | { 1626 | mMap = new haxe.ds.StringMap(); 1627 | } 1628 | 1629 | public function set(key : String, value : Dynamic) 1630 | { 1631 | if (value == null) { 1632 | mMap.remove(key); 1633 | } 1634 | else { 1635 | mMap.set(key, value); 1636 | } 1637 | } 1638 | 1639 | public function get(key : String) : Dynamic 1640 | { 1641 | return mMap.get(key); 1642 | } 1643 | 1644 | public function toString() : String 1645 | { 1646 | var keys : Array = new Array(); 1647 | 1648 | var iter = mMap.keys(); 1649 | 1650 | var ret = "\n\n"; 1651 | 1652 | if (!iter.hasNext()) { 1653 | return ret + "(no values)"; 1654 | } 1655 | 1656 | for (k in iter) { 1657 | keys.push(k); 1658 | } 1659 | 1660 | keys.sort(function (a : String, b : String) { 1661 | return Reflect.compare(a, b); 1662 | }); 1663 | 1664 | var needNewline = false; 1665 | for (k in keys) { 1666 | if (needNewline) { 1667 | ret += "\n"; 1668 | } 1669 | else { 1670 | needNewline = true; 1671 | } 1672 | var value = mMap.get(k); 1673 | ret += ("$." + k + " : " + TypeHelpers.getValueTypeName(value) + 1674 | " = " + TypeHelpers.getValueString(value)); 1675 | } 1676 | 1677 | return ret; 1678 | } 1679 | 1680 | private var mMap : haxe.ds.StringMap; 1681 | } 1682 | 1683 | 1684 | private enum BP 1685 | { 1686 | FileLine(bp : Int, fileName : String, lineNumber : Int); 1687 | ClassFunction(bp : Int, className : String, functionName : String); 1688 | } 1689 | 1690 | 1691 | private class Breakpoint 1692 | { 1693 | public var number(default, null) : Int; 1694 | public var description(default, null) : String; 1695 | public var enabled(default, null) : Bool; 1696 | public var continueCount(default, null) : Int; 1697 | 1698 | public function new(num : Int, desc : String) 1699 | { 1700 | this.number = num; 1701 | this.description = desc; 1702 | this.enabled = true; 1703 | this.mBps = new Array(); 1704 | } 1705 | 1706 | public function delete() 1707 | { 1708 | for (b in mBps) { 1709 | switch (b) { 1710 | case BP.FileLine(bp, fileName, lineNumber): 1711 | Debugger.deleteBreakpoint(bp); 1712 | case BP.ClassFunction(bp, className, functionName): 1713 | Debugger.deleteBreakpoint(bp); 1714 | } 1715 | } 1716 | } 1717 | 1718 | public function addFileLine(fileName : String, lineNumber : Int) 1719 | { 1720 | mBps.push(BP.FileLine(Debugger.addFileLineBreakpoint 1721 | (fileName, lineNumber), fileName, lineNumber)); 1722 | } 1723 | 1724 | public function addClassFunction(className : String, functionName : String) 1725 | { 1726 | mBps.push(BP.ClassFunction(Debugger.addClassFunctionBreakpoint 1727 | (className, functionName), className, 1728 | functionName)); 1729 | } 1730 | 1731 | public function bpCount() : Int 1732 | { 1733 | return mBps.length; 1734 | } 1735 | 1736 | public function bps() : Iterator 1737 | { 1738 | return mBps.iterator(); 1739 | } 1740 | 1741 | public function enable() 1742 | { 1743 | if (this.enabled) { 1744 | return; 1745 | } 1746 | 1747 | var oldBps = mBps; 1748 | mBps = new Array(); 1749 | 1750 | for (b in oldBps) { 1751 | switch (b) { 1752 | case BP.FileLine(bp, fileName, lineNumber): 1753 | this.addFileLine(fileName, lineNumber); 1754 | case BP.ClassFunction(bp, className, functionName): 1755 | this.addClassFunction(className, functionName); 1756 | } 1757 | } 1758 | 1759 | this.enabled = true; 1760 | } 1761 | 1762 | public function disable() 1763 | { 1764 | if (!this.enabled) { 1765 | return; 1766 | } 1767 | 1768 | for (b in mBps) { 1769 | switch (b) { 1770 | case BP.FileLine(bp, fileName, lineNumber): 1771 | Debugger.deleteBreakpoint(bp); 1772 | case BP.ClassFunction(bp, className, functionName): 1773 | Debugger.deleteBreakpoint(bp); 1774 | } 1775 | } 1776 | 1777 | this.enabled = false; 1778 | } 1779 | 1780 | private var mBps : Array; 1781 | } 1782 | 1783 | 1784 | /** 1785 | * Convenience typedef 1786 | **/ 1787 | typedef DbgVarSrc = { var threadNumber : Int; 1788 | var stackFrame : Int; 1789 | var dbgVars : DebuggerVariables; 1790 | var unsafe : Bool; }; 1791 | 1792 | // This is a helper class that can parse Haxe expressions and evaluate them 1793 | // into values, or use them to set references to other values 1794 | private class ExpressionHelper 1795 | { 1796 | public static function getValue(str : String, varSrc : DbgVarSrc) : Dynamic 1797 | { 1798 | try { 1799 | var parsed = ElementParser.parse(str, 0); 1800 | return getElementValue(parsed, varSrc); 1801 | } 1802 | catch (e : ElementException) { 1803 | throw (e.reason + ": " + str.substr(0, e.begin) + " -> " + 1804 | str.substr(e.begin, e.end + 1) + " <- " + 1805 | str.substr(e.end + 1)); 1806 | } 1807 | } 1808 | 1809 | public static function setValue(lhs : String, rhs : String, 1810 | varSrc : DbgVarSrc) : Dynamic 1811 | { 1812 | var lhs_element; 1813 | 1814 | try { 1815 | lhs_element = ElementParser.parse(lhs, 0); 1816 | } 1817 | catch (e : ElementException) { 1818 | throw (e.reason + ": " + lhs.substr(0, e.begin) + " -> " + 1819 | lhs.substr(e.begin, e.end + 1) + " <- " + 1820 | lhs.substr(e.end + 1)); 1821 | } 1822 | 1823 | var lhs_expr = getExpression(lhs_element, varSrc); 1824 | 1825 | var rhs_value = getValue(rhs, varSrc); 1826 | 1827 | switch (lhs_expr) { 1828 | case ExpressionEnum.Value(value): 1829 | throw "Cannot set value"; 1830 | case ExpressionEnum.FieldRef(value, field): 1831 | Reflect.setProperty(value, field, rhs_value); 1832 | case ExpressionEnum.DebuggerFieldRef(field): 1833 | varSrc.dbgVars.set(field, rhs_value); 1834 | case ExpressionEnum.DebuggerFields: 1835 | throw "Cannot assign a value to $"; 1836 | case ExpressionEnum.ArrayRef(value, index): 1837 | var arr : Array = cast value; 1838 | arr[index] = rhs_value; 1839 | case ExpressionEnum.StackRef(name): 1840 | return Debugger.setStackVariableValue 1841 | (varSrc.threadNumber, varSrc.stackFrame, name, rhs_value, 1842 | varSrc.unsafe); 1843 | } 1844 | 1845 | return rhs_value; 1846 | } 1847 | 1848 | // Convert an element into its actual Haxe value 1849 | private static function getElementValue(e : ElementEnum, 1850 | varSrc : DbgVarSrc) : Dynamic 1851 | { 1852 | var expr : ExpressionEnum = getExpression(e, varSrc); 1853 | 1854 | switch (expr) { 1855 | case ExpressionEnum.Value(value): 1856 | return value; 1857 | case ExpressionEnum.FieldRef(value, field): 1858 | return Reflect.getProperty(value, field); 1859 | case ExpressionEnum.DebuggerFieldRef(field): 1860 | var value = varSrc.dbgVars.get(field); 1861 | if (value == null) { 1862 | throw "No such debugger variable $." + field; 1863 | } 1864 | return value; 1865 | case ExpressionEnum.DebuggerFields: 1866 | return varSrc.dbgVars; 1867 | case ExpressionEnum.ArrayRef(value, index): 1868 | var arr : Array = cast value; 1869 | if (index >= arr.length) { 1870 | throw "Out-of-bounds array reference"; 1871 | } 1872 | return arr[index]; 1873 | case ExpressionEnum.StackRef(name): 1874 | return getStackValue(name, varSrc); 1875 | } 1876 | } 1877 | 1878 | // This is some really hairy stuff. This converts an ElementEnum into 1879 | // an ExpressionEnum, which means that any values must be resolved and any 1880 | // function calls must be made, and that can be really complicated 1881 | private static function getExpression(e : ElementEnum, 1882 | varSrc : DbgVarSrc) : ExpressionEnum 1883 | { 1884 | switch (e) { 1885 | case ElementEnum.Value(value, value_begin, value_end): 1886 | // Resolve the value and make a Value out of it 1887 | return ExpressionEnum.Value(resolveValue(value, varSrc)); 1888 | 1889 | case ElementEnum.Array_Value(array): 1890 | // Compose an array and convert all of the array elements into it 1891 | var arr : Array = new Array(); 1892 | for (element in array) { 1893 | // Convert the element into a value and push it onto the array 1894 | arr.push(getElementValue(element, varSrc)); 1895 | } 1896 | // The array is the Value 1897 | return ExpressionEnum.Value(arr); 1898 | 1899 | case ElementEnum.Field(element, field, field_begin, field_end): 1900 | // Convert the element directly into a value and make a field 1901 | // ref out of it and the field name 1902 | return ExpressionEnum.FieldRef(getElementValue(element, varSrc), 1903 | field); 1904 | 1905 | case ElementEnum.DebuggerField(field, field_begin, field_end): 1906 | return ExpressionEnum.DebuggerFieldRef(field); 1907 | 1908 | case ElementEnum.DebuggerFields: 1909 | return ExpressionEnum.DebuggerFields; 1910 | 1911 | case ElementEnum.Array_Element(element, index): 1912 | // Get the array 1913 | var array = getElementValue(element, varSrc); 1914 | var indexValue = getElementValue(index, varSrc); 1915 | switch (Type.typeof(indexValue)) { 1916 | case TInt: 1917 | // Convert the element directly into a value and make an array 1918 | // ref out of it and the index 1919 | return ExpressionEnum.ArrayRef 1920 | (getElementValue(element, varSrc), cast indexValue); 1921 | default: 1922 | throw "Non-integer array index"; 1923 | } 1924 | 1925 | case ElementEnum.Function_Call(element, parameters): 1926 | // o will be the object to call the function on 1927 | var o : Dynamic = null; 1928 | // func will be the function to call 1929 | var func : Dynamic = null; 1930 | switch (element) { 1931 | case ElementEnum.Value(value, value_begin, value_end): 1932 | throw "Cannot call a function on a value"; 1933 | case ElementEnum.Array_Value(array): 1934 | throw "Cannot call a function on an array"; 1935 | case ElementEnum.Field(inner_element, field, 1936 | field_begin, field_end): 1937 | // The function to call is a field of an object. Functions 1938 | // which are fields of objects take the object themselves as 1939 | // the object to call the function on 1940 | o = getElementValue(inner_element, varSrc); 1941 | func = Reflect.getProperty(o, field); 1942 | case ElementEnum.DebuggerField(field, field_begin, field_end): 1943 | // The function to call is a field of the special debugger 1944 | // variable hash table; it cannot take an "this" parameter 1945 | func = getElementValue(element, varSrc); 1946 | case ElementEnum.DebuggerFields: 1947 | throw "Cannot call a function on $"; 1948 | case ElementEnum.Array_Element(inner_element, index): 1949 | // Function calls inside arrays do not take any "this" 1950 | // parameter so leave o as null 1951 | func = getElementValue(element, varSrc); 1952 | case ElementEnum.Function_Call(inner_element, inner_parameters): 1953 | // Functions returned function calls cannot take a "this" 1954 | // parameter so leave o as null 1955 | func = getElementValue(element, varSrc); 1956 | case ElementEnum.Constructor_Call(class_name, parameters, 1957 | class_name_begin, class_name_end): 1958 | // There is no way that an object created by a constructor 1959 | // can be used as a function 1960 | throw "Cannot call a function on a constructed object"; 1961 | case ElementEnum.Path(path, path_begin, path_end): 1962 | func = getElementValue(element, varSrc); 1963 | } 1964 | if ((func == null) || !Reflect.isFunction(func)) { 1965 | throw "No such function"; 1966 | } 1967 | // Convert the args into Dynamics from their ElementEnum form 1968 | var args : Array = new Array(); 1969 | for (p in parameters) { 1970 | args.push(getElementValue(p, varSrc)); 1971 | } 1972 | return ExpressionEnum.Value(Reflect.callMethod(o, func, args)); 1973 | 1974 | case ElementEnum.Constructor_Call(class_name, parameters, 1975 | class_name_begin, class_name_end): 1976 | var klass = Type.resolveClass(class_name); 1977 | if (klass == null) { 1978 | throw "Cannot resolve class " + class_name; 1979 | } 1980 | var args : Array = new Array(); 1981 | for (p in parameters) { 1982 | args.push(getElementValue(p, varSrc)); 1983 | } 1984 | return ExpressionEnum.Value(Type.createInstance(klass, args)); 1985 | 1986 | case ElementEnum.Path(path, path_begin, path_end): 1987 | return resolvePath(path, varSrc); 1988 | } 1989 | } 1990 | 1991 | private static function getStackValue(name : String, 1992 | varSrc : DbgVarSrc) : Dynamic 1993 | { 1994 | var value : Dynamic = Debugger.getStackVariableValue 1995 | (varSrc.threadNumber, varSrc.stackFrame, name, varSrc.unsafe); 1996 | 1997 | if (value == Debugger.THREAD_NOT_STOPPED) { 1998 | throw value; 1999 | } 2000 | 2001 | if (value == Debugger.NONEXISTENT_VALUE) { 2002 | throw value; 2003 | } 2004 | 2005 | return value; 2006 | } 2007 | 2008 | private static function join(arr : Array, sep : String, 2009 | begin : Int, end : Int) : String 2010 | { 2011 | var ret = ""; 2012 | 2013 | for (i in begin ... end) { 2014 | ret += arr[i] + sep; 2015 | } 2016 | 2017 | if (end < arr.length) { 2018 | ret += arr[end]; 2019 | } 2020 | 2021 | return ret; 2022 | } 2023 | 2024 | // The path element comes at the beginning of the expression. Resolve 2025 | // it into the possible types that it can be (a string, number, boolean, 2026 | // etc constant, or a class reference, or a stack variable reference, or a 2027 | // field reference of a class or stack variable). 2028 | private static function resolvePath(path : String, 2029 | varSrc : DbgVarSrc) : ExpressionEnum 2030 | { 2031 | var arr = path.split("."); 2032 | 2033 | // Try to resolve to the magic debugger variable container or a 2034 | // reference to a magic debugger variable debugger 2035 | 2036 | if (arr[0] == "$") { 2037 | if (arr.length == 1) { 2038 | return ExpressionEnum.DebuggerFields; 2039 | } 2040 | if (arr.length == 2) { 2041 | return ExpressionEnum.DebuggerFieldRef(arr[1]); 2042 | } 2043 | var value : Dynamic = varSrc.dbgVars.get(arr[1]); 2044 | if (value == null) { 2045 | throw "No value"; 2046 | } 2047 | var result = resolveField(value, arr, 2); 2048 | if (result == null) { 2049 | throw "No value " + path; 2050 | } 2051 | return result; 2052 | } 2053 | 2054 | // Try to resolve to a constant 2055 | 2056 | try { 2057 | return ExpressionEnum.Value(resolveConstant(path)); 2058 | } 2059 | catch (e : Dynamic) { 2060 | // It wasn't a constant; proceed 2061 | } 2062 | 2063 | // Try to resolve to a static class or static class field 2064 | 2065 | for (index in 0 ... arr.length) { 2066 | var klass = Type.resolveClass(join(arr, ".", 0, index)); 2067 | if (klass == null) { 2068 | continue; 2069 | } 2070 | if (index == (arr.length - 1)) { 2071 | // There's no value to look for, the path was to a class 2072 | return ExpressionEnum.Value(klass); 2073 | } 2074 | // Try to find the static value within the class 2075 | var result = resolveField(klass, arr, index + 1); 2076 | if (result != null) { 2077 | return result; 2078 | } 2079 | } 2080 | 2081 | // Try to resolve to a stack field reference 2082 | var value : Dynamic = Debugger.getStackVariableValue 2083 | (varSrc.threadNumber, varSrc.stackFrame, arr[0], varSrc.unsafe); 2084 | 2085 | // Can't resolve fields on running threads. 2086 | if (value == Debugger.THREAD_NOT_STOPPED) { 2087 | throw value; 2088 | } 2089 | 2090 | var resolveIndex : Int = 0; 2091 | if (value == Debugger.NONEXISTENT_VALUE) { 2092 | // Try to get it as a field of "this" 2093 | value = Debugger.getStackVariableValue 2094 | (varSrc.threadNumber, varSrc.stackFrame, "this", 2095 | varSrc.unsafe); 2096 | if (value == Debugger.THREAD_NOT_STOPPED) { 2097 | throw value; 2098 | } 2099 | else if (value == Debugger.NONEXISTENT_VALUE) { 2100 | throw value; 2101 | } 2102 | 2103 | resolveIndex = 0; 2104 | } 2105 | // Else got the value 2106 | else { 2107 | // If there was no field reference, then it's just the stack value 2108 | if (arr.length == 1) { 2109 | return ExpressionEnum.StackRef(arr[0]); 2110 | } 2111 | 2112 | resolveIndex = 1; 2113 | } 2114 | 2115 | if (value == null) { 2116 | throw "Null dereference " + arr[0]; 2117 | } 2118 | 2119 | var result = resolveField(value, arr, resolveIndex); 2120 | 2121 | if (result == null) { 2122 | throw "No value for " + path; 2123 | } 2124 | 2125 | return result; 2126 | } 2127 | 2128 | private static function resolveField(value : Dynamic, arr : Array, 2129 | index : Int) : Null 2130 | { 2131 | var klass : Class; 2132 | switch (Type.typeof(value)) { 2133 | // TObject means that value is already a Class 2134 | case TObject: 2135 | klass = value; 2136 | case TClass(c): 2137 | klass = Type.getClass(value); // Kills the process if value is empty 2138 | default: 2139 | // The remaining types cannot have fields. 2140 | return null; 2141 | } 2142 | 2143 | var found = false; 2144 | 2145 | // Check the properties list first, which checks all instance vars. 2146 | found = null != Reflect.getProperty(value, arr[index]); 2147 | 2148 | // Now check the static variables. Have to check superclasses, too. 2149 | while (!found && null != klass) { 2150 | // The Type.getXXXFields can return null. 2151 | var cFields = Type.getClassFields(klass); 2152 | if (null != cFields) { 2153 | for (f in cFields) { 2154 | if (f == arr[index]) { 2155 | found = true; 2156 | break; 2157 | } 2158 | } 2159 | } 2160 | klass = Type.getSuperClass(klass); 2161 | } 2162 | 2163 | if (index == (arr.length - 1)) { 2164 | return ExpressionEnum.FieldRef(value, arr[index]); 2165 | } 2166 | 2167 | value = Reflect.getProperty(value, arr[index]); 2168 | 2169 | if (value == null) { 2170 | throw "Null value dereference " + join(arr, ".", 0, index); 2171 | } 2172 | 2173 | return resolveField(value, arr, index + 1); 2174 | } 2175 | 2176 | // Throws an exception if the value cannot be resolved to a constant 2177 | private static function resolveConstant(value : String) : Dynamic 2178 | { 2179 | // String constant 2180 | if (value.charAt(0) == "\"") { 2181 | return value.substring(1, value.length - 1); 2182 | } 2183 | // Bool constant 2184 | else if (value == "true") { 2185 | return true; 2186 | } 2187 | else if (value == "false") { 2188 | return false; 2189 | } 2190 | else if (value == "null") { 2191 | return null; 2192 | } 2193 | // Int/Float constant 2194 | else if (gNumberRegex.match(value)) { 2195 | if (gNumberRegex.matched(1).length > 0) { 2196 | return Std.parseFloat(value); 2197 | } 2198 | else { 2199 | return Std.parseInt(value); 2200 | } 2201 | } 2202 | 2203 | throw "Not a constant"; 2204 | } 2205 | 2206 | // Convert a constant String, Int, Float, Bool, or null, or a stack 2207 | // variable reference, into a value 2208 | private static function resolveValue(value : String, 2209 | varSrc : DbgVarSrc) : Dynamic 2210 | { 2211 | try { 2212 | return resolveConstant(value); 2213 | } 2214 | catch (e : Dynamic) { 2215 | // It wasn't a constant; proceed 2216 | } 2217 | 2218 | // Can only be a stack reference otherwise 2219 | return getStackValue(value, varSrc); 2220 | } 2221 | 2222 | private static var gNumberRegex = ~/^-?[0-9]*(\.?)[0-9]*$/; 2223 | } 2224 | 2225 | 2226 | // This is a single parsed element from a Haxe value expression 2227 | private enum ElementEnum 2228 | { 2229 | // 2230 | // value_begin is the column of the first character of the value 2231 | // value_end is the column of the last character of the value 2232 | Value(value : String, value_begin : Int, value_end : Int); 2233 | // [ , , ... ] 2234 | Array_Value(array : Array); 2235 | // .field 2236 | // field_begin is the column of the first character of the field name 2237 | // field_end is the column of the last character of the field name 2238 | Field(element : ElementEnum, field : String, 2239 | field_begin : Int, field_end : Int); 2240 | // $. 2241 | // field_begin is the column of the first character of the field name 2242 | // field_end is the column of the last character of the field name 2243 | DebuggerField(field : String, field_begin : Int, field_end : Int); 2244 | // $ 2245 | DebuggerFields; 2246 | // [index] 2247 | Array_Element(element : ElementEnum, index : ElementEnum); 2248 | // (, , ...) 2249 | Function_Call(element : ElementEnum, parameters : Array); 2250 | // new (, , ...) 2251 | // class_name_begin is the column of the first character of the class name 2252 | // class_name_end is the column of the last character of the class name 2253 | Constructor_Call(class_name : String, parameters : Array, 2254 | class_name_begin : Int, class_name_end : Int); 2255 | // This is . appearing only at the very beginning of the 2256 | // string. 2257 | Path(path : String, path_begin : Int, path_end : Int); 2258 | } 2259 | 2260 | 2261 | // Representation of an Expression 2262 | private enum ExpressionEnum 2263 | { 2264 | Value(value : Dynamic); 2265 | FieldRef(value : Dynamic, field : String); 2266 | DebuggerFieldRef(field : String); 2267 | DebuggerFields; 2268 | ArrayRef(value : Dynamic, index : Int); 2269 | StackRef(name : String); 2270 | } 2271 | 2272 | 2273 | // This is an exception thrown when a parsing error is encountered 2274 | private class ElementException 2275 | { 2276 | // Ths is the reason for the error 2277 | public var reason(default, null) : String; 2278 | // This is the column of the first character of the invalid part of the 2279 | // expression 2280 | public var begin(default, null) : Int; 2281 | // This is the column of the last character of the invalid part of the 2282 | // expression 2283 | public var end(default, null) : Int; 2284 | 2285 | public function new(reason: String, begin : Int, end : Int) 2286 | { 2287 | this.reason = reason; 2288 | this.begin = begin; 2289 | this.end = end; 2290 | } 2291 | } 2292 | 2293 | 2294 | // This helper class handles parsing of Haxe expressions 2295 | private class ElementParser 2296 | { 2297 | // This parser is implemented to parse the input string in reverse. This 2298 | // is a smaller and simpler implementation than a forward parser but it 2299 | // does mean that some of the errors that it reports are less intuitive. 2300 | public static function parse(str : String, beginColumn : Int) : ElementEnum 2301 | { 2302 | // Look for the last of "\"", ".", "]", or ")" 2303 | var endIndex = maxIndexOf(str, str.length - 1, "\".])"); 2304 | 2305 | // If no index, then treat it as a path 2306 | if (endIndex == -1) { 2307 | var value = StringTools.trim(str); 2308 | var endColumn = beginColumn + (str.length - 1); 2309 | if (value.length == 0) { 2310 | if (endColumn < beginColumn) { 2311 | endColumn = beginColumn; 2312 | } 2313 | throw new ElementException 2314 | ("Empty value", beginColumn, endColumn); 2315 | } 2316 | return ElementEnum.Path(value, beginColumn, endColumn); 2317 | } 2318 | 2319 | switch (str.charAt(endIndex)) { 2320 | case "\"": 2321 | var beginIndex = findBeginQuote(str, endIndex - 1); 2322 | if (beginIndex == -1) { 2323 | throw new ElementException 2324 | ("Mismatched quotes", beginColumn + endIndex, 2325 | beginColumn + (str.length - 1)); 2326 | } 2327 | if (StringTools.trim(str.substring(0, beginIndex)).length > 0) { 2328 | throw new ElementException 2329 | ("Unexpected value before quotes", 2330 | beginColumn, beginColumn + beginIndex); 2331 | } 2332 | return ElementEnum.Value 2333 | (str.substring(beginIndex, endIndex + 1), 2334 | beginColumn + beginIndex, beginColumn + endIndex); 2335 | case ".": 2336 | // Check to see if it's a floating point value 2337 | if (gFloatRegex.match(str)) { 2338 | // Is it a valid float? 2339 | if ((gFloatRegex.matched(2).length == 0) && 2340 | (gFloatRegex.matched(3).length == 0)) { 2341 | throw new ElementException 2342 | ("Invalid value", beginColumn, 2343 | beginColumn + (str.length - 1)); 2344 | } 2345 | // Yes, it's a valid floating point value 2346 | return ElementEnum.Value(StringTools.trim(str), beginColumn, 2347 | beginColumn + (str.length - 1)); 2348 | } 2349 | // Ensure that there is something after the . 2350 | var field = StringTools.trim(str.substring(endIndex + 1)); 2351 | if (field.length == 0) { 2352 | throw new ElementException 2353 | ("Missing field", beginColumn + endIndex, 2354 | beginColumn + endIndex); 2355 | } 2356 | // Ensure that there is something before the . 2357 | var pre = StringTools.trim(str.substring(0, endIndex)); 2358 | if (pre.length == 0) { 2359 | throw new ElementException 2360 | ("Missing value", beginColumn, beginColumn + endIndex); 2361 | } 2362 | // Check for a special debugger construct 2363 | if (pre == "$") { 2364 | return ElementEnum.DebuggerField 2365 | (field, 0, (str.length - 1)); 2366 | } 2367 | // Check to see if it's a path. This can only occur at the 2368 | // very beginning of the string 2369 | if (beginColumn == 0) { 2370 | // that allows for a local value storage pool 2371 | if (gPathRegex.match(str)) { 2372 | // OK, this is at the very beginning of the input string 2373 | // and should be represented as a Path. Check to ensure 2374 | // that it is a valid path. 2375 | return ElementEnum.Path 2376 | (StringTools.trim(str), 0, str.length - 1); 2377 | } 2378 | } 2379 | // Else, it's not a path, it's a normal field reference 2380 | if (gBackendFieldRegex.match(field)) { 2381 | var element = parse(str.substring(0, endIndex), beginColumn); 2382 | return ElementEnum.Field(element, field, beginColumn, 2383 | beginColumn + (str.length - 1)); 2384 | } 2385 | else { 2386 | throw new ElementException 2387 | ("Invalid field name", beginColumn + endIndex + 1, 2388 | beginColumn + (str.length - 1)); 2389 | } 2390 | case "]": 2391 | var beginIndex = levelPreviousIndexOf 2392 | (str, endIndex - 1, "[", beginColumn); 2393 | if (beginIndex == -1) { 2394 | throw new ElementException 2395 | ("Mismatched array end", beginColumn, 2396 | beginColumn + endIndex); 2397 | } 2398 | if (StringTools.trim(str.substring(endIndex + 1)).length > 0) { 2399 | throw new ElementException 2400 | ("Unexpected value after array index", 2401 | beginColumn + endIndex + 1, 2402 | beginColumn + (str.length - 1)); 2403 | } 2404 | // If there is nothing before the open bracket, then this is 2405 | // an array 2406 | if (StringTools.trim(str.substring(0, beginIndex)).length == 0) { 2407 | var array = parseList 2408 | (str.substring(beginIndex + 1, endIndex), 2409 | beginColumn + beginIndex + 1); 2410 | return ElementEnum.Array_Value(array); 2411 | } 2412 | // Else it's an array dereference 2413 | else { 2414 | var element = parse(str.substring(0, beginIndex), beginColumn); 2415 | var index = parse(str.substring(beginIndex + 1, 2416 | endIndex), beginColumn); 2417 | return ElementEnum.Array_Element(element, index); 2418 | } 2419 | default: // ")" 2420 | var beginIndex = levelPreviousIndexOf 2421 | (str, endIndex - 1, "(", beginColumn); 2422 | if (beginIndex == -1) { 2423 | throw new ElementException 2424 | ("Mismatched function parameters begin", beginColumn, 2425 | beginColumn + endIndex); 2426 | } 2427 | if (StringTools.trim(str.substring(endIndex + 1)).length > 0) { 2428 | throw new ElementException 2429 | ("Unexpected value after function call", 2430 | beginColumn + endIndex + 1, 2431 | beginColumn + (str.length - 1)); 2432 | } 2433 | var parameters = parseList(str.substring(beginIndex + 1, 2434 | endIndex), 2435 | beginColumn + beginIndex + 1); 2436 | var value = StringTools.trim(str.substring(0, beginIndex)); 2437 | if (StringTools.startsWith(value, "new ")) { 2438 | if (value.length < 5) { 2439 | throw new ElementException 2440 | ("Invalid constructor call", 0, beginIndex); 2441 | } 2442 | return ElementEnum.Constructor_Call 2443 | (value.substring(4), parameters, beginColumn, 2444 | beginColumn + beginIndex); 2445 | } 2446 | else { 2447 | var element = parse(value, beginColumn); 2448 | return ElementEnum.Function_Call(element, parameters); 2449 | } 2450 | } 2451 | } 2452 | 2453 | private static function parseList(str : String, 2454 | beginColumn : Int) : Array 2455 | { 2456 | var ret = new Array(); 2457 | 2458 | var index = str.length - 1; 2459 | 2460 | while (index >= 0) { 2461 | var current : String; 2462 | var commaIndex = levelPreviousIndexOf(str, index, ",", beginColumn); 2463 | if (commaIndex == -1) { 2464 | current = StringTools.trim(str); 2465 | if (current.length == 0) { 2466 | return ret; 2467 | } 2468 | str = ""; 2469 | } 2470 | else if (commaIndex == 0) { 2471 | throw new ElementException 2472 | ("Missing array element", beginColumn, 2473 | beginColumn + commaIndex); 2474 | } 2475 | else { 2476 | current = StringTools.trim(str.substring(commaIndex + 1)); 2477 | if (current.length == 0) { 2478 | throw new ElementException 2479 | ("Missing array element", beginColumn + commaIndex + 1, 2480 | beginColumn + commaIndex + 1); 2481 | } 2482 | str = str.substring(0, commaIndex); 2483 | } 2484 | 2485 | ret.unshift(parse(current, beginColumn)); 2486 | } 2487 | 2488 | return ret; 2489 | } 2490 | 2491 | private static function maxIndexOf(str : String, index : Int, 2492 | find : String) : Int 2493 | { 2494 | while (index >= 0) { 2495 | var c = str.charAt(index); 2496 | for (f in 0...find.length) { 2497 | if (c == find.charAt(f)) { 2498 | return index; 2499 | } 2500 | } 2501 | index -= 1; 2502 | } 2503 | return -1; 2504 | } 2505 | 2506 | private static function findBeginQuote(str : String, index : Int) : Int 2507 | { 2508 | while (index >= 0) { 2509 | var quoteIndex = str.lastIndexOf("\"", index); 2510 | // Count backslashes before quotes 2511 | var slashCount = 0; 2512 | var si : Int = quoteIndex - 1; 2513 | while ((si >= 0) && (str.charAt(si) == "\\")) { 2514 | slashCount += 1; 2515 | si -= 1; 2516 | } 2517 | // If there are an even number of slashes, then the quote 2518 | // is not escaped 2519 | if ((slashCount % 2) == 0) { 2520 | return quoteIndex; 2521 | } 2522 | // Else the quote is escaped 2523 | else { 2524 | index = quoteIndex - 1; 2525 | } 2526 | } 2527 | return -1; 2528 | } 2529 | 2530 | private static function levelPreviousIndexOf(str : String, index : Int, 2531 | find : String, 2532 | beginColumn : Int) : Int 2533 | { 2534 | var bracketLevel = 0, parenLevel = 0; 2535 | 2536 | while (index >= 0) { 2537 | var char = str.charAt(index); 2538 | if ((char == find) && (bracketLevel == 0) && (parenLevel == 0)) { 2539 | return index; 2540 | } 2541 | else if (char == "]") { 2542 | bracketLevel += 1; 2543 | } 2544 | else if (char == "[") { 2545 | bracketLevel -= 1; 2546 | } 2547 | else if (char == ")") { 2548 | parenLevel += 1; 2549 | } 2550 | else if (char == "(") { 2551 | parenLevel -= 1; 2552 | } 2553 | else if (char == "\"") { 2554 | var beginQuote = findBeginQuote(str, index - 1); 2555 | if (beginQuote == -1) { 2556 | throw new ElementException 2557 | ("Mismatched quotes", 0, beginColumn + index); 2558 | } 2559 | index = beginQuote; 2560 | } 2561 | index -= 1; 2562 | } 2563 | 2564 | return -1; 2565 | } 2566 | 2567 | // The hxcpp backend adds some fields that include spaces, such as 2568 | // "assertion failed", "critical assertion failed", 2569 | // "external assertion failed" 2570 | private static var gBackendFieldRegex = ~/^[\s]*([a-zA-Z_][a-zA-Z0-9_\s]*)[\s]*$/; 2571 | private static var gFieldRegex = ~/^[\s]*([a-zA-Z_][a-zA-Z0-9_]*)[\s]*$/; 2572 | private static var gPathRegex = 2573 | ~/^[\s]*[a-zA-Z_][a-zA-Z0-9_]*([\s]*\.[\s]*[a-zA-Z_][a-zA-Z0-9_]*)*[\s]*$/; 2574 | private static var gFloatRegex = ~/^[\s]*(-)?([0-9]*)\.([0-9]*)[\s]*$/; 2575 | } 2576 | 2577 | 2578 | /** 2579 | * The DebugLog class was developed to ease Sys.println style debugging in 2580 | * the debugger itself. Use it like so: 2581 | * 2582 | * DebugLog.log(1,"Some text"); 2583 | * 2584 | * You can package values into an array in order to print variables without 2585 | * forcing the type to be inferred prior to their actual use: 2586 | * 2587 | * DebugLog.log(1,["var = ", myvar, " array=", ary]); 2588 | * 2589 | * Note that values in arrays are catenated into a single string. If you 2590 | * desire to see the normal haxe array output, use the slightly less efficient: 2591 | * 2592 | * DebugLog.log(1,["ary = ", Std.string(ary)]); 2593 | * 2594 | * To print an exception, including the stack where it occurred, call: 2595 | * 2596 | * DebugLog.logException(1, exception, Callstack.exceptionStack()); 2597 | * 2598 | * To get indented logs, based upon the call stack depth, use setStackAnchor 2599 | * in the function that is your starting point: 2600 | * 2601 | * DebugLog.setStackAnchor(); 2602 | * 2603 | * To programmatically control the level of logging or disable it altogether: 2604 | * 2605 | * DebugLog.gDebugLevel = 0; // 0 Disables 2606 | * 2607 | */ 2608 | private class DebugLog { 2609 | 2610 | /** 2611 | * Log a message to the console log. 2612 | * 2613 | * @level - Minimum log level at which this message will be logged. 2614 | * @msg -- Message to display. Msg will be converted to a string. If 2615 | * msg is an array, then the values of the array will be converted 2616 | * to Strings and catenated into a single message. 2617 | */ 2618 | public static function log(level : Int, msg : Dynamic) : Void { 2619 | logUsingPreviousFrame(level, 1, msg); 2620 | } 2621 | 2622 | 2623 | /** 2624 | * Log a message to the console log. 2625 | * 2626 | * Always skips itself, so the number of frames to skip is from the 2627 | * caller's perspective. (1 for itself, and more if needed.) 2628 | * 2629 | * @level - Minimum log level at which this message will be logged. 2630 | * @framesToSkip - Number of frames to skip for determining function to 2631 | * be logged against. 2632 | * @msg -- Message to display. Msg will be converted to a string. If 2633 | * msg is an array, then the values of the array will be converted 2634 | * to Strings and catenated into a single message. 2635 | */ 2636 | public static function logUsingPreviousFrame(level : Int, 2637 | framesToSkip : Int, 2638 | msg : Dynamic) : Void 2639 | { 2640 | if (checkLevel(level) && null != msg) { 2641 | var buf : StringBuf = new StringBuf(); 2642 | buf.add(getIndentPrefix(1)); 2643 | buf.add(getCallerName(framesToSkip + 1));// Skip ourselves. 2644 | buf.add(": "); 2645 | buf.add(convertValueToString(msg)); 2646 | Sys.println(buf.toString()); 2647 | } 2648 | } 2649 | 2650 | /** 2651 | * Log an exception and, optionally, it's call stack. 2652 | * 2653 | * We need to append the exceptionStack manually because calling into new 2654 | * functions messes with the exception stack, (Callstack.exceptionStack() 2655 | * returns the stack between the current code position and the last thrown 2656 | * exception,) thus we get an incorrect stack if we try to detect it from 2657 | * anywhere except within the function that caught the exception. 2658 | * 2659 | * @level - minimum debug level at which the exception will be logged. 2660 | * @exception - exception to log. 2661 | * @stack - callstack to display (usually, CallStack.exceptionStack()) 2662 | */ 2663 | public static function logException(level : Int, exception : Dynamic, 2664 | ?stack : Array ) : Void { 2665 | if (null == exception) { 2666 | log(1,"Internal error: No exception presented to log. Called from " 2667 | + getCallerName(1)); 2668 | return; 2669 | } 2670 | if (checkLevel(level) && null!= exception) { 2671 | if (null != stack) { 2672 | stack.reverse(); // For some reason, it's in reverse order. 2673 | } 2674 | 2675 | var buf : StringBuf = new StringBuf(); 2676 | buf.add(getIndentPrefix(1)); 2677 | buf.add(getCallerName(1)); 2678 | buf.add(": Caught exception: "); 2679 | buf.add(convertValueToString(exception)); 2680 | buf.add(CallStack.toString(stack)); 2681 | Sys.println(buf.toString()); 2682 | } 2683 | } 2684 | 2685 | public static function setStackAnchor() { 2686 | gIndentAnchor = getCaller(1); 2687 | } 2688 | 2689 | public static function clearStackAnchor() { 2690 | gIndentAnchor = null; 2691 | } 2692 | 2693 | private static function checkLevel(level : Int) : Bool { 2694 | return (0 <= level && level <= gDebugLevel); 2695 | } 2696 | 2697 | private static function getIndentPrefix(framesToSkip : Int) : String { 2698 | var stack: Array = CallStack.callStack(); 2699 | var found: Bool = false; 2700 | var prefix : StringBuf = new StringBuf(); 2701 | for (i in (framesToSkip + 1) ... stack.length) { 2702 | if (compareStackItems(stack[i], gIndentAnchor)) { 2703 | found = true; 2704 | break; 2705 | } 2706 | prefix.add(" "); 2707 | } 2708 | return found ? prefix.toString() : ""; 2709 | } 2710 | 2711 | private static function compareStackItems(lFrame : StackItem, 2712 | rFrame : StackItem) : Bool { 2713 | if (null == lFrame || null == rFrame) { 2714 | return false; 2715 | } 2716 | 2717 | var equal : Bool = false; 2718 | if (Type.enumConstructor(lFrame) == Type.enumConstructor(rFrame)){ 2719 | var rParams : Array = Type.enumParameters(rFrame); 2720 | switch(lFrame) { 2721 | case CFunction: 2722 | equal = true; // Not much we can do with this. 2723 | case Module(m): 2724 | equal = m == rParams[0]; 2725 | case FilePos(stackitem, file, line): 2726 | equal = compareStackItems(stackitem, rParams[0]); 2727 | case Method(classname, method): 2728 | equal = classname == rParams[0] && method == rParams[1]; 2729 | case LocalFunction(v): 2730 | equal = v == rParams[0]; 2731 | } 2732 | } 2733 | return equal; 2734 | } 2735 | 2736 | private static function stackItemToName(frame : StackItem) : String { 2737 | var name : String; 2738 | 2739 | switch(frame) { 2740 | case CFunction: 2741 | name = ""; 2742 | case Module(m): 2743 | name = m; 2744 | case FilePos(stackitem, file, line): 2745 | if (stackitem != null) { 2746 | name = stackItemToName(stackitem); 2747 | } else { 2748 | name = "(" + file + "," + line + ")"; 2749 | } 2750 | case Method(classname, method): 2751 | name = method; 2752 | case LocalFunction(v): 2753 | name = ""; 2754 | } 2755 | return name; 2756 | } 2757 | 2758 | private static function getCallerName(framesToSkip : Int) : String { 2759 | var callerName = ""; 2760 | var callerFrame = getCaller(framesToSkip+1); 2761 | if (null != callerFrame) { 2762 | try { 2763 | callerName = stackItemToName(callerFrame); 2764 | } catch (e: Dynamic) { 2765 | // Ignore it. 2766 | } 2767 | } 2768 | return callerName; 2769 | } 2770 | 2771 | private static function getCaller(framesToSkip : Int) : StackItem { 2772 | var stack : Array = CallStack.callStack(); 2773 | 2774 | // Always ignore this function. 2775 | framesToSkip += 1; 2776 | 2777 | var targetFrame = framesToSkip; // stack.start + framesToSkip 2778 | if (targetFrame >= 0 && targetFrame < stack.length) { 2779 | try { 2780 | return stack[targetFrame]; 2781 | } catch (e:Dynamic) { 2782 | // Ignore it. 2783 | } 2784 | } 2785 | return null; 2786 | } 2787 | 2788 | /** 2789 | * Converts values to strings. Array entries are catenated. 2790 | */ 2791 | private static function convertValueToString(value : Dynamic) : String { 2792 | var v : StringBuf = new StringBuf(); 2793 | v.add(""); 2794 | try { 2795 | var iter : Iterator = getIterator(value); 2796 | if (null != iter) { 2797 | while (iter.hasNext()) { 2798 | v.add(convertValueToString(iter.next())); 2799 | } 2800 | } else { 2801 | v.add(Std.string(value)); 2802 | } 2803 | } catch ( e : Dynamic ) { 2804 | v.add(""); 2807 | } 2808 | return v.toString(); 2809 | } 2810 | 2811 | /** 2812 | * Check to see if an item is an iterable value, such as an array. 2813 | * 2814 | * @returns true if the value supports an Iterable or Iterator 2815 | * interface, though this function cannot guarantee that 2816 | * the functions actually take the proper type and number of 2817 | * arguments. 2818 | */ 2819 | private static function getIterator(value) : Iterator { 2820 | if (null == value) { 2821 | return null; 2822 | } 2823 | try { 2824 | var fld = Reflect.field(value, "iterator"); 2825 | if (null != fld && Reflect.isFunction(fld)) { 2826 | return value.iterator(); 2827 | } 2828 | 2829 | fld = Reflect.field(value, "next"); 2830 | if (null != fld && Reflect.isFunction(fld)) { 2831 | fld = Reflect.field(value, "hasNext"); 2832 | if (null != fld && Reflect.isFunction(fld)) { 2833 | return value; 2834 | } 2835 | } 2836 | } catch (e : Dynamic) { 2837 | log(4, "Exception detecting Iterator"); 2838 | // Ignore it. 2839 | } 2840 | return null; 2841 | } 2842 | 2843 | public static var gDebugLevel = 3; 2844 | public static var gIndentAnchor : StackItem; 2845 | } 2846 | 2847 | private class DebugTimer { 2848 | public function new(logLevel : Int = 1, ?message : Dynamic) : Void { 2849 | mLogLevel = logLevel; 2850 | mMessage = message; 2851 | mStartTime = Date.now().getTime(); 2852 | } 2853 | 2854 | public function logDuration(?msg : Dynamic) : Void { 2855 | var now = Date.now().getTime(); 2856 | var m : Array = 2857 | ["Duration: ", now - mStartTime, " : ", mMessage]; 2858 | if (null != msg) { 2859 | m.push(msg); 2860 | } 2861 | DebugLog.logUsingPreviousFrame(mLogLevel, 1, m); 2862 | } 2863 | 2864 | public function logStart(?msg : Dynamic) : Void { 2865 | var m : Array = ["Starting timer ", mMessage]; 2866 | if (null != msg) { 2867 | m.push(msg); 2868 | } 2869 | DebugLog.logUsingPreviousFrame(mLogLevel, 1, m); 2870 | mStartTime = Date.now().getTime(); 2871 | } 2872 | 2873 | private var mLogLevel : Int; 2874 | private var mStartTime : Float; 2875 | private var mMessage: Dynamic; 2876 | } 2877 | -------------------------------------------------------------------------------- /debugger/HaxeProtocol.hx: -------------------------------------------------------------------------------- 1 | /** ************************************************************************** 2 | * HaxeProtocol.hx 3 | * 4 | * Copyright 2013 TiVo Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | ************************************************************************** **/ 18 | 19 | package debugger; 20 | 21 | import debugger.IController; 22 | 23 | 24 | /** 25 | * This is a helper utility class that handles all message formatting for the 26 | * haxe serialization based remote debugger protocol. 27 | **/ 28 | class HaxeProtocol 29 | { 30 | public static function writeClientIdentification(output : haxe.io.Output) 31 | { 32 | output.writeString(gClientIdentification); 33 | } 34 | 35 | public static function writeServerIdentification(output : haxe.io.Output) 36 | { 37 | output.writeString(gServerIdentification); 38 | } 39 | 40 | public static function readClientIdentification(input : haxe.io.Input) 41 | { 42 | var id = input.read(gClientIdentification.length); 43 | if (id.toString() != gClientIdentification) { 44 | throw "Unexpected client identification string: " + id; 45 | } 46 | } 47 | 48 | public static function readServerIdentification(input : haxe.io.Input) 49 | { 50 | var id = input.read(gServerIdentification.length); 51 | if (id.toString() != gServerIdentification) { 52 | throw "Unexpected server identification string: " + id; 53 | } 54 | } 55 | 56 | public static function writeCommand(output : haxe.io.Output, 57 | command : Command) 58 | { 59 | writeDynamic(output, command); 60 | } 61 | 62 | public static function writeMessage(output : haxe.io.Output, 63 | message : Message) 64 | { 65 | writeDynamic(output, message); 66 | } 67 | 68 | public static function readCommand(input : haxe.io.Input) : Command 69 | { 70 | var raw : Dynamic = readDynamic(input); 71 | 72 | // Convert to command and return 73 | try { 74 | return cast(raw, Command); 75 | } 76 | catch (e : Dynamic) { 77 | throw "Expected Command, but got " + raw + ": " + e; 78 | } 79 | } 80 | 81 | public static function readMessage(input : haxe.io.Input) : Message 82 | { 83 | var raw : Dynamic = readDynamic(input); 84 | 85 | // Convert to command and return 86 | try { 87 | return cast(raw, Message); 88 | } 89 | catch (e : Dynamic) { 90 | throw "Expected Message, but got " + raw + ": " + e; 91 | } 92 | } 93 | 94 | private static function writeDynamic(output : haxe.io.Output, 95 | value : Dynamic) 96 | { 97 | // Serialize it 98 | var string = haxe.Serializer.run(value); 99 | 100 | // Write its length 101 | var msg_len = string.length; 102 | var msg_len_raw = haxe.io.Bytes.alloc(8); 103 | 104 | for (i in 0 ... 8) { 105 | msg_len_raw.set(7 - i, (msg_len % 10) + 48); 106 | msg_len = Std.int(msg_len / 10); 107 | } 108 | 109 | output.write(msg_len_raw); 110 | output.writeString(string); 111 | } 112 | 113 | private static function readDynamic(input : haxe.io.Input) : Dynamic 114 | { 115 | var msg_len_raw = input.read(8); 116 | 117 | // Convert to number 118 | var msg_len : Int = 0; 119 | for (i in 0 ... 8) { 120 | msg_len *= 10; 121 | msg_len += msg_len_raw.get(i) - 48; // 48 is ASCII '0' 122 | } 123 | 124 | // Validate the length. Don't allow messages larger than 125 | // 2 MB. 126 | if (msg_len > (2 * 1024 * 1024)) { 127 | throw "Read bad message length: " + msg_len + "."; 128 | } 129 | 130 | // Read and deserialize message 131 | return haxe.Unserializer.run(input.read(msg_len).toString()); 132 | } 133 | 134 | private static var gClientIdentification = 135 | "Haxe debug client v1.2 coming at you!\n\n"; 136 | private static var gServerIdentification = 137 | "Haxe debug server v1.2 ready and willing, sir!\n\n"; 138 | } 139 | -------------------------------------------------------------------------------- /debugger/HaxeRemote.hx: -------------------------------------------------------------------------------- 1 | /** ************************************************************************** 2 | * HaxeRemote.hx 3 | * 4 | * Copyright 2013 TiVo Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | ************************************************************************** **/ 18 | 19 | package debugger; 20 | 21 | import debugger.HaxeProtocol; 22 | import debugger.IController; 23 | 24 | #if cpp 25 | import cpp.vm.Thread; 26 | import cpp.vm.Mutex; 27 | import cpp.vm.Debugger; 28 | #else 29 | #error "HaxeRemote supported only for cpp targets" 30 | #end 31 | 32 | 33 | /** 34 | * This class creates a networked command-line debugger that communicates with 35 | * a peer using Haxe serialization format. This class should be instantiated 36 | * int the main() function of any program that wishes to be debugged via a 37 | * remote interface. 38 | **/ 39 | class HaxeRemote implements IController 40 | { 41 | /** 42 | * Creates a debugger which will read input from the interface provided 43 | * by the given remote host, and emit output to that remote host. 44 | * 45 | * If the program was not compiled with debugging support, a String 46 | * exception is thrown. 47 | * 48 | * @param startStopped if true, when the breakpoint starts, all other 49 | * threads of the process are stopped until the user instructs the 50 | * debugger to continue those threads. If false, all threads of 51 | * the program will continue to run when the debugger is started 52 | * and will not stop until a debugger command instructs them to do 53 | * so. 54 | * @param host is the host name of the debugging server to connect to 55 | * @param port is the port of the debugging server to connect to 56 | **/ 57 | public function new(startStopped : Bool, host : String, port : Int = 6972) 58 | { 59 | LogDebuggerMessage("Starting App side debugger support."); 60 | 61 | mHost = host; 62 | mPort = port; 63 | mSocket = null; 64 | 65 | // Connect here. We won't reconnect if there is a socket error. 66 | connect(); 67 | 68 | // Spin up the write thread *before* the debugger thread. Otherwise, 69 | // getNextCommand and acceptMessage are called before the write 70 | // thread is ready to queue messages. 71 | mWriteThread = Thread.create(function() {this.writeThreadLoop();}); 72 | 73 | mThread = new DebuggerThread(this, startStopped); 74 | } 75 | 76 | private function closeSocket() { 77 | if (mSocket != null) { 78 | mSocket.close(); 79 | mSocket = null; 80 | } 81 | } 82 | 83 | public function getNextCommand() : Command 84 | { 85 | while (true) { 86 | try { 87 | return HaxeProtocol.readCommand(mSocket.input); 88 | } 89 | catch (e : Dynamic) { 90 | LogDebuggerMessage("Failed to read command from server at " + 91 | mHost + ":" + mPort + ": " + e); 92 | closeSocket(); 93 | throw("Debugger Failure: Error reading socket: " + e); 94 | } 95 | } 96 | } 97 | 98 | /** 99 | * Called when the debugger has a message to deliver. Note that this may 100 | * be called by multiple threads simultaneously if an asynchronous thread 101 | * event occurs. The implementation should probably lock as necessary. 102 | * 103 | * @param message is the message 104 | **/ 105 | public function acceptMessage(message : Message) : Void 106 | { 107 | // We actually write on a separate thread because the write is 108 | // blocking. If we wait on such a write, and the other side of the 109 | // socket is also trying to write, then we deadlock. 110 | 111 | try { 112 | mWriteThread.sendMessage(message); 113 | } 114 | catch(msg : String) { 115 | var outmsg = "Failure sending message to debugger write thread:" 116 | + msg; 117 | LogDebuggerMessage(outmsg); 118 | closeSocket(); 119 | throw(outmsg); 120 | } 121 | } 122 | 123 | private function writeThreadLoop() : Void 124 | { 125 | // Have to turn off debugging on this thread, or else it stops, too. 126 | Debugger.enableCurrentThreadDebugging(false); 127 | 128 | try { 129 | var message : Message; 130 | while (true) { 131 | // Get the next message to write. Block until there is 132 | // something to do. 133 | message = Thread.readMessage(true); 134 | HaxeProtocol.writeMessage(mSocket.output, message); 135 | } 136 | } 137 | catch (msg : String ) { 138 | var errmsg = "An error occurred on the write thread loop " + 139 | "(sending messages to the debugger): " + msg; 140 | LogDebuggerMessage(errmsg); 141 | LogDebuggerMessage("Closing connection."); 142 | closeSocket(); 143 | mWriteThread = null; 144 | // Throwing from here will (should!) finish the thread off. 145 | throw(errmsg); 146 | } 147 | } 148 | 149 | private function connect() 150 | { 151 | var socket : sys.net.Socket = new sys.net.Socket(); 152 | 153 | while (true) { 154 | try { 155 | var host = new sys.net.Host(mHost); 156 | if (host.ip == 0) { 157 | throw "Name lookup error."; 158 | } 159 | socket.connect(host, mPort); 160 | HaxeProtocol.writeClientIdentification(socket.output); 161 | HaxeProtocol.readServerIdentification(socket.input); 162 | LogDebuggerMessage("Connected to debugging server at " + 163 | mHost + ":" + mPort + "."); 164 | 165 | mSocket = socket; 166 | return; 167 | } 168 | catch (e : Dynamic) { 169 | LogDebuggerMessage("Failed to connect to debugging server at " + 170 | mHost + ":" + mPort + " : " + e); 171 | } 172 | closeSocket(); 173 | LogDebuggerMessage("Trying again in 3 seconds."); 174 | Sys.sleep(3); 175 | } 176 | } 177 | 178 | private static function LogDebuggerMessage(message : String) { 179 | Sys.println("Debugger:" + message); 180 | } 181 | 182 | private var mHost : String; 183 | private var mPort : Int; 184 | private var mSocket : sys.net.Socket; 185 | private var mThread : DebuggerThread; 186 | private var mWriteThread : Thread; 187 | } 188 | -------------------------------------------------------------------------------- /debugger/HaxeServer.hx: -------------------------------------------------------------------------------- 1 | /** ************************************************************************** 2 | * HaxeServer.hx 3 | * 4 | * Copyright 2013 TiVo Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | ************************************************************************** **/ 18 | 19 | package debugger; 20 | 21 | import debugger.CommandLineController; 22 | import debugger.HaxeProtocol; 23 | import debugger.IController; 24 | 25 | #if cpp 26 | import cpp.vm.Deque; 27 | import cpp.vm.Thread; 28 | #elseif neko 29 | import neko.vm.Deque; 30 | import neko.vm.Thread; 31 | #else 32 | #error "AdvancedDebuggerServer supported only for cpp and neko targets" 33 | #end 34 | 35 | /** 36 | * This is a standalone program which acts as a debug server speaking with 37 | * clients using Haxe serialization format. To use, start it up (giving an 38 | * optional port number to listen on as the only possible argument). Then run 39 | * a debugging client to connect to it. Now your debug server will send 40 | * commands to and read messages from the remotely being debugged client. 41 | **/ 42 | class HaxeServer 43 | { 44 | public static function main() 45 | { 46 | var host : String = null; 47 | var port : Int = 6972; 48 | 49 | var argv = Sys.args(); 50 | 51 | var iter = 0 ... argv.length; 52 | for (i in iter) { 53 | var arg = argv[i]; 54 | switch (argv[i]) { 55 | case "-host": 56 | if (i == (argv.length - 1)) { 57 | Sys.println("ERROR: -port option requires an argument."); 58 | return -1; 59 | } 60 | else { 61 | i = iter.next(); 62 | host = argv[i]; 63 | } 64 | case "-port": 65 | if (i == (argv.length - 1)) { 66 | Sys.println("ERROR: -port option requires an argument."); 67 | return -1; 68 | } 69 | else { 70 | i = iter.next(); 71 | port = Std.parseInt(argv[i]); 72 | } 73 | default: 74 | Sys.println("ERROR - invalid argument: " + argv[i]); 75 | Sys.println("Usage: HaxeServer [-host (defaults to " + 76 | "0.0.0.0 which means all local interfaces"); 77 | Sys.println(" [-port ] (defaults to " + 78 | "6972"); 79 | Sys.exit(-1); 80 | } 81 | } 82 | 83 | if (host == null) { 84 | host = '0.0.0.0'; 85 | } 86 | 87 | new HaxeServer(new CommandLineController(), host, port); 88 | 89 | return 0; 90 | } 91 | 92 | /** 93 | * Creates a server. This function never returns. 94 | **/ 95 | public function new(controller : IController, host : String, 96 | port : Int) 97 | { 98 | mController = controller; 99 | mSocketQueue = new Deque(); 100 | mCommandQueue = new Deque(); 101 | mReadCommandQueue = new Deque(); 102 | Thread.create(readCommandMain); 103 | 104 | var listenSocket : sys.net.Socket = null; 105 | 106 | while (listenSocket == null) { 107 | listenSocket = new sys.net.Socket(); 108 | try { 109 | listenSocket.bind(new sys.net.Host(host), port); 110 | listenSocket.listen(1); 111 | } 112 | catch (e : Dynamic) { 113 | Sys.println("Failed to bind/listen on " + host + ":" + port + 114 | ": " + e); 115 | Sys.println("Trying again in 3 seconds."); 116 | Sys.sleep(3); 117 | listenSocket.close(); 118 | listenSocket = null; 119 | } 120 | } 121 | 122 | while (true) { 123 | 124 | var socket : sys.net.Socket = null; 125 | 126 | while (socket == null) { 127 | try { 128 | Sys.println("\nListening for client connection on " + 129 | host + ":" + port + " ..."); 130 | socket = listenSocket.accept(); 131 | } 132 | catch (e : Dynamic) { 133 | Sys.println("Failed to accept connection: " + e); 134 | Sys.println("Trying again in 1 second."); 135 | Sys.sleep(1); 136 | } 137 | } 138 | 139 | var peer = socket.peer(); 140 | Sys.println("\nReceived connection from " + peer.host + "."); 141 | 142 | try { 143 | HaxeProtocol.writeServerIdentification(socket.output); 144 | HaxeProtocol.readClientIdentification(socket.input); 145 | } 146 | catch (e : Dynamic) { 147 | Sys.println("Client version not supported."); 148 | Sys.println(e); 149 | // Make sure the socket is being closed so that it doesn't hang 150 | socket.close(); 151 | // Rethrow the exception in case the calling code needs to know about it 152 | throw(e); 153 | } 154 | 155 | // Push the socket to the command thread to read from 156 | mSocketQueue.push(socket); 157 | mReadCommandQueue.push(true); 158 | 159 | try { 160 | while (true) { 161 | // Read messages from server and pass them on to the 162 | // controller. But first check the type; only allow 163 | // the next prompt to be printed on non-thread messages. 164 | var message : Message = 165 | HaxeProtocol.readMessage(socket.input); 166 | 167 | var okToShowPrompt : Bool = false; 168 | 169 | switch (message) { 170 | case ThreadCreated(number): 171 | case ThreadTerminated(number): 172 | case ThreadStarted(number): 173 | case ThreadStopped(number, frameNumber, className, 174 | functionName, fileName, lineNumber): 175 | default: 176 | okToShowPrompt = true; 177 | } 178 | 179 | controller.acceptMessage(message); 180 | 181 | if (okToShowPrompt) { 182 | // OK to show the next prompt; pop whatever is there 183 | // to ensure that there is never more than one element 184 | // in there. This helps with "source" commands that 185 | // issue tons of commands in sequence 186 | while (mReadCommandQueue.pop(false)) { 187 | } 188 | mReadCommandQueue.push(true); 189 | } 190 | } 191 | } 192 | catch (e : haxe.io.Eof) { 193 | Sys.println("Client disconnected.\n"); 194 | } 195 | catch (e : Dynamic) { 196 | Sys.println("Error while reading message from client: " + e); 197 | } 198 | socket.close(); 199 | } 200 | } 201 | 202 | public function readCommandMain() 203 | { 204 | while (true) { 205 | // Get the next socket to use 206 | var socket = mSocketQueue.pop(true); 207 | 208 | // Read commands from the controller and pass them on to the 209 | // server 210 | try { 211 | while (true) { 212 | // Wait until the command prompt should be shown 213 | mReadCommandQueue.pop(true); 214 | 215 | HaxeProtocol.writeCommand 216 | (socket.output, mController.getNextCommand()); 217 | } 218 | } 219 | catch (e : haxe.io.Eof) { 220 | } 221 | catch (e : Dynamic) { 222 | Sys.println("Error while writing command to client: " + e); 223 | socket.close(); 224 | } 225 | } 226 | } 227 | 228 | private var mController : IController; 229 | private var mSocketQueue : Deque; 230 | private var mCommandQueue : Deque; 231 | private var mReadCommandQueue : Deque; 232 | } 233 | -------------------------------------------------------------------------------- /debugger/IController.hx: -------------------------------------------------------------------------------- 1 | /** ************************************************************************** 2 | * IController.hx 3 | * 4 | * Copyright 2013 TiVo Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | ************************************************************************** **/ 18 | 19 | package debugger; 20 | 21 | 22 | /** 23 | * This is the interface by which the debugger thread and user interface 24 | * classes pass commands and messages back and forth. 25 | * 26 | * The methods are called by the debugger thread and are expected to be 27 | * serviced by the user interface thread or a proxy thereof. 28 | **/ 29 | interface IController 30 | { 31 | /** 32 | * Blocking call which returns the next command for the debugger to 33 | * execute. 34 | * 35 | * @return the next command for the debugger to execute 36 | **/ 37 | public function getNextCommand() : Command; 38 | 39 | /** 40 | * Called when the debugger has a message to deliver. Note that this may 41 | * be called by multiple threads simultaneously if an asynchronous thread 42 | * event occurs. The implementation should probably lock as necessary. 43 | * 44 | * @param message is the message 45 | **/ 46 | public function acceptMessage(message : Message) : Void; 47 | } 48 | 49 | 50 | /** 51 | * This enum defines all of the commands that can be sent to a debugger 52 | * thread. For each command, a comment gives the set of response messages 53 | * that can be expected for that command. 54 | **/ 55 | enum Command 56 | { 57 | Exit; 58 | // Response: Exited 59 | 60 | Detach; 61 | // Response: Detached 62 | 63 | Files; 64 | // Response: Files 65 | 66 | FilesFullPath; 67 | // Response: Files 68 | 69 | AllClasses; 70 | // Response: AllClasses 71 | 72 | Classes(continuation : Null); 73 | // Response: Classes 74 | 75 | Mem; 76 | // Response: MemBytes 77 | 78 | Compact; 79 | // Response: Compacted 80 | 81 | Collect; 82 | // Response: Collected 83 | 84 | SetCurrentThread(number : Int); 85 | // Response: ThreadLocation, OK, ErrorNoSuchThread 86 | 87 | AddFileLineBreakpoint(fileName : String, lineNumber : Int); 88 | // Response: FileLineBreakpointNumber, ErrorNoSuchFile 89 | 90 | AddClassFunctionBreakpoint(className : String, functionName : String); 91 | // Response: ClassFunctionBreakpointNumber, ErrorBadClassNameRegex, 92 | // ErrorBadFunctionNameRegex, ErrorNoMatchingFunctions 93 | 94 | ListBreakpoints(enabled : Bool, disabled : Bool); 95 | // Response: Breakpoints 96 | 97 | DescribeBreakpoint(number : Int); 98 | // Response: BreakpointDescription, ErrorNoSuchBreakpoint 99 | 100 | DisableAllBreakpoints; 101 | // Response: BreakpointStatuses 102 | 103 | DisableBreakpointRange(first : Int, last: Int); 104 | // Response: BreakpointStatuses 105 | 106 | EnableAllBreakpoints; 107 | // Response: BreakpointStatuses 108 | 109 | EnableBreakpointRange(first : Int, last: Int); 110 | // Response: BreakpointStatuses 111 | 112 | DeleteAllBreakpoints; 113 | // Response: BreakpointStatuses 114 | 115 | DeleteBreakpointRange(first : Int, last: Int); 116 | // Response: BreakpointStatuses 117 | 118 | DeleteFileLineBreakpoint(fileName : String, lineNumber : Int); 119 | // Response: BreakpointStatuses 120 | 121 | BreakNow; 122 | // Response: OK 123 | 124 | Continue(count : Int); 125 | // Response: OK, ErrorBadCount 126 | 127 | Step(count : Int); 128 | // Response: OK, ErrorCurrentThreadNotStopped, ErrorBadCount 129 | 130 | Next(count : Int); 131 | // Response: OK, ErrorCurrentThreadNotStopped, ErrorBadCount 132 | 133 | Finish(count : Int); 134 | // Response: OK, ErrorCurrentThreadNotStopped, ErrorBadCount 135 | 136 | WhereCurrentThread(unsafe : Bool); 137 | // Response: ThreadsWhere, ErrorCurrentThreadNotStopped 138 | 139 | WhereAllThreads; 140 | // Response: ThreadsWhere 141 | 142 | Up(count : Int); 143 | // Response: ThreadLocation, ErrorCurrentThreadNotStopped, ErrorBadCount 144 | 145 | Down(count : Int); 146 | // Response: ThreadLocation, ErrorCurrentThreadNotStopped, ErrorBadCount 147 | 148 | SetFrame(number : Int); 149 | // Response: ThreadLocation, ErrorCurrentThreadNotStopped, ErrorBadCount 150 | 151 | Variables(unsafe : Bool); 152 | // Response: Variables, ErrorCurrentThreadNotStopped 153 | 154 | PrintExpression(unsafe : Bool, expression : String); 155 | // Response: Value, ErrorCurrentThreadNotStopped, ErrorEvaluatingExpression 156 | 157 | SetExpression(unsafe: Bool, lhs : String, rhs : String); 158 | // Response: Value, ErrorCurrentThreadNotStopped, ErrorEvaluatingExpression 159 | 160 | GetStructured(unsafe : Bool, expression : String); 161 | // Response: Structured, ErrorCurrentThreadNotStopped, 162 | // ErrorEvaluatingExpression 163 | } 164 | 165 | /** 166 | * An array of strings 167 | **/ 168 | typedef StringArray = Array; 169 | 170 | /** 171 | * An enum describing a class 172 | **/ 173 | enum ClassEnum 174 | { 175 | ClassFunction(className : String, hasStatics : Bool); 176 | } 177 | 178 | /** 179 | * An array of enums, possibly truncated so that it's not too large. 180 | * If truncated, a subsequent query for the remainder of the array can be done 181 | * using the nextIndex parameter that points to the next index of the original 182 | * array containing the list of the classes. 183 | **/ 184 | enum ClassList 185 | { 186 | ClassFunction(classArray : Array, nextIndex : String); 187 | } 188 | 189 | /** 190 | * A list of breakpoints 191 | **/ 192 | enum BreakpointList 193 | { 194 | Terminator; 195 | Breakpoint(number : Int, description : String, enabled : Bool, 196 | multi : Bool, next : BreakpointList); 197 | } 198 | 199 | 200 | /** 201 | * A list of locations at which a breakpoint breaks 202 | **/ 203 | enum BreakpointLocationList 204 | { 205 | Terminator; 206 | FileLine(fileName : String, lineNumber : Int, 207 | next : BreakpointLocationList); 208 | ClassFunction(className : String, functionName : String, 209 | next : BreakpointLocationList); 210 | } 211 | 212 | 213 | /** 214 | * A list of breakpoint status that results from disabling, enabling, or 215 | * deleting breakpoints 216 | **/ 217 | enum BreakpointStatusList 218 | { 219 | Terminator; 220 | Nonexistent(number : Int, next : BreakpointStatusList); 221 | Disabled(number : Int, next : BreakpointStatusList); 222 | AlreadyDisabled(number : Int, next : BreakpointStatusList); 223 | Enabled(number : Int, next : BreakpointStatusList); 224 | AlreadyEnabled(number : Int, next : BreakpointStatusList); 225 | Deleted(number : Int, next : BreakpointStatusList); 226 | } 227 | 228 | 229 | /** 230 | * Status of a thread 231 | **/ 232 | enum ThreadStatus 233 | { 234 | Running; 235 | StoppedImmediate; 236 | StoppedBreakpoint(number : Int); 237 | StoppedUncaughtException; 238 | StoppedCriticalError(description : String); 239 | } 240 | 241 | 242 | /** 243 | * A list of call stack frames of a thread 244 | **/ 245 | enum FrameList 246 | { 247 | Terminator; 248 | Frame(isCurrent : Bool, number : Int, className : String, 249 | functionName : String, fileName : String, lineNumber : Int, 250 | next : FrameList); 251 | } 252 | 253 | 254 | /** 255 | * Information about why and where a thread has stopped 256 | **/ 257 | enum ThreadWhereList 258 | { 259 | Terminator; 260 | Where(number : Int, status : ThreadStatus, frameList : FrameList, 261 | next : ThreadWhereList); 262 | } 263 | 264 | 265 | /** 266 | * The type of a value that can be included in a StructuredValue 267 | **/ 268 | enum StructuredValueType 269 | { 270 | TypeNull; 271 | TypeBool; 272 | TypeInt; 273 | TypeFloat; 274 | TypeString; 275 | TypeInstance(className : String); 276 | TypeEnum(enumName : String); 277 | TypeAnonymous(elements : StructuredValueTypeList); 278 | TypeClass(className : String); 279 | TypeFunction; 280 | TypeArray; 281 | } 282 | 283 | 284 | /** 285 | * A list of structured value types 286 | **/ 287 | enum StructuredValueTypeList 288 | { 289 | Terminator; 290 | _Type(type : StructuredValueType, next : StructuredValueTypeList); 291 | } 292 | 293 | 294 | /** 295 | * Types of value containers 296 | **/ 297 | enum StructuredValueListType 298 | { 299 | Anonymous; 300 | Instance(className : String); 301 | _Array; 302 | Class; 303 | } 304 | 305 | 306 | /** 307 | * A list of structured values 308 | **/ 309 | enum StructuredValueList 310 | { 311 | Terminator; 312 | Element(name : String, value : StructuredValue, next : StructuredValueList); 313 | } 314 | 315 | 316 | /** 317 | * A structured value, which includes both the type of the value, and a 318 | * structured representation of the value 319 | **/ 320 | enum StructuredValue 321 | { 322 | // Elided means that the actual value is not presented; its type is 323 | // presented, but the value can only be obtained by issuing a 324 | // GetStructured command with the given getExpression. 325 | Elided(type : StructuredValueType, getExpression : String); 326 | // A single value. 327 | Single(type : StructuredValueType, value : String); 328 | // A list of values 329 | List(type : StructuredValueListType, list : StructuredValueList); 330 | } 331 | 332 | 333 | /** 334 | * Messages are delivered by the debugger thread in response to Commands and 335 | * also spuriously for thread events. 336 | **/ 337 | enum Message 338 | { 339 | // Errors 340 | ErrorInternal(details : String); 341 | ErrorNoSuchThread(number : Int); 342 | ErrorNoSuchFile(fileName : String); 343 | ErrorNoSuchBreakpoint(number : Int); 344 | ErrorBadClassNameRegex(details : String); 345 | ErrorBadFunctionNameRegex(details : String); 346 | ErrorNoMatchingFunctions(className : String, functionName : String, 347 | unresolvableClasses : StringArray); 348 | ErrorBadCount(count : Int); 349 | ErrorCurrentThreadNotStopped(threadNumber : Int); 350 | ErrorEvaluatingExpression(details : String); 351 | 352 | // Normal messages 353 | OK; 354 | Exited; 355 | Detached; 356 | Files(list : StringArray); 357 | AllClasses(list : StringArray); 358 | Classes(list : ClassList); 359 | MemBytes(bytes : Int); 360 | Compacted(bytesBefore : Int, bytesAfter : Int); 361 | Collected(bytesBefore : Int, bytesAfter : Int); 362 | ThreadLocation(number : Int, stackFrame : Int, className : String, 363 | functionName : String, fileName : String, lineNumber : Int); 364 | FileLineBreakpointNumber(number : Int); 365 | ClassFunctionBreakpointNumber(number : Int, 366 | unresolvableClasses : StringArray); 367 | Breakpoints(list : BreakpointList); 368 | BreakpointDescription(number : Int, list : BreakpointLocationList); 369 | BreakpointStatuses(list : BreakpointStatusList); 370 | ThreadsWhere(list : ThreadWhereList); 371 | Variables(list : StringArray); 372 | Value(expression : String, type : String, value : String); 373 | Structured(structuredValue : StructuredValue); 374 | 375 | // Asynchronously delivered on thread events 376 | ThreadCreated(number : Int); 377 | ThreadTerminated(number : Int); 378 | ThreadStarted(number : Int); 379 | ThreadStopped(number : Int, stackFrame : Int, 380 | className : String, functionName : String, 381 | fileName : String, lineNumber : Int); 382 | } 383 | -------------------------------------------------------------------------------- /debugger/Local.hx: -------------------------------------------------------------------------------- 1 | /** ************************************************************************** 2 | * Local.hx 3 | * 4 | * Copyright 2013 TiVo Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | ************************************************************************** **/ 18 | 19 | package debugger; 20 | 21 | 22 | /** 23 | * This class creates a local command-line debugger. This class should be 24 | * instantiated in the main() function of any program that wishes to be 25 | * debugged via the local command line. 26 | **/ 27 | class Local 28 | { 29 | /** 30 | * Creates a debugger which will read input from the command line and 31 | * emit output to the terminal. 32 | * 33 | * If the program was not compiled with debugging support, a String 34 | * exception is thrown. 35 | * 36 | * @param startStopped if true, when the debugger starts, all other 37 | * threads of the process are stopped until the user instructs the 38 | * debugger to continue those threads. If false, all threads of 39 | * the program will continue to run when the debugger is started 40 | * and will not stop until a debugger command instructs them to do 41 | * so. 42 | **/ 43 | public function new(startStopped : Bool) 44 | { 45 | mController = new CommandLineController(); 46 | mThread = new DebuggerThread(mController, startStopped); 47 | } 48 | 49 | private var mController : CommandLineController; 50 | private var mThread : DebuggerThread; 51 | } 52 | -------------------------------------------------------------------------------- /debugger/Version.hx: -------------------------------------------------------------------------------- 1 | package debugger; 2 | class Version { 3 | public static inline var name="0.1.0"; 4 | } 5 | 6 | -------------------------------------------------------------------------------- /haxelib.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "hxcpp-debugger", 3 | "license" : "Apache", 4 | "version" : "1.2.0", 5 | "description" : "Haxe classes implementing a debugger interface to the cpp.vm.Debugger class", 6 | "contributors" : [ "gamehaxe", "tanis2000" ], 7 | "releasenote" : "See Changes.md", 8 | "dependencies" : { 9 | "hxcpp" : "" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /run-server.hxml: -------------------------------------------------------------------------------- 1 | -cp . 2 | -main debugger.HaxeServer 3 | -neko bin/HaxeServer.n 4 | -prompt 5 | -cmd start neko bin/HaxeServer.n 6 | --------------------------------------------------------------------------------