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
lookup_symbol(void * data,uint64_t ref_value,uint64_t * ref_type,uint64_t ref_pc,const char ** ref_name)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
disasm_insn(LLVMDisasmContextRef ctx,uint8_t * image,__u32 len,__u32 pc,char * buf,__u32 buf_sz)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
cmp_u32(const void * _a,const void * _b)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
disasm_one_func(FILE * text_out,uint8_t * image,__u32 len)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
get_jited_program_text(int fd,char * text,size_t text_sz)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
get_jited_program_text(int fd,char * text,size_t text_sz)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