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