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

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.org


Signed-off-by: default avatarAlexei Starovoitov <ast@kernel.org>
parents ff8867af 46862ee8
Loading
Loading
Loading
Loading
+76 −0
Original line number Diff line number Diff line
@@ -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);
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)
{
	struct bpf_verifier_state *cur = env->cur_state;
@@ -779,4 +783,76 @@ static inline bool bpf_type_has_unsafe_modifiers(u32 type)
	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 */
+480 −0
Original line number Diff line number Diff line
@@ -10,6 +10,8 @@
#include <linux/bpf_verifier.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)
{
	/* 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,
	va_end(args);
}
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);
}
+0 −460

File changed.

Preview size limit exceeded, changes collapsed.

+21 −21
Original line number Diff line number Diff line
@@ -40,7 +40,7 @@ static struct bpf_align_test tests[] = {
		},
		.prog_type = BPF_PROG_TYPE_SCHED_CLS,
		.matches = {
			{0, "R1", "ctx(off=0,imm=0)"},
			{0, "R1", "ctx()"},
			{0, "R10", "fp0"},
			{0, "R3_w", "2"},
			{1, "R3_w", "4"},
@@ -68,7 +68,7 @@ static struct bpf_align_test tests[] = {
		},
		.prog_type = BPF_PROG_TYPE_SCHED_CLS,
		.matches = {
			{0, "R1", "ctx(off=0,imm=0)"},
			{0, "R1", "ctx()"},
			{0, "R10", "fp0"},
			{0, "R3_w", "1"},
			{1, "R3_w", "2"},
@@ -97,7 +97,7 @@ static struct bpf_align_test tests[] = {
		},
		.prog_type = BPF_PROG_TYPE_SCHED_CLS,
		.matches = {
			{0, "R1", "ctx(off=0,imm=0)"},
			{0, "R1", "ctx()"},
			{0, "R10", "fp0"},
			{0, "R3_w", "4"},
			{1, "R3_w", "8"},
@@ -119,7 +119,7 @@ static struct bpf_align_test tests[] = {
		},
		.prog_type = BPF_PROG_TYPE_SCHED_CLS,
		.matches = {
			{0, "R1", "ctx(off=0,imm=0)"},
			{0, "R1", "ctx()"},
			{0, "R10", "fp0"},
			{0, "R3_w", "7"},
			{1, "R3_w", "7"},
@@ -162,13 +162,13 @@ static struct bpf_align_test tests[] = {
		},
		.prog_type = BPF_PROG_TYPE_SCHED_CLS,
		.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)"},
			{7, "R3_w", "var_off=(0x0; 0x1fe)"},
			{8, "R3_w", "var_off=(0x0; 0x3fc)"},
			{9, "R3_w", "var_off=(0x0; 0x7f8)"},
			{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)"},
			{18, "R4_w", "var_off=(0x0; 0x1fe0)"},
			{19, "R4_w", "var_off=(0x0; 0xff0)"},
@@ -235,11 +235,11 @@ static struct bpf_align_test tests[] = {
		},
		.prog_type = BPF_PROG_TYPE_SCHED_CLS,
		.matches = {
			{2, "R5_w", "pkt(off=0,r=0,imm=0)"},
			{4, "R5_w", "pkt(off=14,r=0,imm=0)"},
			{5, "R4_w", "pkt(off=14,r=0,imm=0)"},
			{9, "R2", "pkt(off=0,r=18,imm=0)"},
			{10, "R5", "pkt(off=14,r=18,imm=0)"},
			{2, "R5_w", "pkt(r=0)"},
			{4, "R5_w", "pkt(off=14,r=0)"},
			{5, "R4_w", "pkt(off=14,r=0)"},
			{9, "R2", "pkt(r=18)"},
			{10, "R5", "pkt(off=14,r=18)"},
			{10, "R4_w", "var_off=(0x0; 0xff)"},
			{13, "R4_w", "var_off=(0x0; 0xffff)"},
			{14, "R4_w", "var_off=(0x0; 0xffff)"},
@@ -299,7 +299,7 @@ static struct bpf_align_test tests[] = {
			/* Calculated offset in R6 has unknown value, but known
			 * 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)"},
			/* Offset is added to packet pointer R5, resulting in
			 * known fixed offset, and variable offset from R6.
@@ -337,7 +337,7 @@ static struct bpf_align_test tests[] = {
			/* Constant offset is added to R5 packet pointer,
			 * 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 of (4n). See comment for insn #18
			 * for R4 = R5 trick.
@@ -397,7 +397,7 @@ static struct bpf_align_test tests[] = {
			/* Calculated offset in R6 has unknown value, but known
			 * 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)"},
			/* Adding 14 makes R6 be (4n+2) */
			{8, "R6_w", "var_off=(0x2; 0x7fc)"},
@@ -459,7 +459,7 @@ static struct bpf_align_test tests[] = {
		.prog_type = BPF_PROG_TYPE_SCHED_CLS,
		.result = REJECT,
		.matches = {
			{3, "R5_w", "pkt_end(off=0,imm=0)"},
			{3, "R5_w", "pkt_end()"},
			/* (ptr - ptr) << 2 == unknown, (4n) */
			{5, "R5_w", "var_off=(0x0; 0xfffffffffffffffc)"},
			/* (4n) + 14 == (4n+2).  We blow our bounds, because
@@ -513,7 +513,7 @@ static struct bpf_align_test tests[] = {
			/* Calculated offset in R6 has unknown value, but known
			 * 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)"},
			/* Adding 14 makes R6 be (4n+2) */
			{9, "R6_w", "var_off=(0x2; 0x7fc)"},
@@ -566,7 +566,7 @@ static struct bpf_align_test tests[] = {
			/* Calculated offset in R6 has unknown value, but known
			 * 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)"},
			/* Adding 14 makes R6 be (4n+2) */
			{10, "R6_w", "var_off=(0x2; 0x7c)"},
@@ -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
			 * did not have a corresponding bpf insn. Example:
			 * func#0 @0
			 * 0: R1=ctx(off=0,imm=0) R10=fp0
			 * 0: R1=ctx() R10=fp0
			 * 0: (b7) r3 = 2                 ; R3_w=2
			 *
			 * Sometimes it's actually two lines below, e.g. when
			 * 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
			 *   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: (71) r3 = *(u8 *)(r2 +0)           ; R2_w=pkt(off=0,r=8,imm=0) R3_w=scalar(umax=255,var_off=(0x0; 0xff))
			 *   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) R1=ctx() R2_w=pkt(r=8) R3_w=pkt_end() R10=fp0
			 *   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)) {
				cur_line = -1;
+2 −2
Original line number Diff line number Diff line
@@ -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"),
		      "libbpf_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");
	ASSERT_OK_PTR(strstr(bad_log_buf, "invalid access to map value, value_size=16 off=16000 size=4"),
		      "bad_log_not_empty");
@@ -175,7 +175,7 @@ static void bpf_prog_load_log_buf(void)
	opts.log_level = 2;
	fd = bpf_prog_load(BPF_PROG_TYPE_SOCKET_FILTER, "good_prog", "GPL",
			   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");
	if (fd >= 0)
		close(fd);
Loading