|
| 1 | +// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) |
| 2 | +#include <bpf/bpf.h> |
| 3 | +#include <bpf/libbpf.h> |
| 4 | +#include <test_progs.h> |
| 5 | + |
| 6 | +#ifdef HAVE_LLVM_SUPPORT |
| 7 | + |
| 8 | +#include <llvm-c/Core.h> |
| 9 | +#include <llvm-c/Disassembler.h> |
| 10 | +#include <llvm-c/Target.h> |
| 11 | +#include <llvm-c/TargetMachine.h> |
| 12 | + |
| 13 | +/* The intent is to use get_jited_program_text() for small test |
| 14 | + * programs written in BPF assembly, thus assume that 32 local labels |
| 15 | + * would be sufficient. |
| 16 | + */ |
| 17 | +#define MAX_LOCAL_LABELS 32 |
| 18 | + |
| 19 | +static bool llvm_initialized; |
| 20 | + |
| 21 | +struct local_labels { |
| 22 | + bool print_phase; |
| 23 | + __u32 prog_len; |
| 24 | + __u32 cnt; |
| 25 | + __u32 pcs[MAX_LOCAL_LABELS]; |
| 26 | + char names[MAX_LOCAL_LABELS][4]; |
| 27 | +}; |
| 28 | + |
| 29 | +static const char *lookup_symbol(void *data, uint64_t ref_value, uint64_t *ref_type, |
| 30 | + uint64_t ref_pc, const char **ref_name) |
| 31 | +{ |
| 32 | + struct local_labels *labels = data; |
| 33 | + uint64_t type = *ref_type; |
| 34 | + int i; |
| 35 | + |
| 36 | + *ref_type = LLVMDisassembler_ReferenceType_InOut_None; |
| 37 | + *ref_name = NULL; |
| 38 | + if (type != LLVMDisassembler_ReferenceType_In_Branch) |
| 39 | + return NULL; |
| 40 | + /* Depending on labels->print_phase either discover local labels or |
| 41 | + * return a name assigned with local jump target: |
| 42 | + * - if print_phase is true and ref_value is in labels->pcs, |
| 43 | + * return corresponding labels->name. |
| 44 | + * - if print_phase is false, save program-local jump targets |
| 45 | + * in labels->pcs; |
| 46 | + */ |
| 47 | + if (labels->print_phase) { |
| 48 | + for (i = 0; i < labels->cnt; ++i) |
| 49 | + if (labels->pcs[i] == ref_value) |
| 50 | + return labels->names[i]; |
| 51 | + } else { |
| 52 | + if (labels->cnt < MAX_LOCAL_LABELS && ref_value < labels->prog_len) |
| 53 | + labels->pcs[labels->cnt++] = ref_value; |
| 54 | + } |
| 55 | + return NULL; |
| 56 | +} |
| 57 | + |
| 58 | +static int disasm_insn(LLVMDisasmContextRef ctx, uint8_t *image, __u32 len, __u32 pc, |
| 59 | + char *buf, __u32 buf_sz) |
| 60 | +{ |
| 61 | + int i, cnt; |
| 62 | + |
| 63 | + cnt = LLVMDisasmInstruction(ctx, image + pc, len - pc, pc, |
| 64 | + buf, buf_sz); |
| 65 | + if (cnt > 0) |
| 66 | + return cnt; |
| 67 | + PRINT_FAIL("Can't disasm instruction at offset %d:", pc); |
| 68 | + for (i = 0; i < 16 && pc + i < len; ++i) |
| 69 | + printf(" %02x", image[pc + i]); |
| 70 | + printf("\n"); |
| 71 | + return -EINVAL; |
| 72 | +} |
| 73 | + |
| 74 | +static int cmp_u32(const void *_a, const void *_b) |
| 75 | +{ |
| 76 | + __u32 a = *(__u32 *)_a; |
| 77 | + __u32 b = *(__u32 *)_b; |
| 78 | + |
| 79 | + if (a < b) |
| 80 | + return -1; |
| 81 | + if (a > b) |
| 82 | + return 1; |
| 83 | + return 0; |
| 84 | +} |
| 85 | + |
| 86 | +static int disasm_one_func(FILE *text_out, uint8_t *image, __u32 len) |
| 87 | +{ |
| 88 | + char *label, *colon, *triple = NULL; |
| 89 | + LLVMDisasmContextRef ctx = NULL; |
| 90 | + struct local_labels labels = {}; |
| 91 | + __u32 *label_pc, pc; |
| 92 | + int i, cnt, err = 0; |
| 93 | + char buf[64]; |
| 94 | + |
| 95 | + triple = LLVMGetDefaultTargetTriple(); |
| 96 | + ctx = LLVMCreateDisasm(triple, &labels, 0, NULL, lookup_symbol); |
| 97 | + if (!ASSERT_OK_PTR(ctx, "LLVMCreateDisasm")) { |
| 98 | + err = -EINVAL; |
| 99 | + goto out; |
| 100 | + } |
| 101 | + |
| 102 | + cnt = LLVMSetDisasmOptions(ctx, LLVMDisassembler_Option_PrintImmHex); |
| 103 | + if (!ASSERT_EQ(cnt, 1, "LLVMSetDisasmOptions")) { |
| 104 | + err = -EINVAL; |
| 105 | + goto out; |
| 106 | + } |
| 107 | + |
| 108 | + /* discover labels */ |
| 109 | + labels.prog_len = len; |
| 110 | + pc = 0; |
| 111 | + while (pc < len) { |
| 112 | + cnt = disasm_insn(ctx, image, len, pc, buf, 1); |
| 113 | + if (cnt < 0) { |
| 114 | + err = cnt; |
| 115 | + goto out; |
| 116 | + } |
| 117 | + pc += cnt; |
| 118 | + } |
| 119 | + qsort(labels.pcs, labels.cnt, sizeof(*labels.pcs), cmp_u32); |
| 120 | + for (i = 0; i < labels.cnt; ++i) |
| 121 | + /* use (i % 100) to avoid format truncation warning */ |
| 122 | + snprintf(labels.names[i], sizeof(labels.names[i]), "L%d", i % 100); |
| 123 | + |
| 124 | + /* now print with labels */ |
| 125 | + labels.print_phase = true; |
| 126 | + pc = 0; |
| 127 | + while (pc < len) { |
| 128 | + cnt = disasm_insn(ctx, image, len, pc, buf, sizeof(buf)); |
| 129 | + if (cnt < 0) { |
| 130 | + err = cnt; |
| 131 | + goto out; |
| 132 | + } |
| 133 | + label_pc = bsearch(&pc, labels.pcs, labels.cnt, sizeof(*labels.pcs), cmp_u32); |
| 134 | + label = ""; |
| 135 | + colon = ""; |
| 136 | + if (label_pc) { |
| 137 | + label = labels.names[label_pc - labels.pcs]; |
| 138 | + colon = ":"; |
| 139 | + } |
| 140 | + fprintf(text_out, "%x:\t", pc); |
| 141 | + for (i = 0; i < cnt; ++i) |
| 142 | + fprintf(text_out, "%02x ", image[pc + i]); |
| 143 | + for (i = cnt * 3; i < 12 * 3; ++i) |
| 144 | + fputc(' ', text_out); |
| 145 | + fprintf(text_out, "%s%s%s\n", label, colon, buf); |
| 146 | + pc += cnt; |
| 147 | + } |
| 148 | + |
| 149 | +out: |
| 150 | + if (triple) |
| 151 | + LLVMDisposeMessage(triple); |
| 152 | + if (ctx) |
| 153 | + LLVMDisasmDispose(ctx); |
| 154 | + return err; |
| 155 | +} |
| 156 | + |
| 157 | +int get_jited_program_text(int fd, char *text, size_t text_sz) |
| 158 | +{ |
| 159 | + struct bpf_prog_info info = {}; |
| 160 | + __u32 info_len = sizeof(info); |
| 161 | + __u32 jited_funcs, len, pc; |
| 162 | + __u32 *func_lens = NULL; |
| 163 | + FILE *text_out = NULL; |
| 164 | + uint8_t *image = NULL; |
| 165 | + int i, err = 0; |
| 166 | + |
| 167 | + if (!llvm_initialized) { |
| 168 | + LLVMInitializeAllTargetInfos(); |
| 169 | + LLVMInitializeAllTargetMCs(); |
| 170 | + LLVMInitializeAllDisassemblers(); |
| 171 | + llvm_initialized = 1; |
| 172 | + } |
| 173 | + |
| 174 | + text_out = fmemopen(text, text_sz, "w"); |
| 175 | + if (!ASSERT_OK_PTR(text_out, "open_memstream")) { |
| 176 | + err = -errno; |
| 177 | + goto out; |
| 178 | + } |
| 179 | + |
| 180 | + /* first call is to find out jited program len */ |
| 181 | + err = bpf_prog_get_info_by_fd(fd, &info, &info_len); |
| 182 | + if (!ASSERT_OK(err, "bpf_prog_get_info_by_fd #1")) |
| 183 | + goto out; |
| 184 | + |
| 185 | + len = info.jited_prog_len; |
| 186 | + image = malloc(len); |
| 187 | + if (!ASSERT_OK_PTR(image, "malloc(info.jited_prog_len)")) { |
| 188 | + err = -ENOMEM; |
| 189 | + goto out; |
| 190 | + } |
| 191 | + |
| 192 | + jited_funcs = info.nr_jited_func_lens; |
| 193 | + func_lens = malloc(jited_funcs * sizeof(__u32)); |
| 194 | + if (!ASSERT_OK_PTR(func_lens, "malloc(info.nr_jited_func_lens)")) { |
| 195 | + err = -ENOMEM; |
| 196 | + goto out; |
| 197 | + } |
| 198 | + |
| 199 | + memset(&info, 0, sizeof(info)); |
| 200 | + info.jited_prog_insns = (__u64)image; |
| 201 | + info.jited_prog_len = len; |
| 202 | + info.jited_func_lens = (__u64)func_lens; |
| 203 | + info.nr_jited_func_lens = jited_funcs; |
| 204 | + err = bpf_prog_get_info_by_fd(fd, &info, &info_len); |
| 205 | + if (!ASSERT_OK(err, "bpf_prog_get_info_by_fd #2")) |
| 206 | + goto out; |
| 207 | + |
| 208 | + for (pc = 0, i = 0; i < jited_funcs; ++i) { |
| 209 | + fprintf(text_out, "func #%d:\n", i); |
| 210 | + disasm_one_func(text_out, image + pc, func_lens[i]); |
| 211 | + fprintf(text_out, "\n"); |
| 212 | + pc += func_lens[i]; |
| 213 | + } |
| 214 | + |
| 215 | +out: |
| 216 | + if (text_out) |
| 217 | + fclose(text_out); |
| 218 | + if (image) |
| 219 | + free(image); |
| 220 | + if (func_lens) |
| 221 | + free(func_lens); |
| 222 | + return err; |
| 223 | +} |
| 224 | + |
| 225 | +#else /* HAVE_LLVM_SUPPORT */ |
| 226 | + |
| 227 | +int get_jited_program_text(int fd, char *text, size_t text_sz) |
| 228 | +{ |
| 229 | + if (env.verbosity >= VERBOSE_VERY) |
| 230 | + printf("compiled w/o llvm development libraries, can't dis-assembly binary code"); |
| 231 | + return -EOPNOTSUPP; |
| 232 | +} |
| 233 | + |
| 234 | +#endif /* HAVE_LLVM_SUPPORT */ |
0 commit comments