1 // SPDX-License-Identifier: GPL-2.0 2 3 /* 4 * Must come before the linux/compiler.h include, which defines several 5 * macros (e.g. noinline) that conflict with compiler builtins used 6 * by LLVM. 7 */ 8 #pragma GCC diagnostic push 9 #pragma GCC diagnostic ignored "-Wunused-parameter" /* Needed for LLVM <= 15 */ 10 #include <llvm/DebugInfo/Symbolize/Symbolize.h> 11 #include <llvm/Support/TargetSelect.h> 12 #pragma GCC diagnostic pop 13 14 #include <inttypes.h> 15 #include <stdio.h> 16 #include <sys/types.h> 17 #include <linux/compiler.h> 18 extern "C" { 19 #include <linux/zalloc.h> 20 } 21 #include "symbol_conf.h" 22 #include "llvm-c-helpers.h" 23 24 extern "C" 25 char *dso__demangle_sym(struct dso *dso, int kmodule, const char *elf_name); 26 27 using namespace llvm; 28 using llvm::symbolize::LLVMSymbolizer; 29 30 /* 31 * Allocate a static LLVMSymbolizer, which will live to the end of the program. 32 * Unlike the bfd paths, LLVMSymbolizer has its own cache, so we do not need 33 * to store anything in the dso struct. 34 */ 35 static LLVMSymbolizer *get_symbolizer() 36 { 37 static LLVMSymbolizer *instance = nullptr; 38 if (instance == nullptr) { 39 LLVMSymbolizer::Options opts; 40 /* 41 * LLVM sometimes demangles slightly different from the rest 42 * of the code, and this mismatch can cause new_inline_sym() 43 * to get confused and mark non-inline symbol as inlined 44 * (since the name does not properly match up with base_sym). 45 * Thus, disable the demangling and let the rest of the code 46 * handle it. 47 */ 48 opts.Demangle = false; 49 instance = new LLVMSymbolizer(opts); 50 } 51 return instance; 52 } 53 54 /* Returns 0 on error, 1 on success. */ 55 static int extract_file_and_line(const DILineInfo &line_info, char **file, 56 unsigned int *line) 57 { 58 if (file) { 59 if (line_info.FileName == "<invalid>") { 60 /* Match the convention of libbfd. */ 61 *file = nullptr; 62 } else { 63 /* The caller expects to get something it can free(). */ 64 *file = strdup(line_info.FileName.c_str()); 65 if (*file == nullptr) 66 return 0; 67 } 68 } 69 if (line) 70 *line = line_info.Line; 71 return 1; 72 } 73 74 extern "C" 75 int llvm_addr2line(const char *dso_name, u64 addr, 76 char **file, unsigned int *line, 77 bool unwind_inlines, 78 llvm_a2l_frame **inline_frames) 79 { 80 LLVMSymbolizer *symbolizer = get_symbolizer(); 81 object::SectionedAddress sectioned_addr = { 82 addr, 83 object::SectionedAddress::UndefSection 84 }; 85 86 if (unwind_inlines) { 87 Expected<DIInliningInfo> res_or_err = 88 symbolizer->symbolizeInlinedCode(dso_name, 89 sectioned_addr); 90 if (!res_or_err) 91 return 0; 92 unsigned num_frames = res_or_err->getNumberOfFrames(); 93 if (num_frames == 0) 94 return 0; 95 96 if (extract_file_and_line(res_or_err->getFrame(0), 97 file, line) == 0) 98 return 0; 99 100 *inline_frames = (llvm_a2l_frame *)calloc( 101 num_frames, sizeof(**inline_frames)); 102 if (*inline_frames == nullptr) 103 return 0; 104 105 for (unsigned i = 0; i < num_frames; ++i) { 106 const DILineInfo &src = res_or_err->getFrame(i); 107 108 llvm_a2l_frame &dst = (*inline_frames)[i]; 109 if (src.FileName == "<invalid>") 110 /* Match the convention of libbfd. */ 111 dst.filename = nullptr; 112 else 113 dst.filename = strdup(src.FileName.c_str()); 114 dst.funcname = strdup(src.FunctionName.c_str()); 115 dst.line = src.Line; 116 117 if (dst.filename == nullptr || 118 dst.funcname == nullptr) { 119 for (unsigned j = 0; j <= i; ++j) { 120 zfree(&(*inline_frames)[j].filename); 121 zfree(&(*inline_frames)[j].funcname); 122 } 123 zfree(inline_frames); 124 return 0; 125 } 126 } 127 128 return num_frames; 129 } else { 130 if (inline_frames) 131 *inline_frames = nullptr; 132 133 Expected<DILineInfo> res_or_err = 134 symbolizer->symbolizeCode(dso_name, sectioned_addr); 135 if (!res_or_err) 136 return 0; 137 return extract_file_and_line(*res_or_err, file, line); 138 } 139 } 140 141 static char * 142 make_symbol_relative_string(struct dso *dso, const char *sym_name, 143 u64 addr, u64 base_addr) 144 { 145 if (!strcmp(sym_name, "<invalid>")) 146 return NULL; 147 148 char *demangled = dso__demangle_sym(dso, 0, sym_name); 149 if (base_addr && base_addr != addr) { 150 char buf[256]; 151 snprintf(buf, sizeof(buf), "%s+0x%" PRIx64, 152 demangled ? demangled : sym_name, addr - base_addr); 153 free(demangled); 154 return strdup(buf); 155 } else { 156 if (demangled) 157 return demangled; 158 else 159 return strdup(sym_name); 160 } 161 } 162 163 extern "C" 164 char *llvm_name_for_code(struct dso *dso, const char *dso_name, u64 addr) 165 { 166 LLVMSymbolizer *symbolizer = get_symbolizer(); 167 object::SectionedAddress sectioned_addr = { 168 addr, 169 object::SectionedAddress::UndefSection 170 }; 171 Expected<DILineInfo> res_or_err = 172 symbolizer->symbolizeCode(dso_name, sectioned_addr); 173 if (!res_or_err) { 174 return NULL; 175 } 176 return make_symbol_relative_string( 177 dso, res_or_err->FunctionName.c_str(), 178 addr, res_or_err->StartAddress ? *res_or_err->StartAddress : 0); 179 } 180 181 extern "C" 182 char *llvm_name_for_data(struct dso *dso, const char *dso_name, u64 addr) 183 { 184 LLVMSymbolizer *symbolizer = get_symbolizer(); 185 object::SectionedAddress sectioned_addr = { 186 addr, 187 object::SectionedAddress::UndefSection 188 }; 189 Expected<DIGlobal> res_or_err = 190 symbolizer->symbolizeData(dso_name, sectioned_addr); 191 if (!res_or_err) { 192 return NULL; 193 } 194 return make_symbol_relative_string( 195 dso, res_or_err->Name.c_str(), 196 addr, res_or_err->Start); 197 } 198
Linux® is a registered trademark of Linus Torvalds in the United States and other countries.
TOMOYO® is a registered trademark of NTT DATA CORPORATION.