├── tests ├── unit │ ├── TestGuid │ │ ├── compile.txt │ │ ├── input.txt │ │ ├── output.txt │ │ └── Test.hx │ ├── TestNoOptimizeCapture │ │ ├── compile.txt │ │ ├── input.txt │ │ ├── Test.hx │ │ └── output.txt │ ├── TestTryCatch │ │ ├── input.txt │ │ ├── output.txt │ │ └── Test.hx │ ├── TestUnifyArg │ │ ├── input.txt │ │ ├── output.txt │ │ └── Test.hx │ ├── TestStepRecursive │ │ ├── input.txt │ │ ├── Test.hx │ │ └── output.txt │ ├── TestRegReuseArg │ │ ├── input.txt │ │ ├── output.txt │ │ └── Test.hx │ └── TestHint │ │ ├── input.txt │ │ ├── Test.hx │ │ └── output.txt ├── RunCi.hxml └── RunCi.hx ├── hld ├── import.hx ├── Int64Map.hx ├── Align.hx ├── Api.hx ├── HLDebugApi.hx ├── Pointer.hx ├── NodeDebugApiNative.hx ├── Buffer.hx ├── JitInfo.hx ├── Value.hx ├── Main.hx ├── CodeGraph.hx ├── Module.hx └── Debugger.hx ├── icon.png ├── debugger ├── debugger.hxml ├── node_debug.hxml ├── package.json └── .vscode │ ├── tasks.json │ └── launch.json ├── display.hxml ├── hldebug-wrapper ├── lib │ ├── mac │ │ └── hldebug.node │ ├── win │ │ └── hldebug.node │ └── linux │ │ └── hldebug.node ├── package.json ├── index.js ├── binding.gyp └── src │ ├── hldebugger.cc │ ├── debug.c │ └── hl.h ├── .vscodeignore ├── .gitignore ├── .vscode ├── tasks.json ├── settings.json └── launch.json ├── haxelib.json ├── Makefile ├── src ├── Utils.hx └── Extension.hx ├── LICENSE.md ├── README.md ├── .github └── workflows │ └── main.yml ├── bindings.js ├── package.json └── CHANGELOG.md /tests/unit/TestGuid/compile.txt: -------------------------------------------------------------------------------- 1 | -D hl-ver=1.15.0 -------------------------------------------------------------------------------- /tests/unit/TestNoOptimizeCapture/compile.txt: -------------------------------------------------------------------------------- 1 | -D hl_no_opt -------------------------------------------------------------------------------- /hld/import.hx: -------------------------------------------------------------------------------- 1 | import format.hl.Data.HLType; 2 | using format.hl.Tools; 3 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vshaxe/hashlink-debugger/HEAD/icon.png -------------------------------------------------------------------------------- /tests/RunCi.hxml: -------------------------------------------------------------------------------- 1 | --main RunCi 2 | -hl RunCi.hl 3 | 4 | --cmd hl RunCi.hl 5 | -------------------------------------------------------------------------------- /tests/unit/TestTryCatch/input.txt: -------------------------------------------------------------------------------- 1 | --ci 2 | test.hl 3 | "b Test.hx:9" 4 | r 5 | "p e" 6 | q 7 | -------------------------------------------------------------------------------- /debugger/debugger.hxml: -------------------------------------------------------------------------------- 1 | -lib format 2 | -lib hscript 3 | -cp .. 4 | -hl debug.hl 5 | -main hld.Main 6 | -------------------------------------------------------------------------------- /display.hxml: -------------------------------------------------------------------------------- 1 | -lib vscode 2 | -lib vscode-debugadapter 3 | -lib vshaxe 4 | -lib format 5 | -lib hscript 6 | -cp src 7 | -js foo.js 8 | -------------------------------------------------------------------------------- /hldebug-wrapper/lib/mac/hldebug.node: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vshaxe/hashlink-debugger/HEAD/hldebug-wrapper/lib/mac/hldebug.node -------------------------------------------------------------------------------- /hldebug-wrapper/lib/win/hldebug.node: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vshaxe/hashlink-debugger/HEAD/hldebug-wrapper/lib/win/hldebug.node -------------------------------------------------------------------------------- /debugger/node_debug.hxml: -------------------------------------------------------------------------------- 1 | -js debugger.js 2 | -main hld.Main 3 | -cp .. 4 | -lib format 5 | -lib hscript 6 | -lib hxnodejs 7 | -debug -------------------------------------------------------------------------------- /hldebug-wrapper/lib/linux/hldebug.node: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vshaxe/hashlink-debugger/HEAD/hldebug-wrapper/lib/linux/hldebug.node -------------------------------------------------------------------------------- /tests/unit/TestUnifyArg/input.txt: -------------------------------------------------------------------------------- 1 | --ci 2 | test.hl 3 | "b Test.hx:18" 4 | "b Test.hx:19" 5 | r 6 | "p _tmp_item" 7 | r 8 | "p item" 9 | q 10 | -------------------------------------------------------------------------------- /tests/unit/TestGuid/input.txt: -------------------------------------------------------------------------------- 1 | --ci 2 | test.hl 3 | "b Test.hx:5" 4 | "b Test.hx:7" 5 | r 6 | "p id" 7 | "p id2" 8 | r 9 | "p id" 10 | "p id2" 11 | q 12 | -------------------------------------------------------------------------------- /tests/unit/TestStepRecursive/input.txt: -------------------------------------------------------------------------------- 1 | --ci 2 | test.hl 3 | "b Test.hx:11" 4 | r 5 | s 6 | "p x" 7 | n 8 | n 9 | n 10 | "p x" 11 | n 12 | n 13 | q 14 | -------------------------------------------------------------------------------- /tests/unit/TestTryCatch/output.txt: -------------------------------------------------------------------------------- 1 | > b Test.hx:9 2 | Breakpoint set line 9 3 | > r 4 | Thread paused Test.hx:9 ($Test::main) 5 | > p e 6 | "from doThrow" : String 7 | > q 8 | -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | * 2 | */** 3 | !node_modules/**/* 4 | !adapter.js 5 | !bindings.js 6 | !CHANGELOG.md 7 | !extension.js 8 | !icon.png 9 | !LICENSE.md 10 | !package.json 11 | !README.md -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.js 3 | *.js.map 4 | package-lock.json 5 | /*.vsix 6 | /vsce_token.txt 7 | /debugger/args.txt 8 | /debugger/debug.hl 9 | /hldebug-wrapper/build 10 | *.hl 11 | -------------------------------------------------------------------------------- /tests/unit/TestNoOptimizeCapture/input.txt: -------------------------------------------------------------------------------- 1 | --ci 2 | test.hl 3 | "b Test.hx:7" 4 | "b Test.hx:11" 5 | r 6 | "p this" 7 | "p functionvar" 8 | r 9 | "p this" 10 | "p functionvar" 11 | q 12 | -------------------------------------------------------------------------------- /debugger/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "bindings": "^1.5.0", 4 | "deasync": "^0.1.24", 5 | "source-map-support": "^0.5.21", 6 | "hldebug": "file:../hldebug-wrapper" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /tests/unit/TestRegReuseArg/input.txt: -------------------------------------------------------------------------------- 1 | --ci 2 | test.hl 3 | "b Test.hx:18" 4 | "b Test.hx:12" 5 | "b Test.hx:7" 6 | r 7 | "p this" 8 | "p pt" 9 | r 10 | "p functionvar" 11 | "p pt" 12 | r 13 | "p pt" 14 | q 15 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "type": "hxml", 6 | "file": "build.hxml", 7 | "group": { 8 | "kind": "build", 9 | "isDefault": true 10 | } 11 | } 12 | ] 13 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.trimTrailingWhitespace": true, 3 | "haxe.codeGeneration": { 4 | "imports": { 5 | "enableAutoImports": false 6 | } 7 | }, 8 | "haxe.configurations": [ 9 | ["display.hxml"] 10 | ] 11 | } -------------------------------------------------------------------------------- /tests/unit/TestStepRecursive/Test.hx: -------------------------------------------------------------------------------- 1 | class Test { 2 | static function foo( x : Int ) { 3 | if( x > 0 ) { 4 | trace(x); 5 | foo(x-1); 6 | trace(x); 7 | } 8 | } 9 | 10 | static function main() { 11 | foo(3); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /tests/unit/TestTryCatch/Test.hx: -------------------------------------------------------------------------------- 1 | class Test { 2 | static function doThrow() { 3 | throw "from doThrow"; 4 | } 5 | static function main() { 6 | try { 7 | doThrow(); 8 | } catch(e:String) { 9 | trace(e); // break here should diaplay e 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /hldebug-wrapper/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hldebug", 3 | "version": "1.0.0", 4 | "description": "C++ wrapper to the hashlink debugging API", 5 | "main": "./index.js", 6 | "author": "Maurice Doison", 7 | "license": "MIT", 8 | "dependencies": { 9 | "node-addon-api": "1.7.2", 10 | "node-gyp": "^8.4.1" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /hldebug-wrapper/index.js: -------------------------------------------------------------------------------- 1 | switch(process.platform) { 2 | case "win32": 3 | module.exports = require('hldebug/lib/win/hldebug.node'); 4 | break; 5 | case "darwin": 6 | module.exports = require('hldebug/lib/mac/hldebug.node'); 7 | break; 8 | case "linux": 9 | module.exports = require('hldebug/lib/linux/hldebug.node'); 10 | break; 11 | } 12 | -------------------------------------------------------------------------------- /tests/unit/TestNoOptimizeCapture/Test.hx: -------------------------------------------------------------------------------- 1 | class Test { 2 | var classvar = 10; 3 | 4 | public function new() { 5 | var functionvar = 15; 6 | function bar(x) { 7 | classvar = functionvar + x; 8 | functionvar = x + 1; 9 | } 10 | bar(7); 11 | trace(functionvar); 12 | trace(classvar); 13 | } 14 | 15 | static function main() { 16 | new Test(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /debugger/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "haxe", 8 | "args": "active configuration", 9 | "label": "Build", 10 | "group": { 11 | "kind": "build", 12 | "isDefault": true 13 | } 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /haxelib.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "hldebug", 3 | "url" : "https://github.com/HaxeFoundation/hashlink/tree/master/other/debugger", 4 | "license" : "BSD", 5 | "contributors" : ["ncannasse"], 6 | "description" : "A Haxe Library for debugging hashlink virtual machine.", 7 | "version" : "1.0.0", 8 | "dependencies" : { "format" : "", "hxnodejs" : "", "hscript" : "" }, 9 | "releasenote" : "" 10 | } 11 | -------------------------------------------------------------------------------- /hld/Int64Map.hx: -------------------------------------------------------------------------------- 1 | package hld; 2 | 3 | // Map does not work on JS, see https://github.com/HaxeFoundation/haxe/issues/9872 4 | class Int64Map extends haxe.ds.BalancedTree { 5 | override function compare(k1:haxe.Int64, k2:haxe.Int64):Int { 6 | return if( k1 == k2 ) { 7 | 0; 8 | } else if( k1 > k2 ) { 9 | 1; 10 | } else { 11 | -1; 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /tests/unit/TestGuid/output.txt: -------------------------------------------------------------------------------- 1 | > b Test.hx:5 2 | Breakpoint set line 5 3 | > b Test.hx:7 4 | Breakpoint set line 7 5 | > r 6 | Thread paused Test.hx:5 ($Test::main) 7 | > p id 8 | z3K4-MI#w-J#6 : hl.GUID 9 | > p id2 10 | z3K4-MI#w-J#7 : hl.GUID 11 | > r 12 | Thread paused Test.hx:7 ($Test::main) 13 | > p id 14 | SomeName (z3K4-MI#w-J#6) : hl.GUID 15 | > p id2 16 | z3K4-MI#w-J#7 : hl.GUID 17 | > q 18 | -------------------------------------------------------------------------------- /tests/unit/TestGuid/Test.hx: -------------------------------------------------------------------------------- 1 | class Test { 2 | static function main() { 3 | var id : hl.GUID = makeGUID(0xF1561985,0xF15008); 4 | var id2 : hl.GUID = makeGUID(0xF1561985,0xF15009); 5 | hl.Api.registerGUIDName(id, "SomeName"); 6 | Sys.println(Std.string(id)); 7 | Sys.println(Std.string(id2)); 8 | } 9 | 10 | static function makeGUID(high,low) : hl.GUID { 11 | return haxe.Int64.make(high,low); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /tests/unit/TestNoOptimizeCapture/output.txt: -------------------------------------------------------------------------------- 1 | > b Test.hx:7 2 | Breakpoint set line 7 3 | > b Test.hx:11 4 | Breakpoint set line 11 5 | > r 6 | Thread paused Test.hx:7 7 | > p this 8 | Test : Test 9 | classvar = 10 : Int 10 | > p functionvar 11 | [15] : Array 12 | > r 13 | Thread paused Test.hx:11 ($Test::__constructor__) 14 | > p this 15 | Test : Test 16 | classvar = 22 : Int 17 | > p functionvar 18 | [8] : Array 19 | > q 20 | -------------------------------------------------------------------------------- /tests/unit/TestUnifyArg/output.txt: -------------------------------------------------------------------------------- 1 | > b Test.hx:18 2 | Breakpoint set line 18 3 | > b Test.hx:19 4 | Breakpoint set line 19 5 | > r 6 | Thread paused Test.hx:18 (InventoryItemGroup::makeIcon) 7 | > p _tmp_item 8 | {...} : hl.DynObj 9 | k = Item : Item 10 | count = 1 : Int 11 | > r 12 | Thread paused Test.hx:19 (InventoryItemGroup::makeIcon) 13 | > p item 14 | {...} : hl.DynObj 15 | k = Item : Item 16 | count = 1 : Int 17 | > q 18 | -------------------------------------------------------------------------------- /tests/unit/TestHint/input.txt: -------------------------------------------------------------------------------- 1 | --ci 2 | test.hl 3 | "b Test.hx:23" 4 | r 5 | "p i" 6 | "p i:h" 7 | "p i:b" 8 | "p multilineString" 9 | "p multilineString:s" 10 | "p bytes:UI8(0)" 11 | "p bytes:UI16(0)" 12 | "p bytes:I32(0)" 13 | "p bytes:I64(0)" 14 | "p flags" 15 | "p flags:EnumFlags" 16 | "p ef" 17 | "p ef:EnumIndex" 18 | "p cArr" 19 | "p cArr:CArray" 20 | "p cArr:CArray[2]" 21 | "p cArr:CArray[count-1]" 22 | q 23 | -------------------------------------------------------------------------------- /tests/unit/TestStepRecursive/output.txt: -------------------------------------------------------------------------------- 1 | > b Test.hx:11 2 | Breakpoint set line 11 3 | > r 4 | Thread paused Test.hx:11 ($Test::main) 5 | > s 6 | Thread paused Test.hx:3 ($Test::foo) 7 | > p x 8 | 3 : Int 9 | > n 10 | Thread paused Test.hx:4 ($Test::foo) 11 | > n 12 | Thread paused Test.hx:5 ($Test::foo) 13 | > n 14 | Thread paused Test.hx:6 ($Test::foo) 15 | > p x 16 | 3 : Int 17 | > n 18 | Thread paused Test.hx:7 ($Test::foo) 19 | > n 20 | Thread paused Test.hx:11 ($Test::main) 21 | > q 22 | -------------------------------------------------------------------------------- /tests/unit/TestRegReuseArg/output.txt: -------------------------------------------------------------------------------- 1 | > b Test.hx:18 2 | Breakpoint set line 18 3 | > b Test.hx:12 4 | Breakpoint set line 12 5 | > b Test.hx:7 6 | Breakpoint set line 7 7 | > r 8 | Thread paused Test.hx:18 (Test::foo) 9 | > p this 10 | Test : Test 11 | > p pt 12 | inlined : Dynamic 13 | x = 10 : Int 14 | y = 11 : Int 15 | > r 16 | Thread paused Test.hx:7 17 | > p functionvar 18 | 15 : Int 19 | > p pt 20 | inlined : Dynamic 21 | x = 12 : Int 22 | y = 13 : Int 23 | > r 24 | Thread paused Test.hx:12 25 | > p pt 26 | inlined : Dynamic 27 | x = 14 : Int 28 | y = 15 : Int 29 | > q 30 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Adapter", 6 | "type": "node", 7 | "request": "launch", 8 | "runtimeArgs": [ 9 | "--nolazy" 10 | ], 11 | "program": "${workspaceFolder}/adapter.js", 12 | "cwd": "${workspaceFolder}", 13 | "stopOnEntry": false, 14 | "args": [ 15 | "--server=4711" 16 | ], 17 | "sourceMaps": true, 18 | "outFiles": [ 19 | "${workspaceFolder}/*.js" 20 | ], 21 | "preLaunchTask": { 22 | "type" : "haxe", 23 | "args" : "active configuration" 24 | } 25 | } 26 | ] 27 | } -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | 3 | deps: 4 | cd hldebug-wrapper && npm install && rm -rf build node_modules 5 | npm install 6 | cleanup: 7 | find . -name *.obj | xargs rm -f 8 | find . -name *.pdb | xargs rm -f 9 | find . -name *.tlog | xargs rm -rf 10 | find . -name *.map | xargs rm -rf 11 | build: 12 | haxe -cp src -lib vscode -lib vshaxe -lib vscode-debugadapter -D js-es=6 -js extension.js Extension 13 | haxe build.hxml 14 | package: cleanup build 15 | #npm install vsce -g 16 | vsce package 17 | 18 | # to get token : 19 | # - visit https://dev.azure.com/ncannasse/ 20 | # - login (@hotmail) 21 | # - click user / security / Personal Access token 22 | # - select Organization:All + Full Access 23 | publish: 24 | vsce publish -p `cat vsce_token.txt` 25 | -------------------------------------------------------------------------------- /debugger/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Utilisez IntelliSense pour en savoir plus sur les attributs possibles. 3 | // Pointez pour afficher la description des attributs existants. 4 | // Pour plus d'informations, visitez : https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "HashLink", 9 | "request": "launch", 10 | "type": "hl", 11 | "hxml": "debugger.hxml", 12 | "cwd": "${workspaceFolder}", 13 | "argsFile": "args.txt", 14 | "preLaunchTask": "Build" 15 | }, 16 | { 17 | "type": "node", 18 | "request": "launch", 19 | "name": "Node", 20 | "program": "${workspaceFolder}/debugger.js", 21 | "args": [ 22 | "--input", 23 | "args.txt" 24 | ], 25 | "preLaunchTask": "Build" 26 | }, 27 | ] 28 | } -------------------------------------------------------------------------------- /tests/unit/TestRegReuseArg/Test.hx: -------------------------------------------------------------------------------- 1 | class Test { 2 | public function new() { 3 | foo(10, 11); 4 | var functionvar = 15; 5 | function bar(x, y) { 6 | var pt = new Point(x, y); 7 | trace(functionvar, pt.x, pt.y); // nargs=3, captured functionvar at r0, x=pt.x=r1, y=pt.y=r2 8 | } 9 | bar(12, 13); 10 | function bar2(x, y) { 11 | var pt = new Point(x, y); 12 | trace(pt.x, pt.y); // nargs=2, x=pt.x=r0, y=pt.y=r1 13 | } 14 | bar2(14, 15); 15 | } 16 | function foo(x : Int, y : Int) { 17 | var pt = new Point(x, y); 18 | trace(pt.x, pt.y); // nargs=3, this at r0, x=pt.x=r1, y=pt.y=r2 19 | } 20 | 21 | static function main() { 22 | new Test(); 23 | } 24 | } 25 | 26 | class Point { 27 | public var x : Int; 28 | public var y : Int; 29 | public inline function new(x = 0, y = 0) { 30 | this.x = x; 31 | this.y = y; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /tests/unit/TestUnifyArg/Test.hx: -------------------------------------------------------------------------------- 1 | class Test { 2 | static function main() { 3 | var iig = new InventoryItemGroup(); 4 | var it = new Item(); 5 | var data = {count : 1, k : it}; 6 | var icon = iig.makeIcon(data); 7 | trace(icon); 8 | } 9 | } 10 | class SlotGroup { 11 | public function new() {} 12 | public function makeIcon( item : SlotItem ) : String { 13 | return "" + item; 14 | } 15 | } 16 | class InventoryItemGroup extends SlotGroup { 17 | override function makeIcon(item : SlotItem) { 18 | trace(item); // break here should diaplay _tmp_item 19 | return super.makeIcon(item); // break here should display item 20 | } 21 | } 22 | class Item { 23 | public function new() {} 24 | } 25 | abstract SlotItem({ count : Int, k : T }) { 26 | @:from public inline static function from( data : { count : Int, k : T } ) : SlotItem { 27 | return cast data; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Utils.hx: -------------------------------------------------------------------------------- 1 | typedef Arguments = { 2 | cwd:String, 3 | classPaths:Array, 4 | ?hl:String, 5 | ?env:haxe.DynamicAccess, 6 | ?program:String, 7 | ?args:Array, 8 | ?argsFile:String, 9 | ?port:Int, 10 | ?hotReload:Bool, 11 | ?profileSamples:Int, 12 | ?allowEval:Bool 13 | } 14 | 15 | typedef Container = { 16 | var name : String; 17 | var variablesReference : Int; 18 | var ?expensive : Bool; 19 | var ?value : String; 20 | } 21 | 22 | typedef VariableContext = { 23 | var sessionId : String; 24 | var container : Container; 25 | var variable : vscode.debugProtocol.DebugProtocol.Variable; 26 | } 27 | 28 | enum abstract CustomRequestCommand(String) to String { 29 | var OnSessionActive; 30 | var OnSessionInactive; 31 | } 32 | 33 | class Utils { 34 | 35 | inline public static function toString(value:Int, base:Int):String { 36 | #if js 37 | return untyped value.toString(base); 38 | #end 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /tests/unit/TestHint/Test.hx: -------------------------------------------------------------------------------- 1 | class Test { 2 | static function main() { 3 | new Test(10); 4 | } 5 | 6 | public function new(count : Int) { 7 | var i : Int = 7764; 8 | var multilineString : String = "AAA\nBBB"; 9 | var bytes = new hl.Bytes(count); 10 | for( i in 0...count ) { 11 | bytes.setUI8(i, 1 + i); 12 | } 13 | var flags = new haxe.EnumFlags(); 14 | flags.set(BFlag); 15 | flags.set(CFlag); 16 | var ef = MyFlag.CFlag; 17 | var cArr = hl.CArray.alloc(Point, count); 18 | for( i in 0...count ) { 19 | cArr[i].x = 30+i; 20 | cArr[i].y = 130+i; 21 | } 22 | 23 | trace(i, multilineString, bytes, flags, ef, cArr); 24 | } 25 | } 26 | 27 | @:struct class Point { 28 | public var x : Int; 29 | public var y : Int; 30 | public function new( x : Int ) { this.x = x; this.y = x + 100; } 31 | public function toString() : String { return 'Point(x=$x, y=$y)'; } 32 | } 33 | 34 | enum MyFlag { 35 | AFlag; 36 | BFlag; 37 | CFlag; 38 | DFlag; 39 | } 40 | -------------------------------------------------------------------------------- /tests/unit/TestHint/output.txt: -------------------------------------------------------------------------------- 1 | > b Test.hx:23 2 | Breakpoint set line 23 3 | > r 4 | Thread paused Test.hx:23 ($Test::__constructor__) 5 | > p i 6 | 7764 : Int 7 | > p i:h 8 | 0x00001E54 : Int 9 | > p i:b 10 | 0b00000000000000000001111001010100 : Int 11 | > p multilineString 12 | "AAA 13 | BBB" : String 14 | > p multilineString:s 15 | "AAA\nBBB" : String 16 | > p bytes:UI8(0) 17 | 1 : hl.Bytes 18 | > p bytes:UI16(0) 19 | 513 : hl.Bytes 20 | > p bytes:I32(0) 21 | 67305985 : hl.Bytes 22 | > p bytes:I64(0) 23 | 578437695752307201 : hl.Bytes 24 | > p flags 25 | 6 : Int 26 | > p flags:EnumFlags 27 | BFlag | CFlag : Int 28 | > p ef 29 | CFlag : MyFlag 30 | > p ef:EnumIndex 31 | CFlag : MyFlag 32 | > p cArr 33 | #hl_carray : hl.NativeAbstract 34 | > p cArr:CArray 35 | [Point, Point, Point, Point, Point, Point, Point, Point, Point, Point] : hl.NativeAbstract 36 | > p cArr:CArray[2] 37 | Point : Point 38 | x = 32 : Int 39 | y = 132 : Int 40 | > p cArr:CArray[count-1] 41 | Point : Point 42 | x = 39 : Int 43 | y = 139 : Int 44 | > q 45 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 vshaxe contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /hldebug-wrapper/binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | 'targets': [ 3 | { 4 | 'target_name': 'hldebug', 5 | 'sources': [ 'src/debug.c', 'src/hldebugger.cc' ], 6 | 'include_dirs': ["; 11 | 12 | public function new( is64, boolSize ) { 13 | this.is64 = is64; 14 | this.ptrSize = is64 ? 8 : 4; 15 | this.boolSize = boolSize; 16 | } 17 | 18 | inline function get_ptr() { 19 | return ptrSize; 20 | } 21 | 22 | public function typeSize( t : HLType ) { 23 | return switch( t ) { 24 | case HVoid: 0; 25 | case HUi8: 1; 26 | case HUi16: 2; 27 | case HI32, HF32: 4; 28 | case HI64, HF64: 8; 29 | case HBool: boolSize; 30 | default: ptrSize; 31 | } 32 | } 33 | 34 | public function stackSize( t : HLType ) { 35 | return switch( t ) { 36 | case HUi8, HUi16, HBool: ptrSize; 37 | case HI32, HF32 if( is64 ): ptrSize; 38 | default: typeSize(t); 39 | } 40 | } 41 | 42 | inline function rest( v : Int, size : Int ) { 43 | return ( -v) & (size - 1); 44 | } 45 | 46 | public function padSize( v : Int, t : HLType ) { 47 | return t == HVoid ? 0 : rest(v, typeSize(t)); 48 | } 49 | 50 | public function padStruct( v : Int, t : HLType ) { 51 | return rest(v, structSize(t)); 52 | } 53 | 54 | function structSize( t : HLType ) : Int { 55 | if( t.isPtr() ) 56 | return structSizes[structSizes.length - 1]; 57 | return structSizes[t.getIndex()]; 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /hld/Api.hx: -------------------------------------------------------------------------------- 1 | package hld; 2 | 3 | enum abstract WaitResult(Int) { 4 | public var Timeout = -1; 5 | public var Exit = 0; 6 | public var Breakpoint = 1; 7 | public var SingleStep = 2; 8 | public var Error = 3; 9 | public var Handled = 4; 10 | public var StackOverflow = 5; 11 | public var Watchbreak = 0x100; 12 | } 13 | 14 | enum abstract Register(Int) { 15 | public var Esp = 0; 16 | public var Ebp = 1; 17 | public var Eip = 2; 18 | public var EFlags = 3; 19 | public var Dr0 = 4; 20 | public var Dr1 = 5; 21 | public var Dr2 = 6; 22 | public var Dr3 = 7; 23 | public var Dr6 = 8; 24 | public var Dr7 = 9; 25 | public var Eax = 10; 26 | public var Xmm0 = 11; 27 | public inline function toInt() return this; 28 | } 29 | 30 | interface Api { 31 | 32 | function start() : Bool; 33 | function stop() : Void; 34 | function breakpoint() : Bool; 35 | function read( ptr : Pointer, buffer : Buffer, size : Int ) : Bool; 36 | function write( ptr : Pointer, buffer : Buffer, size : Int ) : Bool; 37 | 38 | function readByte( ptr : Pointer, pos : Int ) : Int; 39 | function writeByte( ptr : Pointer, pos : Int, value : Int ) : Void; 40 | 41 | function flush( ptr : Pointer, size : Int ) : Bool; 42 | function wait( timeout : Int ) : { r : WaitResult, tid : Int }; 43 | function resume( tid : Int ) : Bool; 44 | function readRegister( tid : Int, register : Register ) : Pointer; 45 | function writeRegister( tid : Int, register : Register, v : Pointer ) : Bool; 46 | 47 | } 48 | -------------------------------------------------------------------------------- /tests/RunCi.hx: -------------------------------------------------------------------------------- 1 | class RunCi { 2 | static var MAX_TIME_PER_TEST : Float = 2; 3 | 4 | static function main() { 5 | var basePath = Sys.getCwd(); 6 | var debuggerHL = sys.FileSystem.absolutePath(basePath + "../debugger/debug.hl"); 7 | var errorCount = 0; 8 | 9 | var tests = sys.FileSystem.readDirectory(basePath + "unit"); 10 | for( test in tests ) { 11 | var fullPath = sys.FileSystem.absolutePath(basePath + "unit/" + test); 12 | log('[INFO] $test begin'); 13 | changeDirectory(fullPath); 14 | var compileargs = ["--main", "Test", "-hl", "test.hl"]; 15 | try { 16 | var flags = sys.io.File.getContent(fullPath + "/compile.txt").split(" "); 17 | compileargs = compileargs.concat(flags); 18 | } catch( e ) { 19 | } 20 | trace("run haxe with " + compileargs); 21 | Sys.command("haxe", compileargs); 22 | var process = new sys.io.Process("hl", [debuggerHL, "--input", "input.txt"]); 23 | var expectedOutput = sys.io.File.getContent(fullPath + "/output.txt"); 24 | var startingTime = haxe.Timer.stamp(); 25 | var exitCode : Null = 0; 26 | while( true ) { 27 | exitCode = process.exitCode(false); 28 | var currentTime = haxe.Timer.stamp(); 29 | if( exitCode != null || currentTime - startingTime > MAX_TIME_PER_TEST ) 30 | break; 31 | } 32 | var output = process.stdout.readAll().toString(); 33 | process.kill(); 34 | process.close(); 35 | if( exitCode == null ) { 36 | errorCount ++; 37 | log('[ERROR] $test: not terminated in $MAX_TIME_PER_TEST seconds, output:\n$output'); 38 | } else if( exitCode != 0 ) { 39 | errorCount ++; 40 | log('[ERROR] $test: exitCode:$exitCode'); 41 | log('[STDOUT] $output'); 42 | } else if( output != expectedOutput ) { 43 | errorCount ++; 44 | log('[ERROR] $test: output:\n$output'); 45 | } else { 46 | log('[SUCCESS] $test'); 47 | } 48 | } 49 | 50 | changeDirectory(basePath); 51 | log('[INFO] all tests end, error count: $errorCount'); 52 | if( errorCount > 0 ) { 53 | Sys.exit(1); 54 | } 55 | } 56 | 57 | static public function changeDirectory(path : String) { 58 | log('[CWD] Changing directory to $path'); 59 | Sys.setCwd(path); 60 | } 61 | 62 | static public inline function log(msg : String) { 63 | Sys.println(msg); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HashLink Debugger 2 | 3 | This VSCode extension allows you to debug [HashLink](https://hashlink.haxe.org/) JIT applications. 4 | 5 | *Only available on VSCode 64 bit* 6 | 7 | Additional features: 8 | - **Watch hints**: add `:h` to the end of a watch expression (for example, `myvar:h`) to display the value in hexadecimal. For the full list of hints, see [enum Hint](https://github.com/vshaxe/hashlink-debugger/blob/master/hld/Value.hx#L32). 9 | 10 | ## Building from Source 11 | 12 | The following instructions are only relevant for building the extension from source and are **not required when installing it from the marketplace**. 13 | 14 | ### Compiling 15 | 16 | You will need [Haxe 4](https://haxe.org/download/). 17 | 18 | Additionally, you need to install these dependencies: 19 | 20 | ``` 21 | haxelib install vshaxe 22 | haxelib install vscode 23 | haxelib install vscode-debugadapter 24 | haxelib git hscript https://github.com/HaxeFoundation/hscript.git 25 | haxelib git format https://github.com/HaxeFoundation/format.git 26 | ``` 27 | 28 | You will need [NodeJS](https://nodejs.org/en/download), if you would like to compile vscode extension or use commandline nodejs version. 29 | 30 | Additionally, you need to install dependencies: 31 | 32 | ``` 33 | npm install 34 | ``` 35 | 36 | Once all dependencies are ready, you should be able to compile with `make build`. 37 | 38 | #### Commandline version 39 | 40 | Instead of the vscode plugin, you can also compile and run a commandline version, similar to `gdb`: 41 | 42 | Debugger running in HashLink; 43 | ``` 44 | cd hashlink-debugger/debugger 45 | haxe debugger.hxml 46 | hl debug.hl /my/path/filetodebug.hl 47 | ``` 48 | 49 | You can then use gdb-like commands such as run/bt/break/etc. (see [sources](https://github.com/vshaxe/hashlink-debugger/blob/master/hld/Main.hx#L219)) 50 | 51 | The commandline debugger can also be compiled and run using nodejs, by doing: 52 | ``` 53 | cd hashlink-debugger/debugger 54 | haxe node_debug.hxml 55 | npm install 56 | node debugger.js /my/path/filetodebug.hl 57 | ``` 58 | 59 | 60 | ### Installing 61 | 62 | Please note that VSCode does not allow users to have a specific directory for a single extension, so it's easier to clone this repository directly into the `extensions` directory of VSCode (`C:\Users\\.vscode\extensions` on Windows). 63 | 64 | Alternatively, you can run `make package` (requires dependency `npm install vsce -g`) to generate VSCode extension package file (.vsix). It can be used with VSCode > Extensions > Install from VSIX. 65 | 66 | ### Supported Platforms 67 | 68 | Supports Windows, Linux and Mac (Intel chip only, does not work with Rosetta). For OSX/MacOS make sure your Hashlink version is `1.12.0` or higher and you ran `make codesign_osx` during installation. 69 | -------------------------------------------------------------------------------- /hld/HLDebugApi.hx: -------------------------------------------------------------------------------- 1 | package hld; 2 | import hld.Api; 3 | 4 | @:hlNative("std") 5 | class HLDebugApi implements Api { 6 | 7 | // HL low level api 8 | static function debugStart( pid : Int ) : Bool { return false; } 9 | static function debugStop( pid : Int ) : Void {} 10 | static function debugBreakpoint( pid : Int ) : Bool { return false; } 11 | static function debugRead( pid : Int, ptr : hl.Bytes, buffer : Buffer, size : Int ) : Bool { return false; } 12 | static function debugWrite( pid : Int, ptr : hl.Bytes, buffer : Buffer, size : Int ) : Bool { return false; } 13 | static function debugFlush( pid : Int, ptr : hl.Bytes, size : Int ) : Bool { return false; } 14 | static function debugWait( pid : Int, threadId : hl.Ref, timeout : Int ) : Int { return 0; } 15 | static function debugResume( pid : Int, tid : Int ) : Bool { return false; } 16 | static function debugReadRegister( pid : Int, tid : Int, register : Register, is64 : Bool ) : Pointer { return null; } 17 | static function debugWriteRegister( pid : Int, tid : Int, register : Register, v : Pointer, is64 : Bool ) : Bool { return false; } 18 | 19 | var pid : Int; 20 | var tmp : Buffer; 21 | var is64 : Bool; 22 | 23 | public function new( pid : Int, is64 : Bool ) { 24 | if( is64 && !hl.Api.is64() ) 25 | throw "You can't debug a 64 bit process from a 32 bit HL"; 26 | this.pid = pid; 27 | this.is64 = is64; 28 | tmp = new Buffer(1); 29 | } 30 | 31 | public function start() { 32 | return debugStart(pid); 33 | } 34 | 35 | public function stop() { 36 | debugStop(pid); 37 | } 38 | 39 | public function breakpoint() { 40 | return debugBreakpoint(pid); 41 | } 42 | 43 | public function read( ptr : Pointer, buffer : Buffer, size : Int ) : Bool { 44 | return debugRead(pid, ptr, buffer, size); 45 | } 46 | 47 | public function readByte( ptr : Pointer, pos : Int ) : Int { 48 | if( !read(ptr.offset(pos), cast tmp, 1) ) 49 | throw "Failed to read @" + ptr.toString(); 50 | return tmp.getUI8(0); 51 | } 52 | 53 | public function write( ptr : Pointer, buffer : Buffer, size : Int ) : Bool { 54 | return debugWrite(pid, ptr, buffer, size); 55 | } 56 | 57 | public function writeByte( ptr : Pointer, pos : Int, value : Int ) : Void { 58 | tmp.setUI8(0, value & 0xFF); 59 | if( !write(ptr.offset(pos), cast tmp, 1) ) 60 | throw "Failed to write @" + ptr.offset(pos).toString(); 61 | } 62 | 63 | public function flush( ptr : Pointer, size : Int ) : Bool { 64 | return debugFlush(pid, ptr, size); 65 | } 66 | 67 | public function wait( timeout : Int ) : { r : WaitResult, tid : Int } { 68 | var tid = 0; 69 | var r = debugWait(pid, tid, timeout); 70 | return { r : cast r, tid : tid }; 71 | } 72 | 73 | public function resume( tid : Int ) : Bool { 74 | return debugResume(pid, tid); 75 | } 76 | 77 | public function readRegister( tid : Int, register : Register ) : Pointer { 78 | return debugReadRegister(pid, tid, register, is64); 79 | } 80 | 81 | public function writeRegister( tid : Int, register : Register, v : Pointer ) : Bool { 82 | return debugWriteRegister(pid, tid, register, v, is64); 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | # This should disable running the workflow on tags 6 | branches: 7 | - "**" 8 | pull_request: 9 | workflow_dispatch: 10 | # https://github.blog/changelog/2020-07-06-github-actions-manual-triggers-with-workflow_dispatch/ 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v3 17 | - uses: krdlab/setup-haxe@v1 18 | with: 19 | haxe-version: 4.3.6 20 | - name: Print haxe version 21 | run: haxe -version 22 | - name: Install haxelib dependencies 23 | run: | 24 | haxelib git format https://github.com/HaxeFoundation/format.git 25 | haxelib git hscript https://github.com/HaxeFoundation/hscript.git 26 | haxelib install vshaxe 27 | haxelib install vscode 28 | haxelib install vscode-debugadapter 29 | - name: Build extension 30 | run: make build 31 | - name: Build standalone adapter.js 32 | run: haxe build.hxml 33 | - name: Build CLI hl 34 | run: | 35 | cd debugger 36 | haxe debugger.hxml 37 | cd .. 38 | - uses: actions/setup-node@v4 39 | with: 40 | node-version: '22.x' 41 | - name: Print npm and node version 42 | run: | 43 | npm --version 44 | node --version 45 | - name: Install global vsce package 46 | run: | 47 | npm install vsce -g 48 | npm list vsce -g 49 | - name: Package nightly build 50 | run: | 51 | export PKG_VER=$(npm pkg get version | xargs) 52 | export SHORT_HASH=$(git rev-parse --short HEAD) 53 | npm pkg set 'version'=${PKG_VER}-dev-${SHORT_HASH} 54 | # Prevent build node-gyp 55 | rm -f hldebug-wrapper/binding.gyp 56 | npm install 57 | # Double install workaround for node-addon-api invalid error (instead of deduped), see https://github.com/npm/cli/issues/4859 58 | npm install 59 | npm list node-addon-api 60 | make package 61 | cp haxe-hl-${PKG_VER}-dev-${SHORT_HASH}.vsix haxe-hl-dev.vsix 62 | - name: Upload extension artifact 63 | uses: actions/upload-artifact@v4 64 | with: 65 | name: vscode_extension 66 | path: haxe-hl-dev.vsix 67 | 68 | build_and_test_nightly: 69 | runs-on: ubuntu-latest 70 | steps: 71 | - uses: actions/checkout@v3 72 | - uses: krdlab/setup-haxe@v1 73 | with: 74 | haxe-version: latest 75 | - name: Print haxe version 76 | run: haxe -version 77 | - name: Install haxelib dependencies 78 | run: | 79 | haxelib git format https://github.com/HaxeFoundation/format.git 80 | haxelib git hscript https://github.com/HaxeFoundation/hscript.git 81 | haxelib install vshaxe 82 | haxelib install vscode 83 | haxelib install vscode-debugadapter 84 | - name: Build extension 85 | run: make build 86 | - name: Build standalone adapter.js 87 | run: haxe build.hxml 88 | - name: Build CLI hl 89 | run: | 90 | cd debugger 91 | haxe debugger.hxml 92 | cd .. 93 | - name: Install hl 94 | run: | 95 | git clone https://github.com/HaxeFoundation/hashlink.git hashlink 96 | sudo apt-get update 97 | sudo apt-get install -qqy libpng-dev libturbojpeg-dev libvorbis-dev libopenal-dev libsdl2-dev libglu1-mesa-dev libmbedtls-dev libuv1-dev libsqlite3-dev 98 | cd hashlink 99 | make 100 | sudo make install 101 | hl --version 102 | cd .. 103 | - name: Run tests 104 | run: | 105 | cd tests 106 | haxe RunCi.hxml 107 | cd .. 108 | -------------------------------------------------------------------------------- /hld/Pointer.hx: -------------------------------------------------------------------------------- 1 | package hld; 2 | 3 | #if hl 4 | abstract Pointer(hl.Bytes) to hl.Bytes { 5 | 6 | public var i64(get, never) : haxe.Int64; 7 | 8 | public inline function new(b) { 9 | this = b; 10 | } 11 | 12 | public inline function offset(pos:Int) : Pointer { 13 | return new Pointer(this.offset(pos)); 14 | } 15 | 16 | public inline function sub( p : Pointer ) : Int { 17 | return this.subtract(p); 18 | } 19 | 20 | public function toInt() { 21 | return this.address().low; 22 | } 23 | 24 | inline function addr() { 25 | return this.address(); 26 | } 27 | 28 | inline function get_i64() return this.address(); 29 | 30 | public inline function isNull() { 31 | return this == null; 32 | } 33 | 34 | @:op(a > b) static function opGt( a : Pointer, b : Pointer ) : Bool { 35 | return a.addr() > b.addr(); 36 | } 37 | @:op(a >= b) static function opGte( a : Pointer, b : Pointer ) : Bool { 38 | return a.addr() >= b.addr(); 39 | } 40 | @:op(a < b) static function opLt( a : Pointer, b : Pointer ) : Bool { 41 | return a.addr() < b.addr(); 42 | } 43 | @:op(a <= b) static function opLte( a : Pointer, b : Pointer ) : Bool { 44 | return a.addr() <= b.addr(); 45 | } 46 | @:op(a == b) static function opEq( a : Pointer, b : Pointer ) : Bool { 47 | return a.addr() == b.addr(); 48 | } 49 | @:op(a != b) static function opNeq( a : Pointer, b : Pointer ) : Bool { 50 | return a.addr() != b.addr(); 51 | } 52 | 53 | public function toString() { 54 | var i = this.address(); 55 | if( i.high == 0 ) 56 | return "0x" + StringTools.hex(i.low); 57 | return "0x" + StringTools.hex(i.high) + StringTools.hex(i.low, 8); 58 | } 59 | 60 | public static function ofPtr( p : hl.Bytes ) : Pointer { 61 | return cast p; 62 | } 63 | 64 | public static function make( low : Int, high : Int ) { 65 | return new Pointer(hl.Bytes.fromAddress(haxe.Int64.make(high, low))); 66 | } 67 | 68 | } 69 | #else 70 | abstract Pointer(haxe.Int64) to haxe.Int64 { 71 | 72 | public var i64(get, never) : haxe.Int64; 73 | 74 | public inline function new(b) { 75 | this = b; 76 | } 77 | 78 | inline function get_i64() return this; 79 | 80 | public inline function offset(pos:Int) : Pointer { 81 | return new Pointer(this + pos); 82 | } 83 | 84 | public function sub( p : Pointer ) : Int { 85 | var d = this - p.i64; 86 | if( d.high != d.low >> 31 ) 87 | return d.high > 0 ? 0x7FFFFFFF : 0x80000000; // overflow 88 | return d.low; 89 | } 90 | 91 | public function toInt() { 92 | return this.low; 93 | } 94 | 95 | public inline function isNull() { 96 | return this == null || (this.low == 0 && this.high == 0); 97 | } 98 | 99 | @:op(a > b) static function opGt( a : Pointer, b : Pointer ) : Bool { 100 | return a.i64 > b.i64; 101 | } 102 | @:op(a >= b) static function opGte( a : Pointer, b : Pointer ) : Bool { 103 | return a.i64 >= b.i64; 104 | } 105 | @:op(a < b) static function opLt( a : Pointer, b : Pointer ) : Bool { 106 | return a.i64 < b.i64; 107 | } 108 | @:op(a <= b) static function opLte( a : Pointer, b : Pointer ) : Bool { 109 | return a.i64 <= b.i64; 110 | } 111 | @:op(a == b) static function opEq( a : Pointer, b : Pointer ) : Bool { 112 | return a.i64 == b.i64; 113 | } 114 | @:op(a != b) static function opNeq( a : Pointer, b : Pointer ) : Bool { 115 | return a.i64 != b.i64; 116 | } 117 | 118 | public function toString() { 119 | if( this.high == 0 ) 120 | return "0x" + StringTools.hex(this.low); 121 | return "0x" + StringTools.hex(this.high) + StringTools.hex(this.low, 8); 122 | } 123 | 124 | public static function ofPtr( p : haxe.Int64 ) : Pointer { 125 | return cast p; 126 | } 127 | 128 | public static function make( low : Int, high : Int ) { 129 | return new Pointer(haxe.Int64.make(high, low)); 130 | } 131 | 132 | } 133 | #end 134 | -------------------------------------------------------------------------------- /hld/NodeDebugApiNative.hx: -------------------------------------------------------------------------------- 1 | package hld; 2 | import hld.Api; 3 | 4 | @:jsRequire("hldebug") 5 | extern class Native { 6 | static function echo( n : String, len : Int ) : String; 7 | static function debugStart( pid : Int ) : Bool; 8 | static function debugStop( pid : Int ) : Void; 9 | static function debugBreakpoint( pid : Int ) : Bool; 10 | static function debugRead( pid : Int, ptr : String, size : Int ) : String; 11 | static function debugWrite( pid : Int, ptr : String, buffer : String, size : Int ) : Bool; 12 | static function debugFlush( pid : Int, ptr : String, size : Int ) : Bool; 13 | static function debugWait( pid : Int, timeout : Int ) : js.node.Buffer; 14 | static function debugResume( pid : Int, tid : Int ) : Bool; 15 | static function debugReadRegister( pid : Int, tid : Int, register : Register, is64 : Bool ) : String; 16 | static function debugWriteRegister( pid : Int, tid : Int, register : Register, v : String, is64 : Bool ) : Bool; 17 | } 18 | 19 | class NodeDebugApiNative implements Api { 20 | var pid : Int; 21 | var tmp : Buffer; 22 | var tmpByte : Buffer; 23 | var is64 : Bool; 24 | 25 | public function new( pid : Int, is64 : Bool ) { 26 | /*if( is64 && untyped(js.Node).arch != "x64" ) 27 | throw "You can't debug a 64 bit process from a 32 bit nodejs";*/ 28 | this.pid = pid; 29 | this.is64 = is64; 30 | tmp = new Buffer(8); 31 | tmpByte = new Buffer(4); 32 | } 33 | 34 | function makePointer( ptr : Pointer ) : hld.Buffer { 35 | tmp.setI32(0, ptr.i64.low); 36 | tmp.setI32(4, ptr.i64.high); 37 | return tmp; 38 | } 39 | 40 | public function start() { 41 | return Native.debugStart(pid); 42 | } 43 | 44 | public function stop() { 45 | Native.debugStop(pid); 46 | } 47 | 48 | public function breakpoint() { 49 | return Native.debugBreakpoint(pid); 50 | } 51 | 52 | public function read( ptr : Pointer, buffer : Buffer, size : Int ) : Bool { 53 | var b = Buffer.fromBinaryString(Native.debugRead(pid, makePointer(ptr).toBinaryString(), size)); 54 | @:privateAccess buffer.buf = b.buf; 55 | return true; 56 | } 57 | 58 | function internalRead( ptr : Pointer, size : Int ) : Buffer { 59 | return Buffer.fromBinaryString(Native.debugRead(pid, makePointer(ptr).toBinaryString(), size)); 60 | } 61 | 62 | public function readByte( ptr : Pointer, pos : Int ) : Int { 63 | var b = internalRead(ptr.offset(pos), 1); 64 | return b.getUI8(0); 65 | } 66 | 67 | public function write( ptr : Pointer, buffer : Buffer, size : Int ) : Bool { 68 | return Native.debugWrite(pid, makePointer(ptr).toBinaryString(), buffer.toBinaryString(), size); 69 | } 70 | 71 | public function writeByte( ptr : Pointer, pos : Int, value : Int ) : Void { 72 | tmpByte.setUI8(0, value & 0xFF); 73 | if( !write(ptr.offset(pos), tmpByte, 1) ) 74 | throw "Failed to write @" + ptr.offset(pos).toString(); 75 | } 76 | 77 | public function flush( ptr : Pointer, size : Int ) : Bool { 78 | return Native.debugFlush(pid, makePointer(ptr).toBinaryString(), size); 79 | } 80 | 81 | public function wait( timeout : Int ) : { r : WaitResult, tid : Int } { 82 | var buf = Native.debugWait(pid, timeout); 83 | var r = buf.readInt32LE(0); 84 | var tid = buf.readInt32LE(4); 85 | return { r: cast r, tid: tid }; 86 | } 87 | 88 | public function resume( tid : Int ) : Bool { 89 | return Native.debugResume(pid, tid); 90 | } 91 | 92 | public function readRegister( tid : Int, register : Register ) : Pointer { 93 | var buf = Buffer.fromBinaryString(Native.debugReadRegister(pid, tid, register, is64)); 94 | if(buf == null) 95 | return null; 96 | if( register == EFlags ) 97 | return Pointer.make(buf.getUI16(0), 0); 98 | if( !is64 ) 99 | return Pointer.make(buf.getI32(0), 0); 100 | return Pointer.make(buf.getI32(0), buf.getI32(4)); 101 | } 102 | 103 | public function writeRegister( tid : Int, register : Register, v : Pointer ) : Bool { 104 | return Native.debugWriteRegister(pid, tid, register, makePointer(v).toBinaryString(), is64); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /hld/Buffer.hx: -------------------------------------------------------------------------------- 1 | package hld; 2 | 3 | #if hl 4 | 5 | @:forward(getI32, getUI8, setI32, setUI8, getUI16, getF32, getF64, setF64) 6 | abstract Buffer(hl.Bytes) { 7 | 8 | public function new(size) { 9 | this = new hl.Bytes(size); 10 | } 11 | 12 | public function readStringUCS2( pos : Int, length : Int ) { 13 | return @:privateAccess String.fromUCS2(this.sub(pos,(length + 1) << 1)); 14 | } 15 | 16 | public function readStringUTF8() { 17 | return @:privateAccess String.fromUTF8(this); 18 | } 19 | 20 | public function getPointer( pos : Int, align : Align ) { 21 | if( align.is64 ) 22 | return Pointer.make(this.getI32(pos), this.getI32(pos + 4)); 23 | return Pointer.make(this.getI32(pos), 0); 24 | } 25 | 26 | public function setPointer( pos : Int, v : Pointer, align : Align ) { 27 | if( align.is64 ) { 28 | var a = v.i64; 29 | this.setI32(pos, a.low); 30 | this.setI32(pos + 4, a.high); 31 | } else 32 | this.setI32(pos, v.toInt()); 33 | } 34 | 35 | } 36 | 37 | #else 38 | 39 | 40 | class Buffer { 41 | var buf : js.node.Buffer; 42 | 43 | public function new(?size : Int) { 44 | if(size != null) 45 | buf = js.node.Buffer.alloc(size); 46 | } 47 | 48 | public static function fromNodeBuffer(buffer : js.node.Buffer) : Buffer { 49 | var b = new Buffer(); 50 | b.buf = buffer; 51 | return b; 52 | } 53 | 54 | public static function fromArrayBuffer(buffer : js.lib.ArrayBuffer) : Buffer { 55 | var b = new Buffer(); 56 | b.buf = js.node.Buffer.from(buffer); 57 | return b; 58 | } 59 | 60 | public function getPointer( pos : Int, align : Align ) { 61 | if( align.is64 ) 62 | return Pointer.make(getI32(pos), getI32(pos + 4)); 63 | return Pointer.make(getI32(pos), 0); 64 | } 65 | 66 | public function setPointer( pos : Int, v : Pointer, align : Align ) { 67 | if( align.is64 ) { 68 | var a = v.i64; 69 | setI32(pos, a.low); 70 | setI32(pos + 4, a.high); 71 | } else 72 | setI32(pos, v.toInt()); 73 | } 74 | 75 | public inline function getI32(pos) { 76 | return buf.readInt32LE(pos); 77 | } 78 | 79 | public inline function getUI8(pos) { 80 | return buf.readUInt8(pos); 81 | } 82 | 83 | public inline function getUI16(pos) { 84 | return buf.readUInt16LE(pos); 85 | } 86 | 87 | public inline function getF32(pos) { 88 | return buf.readFloatLE(pos); 89 | } 90 | 91 | public inline function getF64(pos) { 92 | return buf.readDoubleLE(pos); 93 | } 94 | 95 | public inline function setI32(pos,value) { 96 | buf.writeInt32LE(value, pos); 97 | } 98 | 99 | public inline function setF64(pos,value) { 100 | buf.writeDoubleLE(value, pos); 101 | } 102 | 103 | public inline function setUI16(pos,value) { 104 | buf.writeUInt16LE(value, pos); 105 | } 106 | 107 | public inline function setUI8(pos,value) { 108 | buf.writeUInt8(value, pos); 109 | } 110 | 111 | public function toBinaryString() : String { 112 | if( buf.length%2 == 1 ) { 113 | // bugfix with utf16 with odd size (1 char gets ignored) 114 | var nbuf = new js.node.Buffer(buf.length+1); 115 | buf.copy(nbuf); 116 | return nbuf.toString("utf16le"); 117 | } 118 | return buf.toString("utf16le"); 119 | } 120 | 121 | public function toNodeBuffer() : js.node.Buffer { 122 | return buf; 123 | } 124 | 125 | public static function fromBinaryString(str : String) : Buffer { 126 | var b = new Buffer(); 127 | b.buf = js.node.Buffer.from(str, 'utf16le'); 128 | return b; 129 | } 130 | 131 | public function readStringUCS2(pos, length) { 132 | var str = ""; 133 | for( i in 0...length ) { 134 | var c = getUI16(pos); 135 | str += String.fromCharCode(c); 136 | pos += 2; 137 | } 138 | return str; 139 | } 140 | 141 | public function readStringUTF8() { 142 | var b = new haxe.io.BytesBuffer(); 143 | var pos = 0; 144 | while( true ) { 145 | var c = getUI8(pos++); 146 | if( c == 0 ) break; 147 | b.addByte(c); 148 | } 149 | return b.getBytes().toString(); 150 | } 151 | 152 | } 153 | 154 | #end 155 | -------------------------------------------------------------------------------- /hld/JitInfo.hx: -------------------------------------------------------------------------------- 1 | package hld; 2 | 3 | private enum DebugFlag { 4 | Is64; // runs in 64 bit mode 5 | Bool4; // bool = 4 bytes (instead of 1) 6 | Threads; // was compiled with threads support 7 | IsWinCall; 8 | } 9 | 10 | class JitInfo { 11 | 12 | public var is64(default, null) : Bool; 13 | public var isWinCall(default, null) : Bool; 14 | public var align(default,null) : Align; 15 | public var hasThreads(get,never) : Bool; 16 | public var pid(default,null) : Int = 0; 17 | 18 | var flags : haxe.EnumFlags; 19 | var input : haxe.io.Input; 20 | 21 | public var oldThreadInfos : { id : Int, stackTop : Pointer, debugExc : Pointer }; 22 | 23 | public var hlVersion : Float; 24 | public var globals : Pointer; 25 | var codeStart : Pointer; 26 | var codeEnd : Pointer; 27 | public var threads : Pointer; 28 | var codeSize : Int; 29 | var allTypes : Pointer; 30 | 31 | var functions : Array<{ start : Pointer, large : Bool, offsets : haxe.io.Bytes }>; 32 | var functionByCodePos : Int64Map; 33 | var module : Module; 34 | 35 | public function new() { 36 | } 37 | 38 | function get_hasThreads() { 39 | return oldThreadInfos != null || flags.has(Threads); 40 | } 41 | 42 | private function readPointer() : Pointer { 43 | if( is64 ) 44 | return Pointer.make(input.readInt32(), input.readInt32()); 45 | return Pointer.make(input.readInt32(),0); 46 | } 47 | 48 | public function read( input : haxe.io.Input, module : Module ) { 49 | this.input = input; 50 | this.module = module; 51 | 52 | if( input.readString(3) != "HLD" ) 53 | return false; 54 | var version = input.readByte() - "0".code; 55 | if( version > 1 ) 56 | return false; 57 | flags = haxe.EnumFlags.ofInt(input.readInt32()); 58 | is64 = flags.has(Is64); 59 | align = new Align(is64, flags.has(Bool4)?4:1); 60 | isWinCall = flags.has(IsWinCall) || Sys.systemName() == "Windows" /* todo : disable this for cross platform remote debug */; 61 | 62 | if( version == 0 ) { 63 | var mainThread = input.readInt32(); 64 | globals = readPointer(); 65 | var debugExc = readPointer(); 66 | var stackTop = readPointer(); 67 | oldThreadInfos = { id : mainThread, stackTop : stackTop, debugExc : debugExc }; 68 | hlVersion = 1.05; 69 | } else { 70 | var ver = input.readInt32(); 71 | hlVersion = (ver >> 16) + ((ver >> 8) & 0xFF) / 100; 72 | if( hlVersion >= 1.07 ) 73 | pid = input.readInt32(); 74 | threads = readPointer(); 75 | globals = readPointer(); 76 | } 77 | codeStart = readPointer(); 78 | codeSize = input.readInt32(); 79 | codeEnd = codeStart.offset(codeSize); 80 | allTypes = readPointer(); 81 | functions = []; 82 | 83 | var structSizes = [0]; 84 | for( i in 1...9 ) 85 | structSizes[i] = input.readInt32(); 86 | @:privateAccess align.structSizes = structSizes; 87 | 88 | var nfunctions = input.readInt32(); 89 | if( nfunctions != module.code.functions.length ) 90 | return false; 91 | 92 | functionByCodePos = new Int64Map(); 93 | for( i in 0...nfunctions ) { 94 | var nops = input.readInt32(); 95 | if( module.code.functions[i].debug.length >> 1 != nops ) 96 | return false; 97 | var start = codeStart.offset(input.readInt32()); 98 | var large = input.readByte() != 0; 99 | var offsets = input.read((nops + 1) * (large ? 4 : 2)); 100 | functionByCodePos.set(start.i64, i); 101 | functions.push({ 102 | start : start, 103 | large : large, 104 | offsets : offsets, 105 | }); 106 | } 107 | return true; 108 | } 109 | 110 | public function getFunctionPos( fidx : Int ) : Pointer { 111 | return functions[fidx].start; 112 | } 113 | 114 | public function getCodePos( fidx : Int, pos : Int ) : Pointer { 115 | var dbg = functions[fidx]; 116 | return dbg.start.offset(dbg.large ? dbg.offsets.getInt32(pos << 2) : dbg.offsets.getUInt16(pos << 1)); 117 | } 118 | 119 | public function isCodePtr( codePtr : Pointer ) : Bool { 120 | if( codePtr < codeStart || codePtr > codeEnd ) 121 | return false; 122 | return true; 123 | } 124 | 125 | public function codePtrToString( codePtr : Pointer ) : String { 126 | if( codePtr < codeStart || codePtr > codeEnd ) 127 | return '$codePtr'; 128 | return '$codePtr(${codePtr.sub(codeStart)})'; 129 | } 130 | 131 | public function resolveAsmPos( codePtr : Pointer ) : Null { 132 | if( !isCodePtr(codePtr) ) 133 | return null; 134 | var min = 0; 135 | var max = functions.length; 136 | while( min < max ) { 137 | var mid = (min + max) >> 1; 138 | var p = functions[mid]; 139 | if( p.start <= codePtr ) 140 | min = mid + 1; 141 | else 142 | max = mid; 143 | } 144 | if( min == 0 ) 145 | return null; 146 | var fidx = (min - 1); 147 | var dbg = functions[fidx]; 148 | var fdebug = module.code.functions[fidx]; 149 | min = 0; 150 | max = fdebug.debug.length>>1; 151 | var relPos = codePtr.sub(dbg.start); 152 | while( min < max ) { 153 | var mid = (min + max) >> 1; 154 | var offset = dbg.large ? dbg.offsets.getInt32(mid * 4) : dbg.offsets.getUInt16(mid * 2); 155 | if( offset <= relPos ) 156 | min = mid + 1; 157 | else 158 | max = mid; 159 | } 160 | return { fidx : fidx, fpos : min - 1, codePos : codePtr, ebp : null }; 161 | } 162 | 163 | public function functionFromAddr( p : Pointer ) { 164 | return functionByCodePos.get(p.i64); 165 | } 166 | 167 | } 168 | -------------------------------------------------------------------------------- /src/Extension.hx: -------------------------------------------------------------------------------- 1 | import js.lib.Promise; 2 | import vscode.*; 3 | import Utils; 4 | 5 | class Extension { 6 | @:expose("activate") 7 | static function main(context:ExtensionContext) { 8 | Vscode.debug.registerDebugConfigurationProvider("hl", {resolveDebugConfiguration: resolveDebugConfiguration, 9 | resolveDebugConfigurationWithSubstitutedVariables: resolveDebugConfigurationWithSubstitutedVariables}); 10 | Vscode.debug.registerDebugAdapterDescriptorFactory("hl", {createDebugAdapterDescriptor: createDebugAdapterDescriptor}); 11 | Vscode.debug.onDidChangeActiveDebugSession(onDidChangeActiveDebugSession); 12 | context.subscriptions.push(Vscode.commands.registerCommand("hldebug.var.formatInt", args -> formatInt(args))); 13 | } 14 | 15 | // Run before preLaunchTask 16 | static function resolveDebugConfiguration(folder:Null, config:DebugConfiguration, 17 | ?token:CancellationToken):ProviderResult { 18 | var config:DebugConfiguration & Arguments = cast config; 19 | if (config.type == null) { 20 | return null; // show launch.json 21 | } 22 | return new Promise(function(resolve:DebugConfiguration->Void, reject) { 23 | var vshaxe:Vshaxe = Vscode.extensions.getExtension("nadako.vshaxe").exports; 24 | vshaxe.getActiveConfiguration().then(function(haxeConfig) { 25 | switch haxeConfig.target { 26 | case Hl(file): 27 | if (config.program == null) { 28 | config.program = file; 29 | } 30 | if (StringTools.endsWith(config.program, ".c")) { 31 | reject('Plase use a HashLink/JIT configuration (found "${config.program}" instead).'); 32 | return; 33 | } 34 | config.classPaths = haxeConfig.classPaths.map(cp -> cp.path); 35 | resolve(config); 36 | 37 | case _: 38 | reject('Please use a Haxe configuration that targets HashLink (found target "${haxeConfig.target.getName().toLowerCase()}" instead).'); 39 | } 40 | }, function(error) { 41 | reject("Unable to retrieve active Haxe configuration: " + error); 42 | }); 43 | }); 44 | } 45 | 46 | // Run after preLaunchTask 47 | static function resolveDebugConfigurationWithSubstitutedVariables(folder:Null, config:DebugConfiguration, 48 | ?token:CancellationToken):ProviderResult { 49 | var config:DebugConfiguration & Arguments = cast config; 50 | if( Sys.systemName() == "Mac" ) { 51 | final hl = config.hl != null ? config.hl : 'hl'; 52 | var hlVersion:String = js.node.ChildProcess.execSync('$hl --version'); 53 | if( hlVersion <= "1.11.0" ) { 54 | final visitButton = "Get from GitHub"; 55 | Vscode.window.showErrorMessage('Your version of Hashlink (${hlVersion}) does not support debugging on Mac. Install a newer version from here:', null, visitButton).then(function(choice) { 56 | if( choice == visitButton ) { 57 | Vscode.env.openExternal(Uri.parse("https://github.com/HaxeFoundation/hashlink")); 58 | } 59 | }); 60 | return null; 61 | } 62 | var validSignature = false; 63 | try { 64 | var entitlements:String = js.node.ChildProcess.execSync('codesign -d --entitlements - "$$(which $hl)"'); 65 | validSignature = entitlements.indexOf("com.apple.security.get-task-allow") >= 0; 66 | } catch( ex: Dynamic ) {} 67 | if( !validSignature ) { 68 | Vscode.window.showErrorMessage('Your Hashlink executable is not properly codesigned. Run `make codesign_osx` during Hashlink installation.\n\nPath: ${hl}'); 69 | return null; 70 | } 71 | } 72 | return config; 73 | } 74 | 75 | static function createDebugAdapterDescriptor(session: DebugSession, ?executable:DebugAdapterExecutable): ProviderResult { 76 | var config = Vscode.workspace.getConfiguration("hldebug"); 77 | var isVerbose = config.get("verbose", false); 78 | var defaultPort = config.get("defaultPort", 6112); 79 | var connectionTimeout = config.get("connectionTimeout", 2); 80 | 81 | /* 82 | // Can be used to communicate with one built-in adapter during execution. Build with -lib format -lib hscript. 83 | if( HLAdapter.inst == null ) { 84 | HLAdapter.DEBUG = isVerbose; 85 | HLAdapter.DEFAULT_PORT = defaultPort; 86 | var adapter = new HLAdapter(); 87 | return new vscode.DebugAdapterInlineImplementation(cast adapter); 88 | } 89 | */ 90 | 91 | if( executable == null ) 92 | Vscode.window.showErrorMessage("No executable specified. Please check your configuration."); 93 | if( isVerbose ) 94 | executable.args.push("--verbose"); 95 | executable.args.push("--defaultPort"); 96 | executable.args.push("" + defaultPort); 97 | executable.args.push("--connectionTimeout"); 98 | executable.args.push("" + connectionTimeout); 99 | return executable; 100 | } 101 | 102 | static var currentSession : DebugSession = null; 103 | static function onDidChangeActiveDebugSession(session: Null) { 104 | currentSession?.customRequest(CustomRequestCommand.OnSessionInactive); 105 | session?.customRequest(CustomRequestCommand.OnSessionActive); 106 | currentSession = session; 107 | } 108 | 109 | static function formatInt( args:VariableContext ) { 110 | var i = Std.parseInt(args.variable.value); 111 | if (i == null) 112 | return; 113 | var msg = args.variable.name + "(" + i + ") = 0x" + Utils.toString(i,16) + " = 0b" + Utils.toString(i,2); 114 | msg += "\n\n(You can also add `:h` `:b` suffix to variables in the watch section.)"; 115 | Vscode.window.showInformationMessage(msg); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /bindings.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Module dependencies. 4 | */ 5 | 6 | var fs = require('fs') 7 | , path = require('path') 8 | , join = path.join 9 | , dirname = path.dirname 10 | , exists = ((fs.accessSync && function (path) { try { fs.accessSync(path); } catch (e) { return false; } return true; }) 11 | || fs.existsSync || path.existsSync) 12 | , defaults = { 13 | arrow: process.env.NODE_BINDINGS_ARROW || ' → ' 14 | , compiled: process.env.NODE_BINDINGS_COMPILED_DIR || 'compiled' 15 | , platform: process.platform 16 | , arch: process.arch 17 | , version: process.versions.node 18 | , bindings: 'bindings.node' 19 | , try: [ 20 | // node-gyp's linked version in the "build" dir 21 | [ 'module_root', 'build', 'bindings' ] 22 | // *** CUSTOM EDIT *** 23 | , [ 'module_root', 'build', 'platform', 'bindings' ] 24 | // node-waf and gyp_addon (a.k.a node-gyp) 25 | , [ 'module_root', 'build', 'Debug', 'bindings' ] 26 | , [ 'module_root', 'build', 'Release', 'bindings' ] 27 | // Debug files, for development (legacy behavior, remove for node v0.9) 28 | , [ 'module_root', 'out', 'Debug', 'bindings' ] 29 | , [ 'module_root', 'Debug', 'bindings' ] 30 | // Release files, but manually compiled (legacy behavior, remove for node v0.9) 31 | , [ 'module_root', 'out', 'Release', 'bindings' ] 32 | , [ 'module_root', 'Release', 'bindings' ] 33 | // Legacy from node-waf, node <= 0.4.x 34 | , [ 'module_root', 'build', 'default', 'bindings' ] 35 | // Production "Release" buildtype binary (meh...) 36 | , [ 'module_root', 'compiled', 'version', 'platform', 'arch', 'bindings' ] 37 | ] 38 | } 39 | 40 | /** 41 | * The main `bindings()` function loads the compiled bindings for a given module. 42 | * It uses V8's Error API to determine the parent filename that this function is 43 | * being invoked from, which is then used to find the root directory. 44 | */ 45 | 46 | function bindings (opts) { 47 | 48 | // Argument surgery 49 | if (typeof opts == 'string') { 50 | opts = { bindings: opts } 51 | } else if (!opts) { 52 | opts = {} 53 | } 54 | 55 | // maps `defaults` onto `opts` object 56 | Object.keys(defaults).map(function(i) { 57 | if (!(i in opts)) opts[i] = defaults[i]; 58 | }); 59 | 60 | // Get the module root 61 | if (!opts.module_root) { 62 | opts.module_root = exports.getRoot(exports.getFileName()) 63 | } 64 | 65 | // Ensure the given bindings name ends with .node 66 | if (path.extname(opts.bindings) != '.node') { 67 | opts.bindings += '.node' 68 | } 69 | 70 | var tries = [] 71 | , i = 0 72 | , l = opts.try.length 73 | , n 74 | , b 75 | , err 76 | 77 | var lasterr; 78 | 79 | for (; i 3 | #include 4 | 5 | extern "C" { 6 | bool hl_debug_start(int pid); 7 | bool hl_debug_stop(int pid); 8 | bool hl_debug_breakpoint(int pid); 9 | bool hl_debug_read(int pid, vbyte* addr, vbyte* buffer, int size); 10 | bool hl_debug_write(int pid, vbyte* addr, vbyte* buffer, int size); 11 | bool hl_debug_flush(int pid, vbyte* addr, int size); 12 | int hl_debug_wait(int pid, int* thread, int timeout); 13 | bool hl_debug_resume(int pid, int thread); 14 | void* hl_debug_read_register(int pid, int thread, int reg, bool is64); 15 | bool hl_debug_write_register(int pid, int thread, int reg, void* value, bool is64); 16 | } 17 | 18 | using namespace Napi; 19 | 20 | inline Napi::String data_to_napi_string(Napi::Env env, void* in_data, int in_len) { 21 | return Napi::String::New(env, (const char16_t*)in_data, in_len/2); 22 | } 23 | 24 | inline std::u16string data_from_napi_string(Napi::String napi_string) { 25 | return napi_string.Utf16Value(); 26 | } 27 | 28 | Napi::Boolean debugStart(const Napi::CallbackInfo& info) { 29 | Napi::Env env = info.Env(); 30 | 31 | int pid = info[0].As().Int32Value(); 32 | 33 | return Napi::Boolean::New(env, hl_debug_start(pid)); 34 | } 35 | 36 | Napi::Boolean debugStop(const Napi::CallbackInfo& info) { 37 | Napi::Env env = info.Env(); 38 | 39 | int pid = info[0].As().Int32Value(); 40 | 41 | return Napi::Boolean::New(env, hl_debug_stop(pid)); 42 | } 43 | 44 | Napi::Boolean debugBreakpoint(const Napi::CallbackInfo& info) { 45 | Napi::Env env = info.Env(); 46 | 47 | int pid = info[0].As().Int32Value(); 48 | 49 | return Napi::Boolean::New(env, hl_debug_breakpoint(pid)); 50 | } 51 | 52 | Napi::String debugRead(const Napi::CallbackInfo& info) { 53 | Napi::Env env = info.Env(); 54 | 55 | int pid = info[0].As().Int32Value(); 56 | std::u16string ptr = data_from_napi_string(info[1].ToString()); 57 | int size = info[2].As().Int32Value(); 58 | 59 | int bufsize = size; 60 | if (bufsize % 2 == 1) 61 | bufsize++; 62 | vbyte* rbuf = (vbyte*) malloc(bufsize); 63 | rbuf[bufsize-1] = 0; 64 | Napi::Boolean r = Napi::Boolean::New(env, hl_debug_read(pid, *(vbyte**)ptr.c_str(), rbuf, size)); 65 | Napi::String ostr = data_to_napi_string(env, rbuf, bufsize); 66 | free(rbuf); 67 | 68 | return ostr; 69 | } 70 | 71 | Napi::Boolean debugWrite(const Napi::CallbackInfo& info) { 72 | Napi::Env env = info.Env(); 73 | 74 | int pid = info[0].As().Int32Value(); 75 | std::u16string ptr = data_from_napi_string(info[1].ToString()); 76 | std::u16string buffer = data_from_napi_string(info[2].ToString()); 77 | int size = info[3].As().Int32Value(); 78 | 79 | bool r = hl_debug_write(pid, *(vbyte**)ptr.c_str(), (vbyte*) buffer.c_str(), size); 80 | return Napi::Boolean::New(env, r); 81 | } 82 | 83 | Napi::Boolean debugFlush(const Napi::CallbackInfo& info) { 84 | Napi::Env env = info.Env(); 85 | 86 | int pid = info[0].As().Int32Value(); 87 | std::u16string ptr = data_from_napi_string(info[1].ToString()); 88 | int size = info[2].As().Int32Value(); 89 | 90 | Napi::Boolean r = Napi::Boolean::New(env, hl_debug_flush(pid, *(vbyte**)ptr.c_str(), size)); 91 | return r; 92 | } 93 | 94 | Napi::Buffer debugWait(const Napi::CallbackInfo& info) { 95 | Napi::Env env = info.Env(); 96 | 97 | int pid = info[0].As().Int32Value(); 98 | int size = info[1].As().Int32Value(); 99 | 100 | int threadId; 101 | int r = hl_debug_wait(pid, &threadId, size); 102 | 103 | Napi::Buffer buffer = Napi::Buffer::New(env, 2); 104 | buffer.Data()[0] = r; 105 | buffer.Data()[1] = threadId; 106 | return buffer; 107 | } 108 | 109 | Napi::Boolean debugResume(const Napi::CallbackInfo& info) { 110 | Napi::Env env = info.Env(); 111 | int pid = info[0].As().Int32Value(); 112 | int tid = info[1].As().Int32Value(); 113 | return Napi::Boolean::New(env, hl_debug_resume(pid, tid)); 114 | } 115 | 116 | Napi::String debugReadRegister(const Napi::CallbackInfo& info) { 117 | Napi::Env env = info.Env(); 118 | int pid = info[0].As().Int32Value(); 119 | int tid = info[1].As().Int32Value(); 120 | int reg = info[2].As().Int32Value(); 121 | bool is64 = info[3].As().Value(); 122 | void* r = hl_debug_read_register(pid, tid, reg, is64); 123 | Napi::String ostr; 124 | if(reg == 3) 125 | ostr = data_to_napi_string(env, &r, 2); 126 | else 127 | ostr = data_to_napi_string(env, &r, 8); 128 | return ostr; 129 | } 130 | 131 | Napi::Boolean debugWriteRegister(const Napi::CallbackInfo& info) { 132 | Napi::Env env = info.Env(); 133 | 134 | int pid = info[0].As().Int32Value(); 135 | int tid = info[1].As().Int32Value(); 136 | int reg = info[2].As().Int32Value(); 137 | std::u16string buffer = data_from_napi_string(info[3].ToString()); 138 | bool is64 = info[4].As().Value(); 139 | 140 | return Napi::Boolean::New(env, hl_debug_write_register(pid, tid, reg, *(vbyte**) buffer.c_str(), is64)); 141 | } 142 | 143 | Napi::Object Init(Napi::Env env, Napi::Object exports) { 144 | exports.Set(Napi::String::New(env, "debugStart"), Napi::Function::New(env, debugStart)); 145 | exports.Set(Napi::String::New(env, "debugStop"), Napi::Function::New(env, debugStop)); 146 | exports.Set(Napi::String::New(env, "debugBreakpoint"), Napi::Function::New(env, debugBreakpoint)); 147 | exports.Set(Napi::String::New(env, "debugRead"), Napi::Function::New(env, debugRead)); 148 | exports.Set(Napi::String::New(env, "debugWrite"), Napi::Function::New(env, debugWrite)); 149 | exports.Set(Napi::String::New(env, "debugFlush"), Napi::Function::New(env, debugFlush)); 150 | exports.Set(Napi::String::New(env, "debugWait"), Napi::Function::New(env, debugWait)); 151 | exports.Set(Napi::String::New(env, "debugResume"), Napi::Function::New(env, debugResume)); 152 | exports.Set(Napi::String::New(env, "debugReadRegister"), Napi::Function::New(env, debugReadRegister)); 153 | exports.Set(Napi::String::New(env, "debugWriteRegister"), Napi::Function::New(env, debugWriteRegister)); 154 | return exports; 155 | } 156 | 157 | NODE_API_MODULE(node_debugger, Init) 158 | -------------------------------------------------------------------------------- /hld/Value.hx: -------------------------------------------------------------------------------- 1 | package hld; 2 | 3 | enum ValueRepr { 4 | VUndef; 5 | VNull; 6 | VInt( i : Int ); 7 | VInt64( i : haxe.Int64 ); 8 | VFloat( v : Float ); 9 | VBool( b : Bool ); 10 | VPointer( v : Pointer ); 11 | VString( v : String, p : Pointer ); 12 | VClosure( f : FunRepr, d : Value, p : Pointer ); 13 | VFunction( f : FunRepr, p : Pointer ); 14 | VMethod( f : FunRepr, obj : Value, p : Pointer ); 15 | VArray( t : HLType, length : Int, read : Int -> Value, p : Pointer ); 16 | VMap( tkey : HLType, nkeys : Int, readKey : Int -> Value, readValue : Int -> Value, p : Pointer ); 17 | VMapPair( key : Value, value : Value ); 18 | VType( t : HLType ); 19 | VEnum( c : String, values : Array, p : Pointer ); 20 | VBytes( length : Int, read : Int -> Int, p : Pointer ); 21 | VInlined( fields : Array ); 22 | VGuid( i : haxe.Int64, name : String ); 23 | } 24 | 25 | enum FunRepr { 26 | FUnknown( p : Pointer ); 27 | FIndex( i : Int, p : Pointer ); 28 | } 29 | 30 | typedef InlinedField = { name : String, addr : Eval.VarAddress } 31 | 32 | enum Hint { 33 | HNone; 34 | HHex; // v:h 35 | HBin; // v:b 36 | HPointer; // v:p 37 | HEscape; // v:s 38 | HNoEscape; 39 | HGuid; // v:GUID 40 | HReadBytes(t : HLType, pos : String); // v:UI8(0), v:UI16(0), v:I32(0), v:I64(0), v:F32(0), v:F64(0) 41 | HArray(t : HLType, size : Null, pos : Null); // v:Array, v:Array, v:Array[pos] (T=Int,Float,UI8,UI16,I32,I64,F32,F64) 42 | HEnumFlags(t : String); // v:EnumFlags, v:haxe.EnumFlags 43 | HEnumIndex(t : String); // v:EnumIndex 44 | HCArray(t : String, size : Null, pos : Null); // v:CArray, v:CArray[pos] 45 | HCdbEnum(t : String); // v:CDB, v:CDBEnum -- for CastleDB 46 | } 47 | 48 | @:structInit class Value { 49 | public var v : ValueRepr; 50 | public var t : HLType; 51 | @:optional public var hint : Hint = HNone; 52 | 53 | // --- static --- 54 | 55 | public static inline function extractHint( expr : String ) : { expr : String, hint : Hint } { 56 | var exprs = expr.split(":"); 57 | var hint = HNone; 58 | if( exprs.length > 1 ) { 59 | hint = Value.parseHint(exprs.pop()); // content after the last ":" is considered as a display hint 60 | expr = exprs.join(":"); 61 | } 62 | return { expr : expr, hint : hint }; 63 | } 64 | 65 | public static function parseHint( s : String ) : Hint { 66 | if( s == "h" ) 67 | return HHex; 68 | if( s == "b" ) 69 | return HBin; 70 | if( s == "p" ) 71 | return HPointer; 72 | if( s == "s" ) 73 | return HEscape; 74 | if( s == "GUID" ) 75 | return HGuid; 76 | if( StringTools.startsWith(s,"UI8(") && StringTools.endsWith(s,")") ) 77 | return HReadBytes(HUi8, s.substr(4, s.length - 5)); 78 | if( StringTools.startsWith(s,"UI16(") && StringTools.endsWith(s,")") ) 79 | return HReadBytes(HUi16, s.substr(5, s.length - 6)); 80 | if( StringTools.startsWith(s,"I32(") && StringTools.endsWith(s,")") ) 81 | return HReadBytes(HI32, s.substr(4, s.length - 5)); 82 | if( StringTools.startsWith(s,"I64(") && StringTools.endsWith(s,")") ) 83 | return HReadBytes(HI64, s.substr(4, s.length - 5)); 84 | if( StringTools.startsWith(s,"F32(") && StringTools.endsWith(s,")") ) 85 | return HReadBytes(HF32, s.substr(4, s.length - 5)); 86 | if( StringTools.startsWith(s,"F64(") && StringTools.endsWith(s,")") ) 87 | return HReadBytes(HF64, s.substr(4, s.length - 5)); 88 | if( StringTools.startsWith(s, "Array<") ) { 89 | var typeStr : String = null; 90 | var size : String = null; 91 | var pos : String = null; 92 | if( StringTools.endsWith(s,">")) { 93 | var parts = s.substr(6, s.length - 7).split(","); 94 | typeStr = parts[0]; 95 | size = parts.length > 1 ? parts[1] : null; 96 | } 97 | if( StringTools.endsWith(s,"]")) { 98 | var parts = s.substr(6, s.length - 7).split(">["); 99 | typeStr = parts[0]; 100 | pos = parts[1]; 101 | } 102 | var btype = switch(typeStr) { 103 | case "UI8": HUi8; 104 | case "UI16": HUi16; 105 | case "I32", "Int": HI32; 106 | case "I64": HI64; 107 | case "F32": HF32; 108 | case "F64", "Float": HF64; 109 | case null, _: return HNone; 110 | } 111 | return HArray(btype, size, pos); 112 | } 113 | if( StringTools.startsWith(s,"EnumFlags<") && StringTools.endsWith(s,">") ) 114 | return HEnumFlags(s.substr(10, s.length - 11)); 115 | if( StringTools.startsWith(s,"haxe.EnumFlags<") && StringTools.endsWith(s,">") ) 116 | return HEnumFlags(s.substr(15, s.length - 16)); 117 | if( StringTools.startsWith(s,"EnumIndex<") && StringTools.endsWith(s,">") ) 118 | return HEnumIndex(s.substr(10, s.length - 11)); 119 | if( StringTools.startsWith(s, "CArray<") ) { 120 | if( StringTools.endsWith(s,">")) { 121 | var parts = s.substr(7, s.length - 8).split(","); 122 | if( parts.length == 2 ) 123 | return HCArray(parts[0], parts[1], null); 124 | } 125 | if( StringTools.endsWith(s,"]")) { 126 | var parts = s.substr(7, s.length - 8).split(">["); 127 | if( parts.length == 2 ) 128 | return HCArray(parts[0], null, parts[1]); 129 | } 130 | } 131 | if( StringTools.startsWith(s,"CDB<") && StringTools.endsWith(s,">") ) 132 | return HCdbEnum(s.substr(4, s.length - 5)); 133 | if( StringTools.startsWith(s,"CDBEnum<") && StringTools.endsWith(s,">") ) 134 | return HCdbEnum(s.substr(8, s.length - 9)); 135 | return HNone; 136 | } 137 | 138 | static final INTBASE = "0123456789ABCDEF"; 139 | public static function intStr( value : Int, base : Int ) : String { 140 | return int64Str(value, base, true); 141 | } 142 | 143 | public static function int64Str( value : haxe.Int64, base : Int, is32bit : Bool = false ) : String { 144 | if( base != 2 && base != 16 ) 145 | throw "Unsupported int base " + base; 146 | var prefix = base == 2 ? "0b" : "0x"; 147 | if( value == haxe.Int64.make(0,0) ) 148 | return prefix + "0"; 149 | var mask = base - 1; 150 | var shift = base == 2 ? 1 : 4; 151 | var maxlen = base == 2 ? (is32bit ? 32 : 64) : (is32bit ? 8 : 16); 152 | var s = ""; 153 | var positive = value >= haxe.Int64.make(0,0); 154 | var abs = positive ? value : -(value+1); // 2's complement 155 | while( abs > 0 ) { 156 | var cur = positive ? (abs.low & mask) : (mask - abs.low & mask); 157 | s = INTBASE.charAt(cur) + s; 158 | abs = abs >> shift; 159 | } 160 | if( s.length < maxlen ) { 161 | var lchar = positive ? "0" : (base == 2 ? "1" : "F"); 162 | s = StringTools.lpad(s, lchar, maxlen); 163 | } 164 | return prefix + s; 165 | } 166 | 167 | public static function parseInt64( str : String ) : haxe.Int64 { 168 | var value = haxe.Int64.make(0, 0); 169 | var base = 16; 170 | var shift = 4; 171 | var i = 0; 172 | if( StringTools.startsWith(str,"0x") ) { 173 | i += 2; 174 | } 175 | while( i < str.length ) { 176 | var c = str.charCodeAt(i); 177 | var cval = if( c >= '0'.code && c <= '9'.code ) c - '0'.code 178 | else if( c >= 'A'.code && c <= 'F'.code ) c - 'A'.code + 10 179 | else if( c >= 'a'.code && c <= 'f'.code ) c - 'a'.code + 10 180 | else 181 | break; 182 | value = (value << shift) + cval; 183 | i++; 184 | } 185 | return value; 186 | } 187 | 188 | static final GUIDBASE = "#&0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; 189 | public static function int64GuidStr( value : haxe.Int64 ) : String { 190 | if( value == 0 ) 191 | return "0"; 192 | var s = ""; 193 | for( i in 0...11 ) { 194 | if( i == 3 || i == 7 ) 195 | s = '-' + s; 196 | s = GUIDBASE.charAt(value.low&63) + s; 197 | value = value >> 6; 198 | } 199 | return s; 200 | } 201 | 202 | public static function intEnumFlags( value : Int, eproto : format.hl.Data.EnumPrototype ) : String { 203 | var f = ""; 204 | for( i in 0...eproto.constructs.length ) { 205 | if( (value >> i) % 2 == 1 ) 206 | f += " | " + eproto.constructs[i].name; 207 | } 208 | if( f.length == 0 ) 209 | f = "(noflag)"; 210 | else 211 | f = f.substr(3); 212 | return f; 213 | } 214 | 215 | public static function intEnumIndex( value : Int, eproto : format.hl.Data.EnumPrototype ) : String { 216 | if( value < 0 || value >= eproto.constructs.length ) 217 | throw "Out of range [0," + eproto.constructs.length + ")"; 218 | return eproto.constructs[value].name; 219 | } 220 | 221 | } 222 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 1.4.34 (December 12, 2025) 2 | 3 | * fixed noDebug option still attach debugger 4 | * added Array UI8/GUID support, map value GUID support for haxe nightly 5 | * fixed stacktrace request with non-VSCode DAP server (#141) 6 | * added guid hint, fixed hex/bin hint on big float 7 | 8 | ## 1.4.33 (September 9, 2025) 9 | 10 | * fixed unknown value length for haxe.io.Bytes 11 | * fixed eval not treat std prefix correctly 12 | * exposed allowEval config which allow auto getter call on non-physical fields 13 | * added OCatch support for haxe nightly 14 | * added array hint for bytes 15 | 16 | ## 1.4.32 (June 30, 2025) 17 | 18 | * fixed filepath resolution on breakpoint for haxe nightly 19 | * fixed display when pause on non-hl thread (F12) 20 | 21 | ## 1.4.31 (June 13, 2025) 22 | 23 | * fixed data breakpoint not showing for parent class field 24 | * fixed filepath resolution for hl/_std and for haxe nightly 25 | * improved display on SysError, now with message 26 | 27 | ## 1.4.30 (May 28, 2025) 28 | 29 | * fixed field offset of packed struct inside object 30 | 31 | ## 1.4.29 (April 17, 2025) 32 | 33 | * improved display on ValueException, now show inner error 34 | * fixed return I64 display 35 | * fixed missing static var inside @:struct 36 | * allow eval hl.NativeArray similar to an Array 37 | * allow inspect inside hl.CArray with hint 38 | 39 | ## 1.4.28 (March 7, 2025) 40 | 41 | * improved support for multi-line string 42 | * added Add To Watch support for Enum, Map (by index) 43 | 44 | ## 1.4.27 (February 4, 2025) 45 | 46 | * fixed vscode not focusing on exception when the stack is empty 47 | * added hl.GUID support for hl & haxe nightly 48 | 49 | ## 1.4.26 (January 23, 2025) 50 | 51 | * fixed error on mac when hl is only available after preLaunchTask (#135) 52 | 53 | ## 1.4.25 (January 16, 2025) 54 | 55 | * fixed expression 1e2, 1e-5 showing as infinity (lib hscript) 56 | * added an early error when trying to debug hl/c 57 | * fixed step next break on inner recursive call 58 | * fixed copy value returning wrong result 59 | 60 | ## 1.4.24 (November 26, 2024) 61 | 62 | * fixed launch error displayed twice 63 | * added error message when bytecode has no debug info 64 | * fixed eval error when variable reuse argument registers 65 | * added comparison string/null 66 | * fixed can't get fields for this warning when break in std 67 | * added pointer hint display for funRepr, HAbstract 68 | * added command @d @f for advanced debugging based on pointer 69 | * fixed evalCall corrupting jit registers R10, R11 70 | * fixed conditional breakpoint in small loop blocking the debugger 71 | 72 | ## 1.4.23 (October 14, 2024) 73 | 74 | * fixed reg corruption when eval call fail early (e.g. unsupported arg) 75 | * added variables section Add To Watch support 76 | 77 | ## 1.4.22 (August 9, 2024) 78 | 79 | * added connection timeout in settings 80 | * improved bin/hex hint display, now as 2's complement and full length 81 | * added ArrayBytes I64 support for haxe nightly build 82 | 83 | ## 1.4.21 (July 23, 2024) 84 | 85 | * fixed stack when Null access .xxx and Can't cast xxx to String 86 | * fixed f32 array display 87 | * added array access for bytes 88 | * added eval hint UI8(i), UI16(i), I32(i), I64(i), F32(i), F64(i) for bytes 89 | 90 | ## 1.4.20 (July 15, 2024) 91 | 92 | * fixed Sys.command detected as access violation on Linux 93 | 94 | ## 1.4.19 (July 11, 2024) 95 | 96 | * fixed restart stopped thread still show as stopped 97 | 98 | ## 1.4.18 (July 5, 2024) 99 | 100 | * fixed multi captured variable 101 | * fixed map value fetch when the size is around 128 102 | * fixed inlined variable display when captured as args 103 | * improved step on throw with try-catch handler 104 | 105 | ## 1.4.17 (July 1, 2024) 106 | 107 | * added break only active process for multi-target debugging 108 | * added eval hint p for other values with pointer representation 109 | 110 | ## 1.4.16 (June 24, 2024) 111 | 112 | * added previous exception stack in variables section when hl.Api.rethrow 113 | * added eval hint p for pointer 114 | 115 | ## 1.4.15 (June 20, 2024) 116 | 117 | * fixed eval call: stack/reg/breakpoint corruption, function resolution 118 | * fixed single captured variable 119 | * fixed macOS code signature verification when hl path has spaces (#132) 120 | 121 | ## 1.4.14 (June 5, 2024) 122 | 123 | * fixed eval this 124 | 125 | ## 1.4.13 (June 3, 2024) 126 | 127 | * improved eval access to class with file/package prefix 128 | * added eval support for null-safe field access operator (?.) 129 | * added comparison for enum value 130 | 131 | ## 1.4.12 (May 30, 2024) 132 | 133 | * fixed comparison pointer/null 134 | * fixed compound launch 135 | 136 | ## 1.4.11 (May 28, 2024) 137 | 138 | * added a pop-up when hl exit code 4 (debug port occupied) 139 | * added comparison for pointer, int64, bool 140 | * fixed step when leaving function on Linux 141 | 142 | ## 1.4.10 (May 2, 2024) 143 | 144 | * fixed thread exception before first breakpoint 145 | * fixed pause before first breakpoint on Linux 146 | 147 | ## 1.4.9 (April 22, 2024) 148 | 149 | * fixed timeout/breakpoint/stop on Linux 150 | 151 | ## 1.4.8 (April 17, 2024) 152 | 153 | * fixed exception window not shown 154 | 155 | ## 1.4.7 (April 16, 2024) 156 | 157 | * added configuration snippets 158 | * added extension settings 159 | * added comparison for string, fixed not equal (!=) not working 160 | * fixed continue not working on Linux 161 | 162 | ## 1.4.6 (April 5, 2024) 163 | 164 | * added eval hint support 165 | 166 | ## 1.4.5 (March 26, 2024) 167 | 168 | * added inlined constructor variable support for haxe nightly build 169 | * improved source line resolution when break by value watch 170 | * improved eval when hovering haxe keywords 171 | * fixed error message when failed to evaluate variable path 172 | * improved eval access to abstract type 173 | * added support for conditional breakpoint 174 | * added debug context menu option show as hex/bin 175 | 176 | ## 1.4.4 (October 13, 2023) 177 | 178 | * fixed dynobj support with hl 1.15 179 | 180 | ## 1.4.3 (September 16, 2023) 181 | 182 | * disable eval of getters by default 183 | * remove deasync requirement (fix vscode 1.82) 184 | * added OPrefetch support 185 | * additional fixes 186 | 187 | ## 1.4.2 (June 23, 2023) 188 | 189 | * fixed some eval calls causing crashes on node 190 | 191 | ## 1.4.0 (January 28, 2023) 192 | 193 | * added support of hl 1.13 maps and @:packed 194 | * added named threads support 195 | * added evaluation of method calls 196 | * object getter resolution 197 | 198 | ## 1.3.4 (July 27, 2022) 199 | 200 | * improved multithread support 201 | * added int64 support 202 | * added @:packed and improved @:struct support 203 | * (again) fixed timeout issue when connecting on debug port 204 | 205 | ## 1.3.2 (April 14, 2022) 206 | 207 | * fixed timeout issue when connecting on debug port 208 | 209 | ## 1.3.1 (March 23, 2022) 210 | 211 | * changed to native API (no longer FFI, super fast) 212 | * fixed timing issue at startup with latest node 213 | 214 | ## 1.2.2 (Jan 29, 2022) 215 | 216 | * added support for return value display 217 | * added support for data breakpoints 218 | * fixed display of empty array/map and closure 219 | * improved set variable 220 | * allow numeric operations in expression eval 221 | * bug fixes 222 | 223 | ## 1.1.2 (May 6, 2021) 224 | 225 | * fixed support for VSCode 1.56 (Electron 12.0) 226 | 227 | ## 1.1.1 (September 26, 2020) 228 | 229 | * fixed pause was broken again 230 | * improved multithread support 231 | * fixed some vars displayed as <...> after some time 232 | * fixed bug when stepping out of some functions 233 | 234 | ## 1.1.0 (June 13, 2020) 235 | 236 | * removed the need to specify `hxml` in `launch.json` (#4) 237 | * added OSX support (contributed by @rcstuber, thanks!) 238 | * added closures context display 239 | * added closures stack (requires HL 1.12+) 240 | * bug fixes 241 | 242 | ## 1.0.0 (February 2, 2020) 243 | 244 | * reimplemented the step system using predictive temporary breakpoints 245 | * improved display of types and values 246 | 247 | ## 0.9.1 (November 30, 2019) 248 | 249 | * fixes for exception stack management in win64 250 | * fixes for stepping in/out in x64 251 | * fixed partial fetching for array/map/bytes 252 | 253 | ## 0.9.0 (November 22, 2019) 254 | 255 | * improved exception stack detection 256 | * added profileSamples configuration on launch (hl 1.11 profiler) 257 | 258 | ## 0.8.2 (November 11, 2019) 259 | 260 | * compatible with VSCode 1.40 (Electron 6.2) 261 | * added hotReload experimental configuration on launch 262 | 263 | ## 0.8.0 (October 10, 2019) 264 | 265 | * added x64 function arguments support 266 | * do not display variables once they are out of scope 267 | 268 | ## 0.7.1 (July 6, 2019) 269 | 270 | * compatible with VSCode 1.36 (Electron 4.2.5) 271 | 272 | ## 0.7.0 (June 11, 2019) 273 | 274 | * added an error message for trying to debug on macOS (#28) 275 | * added optional `hl` and `env` fields to launch configs (#55) 276 | * fixed pause button 277 | * fixed some startup errors on Windows/Linux 278 | 279 | ## 0.6.0 (March 4, 2019) 280 | 281 | * added optional `program` support (#3) 282 | * fixed a crash with compile time cwd != runtime cwd 283 | * fixed "Start Debugging" not doing anything without a `launch.json` 284 | * updated `${workspaceRoot}` to `${workspaceFolder}` 285 | * improved enum display in tree view 286 | * added explicit error on ENOENT 287 | * fixed static variables lookup 288 | * fixed current package type lookup 289 | * make sure to have correct port on launch (#37) 290 | * prevent overflow error when doing pointer difference (#46) 291 | 292 | ## 0.5.2 (February 7, 2019) 293 | 294 | * VSCode 1.31 compatibility (electron 3.1.2) 295 | 296 | ## 0.5.1 (December 3, 2018) 297 | 298 | * More HashLink 1.9 (bytecode 5) support 299 | 300 | ## 0.5.0 (November 18, 2018) 301 | 302 | * VSCode 1.29 compatibility (electron 2.0.12) 303 | * HashLink bytecode 5 support 304 | * stack overflow correctly reported on windows 305 | 306 | ## 0.4.4 (September 17, 2018) 307 | 308 | * VSCode 1.27 compatibility (bugfix stepping) 309 | 310 | ## 0.4.3 (August 26, 2018) 311 | 312 | * VSCode 1.26 compatibility (electron 2.0.5) 313 | * added "break on all exceptions" support 314 | * started set variable implementation (very little support for now) 315 | * fixed HL 1.6- support 316 | 317 | ## 0.4.2 (July 11, 2018) 318 | 319 | * added haxe.io.Bytes custom display 320 | * fixed statics in classes within a package 321 | * fixed error message when var unknown 322 | * fixed with single captured var ptr 323 | 324 | ## 0.4.1 (July 11, 2018) 325 | 326 | * fixed regression regarding locals resolution 327 | 328 | ## 0.4.0 (July 9, 2018) 329 | 330 | * added attach/detach support 331 | * fixed pause 332 | * add member and static vars preview 333 | * fixed static var eval() 334 | * move breakpoint to next valid line when no opcode at this pos 335 | * don't step in hl/haxe standard library anymore 336 | * hl 1.7 support 337 | * many other fixes 338 | 339 | ## 0.3.0 (June 12, 2018) 340 | 341 | * added Linux support 342 | * fixed initialize errors 343 | * fixed newlines mix in debugger trace output 344 | * don't escape strings in exception reports 345 | * improved file resolution for breakpoints 346 | 347 | ## 0.2.0 (April 16, 2018) 348 | 349 | * added HL 1.6 bytecode support 350 | * started threads support 351 | 352 | ## 0.1.0 (April 9, 2018) 353 | 354 | * added class/method in stack trace 355 | * added hover eval (support member and static vars) 356 | * allow access to member vars without this. prefix 357 | * added native Map support 358 | * fixed CALL skip bug when stepping 359 | * group object fields by class scope with inheritance 360 | * bugfix with field hashing in JS 361 | * initial HL debugging 362 | -------------------------------------------------------------------------------- /hldebug-wrapper/src/debug.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C)2005-2016 Haxe Foundation 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE. 21 | */ 22 | #include 23 | #if defined(HL_LINUX) && (defined(__i386__) || defined(__x86_64__)) 24 | # include 25 | # include 26 | # include 27 | # include 28 | # include 29 | # define USE_PTRACE 30 | #endif 31 | 32 | #if defined(HL_MAC) && defined(__x86_64__) 33 | # include 34 | # define MAC_DEBUG 35 | #endif 36 | 37 | #if defined(HL_WIN) 38 | static HANDLE last_process = NULL, last_thread = NULL; 39 | static int last_pid = -1; 40 | static int last_tid = -1; 41 | static HANDLE OpenPID( int pid ) { 42 | if( pid == last_pid ) 43 | return last_process; 44 | CloseHandle(last_process); 45 | last_pid = pid; 46 | last_process = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid); 47 | return last_process; 48 | } 49 | static HANDLE OpenTID( int tid ) { 50 | if( tid == last_tid ) 51 | return last_thread; 52 | CloseHandle(last_thread); 53 | last_tid = tid; 54 | last_thread = OpenThread(THREAD_ALL_ACCESS, FALSE, tid); 55 | return last_thread; 56 | } 57 | static void CleanHandles() { 58 | last_pid = -1; 59 | last_tid = -1; 60 | CloseHandle(last_process); 61 | CloseHandle(last_thread); 62 | last_process = NULL; 63 | last_thread = NULL; 64 | } 65 | #endif 66 | 67 | HL_API bool hl_debug_start( int pid ) { 68 | # if defined(HL_WIN) 69 | last_pid = -1; 70 | return (bool)DebugActiveProcess(pid); 71 | # elif defined(MAC_DEBUG) 72 | return mdbg_session_attach(pid); 73 | # elif defined(USE_PTRACE) 74 | return ptrace(PTRACE_ATTACH,pid,0,0) >= 0; 75 | # else 76 | return false; 77 | # endif 78 | } 79 | 80 | HL_API bool hl_debug_stop( int pid ) { 81 | # if defined(HL_WIN) 82 | BOOL b = DebugActiveProcessStop(pid); 83 | CleanHandles(); 84 | return (bool)b; 85 | # elif defined(MAC_DEBUG) 86 | return mdbg_session_detach(pid); 87 | # elif defined(USE_PTRACE) 88 | kill(pid, SIGTRAP); // DETACH needs ptrace-stop 89 | return ptrace(PTRACE_DETACH,pid,0,0) >= 0; 90 | # else 91 | return false; 92 | # endif 93 | } 94 | 95 | HL_API bool hl_debug_breakpoint( int pid ) { 96 | # if defined(HL_WIN) 97 | return (bool)DebugBreakProcess(OpenPID(pid)); 98 | # elif defined(MAC_DEBUG) 99 | return mdbg_session_pause(pid); 100 | # elif defined(USE_PTRACE) 101 | return kill(pid,SIGTRAP) == 0; 102 | # else 103 | return false; 104 | # endif 105 | } 106 | 107 | HL_API bool hl_debug_read( int pid, vbyte *addr, vbyte *buffer, int size ) { 108 | # if defined(HL_WIN) 109 | return (bool)ReadProcessMemory(OpenPID(pid),addr,buffer,size,NULL); 110 | # elif defined(MAC_DEBUG) 111 | return mdbg_read_memory(pid, addr, buffer, size); 112 | # elif defined(USE_PTRACE) 113 | while( size ) { 114 | long v = ptrace(PTRACE_PEEKDATA,pid,addr,0); 115 | if( size >= sizeof(long) ) 116 | *(long*)buffer = v; 117 | else { 118 | memcpy(buffer,&v,size); 119 | break; 120 | } 121 | addr += sizeof(long); 122 | size -= sizeof(long); 123 | buffer += sizeof(long); 124 | } 125 | return true; 126 | # else 127 | return false; 128 | # endif 129 | } 130 | 131 | HL_API bool hl_debug_write( int pid, vbyte *addr, vbyte *buffer, int size ) { 132 | # if defined(HL_WIN) 133 | return (bool)WriteProcessMemory(OpenPID(pid),addr,buffer,size,NULL); 134 | # elif defined(MAC_DEBUG) 135 | return mdbg_write_memory(pid, addr, buffer, size); 136 | # elif defined(USE_PTRACE) 137 | while( size ) { 138 | int sz = size >= sizeof(long) ? sizeof(long) : size; 139 | long v = *(long*)buffer; 140 | if( sz != sizeof(long) ) { 141 | long cur = ptrace(PTRACE_PEEKDATA,pid,addr); 142 | memcpy((char*)&v+sz,(char*)&cur+sz,sizeof(long)-sz); 143 | } 144 | if( ptrace(PTRACE_POKEDATA,pid,addr,v) < 0 ) 145 | return false; 146 | addr += sz; 147 | size -= sz; 148 | buffer += sz; 149 | } 150 | return true; 151 | # else 152 | return false; 153 | # endif 154 | } 155 | 156 | HL_API bool hl_debug_flush( int pid, vbyte *addr, int size ) { 157 | # if defined(HL_WIN) 158 | return (bool)FlushInstructionCache(OpenPID(pid),addr,size); 159 | # elif defined(MAC_DEBUG) 160 | return true; 161 | # elif defined(USE_PTRACE) 162 | return true; 163 | # else 164 | return false; 165 | # endif 166 | } 167 | 168 | #ifdef MAC_DEBUG 169 | static int get_reg( int r ) { 170 | switch( r ) { 171 | case 0: return REG_RSP; 172 | case 1: return REG_RBP; 173 | case 2: return REG_RIP; 174 | case 3: return REG_RFLAGS; 175 | case 4: return REG_DR0; 176 | case 5: return REG_DR1; 177 | case 6: return REG_DR2; 178 | case 7: return REG_DR3; 179 | case 8: return REG_DR6; 180 | case 9: return REG_DR7; 181 | case 10: return REG_RAX; 182 | } 183 | return -1; 184 | } 185 | #endif 186 | 187 | #ifdef USE_PTRACE 188 | static void *get_reg( int r ) { 189 | struct user_regs_struct *regs = NULL; 190 | struct user *user = NULL; 191 | struct user_fpregs_struct *fp = NULL; 192 | switch( r ) { 193 | case -1: return &user->u_fpstate; 194 | # ifdef HL_64 195 | case 0: return ®s->rsp; 196 | case 1: return ®s->rbp; 197 | case 2: return ®s->rip; 198 | case 10: return ®s->rax; 199 | case 11: return (void*)(-((int_val)&fp->xmm_space[0])-1); 200 | # else 201 | case 0: return ®s->esp; 202 | case 1: return ®s->ebp; 203 | case 2: return ®s->eip; 204 | case 10: return ®s->eax; 205 | case 11: return -1; 206 | # endif 207 | case 3: return ®s->eflags; 208 | default: return &user->u_debugreg[r-4]; 209 | } 210 | return NULL; 211 | } 212 | #endif 213 | 214 | HL_API int hl_debug_wait( int pid, int *thread, int timeout ) { 215 | # if defined(HL_WIN) 216 | DEBUG_EVENT e; 217 | if( !WaitForDebugEvent(&e,timeout) ) 218 | return -1; 219 | *thread = e.dwThreadId; 220 | switch( e.dwDebugEventCode ) { 221 | case EXCEPTION_DEBUG_EVENT: 222 | switch( e.u.Exception.ExceptionRecord.ExceptionCode ) { 223 | case EXCEPTION_BREAKPOINT: 224 | case 0x4000001F: // STATUS_WX86_BREAKPOINT 225 | return 1; 226 | case EXCEPTION_SINGLE_STEP: 227 | case 0x4000001E: // STATUS_WX86_SINGLE_STEP 228 | return 2; 229 | case 0x406D1388: // MS_VC_EXCEPTION (see SetThreadName) 230 | ContinueDebugEvent(e.dwProcessId, e.dwThreadId, DBG_CONTINUE); 231 | break; 232 | case 0xE06D7363: // C++ EH EXCEPTION 233 | case 0x6BA: // File Dialog EXCEPTION 234 | ContinueDebugEvent(e.dwProcessId, e.dwThreadId, DBG_EXCEPTION_NOT_HANDLED); 235 | break; 236 | case EXCEPTION_STACK_OVERFLOW: 237 | return 5; 238 | default: 239 | return 3; 240 | } 241 | break; 242 | case EXIT_PROCESS_DEBUG_EVENT: 243 | return 0; 244 | default: 245 | ContinueDebugEvent(e.dwProcessId, e.dwThreadId, DBG_CONTINUE); 246 | break; 247 | } 248 | return 4; 249 | # elif defined(MAC_DEBUG) 250 | return mdbg_session_wait(pid, thread, timeout); 251 | # elif defined(USE_PTRACE) 252 | int status; 253 | // *** HACK *** 254 | // usleep here is needed for a good result. 255 | // Without it, waitpid can miss many stop event; 256 | // With it, and more we wait, less we miss stop event. 257 | usleep(100 * 1000); 258 | int ret = waitpid(pid, &status, WNOHANG); 259 | if( ret == -1 && errno == ECHILD ) { 260 | *thread = pid; 261 | return 0; 262 | } 263 | *thread = ret; 264 | if( ret <= 0 ) 265 | return -1; 266 | if( WIFEXITED(status) ) 267 | return 0; 268 | if( WIFSTOPPED(status) ) { 269 | int sig = WSTOPSIG(status); 270 | if( sig == SIGSTOP || sig == SIGTRAP ) 271 | return 1; 272 | if( sig == SIGSEGV ) 273 | return 3; 274 | // other signals such as SIGCHLD, ignore and continue 275 | if( ptrace(PTRACE_CONT,pid,0,0) >= 0 ) 276 | return 4; 277 | return 3; 278 | } 279 | return 4; 280 | # else 281 | return 0; 282 | # endif 283 | } 284 | 285 | HL_API bool hl_debug_resume( int pid, int thread ) { 286 | # if defined(HL_WIN) 287 | return (bool)ContinueDebugEvent(pid, thread, DBG_CONTINUE); 288 | # elif defined(MAC_DEBUG) 289 | return mdbg_session_resume(pid); 290 | # elif defined(USE_PTRACE) 291 | return ptrace(PTRACE_CONT,pid,0,0) >= 0; 292 | # else 293 | return false; 294 | # endif 295 | } 296 | 297 | #ifdef HL_WIN 298 | #define DefineGetReg(type,GetFun) \ 299 | REGDATA *GetFun( type *c, int reg ) { \ 300 | switch( reg ) { \ 301 | case 0: return GET_REG(sp); \ 302 | case 1: return GET_REG(bp); \ 303 | case 2: return GET_REG(ip); \ 304 | case 4: return &c->Dr0; \ 305 | case 5: return &c->Dr1; \ 306 | case 6: return &c->Dr2; \ 307 | case 7: return &c->Dr3; \ 308 | case 8: return &c->Dr6; \ 309 | case 9: return &c->Dr7; \ 310 | case 10: return GET_REG(ax); \ 311 | default: return GET_REG(ax); \ 312 | } \ 313 | } 314 | 315 | #define GET_REG(x) &c->E##x 316 | #define REGDATA DWORD 317 | 318 | #ifdef HL_64 319 | DefineGetReg(WOW64_CONTEXT,GetContextReg32); 320 | # undef GET_REG 321 | # undef REGDATA 322 | # define GET_REG(x) &c->R##x 323 | # define REGDATA DWORD64 324 | # endif 325 | 326 | DefineGetReg(CONTEXT,GetContextReg); 327 | 328 | #endif 329 | 330 | HL_API void *hl_debug_read_register( int pid, int thread, int reg, bool is64 ) { 331 | # if defined(HL_WIN) 332 | # ifdef HL_64 333 | if( !is64 ) { 334 | WOW64_CONTEXT c; 335 | c.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS; 336 | if( !Wow64GetThreadContext(OpenTID(thread),&c) ) 337 | return NULL; 338 | if( reg == 3 ) 339 | return (void*)(int_val)c.EFlags; 340 | if( reg == 11 ) 341 | return NULL; // TODO 342 | return (void*)(int_val)*GetContextReg32(&c,reg); 343 | } 344 | # else 345 | if( is64 ) return NULL; 346 | # endif 347 | CONTEXT c; 348 | c.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS; 349 | if( !GetThreadContext(OpenTID(thread),&c) ) 350 | return NULL; 351 | if( reg == 3 ) 352 | return (void*)(int_val)c.EFlags; 353 | if( reg == 11 ) 354 | #ifdef HL_64 355 | return (void*)(int_val)c.FltSave.XmmRegisters[0].Low; 356 | #else 357 | return (void*)*(int_val*)&c.ExtendedRegisters[10*16]; 358 | #endif 359 | return (void*)*GetContextReg(&c,reg); 360 | # elif defined(MAC_DEBUG) 361 | return mdbg_read_register(pid, thread, get_reg(reg), is64); 362 | # elif defined(USE_PTRACE) 363 | void *r = get_reg(reg); 364 | if( ((int_val)r) < 0 ) { 365 | // peek FP ptr 366 | char *addr = (char*)ptrace(PTRACE_PEEKUSER,thread,get_reg(-1),0); 367 | void *out = NULL; 368 | hl_debug_read(pid, addr + (-((int_val)r)-1), (vbyte*)&out, sizeof(void*)); 369 | return out; 370 | } 371 | return (void*)ptrace(PTRACE_PEEKUSER,thread,r,0); 372 | # else 373 | return NULL; 374 | # endif 375 | } 376 | 377 | HL_API bool hl_debug_write_register( int pid, int thread, int reg, void *value, bool is64 ) { 378 | # if defined(HL_WIN) 379 | # ifdef HL_64 380 | if( !is64 ) { 381 | WOW64_CONTEXT c; 382 | c.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS; 383 | if( !Wow64GetThreadContext(OpenTID(thread),&c) ) 384 | return false; 385 | if( reg == 3 ) 386 | c.EFlags = (int)(int_val)value; 387 | else if( reg == 11 ) 388 | return false; // TODO 389 | else 390 | *GetContextReg32(&c,reg) = (DWORD)(int_val)value; 391 | return (bool)Wow64SetThreadContext(OpenTID(thread),&c); 392 | } 393 | # else 394 | if( is64 ) return false; 395 | # endif 396 | CONTEXT c; 397 | c.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS; 398 | if( !GetThreadContext(OpenTID(thread),&c) ) 399 | return false; 400 | if( reg == 3 ) 401 | c.EFlags = (int)(int_val)value; 402 | else if( reg == 11 ) 403 | # ifdef HL_64 404 | c.FltSave.XmmRegisters[0].Low = (int_val)value; 405 | # else 406 | *(int_val*)&c.ExtendedRegisters[10*16] = (int_val)value; 407 | # endif 408 | else 409 | *GetContextReg(&c,reg) = (REGDATA)value; 410 | return (bool)SetThreadContext(OpenTID(thread),&c); 411 | # elif defined(MAC_DEBUG) 412 | return mdbg_write_register(pid, thread, get_reg(reg), value, is64); 413 | # elif defined(USE_PTRACE) 414 | return ptrace(PTRACE_POKEUSER,thread,get_reg(reg),value) >= 0; 415 | # else 416 | return false; 417 | # endif 418 | } 419 | 420 | DEFINE_PRIM(_BOOL, debug_start, _I32); 421 | DEFINE_PRIM(_VOID, debug_stop, _I32); 422 | DEFINE_PRIM(_BOOL, debug_breakpoint, _I32); 423 | DEFINE_PRIM(_BOOL, debug_read, _I32 _BYTES _BYTES _I32); 424 | DEFINE_PRIM(_BOOL, debug_write, _I32 _BYTES _BYTES _I32); 425 | DEFINE_PRIM(_BOOL, debug_flush, _I32 _BYTES _I32); 426 | DEFINE_PRIM(_I32, debug_wait, _I32 _REF(_I32) _I32); 427 | DEFINE_PRIM(_BOOL, debug_resume, _I32 _I32); 428 | DEFINE_PRIM(_BYTES, debug_read_register, _I32 _I32 _I32 _BOOL); 429 | DEFINE_PRIM(_BOOL, debug_write_register, _I32 _I32 _I32 _BYTES _BOOL); 430 | 431 | -------------------------------------------------------------------------------- /hld/Main.hx: -------------------------------------------------------------------------------- 1 | package hld; 2 | 3 | #if debug 4 | @:jsRequire('source-map-support') 5 | extern class SMS { 6 | public static function install() : Void; 7 | } 8 | class SMSInstall { 9 | static function __init__() : Void { 10 | SMS.install(); 11 | } 12 | } 13 | #end 14 | 15 | /** 16 | Commandline interface - GDB like 17 | **/ 18 | class Main { 19 | 20 | var args = Sys.args(); 21 | var debugPort = 5001; 22 | var file = null; 23 | var pid : Null; 24 | var breaks = []; 25 | var dbg : hld.Debugger; 26 | var stdin = Sys.stdin(); 27 | var isCi = false; 28 | 29 | #if nodejs 30 | var process : js.node.child_process.ChildProcess; 31 | #else 32 | var process : sys.io.Process; 33 | #end 34 | 35 | function new() { 36 | } 37 | 38 | function error( msg ) { 39 | Sys.stderr().writeString(msg + "\n"); 40 | Sys.exit(1); 41 | } 42 | 43 | function init() { 44 | var cmd = "hl"; 45 | while( args.length > 0 && args[0].charCodeAt(0) == '-'.code ) { 46 | var param = args.shift(); 47 | switch( param ) { 48 | case "-port": 49 | param = args.shift(); 50 | if( param == null || (debugPort = Std.parseInt(param)) == 0 ) 51 | error("Require port int value"); 52 | case "-attach": 53 | param = args.shift(); 54 | if( param == null || (pid = Std.parseInt(param)) == null ) 55 | error("Require attach process id value"); 56 | case "--cmd": 57 | cmd = args.shift(); 58 | case "--cwd": 59 | Sys.setCwd(args.shift()); 60 | case "--input": 61 | var file = args.shift(); 62 | var inputArgs = ~/[ \n\r\t]+/g.split(StringTools.trim(sys.io.File.getContent(file))); 63 | while( inputArgs.length > 0 ) { 64 | var a = inputArgs.pop(); 65 | if( StringTools.endsWith(a,'"') ) { 66 | if( StringTools.startsWith(a,'"') ) { 67 | args.unshift(a.substr(1,a.length-2)); 68 | continue; 69 | } 70 | var arg = [a.substr(0,a.length-1)]; 71 | while( true ) { 72 | var a = inputArgs.pop(); 73 | if( a == null ) break; 74 | if( StringTools.startsWith(a,'"') ) { 75 | arg.unshift(a.substr(1)); 76 | break; 77 | } 78 | arg.unshift(a); 79 | } 80 | args.unshift(arg.join(" ")); 81 | continue; 82 | } 83 | args.unshift(a); 84 | } 85 | case "--ci": 86 | isCi = true; 87 | case "--debug": 88 | Debugger.DEBUG = true; 89 | default: 90 | error("Unsupported parameter " + param); 91 | } 92 | } 93 | file = args.shift(); 94 | if( file == null ) { 95 | Sys.println("hldebug [-port ] [--cwd ] []"); 96 | Sys.exit(1); 97 | } 98 | if( !sys.FileSystem.exists(file) ) 99 | error(file+" not found"); 100 | 101 | var hlArgs = []; 102 | if( args[0] == "--args" ) { 103 | args.shift(); 104 | while( true ) { 105 | var a = args.shift(); 106 | if( a == null || a == "--" ) break; 107 | hlArgs.push(a); 108 | } 109 | } 110 | 111 | if( pid == null ) { 112 | var args = ["--debug", "" + debugPort, "--debug-wait", file].concat(hlArgs); 113 | #if nodejs 114 | process = js.node.ChildProcess.spawn(cmd, args); 115 | process.stdout.on("data", function(data:String) Sys.print(data)); 116 | process.stderr.on("data", function(data:String) Sys.stderr().writeString(data)); 117 | pid = process.pid; 118 | #else 119 | process = new sys.io.Process(cmd, args); 120 | pid = process.getPid(); 121 | #end 122 | } 123 | 124 | dbg = new hld.Debugger(); 125 | dbg.loadModule(sys.io.File.getBytes(file)); 126 | 127 | function getAPI() : hld.Api { 128 | #if hl 129 | return new hld.HLDebugApi(pid, dbg.is64); 130 | #elseif nodejs 131 | return new hld.NodeDebugApiNative(pid, dbg.is64); 132 | #else 133 | throw "This platform does not have a debug API"; 134 | return null; 135 | #end 136 | } 137 | 138 | dbg.connectTries("127.0.0.1", debugPort, 10, function(b) { 139 | if( !b || !dbg.init(getAPI()) ) { 140 | dumpProcessOut(); 141 | error("Failed to access process #" + pid + " on port " + debugPort + " for debugging"); 142 | return; 143 | } 144 | while( command() ) { 145 | } 146 | Sys.exit(0); 147 | }); 148 | } 149 | 150 | function frameStr( f : Debugger.StackInfo, ?debug ) { 151 | if( f == null ) return "???"; 152 | return f.file+":" + f.line + (f.context == null ? "" : " ("+f.context.obj.name+"::"+f.context.field+")") + (debug ? " @"+f.ebp.toString():""); 153 | } 154 | 155 | function dumpProcessOut() { 156 | if( process == null ) return; 157 | #if nodejs 158 | process.kill(); 159 | #else 160 | if( process.exitCode(false) == null ) process.kill(); 161 | Sys.print(process.stdout.readAll().toString()); 162 | Sys.stderr().writeString(process.stderr.readAll().toString()); 163 | #end 164 | } 165 | 166 | function clearBP() { 167 | var count = breaks.length; 168 | for( b in breaks ) 169 | dbg.removeBreakpoint(b.file, b.line); 170 | breaks = []; 171 | Sys.println(count + " breakpoints removed"); 172 | } 173 | 174 | function handleResult( r : hld.Api.WaitResult ) { 175 | switch( r ) { 176 | case Exit: 177 | #if !nodejs 178 | dbg.resume(); 179 | #end 180 | Sys.println("Process has exit"); 181 | Sys.exit(0); 182 | case Breakpoint: 183 | var threadName = if( isCi ) "Thread" else dbg.getThreadName(dbg.stoppedThread); 184 | Sys.println(threadName + " paused " + frameStr(dbg.getStackFrame())); 185 | var exc = dbg.getException(); 186 | if( exc != null ) 187 | Sys.println("Exception: "+dbg.eval.valueStr(exc)); 188 | case Error: 189 | Sys.println("*** an error has occured, paused ***"); 190 | case Watchbreak: 191 | var w = dbg.watchBreak; 192 | Sys.println("Watch change " + w.ptr.toString() + ":" + w.t.toString() + " = " + dbg.eval.valueStr(dbg.eval.fetch(w)) + " at "+frameStr(dbg.getStackFrame())); 193 | case Timeout: 194 | dbg.customTimeout = null; 195 | handleResult(dbg.pause()); 196 | case StackOverflow: 197 | Sys.println("*** stack overflow, paused ***"); 198 | default: 199 | throw "assert "+r; 200 | } 201 | } 202 | 203 | function command() { 204 | #if !nodejs 205 | if( process != null ) { 206 | var ecode = process.exitCode(false); 207 | if( ecode != null ) { 208 | dumpProcessOut(); 209 | error("Process exit with code " + ecode); 210 | } 211 | } 212 | #end 213 | 214 | Sys.print("> "); 215 | var r = args.shift(); 216 | if( r == null ) 217 | r = stdin.readLine(); 218 | else 219 | Sys.println(r); 220 | var args = ~/[ \t\r\n]+/g.split(r); 221 | var cmd = args.shift(); 222 | inline function nextArg() { 223 | var a = args.shift(); 224 | if( a == null ) throw cmd+" is mising argument"; 225 | return a; 226 | } 227 | switch( cmd ) { 228 | case "q", "quit": 229 | if( !isCi ) 230 | dumpProcessOut(); 231 | return false; 232 | case "r", "run", "c", "continue": 233 | var time = args.shift(); 234 | dbg.customTimeout = time == null ? null : Std.parseFloat(time); 235 | while( true ) { 236 | var r = dbg.run(); 237 | if( r == Handled ) continue; 238 | handleResult(r); 239 | break; 240 | } 241 | case "bt", "backtrace": 242 | for( f in dbg.getBackTrace() ) 243 | Sys.println(frameStr(f)); 244 | case "btdebug": 245 | for( f in dbg.getBackTrace() ) 246 | Sys.println(frameStr(f,true)); 247 | case "where": 248 | Sys.println(frameStr(dbg.getStackFrame())); 249 | case "frame","f": 250 | if( args.length == 1 ) 251 | dbg.currentStackFrame = Std.parseInt(args[0]); 252 | Sys.println(frameStr(dbg.getStackFrame())); 253 | case "up": 254 | dbg.currentStackFrame += args.length == 0 ? 1 : Std.parseInt(args[0]); 255 | if( dbg.currentStackFrame >= dbg.stackFrameCount ) 256 | dbg.currentStackFrame = dbg.stackFrameCount - 1; 257 | Sys.println(frameStr(dbg.getStackFrame())); 258 | case "down": 259 | dbg.currentStackFrame -= args.length == 0 ? 1 : Std.parseInt(args[0]); 260 | if( dbg.currentStackFrame < 0 ) 261 | dbg.currentStackFrame = 0; 262 | Sys.println(frameStr(dbg.getStackFrame())); 263 | case "b", "break": 264 | var fileLine = nextArg().split(":"); 265 | var line = Std.parseInt(fileLine.pop()); 266 | var file = fileLine.join(":"); 267 | var condition = args.shift(); 268 | line = dbg.addBreakpoint(file, line, condition); 269 | if( line >= 0 ) { 270 | breaks.push({file:file, line:line, condition:condition}); 271 | Sys.println("Breakpoint set line " + line + (condition == null ? "" : ", cond: " + condition)); 272 | } else 273 | Sys.println("No breakpoint set"); 274 | case "p", "print", "global": 275 | var expr = nextArg(); 276 | if( expr == null ) { 277 | Sys.println("Requires expression"); 278 | return true; 279 | } 280 | var global = cmd == "global"; 281 | var v = if( Debugger.DEBUG ) dbg.getValue(expr, global) else try dbg.getValue(expr, global) catch( e : Dynamic ) { 282 | Sys.println("Error " + e + haxe.CallStack.toString(haxe.CallStack.exceptionStack())); 283 | return true; 284 | } 285 | if( v == null ) { 286 | Sys.println("Unknown var " + expr); 287 | return true; 288 | } 289 | switch( v.v ) { 290 | case VString(_, _): 291 | if( v.hint == HNone ) 292 | v.hint = HNoEscape; 293 | default: 294 | } 295 | Sys.println(dbg.eval.valueStr(v) + " : " + v.t.toString()); 296 | var fields = dbg.eval.getFields(v); 297 | if( fields != null ) 298 | for( f in fields ) { 299 | var fv = dbg.eval.readField(v, f); 300 | Sys.println(" " + f + " = " + dbg.eval.valueStr(fv) + " : " + fv.t.toString()); 301 | } 302 | switch( v.v ) { 303 | case VClosure(_): 304 | var stack = dbg.getClosureStack(v.v); 305 | for( s in stack ) 306 | Sys.println(" "+frameStr(s)); 307 | default: 308 | } 309 | case "watch", "rwatch": 310 | var expr = nextArg(); 311 | var v = if( Debugger.DEBUG ) dbg.getRef(expr) else try dbg.getRef(expr) catch( e : Dynamic ) { 312 | Sys.println("Error " + e + haxe.CallStack.toString(haxe.CallStack.exceptionStack())); 313 | return true; 314 | }; 315 | if( v == null ) { 316 | Sys.println("Unknown var " + expr); 317 | return true; 318 | } 319 | if( v.ptr == null ) { 320 | Sys.println("Can't watch undefined var"); 321 | return true; 322 | } 323 | try { 324 | dbg.watch(v, cmd == "rwatch"); 325 | } catch( e : Dynamic ) { 326 | Sys.println("Error " + e + haxe.CallStack.toString(haxe.CallStack.exceptionStack())); 327 | return true; 328 | } 329 | Sys.println("Watching " + v.ptr.toString() + ":" + v.t.toString() + " " + dbg.eval.valueStr(dbg.eval.fetch(v))); 330 | case "unwatch": 331 | var param = nextArg(); 332 | var count = 0; 333 | for( w in dbg.getWatches() ) 334 | if( param == null || w.ptr.toString() == param ) { 335 | dbg.unwatch(w); 336 | count++; 337 | } 338 | Sys.println("Unwatch " + count + " addresses"); 339 | case "clear": 340 | switch( args.length ) { 341 | case 0: 342 | clearBP(); 343 | case 1: 344 | var file = args[1]; 345 | var line = Std.parseInt(file); 346 | var count = 0; 347 | for( b in breaks.copy() ) 348 | if( b.file == file || (line != null && b.line == line) ) { 349 | dbg.removeBreakpoint(b.file, b.line); 350 | breaks.remove(b); 351 | count++; 352 | } 353 | Sys.println(count + " breakpoints removed"); 354 | } 355 | case "delete", "d": 356 | clearBP(); 357 | case "next", "n": 358 | handleResult(dbg.step(Next)); 359 | case "step", "s": 360 | handleResult(dbg.step(Into)); 361 | case "finish": 362 | handleResult(dbg.step(Out)); 363 | case "thread": 364 | var arg = args.shift(); 365 | if( arg != null ) { 366 | var tid = Std.parseInt(args.shift()); 367 | if( tid != null ) tid = dbg.getThreads()[tid]; 368 | if( tid != null ) dbg.setCurrentThread(tid); 369 | } 370 | Sys.println(dbg.getThreadName(dbg.currentThread)); 371 | case "info": 372 | function printVar( name : String ) { 373 | var v = dbg.getValue(name); 374 | Sys.println(" " + name+" = " + (v == null ? "???" : dbg.eval.valueStr(v) + " : " + v.t.toString())); 375 | } 376 | switch( args.shift() ) { 377 | case "args": 378 | for( name in dbg.getCurrentVars(true) ) 379 | printVar(name); 380 | case "locals": 381 | for( name in dbg.getCurrentVars(false) ) 382 | printVar(name); 383 | case "variables": 384 | for( name in dbg.getCurrentVars(true).concat(dbg.getCurrentVars(false)) ) 385 | printVar(name); 386 | case "threads": 387 | var cur = dbg.currentThread; 388 | var stack = dbg.currentStackFrame; 389 | var index = 0; 390 | for( tid in dbg.getThreads() ) { 391 | dbg.setCurrentThread(tid); 392 | Sys.println((tid == cur ? "*" : " ")+" Thread "+(index++)+"("+tid+") "+frameStr(dbg.getBackTrace()[0])); 393 | } 394 | dbg.setCurrentThread(cur); 395 | dbg.currentStackFrame = stack; 396 | case "statics": 397 | var cl = dbg.getCurrentClass(); 398 | if( cl == null ) 399 | Sys.println("Class not found"); 400 | else { 401 | Sys.println("Class "+cl); 402 | var fields = dbg.getClassStatics(cl); 403 | for( f in fields ) 404 | printVar(cl+"."+f); 405 | } 406 | default: 407 | Sys.println("Unknown info request"); 408 | } 409 | case "catch": 410 | var arg = args.shift(); 411 | switch( arg ) { 412 | case "throw": 413 | dbg.breakOnThrow = true; 414 | case "uncaught": 415 | dbg.breakOnThrow = false; 416 | default: 417 | Sys.println("Invalid catch mode"); 418 | } 419 | case "set": 420 | var all = args.join(" ").split("="); 421 | var expr = all.shift(); 422 | var value = all.join("="); 423 | if( expr == "" || value == null || value == "" ) { 424 | Sys.println("Syntax: set ="); 425 | return true; 426 | } 427 | var value = dbg.setValue(expr,value); 428 | if( value == null ) 429 | Sys.println("Failed to set expression"); 430 | else 431 | Sys.println(dbg.eval.valueStr(value) + " : " + value.t.toString()); 432 | case "cd": 433 | try Sys.setCwd(args.shift()) catch( e : Dynamic ) Sys.println(""+e); 434 | default: 435 | Sys.println("Unknown command " + r); 436 | } 437 | return true; 438 | } 439 | 440 | static function main() { 441 | new Main().init(); 442 | } 443 | 444 | } 445 | -------------------------------------------------------------------------------- /hld/CodeGraph.hx: -------------------------------------------------------------------------------- 1 | package hld; 2 | 3 | private enum Control { 4 | CNo; 5 | CJCond( d : Int ); 6 | CJAlways( d : Int ); 7 | CTry( d : Int ); 8 | CCatch; 9 | CSwitch( arr : Array ); 10 | CRet; 11 | CThrow; 12 | CLabel; 13 | CCall( fidx : Int ); 14 | } 15 | 16 | typedef LocalAccess = { rid : Int, ?index : Int, ?container : format.hl.Data.EnumPrototype, t : format.hl.Data.HLType }; 17 | 18 | class CodeBlock { 19 | 20 | public var start : Int; 21 | 22 | public var end : Int; // inclusive 23 | public var loop : Bool; 24 | public var prev : Array; 25 | public var next : Array; 26 | public var trap : Array; 27 | 28 | public var writtenRegs : Map; 29 | public var writtenVars : Map>; 30 | 31 | public var visitTag : Int = 0; 32 | public var visitResult : LocalAccess; 33 | 34 | public function new(pos, trapl) { 35 | start = pos; 36 | prev = []; 37 | next = []; 38 | trap = trapl; 39 | writtenRegs = new Map(); 40 | writtenVars = new Map(); 41 | } 42 | 43 | } 44 | 45 | class CodeGraph { 46 | 47 | var module : format.hl.Data; 48 | var fun : format.hl.Data.HLFunction; 49 | var blockPos : Map; 50 | var allBlocks : Map; 51 | var assigns : Map>; 52 | var args : Array<{ hasIndex : Bool, vars : Array }>; 53 | var nargs : Int; 54 | var currentTag : Int = 0; 55 | 56 | var localsRawCache : Array = []; 57 | var localsRawCachePos : Int = -1; 58 | 59 | public function new(md, f) { 60 | this.module = md; 61 | this.fun = f; 62 | blockPos = new Map(); 63 | allBlocks = new Map(); 64 | nargs = switch( fun.t ) { 65 | case HFun(f): f.args.length; 66 | default: throw "assert"; 67 | }; 68 | // build graph 69 | for( i in 0...f.ops.length ) 70 | switch( control(i) ) { 71 | case CJAlways(d), CJCond(d): 72 | allBlocks.set(i + 1 + d, true); 73 | default: 74 | } 75 | makeBlock(0, []); 76 | 77 | // init assign args (slightly complicated, let's handle complex logic here) 78 | args = []; 79 | var r0used = false; 80 | for( a in fun.assigns ) { 81 | if( a.position >= 0 ) break; 82 | if( a.position == -2 && args.length == 0 ) { 83 | r0used = true; 84 | } 85 | if( a.position == -1 ) { 86 | var vname = module.strings[a.varName]; 87 | args.push({ hasIndex : false, vars : [vname] }); 88 | } 89 | } 90 | if( args.length == nargs - 1 ) 91 | if( r0used ) 92 | args.unshift({ hasIndex : true, vars : [] }); 93 | else 94 | args.unshift({ hasIndex : false, vars : ["this"] }); 95 | for( a in fun.assigns ) { 96 | if( a.position >= -1 ) break; 97 | var vname = module.strings[a.varName]; 98 | var r = -a.position - 2; 99 | args[r].vars.push(vname); 100 | } 101 | 102 | // single captured pointer => passed directly 103 | if( args.length >= 1 && args[0].hasIndex && !f.regs[0].match(HEnum({constructs:[{name:""}]})) ) 104 | args[0].hasIndex = false; 105 | 106 | // init assigns 107 | assigns = new Map(); 108 | for( a in fun.assigns ) { 109 | if( a.position < 0 ) continue; 110 | var vname = module.strings[a.varName]; 111 | var vl = assigns.get(a.position); 112 | if( vl == null ) { 113 | vl = []; 114 | assigns.set(a.position, vl); 115 | } 116 | vl.push(vname); 117 | } 118 | 119 | // calculate written registers 120 | for( b in blockPos ) 121 | checkWrites(b); 122 | // absolutes 123 | while( true ) { 124 | var changed = false; 125 | for( b in blockPos ) 126 | for( b2 in b.prev ) { 127 | for( rid in b2.writtenRegs.keys() ) { 128 | var pos = b2.writtenRegs.get(rid); 129 | var cur = b.writtenRegs.get(rid); 130 | if( cur == null || cur > pos ) { 131 | b.writtenRegs.set(rid, pos); 132 | changed = true; 133 | } 134 | } 135 | } 136 | if( !changed ) break; 137 | } 138 | } 139 | 140 | function getBlock( pos : Int ) { 141 | var bpos = pos; 142 | var b; 143 | while( (b = blockPos.get(bpos)) == null ) bpos--; 144 | return b; 145 | } 146 | 147 | public function getNextPos( pos : Int ) : Array { 148 | var b = getBlock(pos); 149 | if( pos == b.end ) 150 | return b.next.map(bn -> bn.start); 151 | return b.trap.concat([pos+1]); 152 | } 153 | 154 | public function isRegisterWritten( rid : Int, pos : Int ) { 155 | if( rid < nargs ) 156 | return true; 157 | var b = getBlock(pos); 158 | var rpos = b.writtenRegs.get(rid); 159 | return rpos != null && rpos < pos; 160 | } 161 | 162 | public function getArgs() : Array { 163 | return filterRaw(getArgsRaw()); 164 | } 165 | 166 | public function getLocals( pos : Int ) : Array { 167 | return filterRaw(getLocalsRaw(pos)); 168 | } 169 | 170 | function filterRaw( raw : Array ) { 171 | var arr = []; 172 | for( a in raw ) { 173 | var name = a.split(".")[0]; 174 | if( arr.indexOf(name) >= 0 ) continue; 175 | arr.push(name); 176 | } 177 | return arr; 178 | } 179 | 180 | function getArgsRaw() : Array { 181 | var arr = []; 182 | for( a in args ) 183 | for( v in a.vars ) 184 | arr.push(v); 185 | return arr; 186 | } 187 | 188 | function getLocalsRaw( pos : Int ) : Array { 189 | if( pos == localsRawCachePos ) 190 | return localsRawCache; 191 | var arr = []; 192 | for( a in fun.assigns ) { 193 | if( a.position > pos ) break; 194 | if( a.position < 0 ) continue; // arg 195 | if( arr.indexOf(a.varName) >= 0 ) continue; 196 | if( getLocal(module.strings[a.varName],pos) == null ) continue; // not written 197 | arr.push(a.varName); 198 | } 199 | localsRawCachePos = pos; 200 | localsRawCache = [for( a in arr ) module.strings[a]]; 201 | return localsRawCache; 202 | } 203 | 204 | public function getReturnReg( pos : Int ) : Null { 205 | switch( fun.ops[pos] ) { 206 | case OCallClosure(dst,_,_), OCallMethod(dst,_,_), OCallThis(dst,_,_), OCall0(dst,_), OCall1(dst,_,_), OCall2(dst,_,_,_), OCall3(dst,_,_,_,_), OCall4(dst,_,_,_,_), OCallN(dst,_,_): 207 | if( fun.regs[dst] != HVoid ) return fun.regs[dst]; 208 | default: 209 | } 210 | return null; 211 | } 212 | 213 | public function getLocal( name : String, pos : Int ) : LocalAccess { 214 | var b = getBlock(pos); 215 | currentTag++; 216 | var l = lookupLocal(b, name, pos); 217 | if( l != null ) 218 | return l; 219 | for( i in 0...args.length ) { 220 | var a = args[i]; 221 | for( k in 0...a.vars.length ) 222 | if( a.vars[k] == name ) { 223 | if( !a.hasIndex ) 224 | return { rid : i, t : fun.regs[i] }; 225 | var en = null; 226 | var t = switch( fun.regs[i] ) { 227 | case HEnum(e): 228 | en = e; 229 | e.constructs[0].params[k]; 230 | default: 231 | throw "assert"; 232 | }; 233 | return { rid : i, index : k, container : en, t : t }; 234 | } 235 | } 236 | return null; 237 | } 238 | 239 | function lookupLocal( b : CodeBlock, name : String, pos : Int ) : LocalAccess { 240 | if( b.visitTag == currentTag ) 241 | return b.visitResult; 242 | b.visitTag = currentTag; 243 | var v = b.writtenVars.get(name); 244 | if( v != null ) { 245 | var last = -1; 246 | for( p in v ) 247 | if( p < pos ) 248 | last = p; 249 | else if( last < 0 ) 250 | break; 251 | if( last >= 0 ) { 252 | var rid = -1; 253 | opFx(fun.ops[last], function(_) {}, function(w) rid = w); 254 | return b.visitResult = { rid : rid, t : fun.regs[rid] }; 255 | } 256 | } 257 | var found : LocalAccess = null; 258 | for( b2 in b.prev ) 259 | if( b2.start < b.start ) { 260 | var l = lookupLocal(b2, name, pos); 261 | // make sure that all branches have written the same register 262 | // if not it's out of scope 263 | if( found != null && (l == null || l.rid != found.rid) ) 264 | return b.visitResult = null; 265 | found = l; 266 | } 267 | return b.visitResult = found; 268 | } 269 | 270 | function checkWrites( b : CodeBlock ) { 271 | for( i in b.start...b.end+1 ) { 272 | opFx(fun.ops[i], function(_) {}, function(rid) if( !b.writtenRegs.exists(rid) ) b.writtenRegs.set(rid, i)); 273 | var vl = assigns.get(i); 274 | if( vl == null ) continue; 275 | for( v in vl ) { 276 | var wl = b.writtenVars.get(v); 277 | if( wl == null ) { 278 | wl = []; 279 | b.writtenVars.set(v, wl); 280 | } 281 | wl.push(i); 282 | } 283 | } 284 | } 285 | 286 | function makeBlock( pos : Int, trapl : Array ) { 287 | var b = blockPos.get(pos); 288 | if( b != null ) 289 | return b; 290 | var b = new CodeBlock(pos, trapl); 291 | blockPos.set(pos, b); 292 | var i = pos; 293 | while( true ) { 294 | inline function goto(d, ?tl : Array) { 295 | var b2 = makeBlock(i + 1 + d, tl == null ? b.trap : tl); 296 | b2.prev.push(b); 297 | return b2; 298 | } 299 | if( i > pos && allBlocks.exists(i) ) { 300 | b.end = i - 1; 301 | b.next = [goto(-1)]; 302 | break; 303 | } 304 | switch( control(i) ) { 305 | case CNo, CCall(_): 306 | i++; 307 | continue; 308 | case CRet: 309 | if( b.trap.length != 0 ) 310 | throw "assert"; 311 | b.end = i; 312 | case CJAlways(d): 313 | b.end = i; 314 | b.next = [goto(d)]; 315 | case CSwitch(pl): 316 | b.end = i; 317 | b.next.push(goto(0)); 318 | for( p in pl ) 319 | b.next.push(goto(p)); 320 | case CJCond(d): 321 | b.end = i; 322 | b.next = [goto(0), goto(d)]; 323 | case CTry(d): 324 | b.end = i; 325 | var tl = b.trap.copy(); 326 | tl.push(i+1+d); 327 | b.next = [goto(0, tl), goto(d)]; 328 | case CThrow: 329 | b.end = i; 330 | if( b.trap.length > 0 ) { 331 | var tl = b.trap.copy(); 332 | var p = tl.pop(); 333 | b.next = [goto(p-1-i, tl)]; 334 | } 335 | case CCatch: 336 | if( b.trap.length == 0 ) 337 | throw "assert"; 338 | var tl = b.trap.copy(); 339 | var p = tl.pop(); 340 | b.end = i; 341 | b.next = [goto(0, tl), goto(p-1-i, tl)]; 342 | case CLabel: 343 | i++; 344 | b.loop = true; 345 | continue; 346 | } 347 | break; 348 | } 349 | return b; 350 | } 351 | 352 | public function control( i ) { 353 | return switch( fun.ops[i] ) { 354 | case OJTrue (_,d), OJFalse (_,d), OJNull (_,d), OJNotNull (_,d), 355 | OJSLt (_, _, d), OJSGte (_, _, d), OJSGt (_, _, d), OJSLte (_, _, d), 356 | OJULt (_, _, d), OJUGte (_, _, d), OJEq (_, _, d), OJNotEq (_, _, d), 357 | OJNotLt (_,_,d), OJNotGte (_,_,d): 358 | CJCond(d); 359 | case OJAlways(d): 360 | CJAlways(d); 361 | case OLabel: 362 | CLabel; 363 | case ORet(_): 364 | CRet; 365 | case OThrow(_), ORethrow(_): 366 | CThrow; 367 | case OSwitch(_,cases,_): 368 | CSwitch(cases); 369 | case OTrap(_,d): 370 | CTry(d); 371 | case OEndTrap(_): 372 | CCatch; 373 | case OCallClosure(_), OCallMethod(_), OCallThis(_): 374 | CCall(-1); 375 | case OCall0(_,idx), OCall1(_,idx,_), OCall2(_,idx,_,_), OCall3(_,idx,_,_,_), OCall4(_,idx,_,_,_), OCallN(_,idx,_): 376 | CCall(idx); 377 | default: 378 | CNo; 379 | } 380 | } 381 | 382 | inline function opFx( op : format.hl.Data.Opcode, read, write ) { 383 | switch( op ) { 384 | case OMov(d,a), ONeg(d,a), ONot(d,a): 385 | read(a); write(d); 386 | case OInt(d,_), OFloat(d,_), OBool(d,_), OBytes(d,_), OString(d,_), ONull(d): 387 | write(d); 388 | case OAdd(d,a,b), OSub(d,a,b), OMul(d,a,b), OSDiv(d,a,b), OUDiv(d,a,b), OSMod(d,a,b), OUMod(d,a,b), OShl(d,a,b), OSShr(d,a,b), OUShr(d,a,b), OAnd(d,a,b), OOr(d,a,b), OXor(d,a,b): 389 | read(a); read(b); write(d); 390 | case OIncr(a), ODecr(a): 391 | read(a); write(a); 392 | case OCall0(d,_): 393 | write(d); 394 | case OCall1(d,_,a): 395 | read(a); write(d); 396 | case OCall2(d,_,a,b): 397 | read(a); read(b); write(d); 398 | case OCall3(d,_,a,b,c): 399 | read(a); read(b); read(c); write(d); 400 | case OCall4(d,_,a,b,c,k): 401 | read(a); read(b); read(c); read(k); write(d); 402 | case OCallN(d,_,rl), OCallMethod(d,_,rl), OCallThis(d,_,rl): 403 | for( r in rl ) read(r); write(d); 404 | case OCallClosure(d,f,rl): 405 | read(f); for( r in rl ) read(r); write(d); 406 | case OStaticClosure(d,_): 407 | write(d); 408 | case OInstanceClosure(d, _, a), OVirtualClosure(d,a,_): 409 | read(a); write(d); 410 | case OGetGlobal(d,_): 411 | write(d); 412 | case OSetGlobal(_,a): 413 | read(a); 414 | case OField(d,a,_), ODynGet(d,a,_): 415 | read(a); write(d); 416 | case OSetField(a,_,b), ODynSet(a,_,b): 417 | read(a); read(b); 418 | case OGetThis(d,_): 419 | write(d); 420 | case OSetThis(_,a): 421 | read(a); 422 | case OJTrue(r,_), OJFalse(r,_), OJNull(r,_), OJNotNull(r,_): 423 | read(r); 424 | case OJSLt(a,b,_), OJSGte(a,b,_), OJSGt(a,b,_), OJSLte(a,b,_), OJULt(a,b,_), OJUGte(a,b,_), OJNotLt(a,b,_), OJNotGte(a,b,_), OJEq(a,b,_), OJNotEq(a,b,_): 425 | read(a); read(b); 426 | case OJAlways(_), OLabel: 427 | // nothing 428 | case OToDyn(d, a), OToSFloat(d,a), OToUFloat(d,a), OToInt(d,a), OSafeCast(d,a), OUnsafeCast(d,a), OToVirtual(d,a): 429 | read(a); write(d); 430 | case ORet(r), OThrow(r), ORethrow(r), OSwitch(r,_,_), ONullCheck(r): 431 | read(r); 432 | case OTrap(r,_): 433 | write(r); 434 | case OEndTrap(_): 435 | // nothing 436 | case OGetUI8(d,a,b), OGetUI16(d,a,b), OGetMem(d,a,b), OGetArray(d,a,b): 437 | read(a); read(b); write(d); 438 | case OSetUI8(a,b,c), OSetUI16(a,b,c), OSetMem(a,b,c), OSetArray(a,b,c): 439 | read(a); read(b); read(c); 440 | case ONew(d): 441 | write(d); 442 | case OArraySize(d, a), OGetType(d,a), OGetTID(d,a), ORef(d, a), OUnref(d,a), OSetref(d, a), OEnumIndex(d, a), OEnumField(d,a,_,_): 443 | read(a); 444 | write(d); 445 | case OType(d,_), OEnumAlloc(d,_): 446 | write(d); 447 | case OMakeEnum(d,_,rl): 448 | for( r in rl ) read(r); 449 | write(d); 450 | case OSetEnumField(a,_,b): 451 | read(a); read(b); 452 | case OAssert: 453 | // nothing 454 | case ORefData(r,d): 455 | read(d); 456 | write(r); 457 | case ORefOffset(r,r2,off): 458 | read(r2); 459 | read(off); 460 | write(r); 461 | case ONop: 462 | // nothing 463 | case OPrefetch(r,_,_): 464 | read(r); 465 | case OAsm(_,_,ropt): 466 | var r = ropt.getReg(); 467 | if (r != null) { 468 | read(r); 469 | write(r); 470 | } 471 | case OCatch(_): 472 | // nothing 473 | } 474 | } 475 | 476 | } 477 | -------------------------------------------------------------------------------- /hld/Module.hx: -------------------------------------------------------------------------------- 1 | package hld; 2 | import format.hl.Data; 3 | 4 | private typedef GlobalAccess = { 5 | var sub : Map; 6 | var gid : Null; 7 | } 8 | 9 | typedef ModuleProto = { 10 | var name : String; 11 | var size : Int; 12 | var padSize : Int; 13 | var largestField : Int; 14 | var fieldNames : Array; 15 | var parent : ModuleProto; 16 | var fields : Map; 21 | var methods : Map; 22 | } 23 | 24 | typedef ModuleEProto = Array<{ name : String, size : Int, params : Array<{ offset : Int, t : HLType }> }>; 25 | 26 | class Module { 27 | 28 | public var code : format.hl.Data; 29 | var fileIndexes : Map; 30 | var functionsByFile : Map>; 31 | var globalsOffsets : Array; 32 | var globalTable : GlobalAccess; 33 | var typeCache : Map; 34 | var protoCache : Map; 35 | var eprotoCache : Map; 36 | var functionRegsCache : Array>; 37 | var align : Align; 38 | var reversedHashes : Map; 39 | var graphCache : Map; 40 | var methods : Array<{ obj : ObjPrototype, field : String }>; 41 | var functionsIndexes : Map; 42 | var isWindows : Bool; 43 | var closureContextId : Int = 0; 44 | 45 | public function new() { 46 | protoCache = new Map(); 47 | eprotoCache = new Map(); 48 | graphCache = new Map(); 49 | functionsIndexes = new Map(); 50 | functionRegsCache = []; 51 | methods = []; 52 | isWindows = Sys.systemName() == "Windows"; 53 | } 54 | 55 | public function getMethodContext( fidx : Int ) { 56 | var f = code.functions[fidx]; 57 | if( f == null ) return null; 58 | return methods[f.findex]; 59 | } 60 | 61 | public function load( data : haxe.io.Bytes ) { 62 | code = new format.hl.Reader().read(new haxe.io.BytesInput(data)); 63 | 64 | if( code.debugFiles == null ) 65 | throw "Debug info not available in the bytecode"; 66 | 67 | for( t in code.types ) 68 | switch( t ) { 69 | case HObj(o), HStruct(o): 70 | for( f in o.proto ) 71 | methods[f.findex] = { obj : o, field : f.name }; 72 | for( b in o.bindings ) 73 | methods[b.mid] = { obj : o, field : fetchField(o, b.fid).name }; 74 | default: 75 | } 76 | 77 | // init files 78 | fileIndexes = new Map(); 79 | for( i in 0...code.debugFiles.length ) { 80 | var f = code.debugFiles[i]; 81 | fileIndexes.set(f, i); 82 | var low = f.split("\\").join("/").toLowerCase(); 83 | fileIndexes.set(low, i); 84 | var fileOnly = low.split("/").pop(); 85 | if( !fileIndexes.exists(fileOnly) ) { 86 | fileIndexes.set(fileOnly, i); 87 | if( StringTools.endsWith(fileOnly,".hx") ) 88 | fileIndexes.set(fileOnly.substr(0, -3), i); 89 | } 90 | } 91 | 92 | functionsByFile = new Map(); 93 | for( ifun in 0...code.functions.length ) { 94 | var f = code.functions[ifun]; 95 | var files = new Map(); 96 | functionsIndexes.set(f.findex, ifun); 97 | for( i in 0...f.debug.length >> 1 ) { 98 | var ifile = f.debug[i << 1]; 99 | var dline = f.debug[(i << 1) + 1]; 100 | var inf = files.get(ifile); 101 | if( inf == null ) { 102 | inf = { f : f, ifun : ifun, lmin : 1000000, lmax : -1 }; 103 | files.set(ifile, inf); 104 | var fl = functionsByFile.get(ifile); 105 | if( fl == null ) { 106 | fl = []; 107 | functionsByFile.set(ifile, fl); 108 | } 109 | fl.push(inf); 110 | } 111 | if( dline < inf.lmin ) inf.lmin = dline; 112 | if( dline > inf.lmax ) inf.lmax = dline; 113 | } 114 | } 115 | for( i in 0...code.natives.length ) 116 | functionsIndexes.set(code.natives[i].findex, code.functions.length + i); 117 | } 118 | 119 | function fetchField( o : ObjPrototype, fid : Int ) { 120 | var pl = []; 121 | var fcount = 0; 122 | while( true ) { 123 | pl.push(o); 124 | fcount += o.fields.length; 125 | if( o.tsuper == null ) break; 126 | switch( o.tsuper ) { 127 | case HObj(s): o = s; 128 | default: throw "assert"; 129 | } 130 | } 131 | if( fid < 0 || fid >= fcount ) 132 | return null; 133 | for( i in 0...pl.length ) { 134 | var o = pl[pl.length - i - 1]; 135 | if( fid < o.fields.length ) 136 | return o.fields[fid]; 137 | fid -= o.fields.length; 138 | } 139 | return null; 140 | } 141 | 142 | public function init( align : Align ) { 143 | this.align = align; 144 | 145 | // init globals 146 | var globalsPos = 0; 147 | globalsOffsets = []; 148 | for( g in code.globals ) { 149 | globalsPos += align.padSize(globalsPos, g); 150 | globalsOffsets.push(globalsPos); 151 | globalsPos += align.typeSize(g); 152 | } 153 | 154 | globalTable = { 155 | sub : new Map(), 156 | gid : null, 157 | }; 158 | function addGlobal( path : Array, gid : Int ) { 159 | var t = globalTable; 160 | for( p in path ) { 161 | if( t.sub == null ) 162 | t.sub = new Map(); 163 | var next = t.sub.get(p); 164 | if( next == null ) { 165 | next = { sub : null, gid : null }; 166 | t.sub.set(p, next); 167 | } 168 | t = next; 169 | } 170 | t.gid = gid; 171 | } 172 | typeCache = []; 173 | for( t in code.types ) 174 | switch( t ) { 175 | case HObj(o), HStruct(o): 176 | typeCache.set(o.name, t); 177 | if( o.globalValue == null ) 178 | continue; 179 | var path = o.name.split("."); 180 | addGlobal(path, o.globalValue); 181 | // Add abstract type's original name as alias 182 | var hasAlias = false; 183 | var apath = [path[0]]; 184 | for( i in 1...path.length ) { 185 | var n0 = apath[apath.length-1]; 186 | var n1 = path[i]; 187 | if( n0.charCodeAt(0) == "_".code && StringTools.endsWith(n1, "_Impl_") ) { 188 | hasAlias = true; 189 | n1 = n1.substring(0, n1.length - 6); 190 | apath.pop(); 191 | } 192 | apath.push(n1); 193 | } 194 | if( hasAlias ) addGlobal(apath, o.globalValue); 195 | case HEnum(e): 196 | if( e.name != null ) 197 | typeCache.set(e.name, t); 198 | if( e.globalValue == null ) 199 | continue; 200 | addGlobal(e.name.split("."), e.globalValue); 201 | default: 202 | } 203 | } 204 | 205 | public function getObjectProto( o : ObjPrototype, isStruct : Bool ) : ModuleProto { 206 | 207 | var p = protoCache.get(o.name); 208 | if( p != null ) 209 | return p; 210 | 211 | var parent = o.tsuper == null ? null : switch( o.tsuper ) { case HObj(o), HStruct(o): getObjectProto(o,isStruct); default: throw "assert"; }; 212 | var size = parent == null ? (isStruct ? 0 : align.ptr) : parent.size - parent.padSize; 213 | var largestField = parent == null ? size : parent.largestField; 214 | var fields = parent == null ? new Map() : [for( k in parent.fields.keys() ) k => parent.fields.get(k)]; 215 | var mindex = 0; 216 | var methods = parent == null ? new Map() : [for( k => v in parent.methods ) k => { 217 | mindex++; 218 | v; 219 | }]; 220 | 221 | for( f in o.fields ) { 222 | var pad = f.t; 223 | switch( pad ) { 224 | case HPacked(t): 225 | // align on packed largest field 226 | switch( t.v ) { 227 | case HStruct(o): 228 | var large = getObjectProto(o,true).largestField; 229 | var pad = size % large; 230 | if( pad != 0 ) 231 | size += large - pad; 232 | if( large > largestField ) 233 | largestField = large; 234 | default: throw "assert"; 235 | } 236 | default: 237 | size += align.padStruct(size, pad); 238 | } 239 | fields.set(f.name, { name : f.name, t : f.t, offset : size }); 240 | size += switch( f.t ) { 241 | case HPacked({ v : HStruct(o) }): getObjectProto(o,true).size; 242 | case HPacked(_): throw "assert"; 243 | default: 244 | var sz = align.typeSize(f.t); 245 | if( sz > largestField ) largestField = sz; 246 | sz; 247 | } 248 | } 249 | 250 | var padSize = 0; 251 | if( largestField > 0 ) { 252 | var pad = size % largestField; 253 | if( pad != 0 ) { 254 | padSize = largestField - pad; 255 | size += padSize; 256 | } 257 | } 258 | 259 | for( m in o.proto ) { 260 | var idx = functionsIndexes.get(m.findex); 261 | var f = code.functions[idx]; 262 | // parent methods are placed before child 263 | if( parent != null && m.pindex >= 0 ) { 264 | var v = parent.methods.get(m.name); 265 | if( v != null ) 266 | methods.set(m.name, { t : f.t, index : v.index, pindex : m.pindex }); 267 | else 268 | methods.set(m.name, { t : f.t, index : mindex++, pindex : m.pindex }); 269 | } else { 270 | methods.set(m.name, { t : f.t, index : mindex++, pindex : m.pindex }); 271 | } 272 | } 273 | 274 | p = { 275 | name : o.name, 276 | size : size, 277 | padSize : padSize, 278 | largestField: largestField, 279 | parent : parent, 280 | fields : fields, 281 | methods : methods, 282 | fieldNames : [for( o in o.fields ) o.name], 283 | }; 284 | protoCache.set(p.name, p); 285 | 286 | return p; 287 | } 288 | 289 | public function getEnumProto( e : EnumPrototype ) : ModuleEProto { 290 | if( e.name == null ) 291 | e.name = "$Closure:"+closureContextId++; 292 | var p = eprotoCache.get(e.name); 293 | if( p != null ) 294 | return p; 295 | p = []; 296 | for( c in e.constructs ) { 297 | var size = align.ptr; 298 | size += align.padStruct(size, HI32); 299 | size += 4; // index 300 | var params = []; 301 | for( t in c.params ) { 302 | size += align.padStruct(size, t); 303 | params.push({ offset : size, t : t }); 304 | size += align.typeSize(t); 305 | } 306 | p.push({ name : c.name, size : size, params : params }); 307 | } 308 | eprotoCache.set(e.name, p); 309 | return p; 310 | } 311 | 312 | public function resolveGlobal( path : Array ) { 313 | var g = globalTable; 314 | while( path.length > 0 ) { 315 | if( g.sub == null ) break; 316 | var p = path[0]; 317 | var n = g.sub.get(p); 318 | if( n == null ) break; 319 | path.shift(); 320 | g = n; 321 | } 322 | return g == globalTable || g.gid == null ? null : { type : code.globals[g.gid], offset : globalsOffsets[g.gid] }; 323 | } 324 | 325 | public function resolveType( path : String ) { 326 | return typeCache.get(path); 327 | } 328 | 329 | public function resolveEnum( path : String ) { 330 | var et = typeCache.get(path); 331 | return switch( et ) { 332 | case HEnum(e): e; 333 | default: null; 334 | } 335 | } 336 | 337 | public function getFileFunctions( file : String ) { 338 | var ifile = fileIndexes.get(file); 339 | if( ifile == null ) 340 | ifile = fileIndexes.get(file.split("\\").join("/").toLowerCase()); 341 | if( ifile == null ) 342 | return null; 343 | var functions = functionsByFile.get(ifile); 344 | if( functions == null ) 345 | return null; 346 | return { functions : functions, fidx : ifile }; 347 | } 348 | 349 | public function getBreaks( file : String, line : Int ) { 350 | var ffuns = getFileFunctions(file); 351 | if( ffuns == null ) 352 | return null; 353 | 354 | var breaks = []; 355 | var funs = ffuns.functions; 356 | var matched = []; 357 | 358 | while( breaks.length == 0 && funs.length > 0 ) { 359 | for( f in funs ) { 360 | if( f.lmin > line || f.lmax < line ) continue; 361 | matched.push(f); 362 | var ifun = f.ifun; 363 | var f = f.f; 364 | var i = 0; 365 | var len = f.debug.length >> 1; 366 | var first = -1; 367 | /** 368 | Because of inlining or switch compilation we might have several instances 369 | of the same code duplicated within the same method, let's match continous 370 | groups 371 | **/ 372 | while( i < len ) { 373 | var dfile = f.debug[i << 1]; 374 | if( dfile != ffuns.fidx ) { 375 | i++; 376 | continue; 377 | } 378 | var dline = f.debug[(i << 1) + 1]; 379 | if( dline != line ) { 380 | i++; 381 | continue; 382 | } 383 | var op = f.ops[i].getIndex(); 384 | if( first == -1 || first == op ) { 385 | first = op; 386 | breaks.push({ ifun : ifun, pos : i }); 387 | } 388 | // skip 389 | i++; 390 | while( i < len ) { 391 | var dfile = f.debug[i << 1]; 392 | var dline = f.debug[(i << 1) + 1]; 393 | if( dfile == ffuns.fidx && dline != line ) 394 | break; 395 | i++; 396 | } 397 | } 398 | } 399 | // breakpoint not found ? move to the next line 400 | if( breaks.length == 0 ) { 401 | funs = matched; 402 | matched = []; 403 | line++; 404 | } 405 | } 406 | return { breaks : breaks, line : line }; 407 | } 408 | 409 | public function isValid( fidx : Int, fpos : Int ) { 410 | var f = code.functions[fidx]; 411 | var fid = f.debug[fpos << 1]; 412 | return code.debugFiles[fid] != "?"; 413 | } 414 | 415 | public function resolveSymbol( fidx : Int, fpos : Int ) { 416 | var f = code.functions[fidx]; 417 | var fid = f.debug[fpos << 1]; 418 | var fline = f.debug[(fpos << 1) + 1]; 419 | return { file : code.debugFiles[fid], line : fline }; 420 | } 421 | 422 | public function getFunctionRegs( fidx : Int ) { 423 | var regs = functionRegsCache[fidx]; 424 | if( regs != null ) 425 | return regs; 426 | var f = code.functions[fidx]; 427 | var nargs = switch( f.t ) { case HFun(f): f.args.length; default: throw "assert"; }; 428 | regs = []; 429 | 430 | var argsSize = 0; 431 | var size = 0; 432 | var floatRegs = 0, intRegs = 0; 433 | for( i in 0...nargs ) { 434 | var t = f.regs[i]; 435 | if( align.is64 && !isWindows ) { 436 | var isFloat = t.match(HF32 | HF64); 437 | if( (isFloat ? ++floatRegs : ++intRegs) <= 6 ) { 438 | // stored in locals 439 | size += align.typeSize(t); 440 | size += align.padSize(size, t); 441 | regs[i] = { t : t, offset : -size }; 442 | continue; 443 | } 444 | } 445 | regs[i] = { t : t, offset : argsSize + align.ptr * 2 }; 446 | argsSize += align.stackSize(t); 447 | } 448 | for( i in nargs...f.regs.length ) { 449 | var t = f.regs[i]; 450 | size += align.typeSize(t); 451 | size += align.padSize(size, t); 452 | regs[i] = { t : t, offset : -size }; 453 | } 454 | functionRegsCache[fidx] = regs; 455 | return regs; 456 | } 457 | 458 | public function reverseHash( h : Int ) { 459 | if( reversedHashes == null ) { 460 | reversedHashes = new Map(); 461 | for( s in code.strings ) 462 | reversedHashes.set(s.hash(), s); 463 | } 464 | return reversedHashes.get(h); 465 | } 466 | 467 | public function getGraph( fidx : Int ) { 468 | var g = graphCache.get(fidx); 469 | if( g != null ) 470 | return g; 471 | g = new CodeGraph(code, code.functions[fidx]); 472 | graphCache.set(fidx, g); 473 | return g; 474 | } 475 | 476 | } 477 | -------------------------------------------------------------------------------- /hldebug-wrapper/src/hl.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C)2005-2016 Haxe Foundation 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE. 21 | */ 22 | #ifndef HL_H 23 | #define HL_H 24 | 25 | /** 26 | Detailed documentation can be found here: 27 | https://github.com/HaxeFoundation/hashlink/wiki/ 28 | **/ 29 | 30 | #define HL_VERSION 0x010C00 31 | 32 | #if defined(_WIN32) 33 | # define HL_WIN 34 | # ifndef _DURANGO 35 | # define HL_WIN_DESKTOP 36 | # endif 37 | #endif 38 | 39 | #if defined(__APPLE__) || defined(__MACH__) || defined(macintosh) 40 | #include 41 | #if TARGET_OS_IOS 42 | #define HL_IOS 43 | #elif TARGET_OS_TV 44 | #define HL_TVOS 45 | #elif TARGET_OS_MAC 46 | #define HL_MAC 47 | #endif 48 | #endif 49 | 50 | #ifdef __ANDROID__ 51 | # define HL_ANDROID 52 | #endif 53 | 54 | #if defined(linux) || defined(__linux__) 55 | # define HL_LINUX 56 | # define _GNU_SOURCE 57 | #endif 58 | 59 | #if defined(HL_IOS) || defined(HL_ANDROID) || defined(HL_TVOS) 60 | # define HL_MOBILE 61 | #endif 62 | 63 | #ifdef __ORBIS__ 64 | # define HL_PS 65 | #endif 66 | 67 | #ifdef __NX__ 68 | # define HL_NX 69 | #endif 70 | 71 | #ifdef _DURANGO 72 | # define HL_XBO 73 | #endif 74 | 75 | #if defined(HL_PS) || defined(HL_NX) || defined(HL_XBO) 76 | # define HL_CONSOLE 77 | #endif 78 | 79 | #if (defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)) && !defined(HL_CONSOLE) 80 | # define HL_BSD 81 | #endif 82 | 83 | #if defined(_64BITS) || defined(__x86_64__) || defined(_M_X64) || defined(__LP64__) 84 | # define HL_64 85 | #endif 86 | 87 | #if defined(__GNUC__) 88 | # define HL_GCC 89 | #endif 90 | 91 | #if defined(__MINGW32__) 92 | # define HL_MINGW 93 | #endif 94 | 95 | #if defined(__CYGWIN__) 96 | # define HL_CYGWIN 97 | #endif 98 | 99 | #if defined(__llvm__) 100 | # define HL_LLVM 101 | #endif 102 | 103 | #if defined(__clang__) 104 | # define HL_CLANG 105 | #endif 106 | 107 | #if defined(_MSC_VER) && !defined(HL_LLVM) 108 | # define HL_VCC 109 | # pragma warning(disable:4996) // remove deprecated C API usage warnings 110 | # pragma warning(disable:4055) // void* - to - function cast 111 | # pragma warning(disable:4152) // void* - to - function cast 112 | # pragma warning(disable:4201) // anonymous struct 113 | # pragma warning(disable:4127) // while( true ) 114 | # pragma warning(disable:4710) // inline disabled 115 | # pragma warning(disable:4711) // inline activated 116 | # pragma warning(disable:4255) // windows include 117 | # pragma warning(disable:4820) // windows include 118 | # pragma warning(disable:4668) // windows include 119 | # pragma warning(disable:4738) // return float bad performances 120 | #endif 121 | 122 | #if defined(HL_VCC) || defined(HL_MINGW) || defined(HL_CYGWIN) 123 | # define HL_WIN_CALL 124 | #endif 125 | 126 | #ifdef _DEBUG 127 | # define HL_DEBUG 128 | #endif 129 | 130 | #ifndef HL_CONSOLE 131 | # define HL_TRACK_ENABLE 132 | #endif 133 | 134 | #ifndef HL_NO_THREADS 135 | # define HL_THREADS 136 | # ifdef HL_VCC 137 | # define HL_THREAD_VAR __declspec( thread ) 138 | # define HL_THREAD_STATIC_VAR HL_THREAD_VAR static 139 | # else 140 | # define HL_THREAD_VAR __thread 141 | # define HL_THREAD_STATIC_VAR static HL_THREAD_VAR 142 | # endif 143 | #else 144 | # define HL_THREAD_VAR 145 | # define HL_THREAD_STATIC_VAR static 146 | #endif 147 | 148 | #include 149 | #ifndef HL_VCC 150 | # include 151 | #endif 152 | 153 | #if defined(HL_VCC) || defined(HL_MINGW) 154 | # define EXPORT __declspec( dllexport ) 155 | # define IMPORT __declspec( dllimport ) 156 | #else 157 | # define EXPORT 158 | # define IMPORT extern 159 | #endif 160 | 161 | #ifdef HL_64 162 | # define HL_WSIZE 8 163 | # define IS_64 1 164 | # ifdef HL_VCC 165 | # define _PTR_FMT L"%IX" 166 | # else 167 | # define _PTR_FMT u"%lX" 168 | # endif 169 | #else 170 | # define HL_WSIZE 4 171 | # define IS_64 0 172 | # ifdef HL_VCC 173 | # define _PTR_FMT L"%IX" 174 | # else 175 | # define _PTR_FMT u"%X" 176 | # endif 177 | #endif 178 | 179 | #ifdef __cplusplus 180 | # define C_FUNCTION_BEGIN extern "C" { 181 | # define C_FUNCTION_END }; 182 | #else 183 | # define C_FUNCTION_BEGIN 184 | # define C_FUNCTION_END 185 | #endif 186 | 187 | typedef intptr_t int_val; 188 | typedef long long int64; 189 | typedef unsigned long long uint64; 190 | 191 | #include 192 | #include 193 | #include 194 | #include 195 | 196 | #if defined(LIBHL_EXPORTS) 197 | #define HL_API extern EXPORT 198 | #elif defined(LIBHL_STATIC) 199 | #define HL_API extern 200 | #else 201 | #define HL_API IMPORT 202 | #endif 203 | 204 | // -------------- UNICODE ----------------------------------- 205 | 206 | #if defined(HL_WIN) && !defined(HL_LLVM) 207 | #if defined(HL_WIN_DESKTOP) && !defined(HL_MINGW) 208 | # include 209 | #elif defined(HL_WIN_DESKTOP) && defined(HL_MINGW) 210 | # include 211 | #else 212 | # include 213 | #endif 214 | # include 215 | typedef wchar_t uchar; 216 | # define USTR(str) L##str 217 | # define HL_NATIVE_UCHAR_FUN 218 | # define usprintf swprintf 219 | # define uprintf wprintf 220 | # define ustrlen wcslen 221 | # define ustrdup _wcsdup 222 | HL_API int uvszprintf( uchar *out, int out_size, const uchar *fmt, va_list arglist ); 223 | # define _utod(s,end) wcstod(s,end) 224 | # define _utoi(s,end) wcstol(s,end,10) 225 | # define ucmp(a,b) wcscmp(a,b) 226 | # define utostr(out,size,str) wcstombs(out,str,size) 227 | #elif defined(HL_MAC) 228 | typedef uint16_t uchar; 229 | # undef USTR 230 | # define USTR(str) u##str 231 | #else 232 | # include 233 | #if defined(HL_IOS) || defined(HL_TVOS) || defined(HL_MAC) 234 | #include 235 | #include 236 | typedef uint16_t char16_t; 237 | typedef uint32_t char32_t; 238 | #else 239 | # include 240 | #endif 241 | typedef char16_t uchar; 242 | # undef USTR 243 | # define USTR(str) u##str 244 | #endif 245 | 246 | C_FUNCTION_BEGIN 247 | HL_API double utod( const uchar *str, uchar **end ); 248 | HL_API int utoi( const uchar *str, uchar **end ); 249 | #ifndef HL_NATIVE_UCHAR_FUN 250 | HL_API int ustrlen( const uchar *str ); 251 | HL_API uchar *ustrdup( const uchar *str ); 252 | HL_API int ucmp( const uchar *a, const uchar *b ); 253 | HL_API int utostr( char *out, int out_size, const uchar *str ); 254 | HL_API int usprintf( uchar *out, int out_size, const uchar *fmt, ... ); 255 | HL_API int uvszprintf( uchar *out, int out_size, const uchar *fmt, va_list arglist ); 256 | HL_API void uprintf( const uchar *fmt, const uchar *str ); 257 | #endif 258 | C_FUNCTION_END 259 | 260 | #if defined(HL_VCC) 261 | # define hl_debug_break() if( IsDebuggerPresent() ) __debugbreak() 262 | #elif defined(HL_PS) && defined(_DEBUG) 263 | # define hl_debug_break() __debugbreak() 264 | #elif defined(HL_NX) 265 | C_FUNCTION_BEGIN 266 | HL_API void hl_debug_break( void ); 267 | C_FUNCTION_END 268 | #elif defined(HL_LINUX) && defined(__i386__) 269 | # ifdef HL_64 270 | # define hl_debug_break() \ 271 | if( hl_detect_debugger() ) \ 272 | __asm__("0: int3;" \ 273 | ".pushsection embed-breakpoints;" \ 274 | ".quad 0b;" \ 275 | ".popsection") 276 | # else 277 | # define hl_debug_break() \ 278 | if( hl_detect_debugger() ) \ 279 | __asm__("0: int3;" \ 280 | ".pushsection embed-breakpoints;" \ 281 | ".long 0b;" \ 282 | ".popsection") 283 | # endif 284 | #elif defined(HL_MAC) 285 | #include 286 | # define hl_debug_break() \ 287 | if( hl_detect_debugger() ) \ 288 | raise(SIGTRAP);//__builtin_trap(); 289 | #else 290 | # define hl_debug_break() 291 | #endif 292 | 293 | #ifdef HL_VCC 294 | # define HL_NO_RETURN(f) __declspec(noreturn) f 295 | # define HL_UNREACHABLE 296 | #else 297 | # define HL_NO_RETURN(f) f __attribute__((noreturn)) 298 | # define HL_UNREACHABLE __builtin_unreachable() 299 | #endif 300 | 301 | // ---- TYPES ------------------------------------------- 302 | 303 | typedef enum { 304 | HVOID = 0, 305 | HUI8 = 1, 306 | HUI16 = 2, 307 | HI32 = 3, 308 | HI64 = 4, 309 | HF32 = 5, 310 | HF64 = 6, 311 | HBOOL = 7, 312 | HBYTES = 8, 313 | HDYN = 9, 314 | HFUN = 10, 315 | HOBJ = 11, 316 | HARRAY = 12, 317 | HTYPE = 13, 318 | HREF = 14, 319 | HVIRTUAL= 15, 320 | HDYNOBJ = 16, 321 | HABSTRACT=17, 322 | HENUM = 18, 323 | HNULL = 19, 324 | HMETHOD = 20, 325 | HSTRUCT = 21, 326 | // --------- 327 | HLAST = 22, 328 | _H_FORCE_INT = 0x7FFFFFFF 329 | } hl_type_kind; 330 | 331 | typedef struct hl_type hl_type; 332 | typedef struct hl_runtime_obj hl_runtime_obj; 333 | typedef struct hl_alloc_block hl_alloc_block; 334 | typedef struct { hl_alloc_block *cur; } hl_alloc; 335 | typedef struct _hl_field_lookup hl_field_lookup; 336 | 337 | typedef struct { 338 | hl_alloc alloc; 339 | void **functions_ptrs; 340 | hl_type **functions_types; 341 | } hl_module_context; 342 | 343 | typedef struct { 344 | hl_type **args; 345 | hl_type *ret; 346 | int nargs; 347 | // storage for closure 348 | hl_type *parent; 349 | struct { 350 | hl_type_kind kind; 351 | void *p; 352 | } closure_type; 353 | struct { 354 | hl_type **args; 355 | hl_type *ret; 356 | int nargs; 357 | hl_type *parent; 358 | } closure; 359 | } hl_type_fun; 360 | 361 | typedef struct { 362 | const uchar *name; 363 | hl_type *t; 364 | int hashed_name; 365 | } hl_obj_field; 366 | 367 | typedef struct { 368 | const uchar *name; 369 | int findex; 370 | int pindex; 371 | int hashed_name; 372 | } hl_obj_proto; 373 | 374 | typedef struct { 375 | int nfields; 376 | int nproto; 377 | int nbindings; 378 | const uchar *name; 379 | hl_type *super; 380 | hl_obj_field *fields; 381 | hl_obj_proto *proto; 382 | int *bindings; 383 | void **global_value; 384 | hl_module_context *m; 385 | hl_runtime_obj *rt; 386 | } hl_type_obj; 387 | 388 | typedef struct { 389 | hl_obj_field *fields; 390 | int nfields; 391 | // runtime 392 | int dataSize; 393 | int *indexes; 394 | hl_field_lookup *lookup; 395 | } hl_type_virtual; 396 | 397 | typedef struct { 398 | const uchar *name; 399 | int nparams; 400 | hl_type **params; 401 | int size; 402 | bool hasptr; 403 | int *offsets; 404 | } hl_enum_construct; 405 | 406 | typedef struct { 407 | const uchar *name; 408 | int nconstructs; 409 | hl_enum_construct *constructs; 410 | void **global_value; 411 | } hl_type_enum; 412 | 413 | struct hl_type { 414 | hl_type_kind kind; 415 | union { 416 | const uchar *abs_name; 417 | hl_type_fun *fun; 418 | hl_type_obj *obj; 419 | hl_type_enum *tenum; 420 | hl_type_virtual *virt; 421 | hl_type *tparam; 422 | }; 423 | void **vobj_proto; 424 | unsigned int *mark_bits; 425 | }; 426 | 427 | C_FUNCTION_BEGIN 428 | 429 | HL_API int hl_type_size( hl_type *t ); 430 | #define hl_pad_size(size,t) ((t)->kind == HVOID ? 0 : ((-(size)) & (hl_type_size(t) - 1))) 431 | HL_API int hl_pad_struct( int size, hl_type *t ); 432 | 433 | HL_API hl_runtime_obj *hl_get_obj_rt( hl_type *ot ); 434 | HL_API hl_runtime_obj *hl_get_obj_proto( hl_type *ot ); 435 | HL_API void hl_flush_proto( hl_type *ot ); 436 | HL_API void hl_init_enum( hl_type *et, hl_module_context *m ); 437 | 438 | /* -------------------- VALUES ------------------------------ */ 439 | 440 | typedef unsigned char vbyte; 441 | 442 | typedef struct { 443 | hl_type *t; 444 | # ifndef HL_64 445 | int __pad; // force align on 16 bytes for double 446 | # endif 447 | union { 448 | bool b; 449 | unsigned char ui8; 450 | unsigned short ui16; 451 | int i; 452 | float f; 453 | double d; 454 | vbyte *bytes; 455 | void *ptr; 456 | int64 i64; 457 | } v; 458 | } vdynamic; 459 | 460 | typedef struct { 461 | hl_type *t; 462 | /* fields data */ 463 | } vobj; 464 | 465 | typedef struct _vvirtual vvirtual; 466 | struct _vvirtual { 467 | hl_type *t; 468 | vdynamic *value; 469 | vvirtual *next; 470 | }; 471 | 472 | #define hl_vfields(v) ((void**)(((vvirtual*)(v))+1)) 473 | 474 | typedef struct { 475 | hl_type *t; 476 | hl_type *at; 477 | int size; 478 | int __pad; // force align on 16 bytes for double 479 | } varray; 480 | 481 | typedef struct _vclosure { 482 | hl_type *t; 483 | void *fun; 484 | int hasValue; 485 | # ifdef HL_64 486 | int stackCount; 487 | # endif 488 | void *value; 489 | } vclosure; 490 | 491 | typedef struct { 492 | vclosure cl; 493 | vclosure *wrappedFun; 494 | } vclosure_wrapper; 495 | 496 | struct _hl_field_lookup { 497 | hl_type *t; 498 | int hashed_name; 499 | int field_index; // negative or zero : index in methods 500 | }; 501 | 502 | typedef struct { 503 | void *ptr; 504 | hl_type *closure; 505 | int fid; 506 | } hl_runtime_binding; 507 | 508 | struct hl_runtime_obj { 509 | hl_type *t; 510 | // absolute 511 | int nfields; 512 | int nproto; 513 | int size; 514 | int nmethods; 515 | int nbindings; 516 | bool hasPtr; 517 | void **methods; 518 | int *fields_indexes; 519 | hl_runtime_binding *bindings; 520 | hl_runtime_obj *parent; 521 | const uchar *(*toStringFun)( vdynamic *obj ); 522 | int (*compareFun)( vdynamic *a, vdynamic *b ); 523 | vdynamic *(*castFun)( vdynamic *obj, hl_type *t ); 524 | vdynamic *(*getFieldFun)( vdynamic *obj, int hfield ); 525 | // relative 526 | int nlookup; 527 | int ninterfaces; 528 | hl_field_lookup *lookup; 529 | int *interfaces; 530 | }; 531 | 532 | typedef struct { 533 | hl_type *t; 534 | hl_field_lookup *lookup; 535 | char *raw_data; 536 | void **values; 537 | int nfields; 538 | int raw_size; 539 | int nvalues; 540 | vvirtual *virtuals; 541 | } vdynobj; 542 | 543 | typedef struct _venum { 544 | hl_type *t; 545 | int index; 546 | } venum; 547 | 548 | HL_API hl_type hlt_void; 549 | HL_API hl_type hlt_i32; 550 | HL_API hl_type hlt_i64; 551 | HL_API hl_type hlt_f64; 552 | HL_API hl_type hlt_f32; 553 | HL_API hl_type hlt_dyn; 554 | HL_API hl_type hlt_array; 555 | HL_API hl_type hlt_bytes; 556 | HL_API hl_type hlt_dynobj; 557 | HL_API hl_type hlt_bool; 558 | HL_API hl_type hlt_abstract; 559 | 560 | HL_API double hl_nan( void ); 561 | HL_API bool hl_is_dynamic( hl_type *t ); 562 | #define hl_is_ptr(t) ((t)->kind >= HBYTES) 563 | HL_API bool hl_same_type( hl_type *a, hl_type *b ); 564 | HL_API bool hl_safe_cast( hl_type *t, hl_type *to ); 565 | 566 | #define hl_aptr(a,t) ((t*)(((varray*)(a))+1)) 567 | 568 | HL_API varray *hl_alloc_array( hl_type *t, int size ); 569 | HL_API vdynamic *hl_alloc_dynamic( hl_type *t ); 570 | HL_API vdynamic *hl_alloc_dynbool( bool b ); 571 | HL_API vdynamic *hl_alloc_obj( hl_type *t ); 572 | HL_API venum *hl_alloc_enum( hl_type *t, int index ); 573 | HL_API vvirtual *hl_alloc_virtual( hl_type *t ); 574 | HL_API vdynobj *hl_alloc_dynobj( void ); 575 | HL_API vbyte *hl_alloc_bytes( int size ); 576 | HL_API vbyte *hl_copy_bytes( const vbyte *byte, int size ); 577 | HL_API int hl_utf8_length( const vbyte *s, int pos ); 578 | HL_API int hl_from_utf8( uchar *out, int outLen, const char *str ); 579 | HL_API char *hl_to_utf8( const uchar *bytes ); 580 | HL_API uchar *hl_to_utf16( const char *str ); 581 | HL_API vdynamic *hl_virtual_make_value( vvirtual *v ); 582 | HL_API hl_obj_field *hl_obj_field_fetch( hl_type *t, int fid ); 583 | 584 | HL_API int hl_hash( vbyte *name ); 585 | HL_API int hl_hash_utf8( const char *str ); // no cache 586 | HL_API int hl_hash_gen( const uchar *name, bool cache_name ); 587 | HL_API vbyte *hl_field_name( int hash ); 588 | 589 | #define hl_error(msg, ...) hl_throw(hl_alloc_strbytes(USTR(msg), ## __VA_ARGS__)) 590 | 591 | HL_API vdynamic *hl_alloc_strbytes( const uchar *msg, ... ); 592 | HL_API void hl_assert( void ); 593 | HL_API HL_NO_RETURN( void hl_throw( vdynamic *v ) ); 594 | HL_API HL_NO_RETURN( void hl_rethrow( vdynamic *v ) ); 595 | HL_API HL_NO_RETURN( void hl_null_access( void ) ); 596 | HL_API void hl_setup_longjump( void *j ); 597 | HL_API void hl_setup_exception( void *resolve_symbol, void *capture_stack ); 598 | HL_API void hl_dump_stack( void ); 599 | HL_API varray *hl_exception_stack( void ); 600 | HL_API bool hl_detect_debugger( void ); 601 | 602 | HL_API vvirtual *hl_to_virtual( hl_type *vt, vdynamic *obj ); 603 | HL_API void hl_init_virtual( hl_type *vt, hl_module_context *ctx ); 604 | HL_API hl_field_lookup *hl_lookup_find( hl_field_lookup *l, int size, int hash ); 605 | HL_API hl_field_lookup *hl_lookup_insert( hl_field_lookup *l, int size, int hash, hl_type *t, int index ); 606 | 607 | HL_API int hl_dyn_geti( vdynamic *d, int hfield, hl_type *t ); 608 | HL_API void *hl_dyn_getp( vdynamic *d, int hfield, hl_type *t ); 609 | HL_API float hl_dyn_getf( vdynamic *d, int hfield ); 610 | HL_API double hl_dyn_getd( vdynamic *d, int hfield ); 611 | 612 | HL_API int hl_dyn_casti( void *data, hl_type *t, hl_type *to ); 613 | HL_API void *hl_dyn_castp( void *data, hl_type *t, hl_type *to ); 614 | HL_API float hl_dyn_castf( void *data, hl_type *t ); 615 | HL_API double hl_dyn_castd( void *data, hl_type *t ); 616 | 617 | #define hl_invalid_comparison 0xAABBCCDD 618 | HL_API int hl_dyn_compare( vdynamic *a, vdynamic *b ); 619 | HL_API vdynamic *hl_make_dyn( void *data, hl_type *t ); 620 | HL_API void hl_write_dyn( void *data, hl_type *t, vdynamic *v, bool is_tmp ); 621 | 622 | HL_API void hl_dyn_seti( vdynamic *d, int hfield, hl_type *t, int value ); 623 | HL_API void hl_dyn_setp( vdynamic *d, int hfield, hl_type *t, void *ptr ); 624 | HL_API void hl_dyn_setf( vdynamic *d, int hfield, float f ); 625 | HL_API void hl_dyn_setd( vdynamic *d, int hfield, double v ); 626 | 627 | typedef enum { 628 | OpAdd, 629 | OpSub, 630 | OpMul, 631 | OpMod, 632 | OpDiv, 633 | OpShl, 634 | OpShr, 635 | OpUShr, 636 | OpAnd, 637 | OpOr, 638 | OpXor, 639 | OpLast 640 | } DynOp; 641 | HL_API vdynamic *hl_dyn_op( int op, vdynamic *a, vdynamic *b ); 642 | 643 | HL_API vclosure *hl_alloc_closure_void( hl_type *t, void *fvalue ); 644 | HL_API vclosure *hl_alloc_closure_ptr( hl_type *fullt, void *fvalue, void *ptr ); 645 | HL_API vclosure *hl_make_fun_wrapper( vclosure *c, hl_type *to ); 646 | HL_API void *hl_wrapper_call( void *value, void **args, vdynamic *ret ); 647 | HL_API void *hl_dyn_call_obj( vdynamic *obj, hl_type *ft, int hfield, void **args, vdynamic *ret ); 648 | HL_API vdynamic *hl_dyn_call( vclosure *c, vdynamic **args, int nargs ); 649 | HL_API vdynamic *hl_dyn_call_safe( vclosure *c, vdynamic **args, int nargs, bool *isException ); 650 | 651 | /* 652 | These macros should be only used when the closure `cl` has been type checked beforehand 653 | so you are sure it's of the used typed. Otherwise use hl_dyn_call 654 | */ 655 | #define hl_call0(ret,cl) \ 656 | (cl->hasValue ? ((ret(*)(vdynamic*))cl->fun)(cl->value) : ((ret(*)())cl->fun)()) 657 | #define hl_call1(ret,cl,t,v) \ 658 | (cl->hasValue ? ((ret(*)(vdynamic*,t))cl->fun)(cl->value,v) : ((ret(*)(t))cl->fun)(v)) 659 | #define hl_call2(ret,cl,t1,v1,t2,v2) \ 660 | (cl->hasValue ? ((ret(*)(vdynamic*,t1,t2))cl->fun)(cl->value,v1,v2) : ((ret(*)(t1,t2))cl->fun)(v1,v2)) 661 | #define hl_call3(ret,cl,t1,v1,t2,v2,t3,v3) \ 662 | (cl->hasValue ? ((ret(*)(vdynamic*,t1,t2,t3))cl->fun)(cl->value,v1,v2,v3) : ((ret(*)(t1,t2,t3))cl->fun)(v1,v2,v3)) 663 | #define hl_call4(ret,cl,t1,v1,t2,v2,t3,v3,t4,v4) \ 664 | (cl->hasValue ? ((ret(*)(vdynamic*,t1,t2,t3,t4))cl->fun)(cl->value,v1,v2,v3,v4) : ((ret(*)(t1,t2,t3,t4))cl->fun)(v1,v2,v3,v4)) 665 | 666 | // ----------------------- THREADS -------------------------------------------------- 667 | 668 | struct _hl_thread; 669 | struct _hl_mutex; 670 | struct _hl_semaphore; 671 | struct _hl_condition; 672 | struct _hl_tls; 673 | typedef struct _hl_thread hl_thread; 674 | typedef struct _hl_mutex hl_mutex; 675 | typedef struct _hl_semaphore hl_semaphore; 676 | typedef struct _hl_condition hl_condition; 677 | typedef struct _hl_tls hl_tls; 678 | 679 | HL_API hl_thread *hl_thread_start( void *callback, void *param, bool withGC ); 680 | HL_API hl_thread *hl_thread_current( void ); 681 | HL_API void hl_thread_yield(void); 682 | HL_API void hl_register_thread( void *stack_top ); 683 | HL_API void hl_unregister_thread( void ); 684 | 685 | HL_API hl_mutex *hl_mutex_alloc( bool gc_thread ); 686 | HL_API void hl_mutex_acquire( hl_mutex *l ); 687 | HL_API bool hl_mutex_try_acquire( hl_mutex *l ); 688 | HL_API void hl_mutex_release( hl_mutex *l ); 689 | HL_API void hl_mutex_free( hl_mutex *l ); 690 | 691 | HL_API hl_semaphore *hl_semaphore_alloc(int value); 692 | HL_API void hl_semaphore_acquire(hl_semaphore *sem); 693 | HL_API bool hl_semaphore_try_acquire(hl_semaphore *sem, vdynamic *timeout); 694 | HL_API void hl_semaphore_release(hl_semaphore *sem); 695 | HL_API void hl_semaphore_free(hl_semaphore *sem); 696 | 697 | HL_API hl_condition *hl_condition_alloc(); 698 | HL_API void hl_condition_acquire(hl_condition *cond); 699 | HL_API bool hl_condition_try_acquire(hl_condition *cond); 700 | HL_API void hl_condition_release(hl_condition *cond); 701 | HL_API void hl_condition_wait(hl_condition *cond); 702 | HL_API bool hl_condition_timed_wait(hl_condition *cond, double timeout); 703 | HL_API void hl_condition_signal(hl_condition *cond); 704 | HL_API void hl_condition_broadcast(hl_condition *cond); 705 | HL_API void hl_condition_free(hl_condition *cond); 706 | 707 | HL_API hl_tls *hl_tls_alloc( bool gc_value ); 708 | HL_API void hl_tls_set( hl_tls *l, void *value ); 709 | HL_API void *hl_tls_get( hl_tls *l ); 710 | HL_API void hl_tls_free( hl_tls *l ); 711 | 712 | // ----------------------- ALLOC -------------------------------------------------- 713 | 714 | #define MEM_HAS_PTR(kind) (!((kind)&2)) 715 | #define MEM_KIND_DYNAMIC 0 716 | #define MEM_KIND_RAW 1 717 | #define MEM_KIND_NOPTR 2 718 | #define MEM_KIND_FINALIZER 3 719 | #define MEM_ALIGN_DOUBLE 128 720 | #define MEM_ZERO 256 721 | 722 | HL_API void *hl_gc_alloc_gen( hl_type *t, int size, int flags ); 723 | HL_API void hl_add_root( void *ptr ); 724 | HL_API void hl_remove_root( void *ptr ); 725 | HL_API void hl_gc_major( void ); 726 | HL_API bool hl_is_gc_ptr( void *ptr ); 727 | 728 | HL_API void hl_blocking( bool b ); 729 | HL_API bool hl_is_blocking( void ); 730 | 731 | typedef void (*hl_types_dump)( void (*)( void *, int) ); 732 | HL_API void hl_gc_set_dump_types( hl_types_dump tdump ); 733 | 734 | #define hl_gc_alloc_noptr(size) hl_gc_alloc_gen(&hlt_bytes,size,MEM_KIND_NOPTR) 735 | #define hl_gc_alloc(t,size) hl_gc_alloc_gen(t,size,MEM_KIND_DYNAMIC) 736 | #define hl_gc_alloc_raw(size) hl_gc_alloc_gen(&hlt_abstract,size,MEM_KIND_RAW) 737 | #define hl_gc_alloc_finalizer(size) hl_gc_alloc_gen(&hlt_abstract,size,MEM_KIND_FINALIZER) 738 | 739 | HL_API void hl_alloc_init( hl_alloc *a ); 740 | HL_API void *hl_malloc( hl_alloc *a, int size ); 741 | HL_API void *hl_zalloc( hl_alloc *a, int size ); 742 | HL_API void hl_free( hl_alloc *a ); 743 | 744 | HL_API void hl_global_init( void ); 745 | HL_API void hl_global_free( void ); 746 | 747 | HL_API void *hl_alloc_executable_memory( int size ); 748 | HL_API void hl_free_executable_memory( void *ptr, int size ); 749 | 750 | // ----------------------- BUFFER -------------------------------------------------- 751 | 752 | typedef struct hl_buffer hl_buffer; 753 | 754 | HL_API hl_buffer *hl_alloc_buffer( void ); 755 | HL_API void hl_buffer_val( hl_buffer *b, vdynamic *v ); 756 | HL_API void hl_buffer_char( hl_buffer *b, uchar c ); 757 | HL_API void hl_buffer_str( hl_buffer *b, const uchar *str ); 758 | HL_API void hl_buffer_cstr( hl_buffer *b, const char *str ); 759 | HL_API void hl_buffer_str_sub( hl_buffer *b, const uchar *str, int len ); 760 | HL_API int hl_buffer_length( hl_buffer *b ); 761 | HL_API uchar *hl_buffer_content( hl_buffer *b, int *len ); 762 | HL_API uchar *hl_to_string( vdynamic *v ); 763 | HL_API const uchar *hl_type_str( hl_type *t ); 764 | HL_API void hl_throw_buffer( hl_buffer *b ); 765 | 766 | // ----------------------- FFI ------------------------------------------------------ 767 | 768 | // match GNU C++ mangling 769 | #define TYPE_STR "vcsilfdbBDPOATR??X?N?S" 770 | 771 | #undef _VOID 772 | #define _NO_ARG 773 | #define _VOID "v" 774 | #define _I8 "c" 775 | #define _I16 "s" 776 | #define _I32 "i" 777 | #define _I64 "l" 778 | #define _F32 "f" 779 | #define _F64 "d" 780 | #define _BOOL "b" 781 | #define _BYTES "B" 782 | #define _DYN "D" 783 | #define _FUN(t, args) "P" args "_" t 784 | #define _OBJ(fields) "O" fields "_" 785 | #define _ARR "A" 786 | #define _TYPE "T" 787 | #define _REF(t) "R" t 788 | #define _ABSTRACT(name) "X" #name "_" 789 | #undef _NULL 790 | #define _NULL(t) "N" t 791 | #define _STRUCT "S" 792 | 793 | #undef _STRING 794 | #define _STRING _OBJ(_BYTES _I32) 795 | 796 | typedef struct { 797 | hl_type *t; 798 | uchar *bytes; 799 | int length; 800 | } vstring; 801 | 802 | #define DEFINE_PRIM(t,name,args) DEFINE_PRIM_WITH_NAME(t,name,args,name) 803 | #define _DEFINE_PRIM_WITH_NAME(t,name,args,realName) C_FUNCTION_BEGIN EXPORT void *hlp_##realName( const char **sign ) { *sign = _FUN(t,args); return (void*)(&HL_NAME(name)); } C_FUNCTION_END 804 | 805 | #if !defined(HL_NAME) 806 | # define HL_NAME(p) p 807 | # ifdef LIBHL_EXPORTS 808 | # define HL_PRIM EXPORT 809 | # undef DEFINE_PRIM 810 | # define DEFINE_PRIM(t,name,args) _DEFINE_PRIM_WITH_NAME(t,hl_##name,args,name) 811 | # define DEFINE_PRIM_WITH_NAME _DEFINE_PRIM_WITH_NAME 812 | # else 813 | # define HL_PRIM 814 | # define DEFINE_PRIM_WITH_NAME(t,name,args,realName) 815 | # endif 816 | #elif defined(LIBHL_STATIC) 817 | # ifdef __cplusplus 818 | # define HL_PRIM extern "C" 819 | # else 820 | # define HL_PRIM 821 | # endif 822 | #define DEFINE_PRIM_WITH_NAME(t,name,args,realName) 823 | #else 824 | # ifdef __cplusplus 825 | # define HL_PRIM extern "C" EXPORT 826 | # else 827 | # define HL_PRIM EXPORT 828 | # endif 829 | # define DEFINE_PRIM_WITH_NAME _DEFINE_PRIM_WITH_NAME 830 | #endif 831 | 832 | #if defined(HL_GCC) && !defined(HL_CONSOLE) 833 | # ifdef HL_CLANG 834 | # define HL_NO_OPT __attribute__ ((optnone)) 835 | # else 836 | # define HL_NO_OPT __attribute__((optimize("-O0"))) 837 | # endif 838 | #else 839 | # define HL_NO_OPT 840 | #endif 841 | 842 | // -------------- EXTRA ------------------------------------ 843 | 844 | #define hl_fatal(msg) hl_fatal_error(msg,__FILE__,__LINE__) 845 | #define hl_fatal1(msg,p0) hl_fatal_fmt(__FILE__,__LINE__,msg,p0) 846 | #define hl_fatal2(msg,p0,p1) hl_fatal_fmt(__FILE__,__LINE__,msg,p0,p1) 847 | #define hl_fatal3(msg,p0,p1,p2) hl_fatal_fmt(__FILE__,__LINE__,msg,p0,p1,p2) 848 | #define hl_fatal4(msg,p0,p1,p2,p3) hl_fatal_fmt(__FILE__,__LINE__,msg,p0,p1,p2,p3) 849 | HL_API void *hl_fatal_error( const char *msg, const char *file, int line ); 850 | HL_API void hl_fatal_fmt( const char *file, int line, const char *fmt, ...); 851 | HL_API void hl_sys_init(void **args, int nargs, void *hlfile); 852 | HL_API void hl_setup_callbacks(void *sc, void *gw); 853 | HL_API void hl_setup_callbacks2(void *sc, void *gw, int flags); 854 | HL_API void hl_setup_reload_check( void *freload, void *param ); 855 | 856 | #include 857 | typedef struct _hl_trap_ctx hl_trap_ctx; 858 | struct _hl_trap_ctx { 859 | jmp_buf buf; 860 | hl_trap_ctx *prev; 861 | vdynamic *tcheck; 862 | }; 863 | #define hl_trap(ctx,r,label) { hl_thread_info *__tinf = hl_get_thread(); ctx.tcheck = NULL; ctx.prev = __tinf->trap_current; __tinf->trap_current = &ctx; if( setjmp(ctx.buf) ) { r = __tinf->exc_value; goto label; } } 864 | #define hl_endtrap(ctx) hl_get_thread()->trap_current = ctx.prev 865 | 866 | #define HL_EXC_MAX_STACK 0x100 867 | #define HL_EXC_RETHROW 1 868 | #define HL_EXC_CATCH_ALL 2 869 | #define HL_EXC_IS_THROW 4 870 | #define HL_THREAD_INVISIBLE 16 871 | #define HL_THREAD_PROFILER_PAUSED 32 872 | #define HL_TREAD_TRACK_SHIFT 16 873 | 874 | #define HL_TRACK_ALLOC 1 875 | #define HL_TRACK_CAST 2 876 | #define HL_TRACK_DYNFIELD 4 877 | #define HL_TRACK_DYNCALL 8 878 | #define HL_TRACK_MASK (HL_TRACK_ALLOC | HL_TRACK_CAST | HL_TRACK_DYNFIELD | HL_TRACK_DYNCALL) 879 | 880 | #define HL_MAX_EXTRA_STACK 64 881 | 882 | typedef struct { 883 | int thread_id; 884 | // gc vars 885 | volatile int gc_blocking; 886 | void *stack_top; 887 | void *stack_cur; 888 | // exception handling 889 | hl_trap_ctx *trap_current; 890 | hl_trap_ctx *trap_uncaught; 891 | vclosure *exc_handler; 892 | vdynamic *exc_value; 893 | int flags; 894 | int exc_stack_count; 895 | // extra 896 | jmp_buf gc_regs; 897 | void *exc_stack_trace[HL_EXC_MAX_STACK]; 898 | void *extra_stack_data[HL_MAX_EXTRA_STACK]; 899 | int extra_stack_size; 900 | } hl_thread_info; 901 | 902 | HL_API hl_thread_info *hl_get_thread(); 903 | 904 | #ifdef HL_TRACK_ENABLE 905 | 906 | typedef struct { 907 | int flags; 908 | void (*on_alloc)(hl_type *,int,int,void*); 909 | void (*on_cast)(hl_type *, hl_type*); 910 | void (*on_dynfield)( vdynamic *, int ); 911 | void (*on_dyncall)( vdynamic *, int ); 912 | } hl_track_info; 913 | 914 | #define hl_is_tracking(flag) ((hl_track.flags&(flag)) && (hl_get_thread()->flags & (flag<; 19 | var forReadWrite : Bool; 20 | } 21 | 22 | @:publicFields @:structInit 23 | class StackRawInfo { 24 | var fidx : Int; 25 | var fpos : Int; 26 | var codePos : Pointer; 27 | var ebp : Null; 28 | } 29 | 30 | @:publicFields @:structInit 31 | class StackInfo { 32 | var file : String; 33 | var line : Int; 34 | var ebp : Pointer; 35 | var context : Null<{ obj : format.hl.Data.ObjPrototype, field : String }>; 36 | } 37 | 38 | class Debugger { 39 | 40 | static inline var INT3 = 0xCC; 41 | static var HW_REGS : Array = [Dr0, Dr1, Dr2, Dr3]; 42 | public static var DEBUG = false; 43 | public static var IGNORED_ROOTS = [ 44 | "hl", 45 | "sys", 46 | "haxe", 47 | "Date", 48 | "EReg", 49 | "Math", 50 | "Reflect", 51 | "Std", 52 | "String", 53 | "StringBuf", 54 | "Sys", 55 | "Type", 56 | "Xml", 57 | "IntIterator", 58 | "ArrayObj" // special 59 | ]; 60 | 61 | var sock : #if hxnodejs js.node.net.Socket #else sys.net.Socket #end; 62 | 63 | var api : Api; 64 | var module : Module; 65 | var jit : JitInfo; 66 | var processExit : Bool; 67 | var ignoredRoots : Map; 68 | 69 | var breakPoints : Array<{ fid : Int, pos : Int, codePos : Pointer, oldByte : Int, condition : String }>; 70 | var nextStep(default,set): Pointer = Pointer.make(0,0); 71 | var currentStack : Array; 72 | var watches : Array; 73 | var threads : Map, ?exceptionTrap: Pointer, name : String }>; 74 | var afterStep = false; 75 | 76 | public var is64(get, never) : Bool; 77 | 78 | public var eval : Eval; 79 | public var currentStackFrame : Int; 80 | public var breakOnThrow(default, set) : Bool; 81 | public var stackFrameCount(get, never) : Int; 82 | public var mainThread(default, null) : Int = 0; 83 | public var stoppedThread(default,set) : Null; 84 | public var currentThread(default,set) : Null; 85 | 86 | public var customTimeout : Null; 87 | 88 | public var watchBreak : Address; // set if breakpoint occur on watch expression 89 | 90 | public function new() { 91 | breakPoints = []; 92 | watches = []; 93 | } 94 | 95 | function set_nextStep(v:Pointer) { 96 | if( DEBUG ) trace("NEXT STEP "+jit.codePtrToString(v)); 97 | return nextStep = v; 98 | } 99 | 100 | function set_currentThread(v) { 101 | currentThread = v; 102 | eval.currentThread = v; 103 | return v; 104 | } 105 | 106 | function set_stoppedThread(v) { 107 | return stoppedThread = currentThread = v; 108 | } 109 | 110 | function get_is64() { 111 | return jit.is64; 112 | } 113 | 114 | public function loadModule( content : haxe.io.Bytes ) { 115 | module = new Module(); 116 | module.load(content); 117 | } 118 | 119 | public function connectTries( host : String, port : Int, timeout : Float, onResult : Bool -> Void ) { 120 | if( timeout <= 0 ) { 121 | onResult(false); 122 | return; 123 | } 124 | var ts = Sys.time(); 125 | connect(host,port,function(b) { 126 | if( b ) { 127 | onResult(true); 128 | return; 129 | } 130 | haxe.Timer.delay(function() { 131 | connectTries(host, port, timeout - (Sys.time()-ts), onResult); 132 | },20); 133 | }); 134 | } 135 | 136 | public function connect( host : String, port : Int, onResult : Bool -> Void ) { 137 | function done(input) { 138 | jit = new JitInfo(); 139 | if( !jit.read(input, module) ) { 140 | close(); 141 | onResult(false); 142 | return; 143 | } 144 | module.init(jit.align); 145 | onResult(true); 146 | } 147 | 148 | #if hxnodejs 149 | var inputData = new haxe.io.BytesBuffer(); 150 | sock = new js.node.net.Socket(); 151 | sock.on("data", function(buf:js.node.Buffer) { 152 | inputData.add(haxe.io.Bytes.ofData(buf.buffer)); 153 | try { 154 | done(new haxe.io.BytesInput(inputData.getBytes())); 155 | } catch( e : haxe.io.Eof ) { 156 | // wait for more data 157 | } 158 | }); 159 | js.node.Dns.lookup(host, {family: 4}, function(err, address:String, family) { 160 | if( err != null ) { 161 | onResult(false); 162 | return; 163 | } 164 | sock.on("error", function(err) { 165 | if( onResult != null ) { 166 | close(); 167 | onResult(false); 168 | return; 169 | } 170 | }); 171 | sock.connect(port, address, function() { 172 | // wait data 173 | }); 174 | }); 175 | #else 176 | sock = new sys.net.Socket(); 177 | try { 178 | sock.connect(new sys.net.Host(host), port); 179 | } catch( e : Dynamic ) { 180 | sock.close(); 181 | Sys.sleep(0.1); 182 | onResult(false); 183 | return; 184 | } 185 | done(sock.input); 186 | #end 187 | } 188 | 189 | public function init( api : Api ) { 190 | this.api = api; 191 | eval = new Eval(module, api, jit); 192 | eval.resumeDebug = evalResumeDebug; 193 | eval.setSingleStep = singleStep; 194 | if( !api.start() ) 195 | return false; 196 | wait(); // wait first break 197 | return true; 198 | } 199 | 200 | function evalResumeDebug() { 201 | resume(); 202 | wait(false, true); 203 | } 204 | 205 | function close() { 206 | if( sock != null ) { 207 | #if hxnodejs sock.destroy() #else sock.close() #end; 208 | sock = null; 209 | } 210 | } 211 | 212 | public function run() { 213 | afterStep = false; 214 | // closing the socket will unlock waiting thread 215 | close(); 216 | if( stoppedThread != null ) 217 | resume(); 218 | return wait(); 219 | } 220 | 221 | public function getThreads() { 222 | var tl = [for( t in threads ) t.id]; 223 | tl.sort(Reflect.compare); 224 | return tl; 225 | } 226 | 227 | public function setCurrentThread(tid) { 228 | currentThread = tid; 229 | prepareStack(); 230 | } 231 | 232 | public function pause() { 233 | if( !api.breakpoint() ) 234 | throw "Failed to break process"; 235 | var r = wait(false, false, true); 236 | // if we have stopped on a not HL thread, let's switch on main thread 237 | var found = false; 238 | for( t in threads ) 239 | if( t.id == stoppedThread ) { 240 | found = true; 241 | break; 242 | } 243 | if( !found ) { 244 | currentThread = mainThread; 245 | prepareStack(); 246 | } 247 | return r; 248 | } 249 | 250 | function singleStep(tid,set=true) { 251 | var r = getReg(tid, EFlags).toInt(); 252 | if( set ) r |= 256 else r &= ~256; 253 | if( DEBUG ) trace("SINGLESTEP "+set); 254 | setReg(tid, EFlags, hld.Pointer.make(r,0)); 255 | } 256 | 257 | public function getException() { 258 | var t = threads.get(currentThread); 259 | if( t == null ) 260 | return null; 261 | var exc = t.exception; 262 | if( exc.isNull() ) 263 | return null; 264 | return eval.readVal(exc, HDyn); 265 | } 266 | 267 | public function getVMExceptionStack() { 268 | var t = threads.get(currentThread); 269 | if( t == null ) 270 | return null; 271 | var stack = t.exceptionStack; 272 | if( stack == null ) 273 | return null; 274 | return stack.map(e -> stackInfo(e)); 275 | } 276 | 277 | public function hasStack() { 278 | return currentStack.length > 0; 279 | } 280 | 281 | public function getCurrentVars( args : Bool ) { 282 | var s = currentStack[currentStackFrame]; 283 | if( s == null ) return []; 284 | var g = module.getGraph(s.fidx); 285 | if( args ) 286 | return g.getArgs(); 287 | var locals = g.getLocals(s.fpos); 288 | if( afterStep && currentStackFrame == 0 && g.getReturnReg(s.fpos) != null ) 289 | locals.push("$ret"); 290 | return locals; 291 | } 292 | 293 | public function getCurrentClass() { 294 | var s = currentStack[currentStackFrame]; 295 | var ctx = module.getMethodContext(s.fidx); 296 | if( ctx == null ) 297 | return null; 298 | var name = ctx.obj.name; 299 | return name.split("$").join(""); 300 | } 301 | 302 | public function getClassStatics( cl : String ) { 303 | var v = getValue(cl, true); 304 | if( v == null ) 305 | throw "No such class "+cl; 306 | var fields = eval.getFields(v); 307 | fields.remove("__name__"); 308 | fields.remove("__type__"); 309 | fields.remove("__meta__"); 310 | fields.remove("__implementedBy__"); 311 | fields.remove("__constructor__"); 312 | return fields; 313 | } 314 | 315 | function wait( onSingleStep = false, onEvalCall = false, onPause = false ) : Api.WaitResult { 316 | var cmd = null; 317 | var condition : String = null; 318 | watchBreak = null; 319 | while( true ) { 320 | cmd = api.wait(customTimeout == null ? 1000 : Math.ceil(customTimeout * 1000)); 321 | 322 | if( cmd.r == Breakpoint && !onEvalCall && (jit.isCodePtr(nextStep) || onSingleStep) ) { 323 | // On Linux, singlestep is not reset 324 | cmd.r = SingleStep; 325 | singleStep(cmd.tid,false); 326 | } 327 | 328 | if( DEBUG ) switch(cmd.r) { 329 | case Error: 330 | trace("**** ERROR ****"); 331 | case Breakpoint: 332 | trace("BREAK"); 333 | case SingleStep: 334 | trace("STEP"); 335 | case Exit: 336 | trace("EXIT"); 337 | case Handled, Timeout: 338 | default: 339 | trace(cmd.r); 340 | } 341 | 342 | var tid = cmd.tid; 343 | switch( cmd.r ) { 344 | case Timeout, Handled: 345 | 346 | if( customTimeout != null ) 347 | return cmd.r; 348 | 349 | case Breakpoint: 350 | var codePos = getCodePos(tid).offset(-1); 351 | for( b in breakPoints ) { 352 | if( b.codePos == codePos ) { 353 | condition = b.condition; 354 | // restore code 355 | setAsm(codePos, b.oldByte); 356 | // move backward 357 | setReg(tid, Eip, getReg(tid, Eip).offset(-1)); 358 | singleStep(tid); 359 | nextStep = codePos; 360 | break; 361 | } 362 | } 363 | break; 364 | case SingleStep: 365 | // restore our breakpoint 366 | if( jit.isCodePtr(nextStep) ) { 367 | setAsm(nextStep, INT3); 368 | nextStep = Pointer.make(0, 0); 369 | } else if( watches.length > 0 ) { 370 | 371 | // check if we have a break on a watchpoint 372 | var dr6 = api.readRegister(tid, Dr6); 373 | var watchBits = dr6.toInt() & 15; 374 | if( watchBits != 0 ) { 375 | for( w in watches ) 376 | for( r in w.regs ) 377 | if( watchBits & (1 << HW_REGS.indexOf(r.r)) != 0 ) { 378 | watchBreak = w.addr; 379 | break; 380 | } 381 | api.writeRegister(tid, Dr6, Pointer.make(0, 0)); 382 | if( watchBreak != null ) { 383 | cmd.r = Watchbreak; 384 | break; 385 | } 386 | } 387 | 388 | } 389 | stoppedThread = tid; 390 | if( onSingleStep ) 391 | return SingleStep; 392 | resume(); 393 | case Exit: 394 | processExit = true; 395 | break; 396 | case Error, Watchbreak, StackOverflow: 397 | break; 398 | } 399 | } 400 | stoppedThread = cmd.tid; 401 | 402 | // Do not overwrite stack on evalCall 403 | if( onEvalCall ) 404 | return cmd.r; 405 | 406 | // in thread-disabled we don't know the main thread id in HL: 407 | // first stop is on a special thread in windows 408 | // wait for second stop with is user-specific 409 | if( jit.oldThreadInfos != null ) 410 | mainThread = jit.oldThreadInfos.id; 411 | else if( mainThread == 0 ) 412 | mainThread = -1; 413 | else if( mainThread == -1 ) 414 | mainThread = stoppedThread; 415 | 416 | readThreads(); 417 | prepareStack(cmd.r == Watchbreak); 418 | eval.onBeforeBreak(); 419 | 420 | // if breakpoint has a condition, try to evaluate and do not actually break on false 421 | if( !onSingleStep && !onEvalCall && !onPause && condition != null ) { 422 | try { 423 | var value = getValue(condition); 424 | if( value != null ) { 425 | switch( value.v ) { 426 | case VBool( b ) if( !b ): return Handled; 427 | default: 428 | } 429 | } 430 | } catch( e : Dynamic ) { 431 | trace("Can't evaluate condition (" + condition + ") for breakpoint: " + e); 432 | } 433 | } 434 | return cmd.r; 435 | } 436 | 437 | public function getThreadName( id : Int, ?opt ) { 438 | var t = threads.get(id); 439 | return t == null || t.name == null ? (opt == null ? "Thread "+id : opt) : t.name+":"+id; 440 | } 441 | 442 | function readThreads() { 443 | var old = jit.oldThreadInfos; 444 | threads = new Map(); 445 | if( old != null ) { 446 | threads.set(old.id, { id : old.id, stackTop : old.stackTop, exception : eval.readPointer(old.debugExc), name : "Main" }); 447 | return; 448 | } 449 | var count = eval.readI32(jit.threads); 450 | var tinfos = eval.readPointer(jit.threads.offset(8)); 451 | var flagsPos = jit.align.ptr * 6 + 8; 452 | var excPos = jit.align.ptr * 5 + 8; 453 | var excTrapPos = jit.align.ptr * 2 + 8; 454 | var excStackCountPos = flagsPos + 4; 455 | var excStackPos = flagsPos + 8 + 256 + (jit.hlVersion >= 1.13 ? 128 : 0); 456 | var namePos = jit.hlVersion >= 1.13 ? flagsPos + 8 : -1; 457 | for( i in 0...count ) { 458 | var tinf = eval.readPointer(tinfos.offset(jit.align.ptr * i)); 459 | var tid = eval.readI32(tinf); 460 | var flags = eval.readI32(tinf.offset(flagsPos)); 461 | if( flags & 16 != 0 ) continue; // invisible 462 | if( tid == 0 ) 463 | tid = mainThread; 464 | else if( mainThread <= 0 ) 465 | mainThread = tid; 466 | var name = null; 467 | if( namePos >= 0 ) { 468 | var tname = @:privateAccess eval.readMem(tinf.offset(namePos), 128).readStringUTF8(); 469 | if( tname != "" ) 470 | name = tname; 471 | } 472 | var trapCtx = eval.readPointer(tinf.offset(excTrapPos)); 473 | var t = { 474 | id : tid, 475 | stackTop : eval.readPointer(tinf.offset(8)), 476 | exception : flags & 4 == 0 ? null : tinf.offset(excPos), 477 | exceptionStack : flags & 1 == 0 ? null : readVMExceptionStack(tinf.offset(excStackPos), eval.readI32(tinf.offset(excStackCountPos))), 478 | exceptionTrap : trapCtx.isNull() ? null : new Pointer(eval.readPointer(trapCtx.offset(10 * 8))), 479 | name : name, 480 | }; 481 | threads.set(tid, t); 482 | } 483 | if( !threads.exists(currentThread) ) 484 | threads.set(currentThread,{ id : currentThread, stackTop: null, exception: null, name : null }); 485 | } 486 | 487 | function readVMExceptionStack(base : Pointer, count : Int) : Array { 488 | var stack = []; 489 | if( count <= 0 || count >= 256 || base.isNull() ) 490 | return stack; 491 | for( i in 0...count ) { 492 | var codePtr = eval.readPointer(base.offset(i * jit.align.ptr)); 493 | var e = jit.resolveAsmPos(codePtr); 494 | if( e != null ) 495 | stack.push(e); 496 | } 497 | return [for( s in stack ) if( module.isValid(s.fidx, s.fpos) ) s]; 498 | } 499 | 500 | function prepareStack( isWatchbreak=false ) { 501 | currentStackFrame = 0; 502 | currentStack = makeStack(currentThread, isWatchbreak); 503 | } 504 | 505 | function skipFunction( fidx : Int ) { 506 | var ctx = module.getMethodContext(fidx); 507 | var name = ctx == null ? new haxe.io.Path(module.resolveSymbol(fidx, 0).file).file : ctx.obj.name.split(".")[0]; 508 | if( name.charCodeAt(0) == "$".code ) name = name.substr(1); 509 | if( ignoredRoots == null ) { 510 | ignoredRoots = new Map(); 511 | for( r in IGNORED_ROOTS ) 512 | ignoredRoots.set(r,true); 513 | } 514 | return ignoredRoots.exists(name); 515 | } 516 | 517 | public function step( mode : StepMode ) : Api.WaitResult { 518 | var tid = currentThread; 519 | var s = currentStack[0]; 520 | var depth = currentStack.length; 521 | var onException = getException() != null; 522 | 523 | if( s == null || onException ) { 524 | if( DEBUG ) trace("Step not supported, continue."); 525 | resume(); 526 | return wait(); 527 | } 528 | 529 | var orig = module.resolveSymbol(s.fidx, s.fpos); 530 | var graph = module.getGraph(s.fidx); 531 | var marked = new Map(); 532 | var currentCodePos = getCodePos(tid); 533 | var onBreakPoint = false; 534 | var immediateProcess = false; 535 | 536 | for( b in breakPoints ) 537 | if( b.fid == s.fidx ) { 538 | if( b.pos == s.fpos ) { 539 | onBreakPoint = true; 540 | } else 541 | marked.set(b.pos, null); 542 | } 543 | 544 | // Add trap breakpoint if current trap is not in current function 545 | var trap = threads.get(tid).exceptionTrap; 546 | if( trap != null ) { 547 | var e = jit.resolveAsmPos(trap); 548 | if( e != null && e.fidx != s.fidx ) { 549 | var old = getAsm(trap); 550 | var bp = { fid : -4, pos : e.fpos, codePos : trap, oldByte : old, condition : null }; 551 | breakPoints.push(bp); 552 | marked.set(-1, bp); 553 | setAsm(trap, INT3); 554 | } 555 | } 556 | 557 | function visitRec( pos : Int ) { 558 | if( marked.exists(pos) ) 559 | return; 560 | var l = module.resolveSymbol(s.fidx, pos); 561 | var c = graph.control(pos); 562 | var lineChange = mode != Out && (l.file != orig.file || l.line != orig.line) && !c.match(CCatch | CJAlways(_)); 563 | switch( c ) { 564 | case CCall(f) if( f >= 0 && mode == Into ): 565 | // skip calls to std library 566 | var fid = @:privateAccess module.functionsIndexes.get(f); 567 | if( fid == null || fid >= module.code.functions.length /* native */ || skipFunction(fid) ) 568 | c = CNo; 569 | default: 570 | } 571 | if( lineChange || c == CRet || (mode == Into && c.match(CCall(_))) ) { 572 | var codePos = jit.getCodePos(s.fidx, pos); 573 | var old = getAsm(codePos); 574 | var bp = { fid : lineChange ? -1 : (c == CRet ? -2 : -3), pos : pos, codePos : codePos, oldByte : old, condition : null }; 575 | breakPoints.push(bp); 576 | marked.set(pos, bp); 577 | if( codePos == currentCodePos && onBreakPoint ) { 578 | immediateProcess = true; 579 | bp.oldByte = -1; 580 | } else 581 | setAsm(codePos, INT3); 582 | // if we are on same op but after the call (after returning from a finish) 583 | if( c.match(CCall(_)) && codePos < currentCodePos ) 584 | visitRec(pos+1); 585 | return; 586 | } 587 | if( !c.match(CNo | CCall(_)) ) 588 | marked.set(pos, null); 589 | for( p in graph.getNextPos(pos) ) { 590 | visitRec(p); 591 | } 592 | } 593 | visitRec(s.fpos); 594 | function cleanup() { 595 | for( bp in marked ) 596 | if( bp != null ) { 597 | if( bp.oldByte == -1 ) 598 | breakPoints.remove(bp); 599 | else 600 | removeBP(bp); 601 | } 602 | } 603 | if( !immediateProcess ) { 604 | while( true ) { 605 | resume(); 606 | var r = wait(); 607 | if( r != Exit && currentStack.length == 0 ) 608 | r = wait(); 609 | if( (r != Breakpoint && r != SingleStep) || currentThread != tid || currentStack.length == 0 || currentStack[0].fidx != s.fidx ) { 610 | cleanup(); 611 | return r; 612 | } 613 | // fix recursive methods that are breaking on the inner function 614 | if( (mode == Out || mode == Next) && currentStack.length > depth ) { 615 | var isRecursive = false; 616 | for( b in breakPoints ) 617 | if( (b.fid == -2 || b.fid == -1) && nextStep == b.codePos ) { 618 | isRecursive = true; 619 | break; 620 | } 621 | if( isRecursive ) { 622 | if( DEBUG ) trace("RECURSIVE"); 623 | continue; 624 | } 625 | } 626 | break; 627 | } 628 | } 629 | // execute until the end of Call/Ret if we stopped on it ! 630 | for( b in breakPoints ) { 631 | if( nextStep != b.codePos || b.fid >= -1 ) continue; 632 | var isRet = b.fid == -2; 633 | while( true ) { 634 | var eip = getReg(tid, Eip); 635 | var op = api.readByte(eip, 0); 636 | if( op == 0x48 ) 637 | op = api.readByte(eip, 1); 638 | singleStep(tid); 639 | resume(); 640 | var r = wait(true); 641 | if( r != SingleStep || currentThread != tid ) 642 | break; 643 | var st = makeStack(tid,false,1)[0]; 644 | if( isRet ) { 645 | if( op == 0xC3 ) { 646 | if( st == null ) { 647 | // ret on final main() ? - run till exit 648 | prepareStack(); 649 | if( currentStack.length == 0 ) { 650 | resume(); 651 | return wait(); 652 | } 653 | } 654 | break; 655 | } 656 | } else { 657 | // call : wait we changed line ! 658 | if( st != null && (st.fidx != s.fidx || st.fpos != b.pos) ) break; 659 | } 660 | } 661 | // in case we singleStepped ! 662 | prepareStack(); 663 | break; 664 | } 665 | cleanup(); 666 | afterStep = true; 667 | return Breakpoint; 668 | } 669 | 670 | function makeStack( tid, isWatchbreak : Bool, max = 0 ) { 671 | var stack = []; 672 | var tinf = threads.get(tid); 673 | if( tinf == null || tinf.stackTop == null ) 674 | return stack; 675 | var esp = getReg(tid, Esp); 676 | var ebp = getReg(tid, Ebp); 677 | var size = tinf.stackTop.sub(esp) + jit.align.ptr; 678 | if( size < 0 ) size = 0; 679 | var mem = readMem(esp.offset(-jit.align.ptr), size); 680 | 681 | var eip = getReg(tid, Eip); 682 | var asmPos = eip; 683 | if( isWatchbreak ) 684 | asmPos = asmPos.offset(-1); 685 | var e = jit.resolveAsmPos(asmPos); 686 | var inProlog = false; 687 | var exc = getException(); 688 | var isExcCantCast = false; 689 | if( exc != null ) { 690 | switch( exc.v ){ 691 | case VString(v,_): 692 | if( StringTools.startsWith(v, "Can't cast ") ) 693 | isExcCantCast = true; 694 | default: 695 | } 696 | } 697 | 698 | //trace(eip,"0x"+api.readByte(eip, 0), e); 699 | 700 | if( e != null && !module.isValid(e.fidx,e.fpos) ) 701 | e = null; 702 | 703 | // if we are on ret, our EBP is wrong, so let's ignore this stack part 704 | if( e != null ) { 705 | var op = api.readByte(eip, 0); 706 | if( op == 0x48 && jit.is64 ) 707 | op = api.readByte(eip, 1); 708 | if( op == 0xC3 ) // RET 709 | e = null; 710 | } 711 | 712 | if( e != null ) { 713 | if( e.fpos < 0 && jit.is64) { 714 | // we can't consider being in a function while we are in the prolog 715 | // because our regs args have not yet been stored on stack 716 | e = null; 717 | } else if( e.fpos < 0 ) { 718 | // we are in function prolog 719 | var delta = jit.getFunctionPos(e.fidx).sub(asmPos); 720 | e.fpos = 0; 721 | if( delta == 0 ) 722 | e.ebp = esp.offset(-jit.align.ptr); // not yet pushed ebp 723 | else 724 | e.ebp = esp; 725 | inProlog = true; 726 | } else 727 | e.ebp = ebp; 728 | if( e != null ) 729 | stack.push(e); 730 | } 731 | 732 | // when requiring only top level stack, do not look further if we are in a C function 733 | // because we need to step so we don't want false positive 734 | if( max == 1 ) return stack; 735 | 736 | // similar to module/module_capture_stack 737 | if( is64 ) { 738 | // on windows x64, we can't guarantee a stack pointer for our native funs... 739 | var skipFirstCheck = (e == null && jit.isWinCall); 740 | for( i in 0...(size >> 3)-1 ) { 741 | var val = mem.getPointer(i << 3, jit.align); 742 | if( (val > esp && val < tinf.stackTop) || (inProlog && i == 0) || skipFirstCheck ) { 743 | var codePtr = skipFirstCheck ? val : mem.getPointer((i + 1) << 3, jit.align); 744 | var e = jit.resolveAsmPos(codePtr); 745 | if( e != null && e.fpos >= 0 ) { 746 | if( skipFirstCheck ) { 747 | e.ebp = ebp; 748 | // this ebp might not be good, so let's look for 749 | // the first potential ebp backup starting after our esi 750 | var validEsp = esp.offset(i << 3); 751 | if( e.ebp < validEsp || e.ebp > tinf.stackTop ) { 752 | var k = i - 1; 753 | if( isExcCantCast && is64 && jit.isWinCall ) { 754 | // Only do this for can't cast, as Null access .xxx has RSP+10h valid but is wrong 755 | // look first at saved RBP at prev RSP+10h 756 | var val2 = mem.getPointer((i + 2) << 3, jit.align); // Can't cast xxx to i32 757 | var val4 = mem.getPointer((i + 4) << 3, jit.align); // Can't cast xxx to obj (e.g String) 758 | var val = null; 759 | if( val2 > validEsp && val2 < tinf.stackTop ) val = val2; 760 | if( val4 > validEsp && val4 < tinf.stackTop ) val = val4; 761 | if( val != null ) { 762 | e.ebp = val; 763 | k = -1; 764 | } 765 | } 766 | var first = true; 767 | while( k > 0 ) { 768 | var val = mem.getPointer((k--) << 3, jit.align); 769 | if( val > validEsp && val < tinf.stackTop ) { 770 | var code = readMem(val.offset(jit.align.ptr),jit.align.ptr).getPointer(0, jit.align); 771 | if( !jit.isCodePtr(code) ) continue; 772 | if( first || val < e.ebp ) { 773 | e.ebp = val; 774 | first = false; 775 | } 776 | } 777 | } 778 | } 779 | skipFirstCheck = false; 780 | } else 781 | e.ebp = val; 782 | stack.push(e); 783 | if( max > 0 && stack.length >= max ) return stack; 784 | } 785 | } 786 | } 787 | } else { 788 | var stackBottom = esp.toInt(); 789 | var stackTop = tinf.stackTop.toInt(); 790 | for( i in 0...size >> 2 ) { 791 | var val = mem.getI32(i << 2); 792 | if( val > stackBottom && val < stackTop || (inProlog && i == 0) ) { 793 | var codePtr = mem.getPointer((i + 1) << 2, jit.align); 794 | var e = jit.resolveAsmPos(codePtr); 795 | if( e != null && e.fpos >= 0 ) { 796 | e.ebp = Pointer.make(val,0); 797 | stack.push(e); 798 | if( max > 0 && stack.length >= max ) return stack; 799 | } 800 | } 801 | } 802 | } 803 | 804 | return [for( s in stack ) if( module.isValid(s.fidx,s.fpos) ) s]; 805 | } 806 | 807 | inline function get_stackFrameCount() return currentStack.length; 808 | 809 | public function getBackTrace() : Array { 810 | return [for( e in currentStack ) stackInfo(e)]; 811 | } 812 | 813 | public function getStackFrame( ?frame ) : StackInfo { 814 | if( frame == null ) frame = currentStackFrame; 815 | var f = currentStack[frame]; 816 | if( f == null ) 817 | return { file : "???", line : 0, ebp : Pointer.make(0, 0), context : null }; 818 | return stackInfo(f); 819 | } 820 | 821 | public function getClosureStack( value ) : Array { 822 | var stack = @:privateAccess eval.getClosureStack(value); 823 | var out = []; 824 | for( ptr in stack ) { 825 | var e = jit.resolveAsmPos(ptr); 826 | if( e == null || !module.isValid(e.fidx,e.fpos) || e.fpos < 0 ) continue; 827 | out.push(stackInfo({ fidx : e.fidx, fpos : e.fpos, ebp: null })); 828 | } 829 | return out; 830 | } 831 | 832 | function stackInfo( f ) : StackInfo { 833 | var s = module.resolveSymbol(f.fidx, f.fpos); 834 | return { file : s.file, line : s.line, ebp : f.ebp, context : module.getMethodContext(f.fidx) }; 835 | } 836 | 837 | public function getValue( expr : String, global = false ) : Value { 838 | var cur = currentStack[currentStackFrame]; 839 | if( cur == null ) return null; 840 | eval.globalContext = global; 841 | eval.setContext(cur.fidx, cur.fpos, cur.ebp); 842 | var v = eval.eval(expr); 843 | eval.globalContext = false; 844 | return v; 845 | } 846 | 847 | public function setValue( expr : String, value : String ) : Value { 848 | var cur = currentStack[currentStackFrame]; 849 | if( cur == null ) return null; 850 | eval.setContext(cur.fidx, cur.fpos, cur.ebp); 851 | return eval.setValue(expr, value); 852 | } 853 | 854 | public function getRef( expr : String, global = false ) : Address { 855 | var cur = currentStack[currentStackFrame]; 856 | if( cur == null ) return null; 857 | eval.globalContext = global; 858 | eval.setContext(cur.fidx, cur.fpos, cur.ebp); 859 | var v = eval.ref(expr); 860 | eval.globalContext = global; 861 | return v; 862 | } 863 | 864 | public function getWatches() { 865 | return [for( w in watches ) w.addr]; 866 | } 867 | 868 | public function watch( a : Address, forReadWrite = false ) { 869 | var size = jit.align.typeSize(a.t); 870 | var availableRegs = HW_REGS.copy(); 871 | for( w in watches ) 872 | for( r in w.regs ) 873 | availableRegs.remove(r.r); 874 | var w : WatchPoint = { 875 | addr : a, 876 | regs : [], 877 | forReadWrite : forReadWrite, 878 | }; 879 | var offset = 0; 880 | var bitSize = [1, 2, 8, 4]; 881 | while( size > 0 ) { 882 | var r = availableRegs.shift(); 883 | if( r == null ) 884 | throw "Not enough hardware register to watch: remove previous watches"; 885 | var v = if( size >= 8 ) 2 else if( size >= 4 ) 3 else if( size >= 2 ) 1 else 0; 886 | w.regs.push({ r : r, offset : offset, bits : v }); 887 | var delta = bitSize[v]; 888 | size -= delta; 889 | offset += delta; 890 | } 891 | watches.push(w); 892 | syncDebugRegs(); 893 | return w; 894 | } 895 | 896 | public function unwatch( a : Address ) { 897 | for( w in watches ) 898 | if( w.addr == a ) { 899 | watches.remove(w); 900 | syncDebugRegs(); 901 | return true; 902 | } 903 | return false; 904 | } 905 | 906 | 907 | function syncDebugRegs() { 908 | var wasPaused = false; 909 | if( currentThread == null ) { 910 | pause(); 911 | wasPaused = true; 912 | } 913 | var dr7 = 0x100; 914 | for( w in watches ) { 915 | for( r in w.regs ) { 916 | var rid = HW_REGS.indexOf(r.r); 917 | dr7 |= 1 << (rid * 2); 918 | dr7 |= ((w.forReadWrite ? 3 : 1) | (r.bits << 2)) << (16 + rid * 4); 919 | } 920 | } 921 | api.writeRegister(currentThread, Dr7, Pointer.make(dr7, 0)); 922 | for( w in watches ) 923 | for( r in w.regs ) 924 | api.writeRegister(currentThread, r.r, w.addr.ptr.offset(r.offset)); 925 | if( wasPaused ) 926 | resume(); 927 | } 928 | 929 | function getCodePos(tid) { 930 | var eip = getReg(tid, Eip); 931 | return eip; 932 | } 933 | 934 | public function resume() { 935 | if( stoppedThread == null ) 936 | throw "No thread stopped"; 937 | if( DEBUG ) trace("RUN " + jit.codePtrToString(getCodePos(currentThread))); 938 | if( !api.resume(stoppedThread) && !processExit ) 939 | throw "Could not resume "+stoppedThread; 940 | stoppedThread = null; 941 | watchBreak = null; 942 | } 943 | 944 | public function end() { 945 | if( stoppedThread != null ) resume(); 946 | if( api != null ) { 947 | api.stop(); 948 | api = null; 949 | } 950 | } 951 | 952 | function readMem( addr : Pointer, size : Int ) { 953 | var mem = new Buffer(size); 954 | if( !api.read(addr, mem, size) ) 955 | throw "Failed to read memory @" + addr.toString() + "[" + size+"]"; 956 | return mem; 957 | } 958 | 959 | function getAsm( ptr : Pointer ) { 960 | if( !jit.isCodePtr(ptr) ) 961 | throw "Assert invalid ptr " + ptr; 962 | return api.readByte(ptr, 0); 963 | } 964 | 965 | function setAsm( ptr : Pointer, byte : Int ) { 966 | if( !jit.isCodePtr(ptr) ) 967 | throw "Assert invalid ptr " + ptr; 968 | if( DEBUG ) trace('Set ${jit.codePtrToString(ptr)}=$byte'); 969 | api.writeByte(ptr, 0, byte); 970 | api.flush(ptr, 1); 971 | } 972 | 973 | function getReg(tid, reg) { 974 | return Pointer.ofPtr(api.readRegister(tid, reg)); 975 | } 976 | 977 | function setReg(tid, reg, value) { 978 | if( !api.writeRegister(tid, reg, value) ) 979 | throw "Failed to set register " + reg; 980 | } 981 | 982 | public function checkBreakpointLine(file : String, line : Int) { 983 | var breaks = module.getBreaks(file, line); 984 | return breaks == null ? -1 : breaks.line; 985 | } 986 | 987 | public function addBreakpoint( file : String, line : Int, condition : Null ) { 988 | var breaks = module.getBreaks(file, line); 989 | if( breaks == null ) 990 | return -1; 991 | // check already defined 992 | var set = false; 993 | for( b in breaks.breaks ) { 994 | var found = false; 995 | for( a in breakPoints ) { 996 | if( a.fid == b.ifun && a.pos == b.pos ) { 997 | found = true; 998 | break; 999 | } 1000 | } 1001 | if( found ) continue; 1002 | 1003 | var codePos = jit.getCodePos(b.ifun, b.pos); 1004 | var old = getAsm(codePos); 1005 | setAsm(codePos, INT3); 1006 | breakPoints.push({ fid : b.ifun, pos : b.pos, oldByte : old, codePos : codePos, condition : condition }); 1007 | set = true; 1008 | } 1009 | return breaks.line; 1010 | } 1011 | 1012 | public function clearBreakpoints( file : String ) { 1013 | var ffuns = module.getFileFunctions(file); 1014 | if( ffuns == null ) 1015 | return; 1016 | for( b in breakPoints.copy() ) 1017 | for( f in ffuns.functions ) 1018 | if( b.fid == f.ifun ) { 1019 | removeBP(b); 1020 | break; 1021 | } 1022 | } 1023 | 1024 | function removeBP( bp ) { 1025 | breakPoints.remove(bp); 1026 | setAsm(bp.codePos, bp.oldByte); 1027 | if( nextStep == bp.codePos ) { 1028 | singleStep(currentThread, false); 1029 | nextStep = Pointer.make(0, 0); 1030 | } 1031 | } 1032 | 1033 | public function removeBreakpoint( file : String, line : Int ) { 1034 | var breaks = module.getBreaks(file, line); 1035 | if( breaks == null ) 1036 | return false; 1037 | var rem = false; 1038 | for( b in breaks.breaks ) 1039 | for( a in breakPoints ) 1040 | if( a.fid == b.ifun && a.pos == b.pos ) { 1041 | rem = true; 1042 | removeBP(a); 1043 | break; 1044 | } 1045 | return rem; 1046 | } 1047 | 1048 | function set_breakOnThrow(b) { 1049 | var count = eval.readI32(jit.threads); 1050 | var tinfos = eval.readPointer(jit.threads.offset(8)); 1051 | var flagsPos = jit.align.ptr * 6 + 8; 1052 | for( i in 0...count ) { 1053 | var tinf = eval.readPointer(tinfos.offset(jit.align.ptr * i)); 1054 | var flags = eval.readI32(tinf.offset(flagsPos)); 1055 | if( b ) flags |= 2 else flags &= ~2; 1056 | eval.writeI32(tinf.offset(flagsPos), flags); 1057 | } 1058 | return breakOnThrow = b; 1059 | } 1060 | 1061 | } 1062 | --------------------------------------------------------------------------------