@@ -27,6 +27,7 @@
#include "gdbsupport/pathstuff.h"
#include "gdbsupport/signals-state-save-restore.h"
#include "gdbsupport/gdb_tilde_expand.h"
+#include "gdbsupport/scoped_pipe.h"
#include <vector>
extern char **environ;
@@ -262,16 +263,157 @@ execv_argv::init_for_shell (const char *exec_file,
m_argv.push_back (NULL);
}
-/* See nat/fork-inferior.h. */
+/* Default implementation of 'trace_me_fail_reason'. Always return
+ an empty string. */
-pid_t
-fork_inferior (const char *exec_file_arg, const std::string &allargs,
- char **env, void (*traceme_fun) (),
- gdb::function_view<void (int)> init_trace_fun,
- void (*pre_trace_fun) (),
- const char *shell_file_arg,
- void (*exec_fun)(const char *file, char * const *argv,
- char * const *env))
+static std::string
+default_trace_me_fail_reason (int err)
+{
+ return {};
+}
+
+/* See fork-inferior.h. */
+
+std::string (*trace_me_fail_reason) (int err)
+ = default_trace_me_fail_reason;
+
+/* See fork-inferior.h. */
+
+void
+write_trace_errno_to_pipe (int writepipe, int trace_errno)
+{
+ ssize_t writeret;
+
+ do
+ {
+ writeret = write (writepipe, &trace_errno, sizeof (trace_errno));
+ }
+ while (writeret < 0 && (errno == EAGAIN || errno == EINTR));
+
+ if (writeret < 0)
+ error (_("Could not write trace errno: %s"), safe_strerror (errno));
+}
+
+/* Helper function to read TRACE_ERRNO from READPIPE, which handles
+ EINTR/EAGAIN and throws and exception if there was an error. */
+
+static int
+read_trace_errno_from_pipe (int readpipe)
+{
+ ssize_t readret;
+ int trace_errno;
+
+ do
+ {
+ readret = read (readpipe, &trace_errno, sizeof (trace_errno));
+ }
+ while (readret < 0 && (errno == EAGAIN || errno == EINTR));
+
+ if (readret < 0)
+ error (_("Could not read trace errno: %s"), safe_strerror (errno));
+
+ return trace_errno;
+}
+
+/* See fork-inferior.h. */
+
+void
+check_child_trace_me_errno (int readpipe)
+{
+ fd_set rset;
+ struct timeval timeout;
+ int ret;
+
+ /* Make sure we have a valid 'trace_me_fail_reason' function
+ defined. */
+ gdb_assert (trace_me_fail_reason != nullptr);
+
+ FD_ZERO (&rset);
+ FD_SET (readpipe, &rset);
+
+ /* Five seconds should be plenty of time to wait for the child's
+ reply. */
+ timeout.tv_sec = 5;
+ timeout.tv_usec = 0;
+
+ do
+ {
+ ret = select (readpipe + 1, &rset, NULL, NULL, &timeout);
+ }
+ while (ret < 0 && (errno == EAGAIN || errno == EINTR));
+
+ if (ret < 0)
+ perror_with_name ("select");
+ else if (ret == 0)
+ error (_("Timeout while waiting for child's trace errno"));
+ else
+ {
+ int child_errno;
+
+ child_errno = read_trace_errno_from_pipe (readpipe);
+
+ if (child_errno != 0)
+ {
+ /* The child can't use TRACE_TRACEME. We have to check whether
+ we know the reason for the failure, and then error out. */
+ std::string reason = trace_me_fail_reason (child_errno);
+
+ if (reason.empty ())
+ reason = "Could not determine reason for trace failure.";
+
+ /* The child is supposed to display a warning containing the
+ safe_strerror message before us, so we just display the
+ possible reason for the failure. */
+ error ("%s", reason.c_str ());
+ }
+ }
+}
+
+/* Helper struct for fork_inferior_1, containing information on
+ whether we should check if TRACEME_FUN was successfully called or
+ not. */
+
+struct traceme_info
+{
+ /* True if we should check whether the call to 'traceme_fun
+ (TRACE_ME...)' succeeded or not. */
+ bool check_trace_me_fail_reason;
+
+ union
+ {
+ /* The non-check version of TRACEME_FUN. It will be set if
+ CHECK_TRACEME_FAIL_REASON is false.
+
+ This function will usually just perform the call to whatever
+ trace function needed to start tracing the inferior (e.g.,
+ ptrace). */
+ void (*traceme_fun_nocheck) ();
+
+ /* The check version of TRACEME_FUN. It will be set if
+ CHECK_TRACEME_FAIL_REASON is true.
+
+ This function will usually perform the call to whatever trace
+ function needed to start tracing the inferior, but will also
+ write its errno value to TRACE_ERRNO_PIPE, so that
+ fork_inferior_1 can check whether it suceeded. */
+ void (*traceme_fun_check) (int trace_errno_pipe);
+ } u;
+};
+
+/* Helper function.
+
+ Depending on the value of TRACEME_INFO.CHECK_TRACEME_FAIL_REASON,
+ this function will check whether the call to TRACEME_FUN succeeded
+ or not. */
+
+static pid_t
+fork_inferior_1 (const char *exec_file_arg, const std::string &allargs,
+ char **env, const struct traceme_info traceme_info,
+ gdb::function_view<void (int)> init_trace_fun,
+ void (*pre_trace_fun) (),
+ const char *shell_file_arg,
+ void (*exec_fun)(const char *file, char * const *argv,
+ char * const *env))
{
pid_t pid;
/* Set debug_fork then attach to the child while it sleeps, to debug. */
@@ -283,6 +425,7 @@ fork_inferior (const char *exec_file_arg, const std::string &allargs,
int save_errno;
const char *inferior_cwd;
std::string expanded_inferior_cwd;
+ scoped_pipe trace_pipe;
/* If no exec file handed to us, get it from the exec-file command
-- with a good, common error message if none is specified. */
@@ -365,12 +508,6 @@ fork_inferior (const char *exec_file_arg, const std::string &allargs,
if (pid == 0)
{
- /* Close all file descriptors except those that gdb inherited
- (usually 0/1/2), so they don't leak to the inferior. Note
- that this closes the file descriptors of all secondary
- UIs. */
- close_most_fds ();
-
/* Change to the requested working directory if the user
requested it. */
if (inferior_cwd != NULL)
@@ -392,7 +529,10 @@ fork_inferior (const char *exec_file_arg, const std::string &allargs,
for the inferior. */
/* "Trace me, Dr. Memory!" */
- (*traceme_fun) ();
+ if (traceme_info.check_trace_me_fail_reason)
+ (*traceme_info.u.traceme_fun_check) (trace_pipe.get_write_end ());
+ else
+ (*traceme_info.u.traceme_fun_nocheck) ();
/* The call above set this process (the "child") as debuggable
by the original gdb process (the "parent"). Since processes
@@ -403,6 +543,12 @@ fork_inferior (const char *exec_file_arg, const std::string &allargs,
saying "not parent". Sorry; you'll have to use print
statements! */
+ /* Close all file descriptors except those that gdb inherited
+ (usually 0/1/2), so they don't leak to the inferior. Note
+ that this closes the file descriptors of all secondary
+ UIs, and the trace errno pipe (if it's open). */
+ close_most_fds ();
+
restore_original_signals_state ();
/* There is no execlpe call, so we have to set the environment
@@ -431,6 +577,13 @@ fork_inferior (const char *exec_file_arg, const std::string &allargs,
_exit (0177);
}
+ if (traceme_info.check_trace_me_fail_reason)
+ {
+ /* Check the trace errno, and inform the user about the reason
+ of the failure, if there was any. */
+ check_child_trace_me_errno (trace_pipe.get_read_end ());
+ }
+
/* Restore our environment in case a vforked child clob'd it. */
environ = save_our_env;
@@ -448,6 +601,48 @@ fork_inferior (const char *exec_file_arg, const std::string &allargs,
return pid;
}
+/* See fork-inferior.h. */
+
+pid_t
+fork_inferior (const char *exec_file_arg, const std::string &allargs,
+ char **env, void (*traceme_fun) (),
+ gdb::function_view<void (int)> init_trace_fun,
+ void (*pre_trace_fun) (),
+ const char *shell_file_arg,
+ void (*exec_fun)(const char *file, char * const *argv,
+ char * const *env))
+{
+ struct traceme_info traceme_info;
+
+ traceme_info.check_trace_me_fail_reason = false;
+ traceme_info.u.traceme_fun_nocheck = traceme_fun;
+
+ return fork_inferior_1 (exec_file_arg, allargs, env, traceme_info,
+ init_trace_fun, pre_trace_fun, shell_file_arg,
+ exec_fun);
+}
+
+/* See fork-inferior.h. */
+
+pid_t
+fork_inferior (const char *exec_file_arg, const std::string &allargs,
+ char **env, void (*traceme_fun) (int trace_errno_pipe),
+ gdb::function_view<void (int)> init_trace_fun,
+ void (*pre_trace_fun) (),
+ const char *shell_file_arg,
+ void (*exec_fun)(const char *file, char * const *argv,
+ char * const *env))
+{
+ struct traceme_info traceme_info;
+
+ traceme_info.check_trace_me_fail_reason = true;
+ traceme_info.u.traceme_fun_check = traceme_fun;
+
+ return fork_inferior_1 (exec_file_arg, allargs, env, traceme_info,
+ init_trace_fun, pre_trace_fun, shell_file_arg,
+ exec_fun);
+}
+
/* See nat/fork-inferior.h. */
ptid_t
@@ -592,7 +787,7 @@ trace_start_error (const char *fmt, ...)
/* See nat/fork-inferior.h. */
void
-trace_start_error_with_name (const char *string)
+trace_start_error_with_name (const char *string, const char *append)
{
- trace_start_error ("%s: %s", string, safe_strerror (errno));
+ trace_start_error ("%s: %s%s", string, safe_strerror (errno), append);
}
@@ -32,17 +32,41 @@ struct process_stratum_target;
#define START_INFERIOR_TRAPS_EXPECTED 1
/* Start an inferior Unix child process and sets inferior_ptid to its
- pid. EXEC_FILE is the file to run. ALLARGS is a string containing
- the arguments to the program. ENV is the environment vector to
- pass. SHELL_FILE is the shell file, or NULL if we should pick
- one. EXEC_FUN is the exec(2) function to use, or NULL for the default
- one. */
-
-/* This function is NOT reentrant. Some of the variables have been
- made static to ensure that they survive the vfork call. */
+ pid.
+
+ EXEC_FILE is the file to run.
+
+ ALLARGS is a string containing the arguments to the program.
+
+ ENV is the environment vector to pass.
+
+ SHELL_FILE is the shell file, or NULL if we should pick one.
+
+ EXEC_FUN is the exec(2) function to use, or NULL for the default
+ one.
+
+ This function is NOT reentrant. Some of the variables have been
+ made static to ensure that they survive the vfork call.
+
+ This function does not check whether the call to TRACEME_FUN
+ succeeded or not. */
extern pid_t fork_inferior (const char *exec_file_arg,
const std::string &allargs,
- char **env, void (*traceme_fun) (),
+ char **env,
+ void (*traceme_fun) (),
+ gdb::function_view<void (int)> init_trace_fun,
+ void (*pre_trace_fun) (),
+ const char *shell_file_arg,
+ void (*exec_fun) (const char *file,
+ char * const *argv,
+ char * const *env));
+
+/* Like fork_inferior above, but check whether the call to TRACEME_FUN
+ succeeded or not. */
+extern pid_t fork_inferior (const char *exec_file_arg,
+ const std::string &allargs,
+ char **env,
+ void (*traceme_fun) (int trace_errno_pipe),
gdb::function_view<void (int)> init_trace_fun,
void (*pre_trace_fun) (),
const char *shell_file_arg,
@@ -82,9 +106,48 @@ extern void trace_start_error (const char *fmt, ...)
ATTRIBUTE_NORETURN ATTRIBUTE_PRINTF (1, 2);
/* Like "trace_start_error", but the error message is constructed by
- combining STRING with the system error message for errno. This
- function does not return. */
-extern void trace_start_error_with_name (const char *string)
+ combining STRING with the system error message for errno, and
+ (optionally) with APPEND. This function does not return. */
+extern void trace_start_error_with_name (const char *string,
+ const char *append = "")
ATTRIBUTE_NORETURN;
+/* Pointer to function which can be called by
+ 'check_child_trace_me_errno' when we need to determine the reason
+ of a e.g. 'ptrace (PTRACE_ME, ...)' failure. ERR is the ERRNO
+ value set by the failing ptrace call.
+
+ By default, the function returns an empty string (see
+ fork-inferior.c).
+
+ This pointer can be overriden by targets that want to personalize
+ the error message printed when trace fails (see linux-nat.c or
+ gdbserver/linux-low.c, for example). */
+extern std::string (*trace_me_fail_reason) (int err);
+
+/* Check the "trace me" errno (generated when executing e.g. 'ptrace
+ (PTRACE_ME, ...)') of the child process that was created by
+ GDB/GDBserver when creating an inferior. The errno value will be
+ passed via a pipe (see 'fork_inferior'), and READPIPE is the read
+ end of the pipe.
+
+ If possible (i.e., if 'trace_me_fail_reason' is defined by the
+ target), then we also try to determine the possible reason for a
+ failure.
+
+ The idea is to wait a few seconds (via 'select') until something is
+ written on READPIPE. When that happens, we check if the child's
+ trace errno is different than 0. If it is, we call the function
+ 'trace_me_fail_reason' and try to obtain the reason for the
+ failure, and then throw an exception (with the reason as the
+ exception's message).
+
+ If nothing is written on the pipe, or if 'select' fails, we also
+ throw exceptions. */
+extern void check_child_trace_me_errno (int readpipe);
+
+/* Helper function to write TRACE_ERRNO to WRITEPIPE, which handles
+ EINTR/EAGAIN and throws an exception if there was an error. */
+extern void write_trace_errno_to_pipe (int writepipe, int trace_errno);
+
#endif /* NAT_FORK_INFERIOR_H */