Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement --debugger option to improve UX when debugging crashes #13157

Merged
merged 3 commits into from
Jan 22, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
95 changes: 95 additions & 0 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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__
Expand Down Expand Up @@ -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)"))));
sfan5 marked this conversation as resolved.
Show resolved Hide resolved
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,
Expand Down Expand Up @@ -535,6 +543,93 @@ 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 (std::getline(iss, checkpath, PATH_DELIM[0])) {
if (!checkpath.empty() && checkpath.back() != DIR_DELIM_CHAR)
checkpath.append(DIR_DELIM);
checkpath.append(name);
if (access(checkpath.c_str(), X_OK) == 0)
return checkpath;
}
return "";
}

#if defined(_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", "--"})
out.push_back(s);
}
}
}

static bool use_debugger(int argc, char *argv[])
{
#if defined(__ANDROID__)
return false;
#else
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);

errno = 0;
execvp(new_args[0], const_cast<char**>(new_args.data()));
warningstream << "execvp: " << strerror(errno) << std::endl;
return false;
#endif
}

static bool init_common(const Settings &cmd_args, int argc, char *argv[])
{
startup_message();
Expand Down
5 changes: 5 additions & 0 deletions src/porting.h
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,11 @@ extern std::string path_locale;
*/
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.
Example: "stone.png" -> "../data/stone.png"
Expand Down
8 changes: 2 additions & 6 deletions util/test_multiplayer.sh
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,6 @@ waitfor () {
exit 1
}

gdbrun () {
gdb -q -batch -ex 'set confirm off' -ex 'r' -ex 'bt' --args "$@"
}

[ -e "$minetest" ] || { echo "executable $minetest missing"; exit 1; }

rm -rf "$worldpath"
Expand All @@ -39,11 +35,11 @@ printf '%s\n' >"$testspath/server.conf" \
ln -s "$dir/helper_mod" "$worldpath/worldmods/"

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"

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"

echo "Waiting for client and server to exit"
Expand Down