dladdr shim for gmodule; try to enable crash reporter on AIX (mono/mono#15808)
authorCalvin Buckley <calvin@cmpct.info>
Mon, 7 Oct 2019 22:19:28 +0000 (19:19 -0300)
committerAlexander Köplinger <alex.koeplinger@outlook.com>
Mon, 7 Oct 2019 22:19:28 +0000 (00:19 +0200)
* Introduce new dladdr wrapper in glib, AIX reimpl, and convert usage

Provides a wrapper around dladdr that should be platform-neutral.

Also provided is a reimplementation for AIX, which should enable
that platform to get some previously dladdr-specific functions like
the crash reporter. It is somewhat flawed in that it only gets info
for symbols in .text, returns non-constant allocated strings, and
allocates heap memory for buffers.

Then convert usages (except a macOS specific one) of dladdr to the
new eglib function. It has a different signature, which should
simplify intent and reduce structiness. Also free memory on AIX due
to limitations of its reimplmenetation.

* Enable (and fix build of) crash reporter on AIX

So far crashes don't seem any different, but the code for it is
built.

* typo fix (guess this isn't compiled on aix)

* attempt to fix macOS and win32 builds

* Implement more of g_module_address on Win32

Implements the filename stuff. Untested; don't have Windows.

* Look for backtrace_symbols in an IBM compat library for i

Enables i to natively dump a stack trace. (AIX "works" too if you
copy the compat lib's code and hack it in - it probably needs to be
in EGlib.) Unfortunately, it's not perfect. The backchain from the
signal handler is empty on i, and AIX sometimes mangles the stack
frame in a way that severely confuses the backtrace code, causing
it to dereference invalid memory by interpreting instructions as a
pointer. (I have seen somewhat similar for the sigaltstack case,
where the native memory dump attempts to read something it should
not.) Perhaps mincore could be used, but that feels like a sloppy
workaround, especially in the "AIX making an invalid stack frame"
case.

* If the file isn't an archive, don't format its name like one

* Fix up search for backtrace_symbols to look in libexecinfo

FreeBSD ships it in base and Haiku has a package; likely others too.

* Win32 suffers in g_module_address as well

* That should habe been guarded with AIX due to limitations

Otherwise seems to free things that shouldn't be freed

* Casting for win32

* Fix up Win32 impl of module address

* Free HMODULE to lower reference count after using it

* Change dladdr based impl to always dup for impl consistency

Means freeing afterwards shouldn't be ifdefable.

* probably shouldn't strdup NULL

Likely why macOS has failures.

* Don't dup/return const strs, but copy to caller-provided buffers

...which can be on the stack. This makes it far less risky to use
in a crash reporter scenario, because malloc can be hosed then.

Also fix profiler up better to work with this new reality.

Commit migrated from https://github.com/mono/mono/commit/01dd63c440d0ad09542b8d3b9301658a6e8a0d30

src/mono/configure.ac
src/mono/mono/eglib/Makefile.am
src/mono/mono/eglib/gmodule-aix.c [new file with mode: 0644]
src/mono/mono/eglib/gmodule-unix.c
src/mono/mono/eglib/gmodule-win32.c
src/mono/mono/eglib/gmodule.h
src/mono/mono/mini/mini-exceptions.c
src/mono/mono/mini/mini-posix.c
src/mono/mono/profiler/log.c

index 7e843e7..d47bc18 100644 (file)
@@ -1174,7 +1174,7 @@ AC_DEFINE(DISABLE_STRUCTURED_CRASH,1,[Do not create structured crash files durin
 fi
 
 case "$host" in
-       *-mingw*|*-*-cygwin*|*-*-aix*|*-*-os400*)
+       *-mingw*|*-*-cygwin*)
                crash_reporting=no
                ;;
 esac
@@ -2217,6 +2217,10 @@ if test x$host_win32 = xno; then
        AC_CHECK_FUNCS(getresuid)
        AC_CHECK_FUNCS(setresuid)
        AC_CHECK_FUNCS(kqueue)
+       # IBM provides a compatibility library for i offering this function.
+       # BSDs and others, have execinfo in base or packages.
+       AC_SEARCH_LIBS(backtrace_symbols, execinfo util)
+       # Two-step so it sets it in config.h
        AC_CHECK_FUNCS(backtrace_symbols)
        AC_CHECK_FUNCS(mkstemp)
        AC_CHECK_FUNCS(mmap)
index 0c5132c..5ca4755 100644 (file)
@@ -15,7 +15,7 @@ win_files  = \
 
 unix_files = \
        gdate-unix.c  gdir-unix.c  gfile-unix.c  gmisc-unix.c   \
-       gmodule-unix.c gtimer-unix.c
+       gmodule-unix.c gtimer-unix.c gmodule-aix.c
 
 if HOST_WIN32
 os_files = $(win_files)
diff --git a/src/mono/mono/eglib/gmodule-aix.c b/src/mono/mono/eglib/gmodule-aix.c
new file mode 100644 (file)
index 0000000..7875967
--- /dev/null
@@ -0,0 +1,233 @@
+/*
+ * gmodule.c: dl* functions, glib style
+ *
+ * Author:
+ *   Gonzalo Paniagua Javier (gonzalo@novell.com)
+ *   Jonathan Chambers (joncham@gmail.com)
+ *   Robert Jordan (robertj@gmx.net)
+ *   Calvin Buckley (calvin@cmpct.info)
+ *
+ * (C) 2006 Novell, Inc.
+ * (C) 2006 Jonathan Chambers
+ * (C) 2019 Calvin Buckley
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+/* In case by some miracle, IBM implements this */
+#if defined(_AIX) && !defined(HAVE_DLADDR)
+#include <config.h>
+
+#include <glib.h>
+#include <gmodule.h>
+
+#include <stdio.h>
+#include <string.h>
+#include <dlfcn.h>
+#include <sys/errno.h>
+#include <stdlib.h>
+
+/* AIX specific headers for loadquery and traceback structure */
+#include <sys/ldr.h>
+#include <sys/debug.h>
+
+/* library filename + ( + member file name + ) + NUL */
+#define AIX_PRINTED_LIB_LEN ((PATH_MAX * 2) + 3)
+
+/*
+ * The structure that holds information for dladdr. Unfortunately, on AIX,
+ * the information returned by loadquery lives in an allocated buffer, so it
+ * should be freed when no longer needed. Note that sname /is/ still constant
+ * (it points to the traceback info in the image), so don't free it.
+ */
+typedef struct _g_dl_info {
+       char* dli_fname;
+       void* dli_fbase;
+       const char* dli_sname;
+       void* dli_saddr;
+} _g_Dl_info;
+
+/**
+ * Gets the base address and name of a symbol.
+ *
+ * This uses the traceback table at the function epilogue to get the base
+ * address and the name of a symbol. As such, this means that the input must
+ * be a word-aligned address within the text section.
+ *
+ * The way to support non-text (data/bss/whatever) would be to use an XCOFF
+ * parser on the image loaded in memory and snarf its symbol table. However,
+ * that is much more complex, and presumably, most addresses passed would be
+ * code in the text section anyways (I hope so, anyways...) Unfortunately,
+ * this does mean that function descriptors, which live in data, won't work.
+ * The traceback approach actually works with JITted code too, provided it
+ * could be emitted with XCOFF traceback...
+ */
+static void
+_g_sym_from_tb(void **sbase, const char **sname, void *where) {
+       unsigned int *s = (unsigned int*)where;
+       while (*s) {
+               /* look for zero word (invalid op) that begins epilogue */
+               s++;
+       }
+       /* We're on a zero word now, seek after the traceback table. */
+       struct tbtable_short *tb = (struct tbtable_short*)(s + 1);
+       /* The extended traceback is variable length, so more seeking. */
+       char *ext = (char*)(tb + 1);
+       /* Skip a lot of cruft, in order according to the ext "structure". */
+       if (tb->fixedparms || tb->floatparms) {
+               ext += sizeof(unsigned int);
+       }
+       if (tb->has_tboff) {
+               /* tb_offset */
+               void *start = (char*)s - *((unsigned int*)ext);
+               ext += sizeof (unsigned int);
+               *sbase = (void*)start;
+       } else {
+               /*
+                * Can we go backwards instead until we hit a null word,
+                * that /precedes/ the block of code?
+                * Does the XCOFF/traceback format allow for that?
+                */
+               *sbase = NULL; /* NULL base address as a sentinel */
+       }
+       if (tb->int_hndl) {
+               ext += sizeof(int);
+       }
+       if (tb->has_ctl) {
+               /* array */
+               int ctlnum =  (*(int*)ext);
+               ext += sizeof(int) + (sizeof(int) * ctlnum);
+       }
+       if (tb->name_present) {
+               /*
+                * The 16-bit name length is here, but the name seems to
+                * include a NUL, so we don't reallocate it, and instead
+                * just point to its location in memory.
+                */
+               ext += sizeof(short);
+               *sname = ext;
+       } else {
+               *sname = NULL;
+       }
+}
+
+/**
+ * Look for the base address and name of both a symbol and the corresponding
+ * executable in memory. This is a simplistic reimplementation for AIX.
+ *
+ * Returns 1 on failure and 0 on success. "s" is the address of the symbol,
+ * and "i" points to a Dl_info structure to fill. Note that i.dli_fname is
+ * not const, and should be freed.
+ */
+static int
+_g_dladdr(void* s, _g_Dl_info* i) {
+       /*
+        * Use stack instead of heap because malloc may be messed up.
+        * Init returned structure members to clear out any garbage.
+        */
+       char *buf = (char*)g_alloca(10000);
+       i->dli_fbase = NULL;
+       i->dli_fname = NULL;
+       i->dli_saddr = NULL;
+       i->dli_sname = NULL;
+       int r = loadquery (L_GETINFO, buf, 10000);
+       if (r == -1) {
+               return 0;
+       }
+       /* The loader info structures are also a linked list. */
+       struct ld_info *cur = (struct ld_info*) buf;
+       while (1) {
+               /*
+                * Check in text and data sections. Function descriptors are
+                * stored in the data section.
+                */
+               char *db = (char*)cur->ldinfo_dataorg;
+               char *tb = (char*)cur->ldinfo_textorg;
+               char *de = db + cur->ldinfo_datasize;
+               char *te = tb + cur->ldinfo_textsize;
+               /* Just casting for comparisons. */
+               char *cs = (char*)s;
+
+               /*
+                * Find the symbol's name and base address. To make it
+                * easier, we use the traceback in the text section.
+                * See the function's comments above as to why.
+                * (Perhaps we could deref if a descriptor though...)
+                */
+               if (cs >= tb && cs <= te) {
+                       _g_sym_from_tb(&i->dli_saddr, &i->dli_sname, s);
+               }
+
+               if ((cs >= db && cs <= de) || (cs >= tb && cs <= te)) {
+                       /* Look for file name and base address. */
+                       i->dli_fbase = tb; /* Includes XCOFF header */
+                       /* library filename + ( + member + ) + NUL */
+                       char *libname = (char*)g_alloca (AIX_PRINTED_LIB_LEN);
+                       char *file_part = cur->ldinfo_filename;
+                       char *member_part = file_part + strlen(file_part) + 1;
+                       /*
+                        * This can't be a const char*, because it exists from
+                        * a stack allocated buffer. Also append the member.
+                        *
+                        * XXX: See if we can't frob usla's memory ranges for
+                        * const strings; but is quite difficult.
+                        */
+                       if (member_part[0] == '\0') {
+                               /* Not an archive, just copy the file name. */
+                               g_strlcpy(libname, file_part, AIX_PRINTED_LIB_LEN);
+                       } else {
+                               /* It's an archive with member. */
+                               sprintf(libname, "%s(%s)", file_part, member_part);
+                       }
+                       i->dli_fname = libname;
+
+                       return 1;
+               } else if (cur->ldinfo_next == 0) {
+                       /* Nothing. */
+                       return 0;
+               } else {
+                       /* Try the next image in memory. */
+                       cur = (struct ld_info*)((char*)cur + cur->ldinfo_next);
+               }
+       }
+}
+
+gboolean
+g_module_address (void *addr, char *file_name, size_t file_name_len,
+                  void **file_base, char *sym_name, size_t sym_name_len,
+                  void **sym_addr)
+{
+       _g_Dl_info dli;
+       int ret = _g_dladdr(addr, &dli);
+       /* This zero-on-failure is unlike other Unix APIs. */
+       if (ret == 0)
+               return FALSE;
+       if (file_name != NULL && file_name_len >= 1)
+               g_strlcpy(file_name, dli.dli_fname, file_name_len);
+       if (file_base != NULL)
+               *file_base = dli.dli_fbase;
+       if (sym_name != NULL && sym_name_len >= 1)
+               g_strlcpy(sym_name, dli.dli_sname, sym_name_len);
+       if (sym_addr != NULL)
+               *sym_addr = dli.dli_saddr;
+       return TRUE;
+}
+#endif
+
index 4c0d53c..1e1d15f 100644 (file)
@@ -80,6 +80,41 @@ g_module_symbol (GModule *module, const gchar *symbol_name, gpointer *symbol)
        return (*symbol != NULL);
 }
 
+#if defined(HAVE_DLADDR)
+gboolean
+g_module_address (void *addr, char *file_name, size_t file_name_len,
+                  void **file_base, char *sym_name, size_t sym_name_len,
+                  void **sym_addr)
+{
+       Dl_info dli;
+       int ret = dladdr(addr, &dli);
+       /* This zero-on-failure is unlike other Unix APIs. */
+       if (ret == 0)
+               return FALSE;
+       /*
+        * AIX/Win32 return non-const, so we use caller-allocated bufs instead
+        */
+       if (file_name != NULL && file_name_len >= 1)
+               g_strlcpy(file_name, dli.dli_fname, file_name_len);
+       if (file_base != NULL)
+               *file_base = dli.dli_fbase;
+       if (sym_name != NULL && sym_name_len >= 1)
+               g_strlcpy(sym_name, dli.dli_sname, sym_name_len);
+       if (sym_addr != NULL)
+               *sym_addr = dli.dli_saddr;
+       return TRUE;
+}
+#elif !defined(_AIX)
+/* AIX has its own implementation that is long enough to be its own file. */
+gboolean
+g_module_address (void *addr, char *file_name, size_t file_name_len,
+                  void **file_base, char *sym_name, size_t sym_name_len,
+                  void **sym_addr)
+{
+       return FALSE;
+}
+#endif
+
 const gchar *
 g_module_error (void)
 {
index 88dabf1..15cbed3 100644 (file)
@@ -140,6 +140,45 @@ g_module_symbol (GModule *module, const gchar *symbol_name, gpointer *symbol)
        }
 }
 
+gboolean
+g_module_address (void *addr, char *file_name, size_t file_name_len,
+                  void **file_base, char *sym_name, size_t sym_name_len,
+                  void **sym_addr)
+{
+       HMODULE module;
+       /*
+        * We have to cast the address because usually this func works with strings,
+        * this being an exception.
+        */
+       BOOL ret = GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, (LPCWSTR)addr, &module);
+       if (ret)
+               return FALSE;
+
+       if (file_name != NULL && file_name_len >= 1) {
+               /* sigh, non-const. AIX for POSIX is the same way. */
+               TCHAR *fname = (TCHAR*)g_alloca(255);
+               DWORD bytes = GetModuleFileName(module, fname, 255);
+               /* XXX: check for ERROR_INSUFFICIENT_BUFFER? */
+               if (bytes) {
+                       /* Convert back to UTF-8 from wide for runtime */
+                       *file_name = '\0'; /* XXX */
+               } else {
+                       *file_name = '\0';
+               }
+       }
+       /* XXX: implement the rest */
+       if (file_base != NULL)
+               *file_base = NULL;
+       if (sym_name != NULL && sym_name_len >= 1)
+               sym_name[0] = '\0';
+       if (sym_addr != NULL)
+               *sym_addr = NULL;
+
+       /* -1 reference count to avoid leaks; Ex variant does +1 refcount */
+       FreeLibrary (module);
+       return TRUE;
+}
+
 #if G_HAVE_API_SUPPORT(HAVE_CLASSIC_WINAPI_SUPPORT)
 const gchar *
 g_module_error (void)
index 7976684..ea53976 100644 (file)
@@ -30,6 +30,10 @@ GModule *g_module_open (const gchar *file, GModuleFlags flags);
 G_EXTERN_C // Used by libtest, at least.
 gboolean g_module_symbol (GModule *module, const gchar *symbol_name,
                          gpointer *symbol);
+/* Caller must provide a suitable buffer. */
+gboolean g_module_address (void *addr, char *file_name, size_t file_name_len,
+                           void **file_base, char *sym_name,
+                           size_t sym_name_len, void **sym_addr);
 const gchar *g_module_error (void);
 gboolean g_module_close (GModule *module);
 gchar *  g_module_build_path (const gchar *directory, const gchar *module_name);
index b12322f..1854a3c 100644 (file)
@@ -94,8 +94,8 @@
 #define MONO_ARCH_CONTEXT_DEF
 #endif
 
-#if !defined(HOST_WIN32) && !defined(DISABLE_CRASH_REPORTING)
-#include <dlfcn.h>
+#if !defined(DISABLE_CRASH_REPORTING)
+#include <gmodule.h>
 #endif
 
 /*
@@ -1532,20 +1532,20 @@ mono_get_portable_ip (intptr_t in_ip, intptr_t *out_ip, gint32 *out_offset, cons
        // Note: it's not safe for us to be interrupted while inside of dl_addr, because if we
        // try to call dl_addr while interrupted while inside the lock, we will try to take a
        // non-recursive lock twice on this thread, and will deadlock.
-       Dl_info info;
-       gboolean success = dladdr ((void*)in_ip, &info);
+       char sname [256], fname [256];
+       void *saddr = NULL, *fbase = NULL;
+       gboolean success = g_module_address ((void*)in_ip, fname, 256, &fbase, sname, 256, &saddr);
        if (!success)
                return FALSE;
 
-       if (!check_whitelisted_module (info.dli_fname, out_module))
+       if (!check_whitelisted_module (fname, out_module))
                return FALSE;
 
-       *out_ip = mono_make_portable_ip ((intptr_t) info.dli_saddr, (intptr_t) info.dli_fbase);
-       *out_offset = in_ip - (intptr_t) info.dli_saddr;
-
-       if (info.dli_saddr && out_name)
-               copy_summary_string_safe (out_name, info.dli_sname);
+       *out_ip = mono_make_portable_ip ((intptr_t) saddr, (intptr_t) fbase);
+       *out_offset = in_ip - (intptr_t) saddr;
 
+       if (saddr && out_name)
+               copy_summary_string_safe (out_name, sname);
        return TRUE;
 }
 
index db13a4b..8ffa188 100644 (file)
@@ -93,9 +93,7 @@
 #endif
 
 #include <fcntl.h>
-#ifndef HOST_WIN32
-#include <dlfcn.h>
-#endif
+#include <gmodule.h>
 #if HAVE_SYS_STAT_H
 #include <sys/stat.h>
 #endif
@@ -938,12 +936,12 @@ dump_native_stacktrace (const char *signal, MonoContext *mctx)
 
        for (int i = 0; i < size; ++i) {
                gpointer ip = array [i];
-               Dl_info info;
-               gboolean success = dladdr ((void*) ip, &info);
+               char sname [256], fname [256];
+               gboolean success = g_module_address ((void*)ip, fname, 256, NULL, sname, 256, NULL);
                if (!success) {
                        g_async_safe_printf ("\t%p - Unknown\n", ip);
                } else {
-                       g_async_safe_printf ("\t%p - %s : %s\n", ip, info.dli_fname, info.dli_sname);
+                       g_async_safe_printf ("\t%p - %s : %s\n", ip, fname, sname);
                }
        }
 
index 4f32ef6..3a79a4b 100644 (file)
@@ -12,6 +12,7 @@
  */
 
 #include <config.h>
+#include <gmodule.h>
 #include <mono/metadata/assembly.h>
 #include <mono/metadata/assembly-internals.h>
 #include <mono/metadata/class-internals.h>
@@ -51,9 +52,6 @@
 #include "log.h"
 #include "helper.h"
 
-#ifdef HAVE_DLFCN_H
-#include <dlfcn.h>
-#endif
 #include <fcntl.h>
 #ifdef HAVE_LINK_H
 #include <link.h>
@@ -2422,7 +2420,7 @@ add_code_pointer (uintptr_t ip)
 }
 
 static void
-dump_usym (const char *name, uintptr_t value, uintptr_t size)
+dump_usym (char *name, uintptr_t value, uintptr_t size)
 {
        int len = strlen (name) + 1;
 
@@ -2442,42 +2440,34 @@ dump_usym (const char *name, uintptr_t value, uintptr_t size)
        EXIT_LOG;
 }
 
-static const char*
-symbol_for (uintptr_t code)
+static gboolean
+symbol_for (uintptr_t code, char *sname, size_t slen)
 {
-#ifdef HAVE_DLADDR
-       Dl_info di;
-
-       if (dladdr ((void *) code, &di))
-               if (di.dli_sname)
-                       return di.dli_sname;
-#endif
-
-       return NULL;
+       return g_module_address ((void *) code, NULL, 0, NULL, sname, slen, NULL);
 }
 
 static void
 dump_unmanaged_coderefs (void)
 {
        int i;
-       const char* last_symbol;
+       char last_symbol [256];
        uintptr_t addr, page_end;
 
        for (i = 0; i < size_code_pages; ++i) {
-               const char* sym;
+               char sym [256];
                if (!code_pages [i] || code_pages [i] & 1)
                        continue;
-               last_symbol = NULL;
+               last_symbol [0] = '\0';
                addr = CPAGE_ADDR (code_pages [i]);
                page_end = addr + CPAGE_SIZE;
                code_pages [i] |= 1;
                /* we dump the symbols for the whole page */
                for (; addr < page_end; addr += 16) {
-                       sym = symbol_for (addr);
-                       if (sym && sym == last_symbol)
+                       gboolean symret = symbol_for (addr, sym, 256);
+                       if (symret && strncmp (sym, last_symbol, 256) == 0)
                                continue;
-                       last_symbol = sym;
-                       if (!sym)
+                       g_strlcpy (last_symbol, sym, 256);
+                       if (sym [0] == '\0')
                                continue;
                        dump_usym (sym, addr, 0); /* let's not guess the size */
                }