1 /* Copyright (C) 2011 Monty Program Ab
2 
3    This program is free software; you can redistribute it and/or modify
4    it under the terms of the GNU General Public License as published by
5    the Free Software Foundation; version 2 of the License.
6 
7    This program is distributed in the hope that it will be useful,
8    but WITHOUT ANY WARRANTY; without even the implied warranty of
9    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10    GNU General Public License for more details.
11 
12    You should have received a copy of the GNU General Public License
13    along with this program; if not, write to the Free Software
14    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335  USA */
15 
16 #include "mysys_priv.h"
17 #include <m_string.h>
18 #include <my_sys.h>
19 #include <my_stacktrace.h>
20 
21 /**
22   strip the path, leave the file name and the last dirname
23 */
24 static const char *strip_path(const char *s) __attribute__((unused));
strip_path(const char * s)25 static const char *strip_path(const char *s)
26 {
27   const char *prev, *last;
28   for(prev= last= s; *s; s++)
29     if (*s == '/' || *s == '\\')
30     {
31       prev= last;
32       last= s + 1;
33     }
34   return prev;
35 }
36 
37 /*
38   The following is very much single-threaded code and it's only supposed
39   to be used on shutdown or for a crash report
40   Or the caller should take care and use mutexes.
41 
42   Also it does not free any its memory. For the same reason -
43   it's only used for crash reports or on shutdown when we already
44   have a memory leak.
45 */
46 
47 #ifdef HAVE_BFD_H
48 #include <bfd.h>
49 static bfd *bfdh= 0;
50 static asymbol **symtable= 0;
51 
52 #if defined(HAVE_LINK_H) && defined(HAVE_DLOPEN)
53 #include <link.h>
54 static ElfW(Addr) offset= 0;
55 #else
56 #define offset 0
57 #endif
58 
59 #ifndef bfd_get_section_flags
60 #define bfd_get_section_flags(H, S) bfd_section_flags(S)
61 #endif /* bfd_get_section_flags */
62 
63 #ifndef bfd_get_section_size
64 #define bfd_get_section_size(S) bfd_section_size(S)
65 #endif /* bfd_get_section_size */
66 
67 #ifndef bfd_get_section_vma
68 #define bfd_get_section_vma(H, S) bfd_section_vma(S)
69 #endif /* bfd_get_section_vma */
70 
71 /**
72   finds a file name, a line number, and a function name corresponding to addr.
73 
74   the function name is demangled.
75   the file name is stripped of its path, only the two last components are kept
76   the resolving logic is mostly based on addr2line of binutils-2.17
77 
78   @return 0 on success, 1 on failure
79 */
my_addr_resolve(void * ptr,my_addr_loc * loc)80 int my_addr_resolve(void *ptr, my_addr_loc *loc)
81 {
82   bfd_vma addr= (intptr)ptr - offset;
83   asection *sec;
84 
85   for (sec= bfdh->sections; sec; sec= sec->next)
86   {
87     bfd_vma start;
88 
89     if ((bfd_get_section_flags(bfdh, sec) & SEC_ALLOC) == 0)
90       continue;
91 
92     start = bfd_get_section_vma(bfdh, sec);
93     if (addr < start || addr >= start + bfd_get_section_size(sec))
94       continue;
95 
96     if (bfd_find_nearest_line(bfdh, sec, symtable, addr - start,
97                               &loc->file, &loc->func, &loc->line))
98     {
99       if (loc->file)
100         loc->file= strip_path(loc->file);
101       else
102         loc->file= "";
103 
104       if (loc->func)
105       {
106         const char *str= bfd_demangle(bfdh, loc->func, 3);
107         if (str)
108           loc->func= str;
109       }
110 
111       return 0;
112     }
113   }
114 
115   return 1;
116 }
117 
my_addr_resolve_init()118 const char *my_addr_resolve_init()
119 {
120   if (!bfdh)
121   {
122     uint unused;
123     char **matching;
124 
125 #if defined(HAVE_LINK_H) && defined(HAVE_DLOPEN)
126     struct link_map *lm = (struct link_map*) dlopen(0, RTLD_NOW);
127     if (lm)
128       offset= lm->l_addr;
129 #endif
130 
131     bfdh= bfd_openr(my_progname, NULL);
132     if (!bfdh)
133       goto err;
134 
135     if (bfd_check_format(bfdh, bfd_archive))
136       goto err;
137     if (!bfd_check_format_matches (bfdh, bfd_object, &matching))
138       goto err;
139 
140     if (bfd_read_minisymbols(bfdh, FALSE, (void *)&symtable, &unused) < 0)
141       goto err;
142   }
143   return 0;
144 
145 err:
146   return bfd_errmsg(bfd_get_error());
147 }
148 #elif defined(HAVE_LIBELF_H)
149 /*
150   another possible implementation.
151 */
152 #elif defined(MY_ADDR_RESOLVE_FORK)
153 /*
154   yet another - just execute addr2line pipe the addresses to it, and parse the
155   output
156 */
157 
158 #include <m_string.h>
159 #include <ctype.h>
160 #include <sys/wait.h>
161 
162 #if defined(HAVE_POLL_H)
163 #include <poll.h>
164 #elif defined(HAVE_SYS_POLL_H)
165 #include <sys/poll.h>
166 #endif /* defined(HAVE_POLL_H) */
167 
168 static int in[2], out[2];
169 static pid_t pid;
170 static char addr2line_binary[1024];
171 static char output[1024];
172 static struct pollfd poll_fds;
173 static Dl_info info;
174 
start_addr2line_fork(const char * binary_path)175 int start_addr2line_fork(const char *binary_path)
176 {
177 
178   if (pid > 0)
179   {
180     /* Don't leak FDs */
181     close(in[1]);
182     close(out[0]);
183     /* Don't create zombie processes. */
184     waitpid(pid, NULL, 0);
185   }
186 
187   if (pipe(in) < 0)
188     return 1;
189   if (pipe(out) < 0)
190     return 1;
191 
192   pid = fork();
193   if (pid == -1)
194     return 1;
195 
196   if (!pid) /* child */
197   {
198     dup2(in[0], 0);
199     dup2(out[1], 1);
200     close(in[0]);
201     close(in[1]);
202     close(out[0]);
203     close(out[1]);
204     execlp("addr2line", "addr2line", "-C", "-f", "-e", binary_path, NULL);
205     exit(1);
206   }
207 
208   close(in[0]);
209   close(out[1]);
210 
211   return 0;
212 }
213 
my_addr_resolve(void * ptr,my_addr_loc * loc)214 int my_addr_resolve(void *ptr, my_addr_loc *loc)
215 {
216   char input[32];
217   size_t len;
218 
219   ssize_t total_bytes_read = 0;
220   ssize_t extra_bytes_read = 0;
221   ssize_t parsed = 0;
222 
223   int ret;
224 
225   int filename_start = -1;
226   int line_number_start = -1;
227 
228   void *offset;
229 
230   poll_fds.fd = out[0];
231   poll_fds.events = POLLIN | POLLRDBAND;
232 
233   if (!dladdr(ptr, &info))
234     return 1;
235 
236   if (strcmp(addr2line_binary, info.dli_fname))
237   {
238     /* We use dli_fname in case the path is longer than the length of our static
239        string. We don't want to allocate anything dynamicaly here as we are in
240        a "crashed" state. */
241     if (start_addr2line_fork(info.dli_fname))
242     {
243       addr2line_binary[0] = '\0';
244       return 2;
245     }
246     /* Save result for future comparisons. */
247     strnmov(addr2line_binary, info.dli_fname, sizeof(addr2line_binary));
248   }
249   offset = info.dli_fbase;
250   len= my_snprintf(input, sizeof(input), "%08x\n", (ulonglong)(ptr - offset));
251   if (write(in[1], input, len) <= 0)
252     return 3;
253 
254 
255   /* 500 ms should be plenty of time for addr2line to issue a response. */
256   /* Read in a loop till all the output from addr2line is complete. */
257   while (parsed == total_bytes_read &&
258          (ret= poll(&poll_fds, 1, 500)))
259   {
260     /* error during poll */
261     if (ret < 0)
262       return 1;
263 
264     extra_bytes_read= read(out[0], output + total_bytes_read,
265                            sizeof(output) - total_bytes_read);
266     if (extra_bytes_read < 0)
267       return 4;
268     /* Timeout or max bytes read. */
269     if (extra_bytes_read == 0)
270       break;
271 
272     total_bytes_read += extra_bytes_read;
273 
274     /* Go through the addr2line response and get the required data.
275        The response is structured in 2 lines. The first line contains the function
276        name, while the second one contains <filename>:<line number> */
277     for (; parsed < total_bytes_read; parsed++)
278     {
279       if (output[parsed] == '\n')
280       {
281         filename_start = parsed + 1;
282         output[parsed] = '\0';
283       }
284       if (filename_start != -1 && output[parsed] == ':')
285       {
286         line_number_start = parsed + 1;
287         output[parsed] = '\0';
288         break;
289       }
290     }
291   }
292 
293   /* Response is malformed. */
294   if (filename_start == -1 || line_number_start == -1)
295    return 5;
296 
297   loc->func= output;
298   loc->file= output + filename_start;
299   loc->line= atoi(output + line_number_start);
300 
301   /* Addr2line was unable to extract any meaningful information. */
302   if (strcmp(loc->file, "??") == 0)
303     return 6;
304 
305   loc->file= strip_path(loc->file);
306 
307   return 0;
308 }
309 
my_addr_resolve_init()310 const char *my_addr_resolve_init()
311 {
312   return 0;
313 }
314 #endif
315