| 1 | |
| 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 <errno.h>
|
| 11 | #include <fcntl.h>
|
| 12 | #include <unistd.h>
|
| 13 | #include <linux/zalloc.h>
|
| 14 |
|
| 15 | #ifdef HAVE_LIBLLVM_SUPPORT
|
| 16 | #include "llvm-c-helpers.h"
|
| 17 | #include <llvm-c/Disassembler.h>
|
| 18 | #include <llvm-c/Target.h>
|
| 19 | #endif
|
| 20 |
|
| 21 | #ifdef HAVE_LIBLLVM_SUPPORT
|
| 22 | static void free_llvm_inline_frames(struct llvm_a2l_frame *inline_frames,
|
| 23 | int num_frames)
|
| 24 | {
|
| 25 | if (inline_frames != NULL) {
|
| 26 | for (int i = 0; i < num_frames; ++i) {
|
| 27 | zfree(&inline_frames[i].filename);
|
| 28 | zfree(&inline_frames[i].funcname);
|
| 29 | }
|
| 30 | zfree(&inline_frames);
|
| 31 | }
|
| 32 | }
|
| 33 | #endif
|
| 34 |
|
| 35 | int llvm__addr2line(const char *dso_name __maybe_unused, u64 addr __maybe_unused,
|
| 36 | char **file __maybe_unused, unsigned int *line __maybe_unused,
|
| 37 | struct dso *dso __maybe_unused, bool unwind_inlines __maybe_unused,
|
| 38 | struct inline_node *node __maybe_unused, struct symbol *sym __maybe_unused)
|
| 39 | {
|
| 40 | #ifdef HAVE_LIBLLVM_SUPPORT
|
| 41 | struct llvm_a2l_frame *inline_frames = NULL;
|
| 42 | int num_frames = llvm_addr2line(dso_name, addr, file, line,
|
| 43 | node && unwind_inlines, &inline_frames);
|
| 44 |
|
| 45 | if (num_frames == 0 || !inline_frames) {
|
| 46 |
|
| 47 | return num_frames;
|
| 48 | }
|
| 49 |
|
| 50 | for (int i = 0; i < num_frames; ++i) {
|
| 51 | struct symbol *inline_sym =
|
| 52 | new_inline_sym(dso, sym, inline_frames[i].funcname);
|
| 53 | char *srcline = NULL;
|
| 54 |
|
| 55 | if (inline_frames[i].filename) {
|
| 56 | srcline =
|
| 57 | srcline_from_fileline(inline_frames[i].filename,
|
| 58 | inline_frames[i].line);
|
| 59 | }
|
| 60 | if (inline_list__append(inline_sym, srcline, node) != 0) {
|
| 61 | free_llvm_inline_frames(inline_frames, num_frames);
|
| 62 | return 0;
|
| 63 | }
|
| 64 | }
|
| 65 | free_llvm_inline_frames(inline_frames, num_frames);
|
| 66 |
|
| 67 | return num_frames;
|
| 68 | #else
|
| 69 | return -1;
|
| 70 | #endif
|
| 71 | }
|
| 72 |
|
| 73 | #ifdef HAVE_LIBLLVM_SUPPORT
|
| 74 | static void init_llvm(void)
|
| 75 | {
|
| 76 | static bool init;
|
| 77 |
|
| 78 | if (!init) {
|
| 79 | LLVMInitializeAllTargetInfos();
|
| 80 | LLVMInitializeAllTargetMCs();
|
| 81 | LLVMInitializeAllDisassemblers();
|
| 82 | init = true;
|
| 83 | }
|
| 84 | }
|
| 85 |
|
| 86 | |
| 87 | * Whenever LLVM wants to resolve an address into a symbol, it calls this
|
| 88 | * callback. We don't ever actually _return_ anything (in particular, because
|
| 89 | * it puts quotation marks around what we return), but we use this as a hint
|
| 90 | * that there is a branch or PC-relative address in the expression that we
|
| 91 | * should add some textual annotation for after the instruction. The caller
|
| 92 | * will use this information to add the actual annotation.
|
| 93 |
|
| 94 | struct symbol_lookup_storage {
|
| 95 | u64 branch_addr;
|
| 96 | u64 pcrel_load_addr;
|
| 97 | };
|
| 98 |
|
| 99 | static const char *
|
| 100 | symbol_lookup_callback(void *disinfo, uint64_t value,
|
| 101 | uint64_t *ref_type,
|
| 102 | uint64_t address __maybe_unused,
|
| 103 | const char **ref __maybe_unused)
|
| 104 | {
|
| 105 | struct symbol_lookup_storage *storage = disinfo;
|
| 106 |
|
| 107 | if (*ref_type == LLVMDisassembler_ReferenceType_In_Branch)
|
| 108 | storage->branch_addr = value;
|
| 109 | else if (*ref_type == LLVMDisassembler_ReferenceType_In_PCrel_Load)
|
| 110 | storage->pcrel_load_addr = value;
|
| 111 | *ref_type = LLVMDisassembler_ReferenceType_InOut_None;
|
| 112 | return NULL;
|
| 113 | }
|
| 114 | #endif
|
| 115 |
|
| 116 | int symbol__disassemble_llvm(const char *filename, struct symbol *sym,
|
| 117 | struct annotate_args *args __maybe_unused)
|
| 118 | {
|
| 119 | #ifdef HAVE_LIBLLVM_SUPPORT
|
| 120 | struct annotation *notes = symbol__annotation(sym);
|
| 121 | struct map *map = args->ms->map;
|
| 122 | struct dso *dso = map__dso(map);
|
| 123 | u64 start = map__rip_2objdump(map, sym->start);
|
| 124 |
|
| 125 | u8 *code_buf = NULL;
|
| 126 |
|
| 127 | const u8 *buf;
|
| 128 | u64 buf_len;
|
| 129 | u64 pc;
|
| 130 | bool is_64bit;
|
| 131 | char disasm_buf[2048];
|
| 132 | size_t disasm_len;
|
| 133 | struct disasm_line *dl;
|
| 134 | LLVMDisasmContextRef disasm = NULL;
|
| 135 | struct symbol_lookup_storage storage;
|
| 136 | char *line_storage = NULL;
|
| 137 | size_t line_storage_len = 0;
|
| 138 | int ret = -1;
|
| 139 |
|
| 140 | if (args->options->objdump_path)
|
| 141 | return -1;
|
| 142 |
|
| 143 | buf = dso__read_symbol(dso, filename, map, sym,
|
| 144 | &code_buf, &buf_len, &is_64bit);
|
| 145 | if (buf == NULL)
|
| 146 | return errno;
|
| 147 |
|
| 148 | init_llvm();
|
| 149 | if (arch__is_x86(args->arch)) {
|
| 150 | const char *triplet = is_64bit ? "x86_64-pc-linux" : "i686-pc-linux";
|
| 151 |
|
| 152 | disasm = LLVMCreateDisasm(triplet, &storage, 0,
|
| 153 | NULL, symbol_lookup_callback);
|
| 154 | } else {
|
| 155 | char triplet[64];
|
| 156 | const char *features = NULL;
|
| 157 |
|
| 158 | scnprintf(triplet, sizeof(triplet), "%s-linux-gnu",
|
| 159 | args->arch->name);
|
| 160 | if (args->arch->id.e_machine == EM_AARCH64)
|
| 161 | features = "+all";
|
| 162 | disasm = LLVMCreateDisasmCPUFeatures(triplet, "",
|
| 163 | features, &storage,
|
| 164 | 0,
|
| 165 | NULL,
|
| 166 | symbol_lookup_callback);
|
| 167 | }
|
| 168 |
|
| 169 | if (disasm == NULL)
|
| 170 | goto err;
|
| 171 |
|
| 172 | if (args->options->disassembler_style &&
|
| 173 | !strcmp(args->options->disassembler_style, "intel"))
|
| 174 | LLVMSetDisasmOptions(disasm,
|
| 175 | LLVMDisassembler_Option_AsmPrinterVariant);
|
| 176 |
|
| 177 | |
| 178 | * This needs to be set after AsmPrinterVariant, due to a bug in LLVM;
|
| 179 | * setting AsmPrinterVariant makes a new instruction printer, making it
|
| 180 | * forget about the PrintImmHex flag (which is applied before if both
|
| 181 | * are given to the same call).
|
| 182 |
|
| 183 | LLVMSetDisasmOptions(disasm, LLVMDisassembler_Option_PrintImmHex);
|
| 184 |
|
| 185 |
|
| 186 | scnprintf(disasm_buf, sizeof(disasm_buf), "%#"PRIx64" <%s>:",
|
| 187 | start, sym->name);
|
| 188 |
|
| 189 | args->offset = -1;
|
| 190 | args->line = disasm_buf;
|
| 191 | args->line_nr = 0;
|
| 192 | args->fileloc = NULL;
|
| 193 | args->ms->sym = sym;
|
| 194 |
|
| 195 | dl = disasm_line__new(args);
|
| 196 | if (dl == NULL)
|
| 197 | goto err;
|
| 198 |
|
| 199 | annotation_line__add(&dl->al, ¬es->src->source);
|
| 200 |
|
| 201 | pc = start;
|
| 202 | for (u64 offset = 0; offset < buf_len; ) {
|
| 203 | unsigned int ins_len;
|
| 204 |
|
| 205 | storage.branch_addr = 0;
|
| 206 | storage.pcrel_load_addr = 0;
|
| 207 |
|
| 208 | |
| 209 | * LLVM's API has the code be disassembled as non-const, cast
|
| 210 | * here as we may be disassembling from mapped read-only memory.
|
| 211 |
|
| 212 | ins_len = LLVMDisasmInstruction(disasm, (u8 *)(buf + offset),
|
| 213 | buf_len - offset, pc,
|
| 214 | disasm_buf, sizeof(disasm_buf));
|
| 215 | if (ins_len == 0)
|
| 216 | goto err;
|
| 217 | disasm_len = strlen(disasm_buf);
|
| 218 |
|
| 219 | if (storage.branch_addr != 0) {
|
| 220 | char *name = llvm_name_for_code(dso, filename,
|
| 221 | storage.branch_addr);
|
| 222 | if (name != NULL) {
|
| 223 | disasm_len += scnprintf(disasm_buf + disasm_len,
|
| 224 | sizeof(disasm_buf) -
|
| 225 | disasm_len,
|
| 226 | " <%s>", name);
|
| 227 | free(name);
|
| 228 | }
|
| 229 | }
|
| 230 | if (storage.pcrel_load_addr != 0) {
|
| 231 | char *name = llvm_name_for_data(dso, filename,
|
| 232 | storage.pcrel_load_addr);
|
| 233 | disasm_len += scnprintf(disasm_buf + disasm_len,
|
| 234 | sizeof(disasm_buf) - disasm_len,
|
| 235 | " # %#"PRIx64,
|
| 236 | storage.pcrel_load_addr);
|
| 237 | if (name) {
|
| 238 | disasm_len += scnprintf(disasm_buf + disasm_len,
|
| 239 | sizeof(disasm_buf) -
|
| 240 | disasm_len,
|
| 241 | " <%s>", name);
|
| 242 | free(name);
|
| 243 | }
|
| 244 | }
|
| 245 |
|
| 246 | args->offset = offset;
|
| 247 | args->line = expand_tabs(disasm_buf, &line_storage,
|
| 248 | &line_storage_len);
|
| 249 | args->line_nr = 0;
|
| 250 | args->fileloc = NULL;
|
| 251 | args->ms->sym = sym;
|
| 252 |
|
| 253 | llvm_addr2line(filename, pc, &args->fileloc,
|
| 254 | (unsigned int *)&args->line_nr, false, NULL);
|
| 255 |
|
| 256 | dl = disasm_line__new(args);
|
| 257 | if (dl == NULL)
|
| 258 | goto err;
|
| 259 |
|
| 260 | annotation_line__add(&dl->al, ¬es->src->source);
|
| 261 |
|
| 262 | free(args->fileloc);
|
| 263 | pc += ins_len;
|
| 264 | offset += ins_len;
|
| 265 | }
|
| 266 |
|
| 267 | ret = 0;
|
| 268 |
|
| 269 | err:
|
| 270 | LLVMDisasmDispose(disasm);
|
| 271 | free(code_buf);
|
| 272 | free(line_storage);
|
| 273 | return ret;
|
| 274 | #else
|
| 275 | pr_debug("The LLVM disassembler isn't linked in for %s in %s\n",
|
| 276 | sym->name, filename);
|
| 277 | return -1;
|
| 278 | #endif
|
| 279 | }
|