initial import of new debugger API
--HG-- extra : convert_revision : svn%3A39bc706e-5318-0410-9160-8a85361fbb7c/trunk%40245
This commit is contained in:
parent
610f628298
commit
378e4d20f3
@ -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,
|
||||
|
@ -487,10 +487,6 @@
|
||||
<Filter
|
||||
Name="Header Files"
|
||||
>
|
||||
<File
|
||||
RelativePath="..\..\sourcepawn\include\sp_typeutil.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\vm\sp_vm_basecontext.h"
|
||||
>
|
||||
@ -519,6 +515,10 @@
|
||||
RelativePath="..\..\sourcepawn\include\sp_file_headers.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\..\sourcepawn\include\sp_typeutil.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\..\sourcepawn\include\sp_vm_api.h"
|
||||
>
|
||||
@ -531,10 +531,6 @@
|
||||
RelativePath="..\..\sourcepawn\include\sp_vm_types.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\..\sourcepawn\include\sp_vm_typeutil.h"
|
||||
>
|
||||
</File>
|
||||
</Filter>
|
||||
</Filter>
|
||||
</Files>
|
||||
|
@ -1,18 +1,32 @@
|
||||
#include <string.h>
|
||||
#include <stdarg.h>
|
||||
#include <assert.h>
|
||||
#include <limits.h>
|
||||
#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;
|
||||
|
@ -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;
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
#include <malloc.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include "sp_file_headers.h"
|
||||
#include "sp_vm_types.h"
|
||||
#include "sp_vm_engine.h"
|
||||
@ -13,8 +14,60 @@
|
||||
#include <sys/mman.h>
|
||||
#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;
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user