// Matrix Construct
//
// Copyright (C) Matrix Construct Developers, Authors & Contributors
// Copyright (C) 2016-2019 Jason Volk <jason@zemos.net>
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice is present in all copies. The
// full license for this software is available in the LICENSE file.

#include <RB_INC_EXECINFO_H
#include <RB_INC_SYS_MMAN_H

#if defined(__GNUC__) && defined(HAVE_EXECINFO_H)
	#define IRCD_BACKTRACE_SUPPORT
#endif

#if defined(IRCD_BACKTRACE_SUPPORT) && defined(HAVE_SYS_MMAN_H) && !defined(__clang__)
	#define IRCD_BACKTRACE_GLIBC_WORKAROUND
#endif

#if defined(IRCD_BACKTRACE_GLIBC_WORKAROUND)
// The problem here is that backtrace(3) performs checks against this value
// which is not correct for our own allocated `ircd::ctx` stacks. To workaround
// this we neutralize the value before calling backtrace(3) and restore it
// afterward. While this symbol is extern accessible, the page its on is
// write-protected at some point, so we have to re-allow writing to it here.
//
// sysdeps/i386/backtrace.c | elf/dl-support.c
// > This is a global variable set at program start time.  It marks the
// > highest used stack address.
//
extern void *
__libc_stack_end;

void
__attribute__((constructor))
ircd_backtrace_allow_libc_fix()
{
	static const auto &prot
	{
		PROT_READ | PROT_WRITE
	};

	const auto &addr
	{
		uintptr_t(&__libc_stack_end) & ~(getpagesize() - 1UL)
	};

	ircd::syscall(::mprotect, (void *)addr, sizeof(__libc_stack_end), prot);
}
#endif defined(IRCD_BACKTRACE_GLIBC_WORKAROUND)

//
// backtrace::backtrace
//

namespace ircd
{
	thread_local std::array<const void *, 512> backtrace_buffer;
}

ircd::backtrace::backtrace()
:backtrace
{
	backtrace_buffer.data(),
	backtrace_buffer.size()
}
{
}

ircd::backtrace::backtrace(const mutable_buffer &buf)
:backtrace
{
	reinterpret_cast<const void **>
	(
		const_cast<const char **>
		(
			std::addressof(data(buf))
		)
	),
	buffer::size(buf) / sizeof(void *)
}
{
}

ircd::backtrace::backtrace(const void **const &array,
                           const size_t &count)
:array
{
	array
}
,count
{
	0UL
}
{
	#if defined(IRCD_BACKTRACE_GLIBC_WORKAROUND)
	const scope_restore stack_check_workaround
	{
		__libc_stack_end, reinterpret_cast<void *>(uintptr_t(-1UL))
	};
	#endif

	#if defined(IRCD_BACKTRACE_SUPPORT)
	this->count = ::backtrace(const_cast<void **>(this->array), count);
	#endif
}