|
@@ -1,1135 +0,0 @@
|
|
|
-package hide.tools.memory;
|
|
|
-
|
|
|
-import format.hl.Data;
|
|
|
-using format.hl.Tools;
|
|
|
-import hide.tools.memory.Block;
|
|
|
-
|
|
|
-typedef TypeValue = { tl : Array<Int>, count : Int, mem : Int }
|
|
|
-
|
|
|
-class Stats {
|
|
|
- var mem : Memory;
|
|
|
- var byT = new Map();
|
|
|
- var allT = [];
|
|
|
-
|
|
|
- public function new(mem) {
|
|
|
- this.mem = mem;
|
|
|
- }
|
|
|
-
|
|
|
- public function add( t : TType, mem : Int ) {
|
|
|
- return addPath([t == null ? 0 : t.tid], mem);
|
|
|
- }
|
|
|
-
|
|
|
- public inline function makeID( t : TType, field : Int ) {
|
|
|
- return t.tid | (field << 24);
|
|
|
- }
|
|
|
-
|
|
|
- public function addPath( tl : Array<Int>, mem : Int ) {
|
|
|
- var key = tl.join(" ");
|
|
|
- var inf = byT.get(key);
|
|
|
- if( inf == null ) {
|
|
|
- inf = { tl : tl, count : 0, mem : 0 };
|
|
|
- byT.set(key, inf);
|
|
|
- allT.push(inf);
|
|
|
- }
|
|
|
- inf.count++;
|
|
|
- inf.mem += mem;
|
|
|
- }
|
|
|
-
|
|
|
- public function print( withSum = false ) {
|
|
|
- sort( @:privateAccess mem.sortByCount );
|
|
|
- var totCount = 0;
|
|
|
- var totMem = 0;
|
|
|
- var max = @:privateAccess mem.maxLines;
|
|
|
- if( max > 0 && allT.length > max ) {
|
|
|
- mem.log("<ignore "+(allT.length - max)+" lines>");
|
|
|
- allT = allT.slice(allT.length - max);
|
|
|
- }
|
|
|
- for( i in allT ) {
|
|
|
- totCount += i.count;
|
|
|
- totMem += i.mem;
|
|
|
- var tpath = getPathStrings(mem, i.tl);
|
|
|
- mem.log(Memory.withColor(i.count + " count, " + Memory.MB(i.mem) + " ", 33) + tpath.join(${Memory.withColor(' > ', 36)}));
|
|
|
- }
|
|
|
- if( withSum )
|
|
|
- mem.log("Total: "+totCount+" count, "+Memory.MB(totMem));
|
|
|
- }
|
|
|
-
|
|
|
- public static function getTypeString(mem : Memory, id : Int){
|
|
|
- var t = mem.types[id & 0xFFFFFF];
|
|
|
- var tstr = t.toString();
|
|
|
- var fid = id >>> 24;
|
|
|
- if( fid > 0 ) {
|
|
|
- var f = t.memFieldsNames[fid-1];
|
|
|
- if( f != null ) tstr += "." + f;
|
|
|
- }
|
|
|
- return tstr;
|
|
|
- }
|
|
|
-
|
|
|
- public static function getPathStrings(mem : Memory, i : Array<Int>){
|
|
|
- var tpath = [];
|
|
|
- for( tid in i )
|
|
|
- tpath.push(getTypeString(mem, tid));
|
|
|
-
|
|
|
- return tpath;
|
|
|
- }
|
|
|
-
|
|
|
- public function sort( byCount = true , asc = true) {
|
|
|
- if( byCount )
|
|
|
- allT.sort(function(i1, i2) return asc ? i1.count - i2.count : i2.count - i1.count);
|
|
|
- else
|
|
|
- allT.sort(function(i1, i2) return asc ? i1.mem - i2.mem : i2.mem - i1.mem);
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-
|
|
|
-enum FieldsMode {
|
|
|
- Full;
|
|
|
- Parents;
|
|
|
- None;
|
|
|
-}
|
|
|
-
|
|
|
-enum FilterMode {
|
|
|
- None;
|
|
|
- Intersect; // Will display only blocks present in all memories
|
|
|
- Unique; // Will display only blocks not present in other memories
|
|
|
-}
|
|
|
-
|
|
|
-class Memory {
|
|
|
-
|
|
|
- public static var readIntCpt = 0;
|
|
|
- public static var readPointerCpt = 0;
|
|
|
- public var memoryDump : sys.io.FileInput;
|
|
|
- public var memoryDumpBytes : haxe.io.Bytes;
|
|
|
- public var pos : Int;
|
|
|
-
|
|
|
- public var is64 : Bool;
|
|
|
- public var bool32 : Bool;
|
|
|
- public var ptrBits : Int;
|
|
|
-
|
|
|
- public var types : Array<TType>;
|
|
|
-
|
|
|
- var otherMems : Array<Memory>;
|
|
|
- var filterMode: FilterMode = None;
|
|
|
-
|
|
|
- var memFile : String;
|
|
|
-
|
|
|
- var privateData : Int;
|
|
|
- var markData : Int;
|
|
|
-
|
|
|
- var sortByCount : Bool;
|
|
|
- var displayFields : FieldsMode = Full;
|
|
|
- var displayProgress = true;
|
|
|
-
|
|
|
- var code : format.hl.Data;
|
|
|
- var pages : Array<Page>;
|
|
|
- var roots : Array<Pointer>;
|
|
|
- var stacks : Array<Stack>;
|
|
|
- var typesPointers : Array<Pointer>;
|
|
|
- var closuresPointers : Array<Pointer>;
|
|
|
- var blocks : Array<Block>;
|
|
|
- var filteredBlocks : Array<Block> = [];
|
|
|
- var baseTypes : Array<{ t : HLType, p : Pointer }>;
|
|
|
- var all : Block;
|
|
|
-
|
|
|
- var toProcess : Array<Block>;
|
|
|
- var tdynObj : TType;
|
|
|
- var tdynObjData : TType;
|
|
|
- var pointerBlock : PointerMap<Block>;
|
|
|
- var pointerType : PointerMap<TType>;
|
|
|
- var falseCandidates : Array<{ b : Block, f : Block, idx : Int }>;
|
|
|
-
|
|
|
- var currentTypeIndex = 0;
|
|
|
- var resolveCache : Map<String,TType> = new Map();
|
|
|
- var maxLines : Int = 100;
|
|
|
-
|
|
|
- function new() {
|
|
|
- }
|
|
|
-
|
|
|
- public function typeSize( t : HLType ) {
|
|
|
- return switch( t ) {
|
|
|
- case HVoid: 0;
|
|
|
- case HUi8: 1;
|
|
|
- case HUi16: 2;
|
|
|
- case HI32, HF32: 4;
|
|
|
- case HI64, HF64: 8;
|
|
|
- case HBool:
|
|
|
- return bool32 ? 4 : 1;
|
|
|
- case HPacked(_), HAt(_):
|
|
|
- throw "assert";
|
|
|
- default:
|
|
|
- return is64 ? 8 : 4;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- public function getType( t : HLType, isNull = false ) : TType {
|
|
|
- // this is quite slow, but we can't use a Map, maybe try a more per-type specific approach ?
|
|
|
- for( t2 in types )
|
|
|
- if( t == t2.t )
|
|
|
- return t2;
|
|
|
- if( isNull )
|
|
|
- return null;
|
|
|
- throw "Type not found " + t.toString();
|
|
|
- return null;
|
|
|
- }
|
|
|
-
|
|
|
- function loadBytecode( arg : String ) {
|
|
|
- if( code != null ) throw "Duplicate code";
|
|
|
- code = new format.hl.Reader(false).read(new haxe.io.BytesInput(sys.io.File.getBytes(arg)));
|
|
|
- log(arg + " code loaded");
|
|
|
- }
|
|
|
-
|
|
|
- // function readInt() {
|
|
|
- // return memoryDump.readInt32();
|
|
|
- // }
|
|
|
-
|
|
|
- // inline function readPointer() : Pointer {
|
|
|
- // var low = memoryDump.readInt32();
|
|
|
- // var high = is64 ? memoryDump.readInt32() : 0;
|
|
|
- // return cast haxe.Int64.make(high,low);
|
|
|
- // }
|
|
|
-
|
|
|
- inline function readPointer() : Pointer {
|
|
|
- readPointerCpt++;
|
|
|
- var low = readInt();
|
|
|
- readIntCpt--;
|
|
|
- var high = 0;
|
|
|
- if (is64) {
|
|
|
- high = readInt();
|
|
|
- readIntCpt--;
|
|
|
- }
|
|
|
- return cast haxe.Int64.make(high,low);
|
|
|
- }
|
|
|
-
|
|
|
- function readInt() {
|
|
|
- readIntCpt++;
|
|
|
- var ch1 = memoryDumpBytes.get(pos);
|
|
|
- var ch2 = memoryDumpBytes.get(pos + 1);
|
|
|
- var ch3 = memoryDumpBytes.get(pos + 2);
|
|
|
- var ch4 = memoryDumpBytes.get(pos + 3);
|
|
|
- pos += 4;
|
|
|
- return memoryDump.bigEndian ? ch4 | (ch3 << 8) | (ch2 << 16) | (ch1 << 24) : ch1 | (ch2 << 8) | (ch3 << 16) | (ch4 << 24);
|
|
|
- }
|
|
|
-
|
|
|
- public static function MB( v : Float ) {
|
|
|
- if( v < 1000 )
|
|
|
- return Std.int(v) + "B";
|
|
|
- if( v < 1024 * 1000 )
|
|
|
- return (Math.round(v * 10 / 1024) / 10)+"KB";
|
|
|
- return (Math.round(v * 10 / (1024 * 1024)) / 10)+"MB";
|
|
|
- }
|
|
|
-
|
|
|
- function loadMemory( arg : String ) {
|
|
|
- memFile = arg;
|
|
|
-
|
|
|
- memoryDump = sys.io.File.read(arg);
|
|
|
- memoryDumpBytes = sys.io.File.getBytes(arg);
|
|
|
-
|
|
|
- pos = 0;
|
|
|
- if( memoryDumpBytes.getString(pos, 3) != "HMD" )
|
|
|
- throw "Invalid memory dump file";
|
|
|
- pos += 3;
|
|
|
-
|
|
|
- var version = memoryDumpBytes.get(pos) - "0".code;
|
|
|
- pos += 1;
|
|
|
-
|
|
|
- if( version != 1 )
|
|
|
- throw "Unsupported format version "+version;
|
|
|
-
|
|
|
- var flags = readInt();
|
|
|
- is64 = (flags & 1) != 0;
|
|
|
- bool32 = (flags & 2) != 0;
|
|
|
- ptrBits = is64 ? 3 : 2;
|
|
|
- var ptrSize = 1 << ptrBits;
|
|
|
-
|
|
|
- privateData = readInt();
|
|
|
- markData = readInt();
|
|
|
-
|
|
|
- // load pages
|
|
|
- var count = readInt();
|
|
|
- pages = [];
|
|
|
- blocks = [];
|
|
|
- for( i in 0...count ) {
|
|
|
- var addr = readPointer();
|
|
|
- var p = new Page();
|
|
|
- p.addr = addr;
|
|
|
- p.kind = cast readInt();
|
|
|
- p.size = readInt();
|
|
|
- p.reserved = readInt();
|
|
|
-
|
|
|
- var readPtr = !p.memHasPtr();
|
|
|
- while( true ) {
|
|
|
- var ptr = readPointer();
|
|
|
- if( ptr.isNull() ) break;
|
|
|
- var size = readInt();
|
|
|
- var b = new Block();
|
|
|
- b.page = p;
|
|
|
- b.addr = ptr;
|
|
|
- b.size = size;
|
|
|
- b.typePtr = @:privateAccess new hide.tools.memory.Pointer(0);
|
|
|
- b.owner = null;
|
|
|
- b.typeKind = null;
|
|
|
- b.subs = null;
|
|
|
- b.parents = null;
|
|
|
- if( readPtr && size >= ptrSize ) b.typePtr = readPointer();
|
|
|
- blocks.push(b);
|
|
|
- }
|
|
|
-
|
|
|
- if( p.memHasPtr() ) {
|
|
|
- p.dataPosition = pos;
|
|
|
- this.pos += p.size;
|
|
|
- }
|
|
|
-
|
|
|
- pages.push(p);
|
|
|
- }
|
|
|
-
|
|
|
- // load roots
|
|
|
- roots = [for( i in 0...readInt() ) readPointer()];
|
|
|
-
|
|
|
- // load stacks
|
|
|
- stacks = [];
|
|
|
- for( i in 0...readInt() ) {
|
|
|
- var s = new Stack();
|
|
|
- s.base = readPointer();
|
|
|
- s.contents = [for( i in 0...readInt() ) readPointer()];
|
|
|
- stacks.push(s);
|
|
|
- }
|
|
|
-
|
|
|
- // load types
|
|
|
-
|
|
|
- baseTypes = [];
|
|
|
- while( true ) {
|
|
|
- var tid = readInt();
|
|
|
- if( tid < 0 ) break;
|
|
|
- var ptr = readPointer();
|
|
|
- baseTypes.push({ t : Type.createEnumIndex(HLType, tid), p : ptr });
|
|
|
- }
|
|
|
-
|
|
|
- typesPointers = [for( i in 0...readInt() ) readPointer()];
|
|
|
- closuresPointers = [for( i in 0...readInt() ) readPointer()];
|
|
|
- }
|
|
|
-
|
|
|
- public function getStats() {
|
|
|
- var pagesSize = 0, reserved = 0;
|
|
|
- var used = 0, gc = 0;
|
|
|
- var fUsed = 0;
|
|
|
- for( p in pages ) {
|
|
|
- pagesSize += p.size;
|
|
|
- reserved += p.reserved;
|
|
|
- }
|
|
|
- for( b in blocks )
|
|
|
- used += b.size;
|
|
|
- for (b in filteredBlocks)
|
|
|
- fUsed += b.size;
|
|
|
-
|
|
|
- var strings = [];
|
|
|
- var string = "--- " + memFile + " ---\n";
|
|
|
- string += pages.length + " pages, " + MB(pagesSize) + " memory\n";
|
|
|
- string += roots.length + " roots, "+ stacks.length + " stacks\n";
|
|
|
- string += code.types.length + " types, " + closuresPointers.length + " closures\n";
|
|
|
- string += blocks.length + " live blocks " + MB(used) + " used, " + MB(pagesSize - used - reserved) + " free, "+MB(privateData + markData)+" gc\n";
|
|
|
- if (filterMode != None)
|
|
|
- string += filteredBlocks.length + " blocks in filter " + MB(fUsed) + " used\n";
|
|
|
-
|
|
|
- strings.push(string);
|
|
|
- for (m in otherMems??[]) strings = strings.concat(m.getStats());
|
|
|
-
|
|
|
- return strings;
|
|
|
- }
|
|
|
-
|
|
|
- public function getStatsObj() {
|
|
|
- var pagesSize = 0, reserved = 0;
|
|
|
- var used = 0, gc = 0;
|
|
|
- var fUsed = 0;
|
|
|
- for( p in pages ) {
|
|
|
- pagesSize += p.size;
|
|
|
- reserved += p.reserved;
|
|
|
- }
|
|
|
- for( b in blocks )
|
|
|
- used += b.size;
|
|
|
- for (b in filteredBlocks)
|
|
|
- fUsed += b.size;
|
|
|
-
|
|
|
- var objs = [];
|
|
|
- var obj = {
|
|
|
- memFile : memFile,
|
|
|
- free: pagesSize - used - reserved,
|
|
|
- used: used,
|
|
|
- totalAllocated: pagesSize - reserved,
|
|
|
- gc : privateData + markData,
|
|
|
- pagesCount : pages.length,
|
|
|
- pagesSize : pagesSize,
|
|
|
- rootsCount : roots.length,
|
|
|
- stackCount : stacks.length,
|
|
|
- typesCount : code.types.length,
|
|
|
- closuresCount : closuresPointers.length,
|
|
|
- blockCount : blocks.length
|
|
|
- };
|
|
|
-
|
|
|
- objs.push(obj);
|
|
|
- for (m in otherMems??[]) objs = objs.concat(m.getStatsObj());
|
|
|
-
|
|
|
- return objs;
|
|
|
- }
|
|
|
-
|
|
|
- function printStats() {
|
|
|
- var pagesSize = 0, reserved = 0;
|
|
|
- var used = 0, gc = 0;
|
|
|
- var fUsed = 0;
|
|
|
- for( p in pages ) {
|
|
|
- pagesSize += p.size;
|
|
|
- reserved += p.reserved;
|
|
|
- }
|
|
|
- for( b in blocks )
|
|
|
- used += b.size;
|
|
|
- for (b in filteredBlocks)
|
|
|
- fUsed += b.size;
|
|
|
- log(withColor("--- " + memFile + " ---", 36));
|
|
|
- log(pages.length + " pages, " + MB(pagesSize) + " memory");
|
|
|
- log(roots.length + " roots, "+ stacks.length + " stacks");
|
|
|
- log(code.types.length + " types, " + closuresPointers.length + " closures");
|
|
|
- log(blocks.length + " live blocks " + MB(used) + " used, " + MB(pagesSize - used - reserved) + " free, "+MB(privateData + markData)+" gc");
|
|
|
- if (filterMode != None)
|
|
|
- log(filteredBlocks.length + " blocks in filter " + MB(fUsed) + " used");
|
|
|
- }
|
|
|
-
|
|
|
- function getTypeNull( t : TType ) {
|
|
|
- if( t.nullWrap != null )
|
|
|
- return t.nullWrap;
|
|
|
- for( t2 in types )
|
|
|
- switch( t2.t ) {
|
|
|
- case HNull(base) if( base == t.t ):
|
|
|
- t.nullWrap = t2;
|
|
|
- return t2;
|
|
|
- default:
|
|
|
- }
|
|
|
- var r = new TType(types.length, HNull(t.t));
|
|
|
- t.nullWrap = r;
|
|
|
- types.push(r);
|
|
|
- return r;
|
|
|
- }
|
|
|
-
|
|
|
- function goto( b : Block ) {
|
|
|
- var p = b.page.dataPosition;
|
|
|
- if( p < 0 ) throw "assert";
|
|
|
- pos = p + b.addr.sub(b.page.addr);
|
|
|
- //memoryDump.seek(p + b.addr.sub(b.page.addr), SeekBegin);
|
|
|
- }
|
|
|
-
|
|
|
- function check() {
|
|
|
- if( code == null ) throw "Missing .hl file";
|
|
|
- if( memoryDump == null ) throw "Missing .dump file";
|
|
|
- if( code.types.length != this.typesPointers.length ) throw "Types count mismatch";
|
|
|
-
|
|
|
- pointerType = new PointerMap();
|
|
|
- var cid = 0;
|
|
|
- types = [for( i in 0...code.types.length ) new TType(i, code.types[i])];
|
|
|
- for( i in 0...typesPointers.length ) {
|
|
|
- pointerType.set(typesPointers[i], types[i]);
|
|
|
- switch( code.types[i] ) {
|
|
|
- case HFun(f):
|
|
|
- var tid = types.length;
|
|
|
- var args = f.args.copy();
|
|
|
- var clparam = args.shift();
|
|
|
- if( clparam == null ) {
|
|
|
- cid++;
|
|
|
- continue;
|
|
|
- }
|
|
|
- switch( clparam ) {
|
|
|
- case HEnum(p) if( p.name == "" ):
|
|
|
- p.name = '<closure$i context>';
|
|
|
- default:
|
|
|
- }
|
|
|
- var ct = new TType(tid, HFun({ args : args, ret : f.ret }), clparam);
|
|
|
- types.push(ct);
|
|
|
- var pt = closuresPointers[cid++];
|
|
|
- if( !pt.isNull() )
|
|
|
- pointerType.set(pt, ct);
|
|
|
- case HObj(o), HStruct(o):
|
|
|
- if( o.tsuper != null ) {
|
|
|
- var found = false;
|
|
|
- for( j in 0...types.length )
|
|
|
- if( types[j].t == o.tsuper ) {
|
|
|
- types[i].parentClass = types[j];
|
|
|
- found = true;
|
|
|
- break;
|
|
|
- }
|
|
|
- if( !found ) throw "Missing parent class";
|
|
|
- }
|
|
|
- default:
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- for( b in baseTypes ) {
|
|
|
- var t = getType(b.t, true);
|
|
|
- if( t == null ) {
|
|
|
- t = new TType(types.length, b.t);
|
|
|
- types.push(t);
|
|
|
- }
|
|
|
- pointerType.set(b.p, t);
|
|
|
- }
|
|
|
-
|
|
|
- var progress = 0;
|
|
|
- pointerBlock = new PointerMap();
|
|
|
-
|
|
|
- var missingTypes = 0;
|
|
|
- for( b in blocks ) {
|
|
|
- progress++;
|
|
|
- if( displayProgress && progress % 1000 == 0 )
|
|
|
- Sys.print((Std.int((progress / blocks.length) * 1000.0) / 10) + "% \r");
|
|
|
- if( b.page.kind == PDynamic ) {
|
|
|
- goto(b);
|
|
|
- b.typePtr = readPointer();
|
|
|
- }
|
|
|
- b.type = pointerType.get(b.typePtr);
|
|
|
- if( b.page.kind == PDynamic && b.type == null && b.typePtr != Pointer.NULL )
|
|
|
- missingTypes++; // types that we don't have in our dump
|
|
|
- b.typePtr = Pointer.NULL;
|
|
|
- if( b.type != null ) {
|
|
|
- switch( b.page.kind ) {
|
|
|
- case PDynamic:
|
|
|
- case PNoPtr:
|
|
|
- if( b.type.hasPtr ) {
|
|
|
- if( b.type.t.match(HEnum(_)) ) {
|
|
|
- // most likely one of the constructor without pointer parameter
|
|
|
- } else
|
|
|
- b.type = null; // false positive
|
|
|
- }
|
|
|
- case PRaw, PFinalizer:
|
|
|
- if( b.type.isDyn )
|
|
|
- b.type = null; // false positive
|
|
|
- }
|
|
|
- }
|
|
|
- if( b.type != null && !b.type.isDyn )
|
|
|
- b.type = getTypeNull(b.type);
|
|
|
- if( b.type != null )
|
|
|
- b.typeKind = KHeader;
|
|
|
- pointerBlock.set(b.addr, b);
|
|
|
- }
|
|
|
-
|
|
|
- //Sys.println(missingTypes+" blocks with unresolved type");
|
|
|
-
|
|
|
- printStats();
|
|
|
-
|
|
|
- // look in roots (higher ownership priority)
|
|
|
- all = new Block();
|
|
|
-
|
|
|
- var broot = new Block();
|
|
|
- broot.type = new TType(types.length, HAbstract("roots"));
|
|
|
- types.push(broot.type);
|
|
|
- broot.depth = 0;
|
|
|
- broot.addParent(all);
|
|
|
-
|
|
|
- for( r in roots ) {
|
|
|
- var b = pointerBlock.get(r);
|
|
|
- if( b == null ) continue;
|
|
|
- b.addParent(broot);
|
|
|
- if( b.type == null )
|
|
|
- b.typeKind = KRoot;
|
|
|
- }
|
|
|
-
|
|
|
- var tinvalid = new TType(types.length, HAbstract("invalid"));
|
|
|
- types.push(tinvalid);
|
|
|
- for( t in types )
|
|
|
- t.buildTypes(this, tinvalid);
|
|
|
-
|
|
|
- var tunknown = new TType(types.length, HAbstract("unknown"));
|
|
|
- types.push(tunknown);
|
|
|
-
|
|
|
- tdynObj = getType(HDynObj);
|
|
|
- tdynObjData = new TType(types.length, HAbstract("dynobjdata"));
|
|
|
- types.push(tdynObjData);
|
|
|
-
|
|
|
- toProcess = blocks.copy();
|
|
|
- falseCandidates = [];
|
|
|
-
|
|
|
-
|
|
|
- while( toProcess.length > 0 )
|
|
|
- buildHierarchy();
|
|
|
-
|
|
|
- var i = hide.tools.memory.Memory.readIntCpt;
|
|
|
- var v = hide.tools.memory.Memory.readPointerCpt;
|
|
|
- trace(i + " // " + v);
|
|
|
- // look in stacks (low priority of ownership)
|
|
|
- var tstacks = new TType(types.length, HAbstract("stack"));
|
|
|
- var bstacks = [];
|
|
|
- types.push(tstacks);
|
|
|
- for( s in stacks ) {
|
|
|
- var bstack = new Block();
|
|
|
- bstack.depth = 10000;
|
|
|
- bstack.type = tstacks;
|
|
|
- bstack.addParent(all);
|
|
|
- bstacks.push(bstack);
|
|
|
- for( r in s.contents ) {
|
|
|
- var b = pointerBlock.get(r);
|
|
|
- if( b != null )
|
|
|
- b.addParent(bstack);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- for( f in falseCandidates )
|
|
|
- if( f.f.owner == null ) {
|
|
|
- f.f.addParent(f.b);
|
|
|
- f.b.type.falsePositive++;
|
|
|
- f.b.type.falsePositiveIndexes[f.idx]++;
|
|
|
- }
|
|
|
-
|
|
|
- // precompute Arrays (no NativeArray intermediate)
|
|
|
- function shortCircuit( native, haxe ) {
|
|
|
- var tnat = resolveType(native, false);
|
|
|
- var tarr = resolveType(haxe, false);
|
|
|
- if( tnat == null || tarr == null ) return;
|
|
|
- for( b in blocks ) {
|
|
|
- if( b.type == tnat && b.owner != null && b.owner.type == tarr && b.subs != null ) {
|
|
|
- for( s in b.subs )
|
|
|
- s.b.addParent(b.owner);
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- shortCircuit("hl.NativeArray","Array<T>");
|
|
|
- // disable for now, this generates unknowns and "Void" links
|
|
|
- //shortCircuit("hl_bytes_map","Map<String,Dynamic>");
|
|
|
- //shortCircuit("hl_int_map","Map<Int,Dynamic>");
|
|
|
- //shortCircuit("hl_obj_map","Map<{},Dynamic>");
|
|
|
-
|
|
|
- // assign depths
|
|
|
-
|
|
|
- Sys.println("Computing depths...");
|
|
|
- broot.markDepth();
|
|
|
- for( b in bstacks ) b.markDepth();
|
|
|
-
|
|
|
- var changed = -1;
|
|
|
- while( changed != 0 ) {
|
|
|
- changed = 0;
|
|
|
- for( b in blocks ) {
|
|
|
- var minD = -1;
|
|
|
- if( b.parents == null ) {
|
|
|
- if( b.owner != null && b.owner.depth >= 0 )
|
|
|
- minD = b.owner.depth;
|
|
|
- } else {
|
|
|
- for( p in b.parents )
|
|
|
- if( p.depth >= 0 && (minD < 0 || p.depth < minD) )
|
|
|
- minD = p.depth;
|
|
|
- }
|
|
|
- if( minD >= 0 ) {
|
|
|
- minD++;
|
|
|
- if( b.depth < 0 || b.depth > minD ) {
|
|
|
- b.depth = minD;
|
|
|
- changed++;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- for( b in blocks )
|
|
|
- b.finalize();
|
|
|
-
|
|
|
- var unk = 0, unkMem = 0, unRef = 0;
|
|
|
- for( b in blocks ) {
|
|
|
- if( b.owner == null ) {
|
|
|
- unRef++;
|
|
|
- if( unRef < 100 )
|
|
|
- log(" "+b.addr.toString()+"["+b.size+"] is not referenced");
|
|
|
- continue;
|
|
|
- }
|
|
|
-
|
|
|
- if( b.type != null )
|
|
|
- continue;
|
|
|
-
|
|
|
- var o = b.owner;
|
|
|
- while( o != null && o.type == null )
|
|
|
- o = o.owner;
|
|
|
- if( o != null )
|
|
|
- switch( o.type.t ) {
|
|
|
- case HAbstract(_):
|
|
|
- b.type = o.type; // data inside this
|
|
|
- b.typeKind = KAbstractData;
|
|
|
- continue;
|
|
|
- default:
|
|
|
- }
|
|
|
-
|
|
|
- b.type = tunknown;
|
|
|
- }
|
|
|
-
|
|
|
- var falseCount = 0;
|
|
|
- for( t in types )
|
|
|
- falseCount += t.falsePositive;
|
|
|
-
|
|
|
- log("Hierarchy built, "+falseCount+" false positives, "+unRef+" unreferenced");
|
|
|
- }
|
|
|
-
|
|
|
- function printFalsePositives( ?typeStr : String ) {
|
|
|
- var falses = [for( t in types ) if( t.falsePositive > 0 && (typeStr == null || t.toString().indexOf(typeStr) >= 0) ) t];
|
|
|
- falses.sort(function(t1, t2) return t1.falsePositive - t2.falsePositive);
|
|
|
- for( f in falses )
|
|
|
- log(f.falsePositive+" count " + f + " "+f.falsePositiveIndexes+"\n "+[for( f in f.memFields ) f.t.toString()]);
|
|
|
- }
|
|
|
-
|
|
|
- function printUnknown() {
|
|
|
- var byT = new Map();
|
|
|
- for( b in blocks ) {
|
|
|
- if( b.type != null ) continue;
|
|
|
-
|
|
|
- var o = b;
|
|
|
- while( o != null && o.type == null )
|
|
|
- o = o.owner;
|
|
|
-
|
|
|
- var t = o == null ? null : o.type;
|
|
|
- var tid = t == null ? -1 : t.tid;
|
|
|
- var inf = byT.get(tid);
|
|
|
- if( inf == null ) {
|
|
|
- inf = { t : t, count : 0, mem : 0 };
|
|
|
- byT.set(tid, inf);
|
|
|
- }
|
|
|
- inf.count++;
|
|
|
- inf.mem += b.size;
|
|
|
- }
|
|
|
- var all = [for( k in byT ) k];
|
|
|
- all.sort(function(i1, i2) return i1.count - i2.count);
|
|
|
- for( a in all )
|
|
|
- log("Unknown "+a.count + " count, " + MB(a.mem)+" "+(a.t == null ? "" : a.t.toString()));
|
|
|
- }
|
|
|
-
|
|
|
- function buildHierarchy() {
|
|
|
- var progress = 0;
|
|
|
- var blocks = toProcess;
|
|
|
- toProcess = [];
|
|
|
-
|
|
|
- for( b in blocks )
|
|
|
- b.removeChildren();
|
|
|
-
|
|
|
- for( b in blocks ) {
|
|
|
- progress++;
|
|
|
- if( displayProgress && progress % 10000 == 0 )
|
|
|
- Sys.print((Std.int(progress * 1000.0 / blocks.length) / 10) + "% \r");
|
|
|
-
|
|
|
- if( !b.page.memHasPtr() )
|
|
|
- continue;
|
|
|
-
|
|
|
- if( b.type != null && !b.type.hasPtr )
|
|
|
- log(" Scanning "+b.type+" "+b.addr.toString());
|
|
|
-
|
|
|
- goto(b);
|
|
|
- var fields = null;
|
|
|
- var start = 0;
|
|
|
- var ptrTags = null;
|
|
|
- var hasFieldNames = false;
|
|
|
- if( b.type != null ) {
|
|
|
- hasFieldNames = b.type.memFieldsNames != null;
|
|
|
- fields = b.type.memFields;
|
|
|
- ptrTags = b.type.ptrTags;
|
|
|
- // enum
|
|
|
- if( b.type.constructs != null ) {
|
|
|
- readPointer(); // type
|
|
|
- var index = readInt();
|
|
|
- fields = b.type.constructs[index];
|
|
|
- if( is64 ) readInt(); // skip, not a pointer anyway
|
|
|
- start += 2;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- for( i in start...(b.size >> ptrBits) ) {
|
|
|
- var r = readPointer();
|
|
|
-
|
|
|
- var bs = pointerBlock.get(r);
|
|
|
- if( bs == null ) continue;
|
|
|
- var ft = fields != null ? fields[i] : null;
|
|
|
-
|
|
|
- if( ptrTags != null && ((ptrTags.get(i >> 3) >>> (i & 7)) & 1) == 0 && !b.type.t.match(HVirtual(_)) )
|
|
|
- continue;
|
|
|
-
|
|
|
- if( b.type == tdynObj && (i == 1 || i == 2 || i == 3) ) {
|
|
|
- if( bs.typeKind != KHeader && (bs.typeKind != null || bs.type != null) )
|
|
|
- trace(bs.typeKind, bs.type);
|
|
|
- else {
|
|
|
- bs.type = tdynObjData;
|
|
|
- bs.typeKind = KDynObjData;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- if( ft != null && !ft.t.isPtr() ) {
|
|
|
- falseCandidates.push({ b : b, f:bs, idx : i });
|
|
|
- continue;
|
|
|
- }
|
|
|
- bs.addParent(b,hasFieldNames ? (i+1) : 0);
|
|
|
-
|
|
|
- if( bs.type == null && ft != null ) {
|
|
|
- if( ft.t.match(HDyn | HObj(_)) ) {
|
|
|
- // we can't infer with a polymorph type
|
|
|
- continue;
|
|
|
- }
|
|
|
- bs.type = ft;
|
|
|
- bs.typeKind = KInferred(b.type, b.typeKind);
|
|
|
- if( bs.subs != null )
|
|
|
- toProcess.push(bs);
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- function printByType() {
|
|
|
- var ctx = new Stats(this);
|
|
|
- for( b in filteredBlocks )
|
|
|
- ctx.add(b.type, b.size);
|
|
|
- ctx.print();
|
|
|
- }
|
|
|
-
|
|
|
- function resolveType( str, showError = true ) {
|
|
|
- var t = resolveCache.get(str);
|
|
|
- if( t != null )
|
|
|
- return t;
|
|
|
- for( i in currentTypeIndex...types.length ) {
|
|
|
- var t = types[i];
|
|
|
- var tstr = t.toString();
|
|
|
- if (tstr != null) {
|
|
|
- resolveCache.set(tstr, t);
|
|
|
- currentTypeIndex = i + 1;
|
|
|
- if( tstr == str )
|
|
|
- return t;
|
|
|
- }
|
|
|
- }
|
|
|
- if( showError )
|
|
|
- log("Type not found '"+str+"'");
|
|
|
- return null;
|
|
|
- }
|
|
|
-
|
|
|
- public function getLocate(tstr : String, up = 0) {
|
|
|
- var ctx = new Stats(this);
|
|
|
-
|
|
|
- var lt = resolveType(tstr);
|
|
|
- if( lt == null ) return ctx;
|
|
|
-
|
|
|
- inline function isVirtualField(t) { t >>>= 24; return t == 1 || t == 2; }
|
|
|
-
|
|
|
- for( b in filteredBlocks )
|
|
|
- if( b.type != null && b.type.match(lt) ) {
|
|
|
- var tl = [];
|
|
|
- var owner = b.owner;
|
|
|
- // skip first virtual field
|
|
|
- if( lt.t != HDynObj && owner != null && owner.type != null && owner.type.t.match(HVirtual(_)) && isVirtualField(owner.makeTID(b,true)) )
|
|
|
- owner = owner.owner;
|
|
|
-
|
|
|
- if( owner != null ) {
|
|
|
- tl.push(owner.makeTID(b,displayFields == Full));
|
|
|
- var k : Int = up;
|
|
|
- while( owner.owner != null && k-- > 0 && owner.owner != all ) {
|
|
|
- var tag = owner.owner.makeTID(owner,displayFields != None);
|
|
|
- owner = owner.owner;
|
|
|
- // remove recursive sequence
|
|
|
- for( i => tag2 in tl )
|
|
|
- if( tag2 == tag ) {
|
|
|
- var seq = true;
|
|
|
- for( n in 0...i ) {
|
|
|
- if( tl[n] != tl[i+1+n] )
|
|
|
- seq = false;
|
|
|
- }
|
|
|
- if( seq ) {
|
|
|
- for( k in 0...i ) tl.shift();
|
|
|
- tag = -1;
|
|
|
- k += i + 1;
|
|
|
- }
|
|
|
- break;
|
|
|
- }
|
|
|
- // don't display virtual wrappers
|
|
|
- if( displayFields != None && owner.type != null && isVirtualField(tag) && owner.type.t.match(HVirtual(_)) ) {
|
|
|
- tag = -1;
|
|
|
- k++;
|
|
|
- }
|
|
|
- if( tag != -1 )
|
|
|
- tl.unshift(tag);
|
|
|
- }
|
|
|
- }
|
|
|
- ctx.addPath(tl, b.size);
|
|
|
- }
|
|
|
-
|
|
|
- return ctx;
|
|
|
- }
|
|
|
-
|
|
|
- function locate( tstr : String, up = 0 ) {
|
|
|
- var ctx = getLocate(tstr, up);
|
|
|
- ctx.print();
|
|
|
- }
|
|
|
-
|
|
|
- function count( tstr : String, excludes : Array<String> ) {
|
|
|
- var t = resolveType(tstr);
|
|
|
- if( t == null ) return;
|
|
|
- var texclude = [];
|
|
|
- for( e in excludes ) {
|
|
|
- var t = resolveType(e);
|
|
|
- if( t == null ) return;
|
|
|
- texclude.push(t);
|
|
|
- }
|
|
|
- var ctx = new Stats(this);
|
|
|
- Block.MARK_UID++;
|
|
|
- var mark = [];
|
|
|
- for( b in filteredBlocks )
|
|
|
- if( b.type == t )
|
|
|
- visitRec(b,ctx,[],mark);
|
|
|
- while( mark.length > 0 ) {
|
|
|
- var b = mark.pop();
|
|
|
- for( s in b.subs )
|
|
|
- visitRec(s.b,ctx,texclude,mark);
|
|
|
- }
|
|
|
- ctx.print(true);
|
|
|
- }
|
|
|
-
|
|
|
- function visitRec( b : Block, ctx : Stats, exclude : Array<TType>, mark : Array<Block> ) {
|
|
|
- if( b.mark == Block.MARK_UID ) return;
|
|
|
- b.mark = Block.MARK_UID;
|
|
|
- if( b.type != null ) for( t in exclude ) if( b.type.match(t) ) return;
|
|
|
- ctx.addPath(b.type == null ? [] : [b.type.tid],b.size);
|
|
|
- if( b.subs != null )
|
|
|
- mark.push(b);
|
|
|
- }
|
|
|
-
|
|
|
- function parents( tstr : String, up = 0 ) {
|
|
|
- var lt = null;
|
|
|
- for( t in types )
|
|
|
- if( t.t.toString() == tstr ) {
|
|
|
- lt = t;
|
|
|
- break;
|
|
|
- }
|
|
|
- if( lt == null ) {
|
|
|
- log("Type not found");
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- var ctx = new Stats(this);
|
|
|
- for( b in filteredBlocks )
|
|
|
- if( b.type == lt )
|
|
|
- for( b in b.getParents() )
|
|
|
- ctx.addPath([if( b.type == null ) 0 else b.type.tid], 0);
|
|
|
- ctx.print();
|
|
|
- }
|
|
|
-
|
|
|
- function subs( tstr : String, down = 0 ) {
|
|
|
- var lt = null;
|
|
|
- for( t in types )
|
|
|
- if( t.t.toString() == tstr ) {
|
|
|
- lt = t;
|
|
|
- break;
|
|
|
- }
|
|
|
- if( lt == null ) {
|
|
|
- log("Type not found");
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- var ctx = new Stats(this);
|
|
|
- var mark = new Map();
|
|
|
- for( b in filteredBlocks )
|
|
|
- if( b.type == lt ) {
|
|
|
- function addRec(tl:Array<Int>,b:Block, k:Int) {
|
|
|
- if( k < 0 ) return;
|
|
|
- if( mark.exists(b) )
|
|
|
- return;
|
|
|
- mark.set(b, true);
|
|
|
- tl.push(b.type == null ? 0 : b.type.tid);
|
|
|
- ctx.addPath(tl, b.size);
|
|
|
- if( b.subs != null ) {
|
|
|
- k--;
|
|
|
- for( s in b.subs )
|
|
|
- addRec(tl.copy(),s.b, k);
|
|
|
- }
|
|
|
- }
|
|
|
- addRec([], b, down);
|
|
|
- }
|
|
|
- ctx.print();
|
|
|
- }
|
|
|
-
|
|
|
- public function setFilterMode(m: FilterMode) {
|
|
|
- filterMode = m;
|
|
|
- switch( m ) {
|
|
|
- case None:
|
|
|
- filteredBlocks = blocks.copy();
|
|
|
- default:
|
|
|
- filteredBlocks = [];
|
|
|
- var progress = 0;
|
|
|
- for( b in blocks ) {
|
|
|
- progress++;
|
|
|
- if( displayProgress && progress % 1000 == 0 )
|
|
|
- Sys.print((Std.int((progress / blocks.length) * 1000.0) / 10) + "% \r");
|
|
|
- if( !isBlockIgnored(b, m) )
|
|
|
- filteredBlocks.push(b);
|
|
|
- }
|
|
|
- if( displayProgress )
|
|
|
- Sys.print(" \r");
|
|
|
- }
|
|
|
- }
|
|
|
- public function isBlockIgnored(b: Block, m: FilterMode) {
|
|
|
- switch( m ) {
|
|
|
- case None:
|
|
|
- return false;
|
|
|
- case Intersect:
|
|
|
- for( m in otherMems ) {
|
|
|
- if( m.pointerBlock.get(b.addr ) == null )
|
|
|
- return true;
|
|
|
- }
|
|
|
- case Unique:
|
|
|
- for( m in otherMems ) {
|
|
|
- if( m.pointerBlock.get(b.addr ) != null )
|
|
|
- return true;
|
|
|
- }
|
|
|
- }
|
|
|
- return false;
|
|
|
- }
|
|
|
-
|
|
|
- public function log(msg:String) {
|
|
|
- Sys.println(msg);
|
|
|
- }
|
|
|
-
|
|
|
- static function parseArgs(str: String) {
|
|
|
- str = StringTools.trim(str);
|
|
|
- var i = 0;
|
|
|
- var tok = "";
|
|
|
- var args = [];
|
|
|
- var escape = false;
|
|
|
- while(i != str.length) {
|
|
|
- var c = str.charAt(i++);
|
|
|
- if(c == '"') {
|
|
|
- escape = !escape;
|
|
|
- }
|
|
|
- else {
|
|
|
- if(c == " " && !escape) {
|
|
|
- if(tok.length > 0) args.push(tok);
|
|
|
- tok = "";
|
|
|
- }
|
|
|
- else
|
|
|
- tok += c;
|
|
|
- }
|
|
|
- }
|
|
|
- if(tok.length > 0) args.push(tok);
|
|
|
- return args;
|
|
|
- }
|
|
|
-
|
|
|
- static var useColor = false;
|
|
|
- static function main() {
|
|
|
- var m = new Memory();
|
|
|
- var others: Array<Memory> = [];
|
|
|
- var filterMode: FilterMode = None;
|
|
|
-
|
|
|
- //hl.Gc.dumpMemory(); Sys.command("cp memory.hl test.hl");
|
|
|
-
|
|
|
- var code = null, memory = null;
|
|
|
- var args = Sys.args();
|
|
|
- while( args.length > 0 ) {
|
|
|
- var arg = args.shift();
|
|
|
- if( StringTools.endsWith(arg, ".hl") ) {
|
|
|
- code = arg;
|
|
|
- m.loadBytecode(arg);
|
|
|
- continue;
|
|
|
- }
|
|
|
- if( arg == "-c" || arg == "--color" ) {
|
|
|
- useColor = true;
|
|
|
- continue;
|
|
|
- }
|
|
|
- if( arg == "--args" ) {
|
|
|
- m.displayProgress = false;
|
|
|
- break;
|
|
|
- }
|
|
|
- if (memory == null) {
|
|
|
- memory = arg;
|
|
|
- m.loadMemory(arg);
|
|
|
- } else {
|
|
|
- var m2 = new Memory();
|
|
|
- m2.loadMemory(arg);
|
|
|
- others.push(m2);
|
|
|
- }
|
|
|
- }
|
|
|
- if( code != null && memory == null ) {
|
|
|
- var dir = new haxe.io.Path(code).dir;
|
|
|
- if( dir == null ) dir = ".";
|
|
|
- memory = dir+"/hlmemory.dump";
|
|
|
- if( sys.FileSystem.exists(memory) ) m.loadMemory(memory);
|
|
|
- }
|
|
|
-
|
|
|
- m.check();
|
|
|
- for (m2 in others) {
|
|
|
- m2.code = m.code;
|
|
|
- m2.check();
|
|
|
- }
|
|
|
- m.otherMems = [for (i in others) i];
|
|
|
- m.setFilterMode(filterMode);
|
|
|
-
|
|
|
- var stdin = Sys.stdin();
|
|
|
- while( true ) {
|
|
|
- Sys.print(withColor("> ", 31));
|
|
|
- var args = parseArgs(args.length > 0 ? args.shift() : stdin.readLine());
|
|
|
- var cmd = args.shift();
|
|
|
- switch( cmd ) {
|
|
|
- case "exit", "quit", "q":
|
|
|
- break;
|
|
|
- case "types":
|
|
|
- m.printByType();
|
|
|
- case "stats":
|
|
|
- m.printStats();
|
|
|
- case "false":
|
|
|
- m.printFalsePositives(args.shift());
|
|
|
- case "unknown":
|
|
|
- m.printUnknown();
|
|
|
- case "locate":
|
|
|
- m.locate(args.shift(), Std.parseInt(args.shift()));
|
|
|
- case "count":
|
|
|
- m.count(args.shift(), args);
|
|
|
- case "parents":
|
|
|
- m.parents(args.shift());
|
|
|
- case "subs":
|
|
|
- m.subs(args.shift(), Std.parseInt(args.shift()));
|
|
|
- case "sort":
|
|
|
- switch( args.shift() ) {
|
|
|
- case "mem":
|
|
|
- m.sortByCount = false;
|
|
|
- case "count":
|
|
|
- m.sortByCount = true;
|
|
|
- case mode:
|
|
|
- Sys.println("Unknown sort mode " + mode);
|
|
|
- }
|
|
|
- case "fields":
|
|
|
- switch( args.shift() ) {
|
|
|
- case "full":
|
|
|
- m.displayFields = Full;
|
|
|
- case "none":
|
|
|
- m.displayFields = None;
|
|
|
- case "parents":
|
|
|
- m.displayFields = Parents;
|
|
|
- case mode:
|
|
|
- Sys.println("Unknown fields mode " + mode);
|
|
|
- }
|
|
|
- case "filter":
|
|
|
- switch( args.shift() ) {
|
|
|
- case "none":
|
|
|
- filterMode = None;
|
|
|
- case "intersect":
|
|
|
- filterMode = Intersect;
|
|
|
- case "unique":
|
|
|
- filterMode = Unique;
|
|
|
- case mode:
|
|
|
- Sys.println("Unknown filter mode " + mode);
|
|
|
- }
|
|
|
- m.setFilterMode(filterMode);
|
|
|
- case "nextDump":
|
|
|
- others.push(m);
|
|
|
- m = others.shift();
|
|
|
- m.otherMems = [for (i in others) i];
|
|
|
- m.setFilterMode(filterMode);
|
|
|
- var ostr = others.length > 0 ? (" (others are " + others.map(m -> m.memFile) + ")") : "";
|
|
|
- Sys.println("Using dump " + m.memFile + ostr);
|
|
|
- case "lines":
|
|
|
- var v = args.shift();
|
|
|
- if( v != null )
|
|
|
- m.maxLines = Std.parseInt(v);
|
|
|
- Sys.println(m.maxLines == 0 ? "Lines limit disabled" : m.maxLines + " maximum lines displayed");
|
|
|
- case null:
|
|
|
- Sys.println("");
|
|
|
- default:
|
|
|
- Sys.println("Unknown command " + cmd);
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // A list of ansi colors is available at
|
|
|
- // https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797#8-16-colors
|
|
|
- public static function withColor(str: String, ansiCol: Int) {
|
|
|
- if (!useColor)
|
|
|
- return str;
|
|
|
- return "\x1B[" + ansiCol + "m" + str + "\x1B[0m";
|
|
|
- }
|
|
|
-}
|