1018 lines
20 KiB
C++
Executable File
1018 lines
20 KiB
C++
Executable File
// vim: set ts=4 sw=4 tw=99 noet:
|
|
//
|
|
// AMX Mod X, based on AMX Mod by Aleksander Naszko ("OLO").
|
|
// Copyright (C) The AMX Mod X Development Team.
|
|
//
|
|
// This software is licensed under the GNU General Public License, version 3 or higher.
|
|
// Additional exceptions apply. For full license details, see LICENSE.txt or visit:
|
|
// https://alliedmods.net/amxmodx-license
|
|
|
|
#include "amxmodx.h"
|
|
#include "debugger.h"
|
|
#include "binlog.h"
|
|
|
|
/**
|
|
* AMX Mod X Debugging Engine
|
|
* Written by David "BAILOPAN" Anderson
|
|
*/
|
|
|
|
enum AmxOpcodes
|
|
{
|
|
OP_NONE, /* invalid opcode */
|
|
OP_LOAD_PRI,
|
|
OP_LOAD_ALT,
|
|
OP_LOAD_S_PRI,
|
|
OP_LOAD_S_ALT,
|
|
OP_LREF_PRI,
|
|
OP_LREF_ALT,
|
|
OP_LREF_S_PRI,
|
|
OP_LREF_S_ALT,
|
|
OP_LOAD_I,
|
|
OP_LODB_I,
|
|
OP_CONST_PRI,
|
|
OP_CONST_ALT,
|
|
OP_ADDR_PRI,
|
|
OP_ADDR_ALT,
|
|
OP_STOR_PRI,
|
|
OP_STOR_ALT,
|
|
OP_STOR_S_PRI,
|
|
OP_STOR_S_ALT,
|
|
OP_SREF_PRI,
|
|
OP_SREF_ALT,
|
|
OP_SREF_S_PRI,
|
|
OP_SREF_S_ALT,
|
|
OP_STOR_I,
|
|
OP_STRB_I,
|
|
OP_LIDX,
|
|
OP_LIDX_B,
|
|
OP_IDXADDR,
|
|
OP_IDXADDR_B,
|
|
OP_ALIGN_PRI,
|
|
OP_ALIGN_ALT,
|
|
OP_LCTRL,
|
|
OP_SCTRL,
|
|
OP_MOVE_PRI,
|
|
OP_MOVE_ALT,
|
|
OP_XCHG,
|
|
OP_PUSH_PRI,
|
|
OP_PUSH_ALT,
|
|
OP_PUSH_R,
|
|
OP_PUSH_C,
|
|
OP_PUSH,
|
|
OP_PUSH_S,
|
|
OP_POP_PRI,
|
|
OP_POP_ALT,
|
|
OP_STACK,
|
|
OP_HEAP,
|
|
OP_PROC,
|
|
OP_RET,
|
|
OP_RETN,
|
|
OP_CALL,
|
|
OP_CALL_PRI,
|
|
OP_JUMP,
|
|
OP_JREL,
|
|
OP_JZER,
|
|
OP_JNZ,
|
|
OP_JEQ,
|
|
OP_JNEQ,
|
|
OP_JLESS,
|
|
OP_JLEQ,
|
|
OP_JGRTR,
|
|
OP_JGEQ,
|
|
OP_JSLESS,
|
|
OP_JSLEQ,
|
|
OP_JSGRTR,
|
|
OP_JSGEQ,
|
|
OP_SHL,
|
|
OP_SHR,
|
|
OP_SSHR,
|
|
OP_SHL_C_PRI,
|
|
OP_SHL_C_ALT,
|
|
OP_SHR_C_PRI,
|
|
OP_SHR_C_ALT,
|
|
OP_SMUL,
|
|
OP_SDIV,
|
|
OP_SDIV_ALT,
|
|
OP_UMUL,
|
|
OP_UDIV,
|
|
OP_UDIV_ALT,
|
|
OP_ADD,
|
|
OP_SUB,
|
|
OP_SUB_ALT,
|
|
OP_AND,
|
|
OP_OR,
|
|
OP_XOR,
|
|
OP_NOT,
|
|
OP_NEG,
|
|
OP_INVERT,
|
|
OP_ADD_C,
|
|
OP_SMUL_C,
|
|
OP_ZERO_PRI,
|
|
OP_ZERO_ALT,
|
|
OP_ZERO,
|
|
OP_ZERO_S,
|
|
OP_SIGN_PRI,
|
|
OP_SIGN_ALT,
|
|
OP_EQ,
|
|
OP_NEQ,
|
|
OP_LESS,
|
|
OP_LEQ,
|
|
OP_GRTR,
|
|
OP_GEQ,
|
|
OP_SLESS,
|
|
OP_SLEQ,
|
|
OP_SGRTR,
|
|
OP_SGEQ,
|
|
OP_EQ_C_PRI,
|
|
OP_EQ_C_ALT,
|
|
OP_INC_PRI,
|
|
OP_INC_ALT,
|
|
OP_INC,
|
|
OP_INC_S,
|
|
OP_INC_I,
|
|
OP_DEC_PRI,
|
|
OP_DEC_ALT,
|
|
OP_DEC,
|
|
OP_DEC_S,
|
|
OP_DEC_I,
|
|
OP_MOVS,
|
|
OP_CMPS,
|
|
OP_FILL,
|
|
OP_HALT,
|
|
OP_BOUNDS,
|
|
OP_SYSREQ_PRI,
|
|
OP_SYSREQ_C,
|
|
OP_FILE, /* obsolete */
|
|
OP_LINE, /* obsolete */
|
|
OP_SYMBOL, /* obsolete */
|
|
OP_SRANGE, /* obsolete */
|
|
OP_JUMP_PRI,
|
|
OP_SWITCH,
|
|
OP_CASETBL,
|
|
OP_SWAP_PRI,
|
|
OP_SWAP_ALT,
|
|
OP_PUSHADDR,
|
|
OP_NOP,
|
|
OP_SYSREQ_D,
|
|
OP_SYMTAG, /* obsolete */
|
|
OP_BREAK,
|
|
/* ----- */
|
|
OP_NUM_OPCODES
|
|
} OPCODE;
|
|
|
|
|
|
const char *GenericError(int err);
|
|
|
|
Debugger::Tracer::~Tracer()
|
|
{
|
|
Clear();
|
|
}
|
|
|
|
void Debugger::Tracer::StepI(cell frm, cell cip)
|
|
{
|
|
if (m_pEnd == NULL)
|
|
{
|
|
assert(m_Reset);
|
|
|
|
if (m_pStart == NULL)
|
|
m_pStart = new trace_info();
|
|
|
|
m_pEnd = m_pStart;
|
|
m_Reset = true;
|
|
m_pEnd->cip = cip;
|
|
m_pEnd->frm = frm;
|
|
m_pEnd->used = true;
|
|
} else {
|
|
if (m_pEnd->frm > frm)
|
|
{
|
|
//the last frame has moved down the stack.
|
|
//push a new call onto our list
|
|
if (m_pEnd->next)
|
|
{
|
|
m_pEnd = m_pEnd->next;
|
|
m_pEnd->used = true;
|
|
} else {
|
|
trace_info *pInfo = new trace_info();
|
|
m_pEnd->next = pInfo;
|
|
pInfo->prev = m_pEnd;
|
|
pInfo->used = true;
|
|
m_pEnd = pInfo;
|
|
}
|
|
//if we're pushing a new call, save the initial frame
|
|
m_pEnd->frm = frm;
|
|
} else if (m_pEnd->frm < frm) {
|
|
//the last frame has moved up the stack.
|
|
//pop a call from our list
|
|
m_pEnd->used = false;
|
|
m_pEnd = m_pEnd->prev;
|
|
}
|
|
//no matter where we are, save the current cip
|
|
m_pEnd->cip = cip;
|
|
}
|
|
}
|
|
|
|
void Debugger::Tracer::Clear()
|
|
{
|
|
trace_info *pInfo, *pNext;
|
|
|
|
pInfo = m_pStart;
|
|
while (pInfo)
|
|
{
|
|
pNext = pInfo->next;
|
|
delete pInfo;
|
|
pInfo = pNext;
|
|
}
|
|
|
|
m_pStart = NULL;
|
|
m_pEnd = NULL;
|
|
m_Error = AMX_ERR_NONE;
|
|
m_Reset = true;
|
|
}
|
|
|
|
void Debugger::Tracer::Reset()
|
|
{
|
|
trace_info *pInfo = m_pStart;
|
|
|
|
while (pInfo && pInfo->used)
|
|
{
|
|
pInfo->used = false;
|
|
pInfo = pInfo->next;
|
|
}
|
|
|
|
m_pEnd = NULL;
|
|
m_Error = AMX_ERR_NONE;
|
|
m_Reset = true;
|
|
}
|
|
|
|
trace_info_t *Debugger::Tracer::GetStart() const
|
|
{
|
|
return m_pStart;
|
|
}
|
|
|
|
trace_info_t *Debugger::Tracer::GetEnd() const
|
|
{
|
|
return m_pEnd;
|
|
}
|
|
|
|
void Debugger::BeginExec()
|
|
{
|
|
m_Top++;
|
|
assert(m_Top >= 0);
|
|
|
|
if (m_Top >= (int)m_pCalls.length())
|
|
{
|
|
Tracer *pTracer = new Tracer();
|
|
m_pCalls.append(pTracer);
|
|
assert(m_Top == static_cast<int>(m_pCalls.length() - 1));
|
|
}
|
|
|
|
m_pCalls[m_Top]->Reset();
|
|
}
|
|
|
|
void Debugger::EndExec()
|
|
{
|
|
assert(m_Top >= 0 && m_Top < (int)m_pCalls.length());
|
|
|
|
m_pCalls[m_Top]->Reset();
|
|
|
|
m_Top--;
|
|
}
|
|
|
|
void Debugger::StepI()
|
|
{
|
|
assert(m_Top >= 0 && m_Top < (int)m_pCalls.length());
|
|
|
|
#if defined BINLOG_ENABLED
|
|
if (g_binlog_level & 32)
|
|
{
|
|
CPluginMngr::CPlugin *pl = g_plugins.findPluginFast(m_pAmx);
|
|
if (pl)
|
|
{
|
|
long line;
|
|
dbg_LookupLine(m_pAmxDbg, m_pAmx->cip, &line);
|
|
g_BinLog.WriteOp(BinLog_SetLine, pl->getId(), (int)(line + 1));
|
|
}
|
|
}
|
|
#endif
|
|
|
|
m_pCalls[m_Top]->StepI(m_pAmx->frm, m_pAmx->cip);
|
|
}
|
|
|
|
void Debugger::Reset()
|
|
{
|
|
//no call state
|
|
m_Top = -1;
|
|
}
|
|
|
|
int Debugger::GetTracedError()
|
|
{
|
|
assert(m_Top >= 0 && m_Top < (int)m_pCalls.length());
|
|
|
|
return m_pCalls[m_Top]->m_Error;
|
|
}
|
|
|
|
void Debugger::SetTracedError(int error)
|
|
{
|
|
assert(m_Top >= 0 && m_Top < (int)m_pCalls.length());
|
|
|
|
m_pCalls[m_Top]->m_Error = error;
|
|
}
|
|
|
|
trace_info_t *Debugger::GetTraceStart() const
|
|
{
|
|
assert(m_Top >= 0 && m_Top < (int)m_pCalls.length());
|
|
|
|
return m_pCalls[m_Top]->GetEnd();
|
|
}
|
|
|
|
bool Debugger::GetTraceInfo(trace_info_t *pTraceInfo, long &line, const char *&function, const char *&file)
|
|
{
|
|
cell addr = pTraceInfo->cip;
|
|
|
|
dbg_LookupFunction(m_pAmxDbg, addr, &function);
|
|
dbg_LookupLine(m_pAmxDbg, addr, &line);
|
|
dbg_LookupFile(m_pAmxDbg, addr, &file);
|
|
|
|
return true;
|
|
}
|
|
|
|
trace_info_t *Debugger::GetNextTrace(trace_info_t *pTraceInfo)
|
|
{
|
|
if (!pTraceInfo->prev || !pTraceInfo->prev->used)
|
|
return NULL;
|
|
|
|
return pTraceInfo->prev;
|
|
}
|
|
|
|
bool Debugger::ErrorExists()
|
|
{
|
|
assert(m_Top >= 0 && m_Top < (int)m_pCalls.length());
|
|
|
|
return (m_pCalls[m_Top]->m_Error != AMX_ERR_NONE);
|
|
}
|
|
|
|
int Debugger::FormatError(char *buffer, size_t maxLength)
|
|
{
|
|
if (!ErrorExists())
|
|
return -1;
|
|
|
|
assert(m_Top >= 0 && m_Top < (int)m_pCalls.length());
|
|
|
|
Tracer *pTracer = m_pCalls[m_Top];
|
|
int error = pTracer->m_Error;
|
|
const char *gen_err = GenericError(error);
|
|
int size = 0;
|
|
//trace_info_t *pTrace = pTracer->GetEnd();
|
|
//cell cip = _CipAsVa(m_pAmx->cip);
|
|
//cell *p_cip = NULL;
|
|
//int amx_err = AMX_ERR_NONE;
|
|
|
|
size += ke::SafeSprintf(buffer, maxLength, "Run time error %d: %s ", error, gen_err);
|
|
buffer += size;
|
|
maxLength -= size;
|
|
|
|
if (error == AMX_ERR_NATIVE || error == AMX_ERR_INVNATIVE)
|
|
{
|
|
char native_name[sNAMEMAX+1];
|
|
int num = 0;
|
|
/*//go two instructions back
|
|
cip -= (sizeof(cell) * 2);
|
|
int instr = _GetOpcodeFromCip(cip, p_cip);
|
|
if (instr == OP_SYSREQ_C)
|
|
{
|
|
num = (int)*p_cip;
|
|
}*/
|
|
//New code only requires this...
|
|
num = (int)(_INT_PTR)m_pAmx->usertags[UT_NATIVE];
|
|
/*amx_err = */amx_GetNative(m_pAmx, num, native_name);
|
|
/*if (num)
|
|
amx_err = amx_GetNative(m_pAmx, (int)*p_cip, native_name);
|
|
else
|
|
amx_err = AMX_ERR_NOTFOUND;*/
|
|
//if (!amx_err)
|
|
size += ke::SafeSprintf(buffer, maxLength, "(native \"%s\")", native_name);
|
|
}
|
|
|
|
return size;
|
|
}
|
|
|
|
cell Debugger::_CipAsVa(cell cip)
|
|
{
|
|
AMX_HEADER *hdr = (AMX_HEADER*)(m_pAmx->base);
|
|
unsigned char *code = m_pAmx->base + (int)hdr->cod;
|
|
|
|
if (cip >= (cell)code && cip < (cell)(m_pAmx->base + (int)hdr->dat))
|
|
{
|
|
return (cell)(cip-(cell)code);
|
|
} else {
|
|
return (cell)(code + cip);
|
|
}
|
|
}
|
|
|
|
int Debugger::_GetOpcodeFromCip(cell cip, cell *&addr)
|
|
{
|
|
AMX_HEADER *hdr = (AMX_HEADER*)(m_pAmx->base);
|
|
unsigned char *code = m_pAmx->base + (int)hdr->cod;
|
|
|
|
cell *p_cip = NULL;
|
|
//test if cip is between these
|
|
if (cip >= (cell)code && cip < (cell)(m_pAmx->base + (int)hdr->dat))
|
|
{
|
|
p_cip = (cell *)(cip);
|
|
} else {
|
|
p_cip = (cell *)(code + cip);
|
|
}
|
|
|
|
//move forward one entry
|
|
addr = p_cip + 1;
|
|
|
|
//p_cip should be aligned to an instruction!
|
|
cell instr = *p_cip;
|
|
|
|
if (instr < 1 || instr >= OP_NUM_OPCODES)
|
|
{
|
|
if (!m_pOpcodeList)
|
|
return 0;
|
|
|
|
//we have an invalid opcode, so try searching for it
|
|
for (cell i=1; i<OP_NUM_OPCODES; i++)
|
|
{
|
|
if ((cell)m_pOpcodeList[i] == instr)
|
|
{
|
|
instr = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (instr < 1 || instr >= OP_NUM_OPCODES)
|
|
instr = 0; //nothing found
|
|
}
|
|
|
|
return (int)instr;
|
|
}
|
|
|
|
void Debugger::_CacheAmxOpcodeList()
|
|
{
|
|
m_pOpcodeList = (cell *)m_pAmx->userdata[UD_OPCODELIST];
|
|
}
|
|
|
|
//by BAILOPAN
|
|
// generic error printing routine
|
|
// for pawn 3.0 this is just a wrapper
|
|
const char *GenericError(int err)
|
|
{
|
|
static const char *amx_errs[] =
|
|
{
|
|
NULL,
|
|
"forced exit",
|
|
"assertion failed",
|
|
"stack error",
|
|
"index out of bounds",
|
|
"memory access",
|
|
"invalid instruction",
|
|
"stack low",
|
|
"heap low",
|
|
"callback",
|
|
"native error",
|
|
"divide",
|
|
"sleep",
|
|
"invalid access state",
|
|
"native not found",
|
|
NULL,
|
|
"out of memory", //16
|
|
"bad file format",
|
|
"bad file version",
|
|
"function not found",
|
|
"invalid entry point",
|
|
"debugger cannot run",
|
|
"plugin un or re-initialized",
|
|
"userdata table full",
|
|
"JIT failed to initialize",
|
|
"parameter error",
|
|
"domain error",
|
|
};
|
|
//does this plugin have line ops?
|
|
const char *geterr = NULL;
|
|
if (err <= 26 && err > 0)
|
|
geterr = amx_errs[err];
|
|
|
|
return geterr ? geterr : "unknown error";
|
|
}
|
|
|
|
int AMXAPI Debugger::DebugHook(AMX *amx)
|
|
{
|
|
Debugger *pDebugger = NULL;
|
|
|
|
if (!amx || !(amx->flags & AMX_FLAG_DEBUG))
|
|
return AMX_ERR_NONE;
|
|
|
|
if (amx->flags & AMX_FLAG_PRENIT)
|
|
return AMX_ERR_NONE;
|
|
|
|
pDebugger = (Debugger *)amx->userdata[UD_DEBUGGER];
|
|
|
|
if (!pDebugger)
|
|
return AMX_ERR_NONE;
|
|
|
|
pDebugger->StepI();
|
|
|
|
return AMX_ERR_NONE;
|
|
}
|
|
|
|
void Debugger::Clear()
|
|
{
|
|
for (size_t i = 0; i < m_pCalls.length(); i++)
|
|
{
|
|
delete m_pCalls[i];
|
|
}
|
|
|
|
m_pCalls.clear();
|
|
}
|
|
|
|
void Debugger::DisplayTrace(const char *message)
|
|
{
|
|
if (message != NULL)
|
|
AMXXLOG_Error("%s", message);
|
|
|
|
char buffer[512];
|
|
int length = FormatError(buffer, sizeof(buffer)-1);
|
|
|
|
const char *filename = _GetFilename();
|
|
const char *version = _GetVersion();
|
|
|
|
AMXXLOG_Error("[AMXX] Displaying debug trace (plugin \"%s\", version \"%s\")", filename, version);
|
|
|
|
if (length != -1) // Don't show blank line if AMX_ERR_NONE is set since there is no error message.
|
|
{
|
|
AMXXLOG_Error("[AMXX] %s", buffer);
|
|
}
|
|
|
|
int count = 0;
|
|
long lLine;
|
|
const char *file, *function;
|
|
trace_info_t *pTrace = GetTraceStart();
|
|
while (pTrace)
|
|
{
|
|
GetTraceInfo(pTrace, lLine, function, file);
|
|
AMXXLOG_Error(
|
|
"[AMXX] [%d] %s::%s (line %d)",
|
|
count,
|
|
file,
|
|
function,
|
|
(int)(lLine + 1)
|
|
);
|
|
count++;
|
|
pTrace = GetNextTrace(pTrace);
|
|
}
|
|
}
|
|
|
|
const char *Debugger::_GetFilename()
|
|
{
|
|
if (m_FileName.length() < 1)
|
|
{
|
|
const char *filename = "";
|
|
CPluginMngr::CPlugin *pl = g_plugins.findPluginFast(m_pAmx);
|
|
if (pl)
|
|
{
|
|
filename = pl->getName();
|
|
} else {
|
|
CList<CScript,AMX*>::iterator a = g_loadedscripts.find(m_pAmx);
|
|
if (a)
|
|
filename = (*a).getName();
|
|
}
|
|
m_FileName = filename;
|
|
}
|
|
|
|
return m_FileName.chars();
|
|
}
|
|
|
|
const char *Debugger::_GetVersion()
|
|
{
|
|
if (m_Version.length() < 1)
|
|
{
|
|
const char *version = "";
|
|
CPluginMngr::CPlugin *pl = g_plugins.findPluginFast(m_pAmx);
|
|
if (pl)
|
|
{
|
|
version = pl->getVersion();
|
|
}
|
|
|
|
m_Version = version;
|
|
}
|
|
|
|
return m_Version.chars();
|
|
}
|
|
|
|
void Debugger::FmtGenericMsg(AMX *amx, int error, char buffer[], size_t maxLength)
|
|
{
|
|
const char *filename = "";
|
|
char native[sNAMEMAX+1];
|
|
|
|
CList<CScript,AMX*>::iterator a = g_loadedscripts.find(amx);
|
|
if (a)
|
|
filename = (*a).getName();
|
|
size_t len = strlen(filename);
|
|
for (size_t i=len-1; i<len; i--)
|
|
{
|
|
if ((filename[i] == '/' || filename[i] == '\\') && i != len - 1)
|
|
{
|
|
filename = &(filename[i+1]);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (error == AMX_ERR_EXIT)
|
|
{
|
|
ke::SafeSprintf(buffer, maxLength, "Run time error %d (plugin \"%s\") - %s", error, filename, GenericError(AMX_ERR_EXIT));
|
|
} else if (error == AMX_ERR_NATIVE) {
|
|
amx_GetNative(amx, reinterpret_cast<long>(amx->usertags[UT_NATIVE]), native);
|
|
ke::SafeSprintf(buffer, maxLength, "Run time error %d (plugin \"%s\") (native \"%s\") - debug not enabled!", error, filename, native);
|
|
} else {
|
|
ke::SafeSprintf(buffer, maxLength, "Run time error %d (plugin \"%s\") - debug not enabled!", error, filename);
|
|
}
|
|
}
|
|
|
|
void Debugger::GenericMessage(AMX *amx, int err)
|
|
{
|
|
static char buffer[512];
|
|
|
|
buffer[0] = '\0';
|
|
Debugger::FmtGenericMsg(amx, err, buffer, sizeof(buffer)-1);
|
|
|
|
if (buffer[0] != '\0')
|
|
AMXXLOG_Error("[AMXX] %s", buffer);
|
|
}
|
|
|
|
Debugger::~Debugger()
|
|
{
|
|
Clear();
|
|
dbg_FreeInfo(m_pAmxDbg);
|
|
delete m_pAmxDbg;
|
|
}
|
|
|
|
int Handler::SetErrorHandler(const char *function)
|
|
{
|
|
int error;
|
|
|
|
error = amx_FindPublic(m_pAmx, function, &m_iErrFunc);
|
|
|
|
if (error != AMX_ERR_NONE && m_iErrFunc < 0)
|
|
m_iErrFunc = -1;
|
|
|
|
return error;
|
|
}
|
|
|
|
int Handler::SetModuleFilter(const char *function)
|
|
{
|
|
int error;
|
|
|
|
error = amx_FindPublic(m_pAmx, function, &m_iModFunc);
|
|
|
|
if (error != AMX_ERR_NONE && m_iModFunc < 0)
|
|
m_iModFunc = -1;
|
|
|
|
return error;
|
|
}
|
|
|
|
int Handler::SetNativeFilter(const char *function)
|
|
{
|
|
int error;
|
|
|
|
error = amx_FindPublic(m_pAmx, function, &m_iNatFunc);
|
|
|
|
if (error != AMX_ERR_NONE && !IsNativeFiltering())
|
|
m_iNatFunc = -1;
|
|
|
|
return error;
|
|
}
|
|
|
|
void Handler::SetErrorMsg(const char *msg)
|
|
{
|
|
if (!msg)
|
|
m_MsgCache = nullptr;
|
|
else
|
|
m_MsgCache = msg;
|
|
}
|
|
|
|
const char *Handler::GetLastMsg()
|
|
{
|
|
if (m_MsgCache.length() < 1)
|
|
return NULL;
|
|
|
|
return m_MsgCache.chars();
|
|
}
|
|
|
|
int Handler::HandleModule(const char *module, bool isClass)
|
|
{
|
|
if (m_iModFunc < 0)
|
|
return 0;
|
|
|
|
/**
|
|
* This is the most minimalistic handler of them all
|
|
*/
|
|
|
|
cell hea_addr, *phys_addr, retval;
|
|
Debugger *pd;
|
|
|
|
pd = DisableDebugHandler(m_pAmx);
|
|
|
|
//temporarily set prenit
|
|
m_pAmx->flags |= AMX_FLAG_PRENIT;
|
|
amx_Push(m_pAmx, isClass ? 1 : 0);
|
|
amx_PushString(m_pAmx, &hea_addr, &phys_addr, module, 0, 0);
|
|
int err = amx_Exec(m_pAmx, &retval, m_iModFunc);
|
|
amx_Release(m_pAmx, hea_addr);
|
|
m_pAmx->flags &= ~AMX_FLAG_PRENIT;
|
|
|
|
EnableDebugHandler(m_pAmx, pd);
|
|
|
|
if (err != AMX_ERR_NONE)
|
|
return 0;
|
|
|
|
return (int)retval;
|
|
}
|
|
|
|
int Handler::HandleNative(const char *native, int index, int trap)
|
|
{
|
|
if (!IsNativeFiltering())
|
|
return 0;
|
|
|
|
/**
|
|
* Our handler here is a bit different from HandleError().
|
|
* For one, there is no current error in pDebugger, so we
|
|
* don't have to save some states.
|
|
*/
|
|
|
|
m_InNativeFilter = true;
|
|
|
|
Debugger *pDebugger = (Debugger *)m_pAmx->userdata[UD_DEBUGGER];
|
|
|
|
if (pDebugger && trap)
|
|
pDebugger->BeginExec();
|
|
else if (pDebugger && !trap)
|
|
DisableDebugHandler(m_pAmx);
|
|
|
|
cell hea_addr, *phys_addr, retval;
|
|
|
|
if (!trap)
|
|
m_pAmx->flags |= AMX_FLAG_PRENIT;
|
|
|
|
amx_Push(m_pAmx, trap);
|
|
amx_Push(m_pAmx, index);
|
|
amx_PushString(m_pAmx, &hea_addr, &phys_addr, native, 0, 0);
|
|
int err = amx_Exec(m_pAmx, &retval, m_iNatFunc);
|
|
if (err != AMX_ERR_NONE)
|
|
{
|
|
//LogError() took care of something for us.
|
|
if (err == -1)
|
|
{
|
|
m_InNativeFilter = false;
|
|
amx_Release(m_pAmx, hea_addr);
|
|
return 1;
|
|
}
|
|
if (!trap)
|
|
{
|
|
AMXXLOG_Error("[AMXX] Runtime failure %d occurred in native filter. Aborting plugin load.", err);
|
|
return 0;
|
|
}
|
|
//handle this manually.
|
|
//we need to push this through an error filter, same as executeForwards!
|
|
if (pDebugger && pDebugger->ErrorExists())
|
|
{
|
|
//don't display, it was already handled.
|
|
} else if (err != -1) {
|
|
LogError(m_pAmx, err, NULL);
|
|
}
|
|
AMXXLOG_Error("[AMXX] NOTE: Runtime failures in native filters are not good!");
|
|
retval = 0;
|
|
}
|
|
if (!trap)
|
|
m_pAmx->flags &= ~AMX_FLAG_PRENIT;
|
|
if (pDebugger && trap)
|
|
pDebugger->EndExec();
|
|
else if (pDebugger && !trap)
|
|
EnableDebugHandler(m_pAmx, pDebugger);
|
|
|
|
amx_Release(m_pAmx, hea_addr);
|
|
|
|
m_InNativeFilter = false;
|
|
|
|
return (int)retval;
|
|
}
|
|
|
|
int Handler::HandleError(const char *msg)
|
|
{
|
|
if (m_iErrFunc <= 0)
|
|
return 0;
|
|
|
|
m_Handling = true;
|
|
m_pTrace = nullptr;
|
|
m_FmtCache = nullptr;
|
|
|
|
Debugger *pDebugger = (Debugger *)m_pAmx->userdata[UD_DEBUGGER];
|
|
|
|
int error = m_pAmx->error;
|
|
|
|
static char _buffer[512];
|
|
if (pDebugger)
|
|
{
|
|
pDebugger->SetTracedError(error);
|
|
m_pTrace = pDebugger->GetTraceStart();
|
|
pDebugger->FormatError(_buffer, sizeof(_buffer)-1);
|
|
m_FmtCache = _buffer;
|
|
pDebugger->BeginExec();
|
|
} else {
|
|
Debugger::FmtGenericMsg(m_pAmx, error, _buffer, sizeof(_buffer)-1);
|
|
m_FmtCache = _buffer;
|
|
}
|
|
|
|
SetErrorMsg(msg);
|
|
|
|
cell hea_addr, *phys_addr, result;
|
|
|
|
amx_PushString(m_pAmx, &hea_addr, &phys_addr, msg, 0, 0);
|
|
amx_Push(m_pAmx, pDebugger ? 1 : 0);
|
|
amx_Push(m_pAmx, error);
|
|
int err = amx_Exec(m_pAmx, &result, m_iErrFunc);
|
|
if (err != AMX_ERR_NONE)
|
|
{
|
|
//handle this manually.
|
|
if (pDebugger)
|
|
{
|
|
pDebugger->SetTracedError(err);
|
|
pDebugger->DisplayTrace(msg);
|
|
} else {
|
|
if (GetLastMsg())
|
|
AMXXLOG_Error("%s", GetLastMsg());
|
|
Debugger::GenericMessage(m_pAmx, err);
|
|
}
|
|
AMXXLOG_Error("[AMXX] NOTE: Runtime failures in an error filter are not good!");
|
|
}
|
|
|
|
if (pDebugger)
|
|
pDebugger->EndExec();
|
|
|
|
amx_Release(m_pAmx, hea_addr);
|
|
|
|
m_Handling = false;
|
|
m_pTrace = nullptr;
|
|
m_FmtCache = nullptr;
|
|
|
|
if (err != AMX_ERR_NONE || !result)
|
|
return 0;
|
|
|
|
return result;
|
|
}
|
|
|
|
static cell AMX_NATIVE_CALL set_error_filter(AMX *amx, cell *params)
|
|
{
|
|
int len;
|
|
char *function = get_amxstring(amx, params[1], 0, len);
|
|
|
|
Handler *pHandler = (Handler *)amx->userdata[UD_HANDLER];
|
|
|
|
if (!pHandler)
|
|
{
|
|
Debugger::GenericMessage(amx, AMX_ERR_NOTFOUND);
|
|
AMXXLOG_Error("[AMXX] Plugin not initialized correctly.");
|
|
return 0;
|
|
}
|
|
|
|
int err = pHandler->SetErrorHandler(function);
|
|
if (err != AMX_ERR_NONE)
|
|
{
|
|
Debugger::GenericMessage(amx, AMX_ERR_NOTFOUND);
|
|
AMXXLOG_Error("[AMXX] Function not found: %s", function);
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static cell AMX_NATIVE_CALL dbg_trace_begin(AMX *amx, cell *params)
|
|
{
|
|
Handler *pHandler = (Handler *)amx->userdata[UD_HANDLER];
|
|
|
|
if (!pHandler)
|
|
return 0; //should never happen
|
|
|
|
trace_info_t *pTrace = pHandler->GetTrace();
|
|
|
|
return (cell)(pTrace);
|
|
}
|
|
|
|
static cell AMX_NATIVE_CALL dbg_trace_next(AMX *amx, cell *params)
|
|
{
|
|
Debugger *pDebugger = (Debugger *)amx->userdata[UD_DEBUGGER];
|
|
|
|
if (!pDebugger)
|
|
return 0;
|
|
|
|
trace_info_t *pTrace = (trace_info_t *)(params[1]);
|
|
|
|
if (pTrace)
|
|
return (cell)(pDebugger->GetNextTrace(pTrace));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static cell AMX_NATIVE_CALL dbg_trace_info(AMX *amx, cell *params)
|
|
{
|
|
Debugger *pDebugger = (Debugger *)amx->userdata[UD_DEBUGGER];
|
|
|
|
if (!pDebugger)
|
|
return 0;
|
|
|
|
trace_info_t *pTrace = (trace_info_t *)(params[1]);
|
|
|
|
if (!pTrace)
|
|
return 0;
|
|
|
|
cell *line_addr = get_amxaddr(amx, params[2]);
|
|
long lLine=-1;
|
|
const char *function=NULL, *file=NULL;
|
|
|
|
pDebugger->GetTraceInfo(pTrace, lLine, function, file);
|
|
|
|
set_amxstring(amx, params[3], function ? function : "", params[4]);
|
|
set_amxstring(amx, params[5], file ? file : "", params[5]);
|
|
*line_addr = (cell)lLine + 1;
|
|
|
|
return 1;
|
|
}
|
|
|
|
static cell AMX_NATIVE_CALL dbg_fmt_error(AMX *amx, cell *params)
|
|
{
|
|
Handler *pHandler = (Handler *)amx->userdata[UD_HANDLER];
|
|
|
|
if (!pHandler)
|
|
return 0;
|
|
|
|
const char *str = pHandler->GetFmtCache();
|
|
|
|
set_amxstring(amx, params[1], str, params[2]);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static cell AMX_NATIVE_CALL set_native_filter(AMX *amx, cell *params)
|
|
{
|
|
Handler *pHandler = (Handler *)amx->userdata[UD_HANDLER];
|
|
|
|
if (!pHandler)
|
|
{
|
|
Debugger::GenericMessage(amx, AMX_ERR_NOTFOUND);
|
|
AMXXLOG_Error("[AMXX] Plugin not initialized correctly.");
|
|
return 0;
|
|
}
|
|
|
|
if (!pHandler->IsNativeFiltering())
|
|
{
|
|
//we can only initialize this during PRENIT
|
|
if (! (amx->flags & AMX_FLAG_PRENIT) )
|
|
return 0;
|
|
}
|
|
|
|
int len;
|
|
char *func = get_amxstring(amx, params[1], 0, len);
|
|
|
|
int err = pHandler->SetNativeFilter(func);
|
|
|
|
if (err != AMX_ERR_NONE)
|
|
{
|
|
Debugger::GenericMessage(amx, AMX_ERR_NOTFOUND);
|
|
AMXXLOG_Error("[AMXX] Function not found: %s", function);
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static cell AMX_NATIVE_CALL set_module_filter(AMX *amx, cell *params)
|
|
{
|
|
if ( !(amx->flags & AMX_FLAG_PRENIT) )
|
|
return -1;
|
|
|
|
Handler *pHandler = (Handler *)amx->userdata[UD_HANDLER];
|
|
|
|
if (!pHandler)
|
|
return -2;
|
|
|
|
int len;
|
|
char *function = get_amxstring(amx, params[1], 0, len);
|
|
|
|
return pHandler->SetModuleFilter(function);
|
|
}
|
|
|
|
|
|
AMX_NATIVE_INFO g_DebugNatives[] = {
|
|
{"set_error_filter", set_error_filter},
|
|
{"dbg_trace_begin", dbg_trace_begin},
|
|
{"dbg_trace_next", dbg_trace_next},
|
|
{"dbg_trace_info", dbg_trace_info},
|
|
{"dbg_fmt_error", dbg_fmt_error},
|
|
{"set_native_filter", set_native_filter},
|
|
{"set_module_filter", set_module_filter},
|
|
{NULL, NULL},
|
|
};
|