Commit f23c7ce3 authored by Daniel Borkmann's avatar Daniel Borkmann

Merge branch 'bpf-libbpf-bitfield-size-relo'

Andrii Nakryiko says:

====================
This patch set adds support for reading bitfields in a relocatable manner
through a set of relocations emitted by Clang, corresponding libbpf support
for those relocations, as well as abstracting details into
BPF_CORE_READ_BITFIELD/BPF_CORE_READ_BITFIELD_PROBED macro.

We also add support for capturing relocatable field size, so that BPF program
code can adjust its logic to actual amount of data it needs to operate on,
even if it changes between kernels. New convenience macro is added to
bpf_core_read.h (bpf_core_field_size(), in the same family of macro as
bpf_core_read() and bpf_core_field_exists()). Corresponding set of selftests
are added to excercise this logic and validate correctness in a variety of
scenarios.

Some of the overly strict logic of matching fields is relaxed to support wider
variety of scenarios. See patch #1 for that.

Patch #1 removes few overly strict test cases.
Patch #2 adds support for bitfield-related relocations.
Patch #3 adds some further adjustments to support generic field size
relocations and introduces bpf_core_field_size() macro.
Patch #4 tests bitfield reading.
Patch #5 tests field size relocations.

v1 -> v2:
  - added direct memory read-based macro and tests for bitfield reads.
====================
Signed-off-by: default avatarDaniel Borkmann <daniel@iogearbox.net>
parents 1574cf83 0b163565
......@@ -12,9 +12,81 @@
*/
enum bpf_field_info_kind {
BPF_FIELD_BYTE_OFFSET = 0, /* field byte offset */
BPF_FIELD_BYTE_SIZE = 1,
BPF_FIELD_EXISTS = 2, /* field existence in target kernel */
BPF_FIELD_SIGNED = 3,
BPF_FIELD_LSHIFT_U64 = 4,
BPF_FIELD_RSHIFT_U64 = 5,
};
#define __CORE_RELO(src, field, info) \
__builtin_preserve_field_info((src)->field, BPF_FIELD_##info)
#if __BYTE_ORDER == __LITTLE_ENDIAN
#define __CORE_BITFIELD_PROBE_READ(dst, src, fld) \
bpf_probe_read((void *)dst, \
__CORE_RELO(src, fld, BYTE_SIZE), \
(const void *)src + __CORE_RELO(src, fld, BYTE_OFFSET))
#else
/* semantics of LSHIFT_64 assumes loading values into low-ordered bytes, so
* for big-endian we need to adjust destination pointer accordingly, based on
* field byte size
*/
#define __CORE_BITFIELD_PROBE_READ(dst, src, fld) \
bpf_probe_read((void *)dst + (8 - __CORE_RELO(src, fld, BYTE_SIZE)), \
__CORE_RELO(src, fld, BYTE_SIZE), \
(const void *)src + __CORE_RELO(src, fld, BYTE_OFFSET))
#endif
/*
* Extract bitfield, identified by src->field, and put its value into u64
* *res. All this is done in relocatable manner, so bitfield changes such as
* signedness, bit size, offset changes, this will be handled automatically.
* This version of macro is using bpf_probe_read() to read underlying integer
* storage. Macro functions as an expression and its return type is
* bpf_probe_read()'s return value: 0, on success, <0 on error.
*/
#define BPF_CORE_READ_BITFIELD_PROBED(src, field, res) ({ \
unsigned long long val; \
\
*res = 0; \
val = __CORE_BITFIELD_PROBE_READ(res, src, field); \
if (!val) { \
*res <<= __CORE_RELO(src, field, LSHIFT_U64); \
val = __CORE_RELO(src, field, RSHIFT_U64); \
if (__CORE_RELO(src, field, SIGNED)) \
*res = ((long long)*res) >> val; \
else \
*res = ((unsigned long long)*res) >> val; \
val = 0; \
} \
val; \
})
/*
* Extract bitfield, identified by src->field, and return its value as u64.
* This version of macro is using direct memory reads and should be used from
* BPF program types that support such functionality (e.g., typed raw
* tracepoints).
*/
#define BPF_CORE_READ_BITFIELD(s, field) ({ \
const void *p = (const void *)s + __CORE_RELO(s, field, BYTE_OFFSET); \
unsigned long long val; \
\
switch (__CORE_RELO(s, field, BYTE_SIZE)) { \
case 1: val = *(const unsigned char *)p; \
case 2: val = *(const unsigned short *)p; \
case 4: val = *(const unsigned int *)p; \
case 8: val = *(const unsigned long long *)p; \
} \
val <<= __CORE_RELO(s, field, LSHIFT_U64); \
if (__CORE_RELO(s, field, SIGNED)) \
val = ((long long)val) >> __CORE_RELO(s, field, RSHIFT_U64); \
else \
val = val >> __CORE_RELO(s, field, RSHIFT_U64); \
val; \
})
/*
* Convenience macro to check that field actually exists in target kernel's.
* Returns:
......@@ -24,6 +96,13 @@ enum bpf_field_info_kind {
#define bpf_core_field_exists(field) \
__builtin_preserve_field_info(field, BPF_FIELD_EXISTS)
/*
* Convenience macro to get byte size of a field. Works for integers,
* struct/unions, pointers, arrays, and enums.
*/
#define bpf_core_field_size(field) \
__builtin_preserve_field_info(field, BPF_FIELD_BYTE_SIZE)
/*
* bpf_core_read() abstracts away bpf_probe_read() call and captures offset
* relocation for source address using __builtin_preserve_access_index()
......
This diff is collapsed.
......@@ -158,7 +158,11 @@ struct bpf_line_info_min {
*/
enum bpf_field_info_kind {
BPF_FIELD_BYTE_OFFSET = 0, /* field byte offset */
BPF_FIELD_BYTE_SIZE = 1,
BPF_FIELD_EXISTS = 2, /* field existence in target kernel */
BPF_FIELD_SIGNED = 3,
BPF_FIELD_LSHIFT_U64 = 4,
BPF_FIELD_RSHIFT_U64 = 5,
};
/* The minimum bpf_field_reloc checked by the loader
......
......@@ -174,21 +174,82 @@
.fails = true, \
}
#define EXISTENCE_DATA(struct_name) STRUCT_TO_CHAR_PTR(struct_name) { \
.a = 42, \
}
#define EXISTENCE_CASE_COMMON(name) \
.case_name = #name, \
.bpf_obj_file = "test_core_reloc_existence.o", \
.btf_src_file = "btf__core_reloc_" #name ".o", \
.relaxed_core_relocs = true \
.relaxed_core_relocs = true
#define EXISTENCE_ERR_CASE(name) { \
EXISTENCE_CASE_COMMON(name), \
.fails = true, \
}
#define BITFIELDS_CASE_COMMON(objfile, test_name_prefix, name) \
.case_name = test_name_prefix#name, \
.bpf_obj_file = objfile, \
.btf_src_file = "btf__core_reloc_" #name ".o"
#define BITFIELDS_CASE(name, ...) { \
BITFIELDS_CASE_COMMON("test_core_reloc_bitfields_probed.o", \
"direct:", name), \
.input = STRUCT_TO_CHAR_PTR(core_reloc_##name) __VA_ARGS__, \
.input_len = sizeof(struct core_reloc_##name), \
.output = STRUCT_TO_CHAR_PTR(core_reloc_bitfields_output) \
__VA_ARGS__, \
.output_len = sizeof(struct core_reloc_bitfields_output), \
}, { \
BITFIELDS_CASE_COMMON("test_core_reloc_bitfields_direct.o", \
"probed:", name), \
.input = STRUCT_TO_CHAR_PTR(core_reloc_##name) __VA_ARGS__, \
.input_len = sizeof(struct core_reloc_##name), \
.output = STRUCT_TO_CHAR_PTR(core_reloc_bitfields_output) \
__VA_ARGS__, \
.output_len = sizeof(struct core_reloc_bitfields_output), \
.direct_raw_tp = true, \
}
#define BITFIELDS_ERR_CASE(name) { \
BITFIELDS_CASE_COMMON("test_core_reloc_bitfields_probed.o", \
"probed:", name), \
.fails = true, \
}, { \
BITFIELDS_CASE_COMMON("test_core_reloc_bitfields_direct.o", \
"direct:", name), \
.direct_raw_tp = true, \
.fails = true, \
}
#define SIZE_CASE_COMMON(name) \
.case_name = #name, \
.bpf_obj_file = "test_core_reloc_size.o", \
.btf_src_file = "btf__core_reloc_" #name ".o", \
.relaxed_core_relocs = true
#define SIZE_OUTPUT_DATA(type) \
STRUCT_TO_CHAR_PTR(core_reloc_size_output) { \
.int_sz = sizeof(((type *)0)->int_field), \
.struct_sz = sizeof(((type *)0)->struct_field), \
.union_sz = sizeof(((type *)0)->union_field), \
.arr_sz = sizeof(((type *)0)->arr_field), \
.arr_elem_sz = sizeof(((type *)0)->arr_field[0]), \
.ptr_sz = sizeof(((type *)0)->ptr_field), \
.enum_sz = sizeof(((type *)0)->enum_field), \
}
#define SIZE_CASE(name) { \
SIZE_CASE_COMMON(name), \
.input_len = 0, \
.output = SIZE_OUTPUT_DATA(struct core_reloc_##name), \
.output_len = sizeof(struct core_reloc_size_output), \
}
#define SIZE_ERR_CASE(name) { \
SIZE_CASE_COMMON(name), \
.fails = true, \
}
struct core_reloc_test_case {
const char *case_name;
const char *bpf_obj_file;
......@@ -199,6 +260,7 @@ struct core_reloc_test_case {
int output_len;
bool fails;
bool relaxed_core_relocs;
bool direct_raw_tp;
};
static struct core_reloc_test_case test_cases[] = {
......@@ -352,6 +414,44 @@ static struct core_reloc_test_case test_cases[] = {
EXISTENCE_ERR_CASE(existence__err_arr_kind),
EXISTENCE_ERR_CASE(existence__err_arr_value_type),
EXISTENCE_ERR_CASE(existence__err_struct_type),
/* bitfield relocation checks */
BITFIELDS_CASE(bitfields, {
.ub1 = 1,
.ub2 = 2,
.ub7 = 96,
.sb4 = -7,
.sb20 = -0x76543,
.u32 = 0x80000000,
.s32 = -0x76543210,
}),
BITFIELDS_CASE(bitfields___bit_sz_change, {
.ub1 = 6,
.ub2 = 0xABCDE,
.ub7 = 1,
.sb4 = -1,
.sb20 = -0x17654321,
.u32 = 0xBEEF,
.s32 = -0x3FEDCBA987654321,
}),
BITFIELDS_CASE(bitfields___bitfield_vs_int, {
.ub1 = 0xFEDCBA9876543210,
.ub2 = 0xA6,
.ub7 = -0x7EDCBA987654321,
.sb4 = -0x6123456789ABCDE,
.sb20 = 0xD00D,
.u32 = -0x76543,
.s32 = 0x0ADEADBEEFBADB0B,
}),
BITFIELDS_CASE(bitfields___just_big_enough, {
.ub1 = 0xF,
.ub2 = 0x0812345678FEDCBA,
}),
BITFIELDS_ERR_CASE(bitfields___err_too_big_bitfield),
/* size relocation checks */
SIZE_CASE(size),
SIZE_CASE(size___diff_sz),
};
struct data {
......@@ -361,9 +461,9 @@ struct data {
void test_core_reloc(void)
{
const char *probe_name = "raw_tracepoint/sys_enter";
struct bpf_object_load_attr load_attr = {};
struct core_reloc_test_case *test_case;
const char *tp_name, *probe_name;
int err, duration = 0, i, equal;
struct bpf_link *link = NULL;
struct bpf_map *data_map;
......@@ -387,6 +487,15 @@ void test_core_reloc(void)
test_case->bpf_obj_file, PTR_ERR(obj)))
continue;
/* for typed raw tracepoints, NULL should be specified */
if (test_case->direct_raw_tp) {
probe_name = "tp_btf/sys_enter";
tp_name = NULL;
} else {
probe_name = "raw_tracepoint/sys_enter";
tp_name = "sys_enter";
}
prog = bpf_object__find_program_by_title(obj, probe_name);
if (CHECK(!prog, "find_probe",
"prog '%s' not found\n", probe_name))
......@@ -407,7 +516,7 @@ void test_core_reloc(void)
goto cleanup;
}
link = bpf_program__attach_raw_tracepoint(prog, "sys_enter");
link = bpf_program__attach_raw_tracepoint(prog, tp_name);
if (CHECK(IS_ERR(link), "attach_raw_tp", "err %ld\n",
PTR_ERR(link)))
goto cleanup;
......
#include "core_reloc_types.h"
void f(struct core_reloc_ints___err_wrong_sz_16 x) {}
void f(struct core_reloc_arrays___err_wrong_val_type x) {}
#include "core_reloc_types.h"
void f(struct core_reloc_arrays___err_wrong_val_type2 x) {}
#include "core_reloc_types.h"
void f(struct core_reloc_ints___err_bitfield x) {}
void f(struct core_reloc_bitfields x) {}
#include "core_reloc_types.h"
void f(struct core_reloc_ints___err_wrong_sz_32 x) {}
void f(struct core_reloc_bitfields___bit_sz_change x) {}
#include "core_reloc_types.h"
void f(struct core_reloc_bitfields___bitfield_vs_int x) {}
#include "core_reloc_types.h"
void f(struct core_reloc_arrays___err_wrong_val_type1 x) {}
void f(struct core_reloc_bitfields___err_too_big_bitfield x) {}
#include "core_reloc_types.h"
void f(struct core_reloc_bitfields___just_big_enough x) {}
#include "core_reloc_types.h"
void f(struct core_reloc_ints___err_wrong_sz_8 x) {}
void f(struct core_reloc_size x) {}
#include "core_reloc_types.h"
void f(struct core_reloc_ints___err_wrong_sz_64 x) {}
void f(struct core_reloc_size___diff_sz x) {}
......@@ -386,14 +386,7 @@ struct core_reloc_arrays___err_non_array {
struct core_reloc_arrays_substruct d[1][2];
};
struct core_reloc_arrays___err_wrong_val_type1 {
char a[5]; /* char instead of int */
char b[2][3][4];
struct core_reloc_arrays_substruct c[3];
struct core_reloc_arrays_substruct d[1][2];
};
struct core_reloc_arrays___err_wrong_val_type2 {
struct core_reloc_arrays___err_wrong_val_type {
int a[5];
char b[2][3][4];
int c[3]; /* value is not a struct */
......@@ -589,67 +582,6 @@ struct core_reloc_ints___bool {
int64_t s64_field;
};
struct core_reloc_ints___err_bitfield {
uint8_t u8_field;
int8_t s8_field;
uint16_t u16_field;
int16_t s16_field;
uint32_t u32_field: 32; /* bitfields are not supported */
int32_t s32_field;
uint64_t u64_field;
int64_t s64_field;
};
struct core_reloc_ints___err_wrong_sz_8 {
uint16_t u8_field; /* not 8-bit anymore */
int16_t s8_field; /* not 8-bit anymore */
uint16_t u16_field;
int16_t s16_field;
uint32_t u32_field;
int32_t s32_field;
uint64_t u64_field;
int64_t s64_field;
};
struct core_reloc_ints___err_wrong_sz_16 {
uint8_t u8_field;
int8_t s8_field;
uint32_t u16_field; /* not 16-bit anymore */
int32_t s16_field; /* not 16-bit anymore */
uint32_t u32_field;
int32_t s32_field;
uint64_t u64_field;
int64_t s64_field;
};
struct core_reloc_ints___err_wrong_sz_32 {
uint8_t u8_field;
int8_t s8_field;
uint16_t u16_field;
int16_t s16_field;
uint64_t u32_field; /* not 32-bit anymore */
int64_t s32_field; /* not 32-bit anymore */
uint64_t u64_field;
int64_t s64_field;
};
struct core_reloc_ints___err_wrong_sz_64 {
uint8_t u8_field;
int8_t s8_field;
uint16_t u16_field;
int16_t s16_field;
uint32_t u32_field;
int32_t s32_field;
uint32_t u64_field; /* not 64-bit anymore */
int32_t s64_field; /* not 64-bit anymore */
};
/*
* MISC
*/
......@@ -730,3 +662,106 @@ struct core_reloc_existence___err_wrong_arr_value_type {
struct core_reloc_existence___err_wrong_struct_type {
int s;
};
/*
* BITFIELDS
*/
/* bitfield read results, all as plain integers */
struct core_reloc_bitfields_output {
int64_t ub1;
int64_t ub2;
int64_t ub7;
int64_t sb4;
int64_t sb20;
int64_t u32;
int64_t s32;
};
struct core_reloc_bitfields {
/* unsigned bitfields */
uint8_t ub1: 1;
uint8_t ub2: 2;
uint32_t ub7: 7;
/* signed bitfields */
int8_t sb4: 4;
int32_t sb20: 20;
/* non-bitfields */
uint32_t u32;
int32_t s32;
};
/* different bit sizes (both up and down) */
struct core_reloc_bitfields___bit_sz_change {
/* unsigned bitfields */
uint16_t ub1: 3; /* 1 -> 3 */
uint32_t ub2: 20; /* 2 -> 20 */
uint8_t ub7: 1; /* 7 -> 1 */
/* signed bitfields */
int8_t sb4: 1; /* 4 -> 1 */
int32_t sb20: 30; /* 20 -> 30 */
/* non-bitfields */
uint16_t u32; /* 32 -> 16 */
int64_t s32; /* 32 -> 64 */
};
/* turn bitfield into non-bitfield and vice versa */
struct core_reloc_bitfields___bitfield_vs_int {
uint64_t ub1; /* 3 -> 64 non-bitfield */
uint8_t ub2; /* 20 -> 8 non-bitfield */
int64_t ub7; /* 7 -> 64 non-bitfield signed */
int64_t sb4; /* 4 -> 64 non-bitfield signed */
uint64_t sb20; /* 20 -> 16 non-bitfield unsigned */
int32_t u32: 20; /* 32 non-bitfield -> 20 bitfield */
uint64_t s32: 60; /* 32 non-bitfield -> 60 bitfield */
};
struct core_reloc_bitfields___just_big_enough {
uint64_t ub1: 4;
uint64_t ub2: 60; /* packed tightly */
uint32_t ub7;
uint32_t sb4;
uint32_t sb20;
uint32_t u32;
uint32_t s32;
} __attribute__((packed)) ;
struct core_reloc_bitfields___err_too_big_bitfield {
uint64_t ub1: 4;
uint64_t ub2: 61; /* packed tightly */
uint32_t ub7;
uint32_t sb4;
uint32_t sb20;
uint32_t u32;
uint32_t s32;
} __attribute__((packed)) ;
/*
* SIZE
*/
struct core_reloc_size_output {
int int_sz;
int struct_sz;
int union_sz;
int arr_sz;
int arr_elem_sz;
int ptr_sz;
int enum_sz;
};
struct core_reloc_size {
int int_field;
struct { int x; } struct_field;
union { int x; } union_field;
int arr_field[4];
void *ptr_field;
enum { VALUE = 123 } enum_field;
};
struct core_reloc_size___diff_sz {
uint64_t int_field;
struct { int x; int y; int z; } struct_field;
union { int x; char bla[123]; } union_field;
char arr_field[10];
void *ptr_field;
enum { OTHER_VALUE = 0xFFFFFFFFFFFFFFFF } enum_field;
};
// SPDX-License-Identifier: GPL-2.0
// Copyright (c) 2019 Facebook
#include <linux/bpf.h>
#include <stdint.h>
#include "bpf_helpers.h"
#include "bpf_core_read.h"
char _license[] SEC("license") = "GPL";
static volatile struct data {
char in[256];
char out[256];
} data;
struct core_reloc_bitfields {
/* unsigned bitfields */
uint8_t ub1: 1;
uint8_t ub2: 2;
uint32_t ub7: 7;
/* signed bitfields */
int8_t sb4: 4;
int32_t sb20: 20;
/* non-bitfields */
uint32_t u32;
int32_t s32;
};
/* bitfield read results, all as plain integers */
struct core_reloc_bitfields_output {
int64_t ub1;
int64_t ub2;
int64_t ub7;
int64_t sb4;
int64_t sb20;
int64_t u32;
int64_t s32;
};
struct pt_regs;
struct trace_sys_enter {
struct pt_regs *regs;
long id;
};
SEC("tp_btf/sys_enter")
int test_core_bitfields_direct(void *ctx)
{
struct core_reloc_bitfields *in = (void *)&data.in;
struct core_reloc_bitfields_output *out = (void *)&data.out;
out->ub1 = BPF_CORE_READ_BITFIELD(in, ub1);
out->ub2 = BPF_CORE_READ_BITFIELD(in, ub2);
out->ub7 = BPF_CORE_READ_BITFIELD(in, ub7);
out->sb4 = BPF_CORE_READ_BITFIELD(in, sb4);
out->sb20 = BPF_CORE_READ_BITFIELD(in, sb20);
out->u32 = BPF_CORE_READ_BITFIELD(in, u32);
out->s32 = BPF_CORE_READ_BITFIELD(in, s32);
return 0;
}
// SPDX-License-Identifier: GPL-2.0
// Copyright (c) 2019 Facebook
#include <linux/bpf.h>
#include <stdint.h>
#include "bpf_helpers.h"
#include "bpf_core_read.h"
char _license[] SEC("license") = "GPL";
static volatile struct data {
char in[256];
char out[256];
} data;
struct core_reloc_bitfields {
/* unsigned bitfields */
uint8_t ub1: 1;
uint8_t ub2: 2;
uint32_t ub7: 7;
/* signed bitfields */
int8_t sb4: 4;
int32_t sb20: 20;
/* non-bitfields */
uint32_t u32;
int32_t s32;
};
/* bitfield read results, all as plain integers */
struct core_reloc_bitfields_output {
int64_t ub1;
int64_t ub2;
int64_t ub7;
int64_t sb4;
int64_t sb20;
int64_t u32;
int64_t s32;
};
#define TRANSFER_BITFIELD(in, out, field) \
if (BPF_CORE_READ_BITFIELD_PROBED(in, field, &res)) \
return 1; \
out->field = res
SEC("raw_tracepoint/sys_enter")
int test_core_bitfields(void *ctx)
{
struct core_reloc_bitfields *in = (void *)&data.in;
struct core_reloc_bitfields_output *out = (void *)&data.out;
uint64_t res;
TRANSFER_BITFIELD(in, out, ub1);
TRANSFER_BITFIELD(in, out, ub2);
TRANSFER_BITFIELD(in, out, ub7);
TRANSFER_BITFIELD(in, out, sb4);
TRANSFER_BITFIELD(in, out, sb20);
TRANSFER_BITFIELD(in, out, u32);
TRANSFER_BITFIELD(in, out, s32);
return 0;
}
// SPDX-License-Identifier: GPL-2.0
// Copyright (c) 2019 Facebook
#include <linux/bpf.h>
#include <stdint.h>
#include "bpf_helpers.h"
#include "bpf_core_read.h"
char _license[] SEC("license") = "GPL";
static volatile struct data {
char in[256];
char out[256];
} data;
struct core_reloc_size_output {
int int_sz;
int struct_sz;
int union_sz;
int arr_sz;
int arr_elem_sz;
int ptr_sz;
int enum_sz;
};
struct core_reloc_size {
int int_field;
struct { int x; } struct_field;
union { int x; } union_field;
int arr_field[4];
void *ptr_field;
enum { VALUE = 123 } enum_field;
};
SEC("raw_tracepoint/sys_enter")
int test_core_size(void *ctx)
{
struct core_reloc_size *in = (void *)&data.in;
struct core_reloc_size_output *out = (void *)&data.out;
out->int_sz = bpf_core_field_size(in->int_field);
out->struct_sz = bpf_core_field_size(in->struct_field);
out->union_sz = bpf_core_field_size(in->union_field);
out->arr_sz = bpf_core_field_size(in->arr_field);
out->arr_elem_sz = bpf_core_field_size(in->arr_field[0]);
out->ptr_sz = bpf_core_field_size(in->ptr_field);
out->enum_sz = bpf_core_field_size(in->enum_field);
return 0;
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment