|
@@ -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();
|