kernel/eka/nkern/win32/ncthrd.cpp
author Tom Cosgrove <tom.cosgrove@nokia.com>
Fri, 28 May 2010 16:29:07 +0100
changeset 30 8aab599e3476
parent 0 a41df078684a
permissions -rw-r--r--
Fix for bug 2283 (RVCT 4.0 support is missing from PDK 3.0.h) Have multiple extension sections in the bld.inf, one for each version of the compiler. The RVCT version building the tools will build the runtime libraries for its version, but make sure we extract all the other versions from zip archives. Also add the archive for RVCT4.

// Copyright (c) 1998-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:
// e32\nkern\win32\ncthrd.cpp
// 
//

// NThreadBase member data
#define __INCLUDE_NTHREADBASE_DEFINES__

#include "nk_priv.h"
#include <emulator.h>

extern "C" void ExcFault(TAny*);

// initial Win32 thread stack size
const TInt KInitialStackSize = 0x1000;

// maximum size of the parameter block passed to a new thread
const TInt KMaxParameterBlock = 512;

// data passed to new thread to enable hand-off of the parameter block
struct SCreateThread
	{
	const SNThreadCreateInfo* iInfo;
	NFastMutex iHandoff;
	};

/**
 * Set the Win32 thread priority based on the thread type.
 * Interrupt/Event threads must be able to preempt normal nKern threads,
 * so they get a higher priority.
 */
static void SetPriority(HANDLE aThread, TEmulThreadType aType)
	{
	TInt p;
	switch (aType)
		{
	default:
		FAULT();
	case EThreadEvent:
		p = THREAD_PRIORITY_ABOVE_NORMAL;
		break;
	case EThreadNKern:
		p = THREAD_PRIORITY_NORMAL;
		break;
		}

	__NK_ASSERT_ALWAYS(SetThreadPriority(aThread, p));
	SetThreadPriorityBoost(aThread, TRUE);		// disable priority boost (for NT)
	}


/**	Create a Win32 thread for use in the emulator.

	@param	aType Type of thread (Event or NKern) - determines Win32 priority
	@param	aThreadFunc Entry point of thread
	@param	aPtr Argument passed to entry point
	@param	aRun TRUE if thread should be resumed immediately
	@return	The Win32 handle to the thread, 0 if an error occurred

	@pre    Call either in thread context.
	@pre	Do not call from bare Win32 threads.

	@see TEmulThreadType
 */
EXPORT_C HANDLE CreateWin32Thread(TEmulThreadType aType, LPTHREAD_START_ROUTINE aThreadFunc, LPVOID aPtr, TBool aRun)
	{
	__NK_ASSERT_DEBUG(!TheScheduler.iCurrentThread || NKern::CurrentContext() == NKern::EThread);

	__LOCK_HOST;
	
	DWORD id;
	HANDLE handle = CreateThread(NULL , KInitialStackSize, aThreadFunc, aPtr, CREATE_SUSPENDED, &id);
	if (handle)
		{
		SetPriority(handle, aType);
		if (aRun)
			ResumeThread(handle);
		}
	return handle;
	}


/** Set some global properties of the emulator
	Called by the Win32 base port during boot.

	@param	aTrace TRUE means trace Win32 thread ID for every thread created
	@param	aSingleCpu TRUE means lock the emulator process to a single CPU

	@internalTechnology
 */
EXPORT_C void NThread::SetProperties(TBool aTrace, TInt aSingleCpu)
	{
	Win32TraceThreadId = aTrace;
	Win32SingleCpu = aSingleCpu;
	}

#if defined(__CW32__) && __MWERKS__ < 0x3200
DWORD NThread__ExceptionHandler(EXCEPTION_RECORD* aException, TAny* /*aRegistrationRecord*/, CONTEXT* aContext)
//
// Hook into exception handling for old version of CW
//
	{
	return NThread::ExceptionHandler(aException, aContext);
	}
#endif // old __CW32__

DWORD WINAPI NThread::StartThread(LPVOID aParam)
//
// Win32 thread function for nKern threads.
//
// The thread first enters this function after the nScheduler has resumed
// it, following the context switch induced by the hand-off mutex.
//
// The parameter block for this thread needs to be copied into its
// own context, before releasing the mutex and handing control back to
// the creating thread.
//
	{
	SCreateThread* init = static_cast<SCreateThread*>(aParam);
	NThread& me=*static_cast<NThread*>(init->iHandoff.iHoldingThread);
	me.iWinThreadId = GetCurrentThreadId();
	SchedulerRegister(me);
#ifdef BTRACE_FAST_MUTEX
	BTraceContext4(BTrace::EFastMutex,BTrace::EFastMutexWait,&init->iHandoff);
#endif
	NKern::Unlock();

#if defined(__CW32__) && __MWERKS__ < 0x3200
	// intercept the win32 exception mechanism manually
    asm {
    	push	ebp
    	mov		eax, -1
    	push	eax
    	push	eax
    	push	offset NThread__ExceptionHandler
    	push	fs:[0]
    	mov		fs:[0], esp

		// realign the stack
    	sub		esp, 0x20
    	and		esp, ~0x1f
		}
#else // ! old __CW32__
	// intercept win32 exceptions in a debuggabble way
__try {
#endif // old __CW32__

	// save the thread entry point and parameter block
	const SNThreadCreateInfo& info = *init->iInfo;
	TUint8 parameterBlock[KMaxParameterBlock];
	TAny* parameter=(TAny*)info.iParameterBlock;
	if (info.iParameterBlockSize)
		{
		__NK_ASSERT_DEBUG(TUint(info.iParameterBlockSize)<=TUint(KMaxParameterBlock));
		parameter=parameterBlock;
		memcpy(parameterBlock,info.iParameterBlock,info.iParameterBlockSize);
		}
	NThreadFunction threadFunction=info.iFunction;

	// Calculate stack base
	me.iUserStackBase = (((TLinAddr)&parameterBlock)+0xfff)&~0xfff; // base address of stack

	// some useful diagnostics for debugging
	if (Win32TraceThreadId)
		KPrintf("Thread %T created @ 0x%x - Win32 Thread ID 0x%x",init->iHandoff.iHoldingThread,init->iHandoff.iHoldingThread,GetCurrentThreadId());

#ifdef MONITOR_THREAD_CPU_TIME
	me.iLastStartTime = 0;  // Don't count NThread setup in cpu time
#endif
 
	// start-up complete, release the handoff mutex, which will re-suspend us
	NKern::FMSignal(&init->iHandoff);

	// thread has been resumed: invoke the thread function
	threadFunction(parameter);

#if !defined(__CW32__) || __MWERKS__ >= 0x3200
	// handle win32 exceptions
} __except (ExceptionFilter(GetExceptionInformation())) {
	// Do nothing - filter does all the work and hooks
	// into EPOC h/w exception mechanism if necessary
	// by thread diversion
}
#endif // !old __CW32__

	NKern::Exit();

	return 0;
	}

static HANDLE InitThread()
//
// Set up the initial thread and return the thread handle
//
	{
	HANDLE p = GetCurrentProcess();
	HANDLE me;
	__NK_ASSERT_ALWAYS(DuplicateHandle(p, GetCurrentThread(), p, &me, 0, FALSE, DUPLICATE_SAME_ACCESS));
	SetPriority(me, EThreadNKern);
	return me;
	}

TInt NThread::Create(SNThreadCreateInfo& aInfo, TBool aInitial)
	{
	iWinThread = NULL;
	iWinThreadId = 0;
	iScheduleLock = NULL;
	iInKernel = 1;
	iDivert = NULL;
	iWakeup = aInitial ? ERelease : EResumeLocked;	// mark new threads as created (=> win32 suspend)

	TInt r=NThreadBase::Create(aInfo,aInitial);
	if (r!=KErrNone)
		return r;

	// the rest has to be all or nothing, we must complete it
	iScheduleLock = CreateEventA(NULL, FALSE, FALSE, NULL);
	if (iScheduleLock == NULL)
		return Emulator::LastError();

	if (aInitial)
		{
		iWinThread = InitThread();
		FastCounterInit();
#ifdef MONITOR_THREAD_CPU_TIME
		iLastStartTime = NKern::FastCounter();
#endif
		iUserStackBase = (((TLinAddr)&r)+0xfff)&~0xfff; // base address of stack
		SchedulerInit(*this);
		return KErrNone;
		}

	// create the thread proper
	//
	SCreateThread start;
	start.iInfo = &aInfo;

	iWinThread = CreateWin32Thread(EThreadNKern, &StartThread, &start, FALSE);
	if (iWinThread == NULL)
		{
		r = Emulator::LastError();
		CloseHandle(iScheduleLock);
		return r;
		}

#ifdef BTRACE_THREAD_IDENTIFICATION
	BTrace4(BTrace::EThreadIdentification,BTrace::ENanoThreadCreate,this);
#endif
	// switch to the new thread to hand over the parameter block
	NKern::Lock();
	ForceResume();	// mark the thread as ready
	// give the thread ownership of the handoff mutex
	start.iHandoff.iHoldingThread = this;
	iHeldFastMutex = &start.iHandoff;
	Suspend(1);		// will defer as holding a fast mutex (implicit critical section)
	// do the hand-over
	start.iHandoff.Wait();
	start.iHandoff.Signal();
	NKern::Unlock();

	return KErrNone;
	}

void NThread__HandleException(TWin32ExcInfo aExc)
//
// Final stage NKern exception handler.
//
// Check for a fatal exception when the kernel is locked
//
// Note that the parameter struct is passed by value, this allows for
// direct access to the exception context created on the call stack by
// NThread::Exception().
//
	{
	if (TheScheduler.iKernCSLocked)
		ExcFault(&aExc);

	// Complete the exception data. Note that the call to EnterKernel() in
	// ExceptionFilter() will have incremented iInKernel after the exception
	// occurred.
	NThread* me = static_cast<NThread*>(TheScheduler.iCurrentThread);
	__NK_ASSERT_DEBUG(me->iInKernel);
	aExc.iFlags = me->iInKernel == 1 ? 0 : TWin32ExcInfo::EExcInKernel;
	aExc.iHandler = NULL;

	// run NThread exception handler in 'kernel' mode
	me->iHandlers->iExceptionHandler(&aExc, me);
	LeaveKernel();

	// If a 'user' handler is set by the kernel handler, run it
	if (aExc.iHandler)
		aExc.iHandler(aExc.iParam[0], aExc.iParam[1]);
	}

void NKern__Unlock()
//
// CW asm ICE workaround
//
	{
	NKern::Unlock();
	}

__NAKED__ void NThread::Exception()
//
// Trampoline to nKern exception handler
// must preserve all registers in the structure defined by TWin32Exc
//
	{
	// this is the TWin32Exc structure
	__asm push Win32ExcAddress			// save return address followed by EBP first to help debugger
	__asm push ebp
	__asm mov ebp, esp
	__asm push cs
	__asm pushfd
	__asm push gs
	__asm push fs
	__asm push es
	__asm push ds
	__asm push ss
	__asm push edi
	__asm push esi
	__asm lea esi, [ebp+8]
	__asm push esi		// original esp
	__asm push ebx
	__asm push edx
	__asm push ecx
	__asm push eax
	__asm push Win32ExcDataAddress
	__asm push Win32ExcCode
	__asm sub esp, 20	// struct init completed by NThread__HandleException()

	__asm call NKern__Unlock

	__asm call NThread__HandleException

	__asm add esp, 28
	__asm pop eax
	__asm pop ecx
	__asm pop edx
	__asm pop ebx
	__asm pop esi		// original ESP - ignore
	__asm pop esi
	__asm pop edi
	__asm pop ebp		// original SS - ignore
	__asm pop ds
	__asm pop es
	__asm pop fs
	__asm pop gs
	__asm popfd
	__asm pop ebp		// original CS - ignore
	__asm pop ebp
	__asm ret
	}

LONG WINAPI NThread::ExceptionFilter(EXCEPTION_POINTERS* aExc)
//
// Filter wrapper for main Win32 exception handler
//
	{
	LONG ret = EXCEPTION_CONTINUE_SEARCH;

	switch (ExceptionHandler(aExc->ExceptionRecord, aExc->ContextRecord))
		{
		case ExceptionContinueExecution:
			{
			ret = EXCEPTION_CONTINUE_EXECUTION;
			}
			break;
		case ExceptionContinueSearch:
		default:
			{
			}
			break;
		}

	return ret;
	}

// From e32/commmon/win32/seh.cpp
extern DWORD CallFinalSEHHandler(EXCEPTION_RECORD* aException, CONTEXT* aContext);

extern void DivertHook();

DWORD NThread::ExceptionHandler(EXCEPTION_RECORD* aException, CONTEXT* aContext)
//
// Win32 exception handler for EPOC threads
//
	{
	if (aException->ExceptionCode == EXCEPTION_BREAKPOINT)
		{
		// Hardcoded breakpoint
		//
		// Jump directly to NT's default unhandled exception handler which will
		// either display a dialog, directly invoke the JIT debugger or do nothing
		// dependent upon the AeDebug and ErrorMode registry settings.
		//
		// Note this handler is always installed on the SEH chain and is always
		// the last handler on this chain, as it is installed by NT in kernel32.dll
		// before invoking the Win32 thread function.
		return CallFinalSEHHandler(aException, aContext);
		}

	// deal with conflict between preemption and diversion
	// the diversion will have been applied to the pre-exception context, not
	// the current context, and thus will get 'lost'. Wake-up of a pre-empted
	// thread with a diversion will not unlock the kernel, so need to deal with
	// the possibility that the kernel may be locked if a diversion exists

	NThread& me = *static_cast<NThread*>(TheScheduler.iCurrentThread);
	if (me.iDiverted && me.iDivert)
		{
		// The thread is being forced to exit - run the diversion outside of Win32 exception handler
		__NK_ASSERT_ALWAYS(TheScheduler.iKernCSLocked == 1);
		aContext->Eip = (TUint32)&DivertHook;
		}
	else
		{
		if (me.iDiverted)
			{
			// The thread is being prodded to pick up its callbacks.  This will happen when the
			// exception handler calls LeaveKernel(), so we can remove the diversion
			__NK_ASSERT_ALWAYS(TheScheduler.iKernCSLocked == 1);
			if (aException->ExceptionAddress == &DivertHook)
				aException->ExceptionAddress = me.iDivertReturn;
			me.iDiverted = EFalse;
			me.iDivertReturn = NULL;
			EnterKernel(FALSE);
			}
		else
			{
			EnterKernel();
			TheScheduler.iKernCSLocked = 1;	// prevent pre-emption
			}
		
		// If the kernel was already locked, this will be detected in the next stage handler
		// run 2nd stage handler outside of Win32 exception context
		Win32ExcAddress = aException->ExceptionAddress;
		Win32ExcDataAddress = (TAny*)aException->ExceptionInformation[1];
		Win32ExcCode = aException->ExceptionCode;
		aContext->Eip = (TUint32)&Exception;
		}
	return ExceptionContinueExecution;
	}

void NThread::Diverted()
//
// Forced diversion go through here, in order to 'enter' the kernel
//
	{
	NThread& me = *static_cast<NThread*>(TheScheduler.iCurrentThread);
	__NK_ASSERT_ALWAYS(TheScheduler.iKernCSLocked == 1);
	__NK_ASSERT_ALWAYS(me.iDiverted);
	NThread::TDivert divert = me.iDivert;
	me.iDiverted = EFalse;
	me.iDivert = NULL;
	me.iDivertReturn = NULL;
	EnterKernel(FALSE);
	if (divert)
		divert();	// does not return
	NKern::Unlock();
	LeaveKernel();
	}

void NThread__Diverted()
	{
	NThread::Diverted();
	}

__NAKED__ void DivertHook()
	{
	// The stack frame is set up like this:
	//
	//		| return address |
	//		| frame pointer  |
	//		| flags			 |
	//		| saved eax		 |
	//		| saved ecx		 |
	//      | saved edx		 |
	//		
	__asm push eax					// reserve word for return address
	__asm push ebp
	__asm mov ebp, esp 
	__asm pushfd
	__asm push eax
	__asm push ecx
	__asm push edx
	__asm mov eax, [TheScheduler.iCurrentThread]
	__asm mov eax, [eax]NThread.iDivertReturn
	__asm mov [esp + 20], eax		// store return address
	__asm call NThread__Diverted
	__asm pop edx
	__asm pop ecx
	__asm pop eax
	__asm popfd
	__asm pop ebp
	__asm ret
	}


void NThread::ApplyDiversion()
	{
	// Called with interrupts disabled and kernel locked
	__NK_ASSERT_ALWAYS(TheScheduler.iKernCSLocked == 1);
	if (iDiverted)
		return;
	CONTEXT c;
	c.ContextFlags=CONTEXT_FULL;
	GetThreadContext(iWinThread, &c);
	__NK_ASSERT_ALWAYS(iDivertReturn == NULL);
	iDivertReturn = (TAny*)c.Eip;
	c.Eip=(TUint32)&DivertHook;
	SetThreadContext(iWinThread, &c);
	iDiverted = ETrue;
	}

void NThread::Divert(TDivert aDivert)
//
// Divert the thread from its current path
// The diversion function is called with the kernel locked and interrupts enabled
//
	{
	iDivert = aDivert;
	if (iWakeup == EResume)
		iWakeup = EResumeDiverted;
	else
		__NK_ASSERT_ALWAYS(iWakeup == ERelease);
	}

void NThread::ExitSync()
//
// Diversion used to terminate 'stillborn' threads.
// On entry, kernel is locked, interrupts are enabled and we hold an interlock mutex
//
	{
	NThreadBase& me=*TheScheduler.iCurrentThread;
	me.iHeldFastMutex->Signal();	// release the interlock
	me.iNState=EDead;				// mark ourselves as dead which will take thread out of scheduler
	TheScheduler.Remove(&me);
	RescheduleNeeded();
	TScheduler::Reschedule();	// this won't return
	FAULT();
	}

void NThread::Stillborn()
//
// Called if the new thread creation was aborted - so it will not be killed in the usual manner
//
// This function needs to exit the thread synchronously as on return we will destroy the thread control block
// Thus wee need to use an interlock that ensure that the target thread runs the exit handler before we continue
//
	{
	// check if the Win32 thread was created
	if (!iWinThread)
		return;

	NKern::Lock();
	Divert(&ExitSync);
	ForceResume();
	// create and assign mutex to stillborn thread
	NFastMutex interlock;
	interlock.iHoldingThread=this;
	iHeldFastMutex=&interlock;
	interlock.Wait();			// interlock on thread exit handler
	interlock.Signal();
	NKern::Unlock();
	}

void NThread::ExitAsync()
//
// Diversion used to terminate 'killed' threads.
// On entry, kernel is locked and interrupts are enabled
//
	{
	NThreadBase& me = *TheScheduler.iCurrentThread;
	me.iCsCount = 0;
	__NK_ASSERT_ALWAYS(static_cast<NThread&>(me).iInKernel>0);
	me.Exit();
	}

void NThreadBase::OnKill()
	{
	}

void NThreadBase::OnExit()
	{
	}

inline void NThread::DoForceExit()
	{
	__NK_ASSERT_DEBUG(TheScheduler.iKernCSLocked);
//
	Divert(&ExitAsync);
	}

void NThreadBase::ForceExit()
//
// Called to force the thread to exit when it resumes
//
	{
	static_cast<NThread*>(this)->DoForceExit();
	}

//
// We need a global lock in the emulator to avoid scheduling reentrancy problems with the host
// in particular, some host API calls acquire host mutexes, preempting such services results
// in suspension of those threads which can cause deadlock if another thread requires that host
// mutex.
//
// Because thread dreaction and code loading also require the same underlying mutex (used
// by NT to protect DLL entrypoint calling), this would be rather complex with a fast mutex.
// For now, keep it simple and use the preemption lock. Note that this means that the
// MS timer DFC may be significantly delayed when loading large DLL trees, for example.
//

void SchedulerLock()
//
// Acquire the global lock. May be called before scheduler running, so handle that case
//
	{
	if (TheScheduler.iCurrentThread)
		{
		EnterKernel();
		NKern::Lock();
		}
	}

void SchedulerUnlock()
//
// Release the global lock. May be called before scheduler running, so handle that case
//
	{
	if (TheScheduler.iCurrentThread)
		{
		NKern::Unlock();
		LeaveKernel();
		}
	}