* Remove CQueue * Remove duplicated files * Move sh_tinyhash to public/ * Remove sm_queue Make NVault use of ke::Deque * Remove sh_string (unused) * Remove CList dependency from CTask * Remove CList dependency from CEvent * Remove CList dependency from modules' functions * Remove CList dependency ForceObject * Remove CList dependency Player Auth * Remove left CList dependencies * Fix msvc project files * Update AMTL * Use InlineList for CScript * Use InlineList for CModule * Use Vector for Player Auth
		
			
				
	
	
		
			1026 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			C++
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			1026 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)
 | 
						|
    {
 | 
						|
        CPluginMngr::CPlugin *pl = g_plugins.findPluginFast(m_pAmx);
 | 
						|
        if (pl)
 | 
						|
        {
 | 
						|
            m_FileName = pl->getName();
 | 
						|
        }
 | 
						|
        else
 | 
						|
        {
 | 
						|
            for (auto script : g_loadedscripts)
 | 
						|
            {
 | 
						|
                if (script->getAMX() == m_pAmx)
 | 
						|
                {
 | 
						|
                    m_FileName = script->getName();
 | 
						|
                    break;
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
    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];
 | 
						|
    for (auto script : g_loadedscripts)
 | 
						|
    {
 | 
						|
        if (script->getAMX() == amx)
 | 
						|
        {
 | 
						|
            filename = script->getName();
 | 
						|
            break;
 | 
						|
        }
 | 
						|
    }
 | 
						|
    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},
 | 
						|
};
 |