mirror of
https://github.com/godotengine/godot
synced 2024-09-05 06:28:34 +00:00
Merge pull request #11061 from marcelofg55/dump_backtrace
Add a segfault handler to dump the backtrace on Windows, Linux and OS X
This commit is contained in:
commit
d6b392825f
|
@ -279,6 +279,9 @@ public:
|
|||
|
||||
bool is_stdout_verbose() const;
|
||||
|
||||
virtual void disable_crash_handler() {}
|
||||
virtual bool is_disable_crash_handler() const { return false; }
|
||||
|
||||
enum CursorShape {
|
||||
CURSOR_ARROW,
|
||||
CURSOR_IBEAM,
|
||||
|
|
|
@ -72,6 +72,10 @@ Error EditorRun::run(const String &p_scene, const String p_custom_args, const Li
|
|||
screen--;
|
||||
}
|
||||
|
||||
if (OS::get_singleton()->is_disable_crash_handler()) {
|
||||
args.push_back("--disable-crash-handler");
|
||||
}
|
||||
|
||||
Rect2 screen_rect;
|
||||
screen_rect.position = OS::get_singleton()->get_screen_position(screen);
|
||||
screen_rect.size = OS::get_singleton()->get_screen_size(screen);
|
||||
|
|
|
@ -948,6 +948,10 @@ void ProjectManager::_open_project_confirm() {
|
|||
|
||||
args.push_back("--editor");
|
||||
|
||||
if (OS::get_singleton()->is_disable_crash_handler()) {
|
||||
args.push_back("--disable-crash-handler");
|
||||
}
|
||||
|
||||
String exec = OS::get_singleton()->get_executable_path();
|
||||
|
||||
OS::ProcessID pid = 0;
|
||||
|
@ -999,6 +1003,10 @@ void ProjectManager::_run_project_confirm() {
|
|||
args.push_back("--path");
|
||||
args.push_back(path);
|
||||
|
||||
if (OS::get_singleton()->is_disable_crash_handler()) {
|
||||
args.push_back("--disable-crash-handler");
|
||||
}
|
||||
|
||||
String exec = OS::get_singleton()->get_executable_path();
|
||||
|
||||
OS::ProcessID pid = 0;
|
||||
|
|
|
@ -194,6 +194,7 @@ void Main::print_help(const char *p_binary) {
|
|||
OS::get_singleton()->print(" --frame-delay <ms> Simulate high CPU load (delay each frame by <ms> milliseconds).\n");
|
||||
OS::get_singleton()->print(" --time-scale <scale> Force time scale (higher values are faster, 1.0 is normal speed).\n");
|
||||
OS::get_singleton()->print(" --disable-render-loop Disable render loop so rendering only occurs when called explicitly from script.\n");
|
||||
OS::get_singleton()->print(" --disable-crash-handler Disable crash handler when supported by the platform code.\n");
|
||||
OS::get_singleton()->print(" --fixed-fps <fps> Forces a fixed ratio between process and fixed_process timing, for use when precision is required, or when rendering to video files. Setting this will disable real-time syncronization, so that run speed is only capped by performance\n");
|
||||
OS::get_singleton()->print("\n");
|
||||
|
||||
|
@ -247,6 +248,8 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
|
|||
performance = memnew(Performance);
|
||||
globals->add_singleton(ProjectSettings::Singleton("Performance", performance));
|
||||
|
||||
GLOBAL_DEF("debug/settings/backtrace/message", String("Please include this when reporting the bug on https://github.com/godotengine/godot/issues"));
|
||||
|
||||
MAIN_PRINT("Main: Parse CMDLine");
|
||||
|
||||
/* argument parsing and main creation */
|
||||
|
@ -582,6 +585,8 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
|
|||
OS::get_singleton()->print("Missing fixed-fps argument, aborting.\n");
|
||||
goto error;
|
||||
}
|
||||
} else if (I->get() == "--disable-crash-handler") {
|
||||
OS::get_singleton()->disable_crash_handler();
|
||||
} else {
|
||||
|
||||
//test for game path
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
Import('env')
|
||||
|
||||
files = [
|
||||
'crash_handler_osx.mm',
|
||||
'os_osx.mm',
|
||||
'godot_main_osx.mm',
|
||||
'audio_driver_osx.cpp',
|
||||
|
@ -12,4 +13,8 @@ files = [
|
|||
'power_osx.cpp',
|
||||
]
|
||||
|
||||
env.Program('#bin/godot', files)
|
||||
prog = env.Program('#bin/godot', files)
|
||||
if (env['target'] == "debug" or env['target'] == "release_debug"):
|
||||
# Build the .dSYM file for atos
|
||||
action = "dsymutil " + File(prog)[0].path + " -o " + File(prog)[0].path + ".dSYM"
|
||||
env.AddPostAction(prog, action)
|
||||
|
|
47
platform/osx/crash_handler_osx.h
Normal file
47
platform/osx/crash_handler_osx.h
Normal file
|
@ -0,0 +1,47 @@
|
|||
/*************************************************************************/
|
||||
/* crash_handler_osx.h */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */
|
||||
/* */
|
||||
/* 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. */
|
||||
/*************************************************************************/
|
||||
#ifndef CRASH_HANDLER_OSX_H
|
||||
#define CRASH_HANDLER_OSX_H
|
||||
|
||||
class CrashHandler {
|
||||
|
||||
bool disabled;
|
||||
|
||||
public:
|
||||
void initialize();
|
||||
|
||||
void disable();
|
||||
bool is_disabled() const { return disabled; };
|
||||
|
||||
CrashHandler();
|
||||
~CrashHandler();
|
||||
};
|
||||
|
||||
#endif
|
178
platform/osx/crash_handler_osx.mm
Normal file
178
platform/osx/crash_handler_osx.mm
Normal file
|
@ -0,0 +1,178 @@
|
|||
/*************************************************************************/
|
||||
/* crash_handler_osx.mm */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */
|
||||
/* */
|
||||
/* 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. */
|
||||
/*************************************************************************/
|
||||
#include "main/main.h"
|
||||
#include "os_osx.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
// Note: Dump backtrace in 32bit mode is getting a bus error on the fgets by the ->execute, so enable only on 64bit
|
||||
#if defined(DEBUG_ENABLED) && defined(__x86_64__)
|
||||
#define CRASH_HANDLER_ENABLED 1
|
||||
#endif
|
||||
|
||||
#ifdef CRASH_HANDLER_ENABLED
|
||||
#include <cxxabi.h>
|
||||
#include <dlfcn.h>
|
||||
#include <execinfo.h>
|
||||
#include <signal.h>
|
||||
|
||||
#include <mach-o/dyld.h>
|
||||
#include <mach-o/getsect.h>
|
||||
|
||||
#ifdef __x86_64__
|
||||
static uint64_t load_address() {
|
||||
const struct segment_command_64 *cmd = getsegbyname("__TEXT");
|
||||
#else
|
||||
static uint32_t load_address() {
|
||||
const struct segment_command *cmd = getsegbyname("__TEXT");
|
||||
#endif
|
||||
char full_path[1024];
|
||||
uint32_t size = sizeof(full_path);
|
||||
|
||||
if (cmd && !_NSGetExecutablePath(full_path, &size)) {
|
||||
uint32_t dyld_count = _dyld_image_count();
|
||||
for (uint32_t i = 0; i < dyld_count; i++) {
|
||||
const char *image_name = _dyld_get_image_name(i);
|
||||
if (image_name && strncmp(image_name, full_path, 1024) == 0) {
|
||||
return cmd->vmaddr + _dyld_get_image_vmaddr_slide(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void handle_crash(int sig) {
|
||||
if (OS::get_singleton() == NULL)
|
||||
return;
|
||||
|
||||
void *bt_buffer[256];
|
||||
size_t size = backtrace(bt_buffer, 256);
|
||||
String _execpath = OS::get_singleton()->get_executable_path();
|
||||
String msg = GLOBAL_GET("debug/settings/backtrace/message");
|
||||
|
||||
// Dump the backtrace to stderr with a message to the user
|
||||
fprintf(stderr, "%s: Program crashed with signal %d\n", __FUNCTION__, sig);
|
||||
fprintf(stderr, "Dumping the backtrace. %ls\n", msg.c_str());
|
||||
char **strings = backtrace_symbols(bt_buffer, size);
|
||||
if (strings) {
|
||||
void *load_addr = (void *)load_address();
|
||||
|
||||
for (int i = 1; i < size; i++) {
|
||||
char fname[1024];
|
||||
Dl_info info;
|
||||
|
||||
snprintf(fname, 1024, "%s", strings[i]);
|
||||
|
||||
// Try to demangle the function name to provide a more readable one
|
||||
if (dladdr(bt_buffer[i], &info) && info.dli_sname) {
|
||||
if (info.dli_sname[0] == '_') {
|
||||
int status;
|
||||
char *demangled = abi::__cxa_demangle(info.dli_sname, NULL, 0, &status);
|
||||
|
||||
if (status == 0 && demangled) {
|
||||
snprintf(fname, 1024, "%s", demangled);
|
||||
}
|
||||
|
||||
if (demangled)
|
||||
free(demangled);
|
||||
}
|
||||
}
|
||||
|
||||
String output = fname;
|
||||
|
||||
// Try to get the file/line number using atos
|
||||
if (bt_buffer[i] > (void *)0x0 && OS::get_singleton()) {
|
||||
List<String> args;
|
||||
char str[1024];
|
||||
|
||||
args.push_back("-o");
|
||||
args.push_back(_execpath);
|
||||
args.push_back("-arch");
|
||||
#ifdef __x86_64__
|
||||
args.push_back("x86_64");
|
||||
#else
|
||||
args.push_back("i386");
|
||||
#endif
|
||||
args.push_back("-l");
|
||||
snprintf(str, 1024, "%p", load_addr);
|
||||
args.push_back(str);
|
||||
snprintf(str, 1024, "%p", bt_buffer[i]);
|
||||
args.push_back(str);
|
||||
|
||||
int ret;
|
||||
String out = "";
|
||||
Error err = OS::get_singleton()->execute(String("atos"), args, true, NULL, &out, &ret);
|
||||
if (err == OK && out.substr(0, 2) != "0x") {
|
||||
out.erase(out.length() - 1, 1);
|
||||
output = out;
|
||||
}
|
||||
}
|
||||
|
||||
fprintf(stderr, "[%d] %ls\n", i, output.c_str());
|
||||
}
|
||||
|
||||
free(strings);
|
||||
}
|
||||
fprintf(stderr, "-- END OF BACKTRACE --\n");
|
||||
|
||||
// Abort to pass the error to the OS
|
||||
abort();
|
||||
}
|
||||
#endif
|
||||
|
||||
CrashHandler::CrashHandler() {
|
||||
disabled = false;
|
||||
}
|
||||
|
||||
CrashHandler::~CrashHandler() {
|
||||
}
|
||||
|
||||
void CrashHandler::disable() {
|
||||
if (disabled)
|
||||
return;
|
||||
|
||||
#ifdef CRASH_HANDLER_ENABLED
|
||||
signal(SIGSEGV, NULL);
|
||||
signal(SIGFPE, NULL);
|
||||
signal(SIGILL, NULL);
|
||||
#endif
|
||||
|
||||
disabled = true;
|
||||
}
|
||||
|
||||
void CrashHandler::initialize() {
|
||||
#ifdef CRASH_HANDLER_ENABLED
|
||||
signal(SIGSEGV, handle_crash);
|
||||
signal(SIGFPE, handle_crash);
|
||||
signal(SIGILL, handle_crash);
|
||||
#endif
|
||||
}
|
|
@ -35,7 +35,6 @@
|
|||
#include <unistd.h>
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
|
||||
int first_arg = 1;
|
||||
const char *dbg_arg = "-NSDocumentRevisionsDebugMode";
|
||||
printf("arguments\n");
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
#ifndef OS_OSX_H
|
||||
#define OS_OSX_H
|
||||
|
||||
#include "crash_handler_osx.h"
|
||||
#include "drivers/alsa/audio_driver_alsa.h"
|
||||
#include "drivers/rtaudio/audio_driver_rtaudio.h"
|
||||
#include "drivers/unix/os_unix.h"
|
||||
|
@ -110,6 +111,8 @@ public:
|
|||
|
||||
power_osx *power_manager;
|
||||
|
||||
CrashHandler crash_handler;
|
||||
|
||||
float _mouse_scale(float p_scale) {
|
||||
if (display_scale > 1.0)
|
||||
return p_scale;
|
||||
|
@ -224,6 +227,9 @@ public:
|
|||
void set_mouse_mode(MouseMode p_mode);
|
||||
MouseMode get_mouse_mode() const;
|
||||
|
||||
void disable_crash_handler();
|
||||
bool is_disable_crash_handler() const;
|
||||
|
||||
OS_OSX();
|
||||
};
|
||||
|
||||
|
|
|
@ -910,6 +910,8 @@ OS::VideoMode OS_OSX::get_default_video_mode() const {
|
|||
|
||||
void OS_OSX::initialize_core() {
|
||||
|
||||
crash_handler.initialize();
|
||||
|
||||
OS_Unix::initialize_core();
|
||||
|
||||
DirAccess::make_default<DirAccessOSX>(DirAccess::ACCESS_RESOURCES);
|
||||
|
@ -2022,3 +2024,11 @@ OS_OSX::OS_OSX() {
|
|||
bool OS_OSX::_check_internal_feature_support(const String &p_feature) {
|
||||
return p_feature == "pc" || p_feature == "s3tc";
|
||||
}
|
||||
|
||||
void OS_OSX::disable_crash_handler() {
|
||||
crash_handler.disable();
|
||||
}
|
||||
|
||||
bool OS_OSX::is_disable_crash_handler() const {
|
||||
return crash_handler.is_disabled();
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ Import('env')
|
|||
|
||||
common_win = [
|
||||
"context_gl_win.cpp",
|
||||
"crash_handler_win.cpp",
|
||||
"os_windows.cpp",
|
||||
"ctxgl_procaddr.cpp",
|
||||
"key_mapping_win.cpp",
|
||||
|
@ -12,7 +13,7 @@ common_win = [
|
|||
"packet_peer_udp_winsock.cpp",
|
||||
"stream_peer_winsock.cpp",
|
||||
"joypad.cpp",
|
||||
"power_windows.cpp",
|
||||
"power_windows.cpp",
|
||||
]
|
||||
|
||||
restarget = "godot_res" + env["OBJSUFFIX"]
|
||||
|
|
211
platform/windows/crash_handler_win.cpp
Normal file
211
platform/windows/crash_handler_win.cpp
Normal file
|
@ -0,0 +1,211 @@
|
|||
/*************************************************************************/
|
||||
/* crash_handler_win.cpp */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */
|
||||
/* */
|
||||
/* 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. */
|
||||
/*************************************************************************/
|
||||
#include "main/main.h"
|
||||
#include "os_windows.h"
|
||||
|
||||
#ifdef CRASH_HANDLER_EXCEPTION
|
||||
|
||||
// Backtrace code code based on: https://stackoverflow.com/questions/6205981/windows-c-stack-trace-from-a-running-app
|
||||
|
||||
#include <psapi.h>
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
|
||||
#pragma comment(lib, "psapi.lib")
|
||||
#pragma comment(lib, "dbghelp.lib")
|
||||
|
||||
// Some versions of imagehlp.dll lack the proper packing directives themselves
|
||||
// so we need to do it.
|
||||
#pragma pack(push, before_imagehlp, 8)
|
||||
#include <imagehlp.h>
|
||||
#pragma pack(pop, before_imagehlp)
|
||||
|
||||
struct module_data {
|
||||
std::string image_name;
|
||||
std::string module_name;
|
||||
void *base_address;
|
||||
DWORD load_size;
|
||||
};
|
||||
|
||||
class symbol {
|
||||
typedef IMAGEHLP_SYMBOL64 sym_type;
|
||||
sym_type *sym;
|
||||
static const int max_name_len = 1024;
|
||||
|
||||
public:
|
||||
symbol(HANDLE process, DWORD64 address)
|
||||
: sym((sym_type *)::operator new(sizeof(*sym) + max_name_len)) {
|
||||
memset(sym, '\0', sizeof(*sym) + max_name_len);
|
||||
sym->SizeOfStruct = sizeof(*sym);
|
||||
sym->MaxNameLength = max_name_len;
|
||||
DWORD64 displacement;
|
||||
|
||||
SymGetSymFromAddr64(process, address, &displacement, sym);
|
||||
}
|
||||
|
||||
std::string name() { return std::string(sym->Name); }
|
||||
std::string undecorated_name() {
|
||||
if (*sym->Name == '\0')
|
||||
return "<couldn't map PC to fn name>";
|
||||
std::vector<char> und_name(max_name_len);
|
||||
UnDecorateSymbolName(sym->Name, &und_name[0], max_name_len, UNDNAME_COMPLETE);
|
||||
return std::string(&und_name[0], strlen(&und_name[0]));
|
||||
}
|
||||
};
|
||||
|
||||
class get_mod_info {
|
||||
HANDLE process;
|
||||
|
||||
public:
|
||||
get_mod_info(HANDLE h)
|
||||
: process(h) {}
|
||||
|
||||
module_data operator()(HMODULE module) {
|
||||
module_data ret;
|
||||
char temp[4096];
|
||||
MODULEINFO mi;
|
||||
|
||||
GetModuleInformation(process, module, &mi, sizeof(mi));
|
||||
ret.base_address = mi.lpBaseOfDll;
|
||||
ret.load_size = mi.SizeOfImage;
|
||||
|
||||
GetModuleFileNameEx(process, module, temp, sizeof(temp));
|
||||
ret.image_name = temp;
|
||||
GetModuleBaseName(process, module, temp, sizeof(temp));
|
||||
ret.module_name = temp;
|
||||
std::vector<char> img(ret.image_name.begin(), ret.image_name.end());
|
||||
std::vector<char> mod(ret.module_name.begin(), ret.module_name.end());
|
||||
SymLoadModule64(process, 0, &img[0], &mod[0], (DWORD64)ret.base_address, ret.load_size);
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
DWORD CrashHandlerException(EXCEPTION_POINTERS *ep) {
|
||||
HANDLE process = GetCurrentProcess();
|
||||
HANDLE hThread = GetCurrentThread();
|
||||
DWORD offset_from_symbol = 0;
|
||||
IMAGEHLP_LINE64 line = { 0 };
|
||||
std::vector<module_data> modules;
|
||||
DWORD cbNeeded;
|
||||
std::vector<HMODULE> module_handles(1);
|
||||
|
||||
if (OS::get_singleton() == NULL || OS::get_singleton()->is_disable_crash_handler()) {
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
|
||||
fprintf(stderr, "%s: Program crashed\n", __FUNCTION__);
|
||||
|
||||
// Load the symbols:
|
||||
if (!SymInitialize(process, NULL, false))
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
|
||||
SymSetOptions(SymGetOptions() | SYMOPT_LOAD_LINES | SYMOPT_UNDNAME);
|
||||
EnumProcessModules(process, &module_handles[0], module_handles.size() * sizeof(HMODULE), &cbNeeded);
|
||||
module_handles.resize(cbNeeded / sizeof(HMODULE));
|
||||
EnumProcessModules(process, &module_handles[0], module_handles.size() * sizeof(HMODULE), &cbNeeded);
|
||||
std::transform(module_handles.begin(), module_handles.end(), std::back_inserter(modules), get_mod_info(process));
|
||||
void *base = modules[0].base_address;
|
||||
|
||||
// Setup stuff:
|
||||
CONTEXT *context = ep->ContextRecord;
|
||||
STACKFRAME64 frame;
|
||||
bool skip_first = false;
|
||||
|
||||
frame.AddrPC.Mode = AddrModeFlat;
|
||||
frame.AddrStack.Mode = AddrModeFlat;
|
||||
frame.AddrFrame.Mode = AddrModeFlat;
|
||||
|
||||
#ifdef _M_X64
|
||||
frame.AddrPC.Offset = context->Rip;
|
||||
frame.AddrStack.Offset = context->Rsp;
|
||||
frame.AddrFrame.Offset = context->Rbp;
|
||||
#else
|
||||
frame.AddrPC.Offset = context->Eip;
|
||||
frame.AddrStack.Offset = context->Esp;
|
||||
frame.AddrFrame.Offset = context->Ebp;
|
||||
|
||||
// Skip the first one to avoid a duplicate on 32-bit mode
|
||||
skip_first = true;
|
||||
#endif
|
||||
|
||||
line.SizeOfStruct = sizeof(line);
|
||||
IMAGE_NT_HEADERS *h = ImageNtHeader(base);
|
||||
DWORD image_type = h->FileHeader.Machine;
|
||||
int n = 0;
|
||||
String msg = GLOBAL_GET("debug/settings/backtrace/message");
|
||||
|
||||
fprintf(stderr, "Dumping the backtrace. %ls\n", msg.c_str());
|
||||
|
||||
do {
|
||||
if (skip_first) {
|
||||
skip_first = false;
|
||||
} else {
|
||||
if (frame.AddrPC.Offset != 0) {
|
||||
std::string fnName = symbol(process, frame.AddrPC.Offset).undecorated_name();
|
||||
|
||||
if (SymGetLineFromAddr64(process, frame.AddrPC.Offset, &offset_from_symbol, &line))
|
||||
fprintf(stderr, "[%d] %s (%s:%d)\n", n, fnName.c_str(), line.FileName, line.LineNumber);
|
||||
else
|
||||
fprintf(stderr, "[%d] %s\n", n, fnName.c_str());
|
||||
} else
|
||||
fprintf(stderr, "[%d] ???\n", n);
|
||||
|
||||
n++;
|
||||
}
|
||||
|
||||
if (!StackWalk64(image_type, process, hThread, &frame, context, NULL, SymFunctionTableAccess64, SymGetModuleBase64, NULL))
|
||||
break;
|
||||
} while (frame.AddrReturn.Offset != 0 && n < 256);
|
||||
|
||||
fprintf(stderr, "-- END OF BACKTRACE --\n");
|
||||
|
||||
SymCleanup(process);
|
||||
|
||||
// Pass the exception to the OS
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
#endif
|
||||
|
||||
CrashHandler::CrashHandler() {
|
||||
disabled = false;
|
||||
}
|
||||
|
||||
CrashHandler::~CrashHandler() {
|
||||
}
|
||||
|
||||
void CrashHandler::disable() {
|
||||
if (disabled)
|
||||
return;
|
||||
|
||||
disabled = true;
|
||||
}
|
||||
|
||||
void CrashHandler::initialize() {
|
||||
}
|
56
platform/windows/crash_handler_win.h
Normal file
56
platform/windows/crash_handler_win.h
Normal file
|
@ -0,0 +1,56 @@
|
|||
/*************************************************************************/
|
||||
/* crash_handler_win.h */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */
|
||||
/* */
|
||||
/* 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. */
|
||||
/*************************************************************************/
|
||||
#ifndef CRASH_HANDLER_WIN_H
|
||||
#define CRASH_HANDLER_WIN_H
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
// Crash handler exception only enabled with MSVC
|
||||
#if defined(DEBUG_ENABLED) && defined(MSVC)
|
||||
#define CRASH_HANDLER_EXCEPTION 1
|
||||
|
||||
extern DWORD CrashHandlerException(EXCEPTION_POINTERS *ep);
|
||||
#endif
|
||||
|
||||
class CrashHandler {
|
||||
|
||||
bool disabled;
|
||||
|
||||
public:
|
||||
void initialize();
|
||||
|
||||
void disable();
|
||||
bool is_disabled() const { return disabled; };
|
||||
|
||||
CrashHandler();
|
||||
~CrashHandler();
|
||||
};
|
||||
|
||||
#endif
|
|
@ -123,7 +123,7 @@ def configure(env):
|
|||
env.Append(LINKFLAGS=['/ENTRY:mainCRTStartup'])
|
||||
|
||||
elif (env["target"] == "debug"):
|
||||
env.Append(CCFLAGS=['/Z7', '/DDEBUG_ENABLED', '/DDEBUG_MEMORY_ENABLED', '/DD3D_DEBUG_INFO', '/Od'])
|
||||
env.Append(CCFLAGS=['/Z7', '/DDEBUG_ENABLED', '/DDEBUG_MEMORY_ENABLED', '/DD3D_DEBUG_INFO', '/Od', '/EHsc'])
|
||||
env.Append(LINKFLAGS=['/SUBSYSTEM:CONSOLE'])
|
||||
env.Append(LINKFLAGS=['/DEBUG'])
|
||||
|
||||
|
|
|
@ -157,24 +157,32 @@ int widechar_main(int argc, wchar_t **argv) {
|
|||
};
|
||||
|
||||
int main(int _argc, char **_argv) {
|
||||
// _argc and _argv are ignored
|
||||
// we are going to use the WideChar version of them instead
|
||||
// _argc and _argv are ignored
|
||||
// we are going to use the WideChar version of them instead
|
||||
|
||||
LPWSTR *wc_argv;
|
||||
int argc;
|
||||
int result;
|
||||
#ifdef CRASH_HANDLER_EXCEPTION
|
||||
__try {
|
||||
#endif
|
||||
LPWSTR *wc_argv;
|
||||
int argc;
|
||||
int result;
|
||||
|
||||
wc_argv = CommandLineToArgvW(GetCommandLineW(), &argc);
|
||||
wc_argv = CommandLineToArgvW(GetCommandLineW(), &argc);
|
||||
|
||||
if (NULL == wc_argv) {
|
||||
wprintf(L"CommandLineToArgvW failed\n");
|
||||
return 0;
|
||||
if (NULL == wc_argv) {
|
||||
wprintf(L"CommandLineToArgvW failed\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
result = widechar_main(argc, wc_argv);
|
||||
|
||||
LocalFree(wc_argv);
|
||||
return result;
|
||||
#ifdef CRASH_HANDLER_EXCEPTION
|
||||
} __except (CrashHandlerException(GetExceptionInformation())) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
result = widechar_main(argc, wc_argv);
|
||||
|
||||
LocalFree(wc_argv);
|
||||
return result;
|
||||
#endif
|
||||
}
|
||||
|
||||
HINSTANCE godot_hinstance = NULL;
|
||||
|
|
|
@ -164,6 +164,8 @@ const char *OS_Windows::get_audio_driver_name(int p_driver) const {
|
|||
|
||||
void OS_Windows::initialize_core() {
|
||||
|
||||
crash_handler.initialize();
|
||||
|
||||
last_button_state = 0;
|
||||
|
||||
//RedirectIOToConsole();
|
||||
|
@ -2368,6 +2370,14 @@ bool OS_Windows::_check_internal_feature_support(const String &p_feature) {
|
|||
return p_feature == "pc" || p_feature == "s3tc";
|
||||
}
|
||||
|
||||
void OS_Windows::disable_crash_handler() {
|
||||
crash_handler.disable();
|
||||
}
|
||||
|
||||
bool OS_Windows::is_disable_crash_handler() const {
|
||||
return crash_handler.is_disabled();
|
||||
}
|
||||
|
||||
OS_Windows::OS_Windows(HINSTANCE _hInstance) {
|
||||
|
||||
key_event_pos = 0;
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
#define OS_WINDOWS_H
|
||||
|
||||
#include "context_gl_win.h"
|
||||
#include "crash_handler_win.h"
|
||||
#include "drivers/rtaudio/audio_driver_rtaudio.h"
|
||||
#include "drivers/wasapi/audio_driver_wasapi.h"
|
||||
#include "os/input.h"
|
||||
|
@ -134,6 +135,8 @@ class OS_Windows : public OS {
|
|||
AudioDriverXAudio2 driver_xaudio2;
|
||||
#endif
|
||||
|
||||
CrashHandler crash_handler;
|
||||
|
||||
void _drag_event(int p_x, int p_y, int idx);
|
||||
void _touch_event(bool p_pressed, int p_x, int p_y, int idx);
|
||||
|
||||
|
@ -284,6 +287,9 @@ public:
|
|||
|
||||
virtual bool _check_internal_feature_support(const String &p_feature);
|
||||
|
||||
void disable_crash_handler();
|
||||
bool is_disable_crash_handler() const;
|
||||
|
||||
OS_Windows(HINSTANCE _hInstance);
|
||||
~OS_Windows();
|
||||
};
|
||||
|
|
|
@ -3,12 +3,13 @@
|
|||
Import('env')
|
||||
|
||||
|
||||
common_x11 = [\
|
||||
"context_gl_x11.cpp",\
|
||||
"os_x11.cpp",\
|
||||
"key_mapping_x11.cpp",\
|
||||
"joypad_linux.cpp",\
|
||||
"power_x11.cpp",\
|
||||
common_x11 = [
|
||||
"context_gl_x11.cpp",
|
||||
"crash_handler_x11.cpp",
|
||||
"os_x11.cpp",
|
||||
"key_mapping_x11.cpp",
|
||||
"joypad_linux.cpp",
|
||||
"power_x11.cpp",
|
||||
]
|
||||
|
||||
env.Program('#bin/godot', ['godot_x11.cpp'] + common_x11)
|
||||
|
|
135
platform/x11/crash_handler_x11.cpp
Normal file
135
platform/x11/crash_handler_x11.cpp
Normal file
|
@ -0,0 +1,135 @@
|
|||
/*************************************************************************/
|
||||
/* crash_handler_x11.cpp */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */
|
||||
/* */
|
||||
/* 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. */
|
||||
/*************************************************************************/
|
||||
#ifdef DEBUG_ENABLED
|
||||
#define CRASH_HANDLER_ENABLED 1
|
||||
#endif
|
||||
|
||||
#include "main/main.h"
|
||||
#include "os_x11.h"
|
||||
|
||||
#ifdef CRASH_HANDLER_ENABLED
|
||||
#include <cxxabi.h>
|
||||
#include <dlfcn.h>
|
||||
#include <execinfo.h>
|
||||
#include <signal.h>
|
||||
|
||||
static void handle_crash(int sig) {
|
||||
if (OS::get_singleton() == NULL)
|
||||
return;
|
||||
|
||||
void *bt_buffer[256];
|
||||
size_t size = backtrace(bt_buffer, 256);
|
||||
String _execpath = OS::get_singleton()->get_executable_path();
|
||||
String msg = GLOBAL_GET("debug/settings/backtrace/message");
|
||||
|
||||
// Dump the backtrace to stderr with a message to the user
|
||||
fprintf(stderr, "%s: Program crashed with signal %d\n", __FUNCTION__, sig);
|
||||
fprintf(stderr, "Dumping the backtrace. %ls\n", msg.c_str());
|
||||
char **strings = backtrace_symbols(bt_buffer, size);
|
||||
if (strings) {
|
||||
for (size_t i = 1; i < size; i++) {
|
||||
char fname[1024];
|
||||
Dl_info info;
|
||||
|
||||
snprintf(fname, 1024, "%s", strings[i]);
|
||||
|
||||
// Try to demangle the function name to provide a more readable one
|
||||
if (dladdr(bt_buffer[i], &info) && info.dli_sname) {
|
||||
if (info.dli_sname[0] == '_') {
|
||||
int status;
|
||||
char *demangled = abi::__cxa_demangle(info.dli_sname, NULL, 0, &status);
|
||||
|
||||
if (status == 0 && demangled) {
|
||||
snprintf(fname, 1024, "%s", demangled);
|
||||
}
|
||||
|
||||
if (demangled)
|
||||
free(demangled);
|
||||
}
|
||||
}
|
||||
|
||||
List<String> args;
|
||||
|
||||
char str[1024];
|
||||
snprintf(str, 1024, "%p", bt_buffer[i]);
|
||||
args.push_back(str);
|
||||
args.push_back("-e");
|
||||
args.push_back(_execpath);
|
||||
|
||||
String output = "";
|
||||
|
||||
// Try to get the file/line number using addr2line
|
||||
if (OS::get_singleton()) {
|
||||
int ret;
|
||||
Error err = OS::get_singleton()->execute(String("addr2line"), args, true, NULL, &output, &ret);
|
||||
if (err == OK) {
|
||||
output.erase(output.length() - 1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
fprintf(stderr, "[%ld] %s (%ls)\n", i, fname, output.c_str());
|
||||
}
|
||||
|
||||
free(strings);
|
||||
}
|
||||
fprintf(stderr, "-- END OF BACKTRACE --\n");
|
||||
|
||||
// Abort to pass the error to the OS
|
||||
abort();
|
||||
}
|
||||
#endif
|
||||
|
||||
CrashHandler::CrashHandler() {
|
||||
disabled = false;
|
||||
}
|
||||
|
||||
CrashHandler::~CrashHandler() {
|
||||
}
|
||||
|
||||
void CrashHandler::disable() {
|
||||
if (disabled)
|
||||
return;
|
||||
|
||||
#ifdef CRASH_HANDLER_ENABLED
|
||||
signal(SIGSEGV, NULL);
|
||||
signal(SIGFPE, NULL);
|
||||
signal(SIGILL, NULL);
|
||||
#endif
|
||||
|
||||
disabled = true;
|
||||
}
|
||||
|
||||
void CrashHandler::initialize() {
|
||||
#ifdef CRASH_HANDLER_ENABLED
|
||||
signal(SIGSEGV, handle_crash);
|
||||
signal(SIGFPE, handle_crash);
|
||||
signal(SIGILL, handle_crash);
|
||||
#endif
|
||||
}
|
47
platform/x11/crash_handler_x11.h
Normal file
47
platform/x11/crash_handler_x11.h
Normal file
|
@ -0,0 +1,47 @@
|
|||
/*************************************************************************/
|
||||
/* crash_handler_x11.h */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */
|
||||
/* */
|
||||
/* 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. */
|
||||
/*************************************************************************/
|
||||
#ifndef CRASH_HANDLER_X11_H
|
||||
#define CRASH_HANDLER_X11_H
|
||||
|
||||
class CrashHandler {
|
||||
|
||||
bool disabled;
|
||||
|
||||
public:
|
||||
void initialize();
|
||||
|
||||
void disable();
|
||||
bool is_disabled() const { return disabled; };
|
||||
|
||||
CrashHandler();
|
||||
~CrashHandler();
|
||||
};
|
||||
|
||||
#endif
|
|
@ -87,6 +87,7 @@ def configure(env):
|
|||
|
||||
elif (env["target"] == "debug"):
|
||||
env.Prepend(CCFLAGS=['-g2', '-DDEBUG_ENABLED', '-DDEBUG_MEMORY_ENABLED'])
|
||||
env.Append(LINKFLAGS=['-rdynamic'])
|
||||
|
||||
## Architecture
|
||||
|
||||
|
|
|
@ -93,6 +93,13 @@ const char *OS_X11::get_audio_driver_name(int p_driver) const {
|
|||
return AudioDriverManager::get_driver(p_driver)->get_name();
|
||||
}
|
||||
|
||||
void OS_X11::initialize_core() {
|
||||
|
||||
crash_handler.initialize();
|
||||
|
||||
OS_Unix::initialize_core();
|
||||
}
|
||||
|
||||
void OS_X11::initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver) {
|
||||
|
||||
long im_event_mask = 0;
|
||||
|
@ -2172,6 +2179,14 @@ int OS_X11::get_power_percent_left() {
|
|||
return power_manager->get_power_percent_left();
|
||||
}
|
||||
|
||||
void OS_X11::disable_crash_handler() {
|
||||
crash_handler.disable();
|
||||
}
|
||||
|
||||
bool OS_X11::is_disable_crash_handler() const {
|
||||
return crash_handler.is_disabled();
|
||||
}
|
||||
|
||||
OS_X11::OS_X11() {
|
||||
|
||||
#ifdef RTAUDIO_ENABLED
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
#define OS_X11_H
|
||||
|
||||
#include "context_gl_x11.h"
|
||||
#include "crash_handler_x11.h"
|
||||
#include "drivers/unix/os_unix.h"
|
||||
#include "os/input.h"
|
||||
#include "servers/visual_server.h"
|
||||
|
@ -170,6 +171,8 @@ class OS_X11 : public OS_Unix {
|
|||
|
||||
PowerX11 *power_manager;
|
||||
|
||||
CrashHandler crash_handler;
|
||||
|
||||
int audio_driver_index;
|
||||
unsigned int capture_idle;
|
||||
bool maximized;
|
||||
|
@ -191,6 +194,7 @@ protected:
|
|||
virtual int get_audio_driver_count() const;
|
||||
virtual const char *get_audio_driver_name(int p_driver) const;
|
||||
|
||||
virtual void initialize_core();
|
||||
virtual void initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver);
|
||||
virtual void finalize();
|
||||
|
||||
|
@ -273,6 +277,9 @@ public:
|
|||
|
||||
void run();
|
||||
|
||||
void disable_crash_handler();
|
||||
bool is_disable_crash_handler() const;
|
||||
|
||||
OS_X11();
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in a new issue