1 // SPDX-License-Identifier: GPL-2.0 2 #include "llvm.h" 3 #include "annotate.h" 4 #include "debug.h" 5 #include "dso.h" 6 #include "map.h" 7 #include "namespaces.h" 8 #include "srcline.h" 9 #include "symbol.h" 10 #include <fcntl.h> 11 #include <unistd.h> 12 #include <linux/zalloc.h> 13 14 #ifdef HAVE_LIBLLVM_SUPPORT 15 #include "llvm-c-helpers.h" 16 #include <llvm-c/Disassembler.h> 17 #include <llvm-c/Target.h> 18 #endif 19 20 #ifdef HAVE_LIBLLVM_SUPPORT 21 static void free_llvm_inline_frames(struct llvm_a2l_frame *inline_frames, 22 int num_frames) 23 { 24 if (inline_frames != NULL) { 25 for (int i = 0; i < num_frames; ++i) { 26 zfree(&inline_frames[i].filename); 27 zfree(&inline_frames[i].funcname); 28 } 29 zfree(&inline_frames); 30 } 31 } 32 #endif 33 34 int llvm__addr2line(const char *dso_name __maybe_unused, u64 addr __maybe_unused, 35 char **file __maybe_unused, unsigned int *line __maybe_unused, 36 struct dso *dso __maybe_unused, bool unwind_inlines __maybe_unused, 37 struct inline_node *node __maybe_unused, struct symbol *sym __maybe_unused) 38 { 39 #ifdef HAVE_LIBLLVM_SUPPORT 40 struct llvm_a2l_frame *inline_frames = NULL; 41 int num_frames = llvm_addr2line(dso_name, addr, file, line, 42 node && unwind_inlines, &inline_frames); 43 44 if (num_frames == 0 || !inline_frames) { 45 /* Error, or we didn't want inlines. */ 46 return num_frames; 47 } 48 49 for (int i = 0; i < num_frames; ++i) { 50 struct symbol *inline_sym = 51 new_inline_sym(dso, sym, inline_frames[i].funcname); 52 char *srcline = NULL; 53 54 if (inline_frames[i].filename) { 55 srcline = 56 srcline_from_fileline(inline_frames[i].filename, 57 inline_frames[i].line); 58 } 59 if (inline_list__append(inline_sym, srcline, node) != 0) { 60 free_llvm_inline_frames(inline_frames, num_frames); 61 return 0; 62 } 63 } 64 free_llvm_inline_frames(inline_frames, num_frames); 65 66 return num_frames; 67 #else 68 return -1; 69 #endif 70 } 71 72 void dso__free_a2l_llvm(struct dso *dso __maybe_unused) 73 { 74 /* Nothing to free. */ 75 } 76 77 78 #if defined(HAVE_LIBLLVM_SUPPORT) 79 struct find_file_offset_data { 80 u64 ip; 81 u64 offset; 82 }; 83 84 /* This will be called for each PHDR in an ELF binary */ 85 static int find_file_offset(u64 start, u64 len, u64 pgoff, void *arg) 86 { 87 struct find_file_offset_data *data = arg; 88 89 if (start <= data->ip && data->ip < start + len) { 90 data->offset = pgoff + data->ip - start; 91 return 1; 92 } 93 return 0; 94 } 95 96 static u8 * 97 read_symbol(const char *filename, struct map *map, struct symbol *sym, 98 u64 *len, bool *is_64bit) 99 { 100 struct dso *dso = map__dso(map); 101 struct nscookie nsc; 102 u64 start = map__rip_2objdump(map, sym->start); 103 u64 end = map__rip_2objdump(map, sym->end); 104 int fd, count; 105 u8 *buf = NULL; 106 struct find_file_offset_data data = { 107 .ip = start, 108 }; 109 110 *is_64bit = false; 111 112 nsinfo__mountns_enter(dso__nsinfo(dso), &nsc); 113 fd = open(filename, O_RDONLY); 114 nsinfo__mountns_exit(&nsc); 115 if (fd < 0) 116 return NULL; 117 118 if (file__read_maps(fd, /*exe=*/true, find_file_offset, &data, 119 is_64bit) == 0) 120 goto err; 121 122 *len = end - start; 123 buf = malloc(*len); 124 if (buf == NULL) 125 goto err; 126 127 count = pread(fd, buf, *len, data.offset); 128 close(fd); 129 fd = -1; 130 131 if ((u64)count != *len) 132 goto err; 133 134 return buf; 135 136 err: 137 if (fd >= 0) 138 close(fd); 139 free(buf); 140 return NULL; 141 } 142 #endif 143 144 /* 145 * Whenever LLVM wants to resolve an address into a symbol, it calls this 146 * callback. We don't ever actually _return_ anything (in particular, because 147 * it puts quotation marks around what we return), but we use this as a hint 148 * that there is a branch or PC-relative address in the expression that we 149 * should add some textual annotation for after the instruction. The caller 150 * will use this information to add the actual annotation. 151 */ 152 #ifdef HAVE_LIBLLVM_SUPPORT 153 struct symbol_lookup_storage { 154 u64 branch_addr; 155 u64 pcrel_load_addr; 156 }; 157 158 static const char * 159 symbol_lookup_callback(void *disinfo, uint64_t value, 160 uint64_t *ref_type, 161 uint64_t address __maybe_unused, 162 const char **ref __maybe_unused) 163 { 164 struct symbol_lookup_storage *storage = disinfo; 165 166 if (*ref_type == LLVMDisassembler_ReferenceType_In_Branch) 167 storage->branch_addr = value; 168 else if (*ref_type == LLVMDisassembler_ReferenceType_In_PCrel_Load) 169 storage->pcrel_load_addr = value; 170 *ref_type = LLVMDisassembler_ReferenceType_InOut_None; 171 return NULL; 172 } 173 #endif 174 175 int symbol__disassemble_llvm(const char *filename, struct symbol *sym, 176 struct annotate_args *args __maybe_unused) 177 { 178 #ifdef HAVE_LIBLLVM_SUPPORT 179 struct annotation *notes = symbol__annotation(sym); 180 struct map *map = args->ms.map; 181 struct dso *dso = map__dso(map); 182 u64 start = map__rip_2objdump(map, sym->start); 183 u8 *buf; 184 u64 len; 185 u64 pc; 186 bool is_64bit; 187 char triplet[64]; 188 char disasm_buf[2048]; 189 size_t disasm_len; 190 struct disasm_line *dl; 191 LLVMDisasmContextRef disasm = NULL; 192 struct symbol_lookup_storage storage; 193 char *line_storage = NULL; 194 size_t line_storage_len = 0; 195 int ret = -1; 196 197 if (args->options->objdump_path) 198 return -1; 199 200 LLVMInitializeAllTargetInfos(); 201 LLVMInitializeAllTargetMCs(); 202 LLVMInitializeAllDisassemblers(); 203 204 buf = read_symbol(filename, map, sym, &len, &is_64bit); 205 if (buf == NULL) 206 return -1; 207 208 if (arch__is(args->arch, "x86")) { 209 if (is_64bit) 210 scnprintf(triplet, sizeof(triplet), "x86_64-pc-linux"); 211 else 212 scnprintf(triplet, sizeof(triplet), "i686-pc-linux"); 213 } else { 214 scnprintf(triplet, sizeof(triplet), "%s-linux-gnu", 215 args->arch->name); 216 } 217 218 disasm = LLVMCreateDisasm(triplet, &storage, 0, NULL, 219 symbol_lookup_callback); 220 if (disasm == NULL) 221 goto err; 222 223 if (args->options->disassembler_style && 224 !strcmp(args->options->disassembler_style, "intel")) 225 LLVMSetDisasmOptions(disasm, 226 LLVMDisassembler_Option_AsmPrinterVariant); 227 228 /* 229 * This needs to be set after AsmPrinterVariant, due to a bug in LLVM; 230 * setting AsmPrinterVariant makes a new instruction printer, making it 231 * forget about the PrintImmHex flag (which is applied before if both 232 * are given to the same call). 233 */ 234 LLVMSetDisasmOptions(disasm, LLVMDisassembler_Option_PrintImmHex); 235 236 /* add the function address and name */ 237 scnprintf(disasm_buf, sizeof(disasm_buf), "%#"PRIx64" <%s>:", 238 start, sym->name); 239 240 args->offset = -1; 241 args->line = disasm_buf; 242 args->line_nr = 0; 243 args->fileloc = NULL; 244 args->ms.sym = sym; 245 246 dl = disasm_line__new(args); 247 if (dl == NULL) 248 goto err; 249 250 annotation_line__add(&dl->al, ¬es->src->source); 251 252 pc = start; 253 for (u64 offset = 0; offset < len; ) { 254 unsigned int ins_len; 255 256 storage.branch_addr = 0; 257 storage.pcrel_load_addr = 0; 258 259 ins_len = LLVMDisasmInstruction(disasm, buf + offset, 260 len - offset, pc, 261 disasm_buf, sizeof(disasm_buf)); 262 if (ins_len == 0) 263 goto err; 264 disasm_len = strlen(disasm_buf); 265 266 if (storage.branch_addr != 0) { 267 char *name = llvm_name_for_code(dso, filename, 268 storage.branch_addr); 269 if (name != NULL) { 270 disasm_len += scnprintf(disasm_buf + disasm_len, 271 sizeof(disasm_buf) - 272 disasm_len, 273 " <%s>", name); 274 free(name); 275 } 276 } 277 if (storage.pcrel_load_addr != 0) { 278 char *name = llvm_name_for_data(dso, filename, 279 storage.pcrel_load_addr); 280 disasm_len += scnprintf(disasm_buf + disasm_len, 281 sizeof(disasm_buf) - disasm_len, 282 " # %#"PRIx64, 283 storage.pcrel_load_addr); 284 if (name) { 285 disasm_len += scnprintf(disasm_buf + disasm_len, 286 sizeof(disasm_buf) - 287 disasm_len, 288 " <%s>", name); 289 free(name); 290 } 291 } 292 293 args->offset = offset; 294 args->line = expand_tabs(disasm_buf, &line_storage, 295 &line_storage_len); 296 args->line_nr = 0; 297 args->fileloc = NULL; 298 args->ms.sym = sym; 299 300 llvm_addr2line(filename, pc, &args->fileloc, 301 (unsigned int *)&args->line_nr, false, NULL); 302 303 dl = disasm_line__new(args); 304 if (dl == NULL) 305 goto err; 306 307 annotation_line__add(&dl->al, ¬es->src->source); 308 309 free(args->fileloc); 310 pc += ins_len; 311 offset += ins_len; 312 } 313 314 ret = 0; 315 316 err: 317 LLVMDisasmDispose(disasm); 318 free(buf); 319 free(line_storage); 320 return ret; 321 #else // HAVE_LIBLLVM_SUPPORT 322 pr_debug("The LLVM disassembler isn't linked in for %s in %s\n", 323 sym->name, filename); 324 return -1; 325 #endif 326 } 327