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-1301 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*/
24static const char *strip_path(const char *s) __attribute__((unused));
25static 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>
49static bfd *bfdh= 0;
50static asymbol **symtable= 0;
51
52#if defined(HAVE_LINK_H) && defined(HAVE_DLOPEN)
53#include <link.h>
54static ElfW(Addr) offset= 0;
55#else
56#define offset 0
57#endif
58
59/**
60 finds a file name, a line number, and a function name corresponding to addr.
61
62 the function name is demangled.
63 the file name is stripped of its path, only the two last components are kept
64 the resolving logic is mostly based on addr2line of binutils-2.17
65
66 @return 0 on success, 1 on failure
67*/
68int my_addr_resolve(void *ptr, my_addr_loc *loc)
69{
70 bfd_vma addr= (intptr)ptr - offset;
71 asection *sec;
72
73 for (sec= bfdh->sections; sec; sec= sec->next)
74 {
75 bfd_vma start;
76
77 if ((bfd_get_section_flags(bfdh, sec) & SEC_ALLOC) == 0)
78 continue;
79
80 start = bfd_get_section_vma(bfdh, sec);
81 if (addr < start || addr >= start + bfd_get_section_size(sec))
82 continue;
83
84 if (bfd_find_nearest_line(bfdh, sec, symtable, addr - start,
85 &loc->file, &loc->func, &loc->line))
86 {
87 if (loc->file)
88 loc->file= strip_path(loc->file);
89 else
90 loc->file= "";
91
92 if (loc->func)
93 {
94 const char *str= bfd_demangle(bfdh, loc->func, 3);
95 if (str)
96 loc->func= str;
97 }
98
99 return 0;
100 }
101 }
102
103 return 1;
104}
105
106const char *my_addr_resolve_init()
107{
108 if (!bfdh)
109 {
110 uint unused;
111 char **matching;
112
113#if defined(HAVE_LINK_H) && defined(HAVE_DLOPEN)
114 struct link_map *lm = (struct link_map*) dlopen(0, RTLD_NOW);
115 if (lm)
116 offset= lm->l_addr;
117#endif
118
119 bfdh= bfd_openr(my_progname, NULL);
120 if (!bfdh)
121 goto err;
122
123 if (bfd_check_format(bfdh, bfd_archive))
124 goto err;
125 if (!bfd_check_format_matches (bfdh, bfd_object, &matching))
126 goto err;
127
128 if (bfd_read_minisymbols(bfdh, FALSE, (void *)&symtable, &unused) < 0)
129 goto err;
130 }
131 return 0;
132
133err:
134 return bfd_errmsg(bfd_get_error());
135}
136#elif defined(HAVE_LIBELF_H)
137/*
138 another possible implementation.
139*/
140#elif defined(MY_ADDR_RESOLVE_FORK)
141/*
142 yet another - just execute addr2line pipe the addresses to it, and parse the
143 output
144*/
145
146#include <m_string.h>
147#include <ctype.h>
148#include <sys/wait.h>
149
150static int in[2], out[2];
151static pid_t pid;
152static char addr2line_binary[1024];
153static char output[1024];
154
155int start_addr2line_fork(const char *binary_path)
156{
157
158 if (pid > 0)
159 {
160 /* Don't leak FDs */
161 close(in[1]);
162 close(out[0]);
163 /* Don't create zombie processes. */
164 waitpid(pid, NULL, 0);
165 }
166
167 if (pipe(in) < 0)
168 return 1;
169 if (pipe(out) < 0)
170 return 1;
171
172 pid = fork();
173 if (pid == -1)
174 return 1;
175
176 if (!pid) /* child */
177 {
178 dup2(in[0], 0);
179 dup2(out[1], 1);
180 close(in[0]);
181 close(in[1]);
182 close(out[0]);
183 close(out[1]);
184 execlp("addr2line", "addr2line", "-C", "-f", "-e", binary_path, NULL);
185 exit(1);
186 }
187
188 close(in[0]);
189 close(out[1]);
190
191 return 0;
192}
193
194int my_addr_resolve(void *ptr, my_addr_loc *loc)
195{
196 char input[32];
197 size_t len;
198
199 ssize_t total_bytes_read = 0;
200 ssize_t extra_bytes_read = 0;
201 ssize_t parsed = 0;
202
203 fd_set set;
204 struct timeval timeout;
205
206 int filename_start = -1;
207 int line_number_start = -1;
208
209 Dl_info info;
210 void *offset;
211
212 if (!dladdr(ptr, &info))
213 return 1;
214
215 if (strcmp(addr2line_binary, info.dli_fname))
216 {
217 /* We use dli_fname in case the path is longer than the length of our static
218 string. We don't want to allocate anything dynamicaly here as we are in
219 a "crashed" state. */
220 if (start_addr2line_fork(info.dli_fname))
221 {
222 addr2line_binary[0] = '\0';
223 return 2;
224 }
225 /* Save result for future comparisons. */
226 strnmov(addr2line_binary, info.dli_fname, sizeof(addr2line_binary));
227 }
228 offset = info.dli_fbase;
229 len= my_snprintf(input, sizeof(input), "%08x\n", (ulonglong)(ptr - offset));
230 if (write(in[1], input, len) <= 0)
231 return 3;
232
233 FD_ZERO(&set);
234 FD_SET(out[0], &set);
235
236 /* 100 ms should be plenty of time for addr2line to issue a response. */
237 timeout.tv_sec = 0;
238 timeout.tv_usec = 100000;
239 /* Read in a loop till all the output from addr2line is complete. */
240 while (parsed == total_bytes_read &&
241 select(out[0] + 1, &set, NULL, NULL, &timeout) > 0)
242 {
243 extra_bytes_read= read(out[0], output + total_bytes_read,
244 sizeof(output) - total_bytes_read);
245 if (extra_bytes_read < 0)
246 return 4;
247 /* Timeout or max bytes read. */
248 if (extra_bytes_read == 0)
249 break;
250
251 total_bytes_read += extra_bytes_read;
252
253 /* Go through the addr2line response and get the required data.
254 The response is structured in 2 lines. The first line contains the function
255 name, while the second one contains <filename>:<line number> */
256 for (; parsed < total_bytes_read; parsed++)
257 {
258 if (output[parsed] == '\n')
259 {
260 filename_start = parsed + 1;
261 output[parsed] = '\0';
262 }
263 if (filename_start != -1 && output[parsed] == ':')
264 {
265 line_number_start = parsed + 1;
266 output[parsed] = '\0';
267 break;
268 }
269 }
270 }
271
272 /* Response is malformed. */
273 if (filename_start == -1 || line_number_start == -1)
274 return 5;
275
276 loc->func= output;
277 loc->file= output + filename_start;
278 loc->line= atoi(output + line_number_start);
279
280 /* Addr2line was unable to extract any meaningful information. */
281 if (strcmp(loc->file, "??") == 0)
282 return 6;
283
284 loc->file= strip_path(loc->file);
285
286 return 0;
287}
288
289const char *my_addr_resolve_init()
290{
291 return 0;
292}
293#endif
294