bpf: BPF CO-RE support

This commit introduces support for BPF Compile Once - Run
Everywhere (CO-RE) in GCC.

gcc/ChangeLog:

	* config/bpf/bpf.c: Adjust includes.
	(bpf_handle_preserve_access_index_attribute): New function.
	(bpf_attribute_table): Use it here.
	(bpf_builtins): Add BPF_BUILTIN_PRESERVE_ACCESS_INDEX.
	(bpf_option_override): Handle "-mco-re" option.
	(bpf_asm_init_sections): New.
	(TARGET_ASM_INIT_SECTIONS): Redefine.
	(bpf_file_end): New.
	(TARGET_ASM_FILE_END): Redefine.
	(bpf_init_builtins): Add "__builtin_preserve_access_index".
	(bpf_core_compute, bpf_core_get_index): New.
	(is_attr_preserve_access): New.
	(bpf_expand_builtin): Handle new builtins.
	(bpf_core_newdecl, bpf_core_is_maybe_aggregate_access): New.
	(bpf_core_walk): New.
	(bpf_resolve_overloaded_builtin): New.
	(TARGET_RESOLVE_OVERLOADED_BUILTIN): Redefine.
	(handle_attr): New.
	(pass_bpf_core_attr): New RTL pass.
	* config/bpf/bpf-passes.def: New file.
	* config/bpf/bpf-protos.h (make_pass_bpf_core_attr): New.
	* config/bpf/coreout.c: New file.
	* config/bpf/coreout.h: Likewise.
	* config/bpf/t-bpf (TM_H): Add $(srcdir)/config/bpf/coreout.h.
	(coreout.o): New rule.
	(PASSES_EXTRA): Add $(srcdir)/config/bpf/bpf-passes.def.
	* config.gcc (bpf): Add coreout.h to extra_headers.
	Add coreout.o to extra_objs.
	Add $(srcdir)/config/bpf/coreout.c to target_gtfiles.
This commit is contained in:
David Faust 2021-08-03 10:27:44 -07:00
parent 0a2bd52f1a
commit 8bdabb3754
7 changed files with 1094 additions and 0 deletions

View file

@ -1525,6 +1525,9 @@ bpf-*-*)
use_collect2=no
extra_headers="bpf-helpers.h"
use_gcc_stdint=provide
extra_headers="coreout.h"
extra_objs="coreout.o"
target_gtfiles="$target_gtfiles \$(srcdir)/config/bpf/coreout.c"
;;
cr16-*-elf)
tm_file="elfos.h ${tm_file} newlib-stdint.h"

View file

@ -0,0 +1,20 @@
/* Declaration of target-specific passes for eBPF.
Copyright (C) 2021 Free Software Foundation, Inc.
This file is part of GCC.
GCC is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.
GCC is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with GCC; see the file COPYING3. If not see
<http://www.gnu.org/licenses/>. */
INSERT_PASS_AFTER (pass_df_initialize_opt, 1, pass_bpf_core_attr);

View file

@ -30,4 +30,6 @@ extern void bpf_print_operand_address (FILE *, rtx);
extern void bpf_expand_prologue (void);
extern void bpf_expand_epilogue (void);
rtl_opt_pass * make_pass_bpf_core_attr (gcc::context *);
#endif /* ! GCC_BPF_PROTOS_H */

View file

@ -56,6 +56,24 @@ along with GCC; see the file COPYING3. If not see
#include "langhooks.h"
#include "flags.h"
#include "cfg.h" /* needed for struct control_flow_graph used in BB macros */
#include "gimple.h"
#include "gimple-iterator.h"
#include "gimple-walk.h"
#include "tree-pass.h"
#include "tree-iterator.h"
#include "context.h"
#include "pass_manager.h"
#include "gimplify.h"
#include "gimplify-me.h"
#include "ctfc.h"
#include "btf.h"
#include "coreout.h"
/* Per-function machine data. */
struct GTY(()) machine_function
{
@ -105,6 +123,27 @@ bpf_handle_fndecl_attribute (tree *node, tree name,
return NULL_TREE;
}
/* Handle preserve_access_index attribute, which can be applied to structs,
unions and classes. Actually adding the attribute to the TYPE_DECL is
taken care of for us, so just warn for types that aren't supported. */
static tree
bpf_handle_preserve_access_index_attribute (tree *node, tree name,
tree args,
int flags,
bool *no_add_attrs)
{
if (TREE_CODE (*node) != RECORD_TYPE && TREE_CODE (*node) != UNION_TYPE)
{
warning (OPT_Wattributes,
"%qE attribute only applies to structure, union and class types",
name);
*no_add_attrs = true;
}
return NULL_TREE;
}
/* Target-specific attributes. */
static const struct attribute_spec bpf_attribute_table[] =
@ -117,6 +156,11 @@ static const struct attribute_spec bpf_attribute_table[] =
{ "kernel_helper", 1, 1, true, false, false, false,
bpf_handle_fndecl_attribute, NULL },
/* CO-RE support: attribute to mark that all accesses to the declared
struct/union/array should be recorded. */
{ "preserve_access_index", 0, -1, false, true, false, true,
bpf_handle_preserve_access_index_attribute, NULL },
/* The last attribute spec is set to be NULL. */
{ NULL, 0, 0, false, false, false, false, NULL, NULL }
};
@ -137,11 +181,18 @@ enum bpf_builtins
BPF_BUILTIN_LOAD_BYTE,
BPF_BUILTIN_LOAD_HALF,
BPF_BUILTIN_LOAD_WORD,
/* Compile Once - Run Everywhere (CO-RE) support. */
BPF_BUILTIN_PRESERVE_ACCESS_INDEX,
BPF_BUILTIN_MAX,
};
static GTY (()) tree bpf_builtins[(int) BPF_BUILTIN_MAX];
void bpf_register_coreattr_pass (void);
/* Initialize the per-function machine status. */
static struct machine_function *
@ -183,11 +234,57 @@ bpf_option_override (void)
if (flag_lto && TARGET_BPF_CORE)
sorry ("BPF CO-RE does not support LTO");
/* -gbtf implies -mcore when using the BPF backend, unless -mno-co-re
is specified. */
if (btf_debuginfo_p () && !(target_flags_explicit & MASK_BPF_CORE))
{
target_flags |= MASK_BPF_CORE;
write_symbols |= BTF_WITH_CORE_DEBUG;
}
}
#undef TARGET_OPTION_OVERRIDE
#define TARGET_OPTION_OVERRIDE bpf_option_override
/* Return FALSE iff -mcore has been specified. */
static bool
ctfc_debuginfo_early_finish_p (void)
{
if (TARGET_BPF_CORE)
return false;
else
return true;
}
#undef TARGET_CTFC_DEBUGINFO_EARLY_FINISH_P
#define TARGET_CTFC_DEBUGINFO_EARLY_FINISH_P ctfc_debuginfo_early_finish_p
/* Implement TARGET_ASM_INIT_SECTIONS. */
static void
bpf_asm_init_sections (void)
{
if (TARGET_BPF_CORE)
btf_ext_init ();
}
#undef TARGET_ASM_INIT_SECTIONS
#define TARGET_ASM_INIT_SECTIONS bpf_asm_init_sections
/* Implement TARGET_ASM_FILE_END. */
static void
bpf_file_end (void)
{
if (TARGET_BPF_CORE)
btf_ext_output ();
}
#undef TARGET_ASM_FILE_END
#define TARGET_ASM_FILE_END bpf_file_end
/* Define target-specific CPP macros. This function in used in the
definition of TARGET_CPU_CPP_BUILTINS in bpf.h */
@ -837,11 +934,18 @@ bpf_init_builtins (void)
build_function_type_list (ullt, ullt, 0));
def_builtin ("__builtin_bpf_load_word", BPF_BUILTIN_LOAD_WORD,
build_function_type_list (ullt, ullt, 0));
def_builtin ("__builtin_preserve_access_index",
BPF_BUILTIN_PRESERVE_ACCESS_INDEX,
build_function_type_list (ptr_type_node, ptr_type_node, 0));
}
#undef TARGET_INIT_BUILTINS
#define TARGET_INIT_BUILTINS bpf_init_builtins
static tree bpf_core_compute (tree, vec<unsigned int> *);
static int bpf_core_get_index (const tree);
static bool is_attr_preserve_access (tree);
/* Expand a call to a BPF-specific built-in function that was set up
with bpf_init_builtins. */
@ -892,7 +996,75 @@ bpf_expand_builtin (tree exp, rtx target ATTRIBUTE_UNUSED,
/* The result of the load is in R0. */
return gen_rtx_REG (ops[0].mode, BPF_R0);
}
else if (code == -1)
{
/* A resolved overloaded builtin, e.g. __bpf_preserve_access_index_si */
tree arg = CALL_EXPR_ARG (exp, 0);
if (arg == NULL_TREE)
return NULL_RTX;
auto_vec<unsigned int, 16> accessors;
tree container;
if (TREE_CODE (arg) == SSA_NAME)
{
gimple *def_stmt = SSA_NAME_DEF_STMT (arg);
if (is_gimple_assign (def_stmt))
arg = gimple_assign_rhs1 (def_stmt);
else
return expand_normal (arg);
}
/* Avoid double-recording information if the argument is an access to
a struct/union marked __attribute__((preserve_access_index)). This
Will be handled by the attribute handling pass. */
if (is_attr_preserve_access (arg))
return expand_normal (arg);
container = bpf_core_compute (arg, &accessors);
/* Any valid use of the builtin must have at least one access. Otherwise,
there is nothing to record and nothing to do. This is primarily a
guard against optimizations leading to unexpected expressions in the
argument of the builtin. For example, if the builtin is used to read
a field of a structure which can be statically determined to hold a
constant value, the argument to the builtin will be optimized to that
constant. This is OK, and means the builtin call is superfluous.
e.g.
struct S foo;
foo.a = 5;
int x = __preserve_access_index (foo.a);
... do stuff with x
'foo.a' in the builtin argument will be optimized to '5' with -01+.
This sequence does not warrant recording a CO-RE relocation. */
if (accessors.length () < 1)
return expand_normal (arg);
accessors.reverse ();
container = TREE_TYPE (container);
rtx_code_label *label = gen_label_rtx ();
LABEL_PRESERVE_P (label) = 1;
emit_label (label);
/* Determine what output section this relocation will apply to.
If this function is associated with a section, use that. Otherwise,
fall back on '.text'. */
const char * section_name;
if (current_function_decl && DECL_SECTION_NAME (current_function_decl))
section_name = DECL_SECTION_NAME (current_function_decl);
else
section_name = ".text";
/* Add the CO-RE relocation information to the BTF container. */
bpf_core_reloc_add (container, section_name, &accessors, label);
return expand_normal (arg);
}
gcc_unreachable ();
}
@ -946,6 +1118,425 @@ bpf_debug_unwind_info ()
#undef TARGET_ASM_ALIGNED_DI_OP
#define TARGET_ASM_ALIGNED_DI_OP "\t.dword\t"
/* BPF Compile Once - Run Everywhere (CO-RE) support routines.
BPF CO-RE is supported in two forms:
- A target builtin, __builtin_preserve_access_index
This builtin accepts a single argument. Any access to an aggregate data
structure (struct, union or array) within the argument will be recorded by
the CO-RE machinery, resulting in a relocation record being placed in the
.BTF.ext section of the output.
It is implemented in bpf_resolve_overloaded_builtin () and
bpf_expand_builtin (), using the supporting routines below.
- An attribute, __attribute__((preserve_access_index))
This attribute can be applied to struct and union types. Any access to a
type with this attribute will be recorded by the CO-RE machinery.
The pass pass_bpf_core_attr, below, implements support for
this attribute. */
/* Traverse the subtree under NODE, which is expected to be some form of
aggregate access the CO-RE machinery cares about (like a read of a member of
a struct or union), collecting access indices for the components and storing
them in the vector referenced by ACCESSORS.
Return the ultimate (top-level) container of the aggregate access. In general,
this will be a VAR_DECL or some kind of REF.
Note that the accessors are computed *in reverse order* of how the BPF
CO-RE machinery defines them. The vector needs to be reversed (or simply
output in reverse order) for the .BTF.ext relocation information. */
static tree
bpf_core_compute (tree node, vec<unsigned int> *accessors)
{
if (TREE_CODE (node) == ADDR_EXPR)
node = TREE_OPERAND (node, 0);
else if (TREE_CODE (node) == INDIRECT_REF
|| TREE_CODE (node) == POINTER_PLUS_EXPR)
{
accessors->safe_push (0);
return TREE_OPERAND (node, 0);
}
while (1)
{
switch (TREE_CODE (node))
{
case COMPONENT_REF:
accessors->safe_push (bpf_core_get_index (TREE_OPERAND (node, 1)));
break;
case ARRAY_REF:
case ARRAY_RANGE_REF:
accessors->safe_push (bpf_core_get_index (node));
break;
case MEM_REF:
accessors->safe_push (bpf_core_get_index (node));
if (TREE_CODE (TREE_OPERAND (node, 0)) == ADDR_EXPR)
node = TREE_OPERAND (TREE_OPERAND (node, 0), 0);
goto done;
default:
goto done;
}
node = TREE_OPERAND (node, 0);
}
done:
return node;
}
/* Compute the index of the NODE in its immediate container.
NODE should be a FIELD_DECL (i.e. of struct or union), or an ARRAY_REF. */
static int
bpf_core_get_index (const tree node)
{
enum tree_code code = TREE_CODE (node);
if (code == FIELD_DECL)
{
/* Lookup the index from the BTF information. Some struct/union members
may not be emitted in BTF; only the BTF container has enough
information to compute the correct index. */
int idx = bpf_core_get_sou_member_index (ctf_get_tu_ctfc (), node);
if (idx >= 0)
return idx;
}
else if (code == ARRAY_REF || code == ARRAY_RANGE_REF || code == MEM_REF)
{
/* For array accesses, the index is operand 1. */
tree index = TREE_OPERAND (node, 1);
/* If the indexing operand is a constant, extracting is trivial. */
if (TREE_CODE (index) == INTEGER_CST && tree_fits_shwi_p (index))
return tree_to_shwi (index);
}
return -1;
}
/* Synthesize a new builtin function declaration at LOC with signature TYPE.
Used by bpf_resolve_overloaded_builtin to resolve calls to
__builtin_preserve_access_index. */
static tree
bpf_core_newdecl (location_t loc, tree type)
{
tree rettype = build_function_type_list (type, type, NULL);
tree newdecl = NULL_TREE;
char name[80];
int len = snprintf (name, sizeof (name), "%s", "__builtin_pai_");
static unsigned long cnt = 0;
len = snprintf (name + len, sizeof (name) - len, "%lu", cnt++);
return add_builtin_function_ext_scope (name, rettype, -1, BUILT_IN_MD, NULL,
NULL_TREE);
}
/* Return whether EXPR could access some aggregate data structure that
BPF CO-RE support needs to know about. */
static int
bpf_core_is_maybe_aggregate_access (tree expr)
{
enum tree_code code = TREE_CODE (expr);
if (code == COMPONENT_REF || code == ARRAY_REF)
return 1;
if (code == ADDR_EXPR)
return bpf_core_is_maybe_aggregate_access (TREE_OPERAND (expr, 0));
return 0;
}
/* Callback function used with walk_tree from bpf_resolve_overloaded_builtin. */
static tree
bpf_core_walk (tree *tp, int *walk_subtrees, void *data)
{
location_t loc = *((location_t *) data);
/* If this is a type, don't do anything. */
if (TYPE_P (*tp))
{
*walk_subtrees = 0;
return NULL_TREE;
}
if (bpf_core_is_maybe_aggregate_access (*tp))
{
tree newdecl = bpf_core_newdecl (loc, TREE_TYPE (*tp));
tree newcall = build_call_expr_loc (loc, newdecl, 1, *tp);
*tp = newcall;
*walk_subtrees = 0;
}
return NULL_TREE;
}
/* Implement TARGET_RESOLVE_OVERLOADED_BUILTIN (see gccint manual section
Target Macros::Misc.).
We use this for the __builtin_preserve_access_index builtin for CO-RE
support.
FNDECL is the declaration of the builtin, and ARGLIST is the list of
arguments passed to it, and is really a vec<tree,_> *.
In this case, the 'operation' implemented by the builtin is a no-op;
the builtin is just a marker. So, the result is simply the argument. */
static tree
bpf_resolve_overloaded_builtin (location_t loc, tree fndecl, void *arglist)
{
if (DECL_MD_FUNCTION_CODE (fndecl) != BPF_BUILTIN_PRESERVE_ACCESS_INDEX)
return NULL_TREE;
/* We only expect one argument, but it may be an arbitrarily-complicated
statement-expression. */
vec<tree, va_gc> *params = static_cast<vec<tree, va_gc> *> (arglist);
unsigned n_params = params ? params->length() : 0;
if (n_params != 1)
{
error_at (loc, "expected exactly 1 argument");
return NULL_TREE;
}
tree param = (*params)[0];
/* If not generating BPF_CORE information, the builtin does nothing. */
if (!TARGET_BPF_CORE)
return param;
/* Do remove_c_maybe_const_expr for the arg.
TODO: WHY do we have to do this here? Why doesn't c-typeck take care
of it before or after this hook? */
if (TREE_CODE (param) == C_MAYBE_CONST_EXPR)
param = C_MAYBE_CONST_EXPR_EXPR (param);
/* Construct a new function declaration with the correct type, and return
a call to it.
Calls with statement-expressions, for example:
_(({ foo->a = 1; foo->u[2].b = 2; }))
require special handling.
We rearrange this into a new block scope in which each statement
becomes a unique builtin call:
{
_ ({ foo->a = 1;});
_ ({ foo->u[2].b = 2;});
}
This ensures that all the relevant information remains within the
expression trees the builtin finally gets. */
walk_tree (&param, bpf_core_walk, (void *) &loc, NULL);
return param;
}
#undef TARGET_RESOLVE_OVERLOADED_BUILTIN
#define TARGET_RESOLVE_OVERLOADED_BUILTIN bpf_resolve_overloaded_builtin
/* Handling for __attribute__((preserve_access_index)) for BPF CO-RE support.
This attribute marks a structure/union/array type as "preseve", so that
every access to that type should be recorded and replayed by the BPF loader;
this is just the same functionality as __builtin_preserve_access_index,
but in the form of an attribute for an entire aggregate type.
Note also that nested structs behave as though they all have the attribute.
For example:
struct X { int a; };
struct Y { struct X bar} __attribute__((preserve_access_index));
struct Y foo;
foo.bar.a;
will record access all the way to 'a', even though struct X does not have
the preserve_access_index attribute.
This is to follow LLVM behavior.
This pass finds all accesses to objects of types marked with the attribute,
and wraps them in the same "low-level" builtins used by the builtin version.
All logic afterwards is therefore identical to the builtin version of
preserve_access_index. */
/* True iff tree T accesses any member of a struct/union/class which is marked
with the PRESERVE_ACCESS_INDEX attribute. */
static bool
is_attr_preserve_access (tree t)
{
if (t == NULL_TREE)
return false;
poly_int64 bitsize, bitpos;
tree var_off;
machine_mode mode;
int sign, reverse, vol;
tree base = get_inner_reference (t, &bitsize, &bitpos, &var_off, &mode,
&sign, &reverse, &vol);
if (TREE_CODE (base) == MEM_REF)
{
return lookup_attribute ("preserve_access_index",
TYPE_ATTRIBUTES (TREE_TYPE (base)));
}
if (TREE_CODE (t) == COMPONENT_REF)
{
/* preserve_access_index propegates into nested structures,
so check whether this is a component of another component
which in turn is part of such a struct. */
const tree op = TREE_OPERAND (t, 0);
if (TREE_CODE (op) == COMPONENT_REF)
return is_attr_preserve_access (op);
const tree container = DECL_CONTEXT (TREE_OPERAND (t, 1));
return lookup_attribute ("preserve_access_index",
TYPE_ATTRIBUTES (container));
}
else if (TREE_CODE (t) == ADDR_EXPR)
return is_attr_preserve_access (TREE_OPERAND (t, 0));
return false;
}
/* The body of pass_bpf_core_attr. Scan RTL for accesses to structs/unions
marked with __attribute__((preserve_access_index)) and generate a CO-RE
relocation for any such access. */
static void
handle_attr_preserve (function *fn)
{
basic_block bb;
rtx_insn *insn;
rtx_code_label *label;
FOR_EACH_BB_FN (bb, fn)
{
FOR_BB_INSNS (bb, insn)
{
if (!NONJUMP_INSN_P (insn))
continue;
rtx pat = PATTERN (insn);
if (GET_CODE (pat) != SET)
continue;
start_sequence();
for (int i = 0; i < 2; i++)
{
rtx mem = XEXP (pat, i);
if (MEM_P (mem))
{
tree expr = MEM_EXPR (mem);
if (!expr)
continue;
if (TREE_CODE (expr) == MEM_REF
&& TREE_CODE (TREE_OPERAND (expr, 0)) == SSA_NAME)
{
gimple *def_stmt = SSA_NAME_DEF_STMT (TREE_OPERAND (expr, 0));
if (is_gimple_assign (def_stmt))
expr = gimple_assign_rhs1 (def_stmt);
}
if (is_attr_preserve_access (expr))
{
auto_vec<unsigned int, 16> accessors;
tree container = bpf_core_compute (expr, &accessors);
if (accessors.length () < 1)
continue;
accessors.reverse ();
container = TREE_TYPE (container);
const char * section_name;
if (DECL_SECTION_NAME (fn->decl))
section_name = DECL_SECTION_NAME (fn->decl);
else
section_name = ".text";
label = gen_label_rtx ();
LABEL_PRESERVE_P (label) = 1;
emit_label (label);
/* Add the CO-RE relocation information to the BTF container. */
bpf_core_reloc_add (container, section_name, &accessors, label);
}
}
}
rtx_insn *seq = get_insns ();
end_sequence ();
emit_insn_before (seq, insn);
}
}
}
/* This pass finds accesses to structures marked with the BPF target attribute
__attribute__((preserve_access_index)). For every such access, a CO-RE
relocation record is generated, to be output in the .BTF.ext section. */
namespace {
const pass_data pass_data_bpf_core_attr =
{
RTL_PASS, /* type */
"bpf_core_attr", /* name */
OPTGROUP_NONE, /* optinfo_flags */
TV_NONE, /* tv_id */
0, /* properties_required */
0, /* properties_provided */
0, /* properties_destroyed */
0, /* todo_flags_start */
0, /* todo_flags_finish */
};
class pass_bpf_core_attr : public rtl_opt_pass
{
public:
pass_bpf_core_attr (gcc::context *ctxt)
: rtl_opt_pass (pass_data_bpf_core_attr, ctxt)
{}
virtual bool gate (function *) { return TARGET_BPF_CORE; }
virtual unsigned int execute (function *);
};
unsigned int
pass_bpf_core_attr::execute (function *fn)
{
handle_attr_preserve (fn);
return 0;
}
} /* Anonymous namespace. */
rtl_opt_pass *
make_pass_bpf_core_attr (gcc::context *ctxt)
{
return new pass_bpf_core_attr (ctxt);
}
/* Finally, build the GCC target. */
struct gcc_target targetm = TARGET_INITIALIZER;

356
gcc/config/bpf/coreout.c Normal file
View file

@ -0,0 +1,356 @@
/* BPF Compile Once - Run Everywhere (CO-RE) support.
Copyright (C) 2021 Free Software Foundation, Inc.
This file is part of GCC.
GCC is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.
GCC is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with GCC; see the file COPYING3. If not see
<http://www.gnu.org/licenses/>. */
#define IN_TARGET_CODE 1
#include "config.h"
#include "system.h"
#include "coretypes.h"
#include "target.h"
#include "memmodel.h"
#include "tm_p.h"
#include "output.h"
#include "dwarf2asm.h"
#include "ctfc.h"
#include "btf.h"
#include "rtl.h"
#include "coreout.h"
/* This file contains data structures and routines for construction and output
of BPF Compile Once - Run Everywhere (BPF CO-RE) information.
eBPF programs written in C usually include Linux kernel headers, so that
they may interact with kernel data structures in a useful way. This
intrudces two major portability issues:
1. Kernel data structures regularly change, with fields added, moved or
deleted between versions. An eBPF program cannot in general be expected
to run on any systems which does not share an identical kernel version to
the system on which it was compiled.
2. Included kernel headers (and used data structures) may be internal, not
exposed in an userspace API, and therefore target-specific. An eBPF
program compiled on an x86_64 machine will include x86_64 kernel headers.
The resulting program may not run well (or at all) in machines of
another architecture.
BPF CO-RE is designed to solve the first issue by leveraging the BPF loader
to adjust references to kernel data structures made by the program as-needed
according to versions of structures actually present on the host kernel.
To achieve this, additional information is placed in a ".BTF.ext" section.
This information tells the loader which references will require adjusting,
and how to perform each necessary adjustment.
For any access to a data structure which may require load-time adjustment,
the following information is recorded (making up a CO-RE relocation record):
- The BTF type ID of the outermost structure which is accessed.
- An access string encoding the accessed member via a series of member and
array indexes. These indexes are used to look up detailed BTF information
about the member.
- The offset of the appropriate instruction to patch in the BPF program.
- An integer specifying what kind of relocation to perform.
A CO-RE-capable BPF loader reads this information together with the BTF
information of the program, compares it against BTF information of the host
kernel, and determines the appropriate way to patch the specified
instruction.
Once all CO-RE relocations are resolved, the program is loaded and verified
as usual. The process can be summarized with the following diagram:
+------------+
| C compiler |
+-----+------+
| BPF + BTF + CO-RE relocations
v
+------------+
+--->| BPF loader |
| +-----+------+
| | BPF (adapted)
BTF | v
| +------------+
+----+ Kernel |
+------------+
Note that a single ELF object may contain multiple eBPF programs. As a
result, a single .BTF.ext section can contain CO-RE relocations for multiple
programs in distinct sections. */
/* Internal representation of a BPF CO-RE relocation record. */
typedef struct GTY (()) bpf_core_reloc {
unsigned int bpfcr_type; /* BTF type ID of container. */
unsigned int bpfcr_astr_off; /* Offset of access string in .BTF
string table. */
rtx_code_label * bpfcr_insn_label; /* RTX label attached to instruction
to patch. */
enum btf_core_reloc_kind bpfcr_kind; /* Kind of relocation to perform. */
} bpf_core_reloc_t;
typedef bpf_core_reloc_t * bpf_core_reloc_ref;
/* Internal representation of a CO-RE relocation (sub)section of the
.BTF.ext information. One such section is generated for each ELF section
in the output object having relocations that a BPF loader must resolve. */
typedef struct GTY (()) bpf_core_section {
/* Name of ELF section to which these CO-RE relocations apply. */
const char * name;
/* Offset of section name in .BTF string table. */
uint32_t name_offset;
/* Relocations in the section. */
vec <bpf_core_reloc_ref, va_gc> * GTY (()) relocs;
} bpf_core_section_t;
typedef bpf_core_section_t * bpf_core_section_ref;
/* BTF.ext debug info section. */
static GTY (()) section * btf_ext_info_section;
static int btf_ext_label_num;
#ifndef BTF_EXT_INFO_SECTION_NAME
#define BTF_EXT_INFO_SECTION_NAME ".BTF.ext"
#endif
#define BTF_EXT_INFO_SECTION_FLAGS (SECTION_DEBUG)
#define MAX_BTF_EXT_LABEL_BYTES 40
static char btf_ext_info_section_label[MAX_BTF_EXT_LABEL_BYTES];
#ifndef BTF_EXT_INFO_SECTION_LABEL
#define BTF_EXT_INFO_SECTION_LABEL "Lbtfext"
#endif
static GTY (()) vec<bpf_core_section_ref, va_gc> *bpf_core_sections;
/* Create a new BPF CO-RE relocation record, and add it to the appropriate
CO-RE section. */
void
bpf_core_reloc_add (const tree type, const char * section_name,
vec<unsigned int> *accessors, rtx_code_label *label)
{
char buf[40];
unsigned int i, n = 0;
/* A valid CO-RE access must have at least one accessor. */
if (accessors->length () < 1)
return;
for (i = 0; i < accessors->length () - 1; i++)
n += snprintf (buf + n, sizeof (buf) - n, "%u:", (*accessors)[i]);
snprintf (buf + n, sizeof (buf) - n, "%u", (*accessors)[i]);
bpf_core_reloc_ref bpfcr = ggc_cleared_alloc<bpf_core_reloc_t> ();
ctf_container_ref ctfc = ctf_get_tu_ctfc ();
/* Buffer the access string in the auxiliary strtab. Since the two string
tables are concatenated, add the length of the first to the offset. */
size_t strtab_len = ctfc_get_strtab_len (ctfc, CTF_STRTAB);
ctf_add_string (ctfc, buf, &(bpfcr->bpfcr_astr_off), CTF_AUX_STRTAB);
bpfcr->bpfcr_astr_off += strtab_len;
bpfcr->bpfcr_type = get_btf_id (ctf_lookup_tree_type (ctfc, type));
bpfcr->bpfcr_insn_label = label;
bpfcr->bpfcr_kind = BPF_RELO_FIELD_BYTE_OFFSET;
/* Add the CO-RE reloc to the appropriate section. */
bpf_core_section_ref sec;
FOR_EACH_VEC_ELT (*bpf_core_sections, i, sec)
if (strcmp (sec->name, section_name) == 0)
{
vec_safe_push (sec->relocs, bpfcr);
return;
}
/* If the CO-RE section does not yet exist, create it. */
sec = ggc_cleared_alloc<bpf_core_section_t> ();
ctf_add_string (ctfc, section_name, &sec->name_offset, CTF_AUX_STRTAB);
sec->name_offset += strtab_len;
if (strcmp (section_name, ""))
ctfc->ctfc_aux_strlen += strlen (section_name) + 1;
sec->name = section_name;
vec_alloc (sec->relocs, 1);
vec_safe_push (sec->relocs, bpfcr);
vec_safe_push (bpf_core_sections, sec);
}
/* Return the 0-based index of the field NODE in its containing struct or union
type. */
int
bpf_core_get_sou_member_index (ctf_container_ref ctfc, const tree node)
{
if (TREE_CODE (node) == FIELD_DECL)
{
const tree container = DECL_CONTEXT (node);
const char * name = IDENTIFIER_POINTER (DECL_NAME (node));
/* Lookup the CTF type info for the containing type. */
dw_die_ref die = lookup_type_die (container);
if (die == NULL)
return -1;
ctf_dtdef_ref dtd = ctf_dtd_lookup (ctfc, die);
if (dtd == NULL)
return -1;
unsigned int kind = CTF_V2_INFO_KIND (dtd->dtd_data.ctti_info);
if (kind != CTF_K_STRUCT && kind != CTF_K_UNION)
return -1;
int i = 0;
ctf_dmdef_t * dmd;
for (dmd = dtd->dtd_u.dtu_members;
dmd != NULL; dmd = (ctf_dmdef_t *) ctf_dmd_list_next (dmd))
{
if (get_btf_id (dmd->dmd_type) > BTF_MAX_TYPE)
continue;
if (strcmp (dmd->dmd_name, name) == 0)
return i;
i++;
}
}
return -1;
}
/* Compute and output the header of a .BTF.ext debug info section. */
static void
output_btfext_header (void)
{
switch_to_section (btf_ext_info_section);
ASM_OUTPUT_LABEL (asm_out_file, btf_ext_info_section_label);
dw2_asm_output_data (2, BTF_MAGIC, "btf_magic");
dw2_asm_output_data (1, BTF_VERSION, "btfext_version");
dw2_asm_output_data (1, 0, "btfext_flags");
dw2_asm_output_data (4, sizeof (struct btf_ext_header), "btfext_hdr_len");
uint32_t func_info_off = 0, func_info_len = 0;
uint32_t line_info_off = 0, line_info_len = 0;
uint32_t core_relo_off = 0, core_relo_len = 0;
/* Header core_relo_len is the sum total length in bytes of all CO-RE
relocation sections. */
size_t i;
bpf_core_section_ref sec;
core_relo_len += vec_safe_length (bpf_core_sections)
* sizeof (struct btf_ext_section_header);
FOR_EACH_VEC_ELT (*bpf_core_sections, i, sec)
core_relo_len +=
vec_safe_length (sec->relocs) * sizeof (struct btf_ext_reloc);
dw2_asm_output_data (4, func_info_off, "func_info_offset");
dw2_asm_output_data (4, func_info_len, "func_info_len");
dw2_asm_output_data (4, line_info_off, "line_info_offset");
dw2_asm_output_data (4, line_info_len, "line_info_len");
dw2_asm_output_data (4, core_relo_off, "core_relo_offset");
dw2_asm_output_data (4, core_relo_len, "core_relo_len");
}
/* Output a single CO-RE relocation record. */
static void
output_asm_btfext_core_reloc (bpf_core_reloc_ref bpfcr)
{
dw2_assemble_integer (4, gen_rtx_LABEL_REF (Pmode, bpfcr->bpfcr_insn_label));
fprintf (asm_out_file, "\t%s bpfcr_insn\n", ASM_COMMENT_START);
dw2_asm_output_data (4, bpfcr->bpfcr_type, "bpfcr_type");
dw2_asm_output_data (4, bpfcr->bpfcr_astr_off, "bpfcr_astr_off");
dw2_asm_output_data (4, bpfcr->bpfcr_kind, "bpfcr_kind");
}
/* Output all CO-RE relocation records for a section. */
static void
output_btfext_core_relocs (bpf_core_section_ref sec)
{
size_t i;
bpf_core_reloc_ref bpfcr;
FOR_EACH_VEC_ELT (*(sec->relocs), i, bpfcr)
output_asm_btfext_core_reloc (bpfcr);
}
/* Output all CO-RE relocation sections. */
static void
output_btfext_core_sections (void)
{
size_t i;
bpf_core_section_ref sec;
FOR_EACH_VEC_ELT (*bpf_core_sections, i, sec)
{
/* BTF Ext section info. */
dw2_asm_output_data (4, sizeof (struct btf_ext_reloc),
"btfext_secinfo_rec_size");
/* Section name offset, refers to the offset of a string with the name of
the section to which these CORE relocations refer, e.g. '.text'.
The string is buffered in the BTF strings table. */
dw2_asm_output_data (4, sec->name_offset, "btfext_secinfo_sec_name_off");
dw2_asm_output_data (4, vec_safe_length (sec->relocs),
"btfext_secinfo_num_recs");
output_btfext_core_relocs (sec);
}
}
/* Initialize sections, labels, and data structures for BTF.ext output. */
void
btf_ext_init (void)
{
btf_ext_info_section = get_section (BTF_EXT_INFO_SECTION_NAME,
BTF_EXT_INFO_SECTION_FLAGS, NULL);
ASM_GENERATE_INTERNAL_LABEL (btf_ext_info_section_label,
BTF_EXT_INFO_SECTION_LABEL,
btf_ext_label_num++);
vec_alloc (bpf_core_sections, 1);
}
/* Output the entire .BTF.ext section. */
void
btf_ext_output (void)
{
output_btfext_header ();
output_btfext_core_sections ();
bpf_core_sections = NULL;
}
#include "gt-coreout.h"

114
gcc/config/bpf/coreout.h Normal file
View file

@ -0,0 +1,114 @@
/* coreout.h - Declarations and definitions related to
BPF Compile Once - Run Everywhere (CO-RE) support.
Copyright (C) 2021 Free Software Foundation, Inc.
This file is part of GCC.
GCC is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.
GCC is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with GCC; see the file COPYING3. If not see
<http://www.gnu.org/licenses/>. */
#ifndef __COREOUT_H
#define __COREOUT_H
#include <stdint.h>
#ifdef __cplusplus
extern "C"
{
#endif
/* .BTF.ext information. */
struct btf_ext_section_header
{
uint32_t kind;
uint32_t sec_name_off;
uint32_t num_records;
};
/* A funcinfo record, in the .BTF.ext funcinfo section. */
struct btf_ext_funcinfo
{
uint32_t insn_off; /* Offset of the first instruction of the function. */
uint32_t type; /* Type ID of a BTF_KIND_FUNC type. */
};
/* A lineinfo record, in the .BTF.ext lineinfo section. */
struct btf_ext_lineinfo
{
uint32_t insn_off; /* Offset of the instruction. */
uint32_t file_name_off; /* Offset of file name in BTF string table. */
uint32_t line_off; /* Offset of source line in BTF string table. */
uint32_t line_col; /* Line number (bits 31-11) and column (11-0). */
};
enum btf_core_reloc_kind
{
BPF_RELO_FIELD_BYTE_OFFSET = 0,
BPF_RELO_FIELD_BYTE_SIZE = 1,
BPF_RELO_FIELD_EXISTS = 2,
BPF_RELO_FIELD_SIGNED = 3,
BPF_RELO_FIELD_LSHIFT_U64 = 4,
BPF_RELO_FIELD_RSHIFT_U64 = 5,
BPF_RELO_TYPE_ID_LOCAL = 6,
BPF_RELO_TYPE_ID_TARGET = 7,
BPF_RELO_TYPE_EXISTS = 8,
BPF_RELO_TYPE_SIZE = 9,
BPF_RELO_ENUMVAL_EXISTS = 10,
BPF_RELO_ENUMVAL_VALUE = 11
};
struct btf_ext_reloc
{
uint32_t insn_off; /* Offset of instruction to be patched. A
section-relative label at compile time. */
uint32_t type_id; /* Type ID of the outermost containing entity, e.g.
the containing structure. */
uint32_t access_str_off; /* Offset of CO-RE accessor string in .BTF strings
section. */
uint32_t kind; /* An enum btf_core_reloc_kind. Note that it always
takes 32 bits. */
};
struct btf_ext_header
{
uint16_t magic; /* Magic number (BTF_MAGIC). */
uint8_t version; /* Data format version (BTF_VERSION). */
uint8_t flags; /* Flags. Currently unused. */
uint32_t hdr_len; /* Length of this header in bytes. */
/* Following offsets are relative to the end of this header, in bytes.
Following lengths are in bytes. */
uint32_t func_info_off; /* Offset of funcinfo section. */
uint32_t func_info_len; /* Length of funcinfo section. */
uint32_t line_info_off; /* Offset of lineinfo section. */
uint32_t line_info_len; /* Length of lineinfo section. */
uint32_t core_relo_off; /* Offset of CO-RE relocation section. */
uint32_t core_relo_len; /* Length of CO-RE relocation section. */
};
extern void btf_ext_init (void);
extern void btf_ext_output (void);
extern void bpf_core_reloc_add (const tree, const char *, vec<unsigned int> *,
rtx_code_label *);
extern int bpf_core_get_sou_member_index (ctf_container_ref, const tree);
#ifdef __cplusplus
}
#endif
#endif /* __COREOUT_H */

View file

@ -0,0 +1,8 @@
TM_H += $(srcdir)/config/bpf/coreout.h
coreout.o: $(srcdir)/config/bpf/coreout.c
$(COMPILE) $<
$(POSTCOMPILE)
PASSES_EXTRA += $(srcdir)/config/bpf/bpf-passes.def