Merge branch 'CO-RE relocation selftests fixes'

Andrii Nakryiko says:

====================

Lorenz Bauer noticed that core_reloc selftest has two inverted CHECK()
conditions, allowing failing tests to pass unnoticed. Fixing that opened up
few long-standing (field existence and direct memory bitfields) and one recent
failures (BTF_KIND_FLOAT relos).

This patch set fixes core_reloc selftest to capture such failures reliably in
the future. It also fixes all the newly failing tests. See individual patches
for details.

This patch set also completes a set of ASSERT_xxx() macros, so now there
should be a very little reason to use verbose and error-prone generic CHECK()
macro.

v1->v2:
  - updated bpf_core_fields_are_compat() comment to mention FLOAT (Lorenz).

Cc: Lorenz Bauer <lmb@cloudflare.com>
====================

Signed-off-by: Alexei Starovoitov <ast@kernel.org>
This commit is contained in:
Alexei Starovoitov 2021-04-26 18:37:14 -07:00
commit 9273150418
18 changed files with 108 additions and 77 deletions

View File

@ -88,11 +88,19 @@ enum bpf_enum_value_kind {
const void *p = (const void *)s + __CORE_RELO(s, field, BYTE_OFFSET); \
unsigned long long val; \
\
/* This is a so-called barrier_var() operation that makes specified \
* variable "a black box" for optimizing compiler. \
* It forces compiler to perform BYTE_OFFSET relocation on p and use \
* its calculated value in the switch below, instead of applying \
* the same relocation 4 times for each individual memory load. \
*/ \
asm volatile("" : "=r"(p) : "0"(p)); \
\
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; \
case 1: val = *(const unsigned char *)p; break; \
case 2: val = *(const unsigned short *)p; break; \
case 4: val = *(const unsigned int *)p; break; \
case 8: val = *(const unsigned long long *)p; break; \
} \
val <<= __CORE_RELO(s, field, LSHIFT_U64); \
if (__CORE_RELO(s, field, SIGNED)) \

View File

@ -5115,6 +5115,7 @@ err_out:
* least one of enums should be anonymous;
* - for ENUMs, check sizes, names are ignored;
* - for INT, size and signedness are ignored;
* - any two FLOATs are always compatible;
* - for ARRAY, dimensionality is ignored, element types are checked for
* compatibility recursively;
* - everything else shouldn't be ever a target of relocation.
@ -5141,6 +5142,7 @@ recur:
switch (btf_kind(local_type)) {
case BTF_KIND_PTR:
case BTF_KIND_FLOAT:
return 1;
case BTF_KIND_FWD:
case BTF_KIND_ENUM: {
@ -6245,8 +6247,8 @@ patch_insn:
/* bpf_core_patch_insn() should know how to handle missing targ_spec */
err = bpf_core_patch_insn(prog, relo, relo_idx, &targ_res);
if (err) {
pr_warn("prog '%s': relo #%d: failed to patch insn at offset %d: %d\n",
prog->name, relo_idx, relo->insn_off, err);
pr_warn("prog '%s': relo #%d: failed to patch insn #%zu: %d\n",
prog->name, relo_idx, relo->insn_off / BPF_INSN_SZ, err);
return -EINVAL;
}

View File

@ -77,7 +77,7 @@ static int test_btf_dump_case(int n, struct btf_dump_test_case *t)
snprintf(out_file, sizeof(out_file), "/tmp/%s.output.XXXXXX", t->file);
fd = mkstemp(out_file);
if (CHECK(fd < 0, "create_tmp", "failed to create file: %d\n", fd)) {
if (!ASSERT_GE(fd, 0, "create_tmp")) {
err = fd;
goto done;
}

View File

@ -6,8 +6,6 @@
#include <test_progs.h>
#include <bpf/btf.h>
static int duration = 0;
void test_btf_endian() {
#if __BYTE_ORDER == __LITTLE_ENDIAN
enum btf_endianness endian = BTF_LITTLE_ENDIAN;
@ -71,7 +69,7 @@ void test_btf_endian() {
/* now modify original BTF */
var_id = btf__add_var(btf, "some_var", BTF_VAR_GLOBAL_ALLOCATED, 1);
CHECK(var_id <= 0, "var_id", "failed %d\n", var_id);
ASSERT_GT(var_id, 0, "var_id");
btf__free(swap_btf);
swap_btf = NULL;

View File

@ -54,7 +54,7 @@ void test_cgroup_link(void)
for (i = 0; i < cg_nr; i++) {
cgs[i].fd = create_and_get_cgroup(cgs[i].path);
if (CHECK(cgs[i].fd < 0, "cg_create", "fail: %d\n", cgs[i].fd))
if (!ASSERT_GE(cgs[i].fd, 0, "cg_create"))
goto cleanup;
}

View File

@ -210,11 +210,6 @@ static int duration = 0;
.bpf_obj_file = "test_core_reloc_existence.o", \
.btf_src_file = "btf__core_reloc_" #name ".o" \
#define FIELD_EXISTS_ERR_CASE(name) { \
FIELD_EXISTS_CASE_COMMON(name), \
.fails = true, \
}
#define BITFIELDS_CASE_COMMON(objfile, test_name_prefix, name) \
.case_name = test_name_prefix#name, \
.bpf_obj_file = objfile, \
@ -222,7 +217,7 @@ static int duration = 0;
#define BITFIELDS_CASE(name, ...) { \
BITFIELDS_CASE_COMMON("test_core_reloc_bitfields_probed.o", \
"direct:", name), \
"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) \
@ -230,7 +225,7 @@ static int duration = 0;
.output_len = sizeof(struct core_reloc_bitfields_output), \
}, { \
BITFIELDS_CASE_COMMON("test_core_reloc_bitfields_direct.o", \
"probed:", name), \
"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) \
@ -551,8 +546,7 @@ static struct core_reloc_test_case test_cases[] = {
ARRAYS_ERR_CASE(arrays___err_too_small),
ARRAYS_ERR_CASE(arrays___err_too_shallow),
ARRAYS_ERR_CASE(arrays___err_non_array),
ARRAYS_ERR_CASE(arrays___err_wrong_val_type1),
ARRAYS_ERR_CASE(arrays___err_wrong_val_type2),
ARRAYS_ERR_CASE(arrays___err_wrong_val_type),
ARRAYS_ERR_CASE(arrays___err_bad_zero_sz_arr),
/* enum/ptr/int handling scenarios */
@ -643,13 +637,25 @@ static struct core_reloc_test_case test_cases[] = {
},
.output_len = sizeof(struct core_reloc_existence_output),
},
FIELD_EXISTS_ERR_CASE(existence__err_int_sz),
FIELD_EXISTS_ERR_CASE(existence__err_int_type),
FIELD_EXISTS_ERR_CASE(existence__err_int_kind),
FIELD_EXISTS_ERR_CASE(existence__err_arr_kind),
FIELD_EXISTS_ERR_CASE(existence__err_arr_value_type),
FIELD_EXISTS_ERR_CASE(existence__err_struct_type),
{
FIELD_EXISTS_CASE_COMMON(existence___wrong_field_defs),
.input = STRUCT_TO_CHAR_PTR(core_reloc_existence___wrong_field_defs) {
},
.input_len = sizeof(struct core_reloc_existence___wrong_field_defs),
.output = STRUCT_TO_CHAR_PTR(core_reloc_existence_output) {
.a_exists = 0,
.b_exists = 0,
.c_exists = 0,
.arr_exists = 0,
.s_exists = 0,
.a_value = 0xff000001u,
.b_value = 0xff000002u,
.c_value = 0xff000003u,
.arr_value = 0xff000004u,
.s_value = 0xff000005u,
},
.output_len = sizeof(struct core_reloc_existence_output),
},
/* bitfield relocation checks */
BITFIELDS_CASE(bitfields, {
@ -858,13 +864,20 @@ void test_core_reloc(void)
"prog '%s' not found\n", probe_name))
goto cleanup;
if (test_case->btf_src_file) {
err = access(test_case->btf_src_file, R_OK);
if (!ASSERT_OK(err, "btf_src_file"))
goto cleanup;
}
load_attr.obj = obj;
load_attr.log_level = 0;
load_attr.target_btf_path = test_case->btf_src_file;
err = bpf_object__load_xattr(&load_attr);
if (err) {
if (!test_case->fails)
CHECK(false, "obj_load", "failed to load prog '%s': %d\n", probe_name, err);
ASSERT_OK(err, "obj_load");
goto cleanup;
}
@ -903,10 +916,8 @@ void test_core_reloc(void)
goto cleanup;
}
if (test_case->fails) {
CHECK(false, "obj_load_fail", "should fail to load prog '%s'\n", probe_name);
if (!ASSERT_FALSE(test_case->fails, "obj_load_should_fail"))
goto cleanup;
}
equal = memcmp(data->out, test_case->output,
test_case->output_len) == 0;

View File

@ -134,7 +134,7 @@ void test_kfree_skb(void)
/* make sure kfree_skb program was triggered
* and it sent expected skb into ring buffer
*/
CHECK_FAIL(!passed);
ASSERT_TRUE(passed, "passed");
err = bpf_map_lookup_elem(bpf_map__fd(global_data), &zero, test_ok);
if (CHECK(err, "get_result",

View File

@ -160,11 +160,8 @@ int test_resolve_btfids(void)
break;
if (i > 0) {
ret = CHECK(test_set.ids[i - 1] > test_set.ids[i],
"sort_check",
"test_set is not sorted\n");
if (ret)
break;
if (!ASSERT_LE(test_set.ids[i - 1], test_set.ids[i], "sort_check"))
return -1;
}
}

View File

@ -42,9 +42,7 @@ void test_snprintf_btf(void)
* and it set expected return values from bpf_trace_printk()s
* and all tests ran.
*/
if (CHECK(bss->ret <= 0,
"bpf_snprintf_btf: got return value",
"ret <= 0 %ld test %d\n", bss->ret, bss->ran_subtests))
if (!ASSERT_GT(bss->ret, 0, "bpf_snprintf_ret"))
goto cleanup;
if (CHECK(bss->ran_subtests == 0, "check if subtests ran",

View File

@ -1,3 +0,0 @@
#include "core_reloc_types.h"
void f(struct core_reloc_existence___err_wrong_arr_kind x) {}

View File

@ -1,3 +0,0 @@
#include "core_reloc_types.h"
void f(struct core_reloc_existence___err_wrong_arr_value_type x) {}

View File

@ -1,3 +0,0 @@
#include "core_reloc_types.h"
void f(struct core_reloc_existence___err_wrong_int_kind x) {}

View File

@ -1,3 +0,0 @@
#include "core_reloc_types.h"
void f(struct core_reloc_existence___err_wrong_int_sz x) {}

View File

@ -1,3 +0,0 @@
#include "core_reloc_types.h"
void f(struct core_reloc_existence___err_wrong_int_type x) {}

View File

@ -1,3 +0,0 @@
#include "core_reloc_types.h"
void f(struct core_reloc_existence___err_wrong_struct_type x) {}

View File

@ -0,0 +1,3 @@
#include "core_reloc_types.h"
void f(struct core_reloc_existence___wrong_field_defs x) {}

View File

@ -700,27 +700,11 @@ struct core_reloc_existence___minimal {
int a;
};
struct core_reloc_existence___err_wrong_int_sz {
short a;
};
struct core_reloc_existence___err_wrong_int_type {
struct core_reloc_existence___wrong_field_defs {
void *a;
int b[1];
};
struct core_reloc_existence___err_wrong_int_kind {
struct{ int x; } c;
};
struct core_reloc_existence___err_wrong_arr_kind {
int arr;
};
struct core_reloc_existence___err_wrong_arr_value_type {
short arr[1];
};
struct core_reloc_existence___err_wrong_struct_type {
int s;
};

View File

@ -130,6 +130,20 @@ extern int test__join_cgroup(const char *path);
#define CHECK_ATTR(condition, tag, format...) \
_CHECK(condition, tag, tattr.duration, format)
#define ASSERT_TRUE(actual, name) ({ \
static int duration = 0; \
bool ___ok = (actual); \
CHECK(!___ok, (name), "unexpected %s: got FALSE\n", (name)); \
___ok; \
})
#define ASSERT_FALSE(actual, name) ({ \
static int duration = 0; \
bool ___ok = !(actual); \
CHECK(!___ok, (name), "unexpected %s: got TRUE\n", (name)); \
___ok; \
})
#define ASSERT_EQ(actual, expected, name) ({ \
static int duration = 0; \
typeof(actual) ___act = (actual); \
@ -163,6 +177,39 @@ extern int test__join_cgroup(const char *path);
___ok; \
})
#define ASSERT_LE(actual, expected, name) ({ \
static int duration = 0; \
typeof(actual) ___act = (actual); \
typeof(expected) ___exp = (expected); \
bool ___ok = ___act <= ___exp; \
CHECK(!___ok, (name), \
"unexpected %s: actual %lld > expected %lld\n", \
(name), (long long)(___act), (long long)(___exp)); \
___ok; \
})
#define ASSERT_GT(actual, expected, name) ({ \
static int duration = 0; \
typeof(actual) ___act = (actual); \
typeof(expected) ___exp = (expected); \
bool ___ok = ___act > ___exp; \
CHECK(!___ok, (name), \
"unexpected %s: actual %lld <= expected %lld\n", \
(name), (long long)(___act), (long long)(___exp)); \
___ok; \
})
#define ASSERT_GE(actual, expected, name) ({ \
static int duration = 0; \
typeof(actual) ___act = (actual); \
typeof(expected) ___exp = (expected); \
bool ___ok = ___act >= ___exp; \
CHECK(!___ok, (name), \
"unexpected %s: actual %lld < expected %lld\n", \
(name), (long long)(___act), (long long)(___exp)); \
___ok; \
})
#define ASSERT_STREQ(actual, expected, name) ({ \
static int duration = 0; \
const char *___act = actual; \
@ -178,7 +225,8 @@ extern int test__join_cgroup(const char *path);
static int duration = 0; \
long long ___res = (res); \
bool ___ok = ___res == 0; \
CHECK(!___ok, (name), "unexpected error: %lld\n", ___res); \
CHECK(!___ok, (name), "unexpected error: %lld (errno %d)\n", \
___res, errno); \
___ok; \
})