/*	$Id: DRuntimeSupport.cpp,v 1.3 1999/01/11 22:38:16 maarten Exp $
	
	Copyright Hekkelman Programmatuur
	Maarten L. Hekkelman
	
	Created: 11/16/98 22:00:15
*/

#include "bdb.h"
#include "DRuntimeSupport.h"
#include "DCpuState.h"
#include "DThread.h"
#include "DTeam.h"
#include "DNub.h"
#include "DSymWorld.h"
#include "DMessages.h"
#include "HStream.h"

enum {
	rtStepOutStepIn = 1,
	rtThrow,
	rtStepOut
};

class DStepOutStepInMachine : public DStateMachine
{
  public:
	DStepOutStepInMachine() {}
	
	virtual EDebugAction NextAction (DThread& thread);

  private:
	ptr_t fBreakAddr;
};

class DStepOutMachine : public DStateMachine
{
  public:
	DStepOutMachine() {}
	
	virtual EDebugAction NextAction (DThread& thread);
};


class DThrowMachine : public DStateMachine
{
  public:
	DThrowMachine() {}
	virtual EDebugAction NextAction (DThread& thread);
};

void CreateRuntimeList(DTeam& team, DRuntimeSupportList& lst)
{
	size_t size;
	const void *p = HResources::GetResource('RTLi', 0, size);
	FailNil(p);
	BMemoryIO mem(p, size);
	HStream<BMemoryIO> str(mem);
	
	long cnt;
	
	str >> cnt;
	
	while (cnt--)
	{
		try
		{
			char name[1024];
			long offset, action, extra;
			
			str >> name >> offset >> action >> extra;
			
			ptr_t pc;
			int size;

			team.GetSymWorld().GetFunctionOffsetAndSize(name, pc, size);

			DRuntimeSupportList::iterator i;
			for (i = lst.begin(); i != lst.end(); i++)
				if ((*i).addr == pc)
					break;
			
			if (i == lst.end())
			{
				DRuntimeInfo ri;
				ri.addr = pc + offset;
				ri.action = action;
				ri.info = extra;
				lst.push_back(ri);
			}
		}
		catch (...)
		{
		}
	}
} // CreateRuntimeProcList

DStateMachine* CheckRuntimeList(DThread& thread, DRuntimeSupportList& lst)
{
	DRuntimeSupportList::iterator i;
	
	for (i = lst.begin(); i != lst.end(); i++)
	{
		if (thread.GetCPU().GetPC() == (*i).addr)
		{
			switch ((*i).action)
			{
				case rtStepOutStepIn:
					return new DStepOutStepInMachine;
				case rtThrow:
					return new DThrowMachine;
				case rtStepOut:
					return new DStepOutMachine;
			}
		}
	}

	return NULL;
} // CheckRuntimeList

//------------------------

DStateMachine::~DStateMachine()
{
} // DStateMachine::~DStateMachine

//------------------------

EDebugAction DStepOutStepInMachine::NextAction(DThread& thread)
{
	ASSERT(!fDone);
	
	EDebugAction action = daNone;
	
	switch (fState)
	{
		case 0:
		{
			ptr_t addr = thread.GetCPU().GetSP();
			
			DNub& nub = thread.GetTeam().GetNub();
			BAutolock nlock(nub);
			nub.Read(addr, addr);
			
			if (addr)
			{
				BAutolock lock(thread.GetTeam());
				thread.GetTeam().SetBreakpoint(addr, thread.GetID());
				action = daStepOut;
				fBreakAddr = addr;
				fState = 1;
			}
			break;
		}
		case 1:
			if (thread.GetCPU().GetPC() == fBreakAddr)
				action = daStep;
			fDone = true;
			break;
	}
	
	return action;
} // DStepOutStepInMachine::NextAction


EDebugAction 
DStepOutMachine::NextAction(DThread &thread)
{
	ASSERT(!fDone);
	
	EDebugAction action = daNone;
	
	switch (fState)
	{
		case 0:
		{
			ptr_t addr = thread.GetCPU().GetSP();
			
			DNub& nub = thread.GetTeam().GetNub();
			BAutolock nlock(nub);
			nub.Read(addr, addr);
			
			if (addr)
			{
				// This is copied from DStepOutStepInMachine::NextAction
				// it's not exactly clear why we are also setting a breakpoint besides
				// just returning daStepOut -- possibly to handle cases when a breakpoint is
				// hit inside the call we are stepping out of ???
				BAutolock lock(thread.GetTeam());
				thread.GetTeam().SetBreakpoint(addr, thread.GetID());
				action = daStepOut;
				fState = 1;
			}
			break;
		}
		case 1:
			fDone = true;
			break;
	}
	
	return action;
}


//------------------------

EDebugAction DThrowMachine::NextAction(DThread& thread)
{
	ASSERT(!fDone);
	
	EDebugAction action = daNone;
	
	switch (fState)
	{
		case 0:
		{
			ptr_t ret;
			DNub& nub = thread.GetTeam().GetNub();
			DCpuState& cpu = thread.GetCPU();
			
			BAutolock lock(nub);
			if (lock.IsLocked())
			{
				nub.Read(cpu.GetSP(), ret);
				
					// this is an option since I'm not sure it will continue to work
					// The idea is to disassemble the instructions just before __throw was called
					// In the event of a user called __throw, gcc inserted some code to 
					// construct an exception object on the stack. Try to see whether
					// these instruction can be found prior to the call to __throw and if so
					// we stop. If not, assume this is an autogenerated call to __throw and
					// ignore it.
					// This is a typical example of a dirty hack. 
				if (gPrefs->GetPrefInt("don't stop for unwinding", 1))
				{
#if __INTEL__
					unsigned char code[4];
					nub.ReadData(ret - 8, code, 3);
					
						// see if this is equal to
						//		addl	$12,%esp
					if (code[0] != 0x83 || code[1] != 0xc4 || code[2] != 0x0c)
					{
						fDone = true;
						action = daRun;
					}
#else
#	error
#endif
				}
				
				if (!fDone)
				{
#if __INTEL__
//					SetSP(cpu, GetSP(cpu) + 4);			// pop the return address of the stack
					cpu.SetSP(cpu.GetSP() + 4);
//					SetPC(cpu, ret - 5);					// a call instruction to __throw is 5 bytes long
					cpu.SetPC(ret - 5);
#else
#	error
#endif
				}
				thread.SetCPU(cpu);
			}
			fState = 1;
			break;
		}
		case 1:
		{
			if (thread.GetCPU().GetPC() == thread.GetTeam().ThrowPC() &&
				thread.LastAction() != daStep)
			{
				action = daRun;	// since a step over a throw never returns
			}
			fDone = true;
			break;
		}
	}
	
	return action;
} // DThrowMachine::NextAction
