mirror of
https://github.com/ivellioscolin/pykd.git
synced 2025-04-21 04:13:22 +08:00
[+] WaitEventException : none of the targets could generate events
[+] Breakpoint debug event [+] Exception debug event [+] watchDog.py - demo for debugEvent::onException and WaitEventException git-svn-id: https://pykd.svn.codeplex.com/svn@69518 9b283d60-5439-405e-af05-b73fd8c4d996
This commit is contained in:
parent
5ac233a473
commit
8596f87b72
@ -30,7 +30,12 @@ setExecutionStatus()
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ( FAILED( hres ) )
|
if ( FAILED( hres ) )
|
||||||
|
{
|
||||||
|
if (E_UNEXPECTED == hres)
|
||||||
|
throw WaitEventException();
|
||||||
|
|
||||||
throw DbgException( "IDebugControl::WaitForEvent failed" );
|
throw DbgException( "IDebugControl::WaitForEvent failed" );
|
||||||
|
}
|
||||||
|
|
||||||
hres = dbgExt->control->GetExecutionStatus( ¤tStatus );
|
hres = dbgExt->control->GetExecutionStatus( ¤tStatus );
|
||||||
|
|
||||||
|
@ -43,12 +43,110 @@ HRESULT debugEvent::GetInterestMask(
|
|||||||
*Mask |= DEBUG_EVENT_LOAD_MODULE;
|
*Mask |= DEBUG_EVENT_LOAD_MODULE;
|
||||||
*Mask |= DEBUG_EVENT_UNLOAD_MODULE;
|
*Mask |= DEBUG_EVENT_UNLOAD_MODULE;
|
||||||
*Mask |= DEBUG_EVENT_SESSION_STATUS;
|
*Mask |= DEBUG_EVENT_SESSION_STATUS;
|
||||||
|
*Mask |= DEBUG_EVENT_EXCEPTION;
|
||||||
|
*Mask |= DEBUG_EVENT_BREAKPOINT;
|
||||||
|
|
||||||
return S_OK;
|
return S_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
HRESULT debugEvent::Breakpoint(
|
||||||
|
__in PDEBUG_BREAKPOINT Bp
|
||||||
|
)
|
||||||
|
{
|
||||||
|
boost::python::dict bpParameters;
|
||||||
|
|
||||||
|
HRESULT hres;
|
||||||
|
ULONG Value = 0;
|
||||||
|
ULONG Value2 = 0;
|
||||||
|
ULONG64 Value64 = 0;
|
||||||
|
std::string str;
|
||||||
|
|
||||||
|
#define _ADD_BP_ULONG(x) \
|
||||||
|
hres = Bp->Get##x(&Value); \
|
||||||
|
BOOST_ASSERT( SUCCEEDED( hres ) || hres == E_NOINTERFACE ); \
|
||||||
|
if (SUCCEEDED( hres )) \
|
||||||
|
bpParameters[#x] = Value;
|
||||||
|
|
||||||
|
#define _ADD_BP_ULONG2(x, n1, n2) \
|
||||||
|
hres = Bp->Get##x(&Value, &Value2); \
|
||||||
|
BOOST_ASSERT( SUCCEEDED( hres ) || hres == E_NOINTERFACE ); \
|
||||||
|
if (SUCCEEDED( hres )) \
|
||||||
|
{ \
|
||||||
|
bpParameters[n1] = Value; bpParameters[n2] = Value2; \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define _ADD_BP_ULONG64(x) \
|
||||||
|
hres = Bp->Get##x(&Value64); \
|
||||||
|
BOOST_ASSERT( SUCCEEDED( hres ) || hres == E_NOINTERFACE ); \
|
||||||
|
if (SUCCEEDED( hres )) \
|
||||||
|
bpParameters[#x] = Value64;
|
||||||
|
|
||||||
|
#define _ADD_BP_STR(x) \
|
||||||
|
Value = 0; \
|
||||||
|
Bp->Get##x(NULL, 0, &Value); \
|
||||||
|
if (Value) \
|
||||||
|
{ \
|
||||||
|
str.resize(Value + 1); \
|
||||||
|
BOOST_VERIFY( SUCCEEDED( \
|
||||||
|
Bp->Get##x(&str[0], (ULONG)str.size(), NULL) \
|
||||||
|
) ); \
|
||||||
|
if (!str.empty()) bpParameters[#x] = str.c_str(); \
|
||||||
|
}
|
||||||
|
|
||||||
|
_ADD_BP_ULONG(Id);
|
||||||
|
_ADD_BP_ULONG2(Type, "BreakType", "ProcType");
|
||||||
|
_ADD_BP_ULONG(Flags);
|
||||||
|
_ADD_BP_ULONG64(Offset);
|
||||||
|
_ADD_BP_ULONG2(DataParameters, "Size", "AccessType");
|
||||||
|
_ADD_BP_ULONG(PassCount);
|
||||||
|
_ADD_BP_ULONG(CurrentPassCount);
|
||||||
|
_ADD_BP_ULONG(MatchThreadId);
|
||||||
|
_ADD_BP_STR(Command);
|
||||||
|
_ADD_BP_STR(OffsetExpression);
|
||||||
|
|
||||||
|
#undef _ADD_BP_ULONG
|
||||||
|
#undef _ADD_BP_ULONG2
|
||||||
|
#undef _ADD_BP_ULONG64
|
||||||
|
#undef _ADD_BP_STR
|
||||||
|
|
||||||
|
PyThread_StateSave pyThreadSave;
|
||||||
|
return onException(bpParameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
HRESULT debugEvent::Exception(
|
||||||
|
__in PEXCEPTION_RECORD64 Exception,
|
||||||
|
__in ULONG FirstChance
|
||||||
|
)
|
||||||
|
{
|
||||||
|
boost::python::list exceptParams;
|
||||||
|
boost::python::dict exceptData;
|
||||||
|
|
||||||
|
// build list of parameters
|
||||||
|
for (ULONG i = 0; i < Exception->NumberParameters; ++i)
|
||||||
|
exceptParams.append(Exception->ExceptionInformation[i]);
|
||||||
|
|
||||||
|
// build dict of exception data
|
||||||
|
#define _ADD_EXCEPTION_ENTRY(x) exceptData[#x] = Exception->Exception##x
|
||||||
|
_ADD_EXCEPTION_ENTRY(Code);
|
||||||
|
_ADD_EXCEPTION_ENTRY(Flags);
|
||||||
|
_ADD_EXCEPTION_ENTRY(Record);
|
||||||
|
_ADD_EXCEPTION_ENTRY(Address);
|
||||||
|
#undef _ADD_EXCEPTION_ENTRY
|
||||||
|
|
||||||
|
exceptData["Parameters"] = exceptParams;
|
||||||
|
|
||||||
|
exceptData["FirstChance"] = (0 != FirstChance);
|
||||||
|
|
||||||
|
PyThread_StateSave pyThreadSave;
|
||||||
|
return onException(exceptData);
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
HRESULT debugEvent::LoadModule(
|
HRESULT debugEvent::LoadModule(
|
||||||
__in ULONG64 ImageFileHandle,
|
__in ULONG64 ImageFileHandle,
|
||||||
__in ULONG64 BaseOffset,
|
__in ULONG64 BaseOffset,
|
||||||
|
@ -16,6 +16,10 @@ public:
|
|||||||
|
|
||||||
virtual ~debugEvent();
|
virtual ~debugEvent();
|
||||||
|
|
||||||
|
virtual ULONG onBreakpoint(boost::python::dict &/*bpParameters*/) = 0;
|
||||||
|
|
||||||
|
virtual ULONG onException(boost::python::dict &/*exceptData*/) = 0;
|
||||||
|
|
||||||
virtual ULONG onLoadModule(const dbgModuleClass &/* module */) = 0;
|
virtual ULONG onLoadModule(const dbgModuleClass &/* module */) = 0;
|
||||||
|
|
||||||
virtual ULONG onUnloadModule(const dbgModuleClass &/* module */) = 0;
|
virtual ULONG onUnloadModule(const dbgModuleClass &/* module */) = 0;
|
||||||
@ -33,6 +37,16 @@ private:
|
|||||||
__out PULONG Mask
|
__out PULONG Mask
|
||||||
);
|
);
|
||||||
|
|
||||||
|
STDMETHOD(Breakpoint)(
|
||||||
|
__in PDEBUG_BREAKPOINT Bp
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
STDMETHOD(Exception)(
|
||||||
|
__in PEXCEPTION_RECORD64 Exception,
|
||||||
|
__in ULONG FirstChance
|
||||||
|
);
|
||||||
|
|
||||||
STDMETHOD(LoadModule)(
|
STDMETHOD(LoadModule)(
|
||||||
__in ULONG64 ImageFileHandle,
|
__in ULONG64 ImageFileHandle,
|
||||||
__in ULONG64 BaseOffset,
|
__in ULONG64 BaseOffset,
|
||||||
@ -68,6 +82,13 @@ class debugEventWrap : public boost::python::wrapper<debugEvent>, public debugEv
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
ULONG onBreakpoint(boost::python::dict &bpParameters) {
|
||||||
|
return handler<boost::python::dict &>("onBreakpoint", bpParameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
ULONG onException(boost::python::dict &exceptData) {
|
||||||
|
return handler<boost::python::dict &>("onException", exceptData);
|
||||||
|
}
|
||||||
|
|
||||||
ULONG onLoadModule(const dbgModuleClass &module) {
|
ULONG onLoadModule(const dbgModuleClass &module) {
|
||||||
return handler<const dbgModuleClass &>("onLoadModule", module );
|
return handler<const dbgModuleClass &>("onLoadModule", module );
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
// òèïû èñêëþ÷åíèé
|
// òèïû èñêëþ÷åíèé
|
||||||
|
|
||||||
PyObject *baseExceptionType = NULL;
|
PyObject *baseExceptionType = NULL;
|
||||||
|
PyObject *eventExceptionType = NULL;
|
||||||
PyObject *typeExceptionType = NULL;
|
PyObject *typeExceptionType = NULL;
|
||||||
PyObject *memoryExceptionType = NULL;
|
PyObject *memoryExceptionType = NULL;
|
||||||
|
|
||||||
@ -22,6 +23,15 @@ void DbgException::exceptionTranslate( const DbgException &e )
|
|||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
void WaitEventException::exceptionTranslate( const WaitEventException &e )
|
||||||
|
{
|
||||||
|
boost::python::object pyExcept(e);
|
||||||
|
|
||||||
|
PyErr_SetObject( eventExceptionType, pyExcept.ptr());
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
void TypeException::exceptionTranslate( const TypeException &e )
|
void TypeException::exceptionTranslate( const TypeException &e )
|
||||||
{
|
{
|
||||||
boost::python::object pyExcept(e);
|
boost::python::object pyExcept(e);
|
||||||
|
@ -10,19 +10,29 @@ class DbgException : public std::exception
|
|||||||
public:
|
public:
|
||||||
|
|
||||||
DbgException( const std::string &desc ) :
|
DbgException( const std::string &desc ) :
|
||||||
std::exception( desc.c_str() )
|
std::exception( desc.c_str() )
|
||||||
{}
|
{}
|
||||||
|
|
||||||
const char* getDesc() const {
|
const char* getDesc() const {
|
||||||
return what();
|
return what();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static
|
static
|
||||||
void
|
void
|
||||||
exceptionTranslate(const DbgException &e );
|
exceptionTranslate(const DbgException &e );
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class WaitEventException : public DbgException
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
WaitEventException()
|
||||||
|
: DbgException( "none of the targets could generate events" )
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
static void exceptionTranslate(const WaitEventException &e);
|
||||||
|
};
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
class TypeException : public DbgException
|
class TypeException : public DbgException
|
||||||
@ -114,6 +124,7 @@ private:
|
|||||||
/////////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
extern PyObject *baseExceptionType;
|
extern PyObject *baseExceptionType;
|
||||||
|
extern PyObject *eventExceptionType;
|
||||||
extern PyObject *typeExceptionType;
|
extern PyObject *typeExceptionType;
|
||||||
extern PyObject *memoryExceptionType;
|
extern PyObject *memoryExceptionType;
|
||||||
|
|
||||||
|
@ -355,11 +355,24 @@ BOOST_PYTHON_MODULE( pykd )
|
|||||||
|
|
||||||
boost::python::class_<debugEventWrap, boost::noncopyable>( "debugEvent",
|
boost::python::class_<debugEventWrap, boost::noncopyable>( "debugEvent",
|
||||||
"Base class for debug events handlers" )
|
"Base class for debug events handlers" )
|
||||||
|
.def( "onBreakpoint", &debugEventWrap::onBreakpoint,
|
||||||
|
"Triggered breakpoint event. Parameter is dict:\n"
|
||||||
|
"{\"Id\":int, \"BreakType\":int, \"ProcType\":int, \"Flags\":int, \"Offset\":int,"
|
||||||
|
" \"Size\":int, \"AccessType\":int, \"PassCount\":int, \"CurrentPassCount\":int,"
|
||||||
|
" \"MatchThreadId\":int, \"Command\":str, \"OffsetExpression\":str}\n"
|
||||||
|
"Detailed information: http://msdn.microsoft.com/en-us/library/ff539284(VS.85).aspx \n"
|
||||||
|
"For ignore event method must return DEBUG_STATUS_NO_CHANGE value" )
|
||||||
|
.def( "onException", &debugEventWrap::onException,
|
||||||
|
"Exception event. Parameter is dict:\n"
|
||||||
|
"{\"Code\":int, \"Flags\":int, \"Record\":int, \"Address\":int,"
|
||||||
|
" \"Parameters\":list_of_int, \"FirstChance\":bool}\n"
|
||||||
|
"Detailed information: http://msdn.microsoft.com/en-us/library/aa363082(VS.85).aspx \n"
|
||||||
|
"For ignore event method must return DEBUG_STATUS_NO_CHANGE value" )
|
||||||
.def( "onLoadModule", &debugEventWrap::onLoadModule,
|
.def( "onLoadModule", &debugEventWrap::onLoadModule,
|
||||||
"Load module event. Parameter is instance of dbgModuleClass. "
|
"Load module event. Parameter is instance of dbgModuleClass.\n"
|
||||||
"For ignore event method must return DEBUG_STATUS_NO_CHANGE value" )
|
"For ignore event method must return DEBUG_STATUS_NO_CHANGE value" )
|
||||||
.def( "onUnloadModule", &debugEventWrap::onUnloadModule,
|
.def( "onUnloadModule", &debugEventWrap::onUnloadModule,
|
||||||
"Unload module event. Parameter is instance of dbgModuleClass. "
|
"Unload module event. Parameter is instance of dbgModuleClass.\n"
|
||||||
"For ignore event method must return DEBUG_STATUS_NO_CHANGE value" );
|
"For ignore event method must return DEBUG_STATUS_NO_CHANGE value" );
|
||||||
|
|
||||||
boost::python::class_<disasm>("disasm", "Class disassemble a processor instructions", boost::python::no_init )
|
boost::python::class_<disasm>("disasm", "Class disassemble a processor instructions", boost::python::no_init )
|
||||||
@ -384,6 +397,10 @@ BOOST_PYTHON_MODULE( pykd )
|
|||||||
.def( "desc", &DbgException::getDesc,
|
.def( "desc", &DbgException::getDesc,
|
||||||
"Get exception description" );
|
"Get exception description" );
|
||||||
|
|
||||||
|
boost::python::class_<WaitEventException, boost::python::bases<DbgException> > waitExceptionClass( "WaitEventException",
|
||||||
|
"Type exception class",
|
||||||
|
boost::python::no_init );
|
||||||
|
|
||||||
boost::python::class_<TypeException, boost::python::bases<DbgException> > typeExceptionClass( "TypeException",
|
boost::python::class_<TypeException, boost::python::bases<DbgException> > typeExceptionClass( "TypeException",
|
||||||
"Type exception class",
|
"Type exception class",
|
||||||
boost::python::no_init );
|
boost::python::no_init );
|
||||||
@ -398,10 +415,12 @@ BOOST_PYTHON_MODULE( pykd )
|
|||||||
"Return target address" );
|
"Return target address" );
|
||||||
|
|
||||||
baseExceptionType = dbgExceptionClass.ptr();
|
baseExceptionType = dbgExceptionClass.ptr();
|
||||||
|
eventExceptionType = waitExceptionClass.ptr();
|
||||||
typeExceptionType = typeExceptionClass.ptr();
|
typeExceptionType = typeExceptionClass.ptr();
|
||||||
memoryExceptionType = memoryExceptionClass.ptr();
|
memoryExceptionType = memoryExceptionClass.ptr();
|
||||||
|
|
||||||
boost::python::register_exception_translator<DbgException>( &DbgException::exceptionTranslate );
|
boost::python::register_exception_translator<DbgException>( &DbgException::exceptionTranslate );
|
||||||
|
boost::python::register_exception_translator<WaitEventException>( &WaitEventException::exceptionTranslate );
|
||||||
boost::python::register_exception_translator<TypeException>( &TypeException::exceptionTranslate );
|
boost::python::register_exception_translator<TypeException>( &TypeException::exceptionTranslate );
|
||||||
boost::python::register_exception_translator<IndexException>( &IndexException::translate);
|
boost::python::register_exception_translator<IndexException>( &IndexException::translate);
|
||||||
boost::python::register_exception_translator<MemoryException>( &MemoryException::translate );
|
boost::python::register_exception_translator<MemoryException>( &MemoryException::translate );
|
||||||
|
97
samples/watchDog.py
Normal file
97
samples/watchDog.py
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
"""
|
||||||
|
Exception watchdog
|
||||||
|
"""
|
||||||
|
|
||||||
|
from pykd import *
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# known exception codes
|
||||||
|
knownExcepCodes = {
|
||||||
|
0xc0000005 : "EXCEPTION_ACCESS_VIOLATION",
|
||||||
|
0x80000002 : "EXCEPTION_DATATYPE_MISALIGNMENT",
|
||||||
|
0x80000003 : "EXCEPTION_BREAKPOINT",
|
||||||
|
0x80000004 : "EXCEPTION_SINGLE_STEP",
|
||||||
|
0xc000008c : "EXCEPTION_ARRAY_BOUNDS_EXCEEDED",
|
||||||
|
0xc000008d : "EXCEPTION_FLT_DENORMAL_OPERAND",
|
||||||
|
0xc000008e : "EXCEPTION_FLT_DIVIDE_BY_ZERO",
|
||||||
|
0xc000008f : "EXCEPTION_FLT_INEXACT_RESULT",
|
||||||
|
0xc0000090 : "EXCEPTION_FLT_INVALID_OPERATION",
|
||||||
|
0xc0000091 : "EXCEPTION_FLT_OVERFLOW",
|
||||||
|
0xc0000092 : "EXCEPTION_FLT_STACK_CHECK",
|
||||||
|
0xc0000093 : "EXCEPTION_FLT_UNDERFLOW",
|
||||||
|
0xc0000094 : "EXCEPTION_INT_DIVIDE_BY_ZERO",
|
||||||
|
0xc0000095 : "EXCEPTION_INT_OVERFLOW",
|
||||||
|
0xc0000096 : "EXCEPTION_PRIV_INSTRUCTION",
|
||||||
|
0xc0000006 : "EXCEPTION_IN_PAGE_ERROR",
|
||||||
|
0xc000001d : "EXCEPTION_ILLEGAL_INSTRUCTION",
|
||||||
|
0xc0000025 : "EXCEPTION_NONCONTINUABLE_EXCEPTION",
|
||||||
|
0xc00000fd : "EXCEPTION_STACK_OVERFLOW",
|
||||||
|
0xc0000026 : "EXCEPTION_INVALID_DISPOSITION",
|
||||||
|
0x80000001 : "EXCEPTION_GUARD_PAGE",
|
||||||
|
0xc0000008 : "EXCEPTION_INVALID_HANDLE",
|
||||||
|
0xc0000194 : "EXCEPTION_POSSIBLE_DEADLOCK",
|
||||||
|
0xc000013a : "CONTROL_C_EXIT"
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class ExceptionHandler(debugEvent):
|
||||||
|
|
||||||
|
def onException(self, exceptData):
|
||||||
|
dprintln("\n *** shit happens")
|
||||||
|
|
||||||
|
exceptCode = exceptData["Code"]
|
||||||
|
dprint("Exception code : ")
|
||||||
|
if exceptCode in knownExcepCodes:
|
||||||
|
dprintln( knownExcepCodes[exceptCode] )
|
||||||
|
else:
|
||||||
|
dprintln( "0x%08x" % exceptCode )
|
||||||
|
|
||||||
|
dprint("Exception flags : ")
|
||||||
|
exceptFlags = exceptData["Flags"]
|
||||||
|
if exceptFlags:
|
||||||
|
if exceptFlags & NONCONTINUABLE_EXCEPTION:
|
||||||
|
exceptFlags &= ~NONCONTINUABLE_EXCEPTION
|
||||||
|
dprint( "NONCONTINUABLE " )
|
||||||
|
if exceptFlags:
|
||||||
|
dprintln( "| 0x%02x" % exceptFlags)
|
||||||
|
else:
|
||||||
|
dprintln( "" )
|
||||||
|
else:
|
||||||
|
dprintln( "0" )
|
||||||
|
|
||||||
|
dprintln("Exception record : 0x%X" % exceptData["Record"])
|
||||||
|
|
||||||
|
exceptAddr = exceptData["Address"]
|
||||||
|
dprintln("\nException address : 0x%X" % exceptAddr)
|
||||||
|
dprintln( dbgCommand("ln 0x%X" % exceptAddr) )
|
||||||
|
|
||||||
|
if len( exceptData["Parameters"] ):
|
||||||
|
dprintln("Parameters : ")
|
||||||
|
for param in exceptData["Parameters"]:
|
||||||
|
dprintln("\t0x%X" % param)
|
||||||
|
|
||||||
|
dprintln("\nFirst chance : " + str( exceptData["FirstChance"] ))
|
||||||
|
|
||||||
|
dprintln( "\n " + dbgCommand( "r" ) )
|
||||||
|
dbgCommand( ".reload" )
|
||||||
|
dprintln( dbgCommand( "kb" ) )
|
||||||
|
|
||||||
|
return DEBUG_STATUS_BREAK
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
|
||||||
|
if len(sys.argv) != 1:
|
||||||
|
startComamnd = ""
|
||||||
|
for i in range(1, len(sys.argv)):
|
||||||
|
startComamnd += sys.argv[i] + " "
|
||||||
|
startProcess(startComamnd)
|
||||||
|
|
||||||
|
exceptionHandler = ExceptionHandler()
|
||||||
|
|
||||||
|
try:
|
||||||
|
go()
|
||||||
|
|
||||||
|
except WaitEventException:
|
||||||
|
dprintln("none of the targets could generate events")
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user