Add bpftool support for dumping streams of a given BPF program. TODO: JSON and filepath support. Signed-off-by: Kumar Kartikeya Dwivedi <memxor@xxxxxxxxx> --- tools/bpf/bpftool/Makefile | 2 +- tools/bpf/bpftool/prog.c | 71 +++++++++++++++++- tools/bpf/bpftool/skeleton/stream.bpf.c | 96 +++++++++++++++++++++++++ 3 files changed, 166 insertions(+), 3 deletions(-) create mode 100644 tools/bpf/bpftool/skeleton/stream.bpf.c diff --git a/tools/bpf/bpftool/Makefile b/tools/bpf/bpftool/Makefile index 9e9a5f006cd2..eb908223c3bb 100644 --- a/tools/bpf/bpftool/Makefile +++ b/tools/bpf/bpftool/Makefile @@ -234,7 +234,7 @@ $(OUTPUT)%.bpf.o: skeleton/%.bpf.c $(OUTPUT)vmlinux.h $(LIBBPF_BOOTSTRAP) $(OUTPUT)%.skel.h: $(OUTPUT)%.bpf.o $(BPFTOOL_BOOTSTRAP) $(QUIET_GEN)$(BPFTOOL_BOOTSTRAP) gen skeleton $< > $@ -$(OUTPUT)prog.o: $(OUTPUT)profiler.skel.h +$(OUTPUT)prog.o: $(OUTPUT)profiler.skel.h $(OUTPUT)stream.skel.h $(OUTPUT)pids.o: $(OUTPUT)pid_iter.skel.h diff --git a/tools/bpf/bpftool/prog.c b/tools/bpf/bpftool/prog.c index f010295350be..d0800fec9c3d 100644 --- a/tools/bpf/bpftool/prog.c +++ b/tools/bpf/bpftool/prog.c @@ -35,12 +35,16 @@ #include "main.h" #include "xlated_dumper.h" +#include "stream.skel.h" + #define BPF_METADATA_PREFIX "bpf_metadata_" #define BPF_METADATA_PREFIX_LEN (sizeof(BPF_METADATA_PREFIX) - 1) enum dump_mode { DUMP_JITED, DUMP_XLATED, + DUMP_STDOUT, + DUMP_STDERR, }; static const bool attach_types[] = { @@ -697,6 +701,55 @@ static int do_show(int argc, char **argv) return err; } +static int process_stream_sample(void *ctx, void *data, size_t len) +{ + FILE *file = ctx; + + fprintf(file, "%s", (char *)data); + fflush(file); + return 0; +} + +static int +prog_dump_stream(struct bpf_prog_info *info, enum dump_mode mode, const char *filepath) +{ + FILE *file = mode == DUMP_STDOUT ? stdout : stderr; + LIBBPF_OPTS(bpf_test_run_opts, opts); + struct ring_buffer *ringbuf; + struct stream_bpf *skel; + int map_fd, ret = -1; + + __u32 prog_id = info->id; + __u32 stream_id = mode == DUMP_STDOUT ? 1 : 2; + + skel = stream_bpf__open_and_load(); + if (!skel) + return -errno; + skel->bss->prog_id = prog_id; + skel->bss->stream_id = stream_id; + + //TODO(kkd): Filepath handling + map_fd = bpf_map__fd(skel->maps.ringbuf); + ringbuf = ring_buffer__new(map_fd, process_stream_sample, file, NULL); + if (!ringbuf) { + ret = -errno; + goto end; + } + do { + skel->bss->written_count = skel->bss->written_size = 0; + ret = bpf_prog_test_run_opts(bpf_program__fd(skel->progs.bpftool_dump_prog_stream), &opts); + ret = -EINVAL; + if (ring_buffer__consume_n(ringbuf, skel->bss->written_count) != skel->bss->written_count) + goto end; + } while (!ret && opts.retval == EAGAIN); + + if (opts.retval != 0) + ret = -EINVAL; +end: + stream_bpf__destroy(skel); + return ret; +} + static int prog_dump(struct bpf_prog_info *info, enum dump_mode mode, char *filepath, bool opcodes, bool visual, bool linum) @@ -719,13 +772,15 @@ prog_dump(struct bpf_prog_info *info, enum dump_mode mode, } buf = u64_to_ptr(info->jited_prog_insns); member_len = info->jited_prog_len; - } else { /* DUMP_XLATED */ + } else if (mode == DUMP_XLATED) { /* DUMP_XLATED */ if (info->xlated_prog_len == 0 || !info->xlated_prog_insns) { p_err("error retrieving insn dump: kernel.kptr_restrict set?"); return -1; } buf = u64_to_ptr(info->xlated_prog_insns); member_len = info->xlated_prog_len; + } else if (mode == DUMP_STDOUT || mode == DUMP_STDERR) { + return prog_dump_stream(info, mode, filepath); } if (info->btf_id) { @@ -898,8 +953,10 @@ static int do_dump(int argc, char **argv) mode = DUMP_JITED; } else if (is_prefix(*argv, "xlated")) { mode = DUMP_XLATED; + } else if (is_prefix(*argv, "stdout") || is_prefix(*argv, "stderr")) { + mode = is_prefix(*argv, "stdout") ? DUMP_STDOUT : DUMP_STDERR; } else { - p_err("expected 'xlated' or 'jited', got: %s", *argv); + p_err("expected 'stdout', 'stderr', 'xlated' or 'jited', got: %s", *argv); return -1; } NEXT_ARG(); @@ -950,6 +1007,14 @@ static int do_dump(int argc, char **argv) } } + if (mode == DUMP_STDOUT || mode == DUMP_STDERR) { + if (opcodes || visual || linum) { + p_err("'%s' is not compatible with 'opcodes', 'visual', or 'linum'", + mode == DUMP_STDOUT ? "stdout" : "stderr"); + goto exit_close; + } + } + if (filepath && (opcodes || visual || linum)) { p_err("'file' is not compatible with 'opcodes', 'visual', or 'linum'"); goto exit_close; @@ -2468,6 +2533,8 @@ static int do_help(int argc, char **argv) "Usage: %1$s %2$s { show | list } [PROG]\n" " %1$s %2$s dump xlated PROG [{ file FILE | [opcodes] [linum] [visual] }]\n" " %1$s %2$s dump jited PROG [{ file FILE | [opcodes] [linum] }]\n" + " %1$s %2$s dump stdout PROG [{ file FILE }]\n" + " %1$s %2$s dump stderr PROG [{ file FILE }]\n" " %1$s %2$s pin PROG FILE\n" " %1$s %2$s { load | loadall } OBJ PATH \\\n" " [type TYPE] [{ offload_dev | xdpmeta_dev } NAME] \\\n" diff --git a/tools/bpf/bpftool/skeleton/stream.bpf.c b/tools/bpf/bpftool/skeleton/stream.bpf.c new file mode 100644 index 000000000000..31b5933e0384 --- /dev/null +++ b/tools/bpf/bpftool/skeleton/stream.bpf.c @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2025 Meta Platforms, Inc. and affiliates. */ +#include <vmlinux.h> +#include <bpf/bpf_tracing.h> +#include <bpf/bpf_helpers.h> + +struct { + __uint(type, BPF_MAP_TYPE_RINGBUF); + __uint(max_entries, 1024 * 1024); +} ringbuf SEC(".maps"); + +struct value { + struct bpf_stream_elem_batch __kptr *batch; +}; + +struct { + __uint(type, BPF_MAP_TYPE_ARRAY); + __type(key, int); + __type(value, struct value); + __uint(max_entries, 1); +} array SEC(".maps"); + +int written_size; +int written_count; +int stream_id; +int prog_id; + +#define ENOENT 2 +#define EAGAIN 11 +#define EFAULT 14 + +SEC("syscall") +int bpftool_dump_prog_stream(void *ctx) +{ + struct bpf_stream_elem_batch *elem_batch; + struct bpf_stream_elem *elem; + struct bpf_stream *stream; + bool cont = false; + struct value *v; + bool ret = 0; + + stream = bpf_prog_stream_get(BPF_STDERR, prog_id); + if (!stream) + return ENOENT; + + v = bpf_map_lookup_elem(&array, &(int){0}); + + if (v->batch) + elem_batch = bpf_kptr_xchg(&v->batch, NULL); + else + elem_batch = bpf_stream_next_elem_batch(stream); + if (!elem_batch) + goto end; + + bpf_repeat(BPF_MAX_LOOPS) { + struct bpf_dynptr dst_dptr, src_dptr; + int size; + + elem = bpf_stream_next_elem(elem_batch); + if (!elem) + break; + size = elem->mem_slice.len; + + if (bpf_dynptr_from_mem_slice(&elem->mem_slice, 0, &src_dptr)) + ret = EFAULT; + if (bpf_ringbuf_reserve_dynptr(&ringbuf, size, 0, &dst_dptr)) + ret = EFAULT; + if (bpf_dynptr_copy(&dst_dptr, 0, &src_dptr, 0, size)) + ret = EFAULT; + bpf_ringbuf_submit_dynptr(&dst_dptr, 0); + + written_count++; + written_size += size; + + bpf_stream_free_elem(elem); + + /* Probe and exit if no more space, probe for twice the typical size.*/ + if (bpf_ringbuf_reserve_dynptr(&ringbuf, 2048, 0, &dst_dptr)) + cont = true; + bpf_ringbuf_discard_dynptr(&dst_dptr, 0); + + if (ret || cont) + break; + } + + if (cont) + elem_batch = bpf_kptr_xchg(&v->batch, elem_batch); + if (elem_batch) + bpf_stream_free_elem_batch(elem_batch); +end: + bpf_prog_stream_put(stream); + + return ret ?: (cont ? EAGAIN : 0); +} + +char _license[] SEC("license") = "GPL"; -- 2.47.1