core/vm/sp_vm_engine.cpp
author David Anderson <dvander@alliedmods.net>
Wed Aug 08 02:27:10 2007 +0000 (2007-08-08)
changeset 1269 27397584df9c
parent 1231 17badaf66ce4
child 1330 ab94316bb52b
permissions -rw-r--r--
- massive overhaul of the voting subsystem
- NOTE: menu API compatibility was COMPLETELY broken for C++!
- cleaned up various aspects of the menu API
- implemented new extended vote results callback
- extended the debug reporter a tiny bit
     1 /**
     2  * vim: set ts=4 :
     3  * ================================================================
     4  * SourcePawn
     5  * Copyright (C) 2004-2007 AlliedModders LLC.  All rights reserved.
     6  * ================================================================
     7  *
     8  * This program is free software; you can redistribute it and/or 
     9  * modify it under the terms of the GNU General Public License, 
    10  * version 3.0, as published by the Free Software Foundation.
    11  * 
    12  * This program is distributed in the hope that it will be useful,
    13  * 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 this program.  If not, see <http://www.gnu.org/licenses/>.
    19  *
    20  * As a special exception, AlliedModders LLC gives you permission to 
    21  * link the code of this program (as well as its derivative works) to 
    22  * "Half-Life 2," the "Source Engine," the "SourcePawn JIT," and any 
    23  * Game MODs that run on software by the Valve Corporation.  You must 
    24  * obey the GNU General Public License in all respects for all other 
    25  * code used. Additionally, AlliedModders LLC grants this exception 
    26  * to all derivative works. AlliedModders LLC defines further 
    27  * exceptions, found in LICENSE.txt (as of this writing, version 
    28  * JULY-31-2007), or <http://www.sourcemod.net/license.php>.
    29  *
    30  * Version: $Id$
    31  */
    32 
    33 #include <malloc.h>
    34 #include <string.h>
    35 #include <assert.h>
    36 #include "sp_file_headers.h"
    37 #include "sp_vm_types.h"
    38 #include "sp_vm_engine.h"
    39 #include "zlib/zlib.h"
    40 #include "sp_vm_basecontext.h"
    41 
    42 #if defined WIN32
    43  #define WIN32_LEAN_AND_MEAN
    44  #include <windows.h>
    45 #elif defined __GNUC__
    46  #include <sys/mman.h>
    47 #endif
    48 
    49 #define INVALID_CIP			0xFFFFFFFF
    50 
    51 using namespace SourcePawn;
    52 
    53 #define ERROR_MESSAGE_MAX		25
    54 static const char *g_ErrorMsgTable[] = 
    55 {
    56 	NULL,
    57 	"Unrecognizable file format",
    58 	"Decompressor was not found",
    59 	"Not enough space on the heap",
    60 	"Invalid parameter or parameter type",
    61 	"Invalid plugin address",
    62 	"Object or index not found",
    63 	"Invalid index or index not found",
    64 	"Not enough space on the stack",
    65 	"Debug section not found or debug not enabled",
    66 	"Invalid instruction",
    67 	"Invalid memory access",
    68 	"Stack went below stack boundary",
    69 	"Heap went below heap boundary",
    70 	"Divide by zero",
    71 	"Array index is out of bounds",
    72 	"Instruction contained invalid parameter",
    73 	"Stack memory leaked by native",
    74 	"Heap memory leaked by native",
    75 	"Dynamic array is too big",
    76 	"Tracker stack is out of bounds",
    77 	"Native is not bound",
    78 	"Maximum number of parameters reached",
    79 	"Native detected error",
    80 	"Plugin not runnable",
    81 	"Call was aborted",
    82 };
    83 
    84 const char *GetSourcePawnErrorMessage(int error)
    85 {
    86 	if (error < 1 || error > ERROR_MESSAGE_MAX)
    87 	{
    88 		return NULL;
    89 	}
    90 
    91 	return g_ErrorMsgTable[error];
    92 }
    93 
    94 SourcePawnEngine::SourcePawnEngine()
    95 {
    96 	m_pDebugHook = NULL;
    97 	m_CallStack = NULL;
    98 	m_FreedCalls = NULL;
    99 	m_CurChain = 0;
   100 #if 0
   101 	m_pFreeFuncs = NULL;
   102 #endif
   103 }
   104 
   105 SourcePawnEngine::~SourcePawnEngine()
   106 {
   107 	TracedCall *pTemp;
   108 	while (m_FreedCalls)
   109 	{
   110 		pTemp = m_FreedCalls->next;
   111 		delete m_FreedCalls;
   112 		m_FreedCalls = pTemp;
   113 	}
   114 
   115 #if 0
   116 	CFunction *pNext;
   117 	while (m_pFreeFuncs)
   118 	{
   119 		pNext = m_pFreeFuncs->m_pNext;
   120 		delete m_pFreeFuncs;
   121 		m_pFreeFuncs = pNext;
   122 	}
   123 #endif
   124 }
   125 
   126 void *SourcePawnEngine::ExecAlloc(size_t size)
   127 {
   128 #if defined WIN32
   129 	return VirtualAlloc(NULL, size, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
   130 #elif defined __GNUC__
   131 	void *base = memalign(sysconf(_SC_PAGESIZE), size);
   132 	if (mprotect(base, size, PROT_READ|PROT_WRITE|PROT_EXEC) != 0)
   133 	{
   134 		free(base);
   135 		return NULL;
   136 	}
   137 	return base;
   138 #endif
   139 }
   140 
   141 void SourcePawnEngine::ExecFree(void *address)
   142 {
   143 #if defined WIN32
   144 	VirtualFree(address, 0, MEM_RELEASE);
   145 #elif defined __GNUC__
   146 	free(address);
   147 #endif
   148 }
   149 
   150 void *SourcePawnEngine::BaseAlloc(size_t size)
   151 {
   152 	return malloc(size);
   153 }
   154 
   155 void SourcePawnEngine::BaseFree(void *memory)
   156 {
   157 	free(memory);
   158 }
   159 
   160 IPluginContext *SourcePawnEngine::CreateBaseContext(sp_context_t *ctx)
   161 {
   162 	return new BaseContext(ctx);
   163 }
   164 
   165 void SourcePawnEngine::FreeBaseContext(IPluginContext *ctx)
   166 {
   167 	delete ctx;
   168 }
   169 
   170 sp_plugin_t *_ReadPlugin(sp_file_hdr_t *hdr, uint8_t *base, sp_plugin_t *plugin, int *err)
   171 {
   172 	char *nameptr;
   173 	uint8_t sectnum = 0;
   174 	sp_file_section_t *secptr = (sp_file_section_t *)(base + sizeof(sp_file_hdr_t));
   175 
   176 	memset(plugin, 0, sizeof(sp_plugin_t));
   177 
   178 	plugin->base = base;
   179 
   180 	while (sectnum < hdr->sections)
   181 	{
   182 		nameptr = (char *)(base + hdr->stringtab + secptr->nameoffs);
   183 
   184 		if (!(plugin->pcode) && !strcmp(nameptr, ".code"))
   185 		{
   186 			sp_file_code_t *cod = (sp_file_code_t *)(base + secptr->dataoffs);
   187 			plugin->pcode = base + secptr->dataoffs + cod->code;
   188 			plugin->pcode_size = cod->codesize;
   189 			plugin->flags = cod->flags;
   190 		}
   191 		else if (!(plugin->data) && !strcmp(nameptr, ".data"))
   192 		{
   193 			sp_file_data_t *dat = (sp_file_data_t *)(base + secptr->dataoffs);
   194 			plugin->data = base + secptr->dataoffs + dat->data;
   195 			plugin->data_size = dat->datasize;
   196 			plugin->memory = dat->memsize;
   197 		}
   198 		else if (!(plugin->info.publics) && !strcmp(nameptr, ".publics"))
   199 		{
   200 			plugin->info.publics_num = secptr->size / sizeof(sp_file_publics_t);
   201 			plugin->info.publics = (sp_file_publics_t *)(base + secptr->dataoffs);
   202 		}
   203 		else if (!(plugin->info.pubvars) && !strcmp(nameptr, ".pubvars"))
   204 		{
   205 			plugin->info.pubvars_num = secptr->size / sizeof(sp_file_pubvars_t);
   206 			plugin->info.pubvars = (sp_file_pubvars_t *)(base + secptr->dataoffs);
   207 		}
   208 		else if (!(plugin->info.natives) && !strcmp(nameptr, ".natives"))
   209 		{
   210 			plugin->info.natives_num = secptr->size / sizeof(sp_file_natives_t);
   211 			plugin->info.natives = (sp_file_natives_t *)(base + secptr->dataoffs);
   212 		}
   213 		else if (!(plugin->info.stringbase) && !strcmp(nameptr, ".names"))
   214 		{
   215 			plugin->info.stringbase = (const char *)(base + secptr->dataoffs);
   216 		}
   217 		else if (!(plugin->debug.files) && !strcmp(nameptr, ".dbg.files"))
   218 		{
   219 			plugin->debug.files = (sp_fdbg_file_t *)(base + secptr->dataoffs);
   220 		}
   221 		else if (!(plugin->debug.lines) && !strcmp(nameptr, ".dbg.lines"))
   222 		{
   223 			plugin->debug.lines = (sp_fdbg_line_t *)(base + secptr->dataoffs);
   224 		}
   225 		else if (!(plugin->debug.symbols) && !strcmp(nameptr, ".dbg.symbols"))
   226 		{
   227 			plugin->debug.symbols = (sp_fdbg_symbol_t *)(base + secptr->dataoffs);
   228 		}
   229 		else if (!(plugin->debug.lines_num) && !strcmp(nameptr, ".dbg.info"))
   230 		{
   231 			sp_fdbg_info_t *inf = (sp_fdbg_info_t *)(base + secptr->dataoffs);
   232 			plugin->debug.files_num = inf->num_files;
   233 			plugin->debug.lines_num = inf->num_lines;
   234 			plugin->debug.syms_num = inf->num_syms;
   235 		}
   236 		else if (!(plugin->debug.stringbase) && !strcmp(nameptr, ".dbg.strings"))
   237 		{
   238 			plugin->debug.stringbase = (const char *)(base + secptr->dataoffs);
   239 		}
   240 
   241 		secptr++;
   242 		sectnum++;
   243 	}
   244 
   245 	if (!(plugin->pcode) || !(plugin->data) || !(plugin->info.stringbase))
   246 	{
   247 		goto return_error;
   248 	}
   249 
   250 	if ((plugin->flags & SP_FLAG_DEBUG) && (!(plugin->debug.files) || !(plugin->debug.lines) || !(plugin->debug.symbols)))
   251 	{
   252 		goto return_error;
   253 	}
   254 
   255 	if (err)
   256 	{
   257 		*err = SP_ERROR_NONE;
   258 	}
   259 
   260 	return plugin;
   261 
   262 return_error:
   263 	if (err)
   264 	{
   265 		*err = SP_ERROR_FILE_FORMAT;
   266 	}
   267 
   268 	return NULL;
   269 }
   270 
   271 sp_plugin_t *SourcePawnEngine::LoadFromFilePointer(FILE *fp, int *err)
   272 {
   273 	sp_file_hdr_t hdr;
   274 	sp_plugin_t *plugin;
   275 	uint8_t *base;
   276 	int z_result;
   277 	int error;
   278 
   279 	if (!fp)
   280 	{
   281 		error = SP_ERROR_NOT_FOUND;
   282 		goto return_error;
   283 	}
   284 
   285 	/* Rewind for safety */
   286 	rewind(fp);
   287 	fread(&hdr, sizeof(sp_file_hdr_t), 1, fp);
   288 
   289 	if (hdr.magic != SPFILE_MAGIC)
   290 	{
   291 		error = SP_ERROR_FILE_FORMAT;
   292 		goto return_error;
   293 	}
   294 
   295 	switch (hdr.compression)
   296 	{
   297 	case SPFILE_COMPRESSION_GZ:
   298 		{
   299 			uint32_t uncompsize = hdr.imagesize - hdr.dataoffs;
   300 			uint32_t compsize = hdr.disksize - hdr.dataoffs;
   301 			uint32_t sectsize = hdr.dataoffs - sizeof(sp_file_hdr_t);
   302 			uLongf destlen = uncompsize;
   303 
   304 			char *tempbuf = (char *)malloc(compsize);
   305 			void *uncompdata = malloc(uncompsize);
   306 			void *sectheader = malloc(sectsize);
   307 
   308 			fread(sectheader, sectsize, 1, fp);
   309 			fread(tempbuf, compsize, 1, fp);
   310 
   311 			z_result = uncompress((Bytef *)uncompdata, &destlen, (Bytef *)tempbuf, compsize);
   312 			free(tempbuf);
   313 			if (z_result != Z_OK)
   314 			{
   315 				free(sectheader);
   316 				free(uncompdata);
   317 				error = SP_ERROR_DECOMPRESSOR;
   318 				goto return_error;
   319 			}
   320 
   321 			base = (uint8_t *)malloc(hdr.imagesize);
   322 			memcpy(base, &hdr, sizeof(sp_file_hdr_t));
   323 			memcpy(base + sizeof(sp_file_hdr_t), sectheader, sectsize);
   324 			free(sectheader);
   325 			memcpy(base + hdr.dataoffs, uncompdata, uncompsize);
   326 			free(uncompdata);
   327 			break;
   328 		}
   329 	case SPFILE_COMPRESSION_NONE:
   330 		{
   331 			base = (uint8_t *)malloc(hdr.imagesize);
   332 			rewind(fp);
   333 			fread(base, hdr.imagesize, 1, fp);
   334 			break;
   335 		}
   336 	default:
   337 		{
   338 			error = SP_ERROR_DECOMPRESSOR;
   339 			goto return_error;
   340 		}
   341 	}
   342 
   343 	plugin = (sp_plugin_t *)malloc(sizeof(sp_plugin_t));
   344 	if (!_ReadPlugin(&hdr, base, plugin, err))
   345 	{
   346 		free(plugin);
   347 		free(base);
   348 		return NULL;
   349 	}
   350 
   351 	plugin->allocflags = 0;
   352 
   353 	return plugin;
   354 
   355 return_error:
   356 	if (err)
   357 	{
   358 		*err = error;
   359 	}
   360 	return NULL;
   361 }
   362 
   363 sp_plugin_t *SourcePawnEngine::LoadFromMemory(void *base, sp_plugin_t *plugin, int *err)
   364 {
   365 	sp_file_hdr_t hdr;
   366 	uint8_t noptr = 0;
   367 
   368 	memcpy(&hdr, base, sizeof(sp_file_hdr_t));
   369 
   370 	if (!plugin)
   371 	{
   372 		plugin = (sp_plugin_t *)malloc(sizeof(sp_plugin_t));
   373 		noptr = 1;
   374 	}
   375 
   376 	if (!_ReadPlugin(&hdr, (uint8_t *)base, plugin, err))
   377 	{
   378 		if (noptr)
   379 		{
   380 			free(plugin);
   381 		}
   382 		return NULL;
   383 	}
   384 
   385 	if (!noptr)
   386 	{
   387 		plugin->allocflags |= SP_FA_SELF_EXTERNAL;
   388 	}
   389 	plugin->allocflags |= SP_FA_BASE_EXTERNAL;
   390 
   391 	return plugin;
   392 }
   393 
   394 int SourcePawnEngine::FreeFromMemory(sp_plugin_t *plugin)
   395 {
   396 	if (!(plugin->allocflags & SP_FA_BASE_EXTERNAL))
   397 	{
   398 		free(plugin->base);
   399 		plugin->base = NULL;
   400 	}
   401 	if (!(plugin->allocflags & SP_FA_SELF_EXTERNAL))
   402 	{
   403 		free(plugin);
   404 	}
   405 
   406 	return SP_ERROR_NONE;
   407 }
   408 
   409 #if 0
   410 void SourcePawnEngine::ReleaseFunctionToPool(CFunction *func)
   411 {
   412 	if (!func)
   413 	{
   414 		return;
   415 	}
   416 	func->Cancel();
   417 	func->m_pNext = m_pFreeFuncs;
   418 	m_pFreeFuncs = func;
   419 }
   420 
   421 CFunction *SourcePawnEngine::GetFunctionFromPool(funcid_t f, IPluginContext *plugin)
   422 {
   423 	if (!m_pFreeFuncs)
   424 	{
   425 		return new CFunction(f, plugin);
   426 	} else {
   427 		CFunction *pFunc = m_pFreeFuncs;
   428 		m_pFreeFuncs = m_pFreeFuncs->m_pNext;
   429 		pFunc->Set(f, plugin);
   430 		return pFunc;
   431 	}
   432 }
   433 #endif
   434 
   435 IDebugListener *SourcePawnEngine::SetDebugListener(IDebugListener *pListener)
   436 {
   437 	IDebugListener *old = m_pDebugHook;
   438 
   439 	m_pDebugHook = pListener;
   440 
   441 	return old;
   442 }
   443 
   444 unsigned int SourcePawnEngine::GetContextCallCount()
   445 {
   446 	if (!m_CallStack)
   447 	{
   448 		return 0;
   449 	}
   450 
   451 	return m_CallStack->chain;
   452 }
   453 
   454 TracedCall *SourcePawnEngine::MakeTracedCall(bool new_chain)
   455 {
   456 	TracedCall *pCall;
   457 
   458 	if (!m_FreedCalls)
   459 	{
   460 		pCall = new TracedCall;
   461 	} else {
   462 		/* Unlink the head node from the free list */
   463 		pCall = m_FreedCalls;
   464 		m_FreedCalls = m_FreedCalls->next;
   465 	}
   466 
   467 	/* Link as the head node into the call stack */
   468 	pCall->next = m_CallStack;
   469 
   470 	if (new_chain)
   471 	{
   472 		pCall->chain = ++m_CurChain;
   473 	} else {
   474 		pCall->chain = m_CurChain;
   475 	}
   476 
   477 	m_CallStack = pCall;
   478 
   479 	return pCall;
   480 }
   481 
   482 void SourcePawnEngine::FreeTracedCall(TracedCall *pCall)
   483 {
   484 	/* Check if this is the top of the call stack */
   485 	if (pCall == m_CallStack)
   486 	{
   487 		m_CallStack = m_CallStack->next;
   488 	}
   489 
   490 	/* Add this to our linked list of freed calls */
   491 	if (!m_FreedCalls)
   492 	{
   493 		m_FreedCalls = pCall;
   494 		m_FreedCalls->next = NULL;
   495 	} else {
   496 		pCall->next = m_FreedCalls;
   497 		m_FreedCalls = pCall;
   498 	}
   499 }
   500 
   501 void SourcePawnEngine::PushTracer(sp_context_t *ctx)
   502 {
   503 	TracedCall *pCall = MakeTracedCall(true);
   504 
   505 	pCall->cip = INVALID_CIP;
   506 	pCall->ctx = ctx;
   507 	pCall->frm = INVALID_CIP;
   508 }
   509 
   510 void SourcePawnEngine::RunTracer(sp_context_t *ctx, uint32_t frame, uint32_t codeip)
   511 {
   512 	assert(m_CallStack != NULL);
   513 	assert(m_CallStack->ctx == ctx);
   514 	assert(m_CallStack->chain == m_CurChain);
   515 
   516 	if (m_CallStack->cip == INVALID_CIP)
   517 	{
   518 		/* We aren't logging anything yet, so begin the trace */
   519 		m_CallStack->cip = codeip;
   520 		m_CallStack->frm = frame;
   521 	} else {
   522 		if (m_CallStack->frm > frame)
   523 		{
   524 			/* The last frame has moved down the stack, 
   525 			 * so we have to push a new call onto our list.
   526 			 */
   527 			TracedCall *pCall = MakeTracedCall(false);
   528 			pCall->ctx = ctx;
   529 			pCall->frm = frame;
   530 		} else if (m_CallStack->frm < frame) {
   531 			/* The last frame has moved up the stack,
   532 			 * so we have to pop the call from our list.
   533 			 */
   534 			FreeTracedCall(m_CallStack);
   535 		}
   536 		/* no matter where we are, update the cip */
   537 		m_CallStack->cip = codeip;
   538 	}
   539 }
   540 
   541 void SourcePawnEngine::PopTracer(int error, const char *msg)
   542 {
   543 	assert(m_CallStack != NULL);
   544 
   545 	if (error != SP_ERROR_NONE && m_pDebugHook)
   546 	{
   547 		uint32_t native = INVALID_CIP;
   548 
   549 		if (m_CallStack->ctx->n_err)
   550 		{
   551 			native = m_CallStack->ctx->n_idx;
   552 		}
   553 
   554 		CContextTrace trace(m_CallStack, error, msg, native);
   555 		m_pDebugHook->OnContextExecuteError(m_CallStack->ctx->context, &trace);
   556 	}
   557 
   558 	/* Now pop the error chain  */
   559 	while (m_CallStack && m_CallStack->chain == m_CurChain)
   560 	{
   561 		FreeTracedCall(m_CallStack);
   562 	}
   563 
   564 	m_CurChain--;
   565 }
   566 
   567 unsigned int SourcePawnEngine::GetEngineAPIVersion()
   568 {
   569 	return SOURCEPAWN_ENGINE_API_VERSION;
   570 }
   571 
   572 CContextTrace::CContextTrace(TracedCall *pStart, int error, const char *msg, uint32_t native) : 
   573  m_Error(error), m_pMsg(msg), m_pStart(pStart), m_pIterator(pStart), m_Native(native)
   574 {
   575 }
   576 
   577 bool CContextTrace::DebugInfoAvailable()
   578 {
   579 	return ((m_pStart->ctx->flags & SP_FLAG_DEBUG) == SP_FLAG_DEBUG);
   580 }
   581 
   582 const char *CContextTrace::GetCustomErrorString()
   583 {
   584 	return m_pMsg;
   585 }
   586 
   587 int CContextTrace::GetErrorCode()
   588 {
   589 	return m_Error;
   590 }
   591 
   592 const char *CContextTrace::GetErrorString()
   593 {
   594 	if (m_Error > ERROR_MESSAGE_MAX || 
   595 		m_Error < 1)
   596 	{
   597 		return "Invalid error code";
   598 	} else {
   599 		return g_ErrorMsgTable[m_Error];
   600 	}
   601 }
   602 
   603 void CContextTrace::ResetTrace()
   604 {
   605 	m_pIterator = m_pStart;
   606 }
   607 
   608 bool CContextTrace::GetTraceInfo(CallStackInfo *trace)
   609 {
   610 	if (!m_pIterator || (m_pIterator->chain != m_pStart->chain))
   611 	{
   612 		return false;
   613 	}
   614 
   615 	if (m_pIterator->cip == INVALID_CIP)
   616 	{
   617 		return false;
   618 	}
   619 
   620 	IPluginContext *pContext = m_pIterator->ctx->context;
   621 	IPluginDebugInfo *pInfo = pContext->GetDebugInfo();
   622 
   623 	if (!pInfo)
   624 	{
   625 		return false;
   626 	}
   627 
   628 	if (!trace)
   629 	{
   630 		m_pIterator = m_pIterator->next;
   631 		return true;
   632 	}
   633 
   634 	if (pInfo->LookupFile(m_pIterator->cip, &(trace->filename)) != SP_ERROR_NONE)
   635 	{
   636 		trace->filename = NULL;
   637 	}
   638 
   639 	if (pInfo->LookupFunction(m_pIterator->cip, &(trace->function)) != SP_ERROR_NONE)
   640 	{
   641 		trace->function = NULL;
   642 	}
   643 
   644 	if (pInfo->LookupLine(m_pIterator->cip, &(trace->line)) != SP_ERROR_NONE)
   645 	{
   646 		trace->line = 0;
   647 	}
   648 
   649 	m_pIterator = m_pIterator->next;
   650 
   651 	return true;
   652 }
   653 
   654 const char *CContextTrace::GetLastNative(uint32_t *index)
   655 {
   656 	if (m_Native == INVALID_CIP)
   657 	{
   658 		return NULL;
   659 	}
   660 
   661 	sp_native_t *native;
   662 	if (m_pIterator->ctx->context->GetNativeByIndex(m_Native, &native) != SP_ERROR_NONE)
   663 	{
   664 		return NULL;
   665 	}
   666 
   667 	if (index)
   668 	{
   669 		*index = m_Native;
   670 	}
   671 
   672 	return native->name;
   673 }