Mozilla/mozilla/js/tamarin/shell/DebugCLI.cpp
gerv%gerv.net 0a3a70857a Bug 236613: fix formatting of MPL/LGPL/GPL tri-license.
git-svn-id: svn://10.0.0.236/trunk@220064 18797224-902f-48f8-a5cc-f745e15eee43
2007-02-13 18:05:02 +00:00

791 lines
18 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) 2004-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 <stdio.h>
#include "avmshell.h"
#ifdef DEBUGGER
namespace avmshell
{
/**
* The array of top level commands that we support.
* They are placed into a Nx2 array, whereby the first component
* is a String which is the command and the 2nd component is the
* integer identifier for the command.
*
* The StringIntArray object provides a convenient wrapper class
* that implements the List interface.
*
* NOTE: order matters! For the case of a single character
* match, we let the first hit act like an unambiguous match.
*/
DebugCLI::StringIntArray DebugCLI::commandArray[] =
{
{ "awatch", CMD_AWATCH },
{ "break", CMD_BREAK },
{ "bt", INFO_STACK_CMD },
{ "continue", CMD_CONTINUE },
{ "cf", CMD_CF },
{ "clear", CMD_CLEAR },
{ "commands", CMD_COMMANDS },
{ "condition", CMD_CONDITION },
{ "delete", CMD_DELETE },
{ "disable", CMD_DISABLE },
{ "disassemble", CMD_DISASSEMBLE },
{ "display", CMD_DISPLAY },
{ "enable", CMD_ENABLE },
{ "finish", CMD_FINISH },
{ "file", CMD_FILE },
{ "help", CMD_HELP },
{ "halt", CMD_HALT },
{ "handle", CMD_HANDLE },
{ "home", CMD_HOME },
{ "info", CMD_INFO },
{ "kill", CMD_KILL },
{ "list", CMD_LIST },
{ "next", CMD_NEXT },
{ "nexti", CMD_NEXT },
{ "mctree", CMD_MCTREE },
{ "print", CMD_PRINT },
{ "pwd", CMD_PWD },
{ "quit", CMD_QUIT },
{ "run", CMD_RUN },
{ "rwatch", CMD_RWATCH },
{ "step", CMD_STEP },
{ "stepi", CMD_STEP },
{ "set", CMD_SET },
{ "show", CMD_SHOW },
{ "source", CMD_SOURCE },
{ "tutorial", CMD_TUTORIAL },
{ "undisplay", CMD_UNDISPLAY },
{ "where", INFO_STACK_CMD },
{ "watch", CMD_WATCH },
{ "what", CMD_WHAT },
{ "viewswf", CMD_VIEW_SWF },
{ NULL, 0 }
};
/**
* Info sub-commands
*/
DebugCLI::StringIntArray DebugCLI::infoCommandArray[] =
{
{ "arguments", INFO_ARGS_CMD },
{ "breakpoints", INFO_BREAK_CMD },
{ "display", INFO_DISPLAY_CMD },
{ "files", INFO_FILES_CMD },
{ "functions", INFO_FUNCTIONS_CMD },
{ "handle", INFO_HANDLE_CMD },
{ "locals", INFO_LOCALS_CMD },
{ "stack", INFO_STACK_CMD },
{ "sources", INFO_SOURCES_CMD },
{ "swfs", INFO_SWFS_CMD },
{ "targets", INFO_TARGETS_CMD },
{ "variables", INFO_VARIABLES_CMD },
{ NULL, 0 }
};
/**
* Show sub-commands
*/
DebugCLI::StringIntArray DebugCLI::showCommandArray[] =
{
{ "break", SHOW_BREAK_CMD },
{ "files", SHOW_FILES_CMD },
{ "functions", SHOW_FUNC_CMD },
{ "memory", SHOW_MEM_CMD },
{ "net", SHOW_NET_CMD },
{ "properties", SHOW_PROPERTIES_CMD },
{ "uri", SHOW_URI_CMD },
{ "variable", SHOW_VAR_CMD },
{ NULL, 0 }
};
/**
* enable sub-commands
*/
DebugCLI::StringIntArray DebugCLI::enableCommandArray[] =
{
{ "breakpoints", CMD_BREAK },
{ "display", CMD_DISPLAY },
{ "delete", CMD_DELETE },
{ "once", ENABLE_ONCE_CMD },
{ NULL, 0 }
};
/**
* disable sub-commands
*/
DebugCLI::StringIntArray DebugCLI::disableCommandArray[] =
{
{ "display", CMD_DISPLAY },
{ "breakpoints", CMD_BREAK },
{ NULL, 0 }
};
DebugCLI::DebugCLI(AvmCore *core)
: Debugger(core)
{
currentSourceLen = -1;
}
DebugCLI::~DebugCLI()
{
if (currentSource) {
delete [] currentSource;
currentSource = NULL;
}
}
char* DebugCLI::nextToken()
{
char *out = currentToken;
if (currentToken) {
while (*currentToken) {
if (*currentToken == ' ' || *currentToken == '\r' || *currentToken == '\n' || *currentToken == '\t') {
*currentToken++ = 0;
break;
}
currentToken++;
}
currentToken = *currentToken ? currentToken : NULL;
}
return out;
}
/**
* Attempt to match given the given string against our set of commands
* @return the command code that was hit.
*/
int DebugCLI::determineCommand(DebugCLI::StringIntArray cmdList[],
const char *input,
int defCmd)
{
int inputLen = strlen(input);
// first check for a comment
if (input[0] == '#') {
return CMD_COMMENT;
}
int match = -1;
bool ambiguous = false;
for (int i=0; cmdList[i].text; i++) {
if (!strncmp(input, cmdList[i].text, inputLen)) {
if (match != -1) {
ambiguous = true;
break;
}
match = i;
}
}
/**
* 3 cases:
* - No hits, return unknown and let our caller
* dump the error.
* - We match unambiguously or we have 1 or more matches
* and the input is a single character. We then take the
* first hit as our command.
* - If we have multiple hits then we dump a 'ambiguous' message
* and puke quietly.
*/
if (match == -1) {
// no command match return unknown
return defCmd;
}
// only 1 match or our input is 1 character or first match is exact
else if (!ambiguous || inputLen == 1 || !strcmp(cmdList[match].text, input)) {
return cmdList[match].id;
}
else {
// matches more than one command dump message and go
core->console << "Ambiguous command '" << input << "': ";
bool first = true;
for (int i=0; cmdList[i].text; i++) {
if (!strncmp(cmdList[i].text, input, inputLen)) {
if (!first) {
core->console << ", ";
} else {
first = false;
}
core->console << cmdList[i].text;
}
}
core->console << ".\n";
return -1;
}
}
const char* DebugCLI::commandNumberToCommandName(DebugCLI::StringIntArray cmdList[],
int cmdNumber)
{
for (int i = 0; cmdList[i].text; i++)
{
if (cmdList[i].id == cmdNumber)
return cmdList[i].text;
}
return "?";
}
void DebugCLI::bt()
{
//core->stackTrace->dump(core->console);
//core->console << '\n';
// obtain information about each frame
int frameCount = core->debugger->frameCount();
for(int k=0; k<frameCount; k++)
{
Atom* ptr;
int count, line;
SourceInfo* src;
DebugFrame* frame = core->debugger->frameAt(k);
// source information
frame->sourceLocation(src, line);
core->console << "#" << k << " ";
// this
Atom a = nullObjectAtom;
frame->dhis(a);
core->console << core->format(a) << ".";
// method
MethodInfo* info = functionFor(src, line);
if (info)
core->console << info->name;
else
core->console << "<unknown>";
core->console << "(";
// dump args
frame->arguments(ptr, count);
for(int i=0; i<count; i++)
{
// write out the name
if ( info && (info->getArgName(i) != core->kundefined) )
core->console << info->getArgName(i) << "=";
core->console << core->format(*ptr++);
if (i<count-1)
core->console << ",";
}
core->console << ") at ";
if (src)
core->console << src->name();
else
core->console << "???";
core->console << ":" << (line) << "\n";
}
}
MethodInfo* DebugCLI::functionFor(SourceInfo* src, int line)
{
MethodInfo* info = NULL;
if (src)
{
// find the function at this location
int size = src->functionCount();
for(int i=0; i<size; i++)
{
MethodInfo* m = src->functionAt(i);
if (line >= m->firstSourceLine && line <= m->lastSourceLine)
{
info = m;
break;
}
}
}
return info;
}
// zero based
char* DebugCLI::lineStart(int linenum)
{
if (!currentSource && currentFile)
setCurrentSource(currentFile);
if (!currentSource) {
return NULL;
}
// linenumbers map to zero based array entry
char *ptr = currentSource;
for (int i=0; i<linenum; i++) {
// Scan to end of line
while (*ptr != '\n') {
if (!*ptr) {
return NULL;
}
ptr++;
}
// Advance past newline
ptr++;
}
return ptr;
}
void DebugCLI::displayLines(int line, int count)
{
if (!lineStart(0)) {
core->console << "No source available for current instruction\n";
} else {
// Display status
int lineAt = line;
while(count-- > 0)
{
char* ptr = lineStart(lineAt-1);
if (!ptr)
{
#if WRAP_AROUND
lineAt = 1;
count++; // reset line number to beginning skip this iteration
#else
break;
#endif
}
else
{
core->console << (lineAt) << ": ";
while (*ptr && *ptr != '\n')
core->console << *ptr++;
core->console << '\n';
lineAt++;
}
}
}
}
void DebugCLI::list(const char* line)
{
int currentLine = (core->callStack) ? core->callStack->linenum : 0;
int linenum = (line) ? atoi(line) : currentLine;
displayLines(linenum, 10);
}
void DebugCLI::printIP()
{
int line = (core->callStack) ? core->callStack->linenum : 0;
displayLines(line, 1);
}
void DebugCLI::breakpoint(char *location)
{
Stringp filename = currentFile;
char *colon = strchr(location, ':');
if (colon) {
*colon = 0;
filename = core->constantString(location);
location = colon+1;
}
AbcFile *abcFile = (AbcFile*) abcAt(0);
if (abcFile == NULL) {
core->console << "No abc file loaded\n";
return;
}
SourceFile *sourceFile = abcFile->sourceNamed(filename);
if (sourceFile == NULL) {
core->console << "No source available; can't set breakpoint.\n";
return;
}
int targetLine = atoi(location);
int breakpointId = ++breakpointCount;
if (breakpointSet(sourceFile, targetLine)) {
core->console << "Breakpoint " << breakpointId << ": file "
<< filename
<< ", " << (targetLine) << ".\n";
BreakAction *breakAction = new (core->GetGC()) BreakAction(sourceFile,
breakpointId,
filename,
targetLine);
breakAction->prev = lastBreakAction;
if (lastBreakAction) {
lastBreakAction->next = breakAction;
} else {
firstBreakAction = breakAction;
}
lastBreakAction = breakAction;
} else {
core->console << "Could not locate specified line.\n";
}
}
void DebugCLI::showBreakpoints()
{
BreakAction *breakAction = firstBreakAction;
while (breakAction) {
breakAction->print(core->console);
breakAction = breakAction->next;
}
}
void DebugCLI::deleteBreakpoint(char *idstr)
{
int id = atoi(idstr);
BreakAction *breakAction = firstBreakAction;
while (breakAction) {
if (breakAction->id == id) {
break;
}
breakAction = breakAction->next;
}
if (breakAction) {
if (breakAction->prev) {
breakAction->prev->next = breakAction->next;
} else {
firstBreakAction = breakAction->next;
}
if (breakAction->next) {
breakAction->next->prev = breakAction->prev;
} else {
lastBreakAction = breakAction->prev;
}
if (breakpointClear(breakAction->sourceFile, breakAction->linenum)) {
core->console << "Breakpoint " << id << " deleted.\n";
} else {
core->console << "Internal error; could not delete breakpoint.\n";
}
} else {
core->console << "Could not find breakpoint.\n";
}
}
void DebugCLI::locals()
{
Atom* ptr;
int count, line;
SourceInfo* src;
DebugFrame* frame = core->debugger->frameAt(0);
// source information
frame->sourceLocation(src, line);
// method
MethodInfo* info = functionFor(src, line);
if (info)
{
frame->arguments(ptr, count);
for(int i=0; i<count; i++)
{
// write out the name
if (info && (info->getLocalName(i) != core->kundefined) )
core->console << info->getLocalName(i) << " = ";
core->console << core->format(*ptr++);
//if (i<count-1)
core->console << "\n";
}
}
}
Atom DebugCLI::ease2Atom(const char* to, Atom baseline)
{
// first make a string out of the value
Atom a = core->newString(to)->atom();
// using the type of baseline try to convert to into an appropriate Atom
if (core->isNumber(baseline))
return core->numberAtom(a);
else if (core->isBoolean(baseline))
return core->booleanAtom(a);
return nullStringAtom;
}
void DebugCLI::set()
{
const char* what = nextToken();
const char* equ = nextToken();
const char* to = nextToken();
if (!to || !equ || !what || *equ != '=')
{
core->console << " Bad format, should be: 'set {variable} = {value}' ";
}
else
{
// look for the varable in our locals or args.
Atom* ptr;
int count, line;
SourceInfo* src;
DebugFrame* frame = core->debugger->frameAt(0);
// source information
frame->sourceLocation(src, line);
if (!src)
{
core->console << "Unable to locate debug information for current source file, so no local or argument names known";
return;
}
// method
MethodInfo* info = functionFor(src, line);
if (!info)
{
core->console << "Unable to find method debug information, so no local or argument names known";
return;
}
frame->arguments(ptr, count);
for(int i=0; i<count; i++)
{
Stringp arg = info->getArgName(i);
if (arg->Equals(what))
{
// match!
Atom a = ease2Atom(to, ptr[i]);
if (a == undefinedAtom)
core->console << " Type mismatch : current value is " << core->format(ptr[i]);
else
frame->setArgument(i, a);
return;
}
}
frame->locals(ptr, count);
for(int i=0; i<count; i++)
{
Stringp local = info->getLocalName(i);
if ( local->Equals(what))
{
// match!
Atom a = ease2Atom(to, ptr[i]);
if (a == undefinedAtom)
core->console << " Type mismatch : current value is " << core->format(ptr[i]);
else
frame->setLocal(i, a);
return;
}
}
}
}
void DebugCLI::print(const char *name)
{
if (!name) {
core->console << "Must specify a name.\n";
return;
}
// todo deal with exceptions
Multiname mname(
core->publicNamespace,
core->constantString(name)
);
#if 0
// rick fixme
Atom objAtom = env->findproperty(outerScope, scopes, extraScopes, &mname, false);
Atom valueAtom = env->getproperty(objAtom, &mname);
core->console << core->string(valueAtom) << '\n';
#endif
}
bool DebugCLI::filterException(Exception *exception)
{
// Filter exceptions when -d switch specified
if (activeFlag) {
core->console << "Exception has been thrown:\n"
<< core->string(exception->atom)
<< '\n';
enterDebugger();
return true;
}
return false;
}
void DebugCLI::info()
{
char *command = nextToken();
int cmd = infoCommandFor(command);
switch (cmd) {
case -1:
// ambiguous, we already printed error message
break;
case INFO_LOCALS_CMD:
locals();
break;
case INFO_BREAK_CMD:
showBreakpoints();
break;
case INFO_UNKNOWN_CMD:
core->console << "Unknown command.\n";
break;
default:
core->console << "Command not implemented.\n";
break;
}
}
void DebugCLI::enterDebugger()
{
setCurrentSource( (core->callStack) ? (core->callStack->filename) : 0 );
if (currentSource == NULL)
{
stepInto();
return;
}
for (;;) {
printIP();
core->console << "(asdb) ";
fflush(stdout);
fgets(commandLine, kMaxCommandLine, stdin);
commandLine[strlen(commandLine)-1] = 0;
if (!commandLine[0]) {
strcpy(commandLine, lastCommand);
} else {
strcpy(lastCommand, commandLine);
}
currentToken = commandLine;
char *command = nextToken();
int cmd = commandFor(command);
switch (cmd) {
case -1:
// ambiguous, we already printed error message
break;
case CMD_INFO:
info();
break;
case CMD_BREAK:
breakpoint(nextToken());
break;
case CMD_DELETE:
deleteBreakpoint(nextToken());
break;
case CMD_LIST:
list(nextToken());
break;
case CMD_UNKNOWN:
core->console << "Unknown command.\n";
break;
case CMD_QUIT:
exit(0);
break;
case CMD_CONTINUE:
return;
case CMD_PRINT:
print(nextToken());
break;
case CMD_NEXT:
stepOver();
return;
case INFO_STACK_CMD:
bt();
break;
case CMD_FINISH:
stepOut();
return;
case CMD_STEP:
stepInto();
return;
case CMD_SET:
set();
break;
default:
core->console << "Command not implemented.\n";
break;
}
}
}
void DebugCLI::setCurrentSource(Stringp file)
{
if (!file)
return;
currentFile = file;
if (currentSource) {
delete [] currentSource;
currentSource = NULL;
currentSourceLen = -1;
}
// Open this file and suck it into memory
FileInputStream f(currentFile->toUTF8String()->c_str());
if (f.valid()) {
currentSourceLen = f.available();
currentSource = new char[currentSourceLen+1];
f.read(currentSource, currentSourceLen);
currentSource[currentSourceLen] = 0;
// whip through converting \r\n to space \n
for(int i=0; i<currentSourceLen-1;i++) {
if (currentSource[i] == '\r' && currentSource[i+1] == '\n')
currentSource[i] = ' ';
}
} else {
core->console << "Error opening source file " << currentFile->c_str() << "\n";
}
}
//
// BreakAction
//
void BreakAction::print(PrintWriter& out)
{
out << id << " at "
<< filename
<< ":" << (linenum) << '\n';
}
}
#endif