mirror of
				https://git.suyu.dev/suyu/suyu.git
				synced 2025-10-31 23:06:43 +08:00 
			
		
		
		
	yuzu: Use a debugger to generate minidumps
yuzu: Move mini_dump out of core startup_checks: Better exception handling
This commit is contained in:
		
							parent
							
								
									3faa1c54b6
								
							
						
					
					
						commit
						f958cbc737
					
				| @ -529,6 +529,7 @@ struct Values { | ||||
|     Setting<bool> use_debug_asserts{false, "use_debug_asserts"}; | ||||
|     Setting<bool> use_auto_stub{false, "use_auto_stub"}; | ||||
|     Setting<bool> enable_all_controllers{false, "enable_all_controllers"}; | ||||
|     Setting<bool> create_crash_dumps{false, "create_crash_dumps"}; | ||||
| 
 | ||||
|     // Miscellaneous
 | ||||
|     Setting<std::string> log_filter{"*:Info", "log_filter"}; | ||||
|  | ||||
| @ -208,6 +208,16 @@ add_executable(yuzu | ||||
|     yuzu.rc | ||||
| ) | ||||
| 
 | ||||
| if (WIN32 AND NOT ("${DBGHELP_LIBRARY}" STREQUAL "DBGHELP_LIBRARY-NOTFOUND")) | ||||
|     target_sources(yuzu PRIVATE | ||||
|         mini_dump.cpp | ||||
|         mini_dump.h | ||||
|     ) | ||||
| 
 | ||||
|     target_link_libraries(yuzu PUBLIC ${DBGHELP_LIBRARY}) | ||||
|     target_compile_definitions(yuzu PRIVATE -DYUZU_DBGHELP) | ||||
| endif() | ||||
| 
 | ||||
| file(GLOB COMPAT_LIST | ||||
|      ${PROJECT_BINARY_DIR}/dist/compatibility_list/compatibility_list.qrc | ||||
|      ${PROJECT_BINARY_DIR}/dist/compatibility_list/compatibility_list.json) | ||||
|  | ||||
| @ -63,7 +63,7 @@ QtControllerSelectorDialog::QtControllerSelectorDialog( | ||||
|     InputCommon::InputSubsystem* input_subsystem_, Core::System& system_) | ||||
|     : QDialog(parent), ui(std::make_unique<Ui::QtControllerSelectorDialog>()), | ||||
|       parameters(std::move(parameters_)), input_subsystem{input_subsystem_}, | ||||
|       input_profiles(std::make_unique<InputProfiles>(system_)), system{system_} { | ||||
|       input_profiles(std::make_unique<InputProfiles>()), system{system_} { | ||||
|     ui->setupUi(this); | ||||
| 
 | ||||
|     player_widgets = { | ||||
|  | ||||
| @ -15,8 +15,7 @@ | ||||
| 
 | ||||
| namespace FS = Common::FS; | ||||
| 
 | ||||
| Config::Config(Core::System& system_, const std::string& config_name, ConfigType config_type) | ||||
|     : type(config_type), system{system_} { | ||||
| Config::Config(const std::string& config_name, ConfigType config_type) : type(config_type) { | ||||
|     global = config_type == ConfigType::GlobalConfig; | ||||
| 
 | ||||
|     Initialize(config_name); | ||||
| @ -546,6 +545,7 @@ void Config::ReadDebuggingValues() { | ||||
|     ReadBasicSetting(Settings::values.use_debug_asserts); | ||||
|     ReadBasicSetting(Settings::values.use_auto_stub); | ||||
|     ReadBasicSetting(Settings::values.enable_all_controllers); | ||||
|     ReadBasicSetting(Settings::values.create_crash_dumps); | ||||
| 
 | ||||
|     qt_config->endGroup(); | ||||
| } | ||||
| @ -1160,6 +1160,7 @@ void Config::SaveDebuggingValues() { | ||||
|     WriteBasicSetting(Settings::values.use_debug_asserts); | ||||
|     WriteBasicSetting(Settings::values.disable_macro_jit); | ||||
|     WriteBasicSetting(Settings::values.enable_all_controllers); | ||||
|     WriteBasicSetting(Settings::values.create_crash_dumps); | ||||
| 
 | ||||
|     qt_config->endGroup(); | ||||
| } | ||||
| @ -1545,7 +1546,6 @@ void Config::Reload() { | ||||
|     ReadValues(); | ||||
|     // To apply default value changes
 | ||||
|     SaveValues(); | ||||
|     system.ApplySettings(); | ||||
| } | ||||
| 
 | ||||
| void Config::Save() { | ||||
|  | ||||
| @ -25,7 +25,7 @@ public: | ||||
|         InputProfile, | ||||
|     }; | ||||
| 
 | ||||
|     explicit Config(Core::System& system_, const std::string& config_name = "qt-config", | ||||
|     explicit Config(const std::string& config_name = "qt-config", | ||||
|                     ConfigType config_type = ConfigType::GlobalConfig); | ||||
|     ~Config(); | ||||
| 
 | ||||
| @ -194,8 +194,6 @@ private: | ||||
|     std::unique_ptr<QSettings> qt_config; | ||||
|     std::string qt_config_loc; | ||||
|     bool global; | ||||
| 
 | ||||
|     Core::System& system; | ||||
| }; | ||||
| 
 | ||||
| // These metatype declarations cannot be in common/settings.h because core is devoid of QT
 | ||||
|  | ||||
| @ -2,6 +2,7 @@ | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later
 | ||||
| 
 | ||||
| #include <QDesktopServices> | ||||
| #include <QMessageBox> | ||||
| #include <QUrl> | ||||
| #include "common/fs/path_util.h" | ||||
| #include "common/logging/backend.h" | ||||
| @ -26,6 +27,16 @@ ConfigureDebug::ConfigureDebug(const Core::System& system_, QWidget* parent) | ||||
| 
 | ||||
|     connect(ui->toggle_gdbstub, &QCheckBox::toggled, | ||||
|             [&]() { ui->gdbport_spinbox->setEnabled(ui->toggle_gdbstub->isChecked()); }); | ||||
| 
 | ||||
|     connect(ui->create_crash_dumps, &QCheckBox::stateChanged, [&](int) { | ||||
|         if (crash_dump_warning_shown) { | ||||
|             return; | ||||
|         } | ||||
|         QMessageBox::warning(this, tr("Restart Required"), | ||||
|                              tr("yuzu is required to restart in order to apply this setting."), | ||||
|                              QMessageBox::Ok, QMessageBox::Ok); | ||||
|         crash_dump_warning_shown = true; | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| ConfigureDebug::~ConfigureDebug() = default; | ||||
| @ -71,7 +82,14 @@ void ConfigureDebug::SetConfiguration() { | ||||
|     ui->disable_web_applet->setChecked(UISettings::values.disable_web_applet.GetValue()); | ||||
| #else | ||||
|     ui->disable_web_applet->setEnabled(false); | ||||
|     ui->disable_web_applet->setText(QString::fromUtf8("Web applet not compiled")); | ||||
|     ui->disable_web_applet->setText(tr("Web applet not compiled")); | ||||
| #endif | ||||
| 
 | ||||
| #ifdef YUZU_DBGHELP | ||||
|     ui->create_crash_dumps->setChecked(Settings::values.create_crash_dumps.GetValue()); | ||||
| #else | ||||
|     ui->create_crash_dumps->setEnabled(false); | ||||
|     ui->create_crash_dumps->setText(tr("MiniDump creation not compiled")); | ||||
| #endif | ||||
| } | ||||
| 
 | ||||
| @ -84,6 +102,7 @@ void ConfigureDebug::ApplyConfiguration() { | ||||
|     Settings::values.enable_fs_access_log = ui->fs_access_log->isChecked(); | ||||
|     Settings::values.reporting_services = ui->reporting_services->isChecked(); | ||||
|     Settings::values.dump_audio_commands = ui->dump_audio_commands->isChecked(); | ||||
|     Settings::values.create_crash_dumps = ui->create_crash_dumps->isChecked(); | ||||
|     Settings::values.quest_flag = ui->quest_flag->isChecked(); | ||||
|     Settings::values.use_debug_asserts = ui->use_debug_asserts->isChecked(); | ||||
|     Settings::values.use_auto_stub = ui->use_auto_stub->isChecked(); | ||||
|  | ||||
| @ -32,4 +32,6 @@ private: | ||||
|     std::unique_ptr<Ui::ConfigureDebug> ui; | ||||
| 
 | ||||
|     const Core::System& system; | ||||
| 
 | ||||
|     bool crash_dump_warning_shown{false}; | ||||
| }; | ||||
|  | ||||
| @ -7,60 +7,60 @@ | ||||
|   </property> | ||||
|  <widget class="QWidget"> | ||||
|   <layout class="QVBoxLayout" name="verticalLayout_1"> | ||||
|     <item> | ||||
|       <layout class="QVBoxLayout" name="verticalLayout_2"> | ||||
|        <item> | ||||
|         <widget class="QGroupBox" name="groupBox"> | ||||
|          <property name="title"> | ||||
|           <string>Debugger</string> | ||||
|          </property> | ||||
|          <layout class="QVBoxLayout" name="verticalLayout_3"> | ||||
|    <item> | ||||
|     <layout class="QVBoxLayout" name="verticalLayout_2"> | ||||
|      <item> | ||||
|       <widget class="QGroupBox" name="groupBox"> | ||||
|        <property name="title"> | ||||
|         <string>Debugger</string> | ||||
|        </property> | ||||
|        <layout class="QVBoxLayout" name="verticalLayout_3"> | ||||
|         <item> | ||||
|          <layout class="QHBoxLayout" name="horizontalLayout_11"> | ||||
|           <item> | ||||
|            <layout class="QHBoxLayout" name="horizontalLayout_11"> | ||||
|             <item> | ||||
|              <widget class="QCheckBox" name="toggle_gdbstub"> | ||||
|               <property name="text"> | ||||
|                <string>Enable GDB Stub</string> | ||||
|               </property> | ||||
|              </widget> | ||||
|             </item> | ||||
|             <item> | ||||
|              <spacer name="horizontalSpacer"> | ||||
|               <property name="orientation"> | ||||
|                <enum>Qt::Horizontal</enum> | ||||
|               </property> | ||||
|               <property name="sizeHint" stdset="0"> | ||||
|                <size> | ||||
|                 <width>40</width> | ||||
|                 <height>20</height> | ||||
|                </size> | ||||
|               </property> | ||||
|              </spacer> | ||||
|             </item> | ||||
|             <item> | ||||
|              <widget class="QLabel" name="label_11"> | ||||
|               <property name="text"> | ||||
|                <string>Port:</string> | ||||
|               </property> | ||||
|              </widget> | ||||
|             </item> | ||||
|             <item> | ||||
|              <widget class="QSpinBox" name="gdbport_spinbox"> | ||||
|               <property name="minimum"> | ||||
|                 <number>1024</number> | ||||
|               </property> | ||||
|               <property name="maximum"> | ||||
|                <number>65535</number> | ||||
|               </property> | ||||
|              </widget> | ||||
|             </item> | ||||
|            </layout> | ||||
|            <widget class="QCheckBox" name="toggle_gdbstub"> | ||||
|             <property name="text"> | ||||
|              <string>Enable GDB Stub</string> | ||||
|             </property> | ||||
|            </widget> | ||||
|           </item> | ||||
|           <item> | ||||
|            <spacer name="horizontalSpacer"> | ||||
|             <property name="orientation"> | ||||
|              <enum>Qt::Horizontal</enum> | ||||
|             </property> | ||||
|             <property name="sizeHint" stdset="0"> | ||||
|              <size> | ||||
|               <width>40</width> | ||||
|               <height>20</height> | ||||
|              </size> | ||||
|             </property> | ||||
|            </spacer> | ||||
|           </item> | ||||
|           <item> | ||||
|            <widget class="QLabel" name="label_11"> | ||||
|             <property name="text"> | ||||
|              <string>Port:</string> | ||||
|             </property> | ||||
|            </widget> | ||||
|           </item> | ||||
|           <item> | ||||
|            <widget class="QSpinBox" name="gdbport_spinbox"> | ||||
|             <property name="minimum"> | ||||
|              <number>1024</number> | ||||
|             </property> | ||||
|             <property name="maximum"> | ||||
|              <number>65535</number> | ||||
|             </property> | ||||
|            </widget> | ||||
|           </item> | ||||
|          </layout> | ||||
|         </widget> | ||||
|        </item> | ||||
|       </layout> | ||||
|         </item> | ||||
|        </layout> | ||||
|       </widget> | ||||
|      </item> | ||||
|     </layout> | ||||
|    </item> | ||||
|    <item> | ||||
|     <widget class="QGroupBox" name="groupBox_2"> | ||||
|      <property name="title"> | ||||
| @ -231,6 +231,13 @@ | ||||
|       <string>Debugging</string> | ||||
|      </property> | ||||
|      <layout class="QGridLayout" name="gridLayout_3"> | ||||
|       <item row="2" column="0"> | ||||
|        <widget class="QCheckBox" name="reporting_services"> | ||||
|         <property name="text"> | ||||
|          <string>Enable Verbose Reporting Services**</string> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|       <item row="0" column="0"> | ||||
|        <widget class="QCheckBox" name="fs_access_log"> | ||||
|         <property name="text"> | ||||
| @ -238,20 +245,20 @@ | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|       <item row="1" column="0"> | ||||
|       <item row="0" column="1"> | ||||
|        <widget class="QCheckBox" name="dump_audio_commands"> | ||||
|         <property name="text"> | ||||
|          <string>Dump Audio Commands To Console**</string> | ||||
|         </property> | ||||
|         <property name="toolTip"> | ||||
|          <string>Enable this to output the latest generated audio command list to the console. Only affects games using the audio renderer.</string> | ||||
|         </property> | ||||
|         <property name="text"> | ||||
|          <string>Dump Audio Commands To Console**</string> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|       <item row="2" column="0"> | ||||
|        <widget class="QCheckBox" name="reporting_services"> | ||||
|       <item row="2" column="1"> | ||||
|        <widget class="QCheckBox" name="create_crash_dumps"> | ||||
|         <property name="text"> | ||||
|          <string>Enable Verbose Reporting Services**</string> | ||||
|          <string>Create Minidump After Crash</string> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
| @ -340,7 +347,6 @@ | ||||
|   <tabstop>disable_loop_safety_checks</tabstop> | ||||
|   <tabstop>fs_access_log</tabstop> | ||||
|   <tabstop>reporting_services</tabstop> | ||||
|   <tabstop>dump_audio_commands</tabstop> | ||||
|   <tabstop>quest_flag</tabstop> | ||||
|   <tabstop>enable_cpu_debugging</tabstop> | ||||
|   <tabstop>use_debug_asserts</tabstop> | ||||
|  | ||||
| @ -65,7 +65,7 @@ void OnDockedModeChanged(bool last_state, bool new_state, Core::System& system) | ||||
| 
 | ||||
| ConfigureInput::ConfigureInput(Core::System& system_, QWidget* parent) | ||||
|     : QWidget(parent), ui(std::make_unique<Ui::ConfigureInput>()), | ||||
|       profiles(std::make_unique<InputProfiles>(system_)), system{system_} { | ||||
|       profiles(std::make_unique<InputProfiles>()), system{system_} { | ||||
|     ui->setupUi(this); | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -42,8 +42,7 @@ ConfigurePerGame::ConfigurePerGame(QWidget* parent, u64 title_id_, const std::st | ||||
|     const auto file_path = std::filesystem::path(Common::FS::ToU8String(file_name)); | ||||
|     const auto config_file_name = title_id == 0 ? Common::FS::PathToUTF8String(file_path.filename()) | ||||
|                                                 : fmt::format("{:016X}", title_id); | ||||
|     game_config = | ||||
|         std::make_unique<Config>(system, config_file_name, Config::ConfigType::PerGameConfig); | ||||
|     game_config = std::make_unique<Config>(config_file_name, Config::ConfigType::PerGameConfig); | ||||
| 
 | ||||
|     addons_tab = std::make_unique<ConfigurePerGameAddons>(system_, this); | ||||
|     audio_tab = std::make_unique<ConfigureAudio>(system_, this); | ||||
|  | ||||
| @ -27,7 +27,7 @@ std::filesystem::path GetNameWithoutExtension(std::filesystem::path filename) { | ||||
| 
 | ||||
| } // namespace
 | ||||
| 
 | ||||
| InputProfiles::InputProfiles(Core::System& system_) : system{system_} { | ||||
| InputProfiles::InputProfiles() { | ||||
|     const auto input_profile_loc = FS::GetYuzuPath(FS::YuzuPath::ConfigDir) / "input"; | ||||
| 
 | ||||
|     if (!FS::IsDir(input_profile_loc)) { | ||||
| @ -43,8 +43,8 @@ InputProfiles::InputProfiles(Core::System& system_) : system{system_} { | ||||
| 
 | ||||
|             if (IsINI(filename) && IsProfileNameValid(name_without_ext)) { | ||||
|                 map_profiles.insert_or_assign( | ||||
|                     name_without_ext, std::make_unique<Config>(system, name_without_ext, | ||||
|                                                                Config::ConfigType::InputProfile)); | ||||
|                     name_without_ext, | ||||
|                     std::make_unique<Config>(name_without_ext, Config::ConfigType::InputProfile)); | ||||
|             } | ||||
| 
 | ||||
|             return true; | ||||
| @ -80,8 +80,7 @@ bool InputProfiles::CreateProfile(const std::string& profile_name, std::size_t p | ||||
|     } | ||||
| 
 | ||||
|     map_profiles.insert_or_assign( | ||||
|         profile_name, | ||||
|         std::make_unique<Config>(system, profile_name, Config::ConfigType::InputProfile)); | ||||
|         profile_name, std::make_unique<Config>(profile_name, Config::ConfigType::InputProfile)); | ||||
| 
 | ||||
|     return SaveProfile(profile_name, player_index); | ||||
| } | ||||
|  | ||||
| @ -15,7 +15,7 @@ class Config; | ||||
| class InputProfiles { | ||||
| 
 | ||||
| public: | ||||
|     explicit InputProfiles(Core::System& system_); | ||||
|     explicit InputProfiles(); | ||||
|     virtual ~InputProfiles(); | ||||
| 
 | ||||
|     std::vector<std::string> GetInputProfileNames(); | ||||
| @ -31,6 +31,4 @@ private: | ||||
|     bool ProfileExistsInMap(const std::string& profile_name) const; | ||||
| 
 | ||||
|     std::unordered_map<std::string, std::unique_ptr<Config>> map_profiles; | ||||
| 
 | ||||
|     Core::System& system; | ||||
| }; | ||||
|  | ||||
| @ -138,6 +138,10 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual | ||||
| #include "yuzu/uisettings.h" | ||||
| #include "yuzu/util/clickable_label.h" | ||||
| 
 | ||||
| #ifdef YUZU_DBGHELP | ||||
| #include "yuzu/mini_dump.h" | ||||
| #endif | ||||
| 
 | ||||
| using namespace Common::Literals; | ||||
| 
 | ||||
| #ifdef USE_DISCORD_PRESENCE | ||||
| @ -269,10 +273,9 @@ bool GMainWindow::CheckDarkMode() { | ||||
| #endif // __linux__
 | ||||
| } | ||||
| 
 | ||||
| GMainWindow::GMainWindow(bool has_broken_vulkan) | ||||
| GMainWindow::GMainWindow(std::unique_ptr<Config> config_, bool has_broken_vulkan) | ||||
|     : ui{std::make_unique<Ui::MainWindow>()}, system{std::make_unique<Core::System>()}, | ||||
|       input_subsystem{std::make_shared<InputCommon::InputSubsystem>()}, | ||||
|       config{std::make_unique<Config>(*system)}, | ||||
|       input_subsystem{std::make_shared<InputCommon::InputSubsystem>()}, config{std::move(config_)}, | ||||
|       vfs{std::make_shared<FileSys::RealVfsFilesystem>()}, | ||||
|       provider{std::make_unique<FileSys::ManualContentProvider>()} { | ||||
| #ifdef __linux__ | ||||
| @ -1637,7 +1640,8 @@ void GMainWindow::BootGame(const QString& filename, u64 program_id, std::size_t | ||||
|         const auto config_file_name = title_id == 0 | ||||
|                                           ? Common::FS::PathToUTF8String(file_path.filename()) | ||||
|                                           : fmt::format("{:016X}", title_id); | ||||
|         Config per_game_config(*system, config_file_name, Config::ConfigType::PerGameConfig); | ||||
|         Config per_game_config(config_file_name, Config::ConfigType::PerGameConfig); | ||||
|         system->ApplySettings(); | ||||
|     } | ||||
| 
 | ||||
|     // Save configurations
 | ||||
| @ -2981,7 +2985,7 @@ void GMainWindow::OnConfigure() { | ||||
| 
 | ||||
|         Settings::values.disabled_addons.clear(); | ||||
| 
 | ||||
|         config = std::make_unique<Config>(*system); | ||||
|         config = std::make_unique<Config>(); | ||||
|         UISettings::values.reset_to_defaults = false; | ||||
| 
 | ||||
|         UISettings::values.game_dirs = std::move(old_game_dirs); | ||||
| @ -3042,6 +3046,7 @@ void GMainWindow::OnConfigure() { | ||||
| 
 | ||||
|     UpdateStatusButtons(); | ||||
|     controller_dialog->refreshConfiguration(); | ||||
|     system->ApplySettings(); | ||||
| } | ||||
| 
 | ||||
| void GMainWindow::OnConfigureTas() { | ||||
| @ -4082,7 +4087,22 @@ void GMainWindow::changeEvent(QEvent* event) { | ||||
| #endif | ||||
| 
 | ||||
| int main(int argc, char* argv[]) { | ||||
|     std::unique_ptr<Config> config = std::make_unique<Config>(); | ||||
|     bool has_broken_vulkan = false; | ||||
|     bool is_child = false; | ||||
|     if (CheckEnvVars(&is_child)) { | ||||
|         return 0; | ||||
|     } | ||||
| 
 | ||||
| #ifdef YUZU_DBGHELP | ||||
|     PROCESS_INFORMATION pi; | ||||
|     if (!is_child && Settings::values.create_crash_dumps.GetValue() && SpawnDebuggee(argv[0], pi)) { | ||||
|         config.reset(nullptr); | ||||
|         DebugDebuggee(pi); | ||||
|         return 0; | ||||
|     } | ||||
| #endif | ||||
| 
 | ||||
|     if (StartupChecks(argv[0], &has_broken_vulkan)) { | ||||
|         return 0; | ||||
|     } | ||||
| @ -4135,7 +4155,7 @@ int main(int argc, char* argv[]) { | ||||
|     // generating shaders
 | ||||
|     setlocale(LC_ALL, "C"); | ||||
| 
 | ||||
|     GMainWindow main_window{has_broken_vulkan}; | ||||
|     GMainWindow main_window{std::move(config), has_broken_vulkan}; | ||||
|     // After settings have been loaded by GMainWindow, apply the filter
 | ||||
|     main_window.show(); | ||||
| 
 | ||||
|  | ||||
| @ -120,7 +120,7 @@ class GMainWindow : public QMainWindow { | ||||
| public: | ||||
|     void filterBarSetChecked(bool state); | ||||
|     void UpdateUITheme(); | ||||
|     explicit GMainWindow(bool has_broken_vulkan); | ||||
|     explicit GMainWindow(std::unique_ptr<Config> config_, bool has_broken_vulkan); | ||||
|     ~GMainWindow() override; | ||||
| 
 | ||||
|     bool DropAction(QDropEvent* event); | ||||
|  | ||||
							
								
								
									
										185
									
								
								src/yuzu/mini_dump.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										185
									
								
								src/yuzu/mini_dump.cpp
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,185 @@ | ||||
| #include <cstdio> | ||||
| #include <ctime> | ||||
| #include <filesystem> | ||||
| #include <windows.h> | ||||
| #include "common/logging/log.h" | ||||
| #include "yuzu/mini_dump.h" | ||||
| #include "yuzu/startup_checks.h" | ||||
| 
 | ||||
| // dbghelp.h must be included after windows.h
 | ||||
| #include <dbghelp.h> | ||||
| 
 | ||||
| void CreateMiniDump(HANDLE process_handle, DWORD process_id, MINIDUMP_EXCEPTION_INFORMATION* info, | ||||
|                     EXCEPTION_POINTERS* pep) { | ||||
|     LOG_INFO(Core, "called"); | ||||
| 
 | ||||
|     char file_name[255]; | ||||
|     const std::time_t the_time = std::time(nullptr); | ||||
|     std::strftime(file_name, 255, "yuzu-crash-%Y%m%d%H%M%S.dmp", std::localtime(&the_time)); | ||||
| 
 | ||||
|     // Open the file
 | ||||
|     HANDLE file_handle = CreateFile(file_name, GENERIC_READ | GENERIC_WRITE, 0, nullptr, | ||||
|                                     CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); | ||||
| 
 | ||||
|     if ((file_handle != nullptr) && (file_handle != INVALID_HANDLE_VALUE)) { | ||||
|         // Create the minidump
 | ||||
|         const MINIDUMP_TYPE dump_type = MiniDumpNormal; | ||||
| 
 | ||||
|         const bool write_dump_status = MiniDumpWriteDump(process_handle, process_id, file_handle, | ||||
|                                                          dump_type, (pep != 0) ? info : 0, 0, 0); | ||||
| 
 | ||||
|         if (!write_dump_status) { | ||||
|             LOG_ERROR(Core, "MiniDumpWriteDump failed. Error: {}", GetLastError()); | ||||
|         } else { | ||||
|             LOG_INFO(Core, "Minidump created."); | ||||
|         } | ||||
| 
 | ||||
|         // Close the file
 | ||||
|         CloseHandle(file_handle); | ||||
| 
 | ||||
|     } else { | ||||
|         LOG_ERROR(Core, "CreateFile failed. Error: {}", GetLastError()); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| bool SpawnDebuggee(const char* arg0, PROCESS_INFORMATION& pi) { | ||||
|     std::memset(&pi, 0, sizeof(pi)); | ||||
| 
 | ||||
|     if (!SpawnChild(arg0, &pi, 0)) { | ||||
|         std::fprintf(stderr, "warning: continuing without crash dumps\n"); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     // Don't debug if we are already being debugged
 | ||||
|     if (IsDebuggerPresent()) { | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     const bool can_debug = DebugActiveProcess(pi.dwProcessId); | ||||
|     if (!can_debug) { | ||||
|         std::fprintf(stderr, | ||||
|                      "warning: DebugActiveProcess failed (%d), continuing without crash dumps\n", | ||||
|                      GetLastError()); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| void DebugDebuggee(PROCESS_INFORMATION& pi) { | ||||
|     DEBUG_EVENT deb_ev; | ||||
| 
 | ||||
|     while (deb_ev.dwDebugEventCode != EXIT_PROCESS_DEBUG_EVENT) { | ||||
|         const bool wait_success = WaitForDebugEvent(&deb_ev, INFINITE); | ||||
|         if (!wait_success) { | ||||
|             std::fprintf(stderr, "error: WaitForDebugEvent failed (%d)\n", GetLastError()); | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         switch (deb_ev.dwDebugEventCode) { | ||||
|         case OUTPUT_DEBUG_STRING_EVENT: | ||||
|         case CREATE_PROCESS_DEBUG_EVENT: | ||||
|         case CREATE_THREAD_DEBUG_EVENT: | ||||
|         case EXIT_PROCESS_DEBUG_EVENT: | ||||
|         case EXIT_THREAD_DEBUG_EVENT: | ||||
|         case LOAD_DLL_DEBUG_EVENT: | ||||
|         case RIP_EVENT: | ||||
|         case UNLOAD_DLL_DEBUG_EVENT: | ||||
|             ContinueDebugEvent(deb_ev.dwProcessId, deb_ev.dwThreadId, DBG_CONTINUE); | ||||
|             break; | ||||
|         case EXCEPTION_DEBUG_EVENT: | ||||
|             EXCEPTION_RECORD& record = deb_ev.u.Exception.ExceptionRecord; | ||||
| 
 | ||||
|             std::fprintf(stderr, "ExceptionCode: 0x%08x %s\n", record.ExceptionCode, | ||||
|                          ExceptionName(record.ExceptionCode)); | ||||
|             if (!deb_ev.u.Exception.dwFirstChance) { | ||||
|                 HANDLE thread_handle = OpenThread(THREAD_ALL_ACCESS, false, deb_ev.dwThreadId); | ||||
|                 if (thread_handle == nullptr) { | ||||
|                     std::fprintf(stderr, "OpenThread failed (%d)\n", GetLastError()); | ||||
|                 } | ||||
|                 if (SuspendThread(thread_handle) == (DWORD)-1) { | ||||
|                     std::fprintf(stderr, "SuspendThread failed (%d)\n", GetLastError()); | ||||
|                 } | ||||
| 
 | ||||
|                 CONTEXT context; | ||||
|                 std::memset(&context, 0, sizeof(context)); | ||||
|                 context.ContextFlags = CONTEXT_ALL; | ||||
|                 if (!GetThreadContext(thread_handle, &context)) { | ||||
|                     std::fprintf(stderr, "GetThreadContext failed (%d)\n", GetLastError()); | ||||
|                     break; | ||||
|                 } | ||||
| 
 | ||||
|                 EXCEPTION_POINTERS ep; | ||||
|                 ep.ExceptionRecord = &record; | ||||
|                 ep.ContextRecord = &context; | ||||
| 
 | ||||
|                 MINIDUMP_EXCEPTION_INFORMATION info; | ||||
|                 info.ThreadId = deb_ev.dwThreadId; | ||||
|                 info.ExceptionPointers = &ep; | ||||
|                 info.ClientPointers = false; | ||||
| 
 | ||||
|                 CreateMiniDump(pi.hProcess, pi.dwProcessId, &info, &ep); | ||||
| 
 | ||||
|                 std::fprintf(stderr, "previous thread suspend count: %d\n", | ||||
|                              ResumeThread(thread_handle)); | ||||
|                 if (CloseHandle(thread_handle) == 0) { | ||||
|                     std::fprintf(stderr, "error: CloseHandle(thread_handle) failed (%d)\n", | ||||
|                                  GetLastError()); | ||||
|                 } | ||||
|             } | ||||
|             ContinueDebugEvent(deb_ev.dwProcessId, deb_ev.dwThreadId, DBG_EXCEPTION_NOT_HANDLED); | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| const char* ExceptionName(DWORD exception) { | ||||
|     switch (exception) { | ||||
|     case EXCEPTION_ACCESS_VIOLATION: | ||||
|         return "EXCEPTION_ACCESS_VIOLATION"; | ||||
|     case EXCEPTION_DATATYPE_MISALIGNMENT: | ||||
|         return "EXCEPTION_DATATYPE_MISALIGNMENT"; | ||||
|     case EXCEPTION_BREAKPOINT: | ||||
|         return "EXCEPTION_BREAKPOINT"; | ||||
|     case EXCEPTION_SINGLE_STEP: | ||||
|         return "EXCEPTION_SINGLE_STEP"; | ||||
|     case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: | ||||
|         return "EXCEPTION_ARRAY_BOUNDS_EXCEEDED"; | ||||
|     case EXCEPTION_FLT_DENORMAL_OPERAND: | ||||
|         return "EXCEPTION_FLT_DENORMAL_OPERAND"; | ||||
|     case EXCEPTION_FLT_DIVIDE_BY_ZERO: | ||||
|         return "EXCEPTION_FLT_DIVIDE_BY_ZERO"; | ||||
|     case EXCEPTION_FLT_INEXACT_RESULT: | ||||
|         return "EXCEPTION_FLT_INEXACT_RESULT"; | ||||
|     case EXCEPTION_FLT_INVALID_OPERATION: | ||||
|         return "EXCEPTION_FLT_INVALID_OPERATION"; | ||||
|     case EXCEPTION_FLT_OVERFLOW: | ||||
|         return "EXCEPTION_FLT_OVERFLOW"; | ||||
|     case EXCEPTION_FLT_STACK_CHECK: | ||||
|         return "EXCEPTION_FLT_STACK_CHECK"; | ||||
|     case EXCEPTION_FLT_UNDERFLOW: | ||||
|         return "EXCEPTION_FLT_UNDERFLOW"; | ||||
|     case EXCEPTION_INT_DIVIDE_BY_ZERO: | ||||
|         return "EXCEPTION_INT_DIVIDE_BY_ZERO"; | ||||
|     case EXCEPTION_INT_OVERFLOW: | ||||
|         return "EXCEPTION_INT_OVERFLOW"; | ||||
|     case EXCEPTION_PRIV_INSTRUCTION: | ||||
|         return "EXCEPTION_PRIV_INSTRUCTION"; | ||||
|     case EXCEPTION_IN_PAGE_ERROR: | ||||
|         return "EXCEPTION_IN_PAGE_ERROR"; | ||||
|     case EXCEPTION_ILLEGAL_INSTRUCTION: | ||||
|         return "EXCEPTION_ILLEGAL_INSTRUCTION"; | ||||
|     case EXCEPTION_NONCONTINUABLE_EXCEPTION: | ||||
|         return "EXCEPTION_NONCONTINUABLE_EXCEPTION"; | ||||
|     case EXCEPTION_STACK_OVERFLOW: | ||||
|         return "EXCEPTION_STACK_OVERFLOW"; | ||||
|     case EXCEPTION_INVALID_DISPOSITION: | ||||
|         return "EXCEPTION_INVALID_DISPOSITION"; | ||||
|     case EXCEPTION_GUARD_PAGE: | ||||
|         return "EXCEPTION_GUARD_PAGE"; | ||||
|     case EXCEPTION_INVALID_HANDLE: | ||||
|         return "EXCEPTION_INVALID_HANDLE"; | ||||
|     default: | ||||
|         return nullptr; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										12
									
								
								src/yuzu/mini_dump.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/yuzu/mini_dump.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,12 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include <windows.h> | ||||
| 
 | ||||
| #include <dbghelp.h> | ||||
| 
 | ||||
| void CreateMiniDump(HANDLE process_handle, DWORD process_id, MINIDUMP_EXCEPTION_INFORMATION* info, | ||||
|                     EXCEPTION_POINTERS* pep); | ||||
| 
 | ||||
| bool SpawnDebuggee(const char* arg0, PROCESS_INFORMATION& pi); | ||||
| void DebugDebuggee(PROCESS_INFORMATION& pi); | ||||
| const char* ExceptionName(DWORD exception); | ||||
| @ -31,19 +31,36 @@ void CheckVulkan() { | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| bool StartupChecks(const char* arg0, bool* has_broken_vulkan) { | ||||
| bool CheckEnvVars(bool* is_child) { | ||||
| #ifdef _WIN32 | ||||
|     // Check environment variable to see if we are the child
 | ||||
|     char variable_contents[8]; | ||||
|     const DWORD startup_check_var = | ||||
|         GetEnvironmentVariableA(STARTUP_CHECK_ENV_VAR, variable_contents, 8); | ||||
|     if (startup_check_var > 0 && std::strncmp(variable_contents, "ON", 8) == 0) { | ||||
|     if (startup_check_var > 0 && std::strncmp(variable_contents, ENV_VAR_ENABLED_TEXT, 8) == 0) { | ||||
|         CheckVulkan(); | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     // Don't perform startup checks if we are a child process
 | ||||
|     char is_child_s[8]; | ||||
|     const DWORD is_child_len = GetEnvironmentVariableA(IS_CHILD_ENV_VAR, is_child_s, 8); | ||||
|     if (is_child_len > 0 && std::strncmp(is_child_s, ENV_VAR_ENABLED_TEXT, 8) == 0) { | ||||
|         *is_child = true; | ||||
|         return false; | ||||
|     } else if (!SetEnvironmentVariableA(IS_CHILD_ENV_VAR, ENV_VAR_ENABLED_TEXT)) { | ||||
|         std::fprintf(stderr, "SetEnvironmentVariableA failed to set %s with error %d\n", | ||||
|                      IS_CHILD_ENV_VAR, GetLastError()); | ||||
|         return true; | ||||
|     } | ||||
| #endif | ||||
|     return false; | ||||
| } | ||||
| 
 | ||||
| bool StartupChecks(const char* arg0, bool* has_broken_vulkan) { | ||||
| #ifdef _WIN32 | ||||
|     // Set the startup variable for child processes
 | ||||
|     const bool env_var_set = SetEnvironmentVariableA(STARTUP_CHECK_ENV_VAR, "ON"); | ||||
|     const bool env_var_set = SetEnvironmentVariableA(STARTUP_CHECK_ENV_VAR, ENV_VAR_ENABLED_TEXT); | ||||
|     if (!env_var_set) { | ||||
|         std::fprintf(stderr, "SetEnvironmentVariableA failed to set %s with error %d\n", | ||||
|                      STARTUP_CHECK_ENV_VAR, GetLastError()); | ||||
| @ -53,7 +70,7 @@ bool StartupChecks(const char* arg0, bool* has_broken_vulkan) { | ||||
|     PROCESS_INFORMATION process_info; | ||||
|     std::memset(&process_info, '\0', sizeof(process_info)); | ||||
| 
 | ||||
|     if (!SpawnChild(arg0, &process_info)) { | ||||
|     if (!SpawnChild(arg0, &process_info, 0)) { | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
| @ -106,7 +123,7 @@ bool StartupChecks(const char* arg0, bool* has_broken_vulkan) { | ||||
| } | ||||
| 
 | ||||
| #ifdef _WIN32 | ||||
| bool SpawnChild(const char* arg0, PROCESS_INFORMATION* pi) { | ||||
| bool SpawnChild(const char* arg0, PROCESS_INFORMATION* pi, int flags) { | ||||
|     STARTUPINFOA startup_info; | ||||
| 
 | ||||
|     std::memset(&startup_info, '\0', sizeof(startup_info)); | ||||
| @ -120,7 +137,7 @@ bool SpawnChild(const char* arg0, PROCESS_INFORMATION* pi) { | ||||
|                                                 nullptr,       // lpProcessAttributes
 | ||||
|                                                 nullptr,       // lpThreadAttributes
 | ||||
|                                                 false,         // bInheritHandles
 | ||||
|                                                 0,             // dwCreationFlags
 | ||||
|                                                 flags,         // dwCreationFlags
 | ||||
|                                                 nullptr,       // lpEnvironment
 | ||||
|                                                 nullptr,       // lpCurrentDirectory
 | ||||
|                                                 &startup_info, // lpStartupInfo
 | ||||
|  | ||||
| @ -7,11 +7,14 @@ | ||||
| #include <windows.h> | ||||
| #endif | ||||
| 
 | ||||
| constexpr char IS_CHILD_ENV_VAR[] = "YUZU_IS_CHILD"; | ||||
| constexpr char STARTUP_CHECK_ENV_VAR[] = "YUZU_DO_STARTUP_CHECKS"; | ||||
| constexpr char ENV_VAR_ENABLED_TEXT[] = "ON"; | ||||
| 
 | ||||
| void CheckVulkan(); | ||||
| bool CheckEnvVars(bool* is_child); | ||||
| bool StartupChecks(const char* arg0, bool* has_broken_vulkan); | ||||
| 
 | ||||
| #ifdef _WIN32 | ||||
| bool SpawnChild(const char* arg0, PROCESS_INFORMATION* pi); | ||||
| bool SpawnChild(const char* arg0, PROCESS_INFORMATION* pi, int flags); | ||||
| #endif | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user