├── thirdparty ├── breakpad │ └── tmp.md └── crashpad │ └── tmp.md ├── modules └── crashpad │ ├── register_types.h │ ├── config.py │ ├── register_types.cpp │ ├── SCsub │ ├── crashpad.h │ └── crashpad.cpp ├── wiki_images ├── Crashpad_GettingStarted_PropertySetup.png ├── Crashpad_GettingStarted_ApplicationFilepath.png └── Crashpad_GettingStarted_InitialPropertySetup.png ├── LICENSE.md ├── README.md └── .gitignore /thirdparty/breakpad/tmp.md: -------------------------------------------------------------------------------- 1 | just a temporary file 2 | -------------------------------------------------------------------------------- /thirdparty/crashpad/tmp.md: -------------------------------------------------------------------------------- 1 | just a temporary file 2 | -------------------------------------------------------------------------------- /modules/crashpad/register_types.h: -------------------------------------------------------------------------------- 1 | 2 | void register_crashpad_types(); 3 | void unregister_crashpad_types(); 4 | -------------------------------------------------------------------------------- /modules/crashpad/config.py: -------------------------------------------------------------------------------- 1 | def can_build(env, platform): 2 | return True 3 | 4 | 5 | def configure(env): 6 | pass 7 | -------------------------------------------------------------------------------- /wiki_images/Crashpad_GettingStarted_PropertySetup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwistedTwigleg/Godot_BacktraceModule/HEAD/wiki_images/Crashpad_GettingStarted_PropertySetup.png -------------------------------------------------------------------------------- /wiki_images/Crashpad_GettingStarted_ApplicationFilepath.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwistedTwigleg/Godot_BacktraceModule/HEAD/wiki_images/Crashpad_GettingStarted_ApplicationFilepath.png -------------------------------------------------------------------------------- /wiki_images/Crashpad_GettingStarted_InitialPropertySetup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwistedTwigleg/Godot_BacktraceModule/HEAD/wiki_images/Crashpad_GettingStarted_InitialPropertySetup.png -------------------------------------------------------------------------------- /modules/crashpad/register_types.cpp: -------------------------------------------------------------------------------- 1 | #include "register_types.h" 2 | 3 | #include "core/class_db.h" 4 | #include "crashpad.h" 5 | 6 | void register_crashpad_types() { 7 | ClassDB::register_class(); 8 | } 9 | 10 | void unregister_crashpad_types() { 11 | // Nothing to do here in this example. 12 | } -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 TwistedTwigleg 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /modules/crashpad/SCsub: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | Import("env") 4 | 5 | env.add_source_files(env.modules_sources, "*.cpp") 6 | 7 | # If compiling on Windows, MacOS, or Linux: 8 | if env["platform"] == "windows" or env["platform"] == "osx" or env["platform"] == "x11": 9 | env.Append(CPPPATH=["#thirdparty/crashpad/"]) 10 | env.Append(CPPPATH=["#thirdparty/crashpad/crashpad/third_party/mini_chromium/mini_chromium/"]) 11 | env.Append(CPPPATH=["#thirdparty/crashpad/crashpad"]) 12 | env.Append(CPPPATH=["#thirdparty/crashpad/crashpad/out/Default/gen/"]) 13 | 14 | # If compiling on Windows 15 | if env["platform"] == "windows": 16 | env.Append(LIBS=File('lib/client.lib')) 17 | env.Append(LIBS=File('lib/util.lib')) 18 | env.Append(LIBS=File('lib/base.lib')) 19 | env.Append(LIBS=File('lib/common.lib')) 20 | # If compiling on Mac (untested) 21 | elif env["platform"] == "osx": 22 | env.Append(LIBS=File('lib/libclient.a')) 23 | env.Append(LIBS=File('lib/libutil.a')) 24 | env.Append(LIBS=File('lib/libbase.a')) 25 | env.Append(LIBS=File('lib/libcommon.a')) 26 | # If compiling on Linux 27 | elif env["platform"] == "x11" or env["platform"] == "server" or env["platform"] == "linuxbsd": 28 | env.Append(LIBS=File('lib/libclient.a')) 29 | env.Append(LIBS=File('lib/libutil.a')) 30 | env.Append(LIBS=File('lib/libbase.a')) 31 | env.Append(LIBS=File('lib/libcommon.a')) 32 | -------------------------------------------------------------------------------- /modules/crashpad/crashpad.h: -------------------------------------------------------------------------------- 1 | /* crashpad.h */ 2 | 3 | #ifndef CRASHPAD_H 4 | #define CRASHPAD_H 5 | 6 | #include "scene/main/node.h" 7 | #include "core/reference.h" 8 | #include "core/os/dir_access.h" 9 | 10 | #if defined WINDOWS_ENABLED || defined OSX_ENABLED || defined X11_ENABLED 11 | #include "crashpad/client/crash_report_database.h" 12 | #include "crashpad/client/crashpad_client.h" 13 | #include "crashpad/client/settings.h" 14 | #endif 15 | 16 | class Crashpad : public Node { 17 | GDCLASS(Crashpad, Node); 18 | 19 | protected: 20 | static void _bind_methods(); 21 | void _notification(int p_notification); 22 | 23 | public: 24 | static bool crashpad_skip_error_upload; 25 | static Dictionary crashpad_user_crash_attributes; 26 | static bool crashpad_upload_godot_log; 27 | 28 | static String crashpad_api_URL; 29 | static String crashpad_api_token; 30 | static String crashpad_database_path; 31 | static String crashpad_application_path; 32 | static bool crashpad_linux_delete_crashpad_database_data_on_start; 33 | static bool crashpad_use_manual_application_extension; 34 | static String crashpad_manual_application_extension; 35 | 36 | #if defined WINDOWS_ENABLED || defined OSX_ENABLED || defined X11_ENABLED 37 | crashpad::CrashpadClient crashpad_client; 38 | bool crashpad_client_init = false; 39 | std::map crashpad_annotations; 40 | std::vector crashpad_arguments; 41 | #endif 42 | 43 | void start_crashpad(); 44 | void force_crash(); 45 | 46 | void set_crashpad_api_url(String new_url); 47 | String get_crashpad_api_url(); 48 | void set_crashpad_api_token(String new_token); 49 | String get_crashpad_api_token(); 50 | void set_crashpad_database_path(String new_path); 51 | String get_crashpad_database_path(); 52 | void set_crashpad_application_path(String new_path); 53 | String get_crashpad_application_path(); 54 | 55 | void set_crashpad_skip_error_upload(bool new_value); 56 | bool get_crashpad_skip_error_upload(); 57 | 58 | void set_crashpad_user_crash_attributes(Dictionary new_value); 59 | Dictionary get_crashpad_user_crash_attributes(); 60 | 61 | void set_crashpad_use_manual_application_extension(bool new_value); 62 | bool get_crashpad_use_manual_application_extension(); 63 | void set_crashpad_manual_application_extension(String new_value); 64 | String get_crashpad_manual_application_extension(); 65 | 66 | void set_crashpad_upload_godot_log(bool new_value); 67 | bool get_crashpad_upload_godot_log(); 68 | 69 | Crashpad(); 70 | ~Crashpad(); 71 | 72 | private: 73 | bool check_for_crashpad_application(); 74 | String get_global_crashpad_application_path(); 75 | bool check_for_crashpad_database(bool make_if_not_exist); 76 | String get_global_path_from_local_path(String input_local_path); 77 | 78 | #if defined WINDOWS_ENABLED || defined OSX_ENABLED || defined X11_ENABLED 79 | std::string get_std_string_from_godot_string(String input); 80 | #endif 81 | 82 | Array get_directory_contents(String root_directory_path, String desired_extension); 83 | void _add_directory_contents(DirAccess *dir, Array files, Array directories, String desired_extension); 84 | void upload_dump_through_curl(String dump_path); 85 | 86 | }; 87 | 88 | #endif // CRASHPAD_H -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Godot Backtrace Module 2 | 3 | This module adds automatic crash generation support to Godot 3.x with the intent of making it possible to send the crash data to [Backtrace](https://backtrace.io/) for automatic error reporting. 4 | 5 | **Why use this module?** With automatic crash reporting, you can track, examine, and resolve errors quickly and efficiently without having to rely solely on user feedback or QA sessions. By using a service like Backtrace, you can have a single hub that makes it easy to track all the issues users may be having with detailed error logs. This allows you to spend more time developing fixes and making a great product, not chasing down information. 6 | 7 | Check out the GitHub Wiki for more information on how to install and use the module in your Godot projects! 8 | 9 | ## Features 10 | 11 | Please note that this module is still a **work in progress!** Changes are still being made, with new features and improvements still to be made to the code. 12 | 13 | The feature list below will continue to grow and improve over time as the module is developed. Please check out the roadmap below for an idea of what is coming! 14 | 15 | * Adds a new node for generating and sending crash report data automatically to online servers 16 | * Adds a `Crashpad` node, which uses [Google Crashpad](https://chromium.googlesource.com/crashpad/crashpad/) to generate and send crash reports 17 | * The module will compile and all platforms but will not function properly unless on the correct OS. 18 | * For example, if using `Crashpad` on iOS or Android, the node will print a warning but otherwise not do anything. 19 | * This allows you to use both nodes in your projects without having to worry about it crashing or breaking. 20 | * The `Crashpad` node exposes all the properties needed for setup in the Godot editor 21 | * Custom attributes can be set for easy sorting and filtering of uploaded error reports 22 | * (*Coming soon to Windows and MacOS*) Supports sending the Godot log files alongside the crash report 23 | * If writing the log to a file is enabled in the project settings, `Crashpad` will upload the log alongside the C++ generated crash 24 | * Written in C++ for fast and efficient error generation 25 | * This allows the code to capture crashes caused by Godot's C++ code and accurately generate symbol files 26 | * Supports Windows, MacOS, and Linux 27 | * MacOS support has not yet been tested, but it should work 28 | 29 | ## Roadmap 30 | 31 | Below is the roadmap for features and additions to be made to this module: 32 | 33 | * `Crashpad` module roadmap: 34 | * Add attachment support to upload Godot log files for Windows and MacOS 35 | * Investigate adding Android support 36 | * Investigate add iOS support 37 | * Investigate adding support for adding a screenshot of the Godot application at the moment of the crash 38 | * Add support for Godot 4.0 39 | * Investigate adding support for `Error-free users` and `Error-free sessions` for Bracktrace 40 | * (*And more! If you have any suggestions, please make a feature request issue!*) 41 | 42 | Please note the roadmap above is not necessarily in priority order and will continue to evolve as development on the module progresses. 43 | 44 | ## Other 45 | 46 | Because this is a Godot module, it will need to be added to Godot source code and then compiled for it to work properly! See the GitHub Wiki for more information on how to install and use the module in your Godot project. Please note that this module is written for Godot 3.x currently, though it should work with any version of Godot 3 without needing many modifications. 47 | 48 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Godot auto generated files 2 | *.gen.* 3 | .import/ 4 | 5 | # Documentation generated by doxygen or from classes.xml 6 | doc/_build/ 7 | 8 | # Javascript specific 9 | *.bc 10 | 11 | # CLion 12 | cmake-build-debug 13 | 14 | # Android specific 15 | .gradle 16 | local.properties 17 | *.iml 18 | .idea 19 | .gradletasknamecache 20 | project.properties 21 | platform/android/java/app/libs/* 22 | platform/android/java/libs/* 23 | platform/android/java/lib/.cxx/ 24 | platform/android/java/nativeSrcsConfigs/.cxx/ 25 | 26 | # General c++ generated files 27 | *.lib 28 | *.o 29 | *.ox 30 | *.a 31 | *.ax 32 | *.d 33 | *.so 34 | *.os 35 | *.Plo 36 | *.lo 37 | # Binutils tmp linker output of the form "stXXXXXX" where "X" is alphanumeric 38 | st[A-Za-z0-9][A-Za-z0-9][A-Za-z0-9][A-Za-z0-9][A-Za-z0-9][A-Za-z0-9] 39 | 40 | # Libs generated files 41 | .deps/* 42 | .dirstamp 43 | 44 | # Gprof output 45 | gmon.out 46 | 47 | # Vim temp files 48 | *.swo 49 | *.swp 50 | 51 | # Qt project files 52 | *.config 53 | *.creator 54 | *.creator.* 55 | *.files 56 | *.includes 57 | *.cflags 58 | *.cxxflags 59 | 60 | # Code::Blocks files 61 | *.cbp 62 | *.layout 63 | *.depend 64 | 65 | # Eclipse CDT files 66 | .cproject 67 | .settings/ 68 | *.pydevproject 69 | *.launch 70 | 71 | # Geany/geany-plugins files 72 | *.geany 73 | .geanyprj 74 | 75 | # Jetbrains IDEs 76 | .idea/ 77 | 78 | # Misc 79 | .DS_Store 80 | __MACOSX 81 | logs/ 82 | 83 | # for projects that use SCons for building: http://http://www.scons.org/ 84 | .sconf_temp 85 | .sconsign*.dblite 86 | *.pyc 87 | 88 | # https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 89 | ## Ignore Visual Studio temporary files, build results, and 90 | ## files generated by popular Visual Studio add-ons. 91 | 92 | # User-specific files 93 | *.suo 94 | *.user 95 | *.sln.docstates 96 | *.sln 97 | *.vcxproj* 98 | 99 | # Custom SCons configuration override 100 | /custom.py 101 | 102 | # Build results 103 | [Dd]ebug/ 104 | [Dd]ebugPublic/ 105 | [Rr]elease/ 106 | x64/ 107 | build/ 108 | bld/ 109 | [Bb]in/ 110 | [Oo]bj/ 111 | *.debug 112 | *.dSYM 113 | 114 | # Visual Studio cache/options directory 115 | .vs/ 116 | 117 | # MSTest test Results 118 | [Tt]est[Rr]esult*/ 119 | [Bb]uild[Ll]og.* 120 | 121 | # Hints for improving IntelliSense, created together with VS project 122 | cpp.hint 123 | 124 | #NUNIT 125 | *.VisualState.xml 126 | TestResult.xml 127 | 128 | *.o 129 | *.a 130 | *_i.c 131 | *_p.c 132 | *_i.h 133 | *.ilk 134 | *.meta 135 | *.obj 136 | *.pch 137 | *.pdb 138 | *.pgc 139 | *.pgd 140 | *.rsp 141 | *.sbr 142 | *.tlb 143 | *.tli 144 | *.tlh 145 | *.tmp 146 | *.tmp_proj 147 | *.bak 148 | *.log 149 | *.vspscc 150 | *.vssscc 151 | .builds 152 | *.pidb 153 | *.svclog 154 | *.scc 155 | *.nib 156 | 157 | # Chutzpah Test files 158 | _Chutzpah* 159 | 160 | # Visual C++ cache files 161 | ipch/ 162 | *.aps 163 | *.ncb 164 | *.opensdf 165 | *.sdf 166 | *.cachefile 167 | *.VC.db 168 | *.VC.opendb 169 | *.VC.VC.opendb 170 | enc_temp_folder/ 171 | 172 | # Visual Studio profiler 173 | *.psess 174 | *.vsp 175 | *.vspx 176 | 177 | # CodeLite project files 178 | *.project 179 | *.workspace 180 | .codelite/ 181 | 182 | # TFS 2012 Local Workspace 183 | $tf/ 184 | 185 | # Guidance Automation Toolkit 186 | *.gpState 187 | 188 | # ReSharper is a .NET coding add-in 189 | _ReSharper*/ 190 | *.[Rr]e[Ss]harper 191 | *.DotSettings.user 192 | 193 | # JustCode is a .NET coding addin-in 194 | .JustCode 195 | 196 | # TeamCity is a build add-in 197 | _TeamCity* 198 | 199 | # DotCover is a Code Coverage Tool 200 | *.dotCover 201 | 202 | # NCrunch 203 | *.ncrunch* 204 | _NCrunch_* 205 | .*crunch*.local.xml 206 | 207 | # MightyMoose 208 | *.mm.* 209 | AutoTest.Net/ 210 | 211 | # Web workbench (sass) 212 | .sass-cache/ 213 | 214 | # Installshield output folder 215 | [Ee]xpress/ 216 | 217 | # DocProject is a documentation generator add-in 218 | DocProject/buildhelp/ 219 | DocProject/Help/*.HxT 220 | DocProject/Help/*.HxC 221 | DocProject/Help/*.hhc 222 | DocProject/Help/*.hhk 223 | DocProject/Help/*.hhp 224 | DocProject/Help/Html2 225 | DocProject/Help/html 226 | 227 | # Click-Once directory 228 | publish/ 229 | 230 | # Publish Web Output 231 | *.[Pp]ublish.xml 232 | *.azurePubxml 233 | 234 | # NuGet Packages Directory 235 | ## TODO: If you have NuGet Package Restore enabled, uncomment the next line 236 | #packages/* 237 | ## TODO: If the tool you use requires repositories.config, also uncomment the next line 238 | #!packages/repositories.config 239 | 240 | # Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets 241 | # This line needs to be after the ignore of the build folder (and the packages folder if the line above has been uncommented) 242 | !packages/build/ 243 | 244 | # Windows Azure Build Output 245 | csx/ 246 | *.build.csdef 247 | 248 | # Windows Store app package directory 249 | AppPackages/ 250 | 251 | # Others 252 | sql/ 253 | *.Cache 254 | ClientBin/ 255 | [Ss]tyle[Cc]op.* 256 | ~$* 257 | *~ 258 | *.dbmdl 259 | *.dbproj.schemaview 260 | *.pfx 261 | *.publishsettings 262 | node_modules/ 263 | __pycache__/ 264 | 265 | # KDE 266 | .directory 267 | 268 | # Kdevelop project files 269 | *.kdev4 270 | 271 | # Kate swap files 272 | *.kate-swp 273 | 274 | # Xcode 275 | xcuserdata/ 276 | *.xcscmblueprint 277 | *.xccheckout 278 | *.xcodeproj/* 279 | 280 | # RIA/Silverlight projects 281 | Generated_Code/ 282 | 283 | # Backup & report files from converting an old project file to a newer 284 | # Visual Studio version. Backup files are not needed, because we have git ;-) 285 | _UpgradeReport_Files/ 286 | Backup*/ 287 | UpgradeLog*.XML 288 | UpgradeLog*.htm 289 | 290 | # SQL Server files 291 | App_Data/*.mdf 292 | App_Data/*.ldf 293 | 294 | # Business Intelligence projects 295 | *.rdl.data 296 | *.bim.layout 297 | *.bim_*.settings 298 | 299 | # Microsoft Fakes 300 | FakesAssemblies/ 301 | 302 | # ========================= 303 | # Windows detritus 304 | # ========================= 305 | 306 | # Windows image file caches 307 | [Tt]humbs.db 308 | [Tt]humbs.db:encryptable 309 | ehthumbs.db 310 | ehthumbs_vista.db 311 | 312 | # Windows stackdumps 313 | *.stackdump 314 | 315 | # Windows shortcuts 316 | *.lnk 317 | 318 | # Folder config file 319 | [Dd]esktop.ini 320 | 321 | # Recycle Bin used on file shares 322 | $RECYCLE.BIN/ 323 | logo.h 324 | *.autosave 325 | 326 | # https://github.com/github/gitignore/blob/master/Global/Tags.gitignore 327 | # Ignore tags created by etags, ctags, gtags (GNU global) and cscope 328 | TAGS 329 | !TAGS/ 330 | tags 331 | *.tags 332 | !tags/ 333 | gtags.files 334 | GTAGS 335 | GRTAGS 336 | GPATH 337 | cscope.files 338 | cscope.out 339 | cscope.in.out 340 | cscope.po.out 341 | godot.creator.* 342 | 343 | projects/ 344 | platform/windows/godot_res.res 345 | 346 | # Visual Studio 2017 and Visual Studio Code workspace folder 347 | /.vs 348 | /.vscode 349 | 350 | # Visual Studio Code workspace file 351 | *.code-workspace 352 | 353 | # Scons construction environment dump 354 | .scons_env.json 355 | 356 | # Scons progress indicator 357 | .scons_node_count 358 | 359 | # ccls cache (https://github.com/MaskRay/ccls) 360 | .ccls-cache/ 361 | 362 | # compile commands (https://clang.llvm.org/docs/JSONCompilationDatabase.html) 363 | compile_commands.json 364 | 365 | # Cppcheck 366 | *.cppcheck 367 | 368 | # https://clangd.llvm.org/ cache folder 369 | .clangd/ 370 | .cache/ 371 | -------------------------------------------------------------------------------- /modules/crashpad/crashpad.cpp: -------------------------------------------------------------------------------- 1 | /* crashpad.cpp */ 2 | 3 | #include "crashpad.h" 4 | #include "core/os/os.h" 5 | #include "core/os/file_access.h" 6 | #include "core/os/dir_access.h" 7 | #include "core/project_settings.h" 8 | 9 | 10 | // We have to use ifdef because Crashpad is currently not supported on Linux 11 | #if defined WINDOWS_ENABLED || defined OSX_ENABLED 12 | #include "crashpad/client/crash_report_database.h" 13 | #include "crashpad/client/crashpad_client.h" 14 | #include "crashpad/client/settings.h" 15 | 16 | // Needed to avoid compiling/linking issues on Windows 17 | #ifdef WINDOWS_ENABLED 18 | #define NOMINMAX 19 | #endif 20 | 21 | // Needed for adding a delay to a thread so we can send the dump via curl 22 | #ifdef X11_ENABLED 23 | #include 24 | #include 25 | #endif 26 | 27 | #endif 28 | 29 | // Static variables 30 | bool Crashpad::crashpad_skip_error_upload = false; 31 | Dictionary Crashpad::crashpad_user_crash_attributes = Dictionary(); 32 | bool Crashpad::crashpad_upload_godot_log = false; 33 | // Crashpad variables 34 | String Crashpad::crashpad_api_URL = ""; 35 | String Crashpad::crashpad_api_token = ""; 36 | String Crashpad::crashpad_database_path = ""; 37 | String Crashpad::crashpad_application_path = ""; 38 | bool Crashpad::crashpad_linux_delete_crashpad_database_data_on_start = true; 39 | bool Crashpad::crashpad_use_manual_application_extension = false; 40 | String Crashpad::crashpad_manual_application_extension = ""; 41 | 42 | 43 | void Crashpad::start_crashpad() { 44 | 45 | #if defined WINDOWS_ENABLED || defined OSX_ENABLED || defined X11_ENABLED 46 | // Make sure the application exists 47 | if (check_for_crashpad_application() == false) { 48 | String application_path = get_global_path_from_local_path(Crashpad::crashpad_application_path); 49 | ERR_PRINT("Cannot find crashpad_handle application! Setup 'application_path' in editor to point to application! Input file path: " + application_path); 50 | print_line("Crashpad Error: Cannot find crashpad_handle application! Setup 'application_path' in editor to point to application! Input file path: " + application_path); 51 | return; 52 | } 53 | // Make sure the crashpad database directory exists or create it if it does not 54 | check_for_crashpad_database(true); 55 | 56 | #if defined WINDOWS_ENABLED 57 | base::FilePath::StringType database_path((get_global_path_from_local_path(Crashpad::crashpad_database_path).c_str())); 58 | base::FilePath::StringType handler_path((get_global_crashpad_application_path().c_str())); 59 | #else 60 | base::FilePath::StringType database_path(get_std_string_from_godot_string(get_global_path_from_local_path(Crashpad::crashpad_database_path))); 61 | base::FilePath::StringType handler_path(get_std_string_from_godot_string(get_global_crashpad_application_path())); 62 | #endif 63 | 64 | base::FilePath db(database_path); 65 | base::FilePath handler(handler_path); 66 | 67 | // Database management is only on Windows and MacOS. For other platforms, we have to upload manually using CURL 68 | // and handle deleting the database files ourselves 69 | #if defined WINDOWS_ENABLED || defined OSX_ENABLED 70 | std::unique_ptr database = crashpad::CrashReportDatabase::Initialize(db); 71 | 72 | if (database == nullptr || database->GetSettings() == NULL) { 73 | ERR_PRINT("Could not initialize crashpad database!"); 74 | print_line("Crashpad Error: Could not initialize crashpad database!"); 75 | return; 76 | } 77 | // Enable automatic uploads 78 | database->GetSettings()->SetUploadsEnabled(true); 79 | #endif 80 | 81 | // The Backtrace URL 82 | String upload_url = Crashpad::crashpad_api_URL + Crashpad::crashpad_api_token + "/minidump"; 83 | std::string upload_url_s = get_std_string_from_godot_string(upload_url); 84 | 85 | // Remove upload limit for now 86 | crashpad_arguments.push_back("--no-rate-limit"); 87 | 88 | // Add annotations 89 | for (int i = 0; i < crashpad_user_crash_attributes.size(); i++) 90 | { 91 | Variant key_variant = crashpad_user_crash_attributes.get_key_at_index(i); 92 | Variant value_variant = crashpad_user_crash_attributes.get_value_at_index(i); 93 | String key_string = (String)key_variant; 94 | String value_string = (String)value_variant; 95 | 96 | crashpad_annotations.insert(std::pair(get_std_string_from_godot_string(key_string), get_std_string_from_godot_string(value_string))); 97 | } 98 | 99 | // Add log file attachment? 100 | if (Crashpad::crashpad_upload_godot_log == true) 101 | { 102 | // Currently not implemented for Windows or MacOS 103 | #if defined WINDOWS_ENABLED || defined OSX_ENABLED 104 | WARN_PRINT("Log file attachment support not yet implemented!"); 105 | print_line("Crashpad Warning: Log file attachment support not yet implemented!"); 106 | #endif 107 | } 108 | 109 | // Skip starting the client? 110 | if (Crashpad::crashpad_skip_error_upload == true) 111 | { 112 | OS::get_singleton()->print("Skipped Crashpad error uploading!"); 113 | print_line("Crashpad Note: Skipped Crashpad error uploading!"); 114 | return; 115 | } 116 | 117 | crashpad_client_init = crashpad_client.StartHandler( 118 | handler, 119 | db, 120 | db, 121 | upload_url_s, 122 | crashpad_annotations, 123 | crashpad_arguments, 124 | true, 125 | true 126 | ); 127 | if (crashpad_client_init == false) { 128 | ERR_PRINT("Could not initialize crashpad client!"); 129 | return; 130 | } 131 | 132 | OS::get_singleton()->print("Crashpad initialized successfully!"); 133 | print_line("Crashpad Note: Crashpad initialized successfully!"); 134 | return; 135 | #endif 136 | 137 | // If running on an unsupported platform, just log an error 138 | WARN_PRINT("Crashpad not supported on this platform!"); 139 | print_line("Crashpad Warning: Crashpad not supported on this platform!"); 140 | return; 141 | 142 | } 143 | 144 | void Crashpad::_notification(int p_notification) 145 | { 146 | // Only needed on Linux. This is because we have to upload the dump ourselves via CURL 147 | // as there is not any database manager for Linux with Crashpad currently. 148 | #if defined X11_ENABLED 149 | if (p_notification == NOTIFICATION_READY) 150 | { 151 | if (Crashpad::crashpad_linux_delete_crashpad_database_data_on_start == true) 152 | { 153 | // Get all the dump files 154 | Array file_dump_search_results = get_directory_contents(get_global_path_from_local_path(Crashpad::crashpad_database_path), ".dmp"); 155 | if (file_dump_search_results.empty() == false) 156 | { 157 | // Delete the dumps 158 | Array file_search_files = (Array)file_dump_search_results.get(0); 159 | for (int i = 0; i < file_search_files.size(); i++) 160 | { 161 | DirAccess::remove_file_or_error(file_search_files[i]); 162 | } 163 | } 164 | // Get all the meta files 165 | Array file_meta_search_results = get_directory_contents(get_global_path_from_local_path(Crashpad::crashpad_database_path), ".meta"); 166 | if (file_meta_search_results.empty() == false) 167 | { 168 | // Delete the meta 169 | Array file_search_files = (Array)file_meta_search_results.get(0); 170 | for (int i = 0; i < file_search_files.size(); i++) 171 | { 172 | DirAccess::remove_file_or_error(file_search_files[i]); 173 | } 174 | } 175 | } 176 | } 177 | else if (p_notification == MainLoop::NOTIFICATION_CRASH) { 178 | ERR_PRINT("Notification of crash found!"); 179 | 180 | // Sleep - so Crashpad can generate the dump 181 | // Not ideal, but Crashpad on Linux doesn't automatically send the crash, so we have to do it manually 182 | // using CURL. 183 | // Credit for snippet: https://stackoverflow.com/questions/4184468/sleep-for-milliseconds 184 | std::this_thread::sleep_for(std::chrono::microseconds(2000)); 185 | 186 | // Get all the dump files 187 | Array file_search_results = get_directory_contents(get_global_path_from_local_path(Crashpad::crashpad_database_path), ".dmp"); 188 | // Was there an error? 189 | if (file_search_results.empty() == true) 190 | { 191 | return; 192 | } 193 | 194 | Array file_search_files = (Array)file_search_results.get(0); 195 | 196 | for (int i = 0; i < file_search_files.size(); i++) 197 | { 198 | upload_dump_through_curl((String)file_search_files[i]); 199 | } 200 | } 201 | #endif 202 | } 203 | 204 | #if defined WINDOWS_ENABLED || defined OSX_ENABLED || defined X11_ENABLED 205 | std::string Crashpad::get_std_string_from_godot_string(String input) 206 | { 207 | // Credit: https://godotengine.org/qa/18552/gdnative-convert-godot-string-to-const-char 208 | std::wstring input_ws = input.c_str(); 209 | return std::string(input_ws.begin(), input_ws.end()); 210 | } 211 | #endif 212 | 213 | Array Crashpad::get_directory_contents(String root_directory_path, String desired_extension) 214 | { 215 | Array files; 216 | Array directories; 217 | if (root_directory_path.ends_with("//")) 218 | { 219 | root_directory_path.remove(root_directory_path.size()-1); 220 | root_directory_path.remove(root_directory_path.size()-1); 221 | } 222 | else if (root_directory_path.ends_with("/")) 223 | { 224 | root_directory_path.remove(root_directory_path.size()-1); 225 | } 226 | DirAccess *dir_link = DirAccess::open(root_directory_path); 227 | 228 | Error error = dir_link->list_dir_begin(); 229 | if (error == Error::ERR_CANT_OPEN) 230 | { 231 | ERR_PRINT("Cannot open Crashpad database folder! Cannot upload crash log automatically"); 232 | return files; 233 | } 234 | 235 | _add_directory_contents(dir_link, files, directories, desired_extension); 236 | 237 | Array return_value; 238 | return_value.append(files); 239 | return_value.append(directories); 240 | return return_value; 241 | } 242 | void Crashpad::_add_directory_contents(DirAccess *dir, Array files, Array directories, String desired_extension) 243 | { 244 | String filename = dir->get_next(); 245 | 246 | while (filename != "") 247 | { 248 | if (filename == "." || filename == "..") 249 | { 250 | filename = dir->get_next(); 251 | } 252 | else 253 | { 254 | String path = dir->get_current_dir() + "/" + filename; 255 | if (dir->current_is_dir()) 256 | { 257 | DirAccess *sub_dir_link = DirAccess::open(path); 258 | sub_dir_link->list_dir_begin(); 259 | directories.append(path); 260 | _add_directory_contents(sub_dir_link, files, directories, desired_extension); 261 | } 262 | else 263 | { 264 | if (path.ends_with(desired_extension)) 265 | { 266 | files.append(path); 267 | } 268 | } 269 | filename = dir->get_next(); 270 | } 271 | } 272 | dir->list_dir_end(); 273 | } 274 | 275 | void Crashpad::upload_dump_through_curl(String dump_path) 276 | { 277 | List arguments; 278 | 279 | // Start uploading using CURL 280 | arguments.push_back("-v"); 281 | 282 | // Upload arguments 283 | for (int i = 0; i < Crashpad::crashpad_user_crash_attributes.size(); i++) 284 | { 285 | Variant key = Crashpad::crashpad_user_crash_attributes.get_key_at_index(i); 286 | Variant value = Crashpad::crashpad_user_crash_attributes.get_value_at_index(i); 287 | String key_string = (String)key; 288 | String value_string = (String)value; 289 | 290 | arguments.push_back("-F"); 291 | arguments.push_back(key_string + "=" + value_string); 292 | } 293 | 294 | // Upload Minidump (setup) 295 | arguments.push_back("-F"); 296 | arguments.push_back("upload_file_minidump=@" + String(dump_path)); 297 | arguments.push_back("-H"); 298 | arguments.push_back("Expect: gzip"); 299 | 300 | // Upload log file (optional) 301 | ProjectSettings* project_singleton = ProjectSettings::get_singleton(); 302 | Variant project_setting_logging_enabled = project_singleton->get_setting("logging/file_logging/enable_file_logging"); 303 | if (project_setting_logging_enabled.get_type() == project_setting_logging_enabled.BOOL && (bool)project_setting_logging_enabled == true) { 304 | Variant logging_filepath = project_singleton->get_setting("logging/file_logging/log_path"); 305 | String logging_filepath_string = (String)logging_filepath; 306 | String log_filepath = project_singleton->globalize_path(logging_filepath_string); 307 | 308 | // Upload log 309 | arguments.push_back("-F"); 310 | arguments.push_back("godot_log.log=@" + log_filepath + "; type=application/text"); 311 | arguments.push_back(Crashpad::crashpad_api_URL + Crashpad::crashpad_api_token + "/minidump"); 312 | } 313 | 314 | // Uploading the actual Minidump 315 | arguments.push_back(Crashpad::crashpad_api_URL + Crashpad::crashpad_api_token + "/minidump"); 316 | 317 | // Calling it on CURL 318 | OS::get_singleton()->execute("curl", arguments); 319 | 320 | // For debugging only: See what is being passed to CURL 321 | /* 322 | String output_test = "curl "; 323 | for (int i = 0; i < arguments.size(); i++) { 324 | output_test += " " + arguments[i]; 325 | } 326 | print_line(output_test); 327 | */ 328 | } 329 | 330 | bool Crashpad::check_for_crashpad_application() 331 | { 332 | // If the application path is set to an empty string, then set it so it's relative to the application 333 | if (Crashpad::crashpad_application_path.empty() == true) 334 | { 335 | Crashpad::crashpad_application_path = "res://crashpad_handler.exe"; 336 | } 337 | 338 | String actual_path = get_global_crashpad_application_path(); 339 | return FileAccess::exists(actual_path); 340 | } 341 | String Crashpad::get_global_crashpad_application_path() 342 | { 343 | String actual_path = ""; 344 | if (Crashpad::crashpad_use_manual_application_extension == true) 345 | { 346 | actual_path = get_global_path_from_local_path(Crashpad::crashpad_application_path + Crashpad::crashpad_manual_application_extension); 347 | } 348 | else 349 | { 350 | // Default to just the file name 351 | actual_path = get_global_path_from_local_path(Crashpad::crashpad_application_path); 352 | 353 | #ifdef WINDOWS_ENABLED 354 | // The default extension is .exe 355 | actual_path = get_global_path_from_local_path(Crashpad::crashpad_application_path + ".exe"); 356 | #endif 357 | 358 | #ifdef OSX_ENABLED 359 | // I believe it is just the name of the file, no extension needed 360 | actual_path = get_global_path_from_local_path(Crashpad::crashpad_application_path); 361 | #endif 362 | 363 | #ifdef X11_ENABLED 364 | // The default extension is nothing, just the file name 365 | actual_path = get_global_path_from_local_path(Crashpad::crashpad_application_path); 366 | #endif 367 | } 368 | return actual_path; 369 | } 370 | 371 | String Crashpad::get_global_path_from_local_path(String input_local_path) 372 | { 373 | String actual_path = input_local_path; 374 | 375 | // Convert res:// to the actual directory 376 | if (input_local_path.begins_with("res://")) 377 | { 378 | actual_path = OS::get_singleton()->get_executable_path().get_base_dir(); 379 | actual_path += input_local_path.replace("res://", "/"); 380 | } 381 | // Convert user:// to the actual directory 382 | else if (input_local_path.begins_with("user://")) 383 | { 384 | actual_path = ProjectSettings::get_singleton()->localize_path(input_local_path); 385 | } 386 | 387 | return actual_path; 388 | } 389 | 390 | bool Crashpad::check_for_crashpad_database(bool make_if_not_exist) 391 | { 392 | if (Crashpad::crashpad_database_path.empty() == true) 393 | { 394 | Crashpad::crashpad_database_path = "res://Crashpad/db/"; 395 | } 396 | 397 | String actual_path = get_global_path_from_local_path(Crashpad::crashpad_database_path); 398 | 399 | if (DirAccess::exists(actual_path) == false) 400 | { 401 | if (make_if_not_exist == true) 402 | { 403 | DirAccess *d = DirAccess::create_for_path(actual_path); 404 | Error err = d->make_dir_recursive(actual_path); 405 | memdelete(d); 406 | return true; 407 | } 408 | else 409 | { 410 | return false; 411 | } 412 | } 413 | return true; 414 | } 415 | 416 | void Crashpad::_bind_methods() { 417 | ClassDB::bind_method(D_METHOD("start_crashpad"), &Crashpad::start_crashpad); 418 | ClassDB::bind_method(D_METHOD("force_crash"), &Crashpad::force_crash); 419 | 420 | // Crashpad setup variables 421 | // ===== 422 | ClassDB::bind_method(D_METHOD("set_api_url", "api_url"), &Crashpad::set_crashpad_api_url); 423 | ClassDB::bind_method(D_METHOD("get_api_url"), &Crashpad::get_crashpad_api_url); 424 | ADD_PROPERTY(PropertyInfo(Variant::STRING, "crashpad_settings/api_url", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT_INTL), "set_api_url", "get_api_url"); 425 | ClassDB::bind_method(D_METHOD("set_api_token", "api_token"), &Crashpad::set_crashpad_api_token); 426 | ClassDB::bind_method(D_METHOD("get_api_token"), &Crashpad::get_crashpad_api_token); 427 | ADD_PROPERTY(PropertyInfo(Variant::STRING, "crashpad_settings/crashpad_api_token", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT_INTL), "set_api_token", "get_api_token"); 428 | ClassDB::bind_method(D_METHOD("set_application_path", "new_path"), &Crashpad::set_crashpad_application_path); 429 | ClassDB::bind_method(D_METHOD("get_application_path"), &Crashpad::get_crashpad_application_path); 430 | ADD_PROPERTY(PropertyInfo(Variant::STRING, "crashpad_settings/application_path", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT_INTL), "set_application_path", "get_application_path"); 431 | ClassDB::bind_method(D_METHOD("set_database_path", "new_path"), &Crashpad::set_crashpad_database_path); 432 | ClassDB::bind_method(D_METHOD("get_database_path"), &Crashpad::get_crashpad_database_path); 433 | ADD_PROPERTY(PropertyInfo(Variant::STRING, "crashpad_settings/database_path", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT_INTL), "set_database_path", "get_database_path"); 434 | 435 | ClassDB::bind_method(D_METHOD("set_use_manual_application_extension", "use_manual_extension"), &Crashpad::set_crashpad_use_manual_application_extension); 436 | ClassDB::bind_method(D_METHOD("get_use_manual_application_extension"), &Crashpad::get_crashpad_use_manual_application_extension); 437 | ADD_PROPERTY(PropertyInfo(Variant::BOOL, "crashpad_settings/use_manual_application_extension", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT_INTL), "set_use_manual_application_extension", "get_use_manual_application_extension"); 438 | ClassDB::bind_method(D_METHOD("set_manual_application_extension", "manual_extension"), &Crashpad::set_crashpad_manual_application_extension); 439 | ClassDB::bind_method(D_METHOD("get_manual_application_extension"), &Crashpad::get_crashpad_manual_application_extension); 440 | ADD_PROPERTY(PropertyInfo(Variant::STRING, "crashpad_settings/manual_application_extension", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT_INTL), "set_manual_application_extension", "get_manual_application_extension"); 441 | // ===== 442 | 443 | // User custom data 444 | // ===== 445 | ClassDB::bind_method(D_METHOD("set_crash_attributes", "crash_attributes"), &Crashpad::set_crashpad_user_crash_attributes); 446 | ClassDB::bind_method(D_METHOD("get_crash_attributes"), &Crashpad::get_crashpad_user_crash_attributes); 447 | ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "custom_data/user_crash_attributes", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT_INTL), "set_crash_attributes", "get_crash_attributes"); 448 | 449 | ClassDB::bind_method(D_METHOD("set_upload_godot_log", "upload_godot_log"), &Crashpad::set_crashpad_upload_godot_log); 450 | ClassDB::bind_method(D_METHOD("get_upload_godot_log"), &Crashpad::get_crashpad_upload_godot_log); 451 | ADD_PROPERTY(PropertyInfo(Variant::BOOL, "custom_data/upload_godot_log", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT_INTL), "set_upload_godot_log", "get_upload_godot_log"); 452 | // ===== 453 | 454 | ClassDB::bind_method(D_METHOD("set_skip_error_upload", "skip_error_upload"), &Crashpad::set_crashpad_skip_error_upload); 455 | ClassDB::bind_method(D_METHOD("get_skip_error_upload"), &Crashpad::get_crashpad_skip_error_upload); 456 | ADD_PROPERTY(PropertyInfo(Variant::BOOL, "skip_error_upload", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT_INTL), "set_skip_error_upload", "get_skip_error_upload"); 457 | } 458 | 459 | void Crashpad::set_crashpad_api_url(String new_url) 460 | { 461 | Crashpad::crashpad_api_URL = new_url; 462 | } 463 | String Crashpad::get_crashpad_api_url() 464 | { 465 | return Crashpad::crashpad_api_URL; 466 | } 467 | void Crashpad::set_crashpad_api_token(String new_token) 468 | { 469 | Crashpad::crashpad_api_token = new_token; 470 | } 471 | String Crashpad::get_crashpad_api_token() 472 | { 473 | return Crashpad::crashpad_api_token; 474 | } 475 | void Crashpad::set_crashpad_application_path(String new_path) 476 | { 477 | Crashpad::crashpad_application_path = new_path; 478 | } 479 | String Crashpad::get_crashpad_application_path() 480 | { 481 | return Crashpad::crashpad_application_path; 482 | } 483 | void Crashpad::set_crashpad_database_path(String new_path) 484 | { 485 | Crashpad::crashpad_database_path = new_path; 486 | } 487 | String Crashpad::get_crashpad_database_path() 488 | { 489 | return Crashpad::crashpad_database_path; 490 | } 491 | 492 | void Crashpad::set_crashpad_skip_error_upload(bool new_value) 493 | { 494 | Crashpad::crashpad_skip_error_upload = new_value; 495 | } 496 | bool Crashpad::get_crashpad_skip_error_upload() 497 | { 498 | return Crashpad::crashpad_skip_error_upload; 499 | } 500 | 501 | void Crashpad::set_crashpad_user_crash_attributes(Dictionary new_value) 502 | { 503 | Crashpad::crashpad_user_crash_attributes = new_value; 504 | } 505 | Dictionary Crashpad::get_crashpad_user_crash_attributes() 506 | { 507 | return Crashpad::crashpad_user_crash_attributes; 508 | } 509 | 510 | void Crashpad::set_crashpad_upload_godot_log(bool new_value) { 511 | Crashpad::crashpad_upload_godot_log = new_value; 512 | } 513 | bool Crashpad::get_crashpad_upload_godot_log() { 514 | return Crashpad::crashpad_upload_godot_log; 515 | } 516 | 517 | void Crashpad::set_crashpad_use_manual_application_extension(bool new_value) { 518 | Crashpad::crashpad_use_manual_application_extension = true; 519 | } 520 | bool Crashpad::get_crashpad_use_manual_application_extension() { 521 | return Crashpad::crashpad_use_manual_application_extension; 522 | } 523 | void Crashpad::set_crashpad_manual_application_extension(String new_value) { 524 | Crashpad::crashpad_manual_application_extension = new_value; 525 | } 526 | String Crashpad::get_crashpad_manual_application_extension() { 527 | return Crashpad::crashpad_manual_application_extension; 528 | } 529 | 530 | void Crashpad::force_crash() 531 | { 532 | volatile int* a = (int*)(NULL); *a = 1; 533 | } 534 | 535 | 536 | Crashpad::Crashpad() 537 | { 538 | Crashpad::crashpad_api_URL = get("crashpad_settings/api_url"); 539 | Crashpad::crashpad_api_token = get("crashpad_settings/api_token"); 540 | Crashpad::crashpad_application_path = get("crashpad_settings/application_path"); 541 | 542 | Crashpad::crashpad_user_crash_attributes = get("custom_data/user_crash_attributes"); 543 | Crashpad::crashpad_upload_godot_log = get("custom_data/upload_godot_log"); 544 | 545 | Crashpad::crashpad_skip_error_upload = get("skip_error_upload"); 546 | 547 | Crashpad::crashpad_use_manual_application_extension = get("crashpad_settings/use_manual_application_extension"); 548 | Crashpad::crashpad_manual_application_extension = get("crashpad_settings/manual_application_extension"); 549 | } 550 | 551 | Crashpad::~Crashpad() 552 | { 553 | 554 | } 555 | --------------------------------------------------------------------------------