f9665b36f29b91bc55a5573ef7914f9646eac5c1
[framework/graphics/cairo.git] / util / cairo-trace / lookup-symbol.c
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
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
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
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
168 _symbol_fini (struct symbol *symbol)
169 {
170 }
171
172 static void
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
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
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 *
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 }