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