blob: 00ee1557d317c600fc033eb8b117d1f57a91605b [file] [log] [blame]
/*
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package flash.swf;
import flash.swf.debug.DebugModule;
import flash.swf.debug.LineRecord;
import flash.swf.debug.RegisterRecord;
import flash.swf.types.FlashUUID;
import flash.util.FileUtils;
import flash.util.IntMap;
import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.File;
import java.util.ArrayList;
import java.net.MalformedURLException;
import java.net.URL;
/**
* The swd file format is as follows
*
* swd(header) (tag)*
*/
public class DebugDecoder
{
public static final int
kDebugScript=0,
kDebugOffset=1,
kDebugBreakpoint=2,
kDebugID=3,
kDebugRegisters=5
;
/**
* table of line numbers, indexed by offset in the SWF file
*/
private SwfDecoder in;
private IntMap modules = new IntMap();
public DebugDecoder(byte[] b)
{
this(new ByteArrayInputStream(b));
}
public DebugDecoder(InputStream in)
{
this.in = new SwfDecoder(in, 0);
}
public void readSwd(DebugHandler h) throws IOException
{
readHeader(h);
readTags(h);
}
void readHeader(DebugHandler handler) throws IOException
{
byte[] sig = new byte[4];
in.readFully(sig);
if (sig[0] != 'F' || sig[1] != 'W' || sig[2] != 'D' || sig[3] < 6)
{
throw new SwfFormatException("not a Flash 6 or later SWD file");
}
in.swfVersion = sig[3];
handler.header(in.swfVersion);
}
public void setTagData(byte[] b) throws IOException
{
in = new SwfDecoder(b, 0);
}
public void readTags(DebugHandler handler) throws IOException
{
// <Object> because it holds groups of {Integer, LineRecord, Integer}
ArrayList<Object> lineRecords = new ArrayList<Object>();
do
{
int tag = (int) in.readUI32();
switch (tag)
{
case kDebugScript:
DebugModule m = new DebugModule();
int id = (int) in.readUI32();
m.id = id;
m.bitmap = (int) in.readUI32();
m.name = in.readString();
m.setText(in.readString());
adjustModuleName(m);
if (modules.contains(id))
{
DebugModule m2 = (DebugModule) modules.get(id);
if (!m.equals(m2))
{
handler.error("Module '" + m2.name + "' has the same ID as Module '" + m.name + "'");
handler.error("Let's check for kDebugOffset that came before Module '" + m2.name + "'");
handler.error("Before: Number of accumulated line records: " + lineRecords.size());
lineRecords = purgeLineRecords(lineRecords, id, handler);
handler.error("After: Number of accumulated line records: " + lineRecords.size());
}
}
modules.put(id, m);
handler.module(m);
break;
case kDebugOffset:
id = (int) in.readUI32();
int lineno = (int) in.readUI32();
DebugModule module = (DebugModule) modules.get(id);
LineRecord lr = new LineRecord(lineno, module);
int offset = (int) in.readUI32();
if (module != null)
{
// not corrupted before we add the offset and offset add fails
boolean wasCorrupt = module.corrupt;
if (!module.addOffset(lr, offset) && !wasCorrupt)
handler.error(module.name+":"+lineno+" does not exist for offset "+offset+", module marked for exclusion from debugging");
handler.offset(offset, lr);
}
else
{
lineRecords.add(new Integer(id));
lineRecords.add(lr);
lineRecords.add(new Integer(offset));
}
break;
case kDebugBreakpoint:
handler.breakpoint((int) in.readUI32());
break;
case kDebugRegisters:
{
offset = (int)in.readUI32();
int size = in.readUI8();
RegisterRecord r = new RegisterRecord(offset, size);
for(int i=0; i<size; i++)
{
int nbr = in.readUI8();
String name = in.readString();
r.addRegister(nbr, name);
}
handler.registers(offset, r);
break;
}
case kDebugID:
FlashUUID uuid = new FlashUUID();
in.readFully(uuid.bytes);
handler.uuid(uuid);
break;
case -1:
break;
default:
throw new SwfFormatException("Unexpected tag id " + tag);
}
if (tag == -1)
{
break;
}
}
while (true);
int i = 0, size = lineRecords.size();
while (i < size)
{
int id = ((Integer) lineRecords.get(i)).intValue();
LineRecord lr = (LineRecord) lineRecords.get(i + 1);
int offset = ((Integer) lineRecords.get(i + 2)).intValue();
lr.module = (DebugModule) modules.get(id);
if (lr.module != null)
{
//System.out.println("updated module "+id+" out of order");
// not corrupted before we add the offset and offset add fails
boolean wasCorrupt = lr.module.corrupt;
if (!lr.module.addOffset(lr, offset) && !wasCorrupt)
handler.error(lr.module.name+":"+lr.lineno+" does not exist for offset "+offset+", module marked for exclusion from debugging");
handler.offset(offset, lr);
}
else
{
handler.error("Could not find debug module (id = " + id + ") for offset = " + offset);
}
i += 3;
}
}
/**
* process any dangling line records that belong to the given module
* @param lineRecords
* @param moduleId
* @param handler
* @return
*/
private ArrayList<Object> purgeLineRecords(ArrayList<Object> lineRecords, final int moduleId, DebugHandler handler)
{
ArrayList<Object> newLineRecords = new ArrayList<Object>();
DebugModule module = (DebugModule) modules.get(moduleId);
int i = 0, size = lineRecords.size();
while (i < size)
{
Integer id = (Integer) lineRecords.get(i);
LineRecord lr = (LineRecord) lineRecords.get(i + 1);
Integer offset = (Integer) lineRecords.get(i + 2);
if (id.intValue() == moduleId)
{
lr.module = module;
if (lr.module != null)
{
lr.module.addOffset(lr, offset.intValue());
handler.offset(offset.intValue(), lr);
}
else
{
handler.error("Could not find kDebugScript with module ID = " + id);
}
}
else
{
newLineRecords.add(id);
newLineRecords.add(lr);
newLineRecords.add(offset);
}
i += 3;
}
return newLineRecords;
}
public static void main(String[] args) throws IOException
{
for (int i=0; i<args.length; i++)
{
// does not need to be buffered because DebugDecoder turns it into a SwfDecoder, which is buffered
InputStream in = new FileInputStream(args[i]);
try
{
new DebugDecoder(in).readSwd(new DebugHandler()
{
public void header(int version)
{
System.out.println("FWD"+version);
}
public void uuid(FlashUUID id)
{
System.out.println("DebugID "+id);
}
public void module(DebugModule dm)
{
System.out.println("DebugScript #" + dm.id + " " + dm.bitmap + " " + dm.name + " (nlines = " +(dm.offsets.length - 1) + ")");
}
public void offset(int offset, LineRecord lr)
{
System.out.println("DebugOffset #" + lr.module.id + ":" + lr.lineno + " " + offset);
}
public void breakpoint(int offset)
{
System.out.println("DebugBreakpoint " + offset);
}
public void registers(int offset, RegisterRecord r)
{
System.out.println("DebugRegisters " + r.toString());
}
public void error(String msg)
{
System.err.println("***ERROR: "+msg);
}
});
System.out.println();
}
finally
{
in.close();
}
}
}
/**
* Royale Enhancement Request: 53160...
*
* If a debug module represents an AS2 class, the module name should be in the form of classname: fileURL
* Matador uses classname: absolutePath (note: absolute, not cannonical)
*
* @param d
*/
protected static final void adjustModuleName(DebugModule d)
{
d.name = adjustModuleName(d.name);
}
public static final String adjustModuleName(String name)
{
if (name.startsWith("<") && name.endsWith(">"))
{
return name;
}
String token1, token2;
// if the url is not malformed, return it
try
{
@SuppressWarnings("unused")
URL u = new URL(name);
// good URL, return...
return name;
}
catch (MalformedURLException ex)
{
// not an URL, continue...
}
File f;
try
{
f = new File(name);
}
catch (java.lang.Error nf)
{
// the CLR will throw this when a java.io.File object is init'd in a location
// that causes a .NET System.SecurityException - can't create File objects on
// .NET as a way of testing whether they are valid files without catching the error
f = null;
}
if (f == null || !f.isFile())
{
int colon = name.indexOf(':');
if (colon != -1)
{
token1 = name.substring(0, colon).trim();
token2 = name.substring(colon + 1).trim();
}
else
{
token1 = "";
token2 = name;
}
}
else
{
token1 = "";
token2 = name;
}
try
{
f = new File(token2);
}
catch (java.lang.Error nf)
{
// the CLR will throw this when a java.io.File object is init'd in a location
// that causes a .NET System.SecurityException - can't create File objects on
// .NET as a way of testing whether they are valid files without catching the error
f = null;
}
if (f != null && f.isFile())
{
try
{
if (token2.indexOf("..") != -1 || token2.indexOf(".") != -1)
{
f = FileUtils.getCanonicalFile(f);
}
token2 = FileUtils.toURL(f).toString();
}
catch (IOException ex)
{
}
}
if (token1.length() == 0)
{
name = token2;
}
else
{
name = token1.trim() + ": " + token2.trim();
}
return name;
}
}