Эх сурвалжийг харах

chore: show callstack on openGL errors reported by glDebugMessageCallback

Almamu 9 сар өмнө
parent
commit
da273b5012

+ 7 - 0
CMakeLists.txt

@@ -118,7 +118,11 @@ find_package(CEF REQUIRED)
 list(REMOVE_ITEM CEF_CXX_COMPILER_FLAGS -fno-exceptions)
 list(REMOVE_ITEM CEF_CXX_COMPILER_FLAGS -fno-rtti)
 list(REMOVE_ITEM CEF_CXX_COMPILER_FLAGS -std=c++17)
+list(REMOVE_ITEM CEF_COMPILER_FLAGS -fvisibility=hidden)
+list(REMOVE_ITEM CEF_COMPILER_FLAGS -fvisibility-inlines-hidden)
 list(APPEND CEF_CXX_COMPILER_FLAGS -std=c++20)
+list(APPEND CEF_COMPILER_FLAGS -rdynamic)
+list(APPEND CEF_EXE_LINKER_FLAGS -rdynamic)
 # remove the vulkan library from the files to copy so it doesn't fail after removing it
 list(REMOVE_ITEM CEF_BINARY_FILES libvulkan.so.1)
 
@@ -266,6 +270,9 @@ set(COMMON_SOURCES
     src/WallpaperEngine/Logging/Log.cpp
     src/WallpaperEngine/Logging/Log.h
 
+    src/WallpaperEngine/Debugging/CallStack.cpp
+    src/WallpaperEngine/Debugging/CallStack.h
+
     src/WallpaperEngine/Application/ApplicationContext.cpp
     src/WallpaperEngine/Application/ApplicationContext.h
     src/WallpaperEngine/Application/WallpaperApplication.cpp

+ 39 - 1
src/WallpaperEngine/Application/WallpaperApplication.cpp

@@ -12,8 +12,9 @@
 #include "WallpaperEngine/Data/Dumpers/StringPrinter.h"
 #include "WallpaperEngine/Data/Parsers/ProjectParser.h"
 
-#include "WallpaperEngine/Data/Model/Wallpaper.h"
 #include "WallpaperEngine/Data/Model/Property.h"
+#include "WallpaperEngine/Data/Model/Wallpaper.h"
+#include "WallpaperEngine/Debugging/CallStack.h"
 
 #if DEMOMODE
 #include "recording.h"
@@ -36,6 +37,34 @@ using namespace WallpaperEngine::Application;
 using namespace WallpaperEngine::Data::Model;
 using namespace WallpaperEngine::FileSystem;
 
+void CustomGLDebugCallback(GLenum source,
+            GLenum type,
+            GLuint id,
+            GLenum severity,
+            GLsizei length,
+            const GLchar *message,
+            const void *userParam) {
+    if (severity != GL_DEBUG_SEVERITY_HIGH) {
+        return;
+    }
+
+    sLog.error("OpenGL error: ", message, ", type: ", type, ", id: ", id);
+
+    std::vector<WallpaperEngine::Debugging::CallStack::CallInfo> callInfo;
+
+    WallpaperEngine::Debugging::CallStack::GetCalls(callInfo);
+
+    for(std::vector<WallpaperEngine::Debugging::CallStack::CallInfo>::size_type i = 0; i < callInfo.size(); ++i) {
+        fprintf(stderr,
+            "[%3lu] %15lu: %s in %s\n",
+            callInfo.size()-i,
+            callInfo[i].offset,
+            callInfo[i].function.c_str(),
+            callInfo[i].module.c_str()
+        );
+    }
+}
+
 WallpaperApplication::WallpaperApplication (ApplicationContext& context) :
     m_context (context) {
     this->loadBackgrounds ();
@@ -653,10 +682,19 @@ void WallpaperApplication::prepareOutputs () {
     }
 }
 
+void WallpaperApplication::setupOpenGLDebugging () {
+#if !NDEBUG
+    glDebugMessageCallback(CustomGLDebugCallback, nullptr);
+    glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
+#endif
+}
+
+
 void WallpaperApplication::show () {
     this->setupOutput ();
     this->setupAudio ();
     this->prepareOutputs ();
+    this->setupOpenGLDebugging ();
 
     static time_t seconds;
     static struct tm* timeinfo;

+ 4 - 0
src/WallpaperEngine/Application/WallpaperApplication.h

@@ -102,6 +102,10 @@ class WallpaperApplication {
      * Prepares the render-context of all the backgrounds so they can be displayed on the screen
      */
     void prepareOutputs ();
+    /**
+     * Prepares output debugging for all opengl errors
+     */
+    void setupOpenGLDebugging ();
     /**
      * Takes an screenshot of the background and saves it to the specified path
      *

+ 327 - 0
src/WallpaperEngine/Debugging/CallStack.cpp

@@ -0,0 +1,327 @@
+/*
+* https://github.com/ZhangJunQCC/CallStack-for-Windows-and-Linux
+BSD 3-Clause License
+
+Copyright (c) 2017, Jun Zhang
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+  list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+  this list of conditions and the following disclaimer in the documentation
+  and/or other materials provided with the distribution.
+
+* Neither the name of the copyright holder nor the names of its
+  contributors may be used to endorse or promote products derived from
+  this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+*/
+
+//
+// Author: Jun Zhang
+// mailto: zhangjunqcc@gmail.com
+//
+
+#include "CallStack.h"
+#include <vector>
+#include <string>
+#include <cstdlib>
+#include <cstdio>
+#include <cassert>
+
+#if defined(_WIN32)
+#include <windows.h>
+#include <imagehlp.h>
+#if defined(__MINGW32__)
+#define PACKAGE         1 // Supress cmake error.
+#define PACKAGE_VERSION 1 // Supress cmake error.
+#include <bfd.h>
+#include <cxxabi.h>
+#endif
+#else
+#include <execinfo.h>
+#include <cxxabi.h>
+#endif
+
+using namespace WallpaperEngine::Debugging;
+using namespace std;
+
+const char* CallStack::Unknown_Function = "[unknown function]";
+const char* CallStack::Unknown_Module = "[unknown module]";
+
+CallStack::CallStack(void)
+{
+    /* Nothing to do */
+}
+
+CallStack::~CallStack(void)
+{
+    /* Nothing to do */
+}
+
+string CallStack::Demangle(const char* name)
+{
+#if defined(_MSC_VER)
+    return string(name);
+#else
+    string dname;
+    int status = 0;
+    char* pdname = abi::__cxa_demangle(name, NULL, 0, &status);
+    if(status == 0)
+    {
+        dname.assign(pdname);
+        free(pdname);
+    }
+    else
+    {
+        dname.assign(CallStack::Unknown_Function);
+    }
+    return dname;
+#endif
+}
+
+#if defined(_WIN32)
+class noncopyable // Like the one in boost.
+{
+protected:
+    noncopyable(void) { /* Nothing to do */ }
+private:
+    noncopyable(const noncopyable&);
+    noncopyable& operator = (const noncopyable&);
+};
+// A global thread mutex class.
+class mutex : noncopyable
+{
+public:
+    mutex(void) { InitializeCriticalSection(&cs); }
+    ~mutex(void) { DeleteCriticalSection(&cs); }
+    void lock(void) { EnterCriticalSection(&cs); }
+    void unlock(void) { LeaveCriticalSection(&cs); }
+private:
+    CRITICAL_SECTION cs;
+} cs;
+#if defined(__MINGW32__)
+class bfd_usage : noncopyable
+{
+public:
+    bfd_usage(void) : abfd(NULL), sec(NULL), symbol_table(NULL)
+    {
+        char procname[MAX_PATH];
+        GetModuleFileName(NULL, procname, sizeof(procname));
+
+        bfd_init();
+        abfd = bfd_openr(procname, NULL); // Obtain the bfd pointer.
+        if(abfd == NULL)
+        {
+            fprintf(stderr, "Error: Failed to parse the executable format.\n");
+            return;
+        }
+
+        char** matching = NULL;
+        if(!(bfd_check_format(abfd, bfd_object) && // Whether the executable format is supported.
+                    bfd_check_format_matches(abfd, bfd_object, &matching) && // Whether the executable format is supported.
+                    (bfd_get_file_flags(abfd) & HAS_SYMS))) // Whether BFD has symbols
+        {
+            bfd_close(abfd);
+            free(matching);
+            fprintf(stderr, "Error: Failed to parse the executable format.\n");
+            return;
+        }
+        free(matching);
+
+        // Obtain the symbol table.
+        unsigned int dummy = 0;
+        if(bfd_read_minisymbols(abfd, FALSE, reinterpret_cast<void**>(&symbol_table), &dummy) == 0 &&
+                bfd_read_minisymbols(abfd, TRUE, reinterpret_cast<void**>(&symbol_table), &dummy) < 0)
+        {
+            free(symbol_table);
+            bfd_close(abfd);
+            fprintf(stderr, "Error: Failed to obtain the symbol table from the executable.\n");
+            return;
+        }
+    }
+
+    ~bfd_usage(void)
+    {
+        free(symbol_table);
+        bfd_close(abfd);
+    }
+
+    string get_function_name(DWORD offset)
+    {
+        fnis_para para;
+        para.symbol_table = symbol_table;
+        para.counter = offset;
+        bfd_map_over_sections(abfd, &find_function_name_in_section, &para); // Call the corresponding functions.
+        return para.function;
+    }
+
+private:
+    bfd* abfd;
+    asection* sec;
+    asymbol** symbol_table;
+
+    class fnis_para
+    {
+    public:
+        string function;
+        asymbol** symbol_table;
+        bfd_vma counter;
+    };
+    static void find_function_name_in_section(bfd* tabfd, asection* tsec, void* tpara)
+    {
+        fnis_para& para = *static_cast<fnis_para*>(tpara);
+        if(!para.function.empty()) return; // We have got the name.
+        if(!(bfd_get_section_flags(tabfd, tsec) & SEC_ALLOC)) return; // Allocate space for this section.
+        bfd_vma vma = bfd_get_section_vma(tabfd, tsec);
+        if(para.counter < vma || vma+bfd_get_section_size(tsec) <= para.counter) return;
+        const char* function = NULL;
+        const char* file = NULL;
+        unsigned int line = 0;
+        // Get corresponding file and function name, and line number.
+        if(bfd_find_nearest_line(tabfd, tsec, para.symbol_table, para.counter-vma, &file, &function, &line) && function)
+        {
+            para.function = CallStack::Demangle(function);
+        }
+    }
+};
+#endif // __MINGW32__
+#endif // _WIN32
+
+void CallStack::GetCalls(vector<CallStack::CallInfo>& calls)
+{
+#if defined(_WIN32)
+
+    // Prepare context.
+    cs.lock();
+    if(!SymInitialize(GetCurrentProcess(), 0, TRUE))
+    {
+        fprintf(stderr, "Error: Failed to initialize symbol context.\n");
+        return;
+    }
+#ifdef __MINGW32__
+    bfd_usage bfdu;
+#endif
+
+    // Prepare variables.
+#if defined(_M_AMD64)
+    const DWORD machine = IMAGE_FILE_MACHINE_AMD64;
+#else
+    const DWORD machine = IMAGE_FILE_MACHINE_I386;
+#endif
+    const HANDLE process = GetCurrentProcess();
+    const HANDLE thread = GetCurrentThread();
+    STACKFRAME frame = { 0 };
+    CONTEXT context = { 0 };
+    context.ContextFlags = CONTEXT_FULL;
+    RtlCaptureContext(&context);
+#if defined(_M_AMD64)
+    frame.AddrPC.Offset = context.Rip;
+    frame.AddrPC.Mode = AddrModeFlat;
+    frame.AddrStack.Offset = context.Rsp;
+    frame.AddrStack.Mode = AddrModeFlat;
+    frame.AddrFrame.Offset = context.Rbp;
+    frame.AddrFrame.Mode = AddrModeFlat;
+#else
+    frame.AddrPC.Offset = context.Eip;
+    frame.AddrPC.Mode = AddrModeFlat;
+    frame.AddrStack.Offset = context.Esp;
+    frame.AddrStack.Mode = AddrModeFlat;
+    frame.AddrFrame.Offset = context.Ebp;
+    frame.AddrFrame.Mode = AddrModeFlat;
+#endif
+    int limit = 0;
+    const bool has_limit = (limit != 0);
+    char symbol_buffer[sizeof(IMAGEHLP_SYMBOL)+255];
+    char module_name_raw[MAX_PATH];
+
+    // Go over stack.
+    calls.clear();
+    while(StackWalk(machine, process, thread, &frame, &context, 0, SymFunctionTableAccess, SymGetModuleBase, 0))
+    {
+        if(has_limit && limit == 0) break; --limit;
+
+        IMAGEHLP_SYMBOL* symbol = reinterpret_cast<IMAGEHLP_SYMBOL*>(symbol_buffer);
+        symbol->SizeOfStruct = (sizeof(*symbol))+255;
+        symbol->MaxNameLength = 254;
+
+#if defined(_WIN64)
+        const DWORD64 module_base = SymGetModuleBase(process, frame.AddrPC.Offset);
+#else
+        const DWORD module_base = SymGetModuleBase(process, frame.AddrPC.Offset);
+#endif
+        string module_name = Unknown_Module;
+        if(module_base && GetModuleFileNameA(reinterpret_cast<HINSTANCE>(module_base), module_name_raw, MAX_PATH)) module_name = module_name_raw;
+
+#if defined(__MINGW32__)
+        string function = bfdu.get_function_name(frame.AddrPC.Offset);
+        if(function.empty())
+        {
+            DWORD64 dummy = 0;
+            const bool got_symbol = SymGetSymFromAddr(process, frame.AddrPC.Offset, &dummy, symbol);
+            function= (got_symbol) ? (symbol->Name) : Unknown_Function;
+        }
+#else
+#if defined(_WIN64)
+        DWORD64 dummy = 0;
+#else
+        DWORD dummy = 0;
+#endif
+        const bool got_symbol = SymGetSymFromAddr(process, frame.AddrPC.Offset, &dummy, symbol);
+        const string function = (got_symbol) ? symbol->Name : Unknown_Function;
+
+#endif
+
+        calls.push_back(CallInfo(frame.AddrPC.Offset, function, module_name));
+    }
+
+    SymCleanup(GetCurrentProcess());
+    cs.unlock();
+
+#else // For Linux and Unix.
+
+    const int BufferSize = 256;
+    void* buffer[BufferSize];
+    const int ncalls = backtrace(buffer, sizeof(buffer));
+    char** callstrings = backtrace_symbols(buffer, ncalls);
+
+    if(callstrings == NULL)
+    {
+        fprintf(stderr, "Error: Failed to obtain the symbol table from the executable.\n");
+        return;
+    }
+
+    calls.clear();
+    for(int i = 0; i < ncalls; ++i)
+    {
+        const string funinfo(callstrings[i]);
+        const int posleftparenthesis = funinfo.rfind('(');
+        const int posplus = funinfo.rfind('+');
+        const int posleftbracket = funinfo.rfind('[');
+        const int posrightbracket = funinfo.rfind(']');
+
+        long long unsigned int offset = strtoull(funinfo.substr(posleftbracket+1, posrightbracket-posleftbracket-1).c_str(), NULL, 0);
+        const string module_name = funinfo.substr(0, posleftparenthesis);
+        const string function = Demangle(funinfo.substr(posleftparenthesis+1, posplus-posleftparenthesis-1).c_str());
+
+        calls.push_back(CallInfo(offset, function, module_name));
+     }
+     free(callstrings);
+
+#endif // _WIN32
+}

+ 75 - 0
src/WallpaperEngine/Debugging/CallStack.h

@@ -0,0 +1,75 @@
+/*
+* https://github.com/ZhangJunQCC/CallStack-for-Windows-and-Linux
+BSD 3-Clause License
+
+Copyright (c) 2017, Jun Zhang
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+  list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+  this list of conditions and the following disclaimer in the documentation
+  and/or other materials provided with the distribution.
+
+* Neither the name of the copyright holder nor the names of its
+  contributors may be used to endorse or promote products derived from
+  this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+*/
+
+//
+// Author: Jun Zhang
+// mailto: zhangjunqcc@gmail.com
+//
+
+#ifndef    __CALLSTACK_H__
+#define    __CALLSTACK_H__
+
+#include <string>
+#include <vector>
+
+namespace WallpaperEngine::Debugging {
+
+using namespace std;
+
+class CallStack {
+public:
+    CallStack(void);
+    ~CallStack(void);
+
+    static const char* Unknown_Function;
+    static const char* Unknown_Module;
+
+    class CallInfo {
+    public:
+        CallInfo(void) { /* Nothing to do */ }
+        CallInfo(long long unsigned int toffset, const string& tfunction, const string& tmodule)
+            : offset(toffset), function(tfunction), module(tmodule) { /* Nothing to do */ }
+        ~CallInfo(void) { /* Nothing to do */ }
+        long unsigned int offset;
+        string function;
+        string module;
+    };
+
+    static void GetCalls(vector<CallStack::CallInfo>& calls); // Get the call information.
+    static string Demangle(const char* name);                 // Demangle the C++ function name.
+};
+
+}
+
+#endif //  __CALLSTACK_H__

+ 2 - 12
src/WallpaperEngine/Render/Drivers/GLFWOpenGLDriver.cpp

@@ -7,6 +7,8 @@
 #endif
 
 #define GLFW_EXPOSE_NATIVE_X11
+#include "WallpaperEngine/Debugging/CallStack.h"
+
 #include <GLFW/glfw3native.h>
 
 #include <unistd.h>
@@ -17,16 +19,6 @@ void CustomGLFWErrorHandler (int errorCode, const char* reason) {
     sLog.error ("GLFW error ", errorCode, ": ", reason);
 }
 
-void CustomGLErrorCallback(GLenum source,
-            GLenum type,
-            GLuint id,
-            GLenum severity,
-            GLsizei length,
-            const GLchar *message,
-            const void *userParam) {
-    sLog.error("OpenGL error: ", message, ", type: ", type, ", id: ", id);
-}
-
 GLFWOpenGLDriver::GLFWOpenGLDriver (
     const char* windowTitle, ApplicationContext& context, WallpaperApplication& app
 ) :
@@ -58,8 +50,6 @@ GLFWOpenGLDriver::GLFWOpenGLDriver (
 
 #if !NDEBUG
     glfwWindowHint (GLFW_OPENGL_DEBUG_CONTEXT, GL_TRUE);
-    glDebugMessageCallback(CustomGLErrorCallback, nullptr);
-    glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
 #endif /* DEBUG */
 
     // create window, size doesn't matter as long as we don't show it

+ 0 - 13
src/WallpaperEngine/Render/Drivers/WaylandOpenGLDriver.cpp

@@ -20,16 +20,6 @@ extern "C" {
 
 using namespace WallpaperEngine::Render::Drivers;
 
-void CustomGLEWErrorCallback(GLenum source,
-            GLenum type,
-            GLuint id,
-            GLenum severity,
-            GLsizei length,
-            const GLchar *message,
-            const void *userParam) {
-    sLog.error("OpenGL error: ", message, ", type: ", type, ", id: ", id);
-}
-
 static void handlePointerEnter (void* data, struct wl_pointer* wl_pointer, uint32_t serial, struct wl_surface* surface,
                                 wl_fixed_t surface_x, wl_fixed_t surface_y) {
     const auto driver = static_cast<WaylandOpenGLDriver*> (data);
@@ -297,9 +287,6 @@ WaylandOpenGLDriver::WaylandOpenGLDriver (ApplicationContext& context, Wallpaper
 
     if (const GLenum result = glewInit (); result != GLEW_OK)
         sLog.error ("Failed to initialize GLEW: ", glewGetErrorString (result));
-
-    glDebugMessageCallback(CustomGLEWErrorCallback, nullptr);
-    glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
 }
 
 WaylandOpenGLDriver::~WaylandOpenGLDriver () {