|  | 
|  | 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