diff --git a/tools/bpf/bpftool/Documentation/bpftool-feature.rst b/tools/bpf/bpftool/Documentation/bpftool-feature.rst
index 255e3b3629a0e5bb0c62e8e47137575d637c5659..53092995f46bf10e9e9eb9b40897a869a9dd703e 100644
--- a/tools/bpf/bpftool/Documentation/bpftool-feature.rst
+++ b/tools/bpf/bpftool/Documentation/bpftool-feature.rst
@@ -19,15 +19,24 @@ SYNOPSIS
 MAP COMMANDS
 =============
 
-|	**bpftool** **feature probe** [**kernel**]
+|	**bpftool** **feature probe** [**kernel**] [**macros** [**prefix** *PREFIX*]]
 |	**bpftool** **feature help**
 
 DESCRIPTION
 ===========
-	**bpftool feature probe** [**kernel**]
+	**bpftool feature probe** [**kernel**] [**macros** [**prefix** *PREFIX*]]
 		  Probe the running kernel and dump a number of eBPF-related
 		  parameters, such as availability of the **bpf()** system call.
 
+		  If the **macros** keyword (but not the **-j** option) is
+		  passed, a subset of the output is dumped as a list of
+		  **#define** macros that are ready to be included in a C
+		  header file, for example. If, additionally, **prefix** is
+		  used to define a *PREFIX*, the provided string will be used
+		  as a prefix to the names of the macros: this can be used to
+		  avoid conflicts on macro names when including the output of
+		  this command as a header file.
+
 		  Keyword **kernel** can be omitted.
 
 		  Note that when probed, some eBPF helpers (e.g.
diff --git a/tools/bpf/bpftool/feature.c b/tools/bpf/bpftool/feature.c
index 55c8d215ca44ca194d11ba31a7a660f9850f115b..a62e637953b72f750814f7b81206ed4b21e2ae67 100644
--- a/tools/bpf/bpftool/feature.c
+++ b/tools/bpf/bpftool/feature.c
@@ -46,13 +46,25 @@ static bool check_procfs(void)
 	return true;
 }
 
+static void uppercase(char *str, size_t len)
+{
+	size_t i;
+
+	for (i = 0; i < len && str[i] != '\0'; i++)
+		str[i] = toupper(str[i]);
+}
+
 /* Printing utility functions */
 
 static void
-print_bool_feature(const char *feat_name, const char *plain_name, bool res)
+print_bool_feature(const char *feat_name, const char *plain_name,
+		   const char *define_name, bool res, const char *define_prefix)
 {
 	if (json_output)
 		jsonw_bool_field(json_wtr, feat_name, res);
+	else if (define_prefix)
+		printf("#define %s%sHAVE_%s\n", define_prefix,
+		       res ? "" : "NO_", define_name);
 	else
 		printf("%s is %savailable\n", plain_name, res ? "" : "NOT ");
 }
@@ -62,6 +74,8 @@ static void print_kernel_option(const char *name, const char *value)
 	char *endptr;
 	int res;
 
+	/* No support for C-style ouptut */
+
 	if (json_output) {
 		if (!value) {
 			jsonw_null_field(json_wtr, name);
@@ -82,25 +96,31 @@ static void print_kernel_option(const char *name, const char *value)
 }
 
 static void
-print_start_section(const char *json_title, const char *plain_title)
+print_start_section(const char *json_title, const char *plain_title,
+		    const char *define_comment, const char *define_prefix)
 {
 	if (json_output) {
 		jsonw_name(json_wtr, json_title);
 		jsonw_start_object(json_wtr);
+	} else if (define_prefix) {
+		printf("%s\n", define_comment);
 	} else {
 		printf("%s\n", plain_title);
 	}
 }
 
 static void
-print_end_then_start_section(const char *json_title, const char *plain_title)
+print_end_then_start_section(const char *json_title, const char *plain_title,
+			     const char *define_comment,
+			     const char *define_prefix)
 {
 	if (json_output)
 		jsonw_end_object(json_wtr);
 	else
 		printf("\n");
 
-	print_start_section(json_title, plain_title);
+	print_start_section(json_title, plain_title, define_comment,
+			    define_prefix);
 }
 
 /* Probing functions */
@@ -134,6 +154,8 @@ static void probe_unprivileged_disabled(void)
 {
 	int res;
 
+	/* No support for C-style ouptut */
+
 	res = read_procfs("/proc/sys/kernel/unprivileged_bpf_disabled");
 	if (json_output) {
 		jsonw_int_field(json_wtr, "unprivileged_bpf_disabled", res);
@@ -158,6 +180,8 @@ static void probe_jit_enable(void)
 {
 	int res;
 
+	/* No support for C-style ouptut */
+
 	res = read_procfs("/proc/sys/net/core/bpf_jit_enable");
 	if (json_output) {
 		jsonw_int_field(json_wtr, "bpf_jit_enable", res);
@@ -186,6 +210,8 @@ static void probe_jit_harden(void)
 {
 	int res;
 
+	/* No support for C-style ouptut */
+
 	res = read_procfs("/proc/sys/net/core/bpf_jit_harden");
 	if (json_output) {
 		jsonw_int_field(json_wtr, "bpf_jit_harden", res);
@@ -214,6 +240,8 @@ static void probe_jit_kallsyms(void)
 {
 	int res;
 
+	/* No support for C-style ouptut */
+
 	res = read_procfs("/proc/sys/net/core/bpf_jit_kallsyms");
 	if (json_output) {
 		jsonw_int_field(json_wtr, "bpf_jit_kallsyms", res);
@@ -238,6 +266,8 @@ static void probe_jit_limit(void)
 {
 	int res;
 
+	/* No support for C-style ouptut */
+
 	res = read_procfs("/proc/sys/net/core/bpf_jit_limit");
 	if (json_output) {
 		jsonw_int_field(json_wtr, "bpf_jit_limit", res);
@@ -409,7 +439,7 @@ static void probe_kernel_image_config(void)
 		print_kernel_option(options[i], NULL);
 }
 
-static bool probe_bpf_syscall(void)
+static bool probe_bpf_syscall(const char *define_prefix)
 {
 	bool res;
 
@@ -418,15 +448,18 @@ static bool probe_bpf_syscall(void)
 
 	print_bool_feature("have_bpf_syscall",
 			   "bpf() syscall",
-			   res);
+			   "BPF_SYSCALL",
+			   res, define_prefix);
 
 	return res;
 }
 
-static void probe_prog_type(enum bpf_prog_type prog_type, bool *supported_types)
+static void
+probe_prog_type(enum bpf_prog_type prog_type, bool *supported_types,
+		const char *define_prefix)
 {
+	char feat_name[128], plain_desc[128], define_name[128];
 	const char *plain_comment = "eBPF program_type ";
-	char feat_name[128], plain_desc[128];
 	size_t maxlen;
 	bool res;
 
@@ -441,14 +474,18 @@ static void probe_prog_type(enum bpf_prog_type prog_type, bool *supported_types)
 	}
 
 	sprintf(feat_name, "have_%s_prog_type", prog_type_name[prog_type]);
+	sprintf(define_name, "%s_prog_type", prog_type_name[prog_type]);
+	uppercase(define_name, sizeof(define_name));
 	sprintf(plain_desc, "%s%s", plain_comment, prog_type_name[prog_type]);
-	print_bool_feature(feat_name, plain_desc, res);
+	print_bool_feature(feat_name, plain_desc, define_name, res,
+			   define_prefix);
 }
 
-static void probe_map_type(enum bpf_map_type map_type)
+static void
+probe_map_type(enum bpf_map_type map_type, const char *define_prefix)
 {
+	char feat_name[128], plain_desc[128], define_name[128];
 	const char *plain_comment = "eBPF map_type ";
-	char feat_name[128], plain_desc[128];
 	size_t maxlen;
 	bool res;
 
@@ -461,12 +498,16 @@ static void probe_map_type(enum bpf_map_type map_type)
 	}
 
 	sprintf(feat_name, "have_%s_map_type", map_type_name[map_type]);
+	sprintf(define_name, "%s_map_type", map_type_name[map_type]);
+	uppercase(define_name, sizeof(define_name));
 	sprintf(plain_desc, "%s%s", plain_comment, map_type_name[map_type]);
-	print_bool_feature(feat_name, plain_desc, res);
+	print_bool_feature(feat_name, plain_desc, define_name, res,
+			   define_prefix);
 }
 
 static void
-probe_helpers_for_progtype(enum bpf_prog_type prog_type, bool supported_type)
+probe_helpers_for_progtype(enum bpf_prog_type prog_type, bool supported_type,
+			   const char *define_prefix)
 {
 	const char *ptype_name = prog_type_name[prog_type];
 	char feat_name[128];
@@ -477,7 +518,7 @@ probe_helpers_for_progtype(enum bpf_prog_type prog_type, bool supported_type)
 		sprintf(feat_name, "%s_available_helpers", ptype_name);
 		jsonw_name(json_wtr, feat_name);
 		jsonw_start_array(json_wtr);
-	} else {
+	} else if (!define_prefix) {
 		printf("eBPF helpers supported for program type %s:",
 		       ptype_name);
 	}
@@ -491,6 +532,10 @@ probe_helpers_for_progtype(enum bpf_prog_type prog_type, bool supported_type)
 		if (json_output) {
 			if (res)
 				jsonw_string(json_wtr, helper_name[id]);
+		} else if (define_prefix) {
+			printf("#define %sBPF__PROG_TYPE_%s__HELPER_%s %s\n",
+			       define_prefix, ptype_name, helper_name[id],
+			       res ? "1" : "0");
 		} else {
 			if (res)
 				printf("\n\t- %s", helper_name[id]);
@@ -499,13 +544,14 @@ probe_helpers_for_progtype(enum bpf_prog_type prog_type, bool supported_type)
 
 	if (json_output)
 		jsonw_end_array(json_wtr);
-	else
+	else if (!define_prefix)
 		printf("\n");
 }
 
 static int do_probe(int argc, char **argv)
 {
 	enum probe_component target = COMPONENT_UNSPEC;
+	const char *define_prefix = NULL;
 	bool supported_types[128] = {};
 	unsigned int i;
 
@@ -527,21 +573,45 @@ static int do_probe(int argc, char **argv)
 			}
 			target = COMPONENT_KERNEL;
 			NEXT_ARG();
+		} else if (is_prefix(*argv, "macros") && !define_prefix) {
+			define_prefix = "";
+			NEXT_ARG();
+		} else if (is_prefix(*argv, "prefix")) {
+			if (!define_prefix) {
+				p_err("'prefix' argument can only be use after 'macros'");
+				return -1;
+			}
+			if (strcmp(define_prefix, "")) {
+				p_err("'prefix' already defined");
+				return -1;
+			}
+			NEXT_ARG();
+
+			if (!REQ_ARGS(1))
+				return -1;
+			define_prefix = GET_ARG();
 		} else {
-			p_err("expected no more arguments, 'kernel', got: '%s'?",
+			p_err("expected no more arguments, 'kernel', 'macros' or 'prefix', got: '%s'?",
 			      *argv);
 			return -1;
 		}
 	}
 
-	if (json_output)
+	if (json_output) {
+		define_prefix = NULL;
 		jsonw_start_object(json_wtr);
+	}
 
 	switch (target) {
 	case COMPONENT_KERNEL:
 	case COMPONENT_UNSPEC:
+		if (define_prefix)
+			break;
+
 		print_start_section("system_config",
-				    "Scanning system configuration...");
+				    "Scanning system configuration...",
+				    NULL, /* define_comment never used here */
+				    NULL); /* define_prefix always NULL here */
 		if (check_procfs()) {
 			probe_unprivileged_disabled();
 			probe_jit_enable();
@@ -560,29 +630,53 @@ static int do_probe(int argc, char **argv)
 	}
 
 	print_start_section("syscall_config",
-			    "Scanning system call availability...");
+			    "Scanning system call availability...",
+			    "/*** System call availability ***/",
+			    define_prefix);
 
-	if (!probe_bpf_syscall())
+	if (!probe_bpf_syscall(define_prefix))
 		/* bpf() syscall unavailable, don't probe other BPF features */
 		goto exit_close_json;
 
 	print_end_then_start_section("program_types",
-				     "Scanning eBPF program types...");
+				     "Scanning eBPF program types...",
+				     "/*** eBPF program types ***/",
+				     define_prefix);
 
 	for (i = BPF_PROG_TYPE_UNSPEC + 1; i < ARRAY_SIZE(prog_type_name); i++)
-		probe_prog_type(i, supported_types);
+		probe_prog_type(i, supported_types, define_prefix);
 
 	print_end_then_start_section("map_types",
-				     "Scanning eBPF map types...");
+				     "Scanning eBPF map types...",
+				     "/*** eBPF map types ***/",
+				     define_prefix);
 
 	for (i = BPF_MAP_TYPE_UNSPEC + 1; i < map_type_name_size; i++)
-		probe_map_type(i);
+		probe_map_type(i, define_prefix);
 
 	print_end_then_start_section("helpers",
-				     "Scanning eBPF helper functions...");
-
+				     "Scanning eBPF helper functions...",
+				     "/*** eBPF helper functions ***/",
+				     define_prefix);
+
+	if (define_prefix)
+		printf("/*\n"
+		       " * Use %sHAVE_PROG_TYPE_HELPER(prog_type_name, helper_name)\n"
+		       " * to determine if <helper_name> is available for <prog_type_name>,\n"
+		       " * e.g.\n"
+		       " *	#if %sHAVE_PROG_TYPE_HELPER(xdp, bpf_redirect)\n"
+		       " *		// do stuff with this helper\n"
+		       " *	#elif\n"
+		       " *		// use a workaround\n"
+		       " *	#endif\n"
+		       " */\n"
+		       "#define %sHAVE_PROG_TYPE_HELPER(prog_type, helper)	\\\n"
+		       "	%sBPF__PROG_TYPE_ ## prog_type ## __HELPER_ ## helper\n",
+		       define_prefix, define_prefix, define_prefix,
+		       define_prefix);
 	for (i = BPF_PROG_TYPE_UNSPEC + 1; i < ARRAY_SIZE(prog_type_name); i++)
-		probe_helpers_for_progtype(i, supported_types[i]);
+		probe_helpers_for_progtype(i, supported_types[i],
+					   define_prefix);
 
 exit_close_json:
 	if (json_output) {
@@ -603,7 +697,7 @@ static int do_help(int argc, char **argv)
 	}
 
 	fprintf(stderr,
-		"Usage: %s %s probe [kernel]\n"
+		"Usage: %s %s probe [kernel] [macros [prefix PREFIX]]\n"
 		"       %s %s help\n"
 		"",
 		bin_name, argv[-2], bin_name, argv[-2]);