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