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 char *cpu, *features; 100 101 triple = LLVMGetDefaultTargetTriple(); 102 103 cpu = LLVMGetHostCPUName(); 104 features = LLVMGetHostCPUFeatures(); 105 106 ctx = LLVMCreateDisasmCPUFeatures(triple, cpu, features, &labels, 0, NULL, lookup_symbol); 107 108 LLVMDisposeMessage(cpu); 109 LLVMDisposeMessage(features); 110 111 if (!ASSERT_OK_PTR(ctx, "LLVMCreateDisasmCPUFeatures")) { 112 err = -EINVAL; 113 goto out; 114 } 115 116 cnt = LLVMSetDisasmOptions(ctx, LLVMDisassembler_Option_PrintImmHex); 117 if (!ASSERT_EQ(cnt, 1, "LLVMSetDisasmOptions")) { 118 err = -EINVAL; 119 goto out; 120 } 121 122 /* discover labels */ 123 labels.prog_len = len; 124 pc = 0; 125 while (pc < len) { 126 cnt = disasm_insn(ctx, image, len, pc, buf, 1); 127 if (cnt < 0) { 128 err = cnt; 129 goto out; 130 } 131 pc += cnt; 132 } 133 qsort(labels.pcs, labels.cnt, sizeof(*labels.pcs), cmp_u32); 134 /* gcc is unable to infer upper bound for labels.cnt and 135 * assumes it to be U32_MAX. U32_MAX takes 10 decimal digits. 136 * snprintf below prints into labels.names[*], which has space 137 * only for two digits and a letter. To avoid truncation 138 * warning use (i < MAX_LOCAL_LABELS), which informs gcc about 139 * printed value upper bound. 140 */ 141 for (i = 0; i < labels.cnt && i < MAX_LOCAL_LABELS; ++i) 142 snprintf(labels.names[i], sizeof(labels.names[i]), "L%d", i); 143 144 /* now print with labels */ 145 labels.print_phase = true; 146 pc = 0; 147 while (pc < len) { 148 cnt = disasm_insn(ctx, image, len, pc, buf, sizeof(buf)); 149 if (cnt < 0) { 150 err = cnt; 151 goto out; 152 } 153 label_pc = bsearch(&pc, labels.pcs, labels.cnt, sizeof(*labels.pcs), cmp_u32); 154 label = ""; 155 colon = ""; 156 if (label_pc) { 157 label = labels.names[label_pc - labels.pcs]; 158 colon = ":"; 159 } 160 fprintf(text_out, "%x:\t", pc); 161 for (i = 0; i < cnt; ++i) 162 fprintf(text_out, "%02x ", image[pc + i]); 163 for (i = cnt * 3; i < 12 * 3; ++i) 164 fputc(' ', text_out); 165 fprintf(text_out, "%s%s%s\n", label, colon, buf); 166 pc += cnt; 167 } 168 169 out: 170 if (triple) 171 LLVMDisposeMessage(triple); 172 if (ctx) 173 LLVMDisasmDispose(ctx); 174 return err; 175 } 176 177 int get_jited_program_text(int fd, char *text, size_t text_sz) 178 { 179 struct bpf_prog_info info = {}; 180 __u32 info_len = sizeof(info); 181 __u32 jited_funcs, len, pc; 182 __u32 *func_lens = NULL; 183 FILE *text_out = NULL; 184 uint8_t *image = NULL; 185 int i, err = 0; 186 187 if (!llvm_initialized) { 188 LLVMInitializeAllTargetInfos(); 189 LLVMInitializeAllTargetMCs(); 190 LLVMInitializeAllDisassemblers(); 191 llvm_initialized = 1; 192 } 193 194 text_out = fmemopen(text, text_sz, "w"); 195 if (!ASSERT_OK_PTR(text_out, "open_memstream")) { 196 err = -errno; 197 goto out; 198 } 199 200 /* first call is to find out jited program len */ 201 err = bpf_prog_get_info_by_fd(fd, &info, &info_len); 202 if (!ASSERT_OK(err, "bpf_prog_get_info_by_fd #1")) 203 goto out; 204 205 len = info.jited_prog_len; 206 image = malloc(len); 207 if (!ASSERT_OK_PTR(image, "malloc(info.jited_prog_len)")) { 208 err = -ENOMEM; 209 goto out; 210 } 211 212 jited_funcs = info.nr_jited_func_lens; 213 func_lens = malloc(jited_funcs * sizeof(__u32)); 214 if (!ASSERT_OK_PTR(func_lens, "malloc(info.nr_jited_func_lens)")) { 215 err = -ENOMEM; 216 goto out; 217 } 218 219 memset(&info, 0, sizeof(info)); 220 info.jited_prog_insns = (__u64)image; 221 info.jited_prog_len = len; 222 info.jited_func_lens = (__u64)func_lens; 223 info.nr_jited_func_lens = jited_funcs; 224 err = bpf_prog_get_info_by_fd(fd, &info, &info_len); 225 if (!ASSERT_OK(err, "bpf_prog_get_info_by_fd #2")) 226 goto out; 227 228 for (pc = 0, i = 0; i < jited_funcs; ++i) { 229 fprintf(text_out, "func #%d:\n", i); 230 disasm_one_func(text_out, image + pc, func_lens[i]); 231 fprintf(text_out, "\n"); 232 pc += func_lens[i]; 233 } 234 235 out: 236 if (text_out) 237 fclose(text_out); 238 if (image) 239 free(image); 240 if (func_lens) 241 free(func_lens); 242 return err; 243 } 244 245 #else /* HAVE_LLVM_SUPPORT */ 246 247 int get_jited_program_text(int fd, char *text, size_t text_sz) 248 { 249 if (env.verbosity >= VERBOSE_VERY) 250 printf("compiled w/o llvm development libraries, can't dis-assembly binary code"); 251 return -EOPNOTSUPP; 252 } 253 254 #endif /* HAVE_LLVM_SUPPORT */ 255