Skip to content

Commit a4a78bb

Browse files
committed
selftests/bpf: add BPF program dump in veristat
This patch adds support for dumping BPF program instructions directly from veristat. While it is already possible to inspect BPF program dump using bpftool, it requires multiple commands. During active development, it's common for developers to use veristat for testing verification. Integrating instruction dumping into veristat reduces the need to switch tools and simplifies the workflow. By making this information more readily accessible, this change aims to streamline the BPF development cycle and improve usability for developers. Signed-off-by: Mykyta Yatsenko <[email protected]>
1 parent fa47913 commit a4a78bb

File tree

2 files changed

+318
-1
lines changed

2 files changed

+318
-1
lines changed

tools/testing/selftests/bpf/Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -847,7 +847,7 @@ $(OUTPUT)/bench: $(OUTPUT)/bench.o \
847847
# snprintf(a, "%s/foo", b); // triggers -Wformat-truncation
848848
$(OUTPUT)/veristat.o: CFLAGS += -Wno-format-truncation
849849
$(OUTPUT)/veristat.o: $(BPFOBJ)
850-
$(OUTPUT)/veristat: $(OUTPUT)/veristat.o
850+
$(OUTPUT)/veristat: $(OUTPUT)/veristat.o $(OUTPUT)/disasm.o
851851
$(call msg,BINARY,,$@)
852852
$(Q)$(CC) $(CFLAGS) $(LDFLAGS) $(filter %.a %.o,$^) $(LDLIBS) -o $@
853853

tools/testing/selftests/bpf/veristat.c

Lines changed: 317 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
#include <limits.h>
2626
#include <assert.h>
2727

28+
#include "disasm.h"
29+
2830
#ifndef ARRAY_SIZE
2931
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
3032
#endif
@@ -181,6 +183,11 @@ struct var_preset {
181183
bool applied;
182184
};
183185

186+
struct kernel_sym {
187+
size_t address;
188+
char name[256];
189+
};
190+
184191
static struct env {
185192
char **filenames;
186193
int filename_cnt;
@@ -227,6 +234,7 @@ static struct env {
227234
char orig_cgroup[PATH_MAX];
228235
char stat_cgroup[PATH_MAX];
229236
int memory_peak_fd;
237+
bool dump;
230238
} env;
231239

232240
static int libbpf_print_fn(enum libbpf_print_level level, const char *format, va_list args)
@@ -295,6 +303,7 @@ static const struct argp_option opts[] = {
295303
"Force BPF verifier failure on register invariant violation (BPF_F_TEST_REG_INVARIANTS program flag)" },
296304
{ "top-src-lines", 'S', "N", 0, "Emit N most frequent source code lines" },
297305
{ "set-global-vars", 'G', "GLOBAL", 0, "Set global variables provided in the expression, for example \"var1 = 1\"" },
306+
{ "dump", 'p', NULL, 0, "Print BPF program dump" },
298307
{},
299308
};
300309

@@ -427,6 +436,9 @@ static error_t parse_arg(int key, char *arg, struct argp_state *state)
427436
return err;
428437
}
429438
break;
439+
case 'p':
440+
env.dump = true;
441+
break;
430442
default:
431443
return ARGP_ERR_UNKNOWN;
432444
}
@@ -891,6 +903,14 @@ static bool is_desc_sym(char c)
891903
return c == 'v' || c == 'V' || c == '.' || c == '!' || c == '_';
892904
}
893905

906+
static const char *ltrim(const char *s)
907+
{
908+
while (isspace(*s))
909+
s++;
910+
911+
return s;
912+
}
913+
894914
static char *rtrim(char *str)
895915
{
896916
int i;
@@ -1554,6 +1574,302 @@ static int parse_rvalue(const char *val, struct rvalue *rvalue)
15541574
return 0;
15551575
}
15561576

1577+
static int kernel_syms_cmp(const void *sym_a, const void *sym_b)
1578+
{
1579+
return ((struct kernel_sym *)sym_a)->address -
1580+
((struct kernel_sym *)sym_b)->address;
1581+
}
1582+
1583+
struct dump_context {
1584+
struct bpf_prog_info *info;
1585+
struct kernel_sym *kernel_syms;
1586+
int kernel_sym_cnt;
1587+
size_t kfunc_base_addr;
1588+
char scratch_buf[512];
1589+
};
1590+
1591+
static void kernel_syms_free(struct dump_context *ctx)
1592+
{
1593+
free(ctx->kernel_syms);
1594+
ctx->kernel_syms = NULL;
1595+
ctx->kernel_sym_cnt = 0;
1596+
}
1597+
1598+
static void kernel_syms_load(struct dump_context *ctx)
1599+
{
1600+
struct kernel_sym *sym;
1601+
char buff[256];
1602+
void *tmp, *address;
1603+
FILE *fp;
1604+
1605+
fp = fopen("/proc/kallsyms", "r");
1606+
if (!fp)
1607+
return;
1608+
while (fgets(buff, sizeof(buff), fp)) {
1609+
tmp = reallocarray(ctx->kernel_syms, ctx->kernel_sym_cnt + 1,
1610+
sizeof(*ctx->kernel_syms));
1611+
if (!tmp)
1612+
goto failure;
1613+
ctx->kernel_syms = tmp;
1614+
sym = ctx->kernel_syms + ctx->kernel_sym_cnt;
1615+
1616+
if (sscanf(buff, "%p %*c %s", &address, sym->name) < 2)
1617+
continue;
1618+
sym->address = (unsigned long)address;
1619+
if (!strcmp(sym->name, "__bpf_call_base")) {
1620+
ctx->kfunc_base_addr = sym->address;
1621+
/* sysctl kernel.kptr_restrict was set */
1622+
if (!sym->address)
1623+
goto failure;
1624+
}
1625+
if (sym->address)
1626+
ctx->kernel_sym_cnt++;
1627+
}
1628+
1629+
fclose(fp);
1630+
qsort(ctx->kernel_syms, ctx->kernel_sym_cnt, sizeof(*ctx->kernel_syms), kernel_syms_cmp);
1631+
return;
1632+
failure:
1633+
kernel_syms_free(ctx);
1634+
fclose(fp);
1635+
}
1636+
1637+
__attribute__((format(printf, 2, 3)))
1638+
static void print_insn(void *private_data, const char *fmt, ...)
1639+
{
1640+
va_list args;
1641+
1642+
va_start(args, fmt);
1643+
vprintf(fmt, args);
1644+
va_end(args);
1645+
}
1646+
1647+
static struct kernel_sym *kernel_syms_search(unsigned long key, struct dump_context *ctx)
1648+
{
1649+
struct kernel_sym sym = {
1650+
.address = key,
1651+
};
1652+
1653+
return ctx->kernel_syms ? bsearch(&sym, ctx->kernel_syms, ctx->kernel_sym_cnt,
1654+
sizeof(*ctx->kernel_syms), kernel_syms_cmp) :
1655+
NULL;
1656+
}
1657+
1658+
static const char *print_call(void *private_data, const struct bpf_insn *insn)
1659+
{
1660+
struct kernel_sym *sym;
1661+
struct dump_context *ctx = (struct dump_context *)private_data;
1662+
size_t address = ctx->kfunc_base_addr + insn->imm;
1663+
struct bpf_prog_info *info = ctx->info;
1664+
1665+
if (insn->src_reg == BPF_PSEUDO_CALL) {
1666+
if ((__u32)insn->imm < info->nr_jited_ksyms && info->jited_ksyms) {
1667+
__u64 *ptr = (void *)(size_t)info->jited_ksyms;
1668+
1669+
address = ptr[insn->imm];
1670+
}
1671+
1672+
sym = kernel_syms_search(address, ctx);
1673+
if (!info->jited_ksyms)
1674+
snprintf(ctx->scratch_buf, sizeof(ctx->scratch_buf), "%+d", insn->off);
1675+
else if (sym)
1676+
snprintf(ctx->scratch_buf, sizeof(ctx->scratch_buf), "%+d#%s", insn->off,
1677+
sym->name);
1678+
else
1679+
snprintf(ctx->scratch_buf, sizeof(ctx->scratch_buf), "%+d#0x%lx", insn->off,
1680+
address);
1681+
} else {
1682+
sym = kernel_syms_search(address, ctx);
1683+
if (sym)
1684+
snprintf(ctx->scratch_buf, sizeof(ctx->scratch_buf), "%s", sym->name);
1685+
else
1686+
snprintf(ctx->scratch_buf, sizeof(ctx->scratch_buf), "0x%lx", address);
1687+
}
1688+
return ctx->scratch_buf;
1689+
}
1690+
1691+
static const char *print_imm(void *private_data, const struct bpf_insn *insn, __u64 full_imm)
1692+
{
1693+
struct dump_context *ctx = (struct dump_context *)private_data;
1694+
1695+
switch (insn->src_reg) {
1696+
case BPF_PSEUDO_MAP_FD:
1697+
snprintf(ctx->scratch_buf, sizeof(ctx->scratch_buf), "map[id:%d]", insn->imm);
1698+
break;
1699+
case BPF_PSEUDO_MAP_VALUE:
1700+
snprintf(ctx->scratch_buf, sizeof(ctx->scratch_buf), "map[id:%d][0]+%d", insn->imm,
1701+
insn[1].imm);
1702+
break;
1703+
case BPF_PSEUDO_MAP_IDX_VALUE:
1704+
snprintf(ctx->scratch_buf, sizeof(ctx->scratch_buf), "map[idx:%d]+%d", insn->imm,
1705+
insn[1].imm);
1706+
break;
1707+
case BPF_PSEUDO_FUNC:
1708+
snprintf(ctx->scratch_buf, sizeof(ctx->scratch_buf), "subprog[%+d]", insn->imm);
1709+
break;
1710+
default:
1711+
snprintf(ctx->scratch_buf, sizeof(ctx->scratch_buf), "0x%llx",
1712+
(unsigned long long)full_imm);
1713+
}
1714+
return ctx->scratch_buf;
1715+
}
1716+
1717+
static void func_printf(void *ctx, const char *fmt, va_list args)
1718+
{
1719+
vprintf(fmt, args);
1720+
}
1721+
1722+
static int prep_prog_info(struct bpf_prog_info *info)
1723+
{
1724+
struct bpf_prog_info holder = {};
1725+
size_t needed = 0;
1726+
void *ptr;
1727+
1728+
holder.xlated_prog_len = info->xlated_prog_len;
1729+
needed += info->xlated_prog_len;
1730+
1731+
holder.nr_jited_ksyms = info->nr_jited_ksyms;
1732+
needed += info->nr_jited_ksyms * sizeof(__u64);
1733+
1734+
holder.nr_jited_func_lens = info->nr_jited_func_lens;
1735+
needed += info->nr_jited_func_lens * sizeof(__u32);
1736+
1737+
holder.nr_func_info = info->nr_func_info;
1738+
holder.func_info_rec_size = info->func_info_rec_size;
1739+
needed += info->nr_func_info * info->func_info_rec_size;
1740+
1741+
holder.nr_line_info = info->nr_line_info;
1742+
holder.line_info_rec_size = info->line_info_rec_size;
1743+
needed += info->nr_line_info * info->line_info_rec_size;
1744+
1745+
holder.nr_jited_line_info = info->nr_jited_line_info;
1746+
holder.jited_line_info_rec_size = info->jited_line_info_rec_size;
1747+
needed += info->nr_jited_line_info * info->jited_line_info_rec_size;
1748+
ptr = malloc(needed);
1749+
if (!ptr)
1750+
return -ENOMEM;
1751+
1752+
holder.xlated_prog_insns = (unsigned long)(ptr);
1753+
ptr += holder.xlated_prog_len;
1754+
1755+
holder.jited_ksyms = (unsigned long)(ptr);
1756+
ptr += holder.nr_jited_ksyms * sizeof(__u64);
1757+
1758+
holder.jited_func_lens = (unsigned long)(ptr);
1759+
ptr += holder.nr_jited_func_lens * sizeof(__u32);
1760+
1761+
holder.func_info = (unsigned long)(ptr);
1762+
ptr += holder.nr_func_info * holder.func_info_rec_size;
1763+
1764+
holder.line_info = (unsigned long)(ptr);
1765+
ptr += holder.nr_line_info * holder.line_info_rec_size;
1766+
1767+
holder.jited_line_info = (unsigned long)(ptr);
1768+
ptr += holder.nr_jited_line_info * holder.jited_line_info_rec_size;
1769+
1770+
*info = holder;
1771+
return 0;
1772+
}
1773+
1774+
static void emit_line_info(const struct btf *btf, const struct bpf_line_info *linfo)
1775+
{
1776+
const char *line = btf__name_by_offset(btf, linfo->line_off);
1777+
1778+
if (!line)
1779+
return;
1780+
line = ltrim(line);
1781+
printf("; %s\n", line);
1782+
}
1783+
1784+
static void emit_func_info(struct btf_dump *d, const struct btf *btf,
1785+
const struct bpf_func_info *finfo)
1786+
{
1787+
LIBBPF_OPTS(btf_dump_emit_type_decl_opts, emit_opts);
1788+
const struct btf_type *t;
1789+
__u32 name_off;
1790+
1791+
name_off = btf__type_by_id(btf, finfo->type_id)->name_off;
1792+
emit_opts.field_name = btf__name_by_offset(btf, name_off);
1793+
if (!emit_opts.field_name) /* field_name can't be NULL */
1794+
emit_opts.field_name = "N/A";
1795+
t = btf__type_by_id(btf, finfo->type_id);
1796+
btf_dump__emit_type_decl(d, t->type, &emit_opts);
1797+
printf(":\n");
1798+
}
1799+
1800+
static void dump_xlated(const struct btf *btf, struct bpf_program *prog, struct bpf_prog_info *info)
1801+
{
1802+
const struct bpf_insn *insn;
1803+
const struct bpf_func_info *finfo;
1804+
const struct bpf_line_info *linfo;
1805+
const struct bpf_prog_linfo *prog_linfo;
1806+
struct btf_dump *d;
1807+
__u32 nr_skip = 0, i, n;
1808+
bool double_insn = false;
1809+
LIBBPF_OPTS(btf_dump_opts, dump_opts);
1810+
LIBBPF_OPTS(btf_dump_emit_type_decl_opts, emit_opts);
1811+
struct dump_context ctx = {
1812+
.info = info,
1813+
.kernel_syms = NULL,
1814+
.kernel_sym_cnt = 0,
1815+
.kfunc_base_addr = 0
1816+
};
1817+
struct bpf_insn_cbs cbs = {
1818+
.cb_print = print_insn,
1819+
.cb_call = print_call,
1820+
.cb_imm = print_imm,
1821+
.private_data = &ctx,
1822+
};
1823+
1824+
/* load symbols for each prog, as prog load could have added new items */
1825+
kernel_syms_load(&ctx);
1826+
1827+
prog_linfo = bpf_prog_linfo__new(info);
1828+
insn = (struct bpf_insn *)info->xlated_prog_insns;
1829+
finfo = (struct bpf_func_info *)info->func_info;
1830+
d = btf_dump__new(btf, func_printf, NULL, &dump_opts);
1831+
n = info->xlated_prog_len / sizeof(*insn);
1832+
1833+
for (i = 0; i < n; i += double_insn ? 2 : 1) {
1834+
if (d && finfo && finfo->insn_off == i) {
1835+
emit_func_info(d, btf, finfo);
1836+
finfo++;
1837+
}
1838+
1839+
if (prog_linfo) {
1840+
linfo = bpf_prog_linfo__lfind(prog_linfo, i, nr_skip);
1841+
if (linfo) {
1842+
emit_line_info(btf, linfo);
1843+
nr_skip++;
1844+
}
1845+
}
1846+
printf("%4u: ", i);
1847+
print_bpf_insn(&cbs, insn + i, false);
1848+
double_insn = insn[i].code == (BPF_LD | BPF_IMM | BPF_DW);
1849+
}
1850+
1851+
kernel_syms_free(&ctx);
1852+
btf_dump__free(d);
1853+
}
1854+
1855+
static void dump(const struct btf *btf, struct bpf_program *prog, struct bpf_prog_info *info,
1856+
int prog_fd)
1857+
{
1858+
int err;
1859+
__u32 info_len = sizeof(*info);
1860+
1861+
if (!env.dump || prog_fd <= 0)
1862+
return;
1863+
1864+
err = prep_prog_info(info);
1865+
if (err)
1866+
return;
1867+
1868+
if (bpf_prog_get_info_by_fd(prog_fd, info, &info_len) != 0)
1869+
return;
1870+
dump_xlated(btf, prog, info);
1871+
}
1872+
15571873
static int process_prog(const char *filename, struct bpf_object *obj, struct bpf_program *prog)
15581874
{
15591875
const char *base_filename = basename(strdupa(filename));
@@ -1634,6 +1950,7 @@ static int process_prog(const char *filename, struct bpf_object *obj, struct bpf
16341950
stats->stats[JITED_SIZE] = info.jited_prog_len;
16351951

16361952
parse_verif_log(buf, buf_sz, stats);
1953+
dump(bpf_object__btf(obj), prog, &info, fd);
16371954

16381955
if (env.verbose) {
16391956
printf("PROCESSING %s/%s, DURATION US: %ld, VERDICT: %s, VERIFIER LOG:\n%s\n",

0 commit comments

Comments
 (0)