Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e2dbee2e2a | ||
|
|
66bccc6a6f | ||
|
|
22194e5baf | ||
|
|
b95f0ece09 | ||
|
|
b691796c4e | ||
|
|
54349c3a55 | ||
|
|
f0302dcfca | ||
|
|
fdc9246665 | ||
|
|
acbd85f4da | ||
|
|
bd8826c5df | ||
|
|
b54b1cada4 | ||
|
|
60684436a7 | ||
|
|
26f3fb5b53 | ||
|
|
c0a9e75c65 | ||
|
|
23c5d1ba6f | ||
|
|
9892caceab |
@@ -0,0 +1,36 @@
|
||||
RABCDAsm Changelog
|
||||
==================
|
||||
|
||||
RABCDAsm v1.3 (2010.11.11)
|
||||
--------------------------
|
||||
|
||||
* Fixed double precision problem
|
||||
* This also fixes problems with illegal default values for function
|
||||
parameters (default values for integer parameters are stored as doubles,
|
||||
which might become out-of-range due to inadequate double precision)
|
||||
* Added Changelog
|
||||
* Documentation markdown fixes
|
||||
|
||||
RABCDAsm v1.2 (2010.11.06)
|
||||
--------------------------
|
||||
|
||||
* Fixed ref generation for orphan objects which were only referenced
|
||||
by other orphans
|
||||
* Better error handling in `abcexport`; warn when no DoABC tags found
|
||||
* Documentation updates
|
||||
|
||||
RABCDAsm v1.1 (2010.06.30)
|
||||
--------------------------
|
||||
|
||||
* Private namespaces are now referenced by auto-generated names
|
||||
* Use `:` to delimit namespace and name in QNames for consistency
|
||||
* Warning: this breaks compatibility with v1.0 disassemblies
|
||||
* Fixed relative include paths
|
||||
* Add optional byte offsets to labels, which allows lossless representation
|
||||
of jumps inside instructions and outside the function bounds
|
||||
* Documentation updates
|
||||
|
||||
RABCDAsm v1.0 (2010.05.05)
|
||||
--------------------------
|
||||
|
||||
* Initial release.
|
||||
@@ -1,7 +1,7 @@
|
||||
Robust ABC (ActionScript Bytecode) [Dis-]Assembler
|
||||
==================================================
|
||||
|
||||
[RABCDAsm][] is a collection of utilities including an ActionScript
|
||||
[RABCDAsm][] is a collection of utilities including an ActionScript 3
|
||||
assembler/disassembler, and a few tools to manipulate SWF files.
|
||||
These are:
|
||||
|
||||
@@ -31,7 +31,7 @@ Motivation and goals
|
||||
--------------------
|
||||
|
||||
This package was created due to lack of similar software out there.
|
||||
Particularly, I needed an utility which would allow me to edit ActionScript
|
||||
Particularly, I needed an utility which would allow me to edit ActionScript 3
|
||||
bytecode with the following properties:
|
||||
|
||||
1. Speed. Less waiting means more productivity. `rabcasm` can assemble large
|
||||
@@ -94,13 +94,17 @@ To disassemble one of the `.abc` files:
|
||||
rabcdasm file0.abc
|
||||
|
||||
This will create a `file0` directory, which will contain `file0.main.asasm`
|
||||
(the main program file) and a file per ActionScript class.
|
||||
(the main program file), `file0.privatens.asasm` (private namespace alias
|
||||
definitions) and a file per ActionScript class.
|
||||
|
||||
To assemble the `.asasm` files back, and update the SWF file:
|
||||
|
||||
rabcasm file0/file0.main.asasm
|
||||
abcreplace file0.swf 0 file0/file0.main.abc
|
||||
|
||||
The second `abcreplace` argument represents the index of the ABC block in the
|
||||
SWF file, and corresponds to the number in the filename created by `abcexport`.
|
||||
|
||||
Syntax
|
||||
======
|
||||
|
||||
@@ -194,10 +198,11 @@ fields.
|
||||
|
||||
`code` blocks - always declared inline of their `body` block - are somewhat
|
||||
different in syntax from other blocks - mostly in that they may contain labels.
|
||||
Labels follow the most common syntax - a word followed by a `:` character.
|
||||
Multiple instruction arguments are comma-separated. Instruction arguments'
|
||||
types depend on the instruction - see the `OpcodeInfo` array in `abcfile.d`
|
||||
for a reference.
|
||||
Labels follow the most common syntax - a word followed by a `:` character,
|
||||
optionally followed by a relative byte offset (in case of pointers inside
|
||||
instructions). Multiple instruction arguments are comma-separated. Instruction
|
||||
arguments' types depend on the instruction - see the `OpcodeInfo` array in
|
||||
`abcfile.d` for a reference.
|
||||
|
||||
`try` blocks - always declared inline of their `body` block - represent an
|
||||
"exception" (try/catch) block. They contain five mandatory fields: `from`,
|
||||
@@ -233,8 +238,12 @@ namespace sets can be specified using `[]`.
|
||||
|
||||
Namespaces have the syntax *type* `(` *parameters* `)` . For types other than
|
||||
`PrivateNamespace` there is only one parameter - a string. `PrivateNamespace`
|
||||
namespaces have a second parameter - an integer to distinguish this private
|
||||
namespace from others.
|
||||
namespaces have a second parameter, a named alias for a particular private
|
||||
namespace. Internally (the ABC file format), private namespaces are
|
||||
distinguished by a numerical index - `rabcdasm` will attempt to give them
|
||||
descriptive names based on their context. Aliases can be defined using the
|
||||
`#privatens` directive. `rabcdasm` will create a separate file containing the
|
||||
aliases (`file0.privatens.asasm`).
|
||||
|
||||
Strings have a syntax similar to C string literals. Strings start and end with
|
||||
a `"`. Supported escape sequences (a backslash followed by a letter) are `\n`
|
||||
@@ -277,6 +286,7 @@ Directives start with a `#`, followed by a word identifying the directive:
|
||||
* `#set` *word* *string* - assigns the contents of the string to the
|
||||
variable *word*.
|
||||
* `#unset` *word* - deletes the variable *word*.
|
||||
* `#privatens` defines a private namespace alias, as described above.
|
||||
|
||||
### Variables
|
||||
|
||||
@@ -287,9 +297,9 @@ be instantiated in two ways:
|
||||
although variables are defined using a string syntax, they are not
|
||||
inserted as a string using this syntax. Thus, the code:
|
||||
|
||||
#set str "Hello, world!"
|
||||
...
|
||||
pushstring $str
|
||||
#set str "Hello, world!"
|
||||
...
|
||||
pushstring $str
|
||||
|
||||
will expand to `pushstring Hello, world!`, which will result in an error.
|
||||
To correct the problem, add escaped quotes around the variable contents
|
||||
@@ -304,20 +314,20 @@ be instantiated in two ways:
|
||||
Here's an example of how to use the above features to create a macro which
|
||||
logs a string literal and the contents of a register:
|
||||
|
||||
#set log "
|
||||
findpropstrict QName(PackageNamespace(\"\"), \"log\")
|
||||
pushstring $\"1\"
|
||||
getlocal $2
|
||||
callpropvoid QName(PackageNamespace(\"\"), \"log\"), 2
|
||||
"
|
||||
|
||||
; ...
|
||||
|
||||
pushbyte 2
|
||||
pushbyte 2
|
||||
add_i
|
||||
setlocal1
|
||||
#call $"log"("two plus two equals", "1")
|
||||
#set log "
|
||||
findpropstrict QName(PackageNamespace(\"\"), \"log\")
|
||||
pushstring $\"1\"
|
||||
getlocal $2
|
||||
callpropvoid QName(PackageNamespace(\"\"), \"log\"), 2
|
||||
"
|
||||
|
||||
; ...
|
||||
|
||||
pushbyte 2
|
||||
pushbyte 2
|
||||
add_i
|
||||
setlocal1
|
||||
#call $"log"("two plus two equals", "1")
|
||||
|
||||
Highlighting
|
||||
------------
|
||||
@@ -337,19 +347,19 @@ instead of indexes, allowing easy manipulation without having to worry about
|
||||
record order or constant pools. Conversion between various states is done as
|
||||
follows:
|
||||
|
||||
file.abc
|
||||
| ^
|
||||
ABCReader | | ABCWriter
|
||||
v |
|
||||
ABCFile
|
||||
| ^
|
||||
ABCtoAS | | AStoABC
|
||||
v |
|
||||
ASProgram
|
||||
| ^
|
||||
Disassembler | | Assembler
|
||||
v |
|
||||
file.asasm
|
||||
file.abc
|
||||
| ^
|
||||
------ ABCReader | | ABCWriter ----
|
||||
/ v | \
|
||||
/ ABCFile \
|
||||
/ | ^ \
|
||||
rabcdasm---------- ABCtoAS | | AStoABC --------rabcasm
|
||||
\ v | /
|
||||
\ ASProgram /
|
||||
\ | ^ /
|
||||
--- Disassembler | | Assembler ----
|
||||
v |
|
||||
file.asasm
|
||||
|
||||
`AStoABC` will rebuild the constant pools, in a manner similar to Adobe's
|
||||
compilers (reverse-sorted by reference count). The exact order will almost
|
||||
@@ -376,24 +386,30 @@ RABCDAsm users.
|
||||
[Git]: http://git-scm.com/
|
||||
[Mercurial]: http://mercurial.selenic.com/
|
||||
|
||||
2. The [Fiddler][] Web Debugging Proxy can be very useful for analysing
|
||||
2. If you plan on making non-trivial changes to SWF files, you should install
|
||||
the [debug Flash Player][]. This will allow you to see validation and
|
||||
run-time error messages, instead of simply getting a blank rectangle.
|
||||
|
||||
[debug Flash Player]: http://www.adobe.com/support/flashplayer/downloads.html
|
||||
|
||||
3. The [Fiddler][] Web Debugging Proxy can be very useful for analysing
|
||||
websites with SWF content. The following script fragment (which is to be
|
||||
placed in the `OnBeforeResponse` function) will automatically save all SWF
|
||||
files while preserving the directory structure.
|
||||
|
||||
if (oSession.oResponse.headers.ExistsAndContains("Content-Type",
|
||||
"application/x-shockwave-flash")) {
|
||||
// Set desired path here
|
||||
var path:String = "C:\\Temp\\FiddlerCapture\\" +
|
||||
oSession.host + oSession.PathAndQuery;
|
||||
if (path.Contains('?'))
|
||||
path = path.Substring(0, path.IndexOf('?'));
|
||||
var dir:String = Path.GetDirectoryName(path);
|
||||
if (!Directory.Exists(dir))
|
||||
Directory.CreateDirectory(dir);
|
||||
oSession.utilDecodeResponse();
|
||||
oSession.SaveResponseBody(path);
|
||||
}
|
||||
if (oSession.oResponse.headers.ExistsAndContains("Content-Type",
|
||||
"application/x-shockwave-flash")) {
|
||||
// Set desired path here
|
||||
var path:String = "C:\\Temp\\FiddlerCapture\\" +
|
||||
oSession.host + oSession.PathAndQuery;
|
||||
if (path.Contains('?'))
|
||||
path = path.Substring(0, path.IndexOf('?'));
|
||||
var dir:String = Path.GetDirectoryName(path);
|
||||
if (!Directory.Exists(dir))
|
||||
Directory.CreateDirectory(dir);
|
||||
oSession.utilDecodeResponse();
|
||||
oSession.SaveResponseBody(path);
|
||||
}
|
||||
|
||||
Once you have edited a SWF file, you can use Fiddler's [AutoResponder][] to
|
||||
replace the original file with your modified version.
|
||||
@@ -404,28 +420,23 @@ RABCDAsm users.
|
||||
Limitations
|
||||
===========
|
||||
|
||||
1. Metadata is currently ignored. I haven't noticed any metadata blocks in any
|
||||
SWF files I've disassembled.
|
||||
* Metadata is currently ignored. I haven't noticed any metadata blocks in any
|
||||
SWF files I've disassembled.
|
||||
|
||||
2. Private namespaces are currently represented by an automatically-assigned
|
||||
integer. This causes problems when comparing disassemblies from two
|
||||
versions of a file, since those numbers are prone to change when classes
|
||||
are added or removed.
|
||||
* `rabcasm` may create a broken file due to not ordering classes by ancestry.
|
||||
|
||||
3. `rabcasm` may create a broken file due to not ordering classes by ancestry.
|
||||
The problem originates from the fact that a class's ancestors (extended
|
||||
class and implemented interfaces) are stored as multinames, and not as
|
||||
class indices. (This makes sense, since classes may extend objects outside
|
||||
the current ABC file.) Since `rabcasm` currently doesn't decode multinames,
|
||||
it is unaware of the class dependencies, and may thus write the classes out
|
||||
of order. This results in a file that, when opened, will fail to load with
|
||||
an error message similar to:
|
||||
|
||||
The problem originates from the fact that a class's ancestors (extended
|
||||
class and implemented interfaces) are stored as multinames, and not as
|
||||
class indices. (This makes sense, since classes may extend objects outside
|
||||
the current ABC file.) Since `rabcasm` currently doesn't decode multinames,
|
||||
it is unaware of the class dependencies, and may thus write the classes out
|
||||
of order. This results in a file that, when opened, will fail to load with
|
||||
an error message similar to:
|
||||
VerifyError: Error #1014: Class AncestorClassName could not be found.
|
||||
|
||||
VerifyError: Error #1014: Class AncestorClassName could not be found.
|
||||
|
||||
The simple work-around is to re-order the classes as they are declared in
|
||||
the `.main.asasm` file, and place ancestors before descendants.
|
||||
The simple work-around is to re-order the classes as they are declared in
|
||||
the `.main.asasm` file, and place ancestors before descendants.
|
||||
|
||||
License
|
||||
=======
|
||||
|
||||
+22
-16
@@ -21,6 +21,7 @@ module abcexport;
|
||||
import std.file;
|
||||
import std.path;
|
||||
import std.string;
|
||||
import std.stdio;
|
||||
import swffile;
|
||||
|
||||
void main(string[] args)
|
||||
@@ -28,22 +29,27 @@ void main(string[] args)
|
||||
if (args.length == 1)
|
||||
throw new Exception("No file specified");
|
||||
foreach (arg; args[1..$])
|
||||
{
|
||||
scope swf = SWFFile.read(cast(ubyte[])read(arg));
|
||||
uint count;
|
||||
foreach (ref tag; swf.tags)
|
||||
if ((tag.type == TagType.DoABC || tag.type == TagType.DoABC2))
|
||||
{
|
||||
ubyte[] abc;
|
||||
if (tag.type == TagType.DoABC)
|
||||
abc = tag.data;
|
||||
else
|
||||
try
|
||||
{
|
||||
scope swf = SWFFile.read(cast(ubyte[])read(arg));
|
||||
uint count = 0;
|
||||
foreach (ref tag; swf.tags)
|
||||
if ((tag.type == TagType.DoABC || tag.type == TagType.DoABC2))
|
||||
{
|
||||
auto p = tag.data.ptr+4; // skip flags
|
||||
while (*p++) {} // skip name
|
||||
abc = tag.data[p-tag.data.ptr..$];
|
||||
ubyte[] abc;
|
||||
if (tag.type == TagType.DoABC)
|
||||
abc = tag.data;
|
||||
else
|
||||
{
|
||||
auto p = tag.data.ptr+4; // skip flags
|
||||
while (*p++) {} // skip name
|
||||
abc = tag.data[p-tag.data.ptr..$];
|
||||
}
|
||||
write(getName(arg) ~ .toString(count++) ~ ".abc", abc);
|
||||
}
|
||||
write(getName(arg) ~ .toString(count++) ~ ".abc", abc);
|
||||
}
|
||||
}
|
||||
if (count == 0)
|
||||
throw new Exception("No DoABC tags found");
|
||||
}
|
||||
catch (Object o)
|
||||
writefln("Error while processing %s: %s", arg, o);
|
||||
}
|
||||
|
||||
@@ -23,12 +23,7 @@ import std.string : format; // exception formatting
|
||||
/**
|
||||
* Implements a shallow representation of an .abc file.
|
||||
* Loading and saving an .abc file using this class should produce
|
||||
* output identical to the input - with one exception:
|
||||
*
|
||||
* exception_info's "to" field may point inside an instruction.
|
||||
* This is apparently valid according to the implementation, however
|
||||
* the exact offset inside the instruction is not preserved - instead,
|
||||
* the field will point to the beginning of the next instruction.
|
||||
* output identical to the input.
|
||||
*/
|
||||
|
||||
class ABCFile
|
||||
@@ -213,6 +208,20 @@ class ABCFile
|
||||
TraitsInfo[] traits;
|
||||
}
|
||||
|
||||
/// Destination for a jump or exception block boundary
|
||||
struct Label
|
||||
{
|
||||
union
|
||||
{
|
||||
struct
|
||||
{
|
||||
uint index; /// instruction index
|
||||
int offset; /// signed offset relative to said instruction
|
||||
}
|
||||
private int absoluteOffset; /// internal temporary value used during reading and writing
|
||||
}
|
||||
}
|
||||
|
||||
struct Instruction
|
||||
{
|
||||
Opcode opcode;
|
||||
@@ -223,15 +232,15 @@ class ABCFile
|
||||
ulong uintv;
|
||||
uint index;
|
||||
|
||||
uint jumpTarget;
|
||||
uint[] switchTargets;
|
||||
Label jumpTarget;
|
||||
Label[] switchTargets;
|
||||
}
|
||||
Argument[] arguments;
|
||||
}
|
||||
|
||||
struct ExceptionInfo
|
||||
{
|
||||
uint from, to, target;
|
||||
Label from, to, target;
|
||||
uint excType;
|
||||
uint varName;
|
||||
}
|
||||
@@ -1268,25 +1277,32 @@ private final class ABCReader
|
||||
size_t len = readU30();
|
||||
uint[] instructionAtOffset = new uint[len];
|
||||
|
||||
void offsetToIndex(ref uint x, bool relaxed = false)
|
||||
void translateLabel(ref ABCFile.Label label)
|
||||
{
|
||||
if (x >= len)
|
||||
int absoluteOffset = label.absoluteOffset;
|
||||
int instructionOffset = absoluteOffset;
|
||||
while (true)
|
||||
{
|
||||
//throw new Exception(format("Jump out of bounds (by %d bytes)", x - len));
|
||||
// Unreachable OOB jumps seem to be "valid"
|
||||
x = 0;
|
||||
return;
|
||||
}
|
||||
if (relaxed)
|
||||
while (instructionAtOffset[x] == uint.max)
|
||||
if (instructionOffset >= cast(int)len)
|
||||
{
|
||||
x++;
|
||||
if (x >= len)
|
||||
throw new Exception("Relaxed jump inside last instruction");
|
||||
label.index = r.instructions.length;
|
||||
instructionOffset = len;
|
||||
break;
|
||||
}
|
||||
x = instructionAtOffset[x];
|
||||
if (x == uint.max)
|
||||
throw new Exception("Jump inside instruction");
|
||||
if (instructionOffset <= 0)
|
||||
{
|
||||
label.index = 0;
|
||||
instructionOffset = 0;
|
||||
break;
|
||||
}
|
||||
if (instructionAtOffset[instructionOffset] != uint.max)
|
||||
{
|
||||
label.index = instructionAtOffset[instructionOffset];
|
||||
break;
|
||||
}
|
||||
instructionOffset--;
|
||||
}
|
||||
label.offset = absoluteOffset-instructionOffset;
|
||||
}
|
||||
|
||||
{
|
||||
@@ -1333,17 +1349,17 @@ private final class ABCReader
|
||||
|
||||
case OpcodeArgumentType.JumpTarget:
|
||||
int delta = readS24();
|
||||
instruction.arguments[i].jumpTarget = offset + delta;
|
||||
instruction.arguments[i].jumpTarget.absoluteOffset = offset + delta;
|
||||
break;
|
||||
|
||||
case OpcodeArgumentType.SwitchDefaultTarget:
|
||||
instruction.arguments[i].jumpTarget = instructionOffset + readS24();
|
||||
instruction.arguments[i].jumpTarget.absoluteOffset = instructionOffset + readS24();
|
||||
break;
|
||||
|
||||
case OpcodeArgumentType.SwitchTargets:
|
||||
instruction.arguments[i].switchTargets.length = readU30()+1;
|
||||
foreach (ref off; instruction.arguments[i].switchTargets)
|
||||
off = instructionOffset + readS24();
|
||||
foreach (ref label; instruction.arguments[i].switchTargets)
|
||||
label.absoluteOffset = instructionOffset + readS24();
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -1363,13 +1379,11 @@ private final class ABCReader
|
||||
{
|
||||
case OpcodeArgumentType.JumpTarget:
|
||||
case OpcodeArgumentType.SwitchDefaultTarget:
|
||||
pos = start + instructionOffsets[ii];
|
||||
offsetToIndex(instruction.arguments[i].jumpTarget);
|
||||
translateLabel(instruction.arguments[i].jumpTarget);
|
||||
break;
|
||||
case OpcodeArgumentType.SwitchTargets:
|
||||
pos = start + instructionOffsets[ii];
|
||||
foreach (ref x; instruction.arguments[i].switchTargets)
|
||||
offsetToIndex(x);
|
||||
translateLabel(x);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -1381,9 +1395,9 @@ private final class ABCReader
|
||||
foreach (ref value; r.exceptions)
|
||||
{
|
||||
value = readExceptionInfo();
|
||||
offsetToIndex(value.from);
|
||||
offsetToIndex(value.to, true);
|
||||
offsetToIndex(value.target);
|
||||
translateLabel(value.from);
|
||||
translateLabel(value.to);
|
||||
translateLabel(value.target);
|
||||
}
|
||||
r.traits.length = readU30();
|
||||
foreach (ref value; r.traits)
|
||||
@@ -1394,9 +1408,9 @@ private final class ABCReader
|
||||
ABCFile.ExceptionInfo readExceptionInfo()
|
||||
{
|
||||
ABCFile.ExceptionInfo r;
|
||||
r.from = readU30();
|
||||
r.to = readU30();
|
||||
r.target = readU30();
|
||||
r.from.absoluteOffset = readU30();
|
||||
r.to.absoluteOffset = readU30();
|
||||
r.target.absoluteOffset = readU30();
|
||||
r.excType = readU30();
|
||||
r.varName = readU30();
|
||||
return r;
|
||||
@@ -1731,7 +1745,9 @@ private final class ABCWriter
|
||||
writeU30(v.initScopeDepth);
|
||||
writeU30(v.maxScopeDepth);
|
||||
|
||||
uint[] instructionOffsets = new uint[v.instructions.length];
|
||||
uint[] instructionOffsets = new uint[v.instructions.length+1];
|
||||
|
||||
uint resolveLabel(ref ABCFile.Label label) { return instructionOffsets[label.index]+label.offset; }
|
||||
|
||||
{
|
||||
// we don't know the length before writing all the instructions - swap buffer with a temporary one
|
||||
@@ -1740,7 +1756,7 @@ private final class ABCWriter
|
||||
buf = new ubyte[1024];
|
||||
pos = 0;
|
||||
|
||||
struct Fixup { uint target, pos, base; }
|
||||
struct Fixup { ABCFile.Label target; uint pos, base; }
|
||||
Fixup[] fixups;
|
||||
|
||||
foreach (ii, ref instruction; v.instructions)
|
||||
@@ -1807,11 +1823,12 @@ private final class ABCWriter
|
||||
}
|
||||
|
||||
buf.length = pos;
|
||||
instructionOffsets[v.instructions.length] = pos;
|
||||
|
||||
foreach (ref fixup; fixups)
|
||||
{
|
||||
pos = fixup.pos;
|
||||
writeS24(instructionOffsets[fixup.target]-fixup.base);
|
||||
writeS24(resolveLabel(fixup.target)-fixup.base);
|
||||
}
|
||||
|
||||
auto code = buf;
|
||||
@@ -1825,9 +1842,9 @@ private final class ABCWriter
|
||||
writeU30(v.exceptions.length);
|
||||
foreach (ref value; v.exceptions)
|
||||
{
|
||||
value.from = instructionOffsets[value.from];
|
||||
value.to = instructionOffsets[value.to];
|
||||
value.target = instructionOffsets[value.target];
|
||||
value.from.absoluteOffset = resolveLabel(value.from);
|
||||
value.to.absoluteOffset = resolveLabel(value.to);
|
||||
value.target.absoluteOffset = resolveLabel(value.target);
|
||||
writeExceptionInfo(value);
|
||||
}
|
||||
writeU30(v.traits.length);
|
||||
@@ -1837,9 +1854,9 @@ private final class ABCWriter
|
||||
|
||||
void writeExceptionInfo(ABCFile.ExceptionInfo v)
|
||||
{
|
||||
writeU30(v.from);
|
||||
writeU30(v.to);
|
||||
writeU30(v.target);
|
||||
writeU30(v.from.absoluteOffset);
|
||||
writeU30(v.to.absoluteOffset);
|
||||
writeU30(v.target.absoluteOffset);
|
||||
writeU30(v.excType);
|
||||
writeU30(v.varName);
|
||||
}
|
||||
|
||||
@@ -52,6 +52,7 @@
|
||||
<word name="#get"/>
|
||||
<word name="#include"/>
|
||||
<word name="#mixin"/>
|
||||
<word name="#privatens"/>
|
||||
<word name="#set"/>
|
||||
<word name="#unset"/>
|
||||
</keywords>
|
||||
|
||||
+3
-3
@@ -250,15 +250,15 @@ final class ASProgram
|
||||
Class classv;
|
||||
Method methodv;
|
||||
|
||||
uint jumpTarget;
|
||||
uint[] switchTargets;
|
||||
ABCFile.Label jumpTarget;
|
||||
ABCFile.Label[] switchTargets;
|
||||
}
|
||||
Argument[] arguments;
|
||||
}
|
||||
|
||||
struct Exception
|
||||
{
|
||||
uint from, to, target;
|
||||
ABCFile.Label from, to, target;
|
||||
Multiname excType;
|
||||
Multiname varName;
|
||||
}
|
||||
|
||||
+64
-31
@@ -21,6 +21,7 @@ module assembler;
|
||||
import std.file;
|
||||
import std.string;
|
||||
import std.conv;
|
||||
import std.path;
|
||||
import abcfile;
|
||||
import asprogram;
|
||||
|
||||
@@ -46,16 +47,22 @@ final class Assembler
|
||||
char* pos;
|
||||
char* end;
|
||||
string[] arguments;
|
||||
string basePath;
|
||||
|
||||
static File load(string filename, string[] arguments = null)
|
||||
{
|
||||
return fromData(filename, cast(string)read(filename), arguments);
|
||||
return fromFile(filename, cast(string)read(filename), arguments);
|
||||
}
|
||||
|
||||
static File fromFile(string filename, string data, string[] arguments = null)
|
||||
{
|
||||
return fromData(filename, data, arguments, getDirName(filename));
|
||||
}
|
||||
|
||||
static File fromData(string name, string data, string[] arguments = null)
|
||||
static File fromData(string name, string data, string[] arguments = null, string basePath = null)
|
||||
{
|
||||
data ~= \0; data = data[0..$-1]; // hack to prevent readWord etc. from checking for end-of-file on every character
|
||||
return File(name, data, data.ptr, data.ptr + data.length, arguments);
|
||||
return File(name, data, data.ptr, data.ptr + data.length, arguments, basePath);
|
||||
}
|
||||
|
||||
Position position()
|
||||
@@ -79,8 +86,14 @@ final class Assembler
|
||||
File[64] files;
|
||||
int fileCount; /// recursion depth
|
||||
|
||||
string basePath;
|
||||
|
||||
string getBasePath()
|
||||
{
|
||||
foreach (ref file; files[0..fileCount])
|
||||
if (file.basePath !is null)
|
||||
return file.basePath;
|
||||
return null;
|
||||
}
|
||||
|
||||
string convertFilename(string filename)
|
||||
{
|
||||
if (filename.length == 0)
|
||||
@@ -89,18 +102,9 @@ final class Assembler
|
||||
foreach (ref c; filename)
|
||||
if (c == '\\')
|
||||
c = '/';
|
||||
version(Windows)
|
||||
{
|
||||
if (filename.length > 2 && filename[1] == ':')
|
||||
return filename;
|
||||
}
|
||||
if (filename[0] == '/')
|
||||
return filename;
|
||||
return basePath ~ filename;
|
||||
return std.path.join(getBasePath, filename);
|
||||
}
|
||||
|
||||
string[string] vars;
|
||||
|
||||
void skipWhitespace()
|
||||
{
|
||||
while (true)
|
||||
@@ -126,6 +130,9 @@ final class Assembler
|
||||
}
|
||||
}
|
||||
|
||||
string[string] vars;
|
||||
uint[string] privateNamespaces;
|
||||
|
||||
void handlePreprocessor()
|
||||
{
|
||||
skipChar(); // #
|
||||
@@ -143,7 +150,7 @@ final class Assembler
|
||||
break;
|
||||
case "get":
|
||||
auto filename = convertFilename(readString());
|
||||
pushFile(File.fromData(filename, toStringLiteral(cast(string)read(filename))));
|
||||
pushFile(File.fromFile(filename, toStringLiteral(cast(string)read(filename))));
|
||||
break;
|
||||
case "set":
|
||||
vars[readWord()] = readString();
|
||||
@@ -151,6 +158,10 @@ final class Assembler
|
||||
case "unset":
|
||||
vars.remove(readWord());
|
||||
break;
|
||||
case "privatens":
|
||||
uint index = cast(uint)readUInt();
|
||||
privateNamespaces[readString()] = index;
|
||||
break;
|
||||
default:
|
||||
files[0].pos -= word.length;
|
||||
throw new Exception("Unknown preprocessor declaration: " ~ word);
|
||||
@@ -545,7 +556,11 @@ final class Assembler
|
||||
if (n.kind == ASType.PrivateNamespace)
|
||||
{
|
||||
expectChar(',');
|
||||
n.privateIndex = cast(uint)readUInt();
|
||||
string name = readString();
|
||||
auto pindex = name in privateNamespaces;
|
||||
if (pindex is null)
|
||||
throw new Exception("Unknown private namespace name");
|
||||
n.privateIndex = *pindex;
|
||||
}
|
||||
expectChar(')');
|
||||
return n;
|
||||
@@ -915,6 +930,24 @@ final class Assembler
|
||||
}
|
||||
}
|
||||
|
||||
ABCFile.Label parseLabel(string label, uint[string] labels)
|
||||
{
|
||||
string name = label;
|
||||
int offset = 0;
|
||||
foreach (i, c; label)
|
||||
if (c=='-' || c=='+')
|
||||
{
|
||||
name = label[0..i];
|
||||
offset = .toInt(label[i..$]);
|
||||
break;
|
||||
}
|
||||
auto lp = name in labels;
|
||||
if (lp is null)
|
||||
throw new Exception("Unknown label " ~ name);
|
||||
|
||||
return ABCFile.Label(*lp, offset);
|
||||
}
|
||||
|
||||
ASProgram.Instruction[] readInstructions(ref uint[string] _labels)
|
||||
{
|
||||
ASProgram.Instruction[] instructions;
|
||||
@@ -994,7 +1027,7 @@ final class Assembler
|
||||
|
||||
case OpcodeArgumentType.SwitchTargets:
|
||||
string[] switchTargetLabels = readList!('[', ']', readWord, false)();
|
||||
instruction.arguments[i].switchTargets = new uint[switchTargetLabels.length];
|
||||
instruction.arguments[i].switchTargets.length = switchTargetLabels.length;
|
||||
foreach (li, s; switchTargetLabels)
|
||||
switchFixups ~= LocalFixup(files[0].position, instructions.length, i, s, li);
|
||||
break;
|
||||
@@ -1011,24 +1044,24 @@ final class Assembler
|
||||
|
||||
foreach (ref f; jumpFixups)
|
||||
{
|
||||
auto lp = f.name in labels;
|
||||
if (lp is null)
|
||||
try
|
||||
instructions[f.ii].arguments[f.ai].jumpTarget = parseLabel(f.name, labels);
|
||||
catch (Object o)
|
||||
{
|
||||
setFile(f.where.load);
|
||||
throw new Exception("Unknown label " ~ f.name);
|
||||
throw o;
|
||||
}
|
||||
instructions[f.ii].arguments[f.ai].jumpTarget = *lp;
|
||||
}
|
||||
|
||||
foreach (ref f; switchFixups)
|
||||
{
|
||||
auto lp = f.name in labels;
|
||||
if (lp is null)
|
||||
try
|
||||
instructions[f.ii].arguments[f.ai].switchTargets[f.si] = parseLabel(f.name, labels);
|
||||
catch (Object o)
|
||||
{
|
||||
setFile(f.where.load);
|
||||
throw new Exception("Unknown label " ~ f.name);
|
||||
throw o;
|
||||
}
|
||||
instructions[f.ii].arguments[f.ai].switchTargets[f.si] = *lp;
|
||||
}
|
||||
|
||||
foreach (ref f; localClassFixups)
|
||||
@@ -1042,16 +1075,16 @@ final class Assembler
|
||||
|
||||
ASProgram.Exception readException(uint[string] labels)
|
||||
{
|
||||
uint readLabel()
|
||||
ABCFile.Label readLabel()
|
||||
{
|
||||
auto word = readWord();
|
||||
auto plabel = word in labels;
|
||||
if (plabel is null)
|
||||
try
|
||||
return parseLabel(word, labels);
|
||||
catch (Object o)
|
||||
{
|
||||
backpedal(word.length);
|
||||
throw new Exception("Unknown label " ~ word);
|
||||
throw o;
|
||||
}
|
||||
return *plabel;
|
||||
}
|
||||
|
||||
ASProgram.Exception e;
|
||||
|
||||
+171
-34
@@ -95,6 +95,9 @@ final class RefBuilder : ASTraitsVisitor
|
||||
ASProgram.Class[string] classByName;
|
||||
ASProgram.Method[string] methodByName;
|
||||
|
||||
string[uint] privateNamespaceNames;
|
||||
uint[string] privateNamespaceByName;
|
||||
|
||||
ASProgram.Multiname[] context;
|
||||
|
||||
this(ASProgram as)
|
||||
@@ -106,12 +109,23 @@ final class RefBuilder : ASTraitsVisitor
|
||||
{
|
||||
foreach (i, ref v; as.scripts)
|
||||
addMethod(v.sinit, "script" ~ .toString(i) ~ "_sinit");
|
||||
foreach (vclass; as.orphanClasses)
|
||||
addClass(vclass, "orphan");
|
||||
foreach (method; as.orphanMethods)
|
||||
addMethod(method, "orphan");
|
||||
super.run();
|
||||
}
|
||||
|
||||
override void visitTrait(ref ASProgram.Trait trait)
|
||||
{
|
||||
context ~= trait.name;
|
||||
auto m = trait.name;
|
||||
|
||||
if (m.kind != ASType.QName)
|
||||
throw new Exception("Trait name is not a QName");
|
||||
|
||||
visitMultiname(m);
|
||||
|
||||
context ~= m;
|
||||
switch (trait.kind)
|
||||
{
|
||||
case TraitKind.Class:
|
||||
@@ -136,16 +150,96 @@ final class RefBuilder : ASTraitsVisitor
|
||||
context = context[0..$-1];
|
||||
}
|
||||
|
||||
string addPrivateNamespace(uint index, string bname)
|
||||
{
|
||||
string name = bname;
|
||||
{
|
||||
int n = 0;
|
||||
uint* pindex;
|
||||
while ((pindex = name in privateNamespaceByName) !is null && *pindex != index)
|
||||
name = bname ~ .toString(++n);
|
||||
}
|
||||
auto pname = index in privateNamespaceNames;
|
||||
if (pname)
|
||||
{
|
||||
if (*pname != name)
|
||||
throw new Exception("Ambiguous private namespace: " ~ *pname ~ " and " ~ name);
|
||||
}
|
||||
else
|
||||
{
|
||||
privateNamespaceNames[index] = name;
|
||||
privateNamespaceByName[name] = index;
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
void visitNamespace(ASProgram.Namespace ns)
|
||||
{
|
||||
if (ns.kind == ASType.PrivateNamespace && context.length>0 && context[0].vQName.ns.kind != ASType.PrivateNamespace)
|
||||
addPrivateNamespace(ns.privateIndex, qNameToString(context[0]));
|
||||
}
|
||||
|
||||
void visitNamespaceSet(ASProgram.Namespace[] nsSet)
|
||||
{
|
||||
foreach (ns; nsSet)
|
||||
visitNamespace(ns);
|
||||
}
|
||||
|
||||
void visitMultiname(ASProgram.Multiname m)
|
||||
{
|
||||
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;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static string qNameToString(ASProgram.Multiname m)
|
||||
{
|
||||
assert(m.kind == ASType.QName);
|
||||
return (m.vQName.ns.name.length ? m.vQName.ns.name ~ ":" : "") ~ m.vQName.name;
|
||||
}
|
||||
|
||||
string contextToString(string field)
|
||||
{
|
||||
string[] strings = new string[context.length + (field ? 1 : 0)];
|
||||
foreach (i, m; context)
|
||||
{
|
||||
// should this check ever fail, it's easy to fix it - just build any unique-ish string from the context
|
||||
if (m.kind != ASType.QName)
|
||||
throw new Exception("Trait name is not a QName");
|
||||
strings[i] = (m.vQName.ns.name.length ? m.vQName.ns.name ~ "." : "") ~ m.vQName.name;
|
||||
}
|
||||
strings[i] = qNameToString(m);
|
||||
if (field)
|
||||
strings[$-1] = field;
|
||||
string s = join(strings, "/");
|
||||
@@ -167,9 +261,9 @@ final class RefBuilder : ASTraitsVisitor
|
||||
return uniqueName;
|
||||
}
|
||||
|
||||
void addClass(ASProgram.Class vclass)
|
||||
void addClass(ASProgram.Class vclass, string field = null)
|
||||
{
|
||||
addObject(vclass, classByName, string.init);
|
||||
addObject(vclass, classByName, field);
|
||||
addMethod(vclass.cinit, "cinit");
|
||||
addMethod(vclass.instance.iinit, "iinit");
|
||||
}
|
||||
@@ -177,6 +271,8 @@ final class RefBuilder : ASTraitsVisitor
|
||||
void addMethod(ASProgram.Method method, string field = null)
|
||||
{
|
||||
addObject(method, methodByName, field);
|
||||
if (method.vbody)
|
||||
visitMethodBody(method.vbody);
|
||||
}
|
||||
|
||||
string getObjectName(T)(T obj, ref T[string] objByName)
|
||||
@@ -197,6 +293,16 @@ final class RefBuilder : ASTraitsVisitor
|
||||
{
|
||||
return getObjectName(method, methodByName);
|
||||
}
|
||||
|
||||
string getPrivateNamespaceName(uint index)
|
||||
{
|
||||
auto pname = index in privateNamespaceNames;
|
||||
if (pname)
|
||||
return *pname;
|
||||
else
|
||||
//throw new Exception("Nameless private namespace: " ~ .toString(index));
|
||||
return addPrivateNamespace(index, "OrphanPrivateNamespace");
|
||||
}
|
||||
}
|
||||
|
||||
final class Disassembler
|
||||
@@ -221,6 +327,10 @@ final class Disassembler
|
||||
|
||||
StringBuilder sb = new StringBuilder(name ~ "/" ~ name ~ ".main.asasm");
|
||||
|
||||
sb ~= "#include ";
|
||||
dumpString(sb, name ~ ".privatens.asasm");
|
||||
sb.newLine();
|
||||
|
||||
sb ~= "program";
|
||||
sb.indent++; sb.newLine();
|
||||
|
||||
@@ -272,6 +382,17 @@ final class Disassembler
|
||||
sb ~= "end ; program"; sb.newLine();
|
||||
|
||||
sb.save();
|
||||
|
||||
// now dump the private namespace indices
|
||||
sb = new StringBuilder(name ~ "/" ~ name ~ ".privatens.asasm");
|
||||
uint[] indices = refs.privateNamespaceNames.keys.sort;
|
||||
foreach (index; indices)
|
||||
{
|
||||
sb ~= "#privatens " ~ .toString(index) ~ " ";
|
||||
dumpString(sb, refs.privateNamespaceNames[index]);
|
||||
sb.newLine();
|
||||
}
|
||||
sb.save();
|
||||
}
|
||||
|
||||
void dumpInt(StringBuilder sb, long v)
|
||||
@@ -295,7 +416,7 @@ final class Disassembler
|
||||
if (v == ABCFile.NULL_DOUBLE)
|
||||
sb ~= "null";
|
||||
else
|
||||
sb ~= .toString(v);
|
||||
sb ~= format("%.18g", v);
|
||||
}
|
||||
|
||||
void dumpString(StringBuilder sb, string str)
|
||||
@@ -345,7 +466,7 @@ final class Disassembler
|
||||
if (kind == ASType.PrivateNamespace)
|
||||
{
|
||||
sb ~= ", ";
|
||||
dumpUInt(sb, privateIndex);
|
||||
dumpString(sb, refs.getPrivateNamespaceName(privateIndex));
|
||||
}
|
||||
sb ~= ')';
|
||||
}
|
||||
@@ -600,10 +721,10 @@ final class Disassembler
|
||||
{
|
||||
string filename = refid.dup;
|
||||
foreach (ref c; filename)
|
||||
if (c == '.')
|
||||
if (c == '.' || c == ':')
|
||||
c = '/';
|
||||
else
|
||||
if (c == '\\' || c == ':' || c == '*' || c == '?' || c == '"' || c == '<' || c == '>' || c == '|')
|
||||
if (c == '\\' || c == '*' || c == '?' || c == '"' || c == '<' || c == '>' || c == '|')
|
||||
c = '_';
|
||||
|
||||
version (Windows)
|
||||
@@ -701,6 +822,18 @@ final class Disassembler
|
||||
sb.newLine();
|
||||
}
|
||||
|
||||
void dumpLabel(StringBuilder sb, ref ABCFile.Label label)
|
||||
{
|
||||
sb ~= 'L';
|
||||
sb ~= .toString(label.index);
|
||||
if (label.offset != 0)
|
||||
{
|
||||
if (label.offset > 0)
|
||||
sb ~= '+';
|
||||
sb ~= .toString(label.offset);
|
||||
}
|
||||
}
|
||||
|
||||
void dumpMethodBody(StringBuilder sb, ASProgram.MethodBody mbody)
|
||||
{
|
||||
sb ~= "body";
|
||||
@@ -712,22 +845,22 @@ final class Disassembler
|
||||
sb ~= "code";
|
||||
sb.newLine();
|
||||
|
||||
bool[] labels = new bool[mbody.instructions.length];
|
||||
bool[] labels = new bool[mbody.instructions.length+1];
|
||||
// reserve exception labels
|
||||
foreach (ref e; mbody.exceptions)
|
||||
labels[e.from] = labels[e.to] = labels[e.target] = true;
|
||||
labels[e.from.index] = labels[e.to.index] = labels[e.target.index] = true;
|
||||
dumpInstructions(sb, mbody.instructions, labels);
|
||||
|
||||
sb ~= "end ; code";
|
||||
sb.newLine();
|
||||
foreach (ref e; mbody.exceptions)
|
||||
{
|
||||
sb ~= "try from L";
|
||||
sb ~= .toString(e.from);
|
||||
sb ~= " to L";
|
||||
sb ~= .toString(e.to);
|
||||
sb ~= " target L";
|
||||
sb ~= .toString(e.target);
|
||||
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 ";
|
||||
@@ -748,22 +881,18 @@ final class Disassembler
|
||||
{
|
||||
case OpcodeArgumentType.JumpTarget:
|
||||
case OpcodeArgumentType.SwitchDefaultTarget:
|
||||
labels[instruction.arguments[i].jumpTarget] = true;
|
||||
labels[instruction.arguments[i].jumpTarget.index] = true;
|
||||
break;
|
||||
case OpcodeArgumentType.SwitchTargets:
|
||||
foreach (ref x; instruction.arguments[i].switchTargets)
|
||||
labels[x] = true;
|
||||
foreach (ref label; instruction.arguments[i].switchTargets)
|
||||
labels[label.index] = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
bool extraNewLine = false;
|
||||
foreach (ii, ref instruction; instructions)
|
||||
|
||||
void checkLabel(uint ii)
|
||||
{
|
||||
if (extraNewLine)
|
||||
sb.newLine();
|
||||
extraNewLine = newLineAfter[instruction.opcode];
|
||||
|
||||
if (labels[ii])
|
||||
{
|
||||
sb.noIndent();
|
||||
@@ -772,6 +901,15 @@ final class Disassembler
|
||||
sb ~= ':';
|
||||
sb.newLine();
|
||||
}
|
||||
}
|
||||
|
||||
bool extraNewLine = false;
|
||||
foreach (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;
|
||||
@@ -823,8 +961,7 @@ final class Disassembler
|
||||
|
||||
case OpcodeArgumentType.JumpTarget:
|
||||
case OpcodeArgumentType.SwitchDefaultTarget:
|
||||
sb ~= 'L';
|
||||
sb ~= .toString(instruction.arguments[i].jumpTarget);
|
||||
dumpLabel(sb, instruction.arguments[i].jumpTarget);
|
||||
break;
|
||||
|
||||
case OpcodeArgumentType.SwitchTargets:
|
||||
@@ -832,8 +969,7 @@ final class Disassembler
|
||||
auto targets = instruction.arguments[i].switchTargets;
|
||||
foreach (ti, t; targets)
|
||||
{
|
||||
sb ~= 'L';
|
||||
sb ~= .toString(t);
|
||||
dumpLabel(sb, t);
|
||||
if (ti < targets.length-1)
|
||||
sb ~= ", ";
|
||||
}
|
||||
@@ -849,6 +985,7 @@ final class Disassembler
|
||||
}
|
||||
sb.newLine();
|
||||
}
|
||||
checkLabel(instructions.length);
|
||||
sb.indent--;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user