2000-04-29 04:23:06 +04:00
|
|
|
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
|
|
|
*
|
|
|
|
* The contents of this file are subject to the Netscape 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/NPL/
|
|
|
|
*
|
|
|
|
* Software distributed under the License is distributed on an "AS
|
|
|
|
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express oqr
|
|
|
|
* implied. See the License for the specific language governing
|
|
|
|
* rights and limitations under the License.
|
|
|
|
*
|
|
|
|
* The Original Code is the JavaScript 2 Prototype.
|
|
|
|
*
|
|
|
|
* The Initial Developer of the Original Code is Netscape
|
|
|
|
* Communications Corporation. Portions created by Netscape are
|
|
|
|
* Copyright (C) 1998 Netscape Communications Corporation. All
|
|
|
|
* Rights Reserved.
|
|
|
|
*
|
|
|
|
* Contributor(s):
|
|
|
|
*
|
|
|
|
* Alternatively, the contents of this file may be used under the
|
|
|
|
* terms of the GNU Public License (the "GPL"), in which case the
|
|
|
|
* provisions of the GPL are applicable instead of those above.
|
|
|
|
* If you wish to allow use of your version of this file only
|
|
|
|
* under the terms of the GPL and not to allow others to use your
|
|
|
|
* version of this file under the NPL, indicate your decision by
|
|
|
|
* deleting the provisions above and replace them with the notice
|
|
|
|
* and other provisions required by the GPL. If you do not delete
|
|
|
|
* the provisions above, a recipient may use your version of this
|
|
|
|
* file under either the NPL or the GPL.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "world.h"
|
|
|
|
#include "debugger.h"
|
|
|
|
|
|
|
|
#include <string>
|
|
|
|
#include <ctype.h>
|
|
|
|
#include <assert.h>
|
|
|
|
|
|
|
|
namespace JavaScript {
|
|
|
|
namespace Debugger {
|
2000-05-05 08:44:51 +04:00
|
|
|
|
|
|
|
using namespace Interpreter;
|
|
|
|
|
2000-04-29 04:23:06 +04:00
|
|
|
enum ShellCommand {
|
|
|
|
ASSEMBLE,
|
2000-05-05 02:42:49 +04:00
|
|
|
AMBIGUOUS,
|
|
|
|
AMBIGUOUS2,
|
|
|
|
CONTINUE,
|
2000-04-29 04:23:06 +04:00
|
|
|
DISSASSEMBLE,
|
|
|
|
EXIT,
|
2000-05-05 02:42:49 +04:00
|
|
|
HELP,
|
|
|
|
LET,
|
|
|
|
PRINT,
|
|
|
|
REGISTER,
|
|
|
|
STEP,
|
2000-04-29 04:23:06 +04:00
|
|
|
COMMAND_COUNT
|
|
|
|
};
|
|
|
|
|
2000-05-05 02:42:49 +04:00
|
|
|
static const char *shell_cmds[][3] = {
|
|
|
|
{"assemble", " ", "nyi"},
|
|
|
|
{"ambiguous", " ", "Test command for ambiguous command detection"},
|
|
|
|
{"ambiguous2", " ", "Test command for ambiguous command detection"},
|
|
|
|
{"continue", " ", "Continue execution until complete."},
|
|
|
|
{"dissassemble", "", "nyi"},
|
|
|
|
{"exit", " ", "nyi"},
|
|
|
|
{"help", " ", "Display this message."},
|
|
|
|
{"let", " ", "Set a debugger environment variable."},
|
|
|
|
{"print", " ", "nyi"},
|
|
|
|
{"register", " ", "Show the value of a single register or all \
|
|
|
|
registers, or set the value of a single register."},
|
|
|
|
{"step", " ", "Execute the current opcode."},
|
|
|
|
{0, 0} /* sentry */
|
|
|
|
};
|
2000-04-29 04:23:06 +04:00
|
|
|
|
2000-05-05 02:42:49 +04:00
|
|
|
enum ShellVariable {
|
|
|
|
TRACE,
|
|
|
|
VARIABLE_COUNT
|
|
|
|
};
|
|
|
|
|
|
|
|
static const char *shell_vars[][3] = {
|
|
|
|
{"trace", "", "(bool) always show opcode before execution."},
|
|
|
|
{0, 0} /* sentry */
|
|
|
|
};
|
|
|
|
|
2000-04-29 04:23:06 +04:00
|
|
|
/* return true if str2 starts with/is str1
|
|
|
|
* XXX ignore case */
|
2000-04-30 18:56:31 +04:00
|
|
|
static bool
|
2000-04-29 04:23:06 +04:00
|
|
|
startsWith (const String &str1, const String &str2)
|
|
|
|
{
|
|
|
|
uint n;
|
|
|
|
size_t m = str1.size();
|
|
|
|
|
|
|
|
if (m > str2.size())
|
|
|
|
return false;
|
|
|
|
|
|
|
|
for (n = 0; n < m; ++n)
|
|
|
|
if (str1[n] != str2[n])
|
|
|
|
return false;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2000-05-05 02:42:49 +04:00
|
|
|
|
|
|
|
/**
|
|
|
|
* locate the best match for |partial| in the command list |list|.
|
|
|
|
* if no matches are found, return |length|, if multiple matches are found,
|
|
|
|
* return |length| plus the number of ambiguous matches
|
|
|
|
*/
|
|
|
|
static uint32
|
|
|
|
matchElement (const String &partial, const char *list[][3], size_t length)
|
2000-04-29 04:23:06 +04:00
|
|
|
{
|
2000-04-29 04:29:56 +04:00
|
|
|
uint32 ambig_matches = 0;
|
2000-05-05 02:42:49 +04:00
|
|
|
uint32 match = length;
|
2000-04-29 04:23:06 +04:00
|
|
|
|
2000-05-05 02:42:49 +04:00
|
|
|
for (uint32 i = 0; i < length ; ++i)
|
2000-04-29 04:23:06 +04:00
|
|
|
{
|
2000-05-05 02:42:49 +04:00
|
|
|
String possibleMatch (widenCString(list[i][0]));
|
|
|
|
if (startsWith(partial, possibleMatch))
|
2000-04-29 04:23:06 +04:00
|
|
|
{
|
2000-05-05 02:42:49 +04:00
|
|
|
if (partial.size() == possibleMatch.size())
|
2000-04-29 04:23:06 +04:00
|
|
|
{
|
|
|
|
/* exact match */
|
|
|
|
ambig_matches = 0;
|
2000-05-05 02:42:49 +04:00
|
|
|
return i;
|
2000-04-29 04:23:06 +04:00
|
|
|
}
|
|
|
|
else if (match == COMMAND_COUNT) /* no match yet */
|
2000-05-05 02:42:49 +04:00
|
|
|
match = i;
|
2000-04-29 04:23:06 +04:00
|
|
|
else
|
|
|
|
++ambig_matches; /* something already matched,
|
|
|
|
* ambiguous command */
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ambig_matches == 0)
|
2000-05-05 02:42:49 +04:00
|
|
|
return match;
|
|
|
|
else
|
|
|
|
return length + ambig_matches;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
showHelp(Formatter &out)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; shell_cmds[i][0] != 0; i++)
|
|
|
|
out << shell_cmds[i][0] << shell_cmds[i][1] << "\t" <<
|
|
|
|
shell_cmds[i][2] << "\n";
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
showCurrentOp(Context* cx, Formatter &aOut)
|
|
|
|
{
|
|
|
|
ICodeModule *iCode = cx->getICode();
|
|
|
|
JSValues ®isters = cx->getRegisters();
|
|
|
|
InstructionIterator pc = cx->getPC();
|
|
|
|
|
|
|
|
printFormat(aOut, "trace [%02u:%04u]: ",
|
|
|
|
iCode->mID, (pc - iCode->its_iCode->begin()));
|
|
|
|
Instruction* i = *pc;
|
|
|
|
stdOut << *i;
|
|
|
|
if (i->op() != BRANCH && i->count() > 0) {
|
|
|
|
aOut << " [";
|
|
|
|
i->printOperands(stdOut, registers);
|
|
|
|
aOut << "]\n";
|
|
|
|
} else {
|
|
|
|
aOut << '\n';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
Shell::listen(Context* cx, InterpretStage stage)
|
|
|
|
{
|
|
|
|
|
|
|
|
if (mTraceFlag)
|
|
|
|
showCurrentOp (cx, mOut);
|
|
|
|
|
|
|
|
if (!(mStopMask & stage))
|
|
|
|
return;
|
|
|
|
|
2000-05-05 09:25:00 +04:00
|
|
|
static String lastLine(widenCString("help\n"));
|
|
|
|
String line;
|
2000-05-05 08:44:51 +04:00
|
|
|
LineReader reader(mIn);
|
|
|
|
|
2000-05-05 02:42:49 +04:00
|
|
|
do {
|
|
|
|
ICodeModule *iCode = cx->getICode();
|
|
|
|
InstructionIterator pc = cx->getPC();
|
|
|
|
|
|
|
|
stdOut << "jsd [pc:";
|
|
|
|
printFormat (stdOut, "%04X", (pc - iCode->its_iCode->begin()));
|
|
|
|
stdOut << "]> ";
|
|
|
|
|
2000-05-05 08:44:51 +04:00
|
|
|
reader.readLine(line);
|
2000-05-05 08:19:20 +04:00
|
|
|
|
2000-05-05 09:25:00 +04:00
|
|
|
if (line[0] == uni::lf)
|
2000-05-05 02:42:49 +04:00
|
|
|
line = lastLine;
|
|
|
|
else
|
|
|
|
lastLine = line;
|
|
|
|
|
2000-05-05 09:25:00 +04:00
|
|
|
} while (doCommand(cx, line));
|
2000-05-05 02:42:49 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* lex and execute the debugger command in |source|, return true if the
|
|
|
|
* command does not require the script being debugged to continue (eg, ask
|
|
|
|
* for more debugger input.)
|
|
|
|
*/
|
|
|
|
bool
|
|
|
|
Shell::doCommand (Interpreter::Context *cx, const String &source)
|
|
|
|
{
|
|
|
|
Lexer lex (mWorld, source, widenCString("debugger console"), 0);
|
|
|
|
const String *cmd;
|
|
|
|
uint32 match;
|
|
|
|
bool rv = true;
|
|
|
|
|
|
|
|
const Token &t = lex.get(true);
|
|
|
|
|
|
|
|
if (t.hasKind(Token::identifier))
|
|
|
|
cmd = &(t.getIdentifier());
|
|
|
|
else
|
|
|
|
{
|
|
|
|
mErr << "you idiot.\n";
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
match = matchElement (*cmd, shell_cmds, (size_t)COMMAND_COUNT);
|
|
|
|
|
|
|
|
if (match <= (uint32)COMMAND_COUNT)
|
|
|
|
switch ((ShellCommand)match)
|
2000-04-29 04:23:06 +04:00
|
|
|
{
|
|
|
|
case COMMAND_COUNT:
|
|
|
|
mErr << "Unknown command '" << *cmd << "'.\n";
|
|
|
|
break;
|
2000-05-05 02:42:49 +04:00
|
|
|
|
|
|
|
case AMBIGUOUS:
|
|
|
|
case AMBIGUOUS2:
|
|
|
|
mErr << "I pity the foo.\n";
|
|
|
|
break;
|
|
|
|
|
|
|
|
case CONTINUE:
|
|
|
|
mStopMask &= (IS_ALL ^ IS_STEP);
|
|
|
|
rv = false;
|
|
|
|
break;
|
|
|
|
|
2000-04-29 04:23:06 +04:00
|
|
|
case DISSASSEMBLE:
|
2000-05-05 02:42:49 +04:00
|
|
|
mOut << *cx->getICode();
|
|
|
|
break;
|
|
|
|
|
|
|
|
case HELP:
|
|
|
|
showHelp (mOut);
|
|
|
|
break;
|
|
|
|
|
2000-04-29 04:23:06 +04:00
|
|
|
case PRINT:
|
2000-05-05 02:42:49 +04:00
|
|
|
doPrint (cx, lex);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case STEP:
|
|
|
|
mStopMask |= IS_STEP;
|
|
|
|
rv = false;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case LET:
|
|
|
|
doSetVariable (lex);
|
|
|
|
break;
|
|
|
|
|
2000-04-29 04:23:06 +04:00
|
|
|
default:
|
2000-05-05 02:42:49 +04:00
|
|
|
mErr << "Input '" << *cmd << "' matched unimplemented " <<
|
|
|
|
"command '" << shell_cmds[match][0] << "'.\n";
|
2000-04-29 04:23:06 +04:00
|
|
|
break;
|
|
|
|
|
|
|
|
}
|
|
|
|
else
|
|
|
|
mErr << "Ambiguous command '" << *cmd << "', " <<
|
2000-05-05 02:42:49 +04:00
|
|
|
(match - (uint32)COMMAND_COUNT + 1) << " similar commands.\n";
|
|
|
|
|
|
|
|
return rv;
|
|
|
|
}
|
2000-04-29 04:23:06 +04:00
|
|
|
|
2000-05-05 02:42:49 +04:00
|
|
|
void
|
|
|
|
Shell::doSetVariable (Lexer &lex)
|
|
|
|
{
|
|
|
|
uint32 match;
|
|
|
|
const String *varname;
|
|
|
|
const Token *t = &(lex.get(true));
|
|
|
|
|
|
|
|
if (t->hasKind(Token::identifier))
|
|
|
|
varname = &(t->getIdentifier());
|
|
|
|
else
|
|
|
|
{
|
|
|
|
mErr << "invalid variable name.\n";
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
match = matchElement (*varname, shell_vars, (size_t)VARIABLE_COUNT);
|
|
|
|
|
|
|
|
if (match <= (uint32)VARIABLE_COUNT)
|
|
|
|
switch ((ShellVariable)match)
|
|
|
|
{
|
|
|
|
case VARIABLE_COUNT:
|
|
|
|
mErr << "Unknown variable '" << *varname << "'.\n";
|
|
|
|
break;
|
|
|
|
|
|
|
|
case TRACE:
|
|
|
|
t = &(lex.get(true));
|
|
|
|
if (t->hasKind(Token::assignment))
|
|
|
|
t = &(lex.get(true)); /* optional = */
|
|
|
|
|
|
|
|
if (t->hasKind(Token::True))
|
|
|
|
mTraceFlag = true;
|
|
|
|
else if (t->hasKind(Token::False))
|
|
|
|
mTraceFlag = false;
|
|
|
|
else
|
|
|
|
goto badval;
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
mErr << "Variable '" << *varname <<
|
|
|
|
"' matched unimplemented variable '" <<
|
|
|
|
shell_vars[match][0] << "'.\n";
|
|
|
|
}
|
|
|
|
else
|
|
|
|
mErr << "Ambiguous variable '" << *varname << "', " <<
|
|
|
|
(match - (uint32)COMMAND_COUNT + 1) << " similar variables.\n";
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
badval:
|
|
|
|
mErr << "Invalid value for variable '" <<
|
|
|
|
shell_vars[(ShellVariable)match][0] << "'\n";
|
2000-04-29 04:23:06 +04:00
|
|
|
|
2000-05-05 02:42:49 +04:00
|
|
|
|
2000-04-29 04:23:06 +04:00
|
|
|
}
|
2000-05-05 02:42:49 +04:00
|
|
|
|
|
|
|
void
|
2000-05-06 07:30:37 +04:00
|
|
|
Shell::doPrint (Context *, Lexer &lex)
|
2000-05-05 02:42:49 +04:00
|
|
|
{
|
|
|
|
const Token *t = &(lex.get(true));
|
2000-04-29 04:23:06 +04:00
|
|
|
|
2000-05-05 02:42:49 +04:00
|
|
|
if (!(t->hasKind(Token::identifier)))
|
|
|
|
{
|
|
|
|
mErr << "Invalid register name.\n";
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
const StringAtom *name = &(t->getIdentifier());
|
|
|
|
|
|
|
|
VariableMap::iterator i = ((cx->getICode())->itsVariables)->find(*name);
|
|
|
|
// if (i)
|
|
|
|
mOut << (*i).first << " = " << (*i).second << "\n";
|
|
|
|
// else
|
|
|
|
// mOut << "No " << *name << " defined.\n";
|
|
|
|
|
|
|
|
*/
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2000-04-29 04:23:06 +04:00
|
|
|
} /* namespace Debugger */
|
|
|
|
} /* namespace JavaScript */
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|