summaryrefslogtreecommitdiffstats
path: root/mysys/my_addr_resolve.c
diff options
context:
space:
mode:
Diffstat (limited to 'mysys/my_addr_resolve.c')
-rw-r--r--mysys/my_addr_resolve.c353
1 files changed, 353 insertions, 0 deletions
diff --git a/mysys/my_addr_resolve.c b/mysys/my_addr_resolve.c
new file mode 100644
index 00000000..376e7368
--- /dev/null
+++ b/mysys/my_addr_resolve.c
@@ -0,0 +1,353 @@
+/* Copyright (C) 2011 Monty Program Ab
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 of the License.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335 USA */
+
+#include "mysys_priv.h"
+#include <m_string.h>
+#include <my_sys.h>
+#include <my_stacktrace.h>
+
+/**
+ strip the path, leave the file name and the last dirname
+*/
+static const char *strip_path(const char *s) __attribute__((unused));
+static const char *strip_path(const char *s)
+{
+ const char *prev, *last;
+ for(prev= last= s; *s; s++)
+ if (*s == '/' || *s == '\\')
+ {
+ prev= last;
+ last= s + 1;
+ }
+ return prev;
+}
+
+/*
+ The following is very much single-threaded code and it's only supposed
+ to be used on shutdown or for a crash report
+ Or the caller should take care and use mutexes.
+
+ Also it does not free any its memory. For the same reason -
+ it's only used for crash reports or on shutdown when we already
+ have a memory leak.
+*/
+
+#ifdef HAVE_BFD_H
+#include <bfd.h>
+static bfd *bfdh= 0;
+static asymbol **symtable= 0;
+
+#if defined(HAVE_LINK_H) && defined(HAVE_DLOPEN)
+#include <link.h>
+static ElfW(Addr) offset= 0;
+#else
+#define offset 0
+#endif
+
+#ifndef bfd_get_section_flags
+#define bfd_get_section_flags(H, S) bfd_section_flags(S)
+#endif /* bfd_get_section_flags */
+
+#ifndef bfd_get_section_size
+#define bfd_get_section_size(S) bfd_section_size(S)
+#endif /* bfd_get_section_size */
+
+#ifndef bfd_get_section_vma
+#define bfd_get_section_vma(H, S) bfd_section_vma(S)
+#endif /* bfd_get_section_vma */
+
+/**
+ finds a file name, a line number, and a function name corresponding to addr.
+
+ the function name is demangled.
+ the file name is stripped of its path, only the two last components are kept
+ the resolving logic is mostly based on addr2line of binutils-2.17
+
+ @return 0 on success, 1 on failure
+*/
+int my_addr_resolve(void *ptr, my_addr_loc *loc)
+{
+ bfd_vma addr= (intptr)ptr - offset;
+ asection *sec;
+
+ for (sec= bfdh->sections; sec; sec= sec->next)
+ {
+ bfd_vma start;
+
+ if ((bfd_get_section_flags(bfdh, sec) & SEC_ALLOC) == 0)
+ continue;
+
+ start = bfd_get_section_vma(bfdh, sec);
+ if (addr < start || addr >= start + bfd_get_section_size(sec))
+ continue;
+
+ if (bfd_find_nearest_line(bfdh, sec, symtable, addr - start,
+ &loc->file, &loc->func, &loc->line))
+ {
+ if (loc->file)
+ loc->file= strip_path(loc->file);
+ else
+ loc->file= "";
+
+ if (loc->func)
+ {
+ const char *str= bfd_demangle(bfdh, loc->func, 3);
+ if (str)
+ loc->func= str;
+ }
+
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+const char *my_addr_resolve_init()
+{
+ if (!bfdh)
+ {
+ uint unused;
+ char **matching;
+
+#if defined(HAVE_LINK_H) && defined(HAVE_DLOPEN)
+ struct link_map *lm = (struct link_map*) dlopen(0, RTLD_NOW);
+ if (lm)
+ offset= lm->l_addr;
+#endif
+
+ bfdh= bfd_openr(my_progname, NULL);
+ if (!bfdh)
+ goto err;
+
+ if (bfd_check_format(bfdh, bfd_archive))
+ goto err;
+ if (!bfd_check_format_matches (bfdh, bfd_object, &matching))
+ goto err;
+
+ if (bfd_read_minisymbols(bfdh, FALSE, (void *)&symtable, &unused) < 0)
+ goto err;
+ }
+ return 0;
+
+err:
+ return bfd_errmsg(bfd_get_error());
+}
+#elif defined(HAVE_LIBELF_H)
+/*
+ another possible implementation.
+*/
+#elif defined(MY_ADDR_RESOLVE_FORK)
+/*
+ yet another - just execute addr2line pipe the addresses to it, and parse the
+ output
+*/
+
+#include <m_string.h>
+#include <ctype.h>
+#include <sys/wait.h>
+
+#if defined(HAVE_POLL_H)
+#include <poll.h>
+#elif defined(HAVE_SYS_POLL_H)
+#include <sys/poll.h>
+#endif /* defined(HAVE_POLL_H) */
+
+static int in[2], out[2];
+static pid_t pid;
+static char addr2line_binary[1024];
+static char output[1024];
+static struct pollfd poll_fds;
+static void *addr_offset;
+
+int start_addr2line_fork(const char *binary_path)
+{
+
+ if (pid > 0)
+ {
+ /* Don't leak FDs */
+ close(in[1]);
+ close(out[0]);
+ /* Don't create zombie processes. */
+ waitpid(pid, NULL, 0);
+ }
+
+ if (pipe(in) < 0)
+ return 1;
+ if (pipe(out) < 0)
+ return 1;
+
+ pid = fork();
+ if (pid == -1)
+ return 1;
+
+ if (!pid) /* child */
+ {
+ dup2(in[0], 0);
+ dup2(out[1], 1);
+ close(in[0]);
+ close(in[1]);
+ close(out[0]);
+ close(out[1]);
+ execlp("addr2line", "addr2line", "-C", "-f", "-e", binary_path, NULL);
+ _exit(1);
+ }
+
+ close(in[0]);
+ close(out[1]);
+
+ return 0;
+}
+
+static int first_error= 0;
+
+static int addr_resolve(void *ptr, my_addr_loc *loc)
+{
+ char input[32];
+ size_t len;
+
+ ssize_t total_bytes_read = 0;
+ ssize_t extra_bytes_read = 0;
+ ssize_t parsed = 0;
+
+ int ret;
+
+ int filename_start = -1;
+ int line_number_start = -1;
+
+ poll_fds.fd = out[0];
+ poll_fds.events = POLLIN | POLLRDBAND;
+
+ len= my_snprintf(input, sizeof(input), "%p\n", ptr);
+ if (write(in[1], input, len) <= 0)
+ {
+ if (!first_error++)
+ fputs("Printing to addr2line failed\n", stderr);
+ return 3;
+ }
+
+
+ /* 5000 ms should be plenty of time for addr2line to issue a response. */
+ /* Read in a loop till all the output from addr2line is complete. */
+ while (parsed == total_bytes_read &&
+ (ret= poll(&poll_fds, 1, 5000)))
+ {
+ /* error during poll */
+ if (ret < 0)
+ return 1;
+
+ extra_bytes_read= read(out[0], output + total_bytes_read,
+ sizeof(output) - total_bytes_read);
+ if (extra_bytes_read < 0)
+ return 4;
+ /* Timeout or max bytes read. */
+ if (extra_bytes_read == 0)
+ break;
+
+ total_bytes_read += extra_bytes_read;
+
+ /* Go through the addr2line response and get the required data.
+ The response is structured in 2 lines. The first line contains the function
+ name, while the second one contains <filename>:<line number> */
+ for (; parsed < total_bytes_read; parsed++)
+ {
+ if (output[parsed] == '\n')
+ {
+ filename_start = parsed + 1;
+ output[parsed] = '\0';
+ }
+ if (filename_start != -1 && output[parsed] == ':')
+ {
+ line_number_start = parsed + 1;
+ output[parsed] = '\0';
+ break;
+ }
+ }
+ }
+
+ /* Response is malformed. */
+ if (filename_start == -1 || line_number_start == -1)
+ return 5;
+
+ loc->func= output;
+ loc->file= output + filename_start;
+ loc->line= atoi(output + line_number_start);
+
+ /* Addr2line was unable to extract any meaningful information. */
+ if ((strcmp(loc->file, "??") == 0 || strcmp(loc->file, "") == 0) &&
+ (loc->func[0] == '?' || loc->line == 0))
+ return 6;
+
+ loc->file= strip_path(loc->file);
+
+ return 0;
+}
+
+
+int my_addr_resolve(void *ptr, my_addr_loc *loc)
+{
+ Dl_info info;
+
+ if (!dladdr(ptr, &info))
+ return 1;
+
+ if (strcmp(addr2line_binary, info.dli_fname))
+ {
+ /*
+ We use dli_fname in case the path is longer than the length of
+ our static string. We don't want to allocate anything
+ dynamically here as we are in a "crashed" state.
+ */
+ if (start_addr2line_fork(info.dli_fname))
+ {
+ if (!first_error++)
+ fputs("Can't start addr2line\n", stderr);
+ addr2line_binary[0] = '\0';
+ return 2;
+ }
+ /* Save result for future comparisons. */
+ strnmov(addr2line_binary, info.dli_fname, sizeof(addr2line_binary));
+
+#ifdef _AIX
+ /*
+ info.dli_fbase is a char on AIX and casting it doesn't fool gcc.
+ leave backtracing broken on AIX until a real solution can be found.
+ */
+ addr_offset= NULL;
+#else
+ /*
+ Check if we should use info.dli_fbase as an offset or not
+ for the base program. This is depending on if the compilation is
+ done with PIE or not.
+ */
+ addr_offset= info.dli_fbase;
+#endif
+#ifndef __PIE__
+ if (strcmp(info.dli_fname, my_progname) == 0 &&
+ addr_resolve((void*) my_addr_resolve, loc) == 0 &&
+ strcmp(loc->func, "my_addr_resolve") == 0)
+ addr_offset= 0;
+#endif
+ }
+
+ return addr_resolve((void*) (ptr - addr_offset), loc);
+}
+
+
+const char *my_addr_resolve_init()
+{
+ return 0;
+}
+#endif