diff --git a/core/interfaces/IHandleSys.h b/core/interfaces/IHandleSys.h
index d2b2c297..f5fc71a2 100644
--- a/core/interfaces/IHandleSys.h
+++ b/core/interfaces/IHandleSys.h
@@ -100,7 +100,7 @@ namespace SourceMod
*
* @param name Name of handle type (NULL or "" to be anonymous)
* @param dispatch Pointer to a valid IHandleTypeDispatch object.
- * @return A new HandleType_t unique ID.
+ * @return A new HandleType_t unique ID, or 0 on failure.
*/
virtual HandleType_t CreateType(const char *name,
IHandleTypeDispatch *dispatch) =0;
@@ -116,7 +116,7 @@ namespace SourceMod
* @param security Pointer to a temporary HandleSecurity object, NULL to use default
* or inherited permissions.
* @param ident Security token for any permissions.
- * @return A new HandleType_t unique ID.
+ * @return A new HandleType_t unique ID, or 0 on failure.
*/
virtual HandleType_t CreateTypeEx(const char *name,
IHandleTypeDispatch *dispatch,
diff --git a/core/msvc8/sourcemod_mm.vcproj b/core/msvc8/sourcemod_mm.vcproj
index aaf946b4..5300a0ac 100644
--- a/core/msvc8/sourcemod_mm.vcproj
+++ b/core/msvc8/sourcemod_mm.vcproj
@@ -487,10 +487,6 @@
-
-
@@ -519,6 +515,10 @@
RelativePath="..\..\sourcepawn\include\sp_file_headers.h"
>
+
+
@@ -531,10 +531,6 @@
RelativePath="..\..\sourcepawn\include\sp_vm_types.h"
>
-
-
diff --git a/core/vm/sp_vm_basecontext.cpp b/core/vm/sp_vm_basecontext.cpp
index 2a4531b4..2a496b56 100644
--- a/core/vm/sp_vm_basecontext.cpp
+++ b/core/vm/sp_vm_basecontext.cpp
@@ -1,18 +1,32 @@
#include
+#include
#include
#include
#include "sp_vm_api.h"
#include "sp_vm_basecontext.h"
+#include "sp_vm_engine.h"
using namespace SourcePawn;
+extern SourcePawnEngine g_SourcePawn;
+
#define CELLBOUNDMAX (INT_MAX/sizeof(cell_t))
#define STACKMARGIN ((cell_t)(16*sizeof(cell_t)))
+int GlobalDebugBreak(sp_context_t *ctx, uint32_t frm, uint32_t cip)
+{
+ g_SourcePawn.RunTracer(ctx, frm, cip);
+
+ return SP_ERROR_NONE;
+}
+
BaseContext::BaseContext(sp_context_t *_ctx)
{
ctx = _ctx;
ctx->context = this;
+ ctx->dbreak = GlobalDebugBreak;
+ m_InExec = false;
+ m_CustomMsg = false;
}
IVirtualMachine *BaseContext::GetVirtualMachine()
@@ -74,11 +88,26 @@ int BaseContext::Execute(funcid_t funcid, cell_t *result)
cell_t save_sp = ctx->sp;
cell_t save_hp = ctx->hp;
+ bool wasExec = m_InExec;
+
+ /* Clear the error state, if any */
+ ctx->n_err = SP_ERROR_NONE;
+ ctx->n_idx = 0;
+ m_InExec = true;
+ m_MsgCache[0] = '\0';
+ m_CustomMsg = false;
+
+ g_SourcePawn.PushTracer(ctx);
+
err = vm->ContextExecute(ctx, code_addr, result);
-
+
+ m_InExec = wasExec;
+
/**
- * :TODO: turn this into an error check
+ * :TODO: Calling from a plugin in here will erase the cached message...
+ * Should that be documented?
*/
+ g_SourcePawn.PopTracer(err, m_CustomMsg ? m_MsgCache : NULL);
#if defined _DEBUG
if (err == SP_ERROR_NONE)
@@ -96,6 +125,51 @@ int BaseContext::Execute(funcid_t funcid, cell_t *result)
return err;
}
+void BaseContext::SetErrorMessage(const char *msg, va_list ap)
+{
+ m_CustomMsg = true;
+
+ vsnprintf(m_MsgCache, sizeof(m_MsgCache), msg, ap);
+}
+
+void BaseContext::ThrowNativeErrorEx(int error, const char *msg, ...)
+{
+ if (!m_InExec)
+ {
+ return;
+ }
+
+ ctx->n_err = error;
+
+ if (msg)
+ {
+ va_list ap;
+ va_start(ap, msg);
+ SetErrorMessage(msg, ap);
+ va_end(ap);
+ }
+}
+
+cell_t BaseContext::ThrowNativeError(const char *msg, ...)
+{
+ if (!m_InExec)
+ {
+ return 0;
+ }
+
+ ctx->n_err = SP_ERROR_NATIVE;
+
+ if (msg)
+ {
+ va_list ap;
+ va_start(ap, msg);
+ SetErrorMessage(msg, ap);
+ va_end(ap);
+ }
+
+ return 0;
+}
+
int BaseContext::HeapAlloc(unsigned int cells, cell_t *local_addr, cell_t **phys_addr)
{
cell_t *addr;
diff --git a/core/vm/sp_vm_basecontext.h b/core/vm/sp_vm_basecontext.h
index 56c7d14e..44f7e6af 100644
--- a/core/vm/sp_vm_basecontext.h
+++ b/core/vm/sp_vm_basecontext.h
@@ -42,12 +42,19 @@ namespace SourcePawn
virtual int BindNative(sp_nativeinfo_t *native);
virtual int BindNativeToAny(SPVM_NATIVE_FUNC native);
virtual int Execute(funcid_t funcid, cell_t *result);
+ virtual void ThrowNativeErrorEx(int error, const char *msg, ...);
+ virtual cell_t ThrowNativeError(const char *msg, ...);
public: //IPluginDebugInfo
virtual int LookupFile(ucell_t addr, const char **filename);
virtual int LookupFunction(ucell_t addr, const char **name);
virtual int LookupLine(ucell_t addr, uint32_t *line);
+ private:
+ void SetErrorMessage(const char *msg, va_list ap);
private:
sp_context_t *ctx;
+ char m_MsgCache[1024];
+ bool m_CustomMsg;
+ bool m_InExec;
};
};
diff --git a/core/vm/sp_vm_engine.cpp b/core/vm/sp_vm_engine.cpp
index 0ca4161f..ae5e0c1a 100644
--- a/core/vm/sp_vm_engine.cpp
+++ b/core/vm/sp_vm_engine.cpp
@@ -1,5 +1,6 @@
#include
#include
+#include
#include "sp_file_headers.h"
#include "sp_vm_types.h"
#include "sp_vm_engine.h"
@@ -13,8 +14,60 @@
#include
#endif
+#define INVALID_CIP 0xFFFFFFFF
+
using namespace SourcePawn;
+#define ERROR_MESSAGE_MAX 23
+static const char *g_ErrorMsgTable[] =
+{
+ NULL,
+ "Unrecognizable file format",
+ "Decompressor was not found",
+ "Not enough space on the heap",
+ "Invalid parameter or parameter type",
+ "Invalid plugin address",
+ "Object or index not found",
+ "Invalid index or index not found",
+ "Not enough space on the stack",
+ "Debug section not found or debug not enabled",
+ "Invalid instruction",
+ "Invalid memory access",
+ "Stack went below stack boundary",
+ "Heap went below heap boundary",
+ "Divide by zero",
+ "Array index is out of bounds",
+ "Instruction contained invalid parameter",
+ "Stack memory leaked by native",
+ "Heap memory leaked by native",
+ "Dynamic array is too big",
+ "Tracker stack is out of bounds",
+ "Native was never bound",
+ "Maximum number of parameters reached",
+ "Native detected error",
+};
+
+SourcePawnEngine::SourcePawnEngine()
+{
+ m_pDebugHook = NULL;
+ m_CallStack = NULL;
+ m_FreedCalls = NULL;
+ m_CurChain = 0;
+}
+
+SourcePawnEngine::~SourcePawnEngine()
+{
+ assert(m_CallStack == NULL);
+
+ TracedCall *pTemp;
+ while (m_FreedCalls)
+ {
+ pTemp = m_FreedCalls->next;
+ delete pTemp;
+ m_FreedCalls = pTemp;
+ }
+}
+
void *SourcePawnEngine::ExecAlloc(size_t size)
{
#if defined WIN32
@@ -302,3 +355,224 @@ int SourcePawnEngine::FreeFromMemory(sp_plugin_t *plugin)
return SP_ERROR_NONE;
}
+
+IDebugListener *SourcePawnEngine::SetDebugListener(IDebugListener *pListener)
+{
+ IDebugListener *old = m_pDebugHook;
+
+ m_pDebugHook = pListener;
+
+ return old;
+}
+
+unsigned int SourcePawnEngine::GetContextCallCount()
+{
+ if (!m_CallStack)
+ {
+ return 0;
+ }
+
+ return m_CallStack->chain;
+}
+
+TracedCall *SourcePawnEngine::MakeTracedCall(bool new_chain)
+{
+ TracedCall *pCall;
+
+ if (!m_FreedCalls)
+ {
+ pCall = new TracedCall;
+ } else {
+ /* Unlink the head node from the free list */
+ pCall = m_FreedCalls;
+ m_FreedCalls = m_FreedCalls->next;
+ if (m_FreedCalls)
+ {
+ m_FreedCalls->prev = NULL;
+ }
+ }
+
+ /* Link as the head node into the call stack */
+ pCall->next = m_CallStack;
+ pCall->prev = NULL;
+
+ if (new_chain)
+ {
+ pCall->chain = ++m_CurChain;
+ } else {
+ pCall->chain = m_CurChain;
+ }
+
+ if (m_CallStack)
+ {
+ m_CallStack->prev = pCall;
+ }
+ m_CallStack = pCall;
+
+ return pCall;
+}
+
+void SourcePawnEngine::FreeTracedCall(TracedCall *pCall)
+{
+ /* Check if this is the top of the call stack */
+ if (pCall == m_CallStack)
+ {
+ m_CallStack = m_CallStack->next;
+ if (m_CallStack)
+ {
+ m_CallStack->prev = NULL;
+ }
+ }
+
+ /* Add this to our linked list of freed calls */
+ if (!m_FreedCalls)
+ {
+ m_FreedCalls = pCall;
+ m_FreedCalls->next = NULL;
+ m_FreedCalls->prev = NULL;
+ } else {
+ pCall->next = m_FreedCalls;
+ pCall->prev = NULL;
+ m_FreedCalls->prev = pCall;
+ m_FreedCalls = pCall;
+ }
+}
+
+void SourcePawnEngine::PushTracer(sp_context_t *ctx)
+{
+ TracedCall *pCall = MakeTracedCall(true);
+
+ pCall->cip = INVALID_CIP;
+ pCall->ctx = ctx;
+ pCall->frm = INVALID_CIP;
+}
+
+void SourcePawnEngine::RunTracer(sp_context_t *ctx, uint32_t frame, uint32_t codeip)
+{
+ assert(m_CallStack != NULL);
+ assert(m_CallStack->ctx == ctx);
+ assert(m_CallStack->chain == m_CurChain);
+
+ if (m_CallStack->cip == INVALID_CIP)
+ {
+ /* We aren't logging anything yet, so begin the trace */
+ m_CallStack->cip = codeip;
+ m_CallStack->frm = frame;
+ } else {
+ if (m_CallStack->frm > frame)
+ {
+ /* The last frame has moved down the stack,
+ * so we have to push a new call onto our list.
+ */
+ TracedCall *pCall = MakeTracedCall(false);
+ pCall->frm = frame;
+ } else if (m_CallStack->frm < frame) {
+ /* The last frame has moved up the stack,
+ * so we have to pop the call from our list.
+ */
+ FreeTracedCall(m_CallStack);
+ }
+ /* no matter where we are, update the cip */
+ m_CallStack->cip = codeip;
+ }
+}
+
+void SourcePawnEngine::PopTracer(int error, const char *msg)
+{
+ assert(m_CallStack != NULL);
+
+ if (error != SP_ERROR_NONE && m_pDebugHook)
+ {
+ CContextTrace trace(m_CallStack, error, msg);
+ m_pDebugHook->OnContextExecuteError(m_CallStack->ctx->context, &trace);
+ }
+
+ /* Now pop the error chain */
+ while (m_CallStack && m_CallStack->chain == m_CurChain)
+ {
+ FreeTracedCall(m_CallStack);
+ }
+
+ m_CurChain--;
+}
+
+CContextTrace::CContextTrace(TracedCall *pStart, int error, const char *msg) :
+ m_Error(error), m_pMsg(msg), m_pStart(pStart), m_pIterator(pStart)
+{
+}
+
+bool CContextTrace::DebugInfoAvailable()
+{
+ return ((m_pStart->ctx->flags & SP_FLAG_DEBUG) == SP_FLAG_DEBUG);
+}
+
+const char *CContextTrace::GetCustomErrorString()
+{
+ return m_pMsg;
+}
+
+int CContextTrace::GetErrorCode()
+{
+ return m_Error;
+}
+
+const char *CContextTrace::GetErrorString()
+{
+ if (m_Error > ERROR_MESSAGE_MAX ||
+ m_Error < 1)
+ {
+ return "Invalid error code";
+ } else {
+ return g_ErrorMsgTable[m_Error];
+ }
+}
+
+void CContextTrace::ResetTrace()
+{
+ m_pIterator = m_pStart;
+}
+
+bool CContextTrace::GetTraceInfo(CallStackInfo *trace)
+{
+ if (m_pIterator->chain != m_pStart->chain)
+ {
+ return false;
+ }
+
+ if (m_pIterator->cip == INVALID_CIP)
+ {
+ return false;
+ }
+
+ IPluginContext *pContext = m_pIterator->ctx->context;
+ IPluginDebugInfo *pInfo = pContext->GetDebugInfo();
+
+ m_pIterator = m_pIterator->next;
+
+ if (!pInfo)
+ {
+ return false;
+ }
+
+ if (!trace)
+ {
+ return true;
+ }
+
+ if (pInfo->LookupFile(m_pIterator->cip, &(trace->filename)) != SP_ERROR_NONE)
+ {
+ trace->filename = NULL;
+ }
+
+ if (pInfo->LookupFunction(m_pIterator->cip, &(trace->function)) != SP_ERROR_NONE)
+ {
+ trace->function = NULL;
+ }
+
+ if (pInfo->LookupLine(m_pIterator->cip, &(trace->line)) != SP_ERROR_NONE)
+ {
+ trace->line = 0;
+ }
+
+ return true;
+}
diff --git a/core/vm/sp_vm_engine.h b/core/vm/sp_vm_engine.h
index fb13f405..b25ec729 100644
--- a/core/vm/sp_vm_engine.h
+++ b/core/vm/sp_vm_engine.h
@@ -5,83 +5,78 @@
namespace SourcePawn
{
+ struct TracedCall
+ {
+ uint32_t cip;
+ uint32_t frm;
+ sp_context_t *ctx;
+ TracedCall *next;
+ TracedCall *prev;
+ unsigned int chain;
+ };
+
+
+ class CContextTrace : public IContextTrace
+ {
+ public:
+ CContextTrace(TracedCall *pStart, int error, const char *msg);
+ public:
+ virtual int GetErrorCode();
+ virtual const char *GetErrorString();
+ virtual bool DebugInfoAvailable();
+ virtual const char *GetCustomErrorString();
+ virtual bool GetTraceInfo(CallStackInfo *trace);
+ virtual void ResetTrace();
+ private:
+ TracedCall *m_pStart;
+ TracedCall *m_pIterator;
+ const char *m_pMsg;
+ int m_Error;
+ };
+
+
class SourcePawnEngine : public ISourcePawnEngine
{
public:
- /**
- * Loads a named file from a file pointer.
- * Using this means base memory will be allocated by the VM.
- * Note: The file handle position may be undefined on entry, and is
- * always undefined on conclusion.
- *
- * @param fp File pointer. May be at any offset. Not closed on return.
- * @param err Optional error code pointer.
- * @return A new plugin structure.
- */
+ SourcePawnEngine();
+ ~SourcePawnEngine();
+ public: //ISourcePawnEngine
sp_plugin_t *LoadFromFilePointer(FILE *fp, int *err);
-
- /**
- * Loads a file from a base memory address.
- *
- * @param base Base address of the plugin's memory region.
- * @param plugin If NULL, a new plugin pointer is returned.
- * Otherwise, the passed pointer is used.
- * @param err Optional error code pointer.
- * @return The resulting plugin pointer.
- */
sp_plugin_t *LoadFromMemory(void *base, sp_plugin_t *plugin, int *err);
-
- /**
- * Frees all of the memory associated with a plugin file.
- * If allocated using SP_LoadFromMemory, the base and plugin pointer
- * itself are not freed (so this may end up doing nothing).
- */
int FreeFromMemory(sp_plugin_t *plugin);
-
- /**
- * Creates a new IContext from a context handle.
- *
- * @param ctx Context to use as a basis for the IPluginContext.
- * @return New IPluginContext handle.
- */
IPluginContext *CreateBaseContext(sp_context_t *ctx);
-
- /**
- * Frees a context.
- *
- * @param ctx Context pointer to free.
- */
void FreeBaseContext(IPluginContext *ctx);
-
- /**
- * Allocates memory.
- *
- * @param size Size of memory to allocate.
- * @return Pointer to memory, NULL if allocation failed.
- */
void *BaseAlloc(size_t size);
-
- /**
- * Frees memory allocated with BaseAlloc.
- *
- * @param memory Memory address to free.
- */
void BaseFree(void *memory);
-
- /**
- * Allocates executable memory.
- *
- * @param size Size of memory to allocate.
- * @return Pointer to memory, NULL if allocation failed.
- */
void *ExecAlloc(size_t size);
+ void ExecFree(void *address);
+ IDebugListener *SetDebugListener(IDebugListener *pListener);
+ unsigned int GetContextCallCount();
+ public: //Debugger Stuff
+ /**
+ * @brief Pushes a context onto the top of the call tracer.
+ *
+ * @param ctx Plugin context.
+ */
+ void PushTracer(sp_context_t *ctx);
/**
- * Frees executable memory.
- *
- * @param address Address to free.
+ * @brief Pops a plugin off the call tracer.
*/
- void ExecFree(void *address);
+ void PopTracer(int error, const char *msg);
+
+ /**
+ * @brief Runs tracer from a debug break.
+ */
+ void RunTracer(sp_context_t *ctx, uint32_t frame, uint32_t codeip);
+ private:
+ TracedCall *MakeTracedCall(bool new_chain);
+ void FreeTracedCall(TracedCall *pCall);
+ private:
+ IDebugListener *m_pDebugHook;
+ TracedCall *m_FreedCalls;
+ TracedCall *m_CallStack;
+ unsigned int m_CurChain;
};
};