diff --git a/tools/bpf/bpftool/Makefile b/tools/bpf/bpftool/Makefile
index bdb6e38c6c5c..06f436e8191a 100644
--- a/tools/bpf/bpftool/Makefile
+++ b/tools/bpf/bpftool/Makefile
@@ -150,6 +150,8 @@ $(OUTPUT)%.skel.h: $(OUTPUT)%.bpf.o $(BPFTOOL_BOOTSTRAP)
 
 $(OUTPUT)prog.o: $(OUTPUT)profiler.skel.h
 
+$(OUTPUT)pids.o: $(OUTPUT)pid_iter.skel.h
+
 endif
 endif
 
diff --git a/tools/bpf/bpftool/btf.c b/tools/bpf/bpftool/btf.c
index faac8189b285..448c89d1faee 100644
--- a/tools/bpf/bpftool/btf.c
+++ b/tools/bpf/bpftool/btf.c
@@ -809,6 +809,22 @@ show_btf_plain(struct bpf_btf_info *info, int fd,
 			printf("%s%u", n++ == 0 ? "  map_ids " : ",",
 			       obj->obj_id);
 	}
+	if (!hash_empty(pids_table.table)) {
+		struct obj_pids *pids;
+		int i;
+
+		hash_for_each_possible(pids_table.table, pids, node, info->id) {
+			if (pids->id != info->id)
+				continue;
+			if (pids->pid_cnt == 0)
+				break;
+
+			printf("  pids");
+			for (i = 0; i < pids->pid_cnt; i++)
+				printf("%c%d", i == 0 ? ' ' : ',', pids->pids[i]);
+			break;
+		}
+	}
 
 	printf("\n");
 }
@@ -841,6 +857,25 @@ show_btf_json(struct bpf_btf_info *info, int fd,
 			jsonw_uint(json_wtr, obj->obj_id);
 	}
 	jsonw_end_array(json_wtr);	/* map_ids */
+	/* PIDs */
+	if (!hash_empty(pids_table.table)) {
+		struct obj_pids *pids;
+		int i;
+
+		hash_for_each_possible(pids_table.table, pids, node, info->id) {
+			if (pids->id != info->id)
+				continue;
+			if (pids->pid_cnt == 0)
+				break;
+
+			jsonw_name(json_wtr, "pids");
+			jsonw_start_array(json_wtr);
+			for (i = 0; i < pids->pid_cnt; i++)
+				jsonw_int(json_wtr, pids->pids[i]);
+			jsonw_end_array(json_wtr);
+			break;
+		}
+	}
 	jsonw_end_object(json_wtr);	/* btf object */
 }
 
@@ -893,6 +928,8 @@ static int do_show(int argc, char **argv)
 			close(fd);
 		return err;
 	}
+	if (show_pids)
+		build_obj_pids_table(&pids_table, BPF_OBJ_BTF);
 
 	if (fd >= 0) {
 		err = show_btf(fd, &btf_prog_table, &btf_map_table);
@@ -939,6 +976,8 @@ static int do_show(int argc, char **argv)
 exit_free:
 	delete_btf_table(&btf_prog_table);
 	delete_btf_table(&btf_map_table);
+	if (show_pids)
+		delete_obj_pids_table(&pids_table);
 
 	return err;
 }
diff --git a/tools/bpf/bpftool/link.c b/tools/bpf/bpftool/link.c
index fca57ee8fafe..d959ce838dfb 100644
--- a/tools/bpf/bpftool/link.c
+++ b/tools/bpf/bpftool/link.c
@@ -143,6 +143,25 @@ static int show_link_close_json(int fd, struct bpf_link_info *info)
 		}
 		jsonw_end_array(json_wtr);
 	}
+	if (!hash_empty(pids_table.table)) {
+		struct obj_pids *pids;
+		int i;
+
+		hash_for_each_possible(pids_table.table, pids, node, info->id) {
+			if (pids->id != info->id)
+				continue;
+			if (pids->pid_cnt == 0)
+				break;
+
+			jsonw_name(json_wtr, "pids");
+			jsonw_start_array(json_wtr);
+			for (i = 0; i < pids->pid_cnt; i++)
+				jsonw_int(json_wtr, pids->pids[i]);
+			jsonw_end_array(json_wtr);
+			break;
+		}
+	}
+
 	jsonw_end_object(json_wtr);
 
 	return 0;
@@ -212,6 +231,22 @@ static int show_link_close_plain(int fd, struct bpf_link_info *info)
 				printf("\n\tpinned %s", obj->path);
 		}
 	}
+	if (!hash_empty(pids_table.table)) {
+		struct obj_pids *pids;
+		int i;
+
+		hash_for_each_possible(pids_table.table, pids, node, info->id) {
+			if (pids->id != info->id)
+				continue;
+			if (pids->pid_cnt == 0)
+				break;
+
+			printf("\n\tpids:");
+			for (i = 0; i < pids->pid_cnt; i++)
+				printf("%c%d", i == 0 ? ' ' : ',', pids->pids[i]);
+			break;
+		}
+	}
 
 	printf("\n");
 
@@ -257,6 +292,8 @@ static int do_show(int argc, char **argv)
 
 	if (show_pinned)
 		build_pinned_obj_table(&link_table, BPF_OBJ_LINK);
+	if (show_pids)
+		build_obj_pids_table(&pids_table, BPF_OBJ_LINK);
 
 	if (argc == 2) {
 		fd = link_parse_fd(&argc, &argv);
@@ -296,6 +333,9 @@ static int do_show(int argc, char **argv)
 	if (json_output)
 		jsonw_end_array(json_wtr);
 
+	if (show_pids)
+		delete_obj_pids_table(&pids_table);
+
 	return errno == ENOENT ? 0 : -1;
 }
 
diff --git a/tools/bpf/bpftool/main.c b/tools/bpf/bpftool/main.c
index bf4d7487552a..53a546e1d9a8 100644
--- a/tools/bpf/bpftool/main.c
+++ b/tools/bpf/bpftool/main.c
@@ -25,12 +25,14 @@ json_writer_t *json_wtr;
 bool pretty_output;
 bool json_output;
 bool show_pinned;
+bool show_pids;
 bool block_mount;
 bool verifier_logs;
 bool relaxed_maps;
 struct pinned_obj_table prog_table;
 struct pinned_obj_table map_table;
 struct pinned_obj_table link_table;
+struct obj_pids_table pids_table;
 
 static void __noreturn clean_and_exit(int i)
 {
@@ -369,6 +371,7 @@ int main(int argc, char **argv)
 	pretty_output = false;
 	json_output = false;
 	show_pinned = false;
+	show_pids = false;
 	block_mount = false;
 	bin_name = argv[0];
 
@@ -377,7 +380,7 @@ int main(int argc, char **argv)
 	hash_init(link_table.table);
 
 	opterr = 0;
-	while ((opt = getopt_long(argc, argv, "Vhpjfmnd",
+	while ((opt = getopt_long(argc, argv, "Vhpjfomnd",
 				  options, NULL)) >= 0) {
 		switch (opt) {
 		case 'V':
@@ -401,6 +404,9 @@ int main(int argc, char **argv)
 		case 'f':
 			show_pinned = true;
 			break;
+		case 'o':
+			show_pids = true;
+			break;
 		case 'm':
 			relaxed_maps = true;
 			break;
diff --git a/tools/bpf/bpftool/main.h b/tools/bpf/bpftool/main.h
index aad7be74e8a7..286134b05687 100644
--- a/tools/bpf/bpftool/main.h
+++ b/tools/bpf/bpftool/main.h
@@ -127,11 +127,13 @@ static const char * const attach_type_name[__MAX_BPF_ATTACH_TYPE] = {
 extern const char * const map_type_name[];
 extern const size_t map_type_name_size;
 
+/* keep in sync with the definition in skeleton/pid_iter.bpf.c */
 enum bpf_obj_type {
 	BPF_OBJ_UNKNOWN,
 	BPF_OBJ_PROG,
 	BPF_OBJ_MAP,
 	BPF_OBJ_LINK,
+	BPF_OBJ_BTF,
 };
 
 extern const char *bin_name;
@@ -139,12 +141,14 @@ extern const char *bin_name;
 extern json_writer_t *json_wtr;
 extern bool json_output;
 extern bool show_pinned;
+extern bool show_pids;
 extern bool block_mount;
 extern bool verifier_logs;
 extern bool relaxed_maps;
 extern struct pinned_obj_table prog_table;
 extern struct pinned_obj_table map_table;
 extern struct pinned_obj_table link_table;
+extern struct obj_pids_table pids_table;
 
 void __printf(1, 2) p_err(const char *fmt, ...);
 void __printf(1, 2) p_info(const char *fmt, ...);
@@ -168,12 +172,26 @@ struct pinned_obj {
 	struct hlist_node hash;
 };
 
+struct obj_pids_table {
+	DECLARE_HASHTABLE(table, 16);
+};
+
+struct obj_pids {
+	struct hlist_node node;
+	__u32 id;
+	int pid_cnt;
+	int *pids;
+};
+
 struct btf;
 struct bpf_line_info;
 
 int build_pinned_obj_table(struct pinned_obj_table *table,
 			   enum bpf_obj_type type);
 void delete_pinned_obj_table(struct pinned_obj_table *tab);
+__weak int build_obj_pids_table(struct obj_pids_table *table,
+				enum bpf_obj_type type);
+__weak void delete_obj_pids_table(struct obj_pids_table *table);
 void print_dev_plain(__u32 ifindex, __u64 ns_dev, __u64 ns_inode);
 void print_dev_json(__u32 ifindex, __u64 ns_dev, __u64 ns_inode);
 
diff --git a/tools/bpf/bpftool/map.c b/tools/bpf/bpftool/map.c
index b9eee19b094c..1fc8b674049e 100644
--- a/tools/bpf/bpftool/map.c
+++ b/tools/bpf/bpftool/map.c
@@ -508,6 +508,24 @@ static int show_map_close_json(int fd, struct bpf_map_info *info)
 		}
 		jsonw_end_array(json_wtr);
 	}
+	if (!hash_empty(pids_table.table)) {
+		struct obj_pids *pids;
+		int i;
+
+		hash_for_each_possible(pids_table.table, pids, node, info->id) {
+			if (pids->id != info->id)
+				continue;
+			if (pids->pid_cnt == 0)
+				break;
+
+			jsonw_name(json_wtr, "pids");
+			jsonw_start_array(json_wtr);
+			for (i = 0; i < pids->pid_cnt; i++)
+				jsonw_int(json_wtr, pids->pids[i]);
+			jsonw_end_array(json_wtr);
+			break;
+		}
+	}
 
 	jsonw_end_object(json_wtr);
 
@@ -596,6 +614,22 @@ static int show_map_close_plain(int fd, struct bpf_map_info *info)
 	if (frozen)
 		printf("%sfrozen", info->btf_id ? "  " : "");
 
+	if (!hash_empty(pids_table.table)) {
+		struct obj_pids *pids;
+		int i;
+
+		hash_for_each_possible(pids_table.table, pids, node, info->id) {
+			if (pids->id != info->id)
+				continue;
+			if (pids->pid_cnt == 0)
+				break;
+
+			printf("\n\tpids:");
+			for (i = 0; i < pids->pid_cnt; i++)
+				printf("%c%d", i == 0 ? ' ' : ',', pids->pids[i]);
+			break;
+		}
+	}
 	printf("\n");
 	return 0;
 }
@@ -654,6 +688,8 @@ static int do_show(int argc, char **argv)
 
 	if (show_pinned)
 		build_pinned_obj_table(&map_table, BPF_OBJ_MAP);
+	if (show_pids)
+		build_obj_pids_table(&pids_table, BPF_OBJ_MAP);
 
 	if (argc == 2)
 		return do_show_subset(argc, argv);
@@ -697,6 +733,9 @@ static int do_show(int argc, char **argv)
 	if (json_output)
 		jsonw_end_array(json_wtr);
 
+	if (show_pids)
+		delete_obj_pids_table(&pids_table);
+
 	return errno == ENOENT ? 0 : -1;
 }
 
diff --git a/tools/bpf/bpftool/pids.c b/tools/bpf/bpftool/pids.c
new file mode 100644
index 000000000000..fb0a2d7213b4
--- /dev/null
+++ b/tools/bpf/bpftool/pids.c
@@ -0,0 +1,150 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+/* Copyright (C) 2020 Facebook */
+#include <errno.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <bpf/bpf.h>
+
+#include "main.h"
+
+#ifdef BPFTOOL_WITHOUT_SKELETONS
+
+int build_obj_pids_table(struct obj_pids_table *table, enum bpf_obj_type type)
+{
+	p_err("bpftool built without PID iterator support");
+	return -ENOTSUP;
+}
+void delete_obj_pids_table(struct obj_pids_table *table) {}
+
+#else /* BPFTOOL_WITHOUT_SKELETONS */
+
+#include "pid_iter.skel.h"
+
+int build_obj_pids_table(struct obj_pids_table *table, enum bpf_obj_type type)
+{
+	struct obj_pids *pids;
+	struct pid_iter_bpf *skel;
+	FILE *f = NULL;
+	int err, ret, fd = -1, pid, i;
+	__u32 id;
+	bool found_id, found_pid;
+
+	hash_init(table->table);
+	set_max_rlimit();
+
+	skel = pid_iter_bpf__open();
+	if (!skel) {
+		p_err("failed to open PID iterator");
+		return -1;
+	}
+
+	skel->rodata->obj_type = type;
+
+	err = pid_iter_bpf__load(skel);
+	if (err) {
+		p_err("failed to load PID iterator: %d", err);
+		goto out;
+	}
+	err = pid_iter_bpf__attach(skel);
+	if (err) {
+		p_err("failed to attach PID iterator: %d", err);
+		goto out;
+	}
+
+	fd = bpf_iter_create(bpf_link__fd(skel->links.iter));
+	if (fd < 0) {
+		err = -errno;
+		p_err("failed to create PID iterator session: %d", err);
+		goto out;
+	}
+
+	f = fdopen(fd, "r");
+	if (!f) {
+		err = -errno;
+		goto out;
+	}
+
+	while (true) {
+		ret = fscanf(f, "%d %u\n", &pid, &id);
+		if (ret == EOF && feof(f))
+			break;
+		if (ret != 2) {
+			err = -EINVAL;
+			p_err("invalid PID iterator output format");
+			goto out;
+		}
+
+		found_id = false;
+		hash_for_each_possible(table->table, pids, node, id) {
+			if (pids->id != id)
+				continue;
+			found_id = true;
+
+			found_pid = false;
+			for (i = 0; i < pids->pid_cnt; i++) {
+				if (pids->pids[i] == pid) {
+					found_pid = true;
+					break;
+				}
+			}
+			if (!found_pid) {
+				void *tmp;
+
+				tmp = realloc(pids->pids, pids->pid_cnt + 1);
+				if (!tmp) {
+					p_err("failed to re-alloc memory for ID %u, PID %d...",
+					      id, pid);
+					break;
+				}
+				pids->pids = tmp;
+				pids->pids[pids->pid_cnt] = pid;
+				pids->pid_cnt++;
+			}
+			break;
+		}
+		if (!found_id) {
+			pids = calloc(1, sizeof(*pids));
+			if (!pids) {
+				p_err("failed to alloc memory for ID %u, PID %d...", id, pid);
+				continue;
+			}
+
+			pids->id = id;
+			pids->pids = malloc(sizeof(*pids->pids));
+			if (!pids->pids) {
+				free(pids);
+				p_err("failed to alloc memory for ID %u, PID %d...", id, pid);
+				continue;
+			}
+			pids->pids[0] = pid;
+			pids->pid_cnt = 1;
+			hash_add(table->table, &pids->node, id);
+		}
+	}
+	err = 0;
+out:
+	if (f)
+		fclose(f);
+	else if (fd >= 0)
+		close(fd);
+	pid_iter_bpf__destroy(skel);
+	return err;
+}
+
+void delete_obj_pids_table(struct obj_pids_table *table)
+{
+	struct obj_pids *pids;
+	struct hlist_node *tmp;
+	unsigned int bkt;
+
+	hash_for_each_safe(table->table, bkt, tmp, pids, node) {
+		hash_del(&pids->node);
+		free(pids->pids);
+		free(pids);
+	}
+}
+
+#endif
diff --git a/tools/bpf/bpftool/prog.c b/tools/bpf/bpftool/prog.c
index 53d47610ff58..6b430f9af2af 100644
--- a/tools/bpf/bpftool/prog.c
+++ b/tools/bpf/bpftool/prog.c
@@ -190,6 +190,24 @@ static void print_prog_json(struct bpf_prog_info *info, int fd)
 		jsonw_end_array(json_wtr);
 	}
 
+	if (!hash_empty(pids_table.table)) {
+		struct obj_pids *pids;
+		int i;
+
+		hash_for_each_possible(pids_table.table, pids, node, info->id) {
+			if (pids->id != info->id)
+				continue;
+			if (pids->pid_cnt == 0)
+				break;
+
+			jsonw_name(json_wtr, "pids");
+			jsonw_start_array(json_wtr);
+			for (i = 0; i < pids->pid_cnt; i++)
+				jsonw_int(json_wtr, pids->pids[i]);
+			jsonw_end_array(json_wtr);
+			break;
+		}
+	}
 	jsonw_end_object(json_wtr);
 }
 
@@ -256,6 +274,23 @@ static void print_prog_plain(struct bpf_prog_info *info, int fd)
 	if (info->btf_id)
 		printf("\n\tbtf_id %d", info->btf_id);
 
+	if (!hash_empty(pids_table.table)) {
+		struct obj_pids *pids;
+		int i;
+
+		hash_for_each_possible(pids_table.table, pids, node, info->id) {
+			if (pids->id != info->id)
+				continue;
+			if (pids->pid_cnt == 0)
+				break;
+
+			printf("\n\tpids:");
+			for (i = 0; i < pids->pid_cnt; i++)
+				printf("%c%d", i == 0 ? ' ' : ',', pids->pids[i]);
+			break;
+		}
+	}
+
 	printf("\n");
 }
 
@@ -321,6 +356,8 @@ static int do_show(int argc, char **argv)
 
 	if (show_pinned)
 		build_pinned_obj_table(&prog_table, BPF_OBJ_PROG);
+	if (show_pids)
+		build_obj_pids_table(&pids_table, BPF_OBJ_PROG);
 
 	if (argc == 2)
 		return do_show_subset(argc, argv);
@@ -362,6 +399,9 @@ static int do_show(int argc, char **argv)
 	if (json_output)
 		jsonw_end_array(json_wtr);
 
+	if (show_pids)
+		delete_obj_pids_table(&pids_table);
+
 	return err;
 }
 
diff --git a/tools/bpf/bpftool/skeleton/pid_iter.bpf.c b/tools/bpf/bpftool/skeleton/pid_iter.bpf.c
new file mode 100644
index 000000000000..9eedf5e56aa7
--- /dev/null
+++ b/tools/bpf/bpftool/skeleton/pid_iter.bpf.c
@@ -0,0 +1,77 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (c) 2020 Facebook
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_core_read.h>
+#include <bpf/bpf_tracing.h>
+
+/* keep in sync with the definition in main.h */
+enum bpf_obj_type {
+	BPF_OBJ_UNKNOWN,
+	BPF_OBJ_PROG,
+	BPF_OBJ_MAP,
+	BPF_OBJ_LINK,
+	BPF_OBJ_BTF,
+};
+
+extern const void bpf_link_fops __ksym;
+extern const void bpf_map_fops __ksym;
+extern const void bpf_prog_fops __ksym;
+extern const void btf_fops __ksym;
+
+const volatile enum bpf_obj_type obj_type = BPF_OBJ_UNKNOWN;
+
+static __always_inline __u32 get_obj_id(void *ent, enum bpf_obj_type type)
+{
+	switch (type) {
+	case BPF_OBJ_PROG:
+		return BPF_CORE_READ((struct bpf_prog *)ent, aux, id);
+	case BPF_OBJ_MAP:
+		return BPF_CORE_READ((struct bpf_map *)ent, id);
+	case BPF_OBJ_BTF:
+		return BPF_CORE_READ((struct btf *)ent, id);
+	case BPF_OBJ_LINK:
+		return BPF_CORE_READ((struct bpf_link *)ent, id);
+	default:
+		return 0;
+	}
+}
+
+SEC("iter/task_file")
+int iter(struct bpf_iter__task_file *ctx)
+{
+	struct file *file = ctx->file;
+	struct task_struct *task = ctx->task;
+	const void *fops;
+	int id;
+
+	if (!file || !task)
+		return 0;
+
+	switch (obj_type) {
+	case BPF_OBJ_PROG:
+		fops = &bpf_prog_fops;
+		break;
+	case BPF_OBJ_MAP:
+		fops = &bpf_map_fops;
+		break;
+	case BPF_OBJ_BTF:
+		fops = &btf_fops;
+		break;
+	case BPF_OBJ_LINK:
+		fops = &bpf_link_fops;
+		break;
+	default:
+		return 0;
+	}
+
+	if (file->f_op != fops)
+		return 0;
+
+	id = get_obj_id(file->private_data, obj_type);
+	BPF_SEQ_PRINTF(ctx->meta->seq, "%d %d\n", task->tgid, id);
+
+	return 0;
+}
+
+char LICENSE[] SEC("license") = "GPL";
