From 378e4d20f3c5f18aa9c4f50bd7697a4a76147285 Mon Sep 17 00:00:00 2001 From: David Anderson Date: Mon, 1 Jan 2007 01:09:53 +0000 Subject: [PATCH] initial import of new debugger API --HG-- extra : convert_revision : svn%3A39bc706e-5318-0410-9160-8a85361fbb7c/trunk%40245 --- core/interfaces/IHandleSys.h | 4 +- core/msvc8/sourcemod_mm.vcproj | 12 +- core/vm/sp_vm_basecontext.cpp | 78 +++++++++- core/vm/sp_vm_basecontext.h | 7 + core/vm/sp_vm_engine.cpp | 274 +++++++++++++++++++++++++++++++++ core/vm/sp_vm_engine.h | 121 +++++++-------- 6 files changed, 421 insertions(+), 75 deletions(-) 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; }; };