Mozilla/mozilla/js/tamarin/core/avmplusDebugger.cpp
wsharp%adobe.com 039e5a464d bug 375561. stejohns+ review. Various fixes from Flash player codebase
git-svn-id: svn://10.0.0.236/trunk@222472 18797224-902f-48f8-a5cc-f745e15eee43
2007-03-27 18:37:45 +00:00

841 lines
22 KiB
C++

/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (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.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is [Open Source Virtual Machine.].
*
* The Initial Developer of the Original Code is
* Adobe System Incorporated.
* Portions created by the Initial Developer are Copyright (C) 1993-2006
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Adobe AS3 Team
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "avmplus.h"
#ifdef DEBUGGER
namespace avmplus
{
using namespace MMgc;
Debugger::TraceLevel Debugger::astrace = Debugger::TRACE_OFF;
uint64 Debugger::astraceStartTime = OSDep::currentTimeMillis();
Debugger::Debugger(AvmCore *core)
: core(core)
, abcList(core->GetGC())
, pool2abcIndex()
{
}
void Debugger::stepInto()
{
stepState.flag = true;
stepState.depth = -1;
// Check that core and core->callStack are non-null before dereferencing
// them, so that it's possible to call stepInto() even before there is
// a stackframe (so execution will stop at the first executed line of code).
stepState.startingDepth = (core && core->callStack) ? core->callStack->depth : 0;
}
void Debugger::stepOver()
{
stepState.flag = true;
stepState.depth = core->callStack->depth;
stepState.startingDepth = core->callStack->depth;
}
void Debugger::stepOut()
{
stepState.flag = true;
stepState.depth = core->callStack->depth - 1;
stepState.startingDepth = core->callStack->depth;
}
void Debugger::stepContinue()
{
// Restore the previous stepping state
stepState = oldStepState;
}
/**
* Set a breakpoint in a particular source flie
* NOTE: if the 'same' source file appears in
* multiple abc files, it is up to the caller
* to ensure that this call is performed for each
* SourceInfo object.
*/
bool Debugger::breakpointSet(SourceInfo* source, int linenum)
{
return source->setBreakpoint(linenum);
}
/**
* Clear a breakpoint on a particular source file
*/
bool Debugger::breakpointClear(SourceInfo* source, int linenum)
{
return source->clearBreakpoint(linenum);
}
void Debugger::debugLine(int linenum)
{
AvmAssert( core->callStack !=0 );
if (!core->callStack)
return;
AvmAssert(linenum > 0);
int prev = core->callStack->linenum;
core->callStack->linenum = linenum;
int line = linenum;
// line number has changed
bool changed = (prev == line) ? false : true;
bool exited = (prev == -1) ? true : false; // are we being called as a result of function exit?
if (!changed && !exited)
return; // still on the same line in the same function?
if (core->profiler->profilingDataWanted && core->profiler->profileSwitch && !core->sampler()->sampling)
{
core->profiler->sendLineTimestamp(line);
}
// tracing information
traceLine(line);
// check if we should stop due to breakpoint or step
bool stop = false;
if (stepState.flag)
{
if (stepState.startingDepth != -1 && core->callStack->depth < stepState.startingDepth)
{
// We stepped out of whatever function was executing when the
// stepInto/stepOver/stepOut command was executed. We may be
// in the middle of a line of code, but we still want to stop
// immediately. See bug 126633.
stop = true;
}
else if (!exited && (stepState.depth == -1 || core->callStack->depth <= stepState.depth) )
{
// We reached the beginning of a new line of code.
stop = true;
}
}
// we didn't decide to stop due to a step, but check if we hit a breakpoint
if (!stop && !exited)
{
AbstractFunction* f = core->callStack->info;
if ( (f->flags & AbstractFunction::ABSTRACT_METHOD) == 0)
{
MethodInfo* m = (MethodInfo*)f;
AbcFile* abc = m->getFile();
if (abc)
{
SourceFile* source = abc->sourceNamed( core->callStack->filename );
if (source && source->hasBreakpoint(line))
{
stop = true;
}
}
}
}
// we still haven't decided to stop; check our watchpoints
if (!stop && !exited)
{
if (hitWatchpoint())
stop = true;
}
if (stop)
{
// Terminate whatever step operation may have been happening. But first,
// save the state of the step, so that if someone calls stepContinue(),
// then we can restore it.
StepState oldOldStepState = oldStepState; // save oldStepState in case of reentrancy
oldStepState = stepState; // save stepState so that stepContinue() can find it
stepState.clear(); // turn off stepping
enterDebugger();
oldStepState = oldOldStepState; // restore oldStepState
}
}
void Debugger::debugFile(Stringp filename)
{
AvmAssert( core->callStack != 0 );
if (!core->callStack)
return;
AvmAssert(filename != 0);
Stringp prev = core->callStack->filename;
core->callStack->filename = filename;
// filename changed
if (prev != filename)
{
if (core->profiler->profilingDataWanted && !core->sampler()->sampling)
{
UTF8String* sourceFile = filename->toUTF8String();
core->profiler->sendDebugFileUrl(sourceFile);
}
}
}
void Debugger::debugMethod(MethodEnv* /*env*/)
{
// nop
}
void Debugger::_debugMethod(MethodEnv* env)
{
traceMethod(env->method);
// can't debug native methods
if ( !(env->method->flags & MethodInfo::NATIVE) )
debugMethod(env);
}
void Debugger::traceMethod(AbstractFunction* fnc, bool ignoreArgs)
{
if (astrace > TRACE_OFF)
{
if (fnc)
{
// disable trace temporarily so associated allocs don't appear
TraceLevel lvl = astrace;
astrace = TRACE_OFF;
// WARNING: don't change the format of output since outside utils depend on it
uint64 delta = OSDep::currentTimeMillis() - astraceStartTime;
core->console << (uint32)(delta) << " AVMINF: MTHD ";
if (fnc->name && (fnc->name->length() > 0) )
core->console << fnc->name;
else
core->console << "<unknown>";
core->console << " (";
if (!ignoreArgs && core->callStack && (lvl == TRACE_METHODS_WITH_ARGS || lvl == TRACE_METHODS_AND_LINES_WITH_ARGS))
{
DebugStackFrame* frame = (DebugStackFrame*)frameAt(0);
int count;
Atom* arr;
if (frame && frame->arguments(arr, count))
{
for(int i=0; i<count; i++)
{
core->console << core->format (arr[i]);
if (i+1 < count)
core->console << ", ";
}
}
}
core->console << ")";
if (!fnc->isFlagSet(AbstractFunction::SUGGEST_INTERP))
{
core->console << " @ 0x";
core->console.writeHexAddr( (uintptr)fnc->impl32);
}
core->console << "\n";
astrace = lvl;
}
}
}
void Debugger::traceLine(int line)
{
if (astrace >= TRACE_METHODS_AND_LINES)
{
Stringp file = core->callStack->filename;
TraceLevel lvl = astrace;
astrace = TRACE_OFF;
// WARNING: don't change the format of output since outside utils depend on it
uint64 delta = OSDep::currentTimeMillis() - astraceStartTime;
core->console << (uint32)(delta) << " AVMINF: LINE ";
if (file)
core->console << " " << line << "\t\t " << file << "\n";
else
core->console << " " << line << "\t\t ??? \n";
astrace = lvl;
}
}
/**
* Called when an abc file is first decoded.
* This method builds a list of source files
* and methods, etc from the given abc file.
*/
void Debugger::processAbc(PoolObject* pool, ScriptBuffer code)
{
// first off we build an AbcInfo object
AbcFile* abc = new (core->GetGC()) AbcFile(core, code.getSize());
// now let's scan the abc resources pulling out what we need
scanResources(abc, pool);
// build a bridging table from pools to abcs
int index = abcList.size();
pool2abcIndex.add(pool, (const void*)index);
// at this point our abc object has been populated with
// source file objects and should ready to go.
// so we add it to the list and we are done
abcList.add(abc);
}
/**
* Scans the pool object and pulls out information about the abc file
* placing it in the AbcFile
*/
void Debugger::scanResources(AbcFile* file, PoolObject* pool)
{
// walk all methods
int mCount = pool->methodCount;
for(int i=0; i<mCount; i++)
{
AbstractFunction* f = pool->methods[i];
if (f->hasMethodBody())
{
// yes there is code for this method
MethodInfo* m = (MethodInfo*)f;
if (m->body_pos)
{
// if body_pos is null we havent got the body yet or
// this is an interface method
scanCode(file, pool, m);
}
}
}
}
/**
* Scans the bytecode and adds source level information to
* the abc info object
*/
bool Debugger::scanCode(AbcFile* file, PoolObject* pool, MethodInfo* m)
{
const byte *abc_start = &m->pool->code()[0];
const byte *pos = m->body_pos;
m->setFile(file);
AvmCore::readU30(pos); // max_stack
AvmCore::readU30(pos); // local_count;
AvmCore::readU30(pos); // init_stack_depth;
AvmCore::readU30(pos); // max_stack_depth;
int code_len = AvmCore::readU30(pos);
const byte *start = pos;
const byte *end = pos + code_len;
int size = 0;
int op_count;
SourceFile* active = NULL; // current source file
for (const byte* pc=start; pc < end; pc += size)
{
op_count = opOperandCount[*pc];
if (op_count == -1 && *pc != OP_lookupswitch)
return false; // ohh very bad, verifier will catch this
size = AvmCore::calculateInstructionWidth(pc);
if (pc+size > end)
return false; // also bad, let the verifier will handle it
switch (*pc)
{
case OP_lookupswitch:
{
// variable length instruction
const byte *pc2 = pc+4;
int case_count = 1 + readU30(pc2);
size += case_count*3;
break;
}
case OP_debug:
{
// form is 8bit type followed by pool entry
// then 4Byte extra info
int type = (uint8)*(pc+1);
switch(type)
{
case DI_LOCAL:
{
// in this case last word contains
// register and line number
const byte* pc2 = pc+2;
int index = readU30(pc2);
int slot = (uint8)*(pc2);
//int line = readS24(pc+5);
//Atom str = pool->cpool[index];
Stringp s = pool->getString(index);
m->setRegName(slot, s);
}
}
break;
}
case OP_debugline:
{
// this means that we have a new source line for the given offset
const byte* pc2 = pc+1;
int line = readU30(pc2);
if (active == NULL)
AvmAssert(0 == 1); // means OP_debugline appeared before OP_debugfile which is WRONG! Fix compiler
else
active->addLine(core, line, m, pc - abc_start);
break;
}
case OP_debugfile:
{
// new or existing source file
const byte* pc2 = pc+1;
Stringp name = pool->getString(readU30(pc2));
active = file->sourceNamed(name);
if (active == NULL)
{
active = new (core->GetGC()) SourceFile(core->GetGC(), name);
file->sourceAdd(active);
}
break;
}
}
}
return true;
}
/**
* Returns the call stack depth (i.e. number of frames).
*/
int Debugger::frameCount()
{
const int MAX_FRAMES = 500; // we need a max, for perf. reasons (bug 175526)
CallStackNode* trace = core->callStack;
int count = 1;
while( (trace = trace->next) != 0 && count < MAX_FRAMES )
count++;
return count;
}
/**
* Set frame to point to the specified frame number
* or null if frame number does not exist.
*/
DebugFrame* Debugger::frameAt(int frameNbr)
{
DebugFrame* frame = NULL;
if (frameNbr >= 0)
{
CallStackNode* trace = locateTrace(frameNbr);
if (trace)
frame = new (core->GetGC()) DebugStackFrame(frameNbr, trace, this);
}
return frame;
}
/**
* Stack frames are labelled 0 to stackTrace->depth
* With zero being the topmost or most recent frame.
*/
CallStackNode* Debugger::locateTrace(int frameNbr)
{
int count = 0;
CallStackNode* trace = core->callStack;
while(count++ < frameNbr && trace != NULL)
trace = trace->next;
return trace;
}
/**
* # of abc files available
*/
int Debugger::abcCount() const
{
return abcList.size();
}
/**
* Get information on each of the abc files that
* have been loaded.
*/
AbcInfo* Debugger::abcAt(int index) const
{
return abcList.get(index);
}
/**
* Contains all known debug information regarding a single
* abc/swf file
*/
AbcFile::AbcFile(AvmCore* core, int size)
: core(core),
source(core->GetGC()),
byteCount(size)
{
sourcemap = new (core->GetGC()) Hashtable(core->GetGC());
}
int AbcFile::sourceCount() const
{
return source.size();
}
SourceInfo* AbcFile::sourceAt(int index) const
{
return source.get(index);
}
int AbcFile::size() const
{
return byteCount;
}
/**
* Find a source file with the given name
*/
SourceFile* AbcFile::sourceNamed(Stringp name)
{
Atom atom = sourcemap->get(name->atom());
if (AvmCore::isUndefined(atom))
return NULL;
uint32 index = AvmCore::integer_u(atom);
return source.get(index);
}
/**
* Add source file to list; no check for uniqueness
* of name
*/
void AbcFile::sourceAdd(SourceFile* s)
{
uint32 index = source.add(s);
sourcemap->add(s->name()->atom(), core->uintToAtom(index));
}
/**
* new source info
*/
SourceFile::SourceFile(MMgc::GC* gc, Stringp name)
: named(name)
, functions(gc)
{
}
Stringp SourceFile::name() const
{
return named;
}
/**
* A line - offset pair should be recorded
*/
void SourceFile::addLine(AvmCore* core, int linenum, MethodInfo* func, int offset)
{
// Add the function to our list if it doesn't exist. Use lastIndexOf() instead of
// indexOf(), because this will be faster in the very common case where the function
// already exists at the end of the list.
int index = functions.lastIndexOf(func);
if (index < 0)
index = functions.add(func);
// line numbers for a given function don't always come in sequential
// order -- for example, I've seen them come out of order if a function
// contains an inner anonymous function -- so, compare the new line
// against firstSourceLine every time we get called
if (func->firstSourceLine == 0 || linenum < func->firstSourceLine)
func->firstSourceLine = linenum;
if (func->offsetInAbc == 0 || offset < func->offsetInAbc)
func->offsetInAbc = offset;
if (func->lastSourceLine == 0 || linenum > func->lastSourceLine)
func->lastSourceLine = linenum;
if (sourceLines == NULL)
sourceLines = new (core->GetGC()) BitSet();
sourceLines->set(linenum);
}
int SourceFile::functionCount() const
{
return functions.size();
}
MethodInfo* SourceFile::functionAt(int index) const
{
return functions.get(index);
}
bool SourceFile::setBreakpoint(int linenum)
{
if (sourceLines == NULL || !sourceLines->get(linenum))
return false;
if (breakpoints == NULL)
breakpoints = new (GC::GetGC(this)) BitSet();
breakpoints->set(linenum);
return true;
}
bool SourceFile::clearBreakpoint(int linenum)
{
if (breakpoints == NULL || !breakpoints->get(linenum))
return false;
breakpoints->clear(linenum);
return true;
}
bool SourceFile::hasBreakpoint(int linenum)
{
return (breakpoints != NULL && breakpoints->get(linenum));
}
DebugStackFrame::DebugStackFrame(int nbr, CallStackNode* tr, Debugger* debug)
: trace(tr)
, debugger(debug)
, frameNbr(nbr)
{
}
/**
* Identifies the source file and source line number
* corresponding to this frame
*/
bool DebugStackFrame::sourceLocation(SourceInfo*& source, int& linenum)
{
// use the method info to locate the abcfile / source
AbstractFunction* m = trace->info;
if (trace->filename && debugger)
{
uintptr index = (uintptr)debugger->pool2abcIndex.get(Atom((PoolObject*)m->pool));
AbcFile* abc = (AbcFile*)debugger->abcAt(index);
source = abc->sourceNamed(trace->filename);
}
linenum = trace->linenum;
// valid info?
return (source != NULL && linenum > 0);
}
/**
* This pointer for the frame
*/
bool DebugStackFrame::dhis(Atom& a)
{
bool worked = false;
if (trace->framep)
{
MethodInfo* info = (MethodInfo*)trace->info;
info->boxLocals(trace->framep, 0, trace->traits, &a, 0, 1); // pull framep[0] = [this]
worked = true;
}
else
{
a = undefinedAtom;
}
return worked;
}
/**
* @return a pointer to an object Atom whose members are
* the arguments passed into a function for this frame
*/
bool DebugStackFrame::arguments(Atom*& ar, int& count)
{
bool worked = true;
if (trace->framep)
{
int firstArgument, pastLastArgument;
argumentBounds(&firstArgument, &pastLastArgument);
count = pastLastArgument - firstArgument;
if ((count > 0) && debugger)
{
// pull the args into an array -- skip [0] which is [this]
ar = (Atom*) debugger->core->GetGC()->Calloc(count, sizeof(Atom), GC::kContainsPointers|GC::kZero);
MethodInfo* info = (MethodInfo*)trace->info;
info->boxLocals(trace->framep, firstArgument, trace->traits, ar, 0, count);
}
}
else
{
worked = false;
count = 0;
}
return worked;
}
bool DebugStackFrame::setArgument(int which, Atom& val)
{
bool worked = false;
if (trace->framep)
{
int firstArgument, pastLastArgument;
argumentBounds(&firstArgument, &pastLastArgument);
int count = pastLastArgument - firstArgument;
if (count > 0 && which < count)
{
// copy the single arg over
MethodInfo* info = (MethodInfo*)trace->info;
info->unboxLocals(&val, 0, trace->traits, trace->framep, firstArgument+which, 1);
worked = true;
}
}
return worked;
}
/**
* @return a pointer to an object Atom whose members are
* the active locals for this frame.
*/
bool DebugStackFrame::locals(Atom*& ar, int& count)
{
bool worked = true;
if (trace->framep)
{
int firstLocal, pastLastLocal;
localBounds(&firstLocal, &pastLastLocal);
count = pastLastLocal - firstLocal;
AvmAssert(count >= 0);
if ((count > 0) && debugger)
{
// frame looks like [this][param0...paramN][local0...localN]
ar = (Atom*) debugger->core->GetGC()->Calloc(count, sizeof(Atom), GC::kContainsPointers|GC::kZero);
MethodInfo* info = (MethodInfo*)trace->info;
info->boxLocals(trace->framep, firstLocal, trace->traits, ar, 0, count);
// If NEED_REST or NEED_ARGUMENTS is set, and the MIR is being used, then the first
// local is actually not an atom at all -- it is an ArrayObject*. So, we need to
// convert it to an atom. (If the interpreter is being used instead of the MIR, then
// it is stored as an atom.)
if (info->flags & (AbstractFunction::NEED_REST | AbstractFunction::NEED_ARGUMENTS))
{
int atomType = ar[0] & 7;
if (atomType == 0) // 0 is not a legal atom type, so ar[0] is not an atom
{
ScriptObject* obj = (ScriptObject*)ar[0];
ar[0] = obj->atom();
}
}
}
}
else
{
worked = false;
count = 0;
}
return worked;
}
bool DebugStackFrame::setLocal(int which, Atom& val)
{
bool worked = false;
if (trace->framep)
{
int firstLocal, pastLastLocal;
localBounds(&firstLocal, &pastLastLocal);
int count = pastLastLocal - firstLocal;
if (count > 0 && which < count)
{
MethodInfo* info = (MethodInfo*)trace->info;
if (which == 0 && (info->flags & (AbstractFunction::NEED_REST | AbstractFunction::NEED_ARGUMENTS)))
{
// They are trying to modify the first local, but that is actually the special
// array for "...rest" or for "arguments". That is too complicated to allow
// right now. We're just going to fail the request.
}
else
{
// copy the single arg over
info->unboxLocals(&val, 0, trace->traits, trace->framep, firstLocal+which, 1);
worked = true;
}
}
}
return worked;
}
// Returns the indices of the arguments: [firstArgument, pastLastArgument)
void DebugStackFrame::argumentBounds(int* firstArgument, int* pastLastArgument)
{
*firstArgument = 1; // because [0] is 'this'
*pastLastArgument = indexOfFirstLocal();
}
// Returns the indices of the locals: [firstLocal, pastLastLocal)
void DebugStackFrame::localBounds(int* firstLocal, int* pastLastLocal)
{
*firstLocal = indexOfFirstLocal();
if (trace->framep)
{
MethodInfo* info = (MethodInfo*) trace->info;
*pastLastLocal = info->local_count;
}
else
{
*pastLastLocal = *firstLocal;
}
}
int DebugStackFrame::indexOfFirstLocal()
{
// 'trace->argc' is the number of arguments that were actually passed in
// to this function, but that is not what we want -- we want
// 'info->param_count', because that is the number of arguments we were
// *expecting* to get. There are two reasons we want 'info->param_count':
//
// (1) if the caller passed in too many args, we want to ignore the
// trailing ones; and
// (2) if the caller passed in too few args to a function that has some
// default parameters, we want to display the args with their default
// values.
return 1 + trace->info->param_count;
}
}
#endif /* DEBUGGER */