This is xnu-11215.1.10. See this file in:
/*
 * Copyright (c) 2019 Apple Inc. All rights reserved.
 *
 * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
 *
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. The rights granted to you under the License
 * may not be used to create, or enable the creation or redistribution of,
 * unlawful or unlicensed copies of an Apple operating system, or to
 * circumvent, violate, or enable the circumvention or violation of, any
 * terms of an Apple operating system software license agreement.
 *
 * Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this file.
 *
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
 */

#include <sys/kdebug.h>
#include <sys/sysctl.h>
#include <san/kcov.h>
#include <san/kcov_data.h>
#include <san/kcov_stksz.h>

#include <mach/vm_param.h>

#ifdef CONFIG_DTRACE
#include <mach/sdt.h>
#endif


/* Enables stack size recording. */
static uint32_t stksz_enabled = 0;

/* Stack size that represents the watermark. */
static uint32_t stksz_threshold = KERNEL_STACK_SIZE + 1; /* Larger than stack == disabled. */

/* Stack size increment to trigger delta event. */
static uint32_t stksz_delta = 0;


/*
 * Sysctl interface for stack size monitoring.
 */

SYSCTL_DECL(_kern_kcov);
SYSCTL_NODE(_kern_kcov, OID_AUTO, stksz, CTLFLAG_RW | CTLFLAG_LOCKED, 0, "stksz");
SYSCTL_INT(_kern_kcov_stksz, OID_AUTO, threshold, CTLFLAG_RW | CTLFLAG_LOCKED, &stksz_threshold,
    KERNEL_STACK_SIZE + 1, "stack size threshold");
SYSCTL_INT(_kern_kcov_stksz, OID_AUTO, delta, CTLFLAG_RW | CTLFLAG_LOCKED, &stksz_delta,
    KERNEL_STACK_SIZE + 1, "stack delta");

static int
sysctl_kcov_stksz_enabled SYSCTL_HANDLER_ARGS
{
	int err;
	int value = *(int *)arg1;

	err = sysctl_io_number(req, value, sizeof(value), &value, NULL);
	if (err) {
		return err;
	}

	if (req->newptr) {
		if (!(value == 0 || value == 1)) {
			return ERANGE;
		}

		/* No change. */
		if (value == stksz_enabled) {
			return 0;
		}

		/* enable/disable ksancov global switch when required. */
		if (stksz_enabled) {
			kcov_disable();
		} else {
			kcov_enable();
		}
		stksz_enabled = value;
	}

	return 0;
}

SYSCTL_PROC(_kern_kcov_stksz, OID_AUTO, enabled, CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_LOCKED,
    &stksz_enabled, 0, sysctl_kcov_stksz_enabled, "I", "stack size recording enabled");


/* Constants to make code below simpler. */
const uint32_t debugid_above = MACHDBG_CODE(DBG_MACH_KCOV, KCOV_STKSZ_THRESHOLD_ABOVE);
const uint32_t debugid_below = MACHDBG_CODE(DBG_MACH_KCOV, KCOV_STKSZ_THRESHOLD_BELOW);
const uint32_t debugid_delta = MACHDBG_CODE(DBG_MACH_KCOV, KCOV_STKSZ_DELTA);


void
kcov_stksz_init_thread(kcov_stksz_thread_t *data)
{
	data->kst_pc = 0;
	data->kst_stksz = 0;
	data->kst_stksz_prev = 0;
	data->kst_stack = 0;
	data->kst_th_above = 0;
}


void
kcov_stksz_update_stack_size(thread_t th, kcov_thread_data_t *kcdata, void *caller, uintptr_t sp)
{
	if (!stksz_enabled) {
		return;
	}

	vm_offset_t stack_base = kcov_stksz_get_thread_stkbase(th);
	vm_offset_t stack_size = kcov_stksz_get_thread_stksize(th);

	/* Ensure that we are in thread's stack. */
	if (sp < stack_base || sp >= stack_base + stack_size) {
		return;
	}

	/* Update kernel thread statistics. */
	kcov_stksz_thread_t *data = &kcdata->ktd_stksz;
	data->kst_pc = (uintptr_t)caller;
	data->kst_stksz_prev = data->kst_stksz;
	data->kst_stksz = (uint32_t)(stack_base + stack_size - sp);

	/* Fire threshold related events. */
	if (!data->kst_th_above && data->kst_stksz > stksz_threshold) {
		data->kst_th_above = true;

#if CONFIG_DTRACE
		if (dtrace_get_thread_inprobe(th) == 0) {
			DTRACE_KCOV1(stksz__threshold__above, uint32_t, data->kst_stksz);
		}
#endif
		if (kdebug_enable && kdebug_debugid_enabled(debugid_above)) {
			KDBG(debugid_above, caller, data->kst_stksz);
		}
	} else if (data->kst_th_above && data->kst_stksz < stksz_threshold) {
		data->kst_th_above = false;

#if CONFIG_DTRACE
		if (dtrace_get_thread_inprobe(th) == 0) {
			DTRACE_KCOV1(stksz__threshold__below, uint32_t, data->kst_stksz);
		}
#endif
		if (kdebug_enable && kdebug_debugid_enabled(debugid_below)) {
			KDBG(debugid_below, caller, data->kst_stksz);
		}
	}

	/* Fire delta related events. */
	if (stksz_delta > 0 && data->kst_stksz > data->kst_stksz_prev) {
		uint32_t delta = data->kst_stksz - data->kst_stksz_prev;
		if (delta > stksz_delta) {
#if CONFIG_DTRACE
			if (dtrace_get_thread_inprobe(th) == 0) {
				DTRACE_KCOV1(stksz__delta, uint32_t, delta);
			}
#endif /* CONFIG_DTRACE */
			if (kdebug_enable && kdebug_debugid_enabled(debugid_delta)) {
				KDBG(debugid_delta, caller, delta);
			}
		}
	}
}