/* -*- mode: C++; c-basic-offset: 4; tab-width: 4 vi:set tabstop=4 expandtab: -*/
//===-- libuwind.cxx --------------------------------------------*- C++ -*-===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
 
#if __ppc__ || __i386__ ||  __x86_64__

#include <mach/mach_types.h>
#include <mach/machine.h>
#include <new>

#include "libunwind.h"
#include "libunwind_priv.h"

#include "UnwindCursor.hpp"
#include "AddressSpace.hpp"

#include "RemoteProcInfo.hpp"

namespace lldb_private {

// setup debug logging hooks
INITIALIZE_DEBUG_PRINT_API
INITIALIZE_DEBUG_PRINT_UNWINDING

// internal object to represent this processes address space
static LocalAddressSpace sThisAddressSpace;

#pragma mark Local API

///
/// record the registers and stack position of the caller 
///
extern int unw_getcontext(unw_context_t*);
// note: unw_getcontext() implemented in assembly

///
/// create a cursor of a thread in this process given 'context' recorded by unw_getcontext()
///
EXPORT int unw_init_local(unw_cursor_t* cursor, unw_context_t* context)
{
	DEBUG_PRINT_API("unw_init_local(cursor=%p, context=%p)\n", cursor, context);
	// use "placement new" to allocate UnwindCursor in the cursor buffer
#if __i386__
	new ((void*)cursor) UnwindCursor<LocalAddressSpace,Registers_x86>(context, sThisAddressSpace);
#elif __x86_64__
	new ((void*)cursor) UnwindCursor<LocalAddressSpace,Registers_x86_64>(context, sThisAddressSpace);
#elif __ppc__
	new ((void*)cursor) UnwindCursor<LocalAddressSpace,Registers_ppc>(context, sThisAddressSpace);
#endif
	AbstractUnwindCursor* co = (AbstractUnwindCursor*)cursor;
	co->setInfoBasedOnIPRegister(NULL);

	return UNW_ESUCCESS;
}

///
/// move cursor to next frame 
///
EXPORT int unw_step(unw_cursor_t* cursor)
{
	DEBUG_PRINT_API("unw_step(cursor=%p)\n", cursor);
	AbstractUnwindCursor* co = (AbstractUnwindCursor*)cursor;
    return co->step();
}

///
/// get value of specified register at cursor position in stack frame
///
EXPORT int unw_get_reg(unw_cursor_t* cursor, unw_regnum_t regNum, unw_word_t* value)
{
	DEBUG_PRINT_API("unw_get_reg(cursor=%p, regNum=%d, &value=%p)\n", cursor, regNum, value);
	AbstractUnwindCursor* co = (AbstractUnwindCursor*)cursor;

    if (co->validReg(regNum) == 0)
        return UNW_EBADREG;
	return co->getReg(regNum, value);
}

///
/// get value of specified float register at cursor position in stack frame
///
EXPORT int unw_get_fpreg(unw_cursor_t* cursor, unw_regnum_t regNum, unw_fpreg_t* value)
{
	DEBUG_PRINT_API("unw_get_fpreg(cursor=%p, regNum=%d, &value=%p)\n", cursor, regNum, value);
	AbstractUnwindCursor* co = (AbstractUnwindCursor*)cursor;

	if ( co->validFloatReg(regNum) ) {
		return co->getFloatReg(regNum, value);
	}
	return UNW_EBADREG;
}

///
/// set value of specified register at cursor position in stack frame
///
EXPORT int unw_set_reg(unw_cursor_t* cursor, unw_regnum_t regNum, unw_word_t value)
{
	DEBUG_PRINT_API("unw_set_reg(cursor=%p, regNum=%d, value=0x%llX)\n", cursor, regNum, value);
	AbstractUnwindCursor* co = (AbstractUnwindCursor*)cursor;

	if ( co->validReg(regNum) ) {
		co->setReg(regNum, value);
		// specical case altering IP to re-find info (being called by personality function)
		if ( regNum == UNW_REG_IP ) {
			unw_proc_info_t info;
			co->getInfo(&info);
			uint64_t orgArgSize = info.gp;
			uint64_t orgFuncStart = info.start_ip;
			co->setInfoBasedOnIPRegister(false);
			// and adjust REG_SP if there was a DW_CFA_GNU_args_size
			if ( (orgFuncStart == info.start_ip) && (orgArgSize != 0) )
				co->setReg(UNW_REG_SP, co->getReg(UNW_REG_SP) + orgArgSize);
		}
		return UNW_ESUCCESS;
	}
	return UNW_EBADREG;
}

///
/// set value of specified float register at cursor position in stack frame
///
EXPORT int unw_set_fpreg(unw_cursor_t* cursor, unw_regnum_t regNum, unw_fpreg_t value)
{
	DEBUG_PRINT_API("unw_set_fpreg(cursor=%p, regNum=%d, value=%g)\n", cursor, regNum, value);
	AbstractUnwindCursor* co = (AbstractUnwindCursor*)cursor;

	if ( co->validFloatReg(regNum) ) {
		return co->setFloatReg(regNum, value);
	}
	return UNW_EBADREG;
}

///
/// resume execution at cursor position (aka longjump) 
///
EXPORT int unw_resume(unw_cursor_t* cursor)
{
	DEBUG_PRINT_API("unw_resume(cursor=%p)\n", cursor);
	AbstractUnwindCursor* co = (AbstractUnwindCursor*)cursor;

	co->jumpto();
	return UNW_EUNSPEC;
}

///
/// returns the name of a register
///
EXPORT const char* unw_regname(unw_cursor_t* cursor, unw_regnum_t regNum)
{
	DEBUG_PRINT_API("unw_regname(cursor=%p, regNum=%d)\n", cursor, regNum);
	AbstractUnwindCursor* co = (AbstractUnwindCursor*)cursor;
	return co->getRegisterName(regNum);
}

///
/// get unwind info at cursor position in stack frame 
///
EXPORT int unw_get_proc_info(unw_cursor_t* cursor, unw_proc_info_t* info)
{
	DEBUG_PRINT_API("unw_get_proc_info(cursor=%p, &info=%p)\n", cursor, info);
	AbstractUnwindCursor* co = (AbstractUnwindCursor*)cursor;
	co->getInfo(info);
	if ( info->end_ip == 0 )
		return UNW_ENOINFO;
	else
		return UNW_ESUCCESS;
}

///
/// checks if a register is a floating-point register 
///
EXPORT int unw_is_fpreg(unw_cursor_t* cursor, unw_regnum_t regNum)
{
	DEBUG_PRINT_API("unw_is_fpreg(cursor=%p, regNum=%d)\n", cursor, regNum);
	AbstractUnwindCursor* co = (AbstractUnwindCursor*)cursor;
	return co->validFloatReg(regNum);
}

///
/// checks if current frame is signal trampoline 
///
EXPORT int unw_is_signal_frame(unw_cursor_t* cursor)
{
	DEBUG_PRINT_API("unw_is_signal_frame(cursor=%p)\n", cursor);
	AbstractUnwindCursor* co = (AbstractUnwindCursor*)cursor;
	return co->isSignalFrame();
}

///
/// get name of function at cursor position in stack frame 
///
EXPORT int unw_get_proc_name(unw_cursor_t* cursor, char* buf, size_t bufLen, unw_word_t* offset)
{
	DEBUG_PRINT_API("unw_get_proc_name(cursor=%p, &buf=%p, bufLen=%ld)\n", cursor, buf, bufLen);
	AbstractUnwindCursor* co = (AbstractUnwindCursor*)cursor;
	if ( co->getFunctionName(buf, bufLen, offset) )
		return UNW_ESUCCESS;
	else
		return UNW_EUNSPEC;
}

#pragma mark Remote API
 
#if defined (SUPPORT_REMOTE_UNWINDING)
EXPORT int unw_init_remote(unw_cursor_t *cursor, unw_addr_space_t as, void *arg)
{
    DEBUG_PRINT_API("init_remote(c=%p, as=%p, arg=%p)\n", cursor, as, arg);
	
    // API docs at http://www.nongnu.org/libunwind/docs.html say we should 
    // handle a local address space but we're not doing the "remote" unwinding
    // with local process accessors so punt on that.

    if(as->type != UNW_REMOTE)
    {
        ABORT("unw_init_remote was passed a non-remote address space");
        return UNW_EINVAL;
    }

	unw_accessors_t* acc = unw_get_accessors(as);
	if(!acc) {
		ABORT("unw_get_accessors returned NULL");
		return UNW_EINVAL;
	}
	
    unw_addr_space_remote* remote = (unw_addr_space_remote*)as;
	
    // use "placement new" to allocate UnwindCursor in the cursor buffer
    // It isn't really necessary to use placement new in the remote API but we'll stay consistent
    // with the rest of the code here.
    switch ( remote->ras->getTargetArch() ) {
        case UNW_TARGET_I386:
            {
                Registers_x86 r;
                // LEAK: "addrSpace" is being leaked every time here with no easy solution.
                // The address space is in the cursor and the cursor may get 
                // duplicated, though if it does get duplicated, it will just be
                // memcpy'ed since unw_cursor_t is just a bunch of uint64_t types...
                OtherAddressSpace<Pointer32<LittleEndian> > *addrSpace = new OtherAddressSpace<Pointer32<LittleEndian> >(as, arg);
                getRemoteContext (remote->ras, r, arg);
                unw_context_t *context = (unw_context_t*) &r;
                new ((void*)cursor) RemoteUnwindCursor<OtherAddressSpace<Pointer32<LittleEndian> >, Registers_x86>(*addrSpace, context, arg);
            }
            break;
        case UNW_TARGET_X86_64:
            {
                Registers_x86_64 r;
                // LEAK: "addrSpace" is being leaked every time here with no easy solution.
                // The address space is in the cursor and the cursor may get 
                // duplicated, though if it does get duplicated, it will just be
                // memcpy'ed since unw_cursor_t is just a bunch of uint64_t types...
                OtherAddressSpace<Pointer64<LittleEndian> > *addrSpace = new OtherAddressSpace<Pointer64<LittleEndian> >(as, arg);
                getRemoteContext (remote->ras, r, arg);
                unw_context_t *context = (unw_context_t*) &r;
                new ((void*)cursor) RemoteUnwindCursor<OtherAddressSpace<Pointer64<LittleEndian> >, Registers_x86_64>(*addrSpace, context, arg);
            }
            break;
            
        case UNW_TARGET_PPC:
              ABORT("ppc not supported for remote unwinds");
            break;

        case UNW_TARGET_ARM:
              ABORT("arm not supported for remote unwinds");
            break;

        default:
            return UNW_EUNSPEC;
    }
	
    AbstractRemoteUnwindCursor* co = (AbstractRemoteUnwindCursor*)cursor;
    co->setRemoteContext(arg);
	
	return UNW_ESUCCESS;
}

// The documentation disagrees about whether or not this returns a pointer.  Now it does.
EXPORT unw_accessors_t* unw_get_accessors(unw_addr_space_t as)
{
	if(as->type != UNW_REMOTE)
	{
		ABORT("unw_get_accessors was passed a non-remote address space");
		return NULL;
	}
	unw_addr_space_remote* remote = (unw_addr_space_remote*)as;
	
	if(remote->type != UNW_REMOTE)
		return NULL;
	
    return remote->ras->getAccessors();
}

EXPORT unw_addr_space_t unw_create_addr_space(unw_accessors_t *ap, unw_targettype_t targarch)
{
	unw_addr_space_remote* remote = (unw_addr_space_remote*)malloc(sizeof(unw_addr_space_remote));
	remote->type = UNW_REMOTE;
    remote->ras = new RemoteProcInfo(ap, targarch);
	return (unw_addr_space_t)remote;
}

EXPORT void unw_flush_caches(unw_addr_space_t as)
{
	if(as->type != UNW_REMOTE)
	{
		ABORT("unw_flush_caches was passed a non-remote address space");
		return;
	}
	unw_addr_space_remote* remote = (unw_addr_space_remote*)as;
	remote->ras->flushAllCaches();
	
	return;
}

EXPORT void unw_image_was_unloaded (unw_addr_space_t as, unw_word_t mh)
{
	if(as->type != UNW_REMOTE)
	{
		ABORT("unw_image_was_unloaded was passed a non-remote address space");
		return;
	}
	unw_addr_space_remote* remote = (unw_addr_space_remote*)as;
	remote->ras->flushCacheByMachHeader(mh);

	return;
}


EXPORT int unw_set_caching_policy(unw_addr_space_t as, unw_caching_policy_t policy)
{
	if(as->type != UNW_REMOTE)
	{
		ABORT("unw_set_caching_policy was passed a non-remote address space");
		return UNW_EINVAL;
	}
	unw_addr_space_remote* remote = (unw_addr_space_remote*)as;
	return remote->ras->setCachingPolicy(policy);
}

EXPORT unw_addr_space_t unw_local_addr_space = (unw_addr_space_t)&sThisAddressSpace;

///
/// delete an address_space object
///
EXPORT void unw_destroy_addr_space(unw_addr_space_t asp)
{
    if(asp->type != UNW_REMOTE) {
        ABORT("unw_destroy_addr_space was passed a non-remote address space");
        return;
    }

    unw_addr_space_remote* remote = (unw_addr_space_remote*)asp;
    delete remote->ras;
}

EXPORT void unw_set_logging_level(unw_addr_space_t as, FILE *f, unw_log_level_t level)
{
    if (as->type != UNW_REMOTE) {
        ABORT("unw_set_logging_level was passed a non-remote address space");
        return;
    }

	unw_addr_space_remote* remote = (unw_addr_space_remote*)as;
	return remote->ras->setLoggingLevel(f, level);
}


EXPORT int unw_end_of_prologue_setup(unw_cursor_t* cursor, unw_word_t start, unw_word_t end, unw_word_t *endofprologue) 
{
    AbstractRemoteUnwindCursor* co = (AbstractRemoteUnwindCursor*)cursor;
    if (!co->remoteUnwindCursor())
        ABORT("unw_end_of_prologue_setup called with a non-remote unwind cursor.");

    return co->endOfPrologueInsns (start, end, endofprologue);
}


#endif // SUPPORT_REMOTE_UNWINDING

#pragma mark Dynamic unwinding API

#if !FOR_DYLD
///
/// SPI: walks cached dwarf entries
///
EXPORT void unw_iterate_dwarf_unwind_cache(void (*func)(unw_word_t ip_start, unw_word_t ip_end, unw_word_t fde, unw_word_t mh))
{
	DEBUG_PRINT_API("unw_iterate_dwarf_unwind_cache(func=%p)\n", func);
	DwarfFDECache<LocalAddressSpace>::iterateCacheEntries(func);
}
#endif // !FOR_DYLD

#if !FOR_DYLD
//
// IPI: for __register_frame()
//
void _unw_add_dynamic_fde(unw_word_t fde)
{
	CFI_Parser<LocalAddressSpace>::FDE_Info fdeInfo;
	CFI_Parser<LocalAddressSpace>::CIE_Info cieInfo;
	const char* message = CFI_Parser<LocalAddressSpace>::decodeFDE(sThisAddressSpace, (LocalAddressSpace::pint_t)fde, & fdeInfo, &cieInfo);
	if ( message == NULL ) {
		// dynamically registered FDEs don't have a mach_header group they are in.  Use fde as mh_group
		unw_word_t mh_group = fdeInfo.fdeStart;
		DwarfFDECache<LocalAddressSpace>::add(mh_group, fdeInfo.pcStart, fdeInfo.pcEnd, fdeInfo.fdeStart);
	}
	else {
		DEBUG_MESSAGE("_unw_add_dynamic_fde: bad fde: %s", message);
	}
}

//
// IPI: for __deregister_frame()
//
void _unw_remove_dynamic_fde(unw_word_t fde)
{
	// fde is own mh_group
	DwarfFDECache<LocalAddressSpace>::removeAllIn(fde);
}
#endif

}; // namespace lldb_private

#endif // __ppc__ || __i386__ ||  __x86_64__
