BPF library already contains BPF instruction formatting functions, but they could only be used via `rte_bpf_dump` to dump result into file. Add new function `rte_bpf_format` to format instruction in various way (hexadecimal, disassembly) into a user-provided buffer, as well as a service function `rte_bpf_insn_is_wide` to detect wide instructions.
Signed-off-by: Marat Khalili <[email protected]> --- lib/bpf/bpf_dump.c | 290 +++++++++++++++++++++++++++------------------ lib/bpf/rte_bpf.h | 51 ++++++++ 2 files changed, 226 insertions(+), 115 deletions(-) diff --git a/lib/bpf/bpf_dump.c b/lib/bpf/bpf_dump.c index 0abaeef8ae98..4fd67ad5a1df 100644 --- a/lib/bpf/bpf_dump.c +++ b/lib/bpf/bpf_dump.c @@ -46,6 +46,38 @@ static const char *const jump_tbl[16] = { [EBPF_JSLT >> 4] = "jslt", [EBPF_JSLE >> 4] = "jsle", }; +RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_bpf_insn_is_wide, 26.07) +bool +rte_bpf_insn_is_wide(const struct ebpf_insn *ins) +{ + return ins->code == (BPF_LD | BPF_IMM | EBPF_DW); +} + + +/* Format one (possibly wide) eBPF command as hexadecimal in objdump format. */ +static int +format_hexadecimal(char *buffer, size_t bufsz, const struct ebpf_insn *ins, + uint32_t flags) +{ + const char *const b = (const char *)ins; + + RTE_ASSERT((flags & RTE_BPF_FORMAT_FLAG_HEXADECIMAL) != 0); + + RTE_BUILD_BUG_ON(sizeof(*ins) != 8); + + if ((flags & RTE_BPF_FORMAT_FLAG_NEVER_WIDE) == 0 && rte_bpf_insn_is_wide(ins)) + return snprintf(buffer, bufsz, + "%02hhx %02hhx %02hhx %02hhx %02hhx %02hhx %02hhx %02hhx " + "%02hhx %02hhx %02hhx %02hhx %02hhx %02hhx %02hhx %02hhx", + b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7], + b[8], b[9], b[10], b[11], b[12], b[13], b[14], b[15]); + else + return snprintf(buffer, bufsz, + "%02hhx %02hhx %02hhx %02hhx %02hhx %02hhx %02hhx %02hhx", + b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]); +} + +/* Return atomic subcommand mnemonic based on BPF_STX immediate. */ static inline const char * atomic_op(int32_t imm) { @@ -59,130 +91,158 @@ atomic_op(int32_t imm) } } -RTE_EXPORT_SYMBOL(rte_bpf_dump) -void rte_bpf_dump(FILE *f, const struct ebpf_insn *buf, uint32_t len) +/* Format one (possibly wide) eBPF command as assembler. */ +static int +format_disassembly(char *buffer, size_t bufsz, const struct ebpf_insn *ins, + uint32_t pc, uint32_t flags) { - uint32_t i; + uint8_t cls = BPF_CLASS(ins->code); + const char *op, *postfix = "", *warning = ""; + char jump[16]; - for (i = 0; i < len; ++i) { - const struct ebpf_insn *ins = buf + i; - uint8_t cls = BPF_CLASS(ins->code); - const char *op, *postfix = "", *warning = ""; + RTE_ASSERT((flags & RTE_BPF_FORMAT_FLAG_HEXADECIMAL) == 0); - fprintf(f, " L%u:\t", i); + switch (cls) { + default: + return snprintf(buffer, bufsz, "unimp 0x%x // class: %s", + ins->code, class_tbl[cls]); + case BPF_ALU: + postfix = "32"; + /* fall through */ + case EBPF_ALU64: + op = alu_op_tbl[BPF_OP_INDEX(ins->code)]; + if (ins->off != 0) + /* Not yet supported variation with non-zero offset. */ + warning = ", off != 0"; + if (BPF_SRC(ins->code) == BPF_X) + return snprintf(buffer, bufsz, "%s%s r%u, r%u%s", op, postfix, ins->dst_reg, + ins->src_reg, warning); + else + return snprintf(buffer, bufsz, "%s%s r%u, #0x%x%s", op, postfix, + ins->dst_reg, ins->imm, warning); + case BPF_LD: + op = "ld"; + postfix = size_tbl[BPF_SIZE_INDEX(ins->code)]; + if (ins->code == (BPF_LD | BPF_IMM | EBPF_DW)) { + uint64_t val; - switch (cls) { - default: - fprintf(f, "unimp 0x%x // class: %s\n", - ins->code, class_tbl[cls]); - break; - case BPF_ALU: - postfix = "32"; - /* fall through */ - case EBPF_ALU64: - op = alu_op_tbl[BPF_OP_INDEX(ins->code)]; - if (ins->off != 0) - /* Not yet supported variation with non-zero offset. */ - warning = ", off != 0"; - if (BPF_SRC(ins->code) == BPF_X) - fprintf(f, "%s%s r%u, r%u%s\n", op, postfix, ins->dst_reg, - ins->src_reg, warning); - else - fprintf(f, "%s%s r%u, #0x%x%s\n", op, postfix, - ins->dst_reg, ins->imm, warning); - break; - case BPF_LD: - op = "ld"; - postfix = size_tbl[BPF_SIZE_INDEX(ins->code)]; - if (ins->code == (BPF_LD | BPF_IMM | EBPF_DW)) { - uint64_t val; - - if (ins->src_reg != 0) - /* Not yet supported variation with non-zero src. */ - warning = ", src != 0"; - val = (uint32_t)ins[0].imm | - (uint64_t)(uint32_t)ins[1].imm << 32; - fprintf(f, "%s%s r%d, #0x%"PRIx64"%s\n", - op, postfix, ins->dst_reg, val, warning); - i++; - } else if (BPF_MODE(ins->code) == BPF_IMM) - fprintf(f, "%s%s r%d, #0x%x\n", op, postfix, - ins->dst_reg, ins->imm); - else if (BPF_MODE(ins->code) == BPF_ABS) - fprintf(f, "%s%s r%d, [%d]\n", op, postfix, - ins->dst_reg, ins->imm); - else if (BPF_MODE(ins->code) == BPF_IND) - fprintf(f, "%s%s r%d, [r%u + %d]\n", op, postfix, - ins->dst_reg, ins->src_reg, ins->imm); - else - fprintf(f, "// BUG: LD opcode 0x%02x in eBPF insns\n", - ins->code); - break; - case BPF_LDX: - op = "ldx"; - postfix = size_tbl[BPF_SIZE_INDEX(ins->code)]; - if (BPF_MODE(ins->code) == BPF_MEM) - fprintf(f, "%s%s r%d, [r%u + %d]\n", op, postfix, ins->dst_reg, - ins->src_reg, ins->off); - else - fprintf(f, "// BUG: LDX opcode 0x%02x in eBPF insns\n", - ins->code); - break; - case BPF_ST: - op = "st"; - postfix = size_tbl[BPF_SIZE_INDEX(ins->code)]; - if (BPF_MODE(ins->code) == BPF_MEM) - fprintf(f, "%s%s [r%d + %d], #0x%x\n", op, postfix, - ins->dst_reg, ins->off, ins->imm); - else - fprintf(f, "// BUG: ST opcode 0x%02x in eBPF insns\n", - ins->code); - break; - case BPF_STX: - if (BPF_MODE(ins->code) == BPF_MEM) - op = "stx"; - else if (BPF_MODE(ins->code) == EBPF_ATOMIC) { - op = atomic_op(ins->imm); - if (op == NULL) { - fprintf(f, "// BUG: ATOMIC operation 0x%x in eBPF insns\n", - ins->imm); - break; - } - } else { - fprintf(f, "// BUG: STX opcode 0x%02x in eBPF insns\n", - ins->code); - break; - } - postfix = size_tbl[BPF_SIZE_INDEX(ins->code)]; - fprintf(f, "%s%s [r%d + %d], r%u\n", op, postfix, - ins->dst_reg, ins->off, ins->src_reg); - break; -#define L(pc, off) ((int)(pc) + 1 + (off)) - case BPF_JMP: - op = jump_tbl[BPF_OP_INDEX(ins->code)]; if (ins->src_reg != 0) - /* Not yet supported variation with non-zero src w/o condition. */ + /* Not yet supported variation with non-zero src. */ warning = ", src != 0"; + val = (uint32_t)ins[0].imm | + (uint64_t)(uint32_t)ins[1].imm << 32; + return snprintf(buffer, bufsz, "%s%s r%d, #0x%"PRIx64"%s", + op, postfix, ins->dst_reg, val, warning); + } + switch (BPF_MODE(ins->code)) { + case BPF_IMM: + return snprintf(buffer, bufsz, "%s%s r%d, #0x%x", op, postfix, + ins->dst_reg, ins->imm); + case BPF_ABS: + return snprintf(buffer, bufsz, "%s%s r%d, [%d]", op, postfix, + ins->dst_reg, ins->imm); + case BPF_IND: + return snprintf(buffer, bufsz, "%s%s r%d, [r%u + %d]", op, postfix, + ins->dst_reg, ins->src_reg, ins->imm); + default: + return snprintf(buffer, bufsz, "// BUG: LD opcode 0x%02x in eBPF insns", + ins->code); + } + case BPF_LDX: + op = "ldx"; + postfix = size_tbl[BPF_SIZE_INDEX(ins->code)]; + if (BPF_MODE(ins->code) == BPF_MEM) + return snprintf(buffer, bufsz, "%s%s r%d, [r%u + %d]", op, postfix, + ins->dst_reg, ins->src_reg, ins->off); + else + return snprintf(buffer, bufsz, "// BUG: LDX opcode 0x%02x in eBPF insns", + ins->code); + case BPF_ST: + op = "st"; + postfix = size_tbl[BPF_SIZE_INDEX(ins->code)]; + if (BPF_MODE(ins->code) == BPF_MEM) + return snprintf(buffer, bufsz, "%s%s [r%d + %d], #0x%x", op, postfix, + ins->dst_reg, ins->off, ins->imm); + else + return snprintf(buffer, bufsz, "// BUG: ST opcode 0x%02x in eBPF insns", + ins->code); + case BPF_STX: + switch (BPF_MODE(ins->code)) { + case BPF_MEM: + op = "stx"; + break; + case EBPF_ATOMIC: + op = atomic_op(ins->imm); if (op == NULL) - fprintf(f, "invalid jump opcode: %#x\n", ins->code); - else if (BPF_OP(ins->code) == BPF_JA) - fprintf(f, "%s L%d%s\n", op, L(i, ins->off), warning); - else if (BPF_OP(ins->code) == EBPF_CALL) - /* Call of helper function with index in immediate. */ - fprintf(f, "%s #%u%s\n", op, ins->imm, warning); - else if (BPF_OP(ins->code) == EBPF_EXIT) - fprintf(f, "%s%s\n", op, warning); - else if (BPF_SRC(ins->code) == BPF_X) - fprintf(f, "%s r%u, r%u, L%d\n", op, ins->dst_reg, - ins->src_reg, L(i, ins->off)); - else - fprintf(f, "%s r%u, #0x%x, L%d\n", op, ins->dst_reg, - ins->imm, L(i, ins->off)); + return snprintf(buffer, bufsz, + "// BUG: ATOMIC operation 0x%x in eBPF insns", ins->imm); break; - case BPF_RET: - fprintf(f, "// BUG: RET opcode 0x%02x in eBPF insns\n", + default: + return snprintf(buffer, bufsz, "// BUG: STX opcode 0x%02x in eBPF insns", ins->code); - break; } + postfix = size_tbl[BPF_SIZE_INDEX(ins->code)]; + return snprintf(buffer, bufsz, "%s%s [r%d + %d], r%u", op, postfix, + ins->dst_reg, ins->off, ins->src_reg); + case BPF_JMP: + op = jump_tbl[BPF_OP_INDEX(ins->code)]; + if (op == NULL) + return snprintf(buffer, bufsz, "invalid jump opcode: %#x", ins->code); + + if ((flags & RTE_BPF_FORMAT_FLAG_ABSOLUTE_JUMPS) != 0) + snprintf(jump, sizeof(jump), "L%d", pc + 1 + ins->off); + else + snprintf(jump, sizeof(jump), "%+d", (int)ins->off); + + if (ins->src_reg != 0) + /* Not yet supported variation with non-zero src w/o condition. */ + warning = ", src != 0"; + switch (BPF_OP(ins->code)) { + case BPF_JA: + return snprintf(buffer, bufsz, "%s %s%s", op, jump, warning); + case EBPF_CALL: + /* Call of helper function with index in immediate. */ + return snprintf(buffer, bufsz, "%s #%u%s", op, ins->imm, warning); + case EBPF_EXIT: + return snprintf(buffer, bufsz, "%s%s", op, warning); + } + + if (BPF_SRC(ins->code) == BPF_X) + return snprintf(buffer, bufsz, "%s r%u, r%u, %s", op, ins->dst_reg, + ins->src_reg, jump); + else + return snprintf(buffer, bufsz, "%s r%u, #0x%x, %s", op, ins->dst_reg, + ins->imm, jump); + case BPF_RET: + return snprintf(buffer, bufsz, "// BUG: RET opcode 0x%02x in eBPF insns", + ins->code); + } +} + +RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_bpf_format, 26.07) +int +rte_bpf_format(char *buffer, size_t bufsz, const struct ebpf_insn *ins, + uint32_t pc, uint32_t flags) +{ + if ((flags & RTE_BPF_FORMAT_FLAG_HEXADECIMAL) != 0) + return format_hexadecimal(buffer, bufsz, ins, flags); + else + return format_disassembly(buffer, bufsz, ins, pc, flags); +} + +RTE_EXPORT_SYMBOL(rte_bpf_dump) +void rte_bpf_dump(FILE *f, const struct ebpf_insn *buf, uint32_t len) +{ + uint32_t i; + char buffer[256]; + + for (i = 0; i < len; ++i) { + const struct ebpf_insn *ins = buf + i; + + format_disassembly(buffer, sizeof(buffer), ins, i, + RTE_BPF_FORMAT_FLAG_DISASSEMBLY | + RTE_BPF_FORMAT_FLAG_ABSOLUTE_JUMPS); + fprintf(f, " L%u:\t%s\n", i, buffer); + i += rte_bpf_insn_is_wide(ins); } } diff --git a/lib/bpf/rte_bpf.h b/lib/bpf/rte_bpf.h index 3c3848925bdf..944e0b79ac8c 100644 --- a/lib/bpf/rte_bpf.h +++ b/lib/bpf/rte_bpf.h @@ -30,6 +30,23 @@ extern "C" { /** Mask with all supported `RTE_BPF_EXEC_FLAG_*` flags set. */ #define RTE_BPF_EXEC_FLAG_MASK RTE_BPF_EXEC_FLAG_JIT +/* Format instructions as assembler. */ +#define RTE_BPF_FORMAT_FLAG_DISASSEMBLY 0 +/* Format instructions as hexadecimal. */ +#define RTE_BPF_FORMAT_FLAG_HEXADECIMAL RTE_BIT32(0) + +/* Only valid in disassembly mode. */ +/* Format jump offsets relative to the next instruction. */ +#define RTE_BPF_FORMAT_FLAG_RELATIVE_JUMPS 0 +/* Format jump targets relative to the start of the program. */ +#define RTE_BPF_FORMAT_FLAG_ABSOLUTE_JUMPS RTE_BIT32(1) + +/* Only valid in hexadecimal mode. */ +/* Format full hexadecimal representation of wide instructions. */ +#define RTE_BPF_FORMAT_FLAG_AUTO_WIDE 0 +/* Format as hexadecimal only first half of wide instructions. */ +#define RTE_BPF_FORMAT_FLAG_NEVER_WIDE RTE_BIT32(2) + /** * Possible types for function/BPF program arguments. */ @@ -387,6 +404,40 @@ __rte_experimental int rte_bpf_get_jit_ex(const struct rte_bpf *bpf, struct rte_bpf_jit_ex *jit); +/** + * Determine instruction width. + * + * @return + * True if ins points to a wide (128-bit) instruction. + */ +__rte_experimental +bool +rte_bpf_insn_is_wide(const struct ebpf_insn *ins); + +/** + * Print eBPF instruction into a buffer. + * + * Semantics of handling buffer size repeats those of snprintf. + * + * @param buffer + * Output buffer (may be NULL if bufsz is zero). + * @param bufsz + * Output buffer size. + * @param ins + * Narrow or wide (depending on opcode) eBPF instruction. That is, when + * `rte_bpf_insn_is_wide` is true `ins[1]` is also accessed. + * @param pc + * Current instruction number for displaying absolute jump targets. + * @param flags + * Bitwise-OR combination of `RTE_BPF_FORMAT_FLAG_*` values. + * @return + * Number of characters to be written excluding terminating zero. + */ +__rte_experimental +int +rte_bpf_format(char *buffer, size_t bufsz, const struct ebpf_insn *ins, + uint32_t pc, uint32_t flags); + /** * Dump epf instructions to a file. * -- 2.43.0

