From 01dd63c440d0ad09542b8d3b9301658a6e8a0d30 Mon Sep 17 00:00:00 2001 From: Calvin Buckley Date: Mon, 7 Oct 2019 19:19:28 -0300 Subject: [PATCH] dladdr shim for gmodule; try to enable crash reporter on AIX (#15808) * 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. --- configure.ac | 6 +- mono/eglib/Makefile.am | 2 +- mono/eglib/gmodule-aix.c | 233 ++++++++++++++++++++++++++++++++++++++++++++ mono/eglib/gmodule-unix.c | 35 +++++++ mono/eglib/gmodule-win32.c | 39 ++++++++ mono/eglib/gmodule.h | 4 + mono/mini/mini-exceptions.c | 20 ++-- mono/mini/mini-posix.c | 10 +- mono/profiler/log.c | 34 +++---- 9 files changed, 343 insertions(+), 40 deletions(-) create mode 100644 mono/eglib/gmodule-aix.c diff --git a/configure.ac b/configure.ac index 7e843e7758e..d47bc18d90f 100644 --- a/configure.ac +++ b/configure.ac @@ -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) diff --git a/mono/eglib/Makefile.am b/mono/eglib/Makefile.am index 0c5132c6338..5ca47551bc8 100644 --- a/mono/eglib/Makefile.am +++ b/mono/eglib/Makefile.am @@ -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/mono/eglib/gmodule-aix.c b/mono/eglib/gmodule-aix.c new file mode 100644 index 00000000000..7875967674d --- /dev/null +++ b/mono/eglib/gmodule-aix.c @@ -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 + +#include +#include + +#include +#include +#include +#include +#include + +/* AIX specific headers for loadquery and traceback structure */ +#include +#include + +/* 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 + diff --git a/mono/eglib/gmodule-unix.c b/mono/eglib/gmodule-unix.c index 4c0d53c208a..1e1d15f7f97 100644 --- a/mono/eglib/gmodule-unix.c +++ b/mono/eglib/gmodule-unix.c @@ -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) { diff --git a/mono/eglib/gmodule-win32.c b/mono/eglib/gmodule-win32.c index 88dabf1f0fd..15cbed35bf8 100644 --- a/mono/eglib/gmodule-win32.c +++ b/mono/eglib/gmodule-win32.c @@ -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) diff --git a/mono/eglib/gmodule.h b/mono/eglib/gmodule.h index 7976684f19c..ea53976b407 100644 --- a/mono/eglib/gmodule.h +++ b/mono/eglib/gmodule.h @@ -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); diff --git a/mono/mini/mini-exceptions.c b/mono/mini/mini-exceptions.c index b12322fbe3e..1854a3ca0ed 100644 --- a/mono/mini/mini-exceptions.c +++ b/mono/mini/mini-exceptions.c @@ -94,8 +94,8 @@ #define MONO_ARCH_CONTEXT_DEF #endif -#if !defined(HOST_WIN32) && !defined(DISABLE_CRASH_REPORTING) -#include +#if !defined(DISABLE_CRASH_REPORTING) +#include #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; } diff --git a/mono/mini/mini-posix.c b/mono/mini/mini-posix.c index db13a4b3812..8ffa1883e35 100644 --- a/mono/mini/mini-posix.c +++ b/mono/mini/mini-posix.c @@ -93,9 +93,7 @@ #endif #include -#ifndef HOST_WIN32 -#include -#endif +#include #if HAVE_SYS_STAT_H #include #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); } } diff --git a/mono/profiler/log.c b/mono/profiler/log.c index 4f32ef66763..3a79a4bd47a 100644 --- a/mono/profiler/log.c +++ b/mono/profiler/log.c @@ -12,6 +12,7 @@ */ #include +#include #include #include #include @@ -51,9 +52,6 @@ #include "log.h" #include "helper.h" -#ifdef HAVE_DLFCN_H -#include -#endif #include #ifdef HAVE_LINK_H #include @@ -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 */ } -- 2.11.4.GIT