1 /*
2 ===========================================================================
3 Copyright (C) 1999-2005 Id Software, Inc.
4 
5 This file is part of Quake III Arena source code.
6 
7 Quake III Arena source code is free software; you can redistribute it
8 and/or modify it under the terms of the GNU General Public License as
9 published by the Free Software Foundation; either version 2 of the License,
10 or (at your option) any later version.
11 
12 Quake III Arena source code is distributed in the hope that it will be
13 useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 GNU General Public License for more details.
16 
17 You should have received a copy of the GNU General Public License
18 along with Quake III Arena source code; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
20 ===========================================================================
21 */
22 // vm.c -- virtual machine
23 
24 /*
25 
26 
27 intermix code and data
28 symbol table
29 
30 a dll has one imported function: VM_SystemCall
31 and one exported function: Perform
32 
33 
34 */
35 
36 #include "vm_local.h"
37 
38 cvar_t	*vm_minQvmHunkMegs;
39 
40 vm_t	*currentVM = NULL;
41 vm_t	*lastVM    = NULL;
42 int		vm_debugLevel;
43 
44 // used by Com_Error to get rid of running vm's before longjmp
45 static int forced_unload;
46 
47 #define	MAX_VM		3
48 vm_t	vmTable[MAX_VM];
49 
50 
51 void VM_VmInfo_f( void );
52 void VM_VmProfile_f( void );
53 
54 
55 
56 #if 0 // 64bit!
57 // converts a VM pointer to a C pointer and
58 // checks to make sure that the range is acceptable
59 void	*VM_VM2C( vmptr_t p, int length ) {
60 	return (void *)p;
61 }
62 #endif
63 
VM_Debug(int level)64 void VM_Debug( int level ) {
65 	vm_debugLevel = level;
66 }
67 
68 /*
69 ==============
70 VM_Init
71 ==============
72 */
VM_Init(void)73 void VM_Init( void ) {
74 	Cvar_Get( "vm_cgame", "2", CVAR_ARCHIVE );	// !@# SHIP WITH SET TO 2
75 	Cvar_Get( "vm_game", "2", CVAR_ARCHIVE );	// !@# SHIP WITH SET TO 2
76 	Cvar_Get( "vm_ui", "2", CVAR_ARCHIVE );		// !@# SHIP WITH SET TO 2
77 
78 	vm_minQvmHunkMegs = Cvar_Get( "vm_minQvmHunkMegs", "2", CVAR_ARCHIVE );
79 	Cvar_CheckRange( vm_minQvmHunkMegs, 0, 1024, qtrue );
80 
81 	Cmd_AddCommand("vmprofile", VM_VmProfile_f); // FIXME: doesn't print anything with +set developer 1
82 	Cmd_AddCommand ("vminfo", VM_VmInfo_f );
83 
84 	Com_Memset( vmTable, 0, sizeof( vmTable ) );
85 }
86 
87 
88 /*
89 ===============
90 VM_ValueToSymbol
91 
92 Assumes a program counter value
93 ===============
94 */
VM_ValueToSymbol(vm_t * vm,int value)95 const char *VM_ValueToSymbol( vm_t *vm, int value ) {
96 	vmSymbol_t	*sym = vm->symbols;
97 	static char		text[MAX_TOKEN_CHARS];
98 
99 	if ( !sym ) {
100 		return "NO SYMBOLS";
101 	}
102 
103 	// find the symbol
104 	while ( sym->next && sym->next->symValue <= value ) {
105 		sym = sym->next;
106 	}
107 
108 	if ( value == sym->symValue ) {
109 		return sym->symName;
110 	}
111 
112 	Com_sprintf( text, sizeof( text ), "%s+%i", sym->symName, value - sym->symValue );
113 
114 	return text;
115 }
116 
117 /*
118 ===============
119 VM_ValueToFunctionSymbol
120 
121 For profiling, find the symbol behind this value
122 ===============
123 */
VM_ValueToFunctionSymbol(vm_t * vm,int value)124 vmSymbol_t *VM_ValueToFunctionSymbol( vm_t *vm, int value ) {
125 	vmSymbol_t	*sym = vm->symbols;
126 	static vmSymbol_t	nullSym;
127 
128 	if ( !sym ) {
129 		return &nullSym;
130 	}
131 
132 	while ( sym->next && sym->next->symValue <= value ) {
133 		sym = sym->next;
134 	}
135 
136 	return sym;
137 }
138 
139 
140 /*
141 ===============
142 VM_SymbolToValue
143 ===============
144 */
VM_SymbolToValue(vm_t * vm,const char * symbol)145 int VM_SymbolToValue( vm_t *vm, const char *symbol ) {
146 	vmSymbol_t	*sym;
147 
148 	for ( sym = vm->symbols ; sym ; sym = sym->next ) {
149 		if ( !strcmp( symbol, sym->symName ) ) {
150 			return sym->symValue;
151 		}
152 	}
153 	return 0;
154 }
155 
156 
157 /*
158 =====================
159 VM_SymbolForCompiledPointer
160 =====================
161 */
162 #if 0 // 64bit!
163 const char *VM_SymbolForCompiledPointer( vm_t *vm, void *code ) {
164 	int			i;
165 
166 	if ( code < (void *)vm->codeBase ) {
167 		return "Before code block";
168 	}
169 	if ( code >= (void *)(vm->codeBase + vm->codeLength) ) {
170 		return "After code block";
171 	}
172 
173 	// find which original instruction it is after
174 	for ( i = 0 ; i < vm->codeLength ; i++ ) {
175 		if ( (void *)vm->instructionPointers[i] > code ) {
176 			break;
177 		}
178 	}
179 	i--;
180 
181 	// now look up the bytecode instruction pointer
182 	return VM_ValueToSymbol( vm, i );
183 }
184 #endif
185 
186 
187 
188 /*
189 ===============
190 ParseHex
191 ===============
192 */
ParseHex(const char * text)193 int	ParseHex( const char *text ) {
194 	int		value = 0;
195 	int		c;
196 
197 	while ( ( c = *text++ ) != 0 ) {
198 		if ( c >= '0' && c <= '9' ) {
199 			value = value * 16 + c - '0';
200 			continue;
201 		}
202 		if ( c >= 'a' && c <= 'f' ) {
203 			value = value * 16 + 10 + c - 'a';
204 			continue;
205 		}
206 		if ( c >= 'A' && c <= 'F' ) {
207 			value = value * 16 + 10 + c - 'A';
208 			continue;
209 		}
210 	}
211 
212 	return value;
213 }
214 
215 /*
216 ===============
217 VM_LoadSymbols
218 ===============
219 */
VM_LoadSymbols(vm_t * vm)220 void VM_LoadSymbols( vm_t *vm ) {
221 	union {
222 		char	*c;
223 		void	*v;
224 	} mapfile;
225 	char *text_p, *token;
226 	char	name[MAX_QPATH];
227 	char	symbols[MAX_QPATH];
228 	vmSymbol_t	**prev, *sym;
229 	int		count;
230 	int		value;
231 	int		chars;
232 	int		segment;
233 	int		numInstructions;
234 
235 	// don't load symbols if not developer
236 	if ( !com_developer->integer ) {
237 		return;
238 	}
239 
240 	COM_StripExtension(vm->name, name, sizeof(name));
241 	Com_sprintf( symbols, sizeof( symbols ), "vm/%s.map", name );
242 	FS_ReadFile( symbols, &mapfile.v );
243 	if ( !mapfile.c ) {
244 		Com_Printf( "Couldn't load symbol file: %s\n", symbols );
245 		return;
246 	}
247 
248 	numInstructions = vm->instructionCount;
249 
250 	// parse the symbols
251 	text_p = mapfile.c;
252 	prev = &vm->symbols;
253 	count = 0;
254 
255 	while ( 1 ) {
256 		token = COM_Parse( &text_p );
257 		if ( !token[0] ) {
258 			break;
259 		}
260 		segment = ParseHex( token );
261 		if ( segment ) {
262 			COM_Parse( &text_p );
263 			COM_Parse( &text_p );
264 			continue;		// only load code segment values
265 		}
266 
267 		token = COM_Parse( &text_p );
268 		if ( !token[0] ) {
269 			Com_Printf( "WARNING: incomplete line at end of file\n" );
270 			break;
271 		}
272 		value = ParseHex( token );
273 
274 		token = COM_Parse( &text_p );
275 		if ( !token[0] ) {
276 			Com_Printf( "WARNING: incomplete line at end of file\n" );
277 			break;
278 		}
279 		chars = strlen( token );
280 		sym = Hunk_Alloc( sizeof( *sym ) + chars, h_high );
281 		*prev = sym;
282 		prev = &sym->next;
283 		sym->next = NULL;
284 
285 		// convert value from an instruction number to a code offset
286 		if ( value >= 0 && value < numInstructions ) {
287 			value = vm->instructionPointers[value];
288 		}
289 
290 		sym->symValue = value;
291 		Q_strncpyz( sym->symName, token, chars + 1 );
292 
293 		count++;
294 	}
295 
296 	vm->numSymbols = count;
297 	Com_Printf( "%i symbols parsed from %s\n", count, symbols );
298 	FS_FreeFile( mapfile.v );
299 }
300 
301 /*
302 ============
303 VM_DllSyscall
304 
305 Dlls will call this directly
306 
307  rcg010206 The horror; the horror.
308 
309   The syscall mechanism relies on stack manipulation to get its args.
310    This is likely due to C's inability to pass "..." parameters to
311    a function in one clean chunk. On PowerPC Linux, these parameters
312    are not necessarily passed on the stack, so while (&arg[0] == arg)
313    is true, (&arg[1] == 2nd function parameter) is not necessarily
314    accurate, as arg's value might have been stored to the stack or
315    other piece of scratch memory to give it a valid address, but the
316    next parameter might still be sitting in a register.
317 
318   Quake's syscall system also assumes that the stack grows downward,
319    and that any needed types can be squeezed, safely, into a signed int.
320 
321   This hack below copies all needed values for an argument to a
322    array in memory, so that Quake can get the correct values. This can
323    also be used on systems where the stack grows upwards, as the
324    presumably standard and safe stdargs.h macros are used.
325 
326   As for having enough space in a signed int for your datatypes, well,
327    it might be better to wait for DOOM 3 before you start porting.  :)
328 
329   The original code, while probably still inherently dangerous, seems
330    to work well enough for the platforms it already works on. Rather
331    than add the performance hit for those platforms, the original code
332    is still in use there.
333 
334   For speed, we just grab 15 arguments, and don't worry about exactly
335    how many the syscall actually needs; the extra is thrown away.
336 
337 ============
338 */
VM_DllSyscall(intptr_t arg,...)339 intptr_t QDECL VM_DllSyscall( intptr_t arg, ... ) {
340 #if !id386 || defined __clang__
341   // rcg010206 - see commentary above
342   intptr_t args[MAX_VMSYSCALL_ARGS];
343   int i;
344   va_list ap;
345 
346   args[0] = arg;
347 
348   va_start(ap, arg);
349   for (i = 1; i < ARRAY_LEN (args); i++)
350     args[i] = va_arg(ap, intptr_t);
351   va_end(ap);
352 
353   return currentVM->systemCall( args );
354 #else // original id code
355 	return currentVM->systemCall( &arg );
356 #endif
357 }
358 
359 
360 /*
361 =================
362 VM_LoadQVM
363 
364 Load a .qvm file
365 =================
366 */
VM_LoadQVM(vm_t * vm,qboolean alloc,qboolean unpure)367 vmHeader_t *VM_LoadQVM( vm_t *vm, qboolean alloc, qboolean unpure)
368 {
369 	int					dataLength;
370 	int					i;
371 	char				filename[MAX_QPATH];
372 	union {
373 		vmHeader_t	*h;
374 		void				*v;
375 	} header;
376 
377 	// load the image
378 	Com_sprintf( filename, sizeof(filename), "vm/%s.mp.qvm", vm->name );
379 	Com_Printf( "Loading vm file %s...\n", filename );
380 
381 	FS_ReadFileDir(filename, vm->searchPath, unpure, &header.v);
382 
383 	if ( !header.h ) {
384 		Com_Printf( "Failed.\n" );
385 		VM_Free( vm );
386 
387 		Com_Printf(S_COLOR_YELLOW "Warning: Couldn't open VM file %s\n", filename);
388 
389 		return NULL;
390 	}
391 
392 	// show where the qvm was loaded from
393 	FS_Which(filename, vm->searchPath);
394 
395 	if( LittleLong( header.h->vmMagic ) == VM_MAGIC_VER2 ) {
396 		Com_Printf( "...which has vmMagic VM_MAGIC_VER2\n" );
397 
398 		// byte swap the header
399 		for ( i = 0 ; i < sizeof( vmHeader_t ) / 4 ; i++ ) {
400 			((int *)header.h)[i] = LittleLong( ((int *)header.h)[i] );
401 		}
402 
403 		// validate
404 		if ( header.h->jtrgLength < 0
405 			|| header.h->bssLength < 0
406 			|| header.h->dataLength < 0
407 			|| header.h->litLength < 0
408 			|| header.h->codeLength <= 0 )
409 		{
410 			VM_Free(vm);
411 			FS_FreeFile(header.v);
412 
413 			Com_Printf(S_COLOR_YELLOW "Warning: %s has bad header\n", filename);
414 			return NULL;
415 		}
416 	} else if( LittleLong( header.h->vmMagic ) == VM_MAGIC ) {
417 		// byte swap the header
418 		// sizeof( vmHeader_t ) - sizeof( int ) is the 1.32b vm header size
419 		for ( i = 0 ; i < ( sizeof( vmHeader_t ) - sizeof( int ) ) / 4 ; i++ ) {
420 			((int *)header.h)[i] = LittleLong( ((int *)header.h)[i] );
421 		}
422 
423 		// validate
424 		if ( header.h->bssLength < 0
425 			|| header.h->dataLength < 0
426 			|| header.h->litLength < 0
427 			|| header.h->codeLength <= 0 )
428 		{
429 			VM_Free(vm);
430 			FS_FreeFile(header.v);
431 
432 			Com_Printf(S_COLOR_YELLOW "Warning: %s has bad header\n", filename);
433 			return NULL;
434 		}
435 	} else {
436 		VM_Free( vm );
437 		FS_FreeFile(header.v);
438 
439 		Com_Printf(S_COLOR_YELLOW "Warning: %s does not have a recognisable "
440 				"magic number in its header\n", filename);
441 		return NULL;
442 	}
443 
444 	// round up to next power of 2 so all data operations can
445 	// be mask protected
446 	dataLength = header.h->dataLength + header.h->litLength +
447 		header.h->bssLength;
448 
449 	vm->heapAlloc = vm->heapLength = dataLength - PROGRAM_STACK_SIZE;
450 
451 	dataLength += vm_minQvmHunkMegs->integer * 1024 * 1024;
452 
453 	for ( i = 0 ; dataLength > ( 1 << i ) ; i++ ) {
454 	}
455 	dataLength = 1 << i;
456 
457 	if(alloc)
458 	{
459 		// allocate zero filled space for initialized and uninitialized data
460 		// leave some space beyond data mask so we can secure all mask operations
461 		vm->dataAlloc = dataLength + 4;
462 		vm->dataBase = Hunk_Alloc(vm->dataAlloc, h_high);
463 		vm->dataMask = dataLength - 1;
464 	}
465 	else
466 	{
467 		// clear the data, but make sure we're not clearing more than allocated
468 		if(vm->dataAlloc != dataLength + 4)
469 		{
470 			VM_Free(vm);
471 			FS_FreeFile(header.v);
472 
473 			Com_Printf(S_COLOR_YELLOW "Warning: Data region size of %s not matching after "
474 					"VM_Restart()\n", filename);
475 			return NULL;
476 		}
477 
478 		Com_Memset(vm->dataBase, 0, vm->dataAlloc);
479 	}
480 
481 	// copy the intialized data
482 	Com_Memcpy( vm->dataBase, (byte *)header.h + header.h->dataOffset,
483 		header.h->dataLength + header.h->litLength );
484 
485 	// byte swap the longs
486 	for ( i = 0 ; i < header.h->dataLength ; i += 4 ) {
487 		*(int *)(vm->dataBase + i) = LittleLong( *(int *)(vm->dataBase + i ) );
488 	}
489 
490 	if(header.h->vmMagic == VM_MAGIC_VER2)
491 	{
492 		int previousNumJumpTableTargets = vm->numJumpTableTargets;
493 
494 		header.h->jtrgLength &= ~0x03;
495 
496 		vm->numJumpTableTargets = header.h->jtrgLength >> 2;
497 		Com_Printf("Loading %d jump table targets\n", vm->numJumpTableTargets);
498 
499 		if(alloc)
500 		{
501 			vm->jumpTableTargets = Hunk_Alloc(header.h->jtrgLength, h_high);
502 		}
503 		else
504 		{
505 			if(vm->numJumpTableTargets != previousNumJumpTableTargets)
506 			{
507 				VM_Free(vm);
508 				FS_FreeFile(header.v);
509 
510 				Com_Printf(S_COLOR_YELLOW "Warning: Jump table size of %s not matching after "
511 						"VM_Restart()\n", filename);
512 				return NULL;
513 			}
514 
515 			Com_Memset(vm->jumpTableTargets, 0, header.h->jtrgLength);
516 		}
517 
518 		Com_Memcpy(vm->jumpTableTargets, (byte *) header.h + header.h->dataOffset +
519 				header.h->dataLength + header.h->litLength, header.h->jtrgLength);
520 
521 		// byte swap the longs
522 		for ( i = 0 ; i < header.h->jtrgLength ; i += 4 ) {
523 			*(int *)(vm->jumpTableTargets + i) = LittleLong( *(int *)(vm->jumpTableTargets + i ) );
524 		}
525 	}
526 
527 	return header.h;
528 }
529 
530 /*
531 =================
532 VM_Restart
533 
534 Reload the data, but leave everything else in place
535 This allows a server to do a map_restart without changing memory allocation
536 
537 We need to make sure that servers can access unpure QVMs (not contained in any pak)
538 even if the client is pure, so take "unpure" as argument.
539 =================
540 */
VM_Restart(vm_t * vm,qboolean unpure)541 vm_t *VM_Restart(vm_t *vm, qboolean unpure)
542 {
543 	vmHeader_t	*header;
544 
545 	// DLL's can't be restarted in place
546 	if ( vm->dllHandle ) {
547 		char	name[MAX_QPATH];
548 		intptr_t	(*systemCall)( intptr_t *parms );
549 
550 		systemCall = vm->systemCall;
551 		Q_strncpyz( name, vm->name, sizeof( name ) );
552 
553 		VM_Free( vm );
554 
555 		vm = VM_Create( name, systemCall, VMI_NATIVE );
556 		return vm;
557 	}
558 
559 	// load the image
560 	Com_Printf("VM_Restart()\n");
561 
562 	if(!(header = VM_LoadQVM(vm, qfalse, unpure)))
563 	{
564 		Com_Error(ERR_DROP, "VM_Restart failed");
565 		return NULL;
566 	}
567 
568 	// free the original file
569 	FS_FreeFile(header);
570 
571 	return vm;
572 }
573 
574 #ifndef DEDICATED
575 extern int cl_connectedToPureServer;
576 #endif
577 
578 /*
579 ================
580 VM_Create
581 
582 If image ends in .qvm it will be interpreted, otherwise
583 it will attempt to load as a system dll
584 ================
585 */
VM_Create(const char * module,intptr_t (* systemCalls)(intptr_t *),vmInterpret_t interpret)586 vm_t *VM_Create( const char *module, intptr_t (*systemCalls)(intptr_t *),
587 				vmInterpret_t interpret ) {
588 	vm_t		*vm;
589 	vmHeader_t	*header;
590 	int			i, remaining, retval;
591 	char filename[MAX_OSPATH];
592 	void *startSearch = NULL;
593 	qboolean pureServer, pureVM;
594 
595 	if ( !module || !module[0] || !systemCalls ) {
596 		Com_Error( ERR_FATAL, "VM_Create: bad parms" );
597 	}
598 
599 	remaining = Hunk_MemoryRemaining();
600 
601 	// see if we already have the VM
602 	for ( i = 0 ; i < MAX_VM ; i++ ) {
603 		if (!Q_stricmp(vmTable[i].name, module)) {
604 			vm = &vmTable[i];
605 			return vm;
606 		}
607 	}
608 
609 	// find a free vm
610 	for ( i = 0 ; i < MAX_VM ; i++ ) {
611 		if ( !vmTable[i].name[0] ) {
612 			break;
613 		}
614 	}
615 
616 	if ( i == MAX_VM ) {
617 		Com_Error( ERR_FATAL, "VM_Create: no free vm_t" );
618 	}
619 
620 	vm = &vmTable[i];
621 
622 	Q_strncpyz(vm->name, module, sizeof(vm->name));
623 
624 #ifdef DEDICATED
625 	pureServer = qfalse;
626 #else
627 	pureServer = (cl_connectedToPureServer) ? qtrue : qfalse;
628 #endif
629 
630 	pureVM = pureServer;
631 
632 	do
633 	{
634 		retval = FS_FindVM(&startSearch, filename, sizeof(filename), module, !pureVM, (interpret != VMI_NATIVE));
635 
636 		if(retval == VMI_COMPILED)
637 		{
638 			vm->searchPath = startSearch;
639 			if((header = VM_LoadQVM(vm, qtrue, !pureVM)))
640 				break;
641 
642 			// VM_Free overwrites the name on failed load
643 			Q_strncpyz(vm->name, module, sizeof(vm->name));
644 		}
645 		else if(retval == VMI_NATIVE)
646 		{
647 			Com_Printf("Try loading dll file %s\n", filename);
648 
649 			vm->dllHandle = Sys_LoadGameDll(filename, &vm->entryPoint, VM_DllSyscall);
650 
651 			if(vm->dllHandle)
652 			{
653 				vm->systemCall = systemCalls;
654 				return vm;
655 			}
656 
657 			Com_Printf("Failed loading dll, trying next\n");
658 		}
659 		else if ( pureServer && pureVM )
660 		{
661 			Com_Printf( "Failed to find pure %s VM, trying unpure\n", module );
662 			startSearch = NULL;
663 			pureVM = qfalse;
664 			retval = 1; // don't exit loop
665 		}
666 	} while(retval >= 0);
667 
668 	if(retval < 0)
669 		return NULL;
670 
671 	vm->systemCall = systemCalls;
672 
673 	// allocate space for the jump targets, which will be filled in by the compile/prep functions
674 	vm->instructionCount = header->instructionCount;
675 	vm->instructionPointers = Hunk_Alloc(vm->instructionCount * sizeof(*vm->instructionPointers), h_high);
676 
677 	// copy or compile the instructions
678 	vm->codeLength = header->codeLength;
679 
680 	vm->compiled = qfalse;
681 
682 #ifdef NO_VM_COMPILED
683 	if(interpret >= VMI_COMPILED) {
684 		Com_Printf("Architecture doesn't have a bytecode compiler, using interpreter\n");
685 		interpret = VMI_BYTECODE;
686 	}
687 #else
688 	if(interpret != VMI_BYTECODE)
689 	{
690 		vm->compiled = qtrue;
691 		VM_Compile( vm, header );
692 	}
693 #endif
694 	// VM_Compile may have reset vm->compiled if compilation failed
695 	if (!vm->compiled)
696 	{
697 		VM_PrepareInterpreter( vm, header );
698 	}
699 
700 	// free the original file
701 	FS_FreeFile( header );
702 
703 	// load the map file
704 	VM_LoadSymbols( vm );
705 
706 	// the stack is implicitly at the end of the image
707 	vm->programStack = vm->dataMask + 1;
708 	vm->stackBottom = vm->programStack - PROGRAM_STACK_SIZE;
709 
710 	// allocate temporary memory down from the bottom of the stack
711 	vm->heapAllocTop = vm->stackBottom;
712 
713 	Com_Printf("%s loaded in %d bytes on the hunk\n", module, remaining - Hunk_MemoryRemaining());
714 
715 	return vm;
716 }
717 
718 /*
719 ==============
720 VM_Free
721 ==============
722 */
VM_Free(vm_t * vm)723 void VM_Free( vm_t *vm ) {
724 
725 	if(!vm) {
726 		return;
727 	}
728 
729 	if(vm->callLevel) {
730 		if(!forced_unload) {
731 			Com_Error( ERR_FATAL, "VM_Free(%s) on running vm", vm->name );
732 			return;
733 		} else {
734 			Com_Printf( "forcefully unloading %s vm\n", vm->name );
735 		}
736 	}
737 
738 	if(vm->destroy)
739 		vm->destroy(vm);
740 
741 	if ( vm->dllHandle ) {
742 		Sys_UnloadDll( vm->dllHandle );
743 		Com_Memset( vm, 0, sizeof( *vm ) );
744 	}
745 #if 0	// now automatically freed by hunk
746 	if ( vm->codeBase ) {
747 		Z_Free( vm->codeBase );
748 	}
749 	if ( vm->dataBase ) {
750 		Z_Free( vm->dataBase );
751 	}
752 	if ( vm->instructionPointers ) {
753 		Z_Free( vm->instructionPointers );
754 	}
755 #endif
756 	Com_Memset( vm, 0, sizeof( *vm ) );
757 
758 	currentVM = NULL;
759 	lastVM = NULL;
760 }
761 
VM_Clear(void)762 void VM_Clear(void) {
763 	int i;
764 	for (i=0;i<MAX_VM; i++) {
765 		VM_Free(&vmTable[i]);
766 	}
767 }
768 
VM_Forced_Unload_Start(void)769 void VM_Forced_Unload_Start(void) {
770 	forced_unload = 1;
771 }
772 
VM_Forced_Unload_Done(void)773 void VM_Forced_Unload_Done(void) {
774 	forced_unload = 0;
775 }
776 
VM_ArgPtr(intptr_t intValue)777 void *VM_ArgPtr( intptr_t intValue ) {
778 	if ( !intValue ) {
779 		return NULL;
780 	}
781 	// currentVM is missing on reconnect
782 	if ( currentVM==NULL )
783 	  return NULL;
784 
785 	if ( currentVM->entryPoint ) {
786 		return (void *)(currentVM->dataBase + intValue);
787 	}
788 	else {
789 		return (void *)(currentVM->dataBase + (intValue & currentVM->dataMask));
790 	}
791 }
792 
VM_ExplicitArgPtr(vm_t * vm,intptr_t intValue)793 void *VM_ExplicitArgPtr( vm_t *vm, intptr_t intValue ) {
794 	if ( !intValue ) {
795 		return NULL;
796 	}
797 
798 	// currentVM is missing on reconnect here as well?
799 	if ( currentVM==NULL )
800 	  return NULL;
801 
802 	//
803 	if ( vm->entryPoint ) {
804 		return (void *)(vm->dataBase + intValue);
805 	}
806 	else {
807 		return (void *)(vm->dataBase + (intValue & vm->dataMask));
808 	}
809 }
810 
VM_IsNative(vm_t * vm)811 qboolean VM_IsNative( vm_t *vm ) {
812 	return ( vm && vm->dllHandle );
813 }
814 
815 
816 /*
817 ==============
818 VM_Call
819 
820 
821 Upon a system call, the stack will look like:
822 
823 sp+32	parm1
824 sp+28	parm0
825 sp+24	return value
826 sp+20	return address
827 sp+16	local1
828 sp+14	local0
829 sp+12	arg1
830 sp+8	arg0
831 sp+4	return stack
832 sp		return address
833 
834 An interpreted function will immediately execute
835 an OP_ENTER instruction, which will subtract space for
836 locals from sp
837 ==============
838 */
839 
VM_Call(vm_t * vm,intptr_t callnum,...)840 intptr_t QDECL VM_Call( vm_t *vm, intptr_t callnum, ... )
841 {
842 	vm_t	*oldVM;
843 	intptr_t r;
844 	int i;
845 
846 	// DHM - Nerve
847 #ifdef UPDATE_SERVER
848 	return 0;
849 #endif
850 
851 	if(!vm || !vm->name[0])
852 		Com_Error(ERR_FATAL, "VM_Call with NULL vm");
853 
854 	oldVM = currentVM;
855 	currentVM = vm;
856 	lastVM = vm;
857 
858 	if ( vm_debugLevel ) {
859 	  Com_Printf( "VM_Call( %d )\n", (int)callnum );
860 	}
861 
862 	++vm->callLevel;
863 	// if we have a dll loaded, call it directly
864 	if ( vm->entryPoint ) {
865 		//rcg010207 -  see dissertation at top of VM_DllSyscall() in this file.
866 		intptr_t args[MAX_VMMAIN_ARGS-1];
867 		va_list ap;
868 		va_start(ap, callnum);
869 		for (i = 0; i < ARRAY_LEN(args); i++) {
870 			args[i] = va_arg(ap, intptr_t);
871 		}
872 		va_end(ap);
873 
874 		r = vm->entryPoint( callnum,  args[0],  args[1],  args[2], args[3],
875                             args[4],  args[5],  args[6], args[7],
876                             args[8],  args[9], args[10], args[11]);
877 	} else {
878 #if ( id386 || idsparc ) && !defined __clang__ // calling convention doesn't need conversion in some cases
879 #ifndef NO_VM_COMPILED
880 		if ( vm->compiled )
881 			r = VM_CallCompiled( vm, (int*)&callnum );
882 		else
883 #endif
884 			r = VM_CallInterpreted( vm, (int*)&callnum );
885 #else
886 		struct {
887 			int callnum;
888 			int args[MAX_VMMAIN_ARGS-1];
889 		} a;
890 		va_list ap;
891 
892 		a.callnum = callnum;
893 		va_start(ap, callnum);
894 		for (i = 0; i < ARRAY_LEN(a.args); i++) {
895 			a.args[i] = va_arg(ap, intptr_t);
896 		}
897 		va_end(ap);
898 #ifndef NO_VM_COMPILED
899 		if ( vm->compiled )
900 			r = VM_CallCompiled( vm, &a.callnum );
901 		else
902 #endif
903 			r = VM_CallInterpreted( vm, &a.callnum );
904 #endif
905 	}
906 	--vm->callLevel;
907 
908 	if ( oldVM != NULL )
909 	  currentVM = oldVM;
910 	return r;
911 }
912 
913 //=================================================================
914 
VM_ProfileSort(const void * a,const void * b)915 static int QDECL VM_ProfileSort( const void *a, const void *b ) {
916 	vmSymbol_t	*sa, *sb;
917 
918 	sa = *(vmSymbol_t **)a;
919 	sb = *(vmSymbol_t **)b;
920 
921 	if ( sa->profileCount < sb->profileCount ) {
922 		return -1;
923 	}
924 	if ( sa->profileCount > sb->profileCount ) {
925 		return 1;
926 	}
927 	return 0;
928 }
929 
930 /*
931 ==============
932 VM_VmProfile_f
933 
934 ==============
935 */
VM_VmProfile_f(void)936 void VM_VmProfile_f( void ) {
937 	vm_t		*vm;
938 	vmSymbol_t	**sorted, *sym;
939 	int			i;
940 	double		total;
941 
942 	if ( !lastVM ) {
943 		return;
944 	}
945 
946 	vm = lastVM;
947 
948 	if ( !vm->numSymbols ) {
949 		Com_Printf("VM symbols not available\n");
950 		return;
951 	}
952 
953 	sorted = Z_Malloc( vm->numSymbols * sizeof( *sorted ) );
954 	sorted[0] = vm->symbols;
955 	total = sorted[0]->profileCount;
956 	for ( i = 1 ; i < vm->numSymbols ; i++ ) {
957 		sorted[i] = sorted[i-1]->next;
958 		total += sorted[i]->profileCount;
959 	}
960 
961 	qsort( sorted, vm->numSymbols, sizeof( *sorted ), VM_ProfileSort );
962 
963 	for ( i = 0 ; i < vm->numSymbols ; i++ ) {
964 		int		perc;
965 
966 		sym = sorted[i];
967 
968 		perc = 100 * (float) sym->profileCount / total;
969 		Com_Printf( "%2i%% %9i %s\n", perc, sym->profileCount, sym->symName );
970 		sym->profileCount = 0;
971 	}
972 
973 	Com_Printf("    %9.0f total\n", total );
974 
975 	Z_Free( sorted );
976 }
977 
978 /*
979 ==============
980 VM_VmInfo_f
981 
982 ==============
983 */
VM_VmInfo_f(void)984 void VM_VmInfo_f( void ) {
985 	vm_t	*vm;
986 	int		i;
987 
988 	Com_Printf( "Registered virtual machines:\n" );
989 	for ( i = 0 ; i < MAX_VM ; i++ ) {
990 		vm = &vmTable[i];
991 		if ( !vm->name[0] ) {
992 			break;
993 		}
994 		Com_Printf( "%s : ", vm->name );
995 		if ( vm->dllHandle ) {
996 			Com_Printf( "native\n" );
997 			continue;
998 		}
999 		if ( vm->compiled ) {
1000 			Com_Printf( "compiled on load\n" );
1001 		} else {
1002 			Com_Printf( "interpreted\n" );
1003 		}
1004 		Com_Printf( "    code length : %7i\n", vm->codeLength );
1005 		Com_Printf( "    table length: %7i\n", vm->instructionCount*4 );
1006 		Com_Printf( "    data length : %7i\n", vm->dataMask + 1 );
1007 		Com_Printf( "    total memory: %7i\n", vm->stackBottom - vm->heapLength );
1008 		Com_Printf( "    free memory : %7i\n", vm->heapAllocTop - vm->heapAlloc );
1009 		Com_Printf( "    used permanent memory: %7i\n", vm->heapAlloc - vm->heapLength );
1010 		Com_Printf( "    used temporary memory: %7i\n", vm->stackBottom - vm->heapAllocTop );
1011 	}
1012 }
1013 
1014 /*
1015 ===============
1016 VM_LogSyscalls
1017 
1018 Insert calls to this while debugging the vm compiler
1019 ===============
1020 */
VM_LogSyscalls(int * args)1021 void VM_LogSyscalls( int *args ) {
1022 	static	int		callnum;
1023 	static	FILE	*f;
1024 
1025 	if ( !f ) {
1026 		f = fopen("syscalls.log", "w" );
1027 	}
1028 	callnum++;
1029 	fprintf(f, "%i: %p (%i) = %i %i %i %i\n", callnum, (void*)(args - (int *)currentVM->dataBase),
1030 		args[0], args[1], args[2], args[3], args[4] );
1031 }
1032 
1033 /*
1034 =================
1035 VM_BlockCopy
1036 Executes a block copy operation within currentVM data space
1037 =================
1038 */
1039 
VM_BlockCopy(unsigned int dest,unsigned int src,size_t n)1040 void VM_BlockCopy(unsigned int dest, unsigned int src, size_t n)
1041 {
1042 	unsigned int dataMask = currentVM->dataMask;
1043 
1044 	if ((dest & dataMask) != dest
1045 	|| (src & dataMask) != src
1046 	|| ((dest + n) & dataMask) != dest + n
1047 	|| ((src + n) & dataMask) != src + n)
1048 	{
1049 		Com_Error(ERR_DROP, "OP_BLOCK_COPY out of range!");
1050 	}
1051 
1052 	Com_Memcpy(currentVM->dataBase + dest, currentVM->dataBase + src, n);
1053 }
1054 
1055 /*
1056 =================
1057 VM_GetTempMemory
1058 
1059 Use for passing data for qvms, use VM_ExplicitArgPtr
1060 to get engine writeable address
1061 =================
1062 */
VM_GetTempMemory(vm_t * vm,int size,const void * initData)1063 unsigned VM_GetTempMemory( vm_t *vm, int size, const void *initData ) {
1064 	int allocSize;
1065 
1066 	if ( vm->dllHandle ) {
1067 		return 0;
1068 	}
1069 
1070 	// align addresses
1071 	allocSize = ( size + 31 ) & ~31;
1072 
1073 	if ( vm->heapAllocTop - allocSize <= vm->heapAlloc ) {
1074 		return 0;
1075 	}
1076 
1077 	vm->heapAllocTop -= allocSize;
1078 
1079 	if ( initData ) {
1080 		Com_Memcpy( vm->dataBase + vm->heapAllocTop, initData, size );
1081 	} else {
1082 		Com_Memset( vm->dataBase + vm->heapAllocTop, 0, size );
1083 	}
1084 
1085 	return vm->heapAllocTop;
1086 }
1087 
1088 /*
1089 =================
1090 VM_FreeTempMemory
1091 
1092 Must free temporary memory in reverse order of allocating.
1093 =================
1094 */
VM_FreeTempMemory(vm_t * vm,unsigned qvmPointer,int size,void * outData)1095 void VM_FreeTempMemory( vm_t *vm, unsigned qvmPointer, int size, void *outData ) {
1096 	int allocSize;
1097 
1098 	if ( vm->dllHandle ) {
1099 		return;
1100 	}
1101 
1102 	// align addresses
1103 	allocSize = ( size + 31 ) & ~31;
1104 
1105 	if ( vm->heapAllocTop + allocSize > vm->stackBottom ) {
1106 		Com_Error( ERR_DROP, "Tried to free too much QVM temporary memory!");
1107 	}
1108 
1109 	if ( outData ) {
1110 		Com_Memcpy( outData, vm->dataBase + vm->heapAllocTop, size );
1111 	}
1112 
1113 	Com_Memset( vm->dataBase + vm->heapAllocTop, 0, size );
1114 
1115 	vm->heapAllocTop += allocSize;
1116 }
1117 
1118 #if 0 // ZTM: new unused code for allocating unfreeable memory in dlls and qvms.
1119 /*
1120 =================
1121 QVM_Alloc
1122 =================
1123 */
1124 unsigned int QVM_Alloc( vm_t *vm, int size ) {
1125 	unsigned int pointer;
1126 	int allocSize;
1127 
1128 	// align addresses
1129 	allocSize = ( size + 31 ) & ~31;
1130 
1131 	if ( vm->heapAlloc + allocSize > vm->heapAllocTop ) {
1132 		Com_Error( ERR_DROP, "QVM_Alloc: %s failed on allocation of %i bytes", vm->name, size );
1133 		return 0;
1134 	}
1135 
1136 	pointer = vm->heapAlloc;
1137 	vm->heapAlloc += allocSize;
1138 
1139 	Com_Memset( vm->dataBase + pointer, 0, size );
1140 
1141 	return pointer;
1142 }
1143 
1144 /*
1145 =================
1146 VM_ExplicitAlloc
1147 =================
1148 */
1149 intptr_t VM_ExplicitAlloc( vm_t *vm, int size ) {
1150 	intptr_t	ptr;
1151 
1152 	if (size < 1)
1153 		Com_Error( ERR_DROP, "VM %s tried to allocate %d bytes of memory", vm->name, size );
1154 
1155 	if ( vm->dllHandle ) {
1156 		ptr = (intptr_t)Hunk_Alloc( size, h_high );
1157 	} else {
1158 		ptr = QVM_Alloc( vm, size );
1159 	}
1160 
1161 	return ptr;
1162 }
1163 
1164 /*
1165 =================
1166 VM_Alloc
1167 =================
1168 */
1169 intptr_t VM_Alloc( int size ) {
1170 	return VM_ExplicitAlloc( currentVM, size );
1171 }
1172 #endif
1173 
1174