mirror of
https://github.com/linux-msm/laptops-kernel.git
synced 2026-08-13 14:19:53 -07:00
Merge branch 'bpf-type-format'
Martin KaFai Lau says: ==================== This patch introduces BPF Type Format (BTF). BTF (BPF Type Format) is the meta data format which describes the data types of BPF program/map. Hence, it basically focus on the C programming language which the modern BPF is primary using. The first use case is to provide a generic pretty print capability for a BPF map. A modified pahole that can convert dwarf to BTF is here: https://github.com/iamkafai/pahole/tree/btf Please see individual patch for details. v5: - Remove BTF_KIND_FLOAT and BTF_KIND_FUNC which are not currently used. They can be added in the future. Some bpf_df_xxx() are removed together. - Add comment in patch 7 to clarify that the new bpffs_map_fops should not be extended further. v4: - Fix warning (remove unneeded semicolon) - Remove a redundant variable (nr_bytes) from btf_int_check_meta() in patch 1. Caught by W=1. v3: - Rebase to bpf-next - Fix sparse warning (by adding static) - Add BTF header logging: btf_verifier_log_hdr() - Fix the alignment test on btf->type_off - Add tests for the BTF header - Lower the max BTF size to 16MB. It should be enough for some time. We could raise it later if it would be needed. v2: - Use kvfree where needed in patch 1 and 2 - Also consider BTF_INT_OFFSET() in the btf_int_check_meta() in patch 1 - Fix an incorrect goto target in map_create() during the btf-error-path in patch 7 - re-org some local vars to keep the rev xmas tree in btf.c ==================== Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
This commit is contained in:
commit
34df9d37ab
22 files changed
+5077
-42
No files matched your search
+17
-3
@@ -22,6 +22,8 @@ struct perf_event;
|
||||
struct bpf_prog;
|
||||
struct bpf_map;
|
||||
struct sock;
|
||||
struct seq_file;
|
||||
struct btf;
|
||||
|
||||
/* map is generic key/value storage optionally accesible by eBPF programs */
|
||||
struct bpf_map_ops {
|
||||
@@ -43,10 +45,14 @@ struct bpf_map_ops {
|
||||
void (*map_fd_put_ptr)(void *ptr);
|
||||
u32 (*map_gen_lookup)(struct bpf_map *map, struct bpf_insn *insn_buf);
|
||||
u32 (*map_fd_sys_lookup_elem)(void *ptr);
|
||||
void (*map_seq_show_elem)(struct bpf_map *map, void *key,
|
||||
struct seq_file *m);
|
||||
int (*map_check_btf)(const struct bpf_map *map, const struct btf *btf,
|
||||
u32 key_type_id, u32 value_type_id);
|
||||
};
|
||||
|
||||
struct bpf_map {
|
||||
/* 1st cacheline with read-mostly members of which some
|
||||
/* The first two cachelines with read-mostly members of which some
|
||||
* are also accessed in fast-path (e.g. ops, max_entries).
|
||||
*/
|
||||
const struct bpf_map_ops *ops ____cacheline_aligned;
|
||||
@@ -62,10 +68,13 @@ struct bpf_map {
|
||||
u32 pages;
|
||||
u32 id;
|
||||
int numa_node;
|
||||
u32 btf_key_id;
|
||||
u32 btf_value_id;
|
||||
struct btf *btf;
|
||||
bool unpriv_array;
|
||||
/* 7 bytes hole */
|
||||
/* 55 bytes hole */
|
||||
|
||||
/* 2nd cacheline with misc members to avoid false sharing
|
||||
/* The 3rd and 4th cacheline with misc members to avoid false sharing
|
||||
* particularly with refcounting.
|
||||
*/
|
||||
struct user_struct *user ____cacheline_aligned;
|
||||
@@ -100,6 +109,11 @@ static inline struct bpf_offloaded_map *map_to_offmap(struct bpf_map *map)
|
||||
return container_of(map, struct bpf_offloaded_map, map);
|
||||
}
|
||||
|
||||
static inline bool bpf_map_support_seq_show(const struct bpf_map *map)
|
||||
{
|
||||
return map->ops->map_seq_show_elem && map->ops->map_check_btf;
|
||||
}
|
||||
|
||||
extern const struct bpf_map_ops bpf_map_offload_ops;
|
||||
|
||||
/* function argument constraints */
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/* Copyright (c) 2018 Facebook */
|
||||
|
||||
#ifndef _LINUX_BTF_H
|
||||
#define _LINUX_BTF_H 1
|
||||
|
||||
#include <linux/types.h>
|
||||
|
||||
struct btf;
|
||||
struct btf_type;
|
||||
union bpf_attr;
|
||||
|
||||
extern const struct file_operations btf_fops;
|
||||
|
||||
void btf_put(struct btf *btf);
|
||||
int btf_new_fd(const union bpf_attr *attr);
|
||||
struct btf *btf_get_by_fd(int fd);
|
||||
int btf_get_info_by_fd(const struct btf *btf,
|
||||
const union bpf_attr *attr,
|
||||
union bpf_attr __user *uattr);
|
||||
/* Figure out the size of a type_id. If type_id is a modifier
|
||||
* (e.g. const), it will be resolved to find out the type with size.
|
||||
*
|
||||
* For example:
|
||||
* In describing "const void *", type_id is "const" and "const"
|
||||
* refers to "void *". The return type will be "void *".
|
||||
*
|
||||
* If type_id is a simple "int", then return type will be "int".
|
||||
*
|
||||
* @btf: struct btf object
|
||||
* @type_id: Find out the size of type_id. The type_id of the return
|
||||
* type is set to *type_id.
|
||||
* @ret_size: It can be NULL. If not NULL, the size of the return
|
||||
* type is set to *ret_size.
|
||||
* Return: The btf_type (resolved to another type with size info if needed).
|
||||
* NULL is returned if type_id itself does not have size info
|
||||
* (e.g. void) or it cannot be resolved to another type that
|
||||
* has size info.
|
||||
* *type_id and *ret_size will not be changed in the
|
||||
* NULL return case.
|
||||
*/
|
||||
const struct btf_type *btf_type_id_size(const struct btf *btf,
|
||||
u32 *type_id,
|
||||
u32 *ret_size);
|
||||
void btf_type_seq_show(const struct btf *btf, u32 type_id, void *obj,
|
||||
struct seq_file *m);
|
||||
|
||||
#endif
|
||||
@@ -95,6 +95,7 @@ enum bpf_cmd {
|
||||
BPF_OBJ_GET_INFO_BY_FD,
|
||||
BPF_PROG_QUERY,
|
||||
BPF_RAW_TRACEPOINT_OPEN,
|
||||
BPF_BTF_LOAD,
|
||||
};
|
||||
|
||||
enum bpf_map_type {
|
||||
@@ -279,6 +280,9 @@ union bpf_attr {
|
||||
*/
|
||||
char map_name[BPF_OBJ_NAME_LEN];
|
||||
__u32 map_ifindex; /* ifindex of netdev to create on */
|
||||
__u32 btf_fd; /* fd pointing to a BTF type data */
|
||||
__u32 btf_key_id; /* BTF type_id of the key */
|
||||
__u32 btf_value_id; /* BTF type_id of the value */
|
||||
};
|
||||
|
||||
struct { /* anonymous struct used by BPF_MAP_*_ELEM commands */
|
||||
@@ -363,6 +367,14 @@ union bpf_attr {
|
||||
__u64 name;
|
||||
__u32 prog_fd;
|
||||
} raw_tracepoint;
|
||||
|
||||
struct { /* anonymous struct for BPF_BTF_LOAD */
|
||||
__aligned_u64 btf;
|
||||
__aligned_u64 btf_log_buf;
|
||||
__u32 btf_size;
|
||||
__u32 btf_log_size;
|
||||
__u32 btf_log_level;
|
||||
};
|
||||
} __attribute__((aligned(8)));
|
||||
|
||||
/* BPF helper function descriptions:
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
|
||||
/* Copyright (c) 2018 Facebook */
|
||||
#ifndef _UAPI__LINUX_BTF_H__
|
||||
#define _UAPI__LINUX_BTF_H__
|
||||
|
||||
#include <linux/types.h>
|
||||
|
||||
#define BTF_MAGIC 0xeB9F
|
||||
#define BTF_MAGIC_SWAP 0x9FeB
|
||||
#define BTF_VERSION 1
|
||||
#define BTF_FLAGS_COMPR 0x01
|
||||
|
||||
struct btf_header {
|
||||
__u16 magic;
|
||||
__u8 version;
|
||||
__u8 flags;
|
||||
|
||||
__u32 parent_label;
|
||||
__u32 parent_name;
|
||||
|
||||
/* All offsets are in bytes relative to the end of this header */
|
||||
__u32 label_off; /* offset of label section */
|
||||
__u32 object_off; /* offset of data object section*/
|
||||
__u32 func_off; /* offset of function section */
|
||||
__u32 type_off; /* offset of type section */
|
||||
__u32 str_off; /* offset of string section */
|
||||
__u32 str_len; /* length of string section */
|
||||
};
|
||||
|
||||
/* Max # of type identifier */
|
||||
#define BTF_MAX_TYPE 0x7fffffff
|
||||
/* Max offset into the string section */
|
||||
#define BTF_MAX_NAME_OFFSET 0x7fffffff
|
||||
/* Max # of struct/union/enum members or func args */
|
||||
#define BTF_MAX_VLEN 0xffff
|
||||
|
||||
/* The type id is referring to a parent BTF */
|
||||
#define BTF_TYPE_PARENT(id) (((id) >> 31) & 0x1)
|
||||
#define BTF_TYPE_ID(id) ((id) & BTF_MAX_TYPE)
|
||||
|
||||
/* String is in the ELF string section */
|
||||
#define BTF_STR_TBL_ELF_ID(ref) (((ref) >> 31) & 0x1)
|
||||
#define BTF_STR_OFFSET(ref) ((ref) & BTF_MAX_NAME_OFFSET)
|
||||
|
||||
struct btf_type {
|
||||
__u32 name;
|
||||
/* "info" bits arrangement
|
||||
* bits 0-15: vlen (e.g. # of struct's members)
|
||||
* bits 16-23: unused
|
||||
* bits 24-28: kind (e.g. int, ptr, array...etc)
|
||||
* bits 29-30: unused
|
||||
* bits 31: root
|
||||
*/
|
||||
__u32 info;
|
||||
/* "size" is used by INT, ENUM, STRUCT and UNION.
|
||||
* "size" tells the size of the type it is describing.
|
||||
*
|
||||
* "type" is used by PTR, TYPEDEF, VOLATILE, CONST and RESTRICT.
|
||||
* "type" is a type_id referring to another type.
|
||||
*/
|
||||
union {
|
||||
__u32 size;
|
||||
__u32 type;
|
||||
};
|
||||
};
|
||||
|
||||
#define BTF_INFO_KIND(info) (((info) >> 24) & 0x1f)
|
||||
#define BTF_INFO_ISROOT(info) (!!(((info) >> 24) & 0x80))
|
||||
#define BTF_INFO_VLEN(info) ((info) & 0xffff)
|
||||
|
||||
#define BTF_KIND_UNKN 0 /* Unknown */
|
||||
#define BTF_KIND_INT 1 /* Integer */
|
||||
#define BTF_KIND_PTR 2 /* Pointer */
|
||||
#define BTF_KIND_ARRAY 3 /* Array */
|
||||
#define BTF_KIND_STRUCT 4 /* Struct */
|
||||
#define BTF_KIND_UNION 5 /* Union */
|
||||
#define BTF_KIND_ENUM 6 /* Enumeration */
|
||||
#define BTF_KIND_FWD 7 /* Forward */
|
||||
#define BTF_KIND_TYPEDEF 8 /* Typedef */
|
||||
#define BTF_KIND_VOLATILE 9 /* Volatile */
|
||||
#define BTF_KIND_CONST 10 /* Const */
|
||||
#define BTF_KIND_RESTRICT 11 /* Restrict */
|
||||
#define BTF_KIND_MAX 11
|
||||
#define NR_BTF_KINDS 12
|
||||
|
||||
/* For some specific BTF_KIND, "struct btf_type" is immediately
|
||||
* followed by extra data.
|
||||
*/
|
||||
|
||||
/* BTF_KIND_INT is followed by a u32 and the following
|
||||
* is the 32 bits arrangement:
|
||||
*/
|
||||
#define BTF_INT_ENCODING(VAL) (((VAL) & 0xff000000) >> 24)
|
||||
#define BTF_INT_OFFSET(VAL) (((VAL & 0x00ff0000)) >> 16)
|
||||
#define BTF_INT_BITS(VAL) ((VAL) & 0x0000ffff)
|
||||
|
||||
/* Attributes stored in the BTF_INT_ENCODING */
|
||||
#define BTF_INT_SIGNED 0x1
|
||||
#define BTF_INT_CHAR 0x2
|
||||
#define BTF_INT_BOOL 0x4
|
||||
#define BTF_INT_VARARGS 0x8
|
||||
|
||||
/* BTF_KIND_ENUM is followed by multiple "struct btf_enum".
|
||||
* The exact number of btf_enum is stored in the vlen (of the
|
||||
* info in "struct btf_type").
|
||||
*/
|
||||
struct btf_enum {
|
||||
__u32 name;
|
||||
__s32 val;
|
||||
};
|
||||
|
||||
/* BTF_KIND_ARRAY is followed by one "struct btf_array" */
|
||||
struct btf_array {
|
||||
__u32 type;
|
||||
__u32 index_type;
|
||||
__u32 nelems;
|
||||
};
|
||||
|
||||
/* BTF_KIND_STRUCT and BTF_KIND_UNION are followed
|
||||
* by multiple "struct btf_member". The exact number
|
||||
* of btf_member is stored in the vlen (of the info in
|
||||
* "struct btf_type").
|
||||
*/
|
||||
struct btf_member {
|
||||
__u32 name;
|
||||
__u32 type;
|
||||
__u32 offset; /* offset in bits */
|
||||
};
|
||||
|
||||
#endif /* _UAPI__LINUX_BTF_H__ */
|
||||
@@ -4,6 +4,7 @@ obj-y := core.o
|
||||
obj-$(CONFIG_BPF_SYSCALL) += syscall.o verifier.o inode.o helpers.o tnum.o
|
||||
obj-$(CONFIG_BPF_SYSCALL) += hashtab.o arraymap.o percpu_freelist.o bpf_lru_list.o lpm_trie.o map_in_map.o
|
||||
obj-$(CONFIG_BPF_SYSCALL) += disasm.o
|
||||
obj-$(CONFIG_BPF_SYSCALL) += btf.o
|
||||
ifeq ($(CONFIG_NET),y)
|
||||
obj-$(CONFIG_BPF_SYSCALL) += devmap.o
|
||||
obj-$(CONFIG_BPF_SYSCALL) += cpumap.o
|
||||
|
||||
@@ -11,11 +11,13 @@
|
||||
* General Public License for more details.
|
||||
*/
|
||||
#include <linux/bpf.h>
|
||||
#include <linux/btf.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/filter.h>
|
||||
#include <linux/perf_event.h>
|
||||
#include <uapi/linux/btf.h>
|
||||
|
||||
#include "map_in_map.h"
|
||||
|
||||
@@ -336,6 +338,52 @@ static void array_map_free(struct bpf_map *map)
|
||||
bpf_map_area_free(array);
|
||||
}
|
||||
|
||||
static void array_map_seq_show_elem(struct bpf_map *map, void *key,
|
||||
struct seq_file *m)
|
||||
{
|
||||
void *value;
|
||||
|
||||
rcu_read_lock();
|
||||
|
||||
value = array_map_lookup_elem(map, key);
|
||||
if (!value) {
|
||||
rcu_read_unlock();
|
||||
return;
|
||||
}
|
||||
|
||||
seq_printf(m, "%u: ", *(u32 *)key);
|
||||
btf_type_seq_show(map->btf, map->btf_value_id, value, m);
|
||||
seq_puts(m, "\n");
|
||||
|
||||
rcu_read_unlock();
|
||||
}
|
||||
|
||||
static int array_map_check_btf(const struct bpf_map *map, const struct btf *btf,
|
||||
u32 btf_key_id, u32 btf_value_id)
|
||||
{
|
||||
const struct btf_type *key_type, *value_type;
|
||||
u32 key_size, value_size;
|
||||
u32 int_data;
|
||||
|
||||
key_type = btf_type_id_size(btf, &btf_key_id, &key_size);
|
||||
if (!key_type || BTF_INFO_KIND(key_type->info) != BTF_KIND_INT)
|
||||
return -EINVAL;
|
||||
|
||||
int_data = *(u32 *)(key_type + 1);
|
||||
/* bpf array can only take a u32 key. This check makes
|
||||
* sure that the btf matches the attr used during map_create.
|
||||
*/
|
||||
if (BTF_INT_BITS(int_data) != 32 || key_size != 4 ||
|
||||
BTF_INT_OFFSET(int_data))
|
||||
return -EINVAL;
|
||||
|
||||
value_type = btf_type_id_size(btf, &btf_value_id, &value_size);
|
||||
if (!value_type || value_size > map->value_size)
|
||||
return -EINVAL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
const struct bpf_map_ops array_map_ops = {
|
||||
.map_alloc_check = array_map_alloc_check,
|
||||
.map_alloc = array_map_alloc,
|
||||
@@ -345,6 +393,8 @@ const struct bpf_map_ops array_map_ops = {
|
||||
.map_update_elem = array_map_update_elem,
|
||||
.map_delete_elem = array_map_delete_elem,
|
||||
.map_gen_lookup = array_map_gen_lookup,
|
||||
.map_seq_show_elem = array_map_seq_show_elem,
|
||||
.map_check_btf = array_map_check_btf,
|
||||
};
|
||||
|
||||
const struct bpf_map_ops percpu_array_map_ops = {
|
||||
|
||||
+2064
File diff suppressed because it is too large.
Load diff
+153
-3
@@ -150,8 +150,154 @@ static int bpf_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode)
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct map_iter {
|
||||
void *key;
|
||||
bool done;
|
||||
};
|
||||
|
||||
static struct map_iter *map_iter(struct seq_file *m)
|
||||
{
|
||||
return m->private;
|
||||
}
|
||||
|
||||
static struct bpf_map *seq_file_to_map(struct seq_file *m)
|
||||
{
|
||||
return file_inode(m->file)->i_private;
|
||||
}
|
||||
|
||||
static void map_iter_free(struct map_iter *iter)
|
||||
{
|
||||
if (iter) {
|
||||
kfree(iter->key);
|
||||
kfree(iter);
|
||||
}
|
||||
}
|
||||
|
||||
static struct map_iter *map_iter_alloc(struct bpf_map *map)
|
||||
{
|
||||
struct map_iter *iter;
|
||||
|
||||
iter = kzalloc(sizeof(*iter), GFP_KERNEL | __GFP_NOWARN);
|
||||
if (!iter)
|
||||
goto error;
|
||||
|
||||
iter->key = kzalloc(map->key_size, GFP_KERNEL | __GFP_NOWARN);
|
||||
if (!iter->key)
|
||||
goto error;
|
||||
|
||||
return iter;
|
||||
|
||||
error:
|
||||
map_iter_free(iter);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void *map_seq_next(struct seq_file *m, void *v, loff_t *pos)
|
||||
{
|
||||
struct bpf_map *map = seq_file_to_map(m);
|
||||
void *key = map_iter(m)->key;
|
||||
|
||||
if (map_iter(m)->done)
|
||||
return NULL;
|
||||
|
||||
if (unlikely(v == SEQ_START_TOKEN))
|
||||
goto done;
|
||||
|
||||
if (map->ops->map_get_next_key(map, key, key)) {
|
||||
map_iter(m)->done = true;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
done:
|
||||
++(*pos);
|
||||
return key;
|
||||
}
|
||||
|
||||
static void *map_seq_start(struct seq_file *m, loff_t *pos)
|
||||
{
|
||||
if (map_iter(m)->done)
|
||||
return NULL;
|
||||
|
||||
return *pos ? map_iter(m)->key : SEQ_START_TOKEN;
|
||||
}
|
||||
|
||||
static void map_seq_stop(struct seq_file *m, void *v)
|
||||
{
|
||||
}
|
||||
|
||||
static int map_seq_show(struct seq_file *m, void *v)
|
||||
{
|
||||
struct bpf_map *map = seq_file_to_map(m);
|
||||
void *key = map_iter(m)->key;
|
||||
|
||||
if (unlikely(v == SEQ_START_TOKEN)) {
|
||||
seq_puts(m, "# WARNING!! The output is for debug purpose only\n");
|
||||
seq_puts(m, "# WARNING!! The output format will change\n");
|
||||
} else {
|
||||
map->ops->map_seq_show_elem(map, key, m);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct seq_operations bpffs_map_seq_ops = {
|
||||
.start = map_seq_start,
|
||||
.next = map_seq_next,
|
||||
.show = map_seq_show,
|
||||
.stop = map_seq_stop,
|
||||
};
|
||||
|
||||
static int bpffs_map_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
struct bpf_map *map = inode->i_private;
|
||||
struct map_iter *iter;
|
||||
struct seq_file *m;
|
||||
int err;
|
||||
|
||||
iter = map_iter_alloc(map);
|
||||
if (!iter)
|
||||
return -ENOMEM;
|
||||
|
||||
err = seq_open(file, &bpffs_map_seq_ops);
|
||||
if (err) {
|
||||
map_iter_free(iter);
|
||||
return err;
|
||||
}
|
||||
|
||||
m = file->private_data;
|
||||
m->private = iter;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int bpffs_map_release(struct inode *inode, struct file *file)
|
||||
{
|
||||
struct seq_file *m = file->private_data;
|
||||
|
||||
map_iter_free(map_iter(m));
|
||||
|
||||
return seq_release(inode, file);
|
||||
}
|
||||
|
||||
/* bpffs_map_fops should only implement the basic
|
||||
* read operation for a BPF map. The purpose is to
|
||||
* provide a simple user intuitive way to do
|
||||
* "cat bpffs/pathto/a-pinned-map".
|
||||
*
|
||||
* Other operations (e.g. write, lookup...) should be realized by
|
||||
* the userspace tools (e.g. bpftool) through the
|
||||
* BPF_OBJ_GET_INFO_BY_FD and the map's lookup/update
|
||||
* interface.
|
||||
*/
|
||||
static const struct file_operations bpffs_map_fops = {
|
||||
.open = bpffs_map_open,
|
||||
.read = seq_read,
|
||||
.release = bpffs_map_release,
|
||||
};
|
||||
|
||||
static int bpf_mkobj_ops(struct dentry *dentry, umode_t mode, void *raw,
|
||||
const struct inode_operations *iops)
|
||||
const struct inode_operations *iops,
|
||||
const struct file_operations *fops)
|
||||
{
|
||||
struct inode *dir = dentry->d_parent->d_inode;
|
||||
struct inode *inode = bpf_get_inode(dir->i_sb, dir, mode);
|
||||
@@ -159,6 +305,7 @@ static int bpf_mkobj_ops(struct dentry *dentry, umode_t mode, void *raw,
|
||||
return PTR_ERR(inode);
|
||||
|
||||
inode->i_op = iops;
|
||||
inode->i_fop = fops;
|
||||
inode->i_private = raw;
|
||||
|
||||
bpf_dentry_finalize(dentry, inode, dir);
|
||||
@@ -167,12 +314,15 @@ static int bpf_mkobj_ops(struct dentry *dentry, umode_t mode, void *raw,
|
||||
|
||||
static int bpf_mkprog(struct dentry *dentry, umode_t mode, void *arg)
|
||||
{
|
||||
return bpf_mkobj_ops(dentry, mode, arg, &bpf_prog_iops);
|
||||
return bpf_mkobj_ops(dentry, mode, arg, &bpf_prog_iops, NULL);
|
||||
}
|
||||
|
||||
static int bpf_mkmap(struct dentry *dentry, umode_t mode, void *arg)
|
||||
{
|
||||
return bpf_mkobj_ops(dentry, mode, arg, &bpf_map_iops);
|
||||
struct bpf_map *map = arg;
|
||||
|
||||
return bpf_mkobj_ops(dentry, mode, arg, &bpf_map_iops,
|
||||
map->btf ? &bpffs_map_fops : NULL);
|
||||
}
|
||||
|
||||
static struct dentry *
|
||||
|
||||
+50
-1
@@ -11,6 +11,7 @@
|
||||
*/
|
||||
#include <linux/bpf.h>
|
||||
#include <linux/bpf_trace.h>
|
||||
#include <linux/btf.h>
|
||||
#include <linux/syscalls.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/sched/signal.h>
|
||||
@@ -26,6 +27,7 @@
|
||||
#include <linux/cred.h>
|
||||
#include <linux/timekeeping.h>
|
||||
#include <linux/ctype.h>
|
||||
#include <linux/btf.h>
|
||||
|
||||
#define IS_FD_ARRAY(map) ((map)->map_type == BPF_MAP_TYPE_PROG_ARRAY || \
|
||||
(map)->map_type == BPF_MAP_TYPE_PERF_EVENT_ARRAY || \
|
||||
@@ -250,6 +252,7 @@ static void bpf_map_free_deferred(struct work_struct *work)
|
||||
|
||||
bpf_map_uncharge_memlock(map);
|
||||
security_bpf_map_free(map);
|
||||
btf_put(map->btf);
|
||||
/* implementation dependent freeing */
|
||||
map->ops->map_free(map);
|
||||
}
|
||||
@@ -415,7 +418,7 @@ static int bpf_obj_name_cpy(char *dst, const char *src)
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define BPF_MAP_CREATE_LAST_FIELD map_ifindex
|
||||
#define BPF_MAP_CREATE_LAST_FIELD btf_value_id
|
||||
/* called via syscall */
|
||||
static int map_create(union bpf_attr *attr)
|
||||
{
|
||||
@@ -449,6 +452,33 @@ static int map_create(union bpf_attr *attr)
|
||||
atomic_set(&map->refcnt, 1);
|
||||
atomic_set(&map->usercnt, 1);
|
||||
|
||||
if (bpf_map_support_seq_show(map) &&
|
||||
(attr->btf_key_id || attr->btf_value_id)) {
|
||||
struct btf *btf;
|
||||
|
||||
if (!attr->btf_key_id || !attr->btf_value_id) {
|
||||
err = -EINVAL;
|
||||
goto free_map_nouncharge;
|
||||
}
|
||||
|
||||
btf = btf_get_by_fd(attr->btf_fd);
|
||||
if (IS_ERR(btf)) {
|
||||
err = PTR_ERR(btf);
|
||||
goto free_map_nouncharge;
|
||||
}
|
||||
|
||||
err = map->ops->map_check_btf(map, btf, attr->btf_key_id,
|
||||
attr->btf_value_id);
|
||||
if (err) {
|
||||
btf_put(btf);
|
||||
goto free_map_nouncharge;
|
||||
}
|
||||
|
||||
map->btf = btf;
|
||||
map->btf_key_id = attr->btf_key_id;
|
||||
map->btf_value_id = attr->btf_value_id;
|
||||
}
|
||||
|
||||
err = security_bpf_map_alloc(map);
|
||||
if (err)
|
||||
goto free_map_nouncharge;
|
||||
@@ -481,6 +511,7 @@ free_map:
|
||||
free_map_sec:
|
||||
security_bpf_map_free(map);
|
||||
free_map_nouncharge:
|
||||
btf_put(map->btf);
|
||||
map->ops->map_free(map);
|
||||
return err;
|
||||
}
|
||||
@@ -2016,6 +2047,8 @@ static int bpf_obj_get_info_by_fd(const union bpf_attr *attr,
|
||||
else if (f.file->f_op == &bpf_map_fops)
|
||||
err = bpf_map_get_info_by_fd(f.file->private_data, attr,
|
||||
uattr);
|
||||
else if (f.file->f_op == &btf_fops)
|
||||
err = btf_get_info_by_fd(f.file->private_data, attr, uattr);
|
||||
else
|
||||
err = -EINVAL;
|
||||
|
||||
@@ -2023,6 +2056,19 @@ static int bpf_obj_get_info_by_fd(const union bpf_attr *attr,
|
||||
return err;
|
||||
}
|
||||
|
||||
#define BPF_BTF_LOAD_LAST_FIELD btf_log_level
|
||||
|
||||
static int bpf_btf_load(const union bpf_attr *attr)
|
||||
{
|
||||
if (CHECK_ATTR(BPF_BTF_LOAD))
|
||||
return -EINVAL;
|
||||
|
||||
if (!capable(CAP_SYS_ADMIN))
|
||||
return -EPERM;
|
||||
|
||||
return btf_new_fd(attr);
|
||||
}
|
||||
|
||||
SYSCALL_DEFINE3(bpf, int, cmd, union bpf_attr __user *, uattr, unsigned int, size)
|
||||
{
|
||||
union bpf_attr attr = {};
|
||||
@@ -2103,6 +2149,9 @@ SYSCALL_DEFINE3(bpf, int, cmd, union bpf_attr __user *, uattr, unsigned int, siz
|
||||
case BPF_RAW_TRACEPOINT_OPEN:
|
||||
err = bpf_raw_tracepoint_open(&attr);
|
||||
break;
|
||||
case BPF_BTF_LOAD:
|
||||
err = bpf_btf_load(&attr);
|
||||
break;
|
||||
default:
|
||||
err = -EINVAL;
|
||||
break;
|
||||
|
||||
@@ -95,6 +95,7 @@ enum bpf_cmd {
|
||||
BPF_OBJ_GET_INFO_BY_FD,
|
||||
BPF_PROG_QUERY,
|
||||
BPF_RAW_TRACEPOINT_OPEN,
|
||||
BPF_BTF_LOAD,
|
||||
};
|
||||
|
||||
enum bpf_map_type {
|
||||
@@ -279,6 +280,9 @@ union bpf_attr {
|
||||
*/
|
||||
char map_name[BPF_OBJ_NAME_LEN];
|
||||
__u32 map_ifindex; /* ifindex of netdev to create on */
|
||||
__u32 btf_fd; /* fd pointing to a BTF type data */
|
||||
__u32 btf_key_id; /* BTF type_id of the key */
|
||||
__u32 btf_value_id; /* BTF type_id of the value */
|
||||
};
|
||||
|
||||
struct { /* anonymous struct used by BPF_MAP_*_ELEM commands */
|
||||
@@ -363,6 +367,14 @@ union bpf_attr {
|
||||
__u64 name;
|
||||
__u32 prog_fd;
|
||||
} raw_tracepoint;
|
||||
|
||||
struct { /* anonymous struct for BPF_BTF_LOAD */
|
||||
__aligned_u64 btf;
|
||||
__aligned_u64 btf_log_buf;
|
||||
__u32 btf_size;
|
||||
__u32 btf_log_size;
|
||||
__u32 btf_log_level;
|
||||
};
|
||||
} __attribute__((aligned(8)));
|
||||
|
||||
/* BPF helper function descriptions:
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
|
||||
/* Copyright (c) 2018 Facebook */
|
||||
#ifndef _UAPI__LINUX_BTF_H__
|
||||
#define _UAPI__LINUX_BTF_H__
|
||||
|
||||
#include <linux/types.h>
|
||||
|
||||
#define BTF_MAGIC 0xeB9F
|
||||
#define BTF_MAGIC_SWAP 0x9FeB
|
||||
#define BTF_VERSION 1
|
||||
#define BTF_FLAGS_COMPR 0x01
|
||||
|
||||
struct btf_header {
|
||||
__u16 magic;
|
||||
__u8 version;
|
||||
__u8 flags;
|
||||
|
||||
__u32 parent_label;
|
||||
__u32 parent_name;
|
||||
|
||||
/* All offsets are in bytes relative to the end of this header */
|
||||
__u32 label_off; /* offset of label section */
|
||||
__u32 object_off; /* offset of data object section*/
|
||||
__u32 func_off; /* offset of function section */
|
||||
__u32 type_off; /* offset of type section */
|
||||
__u32 str_off; /* offset of string section */
|
||||
__u32 str_len; /* length of string section */
|
||||
};
|
||||
|
||||
/* Max # of type identifier */
|
||||
#define BTF_MAX_TYPE 0x7fffffff
|
||||
/* Max offset into the string section */
|
||||
#define BTF_MAX_NAME_OFFSET 0x7fffffff
|
||||
/* Max # of struct/union/enum members or func args */
|
||||
#define BTF_MAX_VLEN 0xffff
|
||||
|
||||
/* The type id is referring to a parent BTF */
|
||||
#define BTF_TYPE_PARENT(id) (((id) >> 31) & 0x1)
|
||||
#define BTF_TYPE_ID(id) ((id) & BTF_MAX_TYPE)
|
||||
|
||||
/* String is in the ELF string section */
|
||||
#define BTF_STR_TBL_ELF_ID(ref) (((ref) >> 31) & 0x1)
|
||||
#define BTF_STR_OFFSET(ref) ((ref) & BTF_MAX_NAME_OFFSET)
|
||||
|
||||
struct btf_type {
|
||||
__u32 name;
|
||||
/* "info" bits arrangement
|
||||
* bits 0-15: vlen (e.g. # of struct's members)
|
||||
* bits 16-23: unused
|
||||
* bits 24-28: kind (e.g. int, ptr, array...etc)
|
||||
* bits 29-30: unused
|
||||
* bits 31: root
|
||||
*/
|
||||
__u32 info;
|
||||
/* "size" is used by INT, ENUM, STRUCT and UNION.
|
||||
* "size" tells the size of the type it is describing.
|
||||
*
|
||||
* "type" is used by PTR, TYPEDEF, VOLATILE, CONST and RESTRICT.
|
||||
* "type" is a type_id referring to another type.
|
||||
*/
|
||||
union {
|
||||
__u32 size;
|
||||
__u32 type;
|
||||
};
|
||||
};
|
||||
|
||||
#define BTF_INFO_KIND(info) (((info) >> 24) & 0x1f)
|
||||
#define BTF_INFO_ISROOT(info) (!!(((info) >> 24) & 0x80))
|
||||
#define BTF_INFO_VLEN(info) ((info) & 0xffff)
|
||||
|
||||
#define BTF_KIND_UNKN 0 /* Unknown */
|
||||
#define BTF_KIND_INT 1 /* Integer */
|
||||
#define BTF_KIND_PTR 2 /* Pointer */
|
||||
#define BTF_KIND_ARRAY 3 /* Array */
|
||||
#define BTF_KIND_STRUCT 4 /* Struct */
|
||||
#define BTF_KIND_UNION 5 /* Union */
|
||||
#define BTF_KIND_ENUM 6 /* Enumeration */
|
||||
#define BTF_KIND_FWD 7 /* Forward */
|
||||
#define BTF_KIND_TYPEDEF 8 /* Typedef */
|
||||
#define BTF_KIND_VOLATILE 9 /* Volatile */
|
||||
#define BTF_KIND_CONST 10 /* Const */
|
||||
#define BTF_KIND_RESTRICT 11 /* Restrict */
|
||||
#define BTF_KIND_MAX 11
|
||||
#define NR_BTF_KINDS 12
|
||||
|
||||
/* For some specific BTF_KIND, "struct btf_type" is immediately
|
||||
* followed by extra data.
|
||||
*/
|
||||
|
||||
/* BTF_KIND_INT is followed by a u32 and the following
|
||||
* is the 32 bits arrangement:
|
||||
*/
|
||||
#define BTF_INT_ENCODING(VAL) (((VAL) & 0xff000000) >> 24)
|
||||
#define BTF_INT_OFFSET(VAL) (((VAL & 0x00ff0000)) >> 16)
|
||||
#define BTF_INT_BITS(VAL) ((VAL) & 0x0000ffff)
|
||||
|
||||
/* Attributes stored in the BTF_INT_ENCODING */
|
||||
#define BTF_INT_SIGNED 0x1
|
||||
#define BTF_INT_CHAR 0x2
|
||||
#define BTF_INT_BOOL 0x4
|
||||
#define BTF_INT_VARARGS 0x8
|
||||
|
||||
/* BTF_KIND_ENUM is followed by multiple "struct btf_enum".
|
||||
* The exact number of btf_enum is stored in the vlen (of the
|
||||
* info in "struct btf_type").
|
||||
*/
|
||||
struct btf_enum {
|
||||
__u32 name;
|
||||
__s32 val;
|
||||
};
|
||||
|
||||
/* BTF_KIND_ARRAY is followed by one "struct btf_array" */
|
||||
struct btf_array {
|
||||
__u32 type;
|
||||
__u32 index_type;
|
||||
__u32 nelems;
|
||||
};
|
||||
|
||||
/* BTF_KIND_STRUCT and BTF_KIND_UNION are followed
|
||||
* by multiple "struct btf_member". The exact number
|
||||
* of btf_member is stored in the vlen (of the info in
|
||||
* "struct btf_type").
|
||||
*/
|
||||
struct btf_member {
|
||||
__u32 name;
|
||||
__u32 type;
|
||||
__u32 offset; /* offset in bits */
|
||||
};
|
||||
|
||||
#endif /* _UAPI__LINUX_BTF_H__ */
|
||||
+1
-1
@@ -1 +1 @@
|
||||
libbpf-y := libbpf.o bpf.o nlattr.o
|
||||
libbpf-y := libbpf.o bpf.o nlattr.o btf.o
|
||||
+77
-19
@@ -73,43 +73,76 @@ static inline int sys_bpf(enum bpf_cmd cmd, union bpf_attr *attr,
|
||||
return syscall(__NR_bpf, cmd, attr, size);
|
||||
}
|
||||
|
||||
int bpf_create_map_node(enum bpf_map_type map_type, const char *name,
|
||||
int key_size, int value_size, int max_entries,
|
||||
__u32 map_flags, int node)
|
||||
int bpf_create_map_xattr(const struct bpf_create_map_attr *create_attr)
|
||||
{
|
||||
__u32 name_len = name ? strlen(name) : 0;
|
||||
__u32 name_len = create_attr->name ? strlen(create_attr->name) : 0;
|
||||
union bpf_attr attr;
|
||||
|
||||
memset(&attr, '\0', sizeof(attr));
|
||||
|
||||
attr.map_type = map_type;
|
||||
attr.key_size = key_size;
|
||||
attr.value_size = value_size;
|
||||
attr.max_entries = max_entries;
|
||||
attr.map_flags = map_flags;
|
||||
memcpy(attr.map_name, name, min(name_len, BPF_OBJ_NAME_LEN - 1));
|
||||
|
||||
if (node >= 0) {
|
||||
attr.map_flags |= BPF_F_NUMA_NODE;
|
||||
attr.numa_node = node;
|
||||
}
|
||||
attr.map_type = create_attr->map_type;
|
||||
attr.key_size = create_attr->key_size;
|
||||
attr.value_size = create_attr->value_size;
|
||||
attr.max_entries = create_attr->max_entries;
|
||||
attr.map_flags = create_attr->map_flags;
|
||||
memcpy(attr.map_name, create_attr->name,
|
||||
min(name_len, BPF_OBJ_NAME_LEN - 1));
|
||||
attr.numa_node = create_attr->numa_node;
|
||||
attr.btf_fd = create_attr->btf_fd;
|
||||
attr.btf_key_id = create_attr->btf_key_id;
|
||||
attr.btf_value_id = create_attr->btf_value_id;
|
||||
|
||||
return sys_bpf(BPF_MAP_CREATE, &attr, sizeof(attr));
|
||||
}
|
||||
|
||||
int bpf_create_map_node(enum bpf_map_type map_type, const char *name,
|
||||
int key_size, int value_size, int max_entries,
|
||||
__u32 map_flags, int node)
|
||||
{
|
||||
struct bpf_create_map_attr map_attr = {};
|
||||
|
||||
map_attr.name = name;
|
||||
map_attr.map_type = map_type;
|
||||
map_attr.map_flags = map_flags;
|
||||
map_attr.key_size = key_size;
|
||||
map_attr.value_size = value_size;
|
||||
map_attr.max_entries = max_entries;
|
||||
if (node >= 0) {
|
||||
map_attr.numa_node = node;
|
||||
map_attr.map_flags |= BPF_F_NUMA_NODE;
|
||||
}
|
||||
|
||||
return bpf_create_map_xattr(&map_attr);
|
||||
}
|
||||
|
||||
int bpf_create_map(enum bpf_map_type map_type, int key_size,
|
||||
int value_size, int max_entries, __u32 map_flags)
|
||||
{
|
||||
return bpf_create_map_node(map_type, NULL, key_size, value_size,
|
||||
max_entries, map_flags, -1);
|
||||
struct bpf_create_map_attr map_attr = {};
|
||||
|
||||
map_attr.map_type = map_type;
|
||||
map_attr.map_flags = map_flags;
|
||||
map_attr.key_size = key_size;
|
||||
map_attr.value_size = value_size;
|
||||
map_attr.max_entries = max_entries;
|
||||
|
||||
return bpf_create_map_xattr(&map_attr);
|
||||
}
|
||||
|
||||
int bpf_create_map_name(enum bpf_map_type map_type, const char *name,
|
||||
int key_size, int value_size, int max_entries,
|
||||
__u32 map_flags)
|
||||
{
|
||||
return bpf_create_map_node(map_type, name, key_size, value_size,
|
||||
max_entries, map_flags, -1);
|
||||
struct bpf_create_map_attr map_attr = {};
|
||||
|
||||
map_attr.name = name;
|
||||
map_attr.map_type = map_type;
|
||||
map_attr.map_flags = map_flags;
|
||||
map_attr.key_size = key_size;
|
||||
map_attr.value_size = value_size;
|
||||
map_attr.max_entries = max_entries;
|
||||
|
||||
return bpf_create_map_xattr(&map_attr);
|
||||
}
|
||||
|
||||
int bpf_create_map_in_map_node(enum bpf_map_type map_type, const char *name,
|
||||
@@ -573,3 +606,28 @@ cleanup:
|
||||
close(sock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int bpf_load_btf(void *btf, __u32 btf_size, char *log_buf, __u32 log_buf_size,
|
||||
bool do_log)
|
||||
{
|
||||
union bpf_attr attr = {};
|
||||
int fd;
|
||||
|
||||
attr.btf = ptr_to_u64(btf);
|
||||
attr.btf_size = btf_size;
|
||||
|
||||
retry:
|
||||
if (do_log && log_buf && log_buf_size) {
|
||||
attr.btf_log_level = 1;
|
||||
attr.btf_log_size = log_buf_size;
|
||||
attr.btf_log_buf = ptr_to_u64(log_buf);
|
||||
}
|
||||
|
||||
fd = sys_bpf(BPF_BTF_LOAD, &attr, sizeof(attr));
|
||||
if (fd == -1 && !do_log && log_buf && log_buf_size) {
|
||||
do_log = true;
|
||||
goto retry;
|
||||
}
|
||||
|
||||
return fd;
|
||||
}
|
||||
@@ -26,6 +26,20 @@
|
||||
#include <linux/bpf.h>
|
||||
#include <stddef.h>
|
||||
|
||||
struct bpf_create_map_attr {
|
||||
const char *name;
|
||||
enum bpf_map_type map_type;
|
||||
__u32 map_flags;
|
||||
__u32 key_size;
|
||||
__u32 value_size;
|
||||
__u32 max_entries;
|
||||
__u32 numa_node;
|
||||
__u32 btf_fd;
|
||||
__u32 btf_key_id;
|
||||
__u32 btf_value_id;
|
||||
};
|
||||
|
||||
int bpf_create_map_xattr(const struct bpf_create_map_attr *create_attr);
|
||||
int bpf_create_map_node(enum bpf_map_type map_type, const char *name,
|
||||
int key_size, int value_size, int max_entries,
|
||||
__u32 map_flags, int node);
|
||||
@@ -87,4 +101,6 @@ int bpf_obj_get_info_by_fd(int prog_fd, void *info, __u32 *info_len);
|
||||
int bpf_prog_query(int target_fd, enum bpf_attach_type type, __u32 query_flags,
|
||||
__u32 *attach_flags, __u32 *prog_ids, __u32 *prog_cnt);
|
||||
int bpf_raw_tracepoint_open(const char *name, int prog_fd);
|
||||
int bpf_load_btf(void *btf, __u32 btf_size, char *log_buf, __u32 log_buf_size,
|
||||
bool do_log);
|
||||
#endif
|
||||
@@ -0,0 +1,374 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/* Copyright (c) 2018 Facebook */
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/btf.h>
|
||||
#include "btf.h"
|
||||
#include "bpf.h"
|
||||
|
||||
#define elog(fmt, ...) { if (err_log) err_log(fmt, ##__VA_ARGS__); }
|
||||
#define max(a, b) ((a) > (b) ? (a) : (b))
|
||||
#define min(a, b) ((a) < (b) ? (a) : (b))
|
||||
|
||||
#define BTF_MAX_NR_TYPES 65535
|
||||
|
||||
static struct btf_type btf_void;
|
||||
|
||||
struct btf {
|
||||
union {
|
||||
struct btf_header *hdr;
|
||||
void *data;
|
||||
};
|
||||
struct btf_type **types;
|
||||
const char *strings;
|
||||
void *nohdr_data;
|
||||
uint32_t nr_types;
|
||||
uint32_t types_size;
|
||||
uint32_t data_size;
|
||||
int fd;
|
||||
};
|
||||
|
||||
static const char *btf_name_by_offset(const struct btf *btf, uint32_t offset)
|
||||
{
|
||||
if (!BTF_STR_TBL_ELF_ID(offset) &&
|
||||
BTF_STR_OFFSET(offset) < btf->hdr->str_len)
|
||||
return &btf->strings[BTF_STR_OFFSET(offset)];
|
||||
else
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int btf_add_type(struct btf *btf, struct btf_type *t)
|
||||
{
|
||||
if (btf->types_size - btf->nr_types < 2) {
|
||||
struct btf_type **new_types;
|
||||
u32 expand_by, new_size;
|
||||
|
||||
if (btf->types_size == BTF_MAX_NR_TYPES)
|
||||
return -E2BIG;
|
||||
|
||||
expand_by = max(btf->types_size >> 2, 16);
|
||||
new_size = min(BTF_MAX_NR_TYPES, btf->types_size + expand_by);
|
||||
|
||||
new_types = realloc(btf->types, sizeof(*new_types) * new_size);
|
||||
if (!new_types)
|
||||
return -ENOMEM;
|
||||
|
||||
if (btf->nr_types == 0)
|
||||
new_types[0] = &btf_void;
|
||||
|
||||
btf->types = new_types;
|
||||
btf->types_size = new_size;
|
||||
}
|
||||
|
||||
btf->types[++(btf->nr_types)] = t;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int btf_parse_hdr(struct btf *btf, btf_print_fn_t err_log)
|
||||
{
|
||||
const struct btf_header *hdr = btf->hdr;
|
||||
u32 meta_left;
|
||||
|
||||
if (btf->data_size < sizeof(struct btf_header)) {
|
||||
elog("BTF header not found\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (hdr->magic != BTF_MAGIC) {
|
||||
elog("Invalid BTF magic:%x\n", hdr->magic);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (hdr->version != BTF_VERSION) {
|
||||
elog("Unsupported BTF version:%u\n", hdr->version);
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
if (hdr->flags) {
|
||||
elog("Unsupported BTF flags:%x\n", hdr->flags);
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
meta_left = btf->data_size - sizeof(*hdr);
|
||||
if (!meta_left) {
|
||||
elog("BTF has no data\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (meta_left < hdr->type_off) {
|
||||
elog("Invalid BTF type section offset:%u\n", hdr->type_off);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (meta_left < hdr->str_off) {
|
||||
elog("Invalid BTF string section offset:%u\n", hdr->str_off);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (hdr->type_off >= hdr->str_off) {
|
||||
elog("BTF type section offset >= string section offset. No type?\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (hdr->type_off & 0x02) {
|
||||
elog("BTF type section is not aligned to 4 bytes\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
btf->nohdr_data = btf->hdr + 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int btf_parse_str_sec(struct btf *btf, btf_print_fn_t err_log)
|
||||
{
|
||||
const struct btf_header *hdr = btf->hdr;
|
||||
const char *start = btf->nohdr_data + hdr->str_off;
|
||||
const char *end = start + btf->hdr->str_len;
|
||||
|
||||
if (!hdr->str_len || hdr->str_len - 1 > BTF_MAX_NAME_OFFSET ||
|
||||
start[0] || end[-1]) {
|
||||
elog("Invalid BTF string section\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
btf->strings = start;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int btf_parse_type_sec(struct btf *btf, btf_print_fn_t err_log)
|
||||
{
|
||||
struct btf_header *hdr = btf->hdr;
|
||||
void *nohdr_data = btf->nohdr_data;
|
||||
void *next_type = nohdr_data + hdr->type_off;
|
||||
void *end_type = nohdr_data + hdr->str_off;
|
||||
|
||||
while (next_type < end_type) {
|
||||
struct btf_type *t = next_type;
|
||||
uint16_t vlen = BTF_INFO_VLEN(t->info);
|
||||
int err;
|
||||
|
||||
next_type += sizeof(*t);
|
||||
switch (BTF_INFO_KIND(t->info)) {
|
||||
case BTF_KIND_INT:
|
||||
next_type += sizeof(int);
|
||||
break;
|
||||
case BTF_KIND_ARRAY:
|
||||
next_type += sizeof(struct btf_array);
|
||||
break;
|
||||
case BTF_KIND_STRUCT:
|
||||
case BTF_KIND_UNION:
|
||||
next_type += vlen * sizeof(struct btf_member);
|
||||
break;
|
||||
case BTF_KIND_ENUM:
|
||||
next_type += vlen * sizeof(struct btf_enum);
|
||||
break;
|
||||
case BTF_KIND_TYPEDEF:
|
||||
case BTF_KIND_PTR:
|
||||
case BTF_KIND_FWD:
|
||||
case BTF_KIND_VOLATILE:
|
||||
case BTF_KIND_CONST:
|
||||
case BTF_KIND_RESTRICT:
|
||||
break;
|
||||
default:
|
||||
elog("Unsupported BTF_KIND:%u\n",
|
||||
BTF_INFO_KIND(t->info));
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
err = btf_add_type(btf, t);
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct btf_type *btf_type_by_id(const struct btf *btf,
|
||||
uint32_t type_id)
|
||||
{
|
||||
if (type_id > btf->nr_types)
|
||||
return NULL;
|
||||
|
||||
return btf->types[type_id];
|
||||
}
|
||||
|
||||
static bool btf_type_is_void(const struct btf_type *t)
|
||||
{
|
||||
return t == &btf_void || BTF_INFO_KIND(t->info) == BTF_KIND_FWD;
|
||||
}
|
||||
|
||||
static bool btf_type_is_void_or_null(const struct btf_type *t)
|
||||
{
|
||||
return !t || btf_type_is_void(t);
|
||||
}
|
||||
|
||||
static int64_t btf_type_size(const struct btf_type *t)
|
||||
{
|
||||
switch (BTF_INFO_KIND(t->info)) {
|
||||
case BTF_KIND_INT:
|
||||
case BTF_KIND_STRUCT:
|
||||
case BTF_KIND_UNION:
|
||||
case BTF_KIND_ENUM:
|
||||
return t->size;
|
||||
case BTF_KIND_PTR:
|
||||
return sizeof(void *);
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
#define MAX_RESOLVE_DEPTH 32
|
||||
|
||||
int64_t btf__resolve_size(const struct btf *btf, uint32_t type_id)
|
||||
{
|
||||
const struct btf_array *array;
|
||||
const struct btf_type *t;
|
||||
uint32_t nelems = 1;
|
||||
int64_t size = -1;
|
||||
int i;
|
||||
|
||||
t = btf_type_by_id(btf, type_id);
|
||||
for (i = 0; i < MAX_RESOLVE_DEPTH && !btf_type_is_void_or_null(t);
|
||||
i++) {
|
||||
size = btf_type_size(t);
|
||||
if (size >= 0)
|
||||
break;
|
||||
|
||||
switch (BTF_INFO_KIND(t->info)) {
|
||||
case BTF_KIND_TYPEDEF:
|
||||
case BTF_KIND_VOLATILE:
|
||||
case BTF_KIND_CONST:
|
||||
case BTF_KIND_RESTRICT:
|
||||
type_id = t->type;
|
||||
break;
|
||||
case BTF_KIND_ARRAY:
|
||||
array = (const struct btf_array *)(t + 1);
|
||||
if (nelems && array->nelems > UINT32_MAX / nelems)
|
||||
return -E2BIG;
|
||||
nelems *= array->nelems;
|
||||
type_id = array->type;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
t = btf_type_by_id(btf, type_id);
|
||||
}
|
||||
|
||||
if (size < 0)
|
||||
return -EINVAL;
|
||||
|
||||
if (nelems && size > UINT32_MAX / nelems)
|
||||
return -E2BIG;
|
||||
|
||||
return nelems * size;
|
||||
}
|
||||
|
||||
int32_t btf__find_by_name(const struct btf *btf, const char *type_name)
|
||||
{
|
||||
uint32_t i;
|
||||
|
||||
if (!strcmp(type_name, "void"))
|
||||
return 0;
|
||||
|
||||
for (i = 1; i <= btf->nr_types; i++) {
|
||||
const struct btf_type *t = btf->types[i];
|
||||
const char *name = btf_name_by_offset(btf, t->name);
|
||||
|
||||
if (name && !strcmp(type_name, name))
|
||||
return i;
|
||||
}
|
||||
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
void btf__free(struct btf *btf)
|
||||
{
|
||||
if (!btf)
|
||||
return;
|
||||
|
||||
if (btf->fd != -1)
|
||||
close(btf->fd);
|
||||
|
||||
free(btf->data);
|
||||
free(btf->types);
|
||||
free(btf);
|
||||
}
|
||||
|
||||
struct btf *btf__new(uint8_t *data, uint32_t size,
|
||||
btf_print_fn_t err_log)
|
||||
{
|
||||
uint32_t log_buf_size = 0;
|
||||
char *log_buf = NULL;
|
||||
struct btf *btf;
|
||||
int err;
|
||||
|
||||
btf = calloc(1, sizeof(struct btf));
|
||||
if (!btf)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
btf->fd = -1;
|
||||
|
||||
if (err_log) {
|
||||
log_buf = malloc(BPF_LOG_BUF_SIZE);
|
||||
if (!log_buf) {
|
||||
err = -ENOMEM;
|
||||
goto done;
|
||||
}
|
||||
*log_buf = 0;
|
||||
log_buf_size = BPF_LOG_BUF_SIZE;
|
||||
}
|
||||
|
||||
btf->data = malloc(size);
|
||||
if (!btf->data) {
|
||||
err = -ENOMEM;
|
||||
goto done;
|
||||
}
|
||||
|
||||
memcpy(btf->data, data, size);
|
||||
btf->data_size = size;
|
||||
|
||||
btf->fd = bpf_load_btf(btf->data, btf->data_size,
|
||||
log_buf, log_buf_size, false);
|
||||
|
||||
if (btf->fd == -1) {
|
||||
err = -errno;
|
||||
elog("Error loading BTF: %s(%d)\n", strerror(errno), errno);
|
||||
if (log_buf && *log_buf)
|
||||
elog("%s\n", log_buf);
|
||||
goto done;
|
||||
}
|
||||
|
||||
err = btf_parse_hdr(btf, err_log);
|
||||
if (err)
|
||||
goto done;
|
||||
|
||||
err = btf_parse_str_sec(btf, err_log);
|
||||
if (err)
|
||||
goto done;
|
||||
|
||||
err = btf_parse_type_sec(btf, err_log);
|
||||
|
||||
done:
|
||||
free(log_buf);
|
||||
|
||||
if (err) {
|
||||
btf__free(btf);
|
||||
return ERR_PTR(err);
|
||||
}
|
||||
|
||||
return btf;
|
||||
}
|
||||
|
||||
int btf__fd(const struct btf *btf)
|
||||
{
|
||||
return btf->fd;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/* Copyright (c) 2018 Facebook */
|
||||
|
||||
#ifndef __BPF_BTF_H
|
||||
#define __BPF_BTF_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define BTF_ELF_SEC ".BTF"
|
||||
|
||||
struct btf;
|
||||
|
||||
typedef int (*btf_print_fn_t)(const char *, ...)
|
||||
__attribute__((format(printf, 1, 2)));
|
||||
|
||||
void btf__free(struct btf *btf);
|
||||
struct btf *btf__new(uint8_t *data, uint32_t size, btf_print_fn_t err_log);
|
||||
int32_t btf__find_by_name(const struct btf *btf, const char *type_name);
|
||||
int64_t btf__resolve_size(const struct btf *btf, uint32_t type_id);
|
||||
int btf__fd(const struct btf *btf);
|
||||
|
||||
#endif
|
||||
+134
-12
@@ -45,6 +45,7 @@
|
||||
|
||||
#include "libbpf.h"
|
||||
#include "bpf.h"
|
||||
#include "btf.h"
|
||||
|
||||
#ifndef EM_BPF
|
||||
#define EM_BPF 247
|
||||
@@ -212,6 +213,8 @@ struct bpf_map {
|
||||
char *name;
|
||||
size_t offset;
|
||||
struct bpf_map_def def;
|
||||
uint32_t btf_key_id;
|
||||
uint32_t btf_value_id;
|
||||
void *priv;
|
||||
bpf_map_clear_priv_t clear_priv;
|
||||
};
|
||||
@@ -256,6 +259,8 @@ struct bpf_object {
|
||||
*/
|
||||
struct list_head list;
|
||||
|
||||
struct btf *btf;
|
||||
|
||||
void *priv;
|
||||
bpf_object_clear_priv_t clear_priv;
|
||||
|
||||
@@ -819,7 +824,15 @@ static int bpf_object__elf_collect(struct bpf_object *obj)
|
||||
data->d_size);
|
||||
else if (strcmp(name, "maps") == 0)
|
||||
obj->efile.maps_shndx = idx;
|
||||
else if (sh.sh_type == SHT_SYMTAB) {
|
||||
else if (strcmp(name, BTF_ELF_SEC) == 0) {
|
||||
obj->btf = btf__new(data->d_buf, data->d_size,
|
||||
__pr_debug);
|
||||
if (IS_ERR(obj->btf)) {
|
||||
pr_warning("Error loading ELF section %s: %ld. Ignored and continue.\n",
|
||||
BTF_ELF_SEC, PTR_ERR(obj->btf));
|
||||
obj->btf = NULL;
|
||||
}
|
||||
} else if (sh.sh_type == SHT_SYMTAB) {
|
||||
if (obj->efile.symbols) {
|
||||
pr_warning("bpf: multiple SYMTAB in %s\n",
|
||||
obj->path);
|
||||
@@ -996,33 +1009,126 @@ bpf_program__collect_reloc(struct bpf_program *prog, GElf_Shdr *shdr,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int bpf_map_find_btf_info(struct bpf_map *map, const struct btf *btf)
|
||||
{
|
||||
struct bpf_map_def *def = &map->def;
|
||||
const size_t max_name = 256;
|
||||
int64_t key_size, value_size;
|
||||
int32_t key_id, value_id;
|
||||
char name[max_name];
|
||||
|
||||
/* Find key type by name from BTF */
|
||||
if (snprintf(name, max_name, "%s_key", map->name) == max_name) {
|
||||
pr_warning("map:%s length of BTF key_type:%s_key is too long\n",
|
||||
map->name, map->name);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
key_id = btf__find_by_name(btf, name);
|
||||
if (key_id < 0) {
|
||||
pr_debug("map:%s key_type:%s cannot be found in BTF\n",
|
||||
map->name, name);
|
||||
return key_id;
|
||||
}
|
||||
|
||||
key_size = btf__resolve_size(btf, key_id);
|
||||
if (key_size < 0) {
|
||||
pr_warning("map:%s key_type:%s cannot get the BTF type_size\n",
|
||||
map->name, name);
|
||||
return key_size;
|
||||
}
|
||||
|
||||
if (def->key_size != key_size) {
|
||||
pr_warning("map:%s key_type:%s has BTF type_size:%ld != key_size:%u\n",
|
||||
map->name, name, key_size, def->key_size);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Find value type from BTF */
|
||||
if (snprintf(name, max_name, "%s_value", map->name) == max_name) {
|
||||
pr_warning("map:%s length of BTF value_type:%s_value is too long\n",
|
||||
map->name, map->name);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
value_id = btf__find_by_name(btf, name);
|
||||
if (value_id < 0) {
|
||||
pr_debug("map:%s value_type:%s cannot be found in BTF\n",
|
||||
map->name, name);
|
||||
return value_id;
|
||||
}
|
||||
|
||||
value_size = btf__resolve_size(btf, value_id);
|
||||
if (value_size < 0) {
|
||||
pr_warning("map:%s value_type:%s cannot get the BTF type_size\n",
|
||||
map->name, name);
|
||||
return value_size;
|
||||
}
|
||||
|
||||
if (def->value_size != value_size) {
|
||||
pr_warning("map:%s value_type:%s has BTF type_size:%ld != value_size:%u\n",
|
||||
map->name, name, value_size, def->value_size);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
map->btf_key_id = key_id;
|
||||
map->btf_value_id = value_id;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
bpf_object__create_maps(struct bpf_object *obj)
|
||||
{
|
||||
struct bpf_create_map_attr create_attr = {};
|
||||
unsigned int i;
|
||||
int err;
|
||||
|
||||
for (i = 0; i < obj->nr_maps; i++) {
|
||||
struct bpf_map_def *def = &obj->maps[i].def;
|
||||
int *pfd = &obj->maps[i].fd;
|
||||
struct bpf_map *map = &obj->maps[i];
|
||||
struct bpf_map_def *def = &map->def;
|
||||
int *pfd = &map->fd;
|
||||
|
||||
create_attr.name = map->name;
|
||||
create_attr.map_type = def->type;
|
||||
create_attr.map_flags = def->map_flags;
|
||||
create_attr.key_size = def->key_size;
|
||||
create_attr.value_size = def->value_size;
|
||||
create_attr.max_entries = def->max_entries;
|
||||
create_attr.btf_fd = 0;
|
||||
create_attr.btf_key_id = 0;
|
||||
create_attr.btf_value_id = 0;
|
||||
|
||||
if (obj->btf && !bpf_map_find_btf_info(map, obj->btf)) {
|
||||
create_attr.btf_fd = btf__fd(obj->btf);
|
||||
create_attr.btf_key_id = map->btf_key_id;
|
||||
create_attr.btf_value_id = map->btf_value_id;
|
||||
}
|
||||
|
||||
*pfd = bpf_create_map_xattr(&create_attr);
|
||||
if (*pfd < 0 && create_attr.btf_key_id) {
|
||||
pr_warning("Error in bpf_create_map_xattr(%s):%s(%d). Retrying without BTF.\n",
|
||||
map->name, strerror(errno), errno);
|
||||
create_attr.btf_fd = 0;
|
||||
create_attr.btf_key_id = 0;
|
||||
create_attr.btf_value_id = 0;
|
||||
map->btf_key_id = 0;
|
||||
map->btf_value_id = 0;
|
||||
*pfd = bpf_create_map_xattr(&create_attr);
|
||||
}
|
||||
|
||||
*pfd = bpf_create_map_name(def->type,
|
||||
obj->maps[i].name,
|
||||
def->key_size,
|
||||
def->value_size,
|
||||
def->max_entries,
|
||||
def->map_flags);
|
||||
if (*pfd < 0) {
|
||||
size_t j;
|
||||
int err = *pfd;
|
||||
|
||||
err = *pfd;
|
||||
pr_warning("failed to create map (name: '%s'): %s\n",
|
||||
obj->maps[i].name,
|
||||
map->name,
|
||||
strerror(errno));
|
||||
for (j = 0; j < i; j++)
|
||||
zclose(obj->maps[j].fd);
|
||||
return err;
|
||||
}
|
||||
pr_debug("create map %s: fd=%d\n", obj->maps[i].name, *pfd);
|
||||
pr_debug("create map %s: fd=%d\n", map->name, *pfd);
|
||||
}
|
||||
|
||||
return 0;
|
||||
@@ -1641,6 +1747,7 @@ void bpf_object__close(struct bpf_object *obj)
|
||||
|
||||
bpf_object__elf_finish(obj);
|
||||
bpf_object__unload(obj);
|
||||
btf__free(obj->btf);
|
||||
|
||||
for (i = 0; i < obj->nr_maps; i++) {
|
||||
zfree(&obj->maps[i].name);
|
||||
@@ -1692,6 +1799,11 @@ unsigned int bpf_object__kversion(struct bpf_object *obj)
|
||||
return obj ? obj->kern_version : 0;
|
||||
}
|
||||
|
||||
int bpf_object__btf_fd(const struct bpf_object *obj)
|
||||
{
|
||||
return obj->btf ? btf__fd(obj->btf) : -1;
|
||||
}
|
||||
|
||||
int bpf_object__set_priv(struct bpf_object *obj, void *priv,
|
||||
bpf_object_clear_priv_t clear_priv)
|
||||
{
|
||||
@@ -1937,6 +2049,16 @@ const char *bpf_map__name(struct bpf_map *map)
|
||||
return map ? map->name : NULL;
|
||||
}
|
||||
|
||||
uint32_t bpf_map__btf_key_id(const struct bpf_map *map)
|
||||
{
|
||||
return map ? map->btf_key_id : 0;
|
||||
}
|
||||
|
||||
uint32_t bpf_map__btf_value_id(const struct bpf_map *map)
|
||||
{
|
||||
return map ? map->btf_value_id : 0;
|
||||
}
|
||||
|
||||
int bpf_map__set_priv(struct bpf_map *map, void *priv,
|
||||
bpf_map_clear_priv_t clear_priv)
|
||||
{
|
||||
|
||||
@@ -78,6 +78,7 @@ int bpf_object__load(struct bpf_object *obj);
|
||||
int bpf_object__unload(struct bpf_object *obj);
|
||||
const char *bpf_object__name(struct bpf_object *obj);
|
||||
unsigned int bpf_object__kversion(struct bpf_object *obj);
|
||||
int bpf_object__btf_fd(const struct bpf_object *obj);
|
||||
|
||||
struct bpf_object *bpf_object__next(struct bpf_object *prev);
|
||||
#define bpf_object__for_each_safe(pos, tmp) \
|
||||
@@ -241,6 +242,8 @@ bpf_map__next(struct bpf_map *map, struct bpf_object *obj);
|
||||
int bpf_map__fd(struct bpf_map *map);
|
||||
const struct bpf_map_def *bpf_map__def(struct bpf_map *map);
|
||||
const char *bpf_map__name(struct bpf_map *map);
|
||||
uint32_t bpf_map__btf_key_id(const struct bpf_map *map);
|
||||
uint32_t bpf_map__btf_value_id(const struct bpf_map *map);
|
||||
|
||||
typedef void (*bpf_map_clear_priv_t)(struct bpf_map *, void *);
|
||||
int bpf_map__set_priv(struct bpf_map *map, void *priv,
|
||||
|
||||
@@ -24,14 +24,15 @@ urandom_read: urandom_read.c
|
||||
# Order correspond to 'make run_tests' order
|
||||
TEST_GEN_PROGS = test_verifier test_tag test_maps test_lru_map test_lpm_map test_progs \
|
||||
test_align test_verifier_log test_dev_cgroup test_tcpbpf_user \
|
||||
test_sock test_sock_addr
|
||||
test_sock test_sock_addr test_btf
|
||||
|
||||
TEST_GEN_FILES = test_pkt_access.o test_xdp.o test_l4lb.o test_tcp_estats.o test_obj_id.o \
|
||||
test_pkt_md_access.o test_xdp_redirect.o test_xdp_meta.o sockmap_parse_prog.o \
|
||||
sockmap_verdict_prog.o dev_cgroup.o sample_ret0.o test_tracepoint.o \
|
||||
test_l4lb_noinline.o test_xdp_noinline.o test_stacktrace_map.o \
|
||||
sample_map_ret0.o test_tcpbpf_kern.o test_stacktrace_build_id.o \
|
||||
sockmap_tcp_msg_prog.o connect4_prog.o connect6_prog.o test_adjust_tail.o
|
||||
sockmap_tcp_msg_prog.o connect4_prog.o connect6_prog.o test_adjust_tail.o \
|
||||
test_btf_haskv.o test_btf_nokv.o
|
||||
|
||||
# Order correspond to 'make run_tests' order
|
||||
TEST_PROGS := test_kmod.sh \
|
||||
@@ -66,6 +67,8 @@ $(BPFOBJ): force
|
||||
|
||||
CLANG ?= clang
|
||||
LLC ?= llc
|
||||
LLVM_OBJCOPY ?= llvm-objcopy
|
||||
BTF_PAHOLE ?= pahole
|
||||
|
||||
PROBE := $(shell $(LLC) -march=bpf -mcpu=probe -filetype=null /dev/null 2>&1)
|
||||
|
||||
@@ -83,9 +86,26 @@ CLANG_FLAGS = -I. -I./include/uapi -I../../../include/uapi \
|
||||
$(OUTPUT)/test_l4lb_noinline.o: CLANG_FLAGS += -fno-inline
|
||||
$(OUTPUT)/test_xdp_noinline.o: CLANG_FLAGS += -fno-inline
|
||||
|
||||
BTF_LLC_PROBE := $(shell $(LLC) -march=bpf -mattr=help |& grep dwarfris)
|
||||
BTF_PAHOLE_PROBE := $(shell $(BTF_PAHOLE) --help |& grep BTF)
|
||||
BTF_OBJCOPY_PROBE := $(shell $(LLVM_OBJCOPY) --version |& grep LLVM)
|
||||
|
||||
ifneq ($(BTF_LLC_PROBE),)
|
||||
ifneq ($(BTF_PAHOLE_PROBE),)
|
||||
ifneq ($(BTF_OBJCOPY_PROBE),)
|
||||
CLANG_FLAGS += -g
|
||||
LLC_FLAGS += -mattr=dwarfris
|
||||
DWARF2BTF = y
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
||||
$(OUTPUT)/%.o: %.c
|
||||
$(CLANG) $(CLANG_FLAGS) \
|
||||
-O2 -target bpf -emit-llvm -c $< -o - | \
|
||||
$(LLC) -march=bpf -mcpu=$(CPU) -filetype=obj -o $@
|
||||
$(LLC) -march=bpf -mcpu=$(CPU) $(LLC_FLAGS) -filetype=obj -o $@
|
||||
ifeq ($(DWARF2BTF),y)
|
||||
$(BTF_PAHOLE) -J $@
|
||||
endif
|
||||
|
||||
EXTRA_CLEAN := $(TEST_CUSTOM_PROGS)
|
||||
File diff suppressed because it is too large.
Load diff
Loaded 20 of 22 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user