/*
   +----------------------------------------------------------------------+
   | Zend Engine                                                          |
   +----------------------------------------------------------------------+
   | Copyright © Zend Technologies Ltd., a subsidiary company of          |
   |     Perforce Software, Inc., and Contributors.                       |
   +----------------------------------------------------------------------+
   | This source file is subject to the Modified BSD License that is      |
   | bundled with this package in the file LICENSE, and is available      |
   | through the World Wide Web at <https://www.php.net/license/>.        |
   |                                                                      |
   | SPDX-License-Identifier: BSD-3-Clause                                |
   +----------------------------------------------------------------------+
   | Authors: Arnaud Le Blanc <arnaud.lb@gmail.com>                       |
   +----------------------------------------------------------------------+
*/

/* Inspired from Chromium's stack_util.cc */

#include "zend.h"
#include "zend_globals.h"
#include "zend_portability.h"
#include "zend_call_stack.h"
#include <stdint.h>
#ifdef ZEND_WIN32
# include <processthreadsapi.h>
# include <memoryapi.h>
#else /* ZEND_WIN32 */
# include <sys/resource.h>
# ifdef HAVE_UNISTD_H
#  include <unistd.h>
# endif
# ifdef HAVE_SYS_TYPES_H
#  include <sys/types.h>
# endif
#endif /* ZEND_WIN32 */
#if (defined(HAVE_PTHREAD_GETATTR_NP) && defined(HAVE_PTHREAD_ATTR_GETSTACK)) || \
    defined(__FreeBSD__) || defined(__APPLE__) || defined(__OpenBSD__) || \
    defined(__NetBSD__) || defined(__DragonFly__) || defined(__sun) || \
    defined(_AIX)
# include <pthread.h>
#endif
#if defined(__FreeBSD__) || defined(__DragonFly__)
# include <pthread_np.h>
# include <sys/mman.h>
# include <sys/sysctl.h>
# include <sys/user.h>
#endif
#ifdef __OpenBSD__
typedef int boolean_t;
# include <tib.h>
# include <pthread_np.h>
# include <sys/sysctl.h>
# include <sys/user.h>
#endif
#ifdef __NetBSD__
# include <sys/sysctl.h>
# include <sys/syscall.h>
#endif
#ifdef __HAIKU__
# include <kernel/OS.h>
#endif
#ifdef __linux__
#include <sys/syscall.h>
#endif
#ifdef __sun
# include <sys/lwp.h>
# ifdef HAVE_LIBPROC_H
#  define _STRUCTURED_PROC 1
#  include <sys/procfs.h>
#  include <libproc.h>
# endif
#include <thread.h>
#endif

#ifdef HAVE_VALGRIND
# include <valgrind/valgrind.h>
#endif

#ifdef ZEND_CHECK_STACK_LIMIT

/* Called once per process or thread */
ZEND_API void zend_call_stack_init(void) {
	if (!zend_call_stack_get(&EG(call_stack))) {
		EG(call_stack) = (zend_call_stack){0};
	}

	switch (EG(max_allowed_stack_size)) {
		case ZEND_MAX_ALLOWED_STACK_SIZE_DETECT: {
			void *base = EG(call_stack).base;
			size_t size = EG(call_stack).max_size;
			if (UNEXPECTED(base == (void*)0)) {
				base = zend_call_stack_position();
				size = zend_call_stack_default_size();
				/* base is not the actual stack base */
				size -= 32 * 1024;
			}
			EG(stack_base) = base;
			EG(stack_limit) = zend_call_stack_limit(base, size, EG(reserved_stack_size));
			break;
		}
		case ZEND_MAX_ALLOWED_STACK_SIZE_UNCHECKED: {
			EG(stack_base) = (void*)0;
			EG(stack_limit) = (void*)0;
			break;
		}
		default: {
			ZEND_ASSERT(EG(max_allowed_stack_size) > 0);
			void *base = EG(call_stack).base;
			if (UNEXPECTED(base == (void*)0)) {
				base = zend_call_stack_position();
			}
			EG(stack_base) = base;
			EG(stack_limit) = zend_call_stack_limit(base, EG(max_allowed_stack_size), EG(reserved_stack_size));
			break;
		}
	}
}

#ifdef __linux__
static bool zend_call_stack_is_main_thread(void) {
# ifdef HAVE_GETTID
	return getpid() == gettid();
# else
	return getpid() == syscall(SYS_gettid);
# endif
}

# if defined(HAVE_PTHREAD_GETATTR_NP) && defined(HAVE_PTHREAD_ATTR_GETSTACK)
static bool zend_call_stack_get_linux_pthread(zend_call_stack *stack)
{
	pthread_attr_t attr;
	int error;
	void *addr;
	size_t max_size;

	/* pthread_getattr_np() will return bogus values for the main thread with
	 * musl or with some old glibc versions */
	ZEND_ASSERT(!zend_call_stack_is_main_thread());

	error = pthread_getattr_np(pthread_self(), &attr);
	if (UNEXPECTED(error)) {
		return false;
	}

	error = pthread_attr_getstack(&attr, &addr, &max_size);
	if (UNEXPECTED(error)) {
		pthread_attr_destroy(&attr);
		return false;
	}

#  if defined(__GLIBC__) && (__GLIBC__ < 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ < 8))
	{
		size_t guard_size;
		/* In glibc prior to 2.8, addr and size include the guard pages */
		error = pthread_attr_getguardsize(&attr, &guard_size);
		if (UNEXPECTED(error)) {
			pthread_attr_destroy(&attr);
			return false;
		}

		addr = (int8_t*)addr + guard_size;
		max_size -= guard_size;
	}
#  endif /* glibc < 2.8 */

	stack->base = (int8_t*)addr + max_size;
	stack->max_size = max_size;

	pthread_attr_destroy(&attr);

	return true;
}
# else /* defined(HAVE_PTHREAD_GETATTR_NP) && defined(HAVE_PTHREAD_ATTR_GETSTACK) */
static bool zend_call_stack_get_linux_pthread(zend_call_stack *stack)
{
	return false;
}
# endif /* defined(HAVE_PTHREAD_GETATTR_NP) && defined(HAVE_PTHREAD_ATTR_GETSTACK) */

static bool zend_call_stack_get_linux_proc_maps(zend_call_stack *stack)
{
	FILE *f;
	char buffer[4096];
	uintptr_t addr_on_stack = (uintptr_t) zend_call_stack_position();
	uintptr_t start, end, prev_end = 0;
	size_t max_size;
	bool found = false;
	struct rlimit rlim;
	int error;

	/* This method is relevant only for the main thread */
	ZEND_ASSERT(zend_call_stack_is_main_thread());

	/* Scan the process memory mappings to find the one containing the stack.
	 *
	 * The end of the stack mapping is the base of the stack. The start is
	 * adjusted by the kernel as the stack grows. The maximum stack size is
	 * determined by RLIMIT_STACK and the previous mapping.
	 *
	 *
	 *                   ^ Higher addresses  ^
	 *                   :                   :
	 *                   :                   :
	 *   Mapping end --> |-------------------| <-- Stack base (stack start)
	 *                   |                   |   ^
	 *                   | Stack Mapping     |   | Stack size
	 *                   |                   |   v
	 * Mapping start --> |-------------------| <-- Current stack end
	 * (adjusted         :                   :
	 *  downwards as the .                   .
	 *  stack grows)     :                   :
	 *                   |-------------------|
	 *                   | Some Mapping      | The previous mapping may prevent
	 *                   |-------------------| stack growth
	 *                   :                   :
	 *                   :                   :
	 *                   v Lower addresses   v
	 */

	f = fopen("/proc/self/maps", "r");
	if (!f) {
		return false;
	}

	while (fgets(buffer, sizeof(buffer), f) && sscanf(buffer, "%" SCNxPTR "-%" SCNxPTR, &start, &end) == 2) {
		if (start <= addr_on_stack && end >= addr_on_stack) {
			found = true;
			break;
		}
		prev_end = end;
	}

	fclose(f);

	if (!found) {
		return false;
	}

	error = getrlimit(RLIMIT_STACK, &rlim);
	if (error || rlim.rlim_cur == RLIM_INFINITY) {
		return false;
	}

	max_size = rlim.rlim_cur;

#ifdef HAVE_VALGRIND
	/* Under Valgrind, the last page is not useable */
	if (RUNNING_ON_VALGRIND) {
		max_size -= zend_get_page_size();
	}
#endif

	/* Previous mapping may prevent the stack from growing */
	if (end - max_size < prev_end) {
		max_size = prev_end - end;
	}

	stack->base = (void*)end;
	stack->max_size = max_size;

	return true;
}

static bool zend_call_stack_get_linux(zend_call_stack *stack)
{
	if (zend_call_stack_is_main_thread()) {
		return zend_call_stack_get_linux_proc_maps(stack);
	}

	return zend_call_stack_get_linux_pthread(stack);
}
#else /* __linux__ */
static bool zend_call_stack_get_linux(zend_call_stack *stack)
{
	return false;
}
#endif /* __linux__ */

#if defined(__FreeBSD__) || defined(__DragonFly__)
static bool zend_call_stack_is_main_thread(void)
{
	int is_main = pthread_main_np();
	return is_main == -1 || is_main == 1;
}

# if defined(HAVE_PTHREAD_ATTR_GET_NP) && defined(HAVE_PTHREAD_ATTR_GETSTACK)
static bool zend_call_stack_get_freebsd_pthread(zend_call_stack *stack)
{
	pthread_attr_t attr;
	int error;
	void *addr;
	size_t max_size;

	/* pthread will return bogus values for the main thread */
	ZEND_ASSERT(!zend_call_stack_is_main_thread());

	pthread_attr_init(&attr);

	error = pthread_attr_get_np(pthread_self(), &attr);
	if (error) {
		goto fail;
	}

	error = pthread_attr_getstack(&attr, &addr, &max_size);
	if (error) {
		goto fail;
	}

	stack->base = (int8_t*)addr + max_size;
	stack->max_size = max_size;

	pthread_attr_destroy(&attr);
	return true;

fail:
	pthread_attr_destroy(&attr);
	return false;
}
# else /* defined(HAVE_PTHREAD_ATTR_GET_NP) && defined(HAVE_PTHREAD_ATTR_GETSTACK) */
static bool zend_call_stack_get_freebsd_pthread(zend_call_stack *stack)
{
	return false;
}
# endif /* defined(HAVE_PTHREAD_ATTR_GET_NP) && defined(HAVE_PTHREAD_ATTR_GETSTACK) */

static bool zend_call_stack_get_freebsd_sysctl(zend_call_stack *stack)
{
	void *stack_base;
	int mib[2] = {CTL_KERN, KERN_USRSTACK};
	size_t len = sizeof(stack_base);
	struct rlimit rlim;
	size_t numguards = 0;

	/* This method is relevant only for the main thread */
	ZEND_ASSERT(zend_call_stack_is_main_thread());

	if (sysctl(mib, sizeof(mib)/sizeof(*mib), &stack_base, &len, NULL, 0) != 0) {
		return false;
	}

	if (getrlimit(RLIMIT_STACK, &rlim) != 0) {
		return false;
	}

	if (rlim.rlim_cur == RLIM_INFINITY) {
		return false;
	}

	len = sizeof(numguards);
	/* For most of the cases, we do not necessarily need to do so as, by default, it is `1` page, but is user writable */
	if (sysctlbyname("security.bsd.stack_guard_page", &numguards, &len, NULL, 0) != 0) {
		return false;
	}

	size_t guard_size = numguards * getpagesize();

	stack->base = stack_base;
	stack->max_size = rlim.rlim_cur - guard_size;

	return true;
}

static bool zend_call_stack_get_freebsd(zend_call_stack *stack)
{
	if (zend_call_stack_is_main_thread()) {
		return zend_call_stack_get_freebsd_sysctl(stack);
	}

	return zend_call_stack_get_freebsd_pthread(stack);
}
#else
static bool zend_call_stack_get_freebsd(zend_call_stack *stack)
{
	return false;
}
#endif /* __FreeBSD__ */

#ifdef ZEND_WIN32
static bool zend_call_stack_get_win32(zend_call_stack *stack)
{
	ULONG_PTR low_limit, high_limit;
	MEMORY_BASIC_INFORMATION guard_region = {0}, uncommitted_region = {0};
	size_t result_size, page_size;

	/* The stack consists of three regions: committed, guard, and uncommitted.
	 * Memory is committed when the guard region is accessed. If only one page
	 * is left in the uncommitted region, a stack overflow error is raised
	 * instead.
	 *
	 * The total useable stack size is the size of the committed and uncommitted
	 * regions less one page.
	 *
	 * http://blogs.msdn.com/b/satyem/archive/2012/08/13/thread-s-stack-memory-management.aspx
	 * https://learn.microsoft.com/en-us/windows/win32/procthread/thread-stack-size
	 *
	 *                ^  Higher addresses  ^
	 *                :                    :
	 *                :                    :
	 * high_limit --> |--------------------|
	 *            ^   |                    |
	 *            |   | Committed region   |
	 *            |   |                    |
	 *            |   |------------------- | <-- guard_region.BaseAddress
	 *   reserved |   |                    |     + guard_region.RegionSize
	 *       size |   | Guard region       |
	 *            |   |                    |
	 *            |   |--------------------| <-- guard_region.BaseAddress,
	 *            |   |                    |     uncommitted_region.BaseAddress
	 *            |   | Uncommitted region |     + uncommitted_region.RegionSize
	 *            v   |                    |
	 * low_limit  --> |------------------- | <-- uncommitted_region.BaseAddress
	 *                :                    :
	 *                :                    :
	 *                v  Lower addresses   v
	 */

	GetCurrentThreadStackLimits(&low_limit, &high_limit);

	result_size = VirtualQuery((void*)low_limit,
			&uncommitted_region, sizeof(uncommitted_region));
	ZEND_ASSERT(result_size >= sizeof(uncommitted_region));

	result_size = VirtualQuery((int8_t*)uncommitted_region.BaseAddress + uncommitted_region.RegionSize,
			&guard_region, sizeof(guard_region));
	ZEND_ASSERT(result_size >= sizeof(uncommitted_region));

	stack->base = (void*)high_limit;
	stack->max_size = (uintptr_t)high_limit - (uintptr_t)low_limit;

	ZEND_ASSERT(stack->max_size > guard_region.RegionSize);
	stack->max_size -= guard_region.RegionSize;

	/* The uncommitted region does not shrink below 1 page */
	page_size = zend_get_page_size();
	ZEND_ASSERT(stack->max_size > page_size);
	stack->max_size -= page_size;

	return true;
}
#else /* ZEND_WIN32 */
static bool zend_call_stack_get_win32(zend_call_stack *stack)
{
	return false;
}
#endif /* ZEND_WIN32 */

#if defined(__APPLE__) && defined(HAVE_PTHREAD_GET_STACKADDR_NP)
static bool zend_call_stack_get_macos(zend_call_stack *stack)
{
	void *base = pthread_get_stackaddr_np(pthread_self());
	size_t max_size;

#if !defined(__aarch64__)
	if (pthread_main_np())
	{
		/* pthread_get_stacksize_np() returns a too low value for the main
		 * thread in OSX 10.9, 10.10:
		 * https://mail.openjdk.org/pipermail/hotspot-dev/2013-October/011353.html
		 * https://github.com/rust-lang/rust/issues/43347
		 */

		/* Stack size is 8MiB by default for main threads
		 * https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/CreatingThreads/CreatingThreads.html */
		max_size = 8 * 1024 * 1024;
	}
	else
#endif
	{
		max_size = pthread_get_stacksize_np(pthread_self());
	}

	stack->base = base;
	stack->max_size = max_size;

	return true;
}
#else /* defined(__APPLE__) && defined(HAVE_PTHREAD_GET_STACKADDR_NP) */
static bool zend_call_stack_get_macos(zend_call_stack *stack)
{
	return false;
}
#endif /* defined(__APPLE__) && defined(HAVE_PTHREAD_GET_STACKADDR_NP) */

#if defined(__OpenBSD__)
#if defined(HAVE_PTHREAD_STACKSEG_NP)
static bool zend_call_stack_get_openbsd_pthread(zend_call_stack *stack)
{
	stack_t ss;

	if (pthread_stackseg_np(pthread_self(), &ss) != 0) {
		return false;
	}

	stack->base = (char *)ss.ss_sp - ss.ss_size;
	stack->max_size = ss.ss_size - sysconf(_SC_PAGE_SIZE);

	return true;
}
#else
static bool zend_call_stack_get_openbsd_pthread(zend_call_stack *stack)
{
	return false;
}
#endif /* defined(HAVE_PTHREAD_STACKSEG_NP) */

static bool zend_call_stack_get_openbsd_vm(zend_call_stack *stack)
{
	struct _ps_strings ps;
	struct rlimit rlim;
	int mib[2] = {CTL_VM, VM_PSSTRINGS };
	size_t len = sizeof(ps), pagesize;

	if (sysctl(mib, 2, &ps, &len, NULL, 0) != 0) {
		return false;
	}

	if (getrlimit(RLIMIT_STACK, &rlim) != 0) {
		return false;
	}

	if (rlim.rlim_cur == RLIM_INFINITY) {
		return false;
	}

	pagesize = sysconf(_SC_PAGE_SIZE);

	stack->base = (void *)((uintptr_t)ps.val + (pagesize - 1) & ~(pagesize - 1));
	stack->max_size = rlim.rlim_cur - pagesize;

	return true;
}

static bool zend_call_stack_get_openbsd(zend_call_stack *stack)
{
	// TIB_THREAD_INITIAL_STACK is private and here we avoid using pthread's api (ie pthread_main_np)
	if (!TIB_GET()->tib_thread || (TIB_GET()->tib_thread_flags & 0x002) != 0) {
		return zend_call_stack_get_openbsd_vm(stack);
	}

	return zend_call_stack_get_openbsd_pthread(stack);
}

#else
static bool zend_call_stack_get_openbsd(zend_call_stack *stack)
{
	return false;
}
#endif /* defined(__OpenBSD__) */
#if defined(__HAIKU__)
static bool zend_call_stack_get_haiku(zend_call_stack *stack)
{
	thread_id id;
	thread_info ti;
	size_t guard_size;

	// unlikely, main thread ought to be always available but we never know
	if ((id = find_thread(NULL)) == B_NAME_NOT_FOUND || get_thread_info(id, &ti) != B_OK) {
		return false;
	}

	// USER_STACK_GUARD_SIZE
	guard_size = sysconf(_SC_PAGESIZE) * 4;

	stack->base = ti.stack_end;
	stack->max_size = ((size_t)ti.stack_end - (size_t)ti.stack_base) - guard_size;

	return true;
}
#else
static bool zend_call_stack_get_haiku(zend_call_stack *stack)
{
	return false;
}
#endif /* defined(__HAIKU__) */

#if defined(__NetBSD__)
# ifdef HAVE_PTHREAD_GETATTR_NP
static bool zend_call_stack_get_netbsd_pthread(zend_call_stack *stack)
{
	pthread_attr_t attr;
	int error;
	void *addr;
	size_t max_size, guard_size;

	error = pthread_getattr_np(pthread_self(), &attr);
	if (error) {
		return false;
	}

	error = pthread_attr_getstack(&attr, &addr, &max_size);
	if (error) {
		return false;
	}

	error = pthread_attr_getguardsize(&attr, &guard_size);
	if (error) {
		return false;
	}

	addr = (char *)addr + guard_size;
	max_size -= guard_size;

	stack->base = (char *)addr + max_size;
	stack->max_size = max_size;

	return true;
}
# else
static bool zend_call_stack_get_netbsd_pthread(zend_call_stack *stack)
{
	return false;
}
# endif /* HAVE_PTHREAD_GETATTR_NP */
static bool zend_call_stack_get_netbsd_vm(zend_call_stack *stack, void **ptr)
{
	/**
	 * NetBSD supports procfs in a similar fashion as Linux
	 * however NetBSD's mid/long term plan is to remove it completely.
	 */
	char *start, *end;
	struct kinfo_vmentry *entry;
	size_t len, max_size;
	uintptr_t addr_on_stack = (uintptr_t) zend_call_stack_position();
	int mib[5] = { CTL_VM, VM_PROC, VM_PROC_MAP, getpid(), sizeof(struct kinfo_vmentry) };
	bool found = false;
	struct rlimit rlim;

	if (sysctl(mib, 5, NULL, &len, NULL, 0) != 0) {
		return false;
	}

	// kinfo_getvmmap uses the same formula, only we do not want to rely on libkvm
	len = len * 4 / 3 ;
	*ptr = malloc(len);

	if (sysctl(mib, 5, *ptr, &len, NULL, 0) != 0) {
		return false;
	}

	start = (char *)*ptr;
	end = start + len;

	while (start < end) {
		entry = (struct kinfo_vmentry *)start;
		if (entry->kve_start <= addr_on_stack && entry->kve_end >= addr_on_stack) {
			found = true;
			break;
		}

		start += sizeof(struct kinfo_vmentry);
	}

	if (!found) {
		return false;
	}

	if (getrlimit(RLIMIT_STACK, &rlim) || rlim.rlim_cur == RLIM_INFINITY) {
		return false;
	}

	max_size = rlim.rlim_cur;

	stack->base = (void *)entry->kve_end;
	stack->max_size = max_size;

	return true;
}


static bool zend_call_stack_get_netbsd(zend_call_stack *stack)
{
	if (syscall(SYS__lwp_self) == 1) {
		void *ptr = NULL;
		bool r = zend_call_stack_get_netbsd_vm(stack, &ptr);
		free(ptr);
		return r;
	}

	return zend_call_stack_get_netbsd_pthread(stack);
}
#else
static bool zend_call_stack_get_netbsd(zend_call_stack *stack)
{
	return false;
}
#endif /* defined(__NetBSD__) */

#if defined(__sun)
static bool zend_call_stack_get_solaris_pthread(zend_call_stack *stack)
{
	stack_t s;
	if (thr_stksegment(&s) < 0) {
		return false;
	}

	stack->max_size = s.ss_size;
	stack->base = s.ss_sp;
	return true;
}

#ifdef HAVE_LIBPROC_H
static bool zend_call_stack_get_solaris_proc_maps(zend_call_stack *stack)
{
	uintptr_t addr_on_stack = (uintptr_t) zend_call_stack_position();
	bool found = false, r = false;
	struct ps_prochandle *proc;
	prmap_t *map, *orig;
	struct rlimit rlim;
	char path[PATH_MAX];
	size_t size;
	ssize_t len = -1;
	pid_t pid;
	int error, fd;

	pid = getpid();
	proc = Pgrab(pid, PGRAB_RDONLY, &error);
	if (!proc) {
		return false;
	}

	size = (1 << 20);
	snprintf(path, sizeof(path), "/proc/%d/map", (int)pid);

	if ((fd = open(path, O_RDONLY)) == -1) {
		Prelease(proc, 0);
		return false;
	}

	orig = malloc(size);
	if (!orig) {
		Prelease(proc, 0);
		close(fd);
		return false;
	}

	while (size > 0 && (len = pread(fd, orig, size, 0)) == size) {
		prmap_t *tmp;
		size <<= 1;
		tmp = realloc(orig, size);
		if (!tmp) {
			goto end;
		}
		orig = tmp;
	}

	for (map = orig; len > 0; ++map) {
		if ((uintptr_t)map->pr_vaddr <= addr_on_stack && (uintptr_t)map->pr_vaddr + map->pr_size >= addr_on_stack) {
			found = true;
			break;
		}
		len -= sizeof(*map);
	}

	if (!found) {
		goto end;
	}

	error = getrlimit(RLIMIT_STACK, &rlim);
	if (error || rlim.rlim_cur == RLIM_INFINITY) {
		goto end;
	}

	stack->base = (void *)map->pr_vaddr + map->pr_size;
	stack->max_size = rlim.rlim_cur;
	r = true;

end:
	free(orig);
	Prelease(proc, 0);
	close(fd);
	return r;
}
#endif

static bool zend_call_stack_get_solaris(zend_call_stack *stack)
{
#ifdef HAVE_LIBPROC_H
	if (_lwp_self() == 1) {
		return zend_call_stack_get_solaris_proc_maps(stack);
	}
#endif
	return zend_call_stack_get_solaris_pthread(stack);
}
#else
static bool zend_call_stack_get_solaris(zend_call_stack *stack)
{
	return false;
}
#endif /* defined(__sun) */

#if defined(_AIX)
static bool zend_call_stack_get_aix_pthread(zend_call_stack *stack)
{
#ifdef HAVE_PTHREAD_GETTHRDS_NP
	pthread_t pt = pthread_self();
	struct __pthrdsinfo thread_info = {0};
	/*
	 * We don't need the register buffer since we only call the function
	 * on our own thread, and since the register buffer is only used for
	 * suspended threads...
	 */
	int regsz = 0;

	if (pthread_getthrds_np(&pt, PTHRDSINFO_QUERY_ALL, &thread_info,
				sizeof(thread_info), NULL, &regsz)) {
		return false;
	}

	/*
	 * These can be null in rare situations, allegedly with user-provided
	 * stacks with pthread (according to OpenJDK)
	 */
	if (!(thread_info.__pi_stackend && thread_info.__pi_stackaddr)) {
		return false;
	}

	/*
	 * The top of the stack (stackend) is not page aligned, there's some
	 * internal stuff above it. Thankfully, we don't need page alignment.
	 *
	 * The size is a little weird. The stacksize field for child threads
	 * is smaller than subtracting the bottom (stackaddr) from the top;
	 * it's about 0x888 to 0x1888 above stackaddr. I'm assuming it rounds
	 * the bottom of the stack to page alignment? The main thread size is
	 * the same as end - addr, but it is variable between systems; also
	 * assuming there's stuff at the top of the stack that gets taken off,
	 * regardless of maximum declared size.
	 *
	 * A somewhat crude diagram is available here:
	 * https://www.ibm.com/docs/en/aix/7.2.0?topic=tuning-thread-environment-variables
	 *
	 * pthread->pt_stk.st_limit is __pi_stackend,
	 * above that is internal pthread junk close to the end of page
	 * pthread->pt_stk.st_base is __pi_stackaddr,
	 * below that is the red zone
	 */
	stack->base = thread_info.__pi_stackend;
	stack->max_size = thread_info.__pi_stackend - thread_info.__pi_stackaddr;
	return true;
#else
	/* pthread likely not linked in; default NTS build behaviour */
	return false;
#endif
}

static bool zend_call_stack_get_aix(zend_call_stack *stack)
{
	/*
	 * While we could use /proc on AIX (and the implementation basically
	 * like the Solaris one, as the procfs is similar), it doesn't work on
	 * PASE. The pthread API works even on the main thread, so we should
	 * use it when we have pthread linked (always with ZTS, maybe not with
	 * NTS builds).
	 */
	return zend_call_stack_get_aix_pthread(stack);
}
#else
static bool zend_call_stack_get_aix(zend_call_stack *stack)
{
	return false;
}
#endif /* defined(_AIX) */

/** Get the stack information for the calling thread */
ZEND_API bool zend_call_stack_get(zend_call_stack *stack)
{
	if (zend_call_stack_get_linux(stack)) {
		return true;
	}

	if (zend_call_stack_get_freebsd(stack)) {
		return true;
	}

	if (zend_call_stack_get_win32(stack)) {
		return true;
	}

	if (zend_call_stack_get_macos(stack)) {
		return true;
	}

	if (zend_call_stack_get_openbsd(stack)) {
		return true;
	}

	if (zend_call_stack_get_netbsd(stack)) {
		return true;
	}

	if (zend_call_stack_get_haiku(stack)) {
		return true;
	}

	if (zend_call_stack_get_solaris(stack)) {
		return true;
	}

	if (zend_call_stack_get_aix(stack)) {
		return true;
	}

	return false;
}

#endif /* ZEND_CHECK_STACK_LIMIT */
