/* ***** 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 << ""; 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; iconsole << 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; imethods[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 */