mirror of
https://github.com/Q3Rally-Team/rallyunlimited-engine.git
synced 2024-11-25 21:51:20 +00:00
2148 lines
49 KiB
C
2148 lines
49 KiB
C
/*
|
|
===========================================================================
|
|
Copyright (C) 1999-2005 Id Software, Inc.
|
|
Copyright (C) 2012-2020 Quake3e project
|
|
|
|
This file is part of Quake III Arena source code.
|
|
|
|
Quake III Arena source code 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.
|
|
|
|
Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
===========================================================================
|
|
*/
|
|
// vm.c -- virtual machine
|
|
|
|
/*
|
|
|
|
|
|
intermix code and data
|
|
symbol table
|
|
|
|
a dll has one imported function: VM_SystemCall
|
|
and one exported function: Perform
|
|
|
|
|
|
*/
|
|
|
|
#include "vm_local.h"
|
|
|
|
opcode_info_t ops[ OP_MAX ] =
|
|
{
|
|
// size, stack, nargs, flags
|
|
{ 0, 0, 0, 0 }, // undef
|
|
{ 0, 0, 0, 0 }, // ignore
|
|
{ 0, 0, 0, 0 }, // break
|
|
|
|
{ 4, 0, 0, 0 }, // enter
|
|
{ 4,-4, 0, 0 }, // leave
|
|
{ 0, 0, 1, 0 }, // call
|
|
{ 0, 4, 0, 0 }, // push
|
|
{ 0,-4, 1, 0 }, // pop
|
|
|
|
{ 4, 4, 0, 0 }, // const
|
|
{ 4, 4, 0, 0 }, // local
|
|
{ 0,-4, 1, 0 }, // jump
|
|
|
|
{ 4,-8, 2, JUMP }, // eq
|
|
{ 4,-8, 2, JUMP }, // ne
|
|
|
|
{ 4,-8, 2, JUMP }, // lti
|
|
{ 4,-8, 2, JUMP }, // lei
|
|
{ 4,-8, 2, JUMP }, // gti
|
|
{ 4,-8, 2, JUMP }, // gei
|
|
|
|
{ 4,-8, 2, JUMP }, // ltu
|
|
{ 4,-8, 2, JUMP }, // leu
|
|
{ 4,-8, 2, JUMP }, // gtu
|
|
{ 4,-8, 2, JUMP }, // geu
|
|
|
|
{ 4,-8, 2, JUMP|FPU }, // eqf
|
|
{ 4,-8, 2, JUMP|FPU }, // nef
|
|
|
|
{ 4,-8, 2, JUMP|FPU }, // ltf
|
|
{ 4,-8, 2, JUMP|FPU }, // lef
|
|
{ 4,-8, 2, JUMP|FPU }, // gtf
|
|
{ 4,-8, 2, JUMP|FPU }, // gef
|
|
|
|
{ 0, 0, 1, 0 }, // load1
|
|
{ 0, 0, 1, 0 }, // load2
|
|
{ 0, 0, 1, 0 }, // load4
|
|
{ 0,-8, 2, 0 }, // store1
|
|
{ 0,-8, 2, 0 }, // store2
|
|
{ 0,-8, 2, 0 }, // store4
|
|
{ 1,-4, 1, 0 }, // arg
|
|
{ 4,-8, 2, 0 }, // bcopy
|
|
|
|
{ 0, 0, 1, 0 }, // sex8
|
|
{ 0, 0, 1, 0 }, // sex16
|
|
|
|
{ 0, 0, 1, 0 }, // negi
|
|
{ 0,-4, 3, 0 }, // add
|
|
{ 0,-4, 3, 0 }, // sub
|
|
{ 0,-4, 3, 0 }, // divi
|
|
{ 0,-4, 3, 0 }, // divu
|
|
{ 0,-4, 3, 0 }, // modi
|
|
{ 0,-4, 3, 0 }, // modu
|
|
{ 0,-4, 3, 0 }, // muli
|
|
{ 0,-4, 3, 0 }, // mulu
|
|
|
|
{ 0,-4, 3, 0 }, // band
|
|
{ 0,-4, 3, 0 }, // bor
|
|
{ 0,-4, 3, 0 }, // bxor
|
|
{ 0, 0, 1, 0 }, // bcom
|
|
|
|
{ 0,-4, 3, 0 }, // lsh
|
|
{ 0,-4, 3, 0 }, // rshi
|
|
{ 0,-4, 3, 0 }, // rshu
|
|
|
|
{ 0, 0, 1, FPU }, // negf
|
|
{ 0,-4, 3, FPU }, // addf
|
|
{ 0,-4, 3, FPU }, // subf
|
|
{ 0,-4, 3, FPU }, // divf
|
|
{ 0,-4, 3, FPU }, // mulf
|
|
|
|
{ 0, 0, 1, 0 }, // cvif
|
|
{ 0, 0, 1, FPU } // cvfi
|
|
};
|
|
|
|
const char *opname[ 256 ] = {
|
|
"OP_UNDEF",
|
|
|
|
"OP_IGNORE",
|
|
|
|
"OP_BREAK",
|
|
|
|
"OP_ENTER",
|
|
"OP_LEAVE",
|
|
"OP_CALL",
|
|
"OP_PUSH",
|
|
"OP_POP",
|
|
|
|
"OP_CONST",
|
|
|
|
"OP_LOCAL",
|
|
|
|
"OP_JUMP",
|
|
|
|
//-------------------
|
|
|
|
"OP_EQ",
|
|
"OP_NE",
|
|
|
|
"OP_LTI",
|
|
"OP_LEI",
|
|
"OP_GTI",
|
|
"OP_GEI",
|
|
|
|
"OP_LTU",
|
|
"OP_LEU",
|
|
"OP_GTU",
|
|
"OP_GEU",
|
|
|
|
"OP_EQF",
|
|
"OP_NEF",
|
|
|
|
"OP_LTF",
|
|
"OP_LEF",
|
|
"OP_GTF",
|
|
"OP_GEF",
|
|
|
|
//-------------------
|
|
|
|
"OP_LOAD1",
|
|
"OP_LOAD2",
|
|
"OP_LOAD4",
|
|
"OP_STORE1",
|
|
"OP_STORE2",
|
|
"OP_STORE4",
|
|
"OP_ARG",
|
|
|
|
"OP_BLOCK_COPY",
|
|
|
|
//-------------------
|
|
|
|
"OP_SEX8",
|
|
"OP_SEX16",
|
|
|
|
"OP_NEGI",
|
|
"OP_ADD",
|
|
"OP_SUB",
|
|
"OP_DIVI",
|
|
"OP_DIVU",
|
|
"OP_MODI",
|
|
"OP_MODU",
|
|
"OP_MULI",
|
|
"OP_MULU",
|
|
|
|
"OP_BAND",
|
|
"OP_BOR",
|
|
"OP_BXOR",
|
|
"OP_BCOM",
|
|
|
|
"OP_LSH",
|
|
"OP_RSHI",
|
|
"OP_RSHU",
|
|
|
|
"OP_NEGF",
|
|
"OP_ADDF",
|
|
"OP_SUBF",
|
|
"OP_DIVF",
|
|
"OP_MULF",
|
|
|
|
"OP_CVIF",
|
|
"OP_CVFI"
|
|
};
|
|
|
|
cvar_t *vm_rtChecks;
|
|
|
|
#ifdef DEBUG
|
|
int vm_debugLevel;
|
|
#endif
|
|
|
|
// used by Com_Error to get rid of running vm's before longjmp
|
|
static int forced_unload;
|
|
|
|
static struct vm_s vmTable[ VM_COUNT ];
|
|
|
|
static const char *vmName[ VM_COUNT ] = {
|
|
"qagame",
|
|
"cgame",
|
|
"ui"
|
|
};
|
|
|
|
static void VM_VmInfo_f( void );
|
|
static void VM_VmProfile_f( void );
|
|
|
|
#ifdef DEBUG
|
|
void VM_Debug( int level ) {
|
|
vm_debugLevel = level;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
==============
|
|
VM_CheckBounds
|
|
==============
|
|
*/
|
|
void VM_CheckBounds( const vm_t *vm, unsigned int address, unsigned int length )
|
|
{
|
|
//if ( !vm->entryPoint )
|
|
{
|
|
if ( (address | length) > vm->dataMask || (address + length) > vm->dataMask )
|
|
{
|
|
//Com_Error( ERR_DROP, "program tried to bypass data segment bounds" );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
==============
|
|
VM_CheckBounds2
|
|
==============
|
|
*/
|
|
void VM_CheckBounds2( const vm_t *vm, unsigned int addr1, unsigned int addr2, unsigned int length )
|
|
{
|
|
//if ( !vm->entryPoint )
|
|
{
|
|
if ( (addr1 | addr2 | length) > vm->dataMask || (addr1 + length) > vm->dataMask || (addr2+length) > vm->dataMask )
|
|
{
|
|
//Com_Error( ERR_DROP, "program tried to bypass data segment bounds" );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
==============
|
|
VM_Init
|
|
==============
|
|
*/
|
|
void VM_Init( void ) {
|
|
#ifndef DEDICATED
|
|
Cvar_Get( "vm_ui", "2", CVAR_ARCHIVE | CVAR_PROTECTED ); // !@# SHIP WITH SET TO 2
|
|
Cvar_Get( "vm_cgame", "2", CVAR_ARCHIVE | CVAR_PROTECTED ); // !@# SHIP WITH SET TO 2
|
|
#endif
|
|
Cvar_Get( "vm_game", "2", CVAR_ARCHIVE | CVAR_PROTECTED ); // !@# SHIP WITH SET TO 2
|
|
|
|
Cmd_AddCommand( "vmprofile", VM_VmProfile_f );
|
|
Cmd_AddCommand( "vminfo", VM_VmInfo_f );
|
|
|
|
Com_Memset( vmTable, 0, sizeof( vmTable ) );
|
|
}
|
|
|
|
|
|
/*
|
|
===============
|
|
VM_ValueToSymbol
|
|
|
|
Assumes a program counter value
|
|
===============
|
|
*/
|
|
const char *VM_ValueToSymbol( vm_t *vm, int value ) {
|
|
vmSymbol_t *sym;
|
|
static char text[MAX_TOKEN_CHARS];
|
|
|
|
sym = vm->symbols;
|
|
if ( !sym ) {
|
|
return "NO SYMBOLS";
|
|
}
|
|
|
|
// find the symbol
|
|
while ( sym->next && sym->next->symValue <= value ) {
|
|
sym = sym->next;
|
|
}
|
|
|
|
if ( value == sym->symValue ) {
|
|
return sym->symName;
|
|
}
|
|
|
|
Com_sprintf( text, sizeof( text ), "%s+%i", sym->symName, value - sym->symValue );
|
|
|
|
return text;
|
|
}
|
|
|
|
|
|
/*
|
|
===============
|
|
VM_ValueToFunctionSymbol
|
|
|
|
For profiling, find the symbol behind this value
|
|
===============
|
|
*/
|
|
vmSymbol_t *VM_ValueToFunctionSymbol( vm_t *vm, int value ) {
|
|
vmSymbol_t *sym;
|
|
static vmSymbol_t nullSym;
|
|
|
|
sym = vm->symbols;
|
|
if ( !sym ) {
|
|
return &nullSym;
|
|
}
|
|
|
|
while ( sym->next && sym->next->symValue <= value ) {
|
|
sym = sym->next;
|
|
}
|
|
|
|
return sym;
|
|
}
|
|
|
|
|
|
/*
|
|
===============
|
|
VM_SymbolToValue
|
|
===============
|
|
*/
|
|
int VM_SymbolToValue( vm_t *vm, const char *symbol ) {
|
|
vmSymbol_t *sym;
|
|
|
|
for ( sym = vm->symbols ; sym ; sym = sym->next ) {
|
|
if ( !strcmp( symbol, sym->symName ) ) {
|
|
return sym->symValue;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*
|
|
=====================
|
|
VM_SymbolForCompiledPointer
|
|
=====================
|
|
*/
|
|
#if 0 // 64bit!
|
|
const char *VM_SymbolForCompiledPointer( vm_t *vm, void *code ) {
|
|
int i;
|
|
|
|
if ( code < (void *)vm->codeBase.ptr ) {
|
|
return "Before code block";
|
|
}
|
|
if ( code >= (void *)(vm->codeBase.ptr + vm->codeLength) ) {
|
|
return "After code block";
|
|
}
|
|
|
|
// find which original instruction it is after
|
|
for ( i = 0 ; i < vm->codeLength ; i++ ) {
|
|
if ( (void *)vm->instructionPointers[i] > code ) {
|
|
break;
|
|
}
|
|
}
|
|
i--;
|
|
|
|
// now look up the bytecode instruction pointer
|
|
return VM_ValueToSymbol( vm, i );
|
|
}
|
|
#endif
|
|
|
|
|
|
/*
|
|
===============
|
|
ParseHex
|
|
===============
|
|
*/
|
|
static int ParseHex( const char *text ) {
|
|
int value;
|
|
int c;
|
|
|
|
value = 0;
|
|
while ( ( c = *text++ ) != 0 ) {
|
|
if ( c >= '0' && c <= '9' ) {
|
|
value = value * 16 + c - '0';
|
|
continue;
|
|
}
|
|
if ( c >= 'a' && c <= 'f' ) {
|
|
value = value * 16 + 10 + c - 'a';
|
|
continue;
|
|
}
|
|
if ( c >= 'A' && c <= 'F' ) {
|
|
value = value * 16 + 10 + c - 'A';
|
|
continue;
|
|
}
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
|
|
/*
|
|
===============
|
|
VM_LoadSymbols
|
|
===============
|
|
*/
|
|
static void VM_LoadSymbols( vm_t *vm ) {
|
|
union {
|
|
char *c;
|
|
void *v;
|
|
} mapfile;
|
|
const char *text_p, *token;
|
|
char name[MAX_QPATH];
|
|
char symbols[MAX_QPATH];
|
|
vmSymbol_t **prev, *sym;
|
|
int count;
|
|
int value;
|
|
int chars;
|
|
int segment;
|
|
int numInstructions;
|
|
|
|
// don't load symbols if not developer
|
|
if ( !com_developer->integer ) {
|
|
return;
|
|
}
|
|
|
|
COM_StripExtension(vm->name, name, sizeof(name));
|
|
Com_sprintf( symbols, sizeof( symbols ), "vm/%s.map", name );
|
|
FS_ReadFile( symbols, &mapfile.v );
|
|
if ( !mapfile.c ) {
|
|
Com_Printf( "Couldn't load symbol file: %s\n", symbols );
|
|
return;
|
|
}
|
|
|
|
numInstructions = vm->instructionCount;
|
|
|
|
// parse the symbols
|
|
text_p = mapfile.c;
|
|
prev = &vm->symbols;
|
|
count = 0;
|
|
|
|
while ( 1 ) {
|
|
token = COM_Parse( &text_p );
|
|
if ( !token[0] ) {
|
|
break;
|
|
}
|
|
segment = ParseHex( token );
|
|
if ( segment ) {
|
|
COM_Parse( &text_p );
|
|
COM_Parse( &text_p );
|
|
continue; // only load code segment values
|
|
}
|
|
|
|
token = COM_Parse( &text_p );
|
|
if ( !token[0] ) {
|
|
Com_Printf( "WARNING: incomplete line at end of file\n" );
|
|
break;
|
|
}
|
|
value = ParseHex( token );
|
|
|
|
token = COM_Parse( &text_p );
|
|
if ( !token[0] ) {
|
|
Com_Printf( "WARNING: incomplete line at end of file\n" );
|
|
break;
|
|
}
|
|
chars = strlen( token );
|
|
sym = Hunk_Alloc( sizeof( *sym ) + chars, h_high );
|
|
*prev = sym;
|
|
prev = &sym->next;
|
|
sym->next = NULL;
|
|
|
|
// convert value from an instruction number to a code offset
|
|
if ( vm->instructionPointers && value >= 0 && value < numInstructions ) {
|
|
value = vm->instructionPointers[value];
|
|
}
|
|
|
|
sym->symValue = value;
|
|
Q_strncpyz( sym->symName, token, chars + 1 );
|
|
|
|
count++;
|
|
}
|
|
|
|
vm->numSymbols = count;
|
|
Com_Printf( "%i symbols parsed from %s\n", count, symbols );
|
|
FS_FreeFile( mapfile.v );
|
|
}
|
|
|
|
|
|
/*
|
|
============
|
|
VM_DllSyscall
|
|
|
|
Dlls will call this directly
|
|
|
|
rcg010206 The horror; the horror.
|
|
|
|
The syscall mechanism relies on stack manipulation to get its args.
|
|
This is likely due to C's inability to pass "..." parameters to
|
|
a function in one clean chunk. On PowerPC Linux, these parameters
|
|
are not necessarily passed on the stack, so while (&arg[0] == arg)
|
|
is true, (&arg[1] == 2nd function parameter) is not necessarily
|
|
accurate, as arg's value might have been stored to the stack or
|
|
other piece of scratch memory to give it a valid address, but the
|
|
next parameter might still be sitting in a register.
|
|
|
|
Quake's syscall system also assumes that the stack grows downward,
|
|
and that any needed types can be squeezed, safely, into a signed int.
|
|
|
|
This hack below copies all needed values for an argument to a
|
|
array in memory, so that Quake can get the correct values. This can
|
|
also be used on systems where the stack grows upwards, as the
|
|
presumably standard and safe stdargs.h macros are used.
|
|
|
|
As for having enough space in a signed int for your datatypes, well,
|
|
it might be better to wait for DOOM 3 before you start porting. :)
|
|
|
|
The original code, while probably still inherently dangerous, seems
|
|
to work well enough for the platforms it already works on. Rather
|
|
than add the performance hit for those platforms, the original code
|
|
is still in use there.
|
|
|
|
For speed, we just grab 15 arguments, and don't worry about exactly
|
|
how many the syscall actually needs; the extra is thrown away.
|
|
|
|
============
|
|
*/
|
|
#if 0 // - disabled because now is different for each module
|
|
intptr_t QDECL VM_DllSyscall( intptr_t arg, ... ) {
|
|
#if !id386 || defined __clang__
|
|
// rcg010206 - see commentary above
|
|
intptr_t args[16];
|
|
va_list ap;
|
|
int i;
|
|
|
|
args[0] = arg;
|
|
|
|
va_start( ap, arg );
|
|
for (i = 1; i < ARRAY_LEN( args ); i++ )
|
|
args[ i ] = va_arg( ap, intptr_t );
|
|
va_end( ap );
|
|
|
|
return currentVM->systemCall( args );
|
|
#else // original id code
|
|
return currentVM->systemCall( &arg );
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
|
|
static void VM_SwapLongs( void *data, int length )
|
|
{
|
|
#ifndef Q3_LITTLE_ENDIAN
|
|
int32_t *ptr;
|
|
int i;
|
|
ptr = (int32_t *) data;
|
|
length /= sizeof( int32_t );
|
|
for ( i = 0; i < length; i++ ) {
|
|
ptr[ i ] = LittleLong( ptr[ i ] );
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
static int Load_JTS( vm_t *vm, uint32_t crc32, void *data, int vmPakIndex ) {
|
|
char filename[MAX_QPATH];
|
|
int header[2];
|
|
int length;
|
|
fileHandle_t fh;
|
|
|
|
// load the image
|
|
Com_sprintf( filename, sizeof(filename), "vm/%s.jts", vm->name );
|
|
if ( data )
|
|
Com_Printf( "Loading jts file %s...\n", filename );
|
|
|
|
length = FS_FOpenFileRead( filename, &fh, qtrue );
|
|
|
|
if ( fh == FS_INVALID_HANDLE ) {
|
|
if ( data )
|
|
Com_Printf( " not found.\n" );
|
|
return -1;
|
|
}
|
|
|
|
if ( fs_lastPakIndex != vmPakIndex ) {
|
|
Com_DPrintf( " invalid pak index %i (expecting %i) for %s.\n", fs_lastPakIndex, vmPakIndex, filename );
|
|
FS_FCloseFile( fh );
|
|
return -1;
|
|
}
|
|
|
|
if ( length < sizeof( header ) ) {
|
|
if ( data )
|
|
Com_Printf( " bad filesize %i for %s.\n", length, filename );
|
|
FS_FCloseFile( fh );
|
|
return -1;
|
|
}
|
|
|
|
if ( FS_Read( header, sizeof( header ), fh ) != sizeof( header ) ) {
|
|
if ( data )
|
|
Com_Printf( " error reading header of %s.\n", filename );
|
|
FS_FCloseFile( fh );
|
|
return -1;
|
|
}
|
|
|
|
// byte swap the header
|
|
VM_SwapLongs( header, sizeof( header ) );
|
|
|
|
if ( (unsigned int)header[0] != crc32 ) {
|
|
if ( data )
|
|
Com_Printf( " crc32 mismatch: %08X <-> %08X.\n", header[0], crc32 );
|
|
FS_FCloseFile( fh );
|
|
return -1;
|
|
}
|
|
|
|
if ( header[1] < 0 || header[1] != (length - (int)sizeof( header ) ) ) {
|
|
if ( data )
|
|
Com_Printf( " bad file header.\n" );
|
|
FS_FCloseFile( fh );
|
|
return -1;
|
|
}
|
|
|
|
length -= sizeof( header ); // skip header and filesize
|
|
|
|
// we need just filesize
|
|
if ( !data ) {
|
|
FS_FCloseFile( fh );
|
|
return length;
|
|
}
|
|
|
|
FS_Read( data, length, fh );
|
|
FS_FCloseFile( fh );
|
|
|
|
// byte swap the data
|
|
VM_SwapLongs( data, length );
|
|
|
|
return length;
|
|
}
|
|
|
|
|
|
/*
|
|
=================
|
|
VM_ValidateHeader
|
|
=================
|
|
*/
|
|
static char *VM_ValidateHeader( vmHeader_t *header, int fileSize )
|
|
{
|
|
static char errMsg[128];
|
|
int n;
|
|
|
|
// truncated
|
|
if ( fileSize < ( sizeof( vmHeader_t ) - sizeof( int32_t ) ) ) {
|
|
sprintf( errMsg, "truncated image header (%i bytes long)", fileSize );
|
|
return errMsg;
|
|
}
|
|
|
|
// bad magic
|
|
if ( LittleLong( header->vmMagic ) != VM_MAGIC && LittleLong( header->vmMagic ) != VM_MAGIC_VER2 ) {
|
|
sprintf( errMsg, "bad file magic %08x", LittleLong( header->vmMagic ) );
|
|
return errMsg;
|
|
}
|
|
|
|
// truncated
|
|
if ( fileSize < sizeof( vmHeader_t ) && LittleLong( header->vmMagic ) != VM_MAGIC_VER2 ) {
|
|
sprintf( errMsg, "truncated image header (%i bytes long)", fileSize );
|
|
return errMsg;
|
|
}
|
|
|
|
if ( LittleLong( header->vmMagic ) == VM_MAGIC_VER2 )
|
|
n = sizeof( vmHeader_t );
|
|
else
|
|
n = ( sizeof( vmHeader_t ) - sizeof( int32_t ) );
|
|
|
|
// byte swap the header
|
|
VM_SwapLongs( header, n );
|
|
|
|
// bad code offset
|
|
if ( header->codeOffset >= fileSize ) {
|
|
sprintf( errMsg, "bad code segment offset %i", header->codeOffset );
|
|
return errMsg;
|
|
}
|
|
|
|
// bad code length
|
|
if ( header->codeLength <= 0 || header->codeOffset + header->codeLength > fileSize ) {
|
|
sprintf( errMsg, "bad code segment length %i", header->codeLength );
|
|
return errMsg;
|
|
}
|
|
|
|
// bad data offset
|
|
if ( header->dataOffset >= fileSize || header->dataOffset != header->codeOffset + header->codeLength ) {
|
|
sprintf( errMsg, "bad data segment offset %i", header->dataOffset );
|
|
return errMsg;
|
|
}
|
|
|
|
// bad data length
|
|
if ( header->dataOffset + header->dataLength > fileSize ) {
|
|
sprintf( errMsg, "bad data segment length %i", header->dataLength );
|
|
return errMsg;
|
|
}
|
|
|
|
if ( header->vmMagic == VM_MAGIC_VER2 ) {
|
|
// bad lit/jtrg length
|
|
if ( header->dataOffset + header->dataLength + header->litLength + header->jtrgLength != fileSize ) {
|
|
sprintf( errMsg, "bad lit/jtrg segment length" );
|
|
return errMsg;
|
|
}
|
|
}
|
|
// bad lit length
|
|
else if ( header->dataOffset + header->dataLength + header->litLength != fileSize ) {
|
|
sprintf( errMsg, "bad lit segment length %i", header->litLength );
|
|
return errMsg;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/*
|
|
=================
|
|
VM_LoadQVM
|
|
|
|
Load a .qvm file
|
|
|
|
if ( alloc )
|
|
- Validate header, swap data
|
|
- Alloc memory for data/instructions
|
|
- Alloc memory for instructionPointers - NOT NEEDED
|
|
- Load instructions
|
|
- Clear/load data
|
|
else
|
|
- Check for header changes
|
|
- Clear/load data
|
|
|
|
=================
|
|
*/
|
|
static vmHeader_t *VM_LoadQVM( vm_t *vm, qboolean alloc ) {
|
|
int length;
|
|
unsigned int dataLength;
|
|
unsigned int dataAlloc;
|
|
int i;
|
|
char filename[MAX_QPATH], *errorMsg;
|
|
unsigned int crc32sum;
|
|
qboolean tryjts;
|
|
vmHeader_t *header;
|
|
int vmPakIndex;
|
|
|
|
// load the image
|
|
Com_sprintf( filename, sizeof(filename), "vm/%s.qvm", vm->name );
|
|
Com_Printf( "Loading vm file %s...\n", filename );
|
|
length = FS_ReadFile( filename, (void **)&header );
|
|
if ( !header ) {
|
|
Com_Printf( "Failed.\n" );
|
|
VM_Free( vm );
|
|
return NULL;
|
|
}
|
|
|
|
vmPakIndex = fs_lastPakIndex;
|
|
|
|
crc32sum = crc32_buffer( (const byte*) header, length );
|
|
|
|
// will also swap header
|
|
errorMsg = VM_ValidateHeader( header, length );
|
|
if ( errorMsg ) {
|
|
VM_Free( vm );
|
|
FS_FreeFile( header );
|
|
Com_Printf( S_COLOR_RED "%s\n", errorMsg );
|
|
return NULL;
|
|
}
|
|
|
|
vm->crc32sum = crc32sum;
|
|
tryjts = qfalse;
|
|
|
|
if( header->vmMagic == VM_MAGIC_VER2 ) {
|
|
Com_Printf( "...which has vmMagic VM_MAGIC_VER2\n" );
|
|
} else {
|
|
tryjts = qtrue;
|
|
}
|
|
|
|
vm->exactDataLength = header->dataLength + header->litLength + header->bssLength;
|
|
dataLength = vm->exactDataLength + PROGRAM_STACK_EXTRA;
|
|
if ( dataLength < PROGRAM_STACK_SIZE + PROGRAM_STACK_EXTRA ) {
|
|
dataLength = PROGRAM_STACK_SIZE + PROGRAM_STACK_EXTRA;
|
|
}
|
|
vm->dataLength = dataLength;
|
|
|
|
// round up to next power of 2 so all data operations can
|
|
// be mask protected
|
|
for ( i = 0 ; dataLength > ( 1 << i ) ; i++ )
|
|
;
|
|
dataLength = 1 << i;
|
|
|
|
// reserve some space for effective LOCAL+LOAD* checks
|
|
dataAlloc = dataLength + VM_DATA_GUARD_SIZE;
|
|
|
|
if ( dataLength >= (1U<<31) || dataAlloc >= (1U<<31) ) {
|
|
// dataLenth is negative int32
|
|
VM_Free( vm );
|
|
FS_FreeFile( header );
|
|
Com_Printf( S_COLOR_RED "%s: data segment is too large\n", __func__ );
|
|
return NULL;
|
|
}
|
|
|
|
if ( alloc ) {
|
|
// allocate zero filled space for initialized and uninitialized data
|
|
vm->dataBase = Hunk_Alloc( dataAlloc, h_high );
|
|
vm->dataMask = dataLength - 1;
|
|
vm->dataAlloc = dataAlloc;
|
|
} else {
|
|
// clear the data, but make sure we're not clearing more than allocated
|
|
if ( vm->dataAlloc != dataAlloc ) {
|
|
VM_Free( vm );
|
|
FS_FreeFile( header );
|
|
Com_Printf( S_COLOR_YELLOW "Warning: Data region size of %s not matching after"
|
|
"VM_Restart()\n", filename );
|
|
return NULL;
|
|
}
|
|
Com_Memset( vm->dataBase, 0, vm->dataAlloc );
|
|
}
|
|
|
|
// copy the intialized data
|
|
Com_Memcpy( vm->dataBase, (byte *)header + header->dataOffset, header->dataLength + header->litLength );
|
|
|
|
// byte swap the longs
|
|
VM_SwapLongs( vm->dataBase, header->dataLength );
|
|
|
|
if( header->vmMagic == VM_MAGIC_VER2 ) {
|
|
int previousNumJumpTableTargets = vm->numJumpTableTargets;
|
|
|
|
header->jtrgLength &= ~0x03;
|
|
|
|
vm->numJumpTableTargets = header->jtrgLength >> 2;
|
|
Com_Printf( "Loading %d jump table targets\n", vm->numJumpTableTargets );
|
|
|
|
if ( alloc ) {
|
|
vm->jumpTableTargets = (int32_t *) Hunk_Alloc( header->jtrgLength, h_high );
|
|
} else {
|
|
if ( vm->numJumpTableTargets != previousNumJumpTableTargets ) {
|
|
VM_Free( vm );
|
|
FS_FreeFile( header );
|
|
|
|
Com_Printf( S_COLOR_YELLOW "Warning: Jump table size of %s not matching after "
|
|
"VM_Restart()\n", filename );
|
|
return NULL;
|
|
}
|
|
|
|
Com_Memset( vm->jumpTableTargets, 0, header->jtrgLength );
|
|
}
|
|
|
|
Com_Memcpy( vm->jumpTableTargets, (byte *)header + header->dataOffset +
|
|
header->dataLength + header->litLength, header->jtrgLength );
|
|
|
|
// byte swap the longs
|
|
VM_SwapLongs( vm->jumpTableTargets, header->jtrgLength );
|
|
}
|
|
|
|
if ( tryjts == qtrue && (length = Load_JTS( vm, crc32sum, NULL, vmPakIndex )) >= 0 ) {
|
|
// we are trying to load newer file?
|
|
if ( vm->jumpTableTargets && vm->numJumpTableTargets != length >> 2 ) {
|
|
Com_Printf( S_COLOR_YELLOW "Reload jts file\n" );
|
|
vm->jumpTableTargets = NULL;
|
|
alloc = qtrue;
|
|
}
|
|
vm->numJumpTableTargets = length >> 2;
|
|
Com_Printf( "Loading %d external jump table targets\n", vm->numJumpTableTargets );
|
|
if ( alloc == qtrue ) {
|
|
vm->jumpTableTargets = (int32_t *) Hunk_Alloc( length, h_high );
|
|
} else {
|
|
Com_Memset( vm->jumpTableTargets, 0, length );
|
|
}
|
|
Load_JTS( vm, crc32sum, vm->jumpTableTargets, vmPakIndex );
|
|
}
|
|
|
|
return header;
|
|
}
|
|
|
|
|
|
static void VM_IgnoreInstructions( instruction_t *buf, const int count ) {
|
|
int i;
|
|
|
|
for ( i = 0; i < count; i++ ) {
|
|
Com_Memset( buf + i, 0, sizeof( *buf ) );
|
|
buf[i].op = OP_IGNORE;
|
|
}
|
|
|
|
buf[0].value = count > 0 ? count - 1 : 0;
|
|
}
|
|
|
|
|
|
static int InvertCondition( int op )
|
|
{
|
|
switch ( op ) {
|
|
case OP_EQ: return OP_NE; // == -> !=
|
|
case OP_NE: return OP_EQ; // != -> ==
|
|
|
|
case OP_LTI: return OP_GEI; // < -> >=
|
|
case OP_LEI: return OP_GTI; // <= -> >
|
|
case OP_GTI: return OP_LEI; // > -> <=
|
|
case OP_GEI: return OP_LTI; // >= -> <
|
|
|
|
case OP_LTU: return OP_GEU;
|
|
case OP_LEU: return OP_GTU;
|
|
case OP_GTU: return OP_LEU;
|
|
case OP_GEU: return OP_LTU;
|
|
|
|
case OP_EQF: return OP_NEF;
|
|
case OP_NEF: return OP_EQF;
|
|
|
|
case OP_LTF: return OP_GEF;
|
|
case OP_LEF: return OP_GTF;
|
|
case OP_GTF: return OP_LEF;
|
|
case OP_GEF: return OP_LTF;
|
|
|
|
default:
|
|
Com_Error( ERR_DROP, "incorrect condition opcode %i", op );
|
|
return op;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
=================
|
|
VM_FindLocal
|
|
|
|
search for specified local variable until end of function
|
|
=================
|
|
*/
|
|
static qboolean VM_FindLocal( int32_t addr, const instruction_t *buf, const instruction_t *end, int32_t *back_addr ) {
|
|
int32_t curr_addr = *back_addr;
|
|
while ( buf < end ) {
|
|
if ( buf->op == OP_LOCAL ) {
|
|
if ( buf->value == addr ) {
|
|
return qtrue;
|
|
}
|
|
++buf; continue;
|
|
}
|
|
if ( ops[ buf->op ].flags & JUMP ) {
|
|
if ( buf->value < curr_addr ) {
|
|
curr_addr = buf->value;
|
|
}
|
|
++buf; continue;
|
|
}
|
|
if ( buf->op == OP_JUMP ) {
|
|
if ( buf->value && buf->value < curr_addr ) {
|
|
curr_addr = buf->value;
|
|
}
|
|
++buf; continue;
|
|
}
|
|
if ( buf->op == OP_PUSH && (buf+1)->op == OP_LEAVE ) {
|
|
break;
|
|
}
|
|
++buf;
|
|
}
|
|
*back_addr = curr_addr;
|
|
return qfalse;
|
|
}
|
|
|
|
|
|
/*
|
|
=================
|
|
VM_Fixup
|
|
|
|
Do some corrections to fix known Q3LCC flaws
|
|
=================
|
|
*/
|
|
static void VM_Fixup( instruction_t *buf, int instructionCount )
|
|
{
|
|
int n;
|
|
instruction_t *i;
|
|
|
|
i = buf;
|
|
n = 0;
|
|
|
|
while ( n < instructionCount )
|
|
{
|
|
if ( i->op == OP_LOCAL ) {
|
|
|
|
// skip useless sequences
|
|
if ( (i+1)->op == OP_LOCAL && (i+0)->value == (i+1)->value && (i+2)->op == OP_LOAD4 && (i+3)->op == OP_STORE4 ) {
|
|
VM_IgnoreInstructions( i, 4 );
|
|
i += 4; n += 4;
|
|
continue;
|
|
}
|
|
|
|
// [0]OP_LOCAL + [1]OP_CONST + [2]OP_CALL + [3]OP_STORE4
|
|
if ( (i+1)->op == OP_CONST && (i+2)->op == OP_CALL && (i+3)->op == OP_STORE4 && !(i+4)->jused ) {
|
|
// [4]OP_CONST|OP_LOCAL (dest) + [5]OP_LOCAL(temp) + [6]OP_LOAD4 + [7]OP_STORE4
|
|
if ( (i+4)->op == OP_CONST || (i+4)->op == OP_LOCAL ) {
|
|
if ( (i+5)->op == OP_LOCAL && (i+5)->value == (i+0)->value && (i+6)->op == OP_LOAD4 && (i+7)->op == OP_STORE4 ) {
|
|
int32_t back_addr = n;
|
|
int32_t curr_addr = n;
|
|
qboolean do_break = qfalse;
|
|
|
|
// make sure that address of (potentially) temporary variable is not referenced further in this function
|
|
if ( VM_FindLocal( i->value, i + 8, buf + instructionCount, &back_addr ) ) {
|
|
i++; n++;
|
|
continue;
|
|
}
|
|
|
|
// we have backward jumps in code then check for references before current position
|
|
while ( back_addr < curr_addr ) {
|
|
curr_addr = back_addr;
|
|
if ( VM_FindLocal( i->value, buf + back_addr, i, &back_addr ) ) {
|
|
do_break = qtrue;
|
|
break;
|
|
}
|
|
}
|
|
if ( do_break ) {
|
|
i++; n++;
|
|
continue;
|
|
}
|
|
|
|
(i+0)->op = (i+4)->op;
|
|
(i+0)->value = (i+4)->value;
|
|
VM_IgnoreInstructions( i + 4, 4 );
|
|
i += 8;
|
|
n += 8;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( i->op == OP_LEAVE && !i->endp ) {
|
|
if ( !(i+1)->jused && (i+1)->op == OP_CONST && (i+2)->op == OP_JUMP ) {
|
|
int v = (i+1)->value;
|
|
if ( buf[ v ].op == OP_PUSH && buf[ v+1 ].op == OP_LEAVE && buf[ v+1 ].endp ) {
|
|
VM_IgnoreInstructions( i + 1, 2 );
|
|
i += 3;
|
|
n += 3;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
//n + 0: if ( cond ) goto label1;
|
|
//n + 2: goto label2;
|
|
//n + 3: label1:
|
|
// ...
|
|
//n + x: label2:
|
|
if ( ( ops[i->op].flags & (JUMP | FPU) ) == JUMP && !(i+1)->jused && (i+1)->op == OP_CONST && (i+2)->op == OP_JUMP ) {
|
|
if ( i->value == n + 3 && (i+1)->value >= n + 3 ) {
|
|
i->op = InvertCondition( i->op );
|
|
i->value = ( i + 1 )->value;
|
|
VM_IgnoreInstructions( i + 1, 2 );
|
|
i += 3;
|
|
n += 3;
|
|
continue;
|
|
}
|
|
}
|
|
i++;
|
|
n++;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
=================
|
|
VM_LoadInstructions
|
|
|
|
loads instructions in structured format
|
|
=================
|
|
*/
|
|
const char *VM_LoadInstructions( const byte *code_pos, int codeLength, int instructionCount, instruction_t *buf )
|
|
{
|
|
static char errBuf[ 128 ];
|
|
const byte *code_start, *code_end;
|
|
int i, n, op0, op1, opStack;
|
|
instruction_t *ci;
|
|
|
|
code_start = code_pos; // for printing
|
|
code_end = code_pos + codeLength;
|
|
|
|
ci = buf;
|
|
opStack = 0;
|
|
op1 = OP_UNDEF;
|
|
|
|
// load instructions and perform some initial calculations/checks
|
|
for ( i = 0; i < instructionCount; i++, ci++, op1 = op0 ) {
|
|
op0 = *code_pos;
|
|
if ( op0 < 0 || op0 >= OP_MAX ) {
|
|
sprintf( errBuf, "bad opcode %02X at offset %d", op0, (int)(code_pos - code_start) );
|
|
return errBuf;
|
|
}
|
|
n = ops[ op0 ].size;
|
|
if ( code_pos + 1 + n > code_end ) {
|
|
sprintf( errBuf, "code_pos > code_end" );
|
|
return errBuf;
|
|
}
|
|
code_pos++;
|
|
ci->op = op0;
|
|
if ( n == 4 ) {
|
|
ci->value = LittleLong( *((int32_t*)code_pos) );
|
|
code_pos += 4;
|
|
} else if ( n == 1 ) {
|
|
ci->value = *((unsigned char*)code_pos);
|
|
code_pos += 1;
|
|
} else {
|
|
ci->value = 0;
|
|
}
|
|
|
|
if ( ops[ op0 ].flags & FPU ) {
|
|
ci->fpu = 1;
|
|
}
|
|
|
|
// setup jump value from previous const
|
|
if ( op0 == OP_JUMP && op1 == OP_CONST ) {
|
|
ci->value = (ci-1)->value;
|
|
}
|
|
|
|
ci->opStack = opStack;
|
|
opStack += ops[ op0 ].stack;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
static qboolean safe_address( instruction_t *ci, instruction_t *proc, int dataLength )
|
|
{
|
|
if ( ci->op == OP_LOCAL ) {
|
|
// local address can't exceed programStack frame plus 256 bytes of passed arguments
|
|
if ( ci->value < 8 || ( proc && ci->value >= proc->value + 256 ) )
|
|
return qfalse;
|
|
return qtrue;
|
|
}
|
|
|
|
if ( ci->op == OP_CONST ) {
|
|
// constant address can't exceed data segment
|
|
if ( ci->value >= dataLength || ci->value < 0 )
|
|
return qfalse;
|
|
return qtrue;
|
|
}
|
|
|
|
return qfalse;
|
|
}
|
|
|
|
|
|
/*
|
|
===============================
|
|
VM_CheckInstructions
|
|
|
|
performs additional consistency and security checks
|
|
===============================
|
|
*/
|
|
const char *VM_CheckInstructions( instruction_t *buf,
|
|
int instructionCount,
|
|
const int32_t *jumpTableTargets,
|
|
int numJumpTableTargets,
|
|
int dataLength )
|
|
{
|
|
static char errBuf[ 128 ];
|
|
instruction_t *opStackPtr[ PROC_OPSTACK_SIZE ];
|
|
int i, m, n, v, op0, op1, opStack, pstack;
|
|
instruction_t *ci, *proc;
|
|
int startp, endp;
|
|
int safe_stores;
|
|
int unsafe_stores;
|
|
|
|
ci = buf;
|
|
opStack = 0;
|
|
|
|
// opstack checks
|
|
for ( i = 0; i < instructionCount; i++, ci++ ) {
|
|
opStack += ops[ ci->op ].stack;
|
|
if ( opStack < 0 ) {
|
|
sprintf( errBuf, "opStack underflow at %i", i );
|
|
return errBuf;
|
|
}
|
|
if ( opStack >= PROC_OPSTACK_SIZE * 4 ) {
|
|
sprintf( errBuf, "opStack overflow at %i", i );
|
|
return errBuf;
|
|
}
|
|
}
|
|
|
|
ci = buf;
|
|
pstack = 0;
|
|
opStack = 0;
|
|
safe_stores = 0;
|
|
unsafe_stores = 0;
|
|
op1 = OP_UNDEF;
|
|
proc = NULL;
|
|
Com_Memset( opStackPtr, 0, sizeof( opStackPtr ) );
|
|
|
|
startp = 0;
|
|
endp = instructionCount - 1;
|
|
|
|
// Additional security checks
|
|
|
|
for ( i = 0; i < instructionCount; i++, ci++, op1 = op0 ) {
|
|
op0 = ci->op;
|
|
|
|
m = ops[ ci->op ].stack;
|
|
opStack += m;
|
|
if ( m >= 0 ) {
|
|
// do some FPU type promotion for more efficient loads
|
|
if ( ci->fpu && ci->op != OP_CVIF ) {
|
|
opStackPtr[ opStack / 4 ]->fpu = 1;
|
|
}
|
|
opStackPtr[ opStack >> 2 ] = ci;
|
|
} else {
|
|
if ( ci->fpu ) {
|
|
if ( m <= -8 ) {
|
|
opStackPtr[ opStack / 4 + 1 ]->fpu = 1;
|
|
opStackPtr[ opStack / 4 + 2 ]->fpu = 1;
|
|
} else {
|
|
opStackPtr[ opStack / 4 + 0 ]->fpu = 1;
|
|
opStackPtr[ opStack / 4 + 1 ]->fpu = 1;
|
|
}
|
|
} else {
|
|
if ( m <= -8 ) {
|
|
//
|
|
} else {
|
|
opStackPtr[ opStack / 4 + 0 ] = ci;
|
|
}
|
|
}
|
|
}
|
|
|
|
// function entry
|
|
if ( op0 == OP_ENTER ) {
|
|
// missing block end
|
|
if ( proc || ( pstack && op1 != OP_LEAVE ) ) {
|
|
sprintf( errBuf, "missing proc end before %i", i );
|
|
return errBuf;
|
|
}
|
|
if ( ci->opStack != 0 ) {
|
|
v = ci->opStack;
|
|
sprintf( errBuf, "bad entry opstack %i at %i", v, i );
|
|
return errBuf;
|
|
}
|
|
v = ci->value;
|
|
if ( v < 0 || v >= PROGRAM_STACK_SIZE || (v & 3) ) {
|
|
sprintf( errBuf, "bad entry programStack %i at %i", v, i );
|
|
return errBuf;
|
|
}
|
|
|
|
pstack = ci->value;
|
|
|
|
// mark jump target
|
|
ci->jused = 1;
|
|
proc = ci;
|
|
startp = i + 1;
|
|
|
|
// locate endproc
|
|
for ( endp = 0, n = i+1 ; n < instructionCount; n++ ) {
|
|
if ( buf[n].op == OP_PUSH && buf[n+1].op == OP_LEAVE ) {
|
|
buf[n+1].endp = 1;
|
|
endp = n;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( endp == 0 ) {
|
|
sprintf( errBuf, "missing end proc for %i", i );
|
|
return errBuf;
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
// proc opstack will carry max.possible opstack value
|
|
if ( proc && ci->opStack > proc->opStack )
|
|
proc->opStack = ci->opStack;
|
|
|
|
// function return
|
|
if ( op0 == OP_LEAVE ) {
|
|
// bad return programStack
|
|
if ( pstack != ci->value ) {
|
|
v = ci->value;
|
|
sprintf( errBuf, "bad programStack %i at %i", v, i );
|
|
return errBuf;
|
|
}
|
|
// bad opStack before return
|
|
if ( ci->opStack != 4 ) {
|
|
v = ci->opStack;
|
|
sprintf( errBuf, "bad opStack %i at %i", v, i );
|
|
return errBuf;
|
|
}
|
|
v = ci->value;
|
|
if ( v < 0 || v >= PROGRAM_STACK_SIZE || (v & 3) ) {
|
|
sprintf( errBuf, "bad return programStack %i at %i", v, i );
|
|
return errBuf;
|
|
}
|
|
if ( op1 == OP_PUSH ) {
|
|
if ( proc == NULL ) {
|
|
sprintf( errBuf, "unexpected proc end at %i", i );
|
|
return errBuf;
|
|
}
|
|
proc = NULL;
|
|
startp = i + 1; // next instruction
|
|
endp = instructionCount - 1; // end of the image
|
|
}
|
|
continue;
|
|
}
|
|
|
|
// conditional jumps
|
|
if ( ops[ ci->op ].flags & JUMP ) {
|
|
v = ci->value;
|
|
// conditional jumps should have opStack >= 8
|
|
if ( ci->opStack < 8 ) {
|
|
sprintf( errBuf, "bad jump opStack %i at %i", ci->opStack, i );
|
|
return errBuf;
|
|
}
|
|
//if ( v >= header->instructionCount ) {
|
|
// allow only local proc jumps
|
|
if ( v < startp || v > endp ) {
|
|
sprintf( errBuf, "jump target %i at %i is out of range (%i,%i)", v, i-1, startp, endp );
|
|
return errBuf;
|
|
}
|
|
if ( buf[v].opStack != ci->opStack - 8 ) {
|
|
n = buf[v].opStack;
|
|
sprintf( errBuf, "jump target %i has bad opStack %i", v, n );
|
|
return errBuf;
|
|
}
|
|
// mark jump target
|
|
buf[v].jused = 1;
|
|
continue;
|
|
}
|
|
|
|
// unconditional jumps
|
|
if ( op0 == OP_JUMP ) {
|
|
// jumps should have opStack >= 4
|
|
if ( ci->opStack < 4 ) {
|
|
sprintf( errBuf, "bad jump opStack %i at %i", ci->opStack, i );
|
|
return errBuf;
|
|
}
|
|
if ( op1 == OP_CONST ) {
|
|
v = buf[i-1].value;
|
|
// allow only local jumps
|
|
if ( v < startp || v > endp ) {
|
|
sprintf( errBuf, "jump target %i at %i is out of range (%i,%i)", v, i-1, startp, endp );
|
|
return errBuf;
|
|
}
|
|
if ( buf[v].opStack != ci->opStack - 4 ) {
|
|
n = buf[v].opStack;
|
|
sprintf( errBuf, "jump target %i has bad opStack %i", v, n );
|
|
return errBuf;
|
|
}
|
|
if ( buf[v].op == OP_ENTER ) {
|
|
n = buf[v].op;
|
|
sprintf( errBuf, "jump target %i has bad opcode %s", v, opname[ n ] );
|
|
return errBuf;
|
|
}
|
|
if ( v == (i-1) ) {
|
|
sprintf( errBuf, "self loop at %i", v );
|
|
return errBuf;
|
|
}
|
|
// mark jump target
|
|
buf[v].jused = 1;
|
|
} else {
|
|
if ( proc )
|
|
proc->swtch = 1;
|
|
else
|
|
ci->swtch = 1;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if ( op0 == OP_CALL ) {
|
|
if ( ci->opStack < 4 ) {
|
|
sprintf( errBuf, "bad call opStack at %i", i );
|
|
return errBuf;
|
|
}
|
|
if ( op1 == OP_CONST ) {
|
|
v = buf[i-1].value;
|
|
// analyse only local function calls
|
|
if ( v >= 0 ) {
|
|
if ( v >= instructionCount ) {
|
|
sprintf( errBuf, "call target %i is out of range", v );
|
|
return errBuf;
|
|
}
|
|
if ( buf[v].op != OP_ENTER ) {
|
|
n = buf[v].op;
|
|
sprintf( errBuf, "call target %i has bad opcode %s", v, opname[ n ] );
|
|
return errBuf;
|
|
}
|
|
if ( v == 0 ) {
|
|
sprintf( errBuf, "explicit vmMain call inside VM at %i", i );
|
|
return errBuf;
|
|
}
|
|
// mark jump target
|
|
buf[v].jused = 1;
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if ( ci->op == OP_ARG ) {
|
|
v = ci->value & 255;
|
|
if ( proc == NULL ) {
|
|
sprintf( errBuf, "missing proc frame for %s %i at %i", opname[ ci->op ], v, i );
|
|
return errBuf;
|
|
}
|
|
// argument can't exceed programStack frame
|
|
if ( v < 8 || v > pstack - 4 || (v & 3) ) {
|
|
sprintf( errBuf, "bad argument address %i at %i", v, i );
|
|
return errBuf;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if ( ci->op == OP_LOCAL ) {
|
|
v = ci->value;
|
|
if ( proc == NULL ) {
|
|
sprintf( errBuf, "missing proc frame for %s %i at %i", opname[ ci->op ], v, i );
|
|
return errBuf;
|
|
}
|
|
if ( (ci+1)->op == OP_LOAD4 || (ci+1)->op == OP_LOAD2 || (ci+1)->op == OP_LOAD1 ) {
|
|
if ( !safe_address( ci, proc, dataLength ) ) {
|
|
sprintf( errBuf, "bad %s address %i at %i", opname[ ci->op ], v, i );
|
|
return errBuf;
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if ( ci->op == OP_LOAD4 && op1 == OP_CONST ) {
|
|
v = (ci-1)->value;
|
|
if ( v < 0 || v > dataLength - 4 ) {
|
|
sprintf( errBuf, "bad %s address %i at %i", opname[ ci->op ], v, i - 1 );
|
|
return errBuf;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if ( ci->op == OP_LOAD2 && op1 == OP_CONST ) {
|
|
v = (ci-1)->value;
|
|
if ( v < 0 || v > dataLength - 2 ) {
|
|
sprintf( errBuf, "bad %s address %i at %i", opname[ ci->op ], v, i - 1 );
|
|
return errBuf;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if ( ci->op == OP_LOAD1 && op1 == OP_CONST ) {
|
|
v = (ci-1)->value;
|
|
if ( v < 0 || v > dataLength - 1 ) {
|
|
sprintf( errBuf, "bad %s address %i at %i", opname[ ci->op ], v, i - 1 );
|
|
return errBuf;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if ( ci->op == OP_STORE4 || ci->op == OP_STORE2 || ci->op == OP_STORE1 ) {
|
|
instruction_t *x = opStackPtr[ opStack / 4 + 1 ];
|
|
if ( x->op == OP_CONST || x->op == OP_LOCAL ) {
|
|
if ( safe_address( x, proc, dataLength ) ) {
|
|
ci->safe = 1;
|
|
safe_stores++;
|
|
continue;
|
|
} else {
|
|
sprintf( errBuf, "bad %s address %i at %i", opname[ ci->op ], x->value, (int)(x - buf) );
|
|
return errBuf;
|
|
}
|
|
}
|
|
unsafe_stores++;
|
|
continue;
|
|
}
|
|
|
|
if ( ci->op == OP_BLOCK_COPY ) {
|
|
instruction_t *src = opStackPtr[ opStack / 4 + 2 ];
|
|
instruction_t *dst = opStackPtr[ opStack / 4 + 1 ];
|
|
int safe = 0;
|
|
v = ci->value;
|
|
if ( v >= dataLength ) {
|
|
sprintf( errBuf, "bad count %i for block copy at %i", v, i - 1 );
|
|
return errBuf;
|
|
}
|
|
if ( src->op == OP_LOCAL || src->op == OP_CONST ) {
|
|
if ( !safe_address( src, proc, dataLength ) ) {
|
|
sprintf( errBuf, "bad src for block copy at %i", (int)(dst - buf) );
|
|
return errBuf;
|
|
}
|
|
src->safe = 1;
|
|
safe++;
|
|
}
|
|
if ( dst->op == OP_LOCAL || dst->op == OP_CONST ) {
|
|
if ( !safe_address( dst, proc, dataLength ) ) {
|
|
sprintf( errBuf, "bad dst for block copy at %i", (int)(dst - buf) );
|
|
return errBuf;
|
|
}
|
|
dst->safe = 1;
|
|
safe++;
|
|
}
|
|
if ( safe == 2 ) {
|
|
ci->safe = 1;
|
|
}
|
|
}
|
|
// op1 = op0;
|
|
// ci++;
|
|
}
|
|
|
|
if ( ( safe_stores + unsafe_stores ) > 0 ) {
|
|
Com_DPrintf( "%s: safe stores - %i (%i%%)\n", __func__, safe_stores, safe_stores * 100 / ( safe_stores + unsafe_stores ) );
|
|
}
|
|
|
|
if ( op1 != OP_UNDEF && op1 != OP_LEAVE ) {
|
|
sprintf( errBuf, "missing return instruction at the end of the image" );
|
|
return errBuf;
|
|
}
|
|
|
|
// ensure that the optimization pass knows about all the jump table targets
|
|
if ( jumpTableTargets ) {
|
|
// first pass - validate
|
|
for( i = 0; i < numJumpTableTargets; i++ ) {
|
|
n = jumpTableTargets[ i ];
|
|
if ( n < 0 || n >= instructionCount ) {
|
|
Com_Printf( S_COLOR_YELLOW "jump target %i set on instruction %i that is out of range [0..%i]",
|
|
i, n, instructionCount - 1 );
|
|
break;
|
|
}
|
|
if ( buf[n].opStack != 0 ) {
|
|
Com_Printf( S_COLOR_YELLOW "jump target %i set on instruction %i (%s) with bad opStack %i\n",
|
|
i, n, opname[ buf[n].op ], buf[n].opStack );
|
|
break;
|
|
}
|
|
}
|
|
if ( i != numJumpTableTargets ) {
|
|
// we may trap this on buggy VM_MAGIC_VER2 images
|
|
// but we can safely optimize code even without JTRGSEG
|
|
// so just switch to VM_MAGIC path here
|
|
goto __noJTS;
|
|
}
|
|
// second pass - apply
|
|
for( i = 0; i < numJumpTableTargets; i++ ) {
|
|
n = jumpTableTargets[ i ];
|
|
buf[ n ].jused = 1;
|
|
}
|
|
} else {
|
|
__noJTS:
|
|
v = 0;
|
|
// instructions with opStack > 0 can't be jump labels so it is safe to optimize/merge
|
|
for ( i = 0, ci = buf; i < instructionCount; i++, ci++ ) {
|
|
if ( ci->op == OP_ENTER ) {
|
|
v = ci->swtch;
|
|
continue;
|
|
}
|
|
// if there is a switch statement in function -
|
|
// mark all potential jump labels
|
|
if ( ci->swtch )
|
|
v = ci->swtch;
|
|
if ( ci->opStack > 0 )
|
|
ci->jused = 0;
|
|
else if ( v )
|
|
ci->jused = 1;
|
|
}
|
|
}
|
|
|
|
VM_Fixup( buf, instructionCount );
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/*
|
|
=================
|
|
VM_ReplaceInstructions
|
|
=================
|
|
*/
|
|
void VM_ReplaceInstructions( vm_t *vm, instruction_t *buf ) {
|
|
instruction_t *ip;
|
|
|
|
//Com_Printf( S_COLOR_GREEN "VMINFO [%s] crc: %08X, ic: %i, dl: %i\n", vm->name, vm->crc32sum, vm->instructionCount, vm->exactDataLength );
|
|
|
|
if ( vm->index == VM_CGAME ) {
|
|
if ( vm->crc32sum == 0x3E93FC1A && vm->instructionCount == 123596 && vm->exactDataLength == 2007536 ) {
|
|
ip = buf + 110190;
|
|
if ( ip->op == OP_ENTER && (ip+183)->op == OP_LEAVE && ip->value == (ip+183)->value ) {
|
|
ip++;
|
|
ip->op = OP_CONST; ip->value = 110372; ip++;
|
|
ip->op = OP_JUMP; ip->value = 0; ip++;
|
|
ip->op = OP_IGNORE; ip->value = 0;
|
|
}
|
|
if ( buf[4358].op == OP_LOCAL && buf[4358].value == 308 && buf[4359].op == OP_CONST && !buf[4359].value ) {
|
|
buf[4359].value++;
|
|
}
|
|
} else
|
|
if ( vm->crc32sum == 0xF0F1AE90 && vm->instructionCount == 123552 && vm->exactDataLength == 2007520 ) {
|
|
ip = buf + 110177;
|
|
if ( ip->op == OP_ENTER && (ip+183)->op == OP_LEAVE && ip->value == (ip+183)->value ) {
|
|
ip++;
|
|
ip->op = OP_CONST; ip->value = 110359; ip++;
|
|
ip->op = OP_JUMP; ip->value = 0; ip++;
|
|
ip->op = OP_IGNORE; ip->value = 0;
|
|
}
|
|
if ( buf[4358].op == OP_LOCAL && buf[4358].value == 308 && buf[4359].op == OP_CONST && !buf[4359].value ) {
|
|
buf[4359].value++;
|
|
}
|
|
} else
|
|
if ( vm->crc32sum == 0x051D4668 && vm->instructionCount == 267812 && vm->exactDataLength == 38064376 ) {
|
|
ip = buf + 235;
|
|
if ( ip->value == 70943 ) {
|
|
VM_IgnoreInstructions( ip, 8 );
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( vm->index == VM_GAME ) {
|
|
if ( vm->crc32sum == 0x5AAE0ACC && vm->instructionCount == 251521 && vm->exactDataLength == 1872720 ) {
|
|
vm->forceDataMask = qtrue; // OSP server doing some bad things with memory
|
|
} else {
|
|
vm->forceDataMask = qfalse;
|
|
}
|
|
}
|
|
|
|
if ( vm->index == VM_UI ) {
|
|
// fix OSP demo UI
|
|
if ( vm->crc32sum == 0xCA84F31D && vm->instructionCount == 78585 && vm->exactDataLength == 542180 ) {
|
|
if ( memcmp( vm->dataBase + 0x3D2E, "dm_67", 5 ) == 0 ) {
|
|
memcpy( vm->dataBase + 0x3D2E, "dm_??", 5 );
|
|
}
|
|
if ( memcmp( vm->dataBase + 0x3D50, "\"%s.%s\"\n", 8 ) == 0 ) {
|
|
memcpy( vm->dataBase + 0x3D50, "\"%s\"\n", 6 );
|
|
}
|
|
}
|
|
// fix defrag-1.91.25 demo UI - masked Q_strupr() calls for directories and filenames
|
|
if ( vm->crc32sum == 0x6E51985F && vm->instructionCount == 125942 && vm->exactDataLength == 1334788 ) {
|
|
ip = buf + 60150;
|
|
if ( ip[0].op == OP_LOCAL && ip[0].value == 28 && ip[1].op == OP_LOAD4 && ip[2].op == OP_ARG && ip[3].value == 124325 ) {
|
|
VM_IgnoreInstructions( ip, 6 );
|
|
ip = buf + 60438;
|
|
VM_IgnoreInstructions( ip, 6 );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
=================
|
|
VM_Restart
|
|
|
|
Reload the data, but leave everything else in place
|
|
This allows a server to do a map_restart without changing memory allocation
|
|
=================
|
|
*/
|
|
vm_t *VM_Restart( vm_t *vm ) {
|
|
vmHeader_t *header;
|
|
|
|
// DLL's can't be restarted in place
|
|
if ( vm->dllHandle ) {
|
|
syscall_t systemCall;
|
|
dllSyscall_t dllSyscall;
|
|
vmIndex_t index;
|
|
|
|
index = vm->index;
|
|
systemCall = vm->systemCall;
|
|
dllSyscall = vm->dllSyscall;
|
|
|
|
VM_Free( vm );
|
|
|
|
vm = VM_Create( index, systemCall, dllSyscall, VMI_NATIVE );
|
|
return vm;
|
|
}
|
|
|
|
// load the image
|
|
if( ( header = VM_LoadQVM( vm, qfalse ) ) == NULL ) {
|
|
Com_Printf( S_COLOR_RED "VM_Restart() failed\n" );
|
|
return NULL;
|
|
}
|
|
|
|
Com_Printf( "VM_Restart()\n" );
|
|
|
|
// free the original file
|
|
FS_FreeFile( header );
|
|
|
|
return vm;
|
|
}
|
|
|
|
|
|
/*
|
|
=================
|
|
Sys_LoadDll
|
|
|
|
Used to load a development dll instead of a virtual machine
|
|
|
|
TTimo: added some verbosity in debug
|
|
=================
|
|
*/
|
|
static void * QDECL VM_LoadDll( const char *name, vmMainFunc_t *entryPoint, dllSyscall_t systemcalls ) {
|
|
|
|
const char *gamedir = Cvar_VariableString( "fs_game" );
|
|
char filename[ MAX_QPATH ];
|
|
void *libHandle;
|
|
dllEntry_t dllEntry;
|
|
|
|
if ( !*gamedir ) {
|
|
gamedir = Cvar_VariableString( "fs_basegame" );
|
|
}
|
|
|
|
Com_sprintf( filename, sizeof( filename ), "%s%c%s" ARCH_STRING DLL_EXT, gamedir, PATH_SEP, name );
|
|
|
|
libHandle = FS_LoadLibrary( filename );
|
|
|
|
if ( !libHandle ) {
|
|
Com_Printf( "VM_LoadDLL '%s' failed\n", filename );
|
|
return NULL;
|
|
}
|
|
|
|
Com_Printf( "VM_LoadDLL '%s' ok\n", filename );
|
|
|
|
dllEntry = /* ( dllEntry_t ) */ Sys_LoadFunction( libHandle, "dllEntry" );
|
|
*entryPoint = /* ( dllSyscall_t ) */ Sys_LoadFunction( libHandle, "vmMain" );
|
|
if ( !*entryPoint || !dllEntry ) {
|
|
Sys_UnloadLibrary( libHandle );
|
|
return NULL;
|
|
}
|
|
|
|
Com_Printf( "VM_LoadDll(%s) found **vmMain** at %p\n", name, *entryPoint );
|
|
dllEntry( systemcalls );
|
|
Com_Printf( "VM_LoadDll(%s) succeeded!\n", name );
|
|
|
|
return libHandle;
|
|
}
|
|
|
|
|
|
/*
|
|
================
|
|
VM_Create
|
|
|
|
If image ends in .qvm it will be interpreted, otherwise
|
|
it will attempt to load as a system dll
|
|
================
|
|
*/
|
|
vm_t *VM_Create( vmIndex_t index, syscall_t systemCalls, dllSyscall_t dllSyscalls, vmInterpret_t interpret ) {
|
|
int remaining;
|
|
const char *name;
|
|
vmHeader_t *header;
|
|
vm_t *vm;
|
|
|
|
if ( !systemCalls ) {
|
|
Com_Error( ERR_FATAL, "VM_Create: bad parms" );
|
|
}
|
|
|
|
if ( (unsigned)index >= VM_COUNT ) {
|
|
Com_Error( ERR_DROP, "VM_Create: bad vm index %i", index );
|
|
}
|
|
|
|
remaining = Hunk_MemoryRemaining();
|
|
|
|
vm = &vmTable[ index ];
|
|
|
|
// see if we already have the VM
|
|
if ( vm->name ) {
|
|
if ( vm->index != index ) {
|
|
Com_Error( ERR_DROP, "VM_Create: bad allocated vm index %i", vm->index );
|
|
return NULL;
|
|
}
|
|
return vm;
|
|
}
|
|
|
|
name = vmName[ index ];
|
|
|
|
vm->name = name;
|
|
vm->index = index;
|
|
vm->systemCall = systemCalls;
|
|
vm->dllSyscall = dllSyscalls;
|
|
vm->privateFlag = CVAR_PRIVATE;
|
|
|
|
// never allow dll loading with a demo
|
|
if ( interpret == VMI_NATIVE ) {
|
|
if ( Cvar_VariableIntegerValue( "fs_restrict" ) ) {
|
|
interpret = VMI_COMPILED;
|
|
}
|
|
}
|
|
|
|
if ( interpret == VMI_NATIVE ) {
|
|
// try to load as a system dll
|
|
Com_Printf( "Loading dll file %s.\n", name );
|
|
vm->dllHandle = VM_LoadDll( name, &vm->entryPoint, dllSyscalls );
|
|
if ( vm->dllHandle ) {
|
|
vm->privateFlag = 0; // allow reading private cvars
|
|
vm->dataAlloc = ~0U;
|
|
vm->dataMask = ~0U;
|
|
vm->dataBase = 0;
|
|
return vm;
|
|
}
|
|
|
|
Com_Printf( "Failed to load dll, looking for qvm.\n" );
|
|
interpret = VMI_COMPILED;
|
|
}
|
|
|
|
// load the image
|
|
if( ( header = VM_LoadQVM( vm, qtrue ) ) == NULL ) {
|
|
return NULL;
|
|
}
|
|
|
|
// allocate space for the jump targets, which will be filled in by the compile/prep functions
|
|
vm->instructionCount = header->instructionCount;
|
|
//vm->instructionPointers = Hunk_Alloc(vm->instructionCount * sizeof(*vm->instructionPointers), h_high);
|
|
vm->instructionPointers = NULL;
|
|
|
|
// copy or compile the instructions
|
|
vm->codeLength = header->codeLength;
|
|
|
|
// the stack is implicitly at the end of the image
|
|
vm->programStack = vm->dataMask + 1;
|
|
vm->stackBottom = vm->programStack - PROGRAM_STACK_SIZE - PROGRAM_STACK_EXTRA;
|
|
|
|
vm->compiled = qfalse;
|
|
|
|
#ifdef NO_VM_COMPILED
|
|
if ( interpret >= VMI_COMPILED ) {
|
|
Com_Printf( "Architecture doesn't have a bytecode compiler, using interpreter\n" );
|
|
interpret = VMI_BYTECODE;
|
|
}
|
|
#else
|
|
if ( interpret >= VMI_COMPILED ) {
|
|
if ( VM_Compile( vm, header ) ) {
|
|
vm->compiled = qtrue;
|
|
}
|
|
}
|
|
#endif
|
|
// VM_Compile may have reset vm->compiled if compilation failed
|
|
if ( !vm->compiled ) {
|
|
if ( !VM_PrepareInterpreter2( vm, header ) ) {
|
|
FS_FreeFile( header ); // free the original file
|
|
VM_Free( vm );
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
// free the original file
|
|
FS_FreeFile( header );
|
|
|
|
// load the map file
|
|
VM_LoadSymbols( vm );
|
|
|
|
Com_Printf( "%s loaded in %d bytes on the hunk\n", vm->name, remaining - Hunk_MemoryRemaining() );
|
|
|
|
return vm;
|
|
}
|
|
|
|
|
|
/*
|
|
==============
|
|
VM_Free
|
|
==============
|
|
*/
|
|
void VM_Free( vm_t *vm ) {
|
|
|
|
if( !vm ) {
|
|
return;
|
|
}
|
|
|
|
if ( vm->callLevel ) {
|
|
if ( !forced_unload ) {
|
|
Com_Error( ERR_FATAL, "VM_Free(%s) on running vm", vm->name );
|
|
return;
|
|
} else {
|
|
Com_Printf( "forcefully unloading %s vm\n", vm->name );
|
|
}
|
|
}
|
|
|
|
if ( vm->destroy )
|
|
vm->destroy( vm );
|
|
|
|
if ( vm->dllHandle )
|
|
Sys_UnloadLibrary( vm->dllHandle );
|
|
|
|
#if 0 // now automatically freed by hunk
|
|
if ( vm->codeBase.ptr ) {
|
|
Z_Free( vm->codeBase.ptr );
|
|
}
|
|
if ( vm->dataBase ) {
|
|
Z_Free( vm->dataBase );
|
|
}
|
|
if ( vm->instructionPointers ) {
|
|
Z_Free( vm->instructionPointers );
|
|
}
|
|
#endif
|
|
Com_Memset( vm, 0, sizeof( *vm ) );
|
|
}
|
|
|
|
|
|
void VM_Clear( void ) {
|
|
int i;
|
|
for ( i = 0; i < VM_COUNT; i++ ) {
|
|
VM_Free( &vmTable[ i ] );
|
|
}
|
|
}
|
|
|
|
|
|
void VM_Forced_Unload_Start(void) {
|
|
forced_unload = 1;
|
|
}
|
|
|
|
|
|
void VM_Forced_Unload_Done(void) {
|
|
forced_unload = 0;
|
|
}
|
|
|
|
|
|
/*
|
|
==============
|
|
VM_Call
|
|
|
|
|
|
Upon a system call, the stack will look like:
|
|
|
|
sp+32 parm1
|
|
sp+28 parm0
|
|
sp+24 return value
|
|
sp+20 return address
|
|
sp+16 local1
|
|
sp+14 local0
|
|
sp+12 arg1
|
|
sp+8 arg0
|
|
sp+4 return stack
|
|
sp return address
|
|
|
|
An interpreted function will immediately execute
|
|
an OP_ENTER instruction, which will subtract space for
|
|
locals from sp
|
|
==============
|
|
*/
|
|
|
|
intptr_t QDECL VM_Call( vm_t *vm, int nargs, int callnum, ... )
|
|
{
|
|
//vm_t *oldVM;
|
|
intptr_t r;
|
|
int i;
|
|
|
|
if ( !vm ) {
|
|
Com_Error( ERR_FATAL, "VM_Call with NULL vm" );
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
if ( vm_debugLevel ) {
|
|
Com_Printf( "VM_Call( %d )\n", callnum );
|
|
}
|
|
|
|
if ( nargs >= MAX_VMMAIN_CALL_ARGS ) {
|
|
Com_Error( ERR_DROP, "VM_Call: nargs >= MAX_VMMAIN_CALL_ARGS" );
|
|
}
|
|
#endif
|
|
|
|
++vm->callLevel;
|
|
// if we have a dll loaded, call it directly
|
|
if ( vm->entryPoint )
|
|
{
|
|
//rcg010207 - see dissertation at top of VM_DllSyscall() in this file.
|
|
int32_t args[MAX_VMMAIN_CALL_ARGS-1];
|
|
va_list ap;
|
|
va_start( ap, callnum );
|
|
for ( i = 0; i < nargs; i++ ) {
|
|
args[i] = va_arg( ap, int32_t );
|
|
}
|
|
va_end( ap );
|
|
|
|
// add more arguments if you're changed MAX_VMMAIN_CALL_ARGS:
|
|
r = vm->entryPoint( callnum, args[0], args[1], args[2] );
|
|
} else {
|
|
#if id386 && !defined __clang__ // calling convention doesn't need conversion in some cases
|
|
#ifndef NO_VM_COMPILED
|
|
if ( vm->compiled )
|
|
r = VM_CallCompiled( vm, nargs+1, (int32_t*)&callnum );
|
|
else
|
|
#endif
|
|
r = VM_CallInterpreted2( vm, nargs+1, (int32_t*)&callnum );
|
|
#else
|
|
int32_t args[MAX_VMMAIN_CALL_ARGS];
|
|
va_list ap;
|
|
|
|
args[0] = callnum;
|
|
va_start( ap, callnum );
|
|
for ( i = 0; i < nargs; i++ ) {
|
|
args[i+1] = va_arg( ap, int32_t );
|
|
}
|
|
va_end(ap);
|
|
#ifndef NO_VM_COMPILED
|
|
if ( vm->compiled )
|
|
r = VM_CallCompiled( vm, nargs+1, &args[0] );
|
|
else
|
|
#endif
|
|
r = VM_CallInterpreted2( vm, nargs+1, &args[0] );
|
|
#endif
|
|
}
|
|
--vm->callLevel;
|
|
|
|
return r;
|
|
}
|
|
|
|
|
|
//=================================================================
|
|
|
|
static int QDECL VM_ProfileSort( const void *a, const void *b ) {
|
|
vmSymbol_t *sa, *sb;
|
|
|
|
sa = *(vmSymbol_t **)a;
|
|
sb = *(vmSymbol_t **)b;
|
|
|
|
if ( sa->profileCount < sb->profileCount ) {
|
|
return -1;
|
|
}
|
|
if ( sa->profileCount > sb->profileCount ) {
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*
|
|
==============
|
|
VM_NameToVM
|
|
==============
|
|
*/
|
|
static vm_t *VM_NameToVM( const char *name )
|
|
{
|
|
vmIndex_t index;
|
|
|
|
if ( !Q_stricmp( name, "game" ) )
|
|
index = VM_GAME;
|
|
else if ( !Q_stricmp( name, "cgame" ) )
|
|
index = VM_CGAME;
|
|
else if ( !Q_stricmp( name, "ui" ) )
|
|
index = VM_UI;
|
|
else {
|
|
Com_Printf( " unknown VM name '%s'\n", name );
|
|
return NULL;
|
|
}
|
|
|
|
if ( !vmTable[ index ].name ) {
|
|
Com_Printf( " %s is not running.\n", name );
|
|
return NULL;
|
|
}
|
|
|
|
return &vmTable[ index ];
|
|
}
|
|
|
|
|
|
/*
|
|
==============
|
|
VM_VmProfile_f
|
|
|
|
==============
|
|
*/
|
|
static void VM_VmProfile_f( void ) {
|
|
vm_t *vm;
|
|
vmSymbol_t **sorted, *sym;
|
|
int i;
|
|
double total;
|
|
|
|
if ( Cmd_Argc() < 2 ) {
|
|
Com_Printf( "usage: %s <game|cgame|ui>\n", Cmd_Argv( 0 ) );
|
|
return;
|
|
}
|
|
|
|
vm = VM_NameToVM( Cmd_Argv( 1 ) );
|
|
if ( vm == NULL ) {
|
|
return;
|
|
}
|
|
|
|
if ( !vm->numSymbols ) {
|
|
return;
|
|
}
|
|
|
|
sorted = Z_Malloc( vm->numSymbols * sizeof( *sorted ) );
|
|
sorted[0] = vm->symbols;
|
|
total = sorted[0]->profileCount;
|
|
for ( i = 1 ; i < vm->numSymbols ; i++ ) {
|
|
sorted[i] = sorted[i-1]->next;
|
|
total += sorted[i]->profileCount;
|
|
}
|
|
|
|
qsort( sorted, vm->numSymbols, sizeof( *sorted ), VM_ProfileSort );
|
|
|
|
for ( i = 0 ; i < vm->numSymbols ; i++ ) {
|
|
int perc;
|
|
|
|
sym = sorted[i];
|
|
|
|
perc = 100 * (float) sym->profileCount / total;
|
|
Com_Printf( "%2i%% %9i %s\n", perc, sym->profileCount, sym->symName );
|
|
sym->profileCount = 0;
|
|
}
|
|
|
|
Com_Printf(" %9.0f total\n", total );
|
|
|
|
Z_Free( sorted );
|
|
}
|
|
|
|
|
|
/*
|
|
==============
|
|
VM_VmInfo_f
|
|
==============
|
|
*/
|
|
static void VM_VmInfo_f( void ) {
|
|
const vm_t *vm;
|
|
int i;
|
|
|
|
Com_Printf( "Registered virtual machines:\n" );
|
|
for ( i = 0 ; i < VM_COUNT ; i++ ) {
|
|
vm = &vmTable[i];
|
|
if ( !vm->name ) {
|
|
continue;
|
|
}
|
|
Com_Printf( "%s : ", vm->name );
|
|
if ( vm->dllHandle ) {
|
|
Com_Printf( "native\n" );
|
|
continue;
|
|
}
|
|
if ( vm->compiled ) {
|
|
Com_Printf( "compiled on load\n" );
|
|
} else {
|
|
Com_Printf( "interpreted\n" );
|
|
}
|
|
Com_Printf( " code length : %7i\n", vm->codeLength );
|
|
Com_Printf( " table length: %7i\n", vm->instructionCount*4 );
|
|
Com_Printf( " data length : %7i\n", vm->dataMask + 1 );
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
===============
|
|
VM_LogSyscalls
|
|
|
|
Insert calls to this while debugging the vm compiler
|
|
===============
|
|
*/
|
|
void VM_LogSyscalls( int *args ) {
|
|
#if 0
|
|
static int callnum;
|
|
static FILE *f;
|
|
|
|
if ( !f ) {
|
|
f = Sys_FOpen( "syscalls.log", "w" );
|
|
if ( !f ) {
|
|
return;
|
|
}
|
|
}
|
|
callnum++;
|
|
fprintf( f, "%i: %p (%i) = %i %i %i %i\n", callnum, (void*)(args - (int *)currentVM->dataBase),
|
|
args[0], args[1], args[2], args[3], args[4] );
|
|
#endif
|
|
}
|