// Copyright (c) 1996-2009 Nokia Corporation and/or its subsidiary(-ies).
// All rights reserved.
// This component and the accompanying materials are made available
// under the terms of the License "Eclipse Public License v1.0"
// which accompanies this distribution, and is available
// at the URL "http://www.eclipse.org/legal/epl-v10.html".
//
// Initial Contributors:
// Nokia Corporation - initial contribution.
//
// Contributors:
//
// Description:
// e32test\system\t_prot.cpp
// Overview:
// Tests that the kernel panics the user rather than dying in 
// various situations
// API Information:
// N/A
// Details:
// - Verify that printing a long string does not panic.
// - Verify that a stack overflow in a thread causes a panic.
// - Create the thread which panics a dead thread, verify that 
// it isn't panicked for doing this.
// - Create the thread which panics a bad handle, verify that 
// it isn't panicked for doing this.
// - Verify that an RSession send from an uninitialised handle
// causes a panic.
// - Verify that the thread causing an exception, for a variety
// of reasons, is panicked.
// - Verify thread writing to kernel data is panicked.
// - Verify that RAM disk access either causes a panic or is
// denied, based on the platform security settings.
// - Verify that an RThread::RequestComplete() with an invalid
// address causes a panic. Verify results are as expected.
// Platforms/Drives/Compatibility:
// All.
// Assumptions/Requirement/Pre-requisites:
// Failures and causes:
// Base Port information:
// 
//

#include <e32test.h>
#include "u32std.h"
#include <e32panic.h>
#include "../mmu/mmudetect.h"

void DoUndefinedInstruction();

const TInt KHeapSize=0x200;
const TInt KThreadReturnValue=9999;

_LIT(KLitKernExec,"KERN-EXEC");

class RSessionTest : public RSessionBase
	{
public:
	void DoSend();
	};


void RSessionTest::DoSend()
	{
    Send(34,TIpcArgs(78));
	//Send(34,(TAny*)78);
	};

LOCAL_D RTest test(_L("T_PROT"));
LOCAL_D RTest t(_L("T_PROT thread"));

enum TAction
	{
	EDataAbort=0,
	EPrefetchAbort=1,
	EUndefinedInstruction=2,
	};

typedef void (*PFV)(void);

#ifndef __MARM__
void DoUndefinedInstruction()
	{
#ifdef __GCC32__	
	asm("int 6");
#else
	_asm int 6;
#endif
	}
#endif

LOCAL_C TInt ExceptionThread(TAny* anAction)
	{
	TAction action=(TAction)((TInt) anAction);
	switch (action)
		{
		case EDataAbort:
			*(TInt*)0=0;
			break;
		case EPrefetchAbort:
			{
//			PFV f=(PFV)NULL;
			PFV f=(PFV)0x80000;	// don't use NULL since it is readable on Snowdrop
			(*f)();
			break;
			}
		case EUndefinedInstruction:
			DoUndefinedInstruction();
			break;
		};
	return 0;
	}

LOCAL_C void RunTestThread(TAction anAction)
	{
	RThread t;
	TInt r=t.Create(_L("TestThread"),ExceptionThread,KDefaultStackSize,KHeapSize,KHeapSize,(TAny*)anAction);
	test(r==KErrNone);
	TRequestStatus s;
	t.Logon(s);
	test(s==KRequestPending);
	t.Resume();
	User::WaitForRequest(s);
	test(t.ExitType()==EExitPanic);
	test(t.ExitCategory()==_L("KERN-EXEC"));
	test(t.ExitReason()==ECausedException);
	CLOSE_AND_WAIT(t);
	}

LOCAL_C TInt UnintThread(TAny* )
	{

	RSessionTest rsb;
	rsb.DoSend();
	FOREVER
		;
	}

void testSession()
//
// Test 1
//
	{
	
	RThread thread;
	TRequestStatus stat;

	test.Next(_L("Create UnintThread"));
	TInt r=thread.Create(_L("UnintThread"),UnintThread,KDefaultStackSize,KHeapSize,KHeapSize,0);
	test(r==KErrNone);

	thread.Logon(stat);
	test(thread.ExitType()==EExitPending);
	test.Next(_L("Resume UnintThread"));
	thread.Resume();
	User::WaitForRequest(stat);
	test.Next(_L("Check UnintThread panicked"));
	test(thread.ExitCategory()==_L("KERN-EXEC"));
//	test(thread.ExitReason()==EBadHandle);
	test(thread.ExitType()==EExitPanic);
	CLOSE_AND_WAIT(thread);
	}

TInt dummy(TAny*)
	{
	return(KErrNone);
	}

// Dennis - modified this to panic a dead thread rather than a bad handle
TInt pdThread(TAny*)
	{
	RThread thread;
	TRequestStatus stat;
	TInt r=thread.Create(_L("dummy"),dummy,KDefaultStackSize,KHeapSize,KHeapSize,0);
	test(r==KErrNone);
	thread.Logon(stat);
	test(thread.ExitType()==EExitPending);
	thread.Resume();
	User::WaitForRequest(stat);
	test(thread.ExitType()==EExitKill);
	test(stat.Int()==KErrNone);
	thread.Panic(_L("MYPANIC"),0x666); // this shouldn't panic pdThread
	test(thread.ExitType()==EExitKill);
	test(thread.ExitReason()==KErrNone);
	CLOSE_AND_WAIT(thread);
	return(KErrNone);
	}

void testPanicDeadThread()
//
// Dennis - modified this to panic a dead thread rather than a bad handle
// Create the thread which panics a dead thread /*bad handle*/
// Check that it isn't panicked for doing this
//
	{
	RThread thread;
	TRequestStatus stat;
	test.Next(_L("Create PanicDeadThread"));
	TInt r=thread.Create(_L("PanicDeadThread"),pdThread,KDefaultStackSize,KHeapSize,KHeapSize,0);
	test(r==KErrNone);
	thread.Logon(stat);
	test(thread.ExitType()==EExitPending);
	test.Next(_L("Resume PanicDeadThread"));
	thread.Resume();
	User::WaitForRequest(stat);
	test.Next(_L("Check PanicDeadThread did not panic"));
	test(thread.ExitReason()==KErrNone);
	test(thread.ExitType()==EExitKill);
	test(thread.ExitCategory()==_L("Kill"));
	CLOSE_AND_WAIT(thread);
	}

TInt doDeadThreadStuff(TAny*)
	{
	RThread thread;
	TRequestStatus stat;
	TInt r=thread.Create(_L("dummy2"),dummy,KDefaultStackSize,KHeapSize,KHeapSize,0);
	test(r==KErrNone);
	thread.Logon(stat);
	test(thread.ExitType()==EExitPending);
	thread.Resume();
	User::WaitForRequest(stat);

	thread.SetPriority(EPriorityNormal);

	CLOSE_AND_WAIT(thread);
	return(KErrNone);
	}

void testDeadThread()
//
// Create the thread which panics a bad handle
// Check that it isn't panicked for doing this
//
	{
	RThread thread;
	TRequestStatus stat;
	test.Next(_L("Create doDeadThreadStuff"));
	TInt r=thread.Create(_L("doDeadThreadStuff"),doDeadThreadStuff,KDefaultStackSize,KHeapSize,KHeapSize,0);
	test(r==KErrNone);
	thread.Logon(stat);
	test(thread.ExitType()==EExitPending);
	test.Next(_L("Resume doDeadThreadStuff"));
	thread.Resume();
	User::WaitForRequest(stat);
	test.Next(_L("Check doDeadThreadStuff did not panic"));
	test(thread.ExitReason()==KErrNone);
	test(thread.ExitType()==EExitKill);
	test(thread.ExitCategory()==_L("Kill"));
	CLOSE_AND_WAIT(thread);
	}

TInt MinimalThread(TAny*)
//
// Minimal thread, used in test 5
//
	{
	return(KErrNone);
	}

LOCAL_C TInt ExceptThread(TAny* )
	{

	TUint* nullPtr=0;
	*nullPtr=0xdead; // BANG!!
	return KErrNone;
	}


void testExcept()
//
// Test thread causing exception is panicked
//
	{
	
	RThread thread;
	TRequestStatus stat;

	test.Next(_L("Create ExceptThread"));
	TInt r=thread.Create(_L("ExceptThread"),ExceptThread,KDefaultStackSize,KHeapSize,KHeapSize,0);
	test(r==KErrNone);

	thread.Logon(stat);
	test(thread.ExitType()==EExitPending);
	test.Next(_L("Resume ExceptThread"));
	thread.Resume();
	User::WaitForRequest(stat);
	test.Next(_L("Check ExceptThread panicked"));
	test(thread.ExitCategory()==_L("KERN-EXEC"));
	test(thread.ExitReason()==ECausedException);
	test(thread.ExitType()==EExitPanic);
	CLOSE_AND_WAIT(thread);
	}

LOCAL_C TInt StackThread(TAny* )
	{

	TFileName heresAnotherOne;
	StackThread((TAny*)heresAnotherOne.Ptr()); // go recursive
	return KErrNone;
	}

void testStackOverflow()
//
// Thread overflowing its stack is panicked
//
	{

	RThread thread;
	TRequestStatus stat;

	test.Next(_L("Create StackThread"));
	TInt r=thread.Create(_L("StackThread"),StackThread,KDefaultStackSize,KHeapSize,KHeapSize,0);
	test(r==KErrNone);

	thread.Logon(stat);
	test(thread.ExitType()==EExitPending);
	test.Next(_L("Resume StackThread"));
	thread.Resume();
	User::WaitForRequest(stat);
	test.Next(_L("Check StackThread panicked"));
	test(thread.ExitCategory()==_L("KERN-EXEC"));
	test(thread.ExitReason()==ECausedException);
	test(thread.ExitType()==EExitPanic);
	CLOSE_AND_WAIT(thread);
	}

LOCAL_C TInt KernWriter(TAny* )
	{

	TUint* kernPtr=(TUint*)KernData();
	*kernPtr=0xdead; // BANG!!
	return KErrNone;
	}

LOCAL_C TInt RamDiskWriter(TAny* )
	{

	TFindChunk fChunk(_L("*TheRamDriveChunk*"));
	TFullName n;
	TInt r=fChunk.Next(n);
	RChunk ch;
	r=ch.Open(fChunk);
	if(r!=KErrNone)
		return r;
	TUint8* rdPtr=ch.Base();
	*rdPtr=0xaa; // BANG!!
	return KErrNone;
	}


void testKernelWriter()
//
// Thread writing to kernel data is panicked
//
	{

	RThread thread;
	TRequestStatus stat;

	test.Next(_L("Create KernWriter"));
	TInt r=thread.Create(_L("KernWriter"),KernWriter,KDefaultStackSize,KHeapSize,KHeapSize,0);
	test(r==KErrNone);

	thread.Logon(stat);
	test(thread.ExitType()==EExitPending);
	test.Next(_L("Resume KernWriter"));
	thread.Resume();
	User::WaitForRequest(stat);
	test.Next(_L("Check KernWriter panicked"));
	test(thread.ExitCategory()==_L("KERN-EXEC"));
	test(thread.ExitReason()==ECausedException);
	test(thread.ExitType()==EExitPanic);
	CLOSE_AND_WAIT(thread);
	}

void testRamDiskAccess()
	{

	RThread thread;
	TRequestStatus stat;

	test.Next(_L("Create RamDiskWriter"));
	TInt r=thread.Create(_L("RamDiskWriter"),RamDiskWriter,KDefaultStackSize,KHeapSize,KHeapSize,0);
	test(r==KErrNone);

	thread.Logon(stat);
	test(thread.ExitType()==EExitPending);
	test.Next(_L("Resume RamDiskWriter"));
	thread.Resume();
	User::WaitForRequest(stat);
	if((!PlatSec::ConfigSetting(PlatSec::EPlatSecProcessIsolation))||(!PlatSec::ConfigSetting(PlatSec::EPlatSecEnforcement)))
		{
		test.Next(_L("Check RamDiskWriter panicked"));
		test(thread.ExitCategory()==_L("KERN-EXEC"));
		test(thread.ExitReason()==ECausedException);
		test(thread.ExitType()==EExitPanic);
		}
	else
		{
		test.Next(_L("Check RamDiskWriter was refused access"));
		test(thread.ExitReason()==KErrPermissionDenied);
		test(thread.ExitType()==EExitKill);
		}
	CLOSE_AND_WAIT(thread);
	}

RThread MainThread;

TInt RequestCompleteThread(TAny* aPtr)
	{
	TRequestStatus** pS=(TRequestStatus**)aPtr;
	MainThread.RequestComplete(*pS,123);
	return 0;
	}

_LIT(KReqCompThreadName,"ReqCompThread");
void StartRequestCompleteThread(RThread& aThread, TRequestStatus** aStatus)
	{
	TInt r=aThread.Create(KReqCompThreadName,RequestCompleteThread,0x1000,0x1000,0x10000,aStatus);
	test (r==KErrNone);
	aThread.SetPriority(EPriorityMore);
	TRequestStatus s;
	aThread.Logon(s);
	aThread.Resume();
	User::WaitForRequest(s);
	}

_LIT(KLitUserCBase,"E32USER-CBase");
GLDEF_C TInt E32Main()
//
// Main
//
	{	

	// don't want just in time debugging as we trap panics
	TBool justInTime=User::JustInTime(); 
	User::SetJustInTime(EFalse); 

	test.Title();
	test.Start(_L("Test protection & panicking"));

#if defined(__EPOC32__)
	// this next test doesn't work under WINS because the string needs to be converted
	// from UNICODE to ascii for printing to STDOUT, and that requires the string to be
	// zero-terminated which panics because the string is too long.
	test.Next(_L("Printing long string doesn't get me shot"));
	test.Printf(_L("012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"));
	test.Printf(_L("\n"));
#endif

	if (HaveVirtMem())
		{
		test.Next(_L("Stack overflow shoots the thread not the kernel"));
		testStackOverflow();
		}

	test.Next(_L("Panicking a closed thread doesn't panic the panicker!!"));
	testPanicDeadThread();

	test.Next(_L("Dead thread tests"));
	testDeadThread();

	test.Next(_L("RSession send from uninitialised handle"));
	testSession();

	test.Next(_L("Thread causing exception is killed"));
	if (HaveVirtMem())
		{
		testExcept();
		RunTestThread(EDataAbort);
		RunTestThread(EPrefetchAbort);
		}
#ifndef __WINS__
	RunTestThread(EUndefinedInstruction);
#endif

#if defined(__EPOC32__)
	if (HaveDirectKernProt())
		{
		test.Next(_L("Thread writing to kernel data is killed"));
		testKernelWriter();
		}

	if (HaveProcessProt())
		{
		test.Next(_L("Check access to RamDisk is denied"));
		testRamDiskAccess();
		}

	if (HaveMMU())
		{
		test.Next(_L("RequestComplete() with bad address"));
		TInt rqc=RThread().RequestCount();
		test(MainThread.Duplicate(RThread())==KErrNone);
		RThread t;
		TRequestStatus s=KRequestPending;
		TRequestStatus* pS=&s;
		StartRequestCompleteThread(t,&pS);
		test(t.ExitType()==EExitKill);
		test(t.ExitReason()==KErrNone);
		CLOSE_AND_WAIT(t);
		test(s==123);
		test(pS==NULL);
		test(RThread().RequestCount()==rqc+1);
		TRequestStatus** bad=(TRequestStatus**)0x80000000;	// kernel space
		StartRequestCompleteThread(t,bad);
		test(t.ExitType()==EExitPanic);
		test(t.ExitReason()==ECausedException);
		test(t.ExitCategory()==KLitKernExec);
		CLOSE_AND_WAIT(t);
		test(RThread().RequestCount()==rqc+1);
		pS=(TRequestStatus*)0x80000000;
		StartRequestCompleteThread(t,&pS);
		// Request status poked user side, so we expect a KERN-EXEC 3...
		test(t.ExitType()==EExitPanic);
		test(t.ExitReason()==ECausedException);
		test(t.ExitCategory()==KLitKernExec);
		CLOSE_AND_WAIT(t);
		test(pS==NULL);
		test(RThread().RequestCount()==rqc+1);
		pS=(TRequestStatus*)(((TUint8*)&s)+0x80000);		// aligned, within chunk max size but invalid
		StartRequestCompleteThread(t,&pS);
		// Request status poked user side, so we expect a KERN-EXEC 3...
		test(t.ExitType()==EExitPanic);
		test(t.ExitReason()==ECausedException);
		test(t.ExitCategory()==KLitKernExec);
		CLOSE_AND_WAIT(t);
		test(pS==NULL);
		test(RThread().RequestCount()==rqc+1);
		}
#endif

	test.End();

	User::SetJustInTime(justInTime);
	return(KErrNone);
	}

