scummvm/engines/sci/engine/scriptdebug.cpp
Willem Jan Palenstijn fab43f0f71 SCI: Fix offsets in disasm for multi-param opcodes
This code was assuming that retval points to the start of the next
instruction, which is only true if the current parameter is the last
one. This fixes op_call printing.
2017-10-21 18:55:40 +02:00

1224 lines
38 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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.
*
*/
// Script debugger functionality. Absolutely not threadsafe.
#include "sci/sci.h"
#include "sci/console.h"
#include "sci/debug.h"
#include "sci/engine/state.h"
#include "sci/engine/kernel.h"
#include "sci/engine/script.h"
#include "sci/engine/scriptdebug.h"
#include "common/algorithm.h"
namespace Sci {
//#define VM_DEBUG_SEND
// This table is only used for debugging. Don't include it for devices
// with not enough available memory (e.g. phones), where REDUCE_MEMORY_USAGE
// is defined
#ifndef REDUCE_MEMORY_USAGE
const char *opcodeNames[] = {
"bnot", "add", "sub", "mul", "div",
"mod", "shr", "shl", "xor", "and",
"or", "neg", "not", "eq?", "ne?",
"gt?", "ge?", "lt?", "le?", "ugt?",
"uge?", "ult?", "ule?", "bt", "bnt",
"jmp", "ldi", "push", "pushi", "toss",
"dup", "link", "call", "callk", "callb",
"calle", "ret", "send", "dummy", "dummy",
"class", "dummy", "self", "super", "&rest",
"lea", "selfID", "dummy", "pprev", "pToa",
"aTop", "pTos", "sTop", "ipToa", "dpToa",
"ipTos", "dpTos", "lofsa", "lofss", "push0",
"push1", "push2", "pushSelf", "line", "lag",
"lal", "lat", "lap", "lsg", "lsl",
"lst", "lsp", "lagi", "lali", "lati",
"lapi", "lsgi", "lsli", "lsti", "lspi",
"sag", "sal", "sat", "sap", "ssg",
"ssl", "sst", "ssp", "sagi", "sali",
"sati", "sapi", "ssgi", "ssli", "ssti",
"sspi", "+ag", "+al", "+at", "+ap",
"+sg", "+sl", "+st", "+sp", "+agi",
"+ali", "+ati", "+api", "+sgi", "+sli",
"+sti", "+spi", "-ag", "-al", "-at",
"-ap", "-sg", "-sl", "-st", "-sp",
"-agi", "-ali", "-ati", "-api", "-sgi",
"-sli", "-sti", "-spi"
};
#endif // REDUCE_MEMORY_USAGE
void DebugState::updateActiveBreakpointTypes() {
int type = 0;
for (Common::List<Breakpoint>::iterator bp = _breakpoints.begin(); bp != _breakpoints.end(); ++bp) {
if (bp->_action != BREAK_NONE)
type |= bp->_type;
}
_activeBreakpointTypes = type;
}
// Disassembles one command from the heap, returns address of next command or 0 if a ret was encountered.
reg_t disassemble(EngineState *s, reg32_t pos, const Object *obj, bool printBWTag, bool printBytecode) {
SegmentObj *mobj = s->_segMan->getSegment(pos.getSegment(), SEG_TYPE_SCRIPT);
Script *script_entity = NULL;
reg_t retval;
retval.setSegment(pos.getSegment());
retval.setOffset(pos.getOffset() + 1);
uint16 param_value = 0xffff; // Suppress GCC warning by setting default value, chose value as invalid to getKernelName etc.
uint i = 0;
Kernel *kernel = g_sci->getKernel();
if (!mobj) {
warning("Disassembly failed: Segment %04x non-existent or not a script", pos.getSegment());
return retval;
} else
script_entity = (Script *)mobj;
uint scr_size = script_entity->getBufSize();
if (pos.getOffset() >= scr_size) {
warning("Trying to disassemble beyond end of script");
return NULL_REG;
}
const byte *scr = script_entity->getBuf();
int16 opparams[4];
byte opsize;
uint bytecount = readPMachineInstruction(scr + pos.getOffset(), opsize, opparams);
const byte opcode = opsize >> 1;
debugN("%04x:%04x: ", PRINT_REG(pos));
if (printBytecode) {
if (pos.getOffset() + bytecount > scr_size) {
warning("Operation arguments extend beyond end of script");
return retval;
}
for (i = 0; i < bytecount; i++)
debugN("%02x ", scr[pos.getOffset() + i]);
for (i = bytecount; i < 5; i++)
debugN(" ");
}
opsize &= 1; // byte if true, word if false
if (printBWTag)
debugN("[%c] ", opsize ? 'B' : 'W');
if (opcode == op_pushSelf && opsize && g_sci->getGameId() != GID_FANMADE) { // 0x3e (62)
// Debug opcode op_file
debugN("file \"%s\"\n", scr + pos.getOffset() + 1); // +1: op_pushSelf size
retval.incOffset(bytecount - 1);
return retval;
}
#ifndef REDUCE_MEMORY_USAGE
debugN("%-5s", opcodeNames[opcode]);
#endif
static const char *defaultSeparator = "\t\t; ";
i = 0;
while (g_sci->_opcode_formats[opcode][i]) {
switch (g_sci->_opcode_formats[opcode][i++]) {
case Script_Invalid:
warning("-Invalid operation-");
break;
case Script_SByte:
case Script_Byte:
param_value = scr[retval.getOffset()];
debugN("\t%02x", param_value);
if (param_value > 9) {
debugN("%s%u", defaultSeparator, param_value);
}
retval.incOffset(1);
break;
case Script_Word:
case Script_SWord:
param_value = READ_SCI11ENDIAN_UINT16(&scr[retval.getOffset()]);
debugN("\t%04x", param_value);
if (param_value > 9) {
debugN("%s%u", defaultSeparator, param_value);
}
retval.incOffset(2);
break;
case Script_SVariable:
case Script_Variable:
case Script_Property:
case Script_Global:
case Script_Local:
case Script_Temp:
case Script_Param:
if (opsize) {
param_value = scr[retval.getOffset()];
retval.incOffset(1);
} else {
param_value = READ_SCI11ENDIAN_UINT16(&scr[retval.getOffset()]);
retval.incOffset(2);
}
if (opcode == op_callk) {
debugN("\t%s[%x],", (param_value < kernel->_kernelFuncs.size()) ?
((param_value < kernel->getKernelNamesSize()) ? kernel->getKernelName(param_value).c_str() : "[Unknown(postulated)]")
: "<invalid>", param_value);
} else if (opcode == op_class || opcode == op_super) {
const reg_t classAddr = s->_segMan->getClassAddress(param_value, SCRIPT_GET_DONT_LOAD, retval.getSegment());
if (!classAddr.isNull()) {
debugN("\t%s", s->_segMan->getObjectName(classAddr));
debugN(opsize ? "[%02x]" : "[%04x]", param_value);
} else {
debugN(opsize ? "\t%02x" : "\t%04x", param_value);
}
debugN(", ");
#ifdef ENABLE_SCI32
} else if (
opcode == op_pToa || opcode == op_aTop ||
opcode == op_pTos || opcode == op_sTop ||
opcode == op_ipToa || opcode == op_dpToa ||
opcode == op_ipTos || opcode == op_dpTos) {
const char *selectorName;
if (getSciVersion() == SCI_VERSION_3) {
if (param_value < kernel->getSelectorNamesSize()) {
selectorName = kernel->getSelectorName(param_value).c_str();
} else {
selectorName = "<invalid>";
}
} else {
if (obj != nullptr) {
const Object *const super = obj->getClass(s->_segMan);
assert(super);
selectorName = kernel->getSelectorName(super->getVarSelector(param_value / 2)).c_str();
} else {
selectorName = "<unavailable>";
}
}
debugN("\t%s[%x]", selectorName, param_value);
#endif
} else {
const char *separator = defaultSeparator;
debugN(opsize ? "\t%02x" : "\t%04x", param_value);
if (param_value > 9) {
debugN("%s%u", separator, param_value);
separator = ", ";
}
if (param_value >= 0x20 && param_value <= 0x7e) {
debugN("%s'%c'", separator, param_value);
separator = ", ";
}
if (opcode == op_pushi && param_value < kernel->getSelectorNamesSize()) {
debugN("%s%s", separator, kernel->getSelectorName(param_value).c_str());
}
}
break;
case Script_Offset: {
assert(opcode == op_lofsa || opcode == op_lofss);
if (opsize) {
param_value = scr[retval.getOffset()];
retval.incOffset(1);
} else {
param_value = READ_SCI11ENDIAN_UINT16(&scr[retval.getOffset()]);
retval.incOffset(2);
}
const uint32 offset = findOffset(param_value, script_entity, pos.getOffset() + bytecount);
reg_t addr;
addr.setSegment(retval.getSegment());
addr.setOffset(offset);
if (getSciVersion() == SCI_VERSION_3 && !s->_segMan->isObject(addr)) {
debugN("\t\"%s\"", s->_segMan->derefString(addr));
} else {
debugN("\t%s", s->_segMan->getObjectName(addr));
}
debugN(opsize ? "[%02x]" : "[%04x]", offset);
break;
}
case Script_SRelative:
if (opsize) {
int8 offset = (int8)scr[retval.getOffset()];
retval.incOffset(1);
debugN("\t%02x [%04x]", 0xff & offset, kOffsetMask & (pos.getOffset() + bytecount + offset));
} else {
int16 offset = (int16)READ_SCI11ENDIAN_UINT16(&scr[retval.getOffset()]);
retval.incOffset(2);
debugN("\t%04x [%04x]", 0xffff & offset, kOffsetMask & (pos.getOffset() + bytecount + offset));
}
break;
case Script_End:
retval = NULL_REG;
break;
default:
error("Internal assertion failed in disassemble()");
}
}
if (pos == s->xs->addr.pc) { // Extra information if debugging the current opcode
if ((opcode == op_pTos) || (opcode == op_sTop) || (opcode == op_pToa) || (opcode == op_aTop) ||
(opcode == op_dpToa) || (opcode == op_ipToa) || (opcode == op_dpTos) || (opcode == op_ipTos)) {
obj = s->_segMan->getObject(s->xs->objp);
if (!obj) {
warning("Attempted to reference on non-object at %04x:%04x", PRINT_REG(s->xs->objp));
} else {
if (getSciVersion() == SCI_VERSION_3)
debugN("\t(%s)", g_sci->getKernel()->getSelectorName(param_value).c_str());
else
debugN("\t(%s)", g_sci->getKernel()->getSelectorName(obj->propertyOffsetToId(s->_segMan, param_value)).c_str());
}
}
}
debugN("\n");
if (pos == s->xs->addr.pc) { // Extra information if debugging the current opcode
if (opcode == op_callk) {
int stackframe = (scr[pos.getOffset() + 2] >> 1) + (s->r_rest);
int argc = ((s->xs->sp)[- stackframe - 1]).getOffset();
bool oldScriptHeader = (getSciVersion() == SCI_VERSION_0_EARLY);
if (!oldScriptHeader)
argc += (s->r_rest);
debugN(" Kernel params: (");
for (int j = 0; j < argc; j++) {
debugN("%04x:%04x", PRINT_REG((s->xs->sp)[j - stackframe]));
if (j + 1 < argc)
debugN(", ");
}
debugN(")\n");
} else if ((opcode == op_send) || (opcode == op_self)) {
int restmod = s->r_rest;
int stackframe = (scr[pos.getOffset() + 1] >> 1) + restmod;
reg_t *sb = s->xs->sp;
uint16 selector;
reg_t fun_ref;
while (stackframe > 0) {
int argc = sb[- stackframe + 1].getOffset();
const char *name = NULL;
reg_t called_obj_addr = s->xs->objp;
if (opcode == op_send)
called_obj_addr = s->r_acc;
else if (opcode == op_self)
called_obj_addr = s->xs->objp;
selector = sb[- stackframe].getOffset();
name = s->_segMan->getObjectName(called_obj_addr);
if (!name)
name = "<invalid>";
debugN(" %s::%s[", name, g_sci->getKernel()->getSelectorName(selector).c_str());
if (!s->_segMan->getObject(called_obj_addr)) {
debugN("INVALID_OBJ");
} else {
switch (lookupSelector(s->_segMan, called_obj_addr, selector, 0, &fun_ref)) {
case kSelectorMethod:
debugN("FUNCT");
argc += restmod;
restmod = 0;
break;
case kSelectorVariable:
debugN("VAR");
break;
case kSelectorNone:
debugN("INVALID");
break;
}
}
debugN("](");
while (argc--) {
debugN("%04x:%04x", PRINT_REG(sb[- stackframe + 2]));
if (argc)
debugN(", ");
stackframe--;
}
debugN(")\n");
stackframe -= 2;
} // while (stackframe > 0)
} // Send-like opcodes
} // (heappos == *p_pc)
return retval;
}
bool isJumpOpcode(EngineState *s, reg_t pos, reg_t& jumpTarget) {
SegmentObj *mobj = s->_segMan->getSegment(pos.getSegment(), SEG_TYPE_SCRIPT);
if (!mobj)
return false;
Script *script_entity = (Script *)mobj;
uint scr_size = script_entity->getScriptSize();
if (pos.getOffset() >= scr_size)
return false;
const byte *scr = script_entity->getBuf();
int16 opparams[4];
byte opsize;
int bytecount = readPMachineInstruction(scr + pos.getOffset(), opsize, opparams);
const byte opcode = opsize >> 1;
switch (opcode) {
case op_bt:
case op_bnt:
case op_jmp:
{
reg_t jmpTarget = pos + bytecount + opparams[0];
// QFG2 has invalid jumps outside the script buffer in script 260
if (jmpTarget.getOffset() >= scr_size)
return false;
jumpTarget = jmpTarget;
}
return true;
default:
return false;
}
}
void SciEngine::scriptDebug() {
EngineState *s = _gamestate;
if (_debugState.seeking && !_debugState.breakpointWasHit) { // Are we looking for something special?
if (_debugState.seeking == kDebugSeekStepOver) {
// are we above seek-level? resume then
if (_debugState.seekLevel < (int)s->_executionStack.size())
return;
_debugState.seeking = kDebugSeekNothing;
}
if (_debugState.seeking != kDebugSeekNothing) {
const reg32_t pc = s->xs->addr.pc;
SegmentObj *mobj = s->_segMan->getSegment(pc.getSegment(), SEG_TYPE_SCRIPT);
if (mobj) {
Script *scr = (Script *)mobj;
const byte *code_buf = scr->getBuf();
uint16 code_buf_size = scr->getBufSize(); // TODO: change to a 32-bit integer for large SCI3 scripts
int opcode = pc.getOffset() >= code_buf_size ? 0 : code_buf[pc.getOffset()];
int op = opcode >> 1;
uint16 paramb1 = pc.getOffset() + 1 >= code_buf_size ? 0 : code_buf[pc.getOffset() + 1];
uint16 paramf1 = (opcode & 1) ? paramb1 : (pc.getOffset() + 2 >= code_buf_size ? 0 : (int16)READ_SCI11ENDIAN_UINT16(code_buf + pc.getOffset() + 1));
switch (_debugState.seeking) {
case kDebugSeekSpecialCallk:
if (paramb1 != _debugState.seekSpecial)
return;
case kDebugSeekCallk:
if (op != op_callk)
return;
break;
case kDebugSeekLevelRet:
if ((op != op_ret) || (_debugState.seekLevel < (int)s->_executionStack.size()-1))
return;
break;
case kDebugSeekGlobal:
if (op < op_sag)
return;
if ((op & 0x3) > 1)
return; // param or temp
if ((op & 0x3) && s->_executionStack.back().local_segment > 0)
return; // locals and not running in script.000
if (paramf1 != _debugState.seekSpecial)
return; // CORRECT global?
break;
default:
break;
}
_debugState.seeking = kDebugSeekNothing;
}
}
// OK, found whatever we were looking for
}
debugN("Step #%d\n", s->scriptStepCounter);
disassemble(s, s->xs->addr.pc, s->_segMan->getObject(s->xs->objp), false, true);
if (_debugState.runningStep) {
_debugState.runningStep--;
return;
}
_debugState.debugging = false;
_console->attach();
}
void Kernel::dumpScriptObject(const SciSpan<const byte> &script, SciSpan<const byte> object) {
const int16 species = object.getInt16SEAt(8);
const int16 superclass = object.getInt16SEAt(10);
const int16 namepos = object.getInt16SEAt(14);
int i = 0;
debugN("Object\n");
//-4 because the size includes the two-word header
Common::hexdump(object.getUnsafeDataAt(0, object.size() - 4), object.size() - 4, 16, object.sourceByteOffset());
debugN("Name: %s\n", namepos ? script.getStringAt(namepos).c_str() : "<unknown>");
debugN("Superclass: %x\n", superclass);
debugN("Species: %x\n", species);
debugN("-info-: %x\n", object.getInt16SEAt(12) & 0xFFFF);
debugN("Function area offset: %x\n", object.getInt16SEAt(4));
int16 selectors = object.getInt16SEAt(6);
debugN("Selectors [%x]:\n", selectors);
object += 8;
while (selectors--) {
debugN(" [#%03x] = 0x%x\n", i++, object.getInt16SEAt(0) & 0xFFFF);
object += 2;
}
selectors = object.getInt16SEAt(0);
int16 overloads = selectors;
debugN("Overridden functions: %x\n", overloads);
object += 2;
if (overloads < 100) {
while (overloads--) {
const int16 selector = object.getInt16SEAt(0);
debugN(" [%03x] %s: @", selector & 0xFFFF, (selector >= 0 && selector < (int)_selectorNames.size()) ? _selectorNames[selector].c_str() : "<?>");
debugN("%04x\n", object.getInt16SEAt(selectors * 2 + 2) & 0xFFFF);
object += 2;
}
}
}
void Kernel::dumpScriptClass(const SciSpan<const byte> &script, SciSpan<const byte> clazz) {
const int16 species = clazz.getInt16SEAt(8);
const int16 superclass = clazz.getInt16SEAt(10);
const int16 namepos = clazz.getInt16SEAt(14);
debugN("Class\n");
Common::hexdump(clazz.getUnsafeDataAt(0, clazz.size() - 4), clazz.size() - 4, 16, clazz.sourceByteOffset());
debugN("Name: %s\n", namepos ? script.getStringAt(namepos).c_str() : "<unknown>");
debugN("Superclass: %x\n", superclass);
debugN("Species: %x\n", species);
debugN("-info-: %x\n", clazz.getInt16SEAt(12) & 0xFFFF);
debugN("Function area offset: %x\n", clazz.getInt16SEAt(4));
int16 selectors = clazz.getInt16SEAt(6);
int16 selectorsize = selectors;
debugN("Selectors [%x]:\n", selectors);
clazz += 8;
selectorsize <<= 1;
while (selectors--) {
const int16 selector = clazz.getInt16SEAt(selectorsize);
debugN(" [%03x] %s = 0x%x\n", selector & 0xFFFF, (selector >= 0 && selector < (int)_selectorNames.size()) ? _selectorNames[selector].c_str() : "<?>", clazz.getInt16SEAt(0) & 0xFFFF);
clazz += 2;
}
clazz += selectorsize;
int16 overloads = clazz.getInt16SEAt(0);
selectors = overloads;
debugN("Overloaded functions: %x\n", overloads);
clazz += 2;
while (overloads--) {
int16 selector = clazz.getInt16SEAt(0);
debugN("selector=%d; selectorNames.size() =%d\n", selector, _selectorNames.size());
debugN(" [%03x] %s: @", selector & 0xFFFF, (selector >= 0 && selector < (int)_selectorNames.size()) ?
_selectorNames[selector].c_str() : "<?>");
debugN("%04x\n", clazz.getInt16SEAt(selectors * 2 + 2) & 0xFFFF);
clazz += 2;
}
}
void Kernel::dissectScript(int scriptNumber, Vocabulary *vocab) {
int objectctr[11] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
uint32 _seeker = 0;
Resource *script = _resMan->findResource(ResourceId(kResourceTypeScript, scriptNumber), false);
if (!script) {
warning("dissectScript(): Script not found!\n");
return;
}
while (_seeker < script->size()) {
int objType = script->getInt16SEAt(_seeker);
int objsize;
uint32 seeker = _seeker + 4;
if (!objType) {
debugN("End of script object (#0) encountered.\n");
debugN("Classes: %i, Objects: %i, Export: %i,\n Var: %i (all base 10)\n",
objectctr[6], objectctr[1], objectctr[7], objectctr[10]);
return;
}
debugN("\n");
objsize = script->getInt16SEAt(_seeker + 2);
debugN("Obj type #%x, size 0x%x: ", objType, objsize);
_seeker += objsize;
if (objType >= 0 && objType < ARRAYSIZE(objectctr))
objectctr[objType]++;
switch (objType) {
case SCI_OBJ_OBJECT:
dumpScriptObject(*script, script->subspan(seeker, objsize));
break;
case SCI_OBJ_CODE:
debugN("Code\n");
Common::hexdump(script->getUnsafeDataAt(seeker, objsize - 4), objsize - 4, 16, seeker);
break;
case SCI_OBJ_SYNONYMS:
debugN("Synonyms\n");
Common::hexdump(script->getUnsafeDataAt(seeker, objsize - 4), objsize - 4, 16, seeker);
break;
case SCI_OBJ_SAID:
debugN("Said\n");
Common::hexdump(script->getUnsafeDataAt(seeker, objsize - 4), objsize - 4, 16, seeker);
debugN("%04x: ", seeker);
vocab->debugDecipherSaidBlock(script->subspan(seeker));
debugN("\n");
break;
case SCI_OBJ_STRINGS:
debugN("Strings\n");
while (script->getUint8At(seeker)) {
const Common::String string = script->getStringAt(seeker);
debugN("%04x: %s", seeker, string.c_str());
seeker += string.size() + 1;
if (seeker > script->size()) {
debugN("[TRUNCATED]");
}
debugN("\n");
}
seeker++; // the ending zero byte
break;
case SCI_OBJ_CLASS:
dumpScriptClass(*script, script->subspan(seeker, objsize));
break;
case SCI_OBJ_EXPORTS:
debugN("Exports\n");
Common::hexdump(script->getUnsafeDataAt(seeker, objsize - 4), objsize - 4, 16, seeker);
break;
case SCI_OBJ_POINTERS:
debugN("Pointers\n");
Common::hexdump(script->getUnsafeDataAt(seeker, objsize - 4), objsize - 4, 16, seeker);
break;
case 9:
debugN("<unknown>\n");
Common::hexdump(script->getUnsafeDataAt(seeker, objsize - 4), objsize - 4, 16, seeker);
break;
case SCI_OBJ_LOCALVARS:
debugN("Local vars\n");
Common::hexdump(script->getUnsafeDataAt(seeker, objsize - 4), objsize - 4, 16, seeker);
break;
default:
debugN("Unsupported!\n");
return;
}
}
debugN("Script ends without terminator\n");
}
bool SciEngine::checkSelectorBreakpoint(BreakpointType breakpointType, reg_t send_obj, int selector) {
Common::String methodName = _gamestate->_segMan->getObjectName(send_obj);
methodName += "::" + getKernel()->getSelectorName(selector);
bool found = false;
Common::List<Breakpoint>::const_iterator bp;
for (bp = _debugState._breakpoints.begin(); bp != _debugState._breakpoints.end(); ++bp) {
if (bp->_action == BREAK_NONE || bp->_type != breakpointType)
continue;
if (bp->_name == methodName ||
(bp->_name.hasSuffix("::") && methodName.hasPrefix(bp->_name))) {
if (!found) // Show message once, but allow multiple actions
_console->debugPrintf("Break on %s (in [%04x:%04x])\n", methodName.c_str(), PRINT_REG(send_obj));
found = true;
if (bp->_action == BREAK_BREAK) {
_debugState.debugging = true;
_debugState.breakpointWasHit = true;
} else if (bp->_action == BREAK_BACKTRACE) {
logBacktrace();
} else if (bp->_action == BREAK_INSPECT) {
printObject(send_obj);
}
}
}
return found;
}
bool SciEngine::checkExportBreakpoint(uint16 script, uint16 pubfunct) {
if (!(_debugState._activeBreakpointTypes & BREAK_EXPORT))
return false;
bool found = false;
uint32 bpaddress = (script << 16 | pubfunct);
Common::List<Breakpoint>::const_iterator bp;
for (bp = _debugState._breakpoints.begin(); bp != _debugState._breakpoints.end(); ++bp) {
if (bp->_action == BREAK_NONE || bp->_type != BREAK_EXPORT)
continue;
if (bp->_address == bpaddress) {
if (!found) // Show message once, but allow multiple actions
_console->debugPrintf("Break on script %d, export %d\n", script, pubfunct);
found = true;
if (bp->_action == BREAK_BREAK) {
_debugState.debugging = true;
_debugState.breakpointWasHit = true;
} else if (bp->_action == BREAK_BACKTRACE) {
logBacktrace();
} else if (bp->_action == BREAK_INSPECT) {
// Ignoring this mode, to make it identical to BREAK_LOG
}
}
}
return found;
}
bool SciEngine::checkAddressBreakpoint(const reg32_t &address) {
if (!(_debugState._activeBreakpointTypes & BREAK_ADDRESS))
return false;
bool found = false;
Common::List<Breakpoint>::const_iterator bp;
for (bp = _debugState._breakpoints.begin(); bp != _debugState._breakpoints.end(); ++bp) {
if (bp->_action == BREAK_NONE || bp->_type != BREAK_ADDRESS)
continue;
if (bp->_regAddress == address) {
if (!found)
_console->debugPrintf("Break at %04x:%04x\n", PRINT_REG(address));
found = true;
if (bp->_action == BREAK_BREAK) {
_debugState.debugging = true;
_debugState.breakpointWasHit = true;
} else if (bp->_action == BREAK_BACKTRACE) {
logBacktrace();
} else if (bp->_action == BREAK_INSPECT) {
// Ignoring this mode, to make it identical to BREAK_LOG
}
}
}
return found;
}
bool matchKernelBreakpointPattern(const Common::String &pattern, const Common::String &name) {
// Pattern:
// A comma-separated list of atoms.
// An atom is a (possibly empty) word, optionally with a ! prefix (for
// a negative-match), and/or a * suffix (for a prefix-match).
// The last matching atom in the pattern takes effect.
// Examples:
// FrameOut : matches only FrameOut
// * : matches everything
// *,!FrameOut : matches everything except FrameOut
// InitBresen,DoBresen : matches InitBresen and DoBresen
// DoSound*,!DoSoundUpdateCues : matches all DoSound sub-functions except
// DoSoundUpdateCues
bool result = false;
Common::String::const_iterator i = pattern.begin();
while (i != pattern.end()) {
Common::String::const_iterator next = Common::find(i, pattern.end(), ',');
bool negative = *i == '!';
if (negative)
i++;
Common::String atom(i, next - i);
bool wildcard = atom.lastChar() == '*';
if (wildcard)
atom.deleteLastChar();
if ((!wildcard && atom == name) || (wildcard && name.hasPrefix(atom)))
result = !negative;
i = next;
if (i != pattern.end())
++i; // skip comma
}
return result;
}
bool SciEngine::checkKernelBreakpoint(const Common::String &name) {
if (!(_debugState._activeBreakpointTypes & BREAK_KERNEL))
return false;
bool found = false;
Common::List<Breakpoint>::const_iterator bp;
for (bp = _debugState._breakpoints.begin(); bp != _debugState._breakpoints.end(); ++bp) {
if (bp->_action == BREAK_NONE || bp->_type != BREAK_KERNEL)
continue;
if (matchKernelBreakpointPattern(bp->_name, name)) {
if (bp->_action == BREAK_BREAK) {
if (!found)
_console->debugPrintf("Break on k%s\n", name.c_str());
_debugState.debugging = true;
_debugState.breakpointWasHit = true;
} else if (bp->_action == BREAK_BACKTRACE) {
if (!found)
_console->debugPrintf("Break on k%s\n", name.c_str());
logBacktrace();
} else if (bp->_action == BREAK_INSPECT) {
// Ignoring this mode, to make it identical to BREAK_LOG
}
found = true;
}
}
return found;
}
void debugSelectorCall(reg_t send_obj, Selector selector, int argc, StackPtr argp, ObjVarRef &varp, reg_t funcp, SegManager *segMan, SelectorType selectorType) {
int activeBreakpointTypes = g_sci->_debugState._activeBreakpointTypes;
const char *objectName = segMan->getObjectName(send_obj);
const char *selectorName = g_sci->getKernel()->getSelectorName(selector).c_str();
Console *con = g_sci->getSciDebugger();
#ifdef VM_DEBUG_SEND
debugN("Send to %04x:%04x (%s), selector %04x (%s):", PRINT_REG(send_obj),
segMan->getObjectName(send_obj), selector,
g_sci->getKernel()->getSelectorName(selector).c_str());
#endif // VM_DEBUG_SEND
switch (selectorType) {
case kSelectorNone:
debugN("\n");
break;
case kSelectorVariable:
#ifdef VM_DEBUG_SEND
if (argc)
debugN("Varselector: Write %04x:%04x\n", PRINT_REG(argp[1]));
else
debugN("Varselector: Read\n");
#endif // VM_DEBUG_SEND
// argc == 0: read selector
// argc == 1: write selector
// argc can be bigger than 1 in some cases, because of a script bug.
// Usually, these aren't fatal.
if ((activeBreakpointTypes & BREAK_SELECTORREAD) ||
(activeBreakpointTypes & BREAK_SELECTORWRITE) ||
argc > 1) {
reg_t selectorValue = *varp.getPointer(segMan);
if (!argc && (activeBreakpointTypes & BREAK_SELECTORREAD)) {
if (g_sci->checkSelectorBreakpoint(BREAK_SELECTORREAD, send_obj, selector))
con->debugPrintf("Read from selector (%s:%s): %04x:%04x\n",
objectName, selectorName,
PRINT_REG(selectorValue));
} else if (argc && (activeBreakpointTypes & BREAK_SELECTORWRITE)) {
if (g_sci->checkSelectorBreakpoint(BREAK_SELECTORWRITE, send_obj, selector))
con->debugPrintf("Write to selector (%s:%s): change %04x:%04x to %04x:%04x\n",
objectName, selectorName,
PRINT_REG(selectorValue), PRINT_REG(argp[1]));
}
if (argc > 1)
debug(kDebugLevelScripts, "Write to selector (%s:%s): change %04x:%04x to %04x:%04x, argc == %d\n",
objectName, selectorName,
PRINT_REG(selectorValue), PRINT_REG(argp[1]), argc);
}
break;
case kSelectorMethod:
#ifndef VM_DEBUG_SEND
if (activeBreakpointTypes & BREAK_SELECTOREXEC) {
if (g_sci->checkSelectorBreakpoint(BREAK_SELECTOREXEC, send_obj, selector)) {
#else
if (true) {
if (true) {
#endif
con->debugPrintf("%s::%s(", objectName, selectorName);
for (int i = 0; i < argc; i++) {
con->debugPrintf("%04x:%04x", PRINT_REG(argp[i+1]));
if (i + 1 < argc)
con->debugPrintf(", ");
}
con->debugPrintf(") at %04x:%04x\n", PRINT_REG(funcp));
}
}
break;
} // switch
}
void debugPropertyAccess(Object *obj, reg_t objp, unsigned int index, reg_t curValue, reg_t newValue, SegManager *segMan, BreakpointType breakpointType) {
const Object *var_container = obj;
if (!obj->isClass() && getSciVersion() != SCI_VERSION_3)
var_container = segMan->getObject(obj->getSuperClassSelector());
uint16 varSelector;
if (getSciVersion() == SCI_VERSION_3) {
varSelector = index;
} else {
index >>= 1;
if (index >= var_container->getVarCount()) {
// TODO: error, warning, debug?
return;
}
varSelector = var_container->getVarSelector(index);
}
if (g_sci->checkSelectorBreakpoint(breakpointType, objp, varSelector)) {
// checkSelectorBreakpoint has already triggered the breakpoint.
// We just output the relevant data here.
Console *con = g_sci->getSciDebugger();
const char *objectName = segMan->getObjectName(objp);
const char *selectorName = g_sci->getKernel()->getSelectorName(varSelector).c_str();
if (breakpointType == BREAK_SELECTORWRITE) {
con->debugPrintf("Write to selector (%s:%s): change %04x:%04x to %04x:%04x\n",
objectName, selectorName,
PRINT_REG(curValue), PRINT_REG(newValue));
} else if (breakpointType == BREAK_SELECTORREAD) {
con->debugPrintf("Read from selector (%s:%s): %04x:%04x\n",
objectName, selectorName,
PRINT_REG(curValue));
} else {
assert(false);
}
}
}
void logKernelCall(const KernelFunction *kernelCall, const KernelSubFunction *kernelSubCall, EngineState *s, int argc, reg_t *argv, reg_t result) {
if (s->abortScriptProcessing != kAbortNone) {
return;
}
Kernel *kernel = g_sci->getKernel();
if (!kernelSubCall) {
debugN("k%s: ", kernelCall->name);
} else {
int callNameLen = strlen(kernelCall->name);
if (strncmp(kernelCall->name, kernelSubCall->name, callNameLen) == 0) {
const char *subCallName = kernelSubCall->name + callNameLen;
debugN("k%s(%s): ", kernelCall->name, subCallName);
} else {
debugN("k%s(%s): ", kernelCall->name, kernelSubCall->name);
}
}
for (int parmNr = 0; parmNr < argc; parmNr++) {
if (parmNr)
debugN(", ");
uint16 regType = kernel->findRegType(argv[parmNr]);
if (regType & SIG_TYPE_NULL)
debugN("0");
else if (regType & SIG_TYPE_UNINITIALIZED)
debugN("UNINIT");
else if (regType & SIG_IS_INVALID)
debugN("INVALID");
else if (regType & SIG_TYPE_INTEGER)
debugN("%d", argv[parmNr].getOffset());
else {
debugN("%04x:%04x", PRINT_REG(argv[parmNr]));
switch (regType) {
case SIG_TYPE_OBJECT:
debugN(" (%s)", s->_segMan->getObjectName(argv[parmNr]));
break;
case SIG_TYPE_REFERENCE:
{
SegmentObj *mobj = s->_segMan->getSegmentObj(argv[parmNr].getSegment());
if (mobj) {
switch (mobj->getType()) {
case SEG_TYPE_HUNK:
{
HunkTable &ht = *(HunkTable *)mobj;
int index = argv[parmNr].getOffset();
if (ht.isValidEntry(index)) {
// NOTE: This ", deleted" isn't as useful as it could
// be because it prints the status _after_ the kernel
// call.
debugN(" ('%s' hunk%s)", ht[index].type, ht[index].mem ? "" : ", deleted");
} else
debugN(" (INVALID hunk ref)");
break;
}
default:
// TODO: Any other segment types which could
// use special handling?
if (kernelCall->function == kSaid) {
SegmentRef saidSpec = s->_segMan->dereference(argv[parmNr]);
if (saidSpec.isRaw) {
debugN(" ('");
g_sci->getVocabulary()->debugDecipherSaidBlock(SciSpan<const byte>(saidSpec.raw, saidSpec.maxSize, Common::String::format("said %04x:%04x", PRINT_REG(argv[parmNr]))));
debugN("')");
} else {
debugN(" (non-raw said-spec)");
}
} else {
debugN(" ('%s')", s->_segMan->getString(argv[parmNr]).c_str());
}
break;
}
}
}
default:
break;
}
}
}
if (result.isPointer())
debugN(" = %04x:%04x\n", PRINT_REG(result));
else
debugN(" = %d\n", result.getOffset());
}
void logBacktrace() {
Console *con = g_sci->getSciDebugger();
EngineState *s = g_sci->getEngineState();
con->debugPrintf("Call stack (current base: 0x%x):\n", s->executionStackBase);
Common::List<ExecStack>::const_iterator iter;
uint i = 0;
for (iter = s->_executionStack.begin();
iter != s->_executionStack.end(); ++iter, ++i) {
const ExecStack &call = *iter;
const char *objname = s->_segMan->getObjectName(call.sendp);
int paramc, totalparamc;
switch (call.type) {
case EXEC_STACK_TYPE_CALL: // Normal function
if (call.type == EXEC_STACK_TYPE_CALL)
con->debugPrintf(" %x: script %d - ", i, s->_segMan->getScript(call.addr.pc.getSegment())->getScriptNumber());
if (call.debugSelector != -1) {
con->debugPrintf("%s::%s(", objname, g_sci->getKernel()->getSelectorName(call.debugSelector).c_str());
} else if (call.debugExportId != -1) {
con->debugPrintf("export %d (", call.debugExportId);
} else if (call.debugLocalCallOffset != -1) {
con->debugPrintf("call %x (", call.debugLocalCallOffset);
}
break;
case EXEC_STACK_TYPE_KERNEL: // Kernel function
if (call.debugKernelSubFunction == -1)
con->debugPrintf(" %x:[%x] k%s(", i, call.debugOrigin, g_sci->getKernel()->getKernelName(call.debugKernelFunction).c_str());
else
con->debugPrintf(" %x:[%x] k%s(", i, call.debugOrigin, g_sci->getKernel()->getKernelName(call.debugKernelFunction, call.debugKernelSubFunction).c_str());
break;
case EXEC_STACK_TYPE_VARSELECTOR:
con->debugPrintf(" %x:[%x] vs%s %s::%s (", i, call.debugOrigin, (call.argc) ? "write" : "read",
objname, g_sci->getKernel()->getSelectorName(call.debugSelector).c_str());
break;
}
totalparamc = call.argc;
if (totalparamc > 16)
totalparamc = 16;
for (paramc = 1; paramc <= totalparamc; paramc++) {
con->debugPrintf("%04x:%04x", PRINT_REG(call.variables_argp[paramc]));
if (paramc < call.argc)
con->debugPrintf(", ");
}
if (call.argc > 16)
con->debugPrintf("...");
con->debugPrintf(")\n ");
if (call.debugOrigin != -1)
con->debugPrintf("by %x ", call.debugOrigin);
con->debugPrintf("obj@%04x:%04x", PRINT_REG(call.objp));
if (call.type == EXEC_STACK_TYPE_CALL) {
con->debugPrintf(" pc=%04x:%04x", PRINT_REG(call.addr.pc));
if (call.sp == CALL_SP_CARRY)
con->debugPrintf(" sp,fp:carry");
else {
con->debugPrintf(" sp=ST:%04x", (unsigned)(call.sp - s->stack_base));
con->debugPrintf(" fp=ST:%04x", (unsigned)(call.fp - s->stack_base));
}
} else
con->debugPrintf(" pc:none");
con->debugPrintf(" argp:ST:%04x", (unsigned)(call.variables_argp - s->stack_base));
con->debugPrintf("\n");
}
}
bool printObject(reg_t pos) {
Console *con = g_sci->getSciDebugger();
EngineState *s = g_sci->getEngineState();
const Object *obj = s->_segMan->getObject(pos);
const Object *var_container = obj;
uint i;
if (!obj) {
con->debugPrintf("[%04x:%04x]: Not an object.\n", PRINT_REG(pos));
return false;
}
// Object header
con->debugPrintf("[%04x:%04x] %s : %3d vars, %3d methods\n", PRINT_REG(pos), s->_segMan->getObjectName(pos),
obj->getVarCount(), obj->getMethodCount());
if (!obj->isClass())
var_container = s->_segMan->getObject(obj->getSuperClassSelector());
con->debugPrintf(" -- member variables:\n");
if (getSciVersion() == SCI_VERSION_3) {
con->debugPrintf(" (----) [---] -size- = 0000:%04x (%d)\n", obj->getVarCount(), obj->getVarCount());
con->debugPrintf(" (----) [---] -classScript- = %04x:%04x (%d)\n", PRINT_REG(obj->getClassScriptSelector()), obj->getClassScriptSelector().getOffset());
con->debugPrintf(" (----) [---] -species- = %04x:%04x (%s)\n", PRINT_REG(obj->getSpeciesSelector()), s->_segMan->getObjectName(obj->getSpeciesSelector()));
con->debugPrintf(" (----) [---] -super- = %04x:%04x (%s)\n", PRINT_REG(obj->getSuperClassSelector()), s->_segMan->getObjectName(obj->getSuperClassSelector()));
con->debugPrintf(" (----) [---] -info- = %04x:%04x (%d)\n", PRINT_REG(obj->getInfoSelector()), obj->getInfoSelector().getOffset());
}
for (i = 0; (uint)i < obj->getVarCount(); i++) {
con->debugPrintf(" ");
if (var_container && i < var_container->getVarCount()) {
uint16 varSelector = var_container->getVarSelector(i);
// Times two commented out for now for easy parsing of vocab.994
con->debugPrintf("(%04x) [%03x] %s = ", i /* *2 */, varSelector, g_sci->getKernel()->getSelectorName(varSelector).c_str());
} else
con->debugPrintf("p#%x = ", i);
reg_t val = obj->getVariable(i);
con->debugPrintf("%04x:%04x", PRINT_REG(val));
if (!val.getSegment())
con->debugPrintf(" (%d)", val.getOffset());
const Object *ref = s->_segMan->getObject(val);
if (ref)
con->debugPrintf(" (%s)", s->_segMan->getObjectName(val));
con->debugPrintf("\n");
}
con->debugPrintf(" -- methods:\n");
Common::Array<Selector> foundMethods;
const Object *protoObj = obj;
do {
for (i = 0; i < protoObj->getMethodCount(); i++) {
const Selector selector = protoObj->getFuncSelector(i);
if (Common::find(foundMethods.begin(), foundMethods.end(), selector) == foundMethods.end()) {
reg_t fptr = protoObj->getFunction(i);
con->debugPrintf(" [%03x] ", selector);
if (protoObj != obj) {
con->debugPrintf("%s::", s->_segMan->getObjectName(protoObj->getPos()));
}
con->debugPrintf("%s = %04x:%04x\n", g_sci->getKernel()->getSelectorName(selector).c_str(), PRINT_REG(fptr));
foundMethods.push_back(selector);
}
}
} while ((protoObj = s->_segMan->getObject(protoObj->getSuperClassSelector())));
Script *scr = s->_segMan->getScriptIfLoaded(pos.getSegment());
if (scr)
con->debugPrintf("\nOwner script: %d\n", scr->getScriptNumber());
return true;
}
} // End of namespace Sci