mirror of
https://github.com/libretro/scummvm.git
synced 2025-01-26 12:48:16 +00:00
SCI: Track correct location and size of temp variables
The VM has been treating the entire area between the frame pointer and the stack pointer as temp variables for the current function. There are two problems with this: 1. The VM hasn't been updating the frame pointer correctly when multiple methods are called within the same send/self/super instruction. 2. The VM has been recalculating the number of temp variables on every instruction as the difference between the two pointers. This is incorrect, as this changes with almost every instruction in ways have nothing to do with the number of temp variables allocated by the link instruction. Meanwhile, the VM has not been recording the number of variables allocated by the link instruction. The first discrepancy caused scripts to behave differently than in SSCI when reading parameters out of bounds in certain situations. It also prevented our uninitialized variable detection from detecting certain reads. The second made the temp-count used for out of bounds detection too large, made debugger output such as `stack` incorrect, and made stepping through the link instruction in the debugger appear to do nothing until stepping through the following instruction. When multiple methods are called by a send/self/super instruction, each method's link instruction increases the stack pointer further. Method B's variables appear after method A's. The VM has been setting the stack pointer correctly but it kept using the previous frame pointer, so method B would re-use method A's stack area instead of the area the VM had just allocated for B. If a script correctly initializes variables before using them and doesn't use out of bounds parameters or temp variables then this discrepancy doesn't make a difference. But a lot of scripts do these bad things and accidentally rely on the undefined values they read. Now we update the frame pointer correctly when "carrying over" to subsequent method calls from the same send/self/super instruction. This matches SSCI behavior. We also now record the number of temp variables that have been allocated by the link instruction and use that instead of incorrectly recalculating on every instruction. Fixes the KQ6 black widow lockup, and other KQ6 music bugs, where scripts call Sound:fade without the required fourth parameter. Sound:fade expects a fourth parameter, so reads it out of bounds, and passes it as the stop-after-completion boolean to kDoSoundFade. Scripts that called Sound:fade as the only method in a send got the same results as in SSCI, but scripts that called Sound:play first didn't. Fixes bugs #5625 #5653 #6120 #6210 #6252 #13944
This commit is contained in:
parent
82c6306443
commit
30fad94e9a
@ -2995,10 +2995,18 @@ bool Console::cmdStack(int argc, const char **argv) {
|
||||
int nr = atoi(argv[1]);
|
||||
|
||||
for (int i = nr; i > 0; i--) {
|
||||
if ((xs.sp - xs.fp - i) == 0)
|
||||
bool isArgc = (xs.sp - xs.variables_argp - i == 0);
|
||||
if (isArgc)
|
||||
debugPrintf("-- parameters --\n");
|
||||
if (xs.tempCount && ((xs.sp - xs.fp - i) == 0))
|
||||
debugPrintf("-- temp variables --\n");
|
||||
if (xs.sp - xs.fp - xs.tempCount - i == 0)
|
||||
debugPrintf("-- local stack --\n");
|
||||
if (xs.sp - i >= _engine->_gamestate->stack_base)
|
||||
debugPrintf("ST:%04x = %04x:%04x\n", (unsigned)(xs.sp - i - _engine->_gamestate->stack_base), PRINT_REG(xs.sp[-i]));
|
||||
debugPrintf("ST:%04x = %04x:%04x%s\n",
|
||||
(unsigned)(xs.sp - i - _engine->_gamestate->stack_base),
|
||||
PRINT_REG(xs.sp[-i]),
|
||||
(isArgc ? " argc" : ""));
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -596,7 +596,7 @@ void run_vm(EngineState *s) {
|
||||
s->variablesSegment[VAR_LOCAL] = local_script->getLocalsSegment();
|
||||
s->variablesBase[VAR_LOCAL] = s->variables[VAR_LOCAL] = local_script->getLocalsBegin();
|
||||
s->variablesMax[VAR_LOCAL] = local_script->getLocalsCount();
|
||||
s->variablesMax[VAR_TEMP] = s->xs->sp - s->xs->fp;
|
||||
s->variablesMax[VAR_TEMP] = s->xs->tempCount;
|
||||
s->variablesMax[VAR_PARAM] = s->xs->argc + 1;
|
||||
}
|
||||
s->variables[VAR_TEMP] = s->xs->fp;
|
||||
@ -621,8 +621,6 @@ void run_vm(EngineState *s) {
|
||||
error("run_vm(): stack underflow, sp: %04x:%04x, fp: %04x:%04x",
|
||||
PRINT_REG(*s->xs->sp), PRINT_REG(*s->xs->fp));
|
||||
|
||||
s->variablesMax[VAR_TEMP] = s->xs->sp - s->xs->fp;
|
||||
|
||||
if (s->xs->addr.pc.getOffset() >= scr->getBufSize())
|
||||
error("run_vm(): program counter gone astray, addr: %d, code buffer size: %d",
|
||||
s->xs->addr.pc.getOffset(), scr->getBufSize());
|
||||
@ -817,6 +815,8 @@ void run_vm(EngineState *s) {
|
||||
break;
|
||||
|
||||
case op_link: // 0x1f (31)
|
||||
s->variablesMax[VAR_TEMP] = s->xs->tempCount = opparams[0];
|
||||
|
||||
// We shouldn't initialize temp variables at all
|
||||
// We put special segment 0xFFFF in there, so that uninitialized reads can get detected
|
||||
for (int i = 0; i < opparams[0]; i++)
|
||||
@ -919,8 +919,7 @@ void run_vm(EngineState *s) {
|
||||
case op_ret: // 0x24 (36)
|
||||
// Return from an execution loop started by call, calle, callb, send, self or super
|
||||
do {
|
||||
StackPtr old_sp2 = s->xs->sp;
|
||||
StackPtr old_fp = s->xs->fp;
|
||||
StackPtr old_sp = s->xs->sp;
|
||||
ExecStack *old_xs = &(s->_executionStack.back());
|
||||
|
||||
if ((int)s->_executionStack.size() - 1 == s->executionStackBase) { // Have we reached the base?
|
||||
@ -952,8 +951,8 @@ void run_vm(EngineState *s) {
|
||||
|
||||
if (s->xs->sp == CALL_SP_CARRY // Used in sends to 'carry' the stack pointer
|
||||
|| s->xs->type != EXEC_STACK_TYPE_CALL) {
|
||||
s->xs->sp = old_sp2;
|
||||
s->xs->fp = old_fp;
|
||||
s->xs->sp = old_sp;
|
||||
s->xs->fp = old_sp;
|
||||
}
|
||||
|
||||
} while (s->xs->type == EXEC_STACK_TYPE_VARSELECTOR);
|
||||
|
@ -91,6 +91,8 @@ struct ExecStack {
|
||||
int argc;
|
||||
StackPtr variables_argp; // Argument pointer
|
||||
|
||||
int tempCount; // Number of temp variables allocated by link opcode
|
||||
|
||||
SegmentId local_segment; // local variables etc
|
||||
|
||||
Selector debugSelector; // The selector which was used to call or -1 if not applicable
|
||||
@ -115,6 +117,7 @@ struct ExecStack {
|
||||
fp = sp = sp_;
|
||||
argc = argc_;
|
||||
variables_argp = argp_;
|
||||
tempCount = 0;
|
||||
if (localsSegment_ != kUninitializedSegment)
|
||||
local_segment = localsSegment_;
|
||||
else
|
||||
|
Loading…
x
Reference in New Issue
Block a user