// Copyright (c) 2008-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\debug\crashMonitor\src\crashlogwalker.cpp
// Class to allow us to traverse the crash log generated by System Crash Monitor
// 
//

/**
 @file
 @internalTechnology
*/

#ifndef __KERNEL_MODE__
#include <e32std.h>
#include <e32std_private.h> 
#include <e32base.h>
#include <e32base_private.h> 
#endif

#include "scmtrace.h"
#include "crashlogwalker.h"

namespace Debug
	{	
	/**
	 * Constructor for log walker
	 * @param aBuffer The buffer containing the crash data
	 */
	TCrashLogWalker::TCrashLogWalker(TDesC8& aBuffer) : 
		iBuffer(aBuffer),
		iReader(const_cast<TUint8*>(iBuffer.Ptr()))
		{
		}
	
	/**
	 * This reads in the crash header from the buffer from the given start point
	 * @param aStartPoint Point to begin reading in buffer
	 * @return One of the OS wide codes
	 */
	TInt TCrashLogWalker::ReadLogHeader(const TInt aStartPoint)
		{		
		iReader.SetPosition(aStartPoint);
		
		TInt err = iCrashHeader.Deserialize(iReader);
		if(err != KErrNone)
			{
			CLTRACE("(TCrashLogWalker::ReadLogHeader) - failed to read crash header");
			return KErrCorrupt;
			}
		
		err = iOffsets.Deserialize(iReader);
		if(err != KErrNone)
			{
			CLTRACE("(TCrashLogWalker::ReadLogHeader) - failed to read offsets");
			return KErrCorrupt;
			}
		
		TRegisterSet set;
		err = set.Deserialize(iReader);
		if(err != KErrNone)
			{
			CLTRACE("(TCrashLogWalker::ReadLogHeader) - failed to read register set");
			return KErrCorrupt;
			}
		
		for(TInt cnt = 0; cnt < set.iNumRegisters; cnt++)
			{
			TRegisterValue val;
			err = val.Deserialize(iReader);
			if(err != KErrNone)
				{
				CLTRACE1("(TCrashLogWalker::ReadLogHeader) - failed to read TRegisterValue cnt = %d", cnt);
				return KErrCorrupt;
				}
						
			HelpAssignRegisterToContext(val);
			}
		
		return VerifyHeader();
		}
	
	/**
	 * Getter for the crash context - This is the CPU register set at the time of crash
	 * @return Crash Context
	 */
	const TRmdArmExcInfo& TCrashLogWalker::GetCrashContext() const
		{
		return iContext;
		}
	
	/**
	 * Returns the crash size for the crash that has just been read, provided the
	 * reading of the header was succesful. 
	 * @see ReadLogHeader
	 * @return Crash Log size
	 */
	TInt TCrashLogWalker::GetCrashSize() const
		{
		return iCrashHeader.iLogSize;
		}
	
	/**
	 * Returns the crash ID for the crash that has just been read, provided the
	 * reading of the header was succesful. 
	 * @see ReadLogHeader
	 * @return Crash Log ID
	 */
	TInt TCrashLogWalker::GetCrashId() const
		{
		return iCrashHeader.iCrashId;
		}
	
	/**
	 * Looks at the member crash log header and checks that it is valid.
	 * @see ReadLogHeader 
	 * @return one of the OS wide codes
	 */
	TInt TCrashLogWalker::VerifyHeader()
		{
		if(iCrashHeader.iId == ESCMTCrashInfo)
			{			
			CLTRACE("TCrashLogWalker::VerifyHeader() OK");
			return KErrNone;
			}
		else
			{
			CLTRACE("TCrashLogWalker::VerifyHeader() FAILED");
			return KErrCorrupt;
			}
		}
	
	/**
	 * Updates the buffer being used by the crash walker and resets reader to use 
	 * the beginning of this
	 * @param aBuffer New buffer
	 */
	void TCrashLogWalker::UpdateBuffer(TDesC8& aBuffer)
		{
		iBuffer = aBuffer;		
		
		//Read from start of this buffer		
		iReader = TByteStreamReader(const_cast<TUint8*>(aBuffer.Ptr()));
		}
	
#ifndef __KERNEL_MODE__
	/**
	 * Gets the next data type from the buffer. If this is NULL it means the buffer was too small.
	 * Call again with a larger buffer. It assumes the buffer contains valid data and leaves with KErrCorrupt 
	 * if it isnt. Note for raw data types, the data will be empty. This should be read with GetRawDataTypeL after reseting the reader to the 
	 * correct position. If you just want to skip a raw data type, move the buffer along the size of (contained in the returned struct)
	 * 
	 * @see GetRawDataTypeL
	 * @see UpdateBuffer
	 * @param aPos Next position that will be read. If we return NULL, this is the position the next buffer should
	 * 			begin from
	 * @param aId ID of the MByteStreamSerializable returned	 
	 * @param buffer size to be used the next time. Unchanged if the current buffer is ok
	 * @return MByteStreamSerializable pointer. Ownership is passed to caller. NULL if failed
	 * @leave KErrCorrupt if the buffer cant be read
	 */
	MByteStreamSerializable* TCrashLogWalker::GetNextDataTypeL(TInt& aPos, SCMStructId& aId, TInt& aBufferSize)
		{
		MByteStreamSerializable* data = NULL;
		
		TInt roomInBuffer = iBuffer.Length() - iReader.CurrentPosition();
		//make sure we have at LEAST 4 bytes in the buffer
		if(roomInBuffer < (TInt)(sizeof(TInt)))
			{
			aBufferSize = sizeof(TInt);
			return NULL;
			}
		
		//this stores the required size in which to deserialize a structure - to make sure 
		//there is room in the buffer
		TInt maxSize = 0;
		aPos = iReader.CurrentPosition();
		aBufferSize = iBuffer.Length();		
		
		//all these data types are defined by their first byte
		aId = (SCMStructId)iBuffer.Ptr()[iReader.CurrentPosition()];		
		
		//ensure we have a valid structure found
		if(aId <= 0 || aId >= ESCMLast)
			{
			//oddness is afoot and the mist of corruption reigns thick
			User::Leave(KErrCorrupt);
			}					
		
		switch(aId)
			{
			case ESCMOffsetsHeader:
				{
				data = new TCrashOffsetsHeader();	
				maxSize = TCrashOffsetsHeader::KSCMCrashOffsetsMaxSize;
				break;
				}
			case ESCMTCrashInfo:
				{
				data = new TCrashInfoHeader();
				maxSize = TCrashInfoHeader::KSCMCrashInfoMaxSize;
				break;
				}
			case ESCMProcessData:
				{
				data = new TProcessData();
				maxSize = TProcessData::KSCMProcessDataMaxSize;
				break;
				}
			case ESCMThreadData:
				{
				data = new TThreadData();
				maxSize = TThreadData::KSCMThreadDataMaxSize;
				break;
				}
			case ESCMThreadStack:
				{
				data = new TThreadStack();
				maxSize = TThreadStack::KSCMThreadStackMaxSize;
				break;
				}
			case ESCMRegisterValue:
				{
				data = new TRegisterValue();
				maxSize = TRegisterValue::KSCMRegisterValueMaxSize;
				break;
				}
			case ESCMRegisterSet:
				{
				data = new TRegisterSet();
				maxSize = TRegisterSet::KSCMRegisterSetMaxSize;
				break;
				}
			case ESCMMemory:
				{
				data = new TMemoryDump();
				maxSize = TMemoryDump::KSCMMemDumpMaxSize;
				break;
				}
			case ESCMCodeSegSet:
				{
				data = new TCodeSegmentSet();
				maxSize = TCodeSegmentSet::KSCMCodeSegSetMaxSize;
				break;
				}
			case ESCMCodeSeg:
				{
				data = new TCodeSegment();
				maxSize = TCodeSegment::KMaxSegmentNameSize;
				break;
				}	
			case ESCMLocks:
				{
				data = new TSCMLockData();
				maxSize = TSCMLockData::KSCMLockDataMaxSize;	
				break;
				}
			case ESCMVariantData:
				{
				data = new TVariantSpecificData();
				maxSize = TVariantSpecificData::KSCMVarSpecMaxSize;	
				break;
				}				
			case ESCMRomHeader:
				{
				data = new TRomHeaderData();
				maxSize = TRomHeaderData::KSCMRomHdrMaxSize;	
				break;
				}				
			case ESCMRawData:
				{
				data = new TRawData();
				
				//This is a special case. The data in here can be any length, so we need to deserialise it
				//to find this length out. The MAX_SIZE of this class is the max size minus data
				//which is fine if we dont assign the TPtr8.				
				if(TRawData::KSCMRawDataMaxSize > roomInBuffer )
					{					
					aBufferSize = (maxSize > aBufferSize) ? maxSize : aBufferSize;
					
					if(data)
						delete data;
					
					return NULL;
					}
				else
					{
					data->Deserialize(iReader);
					maxSize = data->GetSize();
					
					aPos = iReader.CurrentPosition();
					return data;
					}
				}
			case ESCMTraceData:
				{
				data = new TTraceDump();
				maxSize = TTraceDump::KSCMTraceDumpMaxSize;
				break;
				}
			default :
				{
				User::Panic(_L("Unexpected Null. Unrecognised Data type from crash log."), KErrGeneral); //Programming error				
				}							
			}
		
		__ASSERT_ALWAYS((data != NULL), User::Panic(_L("Unexpected Null"), KErrGeneral));
		
		if(maxSize > roomInBuffer )
			{
			//Not enough room in buffer to read this. Tell caller where in the current buffer
			//we were via aPos, and what minimum size buffer should be used next time to allow reading
			aBufferSize = maxSize;
			if(data)
				{
				delete data;
				}
			
			return NULL;
			}
		
		if(!data)
			{
			CLTRACE("Unable to create data structure");
			User::Leave(KErrAbort);
			}
		
		data->Deserialize(iReader);			
		aPos = iReader.CurrentPosition();
		
		return data;
		}
	
	/**
	 * Assuming the next type in the buffer is a TRawData type, this will return the TRawData
	 * types data pointer pointing to the buffer passed via aRawBuf.
	 *  
	 * The difference between this call and GetNextDataTypeL is that GetNextDataTypeL only lets you move along the buffer
	 * it doesnt allow you to specify a buffer in which to store the raw data.
	 * 
	 * @see GetNextDataTypeL
	 * @return TRawData* This is the TRawData object that holds the data via the buffer passed in. Ownership is passed to the caller
	 * @param aPos position in buffer its been found. If we return NULL, this is the position the next buffer should
	 * 			begin from
	 * @param aBufferSize Should we return NULL, that means the descriptor passed in was not big enough and should be of at least aBufferSize bytes
	 * @param aRawBuf The buffer to store the data refered to by the TRawData returned
	 * @param aStartRawPosition The point in the raw data at which we will start to put it into the buffer
	 * @leave One of the OS wide codes
	 */
	TRawData* TCrashLogWalker::GetRawDataTypeL(TInt& aPos, TInt& aBufferSize, TDes8& aRawBuf, TInt aStartRawPosition)
		{								
		//make sure we have at LEAST the size of the struct in the buffer
		if(iBuffer.Length() < TRawData::KSCMRawDataMaxSize)
			{
			aBufferSize = TRawData::KSCMRawDataMaxSize;
			return NULL;
			}
		
		//this stores the required size in which to deserialize a structure - to make sure 
		//there is room in the buffer
		aPos = iReader.CurrentPosition();
		aBufferSize = iBuffer.Length();
		
		//all these data types are defined by their first byte
		TInt id = (SCMStructId)iBuffer.Ptr()[iReader.CurrentPosition()];
		if(id != ESCMRawData)
			{
			User::Leave(KErrCorrupt);
			}		
		
		//Deserialise once to get the length (this will ignore the data in the absence of a Tptr)
		TRawData* data = new TRawData();
		data->Deserialize(iReader);
		
		//reset reader to where the raw data starts again
		iReader.SetPosition(aPos);
		
		//now we know we have room, deserialize into this descriptor	
		aRawBuf.SetMax();	
		data->iData.Set(aRawBuf.MidTPtr(0));
		User::LeaveIfError(data->Deserialize(aStartRawPosition, iReader));
		
		return data;
		}
	
#endif
	
	/**
	 * This is a helper function to convert between the two formats of register
	 * @param aRegVal Resulting register values
	 */
	void TCrashLogWalker::HelpAssignRegisterToContext(const TRegisterValue& aRegVal)
		{
		//only interested in core registers at the moment
		if(aRegVal.iClass != 0 || aRegVal.iSize != 2)
			{
			return;
			}
		
		//Is there a cleverer way to do this with bitmasks and FOFF ?
		switch(aRegVal.iType)
			{
			case 0x0 :
				{
				iContext.iR0 = aRegVal.iValue32;
				break;
				}
			case 0x100 :
				{
				iContext.iR1 = aRegVal.iValue32;
				break;
				}
			case 0x200 :
				{
				iContext.iR2 = aRegVal.iValue32;
				break;
				}
			case 0x300 :
				{
				iContext.iR3 = aRegVal.iValue32;
				break;
				}
			case 0x400 :
				{
				iContext.iR4 = aRegVal.iValue32;
				break;
				}
			case 0x500 :
				{
				iContext.iR5 = aRegVal.iValue32;
				break;
				}
			case 0x600 :
				{
				iContext.iR6 = aRegVal.iValue32;
				break;
				}
			case 0x700 :
				{
				iContext.iR7 = aRegVal.iValue32;
				break;
				}
			case 0x800 :
				{
				iContext.iR8 = aRegVal.iValue32;
				break;
				}
			case 0x900 :
				{
				iContext.iR9 = aRegVal.iValue32;
				break;
				}
			case 0xa00 :
				{
				iContext.iR10 = aRegVal.iValue32;
				break;
				}
			case 0xb00 :
				{
				iContext.iR11 = aRegVal.iValue32;
				break;
				}
			case 0xc00 :
				{
				iContext.iR12 = aRegVal.iValue32;
				break;
				}
			case 0xd00 :
				{
				iContext.iR13 = aRegVal.iValue32;				
				break;
				}
			case 0xe00 :
				{
				iContext.iR14 = aRegVal.iValue32;
				break;
				}
			case 0xf00 :
				{
				iContext.iR15 = aRegVal.iValue32;
				break;
				}
			case 0x1000 :
				{
				iContext.iCpsr = aRegVal.iValue32;
				break;
				}
			case 0x1100 :
				{
				iContext.iR13Svc = aRegVal.iValue32;
				break;
				}
			case 0x1200 :
				{
				iContext.iR14Svc = aRegVal.iValue32;
				break;
				}
			default :
				{
				return;
				}				
			}
		}
	
	/**
	 * Getter for crash header
	 * @return header
	 */
	const TCrashInfoHeader& TCrashLogWalker::GetCrashHeader() const
		{
		return iCrashHeader;
		}
	
	/**
	 * Getter for crash offsets header
	 * @return header
	 */
	const TCrashOffsetsHeader& TCrashLogWalker::GetOffsetsHeader() const
		{
		return iOffsets;
		}			
		
	}
//eof

