/* * Copyright 2010, 2011, 2012 Vladimir Panteleev * This file is part of RABCDAsm. * * RABCDAsm is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * RABCDAsm is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with RABCDAsm. If not, see . */ module disassembler; import std.file; import std.string; import std.array; import std.conv; import std.exception; import std.algorithm; import std.path; import abcfile; import asprogram; import autodata; alias std.array.join join; final class StringBuilder { char[] buf; size_t pos; string filename; this(string filename) { if (exists(filename)) throw new Exception(filename ~ " exists"); this.filename = filename; buf.length = 1024; } void opCatAssign(string s) { checkIndent(); auto end = pos + s.length; while (buf.length < end) buf.length = buf.length*2; buf[pos..end] = s; pos = end; } void opCatAssign(char c) { if (buf.length < pos+1) // speed hack: no loop, no indent check buf.length = buf.length*2; buf[pos++] = c; } void save() { string[] dirSegments = split(filename, "/"); for (int l=0; l 0, "No context"); //assert(ns.name is null, "Named private namespace"); auto myPos = context.length; foreach (i, ref item; context) if (item.type == ContextItem.Type.Multiname && item.multiname.vQName.ns == ns) { myPos = i; break; } if (myPos == 0) { possibleOrphanPrivateNamespaces[ns.privateIndex] = true; return; } auto myContext = context[0..myPos].dup; privateNamespaces.add(ns.privateIndex, myContext); } } void visitNamespaceSet(ASProgram.Namespace[] nsSet) { foreach (ns; nsSet) visitNamespace(ns); } void visitMultiname(ASProgram.Multiname m) { if (m is null) return; with (m) switch (kind) { case ASType.QName: case ASType.QNameA: visitNamespace(vQName.ns); break; case ASType.Multiname: case ASType.MultinameA: visitNamespaceSet(vMultiname.nsSet); break; case ASType.MultinameL: case ASType.MultinameLA: visitNamespaceSet(vMultinameL.nsSet); break; case ASType.TypeName: visitMultiname(vTypeName.name); foreach (param; vTypeName.params) visitMultiname(param); break; default: break; } } void visitMethodBody(ASProgram.MethodBody b) { foreach (ref instruction; b.instructions) foreach (i, type; opcodeInfo[instruction.opcode].argumentTypes) switch (type) { case OpcodeArgumentType.Namespace: visitNamespace(instruction.arguments[i].namespacev); break; case OpcodeArgumentType.Multiname: visitMultiname(instruction.arguments[i].multinamev); break; case OpcodeArgumentType.Class: pushContext("inline_class"); if (isOrphan(instruction.arguments[i].classv)) addClass(instruction.arguments[i].classv); popContext(); break; case OpcodeArgumentType.Method: pushContext("inline_method"); if (isOrphan(instruction.arguments[i].methodv)) addMethod(instruction.arguments[i].methodv); popContext(); break; default: break; } } string contextToString(ContextItem[] context, bool filename) { context = ContextItem.expand(this, context, false); foreach_reverse (i, c; context) if (i>0 && c==context[i-1]) context = context[0..i] ~ context[i+1..$]; ContextItem.Segment[] segments; foreach (ci; context) segments ~= ci.toSegments(filename); string escape(string s) { if (!filename) return s; string result; foreach (c; s) if (c == '.') result ~= '/'; else if (c == ':' || c == '\\' || c == '*' || c == '?' || c == '"' || c == '<' || c == '>' || c == '|' || c < 0x20 || c >= 0x7F || c == ' ' || c == '%') result ~= format("%%%02X", c); else result ~= c; auto pathSegments = result.split("/"); if (!pathSegments.length) pathSegments = [""]; foreach (ref pathSegment; pathSegments) if (pathSegment == "") pathSegment = "%"; return arrayJoin(pathSegments); } string[] strings = new string[segments.length]; foreach (i, ref s; segments) strings[i] = (i>0 ? cast(string)[s.delim] : null) ~ escape(s.str); return arrayJoin(strings); } void addObject(T)(T obj) { objects.add(obj, context); } void addClass(ASProgram.Class vclass) { addObject(vclass); pushContext("cinit"); addMethod(vclass.cinit); popContext(); pushContext("iinit"); addMethod(vclass.instance.iinit); popContext(); pushContext("instance"); visitMultiname(vclass.instance.name); visitMultiname(vclass.instance.superName); foreach (iface; vclass.instance.interfaces) visitMultiname(iface); popContext(); } void addMethod(ASProgram.Method method) { addObject(method); if (method.vbody) visitMethodBody(method.vbody); } } final class Disassembler { ASProgram as; string name, dir; RefBuilder refs; void newInclude(StringBuilder mainsb, string filename, void delegate(StringBuilder) callback, bool doInline = true) { if (doInline) { string base = dirName(mainsb.filename); string full = dir ~ "/" ~ filename; uint up = 0; while (!full.startsWith(base)) base = dirName(base), up++; string rel = replicate("../", up) ~ full[base.length+1..$]; StringBuilder sb = new StringBuilder(full); callback(sb); sb.save(); mainsb ~= "#include "; dumpString(mainsb, rel); mainsb.newLine(); } else callback(mainsb); } this(ASProgram as, string dir, string name) { this.as = as; this.name = name; this.dir = dir; } void disassemble() { refs = new RefBuilder(as); refs.run(); StringBuilder sb = new StringBuilder(dir ~ "/" ~ name ~ ".main.asasm"); sb ~= "#include "; dumpString(sb, name ~ ".privatens.asasm"); sb.newLine(); sb ~= "program"; sb.indent++; sb.newLine(); sb ~= "minorversion "; sb ~= to!string(as.minorVersion); sb.newLine(); sb ~= "majorversion "; sb ~= to!string(as.majorVersion); sb.newLine(); sb.newLine(); foreach (uint i, script; as.scripts) { newInclude(sb, refs.scripts.getFilename(script, "script"), (StringBuilder sb) { dumpScript(sb, script, i); }); } sb.newLine(); if (as.orphanClasses.length) { sb ~= "; ============================= Orphan classes =============================="; sb.newLine(); sb.newLine(); foreach (i, vclass; as.orphanClasses) newInclude(sb, refs.objects.getFilename(vclass, "class"), (StringBuilder sb) { dumpClass(sb, vclass); }); sb.newLine(); } if (as.orphanMethods.length) { sb ~= "; ============================= Orphan methods =============================="; sb.newLine(); sb.newLine(); foreach (i, method; as.orphanMethods) newInclude(sb, refs.objects.getFilename(method, "method"), (StringBuilder sb) { dumpMethod(sb, method, "method"); }); sb.newLine(); } sb.indent--; sb ~= "end ; program"; sb.newLine(); sb.save(); // now dump the private namespace indices sb = new StringBuilder(dir ~ "/" ~ name ~ ".privatens.asasm"); uint[] indices = refs.privateNamespaces.names.keys; bool alphaSortDelegate(uint a, uint b) { return refs.privateNamespaces.names[a] < refs.privateNamespaces.names[b]; } sort!alphaSortDelegate(indices); foreach (index; indices) { void dumpContext(RefBuilder.ContextItem[] context) { sb ~= "; "; foreach (i, c; context) { if (c.type == RefBuilder.ContextItem.Type.Multiname) dumpMultiname(sb, c.multiname); else sb ~= c.str; if (i < context.length-1) sb ~= " -> "; } sb.newLine(); } auto context = refs.privateNamespaces.contexts[index]; dumpContext(context); /*auto contextEx = refs.ContextItem.expand(refs, context); if (context != contextEx) dumpContext(contextEx);*/ sb ~= format("#privatens %4d ", index); dumpString(sb, refs.privateNamespaces.names[index]); sb.newLine(); } sb.save(); } void dumpInt(StringBuilder sb, long v) { if (v == ABCFile.NULL_INT) sb ~= "null"; else sb ~= to!string(v); } void dumpUInt(StringBuilder sb, ulong v) { if (v == ABCFile.NULL_UINT) sb ~= "null"; else sb ~= to!string(v); } void dumpDouble(StringBuilder sb, double v) { if (v == ABCFile.NULL_DOUBLE) sb ~= "null"; else { string s = format("%.18g", v); static double forceDouble(double d) { static double n; n = d; return n; } if (s != "nan" && s != "inf" && s != "-inf") { foreach_reverse (i; 1..s.length) if (s[i]>='0' && s[i]<='8' && forceDouble(to!double(s[0..i] ~ cast(char)(s[i]+1)))==v) s = s[0..i] ~ cast(char)(s[i]+1); while (s.length>2 && s[$-1]!='.' && forceDouble(to!double(s[0..$-1]))==v) s = s[0..$-1]; } sb ~= s; } } void dumpString(StringBuilder sb, string str) { if (str is null) sb ~= "null"; else { static const char[16] hexDigits = "0123456789ABCDEF"; sb ~= '"'; foreach (c; str) if (c == 0x0A) sb ~= `\n`; else if (c == 0x0D) sb ~= `\r`; else if (c == '\\') sb ~= `\\`; else if (c == '"') sb ~= `\"`; else if (c < 0x20) { sb ~= `\x`; sb ~= hexDigits[c / 0x10]; sb ~= hexDigits[c % 0x10]; } else sb ~= c; sb ~= '"'; } } void dumpNamespace(StringBuilder sb, ASProgram.Namespace namespace) { if (namespace is null) sb ~= "null"; else with (namespace) { sb ~= ASTypeNames[kind]; sb ~= '('; dumpString(sb, name); if (kind == ASType.PrivateNamespace) { sb ~= ", "; dumpString(sb, refs.privateNamespaces.getName(privateIndex)); } sb ~= ')'; } } void dumpNamespaceSet(StringBuilder sb, ASProgram.Namespace[] set) { if (set is null) sb ~= "null"; else { sb ~= '['; foreach (i, ns; set) { dumpNamespace(sb, ns); if (i < set.length-1) sb ~= ", "; } sb ~= ']'; } } void dumpMultiname(StringBuilder sb, ASProgram.Multiname multiname) { if (multiname is null) sb ~= "null"; else with (multiname) { sb ~= ASTypeNames[kind]; sb ~= '('; switch (kind) { case ASType.QName: case ASType.QNameA: dumpNamespace(sb, vQName.ns); sb ~= ", "; dumpString(sb, vQName.name); break; case ASType.RTQName: case ASType.RTQNameA: dumpString(sb, vRTQName.name); break; case ASType.RTQNameL: case ASType.RTQNameLA: break; case ASType.Multiname: case ASType.MultinameA: dumpString(sb, vMultiname.name); sb ~= ", "; dumpNamespaceSet(sb, vMultiname.nsSet); break; case ASType.MultinameL: case ASType.MultinameLA: dumpNamespaceSet(sb, vMultinameL.nsSet); break; case ASType.TypeName: dumpMultiname(sb, vTypeName.name); sb ~= '<'; foreach (i, param; vTypeName.params) { dumpMultiname(sb, param); if (i < vTypeName.params.length-1) sb ~= ", "; } sb ~= '>'; break; default: throw new .Exception("Unknown Multiname kind"); } sb ~= ')'; } } void dumpTraits(StringBuilder sb, ASProgram.Trait[] traits, bool inScript = false) { foreach (/*ref*/ trait; traits) { sb ~= "trait "; sb ~= TraitKindNames[trait.kind]; sb ~= ' '; dumpMultiname(sb, trait.name); if (trait.attr) dumpFlags!(true)(sb, trait.attr, TraitAttributeNames); bool inLine = false; switch (trait.kind) { case TraitKind.Slot: case TraitKind.Const: if (trait.vSlot.slotId) { sb ~= " slotid "; dumpUInt(sb, trait.vSlot.slotId); } if (trait.vSlot.typeName) { sb ~= " type "; dumpMultiname(sb, trait.vSlot.typeName); } if (trait.vSlot.value.vkind) { sb ~= " value "; dumpValue(sb, trait.vSlot.value); } inLine = true; break; case TraitKind.Class: if (trait.vClass.slotId) { sb ~= " slotid "; dumpUInt(sb, trait.vClass.slotId); } sb.indent++; sb.newLine(); newInclude(sb, refs.objects.getFilename(trait.vClass.vclass, "class"), (StringBuilder sb) { dumpClass(sb, trait.vClass.vclass); }); break; case TraitKind.Function: if (trait.vFunction.slotId) { sb ~= " slotid "; dumpUInt(sb, trait.vFunction.slotId); } sb.indent++; sb.newLine(); newInclude(sb, refs.objects.getFilename(trait.vFunction.vfunction, "method"), (StringBuilder sb) { dumpMethod(sb, trait.vFunction.vfunction, "method"); }, inScript); break; case TraitKind.Method: case TraitKind.Getter: case TraitKind.Setter: if (trait.vMethod.dispId) { sb ~= " dispid "; dumpUInt(sb, trait.vMethod.dispId); } sb.indent++; sb.newLine(); newInclude(sb, refs.objects.getFilename(trait.vMethod.vmethod, "method"), (StringBuilder sb) { dumpMethod(sb, trait.vMethod.vmethod, "method"); }, inScript); break; default: throw new Exception("Unknown trait kind"); } foreach (metadata; trait.metadata) { if (inLine) { sb.indent++; sb.newLine(); inLine = false; } dumpMetadata(sb, metadata); } if (inLine) { sb ~= " end"; sb.newLine(); } else { sb.indent--; sb ~= "end ; trait"; sb.newLine(); } } } void dumpMetadata(StringBuilder sb, ASProgram.Metadata metadata) { sb ~= "metadata "; dumpString(sb, metadata.name); sb.indent++; sb.newLine(); foreach (ref item; metadata.items) { sb ~= "item "; dumpString(sb, item.key); sb ~= " "; dumpString(sb, item.value); sb.newLine(); } sb.indent--; sb ~= "end ; metadata"; sb.newLine(); } void dumpFlags(bool oneLine = false)(StringBuilder sb, ubyte flags, const string[] names) { for (int i=0; flags; i++, flags>>=1) if (flags & 1) { static if (oneLine) sb ~= " flag "; else sb ~= "flag "; sb ~= names[i]; static if (!oneLine) sb.newLine(); } } void dumpValue(StringBuilder sb, ref ASProgram.Value value) { with (value) { sb ~= ASTypeNames[vkind]; sb ~= '('; switch (vkind) { case ASType.Integer: dumpInt(sb, vint); break; case ASType.UInteger: dumpUInt(sb, vuint); break; case ASType.Double: dumpDouble(sb, vdouble); break; case ASType.Utf8: dumpString(sb, vstring); break; case ASType.Namespace: case ASType.PackageNamespace: case ASType.PackageInternalNs: case ASType.ProtectedNamespace: case ASType.ExplicitNamespace: case ASType.StaticProtectedNs: case ASType.PrivateNamespace: dumpNamespace(sb, vnamespace); break; case ASType.True: case ASType.False: case ASType.Null: case ASType.Undefined: break; default: throw new Exception("Unknown type"); } sb ~= ')'; } } void dumpMethod(StringBuilder sb, ASProgram.Method method, string label) { sb ~= label; sb.indent++; sb.newLine(); if (method.name !is null) { sb ~= "name "; dumpString(sb, method.name); sb.newLine(); } auto refName = refs.objects.getName(method); if (refName) { sb ~= "refid "; dumpString(sb, refName); sb.newLine(); } foreach (m; method.paramTypes) { sb ~= "param "; dumpMultiname(sb, m); sb.newLine(); } if (method.returnType) { sb ~= "returns "; dumpMultiname(sb, method.returnType); sb.newLine(); } dumpFlags(sb, method.flags, MethodFlagNames); foreach (ref v; method.options) { sb ~= "optional "; dumpValue(sb, v); sb.newLine(); } foreach (s; method.paramNames) { sb ~= "paramname "; dumpString(sb, s); sb.newLine(); } if (method.vbody) dumpMethodBody(sb, method.vbody); sb.indent--; sb ~= "end ; method"; sb.newLine(); } void dumpClass(StringBuilder sb, ASProgram.Class vclass) { sb ~= "class"; sb.indent++; sb.newLine(); auto refName = refs.objects.getName(vclass); if (refName) { sb ~= "refid "; dumpString(sb, refName); sb.newLine(); } sb ~= "instance "; dumpInstance(sb, vclass.instance); dumpMethod(sb, vclass.cinit, "cinit"); dumpTraits(sb, vclass.traits); sb.indent--; sb ~= "end ; class"; sb.newLine(); } void dumpInstance(StringBuilder sb, ASProgram.Instance instance) { dumpMultiname(sb, instance.name); sb.indent++; sb.newLine(); if (instance.superName) { sb ~= "extends "; dumpMultiname(sb, instance.superName); sb.newLine(); } foreach (i; instance.interfaces) { sb ~= "implements "; dumpMultiname(sb, i); sb.newLine(); } dumpFlags(sb, instance.flags, InstanceFlagNames); if (instance.protectedNs) { sb ~= "protectedns "; dumpNamespace(sb, instance.protectedNs); sb.newLine(); } dumpMethod(sb, instance.iinit, "iinit"); dumpTraits(sb, instance.traits); sb.indent--; sb ~= "end ; instance"; sb.newLine(); } void dumpScript(StringBuilder sb, ASProgram.Script script, uint index) { sb ~= "script ; "; sb ~= to!string(index); sb.indent++; sb.newLine(); dumpMethod(sb, script.sinit, "sinit"); dumpTraits(sb, script.traits, true); sb.indent--; sb ~= "end ; script"; sb.newLine(); } void dumpUIntField(StringBuilder sb, string name, uint value) { sb ~= name; sb ~= ' '; dumpUInt(sb, value); sb.newLine(); } void dumpLabel(StringBuilder sb, ref ABCFile.Label label) { sb ~= 'L'; sb ~= to!string(label.index); if (label.offset != 0) { if (label.offset > 0) sb ~= '+'; sb ~= to!string(label.offset); } } void dumpMethodBody(StringBuilder sb, ASProgram.MethodBody mbody) { if (mbody.error) { sb ~= "; Error while disassembling method: " ~ mbody.error; sb.newLine(); sb.linePrefix = "; "; } sb ~= "body"; sb.indent++; sb.newLine(); dumpUIntField(sb, "maxstack", mbody.maxStack); dumpUIntField(sb, "localcount", mbody.localCount); dumpUIntField(sb, "initscopedepth", mbody.initScopeDepth); dumpUIntField(sb, "maxscopedepth", mbody.maxScopeDepth); sb ~= "code"; sb.newLine(); bool[] labels = new bool[mbody.instructions.length+1]; // reserve exception labels foreach (ref e; mbody.exceptions) labels[e.from.index] = labels[e.to.index] = labels[e.target.index] = true; sb.indent++; if (mbody.error) foreach (i, b; mbody.rawBytes) { sb ~= format("0x%02X", b); if (i%16==15 || i==mbody.rawBytes.length-1) sb.newLine(); else sb ~= " "; } else dumpInstructions(sb, mbody.instructions, labels); sb.indent--; sb ~= "end ; code"; sb.newLine(); foreach (ref e; mbody.exceptions) { sb ~= "try from "; dumpLabel(sb, e.from); sb ~= " to "; dumpLabel(sb, e.to); sb ~= " target "; dumpLabel(sb, e.target); sb ~= " type "; dumpMultiname(sb, e.excType); sb ~= " name "; dumpMultiname(sb, e.varName); sb ~= " end"; sb.newLine(); } dumpTraits(sb, mbody.traits); sb.indent--; sb ~= "end ; body"; sb.newLine(); sb.linePrefix = null; } void dumpInstructions(StringBuilder sb, ASProgram.Instruction[] instructions, bool[] labels) { foreach (ref instruction; instructions) foreach (i, type; opcodeInfo[instruction.opcode].argumentTypes) switch (type) { case OpcodeArgumentType.JumpTarget: case OpcodeArgumentType.SwitchDefaultTarget: labels[instruction.arguments[i].jumpTarget.index] = true; break; case OpcodeArgumentType.SwitchTargets: foreach (ref label; instruction.arguments[i].switchTargets) labels[label.index] = true; break; default: break; } void checkLabel(uint ii) { if (labels[ii]) { sb.noIndent(); sb ~= 'L'; sb ~= to!string(ii); sb ~= ':'; sb.newLine(); } } bool extraNewLine = false; foreach (uint ii, ref instruction; instructions) { if (extraNewLine) sb.newLine(); extraNewLine = newLineAfter[instruction.opcode]; checkLabel(ii); sb ~= opcodeInfo[instruction.opcode].name; auto argTypes = opcodeInfo[instruction.opcode].argumentTypes; if (argTypes.length) { foreach (i; opcodeInfo[instruction.opcode].name.length..20) sb ~= ' '; foreach (i, type; argTypes) { final switch (type) { case OpcodeArgumentType.Unknown: throw new Exception("Don't know how to disassemble OP_" ~ opcodeInfo[instruction.opcode].name); case OpcodeArgumentType.UByteLiteral: sb ~= to!string(instruction.arguments[i].ubytev); break; case OpcodeArgumentType.IntLiteral: sb ~= to!string(instruction.arguments[i].intv); break; case OpcodeArgumentType.UIntLiteral: sb ~= to!string(instruction.arguments[i].uintv); break; case OpcodeArgumentType.Int: dumpInt(sb, instruction.arguments[i].intv); break; case OpcodeArgumentType.UInt: dumpUInt(sb, instruction.arguments[i].uintv); break; case OpcodeArgumentType.Double: dumpDouble(sb, instruction.arguments[i].doublev); break; case OpcodeArgumentType.String: dumpString(sb, instruction.arguments[i].stringv); break; case OpcodeArgumentType.Namespace: dumpNamespace(sb, instruction.arguments[i].namespacev); break; case OpcodeArgumentType.Multiname: dumpMultiname(sb, instruction.arguments[i].multinamev); break; case OpcodeArgumentType.Class: if (instruction.arguments[i].classv is null) sb ~= "null"; else dumpString(sb, refs.objects.getName(instruction.arguments[i].classv)); break; case OpcodeArgumentType.Method: if (instruction.arguments[i].methodv is null) sb ~= "null"; else dumpString(sb, refs.objects.getName(instruction.arguments[i].methodv)); break; case OpcodeArgumentType.JumpTarget: case OpcodeArgumentType.SwitchDefaultTarget: dumpLabel(sb, instruction.arguments[i].jumpTarget); break; case OpcodeArgumentType.SwitchTargets: sb ~= '['; auto targets = instruction.arguments[i].switchTargets; foreach (ti, t; targets) { dumpLabel(sb, t); if (ti < targets.length-1) sb ~= ", "; } sb ~= ']'; break; } if (i < argTypes.length-1) sb ~= ", "; } } sb.newLine(); } checkLabel(to!uint(instructions.length)); } } private: bool[256] newLineAfter; static this() { foreach (o; [ Opcode.OP_callpropvoid, Opcode.OP_constructsuper, Opcode.OP_initproperty, Opcode.OP_ifeq, Opcode.OP_iffalse, Opcode.OP_ifge, Opcode.OP_ifgt, Opcode.OP_ifle, Opcode.OP_iflt, Opcode.OP_ifne, Opcode.OP_ifnge, Opcode.OP_ifngt, Opcode.OP_ifnle, Opcode.OP_ifnlt, Opcode.OP_ifstricteq, Opcode.OP_ifstrictne, Opcode.OP_iftrue, Opcode.OP_jump, Opcode.OP_lookupswitch, Opcode.OP_pushscope, Opcode.OP_returnvalue, Opcode.OP_returnvoid, Opcode.OP_setglobalslot, Opcode.OP_setlocal, Opcode.OP_setlocal0, Opcode.OP_setlocal1, Opcode.OP_setlocal2, Opcode.OP_setlocal3, Opcode.OP_setproperty, Opcode.OP_setpropertylate, Opcode.OP_setslot, Opcode.OP_setsuper, Opcode.OP_si8, Opcode.OP_si16, Opcode.OP_si32, Opcode.OP_sf32, Opcode.OP_sf64, ]) newLineAfter[o] = true; } /// Force a raw data join (workaround for issue 6064) T[] arrayJoin(T)(T[][] arrays, T[] sep) { return cast(T[])join(cast(ubyte[][])arrays, cast(ubyte[])sep); } /// ditto T[] arrayJoin(T)(T[][] arrays) { return cast(T[])join(cast(ubyte[][])arrays); }