/*
 * SPDX-License-Identifier: MIT
 *
 * Copyright (c) 2022 Olivier Dion <olivier.dion@polymtl.ca>
 *
 * This is a trivial example on how to use libpatch.
 *
 * The idea is to print the value `x` of the function `func(x)` before the
 * function itself can print it.
 *
 * This example is intended to be run on x86-64 with SystemV ABI.  If you desire
 * to try it on a different system, you will need to change the register access
 * -- here RDI -- in the `probe(ctx)` function.
 */

#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>

#include <libpatch/patch.h>

static void probe(struct patch_probe_context *ctx, bool ret);
extern int func(int x);

#define die(FMT, ARGS...)				\
	({						\
		fprintf(stderr, FMT "\n", ##ARGS);	\
		exit(EXIT_FAILURE);			\
	})

#define ensure_patch(FN, ARGS...)		\
	({					\
		patch_err err = FN(ARGS);	\
		if (PATCH_OK != err) {		\
			die(#FN "(): %d", err); \
		}				\
	})

#define array_size(ARR) ((sizeof(ARR)) / (sizeof(ARR[0])))

static int exit_value = 0;

static void install_probe(void)
{
	patch_result *results;
	size_t results_count;
	patch_err err;
	patch_op op = {
		.type           = PATCH_OP_INSTALL,
		.addr.func_sym = "func",
		.probe          = probe,
		.user_data      = &exit_value,
	};
	const patch_opt opts[] = {
		{
			.what    = PATCH_OPT_WXE_BOOLEAN,
			.boolean = false
		},
	};

	ensure_patch(patch_init, opts, array_size(opts));

	ensure_patch(patch_queue, PATCH_FENTRY, &op);

	patch_commit(&results, &results_count);

	if (results_count > 0) {
		exit(EXIT_FAILURE);
	}

	ensure_patch(patch_drop_results, results, results_count);
}

static void probe(struct patch_probe_context *ctx, bool ret)
{
	(void)ret;

	int *e;

	printf("probe:\tx=%d\n",
	       (int)ctx->gregs[PATCH_X86_64_RDI]);

	e = ctx->user_data;

	(*e)++;
}

int func(int x)
{
	printf("func:\tx=%d\n", x);
	return x;
}

int main(void)
{
	int x;

	x = random();

	install_probe();

	printf("main:\tx=%d\n", x);

	assert(x == func(x));

	ensure_patch(patch_fini());

	assert(x == func(x));

	/* Two because of function entry and exit! */
	return exit_value == 2 ? EXIT_SUCCESS : EXIT_FAILURE;
}