scummvm/common/debugger.cpp
Johannes Schickel 0bea9cf47b Made the kyra debug extensions more generic, i.e. scumm engine could replace
their debugC calls now with the new introduced debugC calls.
(A mail how to use it will follow shortly on -devel)
Also now these special engine debug flags can be specified from the commandline.
Also made the -c & --config parameter check more secure.

svn-id: r20695
2006-02-14 23:31:25 +00:00

412 lines
11 KiB
C++

/* ScummVM - Scumm Interpreter
* Copyright (C) 2001-2006 The ScummVM project
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $URL$
* $Id$
*
*/
#include "common/stdafx.h"
#include "common/debugger.h"
#include "common/system.h"
#if USE_CONSOLE
#include "gui/console.h"
#endif
namespace Common {
template <class T>
Debugger<T>::Debugger() {
_frame_countdown = 0;
_dvar_count = 0;
_dcmd_count = 0;
_detach_now = false;
_isAttached = false;
_errStr = NULL;
_firstTime = true;
_debuggerDialog = new GUI::ConsoleDialog(1.0, 0.67F);
_debuggerDialog->setInputCallback(debuggerInputCallback, this);
_debuggerDialog->setCompletionCallback(debuggerCompletionCallback, this);
DCmd_Register("debugflag_list", &Debugger<T>::Cmd_DebugFlagsList);
DCmd_Register("debugflag_enable", &Debugger<T>::Cmd_DebugFlagEnable);
DCmd_Register("debugflag_disable", &Debugger<T>::Cmd_DebugFlagDisable);
}
template <class T>
Debugger<T>::~Debugger() {
delete _debuggerDialog;
}
// Initialisation Functions
template <class T>
int Debugger<T>::DebugPrintf(const char *format, ...) {
va_list argptr;
va_start(argptr, format);
int count;
#if USE_CONSOLE
count = _debuggerDialog->vprintf(format, argptr);
#else
count = ::vprintf(format, argptr);
#endif
va_end (argptr);
return count;
}
#ifndef __SYMBIAN32__ // gcc/UIQ doesn't like the debugger code for some reason? Actually get a cc1plus core dump here :)
template <class T>
void Debugger<T>::attach(const char *entry) {
g_system->setFeatureState(OSystem::kFeatureVirtualKeyboard, true);
if (entry) {
_errStr = strdup(entry);
}
_frame_countdown = 1;
_detach_now = false;
_isAttached = true;
}
template <class T>
void Debugger<T>::detach() {
g_system->setFeatureState(OSystem::kFeatureVirtualKeyboard, false);
_detach_now = false;
_isAttached = false;
}
// Temporary execution handler
template <class T>
void Debugger<T>::onFrame() {
if (_frame_countdown == 0)
return;
--_frame_countdown;
if (!_frame_countdown) {
preEnter();
enter();
postEnter();
// Detach if we're finished with the debugger
if (_detach_now)
detach();
}
}
#endif // of ifndef __SYMBIAN32__ // gcc/UIQ doesn't like the debugger code for some reason? Actually get a cc1plus core dump here :)
// Main Debugger Loop
template <class T>
void Debugger<T>::enter() {
#if USE_CONSOLE
if (_firstTime) {
DebugPrintf("Debugger started, type 'exit' to return to the game.\n");
DebugPrintf("Type 'help' to see a little list of commands and variables.\n");
_firstTime = false;
}
if (_errStr) {
DebugPrintf("ERROR: %s\n\n", _errStr);
free(_errStr);
_errStr = NULL;
}
_debuggerDialog->runModal();
#else
// TODO: compared to the console input, this here is very bare bone.
// For example, no support for tab completion and no history. At least
// we should re-add (optional) support for the readline library.
// Or maybe instead of choosing between a console dialog and stdio,
// we should move that choice into the ConsoleDialog class - that is,
// the console dialog code could be #ifdef'ed to not print to the dialog
// but rather to stdio. This way, we could also reuse the command history
// and tab completion of the console. It would still require a lot of
// work, but at least no dependency on a 3rd party library...
printf("Debugger entered, please switch to this console for input.\n");
int i;
char buf[256];
do {
printf("debug> ");
if (!fgets(buf, sizeof(buf), stdin))
return;
i = strlen(buf);
while (i > 0 && buf[i - 1] == '\n')
buf[--i] = 0;
if (i == 0)
continue;
} while (RunCommand(buf));
#endif
}
// Command execution loop
template <class T>
bool Debugger<T>::RunCommand(const char *inputOrig) {
int i = 0, num_params = 0;
const char *param[256];
char *input = strdup(inputOrig); // One of the rare occasions using strdup is OK (although avoiding strtok might be more elegant here).
// Parse out any params
char *tok = strtok(input, " ");
if (tok) {
do {
param[num_params++] = tok;
} while ((tok = strtok(NULL, " ")) != NULL);
} else {
param[num_params++] = input;
}
for (i=0; i < _dcmd_count; i++) {
if (!strcmp(_dcmds[i].name, param[0])) {
bool result = (((T *)this)->*_dcmds[i].function)(num_params, param);
free(input);
return result;
}
}
// It's not a command, so things get a little tricky for variables. Do fuzzy matching to ignore things like subscripts.
for (i = 0; i < _dvar_count; i++) {
if (!strncmp(_dvars[i].name, param[0], strlen(_dvars[i].name))) {
if (num_params > 1) {
// Alright, we need to check the TYPE of the variable to deref and stuff... the array stuff is a bit ugly :)
switch(_dvars[i].type) {
// Integer
case DVAR_BYTE:
*(byte *)_dvars[i].variable = atoi(param[1]);
DebugPrintf("byte%s = %d\n", param[0], *(byte *)_dvars[i].variable);
break;
case DVAR_INT:
*(int32 *)_dvars[i].variable = atoi(param[1]);
DebugPrintf("(int)%s = %d\n", param[0], *(int32 *)_dvars[i].variable);
break;
// Integer Array
case DVAR_INTARRAY: {
char *chr = (char *)strchr(param[0], '[');
if (!chr) {
DebugPrintf("You must access this array as %s[element]\n", param[0]);
} else {
int element = atoi(chr+1);
int32 *var = *(int32 **)_dvars[i].variable;
if (element >= _dvars[i].optional) {
DebugPrintf("%s is out of range (array is %d elements big)\n", param[0], _dvars[i].optional);
} else {
var[element] = atoi(param[1]);
DebugPrintf("(int)%s = %d\n", param[0], var[element]);
}
}
}
break;
default:
DebugPrintf("Failed to set variable %s to %s - unknown type\n", _dvars[i].name, param[1]);
break;
}
} else {
// And again, type-dependent prints/defrefs. The array one is still ugly.
switch(_dvars[i].type) {
// Integer
case DVAR_BYTE:
DebugPrintf("(byte)%s = %d\n", param[0], *(const byte *)_dvars[i].variable);
break;
case DVAR_INT:
DebugPrintf("(int)%s = %d\n", param[0], *(const int32 *)_dvars[i].variable);
break;
// Integer array
case DVAR_INTARRAY: {
const char *chr = strchr(param[0], '[');
if (!chr) {
DebugPrintf("You must access this array as %s[element]\n", param[0]);
} else {
int element = atoi(chr+1);
const int32 *var = *(const int32 **)_dvars[i].variable;
if (element >= _dvars[i].optional) {
DebugPrintf("%s is out of range (array is %d elements big)\n", param[0], _dvars[i].optional);
} else {
DebugPrintf("(int)%s = %d\n", param[0], var[element]);
}
}
}
break;
// String
case DVAR_STRING:
DebugPrintf("(string)%s = %s\n", param[0], ((Common::String *)_dvars[i].variable)->c_str());
break;
default:
DebugPrintf("%s = (unknown type)\n", param[0]);
break;
}
}
free(input);
return true;
}
}
DebugPrintf("Unknown command or variable\n");
free(input);
return true;
}
// returns true if something has been completed
// completion has to be delete[]-ed then
template <class T>
bool Debugger<T>::TabComplete(const char *input, char*& completion) {
// very basic tab completion
// for now it just supports command completions
// adding completions of command parameters would be nice (but hard) :-)
// maybe also give a list of possible command completions?
// (but this will require changes to console)
if (strchr(input, ' '))
return false; // already finished the first word
unsigned int inputlen = strlen(input);
unsigned int matchlen = 0;
char match[30]; // the max. command name is 30 chars
for (int i = 0; i < _dcmd_count; i++) {
if (!strncmp(_dcmds[i].name, input, inputlen)) {
unsigned int commandlen = strlen(_dcmds[i].name);
if (commandlen == inputlen) { // perfect match
return false;
}
if (commandlen > inputlen) { // possible match
// no previous match
if (matchlen == 0) {
strcpy(match, _dcmds[i].name + inputlen);
matchlen = commandlen - inputlen;
} else {
// take common prefix of previous match and this command
unsigned int j;
for (j = 0; j < matchlen; j++) {
if (match[j] != _dcmds[i].name[inputlen + j]) break;
}
matchlen = j;
}
if (matchlen == 0)
return false;
}
}
}
if (matchlen == 0)
return false;
completion = new char[matchlen + 1];
memcpy(completion, match, matchlen);
completion[matchlen] = 0;
return true;
}
// Variable registration function
template <class T>
void Debugger<T>::DVar_Register(const char *varname, void *pointer, int type, int optional) {
assert(_dvar_count < ARRAYSIZE(_dvars));
strcpy(_dvars[_dvar_count].name, varname);
_dvars[_dvar_count].type = type;
_dvars[_dvar_count].variable = pointer;
_dvars[_dvar_count].optional = optional;
_dvar_count++;
}
// Command registration function
template <class T>
void Debugger<T>::DCmd_Register(const char *cmdname, DebugProc pointer) {
assert(_dcmd_count < ARRAYSIZE(_dcmds));
strcpy(_dcmds[_dcmd_count].name, cmdname);
_dcmds[_dcmd_count].function = pointer;
_dcmd_count++;
}
template <class T>
bool Debugger<T>::Cmd_DebugFlagsList(int argc, const char **argv) {
const Common::Array<Common::EngineDebugLevel> &debugLevels = Common::listSpecialDebugLevels();
DebugPrintf("Engine debug levels:\n");
DebugPrintf("--------------------\n");
if (!debugLevels.size()) {
DebugPrintf("No engine debug levels\n");
return true;
}
for (uint i = 0; i < debugLevels.size(); ++i) {
DebugPrintf("'%s' - Description: %s\n", debugLevels[i].option.c_str(), debugLevels[i].description.c_str());
}
DebugPrintf("\n");
return true;
}
template <class T>
bool Debugger<T>::Cmd_DebugFlagEnable(int argc, const char **argv) {
if (argc < 2) {
DebugPrintf("debugflag_enable <flag>\n");
} else {
if (Common::enableSpecialDebugLevel(argv[1])) {
DebugPrintf("Enabled debug flag '%s'\n", argv[1]);
} else {
DebugPrintf("Failed to enable debug flag '%s'\n", argv[1]);
}
}
return true;
}
template <class T>
bool Debugger<T>::Cmd_DebugFlagDisable(int argc, const char **argv) {
if (argc < 2) {
DebugPrintf("debugflag_disable <flag>\n");
} else {
if (Common::disableSpecialDebugLevel(argv[1])) {
DebugPrintf("Disabled debug flag '%s'\n", argv[1]);
} else {
DebugPrintf("Failed to disable debug flag '%s'\n", argv[1]);
}
}
return true;
}
// Console handler
#if USE_CONSOLE
template <class T>
bool Debugger<T>::debuggerInputCallback(GUI::ConsoleDialog *console, const char *input, void *refCon) {
Debugger *debugger = (Debugger *)refCon;
return debugger->RunCommand(input);
}
template <class T>
bool Debugger<T>::debuggerCompletionCallback(GUI::ConsoleDialog *console, const char *input, char*& completion, void *refCon) {
Debugger *debugger = (Debugger *)refCon;
return debugger->TabComplete(input, completion);
}
#endif
} // End of namespace Common