From 93ef3d32aba0e5d6604896853012563fe0be4614 Mon Sep 17 00:00:00 2001
From: Eugene Syromyatnikov <evgsyr@gmail.com>
Date: Fri, 1 Jun 2018 02:55:21 +0200
Subject: [PATCH] tests: check BPF_OBJ_GET_INFO_BY_FD decoding

* tests/bpf-obj_get_info_by_fd-prog-v.c: New file.
* tests/bpf-obj_get_info_by_fd-prog.c: Likewise.
* tests/bpf-obj_get_info_by_fd-v.c: Likewise.
* tests/bpf-obj_get_info_by_fd.c: Likewise.
* configure.ac (AC_CHECK_HEADERS): Check for struct bpf_insn,
struct bpf_map_info, and struct bpf_prog_info.
* tests/pure_executables.list: Add bpf-obj_get_info_by_fd,
bpf-obj_get_info_by_fd-v, bpf-obj_get_info_by_fd-prog, and
bpf-obj_get_info_by_fd-prog-v.
* tests/.gitignore: Likewise.
* tests/gen_tests.in (bpf-obj_get_info_by_fd,
bpf-obj_get_info_by_fd-v, bpf-obj_get_info_by_fd-prog,
bpf-obj_get_info_by_fd-prog-v): New tests.

Co-Authored-by: Dmitry V. Levin <ldv@altlinux.org>
---
 configure.ac                          |   6 +-
 tests/.gitignore                      |   4 +
 tests/Makefile.am                     |   4 +
 tests/bpf-obj_get_info_by_fd-prog-v.c |   3 +
 tests/bpf-obj_get_info_by_fd-prog.c   |   2 +
 tests/bpf-obj_get_info_by_fd-v.c      |   2 +
 tests/bpf-obj_get_info_by_fd.c        | 499 ++++++++++++++++++++++++++
 tests/gen_tests.in                    |   4 +
 tests/pure_executables.list           |   4 +
 9 files changed, 527 insertions(+), 1 deletion(-)
 create mode 100644 tests/bpf-obj_get_info_by_fd-prog-v.c
 create mode 100644 tests/bpf-obj_get_info_by_fd-prog.c
 create mode 100644 tests/bpf-obj_get_info_by_fd-v.c
 create mode 100644 tests/bpf-obj_get_info_by_fd.c

diff --git a/configure.ac b/configure.ac
index 43197687..f6868f21 100644
--- a/configure.ac
+++ b/configure.ac
@@ -466,7 +466,11 @@ AC_CHECK_HEADERS([linux/input.h], [
 ])
 
 AC_CHECK_HEADERS([linux/bpf.h], [
-	AC_CHECK_TYPES([struct bpf_insn],,, [#include <linux/bpf.h>])
+	AC_CHECK_TYPES(m4_normalize([
+		struct bpf_insn,
+		struct bpf_map_info,
+		struct bpf_prog_info
+	]),,, [#include <linux/bpf.h>])
 	st_BPF_ATTR
 ])
 
diff --git a/tests/.gitignore b/tests/.gitignore
index 13fa77b7..a754f788 100644
--- a/tests/.gitignore
+++ b/tests/.gitignore
@@ -21,6 +21,10 @@ attach-p-cmd-p
 block_reset_raise_run
 bpf
 bpf-v
+bpf-obj_get_info_by_fd
+bpf-obj_get_info_by_fd-v
+bpf-obj_get_info_by_fd-prog
+bpf-obj_get_info_by_fd-prog-v
 bpf-success
 bpf-success-v
 brk
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 17a82336..3cae26c8 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -93,6 +93,10 @@ check_PROGRAMS = $(PURE_EXECUTABLES) \
 	attach-p-cmd-cmd \
 	attach-p-cmd-p \
 	block_reset_raise_run \
+	bpf-obj_get_info_by_fd \
+	bpf-obj_get_info_by_fd-v \
+	bpf-obj_get_info_by_fd-prog \
+	bpf-obj_get_info_by_fd-prog-v \
 	bpf-success \
 	bpf-success-v \
 	caps-abbrev \
diff --git a/tests/bpf-obj_get_info_by_fd-prog-v.c b/tests/bpf-obj_get_info_by_fd-prog-v.c
new file mode 100644
index 00000000..d7e10b43
--- /dev/null
+++ b/tests/bpf-obj_get_info_by_fd-prog-v.c
@@ -0,0 +1,3 @@
+#define CHECK_OBJ_PROG 1
+#define VERBOSE 1
+#include "bpf-obj_get_info_by_fd.c"
diff --git a/tests/bpf-obj_get_info_by_fd-prog.c b/tests/bpf-obj_get_info_by_fd-prog.c
new file mode 100644
index 00000000..19a5f75a
--- /dev/null
+++ b/tests/bpf-obj_get_info_by_fd-prog.c
@@ -0,0 +1,2 @@
+#define CHECK_OBJ_PROG 1
+#include "bpf-obj_get_info_by_fd.c"
diff --git a/tests/bpf-obj_get_info_by_fd-v.c b/tests/bpf-obj_get_info_by_fd-v.c
new file mode 100644
index 00000000..2debc5f1
--- /dev/null
+++ b/tests/bpf-obj_get_info_by_fd-v.c
@@ -0,0 +1,2 @@
+#define VERBOSE 1
+#include "bpf-obj_get_info_by_fd.c"
diff --git a/tests/bpf-obj_get_info_by_fd.c b/tests/bpf-obj_get_info_by_fd.c
new file mode 100644
index 00000000..67b5122a
--- /dev/null
+++ b/tests/bpf-obj_get_info_by_fd.c
@@ -0,0 +1,499 @@
+/*
+ * Check bpf(BPF_OBJ_GET_INFO_BY_FD) decoding.
+ *
+ * Copyright (c) 2018 The strace developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "tests.h"
+
+#ifndef CHECK_OBJ_PROG
+# define CHECK_OBJ_PROG 0
+#endif
+
+#include <inttypes.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/sysmacros.h>
+#include <asm/unistd.h>
+
+#include "print_fields.h"
+#include "scno.h"
+
+#ifdef HAVE_LINUX_BPF_H
+# include <linux/bpf.h>
+#endif
+
+#include "bpf_attr.h"
+
+#include "xlat.h"
+#include "xlat/bpf_map_flags.h"
+#include "xlat/bpf_map_types.h"
+#include "xlat/bpf_prog_types.h"
+
+#define XLAT_MACROS_ONLY
+#include "xlat/bpf_commands.h"
+#include "xlat/bpf_op_alu.h"
+#include "xlat/bpf_op_jmp.h"
+#include "xlat/bpf_size.h"
+#include "xlat/bpf_src.h"
+#include "xlat/ebpf_class.h"
+#include "xlat/ebpf_mode.h"
+#include "xlat/ebpf_op_alu.h"
+#include "xlat/ebpf_regs.h"
+#include "xlat/ebpf_size.h"
+
+#ifndef HAVE_STRUCT_BPF_INSN
+struct bpf_insn {
+	uint8_t	code;
+	uint8_t	dst_reg:4;
+	uint8_t	src_reg:4;
+	int16_t	off;
+	int32_t	imm;
+};
+#endif
+
+static const char *errstr;
+
+static long
+sys_bpf(kernel_ulong_t cmd, void *attr, kernel_ulong_t size)
+{
+	long rc = syscall(__NR_bpf, cmd, attr, size);
+	errstr = sprintrc(rc);
+	return rc;
+}
+
+static void
+print_map_create(void *attr_void, size_t size, long rc)
+{
+	/* struct BPF_MAP_CREATE_struct *attr = attr_void; */
+
+	printf("bpf(BPF_MAP_CREATE, {map_type=BPF_MAP_TYPE_ARRAY, key_size=4"
+	       ", value_size=8, max_entries=1");
+	if (size > offsetof(struct BPF_MAP_CREATE_struct, map_flags))
+		printf(", map_flags=0");
+	if (size > offsetof(struct BPF_MAP_CREATE_struct, inner_map_fd))
+		printf(", inner_map_fd=0</dev/null>");
+	if (size > offsetof(struct BPF_MAP_CREATE_struct, map_name))
+		printf(", map_name=\"test_map\"");
+	if (size > offsetof(struct BPF_MAP_CREATE_struct, map_ifindex))
+		printf(", map_ifindex=0");
+	printf("}, %zu) = ", size);
+	if (rc >= 0)
+		printf("%ld<anon_inode:bpf-map>\n", rc);
+	else
+		puts(errstr);
+}
+
+#if CHECK_OBJ_PROG
+static struct bpf_insn socket_prog[] = {
+	{ /* 0 */
+		.code    = BPF_ALU64 | BPF_K | BPF_MOV,
+		.dst_reg = BPF_REG_1,
+		.imm     = 0,
+	},
+	{ /* 1 */
+		.code    = BPF_STX | BPF_W | BPF_MEM,
+		.dst_reg = BPF_REG_10,
+		.src_reg = BPF_REG_1,
+		.off     = -4,
+	},
+	{ /* 2 */
+		.code = BPF_ALU64 | BPF_X | BPF_MOV,
+		.dst_reg = BPF_REG_2,
+		.src_reg = BPF_REG_10,
+	},
+	{ /* 3 */
+		.code    = BPF_ALU64 | BPF_K | BPF_ADD,
+		.dst_reg = BPF_REG_2,
+		.imm     = -4,
+	},
+	{ /* 4 */
+		.code    = BPF_LD | BPF_DW | BPF_IMM,
+		.dst_reg = BPF_REG_1,
+		.src_reg = 1 /* BPF_PSEUDO_MAP_FD */,
+		.imm     = 0, /* to be set to map fd */
+	},
+	{ /* 5 */
+		.imm     = 0,
+	},
+	{ /* 6 */
+		.code    = BPF_JMP | BPF_K | BPF_CALL,
+		.imm     = 0x1, /* BPF_FUNC_map_lookup_elem */
+	},
+	{ /* 7 */
+		.code    = BPF_ALU64 | BPF_K | BPF_MOV,
+		.dst_reg = BPF_REG_0,
+		.imm     = 0,
+	},
+	{ /* 8 */
+		.code    = BPF_JMP | BPF_K | BPF_EXIT,
+	},
+};
+
+# if VERBOSE
+static const char *socket_prog_fmt =
+	"[{code=BPF_ALU64|BPF_K|BPF_MOV"
+		", dst_reg=BPF_REG_1, src_reg=BPF_REG_0, off=0, imm=0}"
+	", {code=BPF_STX|BPF_W|BPF_MEM"
+		", dst_reg=BPF_REG_10, src_reg=BPF_REG_1, off=-4, imm=0}"
+	", {code=BPF_ALU64|BPF_X|BPF_MOV"
+		", dst_reg=BPF_REG_2, src_reg=BPF_REG_10, off=0, imm=0}"
+	", {code=BPF_ALU64|BPF_K|BPF_ADD"
+		", dst_reg=BPF_REG_2, src_reg=BPF_REG_0, off=0, imm=0xfffffffc}"
+	", {code=BPF_LD|BPF_DW|BPF_IMM"
+		", dst_reg=BPF_REG_1, src_reg=BPF_REG_1, off=0, imm=%#x}"
+	", {code=BPF_LD|BPF_W|BPF_IMM"
+		", dst_reg=BPF_REG_0, src_reg=BPF_REG_0, off=0, imm=0}"
+	", {code=BPF_JMP|BPF_K|BPF_CALL"
+		", dst_reg=BPF_REG_0, src_reg=BPF_REG_0, off=0, imm=0x1}"
+	", {code=BPF_ALU64|BPF_K|BPF_MOV"
+		", dst_reg=BPF_REG_0, src_reg=BPF_REG_0, off=0, imm=0}"
+	", {code=BPF_JMP|BPF_K|BPF_EXIT"
+		", dst_reg=BPF_REG_0, src_reg=BPF_REG_0, off=0, imm=0}"
+	"]";
+# endif /* VERBOSE */
+
+static const char *license = "BSD";
+static char log_buf[4096];
+
+static void
+print_prog_load(void *attr_void, size_t size, long rc)
+{
+	printf("bpf(BPF_PROG_LOAD, {prog_type=BPF_PROG_TYPE_SOCKET_FILTER"
+	       ", insn_cnt=%zu, insns=", ARRAY_SIZE(socket_prog));
+# if VERBOSE
+	printf(socket_prog_fmt, socket_prog[4].imm);
+# else
+	printf("%p", socket_prog);
+# endif
+	if (size > offsetof(struct BPF_PROG_LOAD_struct, license))
+		printf(", license=\"BSD\"");
+	if (size > offsetof(struct BPF_PROG_LOAD_struct, log_buf))
+		printf(", log_level=42, log_size=%zu, log_buf=\"\"",
+		       sizeof(log_buf));
+	if (size > offsetof(struct BPF_PROG_LOAD_struct, kern_version))
+		printf(", kern_version=KERNEL_VERSION(57005, 192, 222)");
+	if (size > offsetof(struct BPF_PROG_LOAD_struct, prog_flags))
+		printf(", prog_flags=0");
+	if (size > offsetof(struct BPF_PROG_LOAD_struct, prog_name))
+		printf(", prog_name=\"test_prog\"");
+	if (size > offsetof(struct BPF_PROG_LOAD_struct, prog_ifindex))
+		printf(", prog_ifindex=0");
+	if (size > offsetof(struct BPF_PROG_LOAD_struct, expected_attach_type))
+		printf(", expected_attach_type=BPF_CGROUP_INET_INGRESS");
+	printf("}, %zu) = ", size);
+	if (rc >= 0)
+		printf("%ld<anon_inode:bpf-prog>\n", rc);
+	else
+		puts(errstr);
+}
+#endif /* CHECK_OBJ_PROG */
+
+static long
+try_bpf(kernel_ulong_t cmd, void (*printer)(void *attr, size_t size, long rc),
+	void *attr, size_t **sizes)
+{
+	long rc;
+
+	for (rc = -1; **sizes; (*sizes)++) {
+		rc = sys_bpf(cmd, attr, **sizes);
+		printer(attr, **sizes, rc);
+
+		if (rc >= 0)
+			break;
+	}
+
+	return rc;
+}
+
+int
+main(void)
+{
+	struct BPF_MAP_CREATE_struct bpf_map_create_attr = {
+		.map_type    = BPF_MAP_TYPE_ARRAY,
+		.key_size    = 4,
+		.value_size  = 8,
+		.max_entries = 1,
+		.map_name    = "test_map",
+	};
+	size_t bpf_map_create_attr_sizes[] = {
+		sizeof(bpf_map_create_attr),
+		offsetofend(struct BPF_MAP_CREATE_struct, max_entries),
+		0,
+	};
+
+#if CHECK_OBJ_PROG
+	struct BPF_PROG_LOAD_struct bpf_prog_load_attr = {
+		.prog_type    = BPF_PROG_TYPE_SOCKET_FILTER,
+		.insn_cnt     = ARRAY_SIZE(socket_prog),
+		.insns        = (uintptr_t) socket_prog,
+		.license      = (uintptr_t) license,
+		.log_level    = 42,
+		.log_size     = sizeof(log_buf),
+		.log_buf      = (uintptr_t) log_buf,
+		.kern_version = 0xdeadc0de,
+		.prog_name    = "test_prog",
+	};
+	size_t bpf_prog_load_attr_sizes[] = {
+		sizeof(bpf_prog_load_attr),
+		offsetofend(struct BPF_PROG_LOAD_struct, prog_name),
+		offsetofend(struct BPF_PROG_LOAD_struct, prog_flags),
+		offsetofend(struct BPF_PROG_LOAD_struct, kern_version),
+		offsetofend(struct BPF_PROG_LOAD_struct, log_buf),
+		offsetofend(struct BPF_PROG_LOAD_struct, license),
+		offsetofend(struct BPF_PROG_LOAD_struct, insns),
+		0,
+	};
+#endif /* CHECK_OBJ_PROG */
+
+	size_t *bpf_map_create_attr_size = bpf_map_create_attr_sizes;
+	int map_fd = try_bpf(BPF_MAP_CREATE, print_map_create,
+			     &bpf_map_create_attr, &bpf_map_create_attr_size);
+	if (map_fd < 0)
+		perror_msg_and_skip("BPF_MAP_CREATE failed");
+
+#if CHECK_OBJ_PROG
+	socket_prog[4].imm = map_fd;
+
+	size_t *bpf_prog_load_attr_size = bpf_prog_load_attr_sizes;
+	int prog_fd = try_bpf(BPF_PROG_LOAD, print_prog_load,
+			      &bpf_prog_load_attr, &bpf_prog_load_attr_size);
+	if (prog_fd < 0)
+		perror_msg_and_skip("BPF_PROG_LOAD failed (log: \"%s\")",
+				    log_buf);
+#endif /* CHECK_OBJ_PROG */
+
+	/*
+	 * This has to be a macro, otherwise the compiler complains that
+	 * initializer element is not constant.
+	 */
+	#define  MAP_INFO_SZ (sizeof(*map_info) + 64)
+	struct bpf_map_info_struct *map_info = calloc(1, MAP_INFO_SZ);
+	struct BPF_OBJ_GET_INFO_BY_FD_struct bpf_map_get_info_attr = {
+		.bpf_fd   = map_fd,
+		.info_len = MAP_INFO_SZ,
+		.info     = (uintptr_t) map_info,
+	};
+
+	int ret = sys_bpf(BPF_OBJ_GET_INFO_BY_FD, &bpf_map_get_info_attr,
+			  sizeof(bpf_map_get_info_attr));
+	if (ret < 0)
+		perror_msg_and_skip("BPF_OBJ_GET_INFO_BY_FD map failed");
+
+	printf("bpf(BPF_OBJ_GET_INFO_BY_FD"
+	       ", {info={bpf_fd=%d<anon_inode:bpf-map>, info_len=%zu",
+	       map_fd, MAP_INFO_SZ);
+	if (bpf_map_get_info_attr.info_len != MAP_INFO_SZ)
+		printf(" => %u", bpf_map_get_info_attr.info_len);
+
+	printf(", info=");
+#if VERBOSE
+	printf("{type=");
+	printxval(bpf_map_types, map_info->type, "BPF_MAP_TYPE_???");
+	PRINT_FIELD_U(", ", *map_info, id);
+	PRINT_FIELD_U(", ", *map_info, key_size);
+	PRINT_FIELD_U(", ", *map_info, value_size);
+	PRINT_FIELD_U(", ", *map_info, max_entries);
+	printf(", map_flags=");
+	printflags(bpf_map_flags, map_info->map_flags, "BPF_F_???");
+
+	if (bpf_map_get_info_attr.info_len >
+	    offsetof(struct bpf_map_info_struct, name)) {
+		printf(", name=");
+		print_quoted_cstring(map_info->name, sizeof(map_info->name));
+	}
+	if (bpf_map_get_info_attr.info_len >
+	    offsetof(struct bpf_map_info_struct, ifindex))
+		printf(", ifindex=%u", map_info->ifindex);
+	if (bpf_map_get_info_attr.info_len >
+	    offsetof(struct bpf_map_info_struct, netns_dev))
+		printf(", netns_dev=makedev(%u, %u)",
+		       major(map_info->netns_dev), minor(map_info->netns_dev));
+	if (bpf_map_get_info_attr.info_len >
+	    offsetof(struct bpf_map_info_struct, netns_ino))
+		printf(", netns_ino=%" PRIu64, map_info->netns_ino);
+	printf("}");
+#else /* !VERBOSE */
+	printf("%p", map_info);
+#endif /* VERBOSE */
+	printf("}}, %zu) = %s\n", sizeof(bpf_map_get_info_attr), errstr);
+
+#if CHECK_OBJ_PROG
+	/*
+	 * This has to be a macro, otherwise the compiler complains that
+	 * initializer element is not constant.
+	 */
+	#define  PROG_INFO_SZ (sizeof(*prog_info) + 64)
+	struct bpf_prog_info_struct *prog_info = calloc(1, PROG_INFO_SZ);
+	struct bpf_insn *xlated_prog = tail_alloc(sizeof(*xlated_prog) * 42);
+	uint32_t *map_ids = tail_alloc(sizeof(*map_ids) * 2);
+	struct BPF_OBJ_GET_INFO_BY_FD_struct bpf_prog_get_info_attr = {
+		.bpf_fd   = prog_fd,
+		.info_len = PROG_INFO_SZ,
+		.info     = (uintptr_t) prog_info,
+	};
+	size_t old_prog_info_len = PROG_INFO_SZ;
+
+	for (unsigned int i = 0; i < 4; i++) {
+		prog_info->jited_prog_len = 0;
+		switch (i) {
+		case 1:
+			prog_info->xlated_prog_insns =
+				(uintptr_t) (xlated_prog + 42);
+			prog_info->xlated_prog_len = 336;
+			prog_info->map_ids = (uintptr_t) (map_ids + 2);
+			prog_info->nr_map_ids = 2;
+			break;
+		case 2:
+			prog_info->xlated_prog_insns = (uintptr_t) xlated_prog;
+			/* TODO: check xlated_prog output */
+			prog_info->xlated_prog_len = 0;
+			prog_info->map_ids = (uintptr_t) map_ids;
+			prog_info->nr_map_ids = 0;
+			break;
+		case 3:
+			prog_info->xlated_prog_insns = (uintptr_t) xlated_prog;
+			prog_info->xlated_prog_len = 0;
+			prog_info->map_ids = (uintptr_t) map_ids;
+			prog_info->nr_map_ids = 2;
+			break;
+		}
+
+		ret = sys_bpf(BPF_OBJ_GET_INFO_BY_FD, &bpf_prog_get_info_attr,
+			      sizeof(bpf_prog_get_info_attr));
+		if (i != 1 && ret < 0)
+			perror_msg_and_skip("BPF_OBJ_GET_INFO_BY_FD"
+					    " prog %u failed", i);
+
+		printf("bpf(BPF_OBJ_GET_INFO_BY_FD"
+		       ", {info={bpf_fd=%d<anon_inode:bpf-prog>, info_len=%zu",
+		       prog_fd, old_prog_info_len);
+		if (!i && bpf_prog_get_info_attr.info_len != PROG_INFO_SZ)
+			printf(" => %u", bpf_prog_get_info_attr.info_len);
+		old_prog_info_len = bpf_prog_get_info_attr.info_len;
+
+		printf(", info=");
+# if VERBOSE
+		printf("{type=");
+		printxval(bpf_prog_types, prog_info->type, "BPF_PROG_TYPE_???");
+		PRINT_FIELD_U(", ", *prog_info, id);
+		printf(", tag=");
+		print_quoted_hex(prog_info->tag, sizeof(prog_info->tag));
+		printf(", jited_prog_len=0");
+		if (prog_info->jited_prog_len)
+			printf(" => %u", prog_info->jited_prog_len);
+		printf(", jited_prog_insns=NULL");
+		switch (i) {
+		case 0:
+			printf(", xlated_prog_len=0");
+			if (prog_info->xlated_prog_len)
+				printf(" => %u", prog_info->xlated_prog_len);
+			printf(", xlated_prog_insns=NULL");
+			break;
+		case 1:
+			printf(", xlated_prog_len=336");
+			if (prog_info->xlated_prog_len != 336)
+				printf(" => %u", prog_info->xlated_prog_len);
+			if (prog_info->xlated_prog_len)
+				printf(", xlated_prog_insns=%p", xlated_prog + 42);
+			else
+				printf(", xlated_prog_insns=[]");
+			break;
+		case 2:
+		case 3:
+			printf(", xlated_prog_len=0");
+			if (prog_info->xlated_prog_len)
+				printf(" => %u", prog_info->xlated_prog_len);
+			printf(", xlated_prog_insns=[]");
+			break;
+		}
+
+		if (bpf_prog_get_info_attr.info_len >
+		    offsetof(struct bpf_prog_info_struct, load_time))
+			printf(", load_time=%" PRIu64, prog_info->load_time);
+		if (bpf_prog_get_info_attr.info_len >
+		    offsetof(struct bpf_prog_info_struct, created_by_uid))
+			printf(", created_by_uid=%u",
+			       prog_info->created_by_uid);
+
+		if (bpf_prog_get_info_attr.info_len >
+		    offsetof(struct bpf_prog_info_struct, map_ids)) {
+			switch (i) {
+			case 0:
+				printf(", nr_map_ids=0");
+				if (prog_info->nr_map_ids)
+					printf(" => 1");
+				printf(", map_ids=NULL");
+				break;
+			case 1:
+				printf(", nr_map_ids=2, map_ids=%p",
+				       map_ids + 2);
+				break;
+			case 2:
+				printf(", nr_map_ids=0");
+				if (prog_info->nr_map_ids)
+					printf(" => 1");
+				printf(", map_ids=[]");
+				break;
+			case 3:
+				printf(", nr_map_ids=2");
+				if (prog_info->nr_map_ids != 2)
+					printf(" => 1");
+				printf(", map_ids=[%u]", map_info->id);
+				break;
+			}
+		}
+
+		if (bpf_prog_get_info_attr.info_len >
+		    offsetof(struct bpf_prog_info_struct, name))
+			printf(", name=\"test_prog\"");
+		if (bpf_prog_get_info_attr.info_len >
+		    offsetof(struct bpf_prog_info_struct, ifindex))
+			printf(", ifindex=%u", prog_info->ifindex);
+		if (bpf_prog_get_info_attr.info_len >
+		    offsetof(struct bpf_prog_info_struct, netns_dev))
+			printf(", netns_dev=makedev(%u, %u)",
+			       major(prog_info->netns_dev),
+			       minor(prog_info->netns_dev));
+		if (bpf_prog_get_info_attr.info_len >
+		    offsetof(struct bpf_prog_info_struct, netns_ino))
+			printf(", netns_ino=%" PRIu64, prog_info->netns_ino);
+
+		printf("}");
+# else /* !VERBOSE */
+		printf("%p", prog_info);
+# endif /* VERBOSE */
+		printf("}}, %zu) = %s\n",
+		       sizeof(bpf_prog_get_info_attr), errstr);
+	}
+#endif /* CHECK_OBJ_PROG */
+
+	puts("+++ exited with 0 +++");
+	return 0;
+}
diff --git a/tests/gen_tests.in b/tests/gen_tests.in
index 4400c485..f3518d19 100644
--- a/tests/gen_tests.in
+++ b/tests/gen_tests.in
@@ -37,6 +37,10 @@ aio	-a14 -e trace=io_setup,io_submit,io_getevents,io_cancel,io_destroy
 alarm	-a10
 bpf	-a20
 bpf-v	-a20 -v -e trace=bpf
+bpf-obj_get_info_by_fd	-a20 -y -e trace=bpf
+bpf-obj_get_info_by_fd-v	-a20 -y -v -e trace=bpf
+bpf-obj_get_info_by_fd-prog	-a20 -y -e trace=bpf
+bpf-obj_get_info_by_fd-prog-v	-a20 -y -v -e trace=bpf
 btrfs	+ioctl.test
 chmod	-a28
 chown	-a28
diff --git a/tests/pure_executables.list b/tests/pure_executables.list
index 80c8ae75..df84fe66 100755
--- a/tests/pure_executables.list
+++ b/tests/pure_executables.list
@@ -10,6 +10,10 @@ aio
 alarm
 bpf
 bpf-v
+bpf-obj_get_info_by_fd
+bpf-obj_get_info_by_fd-v
+bpf-obj_get_info_by_fd-prog
+bpf-obj_get_info_by_fd-prog-v
 brk
 btrfs
 caps
-- 
2.40.0