initial import of new debugger API

--HG--
extra : convert_revision : svn%3A39bc706e-5318-0410-9160-8a85361fbb7c/trunk%40245
This commit is contained in:
David Anderson 2007-01-01 01:09:53 +00:00
parent 610f628298
commit 378e4d20f3
6 changed files with 421 additions and 75 deletions

View File

@ -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,

View File

@ -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>

View File

@ -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;

View File

@ -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;
};
};

View File

@ -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;
}

View File

@ -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;
};
};