mirror of
https://github.com/minetest/minetest.git
synced 2024-08-18 20:44:16 +02:00
Implement --debugger option to improve UX when debugging crashes (#13157)
This commit is contained in:
parent
6f5703baf1
commit
87d509e462
@ -127,6 +127,12 @@ bool IsDir(const std::string &path)
|
|||||||
(attr & FILE_ATTRIBUTE_DIRECTORY));
|
(attr & FILE_ATTRIBUTE_DIRECTORY));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool IsExecutable(const std::string &path)
|
||||||
|
{
|
||||||
|
DWORD type;
|
||||||
|
return GetBinaryType(path.c_str(), &type) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
bool IsDirDelimiter(char c)
|
bool IsDirDelimiter(char c)
|
||||||
{
|
{
|
||||||
return c == '/' || c == '\\';
|
return c == '/' || c == '\\';
|
||||||
@ -309,6 +315,11 @@ bool IsDir(const std::string &path)
|
|||||||
return ((statbuf.st_mode & S_IFDIR) == S_IFDIR);
|
return ((statbuf.st_mode & S_IFDIR) == S_IFDIR);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool IsExecutable(const std::string &path)
|
||||||
|
{
|
||||||
|
return access(path.c_str(), X_OK) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
bool IsDirDelimiter(char c)
|
bool IsDirDelimiter(char c)
|
||||||
{
|
{
|
||||||
return c == '/';
|
return c == '/';
|
||||||
|
@ -60,6 +60,8 @@ bool IsPathAbsolute(const std::string &path);
|
|||||||
|
|
||||||
bool IsDir(const std::string &path);
|
bool IsDir(const std::string &path);
|
||||||
|
|
||||||
|
bool IsExecutable(const std::string &path);
|
||||||
|
|
||||||
inline bool IsFile(const std::string &path)
|
inline bool IsFile(const std::string &path)
|
||||||
{
|
{
|
||||||
return PathExists(path) && !IsDir(path);
|
return PathExists(path) && !IsDir(path);
|
||||||
|
@ -149,28 +149,25 @@ void init_gettext(const char *path, const std::string &configured_language,
|
|||||||
"Restarting " PROJECT_NAME_C " in a new environment!" << std::endl;
|
"Restarting " PROJECT_NAME_C " in a new environment!" << std::endl;
|
||||||
|
|
||||||
std::string parameters;
|
std::string parameters;
|
||||||
|
for (int i = 1; i < argc; i++) {
|
||||||
for (unsigned int i = 1; i < argc; i++) {
|
if (i > 1)
|
||||||
if (!parameters.empty())
|
|
||||||
parameters += ' ';
|
parameters += ' ';
|
||||||
|
parameters += porting::QuoteArgv(argv[i]);
|
||||||
parameters += argv[i];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *ptr_parameters = NULL;
|
char *ptr_parameters = nullptr;
|
||||||
|
|
||||||
if (!parameters.empty())
|
if (!parameters.empty())
|
||||||
ptr_parameters = parameters.c_str();
|
ptr_parameters = ¶meters[0];
|
||||||
|
|
||||||
// Allow calling without an extension
|
// Allow calling without an extension
|
||||||
std::string app_name = argv[0];
|
std::string app_name = argv[0];
|
||||||
if (app_name.compare(app_name.size() - 4, 4, ".exe") != 0)
|
if (app_name.compare(app_name.size() - 4, 4, ".exe") != 0)
|
||||||
app_name += ".exe";
|
app_name += ".exe";
|
||||||
|
|
||||||
STARTUPINFO startup_info = {0};
|
STARTUPINFO startup_info = {};
|
||||||
PROCESS_INFORMATION process_info = {0};
|
PROCESS_INFORMATION process_info = {};
|
||||||
|
|
||||||
bool success = CreateProcess(app_name.c_str(), (char *)ptr_parameters,
|
bool success = CreateProcess(app_name.c_str(), ptr_parameters,
|
||||||
NULL, NULL, false, DETACHED_PROCESS | CREATE_UNICODE_ENVIRONMENT,
|
NULL, NULL, false, DETACHED_PROCESS | CREATE_UNICODE_ENVIRONMENT,
|
||||||
NULL, NULL, &startup_info, &process_info);
|
NULL, NULL, &startup_info, &process_info);
|
||||||
|
|
||||||
|
128
src/main.cpp
128
src/main.cpp
@ -102,6 +102,7 @@ static void list_game_ids();
|
|||||||
static void list_worlds(bool print_name, bool print_path);
|
static void list_worlds(bool print_name, bool print_path);
|
||||||
static bool setup_log_params(const Settings &cmd_args);
|
static bool setup_log_params(const Settings &cmd_args);
|
||||||
static bool create_userdata_path();
|
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 bool init_common(const Settings &cmd_args, int argc, char *argv[]);
|
||||||
static void uninit_common();
|
static void uninit_common();
|
||||||
static void startup_message();
|
static void startup_message();
|
||||||
@ -162,6 +163,11 @@ int main(int argc, char *argv[])
|
|||||||
if (!setup_log_params(cmd_args))
|
if (!setup_log_params(cmd_args))
|
||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
|
if (cmd_args.getFlag("debugger")) {
|
||||||
|
if (!use_debugger(argc, argv))
|
||||||
|
warningstream << "Continuing without debugger" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
porting::signal_handler_init();
|
porting::signal_handler_init();
|
||||||
|
|
||||||
#ifdef __ANDROID__
|
#ifdef __ANDROID__
|
||||||
@ -341,6 +347,8 @@ static void set_allowed_options(OptionList *allowed_options)
|
|||||||
_("Print even more information to console"))));
|
_("Print even more information to console"))));
|
||||||
allowed_options->insert(std::make_pair("trace", ValueSpec(VALUETYPE_FLAG,
|
allowed_options->insert(std::make_pair("trace", ValueSpec(VALUETYPE_FLAG,
|
||||||
_("Print enormous amounts of information to log and console"))));
|
_("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,
|
allowed_options->insert(std::make_pair("logfile", ValueSpec(VALUETYPE_STRING,
|
||||||
_("Set logfile path ('' = no logging)"))));
|
_("Set logfile path ('' = no logging)"))));
|
||||||
allowed_options->insert(std::make_pair("gameid", ValueSpec(VALUETYPE_STRING,
|
allowed_options->insert(std::make_pair("gameid", ValueSpec(VALUETYPE_STRING,
|
||||||
@ -535,6 +543,126 @@ static bool create_userdata_path()
|
|||||||
return success;
|
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[])
|
static bool init_common(const Settings &cmd_args, int argc, char *argv[])
|
||||||
{
|
{
|
||||||
startup_message();
|
startup_message();
|
||||||
|
@ -729,6 +729,38 @@ void attachOrCreateConsole()
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
std::string QuoteArgv(const std::string &arg)
|
||||||
|
{
|
||||||
|
// Quoting rules on Windows are batshit insane, can differ between applications
|
||||||
|
// and there isn't even a stdlib function to deal with it.
|
||||||
|
// Ref: https://learn.microsoft.com/archive/blogs/twistylittlepassagesallalike/everyone-quotes-command-line-arguments-the-wrong-way
|
||||||
|
if (!arg.empty() && arg.find_first_of(" \t\n\v\"") == std::string::npos)
|
||||||
|
return arg;
|
||||||
|
|
||||||
|
std::string ret;
|
||||||
|
ret.reserve(arg.size()+2);
|
||||||
|
ret.push_back('"');
|
||||||
|
for (auto it = arg.begin(); it != arg.end(); ++it) {
|
||||||
|
u32 back = 0;
|
||||||
|
while (it != arg.end() && *it == '\\')
|
||||||
|
++back, ++it;
|
||||||
|
|
||||||
|
if (it == arg.end()) {
|
||||||
|
ret.append(2 * back, '\\');
|
||||||
|
break;
|
||||||
|
} else if (*it == '"') {
|
||||||
|
ret.append(2 * back + 1, '\\');
|
||||||
|
} else {
|
||||||
|
ret.append(back, '\\');
|
||||||
|
}
|
||||||
|
ret.push_back(*it);
|
||||||
|
}
|
||||||
|
ret.push_back('"');
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
int mt_snprintf(char *buf, const size_t buf_size, const char *fmt, ...)
|
int mt_snprintf(char *buf, const size_t buf_size, const char *fmt, ...)
|
||||||
{
|
{
|
||||||
// https://msdn.microsoft.com/en-us/library/bt7tawza.aspx
|
// https://msdn.microsoft.com/en-us/library/bt7tawza.aspx
|
||||||
|
@ -155,6 +155,11 @@ extern std::string path_locale;
|
|||||||
*/
|
*/
|
||||||
extern std::string path_cache;
|
extern std::string path_cache;
|
||||||
|
|
||||||
|
/*
|
||||||
|
Gets the path of our executable.
|
||||||
|
*/
|
||||||
|
bool getCurrentExecPath(char *buf, size_t len);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Get full path of stuff in data directory.
|
Get full path of stuff in data directory.
|
||||||
Example: "stone.png" -> "../data/stone.png"
|
Example: "stone.png" -> "../data/stone.png"
|
||||||
@ -330,6 +335,11 @@ bool secure_rand_fill_buf(void *buf, size_t len);
|
|||||||
// This attaches to the parents process console, or creates a new one if it doesnt exist.
|
// This attaches to the parents process console, or creates a new one if it doesnt exist.
|
||||||
void attachOrCreateConsole();
|
void attachOrCreateConsole();
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
// Quotes an argument for use in a CreateProcess() commandline (not cmd.exe!!)
|
||||||
|
std::string QuoteArgv(const std::string &arg);
|
||||||
|
#endif
|
||||||
|
|
||||||
int mt_snprintf(char *buf, const size_t buf_size, const char *fmt, ...);
|
int mt_snprintf(char *buf, const size_t buf_size, const char *fmt, ...);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -19,10 +19,6 @@ waitfor () {
|
|||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
gdbrun () {
|
|
||||||
gdb -q -batch -ex 'set confirm off' -ex 'r' -ex 'bt' --args "$@"
|
|
||||||
}
|
|
||||||
|
|
||||||
[ -e "$minetest" ] || { echo "executable $minetest missing"; exit 1; }
|
[ -e "$minetest" ] || { echo "executable $minetest missing"; exit 1; }
|
||||||
|
|
||||||
rm -rf "$worldpath"
|
rm -rf "$worldpath"
|
||||||
@ -39,11 +35,11 @@ printf '%s\n' >"$testspath/server.conf" \
|
|||||||
ln -s "$dir/helper_mod" "$worldpath/worldmods/"
|
ln -s "$dir/helper_mod" "$worldpath/worldmods/"
|
||||||
|
|
||||||
echo "Starting server"
|
echo "Starting server"
|
||||||
gdbrun "$minetest" --server --config "$conf_server" --world "$worldpath" --gameid $gameid 2>&1 | sed -u 's/^/(server) /' &
|
"$minetest" --debugger --server --config "$conf_server" --world "$worldpath" --gameid $gameid 2>&1 | sed -u 's/^/(server) /' &
|
||||||
waitfor "$worldpath/startup"
|
waitfor "$worldpath/startup"
|
||||||
|
|
||||||
echo "Starting client"
|
echo "Starting client"
|
||||||
gdbrun "$minetest" --config "$conf_client1" --go --address 127.0.0.1 2>&1 | sed -u 's/^/(client) /' &
|
"$minetest" --debugger --config "$conf_client1" --go --address 127.0.0.1 2>&1 | sed -u 's/^/(client) /' &
|
||||||
waitfor "$worldpath/done"
|
waitfor "$worldpath/done"
|
||||||
|
|
||||||
echo "Waiting for client and server to exit"
|
echo "Waiting for client and server to exit"
|
||||||
|
Loading…
Reference in New Issue
Block a user