Commit 16b3129e authored by Alexei Starovoitov's avatar Alexei Starovoitov

Merge branch 'bpf-verifier-log-improvements'

Andrii Nakryiko says:

====================
BPF verifier log improvements

This patch set moves a big chunk of verifier log related code from gigantic
verifier.c file into more focused kernel/bpf/log.c. This is not essential to
the rest of functionality in this patch set, so I can undo it, but it felt
like it's good to start chipping away from 20K+ verifier.c whenever we can.

The main purpose of the patch set, though, is in improving verifier log
further.

Patches #3-#4 start printing out register state even if that register is
spilled into stack slot. Previously we'd get only spilled register type, but
no additional information, like SCALAR_VALUE's ranges. Super limiting during
debugging. For cases of register spills smaller than 8 bytes, we also print
out STACK_MISC/STACK_ZERO/STACK_INVALID markers. This, among other things,
will make it easier to write tests for these mixed spill/misc cases.

Patch #5 prints map name for PTR_TO_MAP_VALUE/PTR_TO_MAP_KEY/CONST_PTR_TO_MAP
registers. In big production BPF programs, it's important to map assembly to
actual map, and it's often non-trivial. Having map name helps.

Patch #6 just removes visual noise in form of ubiquitous imm=0 and off=0. They
are default values, omit them.

Patch #7 is probably the most controversial, but it reworks how verifier log
prints numbers. For small valued integers we use decimals, but for large ones
we switch to hexadecimal. From personal experience this is a much more useful
convention. We can tune what consitutes "small value", for now it's 16-bit
range.

Patch #8 prints frame number for PTR_TO_CTX registers, if that frame is
different from the "current" one. This removes ambiguity and confusion,
especially in complicated cases with multiple subprogs passing around
pointers.

v2->v3:
  - adjust reg_bounds tester to parse hex form of reg state as well;
  - print reg->range as unsigned (Alexei);
v1->v2:
  - use verbose_snum() for range and offset in register state (Eduard);
  - fixed typos and added acks from Eduard and Stanislav.
====================

Link: https://lore.kernel.org/r/20231118034623.3320920-1-andrii@kernel.orgSigned-off-by: default avatarAlexei Starovoitov <ast@kernel.org>
parents ff8867af 46862ee8
...@@ -680,6 +680,10 @@ int bpf_vlog_init(struct bpf_verifier_log *log, u32 log_level, ...@@ -680,6 +680,10 @@ int bpf_vlog_init(struct bpf_verifier_log *log, u32 log_level,
void bpf_vlog_reset(struct bpf_verifier_log *log, u64 new_pos); void bpf_vlog_reset(struct bpf_verifier_log *log, u64 new_pos);
int bpf_vlog_finalize(struct bpf_verifier_log *log, u32 *log_size_actual); int bpf_vlog_finalize(struct bpf_verifier_log *log, u32 *log_size_actual);
__printf(3, 4) void verbose_linfo(struct bpf_verifier_env *env,
u32 insn_off,
const char *prefix_fmt, ...);
static inline struct bpf_func_state *cur_func(struct bpf_verifier_env *env) static inline struct bpf_func_state *cur_func(struct bpf_verifier_env *env)
{ {
struct bpf_verifier_state *cur = env->cur_state; struct bpf_verifier_state *cur = env->cur_state;
...@@ -779,4 +783,76 @@ static inline bool bpf_type_has_unsafe_modifiers(u32 type) ...@@ -779,4 +783,76 @@ static inline bool bpf_type_has_unsafe_modifiers(u32 type)
return type_flag(type) & ~BPF_REG_TRUSTED_MODIFIERS; return type_flag(type) & ~BPF_REG_TRUSTED_MODIFIERS;
} }
static inline bool type_is_ptr_alloc_obj(u32 type)
{
return base_type(type) == PTR_TO_BTF_ID && type_flag(type) & MEM_ALLOC;
}
static inline bool type_is_non_owning_ref(u32 type)
{
return type_is_ptr_alloc_obj(type) && type_flag(type) & NON_OWN_REF;
}
static inline bool type_is_pkt_pointer(enum bpf_reg_type type)
{
type = base_type(type);
return type == PTR_TO_PACKET ||
type == PTR_TO_PACKET_META;
}
static inline bool type_is_sk_pointer(enum bpf_reg_type type)
{
return type == PTR_TO_SOCKET ||
type == PTR_TO_SOCK_COMMON ||
type == PTR_TO_TCP_SOCK ||
type == PTR_TO_XDP_SOCK;
}
static inline void mark_reg_scratched(struct bpf_verifier_env *env, u32 regno)
{
env->scratched_regs |= 1U << regno;
}
static inline void mark_stack_slot_scratched(struct bpf_verifier_env *env, u32 spi)
{
env->scratched_stack_slots |= 1ULL << spi;
}
static inline bool reg_scratched(const struct bpf_verifier_env *env, u32 regno)
{
return (env->scratched_regs >> regno) & 1;
}
static inline bool stack_slot_scratched(const struct bpf_verifier_env *env, u64 regno)
{
return (env->scratched_stack_slots >> regno) & 1;
}
static inline bool verifier_state_scratched(const struct bpf_verifier_env *env)
{
return env->scratched_regs || env->scratched_stack_slots;
}
static inline void mark_verifier_state_clean(struct bpf_verifier_env *env)
{
env->scratched_regs = 0U;
env->scratched_stack_slots = 0ULL;
}
/* Used for printing the entire verifier state. */
static inline void mark_verifier_state_scratched(struct bpf_verifier_env *env)
{
env->scratched_regs = ~0U;
env->scratched_stack_slots = ~0ULL;
}
const char *reg_type_str(struct bpf_verifier_env *env, enum bpf_reg_type type);
const char *dynptr_type_str(enum bpf_dynptr_type type);
const char *iter_type_str(const struct btf *btf, u32 btf_id);
const char *iter_state_str(enum bpf_iter_state state);
void print_verifier_state(struct bpf_verifier_env *env,
const struct bpf_func_state *state, bool print_all);
void print_insn_state(struct bpf_verifier_env *env, const struct bpf_func_state *state);
#endif /* _LINUX_BPF_VERIFIER_H */ #endif /* _LINUX_BPF_VERIFIER_H */
...@@ -10,6 +10,8 @@ ...@@ -10,6 +10,8 @@
#include <linux/bpf_verifier.h> #include <linux/bpf_verifier.h>
#include <linux/math64.h> #include <linux/math64.h>
#define verbose(env, fmt, args...) bpf_verifier_log_write(env, fmt, ##args)
static bool bpf_verifier_log_attr_valid(const struct bpf_verifier_log *log) static bool bpf_verifier_log_attr_valid(const struct bpf_verifier_log *log)
{ {
/* ubuf and len_total should both be specified (or not) together */ /* ubuf and len_total should both be specified (or not) together */
...@@ -325,3 +327,481 @@ __printf(2, 3) void bpf_log(struct bpf_verifier_log *log, ...@@ -325,3 +327,481 @@ __printf(2, 3) void bpf_log(struct bpf_verifier_log *log,
va_end(args); va_end(args);
} }
EXPORT_SYMBOL_GPL(bpf_log); EXPORT_SYMBOL_GPL(bpf_log);
static const struct bpf_line_info *
find_linfo(const struct bpf_verifier_env *env, u32 insn_off)
{
const struct bpf_line_info *linfo;
const struct bpf_prog *prog;
u32 i, nr_linfo;
prog = env->prog;
nr_linfo = prog->aux->nr_linfo;
if (!nr_linfo || insn_off >= prog->len)
return NULL;
linfo = prog->aux->linfo;
for (i = 1; i < nr_linfo; i++)
if (insn_off < linfo[i].insn_off)
break;
return &linfo[i - 1];
}
static const char *ltrim(const char *s)
{
while (isspace(*s))
s++;
return s;
}
__printf(3, 4) void verbose_linfo(struct bpf_verifier_env *env,
u32 insn_off,
const char *prefix_fmt, ...)
{
const struct bpf_line_info *linfo;
if (!bpf_verifier_log_needed(&env->log))
return;
linfo = find_linfo(env, insn_off);
if (!linfo || linfo == env->prev_linfo)
return;
if (prefix_fmt) {
va_list args;
va_start(args, prefix_fmt);
bpf_verifier_vlog(&env->log, prefix_fmt, args);
va_end(args);
}
verbose(env, "%s\n",
ltrim(btf_name_by_offset(env->prog->aux->btf,
linfo->line_off)));
env->prev_linfo = linfo;
}
static const char *btf_type_name(const struct btf *btf, u32 id)
{
return btf_name_by_offset(btf, btf_type_by_id(btf, id)->name_off);
}
/* string representation of 'enum bpf_reg_type'
*
* Note that reg_type_str() can not appear more than once in a single verbose()
* statement.
*/
const char *reg_type_str(struct bpf_verifier_env *env, enum bpf_reg_type type)
{
char postfix[16] = {0}, prefix[64] = {0};
static const char * const str[] = {
[NOT_INIT] = "?",
[SCALAR_VALUE] = "scalar",
[PTR_TO_CTX] = "ctx",
[CONST_PTR_TO_MAP] = "map_ptr",
[PTR_TO_MAP_VALUE] = "map_value",
[PTR_TO_STACK] = "fp",
[PTR_TO_PACKET] = "pkt",
[PTR_TO_PACKET_META] = "pkt_meta",
[PTR_TO_PACKET_END] = "pkt_end",
[PTR_TO_FLOW_KEYS] = "flow_keys",
[PTR_TO_SOCKET] = "sock",
[PTR_TO_SOCK_COMMON] = "sock_common",
[PTR_TO_TCP_SOCK] = "tcp_sock",
[PTR_TO_TP_BUFFER] = "tp_buffer",
[PTR_TO_XDP_SOCK] = "xdp_sock",
[PTR_TO_BTF_ID] = "ptr_",
[PTR_TO_MEM] = "mem",
[PTR_TO_BUF] = "buf",
[PTR_TO_FUNC] = "func",
[PTR_TO_MAP_KEY] = "map_key",
[CONST_PTR_TO_DYNPTR] = "dynptr_ptr",
};
if (type & PTR_MAYBE_NULL) {
if (base_type(type) == PTR_TO_BTF_ID)
strncpy(postfix, "or_null_", 16);
else
strncpy(postfix, "_or_null", 16);
}
snprintf(prefix, sizeof(prefix), "%s%s%s%s%s%s%s",
type & MEM_RDONLY ? "rdonly_" : "",
type & MEM_RINGBUF ? "ringbuf_" : "",
type & MEM_USER ? "user_" : "",
type & MEM_PERCPU ? "percpu_" : "",
type & MEM_RCU ? "rcu_" : "",
type & PTR_UNTRUSTED ? "untrusted_" : "",
type & PTR_TRUSTED ? "trusted_" : ""
);
snprintf(env->tmp_str_buf, TMP_STR_BUF_LEN, "%s%s%s",
prefix, str[base_type(type)], postfix);
return env->tmp_str_buf;
}
const char *dynptr_type_str(enum bpf_dynptr_type type)
{
switch (type) {
case BPF_DYNPTR_TYPE_LOCAL:
return "local";
case BPF_DYNPTR_TYPE_RINGBUF:
return "ringbuf";
case BPF_DYNPTR_TYPE_SKB:
return "skb";
case BPF_DYNPTR_TYPE_XDP:
return "xdp";
case BPF_DYNPTR_TYPE_INVALID:
return "<invalid>";
default:
WARN_ONCE(1, "unknown dynptr type %d\n", type);
return "<unknown>";
}
}
const char *iter_type_str(const struct btf *btf, u32 btf_id)
{
if (!btf || btf_id == 0)
return "<invalid>";
/* we already validated that type is valid and has conforming name */
return btf_type_name(btf, btf_id) + sizeof(ITER_PREFIX) - 1;
}
const char *iter_state_str(enum bpf_iter_state state)
{
switch (state) {
case BPF_ITER_STATE_ACTIVE:
return "active";
case BPF_ITER_STATE_DRAINED:
return "drained";
case BPF_ITER_STATE_INVALID:
return "<invalid>";
default:
WARN_ONCE(1, "unknown iter state %d\n", state);
return "<unknown>";
}
}
static char slot_type_char[] = {
[STACK_INVALID] = '?',
[STACK_SPILL] = 'r',
[STACK_MISC] = 'm',
[STACK_ZERO] = '0',
[STACK_DYNPTR] = 'd',
[STACK_ITER] = 'i',
};
static void print_liveness(struct bpf_verifier_env *env,
enum bpf_reg_liveness live)
{
if (live & (REG_LIVE_READ | REG_LIVE_WRITTEN | REG_LIVE_DONE))
verbose(env, "_");
if (live & REG_LIVE_READ)
verbose(env, "r");
if (live & REG_LIVE_WRITTEN)
verbose(env, "w");
if (live & REG_LIVE_DONE)
verbose(env, "D");
}
#define UNUM_MAX_DECIMAL U16_MAX
#define SNUM_MAX_DECIMAL S16_MAX
#define SNUM_MIN_DECIMAL S16_MIN
static bool is_unum_decimal(u64 num)
{
return num <= UNUM_MAX_DECIMAL;
}
static bool is_snum_decimal(s64 num)
{
return num >= SNUM_MIN_DECIMAL && num <= SNUM_MAX_DECIMAL;
}
static void verbose_unum(struct bpf_verifier_env *env, u64 num)
{
if (is_unum_decimal(num))
verbose(env, "%llu", num);
else
verbose(env, "%#llx", num);
}
static void verbose_snum(struct bpf_verifier_env *env, s64 num)
{
if (is_snum_decimal(num))
verbose(env, "%lld", num);
else
verbose(env, "%#llx", num);
}
static void print_scalar_ranges(struct bpf_verifier_env *env,
const struct bpf_reg_state *reg,
const char **sep)
{
/* For signed ranges, we want to unify 64-bit and 32-bit values in the
* output as much as possible, but there is a bit of a complication.
* If we choose to print values as decimals, this is natural to do,
* because negative 64-bit and 32-bit values >= -S32_MIN have the same
* representation due to sign extension. But if we choose to print
* them in hex format (see is_snum_decimal()), then sign extension is
* misleading.
* E.g., smin=-2 and smin32=-2 are exactly the same in decimal, but in
* hex they will be smin=0xfffffffffffffffe and smin32=0xfffffffe, two
* very different numbers.
* So we avoid sign extension if we choose to print values in hex.
*/
struct {
const char *name;
u64 val;
bool omit;
} minmaxs[] = {
{"smin", reg->smin_value, reg->smin_value == S64_MIN},
{"smax", reg->smax_value, reg->smax_value == S64_MAX},
{"umin", reg->umin_value, reg->umin_value == 0},
{"umax", reg->umax_value, reg->umax_value == U64_MAX},
{"smin32",
is_snum_decimal((s64)reg->s32_min_value)
? (s64)reg->s32_min_value
: (u32)reg->s32_min_value, reg->s32_min_value == S32_MIN},
{"smax32",
is_snum_decimal((s64)reg->s32_max_value)
? (s64)reg->s32_max_value
: (u32)reg->s32_max_value, reg->s32_max_value == S32_MAX},
{"umin32", reg->u32_min_value, reg->u32_min_value == 0},
{"umax32", reg->u32_max_value, reg->u32_max_value == U32_MAX},
}, *m1, *m2, *mend = &minmaxs[ARRAY_SIZE(minmaxs)];
bool neg1, neg2;
for (m1 = &minmaxs[0]; m1 < mend; m1++) {
if (m1->omit)
continue;
neg1 = m1->name[0] == 's' && (s64)m1->val < 0;
verbose(env, "%s%s=", *sep, m1->name);
*sep = ",";
for (m2 = m1 + 2; m2 < mend; m2 += 2) {
if (m2->omit || m2->val != m1->val)
continue;
/* don't mix negatives with positives */
neg2 = m2->name[0] == 's' && (s64)m2->val < 0;
if (neg2 != neg1)
continue;
m2->omit = true;
verbose(env, "%s=", m2->name);
}
if (m1->name[0] == 's')
verbose_snum(env, m1->val);
else
verbose_unum(env, m1->val);
}
}
static bool type_is_map_ptr(enum bpf_reg_type t) {
switch (base_type(t)) {
case CONST_PTR_TO_MAP:
case PTR_TO_MAP_KEY:
case PTR_TO_MAP_VALUE:
return true;
default:
return false;
}
}
static void print_reg_state(struct bpf_verifier_env *env,
const struct bpf_func_state *state,
const struct bpf_reg_state *reg)
{
enum bpf_reg_type t;
const char *sep = "";
t = reg->type;
if (t == SCALAR_VALUE && reg->precise)
verbose(env, "P");
if (t == SCALAR_VALUE && tnum_is_const(reg->var_off)) {
/* reg->off should be 0 for SCALAR_VALUE */
verbose_snum(env, reg->var_off.value + reg->off);
return;
}
/*
* _a stands for append, was shortened to avoid multiline statements below.
* This macro is used to output a comma separated list of attributes.
*/
#define verbose_a(fmt, ...) ({ verbose(env, "%s" fmt, sep, ##__VA_ARGS__); sep = ","; })
verbose(env, "%s", reg_type_str(env, t));
if (t == PTR_TO_STACK) {
if (state->frameno != reg->frameno)
verbose(env, "[%d]", reg->frameno);
if (tnum_is_const(reg->var_off)) {
verbose_snum(env, reg->var_off.value + reg->off);
return;
}
}
if (base_type(t) == PTR_TO_BTF_ID)
verbose(env, "%s", btf_type_name(reg->btf, reg->btf_id));
verbose(env, "(");
if (reg->id)
verbose_a("id=%d", reg->id);
if (reg->ref_obj_id)
verbose_a("ref_obj_id=%d", reg->ref_obj_id);
if (type_is_non_owning_ref(reg->type))
verbose_a("%s", "non_own_ref");
if (type_is_map_ptr(t)) {
if (reg->map_ptr->name[0])
verbose_a("map=%s", reg->map_ptr->name);
verbose_a("ks=%d,vs=%d",
reg->map_ptr->key_size,
reg->map_ptr->value_size);
}
if (t != SCALAR_VALUE && reg->off) {
verbose_a("off=");
verbose_snum(env, reg->off);
}
if (type_is_pkt_pointer(t)) {
verbose_a("r=");
verbose_unum(env, reg->range);
}
if (tnum_is_const(reg->var_off)) {
/* a pointer register with fixed offset */
if (reg->var_off.value) {
verbose_a("imm=");
verbose_snum(env, reg->var_off.value);
}
} else {
print_scalar_ranges(env, reg, &sep);
if (!tnum_is_unknown(reg->var_off)) {
char tn_buf[48];
tnum_strn(tn_buf, sizeof(tn_buf), reg->var_off);
verbose_a("var_off=%s", tn_buf);
}
}
verbose(env, ")");
#undef verbose_a
}
void print_verifier_state(struct bpf_verifier_env *env, const struct bpf_func_state *state,
bool print_all)
{
const struct bpf_reg_state *reg;
int i;
if (state->frameno)
verbose(env, " frame%d:", state->frameno);
for (i = 0; i < MAX_BPF_REG; i++) {
reg = &state->regs[i];
if (reg->type == NOT_INIT)
continue;
if (!print_all && !reg_scratched(env, i))
continue;
verbose(env, " R%d", i);
print_liveness(env, reg->live);
verbose(env, "=");
print_reg_state(env, state, reg);
}
for (i = 0; i < state->allocated_stack / BPF_REG_SIZE; i++) {
char types_buf[BPF_REG_SIZE + 1];
bool valid = false;
u8 slot_type;
int j;
if (!print_all && !stack_slot_scratched(env, i))
continue;
for (j = 0; j < BPF_REG_SIZE; j++) {
slot_type = state->stack[i].slot_type[j];
if (slot_type != STACK_INVALID)
valid = true;
types_buf[j] = slot_type_char[slot_type];
}
types_buf[BPF_REG_SIZE] = 0;
if (!valid)
continue;
reg = &state->stack[i].spilled_ptr;
switch (state->stack[i].slot_type[BPF_REG_SIZE - 1]) {
case STACK_SPILL:
/* print MISC/ZERO/INVALID slots above subreg spill */
for (j = 0; j < BPF_REG_SIZE; j++)
if (state->stack[i].slot_type[j] == STACK_SPILL)
break;
types_buf[j] = '\0';
verbose(env, " fp%d", (-i - 1) * BPF_REG_SIZE);
print_liveness(env, reg->live);
verbose(env, "=%s", types_buf);
print_reg_state(env, state, reg);
break;
case STACK_DYNPTR:
/* skip to main dynptr slot */
i += BPF_DYNPTR_NR_SLOTS - 1;
reg = &state->stack[i].spilled_ptr;
verbose(env, " fp%d", (-i - 1) * BPF_REG_SIZE);
print_liveness(env, reg->live);
verbose(env, "=dynptr_%s", dynptr_type_str(reg->dynptr.type));
if (reg->ref_obj_id)
verbose(env, "(ref_id=%d)", reg->ref_obj_id);
break;
case STACK_ITER:
/* only main slot has ref_obj_id set; skip others */
if (!reg->ref_obj_id)
continue;
verbose(env, " fp%d", (-i - 1) * BPF_REG_SIZE);
print_liveness(env, reg->live);
verbose(env, "=iter_%s(ref_id=%d,state=%s,depth=%u)",
iter_type_str(reg->iter.btf, reg->iter.btf_id),
reg->ref_obj_id, iter_state_str(reg->iter.state),
reg->iter.depth);
break;
case STACK_MISC:
case STACK_ZERO:
default:
verbose(env, " fp%d", (-i - 1) * BPF_REG_SIZE);
print_liveness(env, reg->live);
verbose(env, "=%s", types_buf);
break;
}
}
if (state->acquired_refs && state->refs[0].id) {
verbose(env, " refs=%d", state->refs[0].id);
for (i = 1; i < state->acquired_refs; i++)
if (state->refs[i].id)
verbose(env, ",%d", state->refs[i].id);
}
if (state->in_callback_fn)
verbose(env, " cb");
if (state->in_async_callback_fn)
verbose(env, " async_cb");
verbose(env, "\n");
if (!print_all)
mark_verifier_state_clean(env);
}
static inline u32 vlog_alignment(u32 pos)
{
return round_up(max(pos + BPF_LOG_MIN_ALIGNMENT / 2, BPF_LOG_ALIGNMENT),
BPF_LOG_MIN_ALIGNMENT) - pos - 1;
}
void print_insn_state(struct bpf_verifier_env *env, const struct bpf_func_state *state)
{
if (env->prev_log_pos && env->prev_log_pos == env->log.end_pos) {
/* remove new line character */
bpf_vlog_reset(&env->log, env->prev_log_pos - 1);
verbose(env, "%*c;", vlog_alignment(env->prev_insn_print_pos), ' ');
} else {
verbose(env, "%d:", env->insn_idx);
}
print_verifier_state(env, state, false);
}
...@@ -337,27 +337,6 @@ struct btf *btf_vmlinux; ...@@ -337,27 +337,6 @@ struct btf *btf_vmlinux;
static DEFINE_MUTEX(bpf_verifier_lock); static DEFINE_MUTEX(bpf_verifier_lock);
static const struct bpf_line_info *
find_linfo(const struct bpf_verifier_env *env, u32 insn_off)
{
const struct bpf_line_info *linfo;
const struct bpf_prog *prog;
u32 i, nr_linfo;
prog = env->prog;
nr_linfo = prog->aux->nr_linfo;
if (!nr_linfo || insn_off >= prog->len)
return NULL;
linfo = prog->aux->linfo;
for (i = 1; i < nr_linfo; i++)
if (insn_off < linfo[i].insn_off)
break;
return &linfo[i - 1];
}
__printf(2, 3) static void verbose(void *private_data, const char *fmt, ...) __printf(2, 3) static void verbose(void *private_data, const char *fmt, ...)
{ {
struct bpf_verifier_env *env = private_data; struct bpf_verifier_env *env = private_data;
...@@ -371,42 +350,6 @@ __printf(2, 3) static void verbose(void *private_data, const char *fmt, ...) ...@@ -371,42 +350,6 @@ __printf(2, 3) static void verbose(void *private_data, const char *fmt, ...)
va_end(args); va_end(args);
} }
static const char *ltrim(const char *s)
{
while (isspace(*s))
s++;
return s;
}
__printf(3, 4) static void verbose_linfo(struct bpf_verifier_env *env,
u32 insn_off,
const char *prefix_fmt, ...)
{
const struct bpf_line_info *linfo;
if (!bpf_verifier_log_needed(&env->log))
return;
linfo = find_linfo(env, insn_off);
if (!linfo || linfo == env->prev_linfo)
return;
if (prefix_fmt) {
va_list args;
va_start(args, prefix_fmt);
bpf_verifier_vlog(&env->log, prefix_fmt, args);
va_end(args);
}
verbose(env, "%s\n",
ltrim(btf_name_by_offset(env->prog->aux->btf,
linfo->line_off)));
env->prev_linfo = linfo;
}
static void verbose_invalid_scalar(struct bpf_verifier_env *env, static void verbose_invalid_scalar(struct bpf_verifier_env *env,
struct bpf_reg_state *reg, struct bpf_reg_state *reg,
struct tnum *range, const char *ctx, struct tnum *range, const char *ctx,
...@@ -425,21 +368,6 @@ static void verbose_invalid_scalar(struct bpf_verifier_env *env, ...@@ -425,21 +368,6 @@ static void verbose_invalid_scalar(struct bpf_verifier_env *env,
verbose(env, " should have been in %s\n", tn_buf); verbose(env, " should have been in %s\n", tn_buf);
} }
static bool type_is_pkt_pointer(enum bpf_reg_type type)
{
type = base_type(type);
return type == PTR_TO_PACKET ||
type == PTR_TO_PACKET_META;
}
static bool type_is_sk_pointer(enum bpf_reg_type type)
{
return type == PTR_TO_SOCKET ||
type == PTR_TO_SOCK_COMMON ||
type == PTR_TO_TCP_SOCK ||
type == PTR_TO_XDP_SOCK;
}
static bool type_may_be_null(u32 type) static bool type_may_be_null(u32 type)
{ {
return type & PTR_MAYBE_NULL; return type & PTR_MAYBE_NULL;
...@@ -463,16 +391,6 @@ static bool reg_not_null(const struct bpf_reg_state *reg) ...@@ -463,16 +391,6 @@ static bool reg_not_null(const struct bpf_reg_state *reg)
type == PTR_TO_MEM; type == PTR_TO_MEM;
} }
static bool type_is_ptr_alloc_obj(u32 type)
{
return base_type(type) == PTR_TO_BTF_ID && type_flag(type) & MEM_ALLOC;
}
static bool type_is_non_owning_ref(u32 type)
{
return type_is_ptr_alloc_obj(type) && type_flag(type) & NON_OWN_REF;
}
static struct btf_record *reg_btf_record(const struct bpf_reg_state *reg) static struct btf_record *reg_btf_record(const struct bpf_reg_state *reg)
{ {
struct btf_record *rec = NULL; struct btf_record *rec = NULL;
...@@ -589,83 +507,6 @@ static bool is_cmpxchg_insn(const struct bpf_insn *insn) ...@@ -589,83 +507,6 @@ static bool is_cmpxchg_insn(const struct bpf_insn *insn)
insn->imm == BPF_CMPXCHG; insn->imm == BPF_CMPXCHG;
} }
/* string representation of 'enum bpf_reg_type'
*
* Note that reg_type_str() can not appear more than once in a single verbose()
* statement.
*/
static const char *reg_type_str(struct bpf_verifier_env *env,
enum bpf_reg_type type)
{
char postfix[16] = {0}, prefix[64] = {0};
static const char * const str[] = {
[NOT_INIT] = "?",
[SCALAR_VALUE] = "scalar",
[PTR_TO_CTX] = "ctx",
[CONST_PTR_TO_MAP] = "map_ptr",
[PTR_TO_MAP_VALUE] = "map_value",
[PTR_TO_STACK] = "fp",
[PTR_TO_PACKET] = "pkt",
[PTR_TO_PACKET_META] = "pkt_meta",
[PTR_TO_PACKET_END] = "pkt_end",
[PTR_TO_FLOW_KEYS] = "flow_keys",
[PTR_TO_SOCKET] = "sock",
[PTR_TO_SOCK_COMMON] = "sock_common",
[PTR_TO_TCP_SOCK] = "tcp_sock",
[PTR_TO_TP_BUFFER] = "tp_buffer",
[PTR_TO_XDP_SOCK] = "xdp_sock",
[PTR_TO_BTF_ID] = "ptr_",
[PTR_TO_MEM] = "mem",
[PTR_TO_BUF] = "buf",
[PTR_TO_FUNC] = "func",
[PTR_TO_MAP_KEY] = "map_key",
[CONST_PTR_TO_DYNPTR] = "dynptr_ptr",
};
if (type & PTR_MAYBE_NULL) {
if (base_type(type) == PTR_TO_BTF_ID)
strncpy(postfix, "or_null_", 16);
else
strncpy(postfix, "_or_null", 16);
}
snprintf(prefix, sizeof(prefix), "%s%s%s%s%s%s%s",
type & MEM_RDONLY ? "rdonly_" : "",
type & MEM_RINGBUF ? "ringbuf_" : "",
type & MEM_USER ? "user_" : "",
type & MEM_PERCPU ? "percpu_" : "",
type & MEM_RCU ? "rcu_" : "",
type & PTR_UNTRUSTED ? "untrusted_" : "",
type & PTR_TRUSTED ? "trusted_" : ""
);
snprintf(env->tmp_str_buf, TMP_STR_BUF_LEN, "%s%s%s",
prefix, str[base_type(type)], postfix);
return env->tmp_str_buf;
}
static char slot_type_char[] = {
[STACK_INVALID] = '?',
[STACK_SPILL] = 'r',
[STACK_MISC] = 'm',
[STACK_ZERO] = '0',
[STACK_DYNPTR] = 'd',
[STACK_ITER] = 'i',
};
static void print_liveness(struct bpf_verifier_env *env,
enum bpf_reg_liveness live)
{
if (live & (REG_LIVE_READ | REG_LIVE_WRITTEN | REG_LIVE_DONE))
verbose(env, "_");
if (live & REG_LIVE_READ)
verbose(env, "r");
if (live & REG_LIVE_WRITTEN)
verbose(env, "w");
if (live & REG_LIVE_DONE)
verbose(env, "D");
}
static int __get_spi(s32 off) static int __get_spi(s32 off)
{ {
return (-off - 1) / BPF_REG_SIZE; return (-off - 1) / BPF_REG_SIZE;
...@@ -735,87 +576,6 @@ static const char *btf_type_name(const struct btf *btf, u32 id) ...@@ -735,87 +576,6 @@ static const char *btf_type_name(const struct btf *btf, u32 id)
return btf_name_by_offset(btf, btf_type_by_id(btf, id)->name_off); return btf_name_by_offset(btf, btf_type_by_id(btf, id)->name_off);
} }
static const char *dynptr_type_str(enum bpf_dynptr_type type)
{
switch (type) {
case BPF_DYNPTR_TYPE_LOCAL:
return "local";
case BPF_DYNPTR_TYPE_RINGBUF:
return "ringbuf";
case BPF_DYNPTR_TYPE_SKB:
return "skb";
case BPF_DYNPTR_TYPE_XDP:
return "xdp";
case BPF_DYNPTR_TYPE_INVALID:
return "<invalid>";
default:
WARN_ONCE(1, "unknown dynptr type %d\n", type);
return "<unknown>";
}
}
static const char *iter_type_str(const struct btf *btf, u32 btf_id)
{
if (!btf || btf_id == 0)
return "<invalid>";
/* we already validated that type is valid and has conforming name */
return btf_type_name(btf, btf_id) + sizeof(ITER_PREFIX) - 1;
}
static const char *iter_state_str(enum bpf_iter_state state)
{
switch (state) {
case BPF_ITER_STATE_ACTIVE:
return "active";
case BPF_ITER_STATE_DRAINED:
return "drained";
case BPF_ITER_STATE_INVALID:
return "<invalid>";
default:
WARN_ONCE(1, "unknown iter state %d\n", state);
return "<unknown>";
}
}
static void mark_reg_scratched(struct bpf_verifier_env *env, u32 regno)
{
env->scratched_regs |= 1U << regno;
}
static void mark_stack_slot_scratched(struct bpf_verifier_env *env, u32 spi)
{
env->scratched_stack_slots |= 1ULL << spi;
}
static bool reg_scratched(const struct bpf_verifier_env *env, u32 regno)
{
return (env->scratched_regs >> regno) & 1;
}
static bool stack_slot_scratched(const struct bpf_verifier_env *env, u64 regno)
{
return (env->scratched_stack_slots >> regno) & 1;
}
static bool verifier_state_scratched(const struct bpf_verifier_env *env)
{
return env->scratched_regs || env->scratched_stack_slots;
}
static void mark_verifier_state_clean(struct bpf_verifier_env *env)
{
env->scratched_regs = 0U;
env->scratched_stack_slots = 0ULL;
}
/* Used for printing the entire verifier state. */
static void mark_verifier_state_scratched(struct bpf_verifier_env *env)
{
env->scratched_regs = ~0U;
env->scratched_stack_slots = ~0ULL;
}
static enum bpf_dynptr_type arg_to_dynptr_type(enum bpf_arg_type arg_type) static enum bpf_dynptr_type arg_to_dynptr_type(enum bpf_arg_type arg_type)
{ {
switch (arg_type & DYNPTR_TYPE_FLAG_MASK) { switch (arg_type & DYNPTR_TYPE_FLAG_MASK) {
...@@ -1355,226 +1115,6 @@ static void scrub_spilled_slot(u8 *stype) ...@@ -1355,226 +1115,6 @@ static void scrub_spilled_slot(u8 *stype)
*stype = STACK_MISC; *stype = STACK_MISC;
} }
static void print_scalar_ranges(struct bpf_verifier_env *env,
const struct bpf_reg_state *reg,
const char **sep)
{
struct {
const char *name;
u64 val;
bool omit;
} minmaxs[] = {
{"smin", reg->smin_value, reg->smin_value == S64_MIN},
{"smax", reg->smax_value, reg->smax_value == S64_MAX},
{"umin", reg->umin_value, reg->umin_value == 0},
{"umax", reg->umax_value, reg->umax_value == U64_MAX},
{"smin32", (s64)reg->s32_min_value, reg->s32_min_value == S32_MIN},
{"smax32", (s64)reg->s32_max_value, reg->s32_max_value == S32_MAX},
{"umin32", reg->u32_min_value, reg->u32_min_value == 0},
{"umax32", reg->u32_max_value, reg->u32_max_value == U32_MAX},
}, *m1, *m2, *mend = &minmaxs[ARRAY_SIZE(minmaxs)];
bool neg1, neg2;
for (m1 = &minmaxs[0]; m1 < mend; m1++) {
if (m1->omit)
continue;
neg1 = m1->name[0] == 's' && (s64)m1->val < 0;
verbose(env, "%s%s=", *sep, m1->name);
*sep = ",";
for (m2 = m1 + 2; m2 < mend; m2 += 2) {
if (m2->omit || m2->val != m1->val)
continue;
/* don't mix negatives with positives */
neg2 = m2->name[0] == 's' && (s64)m2->val < 0;
if (neg2 != neg1)
continue;
m2->omit = true;
verbose(env, "%s=", m2->name);
}
verbose(env, m1->name[0] == 's' ? "%lld" : "%llu", m1->val);
}
}
static void print_verifier_state(struct bpf_verifier_env *env,
const struct bpf_func_state *state,
bool print_all)
{
const struct bpf_reg_state *reg;
enum bpf_reg_type t;
int i;
if (state->frameno)
verbose(env, " frame%d:", state->frameno);
for (i = 0; i < MAX_BPF_REG; i++) {
reg = &state->regs[i];
t = reg->type;
if (t == NOT_INIT)
continue;
if (!print_all && !reg_scratched(env, i))
continue;
verbose(env, " R%d", i);
print_liveness(env, reg->live);
verbose(env, "=");
if (t == SCALAR_VALUE && reg->precise)
verbose(env, "P");
if ((t == SCALAR_VALUE || t == PTR_TO_STACK) &&
tnum_is_const(reg->var_off)) {
/* reg->off should be 0 for SCALAR_VALUE */
verbose(env, "%s", t == SCALAR_VALUE ? "" : reg_type_str(env, t));
verbose(env, "%lld", reg->var_off.value + reg->off);
} else {
const char *sep = "";
verbose(env, "%s", reg_type_str(env, t));
if (base_type(t) == PTR_TO_BTF_ID)
verbose(env, "%s", btf_type_name(reg->btf, reg->btf_id));
verbose(env, "(");
/*
* _a stands for append, was shortened to avoid multiline statements below.
* This macro is used to output a comma separated list of attributes.
*/
#define verbose_a(fmt, ...) ({ verbose(env, "%s" fmt, sep, __VA_ARGS__); sep = ","; })
if (reg->id)
verbose_a("id=%d", reg->id);
if (reg->ref_obj_id)
verbose_a("ref_obj_id=%d", reg->ref_obj_id);
if (type_is_non_owning_ref(reg->type))
verbose_a("%s", "non_own_ref");
if (t != SCALAR_VALUE)
verbose_a("off=%d", reg->off);
if (type_is_pkt_pointer(t))
verbose_a("r=%d", reg->range);
else if (base_type(t) == CONST_PTR_TO_MAP ||
base_type(t) == PTR_TO_MAP_KEY ||
base_type(t) == PTR_TO_MAP_VALUE)
verbose_a("ks=%d,vs=%d",
reg->map_ptr->key_size,
reg->map_ptr->value_size);
if (tnum_is_const(reg->var_off)) {
/* Typically an immediate SCALAR_VALUE, but
* could be a pointer whose offset is too big
* for reg->off
*/
verbose_a("imm=%llx", reg->var_off.value);
} else {
print_scalar_ranges(env, reg, &sep);
if (!tnum_is_unknown(reg->var_off)) {
char tn_buf[48];
tnum_strn(tn_buf, sizeof(tn_buf), reg->var_off);
verbose_a("var_off=%s", tn_buf);
}
}
#undef verbose_a
verbose(env, ")");
}
}
for (i = 0; i < state->allocated_stack / BPF_REG_SIZE; i++) {
char types_buf[BPF_REG_SIZE + 1];
bool valid = false;
int j;
for (j = 0; j < BPF_REG_SIZE; j++) {
if (state->stack[i].slot_type[j] != STACK_INVALID)
valid = true;
types_buf[j] = slot_type_char[state->stack[i].slot_type[j]];
}
types_buf[BPF_REG_SIZE] = 0;
if (!valid)
continue;
if (!print_all && !stack_slot_scratched(env, i))
continue;
switch (state->stack[i].slot_type[BPF_REG_SIZE - 1]) {
case STACK_SPILL:
reg = &state->stack[i].spilled_ptr;
t = reg->type;
verbose(env, " fp%d", (-i - 1) * BPF_REG_SIZE);
print_liveness(env, reg->live);
verbose(env, "=%s", t == SCALAR_VALUE ? "" : reg_type_str(env, t));
if (t == SCALAR_VALUE && reg->precise)
verbose(env, "P");
if (t == SCALAR_VALUE && tnum_is_const(reg->var_off))
verbose(env, "%lld", reg->var_off.value + reg->off);
break;
case STACK_DYNPTR:
i += BPF_DYNPTR_NR_SLOTS - 1;
reg = &state->stack[i].spilled_ptr;
verbose(env, " fp%d", (-i - 1) * BPF_REG_SIZE);
print_liveness(env, reg->live);
verbose(env, "=dynptr_%s", dynptr_type_str(reg->dynptr.type));
if (reg->ref_obj_id)
verbose(env, "(ref_id=%d)", reg->ref_obj_id);
break;
case STACK_ITER:
/* only main slot has ref_obj_id set; skip others */
reg = &state->stack[i].spilled_ptr;
if (!reg->ref_obj_id)
continue;
verbose(env, " fp%d", (-i - 1) * BPF_REG_SIZE);
print_liveness(env, reg->live);
verbose(env, "=iter_%s(ref_id=%d,state=%s,depth=%u)",
iter_type_str(reg->iter.btf, reg->iter.btf_id),
reg->ref_obj_id, iter_state_str(reg->iter.state),
reg->iter.depth);
break;
case STACK_MISC:
case STACK_ZERO:
default:
reg = &state->stack[i].spilled_ptr;
for (j = 0; j < BPF_REG_SIZE; j++)
types_buf[j] = slot_type_char[state->stack[i].slot_type[j]];
types_buf[BPF_REG_SIZE] = 0;
verbose(env, " fp%d", (-i - 1) * BPF_REG_SIZE);
print_liveness(env, reg->live);
verbose(env, "=%s", types_buf);
break;
}
}
if (state->acquired_refs && state->refs[0].id) {
verbose(env, " refs=%d", state->refs[0].id);
for (i = 1; i < state->acquired_refs; i++)
if (state->refs[i].id)
verbose(env, ",%d", state->refs[i].id);
}
if (state->in_callback_fn)
verbose(env, " cb");
if (state->in_async_callback_fn)
verbose(env, " async_cb");
verbose(env, "\n");
if (!print_all)
mark_verifier_state_clean(env);
}
static inline u32 vlog_alignment(u32 pos)
{
return round_up(max(pos + BPF_LOG_MIN_ALIGNMENT / 2, BPF_LOG_ALIGNMENT),
BPF_LOG_MIN_ALIGNMENT) - pos - 1;
}
static void print_insn_state(struct bpf_verifier_env *env,
const struct bpf_func_state *state)
{
if (env->prev_log_pos && env->prev_log_pos == env->log.end_pos) {
/* remove new line character */
bpf_vlog_reset(&env->log, env->prev_log_pos - 1);
verbose(env, "%*c;", vlog_alignment(env->prev_insn_print_pos), ' ');
} else {
verbose(env, "%d:", env->insn_idx);
}
print_verifier_state(env, state, false);
}
/* copy array src of length n * size bytes to dst. dst is reallocated if it's too /* copy array src of length n * size bytes to dst. dst is reallocated if it's too
* small to hold src. This is different from krealloc since we don't want to preserve * small to hold src. This is different from krealloc since we don't want to preserve
* the contents of dst. * the contents of dst.
......
...@@ -40,7 +40,7 @@ static struct bpf_align_test tests[] = { ...@@ -40,7 +40,7 @@ static struct bpf_align_test tests[] = {
}, },
.prog_type = BPF_PROG_TYPE_SCHED_CLS, .prog_type = BPF_PROG_TYPE_SCHED_CLS,
.matches = { .matches = {
{0, "R1", "ctx(off=0,imm=0)"}, {0, "R1", "ctx()"},
{0, "R10", "fp0"}, {0, "R10", "fp0"},
{0, "R3_w", "2"}, {0, "R3_w", "2"},
{1, "R3_w", "4"}, {1, "R3_w", "4"},
...@@ -68,7 +68,7 @@ static struct bpf_align_test tests[] = { ...@@ -68,7 +68,7 @@ static struct bpf_align_test tests[] = {
}, },
.prog_type = BPF_PROG_TYPE_SCHED_CLS, .prog_type = BPF_PROG_TYPE_SCHED_CLS,
.matches = { .matches = {
{0, "R1", "ctx(off=0,imm=0)"}, {0, "R1", "ctx()"},
{0, "R10", "fp0"}, {0, "R10", "fp0"},
{0, "R3_w", "1"}, {0, "R3_w", "1"},
{1, "R3_w", "2"}, {1, "R3_w", "2"},
...@@ -97,7 +97,7 @@ static struct bpf_align_test tests[] = { ...@@ -97,7 +97,7 @@ static struct bpf_align_test tests[] = {
}, },
.prog_type = BPF_PROG_TYPE_SCHED_CLS, .prog_type = BPF_PROG_TYPE_SCHED_CLS,
.matches = { .matches = {
{0, "R1", "ctx(off=0,imm=0)"}, {0, "R1", "ctx()"},
{0, "R10", "fp0"}, {0, "R10", "fp0"},
{0, "R3_w", "4"}, {0, "R3_w", "4"},
{1, "R3_w", "8"}, {1, "R3_w", "8"},
...@@ -119,7 +119,7 @@ static struct bpf_align_test tests[] = { ...@@ -119,7 +119,7 @@ static struct bpf_align_test tests[] = {
}, },
.prog_type = BPF_PROG_TYPE_SCHED_CLS, .prog_type = BPF_PROG_TYPE_SCHED_CLS,
.matches = { .matches = {
{0, "R1", "ctx(off=0,imm=0)"}, {0, "R1", "ctx()"},
{0, "R10", "fp0"}, {0, "R10", "fp0"},
{0, "R3_w", "7"}, {0, "R3_w", "7"},
{1, "R3_w", "7"}, {1, "R3_w", "7"},
...@@ -162,13 +162,13 @@ static struct bpf_align_test tests[] = { ...@@ -162,13 +162,13 @@ static struct bpf_align_test tests[] = {
}, },
.prog_type = BPF_PROG_TYPE_SCHED_CLS, .prog_type = BPF_PROG_TYPE_SCHED_CLS,
.matches = { .matches = {
{6, "R0_w", "pkt(off=8,r=8,imm=0)"}, {6, "R0_w", "pkt(off=8,r=8)"},
{6, "R3_w", "var_off=(0x0; 0xff)"}, {6, "R3_w", "var_off=(0x0; 0xff)"},
{7, "R3_w", "var_off=(0x0; 0x1fe)"}, {7, "R3_w", "var_off=(0x0; 0x1fe)"},
{8, "R3_w", "var_off=(0x0; 0x3fc)"}, {8, "R3_w", "var_off=(0x0; 0x3fc)"},
{9, "R3_w", "var_off=(0x0; 0x7f8)"}, {9, "R3_w", "var_off=(0x0; 0x7f8)"},
{10, "R3_w", "var_off=(0x0; 0xff0)"}, {10, "R3_w", "var_off=(0x0; 0xff0)"},
{12, "R3_w", "pkt_end(off=0,imm=0)"}, {12, "R3_w", "pkt_end()"},
{17, "R4_w", "var_off=(0x0; 0xff)"}, {17, "R4_w", "var_off=(0x0; 0xff)"},
{18, "R4_w", "var_off=(0x0; 0x1fe0)"}, {18, "R4_w", "var_off=(0x0; 0x1fe0)"},
{19, "R4_w", "var_off=(0x0; 0xff0)"}, {19, "R4_w", "var_off=(0x0; 0xff0)"},
...@@ -235,11 +235,11 @@ static struct bpf_align_test tests[] = { ...@@ -235,11 +235,11 @@ static struct bpf_align_test tests[] = {
}, },
.prog_type = BPF_PROG_TYPE_SCHED_CLS, .prog_type = BPF_PROG_TYPE_SCHED_CLS,
.matches = { .matches = {
{2, "R5_w", "pkt(off=0,r=0,imm=0)"}, {2, "R5_w", "pkt(r=0)"},
{4, "R5_w", "pkt(off=14,r=0,imm=0)"}, {4, "R5_w", "pkt(off=14,r=0)"},
{5, "R4_w", "pkt(off=14,r=0,imm=0)"}, {5, "R4_w", "pkt(off=14,r=0)"},
{9, "R2", "pkt(off=0,r=18,imm=0)"}, {9, "R2", "pkt(r=18)"},
{10, "R5", "pkt(off=14,r=18,imm=0)"}, {10, "R5", "pkt(off=14,r=18)"},
{10, "R4_w", "var_off=(0x0; 0xff)"}, {10, "R4_w", "var_off=(0x0; 0xff)"},
{13, "R4_w", "var_off=(0x0; 0xffff)"}, {13, "R4_w", "var_off=(0x0; 0xffff)"},
{14, "R4_w", "var_off=(0x0; 0xffff)"}, {14, "R4_w", "var_off=(0x0; 0xffff)"},
...@@ -299,7 +299,7 @@ static struct bpf_align_test tests[] = { ...@@ -299,7 +299,7 @@ static struct bpf_align_test tests[] = {
/* Calculated offset in R6 has unknown value, but known /* Calculated offset in R6 has unknown value, but known
* alignment of 4. * alignment of 4.
*/ */
{6, "R2_w", "pkt(off=0,r=8,imm=0)"}, {6, "R2_w", "pkt(r=8)"},
{7, "R6_w", "var_off=(0x0; 0x3fc)"}, {7, "R6_w", "var_off=(0x0; 0x3fc)"},
/* Offset is added to packet pointer R5, resulting in /* Offset is added to packet pointer R5, resulting in
* known fixed offset, and variable offset from R6. * known fixed offset, and variable offset from R6.
...@@ -337,7 +337,7 @@ static struct bpf_align_test tests[] = { ...@@ -337,7 +337,7 @@ static struct bpf_align_test tests[] = {
/* Constant offset is added to R5 packet pointer, /* Constant offset is added to R5 packet pointer,
* resulting in reg->off value of 14. * resulting in reg->off value of 14.
*/ */
{26, "R5_w", "pkt(off=14,r=8,"}, {26, "R5_w", "pkt(off=14,r=8)"},
/* Variable offset is added to R5, resulting in a /* Variable offset is added to R5, resulting in a
* variable offset of (4n). See comment for insn #18 * variable offset of (4n). See comment for insn #18
* for R4 = R5 trick. * for R4 = R5 trick.
...@@ -397,7 +397,7 @@ static struct bpf_align_test tests[] = { ...@@ -397,7 +397,7 @@ static struct bpf_align_test tests[] = {
/* Calculated offset in R6 has unknown value, but known /* Calculated offset in R6 has unknown value, but known
* alignment of 4. * alignment of 4.
*/ */
{6, "R2_w", "pkt(off=0,r=8,imm=0)"}, {6, "R2_w", "pkt(r=8)"},
{7, "R6_w", "var_off=(0x0; 0x3fc)"}, {7, "R6_w", "var_off=(0x0; 0x3fc)"},
/* Adding 14 makes R6 be (4n+2) */ /* Adding 14 makes R6 be (4n+2) */
{8, "R6_w", "var_off=(0x2; 0x7fc)"}, {8, "R6_w", "var_off=(0x2; 0x7fc)"},
...@@ -459,7 +459,7 @@ static struct bpf_align_test tests[] = { ...@@ -459,7 +459,7 @@ static struct bpf_align_test tests[] = {
.prog_type = BPF_PROG_TYPE_SCHED_CLS, .prog_type = BPF_PROG_TYPE_SCHED_CLS,
.result = REJECT, .result = REJECT,
.matches = { .matches = {
{3, "R5_w", "pkt_end(off=0,imm=0)"}, {3, "R5_w", "pkt_end()"},
/* (ptr - ptr) << 2 == unknown, (4n) */ /* (ptr - ptr) << 2 == unknown, (4n) */
{5, "R5_w", "var_off=(0x0; 0xfffffffffffffffc)"}, {5, "R5_w", "var_off=(0x0; 0xfffffffffffffffc)"},
/* (4n) + 14 == (4n+2). We blow our bounds, because /* (4n) + 14 == (4n+2). We blow our bounds, because
...@@ -513,7 +513,7 @@ static struct bpf_align_test tests[] = { ...@@ -513,7 +513,7 @@ static struct bpf_align_test tests[] = {
/* Calculated offset in R6 has unknown value, but known /* Calculated offset in R6 has unknown value, but known
* alignment of 4. * alignment of 4.
*/ */
{6, "R2_w", "pkt(off=0,r=8,imm=0)"}, {6, "R2_w", "pkt(r=8)"},
{8, "R6_w", "var_off=(0x0; 0x3fc)"}, {8, "R6_w", "var_off=(0x0; 0x3fc)"},
/* Adding 14 makes R6 be (4n+2) */ /* Adding 14 makes R6 be (4n+2) */
{9, "R6_w", "var_off=(0x2; 0x7fc)"}, {9, "R6_w", "var_off=(0x2; 0x7fc)"},
...@@ -566,7 +566,7 @@ static struct bpf_align_test tests[] = { ...@@ -566,7 +566,7 @@ static struct bpf_align_test tests[] = {
/* Calculated offset in R6 has unknown value, but known /* Calculated offset in R6 has unknown value, but known
* alignment of 4. * alignment of 4.
*/ */
{6, "R2_w", "pkt(off=0,r=8,imm=0)"}, {6, "R2_w", "pkt(r=8)"},
{9, "R6_w", "var_off=(0x0; 0x3c)"}, {9, "R6_w", "var_off=(0x0; 0x3c)"},
/* Adding 14 makes R6 be (4n+2) */ /* Adding 14 makes R6 be (4n+2) */
{10, "R6_w", "var_off=(0x2; 0x7c)"}, {10, "R6_w", "var_off=(0x2; 0x7c)"},
...@@ -659,14 +659,14 @@ static int do_test_single(struct bpf_align_test *test) ...@@ -659,14 +659,14 @@ static int do_test_single(struct bpf_align_test *test)
/* Check the next line as well in case the previous line /* Check the next line as well in case the previous line
* did not have a corresponding bpf insn. Example: * did not have a corresponding bpf insn. Example:
* func#0 @0 * func#0 @0
* 0: R1=ctx(off=0,imm=0) R10=fp0 * 0: R1=ctx() R10=fp0
* 0: (b7) r3 = 2 ; R3_w=2 * 0: (b7) r3 = 2 ; R3_w=2
* *
* Sometimes it's actually two lines below, e.g. when * Sometimes it's actually two lines below, e.g. when
* searching for "6: R3_w=scalar(umax=255,var_off=(0x0; 0xff))": * searching for "6: R3_w=scalar(umax=255,var_off=(0x0; 0xff))":
* from 4 to 6: R0_w=pkt(off=8,r=8,imm=0) R1=ctx(off=0,imm=0) R2_w=pkt(off=0,r=8,imm=0) R3_w=pkt_end(off=0,imm=0) R10=fp0 * from 4 to 6: R0_w=pkt(off=8,r=8) R1=ctx() R2_w=pkt(r=8) R3_w=pkt_end() R10=fp0
* 6: R0_w=pkt(off=8,r=8,imm=0) R1=ctx(off=0,imm=0) R2_w=pkt(off=0,r=8,imm=0) R3_w=pkt_end(off=0,imm=0) R10=fp0 * 6: R0_w=pkt(off=8,r=8) R1=ctx() R2_w=pkt(r=8) R3_w=pkt_end() R10=fp0
* 6: (71) r3 = *(u8 *)(r2 +0) ; R2_w=pkt(off=0,r=8,imm=0) R3_w=scalar(umax=255,var_off=(0x0; 0xff)) * 6: (71) r3 = *(u8 *)(r2 +0) ; R2_w=pkt(r=8) R3_w=scalar(umax=255,var_off=(0x0; 0xff))
*/ */
while (!(p = strstr(line_ptr, m.reg)) || !strstr(p, m.match)) { while (!(p = strstr(line_ptr, m.reg)) || !strstr(p, m.match)) {
cur_line = -1; cur_line = -1;
......
...@@ -78,7 +78,7 @@ static void obj_load_log_buf(void) ...@@ -78,7 +78,7 @@ static void obj_load_log_buf(void)
ASSERT_OK_PTR(strstr(libbpf_log_buf, "prog 'bad_prog': BPF program load failed"), ASSERT_OK_PTR(strstr(libbpf_log_buf, "prog 'bad_prog': BPF program load failed"),
"libbpf_log_not_empty"); "libbpf_log_not_empty");
ASSERT_OK_PTR(strstr(obj_log_buf, "DATASEC license"), "obj_log_not_empty"); ASSERT_OK_PTR(strstr(obj_log_buf, "DATASEC license"), "obj_log_not_empty");
ASSERT_OK_PTR(strstr(good_log_buf, "0: R1=ctx(off=0,imm=0) R10=fp0"), ASSERT_OK_PTR(strstr(good_log_buf, "0: R1=ctx() R10=fp0"),
"good_log_verbose"); "good_log_verbose");
ASSERT_OK_PTR(strstr(bad_log_buf, "invalid access to map value, value_size=16 off=16000 size=4"), ASSERT_OK_PTR(strstr(bad_log_buf, "invalid access to map value, value_size=16 off=16000 size=4"),
"bad_log_not_empty"); "bad_log_not_empty");
...@@ -175,7 +175,7 @@ static void bpf_prog_load_log_buf(void) ...@@ -175,7 +175,7 @@ static void bpf_prog_load_log_buf(void)
opts.log_level = 2; opts.log_level = 2;
fd = bpf_prog_load(BPF_PROG_TYPE_SOCKET_FILTER, "good_prog", "GPL", fd = bpf_prog_load(BPF_PROG_TYPE_SOCKET_FILTER, "good_prog", "GPL",
good_prog_insns, good_prog_insn_cnt, &opts); good_prog_insns, good_prog_insn_cnt, &opts);
ASSERT_OK_PTR(strstr(log_buf, "0: R1=ctx(off=0,imm=0) R10=fp0"), "good_log_2"); ASSERT_OK_PTR(strstr(log_buf, "0: R1=ctx() R10=fp0"), "good_log_2");
ASSERT_GE(fd, 0, "good_fd2"); ASSERT_GE(fd, 0, "good_fd2");
if (fd >= 0) if (fd >= 0)
close(fd); close(fd);
......
...@@ -13,10 +13,13 @@ ...@@ -13,10 +13,13 @@
*/ */
#define U64_MAX ((u64)UINT64_MAX) #define U64_MAX ((u64)UINT64_MAX)
#define U32_MAX ((u32)UINT_MAX) #define U32_MAX ((u32)UINT_MAX)
#define U16_MAX ((u32)UINT_MAX)
#define S64_MIN ((s64)INT64_MIN) #define S64_MIN ((s64)INT64_MIN)
#define S64_MAX ((s64)INT64_MAX) #define S64_MAX ((s64)INT64_MAX)
#define S32_MIN ((s32)INT_MIN) #define S32_MIN ((s32)INT_MIN)
#define S32_MAX ((s32)INT_MAX) #define S32_MAX ((s32)INT_MAX)
#define S16_MIN ((s16)0x80000000)
#define S16_MAX ((s16)0x7fffffff)
typedef unsigned long long ___u64; typedef unsigned long long ___u64;
typedef unsigned int ___u32; typedef unsigned int ___u32;
...@@ -138,13 +141,17 @@ static enum num_t t_unsigned(enum num_t t) ...@@ -138,13 +141,17 @@ static enum num_t t_unsigned(enum num_t t)
} }
} }
#define UNUM_MAX_DECIMAL U16_MAX
#define SNUM_MAX_DECIMAL S16_MAX
#define SNUM_MIN_DECIMAL S16_MIN
static bool num_is_small(enum num_t t, u64 x) static bool num_is_small(enum num_t t, u64 x)
{ {
switch (t) { switch (t) {
case U64: return (u64)x <= 256; case U64: return (u64)x <= UNUM_MAX_DECIMAL;
case U32: return (u32)x <= 256; case U32: return (u32)x <= UNUM_MAX_DECIMAL;
case S64: return (s64)x >= -256 && (s64)x <= 256; case S64: return (s64)x >= SNUM_MIN_DECIMAL && (s64)x <= SNUM_MAX_DECIMAL;
case S32: return (s32)x >= -256 && (s32)x <= 256; case S32: return (s32)x >= SNUM_MIN_DECIMAL && (s32)x <= SNUM_MAX_DECIMAL;
default: printf("num_is_small!\n"); exit(1); default: printf("num_is_small!\n"); exit(1);
} }
} }
...@@ -1023,20 +1030,19 @@ static int parse_reg_state(const char *s, struct reg_state *reg) ...@@ -1023,20 +1030,19 @@ static int parse_reg_state(const char *s, struct reg_state *reg)
*/ */
struct { struct {
const char *pfx; const char *pfx;
const char *fmt;
u64 *dst, def; u64 *dst, def;
bool is_32, is_set; bool is_32, is_set;
} *f, fields[8] = { } *f, fields[8] = {
{"smin=", "%lld", &reg->r[S64].a, S64_MIN}, {"smin=", &reg->r[S64].a, S64_MIN},
{"smax=", "%lld", &reg->r[S64].b, S64_MAX}, {"smax=", &reg->r[S64].b, S64_MAX},
{"umin=", "%llu", &reg->r[U64].a, 0}, {"umin=", &reg->r[U64].a, 0},
{"umax=", "%llu", &reg->r[U64].b, U64_MAX}, {"umax=", &reg->r[U64].b, U64_MAX},
{"smin32=", "%lld", &reg->r[S32].a, (u32)S32_MIN, true}, {"smin32=", &reg->r[S32].a, (u32)S32_MIN, true},
{"smax32=", "%lld", &reg->r[S32].b, (u32)S32_MAX, true}, {"smax32=", &reg->r[S32].b, (u32)S32_MAX, true},
{"umin32=", "%llu", &reg->r[U32].a, 0, true}, {"umin32=", &reg->r[U32].a, 0, true},
{"umax32=", "%llu", &reg->r[U32].b, U32_MAX, true}, {"umax32=", &reg->r[U32].b, U32_MAX, true},
}; };
const char *p, *fmt; const char *p;
int i; int i;
p = strchr(s, '='); p = strchr(s, '=');
...@@ -1050,8 +1056,13 @@ static int parse_reg_state(const char *s, struct reg_state *reg) ...@@ -1050,8 +1056,13 @@ static int parse_reg_state(const char *s, struct reg_state *reg)
long long sval; long long sval;
enum num_t t; enum num_t t;
if (sscanf(p, "%lld", &sval) != 1) if (p[0] == '0' && p[1] == 'x') {
return -EINVAL; if (sscanf(p, "%llx", &sval) != 1)
return -EINVAL;
} else {
if (sscanf(p, "%lld", &sval) != 1)
return -EINVAL;
}
reg->valid = true; reg->valid = true;
for (t = first_t; t <= last_t; t++) { for (t = first_t; t <= last_t; t++) {
...@@ -1075,9 +1086,13 @@ static int parse_reg_state(const char *s, struct reg_state *reg) ...@@ -1075,9 +1086,13 @@ static int parse_reg_state(const char *s, struct reg_state *reg)
if (mcnt) { if (mcnt) {
/* populate all matched fields */ /* populate all matched fields */
fmt = fields[midxs[0]].fmt; if (p[0] == '0' && p[1] == 'x') {
if (sscanf(p, fmt, &val) != 1) if (sscanf(p, "%llx", &val) != 1)
return -EINVAL; return -EINVAL;
} else {
if (sscanf(p, "%lld", &val) != 1)
return -EINVAL;
}
for (i = 0; i < mcnt; i++) { for (i = 0; i < mcnt; i++) {
f = &fields[midxs[i]]; f = &fields[midxs[i]];
......
...@@ -13,22 +13,22 @@ static struct { ...@@ -13,22 +13,22 @@ static struct {
const char *err_msg; const char *err_msg;
} spin_lock_fail_tests[] = { } spin_lock_fail_tests[] = {
{ "lock_id_kptr_preserve", { "lock_id_kptr_preserve",
"5: (bf) r1 = r0 ; R0_w=ptr_foo(id=2,ref_obj_id=2,off=0,imm=0) " "5: (bf) r1 = r0 ; R0_w=ptr_foo(id=2,ref_obj_id=2) "
"R1_w=ptr_foo(id=2,ref_obj_id=2,off=0,imm=0) refs=2\n6: (85) call bpf_this_cpu_ptr#154\n" "R1_w=ptr_foo(id=2,ref_obj_id=2) refs=2\n6: (85) call bpf_this_cpu_ptr#154\n"
"R1 type=ptr_ expected=percpu_ptr_" }, "R1 type=ptr_ expected=percpu_ptr_" },
{ "lock_id_global_zero", { "lock_id_global_zero",
"; R1_w=map_value(off=0,ks=4,vs=4,imm=0)\n2: (85) call bpf_this_cpu_ptr#154\n" "; R1_w=map_value(map=.data.A,ks=4,vs=4)\n2: (85) call bpf_this_cpu_ptr#154\n"
"R1 type=map_value expected=percpu_ptr_" }, "R1 type=map_value expected=percpu_ptr_" },
{ "lock_id_mapval_preserve", { "lock_id_mapval_preserve",
"[0-9]\\+: (bf) r1 = r0 ;" "[0-9]\\+: (bf) r1 = r0 ;"
" R0_w=map_value(id=1,off=0,ks=4,vs=8,imm=0)" " R0_w=map_value(id=1,map=array_map,ks=4,vs=8)"
" R1_w=map_value(id=1,off=0,ks=4,vs=8,imm=0)\n" " R1_w=map_value(id=1,map=array_map,ks=4,vs=8)\n"
"[0-9]\\+: (85) call bpf_this_cpu_ptr#154\n" "[0-9]\\+: (85) call bpf_this_cpu_ptr#154\n"
"R1 type=map_value expected=percpu_ptr_" }, "R1 type=map_value expected=percpu_ptr_" },
{ "lock_id_innermapval_preserve", { "lock_id_innermapval_preserve",
"[0-9]\\+: (bf) r1 = r0 ;" "[0-9]\\+: (bf) r1 = r0 ;"
" R0=map_value(id=2,off=0,ks=4,vs=8,imm=0)" " R0=map_value(id=2,ks=4,vs=8)"
" R1_w=map_value(id=2,off=0,ks=4,vs=8,imm=0)\n" " R1_w=map_value(id=2,ks=4,vs=8)\n"
"[0-9]\\+: (85) call bpf_this_cpu_ptr#154\n" "[0-9]\\+: (85) call bpf_this_cpu_ptr#154\n"
"R1 type=map_value expected=percpu_ptr_" }, "R1 type=map_value expected=percpu_ptr_" },
{ "lock_id_mismatch_kptr_kptr", "bpf_spin_unlock of different lock" }, { "lock_id_mismatch_kptr_kptr", "bpf_spin_unlock of different lock" },
......
...@@ -18,48 +18,48 @@ ...@@ -18,48 +18,48 @@
return *(u64 *)num; \ return *(u64 *)num; \
} }
__msg(": R0_w=-2147483648 R10=fp0") __msg(": R0_w=0xffffffff80000000 R10=fp0")
check_assert(s64, eq, int_min, INT_MIN); check_assert(s64, eq, int_min, INT_MIN);
__msg(": R0_w=2147483647 R10=fp0") __msg(": R0_w=0x7fffffff R10=fp0")
check_assert(s64, eq, int_max, INT_MAX); check_assert(s64, eq, int_max, INT_MAX);
__msg(": R0_w=0 R10=fp0") __msg(": R0_w=0 R10=fp0")
check_assert(s64, eq, zero, 0); check_assert(s64, eq, zero, 0);
__msg(": R0_w=-9223372036854775808 R1_w=-9223372036854775808 R10=fp0") __msg(": R0_w=0x8000000000000000 R1_w=0x8000000000000000 R10=fp0")
check_assert(s64, eq, llong_min, LLONG_MIN); check_assert(s64, eq, llong_min, LLONG_MIN);
__msg(": R0_w=9223372036854775807 R1_w=9223372036854775807 R10=fp0") __msg(": R0_w=0x7fffffffffffffff R1_w=0x7fffffffffffffff R10=fp0")
check_assert(s64, eq, llong_max, LLONG_MAX); check_assert(s64, eq, llong_max, LLONG_MAX);
__msg(": R0_w=scalar(smax=2147483646) R10=fp0") __msg(": R0_w=scalar(smax=0x7ffffffe) R10=fp0")
check_assert(s64, lt, pos, INT_MAX); check_assert(s64, lt, pos, INT_MAX);
__msg(": R0_w=scalar(smax=-1,umin=9223372036854775808,var_off=(0x8000000000000000; 0x7fffffffffffffff))") __msg(": R0_w=scalar(smax=-1,umin=0x8000000000000000,var_off=(0x8000000000000000; 0x7fffffffffffffff))")
check_assert(s64, lt, zero, 0); check_assert(s64, lt, zero, 0);
__msg(": R0_w=scalar(smax=-2147483649,umin=9223372036854775808,umax=18446744071562067967,var_off=(0x8000000000000000; 0x7fffffffffffffff))") __msg(": R0_w=scalar(smax=0xffffffff7fffffff,umin=0x8000000000000000,umax=0xffffffff7fffffff,var_off=(0x8000000000000000; 0x7fffffffffffffff))")
check_assert(s64, lt, neg, INT_MIN); check_assert(s64, lt, neg, INT_MIN);
__msg(": R0_w=scalar(smax=2147483647) R10=fp0") __msg(": R0_w=scalar(smax=0x7fffffff) R10=fp0")
check_assert(s64, le, pos, INT_MAX); check_assert(s64, le, pos, INT_MAX);
__msg(": R0_w=scalar(smax=0) R10=fp0") __msg(": R0_w=scalar(smax=0) R10=fp0")
check_assert(s64, le, zero, 0); check_assert(s64, le, zero, 0);
__msg(": R0_w=scalar(smax=-2147483648,umin=9223372036854775808,umax=18446744071562067968,var_off=(0x8000000000000000; 0x7fffffffffffffff))") __msg(": R0_w=scalar(smax=0xffffffff80000000,umin=0x8000000000000000,umax=0xffffffff80000000,var_off=(0x8000000000000000; 0x7fffffffffffffff))")
check_assert(s64, le, neg, INT_MIN); check_assert(s64, le, neg, INT_MIN);
__msg(": R0_w=scalar(smin=umin=2147483648,umax=9223372036854775807,var_off=(0x0; 0x7fffffffffffffff))") __msg(": R0_w=scalar(smin=umin=0x80000000,umax=0x7fffffffffffffff,var_off=(0x0; 0x7fffffffffffffff))")
check_assert(s64, gt, pos, INT_MAX); check_assert(s64, gt, pos, INT_MAX);
__msg(": R0_w=scalar(smin=umin=1,umax=9223372036854775807,var_off=(0x0; 0x7fffffffffffffff))") __msg(": R0_w=scalar(smin=umin=1,umax=0x7fffffffffffffff,var_off=(0x0; 0x7fffffffffffffff))")
check_assert(s64, gt, zero, 0); check_assert(s64, gt, zero, 0);
__msg(": R0_w=scalar(smin=-2147483647) R10=fp0") __msg(": R0_w=scalar(smin=0xffffffff80000001) R10=fp0")
check_assert(s64, gt, neg, INT_MIN); check_assert(s64, gt, neg, INT_MIN);
__msg(": R0_w=scalar(smin=umin=2147483647,umax=9223372036854775807,var_off=(0x0; 0x7fffffffffffffff))") __msg(": R0_w=scalar(smin=umin=0x7fffffff,umax=0x7fffffffffffffff,var_off=(0x0; 0x7fffffffffffffff))")
check_assert(s64, ge, pos, INT_MAX); check_assert(s64, ge, pos, INT_MAX);
__msg(": R0_w=scalar(smin=0,umax=9223372036854775807,var_off=(0x0; 0x7fffffffffffffff)) R10=fp0") __msg(": R0_w=scalar(smin=0,umax=0x7fffffffffffffff,var_off=(0x0; 0x7fffffffffffffff)) R10=fp0")
check_assert(s64, ge, zero, 0); check_assert(s64, ge, zero, 0);
__msg(": R0_w=scalar(smin=-2147483648) R10=fp0") __msg(": R0_w=scalar(smin=0xffffffff80000000) R10=fp0")
check_assert(s64, ge, neg, INT_MIN); check_assert(s64, ge, neg, INT_MIN);
SEC("?tc") SEC("?tc")
__log_level(2) __failure __log_level(2) __failure
__msg(": R0=0 R1=ctx(off=0,imm=0) R2=scalar(smin=smin32=-2147483646,smax=smax32=2147483645) R10=fp0") __msg(": R0=0 R1=ctx() R2=scalar(smin=0xffffffff80000002,smax=smax32=0x7ffffffd,smin32=0x80000002) R10=fp0")
int check_assert_range_s64(struct __sk_buff *ctx) int check_assert_range_s64(struct __sk_buff *ctx)
{ {
struct bpf_sock *sk = ctx->sk; struct bpf_sock *sk = ctx->sk;
...@@ -75,7 +75,7 @@ int check_assert_range_s64(struct __sk_buff *ctx) ...@@ -75,7 +75,7 @@ int check_assert_range_s64(struct __sk_buff *ctx)
SEC("?tc") SEC("?tc")
__log_level(2) __failure __log_level(2) __failure
__msg(": R1=ctx(off=0,imm=0) R2=scalar(smin=umin=smin32=umin32=4096,smax=umax=smax32=umax32=8192,var_off=(0x0; 0x3fff))") __msg(": R1=ctx() R2=scalar(smin=umin=smin32=umin32=4096,smax=umax=smax32=umax32=8192,var_off=(0x0; 0x3fff))")
int check_assert_range_u64(struct __sk_buff *ctx) int check_assert_range_u64(struct __sk_buff *ctx)
{ {
u64 num = ctx->len; u64 num = ctx->len;
...@@ -86,7 +86,7 @@ int check_assert_range_u64(struct __sk_buff *ctx) ...@@ -86,7 +86,7 @@ int check_assert_range_u64(struct __sk_buff *ctx)
SEC("?tc") SEC("?tc")
__log_level(2) __failure __log_level(2) __failure
__msg(": R0=0 R1=ctx(off=0,imm=0) R2=4096 R10=fp0") __msg(": R0=0 R1=ctx() R2=4096 R10=fp0")
int check_assert_single_range_s64(struct __sk_buff *ctx) int check_assert_single_range_s64(struct __sk_buff *ctx)
{ {
struct bpf_sock *sk = ctx->sk; struct bpf_sock *sk = ctx->sk;
...@@ -103,7 +103,7 @@ int check_assert_single_range_s64(struct __sk_buff *ctx) ...@@ -103,7 +103,7 @@ int check_assert_single_range_s64(struct __sk_buff *ctx)
SEC("?tc") SEC("?tc")
__log_level(2) __failure __log_level(2) __failure
__msg(": R1=ctx(off=0,imm=0) R2=4096 R10=fp0") __msg(": R1=ctx() R2=4096 R10=fp0")
int check_assert_single_range_u64(struct __sk_buff *ctx) int check_assert_single_range_u64(struct __sk_buff *ctx)
{ {
u64 num = ctx->len; u64 num = ctx->len;
...@@ -114,7 +114,7 @@ int check_assert_single_range_u64(struct __sk_buff *ctx) ...@@ -114,7 +114,7 @@ int check_assert_single_range_u64(struct __sk_buff *ctx)
SEC("?tc") SEC("?tc")
__log_level(2) __failure __log_level(2) __failure
__msg(": R1=pkt(off=64,r=64,imm=0) R2=pkt_end(off=0,imm=0) R6=pkt(off=0,r=64,imm=0) R10=fp0") __msg(": R1=pkt(off=64,r=64) R2=pkt_end() R6=pkt(r=64) R10=fp0")
int check_assert_generic(struct __sk_buff *ctx) int check_assert_generic(struct __sk_buff *ctx)
{ {
u8 *data_end = (void *)(long)ctx->data_end; u8 *data_end = (void *)(long)ctx->data_end;
......
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