/** 
 * @file llstacktrace.cpp
 * @brief stack tracing functionality
 *
 * $LicenseInfo:firstyear=2001&license=viewergpl$
 * 
 * Copyright (c) 2001-2009, Linden Research, Inc.
 * 
 * Second Life Viewer Source Code
 * The source code in this file ("Source Code") is provided by Linden Lab
 * to you under the terms of the GNU General Public License, version 2.0
 * ("GPL"), unless you have obtained a separate licensing agreement
 * ("Other License"), formally executed by you and Linden Lab.  Terms of
 * the GPL can be found in doc/GPL-license.txt in this distribution, or
 * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
 * 
 * There are special exceptions to the terms and conditions of the GPL as
 * it is applied to this Source Code. View the full text of the exception
 * in the file doc/FLOSS-exception.txt in this software distribution, or
 * online at
 * http://secondlifegrid.net/programs/open_source/licensing/flossexception
 * 
 * By copying, modifying or distributing this software, you acknowledge
 * that you have read and understood your obligations described above,
 * and agree to abide by those obligations.
 * 
 * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
 * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
 * COMPLETENESS OR PERFORMANCE.
 * $/LicenseInfo$
 */

#include "linden_common.h"
#include "llstacktrace.h"

#ifdef LL_WINDOWS

#include <iostream>
#include <sstream>

#include "windows.h"
#include "Dbghelp.h"

typedef USHORT NTAPI RtlCaptureStackBackTrace_Function(
    IN ULONG frames_to_skip,
    IN ULONG frames_to_capture,
    OUT PVOID *backtrace,
    OUT PULONG backtrace_hash);

static RtlCaptureStackBackTrace_Function* const RtlCaptureStackBackTrace_fn =
   (RtlCaptureStackBackTrace_Function*)
   GetProcAddress(GetModuleHandleA("ntdll.dll"), "RtlCaptureStackBackTrace");

bool ll_get_stack_trace(std::vector<std::string>& lines)
{
	const S32 MAX_STACK_DEPTH = 32;
	const S32 STRING_NAME_LENGTH = 200;
	const S32 FRAME_SKIP = 2;
	static BOOL symbolsLoaded = false;
	static BOOL firstCall = true;

	HANDLE hProc = GetCurrentProcess();

	// load the symbols if they're not loaded
	if(!symbolsLoaded && firstCall)
	{
		symbolsLoaded = SymInitialize(hProc, NULL, true);
		firstCall = false;
	}

	// if loaded, get the call stack
	if(symbolsLoaded)
	{
		// create the frames to hold the addresses
		void* frames[MAX_STACK_DEPTH];
		memset(frames, 0, sizeof(void*)*MAX_STACK_DEPTH);
		S32 depth = 0;

		// get the addresses
		depth = RtlCaptureStackBackTrace_fn(FRAME_SKIP, MAX_STACK_DEPTH, frames, NULL);

		IMAGEHLP_LINE64 line;
		memset(&line, 0, sizeof(IMAGEHLP_LINE64));
		line.SizeOfStruct = sizeof(IMAGEHLP_LINE64);

		// create something to hold address info
		PIMAGEHLP_SYMBOL64 pSym;
		pSym = (PIMAGEHLP_SYMBOL64)malloc(sizeof(IMAGEHLP_SYMBOL64) + STRING_NAME_LENGTH);
		memset(pSym, 0, sizeof(IMAGEHLP_SYMBOL64) + STRING_NAME_LENGTH);
		pSym->MaxNameLength = STRING_NAME_LENGTH;
		pSym->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL64);

		// get address info for each address frame
		// and store
		for(S32 i=0; i < depth; i++)
		{
			std::stringstream stack_line;
			BOOL ret;

			DWORD64 addr = (DWORD64)frames[i];
			ret = SymGetSymFromAddr64(hProc, addr, 0, pSym);
			if(ret)
			{
				stack_line << pSym->Name << " ";
			}

			DWORD dummy;
			ret = SymGetLineFromAddr64(hProc, addr, &dummy, &line);
			if(ret)
			{
				std::string file_name = line.FileName;
				std::string::size_type index = file_name.rfind("\\");
				stack_line << file_name.substr(index + 1, file_name.size()) << ":" << line.LineNumber; 
			}

			lines.push_back(stack_line.str());
		}
		
		free(pSym);

		// TODO: figure out a way to cleanup symbol loading
		// Not hugely necessary, however.
		//SymCleanup(hProc);
		return true;
	}
	else
	{
		lines.push_back("Stack Trace Failed.  PDB symbol info not loaded");
	}

	return false;
}

#else

bool ll_get_stack_trace(std::vector<std::string>& lines)
{
	return false;
}

#endif