694 lines
17 KiB
C++
694 lines
17 KiB
C++
|
#include <windows.h>
|
||
|
#include "faultrep.h"
|
||
|
#include "util.h"
|
||
|
#include "stdio.h"
|
||
|
#include "malloc.h"
|
||
|
|
||
|
BOOL g_fDebug = FALSE;
|
||
|
|
||
|
enum EFaultType
|
||
|
{
|
||
|
eftAV = 0,
|
||
|
eftMisalign,
|
||
|
eftArrayBound,
|
||
|
|
||
|
eftStackOverflowFunc,
|
||
|
eftStackOverflowAlloc,
|
||
|
|
||
|
eftInstrPriv,
|
||
|
eftInstrBad,
|
||
|
|
||
|
eftIntDivZero,
|
||
|
eftIntOverflow,
|
||
|
|
||
|
eftFltDivZero,
|
||
|
eftFltOverflow,
|
||
|
eftFltUnderflow,
|
||
|
eftFltStack,
|
||
|
eftFltInexact,
|
||
|
eftFltDenormal,
|
||
|
eftFltInvalid,
|
||
|
|
||
|
eftExBadRet,
|
||
|
eftExNonCont,
|
||
|
};
|
||
|
|
||
|
// ***************************************************************************
|
||
|
void ShowUsage(void)
|
||
|
{
|
||
|
printf("\n");
|
||
|
printf("gpfme [command] [exception type]\n");
|
||
|
printf("\nCommand options:\n");
|
||
|
printf(" -a: Access violation (default if no command is specified)\n");
|
||
|
#ifndef _WIN64
|
||
|
printf(" -b: Array bound violation\n");
|
||
|
#endif
|
||
|
printf(" -i: Integer divide by 0 (default)\n");
|
||
|
printf(" -iz: Integer divide by 0\n");
|
||
|
printf(" -io: Integer overflow\n");
|
||
|
#ifdef _WIN64
|
||
|
printf(" -m: Data misalignment fault (not available on x86)\n");
|
||
|
#endif
|
||
|
printf(" -s: Stack overflow via infinite recursion (default) \n");
|
||
|
printf(" -sa: Stack overflow via alloca\n");
|
||
|
printf(" -sf: Stack overflow via infinite recursion\n");
|
||
|
printf(" -f: Floating point divide by 0 (default)\n");
|
||
|
printf(" -fi: Floating point inexact result\n");
|
||
|
printf(" -fn: Floating point invalid operation\n");
|
||
|
printf(" -fo: Floating point overflow\n");
|
||
|
printf(" -fu: Floating point underflow\n");
|
||
|
printf(" -fz: Floating point divide by 0\n");
|
||
|
printf(" -n: Execute privilidged instruction (default)\n");
|
||
|
printf(" -ni: Execute invalid instruction\n");
|
||
|
printf(" -np: Execute privilidged instruction\n");
|
||
|
}
|
||
|
|
||
|
|
||
|
// ***************************************************************************
|
||
|
LONG MyFilter(EXCEPTION_POINTERS *pep)
|
||
|
{
|
||
|
static BOOL fGotHere = FALSE;
|
||
|
|
||
|
EFaultRepRetVal frrv;
|
||
|
pfn_REPORTFAULT pfn = NULL;
|
||
|
HMODULE hmodFaultRep = NULL;
|
||
|
WCHAR szDebugger[2 * MAX_PATH];
|
||
|
BOOL fGotDbgr = FALSE;
|
||
|
char wszMsg[2048];
|
||
|
|
||
|
|
||
|
if (g_fDebug &&
|
||
|
(pep->ExceptionRecord->ExceptionCode == EXCEPTION_BREAKPOINT ||
|
||
|
pep->ExceptionRecord->ExceptionCode == EXCEPTION_SINGLE_STEP))
|
||
|
return EXCEPTION_CONTINUE_SEARCH;
|
||
|
|
||
|
if (g_fDebug && fGotHere)
|
||
|
return EXCEPTION_CONTINUE_SEARCH;
|
||
|
|
||
|
fGotHere = TRUE;
|
||
|
|
||
|
if (g_fDebug)
|
||
|
DebugBreak();
|
||
|
|
||
|
if (GetProfileStringW(L"AeDebug", L"Debugger", NULL, szDebugger, sizeofSTRW(szDebugger) - 1))
|
||
|
fGotDbgr = TRUE;
|
||
|
|
||
|
hmodFaultRep = LoadLibrary("faultrep.dll");
|
||
|
if (hmodFaultRep != NULL)
|
||
|
{
|
||
|
pfn = (pfn_REPORTFAULT)GetProcAddress(hmodFaultRep, "ReportFault");
|
||
|
if (pfn != NULL)
|
||
|
{
|
||
|
frrv = (*pfn)(pep, fGotDbgr);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
printf("Unable to get ReportFault function\n");
|
||
|
return EXCEPTION_CONTINUE_SEARCH;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
else
|
||
|
{
|
||
|
printf("Unable to load faultrep.dll\n");
|
||
|
return EXCEPTION_CONTINUE_SEARCH;
|
||
|
}
|
||
|
|
||
|
switch(frrv)
|
||
|
{
|
||
|
case frrvOk:
|
||
|
printf("DW completed fine.\n");
|
||
|
break;
|
||
|
|
||
|
case frrvOkManifest:
|
||
|
printf("DW completed fine in manifest mode.\n");
|
||
|
break;
|
||
|
|
||
|
case frrvOkQueued:
|
||
|
printf("DW completed fine in queue mode.\n");
|
||
|
break;
|
||
|
|
||
|
case frrvLaunchDebugger:
|
||
|
printf("DW completed fine & says to launch debugger.\n");
|
||
|
break;
|
||
|
|
||
|
case frrvErrNoDW:
|
||
|
printf("DW could not be launched.\n");
|
||
|
break;
|
||
|
|
||
|
case frrvErr:
|
||
|
printf("An error occurred.\n");
|
||
|
break;
|
||
|
|
||
|
case frrvErrTimeout:
|
||
|
printf("Timed out waiting for DW to complete.\n");
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
printf("unexpected return value...\n");
|
||
|
break;
|
||
|
|
||
|
}
|
||
|
|
||
|
if (hmodFaultRep != NULL)
|
||
|
FreeLibrary(hmodFaultRep);
|
||
|
|
||
|
return EXCEPTION_EXECUTE_HANDLER;
|
||
|
};
|
||
|
|
||
|
|
||
|
// ***************************************************************************
|
||
|
void NukeStack(void)
|
||
|
{
|
||
|
DWORD rgdw[512];
|
||
|
DWORD a = 1;
|
||
|
|
||
|
// the compiler tries to be smart and not let me deliberately write a
|
||
|
// infinitely recursive function, so gotta do this to work around it.
|
||
|
if(a == 1)
|
||
|
NukeStack();
|
||
|
}
|
||
|
|
||
|
// ***************************************************************************
|
||
|
typedef DWORD (*FAULT_FN)(void);
|
||
|
void DoException(EFaultType eft)
|
||
|
{
|
||
|
switch(eft)
|
||
|
{
|
||
|
// access violation
|
||
|
default:
|
||
|
case eftAV:
|
||
|
{
|
||
|
int *p = NULL;
|
||
|
|
||
|
fprintf(stderr, "Generating an access violation\n");
|
||
|
*p = 1;
|
||
|
fprintf(stderr, "Error: No exception thrown.\n");
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
#ifdef _WIN64
|
||
|
// data misalignment
|
||
|
case eftMisalign:
|
||
|
{
|
||
|
DWORD *pdw;
|
||
|
BYTE rg[8];
|
||
|
|
||
|
fprintf(stderr, "Generating an misalignment fault.\n");
|
||
|
pdw = (DWORD *)&rg[2];
|
||
|
*pdw = 1;
|
||
|
fprintf(stderr, "Error: No exception thrown.\n");
|
||
|
break;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
#ifndef _WIN64
|
||
|
// array bounds failure
|
||
|
case eftArrayBound:
|
||
|
{
|
||
|
fprintf(stderr, "Generating an out-of-bounds exception\n");
|
||
|
|
||
|
__int64 li;
|
||
|
DWORD *pdw = (DWORD *)&li;
|
||
|
|
||
|
*pdw = 1;
|
||
|
pdw++;
|
||
|
*pdw = 2;
|
||
|
|
||
|
// bound will throw an 'array out of bounds' exception
|
||
|
_asm mov eax, 0
|
||
|
_asm bound eax, li
|
||
|
fprintf(stderr, "Error: No exception thrown.\n");
|
||
|
break;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
// stack overflow
|
||
|
case eftStackOverflowFunc:
|
||
|
{
|
||
|
fprintf(stderr, "Generating an stack overflow via recursion\n");
|
||
|
NukeStack();
|
||
|
fprintf(stderr, "Error: No exception thrown.\n");
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// stack overflow
|
||
|
case eftStackOverflowAlloc:
|
||
|
{
|
||
|
LPVOID pv;
|
||
|
DWORD i;
|
||
|
|
||
|
fprintf(stderr, "Generating an stack overflow via alloca\n");
|
||
|
for (i = 0; i < 0xffffffff; i++)
|
||
|
pv = _alloca(512);
|
||
|
fprintf(stderr, "Error: No exception thrown.\n");
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// integer divide by 0
|
||
|
case eftIntDivZero:
|
||
|
{
|
||
|
DWORD x = 4, y = 0;
|
||
|
|
||
|
fprintf(stderr, "Generating an integer div by 0\n");
|
||
|
x = x / y;
|
||
|
fprintf(stderr, "Error: No exception thrown.\n");
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// integer overflow
|
||
|
case eftIntOverflow:
|
||
|
{
|
||
|
|
||
|
fprintf(stderr, "Generating an integer overflow\n");
|
||
|
#ifdef _WIN64
|
||
|
__int64 x = 0x7fffffffffffffff, y = 0x7fffffffffffffff;
|
||
|
x = x + y;
|
||
|
#else
|
||
|
DWORD x = 0x7fffffff, y = 0x7fffffff;
|
||
|
x = x + y;
|
||
|
_asm into
|
||
|
#endif
|
||
|
fprintf(stderr, "Error: No exception thrown.\n");
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// floating point divide by 0
|
||
|
case eftFltDivZero:
|
||
|
{
|
||
|
double x = 4.1, y = 0.0;
|
||
|
WORD wCtl, wCtlNew;
|
||
|
|
||
|
fprintf(stderr, "Generating an floating point div by 0\n");
|
||
|
|
||
|
#ifdef _WIN64
|
||
|
x = x / y;
|
||
|
#else
|
||
|
// got to unmask the floating point exceptions so that the
|
||
|
// processor doesn't handle them on it's own
|
||
|
_asm fnstcw wCtl
|
||
|
wCtlNew = wCtl & 0xffc0;
|
||
|
_asm fldcw wCtlNew
|
||
|
x = x / y;
|
||
|
_asm fldcw wCtl
|
||
|
#endif
|
||
|
fprintf(stderr, "Error: No exception thrown.\n");
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// floating point stack overflow
|
||
|
case eftFltStack:
|
||
|
{
|
||
|
double x;
|
||
|
WORD wCtl, wCtlNew;
|
||
|
|
||
|
fprintf(stderr, "Generating an floating point stack overflow\n");
|
||
|
|
||
|
#ifdef _WIN64
|
||
|
#else
|
||
|
// Got to unmask the floating point exceptions so that the
|
||
|
// processor doesn't handle them on it's own
|
||
|
_asm fnstcw wCtl
|
||
|
wCtlNew = wCtl & 0xffc0;
|
||
|
_asm fldcw wCtlNew
|
||
|
|
||
|
// there should be 8 floating point stack registers, so attempting
|
||
|
// to add a 9th element should nuke it
|
||
|
_asm fld x
|
||
|
_asm fld x
|
||
|
_asm fld x
|
||
|
_asm fld x
|
||
|
_asm fld x
|
||
|
_asm fld x
|
||
|
_asm fld x
|
||
|
_asm fld x
|
||
|
_asm fld x
|
||
|
_asm fldcw wCtl
|
||
|
#endif
|
||
|
fprintf(stderr, "Error: No exception thrown.\n");
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// floating point overflow
|
||
|
case eftFltOverflow:
|
||
|
{
|
||
|
double x = 1.0, y = 10.0;
|
||
|
WORD wCtl, wCtlNew;
|
||
|
DWORD i;
|
||
|
|
||
|
fprintf(stderr, "Generating an floating point overflow\n");
|
||
|
|
||
|
#ifdef _WIN64
|
||
|
#else
|
||
|
// Got to unmask the floating point exceptions so that the
|
||
|
// processor doesn't handle them on it's own
|
||
|
_asm fnstcw wCtl
|
||
|
wCtlNew = wCtl & 0xffe0;
|
||
|
_asm fldcw wCtlNew
|
||
|
#endif
|
||
|
|
||
|
for(i = 0; i < 100000; i++)
|
||
|
x = x * y;
|
||
|
|
||
|
fprintf(stderr, "Error: No exception thrown.\n");
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// floating point invalid op
|
||
|
case eftFltInvalid:
|
||
|
{
|
||
|
double x = 1.0, y = 10.0;
|
||
|
WORD wCtl, wCtlNew;
|
||
|
DWORD i;
|
||
|
|
||
|
#ifdef _WIN64
|
||
|
#else
|
||
|
// Got to unmask the floating point exceptions so that the
|
||
|
// processor doesn't handle them on it's own
|
||
|
_asm fnstcw wCtl
|
||
|
wCtlNew = wCtl & 0xffe0;
|
||
|
_asm fldcw wCtlNew
|
||
|
#endif
|
||
|
|
||
|
fprintf(stderr, "Generating an floating point invalid operation\n");
|
||
|
for(i = 0; i < 100000; i++)
|
||
|
x = x / y;
|
||
|
|
||
|
fprintf(stderr, "Error: No exception thrown.\n");
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// floating point inexact result
|
||
|
case eftFltInexact:
|
||
|
{
|
||
|
double x = 1.0, y = 10.0;
|
||
|
WORD wCtl, wCtlNew;
|
||
|
DWORD i;
|
||
|
|
||
|
#ifdef _WIN64
|
||
|
#else
|
||
|
// Got to unmask the floating point exceptions so that the
|
||
|
// processor doesn't handle them on it's own
|
||
|
_asm fnstcw wCtl
|
||
|
wCtlNew = wCtl & 0xffc0;
|
||
|
_asm fldcw wCtlNew
|
||
|
#endif
|
||
|
|
||
|
fprintf(stderr, "Generating an floating point underflow\n");
|
||
|
for(i = 0; i < 100000; i++)
|
||
|
x = x / y;
|
||
|
|
||
|
fprintf(stderr, "Error: No exception thrown.\n");
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// floating point denormal value
|
||
|
case eftFltDenormal:
|
||
|
{
|
||
|
double x = 1.0;
|
||
|
DWORD i;
|
||
|
WORD wCtl, wCtlNew;
|
||
|
BYTE rg[8] = { 1, 0, 0, 0, 0, 0, 6, 0 };
|
||
|
|
||
|
fprintf(stderr, "Generating a floating point denormal exception\n");
|
||
|
|
||
|
#ifdef _WIN64
|
||
|
#else
|
||
|
// Got to unmask the floating point exceptions so that the
|
||
|
// processor doesn't handle them on it's own
|
||
|
memcpy(&x, rg, 8);
|
||
|
_asm fnstcw wCtl
|
||
|
wCtlNew = wCtl & 0xf2fc;
|
||
|
_asm fldcw wCtlNew
|
||
|
_asm fld x
|
||
|
#endif
|
||
|
|
||
|
fprintf(stderr, "Error: No exception thrown.\n");
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// executing a privilidged instruction
|
||
|
case eftInstrPriv:
|
||
|
{
|
||
|
fprintf(stderr, "Generating an privilidged instruction exception\n");
|
||
|
|
||
|
#ifdef _WIN64
|
||
|
#else
|
||
|
// must be ring 0 to execute HLT
|
||
|
_asm hlt
|
||
|
#endif
|
||
|
fprintf(stderr, "Error: No exception thrown.\n");
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// executing an invalid instruction
|
||
|
case eftInstrBad:
|
||
|
{
|
||
|
FAULT_FN pfn;
|
||
|
BYTE rgc[2048];
|
||
|
|
||
|
FillMemory(rgc, sizeof(rgc), 0);
|
||
|
pfn = (FAULT_FN)(DWORD_PTR)rgc;
|
||
|
|
||
|
fprintf(stderr, "Generating an invalid instruction exception\n");
|
||
|
(*pfn)();
|
||
|
|
||
|
fprintf(stderr, "Error: No exception thrown.\n");
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case eftExNonCont:
|
||
|
{
|
||
|
fprintf(stderr, "Generating an non-continuable exception- **not implemented**\n");
|
||
|
fprintf(stderr, "Error: No exception thrown.\n");
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case eftExBadRet:
|
||
|
{
|
||
|
fprintf(stderr, "Generating an bad exception filter exception- **not implemented**\n");
|
||
|
fprintf(stderr, "Error: No exception thrown.\n");
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// ***************************************************************************
|
||
|
int __cdecl main(int argc, char **argv)
|
||
|
{
|
||
|
EFaultType eft = eftAV;
|
||
|
BOOL fCheckDebug = FALSE;
|
||
|
BOOL fUseTry = FALSE;
|
||
|
BOOL fUseFilter = FALSE;
|
||
|
|
||
|
if (argc >= 3 && (argv[2][0] == '/' || argv[2][0] == '-'))
|
||
|
{
|
||
|
switch(argv[2][1])
|
||
|
{
|
||
|
case 't':
|
||
|
case 'T':
|
||
|
if (argv[2][2] == 'D' || argv[2][2] == 'd')
|
||
|
g_fDebug = TRUE;
|
||
|
fUseTry = TRUE;
|
||
|
break;
|
||
|
|
||
|
case 'g':
|
||
|
case 'G':
|
||
|
if (argv[2][2] == 'D' || argv[2][2] == 'd')
|
||
|
g_fDebug = TRUE;
|
||
|
fUseFilter = TRUE;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (argc >= 2 && (argv[1][0] == '/' || argv[1][0] == '-'))
|
||
|
{
|
||
|
switch(argv[1][1])
|
||
|
{
|
||
|
case 't':
|
||
|
case 'T':
|
||
|
if (argv[1][2] == 'D' || argv[1][2] == 'd')
|
||
|
g_fDebug = TRUE;
|
||
|
fUseTry = TRUE;
|
||
|
break;
|
||
|
|
||
|
case 'g':
|
||
|
case 'G':
|
||
|
if (argv[1][2] == 'D' || argv[1][2] == 'd')
|
||
|
g_fDebug = TRUE;
|
||
|
fUseFilter = TRUE;
|
||
|
break;
|
||
|
|
||
|
// AV
|
||
|
default:
|
||
|
case 'a':
|
||
|
case 'A':
|
||
|
eft = eftAV;
|
||
|
break;
|
||
|
#ifndef _WIN64
|
||
|
// array bounds
|
||
|
case 'b':
|
||
|
case 'B':
|
||
|
eft = eftArrayBound;
|
||
|
break;
|
||
|
#endif
|
||
|
|
||
|
#ifdef _WIN64
|
||
|
// Misalignment
|
||
|
case 'm':
|
||
|
case 'M':
|
||
|
eft = eftMisalign;
|
||
|
break;
|
||
|
#endif
|
||
|
|
||
|
// Stack overflow
|
||
|
case 's':
|
||
|
case 'S':
|
||
|
switch(argv[1][2])
|
||
|
{
|
||
|
default:
|
||
|
case 'f':
|
||
|
case 'F':
|
||
|
eft = eftStackOverflowFunc;
|
||
|
break;
|
||
|
|
||
|
case 'a':
|
||
|
case 'A':
|
||
|
eft = eftStackOverflowAlloc;
|
||
|
break;
|
||
|
};
|
||
|
break;
|
||
|
|
||
|
// integer exceptions
|
||
|
case 'i':
|
||
|
case 'I':
|
||
|
switch(argv[1][2])
|
||
|
{
|
||
|
default:
|
||
|
case 'z':
|
||
|
case 'Z':
|
||
|
eft = eftIntDivZero;
|
||
|
break;
|
||
|
|
||
|
case 'o':
|
||
|
case 'O':
|
||
|
eft = eftIntOverflow;
|
||
|
break;
|
||
|
};
|
||
|
break;
|
||
|
|
||
|
// floating point exceptions
|
||
|
case 'f':
|
||
|
case 'F':
|
||
|
switch(argv[1][2])
|
||
|
{
|
||
|
default:
|
||
|
case 'z':
|
||
|
case 'Z':
|
||
|
eft = eftFltDivZero;
|
||
|
break;
|
||
|
|
||
|
case 'o':
|
||
|
case 'O':
|
||
|
eft = eftFltOverflow;
|
||
|
break;
|
||
|
|
||
|
case 'u':
|
||
|
case 'U':
|
||
|
eft = eftFltUnderflow;
|
||
|
break;
|
||
|
|
||
|
case 'S':
|
||
|
case 's':
|
||
|
eft = eftFltStack;
|
||
|
break;
|
||
|
|
||
|
case 'I':
|
||
|
case 'i':
|
||
|
eft = eftFltInexact;
|
||
|
break;
|
||
|
|
||
|
case 'D':
|
||
|
case 'd':
|
||
|
eft = eftFltDenormal;
|
||
|
break;
|
||
|
|
||
|
case 'N':
|
||
|
case 'n':
|
||
|
eft = eftFltInvalid;
|
||
|
break;
|
||
|
};
|
||
|
break;
|
||
|
|
||
|
// CPU instruction exceptions
|
||
|
case 'n':
|
||
|
case 'N':
|
||
|
switch(argv[1][2])
|
||
|
{
|
||
|
default:
|
||
|
case 'p':
|
||
|
case 'P':
|
||
|
eft = eftInstrPriv;
|
||
|
break;
|
||
|
|
||
|
case 'i':
|
||
|
case 'I':
|
||
|
eft = eftInstrBad;
|
||
|
break;
|
||
|
};
|
||
|
break;
|
||
|
|
||
|
case 'E':
|
||
|
case 'e':
|
||
|
switch(argv[1][2])
|
||
|
{
|
||
|
default:
|
||
|
case 'n':
|
||
|
case 'N':
|
||
|
eft = eftExNonCont;
|
||
|
break;
|
||
|
|
||
|
case 'i':
|
||
|
case 'I':
|
||
|
eft = eftExBadRet;
|
||
|
break;
|
||
|
};
|
||
|
break;
|
||
|
|
||
|
// help
|
||
|
case '?':
|
||
|
ShowUsage();
|
||
|
return 0;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
eft = eftAV;
|
||
|
}
|
||
|
|
||
|
if (fUseFilter)
|
||
|
{
|
||
|
fUseTry = FALSE;
|
||
|
SetUnhandledExceptionFilter(MyFilter);
|
||
|
}
|
||
|
|
||
|
if (fUseTry)
|
||
|
{
|
||
|
__try
|
||
|
{
|
||
|
DoException(eft);
|
||
|
}
|
||
|
__except(MyFilter(GetExceptionInformation()))
|
||
|
{
|
||
|
}
|
||
|
}
|
||
|
|
||
|
else
|
||
|
{
|
||
|
DoException(eft);
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|