]> git.lizzy.rs Git - minetest.git/blobdiff - src/main.cpp
Implement --debugger option to improve UX when debugging crashes (#13157)
[minetest.git] / src / main.cpp
index 35dcb708b2634ffe7ef933e61746c141aefba13f..ec805f5d39e0b8fcfd9e2681f17a75162e2841c3 100644 (file)
@@ -102,6 +102,7 @@ static void list_game_ids();
 static void list_worlds(bool print_name, bool print_path);
 static bool setup_log_params(const Settings &cmd_args);
 static bool create_userdata_path();
+static bool use_debugger(int argc, char *argv[]);
 static bool init_common(const Settings &cmd_args, int argc, char *argv[]);
 static void uninit_common();
 static void startup_message();
@@ -162,6 +163,11 @@ int main(int argc, char *argv[])
        if (!setup_log_params(cmd_args))
                return 1;
 
+       if (cmd_args.getFlag("debugger")) {
+               if (!use_debugger(argc, argv))
+                       warningstream << "Continuing without debugger" << std::endl;
+       }
+
        porting::signal_handler_init();
 
 #ifdef __ANDROID__
@@ -341,6 +347,8 @@ static void set_allowed_options(OptionList *allowed_options)
                        _("Print even more information to console"))));
        allowed_options->insert(std::make_pair("trace", ValueSpec(VALUETYPE_FLAG,
                        _("Print enormous amounts of information to log and console"))));
+       allowed_options->insert(std::make_pair("debugger", ValueSpec(VALUETYPE_FLAG,
+                       _("Try to automatically attach a debugger before starting (convenience option)"))));
        allowed_options->insert(std::make_pair("logfile", ValueSpec(VALUETYPE_STRING,
                        _("Set logfile path ('' = no logging)"))));
        allowed_options->insert(std::make_pair("gameid", ValueSpec(VALUETYPE_STRING,
@@ -535,6 +543,126 @@ static bool create_userdata_path()
        return success;
 }
 
+namespace {
+       std::string findProgram(const char *name)
+       {
+               char *path_c = getenv("PATH");
+               if (!path_c)
+                       return "";
+               std::istringstream iss(path_c);
+               std::string checkpath;
+               while (!iss.eof()) {
+                       std::getline(iss, checkpath, PATH_DELIM[0]);
+                       if (!checkpath.empty() && checkpath.back() != DIR_DELIM_CHAR)
+                               checkpath.push_back(DIR_DELIM_CHAR);
+                       checkpath.append(name);
+                       if (fs::IsExecutable(checkpath))
+                               return checkpath;
+               }
+               return "";
+       }
+
+#ifdef _WIN32
+       const char *debuggerNames[] = {"gdb.exe", "lldb.exe"};
+#else
+       const char *debuggerNames[] = {"gdb", "lldb"};
+#endif
+       template <class T>
+       void getDebuggerArgs(T &out, int i) {
+               if (i == 0) {
+                       for (auto s : {"-q", "--batch", "-iex", "set confirm off",
+                               "-ex", "run", "-ex", "bt", "--args"})
+                               out.push_back(s);
+               } else if (i == 1) {
+                       for (auto s : {"-Q", "-b", "-o", "run", "-k", "bt\nq", "--"})
+                               out.push_back(s);
+               }
+       }
+}
+
+static bool use_debugger(int argc, char *argv[])
+{
+#if defined(__ANDROID__)
+       return false;
+#else
+#ifdef _WIN32
+       if (IsDebuggerPresent()) {
+               warningstream << "Process is already being debugged." << std::endl;
+               return false;
+       }
+#endif
+
+       char exec_path[1024];
+       if (!porting::getCurrentExecPath(exec_path, sizeof(exec_path)))
+               return false;
+
+       int debugger = -1;
+       std::string debugger_path;
+       for (u32 i = 0; i < ARRLEN(debuggerNames); i++) {
+               debugger_path = findProgram(debuggerNames[i]);
+               if (!debugger_path.empty()) {
+                       debugger = i;
+                       break;
+               }
+       }
+       if (debugger == -1) {
+               warningstream << "Couldn't find a debugger to use. Try installing gdb or lldb." << std::endl;
+               return false;
+       }
+
+       // Try to be helpful
+#ifdef NDEBUG
+       if (strcmp(BUILD_TYPE, "RelWithDebInfo") != 0) {
+               warningstream << "It looks like your " PROJECT_NAME_C " executable was built without "
+                       "debug symbols (BUILD_TYPE=" BUILD_TYPE "), so you won't get useful backtraces."
+                       << std::endl;
+       }
+#endif
+
+       std::vector<const char*> new_args;
+       new_args.push_back(debugger_path.c_str());
+       getDebuggerArgs(new_args, debugger);
+       // Copy the existing arguments
+       new_args.push_back(exec_path);
+       for (int i = 1; i < argc; i++) {
+               if (!strcmp(argv[i], "--debugger"))
+                       continue;
+               new_args.push_back(argv[i]);
+       }
+       new_args.push_back(nullptr);
+
+#ifdef _WIN32
+       // Special treatment for Windows
+       std::string cmdline;
+       for (int i = 1; new_args[i]; i++) {
+               if (i > 1)
+                       cmdline += ' ';
+               cmdline += porting::QuoteArgv(new_args[i]);
+       }
+
+       STARTUPINFO startup_info = {};
+       PROCESS_INFORMATION process_info = {};
+       bool ok = CreateProcess(new_args[0], cmdline.empty() ? nullptr : &cmdline[0],
+               nullptr, nullptr, false, CREATE_UNICODE_ENVIRONMENT,
+               nullptr, nullptr, &startup_info, &process_info);
+       if (!ok) {
+               warningstream << "CreateProcess: " << GetLastError() << std::endl;
+               return false;
+       }
+       DWORD exitcode = 0;
+       WaitForSingleObject(process_info.hProcess, INFINITE);
+       GetExitCodeProcess(process_info.hProcess, &exitcode);
+       exit(exitcode);
+       // not reached
+#else
+       errno = 0;
+       execv(new_args[0], const_cast<char**>(new_args.data()));
+       warningstream << "execv: " << strerror(errno) << std::endl;
+       return false;
+#endif
+#endif
+}
+
 static bool init_common(const Settings &cmd_args, int argc, char *argv[])
 {
        startup_message();