1 /* cairo-trace - a utility to record and replay calls to the Cairo library.
2 *
3 * Copyright © 2008 Chris Wilson
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 /*
20 * A less hacky utility to lookup the debug strings for a particular
21 * .text address.
22 * Derived from backtrace-symbols.c in cairo by Chris Wilson.
23 */
24
25 /*
26 A hacky replacement for backtrace_symbols in glibc
27
28 backtrace_symbols in glibc looks up symbols using dladdr which is limited
29 in the symbols that it sees. libbacktracesymbols opens the executable and
30 shared libraries using libbfd and will look up backtrace information using
31 the symbol table and the dwarf line information.
32
33 It may make more sense for this program to use libelf instead of libbfd.
34 However, I have not investigated that yet.
35
36 Derived from addr2line.c from GNU Binutils by Jeff Muizelaar
37
38 Copyright 2007 Jeff Muizelaar
39 */
40
41 /* addr2line.c -- convert addresses to line number and function name
42 Copyright 1997, 1998, 1999, 2000, 2001, 2002 Free Software Foundation, Inc.
43 Contributed by Ulrich Lauther <Ulrich.Lauther@mchp.siemens.de>
44
45 This file was part of GNU Binutils.
46 */
47
48 #define _GNU_SOURCE
49
50 #ifdef HAVE_CONFIG_H
51 #include "config.h"
52 #endif
53
54 #define true 1
55 #define false 0
56
57 #include "lookup-symbol.h"
58
59 #include <unistd.h>
60 #include <stdio.h>
61 #include <stdlib.h>
62 #include <link.h>
63 #include <string.h>
64 #include <pthread.h>
65
66 #if HAVE_BFD
67 #include <bfd.h>
68 #include <libiberty.h>
69
70 struct symtab {
71 bfd *bfd;
72 asymbol **syms;
73 };
74
75 struct symbol {
76 int found;
77 bfd_vma pc;
78 struct symtab *symtab;
79 const char *filename;
80 const char *functionname;
81 unsigned int line;
82 };
83
84
85 static void
_symtab_fini(struct symtab * symtab)86 _symtab_fini (struct symtab *symtab)
87 {
88 free (symtab->syms);
89 if (symtab->bfd != NULL)
90 bfd_close (symtab->bfd);
91 }
92
93 /* Read in the symbol table. */
94 static int
_symtab_init(struct symtab * symtab,const char * filename)95 _symtab_init (struct symtab *symtab, const char *filename)
96 {
97 char **matching;
98 long symcount;
99 unsigned int size;
100
101 symtab->bfd = NULL;
102 symtab->syms = NULL;
103
104 symtab->bfd = bfd_openr (filename, NULL);
105 if (symtab->bfd == NULL)
106 goto BAIL;
107
108 if (bfd_check_format (symtab->bfd, bfd_archive))
109 goto BAIL;
110
111 if (! bfd_check_format_matches (symtab->bfd, bfd_object, &matching))
112 goto BAIL;
113
114 symcount = bfd_read_minisymbols (symtab->bfd, false, (PTR) &symtab->syms, &size);
115 if (symcount == 0) {
116 symcount = bfd_read_minisymbols (symtab->bfd, true /* dynamic */ ,
117 (PTR) &symtab->syms, &size);
118 }
119 if (symcount < 0)
120 goto BAIL;
121
122 if ((bfd_get_file_flags (symtab->bfd) & HAS_SYMS) == 0)
123 goto BAIL;
124
125 return 1;
126
127 BAIL:
128 _symtab_fini (symtab);
129 return 0;
130 }
131
132 /* Look for an address in a section.
133 * This is called via bfd_map_over_sections.
134 */
135 static void
find_address_in_section(bfd * abfd,asection * section,void * data)136 find_address_in_section (bfd *abfd,
137 asection *section,
138 void *data)
139 {
140 bfd_vma vma;
141 bfd_size_type size;
142 struct symbol *symbol = data;
143 struct symtab *symtab = symbol->symtab;
144
145 if (symbol->found)
146 return;
147
148 if ((bfd_get_section_flags (symtab->bfd, section) & SEC_ALLOC) == 0)
149 return;
150
151 vma = bfd_get_section_vma (symtab->bfd, section);
152 if (symbol->pc < vma)
153 return;
154
155 size = bfd_section_size (symtab->bfd, section);
156 if (symbol->pc >= vma + size)
157 return;
158
159 symbol->found = bfd_find_nearest_line (symtab->bfd, section,
160 symtab->syms,
161 symbol->pc - vma,
162 &symbol->filename,
163 &symbol->functionname,
164 &symbol->line);
165 }
166
167 static void
_symbol_fini(struct symbol * symbol)168 _symbol_fini (struct symbol *symbol)
169 {
170 }
171
172 static void
_symbol_init(struct symbol * symbol,struct symtab * symtab,bfd_vma addr)173 _symbol_init (struct symbol *symbol, struct symtab *symtab, bfd_vma addr)
174 {
175 symbol->found = false;
176 symbol->symtab = symtab;
177 symbol->pc = addr;
178 }
179
180 static void
_symbol_print(struct symbol * symbol,char * buf,int buflen,const char * filename)181 _symbol_print (struct symbol *symbol, char *buf, int buflen, const char *filename)
182 {
183 const char *name, *h;
184 char path[1024];
185
186 if (! symbol->found)
187 return;
188
189 name = symbol->functionname;
190 if (name == NULL || *name == '\0')
191 name = "??";
192
193 if (symbol->filename != NULL)
194 filename = symbol->filename;
195 if (strcmp (filename, "/proc/self/exe") == 0) {
196 int len = readlink ("/proc/self/exe", path, sizeof (path) - 1);
197 if (len != -1) {
198 path[len] = '\0';
199 filename = path;
200 }
201 }
202 h = strrchr (filename, '/');
203 if (h != NULL)
204 filename = h + 1;
205
206 if (symbol->line) {
207 snprintf (buf, buflen, "%s() [%s:%u]",
208 name, filename, symbol->line);
209 } else {
210 snprintf (buf, buflen, "%s() [%s]", name, filename);
211 }
212 }
213 #endif
214
215 struct file_match {
216 const char *file;
217 ElfW(Addr) address;
218 ElfW(Addr) base;
219 void *hdr;
220 };
221
222 static int
find_matching_file(struct dl_phdr_info * info,size_t size,void * data)223 find_matching_file (struct dl_phdr_info *info, size_t size, void *data)
224 {
225 struct file_match *match = data;
226 /* This code is modeled from Gfind_proc_info-lsb.c:callback() from libunwind */
227 long n;
228 const ElfW(Phdr) *phdr;
229 ElfW(Addr) load_base = info->dlpi_addr;
230
231 phdr = info->dlpi_phdr;
232 for (n = info->dlpi_phnum; --n >= 0; phdr++) {
233 if (phdr->p_type == PT_LOAD) {
234 ElfW(Addr) vaddr = phdr->p_vaddr + load_base;
235 if (match->address >= vaddr &&
236 match->address < vaddr + phdr->p_memsz)
237 {
238 /* we found a match */
239 match->file = info->dlpi_name;
240 match->base = info->dlpi_addr;
241 return 1;
242 }
243 }
244 }
245
246 return 0;
247 }
248
249 struct symbol_cache_entry {
250 const void *ptr;
251 struct symbol_cache_entry *hash_prev, *hash_next;
252 char name[0];
253 };
254
255 static struct symbol_cache_entry *symbol_cache_hash[13477];
256 static pthread_mutex_t symbol_cache_mutex = PTHREAD_MUTEX_INITIALIZER;
257
258 char *
lookup_symbol(char * buf,int buflen,const void * ptr)259 lookup_symbol (char *buf, int buflen, const void *ptr)
260 {
261 struct file_match match;
262 #if HAVE_BFD
263 struct symtab symtab;
264 struct symbol symbol;
265 #endif
266 struct symbol_cache_entry *cache;
267 int bucket;
268 int len;
269
270 bucket = (unsigned long) ptr % (sizeof (symbol_cache_hash) / sizeof (symbol_cache_hash[0]));
271 pthread_mutex_lock (&symbol_cache_mutex);
272 for (cache = symbol_cache_hash[bucket];
273 cache != NULL;
274 cache = cache->hash_next)
275 {
276 if (cache->ptr == ptr) {
277 if (cache->hash_prev != NULL) {
278 cache->hash_prev->hash_next = cache->hash_next;
279 if (cache->hash_next != NULL)
280 cache->hash_next->hash_prev = cache->hash_prev;
281 cache->hash_prev = NULL;
282 cache->hash_next = symbol_cache_hash[bucket];
283 symbol_cache_hash[bucket]->hash_prev = cache;
284 symbol_cache_hash[bucket] = cache;
285 }
286
287 pthread_mutex_unlock (&symbol_cache_mutex);
288 return cache->name;
289 }
290 }
291 pthread_mutex_unlock (&symbol_cache_mutex);
292
293 match.file = NULL;
294 match.address = (ElfW(Addr)) ptr;
295 dl_iterate_phdr (find_matching_file, &match);
296
297 snprintf (buf, buflen, "0x%llx",
298 (long long unsigned int) match.address);
299
300 if (match.file == NULL || *match.file == '\0')
301 match.file = "/proc/self/exe";
302
303 #if HAVE_BFD
304 if (_symtab_init (&symtab, match.file)) {
305 _symbol_init (&symbol, &symtab, match.address - match.base);
306 bfd_map_over_sections (symtab.bfd, find_address_in_section, &symbol);
307 if (symbol.found)
308 _symbol_print (&symbol, buf, buflen, match.file);
309 _symbol_fini (&symbol);
310
311 _symtab_fini (&symtab);
312 }
313 #endif
314
315 len = strlen (buf);
316 cache = malloc (sizeof (struct symbol_cache_entry) + len + 1);
317 if (cache != NULL) {
318 cache->ptr = ptr;
319 memcpy (cache->name, buf, len + 1);
320
321 pthread_mutex_lock (&symbol_cache_mutex);
322 cache->hash_prev = NULL;
323 cache->hash_next = symbol_cache_hash[bucket];
324 if (symbol_cache_hash[bucket] != NULL)
325 symbol_cache_hash[bucket]->hash_prev = cache;
326 symbol_cache_hash[bucket] = cache;
327 pthread_mutex_unlock (&symbol_cache_mutex);
328 }
329
330 return buf;
331 }
332