Committed new debugger with AMX fixes
This commit is contained in:
parent
7ce59966fc
commit
4738c92b8e
|
@ -30,8 +30,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "amxmodx.h"
|
#include "amxmodx.h"
|
||||||
|
#include "debugger.h"
|
||||||
void AMXAPI amxx_InvalidateTrace(AMX *amx);
|
|
||||||
|
|
||||||
CForward::CForward(const char *name, ForwardExecType et, int numParams, const ForwardParam *paramTypes)
|
CForward::CForward(const char *name, ForwardExecType et, int numParams, const ForwardParam *paramTypes)
|
||||||
{
|
{
|
||||||
|
@ -74,9 +73,9 @@ cell CForward::execute(cell *params, ForwardPreparedArray *preparedArrays)
|
||||||
{
|
{
|
||||||
// Get debug info
|
// Get debug info
|
||||||
AMX *amx = (*iter).pPlugin->getAMX();
|
AMX *amx = (*iter).pPlugin->getAMX();
|
||||||
AMX_DBGINFO *pInfo = (AMX_DBGINFO *)(amx->userdata[2]);
|
Debugger *pDebugger = (Debugger *)amx->userdata[UD_DEBUGGER];
|
||||||
if (pInfo)
|
if (pDebugger)
|
||||||
pInfo->error = AMX_ERR_NONE;
|
pDebugger->BeginExec();
|
||||||
// handle strings & arrays
|
// handle strings & arrays
|
||||||
int i, ax=0;
|
int i, ax=0;
|
||||||
for (i = 0; i < m_NumParams; ++i)
|
for (i = 0; i < m_NumParams; ++i)
|
||||||
|
@ -124,16 +123,17 @@ cell CForward::execute(cell *params, ForwardPreparedArray *preparedArrays)
|
||||||
if (err != AMX_ERR_NONE)
|
if (err != AMX_ERR_NONE)
|
||||||
{
|
{
|
||||||
//Did something else set an error?
|
//Did something else set an error?
|
||||||
if (pInfo && pInfo->error != AMX_ERR_NONE)
|
if (pDebugger && pDebugger->ErrorExists())
|
||||||
{
|
{
|
||||||
//we don't care, something else logged the error.
|
//we don't care, something else logged the error.
|
||||||
} else {
|
} else if (err != -1) {
|
||||||
//nothing logged the error so spit it out anyway
|
//nothing logged the error so spit it out anyway
|
||||||
LogError(amx, err, "");
|
LogError(amx, err, NULL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
amxx_InvalidateTrace(amx);
|
|
||||||
amx->error = AMX_ERR_NONE;
|
amx->error = AMX_ERR_NONE;
|
||||||
|
if (pDebugger)
|
||||||
|
pDebugger->EndExec();
|
||||||
|
|
||||||
// cleanup strings & arrays
|
// cleanup strings & arrays
|
||||||
for (i = 0; i < m_NumParams; ++i)
|
for (i = 0; i < m_NumParams; ++i)
|
||||||
|
@ -229,9 +229,9 @@ cell CSPForward::execute(cell *params, ForwardPreparedArray *preparedArrays)
|
||||||
if (!pPlugin->isExecutable(m_Func))
|
if (!pPlugin->isExecutable(m_Func))
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
AMX_DBGINFO *pInfo = (AMX_DBGINFO *)(m_Amx->userdata[2]);
|
Debugger *pDebugger = (Debugger *)m_Amx->userdata[UD_DEBUGGER];
|
||||||
if (pInfo)
|
if (pDebugger)
|
||||||
pInfo->error = AMX_ERR_NONE;
|
pDebugger->BeginExec();
|
||||||
|
|
||||||
// handle strings & arrays
|
// handle strings & arrays
|
||||||
int i;
|
int i;
|
||||||
|
@ -276,15 +276,16 @@ cell CSPForward::execute(cell *params, ForwardPreparedArray *preparedArrays)
|
||||||
if (err != AMX_ERR_NONE)
|
if (err != AMX_ERR_NONE)
|
||||||
{
|
{
|
||||||
//Did something else set an error?
|
//Did something else set an error?
|
||||||
if (pInfo && pInfo->error != AMX_ERR_NONE)
|
if (pDebugger && pDebugger->ErrorExists())
|
||||||
{
|
{
|
||||||
//we don't care, something else logged the error.
|
//we don't care, something else logged the error.
|
||||||
} else {
|
} else if (err != -1) {
|
||||||
//nothing logged the error so spit it out anyway
|
//nothing logged the error so spit it out anyway
|
||||||
LogError(m_Amx, err, "");
|
LogError(m_Amx, err, NULL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
amxx_InvalidateTrace(m_Amx);
|
if (pDebugger)
|
||||||
|
pDebugger->EndExec();
|
||||||
m_Amx->error = AMX_ERR_NONE;
|
m_Amx->error = AMX_ERR_NONE;
|
||||||
|
|
||||||
// cleanup strings & arrays
|
// cleanup strings & arrays
|
||||||
|
|
|
@ -134,7 +134,7 @@ void CPluginMngr::clear() {
|
||||||
|
|
||||||
CPluginMngr::CPlugin* CPluginMngr::findPluginFast(AMX *amx)
|
CPluginMngr::CPlugin* CPluginMngr::findPluginFast(AMX *amx)
|
||||||
{
|
{
|
||||||
return (CPlugin*)(amx->userdata[3]);
|
return (CPlugin*)(amx->userdata[UD_FINDPLUGIN]);
|
||||||
}
|
}
|
||||||
|
|
||||||
CPluginMngr::CPlugin* CPluginMngr::findPlugin(AMX *amx) {
|
CPluginMngr::CPlugin* CPluginMngr::findPlugin(AMX *amx) {
|
||||||
|
@ -197,7 +197,7 @@ CPluginMngr::CPlugin::CPlugin(int i, const char* p,const char* n, char* e, int d
|
||||||
} else {
|
} else {
|
||||||
status = ps_bad_load;
|
status = ps_bad_load;
|
||||||
}
|
}
|
||||||
amx.userdata[3] = this;
|
amx.userdata[UD_FINDPLUGIN] = this;
|
||||||
paused_fun = 0;
|
paused_fun = 0;
|
||||||
next = 0;
|
next = 0;
|
||||||
id = i;
|
id = i;
|
||||||
|
|
Binary file not shown.
Binary file not shown.
|
@ -450,28 +450,34 @@ int AMXAPI amx_Callback(AMX *amx, cell index, cell *result, cell *params)
|
||||||
* This trick cannot work in the JIT, because the program would need to
|
* This trick cannot work in the JIT, because the program would need to
|
||||||
* be re-JIT-compiled after patching a P-code instruction.
|
* be re-JIT-compiled after patching a P-code instruction.
|
||||||
*/
|
*/
|
||||||
#if defined JIT && !defined NDEBUG
|
#if !defined JIT
|
||||||
if ((amx->flags & AMX_FLAG_JITC)!=0)
|
if (amx->sysreq_d != 0)
|
||||||
assert(amx->sysreq_d==0);
|
{
|
||||||
#endif
|
//if we're about to patch this, and we're debugging, don't patch!
|
||||||
if (amx->sysreq_d!=0) {
|
//otherwise we won't be able to get back native names
|
||||||
/* at the point of the call, the CIP pseudo-register points directly
|
if (amx->flags & AMX_FLAG_DEBUG)
|
||||||
* behind the SYSREQ instruction and its parameter.
|
{
|
||||||
*/
|
amx->sysreq_d = 0;
|
||||||
unsigned char *code=amx->base+(int)hdr->cod+(int)amx->cip-4;
|
} else {
|
||||||
assert(amx->cip >= 4 && amx->cip < (hdr->dat - hdr->cod));
|
/* at the point of the call, the CIP pseudo-register points directly
|
||||||
assert(sizeof(f)<=sizeof(cell)); /* function pointer must fit in a cell */
|
* behind the SYSREQ instruction and its parameter.
|
||||||
|
*/
|
||||||
|
unsigned char *code=amx->base+(int)hdr->cod+(int)amx->cip-4;
|
||||||
|
assert(amx->cip >= 4 && amx->cip < (hdr->dat - hdr->cod));
|
||||||
|
assert(sizeof(f)<=sizeof(cell)); /* function pointer must fit in a cell */
|
||||||
#if defined __GNUC__ || defined ASM32
|
#if defined __GNUC__ || defined ASM32
|
||||||
if (*(cell*)code==index) {
|
if (*(cell*)code==index) {
|
||||||
#else
|
#else
|
||||||
if (*(cell*)code!=OP_SYSREQ_PRI) {
|
if (*(cell*)code!=OP_SYSREQ_PRI) {
|
||||||
assert(*(cell*)(code-sizeof(cell))==OP_SYSREQ_C);
|
assert(*(cell*)(code-sizeof(cell))==OP_SYSREQ_C);
|
||||||
assert(*(cell*)code==index);
|
assert(*(cell*)code==index);
|
||||||
#endif
|
#endif //defined __GNU__ || defined ASM32
|
||||||
*(cell*)(code-sizeof(cell))=amx->sysreq_d;
|
*(cell*)(code-sizeof(cell))=amx->sysreq_d;
|
||||||
*(cell*)code=(cell)f;
|
*(cell*)code=(cell)f;
|
||||||
|
} /* if */
|
||||||
} /* if */
|
} /* if */
|
||||||
} /* if */
|
} /* if */
|
||||||
|
#endif //!defined JIT
|
||||||
|
|
||||||
/* Note:
|
/* Note:
|
||||||
* params[0] == number of bytes for the additional parameters passed to the native function
|
* params[0] == number of bytes for the additional parameters passed to the native function
|
||||||
|
@ -547,6 +553,7 @@ static int amx_BrowseRelocate(AMX *amx)
|
||||||
*/
|
*/
|
||||||
if ((amx->flags & AMX_FLAG_JITC)==0 && sizeof(AMX_NATIVE)<=sizeof(cell))
|
if ((amx->flags & AMX_FLAG_JITC)==0 && sizeof(AMX_NATIVE)<=sizeof(cell))
|
||||||
amx->sysreq_d=opcode_list[OP_SYSREQ_D];
|
amx->sysreq_d=opcode_list[OP_SYSREQ_D];
|
||||||
|
amx->userdata[UD_OPCODELIST] = (void *)opcode_list;
|
||||||
#else
|
#else
|
||||||
/* ANSI C
|
/* ANSI C
|
||||||
* to use direct system requests, a function pointer must fit in a cell;
|
* to use direct system requests, a function pointer must fit in a cell;
|
||||||
|
@ -554,6 +561,7 @@ static int amx_BrowseRelocate(AMX *amx)
|
||||||
*/
|
*/
|
||||||
if (sizeof(AMX_NATIVE)<=sizeof(cell))
|
if (sizeof(AMX_NATIVE)<=sizeof(cell))
|
||||||
amx->sysreq_d=OP_SYSREQ_D;
|
amx->sysreq_d=OP_SYSREQ_D;
|
||||||
|
amx->userdata[UD_OPCODELIST] = (long)NULL;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* start browsing code */
|
/* start browsing code */
|
||||||
|
@ -1676,7 +1684,13 @@ int AMXAPI amx_PushString(AMX *amx, cell *amx_addr, cell **phys_addr, const char
|
||||||
#define SKIPPARAM(n) ( cip=(cell *)cip+(n) )
|
#define SKIPPARAM(n) ( cip=(cell *)cip+(n) )
|
||||||
#define PUSH(v) ( stk-=sizeof(cell), *(cell *)(data+(int)stk)=v )
|
#define PUSH(v) ( stk-=sizeof(cell), *(cell *)(data+(int)stk)=v )
|
||||||
#define POP(v) ( v=*(cell *)(data+(int)stk), stk+=sizeof(cell) )
|
#define POP(v) ( v=*(cell *)(data+(int)stk), stk+=sizeof(cell) )
|
||||||
#define ABORT(amx,v) { (amx)->stk=reset_stk; (amx)->hea=reset_hea; return v; }
|
#define ABORT(amx,v) { (amx)->stk=reset_stk; \
|
||||||
|
(amx)->hea=reset_hea; \
|
||||||
|
(amx)->cip=(cell)cip; \
|
||||||
|
(amx)->pri=pri; \
|
||||||
|
(amx)->alt=alt; \
|
||||||
|
return v; \
|
||||||
|
}
|
||||||
|
|
||||||
#define CHKMARGIN() if (hea+STKMARGIN>stk) return AMX_ERR_STACKERR
|
#define CHKMARGIN() if (hea+STKMARGIN>stk) return AMX_ERR_STACKERR
|
||||||
#define CHKSTACK() if (stk>amx->stp) return AMX_ERR_STACKLOW
|
#define CHKSTACK() if (stk>amx->stp) return AMX_ERR_STACKLOW
|
||||||
|
@ -2471,6 +2485,8 @@ static const void * const amx_opcodelist[] = {
|
||||||
amx->hea=hea;
|
amx->hea=hea;
|
||||||
amx->frm=frm;
|
amx->frm=frm;
|
||||||
amx->stk=stk;
|
amx->stk=stk;
|
||||||
|
amx->pri=pri;
|
||||||
|
amx->alt=alt;
|
||||||
num=amx->callback(amx,pri,&pri,(cell *)(data+(int)stk));
|
num=amx->callback(amx,pri,&pri,(cell *)(data+(int)stk));
|
||||||
if (num!=AMX_ERR_NONE) {
|
if (num!=AMX_ERR_NONE) {
|
||||||
if (num==AMX_ERR_SLEEP) {
|
if (num==AMX_ERR_SLEEP) {
|
||||||
|
@ -2490,6 +2506,8 @@ static const void * const amx_opcodelist[] = {
|
||||||
amx->hea=hea;
|
amx->hea=hea;
|
||||||
amx->frm=frm;
|
amx->frm=frm;
|
||||||
amx->stk=stk;
|
amx->stk=stk;
|
||||||
|
amx->pri=pri;
|
||||||
|
amx->alt=alt;
|
||||||
num=amx->callback(amx,offs,&pri,(cell *)(data+(int)stk));
|
num=amx->callback(amx,offs,&pri,(cell *)(data+(int)stk));
|
||||||
if (num!=AMX_ERR_NONE) {
|
if (num!=AMX_ERR_NONE) {
|
||||||
if (num==AMX_ERR_SLEEP) {
|
if (num==AMX_ERR_SLEEP) {
|
||||||
|
@ -2509,6 +2527,8 @@ static const void * const amx_opcodelist[] = {
|
||||||
amx->hea=hea;
|
amx->hea=hea;
|
||||||
amx->frm=frm;
|
amx->frm=frm;
|
||||||
amx->stk=stk;
|
amx->stk=stk;
|
||||||
|
amx->pri=pri;
|
||||||
|
amx->alt=alt;
|
||||||
pri=((AMX_NATIVE)offs)(amx,(cell *)(data+(int)stk));
|
pri=((AMX_NATIVE)offs)(amx,(cell *)(data+(int)stk));
|
||||||
if (amx->error!=AMX_ERR_NONE) {
|
if (amx->error!=AMX_ERR_NONE) {
|
||||||
if (amx->error==AMX_ERR_SLEEP) {
|
if (amx->error==AMX_ERR_SLEEP) {
|
||||||
|
@ -2578,6 +2598,8 @@ static const void * const amx_opcodelist[] = {
|
||||||
amx->frm=frm;
|
amx->frm=frm;
|
||||||
amx->stk=stk;
|
amx->stk=stk;
|
||||||
amx->hea=hea;
|
amx->hea=hea;
|
||||||
|
amx->pri=pri;
|
||||||
|
amx->alt=alt;
|
||||||
amx->cip=(cell)((unsigned char*)cip-code);
|
amx->cip=(cell)((unsigned char*)cip-code);
|
||||||
num=amx->debug(amx);
|
num=amx->debug(amx);
|
||||||
if (num!=AMX_ERR_NONE) {
|
if (num!=AMX_ERR_NONE) {
|
||||||
|
@ -3445,6 +3467,8 @@ int AMXAPI amx_Exec(AMX *amx, cell *retval, int index)
|
||||||
amx->hea=hea;
|
amx->hea=hea;
|
||||||
amx->frm=frm;
|
amx->frm=frm;
|
||||||
amx->stk=stk;
|
amx->stk=stk;
|
||||||
|
amx->pri=pri;
|
||||||
|
amx->alt=alt;
|
||||||
num=amx->callback(amx,pri,&pri,(cell *)(data+(int)stk));
|
num=amx->callback(amx,pri,&pri,(cell *)(data+(int)stk));
|
||||||
if (num!=AMX_ERR_NONE) {
|
if (num!=AMX_ERR_NONE) {
|
||||||
if (num==AMX_ERR_SLEEP) {
|
if (num==AMX_ERR_SLEEP) {
|
||||||
|
@ -3464,6 +3488,8 @@ int AMXAPI amx_Exec(AMX *amx, cell *retval, int index)
|
||||||
amx->hea=hea;
|
amx->hea=hea;
|
||||||
amx->frm=frm;
|
amx->frm=frm;
|
||||||
amx->stk=stk;
|
amx->stk=stk;
|
||||||
|
amx->pri=pri;
|
||||||
|
amx->alt=alt;
|
||||||
num=amx->callback(amx,offs,&pri,(cell *)(data+(int)stk));
|
num=amx->callback(amx,offs,&pri,(cell *)(data+(int)stk));
|
||||||
if (num!=AMX_ERR_NONE) {
|
if (num!=AMX_ERR_NONE) {
|
||||||
if (num==AMX_ERR_SLEEP) {
|
if (num==AMX_ERR_SLEEP) {
|
||||||
|
@ -3483,6 +3509,8 @@ int AMXAPI amx_Exec(AMX *amx, cell *retval, int index)
|
||||||
amx->hea=hea;
|
amx->hea=hea;
|
||||||
amx->frm=frm;
|
amx->frm=frm;
|
||||||
amx->stk=stk;
|
amx->stk=stk;
|
||||||
|
amx->pri=pri;
|
||||||
|
amx->alt=alt;
|
||||||
pri=((AMX_NATIVE)offs)(amx,(cell *)(data+(int)stk));
|
pri=((AMX_NATIVE)offs)(amx,(cell *)(data+(int)stk));
|
||||||
if (amx->error!=AMX_ERR_NONE) {
|
if (amx->error!=AMX_ERR_NONE) {
|
||||||
if (amx->error==AMX_ERR_SLEEP) {
|
if (amx->error==AMX_ERR_SLEEP) {
|
||||||
|
@ -3546,6 +3574,8 @@ int AMXAPI amx_Exec(AMX *amx, cell *retval, int index)
|
||||||
amx->frm=frm;
|
amx->frm=frm;
|
||||||
amx->stk=stk;
|
amx->stk=stk;
|
||||||
amx->hea=hea;
|
amx->hea=hea;
|
||||||
|
amx->pri=pri;
|
||||||
|
amx->alt=alt;
|
||||||
amx->cip=(cell)((unsigned char*)cip-code);
|
amx->cip=(cell)((unsigned char*)cip-code);
|
||||||
num=amx->debug(amx);
|
num=amx->debug(amx);
|
||||||
if (num!=AMX_ERR_NONE) {
|
if (num!=AMX_ERR_NONE) {
|
||||||
|
|
|
@ -242,6 +242,7 @@ typedef struct tagAMX {
|
||||||
long usertags[AMX_USERNUM] PACKED;
|
long usertags[AMX_USERNUM] PACKED;
|
||||||
//okay userdata[3] in AMX Mod X is for the CPlugin * pointer
|
//okay userdata[3] in AMX Mod X is for the CPlugin * pointer
|
||||||
//we're also gonna set userdata[2] to a special debug structure
|
//we're also gonna set userdata[2] to a special debug structure
|
||||||
|
//lastly, userdata[1] is for opcode_list from amx_BrowseRelocate
|
||||||
void _FAR *userdata[AMX_USERNUM] PACKED;
|
void _FAR *userdata[AMX_USERNUM] PACKED;
|
||||||
/* native functions can raise an error */
|
/* native functions can raise an error */
|
||||||
int error PACKED;
|
int error PACKED;
|
||||||
|
@ -335,23 +336,9 @@ enum {
|
||||||
#define AMX_COMPACTMARGIN 64
|
#define AMX_COMPACTMARGIN 64
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
struct amx_trace
|
#define UD_FINDPLUGIN 3
|
||||||
{
|
#define UD_DEBUGGER 2
|
||||||
cell frm;
|
#define UD_OPCODELIST 1
|
||||||
amx_trace *prev;
|
|
||||||
amx_trace *next;
|
|
||||||
bool used;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct AMX_DBGINFO
|
|
||||||
{
|
|
||||||
void *pDebug; //Pointer to debug data
|
|
||||||
int error; //non-amx_Exec() error setting
|
|
||||||
amx_trace *pTrace; //Pointer to stack trace
|
|
||||||
amx_trace *pTraceFrm;
|
|
||||||
amx_trace *pTraceEnd;
|
|
||||||
cell frm;
|
|
||||||
};
|
|
||||||
|
|
||||||
/* for native functions that use floating point parameters, the following
|
/* for native functions that use floating point parameters, the following
|
||||||
* two macros are convenient for casting a "cell" into a "float" type _without_
|
* two macros are convenient for casting a "cell" into a "float" type _without_
|
||||||
|
|
|
@ -129,6 +129,28 @@
|
||||||
jg near err_stack
|
jg near err_stack
|
||||||
%endmacro
|
%endmacro
|
||||||
|
|
||||||
|
;Normal abort, saves pri/alt
|
||||||
|
%macro _ABORT 1
|
||||||
|
mov ebp,amx
|
||||||
|
mov [ebp+_pri], dword eax ; store values in AMX structure (PRI, ALT)
|
||||||
|
mov [ebp+_alt], dword edx ; store values in AMX structure (PRI, ALT)
|
||||||
|
mov [ebp+_error], dword %1
|
||||||
|
jmp _return
|
||||||
|
%endmacro
|
||||||
|
|
||||||
|
;Checked abort, saves nothing and uses a conditional
|
||||||
|
%macro _CHKABORT 1
|
||||||
|
mov ebp,amx
|
||||||
|
mov [ebp+_error], %1
|
||||||
|
cmp %1, AMX_ERR_NONE
|
||||||
|
jne _return
|
||||||
|
%endmacro
|
||||||
|
|
||||||
|
;Fast abort, only aborts, nothing else
|
||||||
|
%macro _FASTABORT 0
|
||||||
|
jmp _return
|
||||||
|
%endmacro
|
||||||
|
|
||||||
%macro _CHKHEAP 0
|
%macro _CHKHEAP 0
|
||||||
mov ebp,amx
|
mov ebp,amx
|
||||||
mov ebp,[ebp+_hlw]
|
mov ebp,[ebp+_hlw]
|
||||||
|
@ -1196,8 +1218,7 @@ OP_HALT:
|
||||||
mov eax,esi ; EAX=CIP
|
mov eax,esi ; EAX=CIP
|
||||||
sub eax,code
|
sub eax,code
|
||||||
mov [ebp+_cip],eax
|
mov [ebp+_cip],eax
|
||||||
mov eax,ebx ; return the parameter of the HALT opcode
|
_ABORT ebx
|
||||||
jmp _return
|
|
||||||
|
|
||||||
|
|
||||||
OP_BOUNDS:
|
OP_BOUNDS:
|
||||||
|
@ -1221,6 +1242,7 @@ OP_SYSREQ_PRI:
|
||||||
mov alt,edx ; save ALT
|
mov alt,edx ; save ALT
|
||||||
|
|
||||||
mov [ebp+_stk],ecx ; store values in AMX structure (STK, HEA, FRM)
|
mov [ebp+_stk],ecx ; store values in AMX structure (STK, HEA, FRM)
|
||||||
|
;we don't save regs since they're useless after this
|
||||||
mov ecx,hea
|
mov ecx,hea
|
||||||
mov ebx,frm
|
mov ebx,frm
|
||||||
mov [ebp+_hea],ecx
|
mov [ebp+_hea],ecx
|
||||||
|
@ -1251,8 +1273,7 @@ OP_SYSREQ_PRI:
|
||||||
pop edi ; restore saved registers
|
pop edi ; restore saved registers
|
||||||
pop esi
|
pop esi
|
||||||
pop ebp
|
pop ebp
|
||||||
cmp eax,AMX_ERR_NONE
|
_CHKABORT eax ; if result was invalid, leave
|
||||||
jne near _return ; return error code, if any
|
|
||||||
|
|
||||||
mov eax,pri ; get retval into eax (PRI)
|
mov eax,pri ; get retval into eax (PRI)
|
||||||
mov edx,alt ; restore ALT
|
mov edx,alt ; restore ALT
|
||||||
|
@ -1293,8 +1314,8 @@ OP_SYSREQ_D: ; (TR)
|
||||||
pop edi ; restore saved registers
|
pop edi ; restore saved registers
|
||||||
pop esi
|
pop esi
|
||||||
pop ebp
|
pop ebp
|
||||||
cmp DWORD [ebp+_error],AMX_ERR_NONE
|
mov eax,[ebp+_error]
|
||||||
jne near _return ; return error code, if any
|
_CHKABORT eax
|
||||||
|
|
||||||
; function result is in eax (PRI)
|
; function result is in eax (PRI)
|
||||||
mov edx,alt ; restore ALT
|
mov edx,alt ; restore ALT
|
||||||
|
@ -1416,7 +1437,7 @@ OP_BREAK:
|
||||||
mov [ebp+_error],eax ; save EAX (error code) before restoring all regs
|
mov [ebp+_error],eax ; save EAX (error code) before restoring all regs
|
||||||
_RESTOREREGS ; abort run, but restore stack first
|
_RESTOREREGS ; abort run, but restore stack first
|
||||||
mov eax,[ebp+_error] ; get error code in EAX back again
|
mov eax,[ebp+_error] ; get error code in EAX back again
|
||||||
jmp _return ; return error code
|
_FASTABORT
|
||||||
break_noabort:
|
break_noabort:
|
||||||
_RESTOREREGS
|
_RESTOREREGS
|
||||||
mov eax,[ebp+_pri] ; restore PRI and ALT
|
mov eax,[ebp+_pri] ; restore PRI and ALT
|
||||||
|
@ -1425,43 +1446,34 @@ OP_BREAK:
|
||||||
|
|
||||||
|
|
||||||
OP_INVALID:
|
OP_INVALID:
|
||||||
mov eax,AMX_ERR_INVINSTR
|
_ABORT AMX_ERR_INVINSTR
|
||||||
jmp _return
|
|
||||||
|
|
||||||
err_call:
|
err_call:
|
||||||
mov eax,AMX_ERR_CALLBACK
|
_ABORT AMX_ERR_CALLBACK
|
||||||
jmp _return
|
|
||||||
|
|
||||||
err_stack:
|
err_stack:
|
||||||
mov eax,AMX_ERR_STACKERR
|
_ABORT AMX_ERR_STACKERR
|
||||||
jmp _return
|
|
||||||
|
|
||||||
err_stacklow:
|
err_stacklow:
|
||||||
mov eax,AMX_ERR_STACKLOW
|
_ABORT AMX_ERR_STACKLOW
|
||||||
jmp _return
|
|
||||||
|
|
||||||
err_memaccess:
|
err_memaccess:
|
||||||
mov eax,AMX_ERR_MEMACCESS
|
_ABORT AMX_ERR_MEMACCESS
|
||||||
jmp _return
|
|
||||||
|
|
||||||
err_bounds:
|
err_bounds:
|
||||||
mov eax,AMX_ERR_BOUNDS
|
_ABORT AMX_ERR_BOUNDS
|
||||||
jmp _return
|
|
||||||
|
|
||||||
err_heaplow:
|
err_heaplow:
|
||||||
mov eax,AMX_ERR_HEAPLOW
|
_ABORT AMX_ERR_HEAPLOW
|
||||||
jmp _return
|
|
||||||
|
|
||||||
err_divide:
|
err_divide:
|
||||||
mov eax,AMX_ERR_DIVIDE
|
_ABORT AMX_ERR_DIVIDE
|
||||||
jmp _return
|
|
||||||
|
|
||||||
|
|
||||||
_return:
|
_return:
|
||||||
; save a few parameters, mostly for the "sleep"function
|
; save a few parameters, mostly for the "sleep"function
|
||||||
mov ebp,amx ; get amx into ebp
|
mov ebp,amx ; get amx into ebp
|
||||||
mov [ebp+_pri],eax ; store values in AMX structure (PRI, ALT)
|
mov [ebp+_cip],esi ; get corrected cip for amxmodx
|
||||||
mov [ebp+_alt],edx ; store values in AMX structure (PRI, ALT)
|
mov eax,[ebp+_error]; get error code
|
||||||
|
|
||||||
pop esi ; remove FRM from stack
|
pop esi ; remove FRM from stack
|
||||||
|
|
||||||
|
|
3104
amxmodx/amxmodx.cpp
3104
amxmodx/amxmodx.cpp
File diff suppressed because it is too large
Load Diff
|
@ -80,7 +80,6 @@ extern AMX_NATIVE_INFO float_Natives[];
|
||||||
extern AMX_NATIVE_INFO string_Natives[];
|
extern AMX_NATIVE_INFO string_Natives[];
|
||||||
extern AMX_NATIVE_INFO vault_Natives[];
|
extern AMX_NATIVE_INFO vault_Natives[];
|
||||||
|
|
||||||
|
|
||||||
#ifndef __linux__
|
#ifndef __linux__
|
||||||
#define DLLOAD(path) (DLHANDLE)LoadLibrary(path)
|
#define DLLOAD(path) (DLHANDLE)LoadLibrary(path)
|
||||||
#define DLPROC(m,func) GetProcAddress(m,func)
|
#define DLPROC(m,func) GetProcAddress(m,func)
|
||||||
|
@ -133,7 +132,6 @@ struct WeaponsVault {
|
||||||
struct fakecmd_t {
|
struct fakecmd_t {
|
||||||
char args[256];
|
char args[256];
|
||||||
const char *argv[3];
|
const char *argv[3];
|
||||||
//char argv[3][128];
|
|
||||||
int argc;
|
int argc;
|
||||||
bool fake;
|
bool fake;
|
||||||
};
|
};
|
||||||
|
|
784
amxmodx/debugger.cpp
Executable file
784
amxmodx/debugger.cpp
Executable file
|
@ -0,0 +1,784 @@
|
||||||
|
/* AMX Mod X
|
||||||
|
*
|
||||||
|
* by the AMX Mod X Development Team
|
||||||
|
* originally developed by OLO
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License as published by the
|
||||||
|
* Free Software Foundation; either version 2 of the License, or (at
|
||||||
|
* your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful, but
|
||||||
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
* General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
*
|
||||||
|
* In addition, as a special exception, the author gives permission to
|
||||||
|
* link the code of this program with the Half-Life Game Engine ("HL
|
||||||
|
* Engine") and Modified Game Libraries ("MODs") developed by Valve,
|
||||||
|
* L.L.C ("Valve"). You must obey the GNU General Public License in all
|
||||||
|
* respects for all of the code used other than the HL Engine and MODs
|
||||||
|
* from Valve. If you modify this file, you may extend this exception
|
||||||
|
* to your version of the file, but you are not obligated to do so. If
|
||||||
|
* you do not wish to do so, delete this exception statement from your
|
||||||
|
* version.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "amxmodx.h"
|
||||||
|
#include "debugger.h"
|
||||||
|
|
||||||
|
#if !defined WIN32 && !defined _WIN32
|
||||||
|
#define _snprintf sprintf
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.size())
|
||||||
|
{
|
||||||
|
Tracer *pTracer = new Tracer();
|
||||||
|
m_pCalls.push_back(pTracer);
|
||||||
|
assert(m_Top == (m_pCalls.size() - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
m_pCalls[m_Top]->Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Debugger::EndExec()
|
||||||
|
{
|
||||||
|
assert(m_Top >= 0 && m_Top < (int)m_pCalls.size());
|
||||||
|
|
||||||
|
m_pCalls[m_Top]->Reset();
|
||||||
|
|
||||||
|
m_Top--;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Debugger::StepI()
|
||||||
|
{
|
||||||
|
assert(m_Top >= 0 && m_Top < (int)m_pCalls.size());
|
||||||
|
|
||||||
|
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.size());
|
||||||
|
|
||||||
|
return m_pCalls[m_Top]->m_Error;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Debugger::SetTracedError(int error)
|
||||||
|
{
|
||||||
|
assert(m_Top >= 0 && m_Top < (int)m_pCalls.size());
|
||||||
|
|
||||||
|
m_pCalls[m_Top]->m_Error = error;
|
||||||
|
}
|
||||||
|
|
||||||
|
trace_info_t *Debugger::GetTraceStart() const
|
||||||
|
{
|
||||||
|
assert(m_Top >= 0 && m_Top < (int)m_pCalls.size());
|
||||||
|
|
||||||
|
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.size());
|
||||||
|
|
||||||
|
return (m_pCalls[m_Top]->m_Error != AMX_ERR_NONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
#define FLAG_INDIRECT (1<<0)
|
||||||
|
|
||||||
|
//vaddr - the address of our current index vector
|
||||||
|
//base - the base address of which the array is offset to
|
||||||
|
//dim - the current dimension to search
|
||||||
|
//dimNum - the number of dimensions total
|
||||||
|
//sizes[] - an array containing the dimension sizes
|
||||||
|
//Indexes[] - an output array to contain each dimension's index
|
||||||
|
int WalkArray(cell *vaddr, unsigned char *base, cell *addr, int dim, int dimNum, int &flags, int sizes[], int Indexes[])
|
||||||
|
{
|
||||||
|
cell *my_addr;
|
||||||
|
int idx = 0;
|
||||||
|
|
||||||
|
//if we are the second to last walker, we only need to check the ranges of our vector.
|
||||||
|
if (dim == dimNum - 2)
|
||||||
|
{
|
||||||
|
my_addr = vaddr;
|
||||||
|
//first check the actual vectors themselves
|
||||||
|
for (int i=0; i<sizes[dim]; i++)
|
||||||
|
{
|
||||||
|
if (addr == my_addr)
|
||||||
|
return i;
|
||||||
|
my_addr++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
//otherwise, search lower vectors!
|
||||||
|
//vaddr is the address where we can start reading vectors
|
||||||
|
flags |= FLAG_INDIRECT;
|
||||||
|
for (int i=0; i<sizes[dim]; i++)
|
||||||
|
{
|
||||||
|
//the next vector is offset from the last address!
|
||||||
|
//this is funky but that's the internal implementation
|
||||||
|
my_addr = (cell *)((char *)vaddr + i*sizeof(cell) + vaddr[i]);
|
||||||
|
idx = WalkArray(my_addr, base, addr, dim+1, dimNum, flags, sizes, Indexes);
|
||||||
|
if (idx != -1)
|
||||||
|
{
|
||||||
|
Indexes[dim+1] = idx;
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Debugger::FormatError(char *buffer, size_t maxLength)
|
||||||
|
{
|
||||||
|
if (!ErrorExists())
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
assert(m_Top >= 0 && m_Top < (int)m_pCalls.size());
|
||||||
|
|
||||||
|
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 += _snprintf(buffer, maxLength, "Run time error %d: %s ", error, gen_err);
|
||||||
|
buffer += size;
|
||||||
|
maxLength -= size;
|
||||||
|
|
||||||
|
if (error == AMX_ERR_NATIVE)
|
||||||
|
{
|
||||||
|
char native_name[32];
|
||||||
|
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;
|
||||||
|
} else if (instr == OP_SYSREQ_PRI) {
|
||||||
|
num = (int)m_pAmx->pri;
|
||||||
|
}
|
||||||
|
if (num)
|
||||||
|
amx_err = amx_GetNative(m_pAmx, (int)*p_cip, native_name);
|
||||||
|
else
|
||||||
|
amx_err = AMX_ERR_NOTFOUND;
|
||||||
|
if (!amx_err)
|
||||||
|
size += _snprintf(buffer, maxLength, "(native \"%s\")", native_name);
|
||||||
|
} else if (error == AMX_ERR_BOUNDS) {
|
||||||
|
tagAMX_DBG *pDbg = m_pAmxDbg;
|
||||||
|
int symbols = pDbg->hdr->symbols;
|
||||||
|
int index = 0;
|
||||||
|
tagAMX_DBG_SYMBOL **pSymbols = pDbg->symboltbl;
|
||||||
|
tagAMX_DBG_SYMBOL *pSymbol, *pLastSymbol=NULL;
|
||||||
|
const tagAMX_DBG_SYMDIM *pDims;
|
||||||
|
ucell addr = 0;
|
||||||
|
int flags = 0;
|
||||||
|
char v_class, i_dent;
|
||||||
|
cell *arr_addr=NULL, *p_addr=NULL;
|
||||||
|
unsigned char *data = m_pAmx->base + ((AMX_HEADER *)m_pAmx->base)->dat;
|
||||||
|
bool valid=false;
|
||||||
|
//we can't really browse the assembly because
|
||||||
|
// we've no idea what the peephole optimizer did.
|
||||||
|
// so we're gonna try to go out on a limb and guess.
|
||||||
|
if (m_pAmx->alt < 0)
|
||||||
|
{
|
||||||
|
//take a guess that it's local
|
||||||
|
addr = m_pAmx->alt - pTrace->frm;
|
||||||
|
v_class = 1;
|
||||||
|
} else {
|
||||||
|
//take a guess that it's a global
|
||||||
|
//it won't be global if it's passed in from the stack frame, however
|
||||||
|
// doing this with a hardcoded array size is quite rare, and is probably passed
|
||||||
|
// as iREFARRAY not iARRAY!
|
||||||
|
addr = m_pAmx->alt;
|
||||||
|
v_class = 0;
|
||||||
|
}
|
||||||
|
bool found = false;
|
||||||
|
bool _found = true;
|
||||||
|
static char _msgbuf[255];
|
||||||
|
size_t _size = 0;
|
||||||
|
//take a pre-emptive guess at the v_class!
|
||||||
|
//are we GLOBAL (0) or LOCAL (1) ?
|
||||||
|
if (m_pAmx->alt < 0)
|
||||||
|
{
|
||||||
|
v_class = 1;
|
||||||
|
i_dent = iARRAY;
|
||||||
|
arr_addr = (cell *)(data + pTrace->frm + m_pAmx->alt);
|
||||||
|
} else {
|
||||||
|
//it's greater than 0, check other things!
|
||||||
|
if (m_pAmx->alt >= m_pAmx->hlw &&
|
||||||
|
m_pAmx->alt <= m_pAmx->stp)
|
||||||
|
{
|
||||||
|
//it's in the stack somewhere... guess that it's a local!
|
||||||
|
v_class = 1;
|
||||||
|
//relocate it
|
||||||
|
m_pAmx->alt -= pTrace->frm;
|
||||||
|
//alt cannot be zero
|
||||||
|
if (m_pAmx->alt < 0)
|
||||||
|
i_dent = iARRAY;
|
||||||
|
else
|
||||||
|
i_dent = iREFARRAY;
|
||||||
|
arr_addr = (cell *)(data + pTrace->frm + m_pAmx->alt);
|
||||||
|
} else {
|
||||||
|
//guess that it's DAT
|
||||||
|
v_class = 0;
|
||||||
|
i_dent = iARRAY;
|
||||||
|
arr_addr = (cell *)(data + m_pAmx->alt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (index = 0; index < symbols; index++)
|
||||||
|
{
|
||||||
|
pSymbol = pSymbols[index];
|
||||||
|
if (pSymbol->codestart <= (ucell)cip &&
|
||||||
|
pSymbol->codeend >= (ucell)cip &&
|
||||||
|
(pSymbol->ident == iARRAY || pSymbol->ident == iREFARRAY))
|
||||||
|
{
|
||||||
|
amx_err = dbg_GetArrayDim(pDbg, pSymbol, &pDims);
|
||||||
|
if (amx_err != AMX_ERR_NONE)
|
||||||
|
continue;
|
||||||
|
//calculate the size of the array. this is important!
|
||||||
|
ucell size = pDims[0].size;
|
||||||
|
ucell aggre = pDims[0].size;
|
||||||
|
valid = false;
|
||||||
|
for (int16_t i=1; i<pSymbol->dim; i++)
|
||||||
|
{
|
||||||
|
aggre *= pDims[i].size;
|
||||||
|
size += aggre;
|
||||||
|
}
|
||||||
|
if (pSymbol->vclass != v_class)
|
||||||
|
continue;
|
||||||
|
if (pSymbol->ident != i_dent)
|
||||||
|
continue;
|
||||||
|
if (v_class == 1)
|
||||||
|
{
|
||||||
|
if (i_dent == iARRAY)
|
||||||
|
{
|
||||||
|
p_addr = (cell *)(data + pTrace->frm + pSymbol->address);
|
||||||
|
} else if (i_dent == iREFARRAY) {
|
||||||
|
//get the variable off the stack, by reference
|
||||||
|
ucell _addr = (ucell)*((cell *)(data + pTrace->frm + pSymbol->address));
|
||||||
|
p_addr = (cell *)(data + _addr);
|
||||||
|
}
|
||||||
|
} else if (v_class == 0) {
|
||||||
|
p_addr = (cell *)(data + pSymbol->address);
|
||||||
|
}
|
||||||
|
int *sizes = new int[pSymbol->dim];
|
||||||
|
int *indexes = new int[pSymbol->dim];
|
||||||
|
for (int i=0; i<pSymbol->dim; i++)
|
||||||
|
{
|
||||||
|
sizes[i] = pDims[i].size;
|
||||||
|
indexes[i] = -1;
|
||||||
|
}
|
||||||
|
flags = 0;
|
||||||
|
if (pSymbol->dim >= 2)
|
||||||
|
{
|
||||||
|
int dims = pSymbol->dim;
|
||||||
|
indexes[0] = WalkArray(p_addr, data, arr_addr, 0, pSymbol->dim, flags, sizes, indexes);
|
||||||
|
if (indexes[0] == -1)
|
||||||
|
{
|
||||||
|
while (indexes[0] == -1 && --dims > 0)
|
||||||
|
{
|
||||||
|
flags = 0;
|
||||||
|
indexes[0] = WalkArray(p_addr, data, arr_addr, 0, dims, flags, sizes, indexes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//find the last known good dimension
|
||||||
|
for (dims=pSymbol->dim-1; dims>=0; dims--)
|
||||||
|
{
|
||||||
|
if (indexes[dims] != -1)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
//check for the "impossible" case.
|
||||||
|
//if we have [X][-1], and X is zero, the array did not walk properly.
|
||||||
|
if (dims >= 0
|
||||||
|
&& indexes[dims] == 0
|
||||||
|
&& !(flags & FLAG_INDIRECT)
|
||||||
|
&& dims < pSymbol->dim - 1)
|
||||||
|
{
|
||||||
|
//here we have the dreaded MIXED CASE. we don't know whether
|
||||||
|
//[-][X] or [0][-] (where - is a bad input) was intended.
|
||||||
|
//first, we take a guess by checking the bounds.
|
||||||
|
cell *_cip = (cell *)_CipAsVa(cip);
|
||||||
|
_cip -= 1;
|
||||||
|
cell bounds = *_cip;
|
||||||
|
if (sizes[dims] != sizes[dims+1])
|
||||||
|
{
|
||||||
|
//we were checking initial bounds
|
||||||
|
if (bounds == sizes[dims] - 1)
|
||||||
|
{
|
||||||
|
indexes[dims] = m_pAmx->pri;
|
||||||
|
} else if (bounds == sizes[dims+1] - 1) {
|
||||||
|
indexes[dims + 1] = m_pAmx->pri;
|
||||||
|
indexes[dims] = 0;
|
||||||
|
} else {
|
||||||
|
//this should really never happen...
|
||||||
|
_found = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_found = false;
|
||||||
|
}
|
||||||
|
if (!_found)
|
||||||
|
{
|
||||||
|
//we still don't have a good approximation.
|
||||||
|
//the user did something like:
|
||||||
|
//new X[40][40]
|
||||||
|
//we could do some really complicated and random guesswork
|
||||||
|
// but fact is, we have no way of deterministically knowing
|
||||||
|
// what the user intended.
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//set the last know index to our culprit
|
||||||
|
indexes[dims + 1] = m_pAmx->pri;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
indexes[0] = m_pAmx->pri;
|
||||||
|
}
|
||||||
|
_size += _snprintf(&(_msgbuf[_size]), sizeof(_msgbuf)-_size, "(array \"%s", pSymbol->name);
|
||||||
|
for (int i=0; i<pSymbol->dim; i++)
|
||||||
|
_size += _snprintf(&(_msgbuf[_size]), sizeof(_msgbuf)-_size, "[%d]", pDims[i].size);
|
||||||
|
if (_found)
|
||||||
|
{
|
||||||
|
_size += _snprintf(&(_msgbuf[_size]), sizeof(_msgbuf)-_size, "\") (indexed \"");
|
||||||
|
for (int i=0; i<pSymbol->dim; i++)
|
||||||
|
{
|
||||||
|
if (indexes[i] == -1)
|
||||||
|
_size += _snprintf(&(_msgbuf[_size]), sizeof(_msgbuf)-_size, "[]");
|
||||||
|
else
|
||||||
|
_size += _snprintf(&(_msgbuf[_size]), sizeof(_msgbuf)-_size, "[%d]", indexes[i]);
|
||||||
|
}
|
||||||
|
_size += _snprintf(&(_msgbuf[_size]), sizeof(_msgbuf)-_size, "\")");
|
||||||
|
} else {
|
||||||
|
_size += _snprintf(&(_msgbuf[_size]), sizeof(_msgbuf)-_size, "\") (unknown index \"%d\")", m_pAmx->pri);
|
||||||
|
}
|
||||||
|
found = true;
|
||||||
|
delete [] indexes;
|
||||||
|
delete [] sizes;
|
||||||
|
break;
|
||||||
|
} /* symbol validation */
|
||||||
|
} /* is in valid ranges */
|
||||||
|
if (!found)
|
||||||
|
_msgbuf[0] = '\0';
|
||||||
|
|
||||||
|
size += _snprintf(buffer, maxLength, "%s", _msgbuf);
|
||||||
|
}
|
||||||
|
|
||||||
|
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",
|
||||||
|
NULL,
|
||||||
|
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;
|
||||||
|
|
||||||
|
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.size(); i++)
|
||||||
|
delete m_pCalls[i];
|
||||||
|
|
||||||
|
m_pCalls.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
Debugger::~Debugger()
|
||||||
|
{
|
||||||
|
Clear();
|
||||||
|
}
|
136
amxmodx/debugger.h
Executable file
136
amxmodx/debugger.h
Executable file
|
@ -0,0 +1,136 @@
|
||||||
|
/* AMX Mod X
|
||||||
|
*
|
||||||
|
* by the AMX Mod X Development Team
|
||||||
|
* originally developed by OLO
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License as published by the
|
||||||
|
* Free Software Foundation; either version 2 of the License, or (at
|
||||||
|
* your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful, but
|
||||||
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
* General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
*
|
||||||
|
* In addition, as a special exception, the author gives permission to
|
||||||
|
* link the code of this program with the Half-Life Game Engine ("HL
|
||||||
|
* Engine") and Modified Game Libraries ("MODs") developed by Valve,
|
||||||
|
* L.L.C ("Valve"). You must obey the GNU General Public License in all
|
||||||
|
* respects for all of the code used other than the HL Engine and MODs
|
||||||
|
* from Valve. If you modify this file, you may extend this exception
|
||||||
|
* to your version of the file, but you are not obligated to do so. If
|
||||||
|
* you do not wish to do so, delete this exception statement from your
|
||||||
|
* version.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _INCLUDE_DEBUGGER_H_
|
||||||
|
#define _INCLUDE_DEBUGGER_H_
|
||||||
|
|
||||||
|
#include "CVector.h"
|
||||||
|
#include "amxdbg.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Third revision of the AMX Mod X Plugin Debugger.
|
||||||
|
* This final, object oriented version is safe for multiple calls and lets you
|
||||||
|
* fine-tune error handling.
|
||||||
|
* -BAILOPAN
|
||||||
|
*/
|
||||||
|
class Debugger
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
class Tracer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
struct trace_info
|
||||||
|
{
|
||||||
|
trace_info() : cip(0), frm(0), used(false), next(NULL), prev(NULL)
|
||||||
|
{
|
||||||
|
};
|
||||||
|
cell cip;
|
||||||
|
cell frm;
|
||||||
|
trace_info *next;
|
||||||
|
trace_info *prev;
|
||||||
|
bool used;
|
||||||
|
};
|
||||||
|
public:
|
||||||
|
Tracer() : m_Error(0), m_pStart(NULL), m_pEnd(NULL), m_Reset(true) { };
|
||||||
|
~Tracer();
|
||||||
|
public:
|
||||||
|
void StepI(cell frm, cell cip);
|
||||||
|
void Reset();
|
||||||
|
void Clear();
|
||||||
|
Debugger::Tracer::trace_info *GetStart() const;
|
||||||
|
Debugger::Tracer::trace_info *GetEnd() const;
|
||||||
|
public:
|
||||||
|
int m_Error;
|
||||||
|
private:
|
||||||
|
trace_info *m_pStart;
|
||||||
|
trace_info *m_pEnd;
|
||||||
|
bool m_Reset;
|
||||||
|
};
|
||||||
|
public:
|
||||||
|
Debugger(AMX *pAmx, AMX_DBG *pAmxDbg) :
|
||||||
|
m_pAmx(pAmx), m_pAmxDbg(pAmxDbg), m_Top(-1)
|
||||||
|
{
|
||||||
|
_CacheAmxOpcodeList();
|
||||||
|
};
|
||||||
|
~Debugger();
|
||||||
|
public:
|
||||||
|
//Begin a trace for a function
|
||||||
|
void BeginExec();
|
||||||
|
|
||||||
|
//Step through one instruction
|
||||||
|
void StepI();
|
||||||
|
|
||||||
|
//Get/set the last traced error
|
||||||
|
int GetTracedError();
|
||||||
|
void SetTracedError(int error);
|
||||||
|
|
||||||
|
//Get the first trace info of the call stack
|
||||||
|
Debugger::Tracer::trace_info *GetTraceStart() const;
|
||||||
|
|
||||||
|
//Get extra info about the call stack
|
||||||
|
bool GetTraceInfo(Debugger::Tracer::trace_info *pTraceInfo, long &line, const char *&function, const char *&file);
|
||||||
|
|
||||||
|
//Get the next trace in the call stack, NULL if none
|
||||||
|
Debugger::Tracer::trace_info *GetNextTrace(Debugger::Tracer::trace_info *pTraceInfo);
|
||||||
|
|
||||||
|
//Returns true if an error exists
|
||||||
|
bool ErrorExists();
|
||||||
|
|
||||||
|
//Formats the error message into a buffer.
|
||||||
|
//returns length of data copied, or -1 if there is no error.
|
||||||
|
int FormatError(char *buffer, size_t maxLength);
|
||||||
|
|
||||||
|
//End a trace
|
||||||
|
void EndExec();
|
||||||
|
|
||||||
|
//Reset the internal states as if the debugger was inactive
|
||||||
|
void Reset();
|
||||||
|
|
||||||
|
//Destroy internal states for shutdown
|
||||||
|
void Clear();
|
||||||
|
public:
|
||||||
|
//generic static opcode breaker
|
||||||
|
static int AMXAPI DebugHook(AMX *amx);
|
||||||
|
private:
|
||||||
|
void _CacheAmxOpcodeList();
|
||||||
|
int _GetOpcodeFromCip(cell cip, cell *&addr);
|
||||||
|
cell _CipAsVa(cell cip);
|
||||||
|
public:
|
||||||
|
AMX *m_pAmx;
|
||||||
|
AMX_DBG *m_pAmxDbg;
|
||||||
|
int m_Top;
|
||||||
|
cell *m_pOpcodeList;
|
||||||
|
CVector<Tracer *> m_pCalls;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef Debugger::Tracer::trace_info trace_info_t;
|
||||||
|
|
||||||
|
#endif //_INCLUDE_DEBUGGER_H_
|
|
@ -42,6 +42,7 @@
|
||||||
#include "amxdbg.h"
|
#include "amxdbg.h"
|
||||||
#include "newmenus.h"
|
#include "newmenus.h"
|
||||||
#include "natives.h"
|
#include "natives.h"
|
||||||
|
#include "debugger.h"
|
||||||
|
|
||||||
CList<CModule,const char*> g_modules;
|
CList<CModule,const char*> g_modules;
|
||||||
CList<CScript,AMX*> g_loadedscripts;
|
CList<CScript,AMX*> g_loadedscripts;
|
||||||
|
@ -96,166 +97,6 @@ void free_amxmemory(void **ptr)
|
||||||
*ptr = 0;
|
*ptr = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void amxx_FreeTrace(AMX_DBGINFO *pInfo)
|
|
||||||
{
|
|
||||||
amx_trace *pTrace = pInfo->pTrace;
|
|
||||||
amx_trace *pTemp = NULL;
|
|
||||||
|
|
||||||
while (pTrace)
|
|
||||||
{
|
|
||||||
pTemp = pTrace->next;
|
|
||||||
delete pTrace;
|
|
||||||
pTrace = pTemp;
|
|
||||||
}
|
|
||||||
|
|
||||||
pInfo->pTrace = NULL;
|
|
||||||
pInfo->pTraceFrm = NULL;
|
|
||||||
pInfo->pTraceEnd = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
//returns true if this was the last call
|
|
||||||
bool amxx_RemTraceCall(AMX_DBGINFO *pInfo)
|
|
||||||
{
|
|
||||||
amx_trace *pTrace = pInfo->pTraceFrm;
|
|
||||||
|
|
||||||
assert(pTrace != NULL);
|
|
||||||
|
|
||||||
pInfo->pTraceFrm = pTrace->prev;
|
|
||||||
pTrace->used = false;
|
|
||||||
|
|
||||||
if (pInfo->pTraceFrm == NULL)
|
|
||||||
{
|
|
||||||
//invalidate the trace
|
|
||||||
pInfo->frm = 0;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void amxx_FreeDebug(AMX *amx)
|
|
||||||
{
|
|
||||||
AMX_DBGINFO *pInfo = (AMX_DBGINFO *)amx->userdata[2];
|
|
||||||
if (pInfo)
|
|
||||||
{
|
|
||||||
AMX_DBG *pDbg = (AMX_DBG *)pInfo->pDebug;
|
|
||||||
if (pDbg)
|
|
||||||
{
|
|
||||||
dbg_FreeInfo(pDbg);
|
|
||||||
delete pDbg;
|
|
||||||
}
|
|
||||||
if (pInfo->pTrace)
|
|
||||||
amxx_FreeTrace(pInfo);
|
|
||||||
delete pInfo;
|
|
||||||
amx->userdata[2] = NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
amx_trace *amxx_AddTraceCall(AMX_DBGINFO *pInfo)
|
|
||||||
{
|
|
||||||
amx_trace *pTrace = NULL;
|
|
||||||
|
|
||||||
if (pInfo->pTrace == NULL)
|
|
||||||
{
|
|
||||||
pTrace = new amx_trace;
|
|
||||||
memset(pTrace, 0, sizeof(amx_trace));
|
|
||||||
pInfo->pTrace = pTrace;
|
|
||||||
pInfo->pTraceFrm = pTrace;
|
|
||||||
pInfo->pTraceEnd = pTrace;
|
|
||||||
} else if (pInfo->pTraceFrm == NULL) {
|
|
||||||
pTrace = pInfo->pTrace;
|
|
||||||
pInfo->pTraceFrm = pTrace;
|
|
||||||
} else {
|
|
||||||
if (pInfo->pTraceFrm->next == NULL)
|
|
||||||
{
|
|
||||||
//if we are at the end of the list...
|
|
||||||
assert(pInfo->pTraceFrm == pInfo->pTraceEnd);
|
|
||||||
pTrace = new amx_trace;
|
|
||||||
memset(pTrace, 0, sizeof(amx_trace));
|
|
||||||
pTrace->prev = pInfo->pTraceEnd;
|
|
||||||
pInfo->pTraceEnd->next = pTrace;
|
|
||||||
pInfo->pTraceEnd = pTrace;
|
|
||||||
pInfo->pTraceFrm = pTrace;
|
|
||||||
} else {
|
|
||||||
//we are somewhere else. whatever.
|
|
||||||
pTrace = pInfo->pTraceFrm->next;
|
|
||||||
pInfo->pTraceFrm = pTrace;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pTrace->used = true;
|
|
||||||
|
|
||||||
return pTrace;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AMXAPI amxx_InvalidateTrace(AMX *amx)
|
|
||||||
{
|
|
||||||
AMX_DBGINFO *pInfo = (AMX_DBGINFO *)(amx->userdata[2]);
|
|
||||||
if (!pInfo)
|
|
||||||
return;
|
|
||||||
amx_trace *pTrace = pInfo->pTrace;
|
|
||||||
|
|
||||||
while (pTrace && pTrace->used)
|
|
||||||
{
|
|
||||||
pTrace->used = false;
|
|
||||||
pTrace = pTrace->next;
|
|
||||||
}
|
|
||||||
|
|
||||||
pInfo->pTraceFrm = NULL;
|
|
||||||
pInfo->frm = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int AMXAPI amxx_DebugHook(AMX *amx)
|
|
||||||
{
|
|
||||||
AMX_DBGINFO *pInfo = (AMX_DBGINFO *)amx->userdata[2];
|
|
||||||
|
|
||||||
if ( !(amx->flags & AMX_FLAG_DEBUG) || !pInfo )
|
|
||||||
return AMX_ERR_DEBUG;
|
|
||||||
|
|
||||||
enum StackState
|
|
||||||
{
|
|
||||||
Stack_Same,
|
|
||||||
Stack_Push,
|
|
||||||
Stack_Pop,
|
|
||||||
};
|
|
||||||
|
|
||||||
StackState state = Stack_Same;
|
|
||||||
|
|
||||||
if (!pInfo->frm)
|
|
||||||
{
|
|
||||||
pInfo->frm = amx->frm;
|
|
||||||
state = Stack_Push;
|
|
||||||
} else {
|
|
||||||
//Are we stepping through a different frame?
|
|
||||||
if (amx->frm < pInfo->frm)
|
|
||||||
{
|
|
||||||
pInfo->frm = amx->frm;
|
|
||||||
state = Stack_Push;
|
|
||||||
} else if (amx->frm > pInfo->frm) {
|
|
||||||
pInfo->frm = amx->frm;
|
|
||||||
state = Stack_Pop;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (state == Stack_Push)
|
|
||||||
{
|
|
||||||
amx_trace *pTrace = amxx_AddTraceCall(pInfo);
|
|
||||||
pTrace->frm = amx->cip;
|
|
||||||
} else if (state == Stack_Pop) {
|
|
||||||
if (amxx_RemTraceCall(pInfo))
|
|
||||||
{
|
|
||||||
pInfo->frm = 0;
|
|
||||||
}
|
|
||||||
} else if (state == Stack_Same) {
|
|
||||||
//save the cip
|
|
||||||
amx_trace *pTrace = pInfo->pTraceFrm;
|
|
||||||
assert(pTrace != NULL);
|
|
||||||
pTrace->frm = amx->cip;
|
|
||||||
}
|
|
||||||
|
|
||||||
return AMX_ERR_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
int load_amxscript(AMX *amx, void **program, const char *filename, char error[64], int debug)
|
int load_amxscript(AMX *amx, void **program, const char *filename, char error[64], int debug)
|
||||||
{
|
{
|
||||||
*error = 0;
|
*error = 0;
|
||||||
|
@ -368,23 +209,19 @@ int load_amxscript(AMX *amx, void **program, const char *filename, char error[64
|
||||||
return (amx->error = AMX_ERR_INIT);
|
return (amx->error = AMX_ERR_INIT);
|
||||||
}
|
}
|
||||||
|
|
||||||
AMX_DBGINFO *pInfo = new AMX_DBGINFO;
|
|
||||||
memset(pInfo, 0, sizeof(AMX_DBGINFO));
|
|
||||||
amx->userdata[2] = (void *)pInfo;
|
|
||||||
|
|
||||||
pInfo->error = AMX_ERR_NONE;
|
|
||||||
pInfo->pDebug = (void *)pDbg;
|
|
||||||
|
|
||||||
if (will_be_debugged)
|
if (will_be_debugged)
|
||||||
{
|
{
|
||||||
#ifdef JIT
|
|
||||||
amx->flags |= AMX_FLAG_DEBUG;
|
amx->flags |= AMX_FLAG_DEBUG;
|
||||||
#endif
|
amx->flags &= (~AMX_FLAG_JITC);
|
||||||
amx_SetDebugHook(amx, amxx_DebugHook);
|
amx_SetDebugHook(amx, &Debugger::DebugHook);
|
||||||
|
|
||||||
|
Debugger *pDebugger = new Debugger(amx, pDbg);
|
||||||
|
amx->userdata[UD_DEBUGGER] = pDebugger;
|
||||||
} else {
|
} else {
|
||||||
//set this again because amx_Init() erases it!
|
|
||||||
#ifdef JIT
|
#ifdef JIT
|
||||||
|
//set this again because amx_Init() erases it!
|
||||||
amx->flags |= AMX_FLAG_JITC;
|
amx->flags |= AMX_FLAG_JITC;
|
||||||
|
amx->flags &= (~AMX_FLAG_DEBUG);
|
||||||
amx->sysreq_d = NULL;
|
amx->sysreq_d = NULL;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
@ -594,7 +431,9 @@ int set_amxnatives(AMX* amx,char error[128])
|
||||||
int unload_amxscript(AMX* amx, void** program)
|
int unload_amxscript(AMX* amx, void** program)
|
||||||
{
|
{
|
||||||
int flags = amx->flags;
|
int flags = amx->flags;
|
||||||
amxx_FreeDebug(amx);
|
Debugger *pDebugger = (Debugger *)amx->userdata[UD_DEBUGGER];
|
||||||
|
if (pDebugger)
|
||||||
|
delete pDebugger;
|
||||||
CList<CScript,AMX*>::iterator a = g_loadedscripts.find( amx );
|
CList<CScript,AMX*>::iterator a = g_loadedscripts.find( amx );
|
||||||
if ( a ) a.remove();
|
if ( a ) a.remove();
|
||||||
char *prg = (char *)*program;
|
char *prg = (char *)*program;
|
||||||
|
@ -1443,137 +1282,72 @@ void MNF_Log(const char *fmt, ...)
|
||||||
AMXXLOG_Log("%s", msg);
|
AMXXLOG_Log("%s", msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool amxx_GetPluginData(AMX *amx, cell addr, long &line, const char *&filename, const char *&function)
|
|
||||||
{
|
|
||||||
AMX_DBGINFO *pInfo = (AMX_DBGINFO *)(amx->userdata[2]);
|
|
||||||
|
|
||||||
if (pInfo && pInfo->pDebug)
|
|
||||||
{
|
|
||||||
AMX_DBG *pDbg = (AMX_DBG *)pInfo->pDebug;
|
|
||||||
dbg_LookupFunction(pDbg, addr, &function);
|
|
||||||
dbg_LookupLine(pDbg, addr, &line);
|
|
||||||
dbg_LookupFile(pDbg, addr, &filename);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
//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",
|
|
||||||
"divide",
|
|
||||||
"sleep",
|
|
||||||
"invalid access state",
|
|
||||||
NULL,
|
|
||||||
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 = "";
|
|
||||||
else
|
|
||||||
geterr = amx_errs[err];
|
|
||||||
return geterr;
|
|
||||||
}
|
|
||||||
|
|
||||||
//by BAILOPAN
|
//by BAILOPAN
|
||||||
// debugger engine front end
|
// debugger engine front end
|
||||||
void LogError(AMX *amx, int err, const char *fmt, ...)
|
void LogError(AMX *amx, int err, const char *fmt, ...)
|
||||||
{
|
{
|
||||||
//does this plugin have debug info?
|
Debugger *pDebugger = (Debugger *)amx->userdata[UD_DEBUGGER];
|
||||||
va_list arg;
|
|
||||||
AMX_DBGINFO *pInfo = (AMX_DBGINFO *)(amx->userdata[2]);
|
|
||||||
const char *name = get_amxscriptname(amx);
|
|
||||||
static char buf[1024];
|
|
||||||
static char vbuf[1024];
|
|
||||||
*buf = 0;
|
|
||||||
*vbuf = 0;
|
|
||||||
|
|
||||||
if (fmt[0] == '\0')
|
|
||||||
{
|
|
||||||
_snprintf(vbuf, sizeof(vbuf)-1, "Run time error %d (%s)", err, GenericError(err));
|
|
||||||
} else {
|
|
||||||
va_start(arg, fmt);
|
|
||||||
vsprintf(vbuf, fmt, arg);
|
|
||||||
va_end(arg);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool invalidate = false;
|
|
||||||
AMXXLOG_Log("[AMXX] %s", vbuf);
|
|
||||||
if (!pInfo || !(amx->flags & AMX_FLAG_DEBUG) || !pInfo->pDebug)
|
|
||||||
{
|
|
||||||
|
|
||||||
AMXXLOG_Log("[AMXX] Debug is not enabled (plugin \"%s\")", name);
|
|
||||||
invalidate = true;
|
|
||||||
} else {
|
|
||||||
long line;
|
|
||||||
const char *filename = NULL;
|
|
||||||
const char *function = NULL;
|
|
||||||
amx_trace *pTrace = pInfo->pTraceFrm;
|
|
||||||
int i=0, iLine;
|
|
||||||
cell frame;
|
|
||||||
|
|
||||||
AMXXLOG_Log("[AMXX] Displaying call trace (plugin \"%s\")", name);
|
|
||||||
while (pTrace)
|
|
||||||
{
|
|
||||||
frame = pTrace->frm;
|
|
||||||
|
|
||||||
if (amxx_GetPluginData(amx, frame, line, filename, function))
|
|
||||||
{
|
|
||||||
//line seems to be 1 off o_O
|
|
||||||
iLine = static_cast<int>(line) + 1;
|
|
||||||
AMXXLOG_Log("[AMXX] [%d] %s::%s (line %d)",
|
|
||||||
i,
|
|
||||||
filename?filename:"",
|
|
||||||
function?function:"",
|
|
||||||
iLine
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
pTrace->used = false;
|
|
||||||
pTrace = pTrace->prev;
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
//by now we have already invalidated
|
|
||||||
pInfo->pTraceFrm = NULL;
|
|
||||||
pInfo->frm = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (invalidate)
|
|
||||||
amxx_InvalidateTrace(amx);
|
|
||||||
|
|
||||||
//set these so ForwardMngr knows not to call us again
|
|
||||||
//This will also halt the script!
|
|
||||||
amx->error = err;
|
amx->error = err;
|
||||||
pInfo->error = err;
|
CPluginMngr::CPlugin *pl = g_plugins.findPluginFast(amx);
|
||||||
|
|
||||||
|
const char *filename = "";
|
||||||
|
if (pl)
|
||||||
|
{
|
||||||
|
filename = pl->getName();
|
||||||
|
} else {
|
||||||
|
CList<CScript,AMX*>::iterator a = g_loadedscripts.find(amx);
|
||||||
|
if (a)
|
||||||
|
filename = (*a).getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
static char msg_buffer[4096];
|
||||||
|
if (fmt != NULL)
|
||||||
|
{
|
||||||
|
va_list ap;
|
||||||
|
va_start(ap, fmt);
|
||||||
|
_vsnprintf(msg_buffer, sizeof(msg_buffer)-1, fmt, ap);
|
||||||
|
va_end(ap);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fmt != NULL)
|
||||||
|
AMXXLOG_Log("%s", msg_buffer);
|
||||||
|
|
||||||
|
if (!pDebugger)
|
||||||
|
{
|
||||||
|
//give the module's error first. makes the report look nicer.
|
||||||
|
AMXXLOG_Log("[AMXX] Run time error %d (plugin \"%s\") - debug not enabled!", err, filename);
|
||||||
|
AMXXLOG_Log("[AMXX] To enable debug mode, add \"debug\" after the plugin name in plugins.ini (without quotes).");
|
||||||
|
//destroy original error code so the original is not displayed again
|
||||||
|
amx->error = -1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pDebugger->SetTracedError(err);
|
||||||
|
|
||||||
|
char buffer[512];
|
||||||
|
pDebugger->FormatError(buffer, sizeof(buffer)-1);
|
||||||
|
|
||||||
|
AMXXLOG_Log("[AMXX] Displaying debug trace (plugin \"%s\")", filename);
|
||||||
|
AMXXLOG_Log("[AMXX] %s", buffer);
|
||||||
|
|
||||||
|
int count = 0;
|
||||||
|
long lLine;
|
||||||
|
const char *file, *function;
|
||||||
|
trace_info_t *pTrace = pDebugger->GetTraceStart();
|
||||||
|
while (pTrace)
|
||||||
|
{
|
||||||
|
pDebugger->GetTraceInfo(pTrace, lLine, function, file);
|
||||||
|
AMXXLOG_Log(
|
||||||
|
"[AMXX] [%d] %s::%s (line %d)",
|
||||||
|
count,
|
||||||
|
file,
|
||||||
|
function,
|
||||||
|
(int)(lLine + 1)
|
||||||
|
);
|
||||||
|
count++;
|
||||||
|
pTrace = pDebugger->GetNextTrace(pTrace);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MNF_MergeDefinitionFile(const char *file)
|
void MNF_MergeDefinitionFile(const char *file)
|
||||||
|
|
|
@ -42,12 +42,13 @@
|
||||||
<Tool
|
<Tool
|
||||||
Name="VCLinkerTool"
|
Name="VCLinkerTool"
|
||||||
AdditionalOptions="/MACHINE:I386"
|
AdditionalOptions="/MACHINE:I386"
|
||||||
AdditionalDependencies="odbc32.lib odbccp32.lib ..\zlib\zlib.lib"
|
AdditionalDependencies="..\JIT\natives-x86.obj ..\zlib\zlib.lib"
|
||||||
OutputFile="debug/amxmodx_mm.dll"
|
OutputFile="debug/amxmodx_mm.dll"
|
||||||
Version="0.1"
|
Version="0.1"
|
||||||
LinkIncremental="1"
|
LinkIncremental="1"
|
||||||
SuppressStartupBanner="TRUE"
|
SuppressStartupBanner="TRUE"
|
||||||
AdditionalLibraryDirectories="..\extra\lib_win32"
|
AdditionalLibraryDirectories="..\extra\lib_win32"
|
||||||
|
IgnoreDefaultLibraryNames="MSVCRT"
|
||||||
ModuleDefinitionFile=""
|
ModuleDefinitionFile=""
|
||||||
GenerateDebugInformation="TRUE"
|
GenerateDebugInformation="TRUE"
|
||||||
ProgramDatabaseFile=".\debug/amxx_mm.pdb"
|
ProgramDatabaseFile=".\debug/amxx_mm.pdb"
|
||||||
|
@ -639,6 +640,9 @@
|
||||||
<File
|
<File
|
||||||
RelativePath="..\CVault.cpp">
|
RelativePath="..\CVault.cpp">
|
||||||
</File>
|
</File>
|
||||||
|
<File
|
||||||
|
RelativePath="..\debugger.cpp">
|
||||||
|
</File>
|
||||||
<File
|
<File
|
||||||
RelativePath="..\emsg.cpp">
|
RelativePath="..\emsg.cpp">
|
||||||
</File>
|
</File>
|
||||||
|
@ -797,6 +801,9 @@
|
||||||
<File
|
<File
|
||||||
RelativePath="..\CVector.h">
|
RelativePath="..\CVector.h">
|
||||||
</File>
|
</File>
|
||||||
|
<File
|
||||||
|
RelativePath="..\debugger.h">
|
||||||
|
</File>
|
||||||
<File
|
<File
|
||||||
RelativePath="..\fakemeta.h">
|
RelativePath="..\fakemeta.h">
|
||||||
</File>
|
</File>
|
||||||
|
|
|
@ -27,8 +27,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||||
//
|
//
|
||||||
|
|
||||||
VS_VERSION_INFO VERSIONINFO
|
VS_VERSION_INFO VERSIONINFO
|
||||||
FILEVERSION 1,5,5,0
|
FILEVERSION 1,5,6,0
|
||||||
PRODUCTVERSION 1,5,5,0
|
PRODUCTVERSION 1,5,6,0
|
||||||
FILEFLAGSMASK 0x17L
|
FILEFLAGSMASK 0x17L
|
||||||
#ifdef _DEBUG
|
#ifdef _DEBUG
|
||||||
FILEFLAGS 0x1L
|
FILEFLAGS 0x1L
|
||||||
|
@ -45,12 +45,12 @@ BEGIN
|
||||||
BEGIN
|
BEGIN
|
||||||
VALUE "Comments", "AMX Mod X"
|
VALUE "Comments", "AMX Mod X"
|
||||||
VALUE "FileDescription", "AMX Mod X"
|
VALUE "FileDescription", "AMX Mod X"
|
||||||
VALUE "FileVersion", "1.55"
|
VALUE "FileVersion", "1.56"
|
||||||
VALUE "InternalName", "amxmodx"
|
VALUE "InternalName", "amxmodx"
|
||||||
VALUE "LegalCopyright", "Copyright (c) 2004-2005, AMX Mod X Dev Team"
|
VALUE "LegalCopyright", "Copyright (c) 2004-2005, AMX Mod X Dev Team"
|
||||||
VALUE "OriginalFilename", "amxmodx_mm.dll"
|
VALUE "OriginalFilename", "amxmodx_mm.dll"
|
||||||
VALUE "ProductName", "AMX Mod X"
|
VALUE "ProductName", "AMX Mod X"
|
||||||
VALUE "ProductVersion", "1.55"
|
VALUE "ProductVersion", "1.56"
|
||||||
END
|
END
|
||||||
END
|
END
|
||||||
BLOCK "VarFileInfo"
|
BLOCK "VarFileInfo"
|
||||||
|
|
Loading…
Reference in New Issue
Block a user