# HG changeset patch # User stuefe # Date 1504090543 -7200 # Wed Aug 30 12:55:43 2017 +0200 # Node ID 61edad2ec64eaed04683520f494acc46f85d8ae2 # Parent 1a9c2e07a82682c3eff42492d7e32f1d8c6639b8 8185712: [windows] Improve native symbol decoder Reviewed-by: goetz, zgu diff --git a/src/os/aix/vm/decoder_aix.hpp b/src/os/aix/vm/decoder_aix.hpp --- a/src/os/aix/vm/decoder_aix.hpp +++ b/src/os/aix/vm/decoder_aix.hpp @@ -34,8 +34,6 @@ } virtual ~AIXDecoder() {} - virtual bool can_decode_C_frame_in_vm() const { return true; } - virtual bool demangle(const char* symbol, char* buf, int buflen) { return false; } // use AixSymbols::get_function_name to demangle virtual bool decode(address addr, char* buf, int buflen, int* offset, const char* modulepath, bool demangle) { diff --git a/src/os/bsd/vm/decoder_machO.hpp b/src/os/bsd/vm/decoder_machO.hpp --- a/src/os/bsd/vm/decoder_machO.hpp +++ b/src/os/bsd/vm/decoder_machO.hpp @@ -35,9 +35,6 @@ public: MachODecoder() { } virtual ~MachODecoder() { } - virtual bool can_decode_C_frame_in_vm() const { - return true; - } virtual bool demangle(const char* symbol, char* buf, int buflen); virtual bool decode(address pc, char* buf, int buflen, int* offset, const void* base); diff --git a/src/os/windows/vm/decoder_windows.cpp b/src/os/windows/vm/decoder_windows.cpp --- a/src/os/windows/vm/decoder_windows.cpp +++ b/src/os/windows/vm/decoder_windows.cpp @@ -23,136 +23,28 @@ */ #include "precompiled.hpp" -#include "prims/jvm.h" -#include "runtime/arguments.hpp" -#include "runtime/os.hpp" -#include "decoder_windows.hpp" +#include "utilities/decoder.hpp" +#include "symbolengine.hpp" #include "windbghelp.hpp" -WindowsDecoder::WindowsDecoder() { - _can_decode_in_vm = true; - _decoder_status = no_error; - initialize(); +bool Decoder::decode(address addr, char* buf, int buflen, int* offset, const char* modulepath, bool demangle) { + return SymbolEngine::decode(addr, buf, buflen, offset, demangle); } -void WindowsDecoder::initialize() { - if (!has_error()) { - HANDLE hProcess = ::GetCurrentProcess(); - WindowsDbgHelp::symSetOptions(SYMOPT_UNDNAME | SYMOPT_DEFERRED_LOADS | SYMOPT_EXACT_SYMBOLS); - if (!WindowsDbgHelp::symInitialize(hProcess, NULL, TRUE)) { - _decoder_status = helper_init_error; - return; - } - - // set pdb search paths - char paths[MAX_PATH]; - int len = sizeof(paths); - if (!WindowsDbgHelp::symGetSearchPath(hProcess, paths, len)) { - paths[0] = '\0'; - } else { - // available spaces in path buffer - len -= (int)strlen(paths); - } - - char tmp_path[MAX_PATH]; - DWORD dwSize; - HMODULE hJVM = ::GetModuleHandle("jvm.dll"); - tmp_path[0] = '\0'; - // append the path where jvm.dll is located - if (hJVM != NULL && (dwSize = ::GetModuleFileName(hJVM, tmp_path, sizeof(tmp_path))) > 0) { - while (dwSize > 0 && tmp_path[dwSize] != '\\') { - dwSize --; - } - - tmp_path[dwSize] = '\0'; - - if (dwSize > 0 && len > (int)dwSize + 1) { - strncat(paths, os::path_separator(), 1); - strncat(paths, tmp_path, dwSize); - len -= dwSize + 1; - } - } - - // append $JRE/bin. Arguments::get_java_home actually returns $JRE - // path - char *p = Arguments::get_java_home(); - assert(p != NULL, "empty java home"); - size_t java_home_len = strlen(p); - if (len > (int)java_home_len + 5) { - strncat(paths, os::path_separator(), 1); - strncat(paths, p, java_home_len); - strncat(paths, "\\bin", 4); - len -= (int)(java_home_len + 5); - } - - // append $JDK/bin path if it exists - assert(java_home_len < MAX_PATH, "Invalid path length"); - // assume $JRE is under $JDK, construct $JDK/bin path and - // see if it exists or not - if (strncmp(&p[java_home_len - 3], "jre", 3) == 0) { - strncpy(tmp_path, p, java_home_len - 3); - tmp_path[java_home_len - 3] = '\0'; - strncat(tmp_path, "bin", 3); - - // if the directory exists - DWORD dwAttrib = GetFileAttributes(tmp_path); - if (dwAttrib != INVALID_FILE_ATTRIBUTES && - (dwAttrib & FILE_ATTRIBUTE_DIRECTORY)) { - // tmp_path should have the same length as java_home_len, since we only - // replaced 'jre' with 'bin' - if (len > (int)java_home_len + 1) { - strncat(paths, os::path_separator(), 1); - strncat(paths, tmp_path, java_home_len); - } - } - } - - WindowsDbgHelp::symSetSearchPath(hProcess, paths); - - // find out if jvm.dll contains private symbols, by decoding - // current function and comparing the result - address addr = (address)Decoder::demangle; - char buf[MAX_PATH]; - if (decode(addr, buf, sizeof(buf), NULL, NULL, true /* demangle */)) { - _can_decode_in_vm = !strcmp(buf, "Decoder::demangle"); - } - } +bool Decoder::decode(address addr, char* buf, int buflen, int* offset, const void* base) { + return SymbolEngine::decode(addr, buf, buflen, offset, true); } -void WindowsDecoder::uninitialize() {} - -bool WindowsDecoder::can_decode_C_frame_in_vm() const { - return (!has_error() && _can_decode_in_vm); +bool Decoder::get_source_info(address pc, char* buf, size_t buflen, int* line) { + return SymbolEngine::get_source_info(pc, buf, buflen, line); } - -bool WindowsDecoder::decode(address addr, char *buf, int buflen, int* offset, const char* modulepath, bool demangle_name) { - if (!has_error()) { - PIMAGEHLP_SYMBOL64 pSymbol; - char symbolInfo[MAX_PATH + sizeof(IMAGEHLP_SYMBOL64)]; - pSymbol = (PIMAGEHLP_SYMBOL64)symbolInfo; - pSymbol->MaxNameLength = MAX_PATH; - pSymbol->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL64); - DWORD64 displacement; - if (WindowsDbgHelp::symGetSymFromAddr64(::GetCurrentProcess(), (DWORD64)addr, &displacement, pSymbol)) { - if (buf != NULL) { - if (!(demangle_name && demangle(pSymbol->Name, buf, buflen))) { - jio_snprintf(buf, buflen, "%s", pSymbol->Name); - } - } - if(offset != NULL) *offset = (int)displacement; - return true; - } - } - if (buf != NULL && buflen > 0) buf[0] = '\0'; - if (offset != NULL) *offset = -1; - return false; +bool Decoder::demangle(const char* symbol, char* buf, int buflen) { + return SymbolEngine::demangle(symbol, buf, buflen); } -bool WindowsDecoder::demangle(const char* symbol, char *buf, int buflen) { - if (!has_error()) { - return WindowsDbgHelp::unDecorateSymbolName(symbol, buf, buflen, UNDNAME_COMPLETE) > 0; - } - return false; +void Decoder::print_state_on(outputStream* st) { + WindowsDbgHelp::print_state_on(st); + SymbolEngine::print_state_on(st); } diff --git a/src/os/windows/vm/decoder_windows.hpp b/src/os/windows/vm/decoder_windows.hpp deleted file mode 100644 --- a/src/os/windows/vm/decoder_windows.hpp +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (c) 2011, 2017, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. - * - * This code 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 - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - * - */ - -#ifndef OS_WINDOWS_VM_DECODER_WINDOWS_HPP -#define OS_WINDOWS_VM_DECIDER_WINDOWS_HPP - -#include "utilities/decoder.hpp" - -class WindowsDecoder : public AbstractDecoder { - -public: - WindowsDecoder(); - virtual ~WindowsDecoder() { uninitialize(); }; - - bool can_decode_C_frame_in_vm() const; - bool demangle(const char* symbol, char *buf, int buflen); - bool decode(address addr, char *buf, int buflen, int* offset, const char* modulepath, bool demangle); - bool decode(address addr, char *buf, int buflen, int* offset, const void* base) { - ShouldNotReachHere(); - return false; - } - -private: - void initialize(); - void uninitialize(); - - bool _can_decode_in_vm; - -}; - -#endif // OS_WINDOWS_VM_DECODER_WINDOWS_HPP diff --git a/src/os/windows/vm/os_windows.cpp b/src/os/windows/vm/os_windows.cpp --- a/src/os/windows/vm/os_windows.cpp +++ b/src/os/windows/vm/os_windows.cpp @@ -74,6 +74,7 @@ #include "utilities/growableArray.hpp" #include "utilities/macros.hpp" #include "utilities/vmError.hpp" +#include "symbolengine.hpp" #include "windbghelp.hpp" @@ -134,6 +135,8 @@ if (ForceTimeHighResolution) { timeBeginPeriod(1L); } + WindowsDbgHelp::pre_initialize(); + SymbolEngine::pre_initialize(); break; case DLL_PROCESS_DETACH: if (ForceTimeHighResolution) { @@ -1319,6 +1322,8 @@ void * os::dll_load(const char *name, char *ebuf, int ebuflen) { void * result = LoadLibrary(name); if (result != NULL) { + // Recalculate pdb search path if a DLL was loaded successfully. + SymbolEngine::recalc_search_path(); return result; } @@ -4032,6 +4037,8 @@ return JNI_ERR; } + SymbolEngine::recalc_search_path(); + return JNI_OK; } diff --git a/src/os/windows/vm/symbolengine.cpp b/src/os/windows/vm/symbolengine.cpp new file mode 100644 --- /dev/null +++ b/src/os/windows/vm/symbolengine.cpp @@ -0,0 +1,641 @@ +/* + * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#include "precompiled.hpp" +#include "utilities/globalDefinitions.hpp" +#include "symbolengine.hpp" +#include "utilities/debug.hpp" +#include "windbghelp.hpp" + +#include + +#include +#include + + + +// This code may be invoked normally but also as part of error reporting +// In the latter case, we may run under tight memory constraints (native oom) +// or in a stack overflow situation or the C heap may be corrupted. We may +// run very early before VM initialization or very late when C exit handlers +// run. In all these cases, callstacks would still be nice, so lets be robust. +// +// We need a number of buffers - for the pdb search path, module handle +// lists, for demangled symbols, etc. +// +// These buffers, while typically small, may need to be large for corner +// cases (e.g. templatized C++ symbols, or many DLLs loaded). Where do we +// allocate them? +// +// We may be in error handling for a stack overflow, so lets not put them on +// the stack. +// +// Dynamically allocating them may fail if we are handling a native OOM. It +// is also a bit dangerous, as the C heap may be corrupted already. +// +// That leaves pre-allocating them globally, which is safe and should always +// work (if we synchronize access) but incurs an undesirable footprint for +// non-error cases. +// +// We follow a two-way strategy: Allocate the buffers on the C heap in a +// reasonable large size. Failing that, fall back to static preallocated +// buffers. The size of the latter is large enough to handle common scenarios +// but small enough not to drive up the footprint too much (several kb). +// +// We keep these buffers around once allocated, for subsequent requests. This +// means that by running the initialization early at a safe time - before +// any error happens - buffers can be pre-allocated. This increases the chance +// of useful callstacks in error scenarios in exchange for a some cycles spent +// at startup. This behavior can be controlled with -XX:+InitializeDbgHelpEarly +// and is off by default. + +/////// + +// A simple buffer which attempts to allocate an optimal size but will +// fall back to a static minimally sized array on allocation error. +template +class SimpleBufferWithFallback { + T _fallback_buffer[MINIMAL_CAPACITY]; + T* _p; + int _capacity; + + // A sentinel at the end of the buffer to catch overflows. + void imprint_sentinel() { + assert(_p && _capacity > 0, "Buffer must be allocated"); + _p[_capacity - 1] = (T)'X'; + _capacity --; + } + +public: + + SimpleBufferWithFallback () + : _p(NULL), _capacity(0) + {} + + // Note: no destructor because these buffers should, once + // allocated, live until process end. + // ~SimpleBufferWithFallback() + + // Note: We use raw ::malloc/::free here instead of os::malloc()/os::free + // to prevent circularities or secondary crashes during error reporting. + virtual void initialize () { + assert(_p == NULL && _capacity == 0, "Only call once."); + const size_t bytes = OPTIMAL_CAPACITY * sizeof(T); + T* q = (T*) ::malloc(bytes); + if (q != NULL) { + _p = q; + _capacity = OPTIMAL_CAPACITY; + } else { + _p = _fallback_buffer; + _capacity = (int)(sizeof(_fallback_buffer) / sizeof(T)); + } + _p[0] = '\0'; + imprint_sentinel(); + } + + // We need a way to reset the buffer to fallback size for one special + // case, where two buffers need to be of identical capacity. + void reset_to_fallback_capacity() { + if (_p != _fallback_buffer) { + ::free(_p); + } + _p = _fallback_buffer; + _capacity = (int)(sizeof(_fallback_buffer) / sizeof(T)); + _p[0] = '\0'; + imprint_sentinel(); + } + + T* ptr() { return _p; } + const T* ptr() const { return _p; } + int capacity() const { return _capacity; } + +#ifdef ASSERT + void check() const { + assert(_p[_capacity] == (T)'X', "sentinel lost"); + } +#else + void check() const {} +#endif + +}; + +//// + +// ModuleHandleArray: a list holding module handles. Needs to be large enough +// to hold one handle per loaded DLL. +// Note: a standard OpenJDK loads normally ~30 libraries, including system +// libraries, without third party libraries. + +typedef SimpleBufferWithFallback ModuleHandleArrayBase; + +class ModuleHandleArray : public ModuleHandleArrayBase { + + int _num; // Number of handles in this array (may be < capacity). + +public: + + void initialize() { + ModuleHandleArrayBase::initialize(); + _num = 0; + } + + int num() const { return _num; } + void set_num(int n) { + assert(n <= capacity(), "Too large"); + _num = n; + } + + // Compare with another list; returns true if all handles are equal (incl. + // sort order) + bool equals(const ModuleHandleArray& other) const { + if (_num != other._num) { + return false; + } + if (::memcmp(ptr(), other.ptr(), _num * sizeof(HMODULE)) != 0) { + return false; + } + return true; + } + + // Copy content from other list. + void copy_content_from(ModuleHandleArray& other) { + assert(capacity() == other.capacity(), "Different capacities."); + memcpy(ptr(), other.ptr(), other._num * sizeof(HMODULE)); + _num = other._num; + } + +}; + +//// + +// PathBuffer: a buffer to hold and work with a pdb search PATH - a concatenation +// of multiple directories separated by ';'. +// A single directory name can be (NTFS) as long as 32K, but in reality is +// seldom larger than the (historical) MAX_PATH of 260. + +#define MINIMUM_PDB_PATH_LENGTH MAX_PATH * 4 +#define OPTIMAL_PDB_PATH_LENGTH MAX_PATH * 64 + +typedef SimpleBufferWithFallback PathBufferBase; + +class PathBuffer: public PathBufferBase { +public: + + // Search PDB path for a directory. Search is case insensitive. Returns + // true if directory was found in the path, false otherwise. + bool contains_directory(const char* directory) { + if (ptr() == NULL) { + return false; + } + const size_t len = strlen(directory); + if (len == 0) { + return false; + } + char* p = ptr(); + for(;;) { + char* q = strchr(p, ';'); + if (q != NULL) { + if (len == (q - p)) { + if (strnicmp(p, directory, len) == 0) { + return true; + } + } + p = q + 1; + } else { + // tail + return stricmp(p, directory) == 0 ? true : false; + } + } + return false; + } + + // Appends the given directory to the path. Returns false if internal + // buffer size was not sufficient. + bool append_directory(const char* directory) { + const size_t len = strlen(directory); + if (len == 0) { + return false; + } + char* p = ptr(); + const size_t len_now = strlen(p); + const size_t needs_capacity = len_now + 1 + len + 1; // xxx;yy\0 + if (needs_capacity > (size_t)capacity()) { + return false; // OOM + } + if (len_now > 0) { // Not the first path element. + p += len_now; + *p = ';'; + p ++; + } + strcpy(p, directory); + return true; + } + +}; + +// A simple buffer to hold one single file name. A file name can be (NTFS) as +// long as 32K, but in reality is seldom larger than MAX_PATH. +typedef SimpleBufferWithFallback FileNameBuffer; + +// A buffer to hold a C++ symbol. Usually small, but symbols may be larger for +// templates. +#define MINIMUM_SYMBOL_NAME_LEN 128 +#define OPTIMAL_SYMBOL_NAME_LEN 1024 + +typedef SimpleBufferWithFallback SymbolBuffer; + +static struct { + + // Two buffers to hold lists of loaded modules. handles across invocations of + // SymbolEngine::recalc_search_path(). + ModuleHandleArray loaded_modules; + ModuleHandleArray last_loaded_modules; + // Buffer to retrieve and assemble the pdb search path. + PathBuffer search_path; + // Buffer to retrieve directory names for loaded modules. + FileNameBuffer dir_name; + // Buffer to retrieve decoded symbol information (in SymbolEngine::decode) + SymbolBuffer decode_buffer; + + void initialize() { + search_path.initialize(); + dir_name.initialize(); + decode_buffer.initialize(); + + loaded_modules.initialize(); + last_loaded_modules.initialize(); + + // Note: both module lists must have the same capacity. If one allocation + // did fail, let them both fall back to the fallback size. + if (loaded_modules.capacity() != last_loaded_modules.capacity()) { + loaded_modules.reset_to_fallback_capacity(); + last_loaded_modules.reset_to_fallback_capacity(); + } + + assert(search_path.capacity() > 0 && dir_name.capacity() > 0 && + decode_buffer.capacity() > 0 && loaded_modules.capacity() > 0 && + last_loaded_modules.capacity() > 0, "Init error."); + } + +} g_buffers; + + +// Scan the loaded modules. +// +// For each loaded module, add the directory it is located in to the pdb search +// path, but avoid duplicates. Prior search path content is preserved. +// +// If p_search_path_was_updated is not NULL, points to a bool which, upon +// successful return from the function, contains true if the search path +// was updated, false if no update was needed because no new DLLs were +// loaded or unloaded. +// +// Returns true for success, false for error. +static bool recalc_search_path_locked(bool* p_search_path_was_updated) { + + if (p_search_path_was_updated) { + *p_search_path_was_updated = false; + } + + HANDLE hProcess = ::GetCurrentProcess(); + + BOOL success = false; + + // 1) Retrieve current set search path. + // (PDB search path is a global setting and someone might have modified + // it, so take care not to remove directories, just to add our own). + + if (!WindowsDbgHelp::symGetSearchPath(hProcess, g_buffers.search_path.ptr(), + (int)g_buffers.search_path.capacity())) { + return false; + } + DEBUG_ONLY(g_buffers.search_path.check();) + + // 2) Retrieve list of modules handles of all currently loaded modules. + DWORD bytes_needed = 0; + const DWORD buffer_capacity_bytes = (DWORD)g_buffers.loaded_modules.capacity() * sizeof(HMODULE); + success = ::EnumProcessModules(hProcess, g_buffers.loaded_modules.ptr(), + buffer_capacity_bytes, &bytes_needed); + DEBUG_ONLY(g_buffers.loaded_modules.check();) + + // Note: EnumProcessModules is sloppily defined in terms of whether a + // too-small output buffer counts as error. Will it truncate but still + // return TRUE? Nobody knows and the manpage is not telling. So we count + // truncation it as error, disregarding the return value. + if (!success || bytes_needed > buffer_capacity_bytes) { + return false; + } else { + const int num_modules = bytes_needed / sizeof(HMODULE); + g_buffers.loaded_modules.set_num(num_modules); + } + + // Compare the list of module handles with the last list. If the lists are + // identical, no additional dlls were loaded and we can stop. + if (g_buffers.loaded_modules.equals(g_buffers.last_loaded_modules)) { + return true; + } else { + // Remember the new set of module handles and continue. + g_buffers.last_loaded_modules.copy_content_from(g_buffers.loaded_modules); + } + + // 3) For each loaded module: retrieve directory from which it was loaded. + // Add directory to search path (but avoid duplicates). + + bool did_modify_searchpath = false; + + for (int i = 0; i < (int)g_buffers.loaded_modules.num(); i ++) { + + const HMODULE hMod = g_buffers.loaded_modules.ptr()[i]; + char* const filebuffer = g_buffers.dir_name.ptr(); + const int file_buffer_capacity = g_buffers.dir_name.capacity(); + const int len_returned = (int)::GetModuleFileName(hMod, filebuffer, (DWORD)file_buffer_capacity); + DEBUG_ONLY(g_buffers.dir_name.check();) + if (len_returned == 0) { + // Error. This is suspicious - this may happen if a module has just been + // unloaded concurrently after our call to EnumProcessModules and + // GetModuleFileName, but probably just indicates a coding error. + assert(false, "GetModuleFileName failed (%u)", ::GetLastError()); + } else if (len_returned == file_buffer_capacity) { + // Truncation. Just skip this module and continue with the next module. + continue; + } + + // Cut file name part off. + char* last_slash = ::strrchr(filebuffer, '\\'); + if (last_slash == NULL) { + last_slash = ::strrchr(filebuffer, '/'); + } + if (last_slash) { + *last_slash = '\0'; + } + + // If this is already part of the search path, ignore it, otherwise + // append to search path. + if (!g_buffers.search_path.contains_directory(filebuffer)) { + if (!g_buffers.search_path.append_directory(filebuffer)) { + return false; // oom + } + DEBUG_ONLY(g_buffers.search_path.check();) + did_modify_searchpath = true; + } + + } // for each loaded module. + + // If we did not modify the search path, nothing further needs to be done. + if (!did_modify_searchpath) { + return true; + } + + // Set the search path to its new value. + if (!WindowsDbgHelp::symSetSearchPath(hProcess, g_buffers.search_path.ptr())) { + return false; + } + + if (p_search_path_was_updated) { + *p_search_path_was_updated = true; + } + + return true; + +} + +static bool demangle_locked(const char* symbol, char *buf, int buflen) { + + return WindowsDbgHelp::unDecorateSymbolName(symbol, buf, buflen, UNDNAME_COMPLETE) > 0; + +} + +static bool decode_locked(const void* addr, char* buf, int buflen, int* offset, bool do_demangle) { + + assert(g_buffers.decode_buffer.capacity() >= (sizeof(IMAGEHLP_SYMBOL64) + MINIMUM_SYMBOL_NAME_LEN), + "Decode buffer too small."); + assert(buf != NULL && buflen > 0 && offset != NULL, "invalid output buffer."); + + DWORD64 displacement; + PIMAGEHLP_SYMBOL64 pSymbol = NULL; + bool success = false; + + pSymbol = (PIMAGEHLP_SYMBOL64) g_buffers.decode_buffer.ptr(); + pSymbol->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL64); + pSymbol->MaxNameLength = (DWORD)(g_buffers.decode_buffer.capacity() - sizeof(IMAGEHLP_SYMBOL64) - 1); + + // It is unclear how SymGetSymFromAddr64 handles truncation. Experiments + // show it will return TRUE but not zero terminate (which is a really bad + // combination). Lets be super careful. + ::memset(pSymbol->Name, 0, pSymbol->MaxNameLength); // To catch truncation. + + if (WindowsDbgHelp::symGetSymFromAddr64(::GetCurrentProcess(), (DWORD64)addr, &displacement, pSymbol)) { + success = true; + if (pSymbol->Name[pSymbol->MaxNameLength - 1] != '\0') { + // Symbol was truncated. Do not attempt to demangle. Instead, zero terminate the + // truncated string. We still return success - the truncated string may still + // be usable for the caller. + pSymbol->Name[pSymbol->MaxNameLength - 1] = '\0'; + do_demangle = false; + } + + // Attempt to demangle. + if (do_demangle && demangle_locked(pSymbol->Name, buf, buflen)) { + // ok. + } else { + ::strncpy(buf, pSymbol->Name, buflen - 1); + } + buf[buflen - 1] = '\0'; + + *offset = (int)displacement; + } + + DEBUG_ONLY(g_buffers.decode_buffer.check();) + + return success; +} + +static enum { + state_uninitialized = 0, + state_ready = 1, + state_error = 2 +} g_state = state_uninitialized; + +static void initialize() { + + assert(g_state == state_uninitialized, "wrong sequence"); + g_state = state_error; + + // 1) Initialize buffers. + g_buffers.initialize(); + + // 1) Call SymInitialize + HANDLE hProcess = ::GetCurrentProcess(); + WindowsDbgHelp::symSetOptions(SYMOPT_FAIL_CRITICAL_ERRORS | SYMOPT_DEFERRED_LOADS | + SYMOPT_EXACT_SYMBOLS | SYMOPT_LOAD_LINES); + if (!WindowsDbgHelp::symInitialize(hProcess, NULL, TRUE)) { + return; + } + + // Note: we ignore any errors from this point on. The symbol engine may be + // usable enough. + g_state = state_ready; + + (void)recalc_search_path_locked(NULL); + +} + +///////////////////// External functions ////////////////////////// + +// All outside facing functions are synchronized. Also, we run +// initialization on first touch. + +static CRITICAL_SECTION g_cs; + +namespace { // Do not export. + class SymbolEngineEntry { + public: + SymbolEngineEntry() { + ::EnterCriticalSection(&g_cs); + if (g_state == state_uninitialized) { + initialize(); + } + } + ~SymbolEngineEntry() { + ::LeaveCriticalSection(&g_cs); + } + }; +} + +// Called at DLL_PROCESS_ATTACH. +void SymbolEngine::pre_initialize() { + ::InitializeCriticalSection(&g_cs); +} + +bool SymbolEngine::decode(const void* addr, char* buf, int buflen, int* offset, bool do_demangle) { + + assert(buf != NULL && buflen > 0 && offset != NULL, "Argument error"); + buf[0] = '\0'; + *offset = -1; + + if (addr == NULL) { + return false; + } + + SymbolEngineEntry entry_guard; + + // Try decoding the symbol once. If we fail, attempt to rebuild the + // symbol search path - maybe the pc points to a dll whose pdb file is + // outside our search path. Then do attempt the decode again. + bool success = decode_locked(addr, buf, buflen, offset, do_demangle); + if (!success) { + bool did_update_search_path = false; + if (recalc_search_path_locked(&did_update_search_path)) { + if (did_update_search_path) { + success = decode_locked(addr, buf, buflen, offset, do_demangle); + } + } + } + + return success; + +} + +bool SymbolEngine::demangle(const char* symbol, char *buf, int buflen) { + + SymbolEngineEntry entry_guard; + + return demangle_locked(symbol, buf, buflen); + +} + +bool SymbolEngine::recalc_search_path(bool* p_search_path_was_updated) { + + SymbolEngineEntry entry_guard; + + return recalc_search_path_locked(p_search_path_was_updated); + +} + +bool SymbolEngine::get_source_info(const void* addr, char* buf, size_t buflen, + int* line_no) +{ + assert(buf != NULL && buflen > 0 && line_no != NULL, "Argument error"); + buf[0] = '\0'; + *line_no = -1; + + if (addr == NULL) { + return false; + } + + SymbolEngineEntry entry_guard; + + IMAGEHLP_LINE64 lineinfo; + memset(&lineinfo, 0, sizeof(lineinfo)); + lineinfo.SizeOfStruct = sizeof(lineinfo); + DWORD displacement; + if (WindowsDbgHelp::symGetLineFromAddr64(::GetCurrentProcess(), (DWORD64)addr, + &displacement, &lineinfo)) { + if (buf != NULL && buflen > 0 && lineinfo.FileName != NULL) { + // We only return the file name, not the whole path. + char* p = lineinfo.FileName; + char* q = strrchr(lineinfo.FileName, '\\'); + if (q) { + p = q + 1; + } + ::strncpy(buf, p, buflen - 1); + buf[buflen - 1] = '\0'; + } + if (line_no != 0) { + *line_no = lineinfo.LineNumber; + } + return true; + } + return false; +} + +// Print one liner describing state (if library loaded, which functions are +// missing - if any, and the dbhelp API version) +void SymbolEngine::print_state_on(outputStream* st) { + + SymbolEngineEntry entry_guard; + + st->print("symbol engine: "); + + if (g_state == state_uninitialized) { + st->print("uninitialized."); + } else if (g_state == state_error) { + st->print("initialization error."); + } else { + st->print("initialized successfully"); + st->print(" - sym options: 0x%X", WindowsDbgHelp::symGetOptions()); + st->print(" - pdb path: "); + if (WindowsDbgHelp::symGetSearchPath(::GetCurrentProcess(), + g_buffers.search_path.ptr(), + (int)g_buffers.search_path.capacity())) { + st->print_raw(g_buffers.search_path.ptr()); + } else { + st->print_raw("(cannot be retrieved)"); + } + } + st->cr(); + +} diff --git a/src/os/windows/vm/symbolengine.hpp b/src/os/windows/vm/symbolengine.hpp new file mode 100644 --- /dev/null +++ b/src/os/windows/vm/symbolengine.hpp @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, SAP SE. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef OS_WINDOWS_VM_SYMBOLENGINE_HPP +#define OS_WINDOWS_VM_SYMBOLENGINE_HPP + +class outputStream; + +namespace SymbolEngine { + + bool decode(const void* addr, char* buf, int buflen, int* offset, bool do_demangle); + + bool demangle(const char* symbol, char *buf, int buflen); + + // given an address, attempts to retrieve the source file and line number. + bool get_source_info(const void* addr, char* filename, size_t filename_len, + int* line_no); + + // Scan the loaded modules. Add all directories for all loaded modules + // to the current search path, unless they are already part of the search + // path. Prior search path content is preserved, directories are only + // added, never removed. + // If p_search_path_was_updated is not NULL, points to a bool which, upon + // successful return from the function, contains true if the search path + // was updated, false if no update was needed because no new DLLs were + // loaded or unloaded. + // Returns true for success, false for error. + bool recalc_search_path(bool* p_search_path_was_updated = NULL); + + // Print one liner describing state (if library loaded, which functions are + // missing - if any, and the dbhelp API version) + void print_state_on(outputStream* st); + + // Call at DLL_PROCESS_ATTACH. + void pre_initialize(); + +}; + +#endif // #ifndef OS_WINDOWS_VM_SYMBOLENGINE_HPP + + diff --git a/src/os/windows/vm/windbghelp.cpp b/src/os/windows/vm/windbghelp.cpp --- a/src/os/windows/vm/windbghelp.cpp +++ b/src/os/windows/vm/windbghelp.cpp @@ -116,38 +116,36 @@ } + ///////////////////// External functions ////////////////////////// // All outside facing functions are synchronized. Also, we run // initialization on first touch. - -// Call InitializeCriticalSection as early as possible. -class CritSect { - CRITICAL_SECTION cs; -public: - CritSect() { ::InitializeCriticalSection(&cs); } - void enter() { ::EnterCriticalSection(&cs); } - void leave() { ::LeaveCriticalSection(&cs); } -}; - -static CritSect g_cs; +static CRITICAL_SECTION g_cs; -class EntryGuard { -public: - EntryGuard() { - g_cs.enter(); - if (g_state == state_uninitialized) { - initialize(); +namespace { // Do not export. + class WindowsDbgHelpEntry { + public: + WindowsDbgHelpEntry() { + ::EnterCriticalSection(&g_cs); + if (g_state == state_uninitialized) { + initialize(); + } } - } - ~EntryGuard() { - g_cs.leave(); - } -}; + ~WindowsDbgHelpEntry() { + ::LeaveCriticalSection(&g_cs); + } + }; +} + +// Called at DLL_PROCESS_ATTACH. +void WindowsDbgHelp::pre_initialize() { + ::InitializeCriticalSection(&g_cs); +} DWORD WindowsDbgHelp::symSetOptions(DWORD arg) { - EntryGuard entry_guard; + WindowsDbgHelpEntry entry_guard; if (g_pfn_SymSetOptions != NULL) { return g_pfn_SymSetOptions(arg); } @@ -155,7 +153,7 @@ } DWORD WindowsDbgHelp::symGetOptions(void) { - EntryGuard entry_guard; + WindowsDbgHelpEntry entry_guard; if (g_pfn_SymGetOptions != NULL) { return g_pfn_SymGetOptions(); } @@ -163,7 +161,7 @@ } BOOL WindowsDbgHelp::symInitialize(HANDLE hProcess, PCTSTR UserSearchPath, BOOL fInvadeProcess) { - EntryGuard entry_guard; + WindowsDbgHelpEntry entry_guard; if (g_pfn_SymInitialize != NULL) { return g_pfn_SymInitialize(hProcess, UserSearchPath, fInvadeProcess); } @@ -172,7 +170,7 @@ BOOL WindowsDbgHelp::symGetSymFromAddr64(HANDLE hProcess, DWORD64 the_address, PDWORD64 Displacement, PIMAGEHLP_SYMBOL64 Symbol) { - EntryGuard entry_guard; + WindowsDbgHelpEntry entry_guard; if (g_pfn_SymGetSymFromAddr64 != NULL) { return g_pfn_SymGetSymFromAddr64(hProcess, the_address, Displacement, Symbol); } @@ -181,7 +179,7 @@ DWORD WindowsDbgHelp::unDecorateSymbolName(const char* DecoratedName, char* UnDecoratedName, DWORD UndecoratedLength, DWORD Flags) { - EntryGuard entry_guard; + WindowsDbgHelpEntry entry_guard; if (g_pfn_UnDecorateSymbolName != NULL) { return g_pfn_UnDecorateSymbolName(DecoratedName, UnDecoratedName, UndecoratedLength, Flags); } @@ -192,7 +190,7 @@ } BOOL WindowsDbgHelp::symSetSearchPath(HANDLE hProcess, PCTSTR SearchPath) { - EntryGuard entry_guard; + WindowsDbgHelpEntry entry_guard; if (g_pfn_SymSetSearchPath != NULL) { return g_pfn_SymSetSearchPath(hProcess, SearchPath); } @@ -200,7 +198,7 @@ } BOOL WindowsDbgHelp::symGetSearchPath(HANDLE hProcess, PTSTR SearchPath, int SearchPathLength) { - EntryGuard entry_guard; + WindowsDbgHelpEntry entry_guard; if (g_pfn_SymGetSearchPath != NULL) { return g_pfn_SymGetSearchPath(hProcess, SearchPath, SearchPathLength); } @@ -212,7 +210,7 @@ HANDLE hThread, LPSTACKFRAME64 StackFrame, PVOID ContextRecord) { - EntryGuard entry_guard; + WindowsDbgHelpEntry entry_guard; if (g_pfn_StackWalk64 != NULL) { return g_pfn_StackWalk64(MachineType, hProcess, hThread, StackFrame, ContextRecord, @@ -226,7 +224,7 @@ } PVOID WindowsDbgHelp::symFunctionTableAccess64(HANDLE hProcess, DWORD64 AddrBase) { - EntryGuard entry_guard; + WindowsDbgHelpEntry entry_guard; if (g_pfn_SymFunctionTableAccess64 != NULL) { return g_pfn_SymFunctionTableAccess64(hProcess, AddrBase); } @@ -234,7 +232,7 @@ } DWORD64 WindowsDbgHelp::symGetModuleBase64(HANDLE hProcess, DWORD64 dwAddr) { - EntryGuard entry_guard; + WindowsDbgHelpEntry entry_guard; if (g_pfn_SymGetModuleBase64 != NULL) { return g_pfn_SymGetModuleBase64(hProcess, dwAddr); } @@ -245,7 +243,7 @@ MINIDUMP_TYPE DumpType, PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam, PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam, PMINIDUMP_CALLBACK_INFORMATION CallbackParam) { - EntryGuard entry_guard; + WindowsDbgHelpEntry entry_guard; if (g_pfn_MiniDumpWriteDump != NULL) { return g_pfn_MiniDumpWriteDump(hProcess, ProcessId, hFile, DumpType, ExceptionParam, UserStreamParam, CallbackParam); @@ -255,7 +253,7 @@ BOOL WindowsDbgHelp::symGetLineFromAddr64(HANDLE hProcess, DWORD64 dwAddr, PDWORD pdwDisplacement, PIMAGEHLP_LINE64 Line) { - EntryGuard entry_guard; + WindowsDbgHelpEntry entry_guard; if (g_pfn_SymGetLineFromAddr64 != NULL) { return g_pfn_SymGetLineFromAddr64(hProcess, dwAddr, pdwDisplacement, Line); } diff --git a/src/os/windows/vm/windbghelp.hpp b/src/os/windows/vm/windbghelp.hpp --- a/src/os/windows/vm/windbghelp.hpp +++ b/src/os/windows/vm/windbghelp.hpp @@ -66,6 +66,9 @@ // missing - if any, and the dbhelp API version) void print_state_on(outputStream* st); + // Call at DLL_PROCESS_ATTACH. + void pre_initialize(); + }; diff --git a/src/os_cpu/windows_x86/vm/os_windows_x86.cpp b/src/os_cpu/windows_x86/vm/os_windows_x86.cpp --- a/src/os_cpu/windows_x86/vm/os_windows_x86.cpp +++ b/src/os_cpu/windows_x86/vm/os_windows_x86.cpp @@ -50,6 +50,7 @@ #include "runtime/stubRoutines.hpp" #include "runtime/thread.inline.hpp" #include "runtime/timer.hpp" +#include "symbolengine.hpp" #include "unwind_windows_x86.hpp" #include "utilities/events.hpp" #include "utilities/vmError.hpp" @@ -397,6 +398,12 @@ // may not contain what Java expects, and may cause the frame() constructor // to crash. Let's just print out the symbolic address. frame::print_C_frame(st, buf, buf_size, pc); + // print source file and line, if available + char buf[128]; + int line_no; + if (SymbolEngine::get_source_info(pc, buf, sizeof(buf), &line_no)) { + st->print(" (%s:%d)", buf, line_no); + } st->cr(); } lastpc = pc; diff --git a/src/share/vm/runtime/frame.cpp b/src/share/vm/runtime/frame.cpp --- a/src/share/vm/runtime/frame.cpp +++ b/src/share/vm/runtime/frame.cpp @@ -627,16 +627,9 @@ st->print(" " PTR_FORMAT, p2i(pc)); } - // function name - os::dll_address_to_function_name() may return confusing - // names if pc is within jvm.dll or libjvm.so, because JVM only has - // JVM_xxxx and a few other symbols in the dynamic symbol table. Do this - // only for native libraries. - if (!in_vm || Decoder::can_decode_C_frame_in_vm()) { - found = os::dll_address_to_function_name(pc, buf, buflen, &offset); - - if (found) { - st->print(" %s+0x%x", buf, offset); - } + found = os::dll_address_to_function_name(pc, buf, buflen, &offset); + if (found) { + st->print(" %s+0x%x", buf, offset); } } diff --git a/src/share/vm/utilities/decoder.cpp b/src/share/vm/utilities/decoder.cpp --- a/src/share/vm/utilities/decoder.cpp +++ b/src/share/vm/utilities/decoder.cpp @@ -28,10 +28,8 @@ #include "utilities/decoder.hpp" #include "utilities/vmError.hpp" -#if defined(_WINDOWS) - #include "decoder_windows.hpp" - #include "windbghelp.hpp" -#elif defined(__APPLE__) +#ifndef _WINDOWS +#if defined(__APPLE__) #include "decoder_machO.hpp" #elif defined(AIX) #include "decoder_aix.hpp" @@ -67,9 +65,7 @@ AbstractDecoder* Decoder::create_decoder() { AbstractDecoder* decoder; -#if defined(_WINDOWS) - decoder = new (std::nothrow) WindowsDecoder(); -#elif defined (__APPLE__) +#if defined (__APPLE__) decoder = new (std::nothrow)MachODecoder(); #elif defined(AIX) decoder = new (std::nothrow)AIXDecoder(); @@ -136,36 +132,12 @@ return decoder->demangle(symbol, buf, buflen); } -bool Decoder::can_decode_C_frame_in_vm() { - assert(_shared_decoder_lock != NULL, "Just check"); - bool error_handling_thread = os::current_thread_id() == VMError::first_error_tid; - MutexLockerEx locker(error_handling_thread ? NULL : _shared_decoder_lock, true); - AbstractDecoder* decoder = error_handling_thread ? - get_error_handler_instance(): get_shared_instance(); - assert(decoder != NULL, "null decoder"); - return decoder->can_decode_C_frame_in_vm(); +void Decoder::print_state_on(outputStream* st) { } -/* - * Shutdown shared decoder and replace it with - * _do_nothing_decoder. Do nothing with error handler - * instance, since the JVM is going down. - */ -void Decoder::shutdown() { - assert(_shared_decoder_lock != NULL, "Just check"); - MutexLockerEx locker(_shared_decoder_lock, true); - - if (_shared_decoder != NULL && - _shared_decoder != &_do_nothing_decoder) { - delete _shared_decoder; - } - - _shared_decoder = &_do_nothing_decoder; +bool Decoder::get_source_info(address pc, char* buf, size_t buflen, int* line) { + return false; } -void Decoder::print_state_on(outputStream* st) { -#ifdef _WINDOWS - WindowsDbgHelp::print_state_on(st); -#endif -} +#endif // !_WINDOWS diff --git a/src/share/vm/utilities/decoder.hpp b/src/share/vm/utilities/decoder.hpp --- a/src/share/vm/utilities/decoder.hpp +++ b/src/share/vm/utilities/decoder.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2015, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2017, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -58,8 +58,6 @@ // demangle a C++ symbol virtual bool demangle(const char* symbol, char* buf, int buflen) = 0; - // if the decoder can decode symbols in vm - virtual bool can_decode_C_frame_in_vm() const = 0; virtual decoder_status status() const { return _decoder_status; @@ -99,9 +97,6 @@ return false; } - virtual bool can_decode_C_frame_in_vm() const { - return false; - } }; @@ -113,10 +108,11 @@ } static bool decode(address pc, char* buf, int buflen, int* offset, const void* base); static bool demangle(const char* symbol, char* buf, int buflen); - static bool can_decode_C_frame_in_vm(); - // shutdown shared instance - static void shutdown(); + // Attempts to retrieve source file name and line number associated with a pc. + // If buf != NULL, points to a buffer of size buflen which will receive the + // file name. File name will be silently truncated if output buffer is too small. + static bool get_source_info(address pc, char* buf, size_t buflen, int* line); static void print_state_on(outputStream* st); diff --git a/src/share/vm/utilities/decoder_elf.hpp b/src/share/vm/utilities/decoder_elf.hpp --- a/src/share/vm/utilities/decoder_elf.hpp +++ b/src/share/vm/utilities/decoder_elf.hpp @@ -39,8 +39,6 @@ } virtual ~ElfDecoder(); - bool can_decode_C_frame_in_vm() const { return true; } - bool demangle(const char* symbol, char *buf, int buflen); bool decode(address addr, char *buf, int buflen, int* offset, const char* filepath, bool demangle); bool decode(address addr, char *buf, int buflen, int* offset, const void *base) { diff --git a/src/share/vm/utilities/vmError.cpp b/src/share/vm/utilities/vmError.cpp --- a/src/share/vm/utilities/vmError.cpp +++ b/src/share/vm/utilities/vmError.cpp @@ -232,6 +232,13 @@ int count = 0; while (count++ < StackPrintLimit) { fr.print_on_error(st, buf, buf_size); + if (fr.pc()) { // print source file and line, if available + char buf[128]; + int line_no; + if (Decoder::get_source_info(fr.pc(), buf, sizeof(buf), &line_no)) { + st->print(" (%s:%d)", buf, line_no); + } + } st->cr(); // Compiled code may use EBP register on x86 so it looks like // non-walkable C frame. Use frame.sender() for java frames. diff --git a/test/native/runtime/test_os.cpp b/test/native/runtime/test_os.cpp --- a/test/native/runtime/test_os.cpp +++ b/test/native/runtime/test_os.cpp @@ -24,6 +24,7 @@ #include "precompiled.hpp" #include "runtime/os.hpp" #include "unittest.hpp" +#include "utilities/decoder.hpp" static size_t small_page_size() { return os::vm_page_size(); @@ -143,6 +144,122 @@ ASSERT_LT(t, eps) << "bad variance"; } +#ifdef _WIN32 +TEST(os, dll_addr_to_function_valid) { + char buf[128] = ""; + int offset = -1; + address valid_function_pointer = (address) JNI_CreateJavaVM; + ASSERT_TRUE(os::dll_address_to_function_name(valid_function_pointer, + buf, sizeof(buf), &offset, true) == true); + ASSERT_TRUE(strstr(buf, "JNI_CreateJavaVM") != NULL); + ASSERT_TRUE(offset >= 0); +} + +// Test that handing down a too-small output buffer will truncate the output +// string correctly but cause no harm otherwise. +TEST(os, dll_addr_to_function_valid_truncated) { + char buf[128] = ""; + int offset = -1; + memset(buf, 'X', sizeof(buf)); + address valid_function_pointer = (address) JNI_CreateJavaVM; + ASSERT_TRUE(os::dll_address_to_function_name(valid_function_pointer, + buf, 10, &offset, true) == true); + ASSERT_TRUE(buf[10 - 1] == '\0'); + ASSERT_TRUE(buf[10] == 'X'); + // Note: compare the first (sizeof buf - 2) bytes only because Windows decoder + // (actually UndecorateSymbolName()) seems to have a bug where it uses one byte less than + // the buffer would offer. + ASSERT_TRUE(strncmp(buf, "JNI_Crea", 8) == 0); + ASSERT_TRUE(offset >= 0); +} + +// Test that handing down invalid addresses will cause no harm and output buffer +// and offset will contain "" and -1, respectively. +TEST(os, dll_addr_to_function_invalid) { + char buf[128]; + int offset; + address invalid_function_pointers[] = { NULL, (address)1, (address)&offset }; + + for (int i = 0; + i < sizeof(invalid_function_pointers) / sizeof(address); + i ++) + { + address addr = invalid_function_pointers[i]; + strcpy(buf, "blabla"); + offset = 12; + ASSERT_TRUE(os::dll_address_to_function_name(addr, buf, sizeof(buf), + &offset, true) == false); + ASSERT_TRUE(buf[0] == '\0'); + ASSERT_TRUE(offset == -1); + } +} + +TEST(os, decoder_get_source_info_valid) { + char buf[128] = ""; + int line = -1; + address valid_function_pointer = (address) JNI_CreateJavaVM; + ASSERT_TRUE(Decoder::get_source_info(valid_function_pointer, buf, sizeof(buf), &line) == true); + ASSERT_TRUE(strcmp(buf, "jni.cpp") == 0); + ASSERT_TRUE(line >= 0); +} + +// Test that handing down a too-small output buffer will truncate the output +// string correctly but cause no harm otherwise. +TEST(os, decoder_get_source_info_valid_truncated) { + char buf[128] = ""; + int line = -1; + memset(buf, 'X', sizeof(buf)); + address valid_function_pointer = (address) JNI_CreateJavaVM; + ASSERT_TRUE(Decoder::get_source_info(valid_function_pointer, buf, 7, &line) == true); + ASSERT_TRUE(buf[7 - 1] == '\0'); + ASSERT_TRUE(buf[7] == 'X'); + ASSERT_TRUE(strcmp(buf, "jni.cp") == 0); + ASSERT_TRUE(line > 0); +} + +// Test that handing down invalid addresses will cause no harm and output buffer +// and line will contain "" and -1, respectively. +TEST(os, decoder_get_source_info_invalid) { + char buf[128] = ""; + int line = -1; + address invalid_function_pointers[] = { NULL, (address)1, (address)&line }; + + for (int i = 0; + i < sizeof(invalid_function_pointers) / sizeof(address); + i ++) + { + address addr = invalid_function_pointers[i]; + // We should fail but not crash + strcpy(buf, "blabla"); + line = 12; + ASSERT_TRUE(Decoder::get_source_info(addr, buf, sizeof(buf), &line) == false); + // buffer should contain "", offset should contain -1 + ASSERT_TRUE(buf[0] == '\0'); + ASSERT_TRUE(line == -1); + } +} + +#ifdef PLATFORM_PRINT_NATIVE_STACK +TEST(os, platform_print_native_stack) { + bufferedStream bs; + // Note: scratch buffer argument to os::platform_print_native_stack is not + // optional! + char scratch_buffer [255]; + for (int i = 0; i < 3; i ++) { + ASSERT_TRUE(os::platform_print_native_stack(&bs, NULL, scratch_buffer, + sizeof(scratch_buffer))); + ASSERT_TRUE(bs.size() > 0); + // This may depend on debug information files being generated and available + // (e.g. not zipped). + ASSERT_TRUE(::strstr(bs.base(), "platform_print_native_stack") != NULL); +#ifdef _WIN32 + // We have source information on Windows. + ASSERT_TRUE(::strstr(bs.base(), "os_windows_x86.cpp") != NULL); +#endif + } +} +#endif +#endif #ifdef ASSERT TEST_VM_ASSERT_MSG(os, page_size_for_region_with_zero_min_pages, "sanity") { diff --git a/test/runtime/ErrorHandling/CallstackAfterSegfault.java b/test/runtime/ErrorHandling/CallstackAfterSegfault.java new file mode 100644 --- /dev/null +++ b/test/runtime/ErrorHandling/CallstackAfterSegfault.java @@ -0,0 +1,140 @@ +/* +* Copyright (c) 2013, 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + + +/* + * @test + * @bug 8185712 + * @summary Callstacks shall be printed correctly after a sigsegv. + * @library /test/lib + * @requires (vm.debug == true) & (os.family == "windows") + * @author Thomas Stuefe (SAP) + * @modules java.base/jdk.internal.misc + * java.management + */ + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStreamReader; +import java.util.regex.Pattern; + +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.Platform; +import jdk.test.lib.process.ProcessTools; + +public class CallstackAfterSegfault { + + // Whether callstack should show the name of the binary (shared lib/executable) + final static boolean with_binary_name = true; + // Whether callstack should show File information (source file, line) + final static boolean with_source_file_info = Platform.isWindows(); + + private static String buildFramePattern(String library, String functionName, String sourcefile) { + StringBuilder bld = new StringBuilder(); + bld.append(".*"); + if (with_binary_name) { + // We only append the raw library name without extensions or prefix, to match on all platforms. + bld.append(library); + bld.append(".*"); + } + bld.append(functionName); + bld.append(".*"); + if (with_source_file_info) { + bld.append(sourcefile); + bld.append(":\\d+"); + bld.append(".*"); + } + return bld.toString(); + } + + public static void main(String[] args) throws Exception { + + ProcessBuilder pb = ProcessTools.createJavaProcessBuilder( + "-XX:+UnlockDiagnosticVMOptions", + "-Xmx32M", + "-XX:-CreateCoredumpOnCrash", + "-XX:ErrorHandlerTest=14", + "-version"); + + OutputAnalyzer output_detail = new OutputAnalyzer(pb.start()); + + // we should have crashed with a SIGFPE + output_detail.shouldMatch("# A fatal error has been detected by the Java Runtime Environment:.*"); + output_detail.shouldMatch("# +EXCEPTION_ACCESS_VIOLATION.*"); + + // extract hs-err file + String hs_err_file = output_detail.firstMatch("# *(\\S*hs_err_pid\\d+\\.log)", 1); + if (hs_err_file == null) { + throw new RuntimeException("Did not find hs-err file in output.\n"); + } + + // scan hs-err file: File should contain the "[error occurred during error reporting..]" + // markers which show that the secondary error handling kicked in and handled the + // error successfully. As an added test, we check that the last line contains "END.", + // which is an end marker written in the last step and proves that hs-err file was + // completely written. + File f = new File(hs_err_file); + if (!f.exists()) { + throw new RuntimeException("hs-err file missing at " + + f.getAbsolutePath() + ".\n"); + } + + System.out.println("Found hs_err file at " + f.getAbsolutePath() + ". Scanning..."); + + FileInputStream fis = new FileInputStream(f); + BufferedReader br = new BufferedReader(new InputStreamReader(fis)); + String line = null; + + Pattern [] pattern = new Pattern[] { + /* Note: some platforms (e.g. AIX) have demangling off by default. Formulate the pattern such + * that function names match for both mangled and unmangled case. */ + Pattern.compile(buildFramePattern("jvm", "crash_with_segfault", "vmerror.cpp")), + Pattern.compile(buildFramePattern("jvm", "controlled_crash", "vmerror.cpp")), + Pattern.compile(buildFramePattern("jvm", "JNI_CreateJavaVM", "jni.cpp")) + }; + int currentPattern = 0; + + String lastLine = null; + while ((line = br.readLine()) != null) { + if (currentPattern < pattern.length) { + if (pattern[currentPattern].matcher(line).matches()) { + System.out.println("Found: " + line + "."); + currentPattern ++; + } + } + lastLine = line; + } + br.close(); + + if (currentPattern < pattern.length) { + throw new RuntimeException("Missing or incomplete callstack in hs-err file (first missing pattern: " + pattern[currentPattern] + ")"); + } + + System.out.println("OK."); + + } + +} + +